- 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).
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
mcpServersshape, thetyperepair rule, exact${VAR}/${VAR:-default}semantics including the unset-variable behavior. - VS Code servers + VS Code config reference: the
serverswrapper key,${input:}/envFilesecret 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_varsforwarding,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.jsonrun contract, discovery API, the first GitHub-URL lookup step. - Reference servers: npx/uvx/
cmd /cgeneration 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,typeunrepaired, 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. |