@tmcp/transport-http 0.5.4 → 0.5.5
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 +42 -7
- package/package.json +4 -1
- package/src/index.js +18 -36
- package/src/types/index.d.ts +2 -0
- package/src/types/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,38 @@ const transport = new HttpTransport(server, {
|
|
|
81
81
|
});
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### Session Management
|
|
85
|
+
|
|
86
|
+
The HTTP transport supports custom session managers for different deployment scenarios:
|
|
87
|
+
|
|
88
|
+
#### In-Memory Sessions (Default)
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { InMemorySessionManager } from '@tmcp/session-manager';
|
|
92
|
+
|
|
93
|
+
const transport = new HttpTransport(server, {
|
|
94
|
+
sessionManager: new InMemorySessionManager(), // Default behavior
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Redis Sessions (Multi-Server/Serverless)
|
|
99
|
+
|
|
100
|
+
For deployments across multiple servers or serverless environments where sessions need to be shared:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
import { RedisSessionManager } from '@tmcp/session-manager-redis';
|
|
104
|
+
|
|
105
|
+
const transport = new HttpTransport(server, {
|
|
106
|
+
sessionManager: new RedisSessionManager('redis://localhost:6379'),
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**When to use Redis sessions:**
|
|
111
|
+
|
|
112
|
+
- **Multi-server deployments**: When your application runs on multiple servers and clients might connect to different instances
|
|
113
|
+
- **Serverless deployments**: When your transport is deployed on serverless platforms where instances are ephemeral (attention, serverless environment generally kills SSE request after a not-so-long amount of time, this means notification from the server will not reach the client after the shutdown)
|
|
114
|
+
- **Load balancing**: When using load balancers that might route requests to different server instances
|
|
115
|
+
|
|
84
116
|
## Features
|
|
85
117
|
|
|
86
118
|
- **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication
|
|
@@ -116,6 +148,7 @@ interface HttpTransportOptions {
|
|
|
116
148
|
getSessionId: () => string; // Custom session ID generator
|
|
117
149
|
path?: string; // MCP endpoint path (default: '/mcp')
|
|
118
150
|
oauth?: OAuth; // an oauth provider generated from @tmcp/auth
|
|
151
|
+
sessionManager?: SessionManager; // Custom session manager (default: InMemorySessionManager)
|
|
119
152
|
}
|
|
120
153
|
```
|
|
121
154
|
|
|
@@ -262,7 +295,7 @@ Deno.serve({ port: 3000 }, async (req) => {
|
|
|
262
295
|
|
|
263
296
|
### `srvx`
|
|
264
297
|
|
|
265
|
-
If you want
|
|
298
|
+
If you want the same experience across Deno, Bun, and Node.js, you can use [srvx](https://srvx.h3.dev/).
|
|
266
299
|
|
|
267
300
|
```js
|
|
268
301
|
import { McpServer } from 'tmcp';
|
|
@@ -272,12 +305,14 @@ import { serve } from 'srvx';
|
|
|
272
305
|
const server = new McpServer(/* ... */);
|
|
273
306
|
const transport = new HttpTransport(server);
|
|
274
307
|
|
|
275
|
-
serve(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
308
|
+
serve({
|
|
309
|
+
async fetch(req) {
|
|
310
|
+
const response = await transport.respond(req);
|
|
311
|
+
if (response === null) {
|
|
312
|
+
return new Response('Not Found', { status: 404 });
|
|
313
|
+
}
|
|
314
|
+
return response;
|
|
315
|
+
},
|
|
281
316
|
});
|
|
282
317
|
```
|
|
283
318
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmcp/transport-http",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Transport for TMCP using HTTP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
"url": "git+https://github.com/paoloricciuti/tmcp.git",
|
|
29
29
|
"directory": "packages/transport-http"
|
|
30
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@tmcp/session-manager": "^0.1.0"
|
|
33
|
+
},
|
|
31
34
|
"devDependencies": {
|
|
32
35
|
"@modelcontextprotocol/sdk": "^1.17.1",
|
|
33
36
|
"@remix-run/node-fetch-server": "^0.8.0",
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { AuthInfo, McpServer } from "tmcp";
|
|
3
3
|
* @import { OAuth } from "@tmcp/auth";
|
|
4
|
+
* @import { SessionManager } from "@tmcp/session-manager";
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -19,11 +20,13 @@
|
|
|
19
20
|
* getSessionId?: () => string
|
|
20
21
|
* path?: string
|
|
21
22
|
* oauth?: OAuth<"built">
|
|
22
|
-
* cors?: CorsConfig | boolean
|
|
23
|
+
* cors?: CorsConfig | boolean,
|
|
24
|
+
* sessionManager?: SessionManager
|
|
23
25
|
* }} HttpTransportOptions
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
29
|
+
import { InMemorySessionManager } from '@tmcp/session-manager';
|
|
27
30
|
export class HttpTransport {
|
|
28
31
|
/**
|
|
29
32
|
* @type {McpServer<any>}
|
|
@@ -40,15 +43,6 @@ export class HttpTransport {
|
|
|
40
43
|
*/
|
|
41
44
|
#path;
|
|
42
45
|
|
|
43
|
-
/**
|
|
44
|
-
* @type {Map<string, ReadableStreamDefaultController>}
|
|
45
|
-
*/
|
|
46
|
-
#session = new Map();
|
|
47
|
-
/**
|
|
48
|
-
* @type {Map<string, ReadableStream>}
|
|
49
|
-
*/
|
|
50
|
-
#streams = new Map();
|
|
51
|
-
|
|
52
46
|
/**
|
|
53
47
|
* @type {AsyncLocalStorage<ReadableStreamDefaultController | undefined>}
|
|
54
48
|
*/
|
|
@@ -73,6 +67,7 @@ export class HttpTransport {
|
|
|
73
67
|
path = '/mcp',
|
|
74
68
|
oauth,
|
|
75
69
|
cors,
|
|
70
|
+
sessionManager = new InMemorySessionManager(),
|
|
76
71
|
} = options ?? {
|
|
77
72
|
getSessionId: () => crypto.randomUUID(),
|
|
78
73
|
};
|
|
@@ -81,7 +76,7 @@ export class HttpTransport {
|
|
|
81
76
|
this.#oauth = oauth;
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
this.#options = { getSessionId, path, cors };
|
|
79
|
+
this.#options = { getSessionId, path, cors, sessionManager };
|
|
85
80
|
this.#path = path;
|
|
86
81
|
this.#server.on('send', ({ request, context: { sessions } }) => {
|
|
87
82
|
// use the current controller if the request has an id (it means it's a request and not a notification)
|
|
@@ -96,15 +91,10 @@ export class HttpTransport {
|
|
|
96
91
|
);
|
|
97
92
|
return;
|
|
98
93
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
'data: ' + JSON.stringify(request) + '\n\n',
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
94
|
+
this.#options.sessionManager.send(
|
|
95
|
+
sessions,
|
|
96
|
+
'data: ' + JSON.stringify(request) + '\n\n',
|
|
97
|
+
);
|
|
108
98
|
});
|
|
109
99
|
}
|
|
110
100
|
|
|
@@ -195,12 +185,7 @@ export class HttpTransport {
|
|
|
195
185
|
* @param {string} session_id
|
|
196
186
|
*/
|
|
197
187
|
#handle_delete(session_id) {
|
|
198
|
-
|
|
199
|
-
if (controller) {
|
|
200
|
-
controller.close();
|
|
201
|
-
this.#session.delete(session_id);
|
|
202
|
-
this.#streams.delete(session_id);
|
|
203
|
-
}
|
|
188
|
+
this.#options.sessionManager.delete(session_id);
|
|
204
189
|
return new Response(null, {
|
|
205
190
|
status: 204,
|
|
206
191
|
headers: {
|
|
@@ -214,12 +199,11 @@ export class HttpTransport {
|
|
|
214
199
|
* @param {string} session_id
|
|
215
200
|
* @returns
|
|
216
201
|
*/
|
|
217
|
-
#handle_get(session_id) {
|
|
218
|
-
const sessions = this.#
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (existing_stream) {
|
|
202
|
+
async #handle_get(session_id) {
|
|
203
|
+
const sessions = this.#options.sessionManager;
|
|
204
|
+
// If session already exists, return error
|
|
205
|
+
const existing_session = await sessions.has(session_id);
|
|
206
|
+
if (existing_session) {
|
|
223
207
|
return new Response(
|
|
224
208
|
JSON.stringify({
|
|
225
209
|
jsonrpc: '2.0',
|
|
@@ -243,15 +227,13 @@ export class HttpTransport {
|
|
|
243
227
|
// Create new long-lived stream for notifications
|
|
244
228
|
const stream = new ReadableStream({
|
|
245
229
|
start(controller) {
|
|
246
|
-
sessions.
|
|
230
|
+
sessions.create(session_id, controller);
|
|
247
231
|
},
|
|
248
232
|
cancel() {
|
|
249
233
|
sessions.delete(session_id);
|
|
250
|
-
streams.delete(session_id);
|
|
251
234
|
},
|
|
252
235
|
});
|
|
253
236
|
|
|
254
|
-
streams.set(session_id, stream);
|
|
255
237
|
return new Response(stream, {
|
|
256
238
|
headers: {
|
|
257
239
|
'Content-Type': 'text/event-stream',
|
|
@@ -455,7 +437,7 @@ export class HttpTransport {
|
|
|
455
437
|
}
|
|
456
438
|
// Handle GET request - establish long-lived connection for notifications
|
|
457
439
|
else if (method === 'GET') {
|
|
458
|
-
response = this.#handle_get(session_id);
|
|
440
|
+
response = await this.#handle_get(session_id);
|
|
459
441
|
}
|
|
460
442
|
// Handle POST request - process message and respond through event stream
|
|
461
443
|
else if (method === 'POST') {
|
package/src/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare module '@tmcp/transport-http' {
|
|
2
2
|
import type { McpServer } from 'tmcp';
|
|
3
3
|
import type { OAuth } from '@tmcp/auth';
|
|
4
|
+
import type { SessionManager } from '@tmcp/session-manager';
|
|
4
5
|
export class HttpTransport {
|
|
5
6
|
|
|
6
7
|
constructor(server: McpServer<any>, options?: HttpTransportOptions);
|
|
@@ -21,6 +22,7 @@ declare module '@tmcp/transport-http' {
|
|
|
21
22
|
path?: string;
|
|
22
23
|
oauth?: OAuth<"built">;
|
|
23
24
|
cors?: CorsConfig | boolean;
|
|
25
|
+
sessionManager?: SessionManager;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
export {};
|