@orkestrel/mcp 0.0.27 → 0.0.28
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 +1 -1
- package/dist/src/browser/index.d.ts +135 -273
- package/dist/src/browser/index.js +128 -431
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +617 -200
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +881 -547
- package/dist/src/core/index.d.ts +881 -547
- package/dist/src/core/index.js +606 -200
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +275 -591
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +216 -343
- package/dist/src/server/index.d.ts +216 -343
- package/dist/src/server/index.js +269 -576
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -22
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ const client = createMCPClient({
|
|
|
57
57
|
})
|
|
58
58
|
await client.connect()
|
|
59
59
|
const tools = await client.tools()
|
|
60
|
-
const
|
|
60
|
+
const outcome = await client.call('add', { x: 2, y: 5 })
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
The SAME `MCPClient` drives a `createWebSocketClientTransport` or
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
+
import { HTTPClientTransportOptions } from '@orkestrel/mcp';
|
|
2
3
|
import { JSONRPCMessage } from '@orkestrel/mcp';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { MCPClientTransportInterface as MCPClientTransportInterface_2 } from '@orkestrel/mcp';
|
|
4
|
+
import { MCPMessageTransportEventMap } from '@orkestrel/mcp';
|
|
5
|
+
import { MCPMessageTransportInterface } from '@orkestrel/mcp';
|
|
6
|
+
import { MCPMessageTransportInterface as MCPMessageTransportInterface_2 } from '@orkestrel/mcp';
|
|
7
7
|
import { MCPServerInterface } from '@orkestrel/mcp';
|
|
8
8
|
import { MCPTransportInterface } from '@orkestrel/mcp';
|
|
9
9
|
import { MCPTransportInterface as MCPTransportInterface_2 } from '@orkestrel/mcp';
|
|
10
10
|
import { ToolManagerInterface } from '@orkestrel/tool';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Creates the
|
|
14
|
-
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link
|
|
15
|
-
* that drives a REMOTE Streamable-HTTP MCP server over the native `fetch
|
|
16
|
-
*
|
|
13
|
+
* Creates the HTTP CLIENT transport for an
|
|
14
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
15
|
+
* that drives a REMOTE Streamable-HTTP MCP server over the native `fetch`.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
|
|
19
|
+
* Node face's `createHTTPClientTransport` returns, because the class touches `fetch`,
|
|
20
|
+
* `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone. This factory exists so a
|
|
21
|
+
* page imports its transport from the face it already imports everything else from.
|
|
17
22
|
*
|
|
18
23
|
* @remarks
|
|
19
24
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
|
|
@@ -34,7 +39,7 @@ import { ToolManagerInterface } from '@orkestrel/tool';
|
|
|
34
39
|
* onto every request, optional `fetch` (default `globalThis.fetch`), and optional
|
|
35
40
|
* `timeout` (ms, applied with `AbortSignal.timeout`); see
|
|
36
41
|
* {@link HTTPClientTransportOptions}
|
|
37
|
-
* @returns A working {@link
|
|
42
|
+
* @returns A working {@link MCPMessageTransportInterface} over the native `fetch`
|
|
38
43
|
*
|
|
39
44
|
* @example
|
|
40
45
|
* ```ts
|
|
@@ -48,7 +53,7 @@ import { ToolManagerInterface } from '@orkestrel/tool';
|
|
|
48
53
|
* const tools = await client.tools()
|
|
49
54
|
* ```
|
|
50
55
|
*/
|
|
51
|
-
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions):
|
|
56
|
+
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface_2;
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* Creates the browser-face `MessagePort` transport — a
|
|
@@ -80,9 +85,9 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
|
|
|
80
85
|
export declare function createMessagePortTransport(options: MessagePortTransportOptions): MCPTransportInterface;
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
|
-
* Builds
|
|
84
|
-
*
|
|
85
|
-
*
|
|
88
|
+
* Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that
|
|
89
|
+
* routes EVERY inbound event on a hostable scope, portless or port-bearing, to the right
|
|
90
|
+
* binding.
|
|
86
91
|
*
|
|
87
92
|
* @remarks
|
|
88
93
|
* Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` FIRST
|
|
@@ -94,10 +99,10 @@ export declare function createMessagePortTransport(options: MessagePortTransport
|
|
|
94
99
|
* replies), so a repeat is silently dropped.
|
|
95
100
|
*
|
|
96
101
|
* 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
|
|
98
|
-
* would have to remember to empty both — so a long-lived scope such as a Service Worker
|
|
99
|
-
* retain every port it ever accepted, closed and unbound ones included. Membership
|
|
100
|
-
* "already bound?" and `clear()` drops the binding and the dedup together.
|
|
102
|
+
* seen-port set would be a second collection over the same lifetime, and the scope server's
|
|
103
|
+
* `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker
|
|
104
|
+
* would retain every port it ever accepted, closed and unbound ones included. Membership
|
|
105
|
+
* answers "already bound?" and `clear()` drops the binding and the dedup together.
|
|
101
106
|
*
|
|
102
107
|
* This branch fires on EITHER a Service-Worker-shaped scope (its normal per-client
|
|
103
108
|
* channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
|
|
@@ -108,8 +113,8 @@ export declare function createMessagePortTransport(options: MessagePortTransport
|
|
|
108
113
|
*
|
|
109
114
|
* @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over
|
|
110
115
|
* @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto
|
|
111
|
-
* @param teardowns - The shared teardown map
|
|
112
|
-
* @param options - The `
|
|
116
|
+
* @param teardowns - The shared teardown map the scope server's `stop` drains and clears, keyed by the accepted port; each port-bearing event adds one entry
|
|
117
|
+
* @param options - The `ScopeServerOptions` (for `options.accept`)
|
|
113
118
|
* @returns The `message`-event listener to register (and later remove) on the scope
|
|
114
119
|
*
|
|
115
120
|
* @example
|
|
@@ -121,26 +126,60 @@ export declare function createMessagePortTransport(options: MessagePortTransport
|
|
|
121
126
|
* scope.addEventListener('message', onMessage)
|
|
122
127
|
* ```
|
|
123
128
|
*/
|
|
124
|
-
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options:
|
|
129
|
+
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ScopeServerOptions): (event: MessageEvent) => void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates an `MCPServer` hosted inside a worker scope and wires that scope's message events
|
|
133
|
+
* to it — the browser face's bootstrap, and the twin of the Node face's `createStdioServer`.
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* `scope` defaults to `globalThis`, which is `self` inside a dedicated Web Worker or a
|
|
137
|
+
* Service Worker, so a worker boots with `createScopeServer({ tools })` alone; pass a scope
|
|
138
|
+
* explicitly to host a server on a double or on another message-event-bearing object.
|
|
139
|
+
*
|
|
140
|
+
* Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
|
|
141
|
+
* their own `MessagePortTransport` binding. Portless string events use the scope's
|
|
142
|
+
* implicit channel. The returned handle's `stop` removes the listener, unbinds the implicit
|
|
143
|
+
* channel, closes every accepted port binding, and drops the ports themselves — the
|
|
144
|
+
* bindings are held in one map keyed by port, so nothing survives the clear. The served
|
|
145
|
+
* endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
|
|
146
|
+
* worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this factory.
|
|
147
|
+
*
|
|
148
|
+
* @param options - The tools, optional identity, and optional port-event gate; see
|
|
149
|
+
* {@link ScopeServerOptions}
|
|
150
|
+
* @param scope - The hostable scope to wire; defaults to `globalThis`
|
|
151
|
+
* @returns A {@link ScopeServerInterface} whose `stop` ends every binding this call owns
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import { createScopeServer } from '@orkestrel/mcp/browser'
|
|
156
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
157
|
+
*
|
|
158
|
+
* // Inside a Web Worker: the scope defaults to `globalThis`.
|
|
159
|
+
* const worker = createScopeServer({ tools: createToolManager() })
|
|
160
|
+
* // ... later, release every binding this call owns:
|
|
161
|
+
* worker.stop()
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export declare function createScopeServer(options: ScopeServerOptions, scope?: ScopeInterface): ScopeServerInterface;
|
|
125
165
|
|
|
126
166
|
/**
|
|
127
|
-
* Adapts a hostable {@link
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* dedicated-worker shape.
|
|
167
|
+
* Adapts a hostable {@link ScopeInterface} (`self` in a dedicated Web Worker, or any
|
|
168
|
+
* structurally matching double) into a {@link ScopeTransportInterface} — the implicit,
|
|
169
|
+
* portless message channel {@link createScopeServer} binds for the dedicated-worker shape.
|
|
131
170
|
*
|
|
132
171
|
* @remarks
|
|
133
172
|
* `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
|
|
134
173
|
* register the SINGLE handler `deliver` / the underlying close path route through —
|
|
135
|
-
*
|
|
174
|
+
* the scope server's own `scope` `message`-event listener calls `deliver(event.data)`
|
|
136
175
|
* for every portless, string-payload event (there is no native registration point on
|
|
137
|
-
* the scope itself for
|
|
176
|
+
* the scope itself for the scope server to hand a `listen` handler to, so `deliver` is
|
|
138
177
|
* the bridge). `close()` fires the registered `closed` handler — a scope has nothing
|
|
139
178
|
* physically closable, so this is the only teardown signal available.
|
|
140
179
|
*
|
|
141
180
|
* @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`
|
|
142
181
|
* inside a dedicated Web Worker)
|
|
143
|
-
* @returns A {@link ScopeTransportInterface}
|
|
182
|
+
* @returns A {@link ScopeTransportInterface} the scope server binds and drives through `deliver`
|
|
144
183
|
*
|
|
145
184
|
* @example
|
|
146
185
|
* ```ts
|
|
@@ -148,11 +187,11 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
|
|
|
148
187
|
* const unbind = bindServer(server, scopeTransport)
|
|
149
188
|
* ```
|
|
150
189
|
*/
|
|
151
|
-
export declare function createScopeTransport(scope:
|
|
190
|
+
export declare function createScopeTransport(scope: ScopeInterface): ScopeTransportInterface;
|
|
152
191
|
|
|
153
192
|
/**
|
|
154
193
|
* Creates the browser-face WebSocket CLIENT transport for an
|
|
155
|
-
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link
|
|
194
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
156
195
|
* that drives a REMOTE MCP server over the native `WebSocket` global, the browser
|
|
157
196
|
* sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
158
197
|
*
|
|
@@ -167,7 +206,7 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
|
|
|
167
206
|
* @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional
|
|
168
207
|
* `protocols` (the WebSocket subprotocol(s) to request); see
|
|
169
208
|
* {@link WebSocketClientTransportOptions}
|
|
170
|
-
* @returns A working {@link
|
|
209
|
+
* @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`
|
|
171
210
|
*
|
|
172
211
|
* @example
|
|
173
212
|
* ```ts
|
|
@@ -181,168 +220,23 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
|
|
|
181
220
|
* const tools = await client.tools()
|
|
182
221
|
* ```
|
|
183
222
|
*/
|
|
184
|
-
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions):
|
|
223
|
+
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface_2;
|
|
185
224
|
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
* when it is not one — the per-event step {@link readEventStream} folds over.
|
|
189
|
-
*
|
|
190
|
-
* @remarks
|
|
191
|
-
* `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the
|
|
192
|
-
* event's `data`) inside a try/catch and narrows the parsed value with
|
|
193
|
-
* `parseJSONRPCMessage`. Total: malformed JSON or a non-message value yields
|
|
194
|
-
* `undefined`, never throws.
|
|
195
|
-
*
|
|
196
|
-
* @param data - One SSE event's `data` payload
|
|
197
|
-
* @returns The decoded {@link JSONRPCMessage}, or `undefined`
|
|
198
|
-
*/
|
|
199
|
-
export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
|
|
200
|
-
|
|
201
|
-
/** The default server name `serveMCPScope` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */
|
|
202
|
-
export declare const DEFAULT_MCP_SERVER_NAME = "taverna";
|
|
225
|
+
/** Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */
|
|
226
|
+
export declare const DEFAULT_MCP_SERVER_NAME = "@orkestrel/mcp";
|
|
203
227
|
|
|
204
|
-
/**
|
|
228
|
+
/** Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */
|
|
205
229
|
export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
206
230
|
|
|
207
231
|
/**
|
|
208
|
-
*
|
|
209
|
-
* {@link
|
|
210
|
-
*
|
|
211
|
-
* {@link import('@orkestrel/mcp/server').HTTPClientTransport}, honoring the SAME
|
|
212
|
-
* `mcp-session-id` semantics so it interoperates with an `MCPSession`-based server
|
|
213
|
-
* unchanged.
|
|
214
|
-
*
|
|
215
|
-
* @remarks
|
|
216
|
-
* - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
|
|
217
|
-
* message to `options.url` with `content-type: application/json` and an
|
|
218
|
-
* `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
|
|
219
|
-
* answer with either framing) — plus any `options.headers` (for example, an
|
|
220
|
-
* `Authorization` bearer). It then decodes the reply and emits each decoded
|
|
221
|
-
* {@link JSONRPCMessage} on the `message` event the
|
|
222
|
-
* {@link import('@orkestrel/mcp').MCPClientInterface} subscribes to.
|
|
223
|
-
* - **Both reply framings.** A `200` with an `application/json` body is parsed with
|
|
224
|
-
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
|
|
225
|
-
* `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} (the browser
|
|
226
|
-
* face's own `readEventStream`) — the inverse of the server's `openStream` seam, so
|
|
227
|
-
* the wire round-trips. A `202` Accepted (a notification) carries no body and emits
|
|
228
|
-
* nothing.
|
|
229
|
-
* - **Session and protocol headers.** `start()` is a no-op (a
|
|
230
|
-
* request/response transport opens no long-lived connection). The
|
|
231
|
-
* `mcp-session-id` response header, when a STATEFUL server sends one (on
|
|
232
|
-
* `initialize`), is captured into `session` and then ECHOED as the
|
|
233
|
-
* `mcp-session-id` request header on every SUBSEQUENT request — so an
|
|
234
|
-
* `MCPClient` passes a stateful server's session validation. The
|
|
235
|
-
* initialize result's `protocolVersion` is likewise captured, but only
|
|
236
|
-
* when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
|
|
237
|
-
* subsequent legacy requests. Modern requests instead derive protocol and method
|
|
238
|
-
* headers from the message, plus the name header only for `tools/call` — carried in the
|
|
239
|
-
* protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
|
|
240
|
-
* Before initialize returns, neither captured legacy header is sent.
|
|
241
|
-
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
242
|
-
* POST is headerless; the captured `session` persists across `close()`.
|
|
243
|
-
* - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
|
|
244
|
-
* ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
|
|
245
|
-
* never ends would otherwise outlive the transport, with nothing left able to reach it. The
|
|
246
|
-
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
247
|
-
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
248
|
-
* - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
249
|
-
* the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
|
|
250
|
-
* decode failure surfaces on the `error` event rather than escaping `send`.
|
|
251
|
-
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
|
|
252
|
-
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
253
|
-
*
|
|
254
|
-
* @example
|
|
255
|
-
* ```ts
|
|
256
|
-
* const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
|
|
257
|
-
* const client = new MCPClient({ transport })
|
|
258
|
-
* await client.connect()
|
|
259
|
-
* ```
|
|
260
|
-
*/
|
|
261
|
-
export declare class HTTPClientTransport implements MCPClientTransportInterface {
|
|
262
|
-
#private;
|
|
263
|
-
constructor(options: HTTPClientTransportOptions);
|
|
264
|
-
get emitter(): EmitterInterface<MCPClientTransportEventMap>;
|
|
265
|
-
get session(): string | undefined;
|
|
266
|
-
get duplex(): boolean;
|
|
267
|
-
start(): Promise<void>;
|
|
268
|
-
send(message: JSONRPCMessage_2): Promise<void>;
|
|
269
|
-
close(): Promise<void>;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Options for `createHTTPClientTransport` (browser face) — the remote MCP server's
|
|
274
|
-
* URL and any extra request headers.
|
|
275
|
-
*
|
|
276
|
-
* @remarks
|
|
277
|
-
* - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
|
|
278
|
-
* `POST` target every JSON-RPC message is written to). REQUIRED.
|
|
279
|
-
* - `headers` — extra request headers merged onto every `POST` (for example, an
|
|
280
|
-
* `Authorization` bearer for a guarded server). The transport always sets
|
|
281
|
-
* `content-type: application/json` and an `Accept` of both `application/json` and
|
|
282
|
-
* `text/event-stream`; a key supplied here is merged on top.
|
|
283
|
-
* - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
|
|
284
|
-
* `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
|
|
285
|
-
* - `timeout` — an optional per-request timeout in milliseconds; when set, each
|
|
286
|
-
* `fetch` call composes that deadline with the transport's own close through
|
|
287
|
-
* `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
|
|
288
|
-
* ends the request. Omit for no transport-level deadline; the close signal is passed
|
|
289
|
-
* either way.
|
|
290
|
-
*/
|
|
291
|
-
export declare interface HTTPClientTransportOptions {
|
|
292
|
-
readonly url: string;
|
|
293
|
-
readonly headers?: Readonly<Record<string, string>>;
|
|
294
|
-
readonly fetch?: typeof fetch;
|
|
295
|
-
readonly timeout?: number;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* The modern Streamable-HTTP request header carrying the JSON-RPC method. It is
|
|
300
|
-
* emitted on every modern request and never on a legacy request.
|
|
301
|
-
*/
|
|
302
|
-
export declare const MCP_METHOD_HEADER = "mcp-method";
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* The modern Streamable-HTTP request header carrying a named target. The browser
|
|
306
|
-
* HTTP client emits it only for `tools/call`, from that request's `params.name`.
|
|
307
|
-
*/
|
|
308
|
-
export declare const MCP_NAME_HEADER = "mcp-name";
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* The Streamable-HTTP transport header carrying the MCP protocol version. Modern
|
|
312
|
-
* requests derive it from their own `_meta`; legacy requests echo the negotiated
|
|
313
|
-
* initialize result on each subsequent request.
|
|
314
|
-
*/
|
|
315
|
-
export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* The Streamable-HTTP transport header that carries the MCP session id. The browser
|
|
319
|
-
* face's {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
|
|
320
|
-
* ECHOES this header exactly like the Node face's `HTTPClientTransport`
|
|
321
|
-
* (`src/server`), so the same client interoperates with an `MCPSession`-based
|
|
322
|
-
* server unchanged.
|
|
323
|
-
*/
|
|
324
|
-
export declare const MCP_SESSION_HEADER = "mcp-session-id";
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* The WebSocket subprotocol `createWebSocketClientTransport` requests by default —
|
|
328
|
-
* `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
|
|
329
|
-
* §4.1 a client MUST fail the connection if the server returns
|
|
330
|
-
* a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
|
|
331
|
-
* default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
|
|
332
|
-
* only when connecting to a foreign server that speaks a different subprotocol (or `[]`
|
|
333
|
-
* for no subprotocol negotiation at all).
|
|
334
|
-
*/
|
|
335
|
-
export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* The browser-face `MessagePort` transport for the Model Context Protocol — a
|
|
339
|
-
* {@link MCPTransportInterface} over a native `MessagePort`, the genuinely new
|
|
340
|
-
* capability this face adds: MCP over `postMessage`.
|
|
232
|
+
* Carries the Model Context Protocol over a native `MessagePort` from the browser face — a
|
|
233
|
+
* {@link MCPTransportInterface}, the genuinely new capability this face adds: MCP over
|
|
234
|
+
* `postMessage`.
|
|
341
235
|
*
|
|
342
236
|
* @remarks
|
|
343
237
|
* - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
|
|
344
|
-
* / {@link import('
|
|
345
|
-
* carriers of `@orkestrel/mcp`'s `
|
|
238
|
+
* / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only
|
|
239
|
+
* carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a
|
|
346
240
|
* plain duplex channel — the SAME class implements `@orkestrel/mcp`'s
|
|
347
241
|
* `MCPTransportInterface` and is handed to EITHER `bindServer` or
|
|
348
242
|
* `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
|
|
@@ -353,7 +247,7 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
353
247
|
* `MCPTransportInterface` has no separate open/connect step for the caller to hook
|
|
354
248
|
* a start into, so the constructor calls `port.start()` immediately: the port
|
|
355
249
|
* begins dispatching QUEUED messages the moment the transport exists. This is safe
|
|
356
|
-
* inside `
|
|
250
|
+
* inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`
|
|
357
251
|
* before control returns to the event loop), but is a **footgun for direct use**:
|
|
358
252
|
* if you construct `new MessagePortTransport({ port })` and then `await` anything
|
|
359
253
|
* before calling `listen`, messages that arrived in the gap are DROPPED. **Bind
|
|
@@ -365,16 +259,17 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
365
259
|
* non-string `event.data` (a host or a misbehaving peer posting a structured
|
|
366
260
|
* object) is IGNORED — dropped silently, never forwarded, never thrown —
|
|
367
261
|
* because `MCPTransportInterface` carries no `error` channel for this port to
|
|
368
|
-
* surface a non-string frame on (unlike `
|
|
262
|
+
* surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);
|
|
369
263
|
* silently ignoring is the total, contract-shaped choice.
|
|
370
264
|
* - **`messageerror` is IGNORED, not routed to `closed`.** A `messageerror` event
|
|
371
265
|
* (the structured-clone deserialization of an inbound message threw) reports one
|
|
372
266
|
* BAD FRAME, not a dead channel — the port itself keeps working and later, well-
|
|
373
|
-
* formed messages still arrive.
|
|
267
|
+
* formed messages still arrive. This transport registers no listener for it: an
|
|
268
|
+
* unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor
|
|
269
|
+
* reaches this transport, so one bad frame costs exactly that frame and nothing
|
|
270
|
+
* tears the binding down. Routing it to `closed` would tear down the
|
|
374
271
|
* `bindServer`/`bindClient` wiring (and, transitively, every session it carries)
|
|
375
|
-
* over a single malformed frame
|
|
376
|
-
* one frame — so this transport registers a `messageerror` listener that does
|
|
377
|
-
* nothing, deliberately.
|
|
272
|
+
* over a single malformed frame.
|
|
378
273
|
* - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`
|
|
379
274
|
* disconnects it — further `postMessage` calls on EITHER end are silently
|
|
380
275
|
* undelivered, per the platform contract) and fires the registered `closed`
|
|
@@ -420,55 +315,46 @@ export declare interface MessagePortTransportOptions {
|
|
|
420
315
|
}
|
|
421
316
|
|
|
422
317
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
318
|
+
* Describes the structural shape {@link import('./factories.js').createScopeServer} needs
|
|
319
|
+
* from a
|
|
320
|
+
* hostable scope — `self` in a dedicated Web Worker or a Service Worker (or any double
|
|
321
|
+
* matching this shape).
|
|
425
322
|
*
|
|
426
323
|
* @remarks
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* messages; {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
|
|
434
|
-
* reads a request/response SSE reply (the server sends one `data:` event then ends),
|
|
435
|
-
* so this drains to completion.
|
|
436
|
-
*
|
|
437
|
-
* @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
|
|
438
|
-
* @returns Every {@link JSONRPCMessage} the stream carried, in order
|
|
439
|
-
*/
|
|
440
|
-
export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* A duplex {@link MCPTransportInterface} adapting a message-event-bearing SCOPE
|
|
444
|
-
* (`self` in a dedicated Web Worker, or any object shaped the same way) — the
|
|
445
|
-
* internal carrier `serveMCPScope` binds to route the implicit (portless) message
|
|
446
|
-
* channel, plus the `deliver` entry point the scope's own `message` listener pushes
|
|
447
|
-
* an inbound string through (the scope itself never registers `listen`'s handler
|
|
448
|
-
* for the caller — `serveMCPScope`'s dispatcher does, through this `deliver`).
|
|
324
|
+
* Only the members the scope server actually touches: `postMessage` (the
|
|
325
|
+
* dedicated-worker implicit reply channel), and `addEventListener` /
|
|
326
|
+
* `removeEventListener` for `'message'` (every inbound event, portless or
|
|
327
|
+
* port-bearing, arrives through the SAME listener — see {@link ScopeServerOptions}'s
|
|
328
|
+
* doc and the factory). A real `self` / `globalThis` inside a worker satisfies this
|
|
329
|
+
* structurally (it exposes far more, which this narrower shape ignores).
|
|
449
330
|
*/
|
|
450
|
-
export declare interface
|
|
451
|
-
|
|
452
|
-
|
|
331
|
+
export declare interface ScopeInterface {
|
|
332
|
+
postMessage(message: unknown): void;
|
|
333
|
+
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
334
|
+
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
453
335
|
}
|
|
454
336
|
|
|
455
337
|
/**
|
|
456
|
-
*
|
|
338
|
+
* Represents one MCP server hosted inside a worker scope — what
|
|
339
|
+
* {@link import('./factories.js').createScopeServer} returns.
|
|
457
340
|
*
|
|
458
341
|
* @remarks
|
|
459
|
-
* The
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
342
|
+
* The browser twin of the Node face's `StdioServerInterface`, and it publishes only the
|
|
343
|
+
* terminal: the factory arms the scope's `message` listener before it returns, because an
|
|
344
|
+
* event delivered between construction and an explicit `start` would reach nothing. `stop`
|
|
345
|
+
* removes that listener, unbinds the implicit scope channel, and tears down every accepted
|
|
346
|
+
* port binding; it is idempotent, and it ends this handle's lifetime permanently.
|
|
464
347
|
*/
|
|
465
|
-
export declare
|
|
348
|
+
export declare interface ScopeServerInterface {
|
|
349
|
+
/** Ends every binding this scope server owns — idempotent, and permanent for this handle. */
|
|
350
|
+
stop(): void;
|
|
351
|
+
}
|
|
466
352
|
|
|
467
353
|
/**
|
|
468
|
-
* Options for
|
|
469
|
-
* expose plus the optional server identity, mirroring
|
|
470
|
-
* `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version`
|
|
471
|
-
* {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
|
|
354
|
+
* Options for {@link import('./factories.js').createScopeServer} — the live
|
|
355
|
+
* {@link ToolManagerInterface} to expose plus the optional server identity, mirroring
|
|
356
|
+
* `createMCPServer`'s `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version`
|
|
357
|
+
* OPTIONAL (defaulting to {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
|
|
472
358
|
* {@link import('./constants.js').DEFAULT_MCP_SERVER_VERSION}).
|
|
473
359
|
*
|
|
474
360
|
* @remarks
|
|
@@ -476,16 +362,16 @@ export declare function serveMCP(options: ServeMCPOptions): () => void;
|
|
|
476
362
|
* event is accepted; return `false` to drop the event (no binding, no reply).
|
|
477
363
|
* **`accept` gates ONLY port-bearing events** — portless messages bypass it and
|
|
478
364
|
* deliver directly to the implicit scope channel (the tool executes, blind; in a
|
|
479
|
-
* Service Worker the reply is silently dropped — see `
|
|
365
|
+
* Service Worker the reply is silently dropped — see `createScopeServer`'s portless note).
|
|
480
366
|
* Prefer a handshake token in `event.data` as the primary pattern
|
|
481
367
|
* (for example, `(event) => event.data === token`) — for same-origin worker/MessagePort
|
|
482
368
|
* messages `event.origin` is frequently the empty string, making origin
|
|
483
369
|
* allow-listing unreliable; origin checks are meaningful for cross-origin
|
|
484
370
|
* `postMessage` only. When omitted, ALL port-bearing events are accepted — every
|
|
485
371
|
* same-origin context that can reach the scope gets full tool-call access.
|
|
486
|
-
* See `
|
|
372
|
+
* See `createScopeServer`'s trust-boundary and portless-events notes.
|
|
487
373
|
*/
|
|
488
|
-
export declare interface
|
|
374
|
+
export declare interface ScopeServerOptions {
|
|
489
375
|
readonly tools: ToolManagerInterface;
|
|
490
376
|
readonly name?: string;
|
|
491
377
|
readonly version?: string;
|
|
@@ -493,45 +379,21 @@ export declare interface ServeMCPOptions {
|
|
|
493
379
|
}
|
|
494
380
|
|
|
495
381
|
/**
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* channel, closes every accepted port binding, and drops the ports themselves — the
|
|
503
|
-
* bindings are held in one map keyed by port, so nothing survives the clear. The served
|
|
504
|
-
* endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
|
|
505
|
-
* worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
|
|
506
|
-
*
|
|
507
|
-
* @param scope - The hostable worker scope to wire
|
|
508
|
-
* @param options - The tools, optional identity, and optional port-event gate
|
|
509
|
-
* @returns An idempotent disposer for every binding owned by this call
|
|
510
|
-
*/
|
|
511
|
-
export declare function serveMCPScope(scope: ServeMCPScopeInterface, options: ServeMCPOptions): () => void;
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* The structural shape `serveMCPScope` needs from a hostable scope — `self` in a
|
|
515
|
-
* dedicated Web Worker or a Service Worker (or any double matching this shape).
|
|
516
|
-
*
|
|
517
|
-
* @remarks
|
|
518
|
-
* Only the members `serveMCPScope` actually touches: `postMessage` (the
|
|
519
|
-
* dedicated-worker implicit reply channel), and `addEventListener` /
|
|
520
|
-
* `removeEventListener` for `'message'` (every inbound event, portless or
|
|
521
|
-
* port-bearing, arrives through the SAME listener — see {@link ServeMCPOptions}'s
|
|
522
|
-
* doc and the bootstrap factories). A real `self` / `globalThis` inside a worker satisfies this
|
|
523
|
-
* structurally (it exposes far more, which this narrower shape ignores).
|
|
382
|
+
* Adapts a message-event-bearing SCOPE (`self` in a dedicated Web Worker, or any object
|
|
383
|
+
* shaped the same way) as a duplex {@link MCPTransportInterface} — the
|
|
384
|
+
* internal carrier `createScopeServer` binds to route the implicit (portless) message
|
|
385
|
+
* channel, plus the `deliver` entry point the scope's own `message` listener pushes
|
|
386
|
+
* an inbound string through (the scope itself never registers `listen`'s handler
|
|
387
|
+
* for the caller — the scope server's dispatcher does, through this `deliver`).
|
|
524
388
|
*/
|
|
525
|
-
export declare interface
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
389
|
+
export declare interface ScopeTransportInterface extends MCPTransportInterface {
|
|
390
|
+
/** Pushes one inbound message string into the active `listen` handler. */
|
|
391
|
+
deliver(message: string): void;
|
|
529
392
|
}
|
|
530
393
|
|
|
531
394
|
/**
|
|
532
|
-
*
|
|
533
|
-
* {@link
|
|
534
|
-
* `WebSocket` global, the browser sibling of the Node face's
|
|
395
|
+
* Drives a REMOTE MCP server over the native `WebSocket` global from the browser face — a
|
|
396
|
+
* CLIENT {@link MCPMessageTransportInterface}, the browser sibling of the Node face's
|
|
535
397
|
* {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
536
398
|
*
|
|
537
399
|
* @remarks
|
|
@@ -550,8 +412,8 @@ export declare interface ServeMCPScopeInterface {
|
|
|
550
412
|
* reporting `CLOSING` / `CLOSED`, REJECTS with `WebSocket transport is not connected` rather
|
|
551
413
|
* than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`
|
|
552
414
|
* still queues.
|
|
553
|
-
* - **Inbound (`message`).** Each decoded text frame
|
|
554
|
-
*
|
|
415
|
+
* - **Inbound (`message`).** Each decoded text frame runs through the shared
|
|
416
|
+
* `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}
|
|
555
417
|
* re-emits on this transport's `message` event; a non-text (binary) frame or a
|
|
556
418
|
* non-JSON / non-message text frame surfaces on `error` and is DROPPED (never
|
|
557
419
|
* throws on adversarial wire input).
|
|
@@ -564,7 +426,7 @@ export declare interface ServeMCPScopeInterface {
|
|
|
564
426
|
* pre-open queue is DISCARDED — by `close()` and by the native `close` event alike — so a
|
|
565
427
|
* closed transport delivers nothing until a `start()` opens a new connection, and nothing
|
|
566
428
|
* the caller handed the abandoned connection rides that one.
|
|
567
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
429
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every
|
|
568
430
|
* emit the emitter isolates a listener throw; `error` is a DOMAIN event (a
|
|
569
431
|
* transport-level fault).
|
|
570
432
|
*
|
|
@@ -575,14 +437,14 @@ export declare interface ServeMCPScopeInterface {
|
|
|
575
437
|
* await client.connect() // the browser handshakes, then the MCP initialize runs over WS frames
|
|
576
438
|
* ```
|
|
577
439
|
*/
|
|
578
|
-
export declare class WebSocketClientTransport implements
|
|
440
|
+
export declare class WebSocketClientTransport implements MCPMessageTransportInterface {
|
|
579
441
|
#private;
|
|
580
442
|
constructor(options: WebSocketClientTransportOptions);
|
|
581
|
-
get emitter(): EmitterInterface<
|
|
443
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
582
444
|
get session(): string | undefined;
|
|
583
445
|
get duplex(): boolean;
|
|
584
446
|
start(): Promise<void>;
|
|
585
|
-
send(message:
|
|
447
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
586
448
|
close(): Promise<void>;
|
|
587
449
|
}
|
|
588
450
|
|
|
@@ -595,7 +457,7 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
595
457
|
* the native `WebSocket` constructor the same way) URL of the remote server's
|
|
596
458
|
* WebSocket endpoint. REQUIRED.
|
|
597
459
|
* - `protocols` — the WebSocket subprotocol(s) to request. **Defaults to
|
|
598
|
-
* {@link import('
|
|
460
|
+
* {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
|
|
599
461
|
* `createWebSocketServer` selects when the offer contains it. Per
|
|
600
462
|
* RFC 6455 §4.1 a client must fail the connection if the server returns a subprotocol
|
|
601
463
|
* it did not request; Node ≥ 22 (undici) enforces this strictly, so the default saves
|