@orkestrel/mcp 0.0.19 → 0.0.21
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/README.md +23 -22
- package/dist/src/browser/index.d.ts +92 -70
- package/dist/src/browser/index.js +164 -81
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +269 -257
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +351 -358
- package/dist/src/core/index.d.ts +351 -358
- package/dist/src/core/index.js +270 -257
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +331 -173
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +266 -146
- package/dist/src/server/index.d.ts +266 -146
- package/dist/src/server/index.js +331 -173
- package/dist/src/server/index.js.map +1 -1
- package/package.json +13 -10
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ surfaces its tools as local `ToolInterface`s. No agent runtime is required.
|
|
|
8
8
|
The dispatch core is transport- and provider-agnostic
|
|
9
9
|
(`src/core` — JSON-RPC 2.0, no HTTP, no `as`); every transport (Streamable
|
|
10
10
|
HTTP over `@orkestrel/router` / `@orkestrel/server`, WebSocket over
|
|
11
|
-
`@orkestrel/websocket`, and stdio over `
|
|
11
|
+
`@orkestrel/websocket`, and stdio over `@orkestrel/process`) lives one layer
|
|
12
12
|
out (`src/server`), each mechanism, not policy. Part of the `@orkestrel` line.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
@@ -19,8 +19,12 @@ npm install @orkestrel/mcp
|
|
|
19
19
|
|
|
20
20
|
## Requirements
|
|
21
21
|
|
|
22
|
-
- Node.js >=
|
|
23
|
-
- ESM and CommonJS builds ship for both the core and server entry points
|
|
22
|
+
- Node.js >= 22.12.0
|
|
23
|
+
- ESM and CommonJS builds ship for both the core and server entry points; the
|
|
24
|
+
browser entry point ships ESM only
|
|
25
|
+
- TypeScript `moduleResolution` set to `node16`, `nodenext`, or `bundler`. Under
|
|
26
|
+
legacy `node` resolution the `./browser` and `./server` subpaths resolve no
|
|
27
|
+
declarations
|
|
24
28
|
- `@orkestrel/server` and `@orkestrel/router` are peer dependencies (the HTTP
|
|
25
29
|
spine the `./server` transports mount onto)
|
|
26
30
|
|
|
@@ -29,15 +33,16 @@ npm install @orkestrel/mcp
|
|
|
29
33
|
Expose a tool registry over MCP, mounted on the HTTP spine:
|
|
30
34
|
|
|
31
35
|
```ts
|
|
32
|
-
import { createMCPServer } from '@orkestrel/mcp'
|
|
36
|
+
import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
33
37
|
import { createMCPRoutes } from '@orkestrel/mcp/server'
|
|
34
38
|
import { createTool, createToolManager } from '@orkestrel/tool'
|
|
35
39
|
|
|
36
40
|
const tools = createToolManager()
|
|
37
41
|
tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
|
|
38
42
|
|
|
39
|
-
const mcp = createMCPServer({ name: 'calculator', version: '1.0.0', tools })
|
|
40
|
-
|
|
43
|
+
const mcp = createMCPServer({ identity: { name: 'calculator', version: '1.0.0' }, tools })
|
|
44
|
+
// POST /mcp dispatches JSON-RPC (JSON or SSE per Accept):
|
|
45
|
+
const routes = createMCPRoutes(createMCPLegacy(mcp)) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
41
46
|
router.add(routes)
|
|
42
47
|
```
|
|
43
48
|
|
|
@@ -60,19 +65,15 @@ The SAME `MCPClient` drives a `createWebSocketClientTransport` or
|
|
|
60
65
|
|
|
61
66
|
## Guide
|
|
62
67
|
|
|
63
|
-
For the full surface — the JSON-RPC dispatch core, the
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
For the full surface — the JSON-RPC dispatch core, the server transports
|
|
69
|
+
(HTTP, WebSocket, stdio), the native session middleware, and usage
|
|
70
|
+
patterns — see the [MCP guide](https://github.com/orkestrel/mcp/blob/main/guides/mcp.md).
|
|
66
71
|
|
|
67
72
|
## Package
|
|
68
73
|
|
|
69
|
-
Published
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
625.0 kB packed across 18 files, 2.5 MB unpacked.
|
|
73
|
-
|
|
74
|
-
[`CHANGELOG.md`](CHANGELOG.md) lives in the repository and is not in the
|
|
75
|
-
tarball, because `files` is `["dist/src", "README.md"]`.
|
|
74
|
+
Published per the `exports` field in `package.json`: the
|
|
75
|
+
environment-agnostic core (`.`), the Node-only server surface (`./server`),
|
|
76
|
+
and the browser face (`./browser`), which is ESM only.
|
|
76
77
|
|
|
77
78
|
## Proven
|
|
78
79
|
|
|
@@ -91,9 +92,9 @@ of the IDE class has.
|
|
|
91
92
|
|
|
92
93
|
## Declared limits
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
The publication facts, each with its number. Full detail, plus every
|
|
95
96
|
protocol-level gap and non-goal, is in
|
|
96
|
-
[
|
|
97
|
+
[the MCP guide](https://github.com/orkestrel/mcp/blob/main/guides/mcp.md#declared-packaging-limits).
|
|
97
98
|
|
|
98
99
|
- **No IDE evidence.** See above. The conformance number is about the wire
|
|
99
100
|
and does not transfer to a host application.
|
|
@@ -101,12 +102,12 @@ protocol-level gap and non-goal, is in
|
|
|
101
102
|
condition, so `node16`, `nodenext`, and `bundler` resolution find
|
|
102
103
|
declarations. A consumer on legacy `moduleResolution: node` does not read
|
|
103
104
|
`exports` and sees an untyped package.
|
|
104
|
-
- **A build-time version notice
|
|
105
|
+
- **A build-time version notice on every built face.** API Extractor bundles
|
|
105
106
|
TypeScript 5.9.3 through a transitive pin and this project compiles with
|
|
106
107
|
6.0.3, so `build` prints one notice per built face. It is informational:
|
|
107
108
|
`build` exits 0 and every declaration is emitted.
|
|
108
|
-
- **Source maps ship.**
|
|
109
|
-
|
|
109
|
+
- **Source maps ship.** Measured on 2026-08-20, the `.map` files are
|
|
110
|
+
1,168,764 of 2,543,024 unpacked bytes (46.0 percent). They are kept so a consumer debugging a
|
|
110
111
|
protocol library steps into real source.
|
|
111
112
|
|
|
112
113
|
The notice, verbatim:
|
|
@@ -117,4 +118,4 @@ The notice, verbatim:
|
|
|
117
118
|
|
|
118
119
|
## License
|
|
119
120
|
|
|
120
|
-
MIT © [Orkestrel](https://github.com/orkestrel) — see [
|
|
121
|
+
MIT © [Orkestrel](https://github.com/orkestrel) — see the [license](https://github.com/orkestrel/mcp/blob/main/LICENSE).
|
|
@@ -10,18 +10,18 @@ import { MCPTransportInterface as MCPTransportInterface_2 } from '@orkestrel/mcp
|
|
|
10
10
|
import { ToolManagerInterface } from '@orkestrel/tool';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* {@link import('@
|
|
13
|
+
* Creates the browser-face HTTP CLIENT transport for an
|
|
14
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
|
|
15
15
|
* that drives a REMOTE Streamable-HTTP MCP server over the native `fetch`, the
|
|
16
|
-
* browser sibling of the Node face's `createHTTPClientTransport` (`@
|
|
16
|
+
* browser sibling of the Node face's `createHTTPClientTransport` (`@orkestrel/mcp/server`).
|
|
17
17
|
*
|
|
18
18
|
* @remarks
|
|
19
19
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
|
|
20
20
|
* sends is `POST`ed to `options.url` with `content-type: application/json` and an
|
|
21
21
|
* `Accept` of both `application/json` and `text/event-stream` (the server answers
|
|
22
22
|
* with EITHER — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
|
|
23
|
-
* decoded
|
|
24
|
-
* `message` event for the client's id correlation. Add `options.headers` (
|
|
23
|
+
* decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's
|
|
24
|
+
* `message` event for the client's id correlation. Add `options.headers` (for example, an
|
|
25
25
|
* `Authorization` bearer) to reach a guarded server. `start` / `close` hold no
|
|
26
26
|
* connection; against a STATEFUL server it captures the `mcp-session-id` from
|
|
27
27
|
* `initialize` and echoes it on later requests. It also captures the initialize
|
|
@@ -32,7 +32,7 @@ import { ToolManagerInterface } from '@orkestrel/tool';
|
|
|
32
32
|
*
|
|
33
33
|
* @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged
|
|
34
34
|
* onto every request, optional `fetch` (default `globalThis.fetch`), and optional
|
|
35
|
-
* `timeout` (ms, applied
|
|
35
|
+
* `timeout` (ms, applied with `AbortSignal.timeout`); see
|
|
36
36
|
* {@link HTTPClientTransportOptions}
|
|
37
37
|
* @returns A working {@link MCPClientTransportInterface} over the native `fetch`
|
|
38
38
|
*
|
|
@@ -51,11 +51,11 @@ import { ToolManagerInterface } from '@orkestrel/tool';
|
|
|
51
51
|
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface_2;
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
* {@link import('@
|
|
54
|
+
* Creates the browser-face `MessagePort` transport — a
|
|
55
|
+
* {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the
|
|
56
56
|
* SYMMETRIC carrier that works as either a server or a client transport depending on
|
|
57
|
-
* which binder ({@link import('@
|
|
58
|
-
* {@link import('@
|
|
57
|
+
* which binder ({@link import('@orkestrel/mcp').bindServer} or
|
|
58
|
+
* {@link import('@orkestrel/mcp').bindClient}) it is handed to.
|
|
59
59
|
*
|
|
60
60
|
* @remarks
|
|
61
61
|
* `port.start()` runs at construction (see {@link MessagePortTransport}'s doc for
|
|
@@ -65,21 +65,22 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
|
|
|
65
65
|
*
|
|
66
66
|
* @param options - `port` (the `MessagePort` half to drive; REQUIRED); see
|
|
67
67
|
* {@link MessagePortTransportOptions}
|
|
68
|
-
* @returns A working {@link import('@
|
|
68
|
+
* @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port
|
|
69
69
|
*
|
|
70
70
|
* @example
|
|
71
71
|
* ```ts
|
|
72
|
-
* import { bindServer, createMCPServer } from '@orkestrel/mcp'
|
|
72
|
+
* import { bindServer, createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
73
73
|
* import { createMessagePortTransport } from '@orkestrel/mcp/browser'
|
|
74
74
|
*
|
|
75
75
|
* const { port1, port2 } = new MessageChannel()
|
|
76
|
-
*
|
|
76
|
+
* const mcp = createMCPServer({ identity: { name: 's', version: '1.0.0' }, tools })
|
|
77
|
+
* bindServer(createMCPLegacy(mcp), createMessagePortTransport({ port: port1 })) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
77
78
|
* ```
|
|
78
79
|
*/
|
|
79
80
|
export declare function createMessagePortTransport(options: MessagePortTransportOptions): MCPTransportInterface;
|
|
80
81
|
|
|
81
82
|
/**
|
|
82
|
-
*
|
|
83
|
+
* Builds `serveMCPScope`'s `message`-event listener — the unified
|
|
83
84
|
* dispatcher that routes EVERY inbound event on a hostable scope, portless or
|
|
84
85
|
* port-bearing, to the right binding.
|
|
85
86
|
*
|
|
@@ -88,42 +89,48 @@ export declare function createMessagePortTransport(options: MessagePortTransport
|
|
|
88
89
|
* — when the gate returns `false` the event is dropped entirely (no binding, no reply).
|
|
89
90
|
* Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,
|
|
90
91
|
* `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)
|
|
91
|
-
* into `teardowns
|
|
92
|
-
* same `MessagePort` would create duplicate bindings over one port (→ duplicated
|
|
93
|
-
* so
|
|
92
|
+
* into `teardowns` KEYED BY THAT PORT. A port already present is IGNORED — repeated delivery
|
|
93
|
+
* of the same `MessagePort` would create duplicate bindings over one port (→ duplicated
|
|
94
|
+
* replies), so a repeat is silently dropped.
|
|
95
|
+
*
|
|
96
|
+
* The key is what makes `teardowns` the ONLY place an accepted port is remembered. A separate
|
|
97
|
+
* seen-port set would be a second collection over the same lifetime, and the caller's disposer
|
|
98
|
+
* would have to remember to empty both — so a long-lived scope such as a Service Worker would
|
|
99
|
+
* retain every port it ever accepted, closed and unbound ones included. Membership answers
|
|
100
|
+
* "already bound?" and `clear()` drops the binding and the dedup together.
|
|
94
101
|
*
|
|
95
102
|
* This branch fires on EITHER a Service-Worker-shaped scope (its normal per-client
|
|
96
103
|
* channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
|
|
97
104
|
* (the unified design's deliberate cross-case, needing no upfront shape flag). An event
|
|
98
105
|
* with NO ports and a STRING `data` is pushed onto `scopeTransport.deliver` (the
|
|
99
106
|
* implicit, already-bound scope channel); any other event (no ports, non-string data)
|
|
100
|
-
* is silently dropped — total
|
|
107
|
+
* is silently dropped — total, never throws.
|
|
101
108
|
*
|
|
102
109
|
* @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over
|
|
103
110
|
* @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto
|
|
104
|
-
* @param teardowns - The shared teardown
|
|
111
|
+
* @param teardowns - The shared teardown map `serveMCPScope`'s dispose drains and clears, keyed by the accepted port; each port-bearing event adds one entry
|
|
105
112
|
* @param options - The `ServeMCPOptions` (for `options.accept`)
|
|
106
113
|
* @returns The `message`-event listener to register (and later remove) on the scope
|
|
107
114
|
*
|
|
108
115
|
* @example
|
|
109
116
|
* ```ts
|
|
110
|
-
* const teardowns = new
|
|
117
|
+
* const teardowns = new Map<MessagePort, () => void>()
|
|
111
118
|
* const scopeTransport = createScopeTransport(scope)
|
|
112
119
|
* bindServer(server, scopeTransport)
|
|
113
120
|
* const onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)
|
|
114
121
|
* scope.addEventListener('message', onMessage)
|
|
115
122
|
* ```
|
|
116
123
|
*/
|
|
117
|
-
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns:
|
|
124
|
+
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ServeMCPOptions): (event: MessageEvent) => void;
|
|
118
125
|
|
|
119
126
|
/**
|
|
120
|
-
*
|
|
127
|
+
* Adapts a hostable {@link ServeMCPScopeInterface} (`self` in a dedicated Web Worker,
|
|
121
128
|
* or any structurally matching double) into a {@link ScopeTransportInterface} — the
|
|
122
129
|
* implicit, portless message channel `serveMCPScope` binds for the
|
|
123
130
|
* dedicated-worker shape.
|
|
124
131
|
*
|
|
125
132
|
* @remarks
|
|
126
|
-
* `send` writes each outbound string
|
|
133
|
+
* `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
|
|
127
134
|
* register the SINGLE handler `deliver` / the underlying close path route through —
|
|
128
135
|
* `serveMCPScope`'s own `scope` `message`-event listener calls `deliver(event.data)`
|
|
129
136
|
* for every portless, string-payload event (there is no native registration point on
|
|
@@ -133,7 +140,7 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
|
|
|
133
140
|
*
|
|
134
141
|
* @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`
|
|
135
142
|
* inside a dedicated Web Worker)
|
|
136
|
-
* @returns A {@link ScopeTransportInterface} `serveMCPScope` binds and drives
|
|
143
|
+
* @returns A {@link ScopeTransportInterface} `serveMCPScope` binds and drives through `deliver`
|
|
137
144
|
*
|
|
138
145
|
* @example
|
|
139
146
|
* ```ts
|
|
@@ -144,10 +151,10 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
|
|
|
144
151
|
export declare function createScopeTransport(scope: ServeMCPScopeInterface): ScopeTransportInterface;
|
|
145
152
|
|
|
146
153
|
/**
|
|
147
|
-
*
|
|
148
|
-
* {@link import('@
|
|
154
|
+
* Creates the browser-face WebSocket CLIENT transport for an
|
|
155
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
|
|
149
156
|
* that drives a REMOTE MCP server over the native `WebSocket` global, the browser
|
|
150
|
-
* sibling of the Node face's `createWebSocketClientTransport` (`@
|
|
157
|
+
* sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
151
158
|
*
|
|
152
159
|
* @remarks
|
|
153
160
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
@@ -177,13 +184,13 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
|
|
|
177
184
|
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface_2;
|
|
178
185
|
|
|
179
186
|
/**
|
|
180
|
-
*
|
|
187
|
+
* Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
|
|
181
188
|
* when it is not one — the per-event step {@link readEventStream} folds over.
|
|
182
189
|
*
|
|
183
190
|
* @remarks
|
|
184
191
|
* `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the
|
|
185
192
|
* event's `data`) inside a try/catch and narrows the parsed value with
|
|
186
|
-
* `parseJSONRPCMessage`. Total
|
|
193
|
+
* `parseJSONRPCMessage`. Total: malformed JSON or a non-message value yields
|
|
187
194
|
* `undefined`, never throws.
|
|
188
195
|
*
|
|
189
196
|
* @param data - One SSE event's `data` payload
|
|
@@ -201,7 +208,7 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
|
201
208
|
* The browser-face HTTP CLIENT transport for the Model Context Protocol — a
|
|
202
209
|
* {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
|
|
203
210
|
* over the native `fetch`, the browser sibling of the Node face's
|
|
204
|
-
* {@link import('@
|
|
211
|
+
* {@link import('@orkestrel/mcp/server').HTTPClientTransport}, honoring the SAME
|
|
205
212
|
* `mcp-session-id` semantics so it interoperates with an `MCPSession`-based server
|
|
206
213
|
* unchanged.
|
|
207
214
|
*
|
|
@@ -209,12 +216,12 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
|
209
216
|
* - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
|
|
210
217
|
* message to `options.url` with `content-type: application/json` and an
|
|
211
218
|
* `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
|
|
212
|
-
* answer with either framing) — plus any `options.headers` (
|
|
219
|
+
* answer with either framing) — plus any `options.headers` (for example, an
|
|
213
220
|
* `Authorization` bearer). It then decodes the reply and emits each decoded
|
|
214
221
|
* {@link JSONRPCMessage} on the `message` event the
|
|
215
|
-
* {@link import('@
|
|
222
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} subscribes to.
|
|
216
223
|
* - **Both reply framings.** A `200` with an `application/json` body is parsed with
|
|
217
|
-
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded
|
|
224
|
+
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
|
|
218
225
|
* `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} (the browser
|
|
219
226
|
* face's own `readEventStream`) — the inverse of the server's `openStream` seam, so
|
|
220
227
|
* the wire round-trips. A `202` Accepted (a notification) carries no body and emits
|
|
@@ -232,10 +239,15 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
|
232
239
|
* Before initialize returns, neither captured legacy header is sent.
|
|
233
240
|
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
234
241
|
* POST is headerless; the captured `session` persists across `close()`.
|
|
235
|
-
* -
|
|
242
|
+
* - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
|
|
243
|
+
* ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
|
|
244
|
+
* never ends would otherwise outlive the transport, with nothing left able to reach it. The
|
|
245
|
+
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
246
|
+
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
247
|
+
* - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
236
248
|
* the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
|
|
237
249
|
* decode failure surfaces on the `error` event rather than escaping `send`.
|
|
238
|
-
* - **Observable
|
|
250
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
|
|
239
251
|
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
240
252
|
*
|
|
241
253
|
* @example
|
|
@@ -263,15 +275,17 @@ export declare class HTTPClientTransport implements MCPClientTransportInterface
|
|
|
263
275
|
* @remarks
|
|
264
276
|
* - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
|
|
265
277
|
* `POST` target every JSON-RPC message is written to). REQUIRED.
|
|
266
|
-
* - `headers` — extra request headers merged onto every `POST` (
|
|
278
|
+
* - `headers` — extra request headers merged onto every `POST` (for example, an
|
|
267
279
|
* `Authorization` bearer for a guarded server). The transport always sets
|
|
268
280
|
* `content-type: application/json` and an `Accept` of both `application/json` and
|
|
269
281
|
* `text/event-stream`; a key supplied here is merged on top.
|
|
270
282
|
* - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
|
|
271
283
|
* `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
|
|
272
284
|
* - `timeout` — an optional per-request timeout in milliseconds; when set, each
|
|
273
|
-
* `fetch` call
|
|
274
|
-
*
|
|
285
|
+
* `fetch` call composes that deadline with the transport's own close through
|
|
286
|
+
* `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
|
|
287
|
+
* ends the request. Omit for no transport-level deadline; the close signal is passed
|
|
288
|
+
* either way.
|
|
275
289
|
*/
|
|
276
290
|
export declare interface HTTPClientTransportOptions {
|
|
277
291
|
readonly url: string;
|
|
@@ -310,8 +324,8 @@ export declare const MCP_SESSION_HEADER = "mcp-session-id";
|
|
|
310
324
|
|
|
311
325
|
/**
|
|
312
326
|
* The WebSocket subprotocol `createWebSocketClientTransport` requests by default —
|
|
313
|
-
* `'mcp'`,
|
|
314
|
-
*
|
|
327
|
+
* `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
|
|
328
|
+
* §4.1 a client MUST fail the connection if the server returns
|
|
315
329
|
* a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
|
|
316
330
|
* default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
|
|
317
331
|
* only when connecting to a foreign server that speaks a different subprotocol (or `[]`
|
|
@@ -327,13 +341,13 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
327
341
|
* @remarks
|
|
328
342
|
* - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
|
|
329
343
|
* / {@link import('./HTTPClientTransport.js').HTTPClientTransport} (CLIENT-only
|
|
330
|
-
* carriers of `@
|
|
331
|
-
* plain duplex channel — the SAME class implements `@
|
|
344
|
+
* carriers of `@orkestrel/mcp`'s `MCPClientTransportInterface`), a `MessagePort` is a
|
|
345
|
+
* plain duplex channel — the SAME class implements `@orkestrel/mcp`'s
|
|
332
346
|
* `MCPTransportInterface` and is handed to EITHER `bindServer` or
|
|
333
347
|
* `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
|
|
334
348
|
* from the binder it is given to, not from anything this class decides.
|
|
335
349
|
* - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only
|
|
336
|
-
* REQUIRED when listening
|
|
350
|
+
* REQUIRED when listening with `addEventListener` (as opposed to the `onmessage`
|
|
337
351
|
* setter, which implies it) — this transport uses `addEventListener`, and
|
|
338
352
|
* `MCPTransportInterface` has no separate open/connect step for the caller to hook
|
|
339
353
|
* a start into, so the constructor calls `port.start()` immediately: the port
|
|
@@ -348,7 +362,7 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
348
362
|
* structured-clones it — a string clones to an identical string, so the wire stays
|
|
349
363
|
* plain JSON-RPC text like every other transport in this package). Inbound: a
|
|
350
364
|
* non-string `event.data` (a host or a misbehaving peer posting a structured
|
|
351
|
-
* object) is IGNORED — dropped silently, never forwarded, never thrown
|
|
365
|
+
* object) is IGNORED — dropped silently, never forwarded, never thrown —
|
|
352
366
|
* because `MCPTransportInterface` carries no `error` channel for this port to
|
|
353
367
|
* surface a non-string frame on (unlike `MCPClientTransportInterface`'s `emitter`);
|
|
354
368
|
* silently ignoring is the total, contract-shaped choice.
|
|
@@ -366,8 +380,8 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
366
380
|
* handler exactly once, whether the caller closes it once or twice. There is no
|
|
367
381
|
* native "peer closed" signal for a `MessagePort` (unlike a WebSocket's `close`
|
|
368
382
|
* event) — `closed` fires ONLY from this transport's own `close()`.
|
|
369
|
-
* - **Single-handler-replace (the port contract, `@
|
|
370
|
-
* doc).** `listen`/`closed` each hold the
|
|
383
|
+
* - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`
|
|
384
|
+
* doc).** `listen`/`closed` each hold the one active handler; a
|
|
371
385
|
* second call REPLACES the first rather than adding a second subscriber.
|
|
372
386
|
*
|
|
373
387
|
* @example
|
|
@@ -395,17 +409,17 @@ export declare class MessagePortTransport implements MCPTransportInterface_2 {
|
|
|
395
409
|
* {@link MessagePortTransport} sends and listens on.
|
|
396
410
|
*
|
|
397
411
|
* @remarks
|
|
398
|
-
* `port` — the channel half to drive (
|
|
412
|
+
* `port` — the channel half to drive (for example, one side of a `new MessageChannel()`, or
|
|
399
413
|
* the port a `message` event's `ports[0]` carried). REQUIRED. The SAME transport
|
|
400
414
|
* works as either a server or a client carrier — the role comes from whether it is
|
|
401
|
-
* handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@
|
|
415
|
+
* handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@orkestrel/mcp`).
|
|
402
416
|
*/
|
|
403
417
|
export declare interface MessagePortTransportOptions {
|
|
404
418
|
readonly port: MessagePort;
|
|
405
419
|
}
|
|
406
420
|
|
|
407
421
|
/**
|
|
408
|
-
*
|
|
422
|
+
* Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
|
|
409
423
|
* carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
|
|
410
424
|
*
|
|
411
425
|
* @remarks
|
|
@@ -413,8 +427,8 @@ export declare interface MessagePortTransportOptions {
|
|
|
413
427
|
* stream: true })` (handling a multi-byte char split across reads) and
|
|
414
428
|
* `@orkestrel/sse`'s {@link SSEParserInterface} (handling a partial line / in-progress
|
|
415
429
|
* event split across reads), then narrows each dispatched event's `data` to a
|
|
416
|
-
* {@link JSONRPCMessage}
|
|
417
|
-
* event is DROPPED, never thrown — total
|
|
430
|
+
* {@link JSONRPCMessage} through {@link decodeEvent} (so a non-message / non-JSON `data:`
|
|
431
|
+
* event is DROPPED, never thrown — total). A `null` body (no stream) yields no
|
|
418
432
|
* messages; {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
|
|
419
433
|
* reads a request/response SSE reply (the server sends one `data:` event then ends),
|
|
420
434
|
* so this drains to completion.
|
|
@@ -430,15 +444,19 @@ export declare function readEventStream(response: Response): Promise<readonly JS
|
|
|
430
444
|
* internal carrier `serveMCPScope` binds to route the implicit (portless) message
|
|
431
445
|
* channel, plus the `deliver` entry point the scope's own `message` listener pushes
|
|
432
446
|
* an inbound string through (the scope itself never registers `listen`'s handler
|
|
433
|
-
* for the caller — `serveMCPScope`'s dispatcher does,
|
|
447
|
+
* for the caller — `serveMCPScope`'s dispatcher does, through this `deliver`).
|
|
434
448
|
*/
|
|
435
449
|
export declare interface ScopeTransportInterface extends MCPTransportInterface {
|
|
436
|
-
/**
|
|
450
|
+
/** Pushes one inbound message string into the active `listen` handler. */
|
|
437
451
|
deliver(message: string): void;
|
|
438
452
|
}
|
|
439
453
|
|
|
440
454
|
/**
|
|
441
|
-
*
|
|
455
|
+
* Boots an `MCPServer` inside the current hostable worker scope.
|
|
456
|
+
*
|
|
457
|
+
* @remarks
|
|
458
|
+
* The served endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A
|
|
459
|
+
* dual-era worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
|
|
442
460
|
*
|
|
443
461
|
* @param options - The tools, optional identity, and optional port-event gate
|
|
444
462
|
* @returns The disposer returned by {@link serveMCPScope}
|
|
@@ -448,7 +466,7 @@ export declare function serveMCP(options: ServeMCPOptions): () => void;
|
|
|
448
466
|
/**
|
|
449
467
|
* Options for `serveMCP` / `serveMCPScope` — the live {@link ToolManagerInterface} to
|
|
450
468
|
* expose plus the optional server identity, mirroring `createMCPServer`'s
|
|
451
|
-
* `MCPServerOptions` (`@
|
|
469
|
+
* `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version` OPTIONAL (defaulting to
|
|
452
470
|
* {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
|
|
453
471
|
* {@link import('./constants.js').DEFAULT_MCP_SERVER_VERSION}).
|
|
454
472
|
*
|
|
@@ -459,7 +477,7 @@ export declare function serveMCP(options: ServeMCPOptions): () => void;
|
|
|
459
477
|
* deliver directly to the implicit scope channel (the tool executes, blind; in a
|
|
460
478
|
* Service Worker the reply is silently dropped — see `serveMCPScope`'s portless note).
|
|
461
479
|
* Prefer a handshake token in `event.data` as the primary pattern
|
|
462
|
-
* (
|
|
480
|
+
* (for example, `(event) => event.data === token`) — for same-origin worker/MessagePort
|
|
463
481
|
* messages `event.origin` is frequently the empty string, making origin
|
|
464
482
|
* allow-listing unreliable; origin checks are meaningful for cross-origin
|
|
465
483
|
* `postMessage` only. When omitted, ALL port-bearing events are accepted — every
|
|
@@ -474,13 +492,16 @@ export declare interface ServeMCPOptions {
|
|
|
474
492
|
}
|
|
475
493
|
|
|
476
494
|
/**
|
|
477
|
-
*
|
|
495
|
+
* Boots an `MCPServer` inside a hostable worker scope and wires its message events to it.
|
|
478
496
|
*
|
|
479
497
|
* @remarks
|
|
480
498
|
* Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
|
|
481
499
|
* their own `MessagePortTransport` binding. Portless string events use the scope's
|
|
482
500
|
* implicit channel. The returned disposer removes the listener, unbinds the implicit
|
|
483
|
-
* channel,
|
|
501
|
+
* channel, closes every accepted port binding, and drops the ports themselves — the
|
|
502
|
+
* bindings are held in one map keyed by port, so nothing survives the clear. The served
|
|
503
|
+
* endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
|
|
504
|
+
* worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
|
|
484
505
|
*
|
|
485
506
|
* @param scope - The hostable worker scope to wire
|
|
486
507
|
* @param options - The tools, optional identity, and optional port-event gate
|
|
@@ -493,7 +514,7 @@ export declare function serveMCPScope(scope: ServeMCPScopeInterface, options: Se
|
|
|
493
514
|
* dedicated Web Worker or a Service Worker (or any double matching this shape).
|
|
494
515
|
*
|
|
495
516
|
* @remarks
|
|
496
|
-
* Only the
|
|
517
|
+
* Only the members `serveMCPScope` actually touches: `postMessage` (the
|
|
497
518
|
* dedicated-worker implicit reply channel), and `addEventListener` /
|
|
498
519
|
* `removeEventListener` for `'message'` (every inbound event, portless or
|
|
499
520
|
* port-bearing, arrives through the SAME listener — see {@link ServeMCPOptions}'s
|
|
@@ -510,7 +531,7 @@ export declare interface ServeMCPScopeInterface {
|
|
|
510
531
|
* The browser-face WebSocket CLIENT transport for the Model Context Protocol — a
|
|
511
532
|
* {@link MCPClientTransportInterface} that drives a REMOTE MCP server over the native
|
|
512
533
|
* `WebSocket` global, the browser sibling of the Node face's
|
|
513
|
-
* {@link import('@
|
|
534
|
+
* {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
514
535
|
*
|
|
515
536
|
* @remarks
|
|
516
537
|
* - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and
|
|
@@ -525,15 +546,16 @@ export declare interface ServeMCPScopeInterface {
|
|
|
525
546
|
* - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and
|
|
526
547
|
* narrowed with `parseJSONRPCMessage` — a well-formed {@link JSONRPCMessage}
|
|
527
548
|
* re-emits on this transport's `message` event; a non-text (binary) frame or a
|
|
528
|
-
* non-JSON / non-message text frame surfaces on `error` and is DROPPED (
|
|
549
|
+
* non-JSON / non-message text frame surfaces on `error` and is DROPPED (never
|
|
529
550
|
* throws on adversarial wire input).
|
|
530
|
-
* - **`close()`**
|
|
531
|
-
* socket's native `close` event (a server-initiated close) fires the
|
|
532
|
-
* exactly once total — `close()` first flips the guard, so the native event
|
|
533
|
-
* double-emits
|
|
534
|
-
*
|
|
535
|
-
* `
|
|
536
|
-
*
|
|
551
|
+
* - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`
|
|
552
|
+
* (idempotent); the socket's native `close` event (a server-initiated close) fires the
|
|
553
|
+
* SAME `close` exactly once total — `close()` first flips the guard, so the native event
|
|
554
|
+
* never double-emits, and the released socket reports its own close to nobody. Closing before
|
|
555
|
+
* the socket opens resolves the pending `start()` rather than leaving it pending, matching the
|
|
556
|
+
* Node face. A `send` issued after `close()` is silently dropped (not queued), so a closed
|
|
557
|
+
* transport delivers nothing until a `start()` opens a new connection.
|
|
558
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every
|
|
537
559
|
* emit the emitter isolates a listener throw; `error` is a DOMAIN event (a
|
|
538
560
|
* transport-level fault).
|
|
539
561
|
*
|
|
@@ -564,8 +586,8 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
564
586
|
* the native `WebSocket` constructor the same way) URL of the remote server's
|
|
565
587
|
* WebSocket endpoint. REQUIRED.
|
|
566
588
|
* - `protocols` — the WebSocket subprotocol(s) to request. **Defaults to
|
|
567
|
-
* {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**,
|
|
568
|
-
* `createWebSocketServer`
|
|
589
|
+
* {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
|
|
590
|
+
* `createWebSocketServer` selects when the offer contains it. Per
|
|
569
591
|
* RFC 6455 §4.1 a client must fail the connection if the server returns a subprotocol
|
|
570
592
|
* it did not request; Node ≥ 22 (undici) enforces this strictly, so the default saves
|
|
571
593
|
* you from that trap when connecting to this repo's own server. Override only when
|