@mcp-weave/nestjs 0.2.0 → 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/dist/index.js CHANGED
@@ -2,17 +2,17 @@
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');
6
- var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
7
8
  var sse_js = require('@modelcontextprotocol/sdk/server/sse.js');
8
- var http = require('http');
9
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
9
10
 
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
- });
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
14
+
15
+ // src/index.ts
16
16
  function McpServer(options) {
17
17
  return (target) => {
18
18
  const metadata = {
@@ -132,15 +132,162 @@ function getPromptsMetadata(target) {
132
132
  function getParamsMetadata(target) {
133
133
  return Reflect.getMetadata(core.METADATA_KEYS.PARAMS, target) ?? [];
134
134
  }
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
+
277
+ // src/runtime/server.ts
135
278
  var McpRuntimeServer = class {
136
279
  server;
137
280
  instance;
138
281
  metadata;
139
- constructor(target, _options = {}) {
282
+ authOptions;
283
+ authMiddleware;
284
+ constructor(target, options = {}) {
140
285
  this.metadata = core.extractMetadata(target);
141
286
  if (!this.metadata.server) {
142
287
  throw new Error(`Class ${target.name} is not decorated with @McpServer`);
143
288
  }
289
+ this.authOptions = options.auth ?? {};
290
+ this.authMiddleware = createAuthMiddleware(this.authOptions);
144
291
  this.server = new index_js.Server(
145
292
  {
146
293
  name: this.metadata.server.name,
@@ -347,22 +494,29 @@ var McpRuntimeServer = class {
347
494
  const httpServer = http.createServer(async (req, res) => {
348
495
  res.setHeader("Access-Control-Allow-Origin", "*");
349
496
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
350
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
497
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Api-Key");
351
498
  if (req.method === "OPTIONS") {
352
499
  res.writeHead(200);
353
500
  res.end();
354
501
  return;
355
502
  }
503
+ const authResult = await this.authMiddleware(req, res);
504
+ if (!authResult) return;
356
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
+ }
357
511
  if (url.pathname === endpoint && req.method === "GET") {
358
512
  const transport = new sse_js.SSEServerTransport(endpoint, res);
359
513
  await this.server.connect(transport);
360
514
  return;
361
515
  }
362
516
  if (url.pathname === `${endpoint}/message` && req.method === "POST") {
363
- let body = "";
517
+ let _body = "";
364
518
  req.on("data", (chunk) => {
365
- body += chunk.toString();
519
+ _body += chunk.toString();
366
520
  });
367
521
  req.on("end", () => {
368
522
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -406,10 +560,13 @@ var McpRuntimeServer = class {
406
560
  async startWebSocket(options = {}) {
407
561
  const port = options.port ?? 8080;
408
562
  const endpoint = options.endpoint ?? "/ws";
409
- const httpServer = http.createServer((req, res) => {
563
+ const httpServer = http.createServer(async (req, res) => {
410
564
  res.setHeader("Access-Control-Allow-Origin", "*");
411
565
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
412
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Upgrade, Connection");
566
+ res.setHeader(
567
+ "Access-Control-Allow-Headers",
568
+ "Content-Type, Upgrade, Connection, Authorization, X-Api-Key"
569
+ );
413
570
  if (req.method === "OPTIONS") {
414
571
  res.writeHead(200);
415
572
  res.end();
@@ -422,12 +579,15 @@ var McpRuntimeServer = class {
422
579
  status: "ok",
423
580
  server: this.metadata.server?.name,
424
581
  version: this.metadata.server?.version,
425
- transport: "websocket"
582
+ transport: "websocket",
583
+ authEnabled: this.authOptions.enabled ?? false
426
584
  })
427
585
  );
428
586
  return;
429
587
  }
430
- if (req.url === "/") {
588
+ const authResult = await this.authMiddleware(req, res);
589
+ if (!authResult) return;
590
+ if (req.url === "/" || req.url?.startsWith("/?")) {
431
591
  res.writeHead(200, { "Content-Type": "application/json" });
432
592
  res.end(
433
593
  JSON.stringify({
@@ -436,29 +596,53 @@ var McpRuntimeServer = class {
436
596
  websocket: `ws://localhost:${port}${endpoint}`,
437
597
  tools: this.metadata.tools.length,
438
598
  resources: this.metadata.resources.length,
439
- prompts: this.metadata.prompts.length
599
+ prompts: this.metadata.prompts.length,
600
+ client: authResult.auth.clientName,
601
+ requestId: authResult.requestId
440
602
  })
441
603
  );
442
604
  return;
443
605
  }
444
606
  res.writeHead(426, { "Content-Type": "application/json" });
445
- res.end(JSON.stringify({ error: "Upgrade Required", message: `Connect via WebSocket at ${endpoint}` }));
607
+ res.end(
608
+ JSON.stringify({
609
+ error: "Upgrade Required",
610
+ message: `Connect via WebSocket at ${endpoint}`
611
+ })
612
+ );
446
613
  });
447
- httpServer.on("upgrade", (req, socket, _head) => {
614
+ httpServer.on("upgrade", async (req, socket, _head) => {
448
615
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
449
616
  if (url.pathname !== endpoint) {
450
617
  socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
451
618
  socket.destroy();
452
619
  return;
453
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
+ }
454
639
  const key = req.headers["sec-websocket-key"];
455
640
  if (!key) {
456
641
  socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
457
642
  socket.destroy();
458
643
  return;
459
644
  }
460
- const crypto = __require("crypto");
461
- const acceptKey = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
645
+ const acceptKey = crypto__default.default.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
462
646
  socket.write(
463
647
  `HTTP/1.1 101 Switching Protocols\r
464
648
  Upgrade: websocket\r
@@ -468,7 +652,7 @@ Sec-WebSocket-Accept: ${acceptKey}\r
468
652
  `
469
653
  );
470
654
  const sessions = /* @__PURE__ */ new Map();
471
- const sessionId = crypto.randomUUID();
655
+ const sessionId = crypto__default.default.randomUUID();
472
656
  const encodeFrame = (data) => {
473
657
  const payload = Buffer.from(data, "utf8");
474
658
  const length = payload.length;
@@ -532,11 +716,13 @@ Sec-WebSocket-Accept: ${acceptKey}\r
532
716
  }
533
717
  };
534
718
  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
- }));
719
+ send(
720
+ JSON.stringify({
721
+ jsonrpc: "2.0",
722
+ method: "connection/established",
723
+ params: { sessionId, server: this.metadata.server?.name }
724
+ })
725
+ );
540
726
  let messageBuffer = Buffer.alloc(0);
541
727
  socket.on("data", async (chunk) => {
542
728
  messageBuffer = Buffer.concat([messageBuffer, chunk]);
@@ -567,11 +753,13 @@ Sec-WebSocket-Accept: ${acceptKey}\r
567
753
  send(JSON.stringify(response));
568
754
  }
569
755
  } catch (error) {
570
- send(JSON.stringify({
571
- jsonrpc: "2.0",
572
- error: { code: -32700, message: "Parse error" },
573
- id: null
574
- }));
756
+ send(
757
+ JSON.stringify({
758
+ jsonrpc: "2.0",
759
+ error: { code: -32700, message: "Parse error" },
760
+ id: null
761
+ })
762
+ );
575
763
  }
576
764
  }
577
765
  }
@@ -602,7 +790,11 @@ Sec-WebSocket-Accept: ${acceptKey}\r
602
790
  }
603
791
  const req = message;
604
792
  if (req.jsonrpc !== "2.0" || !req.method) {
605
- return { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: req.id ?? null };
793
+ return {
794
+ jsonrpc: "2.0",
795
+ error: { code: -32600, message: "Invalid Request" },
796
+ id: req.id ?? null
797
+ };
606
798
  }
607
799
  try {
608
800
  let result;
@@ -630,21 +822,28 @@ Sec-WebSocket-Accept: ${acceptKey}\r
630
822
  }))
631
823
  };
632
824
  break;
633
- case "tools/call":
825
+ case "tools/call": {
634
826
  const toolParams = req.params;
635
827
  const tool = this.metadata.tools.find((t) => t.name === toolParams.name);
636
828
  if (!tool) {
637
- return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown tool: ${toolParams.name}` }, id: req.id };
829
+ return {
830
+ jsonrpc: "2.0",
831
+ error: { code: -32602, message: `Unknown tool: ${toolParams.name}` },
832
+ id: req.id
833
+ };
638
834
  }
639
835
  const method = Reflect.get(this.instance, tool.propertyKey);
640
836
  const toolResult = await method.apply(this.instance, [toolParams.arguments ?? {}]);
641
837
  result = {
642
- content: [{
643
- type: "text",
644
- text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
645
- }]
838
+ content: [
839
+ {
840
+ type: "text",
841
+ text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
842
+ }
843
+ ]
646
844
  };
647
845
  break;
846
+ }
648
847
  case "resources/list":
649
848
  result = {
650
849
  resources: this.metadata.resources.map((r) => ({
@@ -655,7 +854,7 @@ Sec-WebSocket-Accept: ${acceptKey}\r
655
854
  }))
656
855
  };
657
856
  break;
658
- case "resources/read":
857
+ case "resources/read": {
659
858
  const resParams = req.params;
660
859
  for (const resource of this.metadata.resources) {
661
860
  const uriParams = this.extractUriParams(resource.uri, resParams.uri);
@@ -667,9 +866,14 @@ Sec-WebSocket-Accept: ${acceptKey}\r
667
866
  }
668
867
  }
669
868
  if (!result) {
670
- return { jsonrpc: "2.0", error: { code: -32602, message: `Resource not found: ${resParams.uri}` }, id: req.id };
869
+ return {
870
+ jsonrpc: "2.0",
871
+ error: { code: -32602, message: `Resource not found: ${resParams.uri}` },
872
+ id: req.id
873
+ };
671
874
  }
672
875
  break;
876
+ }
673
877
  case "prompts/list":
674
878
  result = {
675
879
  prompts: this.metadata.prompts.map((p) => ({
@@ -679,18 +883,30 @@ Sec-WebSocket-Accept: ${acceptKey}\r
679
883
  }))
680
884
  };
681
885
  break;
682
- case "prompts/get":
886
+ case "prompts/get": {
683
887
  const promptParams = req.params;
684
888
  const prompt = this.metadata.prompts.find((p) => p.name === promptParams.name);
685
889
  if (!prompt) {
686
- return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` }, id: req.id };
890
+ return {
891
+ jsonrpc: "2.0",
892
+ error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` },
893
+ id: req.id
894
+ };
687
895
  }
688
896
  const promptMethod = Reflect.get(this.instance, prompt.propertyKey);
689
- const promptArgs = this.resolvePromptArgs(prompt.propertyKey, promptParams.arguments ?? {});
897
+ const promptArgs = this.resolvePromptArgs(
898
+ prompt.propertyKey,
899
+ promptParams.arguments ?? {}
900
+ );
690
901
  result = await promptMethod.apply(this.instance, promptArgs);
691
902
  break;
903
+ }
692
904
  default:
693
- return { jsonrpc: "2.0", error: { code: -32601, message: `Method not found: ${req.method}` }, id: req.id };
905
+ return {
906
+ jsonrpc: "2.0",
907
+ error: { code: -32601, message: `Method not found: ${req.method}` },
908
+ id: req.id
909
+ };
694
910
  }
695
911
  return { jsonrpc: "2.0", result, id: req.id };
696
912
  } catch (error) {
@@ -734,13 +950,21 @@ exports.McpRuntimeServer = McpRuntimeServer;
734
950
  exports.McpServer = McpServer;
735
951
  exports.McpTool = McpTool;
736
952
  exports.VERSION = VERSION;
953
+ exports.createAuthMiddleware = createAuthMiddleware;
737
954
  exports.createMcpServer = createMcpServer;
955
+ exports.extractApiKey = extractApiKey;
956
+ exports.generateApiKey = generateApiKey;
957
+ exports.generateRequestId = generateRequestId;
738
958
  exports.getMcpServers = getMcpServers;
739
959
  exports.getParamsMetadata = getParamsMetadata;
740
960
  exports.getPromptsMetadata = getPromptsMetadata;
741
961
  exports.getResourcesMetadata = getResourcesMetadata;
742
962
  exports.getServerMetadata = getServerMetadata;
743
963
  exports.getToolsMetadata = getToolsMetadata;
964
+ exports.hashToken = hashToken;
744
965
  exports.isMcpServer = isMcpServer;
966
+ exports.normalizeApiKeys = normalizeApiKeys;
967
+ exports.sendUnauthorized = sendUnauthorized;
968
+ exports.validateApiKey = validateApiKey;
745
969
  //# sourceMappingURL=index.js.map
746
970
  //# sourceMappingURL=index.js.map