@tmcp/transport-http 0.8.5 → 0.9.0-next.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
@@ -1,6 +1,6 @@
1
1
  # @tmcp/transport-http
2
2
 
3
- An HTTP transport implementation for TMCP (TypeScript Model Context Protocol) servers. This package provides HTTP Streaming based communication for MCP servers over HTTP, enabling web-based clients to interact with your MCP server through standard HTTP requests.
3
+ An HTTP transport implementation for TMCP (TypeScript Model Context Protocol) servers. It supports both initialization-based MCP sessions and the sessionless per-request protocol introduced in MCP `2026-07-28`.
4
4
 
5
5
  ## Installation
6
6
 
@@ -54,14 +54,12 @@ const transport = new HttpTransport(server);
54
54
  import * as http from 'node:http';
55
55
  import { createRequestListener } from '@remix-run/node-fetch-server';
56
56
 
57
- let httpServer = http.createServer(createRequestListener((request)=>{
58
- const response = await transport.respond(request);
59
- if(response){
60
- return response;
61
- }
62
- return new Response(null, { status: 404 });
63
- }));
64
-
57
+ const httpServer = http.createServer(
58
+ createRequestListener(async (request) => {
59
+ const response = await transport.respond(request);
60
+ return response ?? new Response(null, { status: 404 });
61
+ }),
62
+ );
65
63
 
66
64
  httpServer.listen(3000, () => {
67
65
  console.log('MCP HTTP server listening on port 3000');
@@ -74,15 +72,15 @@ httpServer.listen(3000, () => {
74
72
  const transport = new HttpTransport(server, {
75
73
  // Custom MCP endpoint path (default: '/mcp', use null to respond on every path)
76
74
  path: '/api/mcp',
77
- // Custom session ID generation
75
+ // Custom session ID generation for initialization-based clients
78
76
  getSessionId: () => {
79
77
  return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
80
78
  },
81
79
  });
80
+ ```
82
81
 
83
82
  > [!NOTE]
84
83
  > When the transport runs in development mode and you omit the `path` option, a warning is emitted. Future releases will treat an `undefined` path as "respond on every path", so set the property explicitly (for example `path: '/mcp'` or `path: null`) to lock in the behavior you want today.
85
- ```
86
84
 
87
85
  ### With Custom Context
88
86
 
@@ -128,6 +126,8 @@ const response = await transport.respond(req, {
128
126
  });
129
127
  ```
130
128
 
129
+ Handlers can also observe request cancellation through `server.ctx.signal`. The HTTP transport aborts this signal when the incoming request is aborted or the client closes its request-scoped SSE response. Cancellation is cooperative, so handlers should pass the signal to cancellable work or check `signal.aborted` at suitable boundaries.
130
+
131
131
  ### Session Management
132
132
 
133
133
  The HTTP transport supports custom session managers for different deployment scenarios:
@@ -138,6 +138,7 @@ The HTTP transport supports custom session managers for different deployment sce
138
138
  import {
139
139
  InMemoryStreamSessionManager,
140
140
  InMemoryInfoSessionManager,
141
+ InMemorySubscriptionManager,
141
142
  } from '@tmcp/session-manager';
142
143
 
143
144
  const transport = new HttpTransport(server, {
@@ -145,6 +146,7 @@ const transport = new HttpTransport(server, {
145
146
  streams: new InMemoryStreamSessionManager(),
146
147
  info: new InMemoryInfoSessionManager(),
147
148
  },
149
+ subscriptionManager: new InMemorySubscriptionManager(),
148
150
  });
149
151
  ```
150
152
 
@@ -156,6 +158,7 @@ For deployments across multiple servers or serverless environments where session
156
158
  import {
157
159
  RedisStreamSessionManager,
158
160
  RedisInfoSessionManager,
161
+ RedisSubscriptionManager,
159
162
  } from '@tmcp/session-manager-redis';
160
163
 
161
164
  const transport = new HttpTransport(server, {
@@ -163,6 +166,7 @@ const transport = new HttpTransport(server, {
163
166
  streams: new RedisStreamSessionManager('redis://localhost:6379'),
164
167
  info: new RedisInfoSessionManager('redis://localhost:6379'),
165
168
  },
169
+ subscriptionManager: new RedisSubscriptionManager('redis://localhost:6379'),
166
170
  });
167
171
  ```
168
172
 
@@ -175,13 +179,13 @@ const transport = new HttpTransport(server, {
175
179
  ## Features
176
180
 
177
181
  - **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication
178
- - **🔄 Session Management**: Maintains client sessions with automatic session ID generation
182
+ - **🔄 Dual Protocol Support**: Supports both initialization-based sessions and sessionless per-request calls
179
183
  - **📡 Streaming Responses**: Supports streaming responses through SSE
180
184
  - **🛤️ Configurable Path**: Customizable MCP endpoint path with automatic filtering (set `path` to `null` to respond everywhere)
181
185
  - **🔧 Framework Agnostic**: Works with any HTTP server framework (Fastify, Bun, Deno, etc.)
182
186
  - **⚡ Real-time Updates**: Server can push notifications and updates to connected clients
183
187
  - **🛡️ Error Handling**: Graceful error handling for malformed requests
184
- - **🔀 Multiple HTTP Methods**: Supports GET (notifications), POST (messages), and DELETE (disconnect)
188
+ - **🔀 Multiple HTTP Methods**: Supports legacy GET/DELETE session behavior alongside POST-only per-request calls
185
189
  - **🧠 Session Metadata**: Automatically persists client capabilities, info, and log levels and exposes them via `server.ctx.sessionInfo`
186
190
 
187
191
  ## API
@@ -205,18 +209,25 @@ Creates a new HTTP transport instance.
205
209
 
206
210
  ```typescript
207
211
  interface HttpTransportOptions {
208
- getSessionId: () => string; // Custom session ID generator
212
+ getSessionId?: () => string; // Custom session ID generator
209
213
  path?: string | null; // MCP endpoint path (default: '/mcp', null responds on every path)
210
214
  oauth?: OAuth; // an oauth provider generated from @tmcp/auth
215
+ cors?: CorsConfig | boolean; // CORS configuration
216
+ allowedOrigins?: string | string[] | true; // Cross-origin request security policy
211
217
  sessionManager?: {
212
218
  streams?: StreamSessionManager;
213
219
  info?: InfoSessionManager;
214
220
  }; // Provide custom managers; defaults to in-memory implementations
221
+ subscriptionManager?: SubscriptionManager; // Per-request subscription routing
215
222
  disableSse?: boolean; // Disable SSE stream endpoint (GET returns 405)
216
223
  }
217
224
  ```
218
225
 
219
- If you omit `sessionManager` the transport creates `InMemoryStreamSessionManager` and `InMemoryInfoSessionManager` instances for you. You can override either field independently (for example, Redis streams with in-memory metadata during development).
226
+ If you omit `sessionManager` the transport creates `InMemoryStreamSessionManager` and `InMemoryInfoSessionManager` instances for initialization-based clients. Per-request clients never create, read, or return session IDs. You can override either field independently (for example, Redis streams with in-memory metadata during development).
227
+
228
+ If you omit `subscriptionManager`, the transport creates an `InMemorySubscriptionManager`. Supply a distributed implementation when `subscriptions/listen` requests and change publication may reach different server instances.
229
+
230
+ Requests without an `Origin` header and same-origin browser requests are accepted. When `allowedOrigins` is omitted, cross-origin requests are also accepted and the transport warns on the first one. Set an explicit origin or origin list to restrict access, `[]` to reject every cross-origin request, or `true` to explicitly allow every origin without a warning. This request security policy is intentionally independent from `cors`, which only controls response headers.
220
231
 
221
232
  ### Disabling SSE Streams
222
233
 
@@ -228,7 +239,7 @@ const transport = new HttpTransport(server, {
228
239
  });
229
240
  ```
230
241
 
231
- When `disableSse` is `true`, GET requests to the MCP endpoint will return a `405 Method Not Allowed` response. Clients can still communicate via POST requests, but won't receive server-initiated notifications.
242
+ When `disableSse` is `true`, GET requests to the MCP endpoint return `405 Method Not Allowed`, so legacy session-negotiated clients cannot receive server-initiated notifications through a GET stream. Per-request clients can still open modern `subscriptions/listen` streams through POST.
232
243
 
233
244
  #### Methods
234
245
 
@@ -251,15 +262,45 @@ Processes an HTTP request and returns a Response with Server-Sent Events, or nul
251
262
  - **GET**: Establishes long-lived connections for server notifications
252
263
  - **DELETE**: Disconnects sessions and cleans up resources
253
264
 
265
+ ##### `closeSubscription(response)`
266
+
267
+ Gracefully complete the per-request subscription represented by the exact `Response` returned from `respond()`:
268
+
269
+ ```javascript
270
+ const response = await transport.respond(listen_request);
271
+ await transport.closeSubscription(response);
272
+ ```
273
+
274
+ Each listen POST receives an opaque internal routing origin. `Mcp-Session-Id` is never used as modern subscription identity, even when the caller provides it. Closing the response body cancels the stream; HTTP clients do not send `notifications/cancelled` for `2026-07-28` subscriptions.
275
+
276
+ ##### `close()`
277
+
278
+ Cancel every active per-request subscription owned by this transport.
279
+
254
280
  ## Protocol Details
255
281
 
282
+ ### Per-Request Protocol (`2026-07-28`)
283
+
284
+ Each request or notification uses its own POST. A conforming request includes:
285
+
286
+ - `MCP-Protocol-Version`, matching `params._meta.io.modelcontextprotocol/protocolVersion`.
287
+ - `Mcp-Method`, matching the JSON-RPC method.
288
+ - `Mcp-Name` for `tools/call`, `prompts/get`, and `resources/read`, matching `params.name` or `params.uri`.
289
+ - Any recognized `Mcp-Param-*` values declared by `x-mcp-header` annotations in a tool's input JSON Schema.
290
+
291
+ `Mcp-Name` and parameter headers use the specification's `=?base64?...?=` sentinel when a value cannot be represented safely as plain ASCII. Header names are compared case-insensitively; values remain case-sensitive.
292
+
293
+ Successful requests use request-scoped SSE and include `X-Accel-Buffering: no`. Accepted notifications return HTTP 202 without a body. Header mismatches and unsupported versions return HTTP 400; unknown or unavailable methods return HTTP 404. Per-request traffic ignores `Mcp-Session-Id` and `Last-Event-ID`, never returns a session ID, and returns HTTP 405 for GET and DELETE.
294
+
295
+ Initialization-based clients continue to use the behavior documented below on the same transport. Their session header, GET stream, DELETE lifecycle, and JSON-RPC response POSTs remain supported.
296
+
256
297
  ### HTTP Methods
257
298
 
258
299
  The transport supports three HTTP methods:
259
300
 
260
301
  #### POST - Message Processing
261
302
 
262
- Clients send JSON-RPC messages via HTTP POST requests:
303
+ Clients send JSON-RPC messages via HTTP POST requests. Each POST accepts one JSON-RPC message; batch arrays are rejected with `-32600 Invalid Request`. The following example shows initialization-based session behavior:
263
304
 
264
305
  ```http
265
306
  POST /mcp HTTP/1.1
@@ -320,11 +361,11 @@ mcp-session-id: session-to-disconnect
320
361
  Response:
321
362
 
322
363
  ```http
323
- HTTP/1.1 204 No Content
364
+ HTTP/1.1 200 OK
324
365
  mcp-session-id: session-to-disconnect
325
366
  ```
326
367
 
327
- ### Session Management
368
+ ### Legacy Session Management
328
369
 
329
370
  - **Session ID Header**: `mcp-session-id`
330
371
  - **Automatic Generation**: If no session ID is provided, one is generated automatically
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@tmcp/transport-http",
3
- "version": "0.8.5",
3
+ "version": "0.9.0-next.0",
4
+ "license": "MIT",
4
5
  "description": "Transport for TMCP using HTTP",
5
6
  "type": "module",
6
7
  "main": "src/index.js",
@@ -16,8 +17,8 @@
16
17
  "src/**/*"
17
18
  ],
18
19
  "peerDependencies": {
19
- "tmcp": "^1.18.0",
20
- "@tmcp/auth": "^0.3.3 || ^0.4.0"
20
+ "tmcp": "^1.20.0-next.0 || ^1.20.0",
21
+ "@tmcp/auth": "^0.3.3 || ^0.4.0 || ^0.5.0-next.0"
21
22
  },
22
23
  "peerDependenciesMeta": {
23
24
  "@tmcp/auth": {
@@ -36,7 +37,7 @@
36
37
  },
37
38
  "dependencies": {
38
39
  "esm-env": "^1.2.2",
39
- "@tmcp/session-manager": "^0.2.1"
40
+ "@tmcp/session-manager": "^0.3.0-next.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@modelcontextprotocol/sdk": "^1.17.1",
@@ -46,9 +47,9 @@
46
47
  "publint": "^0.3.12",
47
48
  "valibot": "^1.1.0",
48
49
  "vitest": "^4.0.0",
49
- "@tmcp/adapter-valibot": "^0.1.5",
50
- "@tmcp/auth": "^0.4.0",
51
- "tmcp": "^1.19.3"
50
+ "@tmcp/adapter-valibot": "^0.1.6",
51
+ "@tmcp/auth": "^0.5.0-next.0",
52
+ "tmcp": "^1.20.0-next.0"
52
53
  },
53
54
  "publishConfig": {
54
55
  "access": "public"