@orkestrel/scaffold 0.0.94 → 0.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +5 -5
- package/dist/bin/main.js.map +1 -1
- package/dist/host/claude/agents/orkestrel.md +52 -52
- package/dist/host/guides/agent.md +244 -38
- package/dist/host/guides/browser.md +2371 -2510
- package/dist/host/guides/codec.md +2 -2
- package/dist/host/guides/mcp.md +182 -100
- package/dist/host/guides/msg.md +14 -22
- package/dist/host/guides/ollama.md +172 -11
- package/dist/host/guides/server.md +122 -37
- package/dist/host/guides/toolbox.md +47 -40
- package/dist/host/manifest.json +10 -46
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/src/core/index.cjs +5 -5
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +5 -5
- package/dist/src/core/index.js.map +1 -1
- package/package.json +5 -5
package/dist/host/guides/mcp.md
CHANGED
|
@@ -258,11 +258,21 @@ from an absent header. Defaulting one is licensed only for a server that still s
|
|
|
258
258
|
pre-`2025-06-18` clients, and this package does not:
|
|
259
259
|
|
|
260
260
|
- `initialize` — legitimately headerless; nothing is negotiated yet. Accepted.
|
|
261
|
+
- An id-bearing legacy `ping` — answered with `{}` even before `initialize` returns.
|
|
262
|
+
With no session header, it passes through without minting a session, writing session state,
|
|
263
|
+
supplying a protocol header, or stamping the response. An unknown session id remains `404`.
|
|
261
264
|
- A post-`initialize` legacy request on a **live session** — accepted, under the
|
|
262
265
|
revision pinned at that session's `initialize`. That is a negotiated fact, not a
|
|
263
266
|
default.
|
|
264
267
|
- Anything else — nothing identifies the revision, so HTTP `400` + `-32020`.
|
|
265
268
|
|
|
269
|
+
The sessionless `ping` exception follows the pre-initialization allowance in the
|
|
270
|
+
[MCP 2025-11-25 lifecycle specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization)
|
|
271
|
+
and deliberately departs from the missing-session `400` recommendation in the
|
|
272
|
+
[Streamable HTTP session management specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management).
|
|
273
|
+
A client cannot hold the assigned session id before the `InitializeResult`. This exception
|
|
274
|
+
admits only a valid legacy `ping` request; notifications and modern-shaped pings do not qualify.
|
|
275
|
+
|
|
266
276
|
A header naming an unsupported revision is a separate failure: HTTP `400` + `-32022`,
|
|
267
277
|
carrying `{ supported, requested }`.
|
|
268
278
|
|
|
@@ -1376,6 +1386,10 @@ hook. Returning `undefined` from the hook continues into the ordinary live tool
|
|
|
1376
1386
|
The keys are yours because they are how your own policy correlates each answer. A round asking
|
|
1377
1387
|
nothing is refused, because it would seal state no retry could ever satisfy.
|
|
1378
1388
|
|
|
1389
|
+
`MCPInputOptions.clock` supplies epoch milliseconds for every continuation expiry read.
|
|
1390
|
+
It defaults to `Date.now`. Supply a clock beside `continuation` when the host owns the time
|
|
1391
|
+
source; tests can advance that clock during a provider call without replacing the host clock.
|
|
1392
|
+
|
|
1379
1393
|
**`createMCPContinuation` is what protects the `requestState` echo, and it is required.**
|
|
1380
1394
|
`MCPInputOptions.continuation` has no default: the carrier travels through a client that may
|
|
1381
1395
|
have rewritten it, so the integrity of every binding inside it — principal, expiry, original id,
|
|
@@ -1984,6 +1998,30 @@ server-side one on `server.emitter`'s `error` event, a client-side one on
|
|
|
1984
1998
|
|
|
1985
1999
|
### Compose or remove the legacy protocol layer
|
|
1986
2000
|
|
|
2001
|
+
The optional `MCPServerOptions.handshake` hook gates only the legacy `initialize` response.
|
|
2002
|
+
`MCPServerInterface.handshake` exposes it, and `createMCPLegacy` passes it into
|
|
2003
|
+
`MCPLegacyOptions.handshake`. The `MCPHandshakeHandler` receives `MCPMethodOptions`, including
|
|
2004
|
+
the request's abort signal and optional caller context. On the binder carriers (stdio,
|
|
2005
|
+
WebSocket, and `MessagePort`), `ping`, forwarded legacy methods, and modern `server/discover`
|
|
2006
|
+
remain available while the hook waits. Omitting the hook preserves the dispatcher's answer
|
|
2007
|
+
bytes from 0.0.35. For the session-header change on refused HTTP initialization, see
|
|
2008
|
+
[HTTP transport](#http-transport).
|
|
2009
|
+
|
|
2010
|
+
Over HTTP, a separate legacy `ping` POST also completes while the hook waits, without a
|
|
2011
|
+
session id or negotiated protocol header. The session middleware passes that request through
|
|
2012
|
+
without publishing the pending initialization's candidate session.
|
|
2013
|
+
|
|
2014
|
+
An `MCPError` rejection preserves its code, message, and context as JSON-RPC `error.data`
|
|
2015
|
+
under the initialization request id. If the context cannot be serialized, the answer retains
|
|
2016
|
+
the code and message and omits `data`. Any other rejection emits `error` on the shared server
|
|
2017
|
+
emitter and answers `-32603` with `Server error`. A rejection after request cancellation is
|
|
2018
|
+
re-thrown; the stdio binder writes nothing and emits no error. The hook must observe its
|
|
2019
|
+
signal to stop pending work.
|
|
2020
|
+
|
|
2021
|
+
The [MCP 2025-11-25 lifecycle specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization)
|
|
2022
|
+
permits pings while initialization is pending and describes initialization error responses.
|
|
2023
|
+
The hook adds consumer readiness to that legacy exchange; it does not add a modern lifecycle gate.
|
|
2024
|
+
|
|
1987
2025
|
The dated revisions are not a branch inside the server. They are a **decorator over it**.
|
|
1988
2026
|
`MCPLegacy` wraps one `MCPDispatcherInterface` — the minimal `emitter` / `limit` / `dispatch` /
|
|
1989
2027
|
`handle` surface a transport actually needs, and the one `MCPServerInterface` extends — and translates the
|
|
@@ -2076,6 +2114,7 @@ remaining survivor has its own consumer, and they are not the same one:
|
|
|
2076
2114
|
| `inferEra` | Nothing inside `src`. It is the published era helper, and it reads `isMCPModernVersion` then `isMCPLegacyVersion` rather than restating either set. |
|
|
2077
2115
|
| `inferRequestEra` | `MCPServer`'s `request` event and the HTTP ingress in `src/server/handlers.ts` — both report or route on the era a request's own structure selects, and neither reads a revision set. |
|
|
2078
2116
|
| `isInitializeRequest` | Legacy server ingress: `src/server/middlewares.ts` mints and validates a session from it, and `src/server/inferers.ts` exempts a headerless `initialize` from the header demand. |
|
|
2117
|
+
| `isPingRequest` | Legacy server ingress: admits an id-bearing, non-modern ping without a protocol or session header. |
|
|
2079
2118
|
| `MCPLegacyResult` | The unstamped result arm of `JSONRPCResponse` in `src/core/types.ts`, its guard `isMCPLegacyResult`, and the decorator's projection. |
|
|
2080
2119
|
| `MCP_HANDSHAKE_VERSION` | Client-adapter egress in `src/core/MCPLegacyClientTransport.ts`, the legacy handshake anchor in `src/core/helpers.ts` and `src/server/inferers.ts`, and `SUPPORTED_LEGACY_PROTOCOL_VERSIONS`. |
|
|
2081
2120
|
| `MCP_FALLBACK_VERSION` | `SUPPORTED_LEGACY_PROTOCOL_VERSIONS` plus an explicit `MCPLegacyClientTransportOptions.version` pin. |
|
|
@@ -2309,6 +2348,7 @@ A `Shape` cell holds the constant's declared type.
|
|
|
2309
2348
|
| `isMCPResult` | function | Determines whether a value is one modern MCP result. |
|
|
2310
2349
|
| `isMCPLegacyResult` | function | Determines whether a value is one legacy-era MCP result. |
|
|
2311
2350
|
| `isInitializeRequest` | function | Determines whether a parsed value is an MCP `initialize` invocation. |
|
|
2351
|
+
| `isPingRequest` | function | Determines whether a parsed value is a legacy MCP `ping` request. |
|
|
2312
2352
|
| `isMCPVersion` | function | Determines whether a value is a supported `MCPVersion`. |
|
|
2313
2353
|
| `isMCPSubscriptionFilter` | function | Determines whether a value is an MCP `MCPSubscriptionFilter`. |
|
|
2314
2354
|
| `isMCPConsumerFilter` | function | Checks whether a subscription filter leaves the built-in tools family to the server. |
|
|
@@ -2507,7 +2547,7 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
2507
2547
|
| `MCPInputHandler` | type | `(context: MCPInputContext, options: MCPMethodOptions,) => MCPInputRound \| undefined \| Promise<MCPInputRound \| undefined>` | Decides whether the current `tools/call` still needs input from the client. |
|
|
2508
2548
|
| `MCPPrincipalHandler` | type | `(request: JSONRPCRequest, options: MCPMethodOptions,) => string \| Promise<string>` | Derives the deployment-authenticated principal bound into signed request state. |
|
|
2509
2549
|
| `MCPContinuationInterface` | interface | `{} plus seal, open` | Represents the host-neutral integrity and storage port for opaque MRTR continuation state. |
|
|
2510
|
-
| `MCPInputOptions` | interface | `{ continuation, ttl, principal, selector }`
|
|
2550
|
+
| `MCPInputOptions` | interface | `{ continuation, clock?, ttl, principal, selector }` | Configures the consumer policy for the server's multi-round-trip input mechanism. The injected clock is trusted and must return finite epoch milliseconds. |
|
|
2511
2551
|
| `MCPTaskStatus` | type | `'working' \| 'input_required' \| 'completed' \| 'failed' \| 'cancelled'` | Names the lifecycle state of one durable task. |
|
|
2512
2552
|
| `MCPTask` | type | `{ taskId, status, statusMessage?, createdAt, lastUpdatedAt, ttlMs, pollIntervalMs? }` | Represents one durable task's wire snapshot — the payload a deferred `tools/call` answers with. |
|
|
2513
2553
|
| `MCPTaskDetail` | type | `(MCPTask & { status: 'working' }) \| (MCPTask & { status: 'input_required', inputRequests }) \| (MCPTask & { status: 'completed', result }) \| (MCPTask & { status: 'failed', error }) \| (MCPTask & { status: 'cancelled' })` | Represents one task snapshot together with whatever its status carries — the shape `tasks/get` and a task notification report. |
|
|
@@ -2540,6 +2580,7 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
2540
2580
|
| `MCPListenOptions` | interface | `{ signal, capacity? }` | Configures the per-subscription cancellation and bounded buffering policy. |
|
|
2541
2581
|
| `MCPDispatchOptions` | interface | `{ signal?, caller? }` | Represents the per-request execution options every dispatched handler receives. |
|
|
2542
2582
|
| `MCPMethodOptions` | interface | `{ signal, caller? }` | Represents the resolved per-request options one dispatched method receives. |
|
|
2583
|
+
| `MCPHandshakeHandler` | type | `(options: MCPMethodOptions) => Promise<void>` | Gates a legacy `initialize` response on consumer readiness. |
|
|
2543
2584
|
| `MCPSubscriptionHandler` | type | `(notifications: MCPSubscriptionFilter, options: MCPMethodOptions,) => AsyncIterable<JSONRPCNotification> \| Promise<AsyncIterable<JSONRPCNotification>>` | Produces notifications for one honoured `subscriptions/listen` filter. |
|
|
2544
2585
|
| `MCPSubscriptionOptions` | interface | `{ notifications, producer }` | Configures the server's built-in `subscriptions/listen` method. |
|
|
2545
2586
|
| `MCPStream` | type | `AsyncGenerator<JSONRPCNotification, JSONRPCResponse, unknown>` | Represents a held-open modern result: each `yield` is a `JSONRPCNotification`; the `return` value is the terminating response. |
|
|
@@ -2551,10 +2592,10 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
2551
2592
|
| `MCPServerEventMap` | type | `{ request, error }` | Represents the push observation surface of an `MCPServerInterface` — the dispatch moments a fire-and-forget observer (logging, tracing) subscribes to through `server.emitter.on`. |
|
|
2552
2593
|
| `MCPLimitOptions` | interface | `{ message?, metadata?, keys?, state?, content?, subscriptions?, depth? }` | Configures the hostile-input and live-resource bounds for an MCP server. |
|
|
2553
2594
|
| `MCPJSONLimitOptions` | interface | `{ bytes, keys?, depth }` | Limits applied by `isBoundedJSON` to one JSON value. |
|
|
2554
|
-
| `MCPServerOptions` | interface | `{ on?, error?, identity, tools, resources?, prompts?, completion?, execution?, instructions?, cache?, input?, subscription?, task?, limit? }`
|
|
2595
|
+
| `MCPServerOptions` | interface | `{ on?, error?, identity, handshake?, tools, resources?, prompts?, completion?, execution?, instructions?, cache?, input?, subscription?, task?, limit? }` | Options for `createMCPServer` — the server `MCPIdentity`, the live `ToolManagerInterface` it exposes, optional `instructions`, and the reserved `on` hooks. |
|
|
2555
2596
|
| `MCPDispatcherInterface` | interface | `{ emitter, limit } plus dispatch, handle` | Represents the minimal transport-facing MCP dispatch surface. |
|
|
2556
|
-
| `MCPServerInterface` | interface | `MCPDispatcherInterface plus { identity, methods }`
|
|
2557
|
-
| `MCPLegacyOptions` | interface | `{ dispatcher, identity }`
|
|
2597
|
+
| `MCPServerInterface` | interface | `MCPDispatcherInterface plus { identity, handshake, methods }` | Dispatches JSON-RPC 2.0 modern requests over a live `ToolManagerInterface`, with no transport coupling (a transport layer pumps strings through `handle`). |
|
|
2598
|
+
| `MCPLegacyOptions` | interface | `{ dispatcher, identity, handshake? }` | Represents the construction options for the removable legacy protocol decorator. |
|
|
2558
2599
|
| `MCPTransportInterface` | interface | `{} plus send, listen, closed, close` | Represents a duplex message channel an environment face provides to the pure engine — the one port `bindServer` and `bindClient` (`./helpers.js`) pipe an `MCPServerInterface` / `MCPClientInterface` over. |
|
|
2559
2600
|
| `MCPMessageTransportEventMap` | type | `{ message, close, error }` | Lists the observable events of a `MCPMessageTransportInterface` — the moments the `MCPClientInterface` (and any tracer) subscribes to through `transport.emitter.on`. |
|
|
2560
2601
|
| `MCPMessageTransportInterface` | interface | `{ emitter, session, duplex } plus start, send, close` | Pumps JSON-RPC messages to a peer and surfaces received messages on its `emitter`'s `message` event, with no knowledge of the protocol role on either side — a transport-agnostic MCP message carrier. |
|
|
@@ -2570,7 +2611,7 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
2570
2611
|
| `MCPTaskClientInterface` | interface | `{} plus task, update, abort` | Reads, answers, and stops a durable task the peer created — the client half of the stable Tasks extension. |
|
|
2571
2612
|
| `MCPClientInterface` | interface | `{ emitter, connected, version, transport, tasks } plus connect, discover, disconnect, tools, listen, call` | Connects to a remote MCP server over any injected `MCPMessageTransportInterface`, negotiates the modern wire revision, and exposes the server's tools as local `ToolInterface`s an agent can run. |
|
|
2572
2613
|
|
|
2573
|
-
The `emitter`, `identity`, `methods`, and `limit` members of `MCPServerInterface` are
|
|
2614
|
+
The `emitter`, `identity`, `handshake`, `methods`, and `limit` members of `MCPServerInterface` are
|
|
2574
2615
|
`readonly` data members in the Surface rows — its call-signature methods
|
|
2575
2616
|
are documented under [Methods](#methods), and the registry `methods` exposes
|
|
2576
2617
|
has its own method table there. Likewise the `emitter` /
|
|
@@ -2582,6 +2623,17 @@ members; their methods are under [Methods](#methods). The `id` member of
|
|
|
2582
2623
|
|
|
2583
2624
|
### HTTP transport
|
|
2584
2625
|
|
|
2626
|
+
For legacy initialization, `createMCPPostHandler` records the dispatch response in
|
|
2627
|
+
`MCPSessionState.initialization` before framing it as JSON or SSE only when consumer state is
|
|
2628
|
+
an object with `'session' in context.state`. `createMCPSession` stores and advertises a candidate
|
|
2629
|
+
session only when that response contains a result. An `initialize` that would mint a session
|
|
2630
|
+
stores none and advertises no `Mcp-Session-Id` when refused, including when the error travels
|
|
2631
|
+
in an HTTP 200 SSE event. A live session's header is returned unchanged, including on a refused
|
|
2632
|
+
`initialize`. A successful SSE initialization mints a session with or without a hook.
|
|
2633
|
+
|
|
2634
|
+
The [MCP 2025-11-25 Streamable HTTP specification, Session Management](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management)
|
|
2635
|
+
places the session header "on the HTTP response containing the `InitializeResult`".
|
|
2636
|
+
|
|
2585
2637
|
The **Streamable HTTP transport** (`src/server`, through the `@src/server` barrel)
|
|
2586
2638
|
mounts a transport-agnostic `MCPServerInterface` on the `@orkestrel/router` /
|
|
2587
2639
|
`@orkestrel/server` spine as a route. `createMCPRoutes` returns the
|
|
@@ -2711,7 +2763,7 @@ accounting for inbound traffic subtracts a `('tools/list', 0, 'modern')` that pr
|
|
|
2711
2763
|
`tools/list` under id `0` is not told apart on that event.
|
|
2712
2764
|
|
|
2713
2765
|
Headerless
|
|
2714
|
-
legacy `initialize`
|
|
2766
|
+
legacy `initialize` and id-bearing legacy `ping` are accepted; another headerless legacy request is
|
|
2715
2767
|
accepted only through a live session, whose pinned negotiated version the session
|
|
2716
2768
|
middleware supplies; every other headerless request is HTTP `400` + `-32020`.
|
|
2717
2769
|
`GET` / `DELETE` to the path fall through to whatever the router does with an
|
|
@@ -2723,8 +2775,8 @@ live in the session middleware).
|
|
|
2723
2775
|
(`@orkestrel/server`); compose it with `router.use(createMCPSession())` in
|
|
2724
2776
|
front of a session-agnostic `createMCPRoutes(mcp)`. It owns a closure
|
|
2725
2777
|
`Map<string, { session, touched, version }>`, mints a session on an `initialize` POST
|
|
2726
|
-
(`crypto.randomUUID()`),
|
|
2727
|
-
legacy
|
|
2778
|
+
(`crypto.randomUUID()`), permits a legacy `ping` request without a session header,
|
|
2779
|
+
validates the session on other legacy requests, and adds the resumable `GET` SSE stream.
|
|
2728
2780
|
A modern-shaped POST passes straight through without session lookup and ignores
|
|
2729
2781
|
any `mcp-session-id`; the layer otherwise pins the negotiated legacy revision and
|
|
2730
2782
|
supplies it on a headerless live-session request. The same default-on origin validation
|
|
@@ -2790,24 +2842,24 @@ A `Shape` cell holds the constant's declared type.
|
|
|
2790
2842
|
|
|
2791
2843
|
#### Helpers
|
|
2792
2844
|
|
|
2793
|
-
| API | Kind | Summary
|
|
2794
|
-
| ------------------------- | -------- |
|
|
2795
|
-
| `acceptsEventStream` | function | Checks whether the request's `Accept` header opts into a Server-Sent-Events response.
|
|
2796
|
-
| `allowsOrigin` | function | Checks whether an HTTP request satisfies the endpoint's origin gate.
|
|
2797
|
-
| `inferHeaderIssue` | function | Infers the first required MCP HTTP header a request's own body contradicts.
|
|
2798
|
-
| `inferSessionHeaderIssue` | function | Infers the protocol header issue an active legacy session's pinned revision diagnoses.
|
|
2799
|
-
| `inferHeaderTarget` | function | Infers the target one modern request's `Mcp-Name` header must carry.
|
|
2800
|
-
| `inferParameterRefusal` | function | Infers the refusal one `tools/call` earns for a `Mcp-Param-*` header the body contradicts.
|
|
2801
|
-
| `inferLegacyVersion` | function | Infers the legacy revision an `initialize` request negotiates.
|
|
2802
|
-
| `inferStatus` | function | Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
|
|
2803
|
-
| `readSessionHeader` | function | Reads the request's `mcp-session-id` header — the session id a stateful transport validates, or `undefined` when absent.
|
|
2804
|
-
| `readLastEventId` | function | Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it reconnects to the resumable `GET {path}` stream, or `undefined` when absent.
|
|
2805
|
-
| `rejectUnknownSession` | function | Builds the stateful transport's "unknown session" rejection — an HTTP `404` carrying a JSON-RPC error body.
|
|
2806
|
-
| `sendEventStream` | function | Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per notification in order, then the terminating response — and end the exchange however the pump leaves.
|
|
2807
|
-
| `upgradeRequestPath` | function | Reads the path (without the query string) of a raw `node:http` protocol-upgrade request — the `createWebSocketServer` upgrade-path match.
|
|
2808
|
-
| `extractLines` | function | Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared line-framing step both stdio transports (client and server) read their inbound newline-delimited JSON-RPC messages through.
|
|
2809
|
-
| `writeLine` | function | Writes one line to a Node writable stream and waits for its completion callback.
|
|
2810
|
-
| `dispatchLines` | function | Decodes and delivers each complete newline-framed line onto a `MCPMessageTransportEventMap` emitter — the shared per-chunk dispatch step both stdio transports run their framed lines through: the server transport frames with `extractLines`, the client transport
|
|
2845
|
+
| API | Kind | Summary |
|
|
2846
|
+
| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2847
|
+
| `acceptsEventStream` | function | Checks whether the request's `Accept` header opts into a Server-Sent-Events response. |
|
|
2848
|
+
| `allowsOrigin` | function | Checks whether an HTTP request satisfies the endpoint's origin gate. |
|
|
2849
|
+
| `inferHeaderIssue` | function | Infers the first required MCP HTTP header a request's own body contradicts. |
|
|
2850
|
+
| `inferSessionHeaderIssue` | function | Infers the protocol header issue an active legacy session's pinned revision diagnoses. |
|
|
2851
|
+
| `inferHeaderTarget` | function | Infers the target one modern request's `Mcp-Name` header must carry. |
|
|
2852
|
+
| `inferParameterRefusal` | function | Infers the refusal one `tools/call` earns for a `Mcp-Param-*` header the body contradicts. |
|
|
2853
|
+
| `inferLegacyVersion` | function | Infers the legacy revision an `initialize` request negotiates. |
|
|
2854
|
+
| `inferStatus` | function | Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body. |
|
|
2855
|
+
| `readSessionHeader` | function | Reads the request's `mcp-session-id` header — the session id a stateful transport validates, or `undefined` when absent. |
|
|
2856
|
+
| `readLastEventId` | function | Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it reconnects to the resumable `GET {path}` stream, or `undefined` when absent. |
|
|
2857
|
+
| `rejectUnknownSession` | function | Builds the stateful transport's "unknown session" rejection — an HTTP `404` carrying a JSON-RPC error body. |
|
|
2858
|
+
| `sendEventStream` | function | Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per notification in order, then the terminating response — and end the exchange however the pump leaves. |
|
|
2859
|
+
| `upgradeRequestPath` | function | Reads the path (without the query string) of a raw `node:http` protocol-upgrade request — the `createWebSocketServer` upgrade-path match. |
|
|
2860
|
+
| `extractLines` | function | Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared line-framing step both stdio transports (client and server) read their inbound newline-delimited JSON-RPC messages through. |
|
|
2861
|
+
| `writeLine` | function | Writes one line to a Node writable stream and waits for its completion callback. |
|
|
2862
|
+
| `dispatchLines` | function | Decodes and delivers each complete newline-framed line onto a `MCPMessageTransportEventMap` emitter — the shared per-chunk dispatch step both stdio transports run their framed lines through: the server transport frames with `extractLines`, the client transport frames the supervisor's stdout with Node's `readline`. |
|
|
2811
2863
|
|
|
2812
2864
|
_This face declares no `decodeEvent`, `readEventStream`, or `buildResponseError`. Those SSE
|
|
2813
2865
|
decoders and the response-error builder are host-independent and ship from `@orkestrel/mcp`; see
|
|
@@ -2831,7 +2883,7 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
2831
2883
|
| `MCPSessionOptions` | interface | `{ capacity?, ttl? } plus clock?` | Options for the `MCPSession` entity — its folded replay log's capacity and per-event lifetime. |
|
|
2832
2884
|
| `MCPSessionMiddlewareOptions` | interface | `{ path?, ttl?, session?, origin?, keepalive? } plus clock?` | Options for `createMCPSession` — the path the session middleware owns, the session idle time-to-live, and the per-session resumable event-log bound. |
|
|
2833
2885
|
| `MCPSessionInterface` | interface | `{ id } plus attach, detach, push, replay` | Represents one MCP transport session — the per-session entity a `createMCPSession` middleware owns (the `MCPSession` entity), carrying the resumable server→client push channel with its bounded replay log folded in. |
|
|
2834
|
-
| `MCPSessionState` | interface | `{ session? }`
|
|
2886
|
+
| `MCPSessionState` | interface | `{ session?, initialization? }` | Declares the `context.state` slice a `createMCPSession` middleware sets on a validated / minted request — a consumer's `TState` extends this so the downstream route handler can read `context.state.session` to `push` a server-initiated message onto the session's resumable stream. |
|
|
2835
2887
|
| `MCPSessionEvent` | interface | `{ id, message, timestamp }` | Represents one entry of an `MCPSessionInterface`'s folded replay log — a single pushed `JSONRPCMessage` tagged with the monotone event `id` the session assigned and the `timestamp` it was appended at (for the lazy-TTL replay window). |
|
|
2836
2888
|
| `MCPSessionEntry` | interface | `{ session, touched, version }` | Represents the closure store entry a `createMCPSession` middleware keeps per minted session — the live `MCPSession` entity plus the epoch-ms instant it was last touched (the lazy-TTL sweep's idle clock, independent of the session's own replay-log TTL). |
|
|
2837
2889
|
|
|
@@ -2971,7 +3023,7 @@ case non-flowing with `readableFlowing === false`. Attaching a later `data` list
|
|
|
2971
3023
|
resume that stream; the caller must call `resume()` before the listener receives data.
|
|
2972
3024
|
|
|
2973
3025
|
`createStdioClientTransport` is the egress mirror — it builds one supervised
|
|
2974
|
-
`@orkestrel/process` `
|
|
3026
|
+
`@orkestrel/process` `Supervisor` over `options.command` / `options.args` /
|
|
2975
3027
|
`options.env`, and that supervisor spawns the child with
|
|
2976
3028
|
`stdio: ['pipe', 'pipe', 'pipe']`: `stdin`/`stdout` carry the JSON-RPC channel
|
|
2977
3029
|
and `stderr` is piped and retained as a bounded tail on the supervisor, not
|
|
@@ -2999,7 +3051,7 @@ replacing it: each named key overrides the inherited value, every unlisted key
|
|
|
2999
3051
|
is still inherited, and the child therefore receives every secret this process
|
|
3000
3052
|
holds. The server side frames its own input with `extractLines` (fold a
|
|
3001
3053
|
raw chunk into complete lines + a carried remainder); the client side takes its
|
|
3002
|
-
frames from
|
|
3054
|
+
frames from Node's `readline` over the supervisor's stdout instead. Both
|
|
3003
3055
|
decode through `dispatchLines` (decode + emit each complete line as `message` or
|
|
3004
3056
|
`error`) — documented under [HTTP transport § Helpers](#helpers-1) because it
|
|
3005
3057
|
lives in the shared `helpers.ts`.
|
|
@@ -3032,21 +3084,30 @@ landed draws no reply either. `DEFAULT_MCP_DELIVERY` is shorter than
|
|
|
3032
3084
|
`DEFAULT_MCP_REQUEST_TIMEOUT`; that ordering distinguishes a default-bound
|
|
3033
3085
|
undeliverable write from the later deadline for a peer that did not answer.
|
|
3034
3086
|
|
|
3035
|
-
Closing the client
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3087
|
+
Closing the client ends the child's input, waits up to `MCP_STDIO_GRACE` for native exit,
|
|
3088
|
+
and only then terminates a child that remains alive through the supervisor's bounded stop.
|
|
3089
|
+
This follows the [MCP 2025-11-25 lifecycle shutdown order](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#stdio).
|
|
3090
|
+
The specification leaves the duration open; `MCP_STDIO_GRACE` gives EOF cleanup half the
|
|
3091
|
+
existing 5,000 ms process signal grace, or 2,500 ms, before escalation gets its full window.
|
|
3092
|
+
This bounds disconnect without adding another full signal grace before termination. No transport option
|
|
3093
|
+
is needed by a consumer. The input flush shares that deadline, so a child that stops reading
|
|
3094
|
+
cannot hold closure open. A child that exits on input end within the bound receives no termination signal.
|
|
3095
|
+
That child must end its own child processes on input end; only escalation reaches its process tree.
|
|
3096
|
+
Escalation sends `SIGTERM` and then `SIGKILL` on POSIX; Windows uses `taskkill /F /T`.
|
|
3097
|
+
|
|
3098
|
+
If an `MCPClient` request `timeout` is shorter than the grace and the close outlasts that timeout,
|
|
3099
|
+
`disconnect()` rejects with `MCP transport close timed out after <timeout>ms`. The close keeps
|
|
3100
|
+
running, and a later caller joins it while it remains pending.
|
|
3101
|
+
|
|
3102
|
+
Teardown reaches the child's terminal moment: the supervisor freezes `evidence`, closes the
|
|
3103
|
+
line reader, and settles the child's exit. The supervisor's separate `drain` window bounds the
|
|
3104
|
+
wait for inherited output pipes after native exit. Inbound delivery ends at the call rather than
|
|
3105
|
+
at the stream's end: a line already framed behind the one being delivered is dropped rather than emitted onto a transport
|
|
3045
3106
|
whose teardown has begun. A `close()` issued while that teardown is running joins it rather than
|
|
3046
3107
|
opening a second one, so it resolves only after the `close` event has fired.
|
|
3047
3108
|
|
|
3048
3109
|
The tail frozen at that moment is what the supervisor had received by then, not the child's
|
|
3049
|
-
complete output. On Windows the supervisor ends the tree with `taskkill /F /T`, which nothing in
|
|
3110
|
+
complete output. On Windows, after the input grace expires, the supervisor ends the tree with `taskkill /F /T`, which nothing in
|
|
3050
3111
|
the child can intercept: a `SIGTERM` handler never runs there, so the bytes it would have written
|
|
3051
3112
|
never exist. A child that ends on its own closes its `stderr` first, and that tail is complete.
|
|
3052
3113
|
When the terminal moment arrived at the `drain` bound instead — a detached descendant holding the
|
|
@@ -3097,9 +3158,10 @@ const tools = await client.tools()
|
|
|
3097
3158
|
|
|
3098
3159
|
A `Shape` cell holds the constant's declared type.
|
|
3099
3160
|
|
|
3100
|
-
| Constant | Kind | Shape
|
|
3101
|
-
| ---------------------- | ----- |
|
|
3102
|
-
| `DEFAULT_MCP_DELIVERY` | const | `10000`
|
|
3161
|
+
| Constant | Kind | Shape | Summary |
|
|
3162
|
+
| ---------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
3163
|
+
| `DEFAULT_MCP_DELIVERY` | const | `10000` | Sets the default bound in milliseconds on one unconfirmed write to a stdio client transport's child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets. |
|
|
3164
|
+
| `MCP_STDIO_GRACE` | const | `number` | Sets the bound in milliseconds for a stdio server to exit after the client ends its input. |
|
|
3103
3165
|
|
|
3104
3166
|
#### Helpers
|
|
3105
3167
|
|
|
@@ -3227,7 +3289,8 @@ factory. `publish` registers each advertised tool through `registerTool` with th
|
|
|
3227
3289
|
annotations, retains the `AbortController` whose abort is WebMCP's own unregistration path, and
|
|
3228
3290
|
subscribes to the manager's own `emitter` so a later `add`, `remove`, or `clear` reaches the
|
|
3229
3291
|
document registry without a second call; `adopt` reads `getTools` and wraps each
|
|
3230
|
-
`RegisteredTool` as a tool
|
|
3292
|
+
`RegisteredTool` as a tool, excluding `annotations.debugging: true` unless you pass
|
|
3293
|
+
`adopt({ debugging: true })`. Each tool's `execute` runs `executeTool` with the caller's
|
|
3231
3294
|
`ToolContext.signal`; `destroy` aborts the registrations this handle made, releases that
|
|
3232
3295
|
subscription, and leaves a name it never registered alone. WebMCP keys a registration by tool
|
|
3233
3296
|
name per document, so a release takes whatever now stands under that name. See
|
|
@@ -3293,6 +3356,8 @@ A `Shape` cell holds the constant's declared type.
|
|
|
3293
3356
|
| `DEFAULT_MCP_SERVER_NAME` | const | `'@orkestrel/mcp'` | Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. |
|
|
3294
3357
|
| `DEFAULT_MCP_SERVER_VERSION` | const | `'1.0.0'` | Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. |
|
|
3295
3358
|
| `WEBMCP_CHANGE_EVENT` | const | `'toolchange'` | Names the WebMCP registry event the bridge republishes as its own `change`. |
|
|
3359
|
+
| `WEBMCP_ACTIVATED_EVENT` | const | `'toolactivated'` | Names the WebMCP IDL `toolactivated` event the bridge republishes as `activate`. |
|
|
3360
|
+
| `WEBMCP_ABORT_EVENT` | const | `'toolcancel'` | Names the WebMCP IDL `toolcancel` event the bridge republishes as `abort`. |
|
|
3296
3361
|
|
|
3297
3362
|
#### Helpers
|
|
3298
3363
|
|
|
@@ -3313,6 +3378,7 @@ through, and the registry guards. Each projection is the WebMCP direction of the
|
|
|
3313
3378
|
| `collectWebMCPProjections` | function | Collects the WebMCP projection of every tool a registry advertises that WebMCP can carry. |
|
|
3314
3379
|
| `isWebMCPRegistry` | function | Determines whether an unknown value is a WebMCP tool registry. |
|
|
3315
3380
|
| `isWebMCPDocument` | function | Determines whether an unknown value is a document exposing the WebMCP tool registry. |
|
|
3381
|
+
| `isWebMCPToolEvent` | function | Determines whether an unknown value is a WebMCP execution event. |
|
|
3316
3382
|
|
|
3317
3383
|
The SSE decoders `decodeEvent` and `readEventStream` are host-independent and ship from
|
|
3318
3384
|
`@orkestrel/mcp`; see [Core § Helpers](#helpers).
|
|
@@ -3334,7 +3400,7 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
3334
3400
|
| `ScopeServerOptions` | interface | `{ tools, name?, version? } plus accept?` | Options for `createScopeServer` — the live `ToolManagerInterface` to expose plus the optional server identity, mirroring `createMCPServer`'s `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version` optional (defaulting to `DEFAULT_MCP_SERVER_NAME` / `DEFAULT_MCP_SERVER_VERSION`). |
|
|
3335
3401
|
| `PageServerOptions` | interface | `{ tools, name?, version?, client? }` | Options for `createPageServer` — the live `ToolManagerInterface` to expose, the optional server identity, and the optional settings for the client half the pair owns. |
|
|
3336
3402
|
| `PageServerInterface` | interface | `{ client } plus stop` | Represents one MCP server hosted inside the calling page — what `createPageServer` returns. |
|
|
3337
|
-
| `WebMCPAnnotations` | interface | `{ readOnlyHint?, untrustedContentHint?, consequentialHint? }`
|
|
3403
|
+
| `WebMCPAnnotations` | interface | `{ readOnlyHint?, untrustedContentHint?, consequentialHint?, debugging? }` | Describes a tool's observable effects as the WebMCP registry declares them. |
|
|
3338
3404
|
| `WebMCPDescriptor` | interface | `{ name, title?, description, inputSchema?, annotations? }` | Describes the members a WebMCP tool carries into the registry and back out of it. |
|
|
3339
3405
|
| `WebMCPHandlerOptions` | interface | `{ signal }` | Carries the execution signal the WebMCP registry hands a registered tool's callback. |
|
|
3340
3406
|
| `WebMCPExecuteHandler` | type | `(input: Readonly<Record<string, unknown>>, options: WebMCPHandlerOptions,) => Promise<unknown>` | Runs one registered WebMCP tool with the caller's input and the registry's signal. |
|
|
@@ -3343,13 +3409,14 @@ An extended interface's name comes before `plus`, with the members it adds after
|
|
|
3343
3409
|
| `WebMCPRegisterOptions` | interface | `{ exposedTo?, signal? }` | Options for the WebMCP registry's `registerTool` — the exposure list and the unregistration signal. |
|
|
3344
3410
|
| `WebMCPToolsOptions` | interface | `{ fromOrigins? }` | Options for the WebMCP registry's `getTools` — the origins whose tools are read. |
|
|
3345
3411
|
| `WebMCPExecuteOptions` | interface | `{ signal? }` | Options for the WebMCP registry's `executeTool` — the caller's cancellation signal. |
|
|
3412
|
+
| `WebMCPToolEvent` | interface | `{ toolName }` | Carries the tool name a WebMCP execution event reports. |
|
|
3346
3413
|
| `WebMCPRegistryInterface` | interface | `{} plus registerTool, getTools, executeTool, addEventListener, removeEventListener` | Represents the WebMCP tool registry a document exposes as `document.modelContext`. |
|
|
3347
3414
|
| `WebMCPDocument` | interface | `{ modelContext }` | Describes a document that exposes the WebMCP tool registry. |
|
|
3348
3415
|
| `WebMCPProjection` | interface | `{ tool, descriptor }` | Pairs one tool with the WebMCP descriptor a registry advertises for it. |
|
|
3349
|
-
| `ModelContextEventMap` | type | `{ change: readonly [] }`
|
|
3416
|
+
| `ModelContextEventMap` | type | `{ change: readonly [], activate: readonly [name: string], abort: readonly [name: string] }` | Reports changes and execution events from a WebMCP registry. |
|
|
3350
3417
|
| `ModelContextOptions` | interface | `{ document?, on?, error? }` | Options for `createModelContext` — the document to bridge and the emitter's initial wiring. |
|
|
3351
3418
|
| `ModelContextPublishOptions` | interface | `{ origins? }` | Options for `ModelContextInterface.publish` — the origins each registration is exposed to. |
|
|
3352
|
-
| `ModelContextAdoptOptions` | interface | `{ origins? }`
|
|
3419
|
+
| `ModelContextAdoptOptions` | interface | `{ origins?, debugging? }` | Options for `ModelContextInterface.adopt` — the origins and debugging tools to include. |
|
|
3353
3420
|
| `ModelContextInterface` | interface | `{ emitter } plus publish, adopt, destroy` | Represents the bridge between a `ToolManagerInterface` and a document's WebMCP registry — what `createModelContext` returns. |
|
|
3354
3421
|
|
|
3355
3422
|
_This face declares no `HTTPClientTransportOptions`. It is host-independent and ships from
|
|
@@ -3849,8 +3916,8 @@ the Node one also destroys an upgrade request still on the wire.
|
|
|
3849
3916
|
`WebSocketServerTransport` unsubscribes before it runs the close handshake, so a
|
|
3850
3917
|
frame already in flight cannot re-emit on a
|
|
3851
3918
|
transport that has closed. `StdioClientTransport` stops its own line
|
|
3852
|
-
dispatch at the call,
|
|
3853
|
-
|
|
3919
|
+
dispatch at the call, ends the child's input, waits up to `MCP_STDIO_GRACE` for exit,
|
|
3920
|
+
and then terminates any remaining child through the supervisor. Teardown uses the `drain` bound that caps a
|
|
3854
3921
|
descendant-held stdout pipe.
|
|
3855
3922
|
`StdioServerTransport` removes the listeners it put on `input` and `output`, rejects pending
|
|
3856
3923
|
sends, preserves the caller's flowing or non-flowing state and listeners, and does not destroy
|
|
@@ -4081,7 +4148,8 @@ page.stop() // a repeat releases nothing further
|
|
|
4081
4148
|
The WebMCP bridge `createModelContext` returns, implemented by `ModelContext`. `publish` sends
|
|
4082
4149
|
this page's tools out to `document.modelContext`; `adopt` brings that registry's tools back as
|
|
4083
4150
|
`@orkestrel/tool` tools. The `emitter` data member republishes the registry's `toolchange` as
|
|
4084
|
-
`change
|
|
4151
|
+
`change`, `toolactivated` as `activate`, and `toolcancel` as `abort`. The execution events
|
|
4152
|
+
carry the tool name. Subscribe through `emitter.on`; the `destroy` method releases each registry listener.
|
|
4085
4153
|
|
|
4086
4154
|
`publish` takes a snapshot **and** subscribes. The snapshot is taken when `publish` is called,
|
|
4087
4155
|
before the work queues behind an earlier publication, so a manager mutated while a call waits
|
|
@@ -4158,15 +4226,21 @@ manager contains a throwing handler and reports `ToolFailure.error`, which is te
|
|
|
4158
4226
|
value that was thrown no longer exists to forward and a foreign caller receives a fresh
|
|
4159
4227
|
`Error` whose `message` is that text.
|
|
4160
4228
|
|
|
4229
|
+
The `adopt` method excludes a tool carrying `annotations.debugging: true` unless you pass
|
|
4230
|
+
`adopt({ debugging: true })`. A false or omitted hint leaves the tool included either way;
|
|
4231
|
+
the `origins` option continues to filter which registrations the registry returns.
|
|
4232
|
+
|
|
4161
4233
|
**An adopted tool validates nothing.** It advertises the foreign `inputSchema` as its
|
|
4162
4234
|
`parameters` and forwards the arguments it is given. Compiling that schema into a contract
|
|
4163
4235
|
would let one page's unreadable or hostile schema refuse the whole `adopt` call, and the
|
|
4164
4236
|
arguments reach a handler in another document that has to validate them anyway.
|
|
4165
4237
|
|
|
4238
|
+
The bridge exposes these methods.
|
|
4239
|
+
|
|
4166
4240
|
| Method | Returns | Summary |
|
|
4167
4241
|
| --------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
4168
4242
|
| `publish` | `Promise<void>` | Registers every tool the manager holds at this moment, then follows it. |
|
|
4169
|
-
| `adopt` | `Promise<readonly ToolInterface[]>` | Reads the document's registered tools as locally executable tools.
|
|
4243
|
+
| `adopt` | `Promise<readonly ToolInterface[]>` | Reads the document's registered tools as locally executable tools, excluding debugging tools unless requested. |
|
|
4170
4244
|
| `destroy` | `void` | Aborts every registration this handle made, stops following the tool registry, and releases the emitter — idempotent. |
|
|
4171
4245
|
|
|
4172
4246
|
This example publishes a registry to a document that exposes WebMCP, reads the document's own
|
|
@@ -4202,6 +4276,9 @@ the bridge is the supported path, and it is what owns the registration lifetime.
|
|
|
4202
4276
|
types it `Promise<DOMString>` while the specification's own README sample answers the MCP
|
|
4203
4277
|
content shape `{ content: [...] }`. See [WebMCP parity](#webmcp-parity) for that row.
|
|
4204
4278
|
|
|
4279
|
+
The subscription pair accepts `toolchange`, `toolactivated`, and `toolcancel`; the execution
|
|
4280
|
+
events carry `WebMCPToolEvent.toolName`. The registry exposes these methods.
|
|
4281
|
+
|
|
4205
4282
|
| Method | Returns | Summary |
|
|
4206
4283
|
| --------------------- | ------------------------------------------ | --------------------------------------------------------------------- |
|
|
4207
4284
|
| `registerTool` | `Promise<void>` | Registers one tool, resolving when the registry has accepted it. |
|
|
@@ -4782,6 +4859,12 @@ closed — while ordinary upstream completion closes the response without invent
|
|
|
4782
4859
|
- [Client-side durable tasks and the absent poll loop](../tests/src/core/MCPTaskClient.test.ts)
|
|
4783
4860
|
- [A task transition filtered, stamped, and carried to a subscribed client](../tests/src/core/MCPClient.test.ts)
|
|
4784
4861
|
- [What the shared HTTP client transport owes on release, on headers, and on a non-success reply](../tests/src/core/transports/HTTPClientTransport.test.ts)
|
|
4862
|
+
- [The browser bridge to the WebMCP registry, published, adopted, and republished against the IDL-faithful double](../tests/src/browser/ModelContext.test.ts)
|
|
4863
|
+
- [The browser transports, scope server, page server, and feature detection composed against the Node-face servers](../tests/src/browser/factories.test.ts)
|
|
4864
|
+
- [The WebMCP projections and descriptor matching a bridge reconciles against](../tests/src/browser/helpers.test.ts)
|
|
4865
|
+
- [The WebMCP registry, document, and tool-event narrowing guards](../tests/src/browser/validators.test.ts)
|
|
4866
|
+
- [The message-port transport contract over a real `MessageChannel`](../tests/src/browser/transports/MessagePortTransport.test.ts)
|
|
4867
|
+
- [The WebSocket client transport releasing its socket on close and rejecting a send the channel cannot carry](../tests/src/browser/transports/WebSocketClientTransport.test.ts)
|
|
4785
4868
|
- [The server face composed end to end over a real `node:http` listener](../tests/src/server/integration.test.ts)
|
|
4786
4869
|
- [HTTP response lifecycle composition](../tests/src/server/HTTPDisconnect.test.ts)
|
|
4787
4870
|
- [HTTP handler integration](../tests/src/server/handlers.test.ts)
|
|
@@ -4906,28 +4989,33 @@ this package serves over a wire, so each surface it defines is compared here aga
|
|
|
4906
4989
|
package publishes, and every row ends implement, retain, or exclude with the source it was read
|
|
4907
4990
|
from. `G5c` is the verbatim WebIDL, samples, and chromestatus reading taken from the
|
|
4908
4991
|
[WebMCP specification](https://webmachinelearning.github.io/webmcp) on 2026-09-15; `G5` is the
|
|
4909
|
-
surrounding research context.
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
|
4914
|
-
|
|
|
4915
|
-
| `
|
|
4916
|
-
| `
|
|
4917
|
-
|
|
|
4918
|
-
| `
|
|
4919
|
-
| `
|
|
4920
|
-
| `
|
|
4921
|
-
|
|
|
4922
|
-
|
|
|
4923
|
-
|
|
|
4924
|
-
|
|
|
4925
|
-
|
|
|
4926
|
-
|
|
|
4927
|
-
|
|
|
4928
|
-
|
|
|
4929
|
-
|
|
|
4930
|
-
|
|
|
4992
|
+
surrounding research context. The execution-event and debugging rows use the 2026-09-29 draft
|
|
4993
|
+
source (`index.bs`, see the [WebMCP specification draft](https://webmachinelearning.github.io/webmcp/)); the webref IDL snapshot omits those members (see the [webref repository](https://github.com/w3c/webref)). `createModelContext` is where
|
|
4994
|
+
the implemented rows live.
|
|
4995
|
+
|
|
4996
|
+
| WebMCP surface | Ours | Verdict | Source |
|
|
4997
|
+
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
|
4998
|
+
| `registerTool(tool, { exposedTo, signal })` | `ModelContextInterface.publish(tools, { origins })` | implement — `origins` becomes `exposedTo`, and the retained controller's abort is the unregistration path | `G5c` § 1, § 2 |
|
|
4999
|
+
| `getTools({ fromOrigins })` | `ModelContextInterface.adopt({ origins })` | implement — `origins` becomes `fromOrigins`, and each `RegisteredTool` comes back as a `ToolInterface` | `G5c` § 1 |
|
|
5000
|
+
| `executeTool(tool, input, { signal })` | an adopted tool's `execute(args, context)`, and `ToolManagerInterface.execute` on the other side | implement — the caller's `ToolContext.signal` becomes WebMCP's `signal` in both directions | `G5c` § 1 |
|
|
5001
|
+
| `toolchange` event | `ModelContextEventMap.change` | implement — the registry names no changed tool, so ours is a bare signal and a listener re-reads `adopt` | `G5c` § 2 |
|
|
5002
|
+
| `toolactivated` event | `ModelContextEventMap.activate` | implement — republishes the tool name when execution begins | 2026-09-29 draft, `index.bs` |
|
|
5003
|
+
| `toolcancel` event | `ModelContextEventMap.abort` | implement — republishes the tool name; `abort` is the fixed lifecycle vocabulary for cancellation | 2026-09-29 draft, `index.bs` |
|
|
5004
|
+
| `ToolAnnotations.debugging` | `adopt({ debugging })` | implement — excluded by default; the `debugging` option includes the tool | 2026-09-29 draft, `index.bs` |
|
|
5005
|
+
| Registry change events on our own tool registry | `ToolManagerEventMap` (`add`, `remove`, `clear`), followed by `publish` | implement — `@orkestrel/tool` publishes registry events, so `publish` subscribes to the manager's `emitter` and the document registry tracks the tool registry with no second call | `G5c` § 2 |
|
|
5006
|
+
| `inputSchema` (JSON Schema) | `ToolDefinition.parameters`, derived from `ToolOptions.contract` | implement, exceeds in the publish direction — a published tool carries the contract that validates arguments before execution; an adopted tool carries the foreign `inputSchema` as `parameters` and validates nothing | `G5c` § 1; `G5` § 8 |
|
|
5007
|
+
| `ModelContextTool.title` | `ToolDefinition.title` | implement | `G5c` § 1 |
|
|
5008
|
+
| `ToolAnnotations` hints | `ToolAnnotations { pure, untrusted, consequential }`, projected by `toolAnnotationsToWebMCP` | implement — `pure` to `readOnlyHint`, `untrusted` to `untrustedContentHint`, `consequential` to `consequentialHint`; the MCP wire carries `pure` to `readOnlyHint` and `consequential` to `destructiveHint` and drops `untrusted` | `G5c` § 1, § 4 |
|
|
5009
|
+
| Declarative form attributes | none | exclude — `index.bs` marks that section "entirely a TODO", the submission path is a fetch-tool paraphrase, and markup is application policy | `G5c` § 3 |
|
|
5010
|
+
| Consent and user activation | none; `ToolAnnotations.consequential` is the datum a consent layer reads | exclude — no source states an activation requirement, and consent belongs to the user agent | `G5` § 5 |
|
|
5011
|
+
| Origin scoping (`Permissions-Policy: tools`, `allow="tools"`) | `createScopeServer`'s `accept` gate, and `publish`/`adopt`'s `origins` | implement the library half; the header is the application's | `G5c` § 5 |
|
|
5012
|
+
| Cross-document handshake from the client side | none | exclude — separate origin, navigation, and lifecycle work with its own design round | `G5c` § 5 |
|
|
5013
|
+
| Result shape (`Promise<DOMString>` in the IDL against `{ content: [...] }` in the README sample) | the executed tool's value, resolved unchanged | exclude the contradiction, retain ours — the primary source disagrees with itself, and the bridge projects to MCP content blocks in neither direction | `G5c` § 1, § 4 |
|
|
5014
|
+
| Structured refusal | a rejection carrying the failure's message, which is what the specification's own sample catches | exclude — the specification's issue #282 leaves the shape open; do not invent one | `G5` § 8 |
|
|
5015
|
+
| Streaming and partial result on abort | `notifications/progress` plus `MCPCallOptions.progress` | retain, exceeds — WebMCP defines none | `G5` § 8 |
|
|
5016
|
+
| Resources, prompts, sampling, elicitation, tasks | published by `MCPServer` today | retain — no WebMCP counterpart was found in the sources reached, and whether WebMCP addresses any of them is recorded as unknown rather than as an absence | `G5` § 6 |
|
|
5017
|
+
| Server-initiated requests | none | exclude — a modern-protocol non-goal recorded under [Declared non-goals](#declared-non-goals), not a WebMCP gap | `G5` § 6 |
|
|
5018
|
+
| Shipping status | `createModelContext` returning `undefined` | exclude as a dependency — the detection is the return value, there is no `supported` flag, and the chromestatus record, read 2026-09-15 and last updated 2026-08-12, reports `Proposed` with `"flag": false` and `"origintrial": false` | `G5c` § 7 |
|
|
4931
5019
|
|
|
4932
5020
|
## Declared conformance gaps
|
|
4933
5021
|
|
|
@@ -4938,7 +5026,8 @@ A reproducible run is `npm run test:conformance`: it starts the real Streamable
|
|
|
4938
5026
|
server from this package's source and runs `@modelcontextprotocol/conformance` — the
|
|
4939
5027
|
release `package.json` pins as a development dependency — against specification revision
|
|
4940
5028
|
`2026-07-28`. That is a genuine foreign MCP client driving this surface end to end, and the
|
|
4941
|
-
recorded server-mode result is **
|
|
5029
|
+
recorded server-mode result is **147 passed / 0 failed** at runner `0.2.0-alpha.11`, from
|
|
5030
|
+
the run on 2026-09-29 against the built `dist/`, the `dns-rebinding-protection`
|
|
4942
5031
|
security regression guard (2 passed) included. `tests/conformance.test.ts` records that
|
|
4943
5032
|
result scenario by scenario, so a scenario that stops running reddens instead of vanishing
|
|
4944
5033
|
into a total. `http-custom-header-server-validation` is green at 9 passed: SEP-2243 wants a
|
|
@@ -5033,7 +5122,7 @@ scope:
|
|
|
5033
5122
|
|
|
5034
5123
|
**The browser face's honest proof is a real host, not a foreign client.** There is no cheap
|
|
5035
5124
|
foreign browser MCP client to point at it, and inventing one would be a worse instrument
|
|
5036
|
-
than naming the limit — a fixture
|
|
5125
|
+
than naming the limit — a fixture this package's authors wrote agreeing with code they wrote is not independent
|
|
5037
5126
|
evidence. So the browser claims are proven by Playwright driving real Chromium: a real
|
|
5038
5127
|
`WebSocket`, a real `fetch`, and a real `MessageChannel`, against a real Node server running
|
|
5039
5128
|
outside the page's module graph. That is a real **host** exercising the real platform APIs,
|
|
@@ -5145,19 +5234,6 @@ listing keeps projecting. Arrival order cannot merge two listings into one table
|
|
|
5145
5234
|
another cursorless `tools/list` supersedes before its answer arrives is still delivered to
|
|
5146
5235
|
the caller, exclusions and all, and caches nothing.
|
|
5147
5236
|
|
|
5148
|
-
**The stdio client shuts a child down signal-first, not stdin-first — a declared `SHOULD`
|
|
5149
|
-
departure owned by another package.** The stdio page says a client `SHOULD` close the child's
|
|
5150
|
-
`stdin`, wait for it to exit, and terminate it only if it does not. `StdioClientTransport.close`
|
|
5151
|
-
runs `@orkestrel/process`'s bounded teardown, whose ladder is the other way round: the
|
|
5152
|
-
supervisor signals the child (`SIGTERM`, then `SIGKILL` after the grace window; on Windows a
|
|
5153
|
-
`taskkill /F /T` over the tree), and destroys `stdin` after that. **What it costs:** a child
|
|
5154
|
-
that would have exited cleanly on EOF is signalled instead, so its own shutdown work runs
|
|
5155
|
-
against a deadline and, on Windows, does not run at all — a `SIGTERM` handler never fires there,
|
|
5156
|
-
and the diagnostics it would have written never exist. **Closer:** `@orkestrel/process`. The
|
|
5157
|
-
ladder belongs to the supervisor that owns the child, not to a transport reaching around it, so
|
|
5158
|
-
this package adopts a cooperative stop as soon as `Process` offers one; the improvement is
|
|
5159
|
-
recorded against that package.
|
|
5160
|
-
|
|
5161
5237
|
**Tool-invocation rate limiting — not satisfied, and no unit will close it.**
|
|
5162
5238
|
2025-11-25's `server/tools` § Security Considerations binds a server to validate tool
|
|
5163
5239
|
inputs, implement access controls, **rate limit tool invocations**, and sanitize tool
|
|
@@ -5288,7 +5364,9 @@ assuming, and every bridge scenario runs against `tests/fixtures/modelContext.ts
|
|
|
5288
5364
|
implementation of that WebIDL, member for member and nothing beyond it. **What that proves:**
|
|
5289
5365
|
the translation each way — the projected annotations, the exposure and origin options, the
|
|
5290
5366
|
signal carried into a published tool and out to an adopted one, the `toolchange` republication,
|
|
5291
|
-
|
|
5367
|
+
`toolactivated` republished as `activate`, `toolcancel` republished as `abort`, the default
|
|
5368
|
+
exclusion of `annotations.debugging: true` tools unless `adopt({ debugging: true })` requests
|
|
5369
|
+
them, and the registration ownership and event listeners `destroy` releases. **What it does not prove:** that a user
|
|
5292
5370
|
agent's own registry accepts these registrations, or that an agent driving one reaches this
|
|
5293
5371
|
page's tools. **What it costs:** nothing a consumer can reach today, because
|
|
5294
5372
|
`createModelContext` answers `undefined` on every shipping browser, so feature absence stays
|
|
@@ -5313,7 +5391,8 @@ omission, and a consumer meets each of them at install time rather than in a bui
|
|
|
5313
5391
|
|
|
5314
5392
|
**IDE integration is not claimed.** A real foreign protocol client drives the Streamable
|
|
5315
5393
|
HTTP surface end to end — `@modelcontextprotocol/conformance` against revision
|
|
5316
|
-
`2026-07-28`, recorded at
|
|
5394
|
+
`2026-07-28`, recorded at 147 passed / 0 failed by runner `0.2.0-alpha.11` in the run on
|
|
5395
|
+
2026-09-29 against the built `dist/` — and that is a claim about the
|
|
5317
5396
|
wire. No IDE, editor, or agent host has driven this server. The rule is this repository's
|
|
5318
5397
|
own: a claim about an external client stays unproven until one representative real client
|
|
5319
5398
|
of that class drives it end to end, and no client of the IDE class has. **What it costs:**
|
|
@@ -5589,7 +5668,7 @@ the exact statement:
|
|
|
5589
5668
|
The lookup follows `nextCursor` through at most `MCP_LOOKUP_PAGES` pages, so a
|
|
5590
5669
|
replacement `tools/list` that pages the named tool further in than that recognizes
|
|
5591
5670
|
none either; each page dispatched fires the `request` event under the reserved id `0`.
|
|
5592
|
-
Headerless `initialize`
|
|
5671
|
+
Headerless `initialize` and id-bearing legacy `ping` are accepted; a live-session legacy request uses its
|
|
5593
5672
|
pinned negotiated revision; every other headerless request is **400** +
|
|
5594
5673
|
`-32020`. A request without `Origin` is allowed; a canonical `localhost`, `[::1]`,
|
|
5595
5674
|
or `127.0.0.0/8` literal origin is allowed by default; every other present
|
|
@@ -5849,7 +5928,11 @@ the exact statement:
|
|
|
5849
5928
|
parses to an `initialize` request (`isInitializeRequest`) mints a fresh
|
|
5850
5929
|
`MCPSession` (`crypto.randomUUID()`, the `session` knobs), pins the negotiated legacy
|
|
5851
5930
|
revision, and sets
|
|
5852
|
-
`context.state.session
|
|
5931
|
+
`context.state.session`. A legacy `ping` request with no session header passes through
|
|
5932
|
+
without a session state write, protocol-header supply, store write-back, or response stamp.
|
|
5933
|
+
Other unresolved requests return `rejectUnknownSession()` (`404`). The candidate is stored
|
|
5934
|
+
and advertised only after `context.state.initialization` records a successful dispatch
|
|
5935
|
+
result, independently of JSON or SSE framing. It
|
|
5853
5936
|
then forwards a fresh `Request` carrying the buffered text
|
|
5854
5937
|
(`next(forwarded)`) — never the already-consumed original — so the route
|
|
5855
5938
|
re-reads the same body, retains front-middleware state for caller extraction,
|
|
@@ -5908,7 +5991,7 @@ the exact statement:
|
|
|
5908
5991
|
later `data` listener does not resume that stream; the caller must call
|
|
5909
5992
|
`resume()` before the listener receives data.
|
|
5910
5993
|
`createStdioClientTransport(options)` builds one supervised
|
|
5911
|
-
`@orkestrel/process` `
|
|
5994
|
+
`@orkestrel/process` `Supervisor` over `options.command` and `options.args`.
|
|
5912
5995
|
That supervisor spawns with `stdio: ['pipe', 'pipe', 'pipe']`, so the
|
|
5913
5996
|
child's `stderr` is piped and retained as a bounded tail rather than
|
|
5914
5997
|
inherited by the parent. A provided `env` merges over `process.env` rather
|
|
@@ -5933,7 +6016,7 @@ the exact statement:
|
|
|
5933
6016
|
again, so a detached descendant holding the inherited `stderr` can write
|
|
5934
6017
|
after `close()` resolves and those bytes reach no reading this transport
|
|
5935
6018
|
reports. What the frozen tail holds is what the supervisor had received by
|
|
5936
|
-
that moment, rather than the child's complete output: Windows ends the tree
|
|
6019
|
+
that moment, rather than the child's complete output: after the input grace, Windows ends the tree
|
|
5937
6020
|
with `taskkill /F /T`, which nothing in the child can intercept, so a
|
|
5938
6021
|
`SIGTERM` handler never runs there and the bytes it would have written never
|
|
5939
6022
|
exist. A child that exits on its own closes its `stderr` first, so that tail
|
|
@@ -5978,18 +6061,17 @@ the exact statement:
|
|
|
5978
6061
|
deadline for a peer that did not answer; an explicit `0` removes the bound
|
|
5979
6062
|
and leaves such a write pending on the channel until teardown settles it as
|
|
5980
6063
|
the same rejection.
|
|
5981
|
-
The child's `stdout` is drained through
|
|
5982
|
-
`readline`-framed `lines` iterable and every complete line is decoded onto
|
|
6064
|
+
The child's `stdout` is drained through Node's `readline` and every complete line is decoded onto
|
|
5983
6065
|
`message` through the shared `dispatchLines` helper (a malformed line emits
|
|
5984
6066
|
`error`); the child's exit bridges to the transport's `close`. `close()`
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
6067
|
+
ends stdin and waits up to `MCP_STDIO_GRACE` for native exit before the supervisor
|
|
6068
|
+
terminates a child that remains alive. The input flush shares that bound. Teardown closes
|
|
6069
|
+
the reader at the child's terminal moment and fires `close` once. A line already framed behind the one being
|
|
5988
6070
|
delivered is dropped rather than emitted after the teardown began, and the
|
|
5989
6071
|
wait for a descendant-held stdout pipe is capped by the supervisor's `drain`
|
|
5990
6072
|
bound; a `close()` issued while that teardown runs joins it and resolves only
|
|
5991
6073
|
after `close` has fired. The
|
|
5992
|
-
|
|
6074
|
+
escalation is the host's — a POSIX host signals the child's own process
|
|
5993
6075
|
group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
|
|
5994
6076
|
route, while Windows ends the tree with `taskkill /F /T`.
|
|
5995
6077
|
The stdio transports'
|