@mcp-weave/nestjs 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/README.md +41 -0
- package/dist/index.d.mts +44 -5
- package/dist/index.d.ts +44 -5
- package/dist/index.js +374 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +374 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ pnpm add @mcp-weave/nestjs reflect-metadata
|
|
|
27
27
|
- **Method Decorators** - `@McpTool`, `@McpResource`, `@McpPrompt`
|
|
28
28
|
- **Parameter Decorators** - `@McpInput`, `@McpParam`, `@McpPromptArg`
|
|
29
29
|
- **Runtime Server** - Start MCP servers from decorated classes
|
|
30
|
+
- **Multiple Transports** - Stdio (default) and SSE (Server-Sent Events)
|
|
30
31
|
|
|
31
32
|
## Quick Start
|
|
32
33
|
|
|
@@ -215,6 +216,46 @@ const server = new McpRuntimeServer(MyServer, {
|
|
|
215
216
|
await server.start();
|
|
216
217
|
```
|
|
217
218
|
|
|
219
|
+
### SSE Transport (Server-Sent Events)
|
|
220
|
+
|
|
221
|
+
For web-based integrations, use the SSE transport:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { McpRuntimeServer } from '@mcp-weave/nestjs';
|
|
225
|
+
|
|
226
|
+
const server = new McpRuntimeServer(MyServer, {
|
|
227
|
+
transport: 'sse',
|
|
228
|
+
port: 3000,
|
|
229
|
+
endpoint: '/sse',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Starts HTTP server with SSE endpoint
|
|
233
|
+
const httpServer = await server.start();
|
|
234
|
+
|
|
235
|
+
// Server available at:
|
|
236
|
+
// - SSE endpoint: http://localhost:3000/sse
|
|
237
|
+
// - Health check: http://localhost:3000/health
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Or use the `startSSE` method directly:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const server = new McpRuntimeServer(MyServer);
|
|
244
|
+
|
|
245
|
+
// Start with SSE transport
|
|
246
|
+
const httpServer = await server.startSSE({
|
|
247
|
+
port: 8080,
|
|
248
|
+
endpoint: '/mcp',
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**SSE Features:**
|
|
253
|
+
|
|
254
|
+
- CORS enabled by default
|
|
255
|
+
- Health check endpoint at `/health`
|
|
256
|
+
- Session management for multiple clients
|
|
257
|
+
- Automatic cleanup on connection close
|
|
258
|
+
|
|
218
259
|
## Metadata Extraction
|
|
219
260
|
|
|
220
261
|
Extract metadata from decorated classes for code generation:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolInputSchema, PromptArgument, McpParamMetadata, McpPromptMetadata, McpResourceMetadata, McpServerMetadata, McpToolMetadata } from '@mcp-weave/core';
|
|
2
2
|
export { METADATA_KEYS, McpParamMetadata, McpPromptMetadata, McpResourceMetadata, McpServerMetadata, McpToolMetadata, ScannedMetadata, extractMetadata } from '@mcp-weave/core';
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { Server as Server$1 } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { Server } from 'http';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Options for @McpServer decorator
|
|
@@ -209,9 +210,17 @@ declare function getParamsMetadata(target: Function): McpParamMetadata[];
|
|
|
209
210
|
*/
|
|
210
211
|
interface McpRuntimeOptions {
|
|
211
212
|
/**
|
|
212
|
-
* Transport type
|
|
213
|
+
* Transport type: 'stdio' (default), 'sse', or 'websocket'
|
|
213
214
|
*/
|
|
214
|
-
transport?: 'stdio' | 'sse';
|
|
215
|
+
transport?: 'stdio' | 'sse' | 'websocket';
|
|
216
|
+
/**
|
|
217
|
+
* Port for SSE/WebSocket transport (default: 3000)
|
|
218
|
+
*/
|
|
219
|
+
port?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Endpoint path for SSE/WebSocket (default: '/sse' or '/ws')
|
|
222
|
+
*/
|
|
223
|
+
endpoint?: string;
|
|
215
224
|
}
|
|
216
225
|
/**
|
|
217
226
|
* Runtime MCP server that wraps a decorated class
|
|
@@ -230,16 +239,46 @@ declare class McpRuntimeServer {
|
|
|
230
239
|
private resolvePromptArgs;
|
|
231
240
|
private extractUriParams;
|
|
232
241
|
/**
|
|
233
|
-
* Start the MCP server
|
|
242
|
+
* Start the MCP server with stdio transport
|
|
234
243
|
*/
|
|
235
244
|
start(): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Start the MCP server with SSE transport
|
|
247
|
+
*/
|
|
248
|
+
startSSE(options?: {
|
|
249
|
+
port?: number;
|
|
250
|
+
endpoint?: string;
|
|
251
|
+
}): Promise<Server>;
|
|
236
252
|
/**
|
|
237
253
|
* Get the underlying MCP server instance
|
|
238
254
|
*/
|
|
239
|
-
getServer(): Server;
|
|
255
|
+
getServer(): Server$1;
|
|
256
|
+
/**
|
|
257
|
+
* Start the MCP server with WebSocket transport
|
|
258
|
+
*/
|
|
259
|
+
startWebSocket(options?: {
|
|
260
|
+
port?: number;
|
|
261
|
+
endpoint?: string;
|
|
262
|
+
}): Promise<Server>;
|
|
263
|
+
/**
|
|
264
|
+
* Handle WebSocket JSON-RPC messages
|
|
265
|
+
*/
|
|
266
|
+
private handleWebSocketMessage;
|
|
240
267
|
}
|
|
241
268
|
/**
|
|
242
269
|
* Create and start an MCP server from a decorated class
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* // Stdio transport (default)
|
|
273
|
+
* await createMcpServer(MyServer);
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* // SSE transport
|
|
277
|
+
* await createMcpServer(MyServer, { transport: 'sse', port: 3000 });
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* // WebSocket transport
|
|
281
|
+
* await createMcpServer(MyServer, { transport: 'websocket', port: 8080 });
|
|
243
282
|
*/
|
|
244
283
|
declare function createMcpServer(target: Function, options?: McpRuntimeOptions): Promise<McpRuntimeServer>;
|
|
245
284
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolInputSchema, PromptArgument, McpParamMetadata, McpPromptMetadata, McpResourceMetadata, McpServerMetadata, McpToolMetadata } from '@mcp-weave/core';
|
|
2
2
|
export { METADATA_KEYS, McpParamMetadata, McpPromptMetadata, McpResourceMetadata, McpServerMetadata, McpToolMetadata, ScannedMetadata, extractMetadata } from '@mcp-weave/core';
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { Server as Server$1 } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { Server } from 'http';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Options for @McpServer decorator
|
|
@@ -209,9 +210,17 @@ declare function getParamsMetadata(target: Function): McpParamMetadata[];
|
|
|
209
210
|
*/
|
|
210
211
|
interface McpRuntimeOptions {
|
|
211
212
|
/**
|
|
212
|
-
* Transport type
|
|
213
|
+
* Transport type: 'stdio' (default), 'sse', or 'websocket'
|
|
213
214
|
*/
|
|
214
|
-
transport?: 'stdio' | 'sse';
|
|
215
|
+
transport?: 'stdio' | 'sse' | 'websocket';
|
|
216
|
+
/**
|
|
217
|
+
* Port for SSE/WebSocket transport (default: 3000)
|
|
218
|
+
*/
|
|
219
|
+
port?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Endpoint path for SSE/WebSocket (default: '/sse' or '/ws')
|
|
222
|
+
*/
|
|
223
|
+
endpoint?: string;
|
|
215
224
|
}
|
|
216
225
|
/**
|
|
217
226
|
* Runtime MCP server that wraps a decorated class
|
|
@@ -230,16 +239,46 @@ declare class McpRuntimeServer {
|
|
|
230
239
|
private resolvePromptArgs;
|
|
231
240
|
private extractUriParams;
|
|
232
241
|
/**
|
|
233
|
-
* Start the MCP server
|
|
242
|
+
* Start the MCP server with stdio transport
|
|
234
243
|
*/
|
|
235
244
|
start(): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* Start the MCP server with SSE transport
|
|
247
|
+
*/
|
|
248
|
+
startSSE(options?: {
|
|
249
|
+
port?: number;
|
|
250
|
+
endpoint?: string;
|
|
251
|
+
}): Promise<Server>;
|
|
236
252
|
/**
|
|
237
253
|
* Get the underlying MCP server instance
|
|
238
254
|
*/
|
|
239
|
-
getServer(): Server;
|
|
255
|
+
getServer(): Server$1;
|
|
256
|
+
/**
|
|
257
|
+
* Start the MCP server with WebSocket transport
|
|
258
|
+
*/
|
|
259
|
+
startWebSocket(options?: {
|
|
260
|
+
port?: number;
|
|
261
|
+
endpoint?: string;
|
|
262
|
+
}): Promise<Server>;
|
|
263
|
+
/**
|
|
264
|
+
* Handle WebSocket JSON-RPC messages
|
|
265
|
+
*/
|
|
266
|
+
private handleWebSocketMessage;
|
|
240
267
|
}
|
|
241
268
|
/**
|
|
242
269
|
* Create and start an MCP server from a decorated class
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* // Stdio transport (default)
|
|
273
|
+
* await createMcpServer(MyServer);
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* // SSE transport
|
|
277
|
+
* await createMcpServer(MyServer, { transport: 'sse', port: 3000 });
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* // WebSocket transport
|
|
281
|
+
* await createMcpServer(MyServer, { transport: 'websocket', port: 8080 });
|
|
243
282
|
*/
|
|
244
283
|
declare function createMcpServer(target: Function, options?: McpRuntimeOptions): Promise<McpRuntimeServer>;
|
|
245
284
|
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,15 @@ require('reflect-metadata');
|
|
|
4
4
|
var core = require('@mcp-weave/core');
|
|
5
5
|
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
6
6
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
7
|
+
var sse_js = require('@modelcontextprotocol/sdk/server/sse.js');
|
|
8
|
+
var http = require('http');
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
11
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
+
}) : x)(function(x) {
|
|
13
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
+
});
|
|
9
16
|
function McpServer(options) {
|
|
10
17
|
return (target) => {
|
|
11
18
|
const metadata = {
|
|
@@ -125,8 +132,6 @@ function getPromptsMetadata(target) {
|
|
|
125
132
|
function getParamsMetadata(target) {
|
|
126
133
|
return Reflect.getMetadata(core.METADATA_KEYS.PARAMS, target) ?? [];
|
|
127
134
|
}
|
|
128
|
-
|
|
129
|
-
// src/runtime/server.ts
|
|
130
135
|
var McpRuntimeServer = class {
|
|
131
136
|
server;
|
|
132
137
|
instance;
|
|
@@ -326,23 +331,386 @@ var McpRuntimeServer = class {
|
|
|
326
331
|
return params;
|
|
327
332
|
}
|
|
328
333
|
/**
|
|
329
|
-
* Start the MCP server
|
|
334
|
+
* Start the MCP server with stdio transport
|
|
330
335
|
*/
|
|
331
336
|
async start() {
|
|
332
337
|
const transport = new stdio_js.StdioServerTransport();
|
|
333
338
|
await this.server.connect(transport);
|
|
334
339
|
console.error(`MCP server '${this.metadata.server?.name}' started on stdio`);
|
|
335
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Start the MCP server with SSE transport
|
|
343
|
+
*/
|
|
344
|
+
async startSSE(options = {}) {
|
|
345
|
+
const port = options.port ?? 3e3;
|
|
346
|
+
const endpoint = options.endpoint ?? "/sse";
|
|
347
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
348
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
349
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
350
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
351
|
+
if (req.method === "OPTIONS") {
|
|
352
|
+
res.writeHead(200);
|
|
353
|
+
res.end();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
357
|
+
if (url.pathname === endpoint && req.method === "GET") {
|
|
358
|
+
const transport = new sse_js.SSEServerTransport(endpoint, res);
|
|
359
|
+
await this.server.connect(transport);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (url.pathname === `${endpoint}/message` && req.method === "POST") {
|
|
363
|
+
let body = "";
|
|
364
|
+
req.on("data", (chunk) => {
|
|
365
|
+
body += chunk.toString();
|
|
366
|
+
});
|
|
367
|
+
req.on("end", () => {
|
|
368
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
369
|
+
res.end(JSON.stringify({ received: true }));
|
|
370
|
+
});
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (url.pathname === "/health") {
|
|
374
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
375
|
+
res.end(
|
|
376
|
+
JSON.stringify({
|
|
377
|
+
status: "ok",
|
|
378
|
+
server: this.metadata.server?.name,
|
|
379
|
+
version: this.metadata.server?.version
|
|
380
|
+
})
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
385
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
386
|
+
});
|
|
387
|
+
return new Promise((resolve, reject) => {
|
|
388
|
+
httpServer.on("error", reject);
|
|
389
|
+
httpServer.listen(port, () => {
|
|
390
|
+
console.error(
|
|
391
|
+
`MCP server '${this.metadata.server?.name}' started on http://localhost:${port}${endpoint}`
|
|
392
|
+
);
|
|
393
|
+
resolve(httpServer);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
336
397
|
/**
|
|
337
398
|
* Get the underlying MCP server instance
|
|
338
399
|
*/
|
|
339
400
|
getServer() {
|
|
340
401
|
return this.server;
|
|
341
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Start the MCP server with WebSocket transport
|
|
405
|
+
*/
|
|
406
|
+
async startWebSocket(options = {}) {
|
|
407
|
+
const port = options.port ?? 8080;
|
|
408
|
+
const endpoint = options.endpoint ?? "/ws";
|
|
409
|
+
const httpServer = http.createServer((req, res) => {
|
|
410
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
411
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
412
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Upgrade, Connection");
|
|
413
|
+
if (req.method === "OPTIONS") {
|
|
414
|
+
res.writeHead(200);
|
|
415
|
+
res.end();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (req.url === "/health") {
|
|
419
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
420
|
+
res.end(
|
|
421
|
+
JSON.stringify({
|
|
422
|
+
status: "ok",
|
|
423
|
+
server: this.metadata.server?.name,
|
|
424
|
+
version: this.metadata.server?.version,
|
|
425
|
+
transport: "websocket"
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (req.url === "/") {
|
|
431
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
432
|
+
res.end(
|
|
433
|
+
JSON.stringify({
|
|
434
|
+
name: this.metadata.server?.name,
|
|
435
|
+
version: this.metadata.server?.version,
|
|
436
|
+
websocket: `ws://localhost:${port}${endpoint}`,
|
|
437
|
+
tools: this.metadata.tools.length,
|
|
438
|
+
resources: this.metadata.resources.length,
|
|
439
|
+
prompts: this.metadata.prompts.length
|
|
440
|
+
})
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
res.writeHead(426, { "Content-Type": "application/json" });
|
|
445
|
+
res.end(JSON.stringify({ error: "Upgrade Required", message: `Connect via WebSocket at ${endpoint}` }));
|
|
446
|
+
});
|
|
447
|
+
httpServer.on("upgrade", (req, socket, _head) => {
|
|
448
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
449
|
+
if (url.pathname !== endpoint) {
|
|
450
|
+
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
451
|
+
socket.destroy();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const key = req.headers["sec-websocket-key"];
|
|
455
|
+
if (!key) {
|
|
456
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
457
|
+
socket.destroy();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const crypto = __require("crypto");
|
|
461
|
+
const acceptKey = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
|
|
462
|
+
socket.write(
|
|
463
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
464
|
+
Upgrade: websocket\r
|
|
465
|
+
Connection: Upgrade\r
|
|
466
|
+
Sec-WebSocket-Accept: ${acceptKey}\r
|
|
467
|
+
\r
|
|
468
|
+
`
|
|
469
|
+
);
|
|
470
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
471
|
+
const sessionId = crypto.randomUUID();
|
|
472
|
+
const encodeFrame = (data) => {
|
|
473
|
+
const payload = Buffer.from(data, "utf8");
|
|
474
|
+
const length = payload.length;
|
|
475
|
+
let frame;
|
|
476
|
+
if (length < 126) {
|
|
477
|
+
frame = Buffer.alloc(2 + length);
|
|
478
|
+
frame[0] = 129;
|
|
479
|
+
frame[1] = length;
|
|
480
|
+
payload.copy(frame, 2);
|
|
481
|
+
} else if (length < 65536) {
|
|
482
|
+
frame = Buffer.alloc(4 + length);
|
|
483
|
+
frame[0] = 129;
|
|
484
|
+
frame[1] = 126;
|
|
485
|
+
frame.writeUInt16BE(length, 2);
|
|
486
|
+
payload.copy(frame, 4);
|
|
487
|
+
} else {
|
|
488
|
+
frame = Buffer.alloc(10 + length);
|
|
489
|
+
frame[0] = 129;
|
|
490
|
+
frame[1] = 127;
|
|
491
|
+
frame.writeBigUInt64BE(BigInt(length), 2);
|
|
492
|
+
payload.copy(frame, 10);
|
|
493
|
+
}
|
|
494
|
+
return frame;
|
|
495
|
+
};
|
|
496
|
+
const decodeFrame = (buffer) => {
|
|
497
|
+
if (buffer.length < 2) return null;
|
|
498
|
+
const byte0 = buffer[0];
|
|
499
|
+
const byte1 = buffer[1];
|
|
500
|
+
const opcode = byte0 & 15;
|
|
501
|
+
const masked = (byte1 & 128) !== 0;
|
|
502
|
+
let payloadLength = byte1 & 127;
|
|
503
|
+
let offset = 2;
|
|
504
|
+
if (payloadLength === 126) {
|
|
505
|
+
if (buffer.length < 4) return null;
|
|
506
|
+
payloadLength = buffer.readUInt16BE(2);
|
|
507
|
+
offset = 4;
|
|
508
|
+
} else if (payloadLength === 127) {
|
|
509
|
+
if (buffer.length < 10) return null;
|
|
510
|
+
payloadLength = Number(buffer.readBigUInt64BE(2));
|
|
511
|
+
offset = 10;
|
|
512
|
+
}
|
|
513
|
+
if (masked) {
|
|
514
|
+
if (buffer.length < offset + 4 + payloadLength) return null;
|
|
515
|
+
const mask = buffer.slice(offset, offset + 4);
|
|
516
|
+
offset += 4;
|
|
517
|
+
const payload = buffer.slice(offset, offset + payloadLength);
|
|
518
|
+
for (let i = 0; i < payload.length; i++) {
|
|
519
|
+
const maskByte = mask[i % 4] ?? 0;
|
|
520
|
+
payload[i] = (payload[i] ?? 0) ^ maskByte;
|
|
521
|
+
}
|
|
522
|
+
return { opcode, payload: payload.toString("utf8") };
|
|
523
|
+
} else {
|
|
524
|
+
if (buffer.length < offset + payloadLength) return null;
|
|
525
|
+
return { opcode, payload: buffer.slice(offset, offset + payloadLength).toString("utf8") };
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const send = (data) => {
|
|
529
|
+
try {
|
|
530
|
+
socket.write(encodeFrame(data));
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
sessions.set(sessionId, { send });
|
|
535
|
+
send(JSON.stringify({
|
|
536
|
+
jsonrpc: "2.0",
|
|
537
|
+
method: "connection/established",
|
|
538
|
+
params: { sessionId, server: this.metadata.server?.name }
|
|
539
|
+
}));
|
|
540
|
+
let messageBuffer = Buffer.alloc(0);
|
|
541
|
+
socket.on("data", async (chunk) => {
|
|
542
|
+
messageBuffer = Buffer.concat([messageBuffer, chunk]);
|
|
543
|
+
while (messageBuffer.length > 0) {
|
|
544
|
+
const frame = decodeFrame(messageBuffer);
|
|
545
|
+
if (!frame) break;
|
|
546
|
+
let frameSize = 2;
|
|
547
|
+
const byte1 = messageBuffer[1] ?? 0;
|
|
548
|
+
const payloadLength = byte1 & 127;
|
|
549
|
+
if (payloadLength === 126) frameSize = 4;
|
|
550
|
+
else if (payloadLength === 127) frameSize = 10;
|
|
551
|
+
if ((byte1 & 128) !== 0) frameSize += 4;
|
|
552
|
+
frameSize += frame.payload.length;
|
|
553
|
+
messageBuffer = messageBuffer.slice(frameSize);
|
|
554
|
+
if (frame.opcode === 8) {
|
|
555
|
+
socket.end();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (frame.opcode === 9) {
|
|
559
|
+
socket.write(Buffer.from([138, 0]));
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (frame.opcode === 1) {
|
|
563
|
+
try {
|
|
564
|
+
const message = JSON.parse(frame.payload);
|
|
565
|
+
const response = await this.handleWebSocketMessage(message);
|
|
566
|
+
if (response) {
|
|
567
|
+
send(JSON.stringify(response));
|
|
568
|
+
}
|
|
569
|
+
} catch (error) {
|
|
570
|
+
send(JSON.stringify({
|
|
571
|
+
jsonrpc: "2.0",
|
|
572
|
+
error: { code: -32700, message: "Parse error" },
|
|
573
|
+
id: null
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
socket.on("close", () => {
|
|
580
|
+
sessions.delete(sessionId);
|
|
581
|
+
});
|
|
582
|
+
socket.on("error", () => {
|
|
583
|
+
sessions.delete(sessionId);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
return new Promise((resolve, reject) => {
|
|
587
|
+
httpServer.on("error", reject);
|
|
588
|
+
httpServer.listen(port, () => {
|
|
589
|
+
console.error(
|
|
590
|
+
`MCP server '${this.metadata.server?.name}' started on ws://localhost:${port}${endpoint}`
|
|
591
|
+
);
|
|
592
|
+
resolve(httpServer);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Handle WebSocket JSON-RPC messages
|
|
598
|
+
*/
|
|
599
|
+
async handleWebSocketMessage(message) {
|
|
600
|
+
if (!message || typeof message !== "object") {
|
|
601
|
+
return { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: null };
|
|
602
|
+
}
|
|
603
|
+
const req = message;
|
|
604
|
+
if (req.jsonrpc !== "2.0" || !req.method) {
|
|
605
|
+
return { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: req.id ?? null };
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
let result;
|
|
609
|
+
switch (req.method) {
|
|
610
|
+
case "initialize":
|
|
611
|
+
result = {
|
|
612
|
+
protocolVersion: "2024-11-05",
|
|
613
|
+
serverInfo: {
|
|
614
|
+
name: this.metadata.server?.name,
|
|
615
|
+
version: this.metadata.server?.version ?? "1.0.0"
|
|
616
|
+
},
|
|
617
|
+
capabilities: {
|
|
618
|
+
tools: this.metadata.tools.length > 0 ? {} : void 0,
|
|
619
|
+
resources: this.metadata.resources.length > 0 ? {} : void 0,
|
|
620
|
+
prompts: this.metadata.prompts.length > 0 ? {} : void 0
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
break;
|
|
624
|
+
case "tools/list":
|
|
625
|
+
result = {
|
|
626
|
+
tools: this.metadata.tools.map((t) => ({
|
|
627
|
+
name: t.name,
|
|
628
|
+
description: t.description,
|
|
629
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} }
|
|
630
|
+
}))
|
|
631
|
+
};
|
|
632
|
+
break;
|
|
633
|
+
case "tools/call":
|
|
634
|
+
const toolParams = req.params;
|
|
635
|
+
const tool = this.metadata.tools.find((t) => t.name === toolParams.name);
|
|
636
|
+
if (!tool) {
|
|
637
|
+
return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown tool: ${toolParams.name}` }, id: req.id };
|
|
638
|
+
}
|
|
639
|
+
const method = Reflect.get(this.instance, tool.propertyKey);
|
|
640
|
+
const toolResult = await method.apply(this.instance, [toolParams.arguments ?? {}]);
|
|
641
|
+
result = {
|
|
642
|
+
content: [{
|
|
643
|
+
type: "text",
|
|
644
|
+
text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
|
|
645
|
+
}]
|
|
646
|
+
};
|
|
647
|
+
break;
|
|
648
|
+
case "resources/list":
|
|
649
|
+
result = {
|
|
650
|
+
resources: this.metadata.resources.map((r) => ({
|
|
651
|
+
uri: r.uri,
|
|
652
|
+
name: r.name,
|
|
653
|
+
description: r.description,
|
|
654
|
+
mimeType: r.mimeType
|
|
655
|
+
}))
|
|
656
|
+
};
|
|
657
|
+
break;
|
|
658
|
+
case "resources/read":
|
|
659
|
+
const resParams = req.params;
|
|
660
|
+
for (const resource of this.metadata.resources) {
|
|
661
|
+
const uriParams = this.extractUriParams(resource.uri, resParams.uri);
|
|
662
|
+
if (uriParams) {
|
|
663
|
+
const resMethod = Reflect.get(this.instance, resource.propertyKey);
|
|
664
|
+
const args = this.resolveResourceArgs(resource.propertyKey, uriParams);
|
|
665
|
+
result = await resMethod.apply(this.instance, args);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (!result) {
|
|
670
|
+
return { jsonrpc: "2.0", error: { code: -32602, message: `Resource not found: ${resParams.uri}` }, id: req.id };
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
case "prompts/list":
|
|
674
|
+
result = {
|
|
675
|
+
prompts: this.metadata.prompts.map((p) => ({
|
|
676
|
+
name: p.name,
|
|
677
|
+
description: p.description,
|
|
678
|
+
arguments: p.arguments ?? []
|
|
679
|
+
}))
|
|
680
|
+
};
|
|
681
|
+
break;
|
|
682
|
+
case "prompts/get":
|
|
683
|
+
const promptParams = req.params;
|
|
684
|
+
const prompt = this.metadata.prompts.find((p) => p.name === promptParams.name);
|
|
685
|
+
if (!prompt) {
|
|
686
|
+
return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` }, id: req.id };
|
|
687
|
+
}
|
|
688
|
+
const promptMethod = Reflect.get(this.instance, prompt.propertyKey);
|
|
689
|
+
const promptArgs = this.resolvePromptArgs(prompt.propertyKey, promptParams.arguments ?? {});
|
|
690
|
+
result = await promptMethod.apply(this.instance, promptArgs);
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
return { jsonrpc: "2.0", error: { code: -32601, message: `Method not found: ${req.method}` }, id: req.id };
|
|
694
|
+
}
|
|
695
|
+
return { jsonrpc: "2.0", result, id: req.id };
|
|
696
|
+
} catch (error) {
|
|
697
|
+
return {
|
|
698
|
+
jsonrpc: "2.0",
|
|
699
|
+
error: { code: -32e3, message: error instanceof Error ? error.message : "Internal error" },
|
|
700
|
+
id: req.id
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
342
704
|
};
|
|
343
|
-
async function createMcpServer(target, options) {
|
|
705
|
+
async function createMcpServer(target, options = {}) {
|
|
344
706
|
const server = new McpRuntimeServer(target, options);
|
|
345
|
-
|
|
707
|
+
if (options.transport === "sse") {
|
|
708
|
+
await server.startSSE({ port: options.port, endpoint: options.endpoint });
|
|
709
|
+
} else if (options.transport === "websocket") {
|
|
710
|
+
await server.startWebSocket({ port: options.port, endpoint: options.endpoint });
|
|
711
|
+
} else {
|
|
712
|
+
await server.start();
|
|
713
|
+
}
|
|
346
714
|
return server;
|
|
347
715
|
}
|
|
348
716
|
|