@mastra/mcp 1.17.0-alpha.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,9 +16,9 @@ import { RequestContext } from "@mastra/core/request-context";
16
16
  import { makeCoreTool } from "@mastra/core/utils";
17
17
  import { PromptSchema } from "@modelcontextprotocol/core";
18
18
  import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps";
19
- import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
20
- import { ProtocolError, ProtocolErrorCode as ProtocolErrorCode$1, Server } from "@modelcontextprotocol/server";
21
- import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
19
+ import { NodeStreamableHTTPServerTransport, hostHeaderValidation, originValidation, toNodeHandler } from "@modelcontextprotocol/node";
20
+ import { ProtocolError, ProtocolErrorCode as ProtocolErrorCode$1, Server, createMcpHandler } from "@modelcontextprotocol/server";
21
+ import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
22
22
  import { SSEServerTransport } from "@modelcontextprotocol/server-legacy/sse";
23
23
  //#region \0rolldown/runtime.js
24
24
  var __defProp$3 = Object.defineProperty;
@@ -5010,7 +5010,7 @@ const allowsEval = /* @__PURE__*/ cached(() => {
5010
5010
  return false;
5011
5011
  }
5012
5012
  });
5013
- function isPlainObject(o) {
5013
+ function isPlainObject$1(o) {
5014
5014
  if (isObject(o) === false) return false;
5015
5015
  const ctor = o.constructor;
5016
5016
  if (ctor === void 0) return true;
@@ -5021,7 +5021,7 @@ function isPlainObject(o) {
5021
5021
  return true;
5022
5022
  }
5023
5023
  function shallowClone(o) {
5024
- if (isPlainObject(o)) return { ...o };
5024
+ if (isPlainObject$1(o)) return { ...o };
5025
5025
  if (Array.isArray(o)) return [...o];
5026
5026
  if (o instanceof Map) return new Map(o);
5027
5027
  if (o instanceof Set) return new Set(o);
@@ -5104,7 +5104,7 @@ function omit(schema, mask) {
5104
5104
  }));
5105
5105
  }
5106
5106
  function extend(schema, shape) {
5107
- if (!isPlainObject(shape)) throw new Error("Invalid input to extend: expected a plain object");
5107
+ if (!isPlainObject$1(shape)) throw new Error("Invalid input to extend: expected a plain object");
5108
5108
  const checks = schema._zod.def.checks;
5109
5109
  if (checks && checks.length > 0) {
5110
5110
  const existingShape = schema._zod.def.shape;
@@ -5120,7 +5120,7 @@ function extend(schema, shape) {
5120
5120
  } }));
5121
5121
  }
5122
5122
  function safeExtend(schema, shape) {
5123
- if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
5123
+ if (!isPlainObject$1(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
5124
5124
  return clone(schema, mergeDefs(schema._zod.def, { get shape() {
5125
5125
  const _shape = {
5126
5126
  ...schema._zod.def.shape,
@@ -6805,7 +6805,7 @@ function mergeValues(a, b) {
6805
6805
  valid: true,
6806
6806
  data: a
6807
6807
  };
6808
- if (isPlainObject(a) && isPlainObject(b)) {
6808
+ if (isPlainObject$1(a) && isPlainObject$1(b)) {
6809
6809
  const bKeys = Object.keys(b);
6810
6810
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
6811
6811
  const newObj = {
@@ -6973,7 +6973,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
6973
6973
  $ZodType.init(inst, def);
6974
6974
  inst._zod.parse = (payload, ctx) => {
6975
6975
  const input = payload.value;
6976
- if (!isPlainObject(input)) {
6976
+ if (!isPlainObject$1(input)) {
6977
6977
  payload.issues.push({
6978
6978
  expected: "record",
6979
6979
  code: "invalid_type",
@@ -21301,44 +21301,21 @@ function extractModelTextFromToolContent(content) {
21301
21301
  * `toModelOutput` can read MCP `content` without changing the execute return shape.
21302
21302
  */
21303
21303
  const MCP_CALL_TOOL_CONTENT = Symbol.for("mastra.mcp.callToolContent");
21304
- function reserveScalarMcpContent(queue) {
21305
- const reservation = { ready: false };
21306
- queue.push(reservation);
21307
- return reservation;
21308
- }
21309
- function removeScalarMcpContentReservation(queue, reservation) {
21310
- if (!reservation) return;
21311
- const index = queue.indexOf(reservation);
21312
- if (index !== -1) queue.splice(index, 1);
21313
- }
21314
- function attachMcpCallToolContent(structuredContent, content, scalarMcpContentQueue, reservation) {
21315
- if (structuredContent !== null && typeof structuredContent === "object") {
21316
- removeScalarMcpContentReservation(scalarMcpContentQueue, reservation);
21317
- Object.defineProperty(structuredContent, MCP_CALL_TOOL_CONTENT, {
21318
- value: content,
21319
- enumerable: false,
21320
- configurable: true
21321
- });
21322
- return structuredContent;
21323
- }
21324
- if (reservation) {
21325
- reservation.output = structuredContent;
21326
- reservation.content = content;
21327
- reservation.ready = true;
21328
- }
21304
+ function attachMcpCallToolContent(structuredContent, content) {
21305
+ if (structuredContent !== null && typeof structuredContent === "object") Object.defineProperty(structuredContent, MCP_CALL_TOOL_CONTENT, {
21306
+ value: content,
21307
+ enumerable: false,
21308
+ configurable: true
21309
+ });
21329
21310
  return structuredContent;
21330
21311
  }
21331
- function getMcpCallToolContent(output, scalarMcpContentQueue) {
21332
- if (output !== null && typeof output === "object") {
21333
- const attached = output[MCP_CALL_TOOL_CONTENT];
21334
- if (attached !== void 0) return attached;
21335
- }
21336
- const index = scalarMcpContentQueue.findIndex((entry) => entry.ready && Object.is(entry.output, output));
21337
- if (index !== -1) return scalarMcpContentQueue.splice(index, 1)[0]?.content;
21312
+ function getMcpCallToolContent(output) {
21313
+ if (output === null || typeof output !== "object") return void 0;
21314
+ return output[MCP_CALL_TOOL_CONTENT];
21338
21315
  }
21339
- function createStructuredToolToModelOutput(scalarMcpContentQueue) {
21316
+ function createStructuredToolToModelOutput() {
21340
21317
  return (output) => {
21341
- const modelText = extractModelTextFromToolContent(getMcpCallToolContent(output, scalarMcpContentQueue));
21318
+ const modelText = extractModelTextFromToolContent(getMcpCallToolContent(output));
21342
21319
  if (modelText !== void 0) return {
21343
21320
  type: "text",
21344
21321
  value: modelText
@@ -21462,12 +21439,14 @@ var InternalMastraMCPClient = class extends MastraBase {
21462
21439
  "io.modelcontextprotocol/ui": {}
21463
21440
  }
21464
21441
  };
21442
+ const versionNegotiation = server.protocolVersion === void 0 ? void 0 : { mode: server.protocolVersion === "auto" ? "auto" : { pin: server.protocolVersion } };
21465
21443
  this.client = new Client({
21466
21444
  name,
21467
21445
  version
21468
21446
  }, {
21469
21447
  capabilities: clientCapabilities,
21470
- ...server.jsonSchemaValidator ? { jsonSchemaValidator: server.jsonSchemaValidator } : {}
21448
+ ...server.jsonSchemaValidator ? { jsonSchemaValidator: server.jsonSchemaValidator } : {},
21449
+ ...versionNegotiation ? { versionNegotiation } : {}
21471
21450
  });
21472
21451
  this.setupLogging();
21473
21452
  if (hasRoots) this.setupRootsHandler();
@@ -21635,7 +21614,7 @@ var InternalMastraMCPClient = class extends MastraBase {
21635
21614
  this.markNeedsAuth(streamableTransport);
21636
21615
  throw error;
21637
21616
  }
21638
- if (isUrlPolicyError(error)) throw error;
21617
+ if (isUrlPolicyError(error) || this.serverConfig.protocolVersion === "2026-07-28") throw error;
21639
21618
  const status = error instanceof SdkHttpError ? error.status : void 0;
21640
21619
  if (status !== void 0 && !SSE_FALLBACK_STATUS_CODES.includes(status)) throw error;
21641
21620
  shouldTrySSE = true;
@@ -22186,7 +22165,6 @@ var InternalMastraMCPClient = class extends MastraBase {
22186
22165
  ...toolMeta ? { _meta: toolMeta } : {},
22187
22166
  ...annotations ? { annotations } : {}
22188
22167
  } } : {};
22189
- const scalarMcpContentQueue = [];
22190
22168
  const mastraTool = createTool({
22191
22169
  id: `${this.name}_${tool.name}`,
22192
22170
  description: tool.description || "",
@@ -22202,11 +22180,10 @@ var InternalMastraMCPClient = class extends MastraBase {
22202
22180
  forwardInstructions: this.forwardInstructions,
22203
22181
  instructionsMaxLength: this.instructionsMaxLength
22204
22182
  },
22205
- ...tool.outputSchema ? { toModelOutput: createStructuredToolToModelOutput(scalarMcpContentQueue) } : {},
22183
+ ...tool.outputSchema ? { toModelOutput: createStructuredToolToModelOutput() } : {},
22206
22184
  execute: async (input, context) => {
22207
22185
  if (serverMeta.connectFirst) await this.connect();
22208
22186
  const operationContext = context?.requestContext ?? null;
22209
- const scalarMcpContentReservation = tool.outputSchema ? reserveScalarMcpContent(scalarMcpContentQueue) : void 0;
22210
22187
  return this.operationContextStore.run(operationContext, async () => {
22211
22188
  const executeToolCall = async () => {
22212
22189
  this.log("debug", `Executing tool: ${tool.name}`, {
@@ -22242,8 +22219,7 @@ var InternalMastraMCPClient = class extends MastraBase {
22242
22219
  });
22243
22220
  }
22244
22221
  this.log("debug", `Tool executed successfully: ${tool.name}`);
22245
- if (res.structuredContent !== void 0) return attachMcpCallToolContent(res.structuredContent, res.content, scalarMcpContentQueue, scalarMcpContentReservation);
22246
- removeScalarMcpContentReservation(scalarMcpContentQueue, scalarMcpContentReservation);
22222
+ if (res.structuredContent !== void 0) return attachMcpCallToolContent(res.structuredContent, res.content);
22247
22223
  return res;
22248
22224
  };
22249
22225
  const failedTransport = this.transport;
@@ -22263,7 +22239,6 @@ var InternalMastraMCPClient = class extends MastraBase {
22263
22239
  reconnectError: reconnectError instanceof Error ? reconnectError.stack : String(reconnectError),
22264
22240
  toolArgs: input
22265
22241
  });
22266
- removeScalarMcpContentReservation(scalarMcpContentQueue, scalarMcpContentReservation);
22267
22242
  throw reconnectError;
22268
22243
  }
22269
22244
  }
@@ -22271,7 +22246,6 @@ var InternalMastraMCPClient = class extends MastraBase {
22271
22246
  error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
22272
22247
  toolArgs: input
22273
22248
  });
22274
- removeScalarMcpContentReservation(scalarMcpContentQueue, scalarMcpContentReservation);
22275
22249
  throw e;
22276
22250
  }
22277
22251
  });
@@ -24299,7 +24273,7 @@ var streamSSE = (c, cb, onError) => {
24299
24273
  return c.newResponse(stream.responseReadable);
24300
24274
  };
24301
24275
  //#endregion
24302
- //#region ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@modelcontextprotocol/sdk/1.29.0/989ce0cb833930f9ef8da3e25bac11110cfeac46669bc69ae9534a6aa55b0aa7/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
24276
+ //#region ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@modelcontextprotocol/sdk/1.29.0/7d2f0982429dbe53f84f7249d02074ef571f5d1c95137997d9ed2dfe102a137b/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
24303
24277
  const RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
24304
24278
  /**
24305
24279
  * Assert 'object' type schema.
@@ -26130,7 +26104,7 @@ union([
26130
26104
  CreateTaskResultSchema
26131
26105
  ]);
26132
26106
  //#endregion
26133
- //#region ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@/hono-mcp-server-sse-transport/0.0.7/518df2f53e0bbb46e48def5118fc7f6b5bba3a268854c8a5c7e83260299d376e/node_modules/hono-mcp-server-sse-transport/build/sse.js
26107
+ //#region ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@/hono-mcp-server-sse-transport/0.0.7/d5ae5a23aa2b79db7055621f11a7cbc8d27f7caba1eeb308ab2cf5bcb1a8e5b8/node_modules/hono-mcp-server-sse-transport/build/sse.js
26134
26108
  const MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024;
26135
26109
  var SSETransport = class {
26136
26110
  messageUrl;
@@ -26205,6 +26179,132 @@ var SSETransport = class {
26205
26179
  }
26206
26180
  };
26207
26181
  //#endregion
26182
+ //#region src/server/mrtrElicitation.ts
26183
+ /**
26184
+ * Elicitation on the 2026-07-28 protocol revision (multi-round-trip requests).
26185
+ *
26186
+ * The modern era has no server→client request channel, so `server.elicitInput()`
26187
+ * throws there by design. Instead, the `tools/call` handler must RETURN an
26188
+ * `input_required` result carrying the embedded elicitation request; the client
26189
+ * answers it locally and RETRIES the call with the response attached
26190
+ * (`inputResponses`). This module keeps the promise-shaped
26191
+ * `elicitation.sendRequest()` API tools already use by replaying the tool:
26192
+ *
26193
+ * 1. Each `sendRequest` call is assigned a deterministic sequence key.
26194
+ * 2. If the current (retried) request carries an answer for that key — either in
26195
+ * this round's `inputResponses` or in the accumulated `requestState` from
26196
+ * prior rounds — the promise resolves with it immediately.
26197
+ * 3. Otherwise a {@link ElicitationReplayInterrupt} is thrown; the `tools/call`
26198
+ * handler converts it into an {@link InputRequiredResult} return, and the
26199
+ * client's retry re-executes the tool from the top.
26200
+ *
26201
+ * Consequences tools must live with on the modern leg:
26202
+ * - Code before an unanswered elicitation re-executes on every round; keep side
26203
+ * effects idempotent or place them after the last elicitation.
26204
+ * - Elicitation order must be deterministic for a given input, since answers are
26205
+ * matched by call sequence.
26206
+ */
26207
+ const ELICIT_KEY_PREFIX = "mastra_elicit_";
26208
+ /**
26209
+ * Thrown by the replay-based `sendRequest` when the current round carries no
26210
+ * answer for the elicitation. Never surfaces to tools or clients: the
26211
+ * `tools/call` handler converts it into an `input_required` result.
26212
+ */
26213
+ var ElicitationReplayInterrupt = class extends Error {
26214
+ key;
26215
+ params;
26216
+ answered;
26217
+ constructor(key, params, answered) {
26218
+ super(`Elicitation '${key}' requires client input (multi-round-trip)`);
26219
+ this.key = key;
26220
+ this.params = params;
26221
+ this.answered = answered;
26222
+ this.name = "ElicitationReplayInterrupt";
26223
+ }
26224
+ };
26225
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26226
+ const looksLikeElicitResult = (value) => isPlainObject(value) && typeof value.action === "string";
26227
+ /**
26228
+ * Decodes the accumulated prior-round answers from the request's opaque
26229
+ * `requestState` echo. The state round-trips through the client, so it is
26230
+ * attacker-controlled input on re-entry — but its only content is the client's
26231
+ * own past elicitation answers, which are untrusted user input either way, so
26232
+ * tampering grants the client nothing it does not already control. Anything
26233
+ * that does not parse as a map of elicitation results is discarded.
26234
+ */
26235
+ const decodePriorAnswers = (extra) => {
26236
+ const raw = extra.mcpReq.requestState();
26237
+ if (typeof raw !== "string" || raw.length === 0) return {};
26238
+ try {
26239
+ const parsed = JSON.parse(raw);
26240
+ if (!isPlainObject(parsed)) return {};
26241
+ const answers = {};
26242
+ for (const [key, value] of Object.entries(parsed)) if (key.startsWith(ELICIT_KEY_PREFIX) && looksLikeElicitResult(value)) answers[key] = value;
26243
+ return answers;
26244
+ } catch {
26245
+ return {};
26246
+ }
26247
+ };
26248
+ /** This round's bare responses that answer our elicitation keys. */
26249
+ const currentRoundAnswers = (extra) => {
26250
+ const answers = {};
26251
+ for (const [key, value] of Object.entries(extra.mcpReq.inputResponses ?? {})) if (key.startsWith(ELICIT_KEY_PREFIX) && looksLikeElicitResult(value)) answers[key] = value;
26252
+ return answers;
26253
+ };
26254
+ /**
26255
+ * Whether this request is served on the 2026-07-28 era. Modern clients always
26256
+ * attach the reserved per-request `_meta` envelope (the 2026 codec enforces the
26257
+ * required keys at dispatch); legacy requests never carry it.
26258
+ */
26259
+ const isModernEraRequest = (extra) => extra.mcpReq.envelope !== void 0;
26260
+ /**
26261
+ * Builds the replay-based {@link ElicitationActions} for one modern-era tool
26262
+ * execution. Answers already provided by the client resolve immediately; the
26263
+ * first unanswered elicitation throws {@link ElicitationReplayInterrupt}.
26264
+ *
26265
+ * The interrupt is also reported through `onInterrupt` before it is thrown:
26266
+ * tool wrappers (e.g. Mastra's tool builder) re-wrap thrown errors, so the
26267
+ * `tools/call` handler cannot rely on `instanceof` on whatever escapes the
26268
+ * tool — the out-of-band capture is the authoritative signal.
26269
+ */
26270
+ const createReplayElicitation = (extra, onInterrupt) => {
26271
+ const prior = decodePriorAnswers(extra);
26272
+ const current = currentRoundAnswers(extra);
26273
+ const answered = {
26274
+ ...prior,
26275
+ ...current
26276
+ };
26277
+ let sequence = 0;
26278
+ return { sendRequest: async (request) => {
26279
+ const key = `${ELICIT_KEY_PREFIX}${sequence++}`;
26280
+ const answer = answered[key];
26281
+ if (answer !== void 0) return answer;
26282
+ const interrupt = new ElicitationReplayInterrupt(key, request, answered);
26283
+ onInterrupt(interrupt);
26284
+ throw interrupt;
26285
+ } };
26286
+ };
26287
+ /**
26288
+ * Converts a caught {@link ElicitationReplayInterrupt} into the
26289
+ * `input_required` result the `tools/call` handler returns to the client.
26290
+ * Prior answers travel in `requestState` because each retry only carries the
26291
+ * current round's `inputResponses`.
26292
+ */
26293
+ const replayInterruptToInputRequired = (interrupt) => {
26294
+ const params = {
26295
+ mode: "form",
26296
+ ...interrupt.params
26297
+ };
26298
+ return {
26299
+ resultType: "input_required",
26300
+ inputRequests: { [interrupt.key]: {
26301
+ method: "elicitation/create",
26302
+ params
26303
+ } },
26304
+ ...Object.keys(interrupt.answered).length > 0 ? { requestState: JSON.stringify(interrupt.answered) } : {}
26305
+ };
26306
+ };
26307
+ //#endregion
26208
26308
  //#region src/server/notificationBroadcast.ts
26209
26309
  /**
26210
26310
  * Sends a notification to every active server instance (the main stdio/SSE
@@ -26261,6 +26361,7 @@ var ServerPromptActions = class {
26261
26361
  getLogger;
26262
26362
  getSdkServers;
26263
26363
  clearDefinedPrompts;
26364
+ getModernEraNotifier;
26264
26365
  /**
26265
26366
  * @internal
26266
26367
  */
@@ -26268,6 +26369,7 @@ var ServerPromptActions = class {
26268
26369
  this.getLogger = dependencies.getLogger;
26269
26370
  this.getSdkServers = dependencies.getSdkServers;
26270
26371
  this.clearDefinedPrompts = dependencies.clearDefinedPrompts;
26372
+ this.getModernEraNotifier = dependencies.getModernEraNotifier;
26271
26373
  }
26272
26374
  /**
26273
26375
  * Notifies clients that the overall list of available prompts has changed.
@@ -26286,6 +26388,7 @@ var ServerPromptActions = class {
26286
26388
  async notifyListChanged() {
26287
26389
  this.getLogger().info("Prompt list change externally notified. Clearing definedPrompts and sending notification.");
26288
26390
  this.clearDefinedPrompts();
26391
+ this.getModernEraNotifier?.()?.promptsChanged();
26289
26392
  await broadcastNotification({
26290
26393
  servers: this.getSdkServers(),
26291
26394
  send: (server) => server.sendPromptListChanged(),
@@ -26312,6 +26415,7 @@ var ServerResourceActions = class {
26312
26415
  getSubscribedServers;
26313
26416
  getLogger;
26314
26417
  getSdkServers;
26418
+ getModernEraNotifier;
26315
26419
  /**
26316
26420
  * @internal
26317
26421
  */
@@ -26319,6 +26423,7 @@ var ServerResourceActions = class {
26319
26423
  this.getSubscribedServers = dependencies.getSubscribedServers;
26320
26424
  this.getLogger = dependencies.getLogger;
26321
26425
  this.getSdkServers = dependencies.getSdkServers;
26426
+ this.getModernEraNotifier = dependencies.getModernEraNotifier;
26322
26427
  }
26323
26428
  /**
26324
26429
  * Notifies subscribed clients that a specific resource has been updated.
@@ -26338,6 +26443,7 @@ var ServerResourceActions = class {
26338
26443
  * ```
26339
26444
  */
26340
26445
  async notifyUpdated({ uri }) {
26446
+ this.getModernEraNotifier?.()?.resourceUpdated(uri);
26341
26447
  const subscribedServers = this.getSubscribedServers(uri);
26342
26448
  if (subscribedServers.length === 0) {
26343
26449
  this.getLogger().debug(`Resource ${uri} was updated, but no active subscriptions for it.`);
@@ -26370,6 +26476,7 @@ var ServerResourceActions = class {
26370
26476
  */
26371
26477
  async notifyListChanged() {
26372
26478
  this.getLogger().info("Resource list change externally notified. Sending notification.");
26479
+ this.getModernEraNotifier?.()?.resourcesChanged();
26373
26480
  await broadcastNotification({
26374
26481
  servers: this.getSdkServers(),
26375
26482
  send: (server) => server.sendResourceListChanged(),
@@ -26395,6 +26502,7 @@ var ServerToolActions = class {
26395
26502
  getSdkServers;
26396
26503
  addTools;
26397
26504
  removeTools;
26505
+ getModernEraNotifier;
26398
26506
  /**
26399
26507
  * @internal
26400
26508
  */
@@ -26403,6 +26511,7 @@ var ServerToolActions = class {
26403
26511
  this.getSdkServers = dependencies.getSdkServers;
26404
26512
  this.addTools = dependencies.addTools;
26405
26513
  this.removeTools = dependencies.removeTools;
26514
+ this.getModernEraNotifier = dependencies.getModernEraNotifier;
26406
26515
  }
26407
26516
  /**
26408
26517
  * Registers new tools on the running server and notifies connected clients
@@ -26461,6 +26570,7 @@ var ServerToolActions = class {
26461
26570
  */
26462
26571
  async notifyListChanged() {
26463
26572
  this.getLogger().info("Tool list changed. Sending notification.");
26573
+ this.getModernEraNotifier?.()?.toolsChanged();
26464
26574
  await broadcastNotification({
26465
26575
  servers: this.getSdkServers(),
26466
26576
  send: (server) => server.sendToolListChanged(),
@@ -26501,6 +26611,14 @@ const LOG_LEVEL_SEVERITY = {
26501
26611
  alert: 6,
26502
26612
  emergency: 7
26503
26613
  };
26614
+ const ACCEPTED_MODERN_ERA_HTTP_OPTION_KEYS = /* @__PURE__ */ new Set([
26615
+ "allowedHosts",
26616
+ "allowedOrigins",
26617
+ "enableDnsRebindingProtection",
26618
+ "serverless",
26619
+ "serverlessStreaming",
26620
+ "sessionIdGenerator"
26621
+ ]);
26504
26622
  /**
26505
26623
  * MCPServer exposes Mastra tools, agents, and workflows as a Model Context Protocol (MCP) server.
26506
26624
  *
@@ -26547,6 +26665,12 @@ var MCPServer = class extends MCPServerBase {
26547
26665
  fga;
26548
26666
  subscriptionsByInstance = /* @__PURE__ */ new WeakMap();
26549
26667
  loggingLevels = /* @__PURE__ */ new WeakMap();
26668
+ protocolVersion;
26669
+ cacheHints;
26670
+ modernEraHandler;
26671
+ modernEraNodeHandler;
26672
+ stdioHandle;
26673
+ stdioServerInstance;
26550
26674
  /**
26551
26675
  * Provides methods to notify clients about resource changes.
26552
26676
  *
@@ -26698,6 +26822,8 @@ var MCPServer = class extends MCPServerBase {
26698
26822
  */
26699
26823
  constructor(opts) {
26700
26824
  super(opts);
26825
+ this.protocolVersion = opts.protocolVersion;
26826
+ this.cacheHints = opts.cacheHints;
26701
26827
  this.resourceOptions = this.mergeAppResources(opts.resources, opts.appResources);
26702
26828
  this.hasUiResources = !!opts.appResources && Object.keys(opts.appResources).length > 0;
26703
26829
  this.promptOptions = opts.prompts;
@@ -26723,7 +26849,8 @@ var MCPServer = class extends MCPServerBase {
26723
26849
  }, {
26724
26850
  capabilities,
26725
26851
  ...this.instructions ? { instructions: this.instructions } : {},
26726
- ...this.jsonSchemaValidator ? { jsonSchemaValidator: this.jsonSchemaValidator } : {}
26852
+ ...this.jsonSchemaValidator ? { jsonSchemaValidator: this.jsonSchemaValidator } : {},
26853
+ ...this.servesModernEra() && this.cacheHints ? { cacheHints: this.cacheHints } : {}
26727
26854
  });
26728
26855
  this.logger.info("Initialized MCPServer", {
26729
26856
  name: this.name,
@@ -26734,23 +26861,27 @@ var MCPServer = class extends MCPServerBase {
26734
26861
  });
26735
26862
  this.sseHonoTransports = /* @__PURE__ */ new Map();
26736
26863
  this.registerHandlersOnServer(this.server);
26864
+ const getModernEraNotifier = () => this.servesModernEra() ? this.modernEraHandler?.notify : void 0;
26737
26865
  this.resources = new ServerResourceActions({
26738
26866
  getSubscribedServers: (uri) => this.getAllSdkServers().filter((server) => this.subscriptionsByInstance.get(server)?.has(uri)),
26739
26867
  getLogger: () => this.logger,
26740
- getSdkServers: () => this.getAllSdkServers()
26868
+ getSdkServers: () => this.getAllSdkServers(),
26869
+ getModernEraNotifier
26741
26870
  });
26742
26871
  this.prompts = new ServerPromptActions({
26743
26872
  getLogger: () => this.logger,
26744
26873
  getSdkServers: () => this.getAllSdkServers(),
26745
26874
  clearDefinedPrompts: () => {
26746
26875
  this.definedPrompts = void 0;
26747
- }
26876
+ },
26877
+ getModernEraNotifier
26748
26878
  });
26749
26879
  this.toolActions = new ServerToolActions({
26750
26880
  getLogger: () => this.logger,
26751
26881
  getSdkServers: () => this.getAllSdkServers(),
26752
26882
  addTools: (tools) => this.addTools(tools),
26753
- removeTools: (toolIds) => this.removeTools(toolIds)
26883
+ removeTools: (toolIds) => this.removeTools(toolIds),
26884
+ getModernEraNotifier
26754
26885
  });
26755
26886
  this.elicitation = { sendRequest: async (request, options) => {
26756
26887
  return this.handleElicitationRequest(request, void 0, options);
@@ -26766,7 +26897,69 @@ var MCPServer = class extends MCPServerBase {
26766
26897
  * cannot receive notifications.
26767
26898
  */
26768
26899
  getAllSdkServers() {
26769
- return [this.server, ...this.httpServerInstances.values()].filter((server) => server.transport !== void 0);
26900
+ return [
26901
+ this.server,
26902
+ this.stdioServerInstance,
26903
+ ...this.httpServerInstances.values()
26904
+ ].filter((server) => server?.transport !== void 0);
26905
+ }
26906
+ /**
26907
+ * Whether the server is pinned to the `2026-07-28` protocol revision.
26908
+ * When false (the default), all behavior is byte-identical to the legacy era.
26909
+ */
26910
+ servesModernEra() {
26911
+ return this.protocolVersion === "2026-07-28";
26912
+ }
26913
+ assertModernEraHTTPOptions(options) {
26914
+ if (!options) return;
26915
+ const incompatibleOptions = new Set(Object.keys(options).filter((option) => !ACCEPTED_MODERN_ERA_HTTP_OPTION_KEYS.has(option)));
26916
+ if (options.sessionIdGenerator !== void 0) incompatibleOptions.add("sessionIdGenerator");
26917
+ if (options.serverless === false) incompatibleOptions.add("serverless");
26918
+ if (options.serverlessStreaming === false) incompatibleOptions.add("serverlessStreaming");
26919
+ if (incompatibleOptions.size === 0) return;
26920
+ const names = [...incompatibleOptions].sort();
26921
+ throw new MastraError({
26922
+ id: "MCP_SERVER_MODERN_HTTP_OPTIONS_INCOMPATIBLE",
26923
+ domain: ErrorDomain.MCP,
26924
+ category: ErrorCategory.USER,
26925
+ text: `startHTTP options ${names.map((name) => `"${name}"`).join(", ")} are incompatible with protocolVersion "2026-07-28"`,
26926
+ details: { incompatibleOptions: names.join(", ") }
26927
+ });
26928
+ }
26929
+ validateHTTPRequestHeaders(req, res, options) {
26930
+ if (!options?.enableDnsRebindingProtection) return true;
26931
+ if (options.allowedHosts?.length) {
26932
+ if (!hostHeaderValidation(options.allowedHosts.map((host) => new URL(`http://${host}`).hostname))(req, res)) return false;
26933
+ }
26934
+ if (options.allowedOrigins?.length) {
26935
+ if (!originValidation(options.allowedOrigins.map((origin) => new URL(origin).hostname))(req, res)) return false;
26936
+ }
26937
+ return true;
26938
+ }
26939
+ /**
26940
+ * Lazily creates the dual-era HTTP handler used when `protocolVersion: '2026-07-28'`
26941
+ * is set: modern (per-request envelope) clients are served natively and legacy
26942
+ * clients are served through the SDK's built-in stateless fallback, both from the
26943
+ * same endpoint. Each request gets a fresh server instance from
26944
+ * `createServerInstance()`, so all registered handlers apply to both eras.
26945
+ */
26946
+ getModernEraHandler() {
26947
+ if (!this.modernEraHandler) this.modernEraHandler = createMcpHandler(() => this.createServerInstance(), {
26948
+ legacy: "stateless",
26949
+ onerror: (error) => {
26950
+ this.logger.error("MCP handler error", { error: error.toString() });
26951
+ }
26952
+ });
26953
+ return this.modernEraHandler;
26954
+ }
26955
+ /**
26956
+ * Node `(req, res)` adapter over the dual-era handler's web-standard `fetch`.
26957
+ */
26958
+ getModernEraNodeHandler() {
26959
+ if (!this.modernEraNodeHandler) this.modernEraNodeHandler = toNodeHandler(this.getModernEraHandler(), { onerror: (error) => {
26960
+ this.logger.error("MCP Node handler adapter error", { error: error.toString() });
26961
+ } });
26962
+ return this.modernEraNodeHandler;
26770
26963
  }
26771
26964
  /**
26772
26965
  * Determines whether a log message at the given level should be sent to the
@@ -27003,7 +27196,8 @@ var MCPServer = class extends MCPServerBase {
27003
27196
  }, {
27004
27197
  capabilities,
27005
27198
  ...this.instructions ? { instructions: this.instructions } : {},
27006
- ...this.jsonSchemaValidator ? { jsonSchemaValidator: this.jsonSchemaValidator } : {}
27199
+ ...this.jsonSchemaValidator ? { jsonSchemaValidator: this.jsonSchemaValidator } : {},
27200
+ ...this.servesModernEra() && this.cacheHints ? { cacheHints: this.cacheHints } : {}
27007
27201
  });
27008
27202
  this.registerHandlersOnServer(serverInstance);
27009
27203
  return serverInstance;
@@ -27046,6 +27240,7 @@ var MCPServer = class extends MCPServerBase {
27046
27240
  serverInstance.setRequestHandler("tools/call", async (request, ctx) => {
27047
27241
  const startTime = Date.now();
27048
27242
  const extra = toMCPRequestHandlerExtra(ctx);
27243
+ let replayInterrupt;
27049
27244
  try {
27050
27245
  const tool = this.convertedTools[request.params.name];
27051
27246
  if (!tool) {
@@ -27085,11 +27280,20 @@ var MCPServer = class extends MCPServerBase {
27085
27280
  isError: true
27086
27281
  };
27087
27282
  }
27088
- const sessionElicitation = { sendRequest: async (request, options) => {
27283
+ const sessionElicitation = isModernEraRequest(extra) ? createReplayElicitation(extra, (interrupt) => {
27284
+ replayInterrupt = interrupt;
27285
+ }) : { sendRequest: async (request, options) => {
27089
27286
  return this.handleElicitationRequest(request, serverInstance, options);
27090
27287
  } };
27091
27288
  const proxiedContext = await this.createProxiedRequestContext(extra);
27092
27289
  const sessionLog = async (level, message, data) => {
27290
+ if (this.servesModernEra()) {
27291
+ await extra.mcpReq.log(level, {
27292
+ message,
27293
+ ...data
27294
+ }, this.name);
27295
+ return;
27296
+ }
27093
27297
  if (!this.shouldSendLog(serverInstance, level)) return;
27094
27298
  await extra.sendNotification({
27095
27299
  method: "notifications/message",
@@ -27179,6 +27383,10 @@ var MCPServer = class extends MCPServerBase {
27179
27383
  }];
27180
27384
  return response;
27181
27385
  } catch (error) {
27386
+ if (replayInterrupt !== void 0) {
27387
+ this.logger.debug(`CallTool: Tool '${request.params.name}' requires client input (multi-round-trip).`, { key: replayInterrupt.key });
27388
+ return replayInterruptToInputRequired(replayInterrupt);
27389
+ }
27182
27390
  const duration = Date.now() - startTime;
27183
27391
  if (error instanceof Error && "issues" in error && Array.isArray(error.issues)) {
27184
27392
  const issues = error.issues;
@@ -27594,6 +27802,17 @@ var MCPServer = class extends MCPServerBase {
27594
27802
  * ```
27595
27803
  */
27596
27804
  async startStdio() {
27805
+ if (this.servesModernEra()) {
27806
+ this.stdioHandle = serveStdio(() => {
27807
+ const serverInstance = this.createServerInstance();
27808
+ this.stdioServerInstance = serverInstance;
27809
+ return serverInstance;
27810
+ }, { onerror: (error) => {
27811
+ this.logger.error("MCP stdio handler error", { error: error.toString() });
27812
+ } });
27813
+ this.logger.info("Started MCP Server (stdio, 2026-07-28 dual-era)");
27814
+ return;
27815
+ }
27597
27816
  this.stdioTransport = new StdioServerTransport();
27598
27817
  try {
27599
27818
  await this.server.connect(this.stdioTransport);
@@ -27828,6 +28047,34 @@ var MCPServer = class extends MCPServerBase {
27828
28047
  res.end();
27829
28048
  return;
27830
28049
  }
28050
+ if (this.servesModernEra()) this.assertModernEraHTTPOptions(options);
28051
+ if (!this.validateHTTPRequestHeaders(req, res, options)) return;
28052
+ if (this.servesModernEra()) {
28053
+ try {
28054
+ await this.getModernEraNodeHandler()(req, res);
28055
+ } catch (error) {
28056
+ const mastraError = new MastraError({
28057
+ id: "MCP_SERVER_HTTP_CONNECTION_FAILED",
28058
+ domain: ErrorDomain.MCP,
28059
+ category: ErrorCategory.USER,
28060
+ text: "Failed to handle MCP request on the 2026-07-28 protocol path"
28061
+ }, error);
28062
+ this.logger.trackException(mastraError);
28063
+ this.logger.error("Error handling HTTP request (2026-07-28 path)", { error: mastraError });
28064
+ if (!res.headersSent) {
28065
+ res.writeHead(500, { "Content-Type": "application/json" });
28066
+ res.end(JSON.stringify({
28067
+ jsonrpc: "2.0",
28068
+ error: {
28069
+ code: -32603,
28070
+ message: "Internal server error"
28071
+ },
28072
+ id: null
28073
+ }));
28074
+ }
28075
+ }
28076
+ return;
28077
+ }
27831
28078
  if (options?.serverless || options && "sessionIdGenerator" in options && options.sessionIdGenerator === void 0) {
27832
28079
  this.logger.debug("Running in stateless mode");
27833
28080
  const enableJsonResponse = options?.enableJsonResponse ?? !options?.serverlessStreaming;
@@ -28136,6 +28383,16 @@ var MCPServer = class extends MCPServerBase {
28136
28383
  */
28137
28384
  async close() {
28138
28385
  try {
28386
+ if (this.stdioHandle) {
28387
+ await this.stdioHandle.close();
28388
+ this.stdioHandle = void 0;
28389
+ this.stdioServerInstance = void 0;
28390
+ }
28391
+ if (this.modernEraHandler) {
28392
+ await this.modernEraHandler.close();
28393
+ this.modernEraHandler = void 0;
28394
+ this.modernEraNodeHandler = void 0;
28395
+ }
28139
28396
  if (this.stdioTransport) {
28140
28397
  await this.stdioTransport.close?.();
28141
28398
  this.stdioTransport = void 0;