@tmcp/transport-http 0.1.1 → 0.2.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +101 -42
- package/package.json +3 -3
- package/src/index.d.ts +4 -2
- package/src/index.js +173 -17
package/CHANGELOG.md
CHANGED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Paolo Ricciuti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ server.tool(
|
|
|
50
50
|
},
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
// Create the HTTP transport
|
|
53
|
+
// Create the HTTP transport (defaults to '/mcp' path)
|
|
54
54
|
const transport = new HttpTransport(server);
|
|
55
55
|
|
|
56
56
|
// Use with your preferred HTTP server
|
|
@@ -58,25 +58,32 @@ const transport = new HttpTransport(server);
|
|
|
58
58
|
import { createServer } from 'http';
|
|
59
59
|
|
|
60
60
|
const httpServer = createServer(async (req, res) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
61
|
+
try {
|
|
62
|
+
let body = '';
|
|
63
|
+
req.on('data', (chunk) => (body += chunk));
|
|
64
|
+
req.on('end', async () => {
|
|
65
|
+
const request = new Request(`http://localhost${req.url}`, {
|
|
66
|
+
method: req.method,
|
|
67
|
+
headers: req.headers,
|
|
68
|
+
body: req.method === 'POST' ? body : undefined,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const response = await transport.respond(request);
|
|
72
|
+
|
|
73
|
+
// If response is null, the request wasn't for MCP
|
|
74
|
+
if (response === null) {
|
|
75
|
+
res.statusCode = 404;
|
|
76
|
+
res.end('Not Found');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Copy response to Node.js response
|
|
81
|
+
response.headers.forEach((value, key) => {
|
|
82
|
+
res.setHeader(key, value);
|
|
83
|
+
});
|
|
84
|
+
res.statusCode = response.status;
|
|
79
85
|
|
|
86
|
+
if (response.body) {
|
|
80
87
|
const reader = response.body.getReader();
|
|
81
88
|
const pump = async () => {
|
|
82
89
|
const { done, value } = await reader.read();
|
|
@@ -88,14 +95,13 @@ const httpServer = createServer(async (req, res) => {
|
|
|
88
95
|
pump();
|
|
89
96
|
};
|
|
90
97
|
pump();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
res.
|
|
98
|
-
res.end('Not Found');
|
|
98
|
+
} else {
|
|
99
|
+
res.end();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
res.statusCode = 500;
|
|
104
|
+
res.end('Internal Server Error');
|
|
99
105
|
}
|
|
100
106
|
});
|
|
101
107
|
|
|
@@ -104,12 +110,14 @@ httpServer.listen(3000, () => {
|
|
|
104
110
|
});
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
### With Custom
|
|
113
|
+
### With Custom Configuration
|
|
108
114
|
|
|
109
115
|
```javascript
|
|
110
116
|
const transport = new HttpTransport(server, {
|
|
117
|
+
// Custom MCP endpoint path (default: '/mcp')
|
|
118
|
+
path: '/api/mcp',
|
|
119
|
+
// Custom session ID generation
|
|
111
120
|
getSessionId: () => {
|
|
112
|
-
// Custom session ID generation
|
|
113
121
|
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
114
122
|
},
|
|
115
123
|
});
|
|
@@ -120,9 +128,11 @@ const transport = new HttpTransport(server, {
|
|
|
120
128
|
- **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication
|
|
121
129
|
- **🔄 Session Management**: Maintains client sessions with automatic session ID generation
|
|
122
130
|
- **📡 Streaming Responses**: Supports streaming responses through SSE
|
|
131
|
+
- **🛤️ Configurable Path**: Customizable MCP endpoint path with automatic filtering
|
|
123
132
|
- **🔧 Framework Agnostic**: Works with any HTTP server framework (Fastify, Bun, Deno, etc.)
|
|
124
133
|
- **⚡ Real-time Updates**: Server can push notifications and updates to connected clients
|
|
125
134
|
- **🛡️ Error Handling**: Graceful error handling for malformed requests
|
|
135
|
+
- **🔀 Multiple HTTP Methods**: Supports GET (notifications), POST (messages), and DELETE (disconnect)
|
|
126
136
|
|
|
127
137
|
## API
|
|
128
138
|
|
|
@@ -146,14 +156,15 @@ Creates a new HTTP transport instance.
|
|
|
146
156
|
```typescript
|
|
147
157
|
interface HttpTransportOptions {
|
|
148
158
|
getSessionId: () => string; // Custom session ID generator
|
|
159
|
+
path?: string; // MCP endpoint path (default: '/mcp')
|
|
149
160
|
}
|
|
150
161
|
```
|
|
151
162
|
|
|
152
163
|
#### Methods
|
|
153
164
|
|
|
154
|
-
##### `respond(request: Request): Promise<Response>`
|
|
165
|
+
##### `respond(request: Request): Promise<Response | null>`
|
|
155
166
|
|
|
156
|
-
Processes an HTTP request and returns a Response with Server-Sent Events.
|
|
167
|
+
Processes an HTTP request and returns a Response with Server-Sent Events, or null if the request path doesn't match the configured MCP path.
|
|
157
168
|
|
|
158
169
|
**Parameters:**
|
|
159
170
|
|
|
@@ -161,12 +172,21 @@ Processes an HTTP request and returns a Response with Server-Sent Events.
|
|
|
161
172
|
|
|
162
173
|
**Returns:**
|
|
163
174
|
|
|
164
|
-
- A Response object with SSE stream for ongoing communication
|
|
175
|
+
- A Response object with SSE stream for ongoing communication, or null if the request path doesn't match the MCP endpoint
|
|
176
|
+
|
|
177
|
+
**HTTP Methods:**
|
|
178
|
+
|
|
179
|
+
- **POST**: Processes MCP messages and returns short-lived event stream responses
|
|
180
|
+
- **GET**: Establishes long-lived connections for server notifications
|
|
181
|
+
- **DELETE**: Disconnects sessions and cleans up resources
|
|
165
182
|
|
|
166
183
|
## Protocol Details
|
|
167
184
|
|
|
168
|
-
###
|
|
185
|
+
### HTTP Methods
|
|
169
186
|
|
|
187
|
+
The transport supports three HTTP methods:
|
|
188
|
+
|
|
189
|
+
#### POST - Message Processing
|
|
170
190
|
Clients send JSON-RPC messages via HTTP POST requests:
|
|
171
191
|
|
|
172
192
|
```http
|
|
@@ -182,9 +202,27 @@ mcp-session-id: optional-session-id
|
|
|
182
202
|
}
|
|
183
203
|
```
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
Response: Short-lived event stream that closes after sending the response:
|
|
206
|
+
|
|
207
|
+
```http
|
|
208
|
+
HTTP/1.1 200 OK
|
|
209
|
+
Content-Type: text/event-stream
|
|
210
|
+
Cache-Control: no-cache
|
|
211
|
+
mcp-session-id: generated-or-provided-session-id
|
|
212
|
+
|
|
213
|
+
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### GET - Notification Stream
|
|
218
|
+
Establishes long-lived connections for server notifications:
|
|
219
|
+
|
|
220
|
+
```http
|
|
221
|
+
GET /mcp HTTP/1.1
|
|
222
|
+
mcp-session-id: optional-session-id
|
|
223
|
+
```
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
Response: Long-lived event stream for server notifications:
|
|
188
226
|
|
|
189
227
|
```http
|
|
190
228
|
HTTP/1.1 200 OK
|
|
@@ -193,8 +231,23 @@ Cache-Control: no-cache
|
|
|
193
231
|
Connection: keep-alive
|
|
194
232
|
mcp-session-id: generated-or-provided-session-id
|
|
195
233
|
|
|
196
|
-
data: {"jsonrpc":"2.0","
|
|
234
|
+
data: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### DELETE - Session Disconnect
|
|
239
|
+
Disconnects a session and cleans up resources:
|
|
197
240
|
|
|
241
|
+
```http
|
|
242
|
+
DELETE /mcp HTTP/1.1
|
|
243
|
+
mcp-session-id: session-to-disconnect
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Response:
|
|
247
|
+
|
|
248
|
+
```http
|
|
249
|
+
HTTP/1.1 204 No Content
|
|
250
|
+
mcp-session-id: session-to-disconnect
|
|
198
251
|
```
|
|
199
252
|
|
|
200
253
|
### Session Management
|
|
@@ -218,10 +271,11 @@ const transport = new HttpTransport(server);
|
|
|
218
271
|
Bun.serve({
|
|
219
272
|
port: 3000,
|
|
220
273
|
async fetch(req) {
|
|
221
|
-
|
|
222
|
-
|
|
274
|
+
const response = await transport.respond(req);
|
|
275
|
+
if (response === null) {
|
|
276
|
+
return new Response('Not Found', { status: 404 });
|
|
223
277
|
}
|
|
224
|
-
return
|
|
278
|
+
return response;
|
|
225
279
|
},
|
|
226
280
|
});
|
|
227
281
|
```
|
|
@@ -236,10 +290,11 @@ const server = new McpServer(/* ... */);
|
|
|
236
290
|
const transport = new HttpTransport(server);
|
|
237
291
|
|
|
238
292
|
Deno.serve({ port: 3000 }, async (req) => {
|
|
239
|
-
|
|
240
|
-
|
|
293
|
+
const response = await transport.respond(req);
|
|
294
|
+
if (response === null) {
|
|
295
|
+
return new Response('Not Found', { status: 404 });
|
|
241
296
|
}
|
|
242
|
-
return
|
|
297
|
+
return response;
|
|
243
298
|
});
|
|
244
299
|
```
|
|
245
300
|
|
|
@@ -278,6 +333,10 @@ pnpm lint
|
|
|
278
333
|
- [`@tmcp/adapter-zod`](../adapter-zod) - Zod schema adapter
|
|
279
334
|
- [`@tmcp/adapter-valibot`](../adapter-valibot) - Valibot schema adapter
|
|
280
335
|
|
|
336
|
+
## Acknowledgments
|
|
337
|
+
|
|
338
|
+
Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm.
|
|
339
|
+
|
|
281
340
|
## License
|
|
282
341
|
|
|
283
342
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmcp/transport-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Transport for TMCP using HTTP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./package.json": "./package.json"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"tmcp": "^1.
|
|
16
|
+
"tmcp": "^1.7.0"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"tmcp",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"publint": "^0.3.12",
|
|
30
|
-
"tmcp": "^1.
|
|
30
|
+
"tmcp": "^1.7.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
package/src/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {{
|
|
6
6
|
* getSessionId: () => string
|
|
7
|
+
* path?: string
|
|
7
8
|
* }} HttpTransportOptions
|
|
8
9
|
*/
|
|
9
10
|
export class HttpTransport {
|
|
@@ -16,12 +17,13 @@ export class HttpTransport {
|
|
|
16
17
|
/**
|
|
17
18
|
*
|
|
18
19
|
* @param {Request} request
|
|
19
|
-
* @returns
|
|
20
|
+
* @returns {Promise<Response | null>}
|
|
20
21
|
*/
|
|
21
|
-
respond(request: Request): Promise<Response>;
|
|
22
|
+
respond(request: Request): Promise<Response | null>;
|
|
22
23
|
#private;
|
|
23
24
|
}
|
|
24
25
|
export type HttpTransportOptions = {
|
|
25
26
|
getSessionId: () => string;
|
|
27
|
+
path?: string;
|
|
26
28
|
};
|
|
27
29
|
import type { McpServer } from "tmcp";
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {{
|
|
7
7
|
* getSessionId: () => string
|
|
8
|
+
* path?: string
|
|
8
9
|
* }} HttpTransportOptions
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -19,6 +20,11 @@ export class HttpTransport {
|
|
|
19
20
|
*/
|
|
20
21
|
#options;
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @type {string}
|
|
25
|
+
*/
|
|
26
|
+
#path;
|
|
27
|
+
|
|
22
28
|
/**
|
|
23
29
|
* @type {Map<string, ReadableStreamDefaultController>}
|
|
24
30
|
*/
|
|
@@ -38,6 +44,7 @@ export class HttpTransport {
|
|
|
38
44
|
this.#options = options || {
|
|
39
45
|
getSessionId: () => crypto.randomUUID(),
|
|
40
46
|
};
|
|
47
|
+
this.#path = this.#options.path || '/mcp';
|
|
41
48
|
this.#server.on('send', ({ request, context: { sessions } }) => {
|
|
42
49
|
for (let [session_id, controller] of this.#session.entries()) {
|
|
43
50
|
if (sessions === undefined || sessions.includes(session_id)) {
|
|
@@ -49,42 +56,54 @@ export class HttpTransport {
|
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} session_id
|
|
61
|
+
*/
|
|
62
|
+
#handle_delete(session_id) {
|
|
63
|
+
const controller = this.#session.get(session_id);
|
|
64
|
+
if (controller) {
|
|
65
|
+
controller.close();
|
|
66
|
+
this.#session.delete(session_id);
|
|
67
|
+
this.#streams.delete(session_id);
|
|
68
|
+
}
|
|
69
|
+
return new Response(null, {
|
|
70
|
+
status: 204,
|
|
71
|
+
headers: {
|
|
72
|
+
'mcp-session-id': session_id,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
52
77
|
/**
|
|
53
78
|
*
|
|
54
|
-
* @param {
|
|
79
|
+
* @param {string} session_id
|
|
55
80
|
* @returns
|
|
56
81
|
*/
|
|
57
|
-
|
|
82
|
+
#handle_get(session_id) {
|
|
58
83
|
const sessions = this.#session;
|
|
59
84
|
const streams = this.#streams;
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.#options.getSessionId();
|
|
65
|
-
const controller = sessions.get(session_id);
|
|
66
|
-
const response = await this.#server.receive(body, session_id);
|
|
67
|
-
|
|
68
|
-
if (controller) {
|
|
69
|
-
controller.enqueue('data: ' + JSON.stringify(response) + '\n\n');
|
|
70
|
-
return new Response(null, {
|
|
71
|
-
status: 204,
|
|
85
|
+
// If session already exists, return existing stream
|
|
86
|
+
const existing_stream = this.#streams.get(session_id);
|
|
87
|
+
if (existing_stream) {
|
|
88
|
+
return new Response(existing_stream, {
|
|
72
89
|
headers: {
|
|
73
90
|
'Content-Type': 'text/event-stream',
|
|
91
|
+
'Cache-Control': 'no-cache',
|
|
92
|
+
Connection: 'keep-alive',
|
|
74
93
|
'mcp-session-id': session_id,
|
|
75
94
|
},
|
|
95
|
+
status: 200,
|
|
76
96
|
});
|
|
77
97
|
}
|
|
78
98
|
|
|
99
|
+
// Create new long-lived stream for notifications
|
|
79
100
|
const stream = new ReadableStream({
|
|
80
101
|
start(controller) {
|
|
81
102
|
sessions.set(session_id, controller);
|
|
82
|
-
controller.enqueue(
|
|
83
|
-
'data: ' + JSON.stringify(response) + '\n\n',
|
|
84
|
-
);
|
|
85
103
|
},
|
|
86
104
|
cancel() {
|
|
87
105
|
sessions.delete(session_id);
|
|
106
|
+
streams.delete(session_id);
|
|
88
107
|
},
|
|
89
108
|
});
|
|
90
109
|
|
|
@@ -99,4 +118,141 @@ export class HttpTransport {
|
|
|
99
118
|
status: 200,
|
|
100
119
|
});
|
|
101
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
*
|
|
124
|
+
* @param {string} session_id
|
|
125
|
+
* @param {Request} request
|
|
126
|
+
*/
|
|
127
|
+
async #handle_post(session_id, request) {
|
|
128
|
+
// Check Content-Type header
|
|
129
|
+
const content_type = request.headers.get('content-type');
|
|
130
|
+
if (!content_type || !content_type.includes('application/json')) {
|
|
131
|
+
return new Response(
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
jsonrpc: '2.0',
|
|
134
|
+
error: {
|
|
135
|
+
code: -32600,
|
|
136
|
+
message: 'Invalid Request',
|
|
137
|
+
data: 'Content-Type must be application/json',
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
{
|
|
141
|
+
status: 415,
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
'mcp-session-id': session_id,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const body = await request.clone().json();
|
|
152
|
+
const response = await this.#server.receive(body, session_id);
|
|
153
|
+
|
|
154
|
+
// Determine status code based on response type
|
|
155
|
+
// 202 Accepted for notifications/responses, 200 OK for standard requests
|
|
156
|
+
const status = response == null || response.id == null ? 202 : 200;
|
|
157
|
+
|
|
158
|
+
// Create a short-lived stream that closes after sending the response
|
|
159
|
+
const stream = new ReadableStream({
|
|
160
|
+
start(controller) {
|
|
161
|
+
controller.enqueue(
|
|
162
|
+
'data: ' + JSON.stringify(response) + '\n\n',
|
|
163
|
+
);
|
|
164
|
+
controller.close();
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return new Response(stream, {
|
|
169
|
+
headers: {
|
|
170
|
+
'Content-Type': 'text/event-stream',
|
|
171
|
+
'Cache-Control': 'no-cache',
|
|
172
|
+
'mcp-session-id': session_id,
|
|
173
|
+
},
|
|
174
|
+
status,
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// Handle JSON parsing errors
|
|
178
|
+
return new Response(
|
|
179
|
+
JSON.stringify({
|
|
180
|
+
jsonrpc: '2.0',
|
|
181
|
+
error: {
|
|
182
|
+
code: -32700,
|
|
183
|
+
message: 'Parse error',
|
|
184
|
+
data: /** @type {Error} */ (error).message,
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
{
|
|
188
|
+
status: 400,
|
|
189
|
+
headers: {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
'mcp-session-id': session_id,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
*
|
|
200
|
+
* @param {string} method
|
|
201
|
+
* @returns
|
|
202
|
+
*/
|
|
203
|
+
#handle_default(method) {
|
|
204
|
+
return new Response(
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
jsonrpc: '2.0',
|
|
207
|
+
error: {
|
|
208
|
+
code: -32601,
|
|
209
|
+
message: 'Method not found',
|
|
210
|
+
data: `HTTP method ${method} not supported`,
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
213
|
+
{
|
|
214
|
+
status: 405,
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Type': 'application/json',
|
|
217
|
+
Allow: 'GET, POST, DELETE',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
*
|
|
225
|
+
* @param {Request} request
|
|
226
|
+
* @returns {Promise<Response | null>}
|
|
227
|
+
*/
|
|
228
|
+
async respond(request) {
|
|
229
|
+
const url = new URL(request.url);
|
|
230
|
+
|
|
231
|
+
// Check if the request path matches the configured MCP path
|
|
232
|
+
if (url.pathname !== this.#path) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const method = request.method;
|
|
236
|
+
const session_id =
|
|
237
|
+
request.headers.get('mcp-session-id') ||
|
|
238
|
+
this.#options.getSessionId();
|
|
239
|
+
|
|
240
|
+
// Handle DELETE request - disconnect session
|
|
241
|
+
if (method === 'DELETE') {
|
|
242
|
+
return this.#handle_delete(session_id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle GET request - establish long-lived connection for notifications
|
|
246
|
+
if (method === 'GET') {
|
|
247
|
+
return this.#handle_get(session_id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Handle POST request - process message and respond through event stream
|
|
251
|
+
if (method === 'POST') {
|
|
252
|
+
return this.#handle_post(session_id, request);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Method not supported
|
|
256
|
+
return this.#handle_default(method);
|
|
257
|
+
}
|
|
102
258
|
}
|