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