You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #5743. Surfaced during the #5742 regression-gate audit (matrix §11).
Problem
Mid-tools/call elicitation from vMCP to a downstream client has two unaddressed failure modes under the current go-sdk-backed shim (toolhive-core v0.0.32):
1. Advertised-but-no-stream hangs to timeout (single-pod)
The shim runs Streamable HTTP with JSONResponse on, so go-sdk routes a server→client elicitation to the client's standalone SSE ("") stream, not the tools/call response. A client MUST hold that stream open (transport.WithContinuousListening()); the default is DisableStandaloneSSE: true.
Today the only fast-fail is the capability gate: if the client never advertised elicitation at initialize, go-sdk's Elicit rejects immediately (covered by TestForwarding_Elicitation_NoDownstreamCapability). But a client that advertised the capability yet holds no open standalone stream is a runtime condition with no guard — the elicitation is handed to ss.Elicit and blocks until the caller's context deadline. That is a hang-to-timeout, not a clean fail-fast.
Design question: a pre-dispatch guard belongs in pkg/vmcp/server, keyed on the downstream client's advertised capabilities recorded on MultiSession at initialize (NOT context values — see the vMCP anti-patterns rule). The hard part is detecting "no open standalone stream": that state lives in go-sdk/shim stream lifecycle and is not currently surfaced upward. Reaching into SDK internals would violate the adapter boundary (anti-pattern #5), so this likely needs an upstream mcpcompat accessor for stream presence before a clean in-repo guard can exist.
2. Cross-pod origination is undeliverable
clientSession.RequestElicitation loads the go-sdk session bound to this pod. A server→client request can only be delivered by the pod currently holding the client's standalone stream. If the tools/call executes on pod A but the client's GET stream is on pod B, an elicitation from A cannot reach the client. The shim's cross-replica tests cover keep-alive/notification delivery on a rehydrated stream, not server-initiated request/response (elicitation/sampling) origination from the non-stream-holding replica. This needs session affinity (LB pins the standalone stream and tool calls to the same replica) or an MRTR-era redesign (2026-07-28 replaces server-initiated requests with client-polled MRTR — see #5743).
Proposed work
Upstream: add a mcpcompat accessor exposing whether a session holds an open standalone stream.
In-repo: pre-dispatch fast-fail in the vMCP server when elicitation is requested but the downstream advertised the capability without an open stream — return a clean error, not a hang. Add the regression test (TestForwarding_Elicitation_AdvertisedButNoStream_FastFails) that the Add regression tests gating the mcp-go to go-sdk migration #5742 gate leaves skipped/documented today.
Design + document the cross-pod story (session affinity requirement, or defer to MRTR). Update docs/arch/10-virtual-mcp-architecture.md.
Notes
Freeze investment appropriately: the 2026-07-28 revision replaces server-initiated requests with MRTR, so the cross-pod fix should be shaped MRTR-first rather than doubling down on the SSE standalone-stream model.
Part of #5743. Surfaced during the #5742 regression-gate audit (matrix §11).
Problem
Mid-
tools/callelicitation from vMCP to a downstream client has two unaddressed failure modes under the currentgo-sdk-backed shim (toolhive-core v0.0.32):1. Advertised-but-no-stream hangs to timeout (single-pod)
The shim runs Streamable HTTP with
JSONResponseon, sogo-sdkroutes a server→client elicitation to the client's standalone SSE ("") stream, not thetools/callresponse. A client MUST hold that stream open (transport.WithContinuousListening()); the default isDisableStandaloneSSE: true.Today the only fast-fail is the capability gate: if the client never advertised
elicitationatinitialize,go-sdk'sElicitrejects immediately (covered byTestForwarding_Elicitation_NoDownstreamCapability). But a client that advertised the capability yet holds no open standalone stream is a runtime condition with no guard — the elicitation is handed toss.Elicitand blocks until the caller's context deadline. That is a hang-to-timeout, not a clean fail-fast.Delivery path: composer →
vmcp.ElicitationRequester→pkg/vmcp/server/sdk_elicitation_adapter.go→ shimRequestElicitation→go-sdkServerSession.Elicit.Design question: a pre-dispatch guard belongs in
pkg/vmcp/server, keyed on the downstream client's advertised capabilities recorded onMultiSessionat initialize (NOT context values — see the vMCP anti-patterns rule). The hard part is detecting "no open standalone stream": that state lives ingo-sdk/shim stream lifecycle and is not currently surfaced upward. Reaching into SDK internals would violate the adapter boundary (anti-pattern #5), so this likely needs an upstreammcpcompataccessor for stream presence before a clean in-repo guard can exist.2. Cross-pod origination is undeliverable
clientSession.RequestElicitationloads thego-sdksession bound to this pod. A server→client request can only be delivered by the pod currently holding the client's standalone stream. If thetools/callexecutes on pod A but the client's GET stream is on pod B, an elicitation from A cannot reach the client. The shim's cross-replica tests cover keep-alive/notification delivery on a rehydrated stream, not server-initiated request/response (elicitation/sampling) origination from the non-stream-holding replica. This needs session affinity (LB pins the standalone stream and tool calls to the same replica) or an MRTR-era redesign (2026-07-28 replaces server-initiated requests with client-polled MRTR — see #5743).Proposed work
mcpcompataccessor exposing whether a session holds an open standalone stream.TestForwarding_Elicitation_AdvertisedButNoStream_FastFails) that the Add regression tests gating the mcp-go to go-sdk migration #5742 gate leaves skipped/documented today.docs/arch/10-virtual-mcp-architecture.md.Notes
Generated with Claude Code