@mcp-weave/nestjs 0.1.1 → 0.3.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 +168 -7
- package/dist/index.d.ts +168 -7
- package/dist/index.js +596 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +585 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
4
|
var core = require('@mcp-weave/core');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
var http = require('http');
|
|
5
7
|
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
8
|
+
var sse_js = require('@modelcontextprotocol/sdk/server/sse.js');
|
|
6
9
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
7
10
|
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
14
|
+
|
|
8
15
|
// src/index.ts
|
|
9
16
|
function McpServer(options) {
|
|
10
17
|
return (target) => {
|
|
@@ -126,16 +133,161 @@ function getParamsMetadata(target) {
|
|
|
126
133
|
return Reflect.getMetadata(core.METADATA_KEYS.PARAMS, target) ?? [];
|
|
127
134
|
}
|
|
128
135
|
|
|
136
|
+
// src/auth/index.ts
|
|
137
|
+
function normalizeApiKeys(keys) {
|
|
138
|
+
if (!keys) return [];
|
|
139
|
+
if (typeof keys === "string") {
|
|
140
|
+
return [{ key: keys, name: "default" }];
|
|
141
|
+
}
|
|
142
|
+
return keys.map((k, index) => {
|
|
143
|
+
if (typeof k === "string") {
|
|
144
|
+
return { key: k, name: `key-${index + 1}` };
|
|
145
|
+
}
|
|
146
|
+
return k;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function extractApiKey(req, headerName = "x-api-key", queryParamName = "api_key") {
|
|
150
|
+
const headerKey = req.headers[headerName.toLowerCase()];
|
|
151
|
+
if (headerKey) {
|
|
152
|
+
return Array.isArray(headerKey) ? headerKey[0] : headerKey;
|
|
153
|
+
}
|
|
154
|
+
const authHeader = req.headers["authorization"];
|
|
155
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
156
|
+
return authHeader.slice(7);
|
|
157
|
+
}
|
|
158
|
+
const url = req.url ?? "";
|
|
159
|
+
const queryIndex = url.indexOf("?");
|
|
160
|
+
if (queryIndex !== -1) {
|
|
161
|
+
const searchParams = new URLSearchParams(url.slice(queryIndex + 1));
|
|
162
|
+
const queryKey = searchParams.get(queryParamName);
|
|
163
|
+
if (queryKey) return queryKey;
|
|
164
|
+
}
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
function validateApiKey(token, apiKeys) {
|
|
168
|
+
if (!token) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: "No API key provided"
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const matchedKey = apiKeys.find((k) => k.key === token);
|
|
175
|
+
if (!matchedKey) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
error: "Invalid API key"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
clientId: hashToken(token),
|
|
184
|
+
clientName: matchedKey.name,
|
|
185
|
+
scopes: matchedKey.scopes,
|
|
186
|
+
metadata: matchedKey.metadata
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function hashToken(token) {
|
|
190
|
+
if (token.length <= 8) {
|
|
191
|
+
return "****";
|
|
192
|
+
}
|
|
193
|
+
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
|
194
|
+
}
|
|
195
|
+
function generateRequestId() {
|
|
196
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
197
|
+
}
|
|
198
|
+
function createAuthMiddleware(options = {}) {
|
|
199
|
+
const {
|
|
200
|
+
enabled = false,
|
|
201
|
+
apiKeys,
|
|
202
|
+
headerName = "x-api-key",
|
|
203
|
+
queryParamName = "api_key",
|
|
204
|
+
authenticate,
|
|
205
|
+
onAuthFailure,
|
|
206
|
+
onAuthSuccess
|
|
207
|
+
} = options;
|
|
208
|
+
const normalizedKeys = normalizeApiKeys(apiKeys);
|
|
209
|
+
return async (req, res) => {
|
|
210
|
+
const requestId = generateRequestId();
|
|
211
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
212
|
+
if (!enabled) {
|
|
213
|
+
return {
|
|
214
|
+
request: req,
|
|
215
|
+
auth: { success: true, clientId: "anonymous" },
|
|
216
|
+
requestId,
|
|
217
|
+
timestamp
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const token = extractApiKey(req, headerName, queryParamName);
|
|
221
|
+
let result;
|
|
222
|
+
if (authenticate) {
|
|
223
|
+
const authResult = await authenticate(token, req);
|
|
224
|
+
if (typeof authResult === "boolean") {
|
|
225
|
+
result = {
|
|
226
|
+
success: authResult,
|
|
227
|
+
clientId: authResult ? hashToken(token ?? "unknown") : void 0,
|
|
228
|
+
error: authResult ? void 0 : "Authentication failed"
|
|
229
|
+
};
|
|
230
|
+
} else {
|
|
231
|
+
result = authResult;
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
result = validateApiKey(token, normalizedKeys);
|
|
235
|
+
}
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
onAuthFailure?.(req, result.error ?? "Unknown error");
|
|
238
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
239
|
+
res.end(
|
|
240
|
+
JSON.stringify({
|
|
241
|
+
error: "Unauthorized",
|
|
242
|
+
message: result.error ?? "Authentication required",
|
|
243
|
+
requestId
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const authRequest = {
|
|
249
|
+
request: req,
|
|
250
|
+
auth: result,
|
|
251
|
+
requestId,
|
|
252
|
+
timestamp
|
|
253
|
+
};
|
|
254
|
+
onAuthSuccess?.(req, result);
|
|
255
|
+
return authRequest;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function sendUnauthorized(res, message = "Unauthorized", requestId) {
|
|
259
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
260
|
+
res.end(
|
|
261
|
+
JSON.stringify({
|
|
262
|
+
error: "Unauthorized",
|
|
263
|
+
message,
|
|
264
|
+
requestId
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
function generateApiKey(prefix = "mcp") {
|
|
269
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
270
|
+
let key = "";
|
|
271
|
+
for (let i = 0; i < 32; i++) {
|
|
272
|
+
key += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
273
|
+
}
|
|
274
|
+
return `${prefix}_${key}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
129
277
|
// src/runtime/server.ts
|
|
130
278
|
var McpRuntimeServer = class {
|
|
131
279
|
server;
|
|
132
280
|
instance;
|
|
133
281
|
metadata;
|
|
134
|
-
|
|
282
|
+
authOptions;
|
|
283
|
+
authMiddleware;
|
|
284
|
+
constructor(target, options = {}) {
|
|
135
285
|
this.metadata = core.extractMetadata(target);
|
|
136
286
|
if (!this.metadata.server) {
|
|
137
287
|
throw new Error(`Class ${target.name} is not decorated with @McpServer`);
|
|
138
288
|
}
|
|
289
|
+
this.authOptions = options.auth ?? {};
|
|
290
|
+
this.authMiddleware = createAuthMiddleware(this.authOptions);
|
|
139
291
|
this.server = new index_js.Server(
|
|
140
292
|
{
|
|
141
293
|
name: this.metadata.server.name,
|
|
@@ -326,23 +478,455 @@ var McpRuntimeServer = class {
|
|
|
326
478
|
return params;
|
|
327
479
|
}
|
|
328
480
|
/**
|
|
329
|
-
* Start the MCP server
|
|
481
|
+
* Start the MCP server with stdio transport
|
|
330
482
|
*/
|
|
331
483
|
async start() {
|
|
332
484
|
const transport = new stdio_js.StdioServerTransport();
|
|
333
485
|
await this.server.connect(transport);
|
|
334
486
|
console.error(`MCP server '${this.metadata.server?.name}' started on stdio`);
|
|
335
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Start the MCP server with SSE transport
|
|
490
|
+
*/
|
|
491
|
+
async startSSE(options = {}) {
|
|
492
|
+
const port = options.port ?? 3e3;
|
|
493
|
+
const endpoint = options.endpoint ?? "/sse";
|
|
494
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
495
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
496
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
497
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Api-Key");
|
|
498
|
+
if (req.method === "OPTIONS") {
|
|
499
|
+
res.writeHead(200);
|
|
500
|
+
res.end();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const authResult = await this.authMiddleware(req, res);
|
|
504
|
+
if (!authResult) return;
|
|
505
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
506
|
+
if (this.authOptions.enabled) {
|
|
507
|
+
console.error(
|
|
508
|
+
`[${authResult.requestId}] ${authResult.auth.clientName ?? authResult.auth.clientId} - ${req.method} ${url.pathname}`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
if (url.pathname === endpoint && req.method === "GET") {
|
|
512
|
+
const transport = new sse_js.SSEServerTransport(endpoint, res);
|
|
513
|
+
await this.server.connect(transport);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (url.pathname === `${endpoint}/message` && req.method === "POST") {
|
|
517
|
+
let _body = "";
|
|
518
|
+
req.on("data", (chunk) => {
|
|
519
|
+
_body += chunk.toString();
|
|
520
|
+
});
|
|
521
|
+
req.on("end", () => {
|
|
522
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
523
|
+
res.end(JSON.stringify({ received: true }));
|
|
524
|
+
});
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (url.pathname === "/health") {
|
|
528
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
529
|
+
res.end(
|
|
530
|
+
JSON.stringify({
|
|
531
|
+
status: "ok",
|
|
532
|
+
server: this.metadata.server?.name,
|
|
533
|
+
version: this.metadata.server?.version
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
539
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
540
|
+
});
|
|
541
|
+
return new Promise((resolve, reject) => {
|
|
542
|
+
httpServer.on("error", reject);
|
|
543
|
+
httpServer.listen(port, () => {
|
|
544
|
+
console.error(
|
|
545
|
+
`MCP server '${this.metadata.server?.name}' started on http://localhost:${port}${endpoint}`
|
|
546
|
+
);
|
|
547
|
+
resolve(httpServer);
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
}
|
|
336
551
|
/**
|
|
337
552
|
* Get the underlying MCP server instance
|
|
338
553
|
*/
|
|
339
554
|
getServer() {
|
|
340
555
|
return this.server;
|
|
341
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Start the MCP server with WebSocket transport
|
|
559
|
+
*/
|
|
560
|
+
async startWebSocket(options = {}) {
|
|
561
|
+
const port = options.port ?? 8080;
|
|
562
|
+
const endpoint = options.endpoint ?? "/ws";
|
|
563
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
564
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
565
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
566
|
+
res.setHeader(
|
|
567
|
+
"Access-Control-Allow-Headers",
|
|
568
|
+
"Content-Type, Upgrade, Connection, Authorization, X-Api-Key"
|
|
569
|
+
);
|
|
570
|
+
if (req.method === "OPTIONS") {
|
|
571
|
+
res.writeHead(200);
|
|
572
|
+
res.end();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (req.url === "/health") {
|
|
576
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
577
|
+
res.end(
|
|
578
|
+
JSON.stringify({
|
|
579
|
+
status: "ok",
|
|
580
|
+
server: this.metadata.server?.name,
|
|
581
|
+
version: this.metadata.server?.version,
|
|
582
|
+
transport: "websocket",
|
|
583
|
+
authEnabled: this.authOptions.enabled ?? false
|
|
584
|
+
})
|
|
585
|
+
);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const authResult = await this.authMiddleware(req, res);
|
|
589
|
+
if (!authResult) return;
|
|
590
|
+
if (req.url === "/" || req.url?.startsWith("/?")) {
|
|
591
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
592
|
+
res.end(
|
|
593
|
+
JSON.stringify({
|
|
594
|
+
name: this.metadata.server?.name,
|
|
595
|
+
version: this.metadata.server?.version,
|
|
596
|
+
websocket: `ws://localhost:${port}${endpoint}`,
|
|
597
|
+
tools: this.metadata.tools.length,
|
|
598
|
+
resources: this.metadata.resources.length,
|
|
599
|
+
prompts: this.metadata.prompts.length,
|
|
600
|
+
client: authResult.auth.clientName,
|
|
601
|
+
requestId: authResult.requestId
|
|
602
|
+
})
|
|
603
|
+
);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
res.writeHead(426, { "Content-Type": "application/json" });
|
|
607
|
+
res.end(
|
|
608
|
+
JSON.stringify({
|
|
609
|
+
error: "Upgrade Required",
|
|
610
|
+
message: `Connect via WebSocket at ${endpoint}`
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
});
|
|
614
|
+
httpServer.on("upgrade", async (req, socket, _head) => {
|
|
615
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
616
|
+
if (url.pathname !== endpoint) {
|
|
617
|
+
socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
618
|
+
socket.destroy();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (this.authOptions.enabled) {
|
|
622
|
+
const apiKeys = normalizeApiKeys(this.authOptions.apiKeys);
|
|
623
|
+
const token = extractApiKey(
|
|
624
|
+
req,
|
|
625
|
+
this.authOptions.headerName,
|
|
626
|
+
this.authOptions.queryParamName
|
|
627
|
+
);
|
|
628
|
+
const authResult = validateApiKey(token, apiKeys);
|
|
629
|
+
if (!authResult.success) {
|
|
630
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
631
|
+
socket.destroy();
|
|
632
|
+
this.authOptions.onAuthFailure?.(req, authResult.error ?? "Auth failed");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
console.error(
|
|
636
|
+
`[WebSocket] Client connected: ${authResult.clientName ?? authResult.clientId}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
const key = req.headers["sec-websocket-key"];
|
|
640
|
+
if (!key) {
|
|
641
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
642
|
+
socket.destroy();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const acceptKey = crypto__default.default.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
|
|
646
|
+
socket.write(
|
|
647
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
648
|
+
Upgrade: websocket\r
|
|
649
|
+
Connection: Upgrade\r
|
|
650
|
+
Sec-WebSocket-Accept: ${acceptKey}\r
|
|
651
|
+
\r
|
|
652
|
+
`
|
|
653
|
+
);
|
|
654
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
655
|
+
const sessionId = crypto__default.default.randomUUID();
|
|
656
|
+
const encodeFrame = (data) => {
|
|
657
|
+
const payload = Buffer.from(data, "utf8");
|
|
658
|
+
const length = payload.length;
|
|
659
|
+
let frame;
|
|
660
|
+
if (length < 126) {
|
|
661
|
+
frame = Buffer.alloc(2 + length);
|
|
662
|
+
frame[0] = 129;
|
|
663
|
+
frame[1] = length;
|
|
664
|
+
payload.copy(frame, 2);
|
|
665
|
+
} else if (length < 65536) {
|
|
666
|
+
frame = Buffer.alloc(4 + length);
|
|
667
|
+
frame[0] = 129;
|
|
668
|
+
frame[1] = 126;
|
|
669
|
+
frame.writeUInt16BE(length, 2);
|
|
670
|
+
payload.copy(frame, 4);
|
|
671
|
+
} else {
|
|
672
|
+
frame = Buffer.alloc(10 + length);
|
|
673
|
+
frame[0] = 129;
|
|
674
|
+
frame[1] = 127;
|
|
675
|
+
frame.writeBigUInt64BE(BigInt(length), 2);
|
|
676
|
+
payload.copy(frame, 10);
|
|
677
|
+
}
|
|
678
|
+
return frame;
|
|
679
|
+
};
|
|
680
|
+
const decodeFrame = (buffer) => {
|
|
681
|
+
if (buffer.length < 2) return null;
|
|
682
|
+
const byte0 = buffer[0];
|
|
683
|
+
const byte1 = buffer[1];
|
|
684
|
+
const opcode = byte0 & 15;
|
|
685
|
+
const masked = (byte1 & 128) !== 0;
|
|
686
|
+
let payloadLength = byte1 & 127;
|
|
687
|
+
let offset = 2;
|
|
688
|
+
if (payloadLength === 126) {
|
|
689
|
+
if (buffer.length < 4) return null;
|
|
690
|
+
payloadLength = buffer.readUInt16BE(2);
|
|
691
|
+
offset = 4;
|
|
692
|
+
} else if (payloadLength === 127) {
|
|
693
|
+
if (buffer.length < 10) return null;
|
|
694
|
+
payloadLength = Number(buffer.readBigUInt64BE(2));
|
|
695
|
+
offset = 10;
|
|
696
|
+
}
|
|
697
|
+
if (masked) {
|
|
698
|
+
if (buffer.length < offset + 4 + payloadLength) return null;
|
|
699
|
+
const mask = buffer.slice(offset, offset + 4);
|
|
700
|
+
offset += 4;
|
|
701
|
+
const payload = buffer.slice(offset, offset + payloadLength);
|
|
702
|
+
for (let i = 0; i < payload.length; i++) {
|
|
703
|
+
const maskByte = mask[i % 4] ?? 0;
|
|
704
|
+
payload[i] = (payload[i] ?? 0) ^ maskByte;
|
|
705
|
+
}
|
|
706
|
+
return { opcode, payload: payload.toString("utf8") };
|
|
707
|
+
} else {
|
|
708
|
+
if (buffer.length < offset + payloadLength) return null;
|
|
709
|
+
return { opcode, payload: buffer.slice(offset, offset + payloadLength).toString("utf8") };
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const send = (data) => {
|
|
713
|
+
try {
|
|
714
|
+
socket.write(encodeFrame(data));
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
sessions.set(sessionId, { send });
|
|
719
|
+
send(
|
|
720
|
+
JSON.stringify({
|
|
721
|
+
jsonrpc: "2.0",
|
|
722
|
+
method: "connection/established",
|
|
723
|
+
params: { sessionId, server: this.metadata.server?.name }
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
let messageBuffer = Buffer.alloc(0);
|
|
727
|
+
socket.on("data", async (chunk) => {
|
|
728
|
+
messageBuffer = Buffer.concat([messageBuffer, chunk]);
|
|
729
|
+
while (messageBuffer.length > 0) {
|
|
730
|
+
const frame = decodeFrame(messageBuffer);
|
|
731
|
+
if (!frame) break;
|
|
732
|
+
let frameSize = 2;
|
|
733
|
+
const byte1 = messageBuffer[1] ?? 0;
|
|
734
|
+
const payloadLength = byte1 & 127;
|
|
735
|
+
if (payloadLength === 126) frameSize = 4;
|
|
736
|
+
else if (payloadLength === 127) frameSize = 10;
|
|
737
|
+
if ((byte1 & 128) !== 0) frameSize += 4;
|
|
738
|
+
frameSize += frame.payload.length;
|
|
739
|
+
messageBuffer = messageBuffer.slice(frameSize);
|
|
740
|
+
if (frame.opcode === 8) {
|
|
741
|
+
socket.end();
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (frame.opcode === 9) {
|
|
745
|
+
socket.write(Buffer.from([138, 0]));
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (frame.opcode === 1) {
|
|
749
|
+
try {
|
|
750
|
+
const message = JSON.parse(frame.payload);
|
|
751
|
+
const response = await this.handleWebSocketMessage(message);
|
|
752
|
+
if (response) {
|
|
753
|
+
send(JSON.stringify(response));
|
|
754
|
+
}
|
|
755
|
+
} catch (error) {
|
|
756
|
+
send(
|
|
757
|
+
JSON.stringify({
|
|
758
|
+
jsonrpc: "2.0",
|
|
759
|
+
error: { code: -32700, message: "Parse error" },
|
|
760
|
+
id: null
|
|
761
|
+
})
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
socket.on("close", () => {
|
|
768
|
+
sessions.delete(sessionId);
|
|
769
|
+
});
|
|
770
|
+
socket.on("error", () => {
|
|
771
|
+
sessions.delete(sessionId);
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
775
|
+
httpServer.on("error", reject);
|
|
776
|
+
httpServer.listen(port, () => {
|
|
777
|
+
console.error(
|
|
778
|
+
`MCP server '${this.metadata.server?.name}' started on ws://localhost:${port}${endpoint}`
|
|
779
|
+
);
|
|
780
|
+
resolve(httpServer);
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Handle WebSocket JSON-RPC messages
|
|
786
|
+
*/
|
|
787
|
+
async handleWebSocketMessage(message) {
|
|
788
|
+
if (!message || typeof message !== "object") {
|
|
789
|
+
return { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: null };
|
|
790
|
+
}
|
|
791
|
+
const req = message;
|
|
792
|
+
if (req.jsonrpc !== "2.0" || !req.method) {
|
|
793
|
+
return {
|
|
794
|
+
jsonrpc: "2.0",
|
|
795
|
+
error: { code: -32600, message: "Invalid Request" },
|
|
796
|
+
id: req.id ?? null
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
let result;
|
|
801
|
+
switch (req.method) {
|
|
802
|
+
case "initialize":
|
|
803
|
+
result = {
|
|
804
|
+
protocolVersion: "2024-11-05",
|
|
805
|
+
serverInfo: {
|
|
806
|
+
name: this.metadata.server?.name,
|
|
807
|
+
version: this.metadata.server?.version ?? "1.0.0"
|
|
808
|
+
},
|
|
809
|
+
capabilities: {
|
|
810
|
+
tools: this.metadata.tools.length > 0 ? {} : void 0,
|
|
811
|
+
resources: this.metadata.resources.length > 0 ? {} : void 0,
|
|
812
|
+
prompts: this.metadata.prompts.length > 0 ? {} : void 0
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
break;
|
|
816
|
+
case "tools/list":
|
|
817
|
+
result = {
|
|
818
|
+
tools: this.metadata.tools.map((t) => ({
|
|
819
|
+
name: t.name,
|
|
820
|
+
description: t.description,
|
|
821
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} }
|
|
822
|
+
}))
|
|
823
|
+
};
|
|
824
|
+
break;
|
|
825
|
+
case "tools/call": {
|
|
826
|
+
const toolParams = req.params;
|
|
827
|
+
const tool = this.metadata.tools.find((t) => t.name === toolParams.name);
|
|
828
|
+
if (!tool) {
|
|
829
|
+
return {
|
|
830
|
+
jsonrpc: "2.0",
|
|
831
|
+
error: { code: -32602, message: `Unknown tool: ${toolParams.name}` },
|
|
832
|
+
id: req.id
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
const method = Reflect.get(this.instance, tool.propertyKey);
|
|
836
|
+
const toolResult = await method.apply(this.instance, [toolParams.arguments ?? {}]);
|
|
837
|
+
result = {
|
|
838
|
+
content: [
|
|
839
|
+
{
|
|
840
|
+
type: "text",
|
|
841
|
+
text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
|
|
842
|
+
}
|
|
843
|
+
]
|
|
844
|
+
};
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
case "resources/list":
|
|
848
|
+
result = {
|
|
849
|
+
resources: this.metadata.resources.map((r) => ({
|
|
850
|
+
uri: r.uri,
|
|
851
|
+
name: r.name,
|
|
852
|
+
description: r.description,
|
|
853
|
+
mimeType: r.mimeType
|
|
854
|
+
}))
|
|
855
|
+
};
|
|
856
|
+
break;
|
|
857
|
+
case "resources/read": {
|
|
858
|
+
const resParams = req.params;
|
|
859
|
+
for (const resource of this.metadata.resources) {
|
|
860
|
+
const uriParams = this.extractUriParams(resource.uri, resParams.uri);
|
|
861
|
+
if (uriParams) {
|
|
862
|
+
const resMethod = Reflect.get(this.instance, resource.propertyKey);
|
|
863
|
+
const args = this.resolveResourceArgs(resource.propertyKey, uriParams);
|
|
864
|
+
result = await resMethod.apply(this.instance, args);
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (!result) {
|
|
869
|
+
return {
|
|
870
|
+
jsonrpc: "2.0",
|
|
871
|
+
error: { code: -32602, message: `Resource not found: ${resParams.uri}` },
|
|
872
|
+
id: req.id
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
case "prompts/list":
|
|
878
|
+
result = {
|
|
879
|
+
prompts: this.metadata.prompts.map((p) => ({
|
|
880
|
+
name: p.name,
|
|
881
|
+
description: p.description,
|
|
882
|
+
arguments: p.arguments ?? []
|
|
883
|
+
}))
|
|
884
|
+
};
|
|
885
|
+
break;
|
|
886
|
+
case "prompts/get": {
|
|
887
|
+
const promptParams = req.params;
|
|
888
|
+
const prompt = this.metadata.prompts.find((p) => p.name === promptParams.name);
|
|
889
|
+
if (!prompt) {
|
|
890
|
+
return {
|
|
891
|
+
jsonrpc: "2.0",
|
|
892
|
+
error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` },
|
|
893
|
+
id: req.id
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
const promptMethod = Reflect.get(this.instance, prompt.propertyKey);
|
|
897
|
+
const promptArgs = this.resolvePromptArgs(
|
|
898
|
+
prompt.propertyKey,
|
|
899
|
+
promptParams.arguments ?? {}
|
|
900
|
+
);
|
|
901
|
+
result = await promptMethod.apply(this.instance, promptArgs);
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
default:
|
|
905
|
+
return {
|
|
906
|
+
jsonrpc: "2.0",
|
|
907
|
+
error: { code: -32601, message: `Method not found: ${req.method}` },
|
|
908
|
+
id: req.id
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return { jsonrpc: "2.0", result, id: req.id };
|
|
912
|
+
} catch (error) {
|
|
913
|
+
return {
|
|
914
|
+
jsonrpc: "2.0",
|
|
915
|
+
error: { code: -32e3, message: error instanceof Error ? error.message : "Internal error" },
|
|
916
|
+
id: req.id
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
342
920
|
};
|
|
343
|
-
async function createMcpServer(target, options) {
|
|
921
|
+
async function createMcpServer(target, options = {}) {
|
|
344
922
|
const server = new McpRuntimeServer(target, options);
|
|
345
|
-
|
|
923
|
+
if (options.transport === "sse") {
|
|
924
|
+
await server.startSSE({ port: options.port, endpoint: options.endpoint });
|
|
925
|
+
} else if (options.transport === "websocket") {
|
|
926
|
+
await server.startWebSocket({ port: options.port, endpoint: options.endpoint });
|
|
927
|
+
} else {
|
|
928
|
+
await server.start();
|
|
929
|
+
}
|
|
346
930
|
return server;
|
|
347
931
|
}
|
|
348
932
|
|
|
@@ -366,13 +950,21 @@ exports.McpRuntimeServer = McpRuntimeServer;
|
|
|
366
950
|
exports.McpServer = McpServer;
|
|
367
951
|
exports.McpTool = McpTool;
|
|
368
952
|
exports.VERSION = VERSION;
|
|
953
|
+
exports.createAuthMiddleware = createAuthMiddleware;
|
|
369
954
|
exports.createMcpServer = createMcpServer;
|
|
955
|
+
exports.extractApiKey = extractApiKey;
|
|
956
|
+
exports.generateApiKey = generateApiKey;
|
|
957
|
+
exports.generateRequestId = generateRequestId;
|
|
370
958
|
exports.getMcpServers = getMcpServers;
|
|
371
959
|
exports.getParamsMetadata = getParamsMetadata;
|
|
372
960
|
exports.getPromptsMetadata = getPromptsMetadata;
|
|
373
961
|
exports.getResourcesMetadata = getResourcesMetadata;
|
|
374
962
|
exports.getServerMetadata = getServerMetadata;
|
|
375
963
|
exports.getToolsMetadata = getToolsMetadata;
|
|
964
|
+
exports.hashToken = hashToken;
|
|
376
965
|
exports.isMcpServer = isMcpServer;
|
|
966
|
+
exports.normalizeApiKeys = normalizeApiKeys;
|
|
967
|
+
exports.sendUnauthorized = sendUnauthorized;
|
|
968
|
+
exports.validateApiKey = validateApiKey;
|
|
377
969
|
//# sourceMappingURL=index.js.map
|
|
378
970
|
//# sourceMappingURL=index.js.map
|