@orkestrel/mcp 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/browser/index.d.ts +631 -0
- package/dist/src/browser/index.js +755 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/core/index.cjs +173 -0
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +136 -0
- package/dist/src/core/index.d.ts +136 -0
- package/dist/src/core/index.js +171 -1
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +92 -38
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +68 -22
- package/dist/src/server/index.d.ts +68 -22
- package/dist/src/server/index.js +93 -40
- package/dist/src/server/index.js.map +1 -1
- package/package.json +21 -9
|
@@ -5,6 +5,81 @@ import { ToolInterface } from '@orkestrel/agent';
|
|
|
5
5
|
import { ToolManagerInterface } from '@orkestrel/agent';
|
|
6
6
|
import { ToolResult } from '@orkestrel/agent';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
|
|
10
|
+
* inbound message is decoded and delivered onto the client's OWN transport
|
|
11
|
+
* (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
|
|
12
|
+
* client's correlated pending requests exactly as a direct reply would.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The client's outbound writes flow through `client.transport.send` — its existing,
|
|
16
|
+
* unmodified request/response correlation — so `client` must have been constructed
|
|
17
|
+
* with a {@link import('./types.js').ClientTransportInterface} that itself carries
|
|
18
|
+
* the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
|
|
19
|
+
* the additive factory that adapts an {@link MCPTransportInterface} into that shape);
|
|
20
|
+
* this binder then completes the inbound half by decoding each message and pushing it
|
|
21
|
+
* onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
|
|
22
|
+
* exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
|
|
23
|
+
* inbound message is DROPPED (§14, total — never throws); a delivery fault is routed to
|
|
24
|
+
* `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
|
|
25
|
+
* DETACHES this binder (further inbound messages and the transport's `closed` signal are
|
|
26
|
+
* ignored) WITHOUT closing the transport.
|
|
27
|
+
*
|
|
28
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
29
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
30
|
+
* `bindClient` call on the SAME transport is never double-dispatched by a stale
|
|
31
|
+
* subscription left behind — an unbind→rebind cycle delivers exactly one `message`
|
|
32
|
+
* emit per inbound reply.
|
|
33
|
+
*
|
|
34
|
+
* @param client - The transport-agnostic client whose transport to deliver messages onto
|
|
35
|
+
* @param transport - The duplex channel to pipe the client over
|
|
36
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
41
|
+
* const unbind = bindClient(client, transport)
|
|
42
|
+
* await client.connect()
|
|
43
|
+
* // ... later, detach without closing:
|
|
44
|
+
* unbind()
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function bindClient(client: MCPClientInterface, transport: MCPTransportInterface): () => void;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPServerInterface} — every
|
|
51
|
+
* inbound message runs through `server.handle`, and a defined reply is written back
|
|
52
|
+
* via `transport.send`.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* `server.handle` already turns a malformed message into a serialized `-32700` /
|
|
56
|
+
* `-32600` reply and a notification into `undefined` (no reply), so this binder adds
|
|
57
|
+
* no parsing of its own. A `transport.send` throw or rejection is caught and routed
|
|
58
|
+
* to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
|
|
59
|
+
* a listener on that event that itself throws is swallowed (the end of the line —
|
|
60
|
+
* the caller's own bug, never this binder's). The returned unbind DETACHES this
|
|
61
|
+
* binder (further inbound messages and the transport's `closed` signal are ignored)
|
|
62
|
+
* WITHOUT closing the transport — closing is the caller's decision.
|
|
63
|
+
*
|
|
64
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
65
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
66
|
+
* `bindServer` call on the SAME transport is never double-dispatched by a stale
|
|
67
|
+
* subscription left behind — an unbind→rebind cycle yields exactly one reply per
|
|
68
|
+
* request.
|
|
69
|
+
*
|
|
70
|
+
* @param server - The transport-agnostic server to dispatch inbound messages over
|
|
71
|
+
* @param transport - The duplex channel to pipe the server over
|
|
72
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const unbind = bindServer(server, transport)
|
|
77
|
+
* // ... later, detach without closing:
|
|
78
|
+
* unbind()
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function bindServer(server: MCPServerInterface, transport: MCPTransportInterface): () => void;
|
|
82
|
+
|
|
8
83
|
/**
|
|
9
84
|
* Map a {@link ToolManagerInterface}'s definitions to MCP `tools/list` descriptors
|
|
10
85
|
* — renaming `parameters` to the wire's `inputSchema`.
|
|
@@ -110,6 +185,36 @@ export declare interface ClientTransportInterface {
|
|
|
110
185
|
close(): Promise<void>;
|
|
111
186
|
}
|
|
112
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Adapt an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
190
|
+
* channel) into a {@link ClientTransportInterface} — the additive bridge that lets
|
|
191
|
+
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
192
|
+
* existing shape.
|
|
193
|
+
*
|
|
194
|
+
* @remarks
|
|
195
|
+
* Hand the RESULT to `createMCPClient({ transport })`, then pass the SAME
|
|
196
|
+
* `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
|
|
197
|
+
* wiring: `send` serializes each outbound {@link JSONRPCMessage} (or batch, one per
|
|
198
|
+
* message) and writes it via `transport.send`; `close` closes the underlying
|
|
199
|
+
* `transport`; `start` is a no-op (the duplex channel is already open by the time
|
|
200
|
+
* it is handed in — there is no separate connect step at this layer); `session` is
|
|
201
|
+
* always `undefined` (session correlation is a higher-level concern the duplex port
|
|
202
|
+
* does not carry). Inbound delivery (`emitter`'s `message` / `close` events) is
|
|
203
|
+
* `bindClient`'s job, not this factory's — the returned object exposes a `message`-
|
|
204
|
+
* capable emitter for `bindClient` to push onto.
|
|
205
|
+
*
|
|
206
|
+
* @param transport - The duplex channel to adapt
|
|
207
|
+
* @returns A {@link ClientTransportInterface} `createMCPClient` can drive
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
212
|
+
* const unbind = bindClient(client, transport)
|
|
213
|
+
* await client.connect()
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export declare function createDuplexClientTransport(transport: MCPTransportInterface): ClientTransportInterface;
|
|
217
|
+
|
|
113
218
|
/**
|
|
114
219
|
* Create a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
|
|
115
220
|
* MCP server over an injected {@link import('./types.js').ClientTransportInterface},
|
|
@@ -657,6 +762,12 @@ export declare class MCPServer implements MCPServerInterface {
|
|
|
657
762
|
export declare type MCPServerEventMap = {
|
|
658
763
|
/** A request is being dispatched — its `method` and correlating `id` (`null` for a notification). */
|
|
659
764
|
readonly request: readonly [method: string, id: string | number | null];
|
|
765
|
+
/**
|
|
766
|
+
* A transport-level fault surfaced while a bound {@link MCPTransportInterface} was
|
|
767
|
+
* piping a reply out (a `send` throw or rejection from {@link bindServer}). A DOMAIN
|
|
768
|
+
* event (a genuine I/O fault), distinct from the emitter's own listener-error channel.
|
|
769
|
+
*/
|
|
770
|
+
readonly error: readonly [error: unknown];
|
|
660
771
|
};
|
|
661
772
|
|
|
662
773
|
/** The server identity echoed in the MCP `initialize` result's `serverInfo`. */
|
|
@@ -769,6 +880,31 @@ export declare interface MCPToolResult {
|
|
|
769
880
|
readonly isError?: boolean;
|
|
770
881
|
}
|
|
771
882
|
|
|
883
|
+
/**
|
|
884
|
+
* A duplex message channel an environment face provides to the pure engine — the
|
|
885
|
+
* one port `bindServer` and `bindClient` (`./helpers.js`) pipe an
|
|
886
|
+
* {@link MCPServerInterface} / {@link MCPClientInterface} over.
|
|
887
|
+
*
|
|
888
|
+
* @remarks
|
|
889
|
+
* Messages are already-serialized JSON-RPC strings; the transport owns framing
|
|
890
|
+
* (a WS text frame, an SSE `data:` event, a newline-terminated stdio line, a
|
|
891
|
+
* `postMessage` payload) and never parses the string itself. `listen` and
|
|
892
|
+
* `closed` each register THE SINGLE handler for their event — a second call
|
|
893
|
+
* REPLACES the first (matching the emitter-free, minimal-surface carrier idiom
|
|
894
|
+
* `bindServer` / `bindClient` themselves rely on), not an additive subscription
|
|
895
|
+
* list.
|
|
896
|
+
*/
|
|
897
|
+
export declare interface MCPTransportInterface {
|
|
898
|
+
/** Deliver one outbound JSON-RPC message (already serialized). */
|
|
899
|
+
readonly send: (message: string) => void | Promise<void>;
|
|
900
|
+
/** Register the single inbound-message handler — a second call REPLACES the first. */
|
|
901
|
+
readonly listen: (handler: (message: string) => void) => void;
|
|
902
|
+
/** Register the single closed handler — a second call REPLACES the first. */
|
|
903
|
+
readonly closed: (handler: () => void) => void;
|
|
904
|
+
/** Close the underlying channel. */
|
|
905
|
+
readonly close: () => void | Promise<void>;
|
|
906
|
+
}
|
|
907
|
+
|
|
772
908
|
/**
|
|
773
909
|
* Narrow an already-parsed value to a {@link JSONRPCMessage}, or `undefined` when
|
|
774
910
|
* it is not one.
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -5,6 +5,81 @@ import { ToolInterface } from '@orkestrel/agent';
|
|
|
5
5
|
import { ToolManagerInterface } from '@orkestrel/agent';
|
|
6
6
|
import { ToolResult } from '@orkestrel/agent';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
|
|
10
|
+
* inbound message is decoded and delivered onto the client's OWN transport
|
|
11
|
+
* (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
|
|
12
|
+
* client's correlated pending requests exactly as a direct reply would.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The client's outbound writes flow through `client.transport.send` — its existing,
|
|
16
|
+
* unmodified request/response correlation — so `client` must have been constructed
|
|
17
|
+
* with a {@link import('./types.js').ClientTransportInterface} that itself carries
|
|
18
|
+
* the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
|
|
19
|
+
* the additive factory that adapts an {@link MCPTransportInterface} into that shape);
|
|
20
|
+
* this binder then completes the inbound half by decoding each message and pushing it
|
|
21
|
+
* onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
|
|
22
|
+
* exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
|
|
23
|
+
* inbound message is DROPPED (§14, total — never throws); a delivery fault is routed to
|
|
24
|
+
* `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
|
|
25
|
+
* DETACHES this binder (further inbound messages and the transport's `closed` signal are
|
|
26
|
+
* ignored) WITHOUT closing the transport.
|
|
27
|
+
*
|
|
28
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
29
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
30
|
+
* `bindClient` call on the SAME transport is never double-dispatched by a stale
|
|
31
|
+
* subscription left behind — an unbind→rebind cycle delivers exactly one `message`
|
|
32
|
+
* emit per inbound reply.
|
|
33
|
+
*
|
|
34
|
+
* @param client - The transport-agnostic client whose transport to deliver messages onto
|
|
35
|
+
* @param transport - The duplex channel to pipe the client over
|
|
36
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
41
|
+
* const unbind = bindClient(client, transport)
|
|
42
|
+
* await client.connect()
|
|
43
|
+
* // ... later, detach without closing:
|
|
44
|
+
* unbind()
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function bindClient(client: MCPClientInterface, transport: MCPTransportInterface): () => void;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPServerInterface} — every
|
|
51
|
+
* inbound message runs through `server.handle`, and a defined reply is written back
|
|
52
|
+
* via `transport.send`.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* `server.handle` already turns a malformed message into a serialized `-32700` /
|
|
56
|
+
* `-32600` reply and a notification into `undefined` (no reply), so this binder adds
|
|
57
|
+
* no parsing of its own. A `transport.send` throw or rejection is caught and routed
|
|
58
|
+
* to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
|
|
59
|
+
* a listener on that event that itself throws is swallowed (the end of the line —
|
|
60
|
+
* the caller's own bug, never this binder's). The returned unbind DETACHES this
|
|
61
|
+
* binder (further inbound messages and the transport's `closed` signal are ignored)
|
|
62
|
+
* WITHOUT closing the transport — closing is the caller's decision.
|
|
63
|
+
*
|
|
64
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
65
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
66
|
+
* `bindServer` call on the SAME transport is never double-dispatched by a stale
|
|
67
|
+
* subscription left behind — an unbind→rebind cycle yields exactly one reply per
|
|
68
|
+
* request.
|
|
69
|
+
*
|
|
70
|
+
* @param server - The transport-agnostic server to dispatch inbound messages over
|
|
71
|
+
* @param transport - The duplex channel to pipe the server over
|
|
72
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const unbind = bindServer(server, transport)
|
|
77
|
+
* // ... later, detach without closing:
|
|
78
|
+
* unbind()
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function bindServer(server: MCPServerInterface, transport: MCPTransportInterface): () => void;
|
|
82
|
+
|
|
8
83
|
/**
|
|
9
84
|
* Map a {@link ToolManagerInterface}'s definitions to MCP `tools/list` descriptors
|
|
10
85
|
* — renaming `parameters` to the wire's `inputSchema`.
|
|
@@ -110,6 +185,36 @@ export declare interface ClientTransportInterface {
|
|
|
110
185
|
close(): Promise<void>;
|
|
111
186
|
}
|
|
112
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Adapt an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
190
|
+
* channel) into a {@link ClientTransportInterface} — the additive bridge that lets
|
|
191
|
+
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
192
|
+
* existing shape.
|
|
193
|
+
*
|
|
194
|
+
* @remarks
|
|
195
|
+
* Hand the RESULT to `createMCPClient({ transport })`, then pass the SAME
|
|
196
|
+
* `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
|
|
197
|
+
* wiring: `send` serializes each outbound {@link JSONRPCMessage} (or batch, one per
|
|
198
|
+
* message) and writes it via `transport.send`; `close` closes the underlying
|
|
199
|
+
* `transport`; `start` is a no-op (the duplex channel is already open by the time
|
|
200
|
+
* it is handed in — there is no separate connect step at this layer); `session` is
|
|
201
|
+
* always `undefined` (session correlation is a higher-level concern the duplex port
|
|
202
|
+
* does not carry). Inbound delivery (`emitter`'s `message` / `close` events) is
|
|
203
|
+
* `bindClient`'s job, not this factory's — the returned object exposes a `message`-
|
|
204
|
+
* capable emitter for `bindClient` to push onto.
|
|
205
|
+
*
|
|
206
|
+
* @param transport - The duplex channel to adapt
|
|
207
|
+
* @returns A {@link ClientTransportInterface} `createMCPClient` can drive
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
212
|
+
* const unbind = bindClient(client, transport)
|
|
213
|
+
* await client.connect()
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export declare function createDuplexClientTransport(transport: MCPTransportInterface): ClientTransportInterface;
|
|
217
|
+
|
|
113
218
|
/**
|
|
114
219
|
* Create a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
|
|
115
220
|
* MCP server over an injected {@link import('./types.js').ClientTransportInterface},
|
|
@@ -657,6 +762,12 @@ export declare class MCPServer implements MCPServerInterface {
|
|
|
657
762
|
export declare type MCPServerEventMap = {
|
|
658
763
|
/** A request is being dispatched — its `method` and correlating `id` (`null` for a notification). */
|
|
659
764
|
readonly request: readonly [method: string, id: string | number | null];
|
|
765
|
+
/**
|
|
766
|
+
* A transport-level fault surfaced while a bound {@link MCPTransportInterface} was
|
|
767
|
+
* piping a reply out (a `send` throw or rejection from {@link bindServer}). A DOMAIN
|
|
768
|
+
* event (a genuine I/O fault), distinct from the emitter's own listener-error channel.
|
|
769
|
+
*/
|
|
770
|
+
readonly error: readonly [error: unknown];
|
|
660
771
|
};
|
|
661
772
|
|
|
662
773
|
/** The server identity echoed in the MCP `initialize` result's `serverInfo`. */
|
|
@@ -769,6 +880,31 @@ export declare interface MCPToolResult {
|
|
|
769
880
|
readonly isError?: boolean;
|
|
770
881
|
}
|
|
771
882
|
|
|
883
|
+
/**
|
|
884
|
+
* A duplex message channel an environment face provides to the pure engine — the
|
|
885
|
+
* one port `bindServer` and `bindClient` (`./helpers.js`) pipe an
|
|
886
|
+
* {@link MCPServerInterface} / {@link MCPClientInterface} over.
|
|
887
|
+
*
|
|
888
|
+
* @remarks
|
|
889
|
+
* Messages are already-serialized JSON-RPC strings; the transport owns framing
|
|
890
|
+
* (a WS text frame, an SSE `data:` event, a newline-terminated stdio line, a
|
|
891
|
+
* `postMessage` payload) and never parses the string itself. `listen` and
|
|
892
|
+
* `closed` each register THE SINGLE handler for their event — a second call
|
|
893
|
+
* REPLACES the first (matching the emitter-free, minimal-surface carrier idiom
|
|
894
|
+
* `bindServer` / `bindClient` themselves rely on), not an additive subscription
|
|
895
|
+
* list.
|
|
896
|
+
*/
|
|
897
|
+
export declare interface MCPTransportInterface {
|
|
898
|
+
/** Deliver one outbound JSON-RPC message (already serialized). */
|
|
899
|
+
readonly send: (message: string) => void | Promise<void>;
|
|
900
|
+
/** Register the single inbound-message handler — a second call REPLACES the first. */
|
|
901
|
+
readonly listen: (handler: (message: string) => void) => void;
|
|
902
|
+
/** Register the single closed handler — a second call REPLACES the first. */
|
|
903
|
+
readonly closed: (handler: () => void) => void;
|
|
904
|
+
/** Close the underlying channel. */
|
|
905
|
+
readonly close: () => void | Promise<void>;
|
|
906
|
+
}
|
|
907
|
+
|
|
772
908
|
/**
|
|
773
909
|
* Narrow an already-parsed value to a {@link JSONRPCMessage}, or `undefined` when
|
|
774
910
|
* it is not one.
|
package/dist/src/core/index.js
CHANGED
|
@@ -279,6 +279,134 @@ function initializeResult(name, version, requested) {
|
|
|
279
279
|
}
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPServerInterface} — every
|
|
284
|
+
* inbound message runs through `server.handle`, and a defined reply is written back
|
|
285
|
+
* via `transport.send`.
|
|
286
|
+
*
|
|
287
|
+
* @remarks
|
|
288
|
+
* `server.handle` already turns a malformed message into a serialized `-32700` /
|
|
289
|
+
* `-32600` reply and a notification into `undefined` (no reply), so this binder adds
|
|
290
|
+
* no parsing of its own. A `transport.send` throw or rejection is caught and routed
|
|
291
|
+
* to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
|
|
292
|
+
* a listener on that event that itself throws is swallowed (the end of the line —
|
|
293
|
+
* the caller's own bug, never this binder's). The returned unbind DETACHES this
|
|
294
|
+
* binder (further inbound messages and the transport's `closed` signal are ignored)
|
|
295
|
+
* WITHOUT closing the transport — closing is the caller's decision.
|
|
296
|
+
*
|
|
297
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
298
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
299
|
+
* `bindServer` call on the SAME transport is never double-dispatched by a stale
|
|
300
|
+
* subscription left behind — an unbind→rebind cycle yields exactly one reply per
|
|
301
|
+
* request.
|
|
302
|
+
*
|
|
303
|
+
* @param server - The transport-agnostic server to dispatch inbound messages over
|
|
304
|
+
* @param transport - The duplex channel to pipe the server over
|
|
305
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* const unbind = bindServer(server, transport)
|
|
310
|
+
* // ... later, detach without closing:
|
|
311
|
+
* unbind()
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
function bindServer(server, transport) {
|
|
315
|
+
let active = true;
|
|
316
|
+
transport.listen((message) => {
|
|
317
|
+
if (!active) return;
|
|
318
|
+
(async () => {
|
|
319
|
+
try {
|
|
320
|
+
const response = await server.handle(message);
|
|
321
|
+
if (response !== void 0) await transport.send(response);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
try {
|
|
324
|
+
server.emitter.emit("error", error);
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
})();
|
|
328
|
+
});
|
|
329
|
+
transport.closed(() => {
|
|
330
|
+
active = false;
|
|
331
|
+
});
|
|
332
|
+
return () => {
|
|
333
|
+
active = false;
|
|
334
|
+
transport.listen(() => {});
|
|
335
|
+
transport.closed(() => {});
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Pipe an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
|
|
340
|
+
* inbound message is decoded and delivered onto the client's OWN transport
|
|
341
|
+
* (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
|
|
342
|
+
* client's correlated pending requests exactly as a direct reply would.
|
|
343
|
+
*
|
|
344
|
+
* @remarks
|
|
345
|
+
* The client's outbound writes flow through `client.transport.send` — its existing,
|
|
346
|
+
* unmodified request/response correlation — so `client` must have been constructed
|
|
347
|
+
* with a {@link import('./types.js').ClientTransportInterface} that itself carries
|
|
348
|
+
* the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
|
|
349
|
+
* the additive factory that adapts an {@link MCPTransportInterface} into that shape);
|
|
350
|
+
* this binder then completes the inbound half by decoding each message and pushing it
|
|
351
|
+
* onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
|
|
352
|
+
* exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
|
|
353
|
+
* inbound message is DROPPED (§14, total — never throws); a delivery fault is routed to
|
|
354
|
+
* `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
|
|
355
|
+
* DETACHES this binder (further inbound messages and the transport's `closed` signal are
|
|
356
|
+
* ignored) WITHOUT closing the transport.
|
|
357
|
+
*
|
|
358
|
+
* `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
|
|
359
|
+
* DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
|
|
360
|
+
* `bindClient` call on the SAME transport is never double-dispatched by a stale
|
|
361
|
+
* subscription left behind — an unbind→rebind cycle delivers exactly one `message`
|
|
362
|
+
* emit per inbound reply.
|
|
363
|
+
*
|
|
364
|
+
* @param client - The transport-agnostic client whose transport to deliver messages onto
|
|
365
|
+
* @param transport - The duplex channel to pipe the client over
|
|
366
|
+
* @returns Detach this binder from the transport (does not close it)
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
371
|
+
* const unbind = bindClient(client, transport)
|
|
372
|
+
* await client.connect()
|
|
373
|
+
* // ... later, detach without closing:
|
|
374
|
+
* unbind()
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
function bindClient(client, transport) {
|
|
378
|
+
let active = true;
|
|
379
|
+
transport.listen((message) => {
|
|
380
|
+
if (!active) return;
|
|
381
|
+
let parsed;
|
|
382
|
+
try {
|
|
383
|
+
parsed = JSON.parse(message);
|
|
384
|
+
} catch {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const decoded = parseJSONRPCMessage(parsed);
|
|
388
|
+
if (decoded === void 0) return;
|
|
389
|
+
try {
|
|
390
|
+
client.transport.emitter.emit("message", decoded);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
try {
|
|
393
|
+
client.transport.emitter.emit("error", error);
|
|
394
|
+
} catch {}
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
transport.closed(() => {
|
|
398
|
+
if (!active) return;
|
|
399
|
+
active = false;
|
|
400
|
+
try {
|
|
401
|
+
client.transport.emitter.emit("close");
|
|
402
|
+
} catch {}
|
|
403
|
+
});
|
|
404
|
+
return () => {
|
|
405
|
+
active = false;
|
|
406
|
+
transport.listen(() => {});
|
|
407
|
+
transport.closed(() => {});
|
|
408
|
+
};
|
|
409
|
+
}
|
|
282
410
|
//#endregion
|
|
283
411
|
//#region src/core/MCPServer.ts
|
|
284
412
|
/**
|
|
@@ -642,7 +770,49 @@ function createMCPServer(options) {
|
|
|
642
770
|
function createMCPClient(options) {
|
|
643
771
|
return new MCPClient(options);
|
|
644
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Adapt an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
775
|
+
* channel) into a {@link ClientTransportInterface} — the additive bridge that lets
|
|
776
|
+
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
777
|
+
* existing shape.
|
|
778
|
+
*
|
|
779
|
+
* @remarks
|
|
780
|
+
* Hand the RESULT to `createMCPClient({ transport })`, then pass the SAME
|
|
781
|
+
* `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
|
|
782
|
+
* wiring: `send` serializes each outbound {@link JSONRPCMessage} (or batch, one per
|
|
783
|
+
* message) and writes it via `transport.send`; `close` closes the underlying
|
|
784
|
+
* `transport`; `start` is a no-op (the duplex channel is already open by the time
|
|
785
|
+
* it is handed in — there is no separate connect step at this layer); `session` is
|
|
786
|
+
* always `undefined` (session correlation is a higher-level concern the duplex port
|
|
787
|
+
* does not carry). Inbound delivery (`emitter`'s `message` / `close` events) is
|
|
788
|
+
* `bindClient`'s job, not this factory's — the returned object exposes a `message`-
|
|
789
|
+
* capable emitter for `bindClient` to push onto.
|
|
790
|
+
*
|
|
791
|
+
* @param transport - The duplex channel to adapt
|
|
792
|
+
* @returns A {@link ClientTransportInterface} `createMCPClient` can drive
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* ```ts
|
|
796
|
+
* const client = createMCPClient({ transport: createDuplexClientTransport(transport) })
|
|
797
|
+
* const unbind = bindClient(client, transport)
|
|
798
|
+
* await client.connect()
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
function createDuplexClientTransport(transport) {
|
|
802
|
+
return {
|
|
803
|
+
emitter: new Emitter(),
|
|
804
|
+
session: void 0,
|
|
805
|
+
async start() {},
|
|
806
|
+
async send(message) {
|
|
807
|
+
const messages = Array.isArray(message) ? message : [message];
|
|
808
|
+
for (const one of messages) await transport.send(JSON.stringify(one));
|
|
809
|
+
},
|
|
810
|
+
async close() {
|
|
811
|
+
await transport.close();
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
645
815
|
//#endregion
|
|
646
|
-
export { DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_REQUEST_TIMEOUT, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPServer, MCP_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, buildToolDescriptors, buildToolResult, createMCPClient, createMCPServer, initializeResult, isInitializeRequest, isJSONRPCMessage, isJSONRPCRequest, isJSONRPCResponse, isRequestId, jsonRPCError, jsonRPCResult, parseJSONRPCMessage };
|
|
816
|
+
export { DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_REQUEST_TIMEOUT, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPServer, MCP_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, bindClient, bindServer, buildToolDescriptors, buildToolResult, createDuplexClientTransport, createMCPClient, createMCPServer, initializeResult, isInitializeRequest, isJSONRPCMessage, isJSONRPCRequest, isJSONRPCResponse, isRequestId, jsonRPCError, jsonRPCResult, parseJSONRPCMessage };
|
|
647
817
|
|
|
648
818
|
//# sourceMappingURL=index.js.map
|