@mastra/mcp 1.0.0-beta.0 → 1.0.0-beta.10

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/README.md +250 -24
  3. package/dist/__fixtures__/tools.d.ts +8 -5
  4. package/dist/__fixtures__/tools.d.ts.map +1 -1
  5. package/dist/client/{elicitationActions.d.ts → actions/elicitation.d.ts} +2 -2
  6. package/dist/client/actions/elicitation.d.ts.map +1 -0
  7. package/dist/client/actions/progress.d.ts +23 -0
  8. package/dist/client/actions/progress.d.ts.map +1 -0
  9. package/dist/client/{promptActions.d.ts → actions/prompt.d.ts} +2 -2
  10. package/dist/client/actions/prompt.d.ts.map +1 -0
  11. package/dist/client/{resourceActions.d.ts → actions/resource.d.ts} +2 -2
  12. package/dist/client/actions/resource.d.ts.map +1 -0
  13. package/dist/client/client.d.ts +79 -132
  14. package/dist/client/client.d.ts.map +1 -1
  15. package/dist/client/configuration.d.ts +23 -1
  16. package/dist/client/configuration.d.ts.map +1 -1
  17. package/dist/client/index.d.ts +3 -1
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/oauth-provider.d.ts +230 -0
  20. package/dist/client/oauth-provider.d.ts.map +1 -0
  21. package/dist/client/types.d.ts +237 -0
  22. package/dist/client/types.d.ts.map +1 -0
  23. package/dist/docs/README.md +33 -0
  24. package/dist/docs/SKILL.md +46 -0
  25. package/dist/docs/SOURCE_MAP.json +59 -0
  26. package/dist/docs/mcp/01-overview.md +372 -0
  27. package/dist/docs/mcp/02-publishing-mcp-server.md +111 -0
  28. package/dist/docs/tools/01-reference.md +2306 -0
  29. package/dist/docs/tools-mcp/01-mcp-overview.md +384 -0
  30. package/dist/index.cjs +851 -107
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +774 -92
  35. package/dist/index.js.map +1 -1
  36. package/dist/server/index.d.ts +1 -0
  37. package/dist/server/index.d.ts.map +1 -1
  38. package/dist/server/oauth-middleware.d.ts +142 -0
  39. package/dist/server/oauth-middleware.d.ts.map +1 -0
  40. package/dist/server/server.d.ts +20 -4
  41. package/dist/server/server.d.ts.map +1 -1
  42. package/dist/server/types.d.ts +11 -4
  43. package/dist/server/types.d.ts.map +1 -1
  44. package/dist/shared/index.d.ts +2 -0
  45. package/dist/shared/index.d.ts.map +1 -0
  46. package/dist/shared/oauth-types.d.ts +137 -0
  47. package/dist/shared/oauth-types.d.ts.map +1 -0
  48. package/package.json +17 -14
  49. package/dist/client/elicitationActions.d.ts.map +0 -1
  50. package/dist/client/promptActions.d.ts.map +0 -1
  51. package/dist/client/resourceActions.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
+ import $RefParser from '@apidevtools/json-schema-ref-parser';
1
2
  import { MastraBase } from '@mastra/core/base';
2
3
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
4
- import equal from 'fast-deep-equal';
5
- import { v5 } from 'uuid';
6
- import $RefParser from '@apidevtools/json-schema-ref-parser';
7
4
  import { createTool } from '@mastra/core/tools';
8
- import { makeCoreTool, isZodType } from '@mastra/core/utils';
5
+ import { isZodType, makeCoreTool } from '@mastra/core/utils';
9
6
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
10
7
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
11
8
  import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
12
9
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
13
- import { ListToolsRequestSchema, CallToolRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListPromptsRequestSchema, PromptSchema, GetPromptRequestSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListResourceTemplatesResultSchema, ListPromptsResultSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, ElicitRequestSchema, CallToolResultSchema, JSONRPCMessageSchema, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
10
+ import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
11
+ import { ListRootsRequestSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListResourceTemplatesResultSchema, ListPromptsResultSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, ElicitRequestSchema, ProgressNotificationSchema, ListToolsRequestSchema, CallToolRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListPromptsRequestSchema, PromptSchema, GetPromptRequestSchema, CallToolResultSchema, ErrorCode, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
14
12
  import { asyncExitHook, gracefulExit } from 'exit-hook';
15
13
  import { z } from 'zod';
16
14
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
17
15
  import { convertJsonSchemaToZod as convertJsonSchemaToZod$1 } from 'zod-from-json-schema-v3';
16
+ import equal from 'fast-deep-equal';
17
+ import { v5 } from 'uuid';
18
18
  import { randomUUID } from 'crypto';
19
19
  import { MCPServerBase } from '@mastra/core/mcp';
20
20
  import { RequestContext } from '@mastra/core/request-context';
@@ -22,10 +22,11 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
22
22
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
23
23
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
24
24
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
25
+ export { UnauthorizedError, auth, buildDiscoveryUrls, discoverAuthorizationServerMetadata, discoverOAuthMetadata, discoverOAuthProtectedResourceMetadata, exchangeAuthorization, extractResourceMetadataUrl, parseErrorResponse, refreshAuthorization, registerClient, selectResourceURL, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
25
26
 
26
- // src/client/configuration.ts
27
+ // src/client/client.ts
27
28
 
28
- // src/client/elicitationActions.ts
29
+ // src/client/actions/elicitation.ts
29
30
  var ElicitationClientActions = class {
30
31
  client;
31
32
  logger;
@@ -78,6 +79,23 @@ var ElicitationClientActions = class {
78
79
  this.client.setElicitationRequestHandler(handler);
79
80
  }
80
81
  };
82
+
83
+ // src/client/actions/progress.ts
84
+ var ProgressClientActions = class {
85
+ client;
86
+ logger;
87
+ constructor({ client, logger }) {
88
+ this.client = client;
89
+ this.logger = logger;
90
+ }
91
+ /**
92
+ * Set a notification handler for progress updates.
93
+ * @param handler The callback function to handle progress notifications.
94
+ */
95
+ onUpdate(handler) {
96
+ this.client.setProgressNotificationHandler(handler);
97
+ }
98
+ };
81
99
  var PromptClientActions = class {
82
100
  client;
83
101
  logger;
@@ -366,6 +384,8 @@ var ResourceClientActions = class {
366
384
  };
367
385
 
368
386
  // src/client/client.ts
387
+ var DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC = 3e3;
388
+ var SSE_FALLBACK_STATUS_CODES = [400, 404, 405];
369
389
  function convertLogLevelToLoggerMethod(level) {
370
390
  switch (level) {
371
391
  case "debug":
@@ -390,15 +410,21 @@ var InternalMastraMCPClient = class extends MastraBase {
390
410
  timeout;
391
411
  logHandler;
392
412
  enableServerLogs;
413
+ enableProgressTracking;
393
414
  serverConfig;
394
415
  transport;
395
416
  currentOperationContext = null;
417
+ exitHookUnsubscribe;
418
+ sigTermHandler;
419
+ _roots;
396
420
  /** Provides access to resource operations (list, read, subscribe, etc.) */
397
421
  resources;
398
422
  /** Provides access to prompt operations (list, get, notifications) */
399
423
  prompts;
400
424
  /** Provides access to elicitation operations (request handling) */
401
425
  elicitation;
426
+ /** Provides access to progress operations (notifications) */
427
+ progress;
402
428
  /**
403
429
  * @internal
404
430
  */
@@ -415,7 +441,15 @@ var InternalMastraMCPClient = class extends MastraBase {
415
441
  this.logHandler = server.logger;
416
442
  this.enableServerLogs = server.enableServerLogs ?? true;
417
443
  this.serverConfig = server;
418
- const clientCapabilities = { ...capabilities, elicitation: {} };
444
+ this.enableProgressTracking = !!server.enableProgressTracking;
445
+ this._roots = server.roots ?? [];
446
+ const hasRoots = this._roots.length > 0 || !!capabilities.roots;
447
+ const clientCapabilities = {
448
+ ...capabilities,
449
+ elicitation: {},
450
+ // Auto-enable roots capability if roots are provided
451
+ ...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {}
452
+ };
419
453
  this.client = new Client(
420
454
  {
421
455
  name,
@@ -426,9 +460,13 @@ var InternalMastraMCPClient = class extends MastraBase {
426
460
  }
427
461
  );
428
462
  this.setupLogging();
463
+ if (hasRoots) {
464
+ this.setupRootsHandler();
465
+ }
429
466
  this.resources = new ResourceClientActions({ client: this, logger: this.logger });
430
467
  this.prompts = new PromptClientActions({ client: this, logger: this.logger });
431
468
  this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
469
+ this.progress = new ProgressClientActions({ client: this, logger: this.logger });
432
470
  }
433
471
  /**
434
472
  * Log a message at the specified level
@@ -467,6 +505,63 @@ var InternalMastraMCPClient = class extends MastraBase {
467
505
  );
468
506
  }
469
507
  }
508
+ /**
509
+ * Set up handler for roots/list requests from the server.
510
+ *
511
+ * Per MCP spec (https://modelcontextprotocol.io/specification/2025-11-25/client/roots):
512
+ * When a server sends a roots/list request, the client responds with the configured roots.
513
+ */
514
+ setupRootsHandler() {
515
+ this.log("debug", "Setting up roots/list request handler");
516
+ this.client.setRequestHandler(ListRootsRequestSchema, async () => {
517
+ this.log("debug", `Responding to roots/list request with ${this._roots.length} roots`);
518
+ return { roots: this._roots };
519
+ });
520
+ }
521
+ /**
522
+ * Get the currently configured roots.
523
+ *
524
+ * @returns Array of configured filesystem roots
525
+ */
526
+ get roots() {
527
+ return [...this._roots];
528
+ }
529
+ /**
530
+ * Update the list of filesystem roots and notify the server.
531
+ *
532
+ * Per MCP spec, when roots change, the client sends a `notifications/roots/list_changed`
533
+ * notification to inform the server that it should re-fetch the roots list.
534
+ *
535
+ * @param roots - New list of filesystem roots
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * await client.setRoots([
540
+ * { uri: 'file:///home/user/projects', name: 'Projects' },
541
+ * { uri: 'file:///tmp', name: 'Temp' }
542
+ * ]);
543
+ * ```
544
+ */
545
+ async setRoots(roots) {
546
+ this.log("debug", `Updating roots to ${roots.length} entries`);
547
+ this._roots = [...roots];
548
+ await this.sendRootsListChanged();
549
+ }
550
+ /**
551
+ * Send a roots/list_changed notification to the server.
552
+ *
553
+ * Per MCP spec, clients that support `listChanged` MUST send this notification
554
+ * when the list of roots changes. The server will then call roots/list to get
555
+ * the updated list.
556
+ */
557
+ async sendRootsListChanged() {
558
+ if (!this.transport) {
559
+ this.log("debug", "Cannot send roots/list_changed: not connected");
560
+ return;
561
+ }
562
+ this.log("debug", "Sending notifications/roots/list_changed");
563
+ await this.client.notification({ method: "notifications/roots/list_changed" });
564
+ }
470
565
  async connectStdio(command) {
471
566
  this.log("debug", `Using Stdio transport for command: ${command}`);
472
567
  try {
@@ -483,7 +578,7 @@ var InternalMastraMCPClient = class extends MastraBase {
483
578
  }
484
579
  }
485
580
  async connectHttp(url) {
486
- const { requestInit, eventSourceInit, authProvider } = this.serverConfig;
581
+ const { requestInit, eventSourceInit, authProvider, connectTimeout, fetch: fetch2 } = this.serverConfig;
487
582
  this.log("debug", `Attempting to connect to URL: ${url}`);
488
583
  let shouldTrySSE = url.pathname.endsWith(`/sse`);
489
584
  if (!shouldTrySSE) {
@@ -492,25 +587,33 @@ var InternalMastraMCPClient = class extends MastraBase {
492
587
  const streamableTransport = new StreamableHTTPClientTransport(url, {
493
588
  requestInit,
494
589
  reconnectionOptions: this.serverConfig.reconnectionOptions,
495
- authProvider
590
+ authProvider,
591
+ fetch: fetch2
496
592
  });
497
593
  await this.client.connect(streamableTransport, {
498
- timeout: (
499
- // this is hardcoded to 3s because the long default timeout would be extremely slow for sse backwards compat (60s)
500
- 3e3
501
- )
594
+ timeout: connectTimeout ?? DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC
502
595
  });
503
596
  this.transport = streamableTransport;
504
597
  this.log("debug", "Successfully connected using Streamable HTTP transport.");
505
598
  } catch (error) {
506
599
  this.log("debug", `Streamable HTTP transport failed: ${error}`);
600
+ const status = error?.code;
601
+ if (status !== void 0 && !SSE_FALLBACK_STATUS_CODES.includes(status)) {
602
+ throw error;
603
+ }
507
604
  shouldTrySSE = true;
508
605
  }
509
606
  }
510
607
  if (shouldTrySSE) {
511
608
  this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
512
609
  try {
513
- const sseTransport = new SSEClientTransport(url, { requestInit, eventSourceInit, authProvider });
610
+ const sseEventSourceInit = fetch2 ? { ...eventSourceInit, fetch: fetch2 } : eventSourceInit;
611
+ const sseTransport = new SSEClientTransport(url, {
612
+ requestInit,
613
+ eventSourceInit: sseEventSourceInit,
614
+ authProvider,
615
+ fetch: fetch2
616
+ });
514
617
  await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
515
618
  this.transport = sseTransport;
516
619
  this.log("debug", "Successfully connected using deprecated HTTP+SSE transport.");
@@ -563,14 +666,19 @@ var InternalMastraMCPClient = class extends MastraBase {
563
666
  reject(e);
564
667
  }
565
668
  });
566
- asyncExitHook(
567
- async () => {
568
- this.log("debug", `Disconnecting MCP server during exit`);
569
- await this.disconnect();
570
- },
571
- { wait: 5e3 }
572
- );
573
- process.on("SIGTERM", () => gracefulExit());
669
+ if (!this.exitHookUnsubscribe) {
670
+ this.exitHookUnsubscribe = asyncExitHook(
671
+ async () => {
672
+ this.log("debug", `Disconnecting MCP server during exit`);
673
+ await this.disconnect();
674
+ },
675
+ { wait: 5e3 }
676
+ );
677
+ }
678
+ if (!this.sigTermHandler) {
679
+ this.sigTermHandler = () => gracefulExit();
680
+ process.on("SIGTERM", this.sigTermHandler);
681
+ }
574
682
  this.log("debug", `Successfully connected to MCP server`);
575
683
  return this.isConnected;
576
684
  }
@@ -605,8 +713,64 @@ var InternalMastraMCPClient = class extends MastraBase {
605
713
  throw e;
606
714
  } finally {
607
715
  this.transport = void 0;
608
- this.isConnected = Promise.resolve(false);
716
+ this.isConnected = null;
717
+ if (this.exitHookUnsubscribe) {
718
+ this.exitHookUnsubscribe();
719
+ this.exitHookUnsubscribe = void 0;
720
+ }
721
+ if (this.sigTermHandler) {
722
+ process.off("SIGTERM", this.sigTermHandler);
723
+ this.sigTermHandler = void 0;
724
+ }
725
+ }
726
+ }
727
+ /**
728
+ * Checks if an error indicates a session invalidation that requires reconnection.
729
+ *
730
+ * Common session-related errors include:
731
+ * - "No valid session ID provided" (HTTP 400)
732
+ * - "Server not initialized" (HTTP 400)
733
+ * - "Not connected" (protocol state error)
734
+ * - Connection refused errors
735
+ *
736
+ * @param error - The error to check
737
+ * @returns true if the error indicates a session problem requiring reconnection
738
+ *
739
+ * @internal
740
+ */
741
+ isSessionError(error) {
742
+ if (!(error instanceof Error)) {
743
+ return false;
744
+ }
745
+ const errorMessage = error.message.toLowerCase();
746
+ return errorMessage.includes("no valid session") || errorMessage.includes("session") || errorMessage.includes("server not initialized") || errorMessage.includes("not connected") || errorMessage.includes("http 400") || errorMessage.includes("http 401") || errorMessage.includes("http 403") || errorMessage.includes("econnrefused") || errorMessage.includes("fetch failed") || errorMessage.includes("connection refused");
747
+ }
748
+ /**
749
+ * Forces a reconnection to the MCP server by disconnecting and reconnecting.
750
+ *
751
+ * This is useful when the session becomes invalid (e.g., after server restart)
752
+ * and the client needs to establish a fresh connection.
753
+ *
754
+ * @returns Promise resolving when reconnection is complete
755
+ * @throws {Error} If reconnection fails
756
+ *
757
+ * @internal
758
+ */
759
+ async forceReconnect() {
760
+ this.log("debug", "Forcing reconnection to MCP server...");
761
+ try {
762
+ if (this.transport) {
763
+ await this.transport.close();
764
+ }
765
+ } catch (e) {
766
+ this.log("debug", "Error during force disconnect (ignored)", {
767
+ error: e instanceof Error ? e.message : String(e)
768
+ });
609
769
  }
770
+ this.transport = void 0;
771
+ this.isConnected = null;
772
+ await this.connect();
773
+ this.log("debug", "Successfully reconnected to MCP server");
610
774
  }
611
775
  async listResources() {
612
776
  this.log("debug", `Requesting resources from MCP server`);
@@ -694,6 +858,12 @@ var InternalMastraMCPClient = class extends MastraBase {
694
858
  return handler(request.params);
695
859
  });
696
860
  }
861
+ setProgressNotificationHandler(handler) {
862
+ this.log("debug", "Setting progress notification handler");
863
+ this.client.setNotificationHandler(ProgressNotificationSchema, (notification) => {
864
+ handler(notification.params);
865
+ });
866
+ }
697
867
  async convertInputSchema(inputSchema) {
698
868
  if (isZodType(inputSchema)) {
699
869
  return inputSchema;
@@ -767,7 +937,7 @@ var InternalMastraMCPClient = class extends MastraBase {
767
937
  }
768
938
  async tools() {
769
939
  this.log("debug", `Requesting tools from MCP server`);
770
- const { tools } = await this.client.listTools({ timeout: this.timeout });
940
+ const { tools } = await this.client.listTools({}, { timeout: this.timeout });
771
941
  const toolsRes = {};
772
942
  for (const tool of tools) {
773
943
  this.log("debug", `Processing tool: ${tool.name}`);
@@ -780,12 +950,14 @@ var InternalMastraMCPClient = class extends MastraBase {
780
950
  execute: async (input, context) => {
781
951
  const previousContext = this.currentOperationContext;
782
952
  this.currentOperationContext = context?.requestContext || null;
783
- try {
784
- this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input });
953
+ const executeToolCall = async () => {
954
+ this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input, runId: context?.runId });
785
955
  const res = await this.client.callTool(
786
956
  {
787
957
  name: tool.name,
788
- arguments: input
958
+ arguments: input,
959
+ // Use runId as progress token if available, otherwise generate a random UUID
960
+ ...this.enableProgressTracking ? { _meta: { progressToken: context?.runId || crypto.randomUUID() } } : {}
789
961
  },
790
962
  CallToolResultSchema,
791
963
  {
@@ -793,8 +965,31 @@ var InternalMastraMCPClient = class extends MastraBase {
793
965
  }
794
966
  );
795
967
  this.log("debug", `Tool executed successfully: ${tool.name}`);
968
+ if (res.structuredContent !== void 0) {
969
+ return res.structuredContent;
970
+ }
796
971
  return res;
972
+ };
973
+ try {
974
+ return await executeToolCall();
797
975
  } catch (e) {
976
+ if (this.isSessionError(e)) {
977
+ this.log("debug", `Session error detected for tool ${tool.name}, attempting reconnection...`, {
978
+ error: e instanceof Error ? e.message : String(e)
979
+ });
980
+ try {
981
+ await this.forceReconnect();
982
+ this.log("debug", `Retrying tool ${tool.name} after reconnection...`);
983
+ return await executeToolCall();
984
+ } catch (reconnectError) {
985
+ this.log("error", `Reconnection or retry failed for tool ${tool.name}`, {
986
+ originalError: e instanceof Error ? e.message : String(e),
987
+ reconnectError: reconnectError instanceof Error ? reconnectError.stack : String(reconnectError),
988
+ toolArgs: input
989
+ });
990
+ throw e;
991
+ }
992
+ }
798
993
  this.log("error", `Error calling tool: ${tool.name}`, {
799
994
  error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
800
995
  toolArgs: input
@@ -818,8 +1013,6 @@ var InternalMastraMCPClient = class extends MastraBase {
818
1013
  return toolsRes;
819
1014
  }
820
1015
  };
821
-
822
- // src/client/configuration.ts
823
1016
  var mcpClientInstances = /* @__PURE__ */ new Map();
824
1017
  var MCPClient = class extends MastraBase {
825
1018
  serverConfigs = {};
@@ -892,6 +1085,48 @@ To fix this you have three different options:
892
1085
  this.addToInstanceCache();
893
1086
  return this;
894
1087
  }
1088
+ /**
1089
+ * Provides access to progress-related operations for tracking long-running operations.
1090
+ *
1091
+ * Progress tracking allows MCP servers to send updates about the status of ongoing operations,
1092
+ * providing real-time feedback to users about task completion and current state.
1093
+ *
1094
+ * @example
1095
+ * ```typescript
1096
+ * // Set up handler for progress updates from a server
1097
+ * await mcp.progress.onUpdate('serverName', (params) => {
1098
+ * console.log(`Progress: ${params.progress}%`);
1099
+ * console.log(`Status: ${params.message}`);
1100
+ *
1101
+ * if (params.total) {
1102
+ * console.log(`Completed ${params.progress} of ${params.total} items`);
1103
+ * }
1104
+ * });
1105
+ * ```
1106
+ */
1107
+ get progress() {
1108
+ this.addToInstanceCache();
1109
+ return {
1110
+ onUpdate: async (serverName, handler) => {
1111
+ try {
1112
+ const internalClient = await this.getConnectedClientForServer(serverName);
1113
+ return internalClient.progress.onUpdate(handler);
1114
+ } catch (err) {
1115
+ throw new MastraError(
1116
+ {
1117
+ id: "MCP_CLIENT_ON_UPDATE_PROGRESS_FAILED",
1118
+ domain: ErrorDomain.MCP,
1119
+ category: ErrorCategory.THIRD_PARTY,
1120
+ details: {
1121
+ serverName
1122
+ }
1123
+ },
1124
+ err
1125
+ );
1126
+ }
1127
+ }
1128
+ };
1129
+ }
895
1130
  /**
896
1131
  * Provides access to elicitation-related operations for interactive user input collection.
897
1132
  *
@@ -1580,14 +1815,204 @@ To fix this you have three different options:
1580
1815
  }
1581
1816
  };
1582
1817
 
1583
- // ../../node_modules/.pnpm/hono@4.10.3/node_modules/hono/dist/utils/stream.js
1818
+ // src/client/oauth-provider.ts
1819
+ var InMemoryOAuthStorage = class {
1820
+ data = /* @__PURE__ */ new Map();
1821
+ set(key, value) {
1822
+ this.data.set(key, value);
1823
+ }
1824
+ get(key) {
1825
+ return this.data.get(key);
1826
+ }
1827
+ delete(key) {
1828
+ this.data.delete(key);
1829
+ }
1830
+ clear() {
1831
+ this.data.clear();
1832
+ }
1833
+ };
1834
+ var MCPOAuthClientProvider = class {
1835
+ _redirectUrl;
1836
+ _clientMetadata;
1837
+ storage;
1838
+ onRedirect;
1839
+ generateState;
1840
+ _clientInfo;
1841
+ constructor(options) {
1842
+ this._redirectUrl = options.redirectUrl;
1843
+ this._clientMetadata = options.clientMetadata;
1844
+ this._clientInfo = options.clientInformation;
1845
+ this.storage = options.storage ?? new InMemoryOAuthStorage();
1846
+ this.onRedirect = options.onRedirectToAuthorization;
1847
+ this.generateState = options.stateGenerator ?? (() => crypto.randomUUID());
1848
+ }
1849
+ /**
1850
+ * The URL to redirect the user agent to after authorization.
1851
+ */
1852
+ get redirectUrl() {
1853
+ return this._redirectUrl;
1854
+ }
1855
+ /**
1856
+ * Metadata about this OAuth client.
1857
+ */
1858
+ get clientMetadata() {
1859
+ return this._clientMetadata;
1860
+ }
1861
+ /**
1862
+ * Returns a OAuth2 state parameter.
1863
+ */
1864
+ async state() {
1865
+ return this.generateState();
1866
+ }
1867
+ /**
1868
+ * Loads information about this OAuth client.
1869
+ */
1870
+ async clientInformation() {
1871
+ if (this._clientInfo) {
1872
+ return this._clientInfo;
1873
+ }
1874
+ const stored = await this.storage.get("client_info");
1875
+ if (stored) {
1876
+ try {
1877
+ return JSON.parse(stored);
1878
+ } catch {
1879
+ }
1880
+ }
1881
+ return void 0;
1882
+ }
1883
+ /**
1884
+ * Saves dynamically registered client information.
1885
+ */
1886
+ async saveClientInformation(clientInformation) {
1887
+ this._clientInfo = clientInformation;
1888
+ await this.storage.set("client_info", JSON.stringify(clientInformation));
1889
+ }
1890
+ /**
1891
+ * Loads existing OAuth tokens.
1892
+ */
1893
+ async tokens() {
1894
+ const stored = await this.storage.get("tokens");
1895
+ if (stored) {
1896
+ try {
1897
+ return JSON.parse(stored);
1898
+ } catch {
1899
+ }
1900
+ }
1901
+ return void 0;
1902
+ }
1903
+ /**
1904
+ * Stores new OAuth tokens after successful authorization.
1905
+ */
1906
+ async saveTokens(tokens) {
1907
+ await this.storage.set("tokens", JSON.stringify(tokens));
1908
+ }
1909
+ /**
1910
+ * Invoked to redirect the user agent to the authorization URL.
1911
+ */
1912
+ async redirectToAuthorization(authorizationUrl) {
1913
+ if (this.onRedirect) {
1914
+ await this.onRedirect(authorizationUrl);
1915
+ } else {
1916
+ console.info(`Authorization required. Please visit: ${authorizationUrl.toString()}`);
1917
+ }
1918
+ }
1919
+ /**
1920
+ * Saves a PKCE code verifier before redirecting to authorization.
1921
+ */
1922
+ async saveCodeVerifier(codeVerifier) {
1923
+ await this.storage.set("code_verifier", codeVerifier);
1924
+ }
1925
+ /**
1926
+ * Loads the PKCE code verifier for validating authorization result.
1927
+ */
1928
+ async codeVerifier() {
1929
+ const verifier = await this.storage.get("code_verifier");
1930
+ if (!verifier) {
1931
+ throw new Error("No code verifier found. Authorization flow may not have started properly.");
1932
+ }
1933
+ return verifier;
1934
+ }
1935
+ /**
1936
+ * Optional: Custom client authentication for token requests.
1937
+ * Uses default behavior if not implemented.
1938
+ */
1939
+ async addClientAuthentication(_headers, _params, _url, _metadata) {
1940
+ }
1941
+ /**
1942
+ * Invalidate credentials when server indicates they're no longer valid.
1943
+ */
1944
+ async invalidateCredentials(scope) {
1945
+ switch (scope) {
1946
+ case "all":
1947
+ await this.storage.delete("tokens");
1948
+ await this.storage.delete("client_info");
1949
+ await this.storage.delete("code_verifier");
1950
+ this._clientInfo = void 0;
1951
+ break;
1952
+ case "client":
1953
+ await this.storage.delete("client_info");
1954
+ this._clientInfo = void 0;
1955
+ break;
1956
+ case "tokens":
1957
+ await this.storage.delete("tokens");
1958
+ break;
1959
+ case "verifier":
1960
+ await this.storage.delete("code_verifier");
1961
+ break;
1962
+ }
1963
+ }
1964
+ /**
1965
+ * Clear all stored OAuth data.
1966
+ * Useful for logging out or resetting state.
1967
+ */
1968
+ async clear() {
1969
+ await this.invalidateCredentials("all");
1970
+ }
1971
+ /**
1972
+ * Check if the provider has valid (non-expired) tokens.
1973
+ */
1974
+ async hasValidTokens() {
1975
+ const currentTokens = await this.tokens();
1976
+ if (!currentTokens) return false;
1977
+ if (!currentTokens.access_token) return false;
1978
+ return true;
1979
+ }
1980
+ };
1981
+ function createSimpleTokenProvider(accessToken, options) {
1982
+ const tokens = {
1983
+ access_token: accessToken,
1984
+ token_type: options.tokenType ?? "Bearer",
1985
+ refresh_token: options.refreshToken,
1986
+ expires_in: options.expiresIn,
1987
+ scope: options.scope
1988
+ };
1989
+ const storage = new InMemoryOAuthStorage();
1990
+ storage.set("tokens", JSON.stringify(tokens));
1991
+ if (options.clientInformation) {
1992
+ storage.set("client_info", JSON.stringify(options.clientInformation));
1993
+ }
1994
+ return new MCPOAuthClientProvider({
1995
+ redirectUrl: options.redirectUrl,
1996
+ clientMetadata: options.clientMetadata,
1997
+ clientInformation: options.clientInformation,
1998
+ storage
1999
+ });
2000
+ }
2001
+
2002
+ // ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/utils/stream.js
1584
2003
  var StreamingApi = class {
1585
2004
  writer;
1586
2005
  encoder;
1587
2006
  writable;
1588
2007
  abortSubscribers = [];
1589
2008
  responseReadable;
2009
+ /**
2010
+ * Whether the stream has been aborted.
2011
+ */
1590
2012
  aborted = false;
2013
+ /**
2014
+ * Whether the stream has been closed normally.
2015
+ */
1591
2016
  closed = false;
1592
2017
  constructor(writable, _readable) {
1593
2018
  this.writable = writable;
@@ -1639,6 +2064,10 @@ var StreamingApi = class {
1639
2064
  onAbort(listener) {
1640
2065
  this.abortSubscribers.push(listener);
1641
2066
  }
2067
+ /**
2068
+ * Abort the stream.
2069
+ * You can call this method when stream is aborted by external event.
2070
+ */
1642
2071
  abort() {
1643
2072
  if (!this.aborted) {
1644
2073
  this.aborted = true;
@@ -1647,7 +2076,7 @@ var StreamingApi = class {
1647
2076
  }
1648
2077
  };
1649
2078
 
1650
- // ../../node_modules/.pnpm/hono@4.10.3/node_modules/hono/dist/helper/streaming/utils.js
2079
+ // ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/helper/streaming/utils.js
1651
2080
  var isOldBunVersion = () => {
1652
2081
  const version = typeof Bun !== "undefined" ? Bun.version : void 0;
1653
2082
  if (version === void 0) {
@@ -1658,7 +2087,7 @@ var isOldBunVersion = () => {
1658
2087
  return result;
1659
2088
  };
1660
2089
 
1661
- // ../../node_modules/.pnpm/hono@4.10.3/node_modules/hono/dist/utils/html.js
2090
+ // ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/utils/html.js
1662
2091
  var HtmlEscapedCallbackPhase = {
1663
2092
  Stringify: 1};
1664
2093
  var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
@@ -1689,7 +2118,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
1689
2118
  }
1690
2119
  };
1691
2120
 
1692
- // ../../node_modules/.pnpm/hono@4.10.3/node_modules/hono/dist/helper/streaming/sse.js
2121
+ // ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/helper/streaming/sse.js
1693
2122
  var SSEStreamingApi = class extends StreamingApi {
1694
2123
  constructor(writable, readable) {
1695
2124
  super(writable, readable);
@@ -2137,7 +2566,16 @@ var MCPServer = class extends MCPServerBase {
2137
2566
  if (opts.prompts) {
2138
2567
  capabilities.prompts = { listChanged: true };
2139
2568
  }
2140
- this.server = new Server({ name: this.name, version: this.version }, { capabilities });
2569
+ this.server = new Server(
2570
+ {
2571
+ name: this.name,
2572
+ version: this.version
2573
+ },
2574
+ {
2575
+ capabilities,
2576
+ ...this.instructions ? { instructions: this.instructions } : {}
2577
+ }
2578
+ );
2141
2579
  this.logger.info(
2142
2580
  `Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(", ")} and resources. Capabilities: ${JSON.stringify(capabilities)}`
2143
2581
  );
@@ -2162,8 +2600,8 @@ var MCPServer = class extends MCPServerBase {
2162
2600
  }
2163
2601
  });
2164
2602
  this.elicitation = {
2165
- sendRequest: async (request) => {
2166
- return this.handleElicitationRequest(request);
2603
+ sendRequest: async (request, options) => {
2604
+ return this.handleElicitationRequest(request, void 0, options);
2167
2605
  }
2168
2606
  };
2169
2607
  }
@@ -2173,15 +2611,51 @@ var MCPServer = class extends MCPServerBase {
2173
2611
  *
2174
2612
  * @param request - The elicitation request containing message and schema
2175
2613
  * @param serverInstance - Optional server instance to use; defaults to main server for backward compatibility
2614
+ * @param options - Optional request options (timeout, signal, etc.)
2176
2615
  * @returns Promise that resolves to the client's response
2177
2616
  */
2178
- async handleElicitationRequest(request, serverInstance) {
2617
+ async handleElicitationRequest(request, serverInstance, options) {
2179
2618
  this.logger.debug(`Sending elicitation request: ${request.message}`);
2180
2619
  const server = serverInstance || this.server;
2181
- const response = await server.elicitInput(request);
2620
+ const response = await server.elicitInput(request, options);
2182
2621
  this.logger.debug(`Received elicitation response: ${JSON.stringify(response)}`);
2183
2622
  return response;
2184
2623
  }
2624
+ /**
2625
+ * Reads and parses the JSON body from an HTTP request.
2626
+ * If the request body was already parsed by middleware (e.g., express.json()),
2627
+ * it uses the pre-parsed body from req.body. Otherwise, it reads from the stream.
2628
+ *
2629
+ * This allows the MCP server to work with Express apps that use express.json()
2630
+ * globally without requiring special route exclusions.
2631
+ *
2632
+ * @param req - The incoming HTTP request
2633
+ * @param options - Optional configuration
2634
+ * @param options.preParsedOnly - If true, only return pre-parsed body from middleware,
2635
+ * returning undefined if not available. This allows the caller to fall back to
2636
+ * their own body reading logic (e.g., SDK's getRawBody with size limits).
2637
+ */
2638
+ async readJsonBody(req, options) {
2639
+ const reqWithBody = req;
2640
+ if (reqWithBody.body !== void 0) {
2641
+ return reqWithBody.body;
2642
+ }
2643
+ if (options?.preParsedOnly) {
2644
+ return void 0;
2645
+ }
2646
+ return new Promise((resolve, reject) => {
2647
+ let data = "";
2648
+ req.on("data", (chunk) => data += chunk);
2649
+ req.on("end", () => {
2650
+ try {
2651
+ resolve(JSON.parse(data));
2652
+ } catch (e) {
2653
+ reject(e);
2654
+ }
2655
+ });
2656
+ req.on("error", reject);
2657
+ });
2658
+ }
2185
2659
  /**
2186
2660
  * Creates a new Server instance configured with all handlers for HTTP sessions.
2187
2661
  * Each HTTP client connection gets its own Server instance to avoid routing conflicts.
@@ -2198,7 +2672,16 @@ var MCPServer = class extends MCPServerBase {
2198
2672
  if (this.promptOptions) {
2199
2673
  capabilities.prompts = { listChanged: true };
2200
2674
  }
2201
- const serverInstance = new Server({ name: this.name, version: this.version }, { capabilities });
2675
+ const serverInstance = new Server(
2676
+ {
2677
+ name: this.name,
2678
+ version: this.version
2679
+ },
2680
+ {
2681
+ capabilities,
2682
+ ...this.instructions ? { instructions: this.instructions } : {}
2683
+ }
2684
+ );
2202
2685
  this.registerHandlersOnServer(serverInstance);
2203
2686
  return serverInstance;
2204
2687
  }
@@ -2219,6 +2702,12 @@ var MCPServer = class extends MCPServerBase {
2219
2702
  if (tool.outputSchema) {
2220
2703
  toolSpec.outputSchema = tool.outputSchema.jsonSchema;
2221
2704
  }
2705
+ if (tool.mcp?.annotations) {
2706
+ toolSpec.annotations = tool.mcp.annotations;
2707
+ }
2708
+ if (tool.mcp?._meta) {
2709
+ toolSpec._meta = tool.mcp._meta;
2710
+ }
2222
2711
  return toolSpec;
2223
2712
  })
2224
2713
  };
@@ -2267,8 +2756,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2267
2756
  };
2268
2757
  }
2269
2758
  const sessionElicitation = {
2270
- sendRequest: async (request2) => {
2271
- return this.handleElicitationRequest(request2, serverInstance);
2759
+ sendRequest: async (request2, options) => {
2760
+ return this.handleElicitationRequest(request2, serverInstance, options);
2272
2761
  }
2273
2762
  };
2274
2763
  const mcpOptions = {
@@ -2568,7 +3057,16 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2568
3057
  `Executing agent tool '${agentToolName}' for agent '${agent.name}' with message: "${inputData.message}"`
2569
3058
  );
2570
3059
  try {
2571
- const response = await agent.generate(inputData.message, context);
3060
+ const proxiedContext = context?.requestContext || new RequestContext();
3061
+ if (context?.mcp?.extra) {
3062
+ Object.entries(context.mcp.extra).forEach(([key, value]) => {
3063
+ proxiedContext.set(key, value);
3064
+ });
3065
+ }
3066
+ const response = await agent.generate(inputData.message, {
3067
+ ...context ?? {},
3068
+ requestContext: proxiedContext
3069
+ });
2572
3070
  return response;
2573
3071
  } catch (error) {
2574
3072
  this.logger.error(`Error executing agent tool '${agentToolName}' for agent '${agent.name}':`, error);
@@ -2632,10 +3130,16 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2632
3130
  inputData
2633
3131
  );
2634
3132
  try {
2635
- const run2 = await workflow.createRun({ runId: context?.requestContext?.get("runId") });
3133
+ const proxiedContext = context?.requestContext || new RequestContext();
3134
+ if (context?.mcp?.extra) {
3135
+ Object.entries(context.mcp.extra).forEach(([key, value]) => {
3136
+ proxiedContext.set(key, value);
3137
+ });
3138
+ }
3139
+ const run2 = await workflow.createRun({ runId: proxiedContext?.get("runId") });
2636
3140
  const response = await run2.start({
2637
3141
  inputData,
2638
- requestContext: context?.requestContext,
3142
+ requestContext: proxiedContext,
2639
3143
  tracingContext: context?.tracingContext
2640
3144
  });
2641
3145
  return response;
@@ -2791,7 +3295,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2791
3295
  *
2792
3296
  * @example
2793
3297
  * ```typescript
2794
- * import http from 'http';
3298
+ * import http from 'node:http';
2795
3299
  *
2796
3300
  * const httpServer = http.createServer(async (req, res) => {
2797
3301
  * await server.startSSE({
@@ -2822,7 +3326,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2822
3326
  res.end("SSE connection not established");
2823
3327
  return;
2824
3328
  }
2825
- await this.sseTransport.handlePostMessage(req, res);
3329
+ const parsedBody = await this.readJsonBody(req, { preParsedOnly: true });
3330
+ await this.sseTransport.handlePostMessage(req, res, parsedBody);
2826
3331
  } else {
2827
3332
  this.logger.debug("Unknown path:", { path: url.pathname });
2828
3333
  res.writeHead(404);
@@ -2949,8 +3454,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2949
3454
  *
2950
3455
  * @example
2951
3456
  * ```typescript
2952
- * import http from 'http';
2953
- * import { randomUUID } from 'crypto';
3457
+ * import http from 'node:http';
3458
+ * import { randomUUID } from 'node:crypto';
2954
3459
  *
2955
3460
  * const httpServer = http.createServer(async (req, res) => {
2956
3461
  * await server.startHTTP({
@@ -2994,7 +3499,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
2994
3499
  httpPath,
2995
3500
  req,
2996
3501
  res,
2997
- options = { sessionIdGenerator: () => randomUUID() }
3502
+ options
2998
3503
  }) {
2999
3504
  this.logger.debug(`startHTTP: Received ${req.method} request to ${url.pathname}`);
3000
3505
  if (url.pathname !== httpPath) {
@@ -3003,11 +3508,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3003
3508
  res.end();
3004
3509
  return;
3005
3510
  }
3006
- if (options?.serverless) {
3007
- this.logger.debug("startHTTP: Running in serverless (stateless) mode");
3511
+ const isStatelessMode = options?.serverless || options && "sessionIdGenerator" in options && options.sessionIdGenerator === void 0;
3512
+ if (isStatelessMode) {
3513
+ this.logger.debug("startHTTP: Running in stateless mode (serverless or sessionIdGenerator: undefined)");
3008
3514
  await this.handleServerlessRequest(req, res);
3009
3515
  return;
3010
3516
  }
3517
+ const mergedOptions = {
3518
+ sessionIdGenerator: () => randomUUID(),
3519
+ // default: enabled
3520
+ ...options
3521
+ // user-provided overrides default
3522
+ };
3011
3523
  const sessionId = req.headers["mcp-session-id"];
3012
3524
  let transport;
3013
3525
  this.logger.debug(
@@ -3022,40 +3534,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3022
3534
  `startHTTP: Handling GET request for existing session ${sessionId}. Calling transport.handleRequest.`
3023
3535
  );
3024
3536
  }
3025
- const body = req.method === "POST" ? await new Promise((resolve, reject) => {
3026
- let data = "";
3027
- req.on("data", (chunk) => data += chunk);
3028
- req.on("end", () => {
3029
- try {
3030
- resolve(JSON.parse(data));
3031
- } catch (e) {
3032
- reject(e);
3033
- }
3034
- });
3035
- req.on("error", reject);
3036
- }) : void 0;
3537
+ const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
3037
3538
  await transport.handleRequest(req, res, body);
3038
3539
  } else {
3039
3540
  this.logger.debug(`startHTTP: No existing Streamable HTTP session ID found. ${req.method}`);
3040
3541
  if (req.method === "POST") {
3041
- const body = await new Promise((resolve, reject) => {
3042
- let data = "";
3043
- req.on("data", (chunk) => data += chunk);
3044
- req.on("end", () => {
3045
- try {
3046
- resolve(JSON.parse(data));
3047
- } catch (e) {
3048
- reject(e);
3049
- }
3050
- });
3051
- req.on("error", reject);
3052
- });
3542
+ const body = await this.readJsonBody(req);
3053
3543
  const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
3054
3544
  if (isInitializeRequest(body)) {
3055
3545
  this.logger.debug("startHTTP: Received Streamable HTTP initialize request, creating new transport.");
3056
3546
  transport = new StreamableHTTPServerTransport({
3057
- ...options,
3058
- sessionIdGenerator: () => randomUUID(),
3547
+ ...mergedOptions,
3548
+ sessionIdGenerator: mergedOptions.sessionIdGenerator,
3059
3549
  onsessioninitialized: (id) => {
3060
3550
  this.streamableHTTPTransports.set(id, transport);
3061
3551
  }
@@ -3159,18 +3649,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
3159
3649
  async handleServerlessRequest(req, res) {
3160
3650
  try {
3161
3651
  this.logger.debug(`handleServerlessRequest: Received ${req.method} request`);
3162
- const body = req.method === "POST" ? await new Promise((resolve, reject) => {
3163
- let data = "";
3164
- req.on("data", (chunk) => data += chunk);
3165
- req.on("end", () => {
3166
- try {
3167
- resolve(JSON.parse(data));
3168
- } catch (e) {
3169
- reject(new Error(`Invalid JSON in request body: ${e instanceof Error ? e.message : String(e)}`));
3170
- }
3171
- });
3172
- req.on("error", reject);
3173
- }) : void 0;
3652
+ const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
3174
3653
  this.logger.debug(`handleServerlessRequest: Processing ${req.method} request`, {
3175
3654
  method: body?.method,
3176
3655
  id: body?.id
@@ -3600,7 +4079,210 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
3600
4079
  }
3601
4080
  }
3602
4081
  };
4082
+ function escapeHeaderValue(value) {
4083
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4084
+ }
4085
+ function generateWWWAuthenticateHeader(options = {}) {
4086
+ const params = [];
4087
+ if (options.resourceMetadataUrl) {
4088
+ params.push(`resource_metadata="${escapeHeaderValue(options.resourceMetadataUrl)}"`);
4089
+ }
4090
+ if (options.additionalParams) {
4091
+ for (const [key, value] of Object.entries(options.additionalParams)) {
4092
+ params.push(`${key}="${escapeHeaderValue(value)}"`);
4093
+ }
4094
+ }
4095
+ if (params.length === 0) {
4096
+ return "Bearer";
4097
+ }
4098
+ return `Bearer ${params.join(", ")}`;
4099
+ }
4100
+ function generateProtectedResourceMetadata(config) {
4101
+ return {
4102
+ resource: config.resource,
4103
+ authorization_servers: config.authorizationServers,
4104
+ scopes_supported: config.scopesSupported ?? ["mcp:read", "mcp:write"],
4105
+ bearer_methods_supported: ["header"],
4106
+ ...config.resourceName && { resource_name: config.resourceName },
4107
+ ...config.resourceDocumentation && {
4108
+ resource_documentation: config.resourceDocumentation
4109
+ }
4110
+ };
4111
+ }
4112
+ function extractBearerToken(authHeader) {
4113
+ if (!authHeader) return void 0;
4114
+ const prefix = "bearer ";
4115
+ if (authHeader.length <= prefix.length) return void 0;
4116
+ if (authHeader.slice(0, prefix.length).toLowerCase() !== prefix) return void 0;
4117
+ const token = authHeader.slice(prefix.length).trim();
4118
+ return token || void 0;
4119
+ }
4120
+
4121
+ // src/server/oauth-middleware.ts
4122
+ function createOAuthMiddleware(options) {
4123
+ const { oauth, mcpPath = "/mcp", logger } = options;
4124
+ const protectedResourceMetadata = generateProtectedResourceMetadata(oauth);
4125
+ const wellKnownPath = "/.well-known/oauth-protected-resource";
4126
+ const resourceMetadataUrl = new URL(wellKnownPath, oauth.resource).toString();
4127
+ return async function oauthMiddleware(req, res, url) {
4128
+ logger?.debug?.(`OAuth middleware: ${req.method} ${url.pathname}`);
4129
+ if (url.pathname === wellKnownPath && req.method === "GET") {
4130
+ logger?.debug?.("OAuth middleware: Serving Protected Resource Metadata");
4131
+ res.writeHead(200, {
4132
+ "Content-Type": "application/json",
4133
+ "Cache-Control": "max-age=3600",
4134
+ "Access-Control-Allow-Origin": "*"
4135
+ });
4136
+ res.end(JSON.stringify(protectedResourceMetadata));
4137
+ return { proceed: false, handled: true };
4138
+ }
4139
+ if (url.pathname === wellKnownPath && req.method === "OPTIONS") {
4140
+ res.writeHead(204, {
4141
+ "Access-Control-Allow-Origin": "*",
4142
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
4143
+ "Access-Control-Allow-Headers": "Content-Type",
4144
+ "Access-Control-Max-Age": "86400"
4145
+ });
4146
+ res.end();
4147
+ return { proceed: false, handled: true };
4148
+ }
4149
+ if (!url.pathname.startsWith(mcpPath)) {
4150
+ return { proceed: true, handled: false };
4151
+ }
4152
+ const authHeader = req.headers["authorization"];
4153
+ const token = extractBearerToken(authHeader);
4154
+ if (!token) {
4155
+ logger?.debug?.("OAuth middleware: No bearer token provided");
4156
+ res.writeHead(401, {
4157
+ "Content-Type": "application/json",
4158
+ "WWW-Authenticate": generateWWWAuthenticateHeader({ resourceMetadataUrl })
4159
+ });
4160
+ res.end(
4161
+ JSON.stringify({
4162
+ error: "unauthorized",
4163
+ error_description: "Bearer token required"
4164
+ })
4165
+ );
4166
+ return { proceed: false, handled: true };
4167
+ }
4168
+ if (oauth.validateToken) {
4169
+ logger?.debug?.("OAuth middleware: Validating token");
4170
+ const validationResult = await oauth.validateToken(token, oauth.resource);
4171
+ if (!validationResult.valid) {
4172
+ logger?.debug?.(`OAuth middleware: Token validation failed: ${validationResult.error}`);
4173
+ res.writeHead(401, {
4174
+ "Content-Type": "application/json",
4175
+ "WWW-Authenticate": generateWWWAuthenticateHeader({
4176
+ resourceMetadataUrl,
4177
+ additionalParams: {
4178
+ error: validationResult.error || "invalid_token",
4179
+ ...validationResult.errorDescription && {
4180
+ error_description: validationResult.errorDescription
4181
+ }
4182
+ }
4183
+ })
4184
+ });
4185
+ res.end(
4186
+ JSON.stringify({
4187
+ error: validationResult.error || "invalid_token",
4188
+ error_description: validationResult.errorDescription || "Token validation failed"
4189
+ })
4190
+ );
4191
+ return { proceed: false, handled: true, tokenValidation: validationResult };
4192
+ }
4193
+ logger?.debug?.("OAuth middleware: Token validated successfully");
4194
+ return { proceed: true, handled: false, tokenValidation: validationResult };
4195
+ }
4196
+ logger?.debug?.("OAuth middleware: No token validation configured, accepting token");
4197
+ return {
4198
+ proceed: true,
4199
+ handled: false,
4200
+ tokenValidation: { valid: true }
4201
+ };
4202
+ };
4203
+ }
4204
+ function createStaticTokenValidator(validTokens) {
4205
+ const tokenSet = new Set(validTokens);
4206
+ return async (token) => {
4207
+ if (tokenSet.has(token)) {
4208
+ return { valid: true, scopes: ["mcp:read", "mcp:write"] };
4209
+ }
4210
+ return {
4211
+ valid: false,
4212
+ error: "invalid_token",
4213
+ errorDescription: "Token not recognized"
4214
+ };
4215
+ };
4216
+ }
4217
+ function createIntrospectionValidator(introspectionEndpoint, clientCredentials) {
4218
+ return async (token, resource) => {
4219
+ try {
4220
+ const headers = {
4221
+ "Content-Type": "application/x-www-form-urlencoded"
4222
+ };
4223
+ if (clientCredentials) {
4224
+ if (clientCredentials.clientId.includes(":")) {
4225
+ return {
4226
+ valid: false,
4227
+ error: "invalid_request",
4228
+ errorDescription: "clientId cannot contain a colon character per RFC 7617"
4229
+ };
4230
+ }
4231
+ const credentials = Buffer.from(`${clientCredentials.clientId}:${clientCredentials.clientSecret}`).toString(
4232
+ "base64"
4233
+ );
4234
+ headers["Authorization"] = `Basic ${credentials}`;
4235
+ }
4236
+ const response = await fetch(introspectionEndpoint, {
4237
+ method: "POST",
4238
+ headers,
4239
+ body: new URLSearchParams({
4240
+ token,
4241
+ token_type_hint: "access_token"
4242
+ })
4243
+ });
4244
+ if (!response.ok) {
4245
+ return {
4246
+ valid: false,
4247
+ error: "server_error",
4248
+ errorDescription: `Introspection failed: ${response.status}`
4249
+ };
4250
+ }
4251
+ const data = await response.json();
4252
+ if (!data.active) {
4253
+ return {
4254
+ valid: false,
4255
+ error: "invalid_token",
4256
+ errorDescription: "Token is not active"
4257
+ };
4258
+ }
4259
+ if (data.aud) {
4260
+ const audiences = Array.isArray(data.aud) ? data.aud : [data.aud];
4261
+ if (!audiences.includes(resource)) {
4262
+ return {
4263
+ valid: false,
4264
+ error: "invalid_token",
4265
+ errorDescription: "Token audience does not match this resource"
4266
+ };
4267
+ }
4268
+ }
4269
+ return {
4270
+ valid: true,
4271
+ scopes: data.scope?.trim().split(" ").filter((s) => s !== "") || [],
4272
+ subject: data.sub,
4273
+ expiresAt: data.exp,
4274
+ claims: data
4275
+ };
4276
+ } catch (error) {
4277
+ return {
4278
+ valid: false,
4279
+ error: "server_error",
4280
+ errorDescription: error instanceof Error ? error.message : "Introspection failed"
4281
+ };
4282
+ }
4283
+ };
4284
+ }
3603
4285
 
3604
- export { MCPClient, MCPServer };
4286
+ export { InMemoryOAuthStorage, InternalMastraMCPClient, MCPClient, MCPOAuthClientProvider, MCPServer, createIntrospectionValidator, createOAuthMiddleware, createSimpleTokenProvider, createStaticTokenValidator, extractBearerToken, generateProtectedResourceMetadata, generateWWWAuthenticateHeader };
3605
4287
  //# sourceMappingURL=index.js.map
3606
4288
  //# sourceMappingURL=index.js.map