Skip to content

[FEATURE] Make the remote MCP client spec-compliant and dual-era: initialize fallback and 2026-07-28 request metadata #5192

Open

Description

@dberesford

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 Request to 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 answers 400 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 only content-type and user-agent: veryfront/0.1.1276, no Mcp-Session-Id and no MCP-Protocol-Version. Veryfront surfaced it as Failed 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 with Unknown tool reference: policies-mcp__get_policy_details.
  • A strict 2026-07-28 server must reject the request for missing headers with 400 and a HeaderMismatch JSON-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

  1. Send the 2026-07-28 request metadata on every POST: MCP-Protocol-Version, Mcp-Method, Mcp-Name for tools/call, and the _meta.io.modelcontextprotocol/* fields in the body, as the Streamable HTTP binding requires.
  2. Implement the backward-compatibility path the specification describes: on a 400 without a recognised modern error body, perform initialize (negotiating 2025-06-18 or 2025-11-25), send notifications/initialized, keep the Mcp-Session-Id per origin and include it on subsequent requests, re-initialise on 404, and DELETE the session when the run ends. Cache the era per origin, as the specification suggests.

Component

Tools, Agents

Alternatives Considered

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

  1. kwakayama commented on Oct 16, 2026

    @kwakayama
    Contributor

    This was generated by AI during triage.

    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 initialize handshake with session tracking when a server rejects the modern request.

    Current behavior:

    • src/tool/remote-mcp.ts posts tools/list and tools/call as bare JSON-RPC requests. Only caller-supplied headers plus Content-Type and Accept are sent.
    • A code search found no initialize call, no Mcp-Session-Id handling, no MCP-Protocol-Version, Mcp-Method or Mcp-Name header, and no DELETE of a session in this module. The only protocol-version and session-header handling in src/ is in the MCP server code (src/mcp/), not the client.
    • Any non-2xx response surfaces as Remote MCP request failed (<status>).
    • I did not reproduce the gateway failure. The report's Envoy AI Gateway behavior is the author's observation and is unverified here.
    • No duplicate issue was found.

    Desired behavior:

    • Every POST carries MCP-Protocol-Version, Mcp-Method, Mcp-Name (for tools/call) and _meta.io.modelcontextprotocol/protocolVersion in the body, as the 2026-07-28 Streamable HTTP binding requires.
    • On a 400 whose body is not a recognised modern JSON-RPC error, the client:
      • sends initialize, negotiating 2025-06-18 or 2025-11-25;
      • sends notifications/initialized;
      • stores Mcp-Session-Id per origin and sends it on later requests;
      • re-initialises on 404 and retries once;
      • sends DELETE for the session when the run ends.
    • The negotiated era is cached per origin.
    • Servers that accept unlabelled requests keep working. Existing caller-supplied headers, listMeta, _meta and run-correlation behavior, size limits, timeouts and the outbound guard (guardedOutboundFetch) stay unchanged.
    • Bearer headers must not leak into new logs or errors. Session ids must stay scoped per origin and per run, not shared across tenants.

    Key interfaces:

    • remote-mcp tool source (RemoteMCPToolSourceConfig, tools/list and tools/call paths) behind an agent's mcpServers entry with transport.type: "http".
    • MCP Streamable HTTP transport, revisions 2025-06-18, 2025-11-25 and 2026-07-28.
    • Error behavior through the VeryfrontError registry.

    Acceptance criteria:

    • A modern server receives all required headers and _meta fields on tools/list and tools/call.
    • A session-requiring server that answers 400 to the first request gets initialize, notifications/initialized, then the original request with Mcp-Session-Id.
    • 404 on a session triggers one re-initialisation and retry.
    • The session is deleted when the run ends, and the era and session are cached per origin.
    • A modern 400 JSON-RPC error (for example HeaderMismatch) does not trigger the fallback.
    • Regression tests in src/tool/remote-mcp.test.ts use withMockFetch and cover the paths above.
    • Relevant formatting, lint, typecheck, and CI checks pass.
    • Existing behavior outside this issue remains unchanged.

    Out of scope:

    • Unrelated refactors of remote-mcp.ts
    • Server-side MCP changes
    • Gateway-specific workarounds
    • New dependencies

    Verification plan:

    • deno task test:file src/tool/remote-mcp.test.ts
    • deno fmt --check
    • deno task lint:ci
    • deno task typecheck
    • The standard cicd.yml PR 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

  2. dberesford commented on Oct 16, 2026

    @dberesford
    Author

    Correction from the author, after further testing on the same setup (veryfront 0.1.1276, Envoy AI Gateway v1.1.0):

    • "cannot use mcpServers at all today" is too broad. The auth: { type: "headers", headers: () => ... } callback can itself POST initialize and notifications/initialized to the server and return the Mcp-Session-Id it was given, and with that the built-in client's tools/list and tools/call go 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.
    • "a local proxy ... hides the client from the gateway's identity and policy checks" was an opinion, not a tested fact: a forwarding intermediary can preserve the bearer. Please read that line as a preference against another hop.
    • Agent Router's stateless 2026-07-28 support: the era detection and fanout pull requests (Resolve Mistral through Veryfront Cloud catalog #2518, chore: bump veryfront for coverage ci release #2545 there) are merged; Tag eval runs for gateway billing reconciliation #2692 is still open, and the v1.2.0 notes say the feature is not yet enabled. I wrote "merged" too loosely.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmediumMedium priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions