Repository navigation
[FEATURE] Make the remote MCP client spec-compliant and dual-era: initialize fallback and 2026-07-28 request metadata #5192
Description
Problem Statement
An agent's mcpServers entry with transport.type: "http" is served by src/tool/remote-mcp.ts, which posts tools/list and tools/call as bare JSON-RPC requests with Content-Type and Accept and nothing else. It never sends initialize, never carries an Mcp-Session-Id, and sends none of the request metadata that MCP revision 2026-07-28 requires on every POST: MCP-Protocol-Version, Mcp-Method, Mcp-Name, and _meta.io.modelcontextprotocol/protocolVersion in the body. It therefore matches neither era of the Streamable HTTP transport:
- A 2025-era server or gateway that assigns sessions answers
400 Bad Requestto the first request, as the 2025-06-18 specification says it should for a request without a session. We saw this with Envoy AI Gateway (Agent Router) v1.1.0, whose MCP proxy answers400 missing session ID; v1.2.0 still requires sessions, with stateless support "in progress and not yet enabled" per its release notes. The gateway's authorisation log confirmed the request carried onlycontent-typeanduser-agent: veryfront/0.1.1276, noMcp-Session-Idand noMCP-Protocol-Version. Veryfront surfaced it asFailed to fetch remote tool definitions from source sourceId=policies-mcp error="Remote MCP request failed (400)"in the server log, and the agent then failed withUnknown tool reference: policies-mcp__get_policy_details. - A strict 2026-07-28 server must reject the request for missing headers with
400and aHeaderMismatchJSON-RPC error.
The 2026-07-28 specification's compatibility matrix expects a client that interoperates with both eras to be dual-era: attempt a modern request, and on a 400 whose body is not a recognised modern JSON-RPC error, fall back to initialize and continue with the legacy session. The current client has no fallback, so it only works with servers that accept unlabelled requests.
Why it matters: an organisation that puts its MCP servers behind a gateway for identity, policy and audit cannot use mcpServers at all today, even though the same gateway serves the app's model calls. The same tool is reachable through that gateway with a compliant handshake from inside the same pod: initialize, notifications/initialized, tools/list, tools/call, DELETE all succeed with the app's bearer.
Proposed Solution
- Send the 2026-07-28 request metadata on every POST:
MCP-Protocol-Version,Mcp-Method,Mcp-Namefortools/call, and the_meta.io.modelcontextprotocol/*fields in the body, as the Streamable HTTP binding requires. - Implement the backward-compatibility path the specification describes: on a
400without a recognised modern error body, performinitialize(negotiating 2025-06-18 or 2025-11-25), sendnotifications/initialized, keep theMcp-Session-Idper origin and include it on subsequent requests, re-initialise on404, andDELETEthe session when the run ends. Cache the era per origin, as the specification suggests.
Component
Tools, Agents
Alternatives Considered
- Waiting for the gateway to accept stateless requests. Agent Router has stateless 2026-07-28 support merged but not released or enabled (feat: mcp spec Release Candidate (2025-07-28) theagentrouter/agent-router#2323, pull request Tag eval runs for gateway billing reconciliation #2692). Even then a strict modern server would reject the client for the missing headers.
- A local proxy that adds the handshake. It hides the client from the gateway's identity and policy checks, and it is a patch around the framework rather than a configuration.
Example Use Case
As a platform team running a Veryfront multi-agent app behind an MCP gateway that requires sessions (Envoy AI Gateway MCPRoute with OPA authorising each tools/call by bearer), I want the agent's mcpServers entry to complete the Streamable HTTP handshake and carry the session id, so that remote tool calls are admitted, authorised and recorded by the same gateway that already serves the app's model calls.
Verification
- I have searched the existing issues to make sure this is not a duplicate
- I have provided sufficient context for the team to understand the request
Activity
kwakayama commentedon Oct 16, 2026
Agent Brief
Category: enhancement
Summary:
Make the remote MCP HTTP client interoperate with both MCP eras: send 2026-07-28 request metadata, and fall back to an
initializehandshake with session tracking when a server rejects the modern request.Current behavior:
src/tool/remote-mcp.tspoststools/listandtools/callas bare JSON-RPC requests. Only caller-supplied headers plusContent-TypeandAcceptare sent.initializecall, noMcp-Session-Idhandling, noMCP-Protocol-Version,Mcp-MethodorMcp-Nameheader, and noDELETEof a session in this module. The only protocol-version and session-header handling insrc/is in the MCP server code (src/mcp/), not the client.Remote MCP request failed (<status>).Desired behavior:
MCP-Protocol-Version,Mcp-Method,Mcp-Name(fortools/call) and_meta.io.modelcontextprotocol/protocolVersionin the body, as the 2026-07-28 Streamable HTTP binding requires.400whose body is not a recognised modern JSON-RPC error, the client:initialize, negotiating 2025-06-18 or 2025-11-25;notifications/initialized;Mcp-Session-Idper origin and sends it on later requests;404and retries once;DELETEfor the session when the run ends.headers,listMeta,_metaand run-correlation behavior, size limits, timeouts and the outbound guard (guardedOutboundFetch) stay unchanged.Key interfaces:
remote-mcptool source (RemoteMCPToolSourceConfig,tools/listandtools/callpaths) behind an agent'smcpServersentry withtransport.type: "http".VeryfrontErrorregistry.Acceptance criteria:
_metafields ontools/listandtools/call.400to the first request getsinitialize,notifications/initialized, then the original request withMcp-Session-Id.404on a session triggers one re-initialisation and retry.400JSON-RPC error (for exampleHeaderMismatch) does not trigger the fallback.src/tool/remote-mcp.test.tsusewithMockFetchand cover the paths above.Out of scope:
remote-mcp.tsVerification plan:
deno task test:file src/tool/remote-mcp.test.tsdeno fmt --checkdeno task lint:cideno task typecheckcicd.ymlPR checks (lint, typecheck, unit tests)Open point for maintainers: the exact definition of a "recognised modern error body" and the session lifetime relative to a run should be confirmed against the 2026-07-28 specification during implementation.
Handoff: ready for agent
Thanks @dberesford. I reviewed the report against the current Veryfront Code implementation and reduced it to the Agent Brief above. The remaining work is bounded by the acceptance criteria and verification plan.
Generated by Claude Code
dberesford commentedon Oct 16, 2026
Correction from the author, after further testing on the same setup (veryfront 0.1.1276, Envoy AI Gateway v1.1.0):
mcpServersat all today" is too broad. Theauth: { type: "headers", headers: () => ... }callback can itself POSTinitializeandnotifications/initializedto the server and return theMcp-Session-Idit was given, and with that the built-in client'stools/listandtools/callgo through a session-requiring gateway. It is a workaround with a cost (one session per request, never closed), not a fix, but it means the gap is in the default lifecycle rather than a hard block.The request stands as written in "Proposed Solution": a dual-era client that completes the handshake and carries the session, or sends the 2026-07-28 request metadata.