@orkestrel/mcp 0.0.3 → 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.
@@ -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.
@@ -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.
@@ -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