@orkestrel/mcp 0.0.13 → 0.0.15
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 +2 -3
- package/dist/src/browser/index.js +214 -214
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +18 -4
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +22 -6
- package/dist/src/server/index.d.ts +22 -6
- package/dist/src/server/index.js +18 -4
- package/dist/src/server/index.js.map +1 -1
- package/package.json +20 -16
|
@@ -18,6 +18,7 @@ import { MiddlewareHandler } from '@orkestrel/server';
|
|
|
18
18
|
import { NodeWebSocketInterface } from '@orkestrel/websocket';
|
|
19
19
|
import { RouteContext } from '@orkestrel/router';
|
|
20
20
|
import { RouteInput } from '@orkestrel/router';
|
|
21
|
+
import { ServerEventMap } from '@orkestrel/server';
|
|
21
22
|
import { StreamInterface } from '@orkestrel/server';
|
|
22
23
|
import { TokenSecret } from '@orkestrel/server';
|
|
23
24
|
import { UpgradeHandler } from '@orkestrel/server';
|
|
@@ -242,7 +243,7 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
242
243
|
* const routes = createMCPRoutes(createMCPLegacy(mcp)) // both eras; pass `mcp` for modern only
|
|
243
244
|
* ```
|
|
244
245
|
*/
|
|
245
|
-
export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPTransportOptions<TState>):
|
|
246
|
+
export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPTransportOptions<TState>): ReadonlyArray<RouteInput<string, TState>>;
|
|
246
247
|
|
|
247
248
|
/**
|
|
248
249
|
* Create the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
|
|
@@ -445,13 +446,21 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
445
446
|
* as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
|
|
446
447
|
* ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
|
|
447
448
|
* escaping the (async) message pump.
|
|
449
|
+
* - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
|
|
450
|
+
* `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles
|
|
451
|
+
* at once and each client reads a clean goodbye. Node detaches an upgraded socket from the
|
|
452
|
+
* connection set the spine's own close walks, so the claimant is the only thing that can end
|
|
453
|
+
* it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and
|
|
454
|
+
* then have the connection cut mid-protocol. A socket the peer already dropped is gone from
|
|
455
|
+
* the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
|
|
448
456
|
*
|
|
449
457
|
* It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade
|
|
450
458
|
* handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated
|
|
451
459
|
* upgrade so it never reaches this pump.
|
|
452
460
|
*
|
|
453
461
|
* @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
|
|
454
|
-
* @param options -
|
|
462
|
+
* @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its
|
|
463
|
+
* sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
|
|
455
464
|
* (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
|
|
456
465
|
* @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
|
|
457
466
|
*
|
|
@@ -461,10 +470,10 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
461
470
|
* import { createWebSocketServer } from '@src/server'
|
|
462
471
|
*
|
|
463
472
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
464
|
-
* server.upgrade(createWebSocketServer(mcp
|
|
473
|
+
* server.upgrade(createWebSocketServer(mcp, { emitter: server.emitter })) // ws://…/mcp
|
|
465
474
|
* ```
|
|
466
475
|
*/
|
|
467
|
-
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options
|
|
476
|
+
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
|
|
468
477
|
|
|
469
478
|
/**
|
|
470
479
|
* Decode one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
|
|
@@ -1427,10 +1436,16 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1427
1436
|
}
|
|
1428
1437
|
|
|
1429
1438
|
/**
|
|
1430
|
-
* Options for `createWebSocketServer` —
|
|
1431
|
-
* subprotocol negotiated.
|
|
1439
|
+
* Options for `createWebSocketServer` — the spine lifecycle the ingress follows, plus where
|
|
1440
|
+
* the WebSocket upgrade is accepted and the subprotocol negotiated.
|
|
1432
1441
|
*
|
|
1433
1442
|
* @remarks
|
|
1443
|
+
* - `emitter` — the emitter of the `@orkestrel/server` spine this handler is registered on
|
|
1444
|
+
* (`server.emitter`). REQUIRED: on its `stop` event the handler closes every socket it
|
|
1445
|
+
* still owns with the RFC 6455 close handshake, so the spine's drain settles at once. An
|
|
1446
|
+
* upgraded socket is detached from the connection set the spine's own close walks, so
|
|
1447
|
+
* nothing but the claimant can end it — leave it open and `stop()` spends its whole
|
|
1448
|
+
* `drain` budget and then cuts the connection mid-protocol.
|
|
1434
1449
|
* - `path` — the request path the upgrade handler CLAIMS; defaults to
|
|
1435
1450
|
* {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`, the same path the HTTP
|
|
1436
1451
|
* transport mounts at). A protocol-upgrade request to any OTHER path is DECLINED
|
|
@@ -1445,6 +1460,7 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1445
1460
|
* before this one can decline an unauthenticated upgrade).
|
|
1446
1461
|
*/
|
|
1447
1462
|
export declare interface WebSocketServerOptions {
|
|
1463
|
+
readonly emitter: EmitterInterface<ServerEventMap>;
|
|
1448
1464
|
readonly path?: string;
|
|
1449
1465
|
readonly subprotocol?: string;
|
|
1450
1466
|
}
|
|
@@ -18,6 +18,7 @@ import { MiddlewareHandler } from '@orkestrel/server';
|
|
|
18
18
|
import { NodeWebSocketInterface } from '@orkestrel/websocket';
|
|
19
19
|
import { RouteContext } from '@orkestrel/router';
|
|
20
20
|
import { RouteInput } from '@orkestrel/router';
|
|
21
|
+
import { ServerEventMap } from '@orkestrel/server';
|
|
21
22
|
import { StreamInterface } from '@orkestrel/server';
|
|
22
23
|
import { TokenSecret } from '@orkestrel/server';
|
|
23
24
|
import { UpgradeHandler } from '@orkestrel/server';
|
|
@@ -242,7 +243,7 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
242
243
|
* const routes = createMCPRoutes(createMCPLegacy(mcp)) // both eras; pass `mcp` for modern only
|
|
243
244
|
* ```
|
|
244
245
|
*/
|
|
245
|
-
export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPTransportOptions<TState>):
|
|
246
|
+
export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPTransportOptions<TState>): ReadonlyArray<RouteInput<string, TState>>;
|
|
246
247
|
|
|
247
248
|
/**
|
|
248
249
|
* Create the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
|
|
@@ -445,13 +446,21 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
445
446
|
* as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
|
|
446
447
|
* ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
|
|
447
448
|
* escaping the (async) message pump.
|
|
449
|
+
* - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
|
|
450
|
+
* `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles
|
|
451
|
+
* at once and each client reads a clean goodbye. Node detaches an upgraded socket from the
|
|
452
|
+
* connection set the spine's own close walks, so the claimant is the only thing that can end
|
|
453
|
+
* it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and
|
|
454
|
+
* then have the connection cut mid-protocol. A socket the peer already dropped is gone from
|
|
455
|
+
* the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
|
|
448
456
|
*
|
|
449
457
|
* It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade
|
|
450
458
|
* handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated
|
|
451
459
|
* upgrade so it never reaches this pump.
|
|
452
460
|
*
|
|
453
461
|
* @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
|
|
454
|
-
* @param options -
|
|
462
|
+
* @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its
|
|
463
|
+
* sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
|
|
455
464
|
* (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
|
|
456
465
|
* @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
|
|
457
466
|
*
|
|
@@ -461,10 +470,10 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
461
470
|
* import { createWebSocketServer } from '@src/server'
|
|
462
471
|
*
|
|
463
472
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
464
|
-
* server.upgrade(createWebSocketServer(mcp
|
|
473
|
+
* server.upgrade(createWebSocketServer(mcp, { emitter: server.emitter })) // ws://…/mcp
|
|
465
474
|
* ```
|
|
466
475
|
*/
|
|
467
|
-
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options
|
|
476
|
+
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
|
|
468
477
|
|
|
469
478
|
/**
|
|
470
479
|
* Decode one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
|
|
@@ -1427,10 +1436,16 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1427
1436
|
}
|
|
1428
1437
|
|
|
1429
1438
|
/**
|
|
1430
|
-
* Options for `createWebSocketServer` —
|
|
1431
|
-
* subprotocol negotiated.
|
|
1439
|
+
* Options for `createWebSocketServer` — the spine lifecycle the ingress follows, plus where
|
|
1440
|
+
* the WebSocket upgrade is accepted and the subprotocol negotiated.
|
|
1432
1441
|
*
|
|
1433
1442
|
* @remarks
|
|
1443
|
+
* - `emitter` — the emitter of the `@orkestrel/server` spine this handler is registered on
|
|
1444
|
+
* (`server.emitter`). REQUIRED: on its `stop` event the handler closes every socket it
|
|
1445
|
+
* still owns with the RFC 6455 close handshake, so the spine's drain settles at once. An
|
|
1446
|
+
* upgraded socket is detached from the connection set the spine's own close walks, so
|
|
1447
|
+
* nothing but the claimant can end it — leave it open and `stop()` spends its whole
|
|
1448
|
+
* `drain` budget and then cuts the connection mid-protocol.
|
|
1434
1449
|
* - `path` — the request path the upgrade handler CLAIMS; defaults to
|
|
1435
1450
|
* {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`, the same path the HTTP
|
|
1436
1451
|
* transport mounts at). A protocol-upgrade request to any OTHER path is DECLINED
|
|
@@ -1445,6 +1460,7 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1445
1460
|
* before this one can decline an unauthenticated upgrade).
|
|
1446
1461
|
*/
|
|
1447
1462
|
export declare interface WebSocketServerOptions {
|
|
1463
|
+
readonly emitter: EmitterInterface<ServerEventMap>;
|
|
1448
1464
|
readonly path?: string;
|
|
1449
1465
|
readonly subprotocol?: string;
|
|
1450
1466
|
}
|
package/dist/src/server/index.js
CHANGED
|
@@ -1601,13 +1601,21 @@ function createHTTPClientTransport(options) {
|
|
|
1601
1601
|
* as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
|
|
1602
1602
|
* ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
|
|
1603
1603
|
* escaping the (async) message pump.
|
|
1604
|
+
* - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
|
|
1605
|
+
* `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles
|
|
1606
|
+
* at once and each client reads a clean goodbye. Node detaches an upgraded socket from the
|
|
1607
|
+
* connection set the spine's own close walks, so the claimant is the only thing that can end
|
|
1608
|
+
* it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and
|
|
1609
|
+
* then have the connection cut mid-protocol. A socket the peer already dropped is gone from
|
|
1610
|
+
* the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
|
|
1604
1611
|
*
|
|
1605
1612
|
* It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade
|
|
1606
1613
|
* handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated
|
|
1607
1614
|
* upgrade so it never reaches this pump.
|
|
1608
1615
|
*
|
|
1609
1616
|
* @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
|
|
1610
|
-
* @param options -
|
|
1617
|
+
* @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its
|
|
1618
|
+
* sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
|
|
1611
1619
|
* (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
|
|
1612
1620
|
* @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
|
|
1613
1621
|
*
|
|
@@ -1617,12 +1625,16 @@ function createHTTPClientTransport(options) {
|
|
|
1617
1625
|
* import { createWebSocketServer } from '@src/server'
|
|
1618
1626
|
*
|
|
1619
1627
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
1620
|
-
* server.upgrade(createWebSocketServer(mcp
|
|
1628
|
+
* server.upgrade(createWebSocketServer(mcp, { emitter: server.emitter })) // ws://…/mcp
|
|
1621
1629
|
* ```
|
|
1622
1630
|
*/
|
|
1623
1631
|
function createWebSocketServer(mcp, options) {
|
|
1624
|
-
const path = options
|
|
1625
|
-
const subprotocol = options
|
|
1632
|
+
const path = options.path ?? "/mcp";
|
|
1633
|
+
const subprotocol = options.subprotocol ?? "mcp";
|
|
1634
|
+
const live = /* @__PURE__ */ new Set();
|
|
1635
|
+
options.emitter.on("stop", () => {
|
|
1636
|
+
for (const transport of live) transport.close();
|
|
1637
|
+
});
|
|
1626
1638
|
return (request, socket, head) => {
|
|
1627
1639
|
const upgrade = request.headers["upgrade"];
|
|
1628
1640
|
if (!isString(upgrade) || upgrade.toLowerCase() !== "websocket") return false;
|
|
@@ -1637,6 +1649,8 @@ function createWebSocketServer(mcp, options) {
|
|
|
1637
1649
|
head,
|
|
1638
1650
|
protocol: subprotocol
|
|
1639
1651
|
}));
|
|
1652
|
+
live.add(transport);
|
|
1653
|
+
transport.emitter.on("close", () => live.delete(transport));
|
|
1640
1654
|
bindServer(mcp, bridgeMessageTransport(transport));
|
|
1641
1655
|
transport.start();
|
|
1642
1656
|
return true;
|