@tmcp/transport-http 0.1.1
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 +9 -0
- package/README.md +283 -0
- package/jsconfig.json +16 -0
- package/package.json +39 -0
- package/src/index.d.ts +27 -0
- package/src/index.js +102 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @tmcp/transport-http
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @tmcp/transport-http tmcp
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @tmcp/transport-http tmcp
|
|
11
|
+
# or
|
|
12
|
+
yarn add @tmcp/transport-http tmcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Setup
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { McpServer } from 'tmcp';
|
|
21
|
+
import { HttpTransport } from '@tmcp/transport-http';
|
|
22
|
+
|
|
23
|
+
// Create your MCP server
|
|
24
|
+
const server = new McpServer(
|
|
25
|
+
{
|
|
26
|
+
name: 'my-http-server',
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
description: 'My HTTP MCP server',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
adapter: new YourSchemaAdapter(),
|
|
32
|
+
capabilities: {
|
|
33
|
+
tools: { listChanged: true },
|
|
34
|
+
prompts: { listChanged: true },
|
|
35
|
+
resources: { listChanged: true },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Add your tools, prompts, and resources
|
|
41
|
+
server.tool(
|
|
42
|
+
{
|
|
43
|
+
name: 'example_tool',
|
|
44
|
+
description: 'An example tool',
|
|
45
|
+
},
|
|
46
|
+
async () => {
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: 'text', text: 'Hello from HTTP!' }],
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Create the HTTP transport
|
|
54
|
+
const transport = new HttpTransport(server);
|
|
55
|
+
|
|
56
|
+
// Use with your preferred HTTP server
|
|
57
|
+
// Example with Node.js built-in server:
|
|
58
|
+
import { createServer } from 'http';
|
|
59
|
+
|
|
60
|
+
const httpServer = createServer(async (req, res) => {
|
|
61
|
+
if (req.method === 'POST' && req.url === '/mcp') {
|
|
62
|
+
try {
|
|
63
|
+
let body = '';
|
|
64
|
+
req.on('data', (chunk) => (body += chunk));
|
|
65
|
+
req.on('end', async () => {
|
|
66
|
+
const request = new Request('http://localhost/mcp', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: req.headers,
|
|
69
|
+
body: body,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const response = await transport.respond(request);
|
|
73
|
+
|
|
74
|
+
// Copy response to Node.js response
|
|
75
|
+
response.headers.forEach((value, key) => {
|
|
76
|
+
res.setHeader(key, value);
|
|
77
|
+
});
|
|
78
|
+
res.statusCode = response.status;
|
|
79
|
+
|
|
80
|
+
const reader = response.body.getReader();
|
|
81
|
+
const pump = async () => {
|
|
82
|
+
const { done, value } = await reader.read();
|
|
83
|
+
if (done) {
|
|
84
|
+
res.end();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
res.write(value);
|
|
88
|
+
pump();
|
|
89
|
+
};
|
|
90
|
+
pump();
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
res.statusCode = 500;
|
|
94
|
+
res.end('Internal Server Error');
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
res.statusCode = 404;
|
|
98
|
+
res.end('Not Found');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
httpServer.listen(3000, () => {
|
|
103
|
+
console.log('MCP HTTP server listening on port 3000');
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With Custom Session Management
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const transport = new HttpTransport(server, {
|
|
111
|
+
getSessionId: () => {
|
|
112
|
+
// Custom session ID generation
|
|
113
|
+
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- **🌐 HTTP/SSE Communication**: Uses Server-Sent Events for real-time bidirectional communication
|
|
121
|
+
- **🔄 Session Management**: Maintains client sessions with automatic session ID generation
|
|
122
|
+
- **📡 Streaming Responses**: Supports streaming responses through SSE
|
|
123
|
+
- **🔧 Framework Agnostic**: Works with any HTTP server framework (Fastify, Bun, Deno, etc.)
|
|
124
|
+
- **⚡ Real-time Updates**: Server can push notifications and updates to connected clients
|
|
125
|
+
- **🛡️ Error Handling**: Graceful error handling for malformed requests
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
### `HttpTransport`
|
|
130
|
+
|
|
131
|
+
#### Constructor
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
new HttpTransport(server: McpServer, options?: HttpTransportOptions)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Creates a new HTTP transport instance.
|
|
138
|
+
|
|
139
|
+
**Parameters:**
|
|
140
|
+
|
|
141
|
+
- `server` - A TMCP server instance to handle incoming requests
|
|
142
|
+
- `options` - Optional configuration for the transport
|
|
143
|
+
|
|
144
|
+
**Options:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface HttpTransportOptions {
|
|
148
|
+
getSessionId: () => string; // Custom session ID generator
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Methods
|
|
153
|
+
|
|
154
|
+
##### `respond(request: Request): Promise<Response>`
|
|
155
|
+
|
|
156
|
+
Processes an HTTP request and returns a Response with Server-Sent Events.
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
|
|
160
|
+
- `request` - A Web API Request object containing the JSON-RPC message
|
|
161
|
+
|
|
162
|
+
**Returns:**
|
|
163
|
+
|
|
164
|
+
- A Response object with SSE stream for ongoing communication
|
|
165
|
+
|
|
166
|
+
## Protocol Details
|
|
167
|
+
|
|
168
|
+
### Request Format
|
|
169
|
+
|
|
170
|
+
Clients send JSON-RPC messages via HTTP POST requests:
|
|
171
|
+
|
|
172
|
+
```http
|
|
173
|
+
POST /mcp HTTP/1.1
|
|
174
|
+
Content-Type: application/json
|
|
175
|
+
mcp-session-id: optional-session-id
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
"jsonrpc": "2.0",
|
|
179
|
+
"id": 1,
|
|
180
|
+
"method": "tools/list",
|
|
181
|
+
"params": {}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Response Format
|
|
186
|
+
|
|
187
|
+
The server responds with Server-Sent Events:
|
|
188
|
+
|
|
189
|
+
```http
|
|
190
|
+
HTTP/1.1 200 OK
|
|
191
|
+
Content-Type: text/event-stream
|
|
192
|
+
Cache-Control: no-cache
|
|
193
|
+
Connection: keep-alive
|
|
194
|
+
mcp-session-id: generated-or-provided-session-id
|
|
195
|
+
|
|
196
|
+
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Session Management
|
|
201
|
+
|
|
202
|
+
- **Session ID Header**: `mcp-session-id`
|
|
203
|
+
- **Automatic Generation**: If no session ID is provided, one is generated automatically
|
|
204
|
+
- **Session Persistence**: Sessions persist across multiple requests until the client disconnects
|
|
205
|
+
- **Server Notifications**: Server can send notifications to all active sessions
|
|
206
|
+
|
|
207
|
+
## Framework Examples
|
|
208
|
+
|
|
209
|
+
### Bun
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
import { McpServer } from 'tmcp';
|
|
213
|
+
import { HttpTransport } from '@tmcp/transport-http';
|
|
214
|
+
|
|
215
|
+
const server = new McpServer(/* ... */);
|
|
216
|
+
const transport = new HttpTransport(server);
|
|
217
|
+
|
|
218
|
+
Bun.serve({
|
|
219
|
+
port: 3000,
|
|
220
|
+
async fetch(req) {
|
|
221
|
+
if (req.method === 'POST' && new URL(req.url).pathname === '/mcp') {
|
|
222
|
+
return await transport.respond(req);
|
|
223
|
+
}
|
|
224
|
+
return new Response('Not Found', { status: 404 });
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Deno
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
import { McpServer } from 'tmcp';
|
|
233
|
+
import { HttpTransport } from '@tmcp/transport-http';
|
|
234
|
+
|
|
235
|
+
const server = new McpServer(/* ... */);
|
|
236
|
+
const transport = new HttpTransport(server);
|
|
237
|
+
|
|
238
|
+
Deno.serve({ port: 3000 }, async (req) => {
|
|
239
|
+
if (req.method === 'POST' && new URL(req.url).pathname === '/mcp') {
|
|
240
|
+
return await transport.respond(req);
|
|
241
|
+
}
|
|
242
|
+
return new Response('Not Found', { status: 404 });
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Error Handling
|
|
247
|
+
|
|
248
|
+
The transport includes comprehensive error handling:
|
|
249
|
+
|
|
250
|
+
- **Malformed JSON**: Invalid JSON requests return appropriate error responses
|
|
251
|
+
- **Session Management**: Automatic cleanup of disconnected sessions
|
|
252
|
+
- **Server Errors**: Server processing errors are propagated to clients
|
|
253
|
+
|
|
254
|
+
## Development
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Install dependencies
|
|
258
|
+
pnpm install
|
|
259
|
+
|
|
260
|
+
# Generate TypeScript declarations
|
|
261
|
+
pnpm generate:types
|
|
262
|
+
|
|
263
|
+
# Lint the code
|
|
264
|
+
pnpm lint
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Requirements
|
|
268
|
+
|
|
269
|
+
- Node.js 16+ (for native ES modules and Web API support)
|
|
270
|
+
- A TMCP server instance
|
|
271
|
+
- An HTTP server framework or runtime
|
|
272
|
+
- A schema adapter (Zod, Valibot, etc.)
|
|
273
|
+
|
|
274
|
+
## Related Packages
|
|
275
|
+
|
|
276
|
+
- [`tmcp`](../tmcp) - Core TMCP server implementation
|
|
277
|
+
- [`@tmcp/transport-stdio`](../transport-stdio) - Standard I/O transport
|
|
278
|
+
- [`@tmcp/adapter-zod`](../adapter-zod) - Zod schema adapter
|
|
279
|
+
- [`@tmcp/adapter-valibot`](../adapter-valibot) - Valibot schema adapter
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT
|
package/jsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"checkJs": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"emitDeclarationOnly": false,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tmcp/transport-http",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Transport for TMCP using HTTP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"tmcp": "^1.5.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"tmcp",
|
|
20
|
+
"http",
|
|
21
|
+
"transport"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/paoloricciuti/tmcp.git",
|
|
26
|
+
"directory": "packages/transport-http"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"publint": "^0.3.12",
|
|
30
|
+
"tmcp": "^1.5.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"generate:types": "tsc ./src/*.js --declaration --emitDeclarationOnly --target esnext --allowJs --moduleresolution nodenext --module nodenext && publint",
|
|
37
|
+
"prepublish": "pnpm generate:types"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { McpServer, ClientCapabilities } from "tmcp";
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* getSessionId: () => string
|
|
7
|
+
* }} HttpTransportOptions
|
|
8
|
+
*/
|
|
9
|
+
export class HttpTransport {
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {McpServer<any>} server
|
|
13
|
+
* @param {HttpTransportOptions} [options]
|
|
14
|
+
*/
|
|
15
|
+
constructor(server: McpServer<any>, options?: HttpTransportOptions);
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param {Request} request
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
respond(request: Request): Promise<Response>;
|
|
22
|
+
#private;
|
|
23
|
+
}
|
|
24
|
+
export type HttpTransportOptions = {
|
|
25
|
+
getSessionId: () => string;
|
|
26
|
+
};
|
|
27
|
+
import type { McpServer } from "tmcp";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { McpServer, ClientCapabilities } from "tmcp";
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* getSessionId: () => string
|
|
8
|
+
* }} HttpTransportOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class HttpTransport {
|
|
12
|
+
/**
|
|
13
|
+
* @type {McpServer<any>}
|
|
14
|
+
*/
|
|
15
|
+
#server;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {HttpTransportOptions}
|
|
19
|
+
*/
|
|
20
|
+
#options;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @type {Map<string, ReadableStreamDefaultController>}
|
|
24
|
+
*/
|
|
25
|
+
#session = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* @type {Map<string, ReadableStream>}
|
|
28
|
+
*/
|
|
29
|
+
#streams = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {McpServer<any>} server
|
|
34
|
+
* @param {HttpTransportOptions} [options]
|
|
35
|
+
*/
|
|
36
|
+
constructor(server, options) {
|
|
37
|
+
this.#server = server;
|
|
38
|
+
this.#options = options || {
|
|
39
|
+
getSessionId: () => crypto.randomUUID(),
|
|
40
|
+
};
|
|
41
|
+
this.#server.on('send', ({ request, context: { sessions } }) => {
|
|
42
|
+
for (let [session_id, controller] of this.#session.entries()) {
|
|
43
|
+
if (sessions === undefined || sessions.includes(session_id)) {
|
|
44
|
+
controller.enqueue(
|
|
45
|
+
'data: ' + JSON.stringify(request) + '\n\n',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param {Request} request
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
async respond(request) {
|
|
58
|
+
const sessions = this.#session;
|
|
59
|
+
const streams = this.#streams;
|
|
60
|
+
|
|
61
|
+
const body = await request.json();
|
|
62
|
+
const session_id =
|
|
63
|
+
request.headers.get('mcp-session-id') ||
|
|
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,
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'text/event-stream',
|
|
74
|
+
'mcp-session-id': session_id,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stream = new ReadableStream({
|
|
80
|
+
start(controller) {
|
|
81
|
+
sessions.set(session_id, controller);
|
|
82
|
+
controller.enqueue(
|
|
83
|
+
'data: ' + JSON.stringify(response) + '\n\n',
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
cancel() {
|
|
87
|
+
sessions.delete(session_id);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
streams.set(session_id, stream);
|
|
92
|
+
return new Response(stream, {
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'text/event-stream',
|
|
95
|
+
'Cache-Control': 'no-cache',
|
|
96
|
+
Connection: 'keep-alive',
|
|
97
|
+
'mcp-session-id': session_id,
|
|
98
|
+
},
|
|
99
|
+
status: 200,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|