@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.
@@ -1602,13 +1602,21 @@ function createHTTPClientTransport(options) {
1602
1602
  * as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
1603
1603
  * ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
1604
1604
  * escaping the (async) message pump.
1605
+ * - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
1606
+ * `stop` event, closes each one with the RFC 6455 close handshake, so the spine's drain settles
1607
+ * at once and each client reads a clean goodbye. Node detaches an upgraded socket from the
1608
+ * connection set the spine's own close walks, so the claimant is the only thing that can end
1609
+ * it: an ingress that held its sockets open would cost `stop()` the whole `drain` budget and
1610
+ * then have the connection cut mid-protocol. A socket the peer already dropped is gone from
1611
+ * the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
1605
1612
  *
1606
1613
  * It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade
1607
1614
  * handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated
1608
1615
  * upgrade so it never reaches this pump.
1609
1616
  *
1610
1617
  * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
1611
- * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
1618
+ * @param options - The spine's `emitter` (REQUIRED the `stop` event this ingress closes its
1619
+ * sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
1612
1620
  * (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
1613
1621
  * @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
1614
1622
  *
@@ -1618,12 +1626,16 @@ function createHTTPClientTransport(options) {
1618
1626
  * import { createWebSocketServer } from '@src/server'
1619
1627
  *
1620
1628
  * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
1621
- * server.upgrade(createWebSocketServer(mcp)) // an MCP client now connects over ws://…/mcp
1629
+ * server.upgrade(createWebSocketServer(mcp, { emitter: server.emitter })) // ws://…/mcp
1622
1630
  * ```
1623
1631
  */
1624
1632
  function createWebSocketServer(mcp, options) {
1625
- const path = options?.path ?? "/mcp";
1626
- const subprotocol = options?.subprotocol ?? "mcp";
1633
+ const path = options.path ?? "/mcp";
1634
+ const subprotocol = options.subprotocol ?? "mcp";
1635
+ const live = /* @__PURE__ */ new Set();
1636
+ options.emitter.on("stop", () => {
1637
+ for (const transport of live) transport.close();
1638
+ });
1627
1639
  return (request, socket, head) => {
1628
1640
  const upgrade = request.headers["upgrade"];
1629
1641
  if (!(0, _orkestrel_contract.isString)(upgrade) || upgrade.toLowerCase() !== "websocket") return false;
@@ -1638,6 +1650,8 @@ function createWebSocketServer(mcp, options) {
1638
1650
  head,
1639
1651
  protocol: subprotocol
1640
1652
  }));
1653
+ live.add(transport);
1654
+ transport.emitter.on("close", () => live.delete(transport));
1641
1655
  (0, _src_core.bindServer)(mcp, bridgeMessageTransport(transport));
1642
1656
  transport.start();
1643
1657
  return true;