Skip to content

Support MRTR pass-through for Modern backends in vMCP #6059

Description

@JAORMX

Summary

When a Modern (2026-07-28) backend answers a relayed tools/call, prompts/get or resources/read with resultType: "input_required", vMCP currently fails with errModernInputRequired — a deliberate seam at pkg/vmcp/client/modern.go:64. This issue tracks implementing the relay.

This is the coherent shape of MRTR support in vMCP, and it is deliberately not the shape that was rejected. Recording the distinction, because the two look similar and only one is buildable.

What to build: pure relay, stateless at vMCP

Per SEP-2322's ephemeral flow, the server holds no state between rounds — the handler is re-invoked from scratch and all cross-round state round-trips through an opaque requestState blob. So:

  1. Forward the backend's inputRequests to the Modern client.
  2. Wrap the backend's opaque requestState with vMCP routing context (which backend it came from), keeping it opaque to the client.
  3. On the client's re-issued call carrying inputResponses plus the echoed requestState, unwrap it and relay to the same backend, which re-invokes its own handler.

vMCP holds no per-round state. That is what makes it viable under a revision that removed sessions.

Prerequisite: the egress half — driving and propagating the loop in pkg/vmcp/client. The errModernInputRequired seam is where that begins.

What was rejected, and why

Bridging a Legacy backend's live mid-call elicitation to a Modern client. That backend interaction is a blocked goroutine holding an open session; it cannot be serialized into requestState, and re-invoking is impossible because side effects have already run. Making it work would mean parking the in-flight call server-side behind an unguessable resume token, with TTL and eviction, replica affinity (no Mcp-Session-Id means a load balancer cannot route the resume), and identity binding on a token that becomes a capability to hijack someone else's in-flight call.

That is a session in disguise — precisely what 2026-07-28 removed. The specification's own answer for genuinely stateful input_required work is the Tasks extension (SEP-2663: tools/call returns resultType: "task" with a taskId; the client polls tasks/get and answers via inputResponses on tasks/update). If Legacy-backend elicitation for Modern clients is ever genuinely required, Tasks is the path — not parked calls.

See the "elicitation and sampling are unavailable to Modern clients" section in docs/arch/10-virtual-mcp-architecture.md for the full rationale as landed.

Scope note worth reading before prioritising

SEP-2577 deprecates sampling, logging and roots as of 2026-07-28 (annotation-only, functional for at least twelve months), with the sanctioned migration for sampling being direct LLM-provider integration. So by the time Modern backends exist to relay from, elicitation is MRTR's only durable consumer. That materially shrinks the value of this work relative to how it looks at first glance, and is why it was deferred rather than built alongside the Modern client-facing surface.

Also relevant: go-sdk's assertServerInitiatedRequestAllowed gates elicitation, sampling and roots on the negotiated protocol version alone and never consults client capabilities — so there is no "advertise less" alternative that recovers these features for Modern clients. It is MRTR or nothing.

Refs #5743. Related: #6051 (the disposition that made the current behaviour explicit and honest).

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions