@orkestrel/mcp 0.0.27 → 0.0.29
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 +12 -15
- package/dist/src/browser/index.d.ts +184 -324
- package/dist/src/browser/index.js +166 -469
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +826 -352
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1265 -855
- package/dist/src/core/index.d.ts +1265 -855
- package/dist/src/core/index.js +815 -352
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +364 -680
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +395 -516
- package/dist/src/server/index.d.ts +395 -516
- package/dist/src/server/index.js +358 -665
- package/dist/src/server/index.js.map +1 -1
- package/package.json +26 -27
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
import { EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
-
import {
|
|
3
|
-
import { JSONRPCMessage
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { MCPTransportInterface as MCPTransportInterface_2 } from '@orkestrel/mcp';
|
|
10
|
-
import { ToolManagerInterface } from '@orkestrel/tool';
|
|
1
|
+
import type { EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
+
import type { HTTPClientTransportOptions } from '@orkestrel/mcp';
|
|
3
|
+
import type { JSONRPCMessage } from '@orkestrel/mcp';
|
|
4
|
+
import type { MCPMessageTransportEventMap } from '@orkestrel/mcp';
|
|
5
|
+
import type { MCPMessageTransportInterface } from '@orkestrel/mcp';
|
|
6
|
+
import type { MCPServerInterface } from '@orkestrel/mcp';
|
|
7
|
+
import type { MCPTransportInterface } from '@orkestrel/mcp';
|
|
8
|
+
import type { ToolManagerInterface } from '@orkestrel/tool';
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
|
-
* Creates the
|
|
14
|
-
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link
|
|
15
|
-
* that drives a
|
|
16
|
-
*
|
|
11
|
+
* Creates the HTTP client transport for an
|
|
12
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
13
|
+
* that drives a remote Streamable-HTTP MCP server over the native `fetch`.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
|
|
17
|
+
* Node face's `createHTTPClientTransport` returns, because the class touches `fetch`,
|
|
18
|
+
* `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone. This factory exists so a
|
|
19
|
+
* page imports its transport from the face it already imports everything else from.
|
|
17
20
|
*
|
|
18
21
|
* @remarks
|
|
19
22
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
|
|
20
23
|
* sends is `POST`ed to `options.url` with `content-type: application/json` and an
|
|
21
24
|
* `Accept` of both `application/json` and `text/event-stream` (the server answers
|
|
22
|
-
* with
|
|
25
|
+
* with either — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
|
|
23
26
|
* decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's
|
|
24
27
|
* `message` event for the client's id correlation. Add `options.headers` (for example, an
|
|
25
28
|
* `Authorization` bearer) to reach a guarded server. `start` / `close` hold no
|
|
26
|
-
* connection; against a
|
|
29
|
+
* connection; against a stateful server it captures the `mcp-session-id` from
|
|
27
30
|
* `initialize` and echoes it on later requests. It also captures the initialize
|
|
28
31
|
* result's `protocolVersion` and sends `mcp-protocol-version` alone on subsequent
|
|
29
32
|
* legacy requests. Modern requests instead derive `mcp-protocol-version` and
|
|
30
33
|
* `mcp-method` from the message, plus `mcp-name` only for `tools/call`, so the
|
|
31
34
|
* same `MCPClient` passes either era's protocol gates without caller wiring.
|
|
32
35
|
*
|
|
33
|
-
* @param options - `url` (the remote endpoint;
|
|
36
|
+
* @param options - `url` (the remote endpoint; required), optional `headers` merged
|
|
34
37
|
* onto every request, optional `fetch` (default `globalThis.fetch`), and optional
|
|
35
38
|
* `timeout` (ms, applied with `AbortSignal.timeout`); see
|
|
36
39
|
* {@link HTTPClientTransportOptions}
|
|
37
|
-
* @returns A working {@link
|
|
40
|
+
* @returns A working {@link MCPMessageTransportInterface} over the native `fetch`
|
|
38
41
|
*
|
|
39
42
|
* @example
|
|
40
43
|
* ```ts
|
|
@@ -48,12 +51,12 @@ import { ToolManagerInterface } from '@orkestrel/tool';
|
|
|
48
51
|
* const tools = await client.tools()
|
|
49
52
|
* ```
|
|
50
53
|
*/
|
|
51
|
-
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions):
|
|
54
|
+
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface;
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Creates the browser-face `MessagePort` transport — a
|
|
55
58
|
* {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the
|
|
56
|
-
*
|
|
59
|
+
* symmetric carrier that works as either a server or a client transport depending on
|
|
57
60
|
* which binder ({@link import('@orkestrel/mcp').bindServer} or
|
|
58
61
|
* {@link import('@orkestrel/mcp').bindClient}) it is handed to.
|
|
59
62
|
*
|
|
@@ -63,7 +66,7 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
|
|
|
63
66
|
* dropped, never thrown); `messageerror` is ignored (one bad frame does not close the
|
|
64
67
|
* channel); `close()` closes the port and fires `closed` exactly once.
|
|
65
68
|
*
|
|
66
|
-
* @param options - `port` (the `MessagePort` half to drive;
|
|
69
|
+
* @param options - `port` (the `MessagePort` half to drive; required); see
|
|
67
70
|
* {@link MessagePortTransportOptions}
|
|
68
71
|
* @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port
|
|
69
72
|
*
|
|
@@ -80,36 +83,36 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
|
|
|
80
83
|
export declare function createMessagePortTransport(options: MessagePortTransportOptions): MCPTransportInterface;
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
|
-
* Builds
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
+
* Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that
|
|
87
|
+
* routes every inbound event on a hostable scope, portless or port-bearing, to the right
|
|
88
|
+
* binding.
|
|
86
89
|
*
|
|
87
90
|
* @remarks
|
|
88
|
-
* Port-bearing events (`event.ports.length > 0`) are gated by `options.accept`
|
|
91
|
+
* Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` first
|
|
89
92
|
* — when the gate returns `false` the event is dropped entirely (no binding, no reply).
|
|
90
93
|
* Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,
|
|
91
94
|
* `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)
|
|
92
|
-
* into `teardowns`
|
|
95
|
+
* into `teardowns` keyed by that port. A port already present is ignored — repeated delivery
|
|
93
96
|
* of the same `MessagePort` would create duplicate bindings over one port (→ duplicated
|
|
94
97
|
* replies), so a repeat is silently dropped.
|
|
95
98
|
*
|
|
96
|
-
* The key is what makes `teardowns` the
|
|
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.
|
|
99
|
+
* The key is what makes `teardowns` the only place an accepted port is remembered. A separate
|
|
100
|
+
* seen-port set would be a second collection over the same lifetime, and the scope server's
|
|
101
|
+
* `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker
|
|
102
|
+
* would retain every port it ever accepted, closed and unbound ones included. Membership
|
|
103
|
+
* answers "already bound?" and `clear()` drops the binding and the dedup together.
|
|
101
104
|
*
|
|
102
|
-
* This branch fires on
|
|
105
|
+
* This branch fires on either a Service-Worker-shaped scope (its normal per-client
|
|
103
106
|
* channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
|
|
104
107
|
* (the unified design's deliberate cross-case, needing no upfront shape flag). An event
|
|
105
|
-
* with
|
|
108
|
+
* with no ports and a string `data` is pushed onto `scopeTransport.deliver` (the
|
|
106
109
|
* implicit, already-bound scope channel); any other event (no ports, non-string data)
|
|
107
110
|
* is silently dropped — total, never throws.
|
|
108
111
|
*
|
|
109
112
|
* @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over
|
|
110
113
|
* @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto
|
|
111
|
-
* @param teardowns - The shared teardown map
|
|
112
|
-
* @param options - The `
|
|
114
|
+
* @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
|
|
115
|
+
* @param options - The `ScopeServerOptions` (for `options.accept`)
|
|
113
116
|
* @returns The `message`-event listener to register (and later remove) on the scope
|
|
114
117
|
*
|
|
115
118
|
* @example
|
|
@@ -121,26 +124,60 @@ export declare function createMessagePortTransport(options: MessagePortTransport
|
|
|
121
124
|
* scope.addEventListener('message', onMessage)
|
|
122
125
|
* ```
|
|
123
126
|
*/
|
|
124
|
-
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options:
|
|
127
|
+
export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ScopeServerOptions): (event: MessageEvent) => void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates an `MCPServer` hosted inside a worker scope and wires that scope's message events
|
|
131
|
+
* to it — the browser face's bootstrap, and the twin of the Node face's `createStdioServer`.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* `scope` defaults to `globalThis`, which is `self` inside a dedicated Web Worker or a
|
|
135
|
+
* Service Worker, so a worker boots with `createScopeServer({ tools })` alone; pass a scope
|
|
136
|
+
* explicitly to host a server on a double or on another message-event-bearing object.
|
|
137
|
+
*
|
|
138
|
+
* Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
|
|
139
|
+
* their own `MessagePortTransport` binding. Portless string events use the scope's
|
|
140
|
+
* implicit channel. The returned handle's `stop` removes the listener, unbinds the implicit
|
|
141
|
+
* channel, closes every accepted port binding, and drops the ports themselves — the
|
|
142
|
+
* bindings are held in one map keyed by port, so nothing survives the clear. The served
|
|
143
|
+
* endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
|
|
144
|
+
* worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this factory.
|
|
145
|
+
*
|
|
146
|
+
* @param options - The tools, optional identity, and optional port-event gate; see
|
|
147
|
+
* {@link ScopeServerOptions}
|
|
148
|
+
* @param scope - The hostable scope to wire; defaults to `globalThis`
|
|
149
|
+
* @returns A {@link ScopeServerInterface} whose `stop` ends every binding this call owns
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { createScopeServer } from '@orkestrel/mcp/browser'
|
|
154
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
155
|
+
*
|
|
156
|
+
* // Inside a Web Worker: the scope defaults to `globalThis`.
|
|
157
|
+
* const worker = createScopeServer({ tools: createToolManager() })
|
|
158
|
+
* // ... later, release every binding this call owns:
|
|
159
|
+
* worker.stop()
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export declare function createScopeServer(options: ScopeServerOptions, scope?: ScopeInterface): ScopeServerInterface;
|
|
125
163
|
|
|
126
164
|
/**
|
|
127
|
-
* Adapts a hostable {@link
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* dedicated-worker shape.
|
|
165
|
+
* Adapts a hostable {@link ScopeInterface} (`self` in a dedicated Web Worker, or any
|
|
166
|
+
* structurally matching double) into a {@link ScopeTransportInterface} — the implicit,
|
|
167
|
+
* portless message channel {@link createScopeServer} binds for the dedicated-worker shape.
|
|
131
168
|
*
|
|
132
169
|
* @remarks
|
|
133
170
|
* `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
|
|
134
|
-
* register the
|
|
135
|
-
*
|
|
171
|
+
* register the single handler `deliver` / the underlying close path route through —
|
|
172
|
+
* the scope server's own `scope` `message`-event listener calls `deliver(event.data)`
|
|
136
173
|
* for every portless, string-payload event (there is no native registration point on
|
|
137
|
-
* the scope itself for
|
|
174
|
+
* the scope itself for the scope server to hand a `listen` handler to, so `deliver` is
|
|
138
175
|
* the bridge). `close()` fires the registered `closed` handler — a scope has nothing
|
|
139
176
|
* physically closable, so this is the only teardown signal available.
|
|
140
177
|
*
|
|
141
178
|
* @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`
|
|
142
179
|
* inside a dedicated Web Worker)
|
|
143
|
-
* @returns A {@link ScopeTransportInterface}
|
|
180
|
+
* @returns A {@link ScopeTransportInterface} the scope server binds and drives through `deliver`
|
|
144
181
|
*
|
|
145
182
|
* @example
|
|
146
183
|
* ```ts
|
|
@@ -148,26 +185,26 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
|
|
|
148
185
|
* const unbind = bindServer(server, scopeTransport)
|
|
149
186
|
* ```
|
|
150
187
|
*/
|
|
151
|
-
export declare function createScopeTransport(scope:
|
|
188
|
+
export declare function createScopeTransport(scope: ScopeInterface): ScopeTransportInterface;
|
|
152
189
|
|
|
153
190
|
/**
|
|
154
|
-
* Creates the browser-face WebSocket
|
|
155
|
-
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link
|
|
156
|
-
* that drives a
|
|
157
|
-
* sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
191
|
+
* Creates the browser-face WebSocket client transport for an
|
|
192
|
+
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
193
|
+
* that drives a remote MCP server over the native `WebSocket` global. This factory is the
|
|
194
|
+
* browser sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
158
195
|
*
|
|
159
196
|
* @remarks
|
|
160
197
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
161
198
|
* opens `new WebSocket(options.url, options.protocols)` and awaits the native
|
|
162
199
|
* `'open'` event — the RFC 6455 handshake itself is the browser's concern. Each
|
|
163
|
-
* JSON-RPC message the client `send`s before the socket opens is
|
|
200
|
+
* JSON-RPC message the client `send`s before the socket opens is queued and flushed,
|
|
164
201
|
* in order, once it does; each decoded reply is surfaced on the transport's
|
|
165
202
|
* `message` event for the client's id correlation.
|
|
166
203
|
*
|
|
167
|
-
* @param options - `url` (the remote WebSocket endpoint;
|
|
204
|
+
* @param options - `url` (the remote WebSocket endpoint; required) and optional
|
|
168
205
|
* `protocols` (the WebSocket subprotocol(s) to request); see
|
|
169
206
|
* {@link WebSocketClientTransportOptions}
|
|
170
|
-
* @returns A working {@link
|
|
207
|
+
* @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`
|
|
171
208
|
*
|
|
172
209
|
* @example
|
|
173
210
|
* ```ts
|
|
@@ -181,209 +218,65 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
|
|
|
181
218
|
* const tools = await client.tools()
|
|
182
219
|
* ```
|
|
183
220
|
*/
|
|
184
|
-
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions):
|
|
221
|
+
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface;
|
|
185
222
|
|
|
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";
|
|
223
|
+
/** Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */
|
|
224
|
+
export declare const DEFAULT_MCP_SERVER_NAME = "@orkestrel/mcp";
|
|
203
225
|
|
|
204
|
-
/**
|
|
226
|
+
/** Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */
|
|
205
227
|
export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
206
228
|
|
|
207
229
|
/**
|
|
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`.
|
|
230
|
+
* Carries the Model Context Protocol over a native `MessagePort` from the browser face — a
|
|
231
|
+
* {@link MCPTransportInterface}, the genuinely new capability this face adds: MCP over
|
|
232
|
+
* `postMessage`.
|
|
341
233
|
*
|
|
342
234
|
* @remarks
|
|
343
235
|
* - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
|
|
344
|
-
* / {@link import('
|
|
345
|
-
* carriers of `@orkestrel/mcp`'s `
|
|
346
|
-
* plain duplex channel — the
|
|
347
|
-
* `MCPTransportInterface` and is handed to
|
|
236
|
+
* / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only
|
|
237
|
+
* carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a
|
|
238
|
+
* plain duplex channel — the same class implements `@orkestrel/mcp`'s
|
|
239
|
+
* `MCPTransportInterface` and is handed to either `bindServer` or
|
|
348
240
|
* `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
|
|
349
241
|
* from the binder it is given to, not from anything this class decides.
|
|
350
242
|
* - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only
|
|
351
|
-
*
|
|
243
|
+
* required when listening with `addEventListener` (as opposed to the `onmessage`
|
|
352
244
|
* setter, which implies it) — this transport uses `addEventListener`, and
|
|
353
245
|
* `MCPTransportInterface` has no separate open/connect step for the caller to hook
|
|
354
246
|
* a start into, so the constructor calls `port.start()` immediately: the port
|
|
355
|
-
* begins dispatching
|
|
356
|
-
* inside `
|
|
247
|
+
* begins dispatching queued messages the moment the transport exists. This is safe
|
|
248
|
+
* inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`
|
|
357
249
|
* before control returns to the event loop), but is a **footgun for direct use**:
|
|
358
250
|
* if you construct `new MessagePortTransport({ port })` and then `await` anything
|
|
359
|
-
* before calling `listen`, messages that arrived in the gap are
|
|
251
|
+
* before calling `listen`, messages that arrived in the gap are dropped. **Bind
|
|
360
252
|
* synchronously after construction** — do not interleave an `await` between
|
|
361
253
|
* `new MessagePortTransport(…)` and `bindServer` / `listen`.
|
|
362
254
|
* - **String payloads only.** `send` posts the message string as-is (`postMessage`
|
|
363
255
|
* structured-clones it — a string clones to an identical string, so the wire stays
|
|
364
256
|
* plain JSON-RPC text like every other transport in this package). Inbound: a
|
|
365
257
|
* non-string `event.data` (a host or a misbehaving peer posting a structured
|
|
366
|
-
* object) is
|
|
258
|
+
* object) is ignored — dropped silently, never forwarded, never thrown —
|
|
367
259
|
* because `MCPTransportInterface` carries no `error` channel for this port to
|
|
368
|
-
* surface a non-string frame on (unlike `
|
|
260
|
+
* surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);
|
|
369
261
|
* silently ignoring is the total, contract-shaped choice.
|
|
370
|
-
* - **`messageerror` is
|
|
262
|
+
* - **`messageerror` is ignored, not routed to `closed`.** A `messageerror` event
|
|
371
263
|
* (the structured-clone deserialization of an inbound message threw) reports one
|
|
372
|
-
*
|
|
373
|
-
* formed messages still arrive.
|
|
264
|
+
* bad frame, not a dead channel — the port itself keeps working and later, well-
|
|
265
|
+
* formed messages still arrive. This transport registers no listener for it: an
|
|
266
|
+
* unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor
|
|
267
|
+
* reaches this transport, so one bad frame costs exactly that frame and nothing
|
|
268
|
+
* tears the binding down. Routing it to `closed` would tear down the
|
|
374
269
|
* `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.
|
|
270
|
+
* over a single malformed frame.
|
|
378
271
|
* - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`
|
|
379
|
-
* disconnects it — further `postMessage` calls on
|
|
272
|
+
* disconnects it — further `postMessage` calls on either end are silently
|
|
380
273
|
* undelivered, per the platform contract) and fires the registered `closed`
|
|
381
274
|
* handler exactly once, whether the caller closes it once or twice. There is no
|
|
382
275
|
* native "peer closed" signal for a `MessagePort` (unlike a WebSocket's `close`
|
|
383
|
-
* event) — `closed` fires
|
|
276
|
+
* event) — `closed` fires only from this transport's own `close()`.
|
|
384
277
|
* - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`
|
|
385
278
|
* doc).** `listen`/`closed` each hold the one active handler; a
|
|
386
|
-
* second call
|
|
279
|
+
* second call replaces the first rather than adding a second subscriber.
|
|
387
280
|
*
|
|
388
281
|
* @example
|
|
389
282
|
* ```ts
|
|
@@ -396,7 +289,7 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
396
289
|
* bindClient(client, clientTransport) // port2 side is the client's carrier
|
|
397
290
|
* ```
|
|
398
291
|
*/
|
|
399
|
-
export declare class MessagePortTransport implements
|
|
292
|
+
export declare class MessagePortTransport implements MCPTransportInterface {
|
|
400
293
|
#private;
|
|
401
294
|
constructor(options: MessagePortTransportOptions);
|
|
402
295
|
send(message: string): void;
|
|
@@ -411,7 +304,7 @@ export declare class MessagePortTransport implements MCPTransportInterface_2 {
|
|
|
411
304
|
*
|
|
412
305
|
* @remarks
|
|
413
306
|
* `port` — the channel half to drive (for example, one side of a `new MessageChannel()`, or
|
|
414
|
-
* the port a `message` event's `ports[0]` carried).
|
|
307
|
+
* the port a `message` event's `ports[0]` carried). Required. The same transport
|
|
415
308
|
* works as either a server or a client carrier — the role comes from whether it is
|
|
416
309
|
* handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@orkestrel/mcp`).
|
|
417
310
|
*/
|
|
@@ -420,72 +313,63 @@ export declare interface MessagePortTransportOptions {
|
|
|
420
313
|
}
|
|
421
314
|
|
|
422
315
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
316
|
+
* Describes the structural shape {@link import('./factories.js').createScopeServer} needs
|
|
317
|
+
* from a
|
|
318
|
+
* hostable scope — `self` in a dedicated Web Worker or a Service Worker (or any double
|
|
319
|
+
* matching this shape).
|
|
425
320
|
*
|
|
426
321
|
* @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`).
|
|
322
|
+
* Only the members the scope server actually touches: `postMessage` (the
|
|
323
|
+
* dedicated-worker implicit reply channel), and `addEventListener` /
|
|
324
|
+
* `removeEventListener` for `'message'` (every inbound event, portless or
|
|
325
|
+
* port-bearing, arrives through the same listener — see {@link ScopeServerOptions}'s
|
|
326
|
+
* doc and the factory). A real `self` / `globalThis` inside a worker satisfies this
|
|
327
|
+
* structurally (it exposes far more, which this narrower shape ignores).
|
|
449
328
|
*/
|
|
450
|
-
export declare interface
|
|
451
|
-
|
|
452
|
-
|
|
329
|
+
export declare interface ScopeInterface {
|
|
330
|
+
postMessage(message: unknown): void;
|
|
331
|
+
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
332
|
+
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
453
333
|
}
|
|
454
334
|
|
|
455
335
|
/**
|
|
456
|
-
*
|
|
336
|
+
* Represents one MCP server hosted inside a worker scope — what
|
|
337
|
+
* {@link import('./factories.js').createScopeServer} returns.
|
|
457
338
|
*
|
|
458
339
|
* @remarks
|
|
459
|
-
* The
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
340
|
+
* The browser twin of the Node face's `StdioServerInterface`, and it publishes only the
|
|
341
|
+
* terminal: the factory arms the scope's `message` listener before it returns, because an
|
|
342
|
+
* event delivered between construction and an explicit `start` would reach nothing. `stop`
|
|
343
|
+
* removes that listener, unbinds the implicit scope channel, and tears down every accepted
|
|
344
|
+
* port binding; it is idempotent, and it ends this handle's lifetime permanently.
|
|
464
345
|
*/
|
|
465
|
-
export declare
|
|
346
|
+
export declare interface ScopeServerInterface {
|
|
347
|
+
/** Ends every binding this scope server owns — idempotent, and permanent for this handle. */
|
|
348
|
+
stop(): void;
|
|
349
|
+
}
|
|
466
350
|
|
|
467
351
|
/**
|
|
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} /
|
|
352
|
+
* Options for {@link import('./factories.js').createScopeServer} — the live
|
|
353
|
+
* {@link ToolManagerInterface} to expose plus the optional server identity, mirroring
|
|
354
|
+
* `createMCPServer`'s `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version`
|
|
355
|
+
* optional (defaulting to {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
|
|
472
356
|
* {@link import('./constants.js').DEFAULT_MCP_SERVER_VERSION}).
|
|
473
357
|
*
|
|
474
358
|
* @remarks
|
|
475
359
|
* - `accept` — optional identity gate consulted **before** a port-bearing `message`
|
|
476
360
|
* event is accepted; return `false` to drop the event (no binding, no reply).
|
|
477
|
-
* **`accept` gates
|
|
361
|
+
* **`accept` gates only port-bearing events** — portless messages bypass it and
|
|
478
362
|
* deliver directly to the implicit scope channel (the tool executes, blind; in a
|
|
479
|
-
* Service Worker the reply is silently dropped — see `
|
|
363
|
+
* Service Worker the reply is silently dropped — see `createScopeServer`'s portless note).
|
|
480
364
|
* Prefer a handshake token in `event.data` as the primary pattern
|
|
481
365
|
* (for example, `(event) => event.data === token`) — for same-origin worker/MessagePort
|
|
482
366
|
* messages `event.origin` is frequently the empty string, making origin
|
|
483
367
|
* allow-listing unreliable; origin checks are meaningful for cross-origin
|
|
484
|
-
* `postMessage` only. When omitted,
|
|
368
|
+
* `postMessage` only. When omitted, all port-bearing events are accepted — every
|
|
485
369
|
* same-origin context that can reach the scope gets full tool-call access.
|
|
486
|
-
* See `
|
|
370
|
+
* See `createScopeServer`'s trust-boundary and portless-events notes.
|
|
487
371
|
*/
|
|
488
|
-
export declare interface
|
|
372
|
+
export declare interface ScopeServerOptions {
|
|
489
373
|
readonly tools: ToolManagerInterface;
|
|
490
374
|
readonly name?: string;
|
|
491
375
|
readonly version?: string;
|
|
@@ -493,79 +377,55 @@ export declare interface ServeMCPOptions {
|
|
|
493
377
|
}
|
|
494
378
|
|
|
495
379
|
/**
|
|
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).
|
|
380
|
+
* Adapts a message-event-bearing scope (`self` in a dedicated Web Worker, or any object
|
|
381
|
+
* shaped the same way) as a duplex {@link MCPTransportInterface} — the
|
|
382
|
+
* internal carrier `createScopeServer` binds to route the implicit (portless) message
|
|
383
|
+
* channel, plus the `deliver` entry point the scope's own `message` listener pushes
|
|
384
|
+
* an inbound string through (the scope itself never registers `listen`'s handler
|
|
385
|
+
* for the caller — the scope server's dispatcher does, through this `deliver`).
|
|
524
386
|
*/
|
|
525
|
-
export declare interface
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
387
|
+
export declare interface ScopeTransportInterface extends MCPTransportInterface {
|
|
388
|
+
/** Pushes one inbound message string into the active `listen` handler. */
|
|
389
|
+
deliver(message: string): void;
|
|
529
390
|
}
|
|
530
391
|
|
|
531
392
|
/**
|
|
532
|
-
*
|
|
533
|
-
* {@link
|
|
534
|
-
*
|
|
535
|
-
* {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
393
|
+
* Drives a remote MCP server over the native `WebSocket` global from the browser face, as a
|
|
394
|
+
* client {@link MCPMessageTransportInterface}. This class is the browser sibling of the Node
|
|
395
|
+
* face's {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
536
396
|
*
|
|
537
397
|
* @remarks
|
|
538
398
|
* - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and
|
|
539
399
|
* waits for the native `'open'` event — the RFC 6455 handshake itself is entirely
|
|
540
400
|
* the host's concern, so this transport carries none of the Node client's
|
|
541
401
|
* `node:crypto` / `node:http(s)` machinery. A connection failure (the native
|
|
542
|
-
* `'error'` event while not yet `OPEN`)
|
|
402
|
+
* `'error'` event while not yet `OPEN`) rejects `start()`.
|
|
543
403
|
* - **Queued sends.** `send` writes each message as one text frame immediately once
|
|
544
404
|
* the socket is `OPEN`; a `send` issued before `'open'` fires (or before `start()`
|
|
545
|
-
* is even called) is
|
|
546
|
-
* so a caller need not await `start()` before calling `send`. A queue rides
|
|
547
|
-
* connection: a close
|
|
548
|
-
* - **A closed channel
|
|
405
|
+
* is even called) is queued and flushed, in order, the moment the socket opens —
|
|
406
|
+
* so a caller need not await `start()` before calling `send`. A queue rides one
|
|
407
|
+
* connection: a close discards whatever is still in it.
|
|
408
|
+
* - **A closed channel rejects.** The native socket confirms nothing about a write, so this
|
|
549
409
|
* transport answers from its own state: a `send` after `close()`, or on a socket already
|
|
550
|
-
* reporting `CLOSING` / `CLOSED`,
|
|
410
|
+
* reporting `CLOSING` / `CLOSED`, rejects with `WebSocket transport is not connected` rather
|
|
551
411
|
* than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`
|
|
552
412
|
* still queues.
|
|
553
|
-
* - **Inbound (`message`).** Each decoded text frame
|
|
554
|
-
*
|
|
413
|
+
* - **Inbound (`message`).** Each decoded text frame runs through the shared
|
|
414
|
+
* `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}
|
|
555
415
|
* re-emits on this transport's `message` event; a non-text (binary) frame or a
|
|
556
|
-
* non-JSON / non-message text frame surfaces on `error` and is
|
|
416
|
+
* non-JSON / non-message text frame surfaces on `error` and is dropped (never
|
|
557
417
|
* throws on adversarial wire input).
|
|
558
418
|
* - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`
|
|
559
419
|
* (idempotent); the socket's native `close` event (a server-initiated close) fires the
|
|
560
|
-
*
|
|
420
|
+
* same `close` exactly once total — `close()` first flips the guard, so the native event
|
|
561
421
|
* never double-emits, and the released socket reports its own close to nobody. Closing before
|
|
562
422
|
* the socket opens resolves the pending `start()` rather than leaving it pending, matching the
|
|
563
|
-
* Node face. A `send` issued after `close()`
|
|
564
|
-
* pre-open queue is
|
|
423
|
+
* Node face. A `send` issued after `close()` rejects (it is never queued), and the
|
|
424
|
+
* pre-open queue is discarded — by `close()` and by the native `close` event alike — so a
|
|
565
425
|
* closed transport delivers nothing until a `start()` opens a new connection, and nothing
|
|
566
426
|
* the caller handed the abandoned connection rides that one.
|
|
567
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
568
|
-
* emit the emitter isolates a listener throw; `error` is a
|
|
427
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every
|
|
428
|
+
* emit the emitter isolates a listener throw; `error` is a domain event (a
|
|
569
429
|
* transport-level fault).
|
|
570
430
|
*
|
|
571
431
|
* @example
|
|
@@ -575,14 +435,14 @@ export declare interface ServeMCPScopeInterface {
|
|
|
575
435
|
* await client.connect() // the browser handshakes, then the MCP initialize runs over WS frames
|
|
576
436
|
* ```
|
|
577
437
|
*/
|
|
578
|
-
export declare class WebSocketClientTransport implements
|
|
438
|
+
export declare class WebSocketClientTransport implements MCPMessageTransportInterface {
|
|
579
439
|
#private;
|
|
580
440
|
constructor(options: WebSocketClientTransportOptions);
|
|
581
|
-
get emitter(): EmitterInterface<
|
|
441
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
582
442
|
get session(): string | undefined;
|
|
583
443
|
get duplex(): boolean;
|
|
584
444
|
start(): Promise<void>;
|
|
585
|
-
send(message:
|
|
445
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
586
446
|
close(): Promise<void>;
|
|
587
447
|
}
|
|
588
448
|
|
|
@@ -593,9 +453,9 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
593
453
|
* @remarks
|
|
594
454
|
* - `url` — the absolute `ws://` / `wss://` (or `http://` / `https://`, accepted by
|
|
595
455
|
* the native `WebSocket` constructor the same way) URL of the remote server's
|
|
596
|
-
* WebSocket endpoint.
|
|
456
|
+
* WebSocket endpoint. Required.
|
|
597
457
|
* - `protocols` — the WebSocket subprotocol(s) to request. **Defaults to
|
|
598
|
-
* {@link import('
|
|
458
|
+
* {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
|
|
599
459
|
* `createWebSocketServer` selects when the offer contains it. Per
|
|
600
460
|
* RFC 6455 §4.1 a client must fail the connection if the server returns a subprotocol
|
|
601
461
|
* it did not request; Node ≥ 22 (undici) enforces this strictly, so the default saves
|
|
@@ -609,7 +469,7 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
609
469
|
* upgrade request header at all — there is no seam for an `Authorization` bearer to reach.
|
|
610
470
|
* The Node face owns its own `node:http(s)` upgrade request and therefore can, which is why
|
|
611
471
|
* only that side offers `headers`. Reach a guarded server from a page with a credential the
|
|
612
|
-
* platform
|
|
472
|
+
* platform does carry: a cookie the browser attaches to the upgrade, a subprotocol token, or
|
|
613
473
|
* a signed value in the URL. Adding a `headers` key here would be an option that silently
|
|
614
474
|
* did nothing.
|
|
615
475
|
*/
|