@ttoss/http-server-mcp 0.40.0 → 0.41.0

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 CHANGED
@@ -676,6 +676,7 @@ Creates a Koa router configured to handle MCP protocol requests.
676
676
  - `createMcpServer` (`McpServerFactory`) — Per-request factory serving the `2026-07-28` revision; see [Serving the `2026-07-28` revision](#serving-the-2026-07-28-revision)
677
677
  - `bus` (`ServerEventBus`) — Change-event bus `subscriptions/listen` streams subscribe to (default: in-process); see [Change notifications and shutdown](#change-notifications-and-shutdown)
678
678
  - `keepAliveMs` (`number`) — SSE keepalive interval for `2026-07-28` streams (default: `15000`)
679
+ - `streaming` (`boolean`) — `false` on a host that buffers the response (AWS Lambda behind API Gateway): no change notifications are advertised, so `subscriptions/listen` completes at once (default: `true`); see [Hosting without streaming](#hosting-without-streaming-aws-lambda)
679
680
  - `apiBaseUrl` (`string`) — Base URL prepended to relative paths in `apiCall`
680
681
  - `getApiHeaders` (`(ctx: Context) => Record<string, string>`) — Return headers to inject into every `apiCall` for this request
681
682
  - `auth` (`McpAuthOptions`) — OAuth/JWT authentication; see [Authentication](#authentication)
@@ -1052,6 +1053,19 @@ process.on('SIGTERM', async () => {
1052
1053
 
1053
1054
  The default bus is in-process, so `notify` reaches the streams this process holds. Behind several replicas, pass a `bus` implementing `ServerEventBus` over a shared pub/sub. Keep `keepAliveMs` below the idle timeout of anything in front of the server, or a quiet stream is cut there. 2025-era traffic is served statelessly, so it holds no stream to notify.
1054
1055
 
1056
+ ### Hosting without streaming (AWS Lambda)
1057
+
1058
+ A host that buffers the whole response — AWS Lambda behind API Gateway, or any runtime without response streaming — never delivers a stream that stays open: the invocation dies at its timeout, and the client reopens the stream at once. Pass `streaming: false` there:
1059
+
1060
+ ```typescript
1061
+ const mcpRouter = createMcpRouter(buildServer(), {
1062
+ createMcpServer: buildServer,
1063
+ streaming: false,
1064
+ });
1065
+ ```
1066
+
1067
+ The router then stops every server instance it serves from advertising change notifications: `listChanged` for tools, prompts and resources, and `resources.subscribe`. `McpServer` turns them on by default for each kind you register, which is what makes the SDK hold a `subscriptions/listen` open; with nothing advertised, a listen is acknowledged and completed in the same response. A kind you register nothing for stays unadvertised. Nothing is lost on such a host: no process outlives the request to send a notification, so `notify` and `close()` reach no client.
1068
+
1055
1069
  **Supported HTTP methods:**
1056
1070
 
1057
1071
  - `POST /mcp` - Send JSON-RPC requests/notifications
package/dist/index.cjs CHANGED
@@ -55,6 +55,49 @@ var NO_NOTIFIER = {
55
55
  resourceUpdated: () => {}
56
56
  };
57
57
  /**
58
+ * Stops the server advertising change notifications for the tools, prompts
59
+ * and resources it serves, keeping every other capability as registered.
60
+ *
61
+ * `McpServer` advertises `listChanged: true` for each kind the moment one is
62
+ * registered, and the SDK then honours a `2026-07-28` `subscriptions/listen`
63
+ * with an SSE stream it never ends. A host that buffers the response until the
64
+ * handler returns — AWS Lambda behind API Gateway — delivers nothing and kills
65
+ * the invocation at its timeout, and the client reopens the stream at once, so
66
+ * each open session keeps one invocation busy for good (measured on a
67
+ * production Lambda in 2026-10: one 30 s timeout per session every ~30 s).
68
+ * With nothing advertised the SDK acknowledges the
69
+ * listen and completes it in the same response. A kind with nothing
70
+ * registered stays unadvertised, since advertising it would answer its `list`
71
+ * call with "method not found".
72
+ */
73
+ var withoutChangeNotifications = product => {
74
+ const server = product instanceof _modelcontextprotocol_server.McpServer ? product.server : product;
75
+ const {
76
+ tools,
77
+ prompts,
78
+ resources
79
+ } = server.getCapabilities();
80
+ const off = {};
81
+ if (tools) off.tools = {
82
+ listChanged: false
83
+ };
84
+ if (prompts) off.prompts = {
85
+ listChanged: false
86
+ };
87
+ if (resources) off.resources = {
88
+ listChanged: false,
89
+ subscribe: false
90
+ };
91
+ server.registerCapabilities(off);
92
+ return product;
93
+ };
94
+ /** `factory`, with each instance it builds passed through {@link withoutChangeNotifications}. */
95
+ var servingNoChangeNotifications = factory => {
96
+ return factory && (async ctx => {
97
+ return withoutChangeNotifications(await factory(ctx));
98
+ });
99
+ };
100
+ /**
58
101
  * Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
59
102
  * missing header, which the classifier would read as "header present but
60
103
  * empty" rather than absent.
@@ -141,10 +184,13 @@ var createMcpRequestServer = ({
141
184
  server,
142
185
  sessionIdGenerator,
143
186
  bus,
144
- keepAliveMs
187
+ keepAliveMs,
188
+ streaming = true
145
189
  }) => {
146
190
  const isStateful = sessionIdGenerator !== void 0;
147
- const modern = createMcpServer ? (0, _modelcontextprotocol_server.createMcpHandler)(createMcpServer, {
191
+ if (!streaming) withoutChangeNotifications(server);
192
+ const factory = streaming ? createMcpServer : servingNoChangeNotifications(createMcpServer);
193
+ const modern = factory ? (0, _modelcontextprotocol_server.createMcpHandler)(factory, {
148
194
  legacy: "reject",
149
195
  bus,
150
196
  keepAliveMs
@@ -1644,6 +1690,7 @@ var createMcpRouter = (server, options = {}) => {
1644
1690
  createMcpServer,
1645
1691
  bus,
1646
1692
  keepAliveMs,
1693
+ streaming,
1647
1694
  apiBaseUrl,
1648
1695
  getApiHeaders,
1649
1696
  auth
@@ -1654,7 +1701,8 @@ var createMcpRouter = (server, options = {}) => {
1654
1701
  sessionIdGenerator,
1655
1702
  createMcpServer,
1656
1703
  bus,
1657
- keepAliveMs
1704
+ keepAliveMs,
1705
+ streaming
1658
1706
  });
1659
1707
  const router = new _ttoss_http_server.Router();
1660
1708
  let authCheck;
package/dist/index.d.cts CHANGED
@@ -1707,6 +1707,18 @@ export interface McpRouterOptions {
1707
1707
  * @default 15000
1708
1708
  */
1709
1709
  keepAliveMs?: number;
1710
+ /**
1711
+ * Whether the host delivers a response while the handler is still writing
1712
+ * it. Set `false` on a host that buffers the whole response — AWS Lambda
1713
+ * behind API Gateway, or any serverless runtime without response streaming.
1714
+ * The router then stops every instance it serves from advertising change
1715
+ * notifications (`listChanged` for tools, prompts and resources, and
1716
+ * `resources.subscribe`), so a `2026-07-28` `subscriptions/listen` is
1717
+ * acknowledged and completed at once instead of being held until the
1718
+ * platform's timeout. `notify` then reaches no client.
1719
+ * @default true
1720
+ */
1721
+ streaming?: boolean;
1710
1722
  /**
1711
1723
  * Base URL prepended to relative paths passed to `apiCall` (paths starting
1712
1724
  * with `/`). Tool handlers can then call `apiCall('GET', '/resource')` without
package/dist/index.d.mts CHANGED
@@ -1708,6 +1708,18 @@ export interface McpRouterOptions {
1708
1708
  * @default 15000
1709
1709
  */
1710
1710
  keepAliveMs?: number;
1711
+ /**
1712
+ * Whether the host delivers a response while the handler is still writing
1713
+ * it. Set `false` on a host that buffers the whole response — AWS Lambda
1714
+ * behind API Gateway, or any serverless runtime without response streaming.
1715
+ * The router then stops every instance it serves from advertising change
1716
+ * notifications (`listChanged` for tools, prompts and resources, and
1717
+ * `resources.subscribe`), so a `2026-07-28` `subscriptions/listen` is
1718
+ * acknowledged and completed at once instead of being held until the
1719
+ * platform's timeout. `notify` then reaches no client.
1720
+ * @default true
1721
+ */
1722
+ streaming?: boolean;
1711
1723
  /**
1712
1724
  * Base URL prepended to relative paths passed to `apiCall` (paths starting
1713
1725
  * with `/`). Tool handlers can then call `apiCall('GET', '/resource')` without
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { Router } from "@ttoss/http-server";
5
5
  import { authMiddleware } from "@ttoss/http-server-auth";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { NodeStreamableHTTPServerTransport, toNodeHandler } from "@modelcontextprotocol/node";
8
- import { InMemoryServerEventBus, McpServer, ProtocolErrorCode, SUPPORTED_PROTOCOL_VERSIONS, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
8
+ import { InMemoryServerEventBus, McpServer, McpServer as McpServer$1, ProtocolErrorCode, SUPPORTED_PROTOCOL_VERSIONS, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
9
9
  import { z } from "zod";
10
10
 
11
11
  //#region src/context.ts
@@ -52,6 +52,49 @@ var NO_NOTIFIER = {
52
52
  resourceUpdated: () => {}
53
53
  };
54
54
  /**
55
+ * Stops the server advertising change notifications for the tools, prompts
56
+ * and resources it serves, keeping every other capability as registered.
57
+ *
58
+ * `McpServer` advertises `listChanged: true` for each kind the moment one is
59
+ * registered, and the SDK then honours a `2026-07-28` `subscriptions/listen`
60
+ * with an SSE stream it never ends. A host that buffers the response until the
61
+ * handler returns — AWS Lambda behind API Gateway — delivers nothing and kills
62
+ * the invocation at its timeout, and the client reopens the stream at once, so
63
+ * each open session keeps one invocation busy for good (measured on a
64
+ * production Lambda in 2026-10: one 30 s timeout per session every ~30 s).
65
+ * With nothing advertised the SDK acknowledges the
66
+ * listen and completes it in the same response. A kind with nothing
67
+ * registered stays unadvertised, since advertising it would answer its `list`
68
+ * call with "method not found".
69
+ */
70
+ var withoutChangeNotifications = product => {
71
+ const server = product instanceof McpServer$1 ? product.server : product;
72
+ const {
73
+ tools,
74
+ prompts,
75
+ resources
76
+ } = server.getCapabilities();
77
+ const off = {};
78
+ if (tools) off.tools = {
79
+ listChanged: false
80
+ };
81
+ if (prompts) off.prompts = {
82
+ listChanged: false
83
+ };
84
+ if (resources) off.resources = {
85
+ listChanged: false,
86
+ subscribe: false
87
+ };
88
+ server.registerCapabilities(off);
89
+ return product;
90
+ };
91
+ /** `factory`, with each instance it builds passed through {@link withoutChangeNotifications}. */
92
+ var servingNoChangeNotifications = factory => {
93
+ return factory && (async ctx => {
94
+ return withoutChangeNotifications(await factory(ctx));
95
+ });
96
+ };
97
+ /**
55
98
  * Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
56
99
  * missing header, which the classifier would read as "header present but
57
100
  * empty" rather than absent.
@@ -138,10 +181,13 @@ var createMcpRequestServer = ({
138
181
  server,
139
182
  sessionIdGenerator,
140
183
  bus,
141
- keepAliveMs
184
+ keepAliveMs,
185
+ streaming = true
142
186
  }) => {
143
187
  const isStateful = sessionIdGenerator !== void 0;
144
- const modern = createMcpServer ? createMcpHandler(createMcpServer, {
188
+ if (!streaming) withoutChangeNotifications(server);
189
+ const factory = streaming ? createMcpServer : servingNoChangeNotifications(createMcpServer);
190
+ const modern = factory ? createMcpHandler(factory, {
145
191
  legacy: "reject",
146
192
  bus,
147
193
  keepAliveMs
@@ -1641,6 +1687,7 @@ var createMcpRouter = (server, options = {}) => {
1641
1687
  createMcpServer,
1642
1688
  bus,
1643
1689
  keepAliveMs,
1690
+ streaming,
1644
1691
  apiBaseUrl,
1645
1692
  getApiHeaders,
1646
1693
  auth
@@ -1651,7 +1698,8 @@ var createMcpRouter = (server, options = {}) => {
1651
1698
  sessionIdGenerator,
1652
1699
  createMcpServer,
1653
1700
  bus,
1654
- keepAliveMs
1701
+ keepAliveMs,
1702
+ streaming
1655
1703
  });
1656
1704
  const router = new Router();
1657
1705
  let authCheck;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,9 +37,9 @@
37
37
  "@modelcontextprotocol/node": "^2.1.1",
38
38
  "@modelcontextprotocol/server": "^2.3.1",
39
39
  "zod": "^4.6.7",
40
- "@ttoss/auth-core": "^0.20.1",
41
40
  "@ttoss/http-server": "^0.14.2",
42
41
  "@ttoss/http-server-auth": "^0.10.2",
42
+ "@ttoss/auth-core": "^0.20.1",
43
43
  "@ttoss/i18n-core": "^0.9.2"
44
44
  },
45
45
  "devDependencies": {