@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 +60 -19
- package/package.json +8 -7
- package/src/index.js +684 -134
- package/src/request-headers.js +251 -0
- package/src/types/index.d.ts +24 -1
- package/src/types/index.d.ts.map +3 -1
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.
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return response;
|
|
61
|
-
}
|
|
62
|
-
|
|
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
|
-
- **🔄
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
50
|
-
"@tmcp/auth": "^0.
|
|
51
|
-
"tmcp": "^1.
|
|
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"
|