Skip to content

feat(mcpServer): probe_mcp_server looks at a URL before it is registered (CLEAN-78) - #105

Merged
maksymhryzodub-prog merged 1 commit into
mainfrom
feat/CLEAN-78-mcp-probe
Sep 24, 2026
Merged

maksymhryzodub-prog merged 1 commit into
mainfrom
feat/CLEAN-78-mcp-probe

Conversation

@maksymhryzodub-prog

@maksymhryzodub-prog maksymhryzodub-prog commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Jira: CLEAN-78

Why

CLEAN-109 gave the Rancher agent the MCP registry tools, but nothing could look at an endpoint first: to register https://mcp.silpo.ua/mcp the person had to know it speaks streamable HTTP and wants OAuth. The admin form also still had no oauth option.

What

API — probe_mcp_server({ url } | { id }), operator-only like the rest of the registry (api/src/slices/mcpServer/mcpProbe.tool.ts):

  • McpProbeService holds the decisions: OAuth metadata on the origin first, then a handshake over streamable HTTP, SSE only when the endpoint answers 404/405; a 401 next to RFC 8414 metadata reads as authType: oauth (with dynamic-registration / PKCE / scopes), a 401 without it as bearer, a clean handshake as none plus the tool list and server name. One next line tells the model what to call.
  • McpProbeConnector is the SDK half (same client transports the runtime uses), behind an interface so the service is unit-tested without a socket.
  • A pasted URL passes the A2A public-address guard (a probe reports tools/list into the chat — exactly what an SSRF wants); a registered row by id skips it, since built-ins live on cluster hosts. Results never carry a credential; a stored bearer/header is sent and only its verdict comes back. Both spellings of a header credential are accepted (Header-Name: value as the runtime parses, and the JSON the form used to show).
  • Contract row in specs/016-agent-tool-parity/contracts/tools.md; 21 new tests.

Admin — oauth in the auth-type select (no value field, a note on how the connect happens), the header label now shows the Header-Name: value form the runtime actually reads, and the MCP servers page points at the Rancher chat and its Tools panel. Twin-console check: app/ has no mcpServer slice, nothing to mirror.

Verification

  • api: jest on mcpServer + mcp → 15 suites, 154 tests green; tsc --noEmit clean; eslint clean on the new files.
  • admin: npx nuxt typecheck clean.
  • Not exercised against the live Silpo endpoint from this machine. Smoke test in the Rancher chat: "Check what the MCP server at https://mcp.silpo.ua/mcp offers" → expect transport: streamableHttp, authType: oauth, oauth.dynamicRegistration: true, tools: null, and a next line pointing at register_mcp_server + start_mcp_oauth.

Stacked on top: #CLEAN-80 PR (per-user tokens) targets this branch.

Live test (2026-09-23, Rancher chat tools over the ranch MCP endpoint, real Silpo)

  • probe_mcp_server { url: https://mcp.silpo.ua/mcp } → authType: oauth, dynamic registration + PKCE detected, tools: null, next pointing at register_mcp_server / start_mcp_oauth.
  • by id on the open CleanSlice server → reachable, 4 tools, server name; a private URL is refused before any request; a built-in row on a cluster host is probed guard-free and reported unreachable honestly.
  • register_mcp_server → set_template_mcps → list_agent_mcps worked as a chain for Silpo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KtJu4QscRKbDdj8WLL9aVL

…red (CLEAN-78)

The registry tools from CLEAN-109 register whatever they are told; nothing
could look at an endpoint first. Pasting https://mcp.silpo.ua/mcp into the
Rancher chat meant the person had to know it speaks streamable HTTP and
wants OAuth. Now the operator tool probe_mcp_server({ url } | { id }) reports
the transport that answered, whether a credential is required and which
authType to register with (oauth when the origin publishes RFC 8414
metadata next to a 401, bearer otherwise, none when the handshake went
through), the OAuth capabilities (dynamic registration, PKCE, scopes), the
server name and its tool list when it could be asked, and one line on what
to call next.

McpProbeService holds the decisions (streamable HTTP first, SSE only on a
404/405, a refusal is an answer) behind IMcpProbeConnector; the data-layer
connector uses the MCP SDK's own client transports the way the runtime does,
so "reachable" here and "the agent can connect" mean the same thing. A pasted
url passes the A2A public-address guard; a registered row (by id) skips it,
because the built-ins live on cluster hosts. Results never carry a credential.

Admin: the form gains the oauth auth type (no value field, a note on how the
connect happens), the header label now shows the `Header-Name: value` form
the runtime actually parses instead of a JSON spelling it never read, and the
MCP servers page points at the Rancher chat and its Tools panel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KtJu4QscRKbDdj8WLL9aVL
@maksymhryzodub-prog
maksymhryzodub-prog merged commit 03f5c59 into main Sep 24, 2026
1 check passed
@maksymhryzodub-prog
maksymhryzodub-prog deleted the feat/CLEAN-78-mcp-probe branch September 24, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant