Files
tai/research/mcp-client-support/conclusions/mcp-resolver-agent-design.md
T
retoor 58786386cf v1.43.0: automatic MCP GitHub resolver agent plus stdlib-only OAuth login
- mcp_resolve_agent: bounded fork subagent fallback when the
  deterministic GitHub pass finds no run contract (MCP_RESOLVER_*).
- McpOAuth/McpOAuthCallback: discovery, DCR, PKCE loopback, token
  exchange, sealed per-profile token storage, one-shot refresh.
- McpHttpError carries status plus WWW-Authenticate through the
  client; bridge retries once after refresh, else names mcp_login.
- mcp_add stores 401-gated servers disabled; mcp_login approves,
  verifies, enables; mcp_remove drops tokens.
- Docs, conclusions, 17 new tests (1209 + 16 green).
2026-10-18 20:38:52 +02:00

5.6 KiB

Conclusion: pasted configs and GitHub URLs resolve through a normalizer plus a resolver agent

Metadata

Field Value
Institute Molodetz Institute - Research & Retrieval Division
Subject mcp-client-support
Document type Conclusion
Tier N/A
Date decided 2026-10-18
Decided by retoor, advised by tai (agent, Claude Sonnet 5)
Status Active
Confidence High

Question / problem statement

The user wants to paste any of four shapes, a JSON mcpServers block (URL form or command form, with headers and ${VAR} secrets), a TOML [mcp_servers.*] table, or a bare GitHub repo URL, and have tai turn it into a stored, runnable, per-profile-enableable server connection. Config formats differ per client and no spec constrains them, so what deterministic rules plus agentic fallback resolve every shape?

Conclusion

Resolution is two layers. A deterministic normalizer handles pasted configs: accept both mcpServers and servers wrappers; repair entries that carry a url without type (Claude Code treats those as config errors); expand ${VAR} and ${VAR:-default} in command, args, env, url, and headers from process env first, then vault secrets, warning and keeping the literal on unresolved names; translate Cursor ${env:NAME} and VS Code ${input:id}/${workspaceFolder} spellings into the same expansion; parse TOML [mcp_servers.*] (3.11+, clean error below) mapping env/env_vars, url/http_headers/env_http_headers, enabled, enabled_tools/disabled_tools, and timeouts onto the stored row; and emit cmd /c wrapping for npx on Windows while leaving uvx alone. Anything the normalizer cannot close, a bare GitHub URL, a Smithery reference, a vague description, goes to a resolver subagent (fork/poll) that walks a fixed lookup chain (official registry API by repo path, server.json/mcpName, package.json binaries, README run block, Glama page, then a live initialize + tools/list probe) and returns exactly one normalized entry or a loud failure. Every resolved entry is verified by a live handshake before it is stored.

Supporting sources

  • Claude Code docs: canonical mcpServers shape, the type repair rule, exact ${VAR}/${VAR:-default} semantics including the unset-variable behavior.
  • VS Code servers + VS Code config reference: the servers wrapper key, ${input:}/envFile secret idioms to translate, enable-state-stored-separately precedent.
  • Cursor docs: ${env:NAME} spelling and no-type-needed remote entries, proving per-client placeholder rewriting is required.
  • Codex TOML: the [mcp_servers.*] schema, env_vars forwarding, enabled/enabled_tools/disabled_tools, timeout fields.
  • mcp-remote: the remote-URL-to-stdio recipe (--header, --ignore-tool, no-spaces workaround) the resolver emits when direct Streamable HTTP is unsuitable.
  • Registry quickstart: metadata-only registry, server.json run contract, discovery API, the first GitHub-URL lookup step.
  • Reference servers: npx/uvx/cmd /c generation rules and the archived-predecessor warning.
  • Glama directory + API: keyed fallback catalog with attribution obligations.
  • Transports: the anchor that client config is convention, not protocol, which is why the normalizer exists at all.

Alternatives considered and rejected

  • Manual-only registration (user fills every field): rejected by the request; paste-and-resolve is the feature.
  • Regex-only parsing without an agent fallback: covers the four pasted shapes but cannot resolve GitHub URLs, which need registry/repo/README reasoning.
  • Storing the pasted blob verbatim and interpreting it at connect time: leaves ${VAR} unresolved, type unrepaired, and secrets in cleartext config; normalization at registration time is strictly better.
  • Adopting one client's exact file format (.mcp.json, config.toml) as tai's store: tai already owns a sealed vault with per-profile slices; the vault is the store, foreign formats are import/export shapes.

Risks / open questions

  • Registry preview status: the official registry may break or reset data; the resolver must treat it as a hint and always verify with a live handshake.
  • ${VAR} names that exist neither in env nor vault: current rule (warn, keep literal) can strand a server; revisit with an interactive secret prompt at registration if users hit it.
  • GitHub repos without any machine-readable run contract need README interpretation, which can misread; the live-probe requirement bounds the damage to a failed registration, never a bad stored entry.
  • Smithery/Glama API shapes may drift; the resolver pins only to what the dossiers verified and re-checks on failure.

Action taken

Built in v1.42.0 (normalizer, deterministic contents-API chain, live-verify-before-store). v1.43.0 adds the fork-based fallback mcp_resolve_agent: when the deterministic pass finds nothing, a bounded same-profile subagent reads the repo and returns one fenced JSON entry or {"error", "evidence"}. The registry/Glama steps from the research stayed out: the contents API plus README reasoning covered real repos without new dependencies.

Changelog

Date Change
2026-10-18 Initial conclusion recorded.
2026-10-18 v1.42.0 built the normalizer and deterministic chain; v1.43.0 adds the fork-based resolver subagent fallback.