@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.mjs CHANGED
@@ -1,17 +1,13 @@
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';
5
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
7
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
7
- import { createServer } from 'http';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
9
 
9
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
- }) : x)(function(x) {
12
- if (typeof require !== "undefined") return require.apply(this, arguments);
13
- throw Error('Dynamic require of "' + x + '" is not supported');
14
- });
10
+ // src/index.ts
15
11
  function McpServer(options) {
16
12
  return (target) => {
17
13
  const metadata = {
@@ -131,15 +127,162 @@ function getPromptsMetadata(target) {
131
127
  function getParamsMetadata(target) {
132
128
  return Reflect.getMetadata(METADATA_KEYS.PARAMS, target) ?? [];
133
129
  }
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
+
272
+ // src/runtime/server.ts
134
273
  var McpRuntimeServer = class {
135
274
  server;
136
275
  instance;
137
276
  metadata;
138
- constructor(target, _options = {}) {
277
+ authOptions;
278
+ authMiddleware;
279
+ constructor(target, options = {}) {
139
280
  this.metadata = extractMetadata(target);
140
281
  if (!this.metadata.server) {
141
282
  throw new Error(`Class ${target.name} is not decorated with @McpServer`);
142
283
  }
284
+ this.authOptions = options.auth ?? {};
285
+ this.authMiddleware = createAuthMiddleware(this.authOptions);
143
286
  this.server = new Server(
144
287
  {
145
288
  name: this.metadata.server.name,
@@ -346,22 +489,29 @@ var McpRuntimeServer = class {
346
489
  const httpServer = createServer(async (req, res) => {
347
490
  res.setHeader("Access-Control-Allow-Origin", "*");
348
491
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
349
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
492
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Api-Key");
350
493
  if (req.method === "OPTIONS") {
351
494
  res.writeHead(200);
352
495
  res.end();
353
496
  return;
354
497
  }
498
+ const authResult = await this.authMiddleware(req, res);
499
+ if (!authResult) return;
355
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
+ }
356
506
  if (url.pathname === endpoint && req.method === "GET") {
357
507
  const transport = new SSEServerTransport(endpoint, res);
358
508
  await this.server.connect(transport);
359
509
  return;
360
510
  }
361
511
  if (url.pathname === `${endpoint}/message` && req.method === "POST") {
362
- let body = "";
512
+ let _body = "";
363
513
  req.on("data", (chunk) => {
364
- body += chunk.toString();
514
+ _body += chunk.toString();
365
515
  });
366
516
  req.on("end", () => {
367
517
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -405,10 +555,13 @@ var McpRuntimeServer = class {
405
555
  async startWebSocket(options = {}) {
406
556
  const port = options.port ?? 8080;
407
557
  const endpoint = options.endpoint ?? "/ws";
408
- const httpServer = createServer((req, res) => {
558
+ const httpServer = createServer(async (req, res) => {
409
559
  res.setHeader("Access-Control-Allow-Origin", "*");
410
560
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
411
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Upgrade, Connection");
561
+ res.setHeader(
562
+ "Access-Control-Allow-Headers",
563
+ "Content-Type, Upgrade, Connection, Authorization, X-Api-Key"
564
+ );
412
565
  if (req.method === "OPTIONS") {
413
566
  res.writeHead(200);
414
567
  res.end();
@@ -421,12 +574,15 @@ var McpRuntimeServer = class {
421
574
  status: "ok",
422
575
  server: this.metadata.server?.name,
423
576
  version: this.metadata.server?.version,
424
- transport: "websocket"
577
+ transport: "websocket",
578
+ authEnabled: this.authOptions.enabled ?? false
425
579
  })
426
580
  );
427
581
  return;
428
582
  }
429
- if (req.url === "/") {
583
+ const authResult = await this.authMiddleware(req, res);
584
+ if (!authResult) return;
585
+ if (req.url === "/" || req.url?.startsWith("/?")) {
430
586
  res.writeHead(200, { "Content-Type": "application/json" });
431
587
  res.end(
432
588
  JSON.stringify({
@@ -435,28 +591,52 @@ var McpRuntimeServer = class {
435
591
  websocket: `ws://localhost:${port}${endpoint}`,
436
592
  tools: this.metadata.tools.length,
437
593
  resources: this.metadata.resources.length,
438
- prompts: this.metadata.prompts.length
594
+ prompts: this.metadata.prompts.length,
595
+ client: authResult.auth.clientName,
596
+ requestId: authResult.requestId
439
597
  })
440
598
  );
441
599
  return;
442
600
  }
443
601
  res.writeHead(426, { "Content-Type": "application/json" });
444
- res.end(JSON.stringify({ error: "Upgrade Required", message: `Connect via WebSocket at ${endpoint}` }));
602
+ res.end(
603
+ JSON.stringify({
604
+ error: "Upgrade Required",
605
+ message: `Connect via WebSocket at ${endpoint}`
606
+ })
607
+ );
445
608
  });
446
- httpServer.on("upgrade", (req, socket, _head) => {
609
+ httpServer.on("upgrade", async (req, socket, _head) => {
447
610
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
448
611
  if (url.pathname !== endpoint) {
449
612
  socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
450
613
  socket.destroy();
451
614
  return;
452
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
+ }
453
634
  const key = req.headers["sec-websocket-key"];
454
635
  if (!key) {
455
636
  socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
456
637
  socket.destroy();
457
638
  return;
458
639
  }
459
- const crypto = __require("crypto");
460
640
  const acceptKey = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
461
641
  socket.write(
462
642
  `HTTP/1.1 101 Switching Protocols\r
@@ -531,11 +711,13 @@ Sec-WebSocket-Accept: ${acceptKey}\r
531
711
  }
532
712
  };
533
713
  sessions.set(sessionId, { send });
534
- send(JSON.stringify({
535
- jsonrpc: "2.0",
536
- method: "connection/established",
537
- params: { sessionId, server: this.metadata.server?.name }
538
- }));
714
+ send(
715
+ JSON.stringify({
716
+ jsonrpc: "2.0",
717
+ method: "connection/established",
718
+ params: { sessionId, server: this.metadata.server?.name }
719
+ })
720
+ );
539
721
  let messageBuffer = Buffer.alloc(0);
540
722
  socket.on("data", async (chunk) => {
541
723
  messageBuffer = Buffer.concat([messageBuffer, chunk]);
@@ -566,11 +748,13 @@ Sec-WebSocket-Accept: ${acceptKey}\r
566
748
  send(JSON.stringify(response));
567
749
  }
568
750
  } catch (error) {
569
- send(JSON.stringify({
570
- jsonrpc: "2.0",
571
- error: { code: -32700, message: "Parse error" },
572
- id: null
573
- }));
751
+ send(
752
+ JSON.stringify({
753
+ jsonrpc: "2.0",
754
+ error: { code: -32700, message: "Parse error" },
755
+ id: null
756
+ })
757
+ );
574
758
  }
575
759
  }
576
760
  }
@@ -601,7 +785,11 @@ Sec-WebSocket-Accept: ${acceptKey}\r
601
785
  }
602
786
  const req = message;
603
787
  if (req.jsonrpc !== "2.0" || !req.method) {
604
- return { jsonrpc: "2.0", error: { code: -32600, message: "Invalid Request" }, id: req.id ?? null };
788
+ return {
789
+ jsonrpc: "2.0",
790
+ error: { code: -32600, message: "Invalid Request" },
791
+ id: req.id ?? null
792
+ };
605
793
  }
606
794
  try {
607
795
  let result;
@@ -629,21 +817,28 @@ Sec-WebSocket-Accept: ${acceptKey}\r
629
817
  }))
630
818
  };
631
819
  break;
632
- case "tools/call":
820
+ case "tools/call": {
633
821
  const toolParams = req.params;
634
822
  const tool = this.metadata.tools.find((t) => t.name === toolParams.name);
635
823
  if (!tool) {
636
- return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown tool: ${toolParams.name}` }, id: req.id };
824
+ return {
825
+ jsonrpc: "2.0",
826
+ error: { code: -32602, message: `Unknown tool: ${toolParams.name}` },
827
+ id: req.id
828
+ };
637
829
  }
638
830
  const method = Reflect.get(this.instance, tool.propertyKey);
639
831
  const toolResult = await method.apply(this.instance, [toolParams.arguments ?? {}]);
640
832
  result = {
641
- content: [{
642
- type: "text",
643
- text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
644
- }]
833
+ content: [
834
+ {
835
+ type: "text",
836
+ text: typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)
837
+ }
838
+ ]
645
839
  };
646
840
  break;
841
+ }
647
842
  case "resources/list":
648
843
  result = {
649
844
  resources: this.metadata.resources.map((r) => ({
@@ -654,7 +849,7 @@ Sec-WebSocket-Accept: ${acceptKey}\r
654
849
  }))
655
850
  };
656
851
  break;
657
- case "resources/read":
852
+ case "resources/read": {
658
853
  const resParams = req.params;
659
854
  for (const resource of this.metadata.resources) {
660
855
  const uriParams = this.extractUriParams(resource.uri, resParams.uri);
@@ -666,9 +861,14 @@ Sec-WebSocket-Accept: ${acceptKey}\r
666
861
  }
667
862
  }
668
863
  if (!result) {
669
- return { jsonrpc: "2.0", error: { code: -32602, message: `Resource not found: ${resParams.uri}` }, id: req.id };
864
+ return {
865
+ jsonrpc: "2.0",
866
+ error: { code: -32602, message: `Resource not found: ${resParams.uri}` },
867
+ id: req.id
868
+ };
670
869
  }
671
870
  break;
871
+ }
672
872
  case "prompts/list":
673
873
  result = {
674
874
  prompts: this.metadata.prompts.map((p) => ({
@@ -678,18 +878,30 @@ Sec-WebSocket-Accept: ${acceptKey}\r
678
878
  }))
679
879
  };
680
880
  break;
681
- case "prompts/get":
881
+ case "prompts/get": {
682
882
  const promptParams = req.params;
683
883
  const prompt = this.metadata.prompts.find((p) => p.name === promptParams.name);
684
884
  if (!prompt) {
685
- return { jsonrpc: "2.0", error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` }, id: req.id };
885
+ return {
886
+ jsonrpc: "2.0",
887
+ error: { code: -32602, message: `Unknown prompt: ${promptParams.name}` },
888
+ id: req.id
889
+ };
686
890
  }
687
891
  const promptMethod = Reflect.get(this.instance, prompt.propertyKey);
688
- const promptArgs = this.resolvePromptArgs(prompt.propertyKey, promptParams.arguments ?? {});
892
+ const promptArgs = this.resolvePromptArgs(
893
+ prompt.propertyKey,
894
+ promptParams.arguments ?? {}
895
+ );
689
896
  result = await promptMethod.apply(this.instance, promptArgs);
690
897
  break;
898
+ }
691
899
  default:
692
- return { jsonrpc: "2.0", error: { code: -32601, message: `Method not found: ${req.method}` }, id: req.id };
900
+ return {
901
+ jsonrpc: "2.0",
902
+ error: { code: -32601, message: `Method not found: ${req.method}` },
903
+ id: req.id
904
+ };
693
905
  }
694
906
  return { jsonrpc: "2.0", result, id: req.id };
695
907
  } catch (error) {
@@ -716,6 +928,6 @@ async function createMcpServer(target, options = {}) {
716
928
  // src/index.ts
717
929
  var VERSION = "0.1.0";
718
930
 
719
- 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 };
720
932
  //# sourceMappingURL=index.mjs.map
721
933
  //# sourceMappingURL=index.mjs.map