@mastra/mcp 1.13.1-alpha.0 → 1.14.0-alpha.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.cjs CHANGED
@@ -31491,6 +31491,7 @@ var InternalMastraMCPClient = class extends base.MastraBase {
31491
31491
  sigHupHandler;
31492
31492
  serverInstructions;
31493
31493
  _roots;
31494
+ hasElicitationCapability;
31494
31495
  requireToolApproval;
31495
31496
  onToolError;
31496
31497
  /** Provides access to resource operations (list, read, subscribe, etc.) */
@@ -31521,13 +31522,13 @@ var InternalMastraMCPClient = class extends base.MastraBase {
31521
31522
  this.requireToolApproval = server.requireToolApproval;
31522
31523
  this.onToolError = server.onToolError ?? "throw";
31523
31524
  this._roots = server.roots ?? [];
31525
+ this.hasElicitationCapability = capabilities.elicitation !== void 0;
31524
31526
  const hasRoots = this._roots.length > 0 || !!capabilities.roots;
31525
31527
  const clientCapabilities = {
31526
31528
  ...capabilities,
31527
- // Merge elicitation capabilities instead of overwriting
31528
- elicitation: {
31529
- ...capabilities.elicitation ?? {}
31530
- },
31529
+ // Only advertise elicitation when explicitly configured or when a handler
31530
+ // registers it before connect(). `elicitation: {}` is legacy form support.
31531
+ ...capabilities.elicitation !== void 0 ? { elicitation: { ...capabilities.elicitation } } : {},
31531
31532
  // Auto-enable roots capability if roots are provided
31532
31533
  ...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {},
31533
31534
  // Advertise MCP Apps extension support so servers know we can render UI resources
@@ -31942,6 +31943,16 @@ var InternalMastraMCPClient = class extends base.MastraBase {
31942
31943
  handler();
31943
31944
  });
31944
31945
  }
31946
+ /**
31947
+ * Register a handler to be called when the tool list changes on the server.
31948
+ * Use this to re-fetch tools via `tools()` when notified.
31949
+ */
31950
+ setToolListChangedNotificationHandler(handler) {
31951
+ this.log("debug", "Setting tool list changed notification handler");
31952
+ this.client.setNotificationHandler(types_js.ToolListChangedNotificationSchema, () => {
31953
+ handler();
31954
+ });
31955
+ }
31945
31956
  setResourceUpdatedNotificationHandler(handler) {
31946
31957
  this.log("debug", "Setting resource updated notification handler");
31947
31958
  this.client.setNotificationHandler(types_js.ResourceUpdatedNotificationSchema, (notification) => {
@@ -31956,6 +31967,17 @@ var InternalMastraMCPClient = class extends base.MastraBase {
31956
31967
  }
31957
31968
  setElicitationRequestHandler(handler) {
31958
31969
  this.log("debug", "Setting elicitation request handler");
31970
+ if (!this.hasElicitationCapability) {
31971
+ try {
31972
+ this.client.registerCapabilities({ elicitation: { form: {} } });
31973
+ this.hasElicitationCapability = true;
31974
+ } catch (error51) {
31975
+ throw new Error(
31976
+ "Cannot register an elicitation handler after connecting unless elicitation capability was configured before initialization.",
31977
+ { cause: error51 }
31978
+ );
31979
+ }
31980
+ }
31959
31981
  this.client.setRequestHandler(types_js.ElicitRequestSchema, async (request) => {
31960
31982
  this.log("debug", `Received elicitation request: ${request.params.message}`);
31961
31983
  return handler(request.params);
@@ -32418,7 +32440,7 @@ To fix this you have three different options:
32418
32440
  */
32419
32441
  onRequest: async (serverName, handler) => {
32420
32442
  try {
32421
- const internalClient = await this.getConnectedClientForServer(serverName);
32443
+ const internalClient = await this.getClientForServer(serverName);
32422
32444
  return internalClient.elicitation.onRequest(handler);
32423
32445
  } catch (err) {
32424
32446
  throw new error$1.MastraError(
@@ -32843,6 +32865,58 @@ To fix this you have three different options:
32843
32865
  }
32844
32866
  };
32845
32867
  }
32868
+ /**
32869
+ * Provides access to tool-related notification operations across all configured servers.
32870
+ *
32871
+ * To fetch tools, use `listTools()` or `listToolsets()`.
32872
+ *
32873
+ * @example
32874
+ * ```typescript
32875
+ * // React to tool list changes on a server
32876
+ * await mcp.tools.onListChanged('weatherServer', async () => {
32877
+ * console.log('Tool list changed, re-fetching...');
32878
+ * const tools = await mcp.listTools();
32879
+ * });
32880
+ * ```
32881
+ */
32882
+ get tools() {
32883
+ this.addToInstanceCache();
32884
+ return {
32885
+ /**
32886
+ * Sets a notification handler for when the tool list changes on a server.
32887
+ *
32888
+ * @param serverName - Name of the server to monitor
32889
+ * @param handler - Callback function invoked when tools are added/removed/modified
32890
+ * @returns Promise resolving when handler is registered
32891
+ * @throws {MastraError} If setting up the handler fails
32892
+ *
32893
+ * @example
32894
+ * ```typescript
32895
+ * await mcp.tools.onListChanged('weatherServer', async () => {
32896
+ * const tools = await mcp.listTools();
32897
+ * });
32898
+ * ```
32899
+ */
32900
+ onListChanged: async (serverName, handler) => {
32901
+ try {
32902
+ const internalClient = await this.getConnectedClientForServer(serverName);
32903
+ return internalClient.setToolListChangedNotificationHandler(handler);
32904
+ } catch (error51) {
32905
+ throw new error$1.MastraError(
32906
+ {
32907
+ id: "MCP_CLIENT_ON_LIST_CHANGED_TOOLS_FAILED",
32908
+ domain: error$1.ErrorDomain.MCP,
32909
+ category: error$1.ErrorCategory.THIRD_PARTY,
32910
+ details: {
32911
+ serverName
32912
+ }
32913
+ },
32914
+ error51
32915
+ );
32916
+ }
32917
+ }
32918
+ };
32919
+ }
32846
32920
  addToInstanceCache() {
32847
32921
  if (!mcpClientInstances.has(this.id)) {
32848
32922
  mcpClientInstances.set(this.id, this);
@@ -33134,7 +33208,14 @@ To fix this you have three different options:
33134
33208
  if (!client) return null;
33135
33209
  return client.stderr;
33136
33210
  }
33137
- async getConnectedClient(name, config2) {
33211
+ getServerConfig(serverName) {
33212
+ const serverConfig = this.serverConfigs[serverName];
33213
+ if (!serverConfig) {
33214
+ throw new Error(`Server configuration not found for name: ${serverName}`);
33215
+ }
33216
+ return serverConfig;
33217
+ }
33218
+ async getOrCreateClient(name, config2) {
33138
33219
  if (this.disconnectPromise) {
33139
33220
  await this.disconnectPromise;
33140
33221
  }
@@ -33145,10 +33226,8 @@ To fix this you have three different options:
33145
33226
  if (!existingClient) {
33146
33227
  throw new Error(`Client ${name} exists but is undefined`);
33147
33228
  }
33148
- await existingClient.connect();
33149
33229
  return existingClient;
33150
33230
  }
33151
- this.logger.debug("Connecting to MCP server", { name });
33152
33231
  const mcpClient = new InternalMastraMCPClient({
33153
33232
  name,
33154
33233
  server: config2,
@@ -33157,35 +33236,42 @@ To fix this you have three different options:
33157
33236
  });
33158
33237
  mcpClient.__setLogger(this.logger);
33159
33238
  this.mcpClientsById.set(name, mcpClient);
33239
+ return mcpClient;
33240
+ }
33241
+ async getConnectedClient(name, config2) {
33242
+ this.logger.debug("Connecting to MCP server", { name });
33243
+ const mcpClient = await this.getOrCreateClient(name, config2);
33160
33244
  try {
33161
33245
  await mcpClient.connect();
33162
33246
  } catch (e) {
33163
- const mastraError = new error$1.MastraError(
33164
- {
33165
- id: "MCP_CLIENT_CONNECT_FAILED",
33166
- domain: error$1.ErrorDomain.MCP,
33167
- category: error$1.ErrorCategory.THIRD_PARTY,
33168
- text: `Failed to connect to MCP server ${name}: ${e instanceof Error ? e.stack || e.message : String(e)}`,
33169
- details: {
33170
- name
33171
- }
33172
- },
33173
- e
33174
- );
33175
- this.logger.trackException(mastraError);
33176
- this.logger.error("MCPClient errored connecting to MCP server:", { error: mastraError.toString() });
33177
- this.mcpClientsById.delete(name);
33178
- throw mastraError;
33247
+ throw this.handleConnectError(name, e);
33179
33248
  }
33180
33249
  this.logger.debug("Connected to MCP server", { name });
33181
33250
  return mcpClient;
33182
33251
  }
33252
+ handleConnectError(name, error51) {
33253
+ const mastraError = new error$1.MastraError(
33254
+ {
33255
+ id: "MCP_CLIENT_CONNECT_FAILED",
33256
+ domain: error$1.ErrorDomain.MCP,
33257
+ category: error$1.ErrorCategory.THIRD_PARTY,
33258
+ text: `Failed to connect to MCP server ${name}: ${error51 instanceof Error ? error51.stack || error51.message : String(error51)}`,
33259
+ details: {
33260
+ name
33261
+ }
33262
+ },
33263
+ error51
33264
+ );
33265
+ this.logger.trackException(mastraError);
33266
+ this.logger.error("MCPClient errored connecting to MCP server:", { error: mastraError.toString() });
33267
+ this.mcpClientsById.delete(name);
33268
+ return mastraError;
33269
+ }
33183
33270
  async getConnectedClientForServer(serverName) {
33184
- const serverConfig = this.serverConfigs[serverName];
33185
- if (!serverConfig) {
33186
- throw new Error(`Server configuration not found for name: ${serverName}`);
33187
- }
33188
- return this.getConnectedClient(serverName, serverConfig);
33271
+ return this.getConnectedClient(serverName, this.getServerConfig(serverName));
33272
+ }
33273
+ async getClientForServer(serverName) {
33274
+ return this.getOrCreateClient(serverName, this.getServerConfig(serverName));
33189
33275
  }
33190
33276
  async getToolsForServer(serverName) {
33191
33277
  for (let attempt = 1; attempt <= TOOL_DISCOVERY_MAX_ATTEMPTS; attempt++) {
@@ -33650,16 +33736,55 @@ var SSETransport = class {
33650
33736
  });
33651
33737
  }
33652
33738
  };
33739
+ async function broadcastNotification({
33740
+ servers,
33741
+ send,
33742
+ logger,
33743
+ errorId,
33744
+ errorText,
33745
+ errorDetails
33746
+ }) {
33747
+ const errors = [];
33748
+ for (const server of servers) {
33749
+ try {
33750
+ await send(server);
33751
+ } catch (error51) {
33752
+ errors.push(error51);
33753
+ }
33754
+ }
33755
+ if (errors.length === 0) return;
33756
+ const mastraError = new error$1.MastraError(
33757
+ {
33758
+ id: errorId,
33759
+ domain: error$1.ErrorDomain.MCP,
33760
+ category: error$1.ErrorCategory.THIRD_PARTY,
33761
+ text: errorText,
33762
+ details: {
33763
+ ...errorDetails,
33764
+ failedInstances: errors.length,
33765
+ totalInstances: servers.length
33766
+ }
33767
+ },
33768
+ errors[0]
33769
+ );
33770
+ logger.error(`${errorText}:`, { error: mastraError.toString() });
33771
+ if (errors.length === servers.length) {
33772
+ logger.trackException(mastraError);
33773
+ throw mastraError;
33774
+ }
33775
+ }
33776
+
33777
+ // src/server/promptActions.ts
33653
33778
  var ServerPromptActions = class {
33654
33779
  getLogger;
33655
- getSdkServer;
33780
+ getSdkServers;
33656
33781
  clearDefinedPrompts;
33657
33782
  /**
33658
33783
  * @internal
33659
33784
  */
33660
33785
  constructor(dependencies) {
33661
33786
  this.getLogger = dependencies.getLogger;
33662
- this.getSdkServer = dependencies.getSdkServer;
33787
+ this.getSdkServers = dependencies.getSdkServers;
33663
33788
  this.clearDefinedPrompts = dependencies.clearDefinedPrompts;
33664
33789
  }
33665
33790
  /**
@@ -33668,7 +33793,7 @@ var ServerPromptActions = class {
33668
33793
  * This clears the internal prompt cache and sends a `notifications/prompts/list_changed`
33669
33794
  * message to all clients, prompting them to re-fetch the prompt list.
33670
33795
  *
33671
- * @throws {MastraError} If sending the notification fails
33796
+ * @throws {MastraError} If sending the notification fails on all server instances
33672
33797
  *
33673
33798
  * @example
33674
33799
  * ```typescript
@@ -33679,47 +33804,39 @@ var ServerPromptActions = class {
33679
33804
  async notifyListChanged() {
33680
33805
  this.getLogger().info("Prompt list change externally notified. Clearing definedPrompts and sending notification.");
33681
33806
  this.clearDefinedPrompts();
33682
- try {
33683
- await this.getSdkServer().sendPromptListChanged();
33684
- } catch (error51) {
33685
- const mastraError = new error$1.MastraError(
33686
- {
33687
- id: "MCP_SERVER_PROMPT_LIST_CHANGED_NOTIFICATION_FAILED",
33688
- domain: error$1.ErrorDomain.MCP,
33689
- category: error$1.ErrorCategory.THIRD_PARTY,
33690
- text: "Failed to send prompt list changed notification"
33691
- },
33692
- error51
33693
- );
33694
- this.getLogger().error("Failed to send prompt list changed notification:", {
33695
- error: mastraError.toString()
33696
- });
33697
- this.getLogger().trackException(mastraError);
33698
- throw mastraError;
33699
- }
33807
+ await broadcastNotification({
33808
+ servers: this.getSdkServers(),
33809
+ send: (server) => server.sendPromptListChanged(),
33810
+ logger: this.getLogger(),
33811
+ errorId: "MCP_SERVER_PROMPT_LIST_CHANGED_NOTIFICATION_FAILED",
33812
+ errorText: "Failed to send prompt list changed notification"
33813
+ });
33700
33814
  }
33701
33815
  };
33816
+
33817
+ // src/server/resourceActions.ts
33702
33818
  var ServerResourceActions = class {
33703
- getSubscriptions;
33819
+ getSubscribedServers;
33704
33820
  getLogger;
33705
- getSdkServer;
33821
+ getSdkServers;
33706
33822
  /**
33707
33823
  * @internal
33708
33824
  */
33709
33825
  constructor(dependencies) {
33710
- this.getSubscriptions = dependencies.getSubscriptions;
33826
+ this.getSubscribedServers = dependencies.getSubscribedServers;
33711
33827
  this.getLogger = dependencies.getLogger;
33712
- this.getSdkServer = dependencies.getSdkServer;
33828
+ this.getSdkServers = dependencies.getSdkServers;
33713
33829
  }
33714
33830
  /**
33715
33831
  * Notifies subscribed clients that a specific resource has been updated.
33716
33832
  *
33717
- * If clients are subscribed to the resource URI, they will receive a
33718
- * `notifications/resources/updated` message to re-fetch the resource content.
33833
+ * Only clients that subscribed to the resource URI (via `resources/subscribe`)
33834
+ * receive a `notifications/resources/updated` message prompting them to
33835
+ * re-fetch the resource content.
33719
33836
  *
33720
33837
  * @param params - Notification parameters
33721
33838
  * @param params.uri - URI of the resource that was updated
33722
- * @throws {MastraError} If sending the notification fails
33839
+ * @throws {MastraError} If sending the notification fails on all subscribed server instances
33723
33840
  *
33724
33841
  * @example
33725
33842
  * ```typescript
@@ -33728,32 +33845,20 @@ var ServerResourceActions = class {
33728
33845
  * ```
33729
33846
  */
33730
33847
  async notifyUpdated({ uri }) {
33731
- if (this.getSubscriptions().has(uri)) {
33732
- this.getLogger().info(`Sending notifications/resources/updated for externally notified resource: ${uri}`);
33733
- try {
33734
- await this.getSdkServer().sendResourceUpdated({ uri });
33735
- } catch (error51) {
33736
- const mastraError = new error$1.MastraError(
33737
- {
33738
- id: "MCP_SERVER_RESOURCE_UPDATED_NOTIFICATION_FAILED",
33739
- domain: error$1.ErrorDomain.MCP,
33740
- category: error$1.ErrorCategory.THIRD_PARTY,
33741
- text: "Failed to send resource updated notification",
33742
- details: {
33743
- uri
33744
- }
33745
- },
33746
- error51
33747
- );
33748
- this.getLogger().trackException(mastraError);
33749
- this.getLogger().error("Failed to send resource updated notification:", {
33750
- error: mastraError.toString()
33751
- });
33752
- throw mastraError;
33753
- }
33754
- } else {
33848
+ const subscribedServers = this.getSubscribedServers(uri);
33849
+ if (subscribedServers.length === 0) {
33755
33850
  this.getLogger().debug(`Resource ${uri} was updated, but no active subscriptions for it.`);
33851
+ return;
33756
33852
  }
33853
+ this.getLogger().info(`Sending notifications/resources/updated for externally notified resource: ${uri}`);
33854
+ await broadcastNotification({
33855
+ servers: subscribedServers,
33856
+ send: (server) => server.sendResourceUpdated({ uri }),
33857
+ logger: this.getLogger(),
33858
+ errorId: "MCP_SERVER_RESOURCE_UPDATED_NOTIFICATION_FAILED",
33859
+ errorText: "Failed to send resource updated notification",
33860
+ errorDetails: { uri }
33861
+ });
33757
33862
  }
33758
33863
  /**
33759
33864
  * Notifies clients that the overall list of available resources has changed.
@@ -33762,7 +33867,7 @@ var ServerResourceActions = class {
33762
33867
  * them to re-fetch the resource list. Resource lists and templates are always evaluated
33763
33868
  * per request, so there is no server-side cache to clear.
33764
33869
  *
33765
- * @throws {MastraError} If sending the notification fails
33870
+ * @throws {MastraError} If sending the notification fails on all server instances
33766
33871
  *
33767
33872
  * @example
33768
33873
  * ```typescript
@@ -33772,28 +33877,110 @@ var ServerResourceActions = class {
33772
33877
  */
33773
33878
  async notifyListChanged() {
33774
33879
  this.getLogger().info("Resource list change externally notified. Sending notification.");
33775
- try {
33776
- await this.getSdkServer().sendResourceListChanged();
33777
- } catch (error51) {
33778
- const mastraError = new error$1.MastraError(
33779
- {
33780
- id: "MCP_SERVER_RESOURCE_LIST_CHANGED_NOTIFICATION_FAILED",
33781
- domain: error$1.ErrorDomain.MCP,
33782
- category: error$1.ErrorCategory.THIRD_PARTY,
33783
- text: "Failed to send resource list changed notification"
33784
- },
33785
- error51
33786
- );
33787
- this.getLogger().trackException(mastraError);
33788
- this.getLogger().error("Failed to send resource list changed notification:", {
33789
- error: mastraError.toString()
33790
- });
33791
- throw mastraError;
33880
+ await broadcastNotification({
33881
+ servers: this.getSdkServers(),
33882
+ send: (server) => server.sendResourceListChanged(),
33883
+ logger: this.getLogger(),
33884
+ errorId: "MCP_SERVER_RESOURCE_LIST_CHANGED_NOTIFICATION_FAILED",
33885
+ errorText: "Failed to send resource list changed notification"
33886
+ });
33887
+ }
33888
+ };
33889
+
33890
+ // src/server/toolActions.ts
33891
+ var ServerToolActions = class {
33892
+ getLogger;
33893
+ getSdkServers;
33894
+ addTools;
33895
+ removeTools;
33896
+ /**
33897
+ * @internal
33898
+ */
33899
+ constructor(dependencies) {
33900
+ this.getLogger = dependencies.getLogger;
33901
+ this.getSdkServers = dependencies.getSdkServers;
33902
+ this.addTools = dependencies.addTools;
33903
+ this.removeTools = dependencies.removeTools;
33904
+ }
33905
+ /**
33906
+ * Registers new tools on the running server and notifies connected clients
33907
+ * that the tool list has changed.
33908
+ *
33909
+ * Tools are keyed by their record key, the same as tools passed to the
33910
+ * `MCPServer` constructor. Adding a tool under an existing key replaces it.
33911
+ *
33912
+ * @param tools - Tools to register
33913
+ * @throws {MastraError} If sending the notification fails on all server instances
33914
+ *
33915
+ * @example
33916
+ * ```typescript
33917
+ * await server.toolActions.add({ myNewTool });
33918
+ * ```
33919
+ */
33920
+ async add(tools) {
33921
+ this.addTools(tools);
33922
+ await this.notifyListChanged();
33923
+ }
33924
+ /**
33925
+ * Removes tools from the running server and notifies connected clients that
33926
+ * the tool list has changed.
33927
+ *
33928
+ * Unknown tool IDs are ignored (logged as a warning). If no tools were
33929
+ * actually removed, no notification is sent.
33930
+ *
33931
+ * @param toolIds - IDs of the tools to remove
33932
+ * @throws {MastraError} If sending the notification fails on all server instances
33933
+ *
33934
+ * @example
33935
+ * ```typescript
33936
+ * await server.toolActions.remove(['myNewTool']);
33937
+ * ```
33938
+ */
33939
+ async remove(toolIds) {
33940
+ const removed = this.removeTools(toolIds);
33941
+ if (removed.length === 0) {
33942
+ this.getLogger().debug("No tools were removed; skipping tool list changed notification.");
33943
+ return;
33792
33944
  }
33945
+ await this.notifyListChanged();
33946
+ }
33947
+ /**
33948
+ * Notifies clients that the overall list of available tools has changed.
33949
+ *
33950
+ * This sends a `notifications/tools/list_changed` message to all clients,
33951
+ * prompting them to re-fetch the tool list.
33952
+ *
33953
+ * @throws {MastraError} If sending the notification fails on all server instances
33954
+ *
33955
+ * @example
33956
+ * ```typescript
33957
+ * // After changing which tools are available
33958
+ * await server.toolActions.notifyListChanged();
33959
+ * ```
33960
+ */
33961
+ async notifyListChanged() {
33962
+ this.getLogger().info("Tool list changed. Sending notification.");
33963
+ await broadcastNotification({
33964
+ servers: this.getSdkServers(),
33965
+ send: (server) => server.sendToolListChanged(),
33966
+ logger: this.getLogger(),
33967
+ errorId: "MCP_SERVER_TOOL_LIST_CHANGED_NOTIFICATION_FAILED",
33968
+ errorText: "Failed to send tool list changed notification"
33969
+ });
33793
33970
  }
33794
33971
  };
33795
33972
 
33796
33973
  // src/server/server.ts
33974
+ var LOG_LEVEL_SEVERITY = {
33975
+ debug: 0,
33976
+ info: 1,
33977
+ notice: 2,
33978
+ warning: 3,
33979
+ error: 4,
33980
+ critical: 5,
33981
+ alert: 6,
33982
+ emergency: 7
33983
+ };
33797
33984
  var MCPServer = class extends mcp.MCPServerBase {
33798
33985
  server;
33799
33986
  stdioTransport;
@@ -33812,8 +33999,12 @@ var MCPServer = class extends mcp.MCPServerBase {
33812
33999
  jsonSchemaValidator;
33813
34000
  mapAuthInfoToUser;
33814
34001
  fga;
33815
- subscriptions = /* @__PURE__ */ new Set();
33816
- currentLoggingLevel;
34002
+ // Resource subscriptions per server instance (main + per HTTP session), set via
34003
+ // resources/subscribe. Note: legacy SSE sessions share the main instance, so they
34004
+ // share one subscription set; streamable HTTP sessions are isolated per session.
34005
+ subscriptionsByInstance = /* @__PURE__ */ new WeakMap();
34006
+ // Minimum logging level per server instance (main + per HTTP session), set via logging/setLevel
34007
+ loggingLevels = /* @__PURE__ */ new WeakMap();
33817
34008
  /**
33818
34009
  * Provides methods to notify clients about resource changes.
33819
34010
  *
@@ -33837,6 +34028,24 @@ var MCPServer = class extends mcp.MCPServerBase {
33837
34028
  * ```
33838
34029
  */
33839
34030
  prompts;
34031
+ /**
34032
+ * Provides methods to dynamically manage tools and notify clients about
34033
+ * tool list changes. Named `toolActions` because `tools()` is the tool
34034
+ * registry getter inherited from `MCPServerBase`.
34035
+ *
34036
+ * @example
34037
+ * ```typescript
34038
+ * // Register a new tool at runtime and notify clients
34039
+ * await server.toolActions.add({ myNewTool });
34040
+ *
34041
+ * // Remove a tool and notify clients
34042
+ * await server.toolActions.remove(['myNewTool']);
34043
+ *
34044
+ * // Notify that the tool list changed (e.g. authorization changes)
34045
+ * await server.toolActions.notifyListChanged();
34046
+ * ```
34047
+ */
34048
+ toolActions;
33840
34049
  /**
33841
34050
  * Provides methods for interactive user input collection during tool execution.
33842
34051
  *
@@ -33954,7 +34163,7 @@ var MCPServer = class extends mcp.MCPServerBase {
33954
34163
  this.mapAuthInfoToUser = opts.mapAuthInfoToUser;
33955
34164
  this.fga = opts.fga;
33956
34165
  const capabilities = {
33957
- tools: {},
34166
+ tools: { listChanged: true },
33958
34167
  logging: { enabled: true }
33959
34168
  };
33960
34169
  if (this.resourceOptions) {
@@ -33993,23 +34202,144 @@ var MCPServer = class extends mcp.MCPServerBase {
33993
34202
  this.sseHonoTransports = /* @__PURE__ */ new Map();
33994
34203
  this.registerHandlersOnServer(this.server);
33995
34204
  this.resources = new ServerResourceActions({
33996
- getSubscriptions: () => this.subscriptions,
34205
+ getSubscribedServers: (uri) => this.getAllSdkServers().filter((server) => this.subscriptionsByInstance.get(server)?.has(uri)),
33997
34206
  getLogger: () => this.logger,
33998
- getSdkServer: () => this.server
34207
+ getSdkServers: () => this.getAllSdkServers()
33999
34208
  });
34000
34209
  this.prompts = new ServerPromptActions({
34001
34210
  getLogger: () => this.logger,
34002
- getSdkServer: () => this.server,
34211
+ getSdkServers: () => this.getAllSdkServers(),
34003
34212
  clearDefinedPrompts: () => {
34004
34213
  this.definedPrompts = void 0;
34005
34214
  }
34006
34215
  });
34216
+ this.toolActions = new ServerToolActions({
34217
+ getLogger: () => this.logger,
34218
+ getSdkServers: () => this.getAllSdkServers(),
34219
+ addTools: (tools) => this.addTools(tools),
34220
+ removeTools: (toolIds) => this.removeTools(toolIds)
34221
+ });
34007
34222
  this.elicitation = {
34008
34223
  sendRequest: async (request, options) => {
34009
34224
  return this.handleElicitationRequest(request, void 0, options);
34010
34225
  }
34011
34226
  };
34012
34227
  }
34228
+ /**
34229
+ * Returns every connected SDK server instance: the main instance (stdio/SSE
34230
+ * transports) plus one instance per streamable HTTP session. Used to
34231
+ * broadcast notifications to all connected clients. Instances without a
34232
+ * connected transport are skipped.
34233
+ *
34234
+ * Note: stateless/serverless requests use transient server instances and
34235
+ * cannot receive notifications.
34236
+ */
34237
+ getAllSdkServers() {
34238
+ return [this.server, ...this.httpServerInstances.values()].filter((server) => server.transport !== void 0);
34239
+ }
34240
+ /**
34241
+ * Determines whether a log message at the given level should be sent to the
34242
+ * client connected to the given server instance, honoring the minimum level
34243
+ * the client set via `logging/setLevel` (RFC 5424 severity ordering).
34244
+ * When the client never set a level, all messages are sent.
34245
+ */
34246
+ shouldSendLog(serverInstance, level) {
34247
+ const minimumLevel = this.loggingLevels.get(serverInstance);
34248
+ if (minimumLevel === void 0) return true;
34249
+ return LOG_LEVEL_SEVERITY[level] >= LOG_LEVEL_SEVERITY[minimumLevel];
34250
+ }
34251
+ /**
34252
+ * Sends a `notifications/message` log notification to connected clients.
34253
+ *
34254
+ * The notification is broadcast to every active server instance, honoring
34255
+ * the minimum logging level each client set via `logging/setLevel`.
34256
+ *
34257
+ * @param params - Log message parameters
34258
+ * @param params.level - Log severity level
34259
+ * @param params.data - Arbitrary JSON-serializable data to log
34260
+ * @param params.logger - Optional logger name
34261
+ * @throws {MastraError} If sending the notification fails on all eligible server instances
34262
+ *
34263
+ * @example
34264
+ * ```typescript
34265
+ * await server.sendLoggingMessage({
34266
+ * level: 'info',
34267
+ * data: { message: 'Sync completed', itemsProcessed: 42 },
34268
+ * });
34269
+ * ```
34270
+ */
34271
+ async sendLoggingMessage(params) {
34272
+ const eligibleServers = this.getAllSdkServers().filter((server) => this.shouldSendLog(server, params.level));
34273
+ if (eligibleServers.length === 0) {
34274
+ this.logger.debug("No eligible clients for log message; skipping.", { level: params.level });
34275
+ return;
34276
+ }
34277
+ await broadcastNotification({
34278
+ servers: eligibleServers,
34279
+ send: (server) => server.sendLoggingMessage(params),
34280
+ logger: this.logger,
34281
+ errorId: "MCP_SERVER_LOGGING_MESSAGE_NOTIFICATION_FAILED",
34282
+ errorText: "Failed to send logging message notification",
34283
+ errorDetails: { level: params.level }
34284
+ });
34285
+ }
34286
+ /**
34287
+ * Registers new tools on the running server. Tools are merged into both the
34288
+ * converted tool registry (used by list/call handlers) and the original
34289
+ * tools config so they survive tool re-conversion when the server is
34290
+ * registered with a Mastra instance.
34291
+ */
34292
+ addTools(tools) {
34293
+ const converted = this.convertTools(tools);
34294
+ for (const key of Object.keys(converted)) {
34295
+ if (this.convertedTools[key]) {
34296
+ this.logger.warn(`Tool '${key}' already exists and will be replaced.`);
34297
+ }
34298
+ }
34299
+ this.convertedTools = { ...this.convertedTools, ...converted };
34300
+ this.originalTools = { ...this.originalTools, ...tools };
34301
+ if (this.mastra) {
34302
+ for (const [key, tool] of Object.entries(tools)) {
34303
+ if (tool && typeof tool === "object" && "id" in tool) {
34304
+ this.mastra.addTool(tool, this.mastraToolKey(key, tool));
34305
+ }
34306
+ }
34307
+ }
34308
+ }
34309
+ /**
34310
+ * Removes tools from the running server by tool ID.
34311
+ *
34312
+ * @returns The IDs of the tools that were actually removed
34313
+ */
34314
+ removeTools(toolIds) {
34315
+ const removed = [];
34316
+ const convertedTools = { ...this.convertedTools };
34317
+ const originalTools = { ...this.originalTools };
34318
+ for (const toolId of toolIds) {
34319
+ if (convertedTools[toolId]) {
34320
+ const originalTool = originalTools[toolId];
34321
+ delete convertedTools[toolId];
34322
+ delete originalTools[toolId];
34323
+ removed.push(toolId);
34324
+ if (this.mastra && originalTool && typeof originalTool === "object" && "id" in originalTool) {
34325
+ this.mastra.removeTool(this.mastraToolKey(toolId, originalTool));
34326
+ }
34327
+ } else {
34328
+ this.logger.warn(`Cannot remove tool '${toolId}': tool not found.`);
34329
+ }
34330
+ }
34331
+ this.convertedTools = convertedTools;
34332
+ this.originalTools = originalTools;
34333
+ return removed;
34334
+ }
34335
+ /**
34336
+ * The key a tool is registered under in the Mastra instance's tool
34337
+ * registry: the tool's intrinsic ID when present (avoids collisions across
34338
+ * MCP servers), falling back to its record key. Mirrors __registerMastra.
34339
+ */
34340
+ mastraToolKey(key, tool) {
34341
+ return "id" in tool && typeof tool.id === "string" ? tool.id : key;
34342
+ }
34013
34343
  /**
34014
34344
  * Handle an elicitation request by sending it to the connected client.
34015
34345
  * This method sends an elicitation/create request to the client and waits for the response.
@@ -34142,7 +34472,7 @@ var MCPServer = class extends mcp.MCPServerBase {
34142
34472
  */
34143
34473
  createServerInstance() {
34144
34474
  const capabilities = {
34145
- tools: {},
34475
+ tools: { listChanged: true },
34146
34476
  logging: { enabled: true }
34147
34477
  };
34148
34478
  if (this.resourceOptions) {
@@ -34261,6 +34591,33 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34261
34591
  }
34262
34592
  };
34263
34593
  const proxiedContext = await this.createProxiedRequestContext(extra);
34594
+ const sessionLog = async (level, message, data) => {
34595
+ if (!this.shouldSendLog(serverInstance, level)) return;
34596
+ await extra.sendNotification({
34597
+ method: "notifications/message",
34598
+ params: {
34599
+ level,
34600
+ logger: this.name,
34601
+ data: { message, ...data }
34602
+ }
34603
+ });
34604
+ };
34605
+ const progressToken = extra._meta?.progressToken;
34606
+ const sessionProgress = async (params) => {
34607
+ if (progressToken === void 0) {
34608
+ this.logger.debug("Tool attempted to send progress but the caller sent no progressToken; skipping.", {
34609
+ tool: request.params.name
34610
+ });
34611
+ return;
34612
+ }
34613
+ await extra.sendNotification({
34614
+ method: "notifications/progress",
34615
+ params: {
34616
+ progressToken,
34617
+ ...params
34618
+ }
34619
+ });
34620
+ };
34264
34621
  const mcpOptions = {
34265
34622
  messages: [],
34266
34623
  toolCallId: "",
@@ -34268,7 +34625,9 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34268
34625
  // Pass MCP-specific context through the mcp property
34269
34626
  mcp: {
34270
34627
  elicitation: sessionElicitation,
34271
- extra
34628
+ extra,
34629
+ log: sessionLog,
34630
+ progress: sessionProgress
34272
34631
  },
34273
34632
  // @ts-expect-error this is to let people know that the elicitation and extra keys are now nested under mcp.elicitation and mcp.extra in tool arguments
34274
34633
  get elicitation() {
@@ -34356,7 +34715,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34356
34715
  }
34357
34716
  });
34358
34717
  serverInstance.setRequestHandler(types_js.SetLevelRequestSchema, async (request) => {
34359
- this.currentLoggingLevel = request.params.level;
34718
+ this.loggingLevels.set(serverInstance, request.params.level);
34360
34719
  this.logger.debug("Logging level set", { level: request.params.level });
34361
34720
  return {};
34362
34721
  });
@@ -34400,11 +34759,13 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34400
34759
  try {
34401
34760
  const resourcesOrResourceContent = await capturedResourceOptions.getResourceContent({ uri, extra });
34402
34761
  const resourcesContent = Array.isArray(resourcesOrResourceContent) ? resourcesOrResourceContent : [resourcesOrResourceContent];
34762
+ const resourceMeta = resource._meta ? { _meta: resource._meta } : {};
34403
34763
  const contents = resourcesContent.map((resourceContent) => {
34404
34764
  if ("text" in resourceContent && resourceContent.text !== void 0) {
34405
34765
  return {
34406
34766
  uri: resource.uri,
34407
34767
  mimeType: resource.mimeType,
34768
+ ...resourceMeta,
34408
34769
  text: resourceContent.text
34409
34770
  };
34410
34771
  }
@@ -34415,6 +34776,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34415
34776
  return {
34416
34777
  uri: resource.uri,
34417
34778
  mimeType: resource.mimeType,
34779
+ ...resourceMeta,
34418
34780
  blob
34419
34781
  };
34420
34782
  });
@@ -34445,13 +34807,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
34445
34807
  serverInstance.setRequestHandler(types_js.SubscribeRequestSchema, async (request) => {
34446
34808
  const uri = request.params.uri;
34447
34809
  this.logger.info("Received resources/subscribe request", { uri });
34448
- this.subscriptions.add(uri);
34810
+ let subscriptions = this.subscriptionsByInstance.get(serverInstance);
34811
+ if (!subscriptions) {
34812
+ subscriptions = /* @__PURE__ */ new Set();
34813
+ this.subscriptionsByInstance.set(serverInstance, subscriptions);
34814
+ }
34815
+ subscriptions.add(uri);
34449
34816
  return {};
34450
34817
  });
34451
34818
  serverInstance.setRequestHandler(types_js.UnsubscribeRequestSchema, async (request) => {
34452
34819
  const uri = request.params.uri;
34453
34820
  this.logger.info("Received resources/unsubscribe request", { uri });
34454
- this.subscriptions.delete(uri);
34821
+ this.subscriptionsByInstance.get(serverInstance)?.delete(uri);
34455
34822
  return {};
34456
34823
  });
34457
34824
  }
@@ -35021,7 +35388,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
35021
35388
  const isStatelessMode = options?.serverless || options && "sessionIdGenerator" in options && options.sessionIdGenerator === void 0;
35022
35389
  if (isStatelessMode) {
35023
35390
  this.logger.debug("Running in stateless mode");
35024
- await this.handleServerlessRequest(req, res);
35391
+ const enableJsonResponse = options?.enableJsonResponse ?? !options?.serverlessStreaming;
35392
+ await this.handleServerlessRequest(req, res, { enableJsonResponse });
35025
35393
  return;
35026
35394
  }
35027
35395
  const mergedOptions = {
@@ -35065,11 +35433,14 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
35065
35433
  const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
35066
35434
  if (isInitializeRequest(body)) {
35067
35435
  this.logger.debug("Received initialize request, creating new transport");
35436
+ const sessionServerInstance = this.createServerInstance();
35068
35437
  transport = new streamableHttp_js$1.StreamableHTTPServerTransport({
35069
35438
  ...mergedOptions,
35070
35439
  sessionIdGenerator: mergedOptions.sessionIdGenerator,
35071
35440
  onsessioninitialized: (id) => {
35072
35441
  this.streamableHTTPTransports.set(id, transport);
35442
+ this.httpServerInstances.set(id, sessionServerInstance);
35443
+ this.logger.debug("Session initialized and stored", { sessionId: id });
35073
35444
  }
35074
35445
  });
35075
35446
  transport.onclose = () => {
@@ -35083,15 +35454,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
35083
35454
  }
35084
35455
  }
35085
35456
  };
35086
- const sessionServerInstance = this.createServerInstance();
35087
35457
  await sessionServerInstance.connect(transport);
35088
- if (transport.sessionId) {
35089
- this.streamableHTTPTransports.set(transport.sessionId, transport);
35090
- this.httpServerInstances.set(transport.sessionId, sessionServerInstance);
35091
- this.logger.debug("Session initialized and stored", { sessionId: transport.sessionId });
35092
- } else {
35093
- this.logger.warn("Transport initialized without a session ID");
35094
- }
35095
35458
  return await transport.handleRequest(req, res, body);
35096
35459
  } else {
35097
35460
  this.logger.warn("Received non-initialize POST request without session ID");
@@ -35162,21 +35525,26 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
35162
35525
  *
35163
35526
  * @param req - Incoming HTTP request
35164
35527
  * @param res - HTTP response object
35528
+ * @param options - Transport options for this request
35529
+ * @param options.enableJsonResponse - When `true` (default), buffers and returns a single
35530
+ * JSON-RPC response. When `false`, the request is handled with request-scoped SSE streaming,
35531
+ * so in-request `notifications/progress` reach the client before the final result.
35165
35532
  * @private
35166
35533
  */
35167
- async handleServerlessRequest(req, res) {
35534
+ async handleServerlessRequest(req, res, { enableJsonResponse = true } = {}) {
35168
35535
  try {
35169
35536
  this.logger.debug("Received serverless request", { method: req.method });
35170
35537
  const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
35171
35538
  this.logger.debug("Processing serverless request", {
35172
35539
  method: req.method,
35173
35540
  bodyMethod: body?.method,
35174
- id: body?.id
35541
+ id: body?.id,
35542
+ enableJsonResponse
35175
35543
  });
35176
35544
  const transientServer = this.createServerInstance();
35177
35545
  const tempTransport = new streamableHttp_js$1.StreamableHTTPServerTransport({
35178
35546
  sessionIdGenerator: void 0,
35179
- enableJsonResponse: true
35547
+ enableJsonResponse
35180
35548
  });
35181
35549
  await transientServer.connect(tempTransport);
35182
35550
  await tempTransport.handleRequest(req, res, body);