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
Replace vMCP Modern kill-switch with a capability gate (#6033)
Delete the TOOLHIVE_VMCP_MODERN_STATELESS env var and gate MCP
2026-07-28 (Modern) dispatch on instance capabilities instead:
modernDispatchBlockers() enumerates enabled features the stateless
path cannot serve (currently the optimizer, tracked for parity in
#6089). A gated instance steers Modern-first clients onto the Legacy
handshake via the SDK's discover answer and refuses direct Modern
requests with a conformant -32022.
sessionmanager.New now rejects an optimizer configured without
AdvertiseFromCore, closing a fail-open path where the gate saw no
blocker while the optimizer indexed without serving anyone.
Operator session-semantics e2e specs are ported onto era-pinned
RawMCPClient primitives (mcpcompat cannot pin a revision, #5911), and
the raw client parses SSE-framed POST responses.
Closes#5959. Follow-ups: #6101, #6102, #6103, #6104.
Copy file name to clipboardExpand all lines: docs/arch/10-virtual-mcp-architecture.md
+79Lines changed: 79 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -488,6 +488,85 @@ The four Modern list verbs (`tools/list`, `resources/list`, `resources/templates
488
488
489
489
The completion handler is a single global handler installed via `WithCompletionHandler`, so it recovers the session from the SDK request context rather than a per-session closure. Setting it makes the shim auto-advertise the `completions` capability at initialize.
490
490
491
+
### Served MCP revisions: the Modern capability gate
492
+
493
+
vMCP serves two client-facing MCP revisions: **Legacy** (2025-11-25, the SDK
494
+
session path) always, and **Modern** (2026-07-28, `classifyingHandler →
495
+
dispatchModern`, stateless) **conditionally** — only when every enabled feature
496
+
of the instance is servable by the stateless dispatch path. The condition is
497
+
`modernDispatchBlockers` (`pkg/vmcp/server/modern_gate.go`), an explicit
498
+
enumeration that replaced the temporary `TOOLHIVE_VMCP_MODERN_STATELESS`
499
+
env-var kill-switch (#5959): instead of a global "don't serve Modern", the
500
+
instance serves Modern exactly when it can serve it correctly.
501
+
502
+
Features that currently gate Modern off, and why:
503
+
504
+
| Feature | Why the stateless path cannot serve it |
| Optimizer (`find_tool`/`call_tool`) | The meta-tools are Serve-layer and **session-scoped** (`serve_optimizer.go`): each session builds an FTS5 index over its advertised set and swaps the two meta-tools in. The index is deliberately not in the stateless core, and `dispatchModern` serves `tools/*` straight from `core.ListTools`/`core.CallTool` — a Modern client would silently receive the raw aggregated tool set and `tools/call find_tool` would fail. Parity needs an identity- or instance-scoped index |
507
+
508
+
"Cannot serve" means a Modern client would silently get different behavior than
509
+
the feature promises — not merely that the feature is session-flavored.
510
+
Redis-backed session sharing, for example, does **not** gate Modern: Legacy
511
+
clients keep their shared, reconstructible sessions while Modern clients are
512
+
sessionless by design and store nothing, a coexistence asserted end-to-end by
and the full-handler pair in `modern_gate_integration_test.go`. Achieving
567
+
Modern parity for a feature means deleting its entry and updating those tests —
568
+
nothing else needs to change.
569
+
491
570
### Subscription limitation (ack-level)
492
571
493
572
vMCP advertises `resources.subscribe: true` and answers `resources/subscribe` / `resources/unsubscribe` at **ack level**: the request is accepted (enforcing session binding and validating the URI is an advertised, admitted resource), and go-sdk records the subscription. vMCP does **not** currently propagate backend `notifications/resources/updated` to the subscribed client — doing so requires persistent per-session backend connections, which is out of scope. Clients that subscribe will receive a success ack but no update stream yet.
| 3 |`optimizer.embeddingService` in config YAML | FTS5 + external embedding service | User-managed |`find_tool`, `call_tool` only |
105
105
106
+
Any tier >= 1 keeps clients on MCP 2025-11-25 (Legacy): the `find_tool`/`call_tool` meta-tools are session-scoped, so Modern-capable (2026-07-28) clients are negotiated down to Legacy by the capability gate (`pkg/vmcp/server/modern_gate.go`; Modern parity is tracked in #6089).
107
+
106
108
Tier 2 (`--optimizer-embedding`) implies `--optimizer`. The TEI container is started automatically and stopped on server shutdown.
0 commit comments