@mastra/mcp 2.9.0-alpha.0 → 2.9.0-alpha.1

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
@@ -14411,6 +14411,35 @@ function isReconnectableMCPError(error) {
14411
14411
  const errorMessage = error.message.toLowerCase();
14412
14412
  return errorMessage.includes("not connected") || errorMessage.includes("http 400") || errorMessage.includes("http 401") || errorMessage.includes("http 403") || errorMessage.includes("http 404") || errorMessage.includes("econnrefused") || errorMessage.includes("fetch failed") || errorMessage.includes("connection refused") || errorMessage.includes("connection closed") || errorMessage.includes("typeerror: terminated");
14413
14413
  }
14414
+ const CONNECTION_NOT_ESTABLISHED_CODES = /* @__PURE__ */ new Set([
14415
+ "ECONNREFUSED",
14416
+ "ENOTFOUND",
14417
+ "EAI_AGAIN",
14418
+ "UND_ERR_CONNECT_TIMEOUT"
14419
+ ]);
14420
+ const REJECTED_BEFORE_DISPATCH_STATUSES = /* @__PURE__ */ new Set([
14421
+ 400,
14422
+ 401,
14423
+ 403,
14424
+ 404
14425
+ ]);
14426
+ /**
14427
+ * Whether a reconnectable failure proves the request never reached the server's
14428
+ * handler, so sending it again cannot repeat its effects.
14429
+ *
14430
+ * A reset socket (`fetch failed` with `UND_ERR_SOCKET`), a cut-off response
14431
+ * (`terminated`) or a closed transport (`Connection closed`) can all follow a
14432
+ * request the server already received and ran, so they return false.
14433
+ */
14434
+ function isUnsentRequestError(error) {
14435
+ if (!isReconnectableMCPError(error)) return false;
14436
+ const { httpStatus, code } = getMCPDiscoveryErrorDetails(error);
14437
+ if (typeof code === "number") return false;
14438
+ if (httpStatus !== void 0) return REJECTED_BEFORE_DISPATCH_STATUSES.has(httpStatus);
14439
+ const message = error.message.toLowerCase();
14440
+ if (message.includes("not connected") || message.includes("econnrefused") || message.includes("connection refused") || /\bhttp 40[0134]\b/.test(message)) return true;
14441
+ return typeof code === "string" && CONNECTION_NOT_ESTABLISHED_CODES.has(code);
14442
+ }
14414
14443
  const GATEWAY_UNAVAILABLE_STATUSES = /* @__PURE__ */ new Set([
14415
14444
  502,
14416
14445
  503,
@@ -17228,7 +17257,26 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
17228
17257
  try {
17229
17258
  return await executeToolCall();
17230
17259
  } catch (e) {
17231
- if (!(e instanceof _mastra_core_error.MastraError && (e.id === "MCP_CLIENT_TOOL_EXECUTION_FAILED" || e.id.startsWith("MCP_CLIENT_TOOL_TASK_"))) && isReconnectableMCPError(e)) {
17260
+ const isToolExecutionError = e instanceof _mastra_core_error.MastraError && (e.id === "MCP_CLIENT_TOOL_EXECUTION_FAILED" || e.id.startsWith("MCP_CLIENT_TOOL_TASK_"));
17261
+ if (!isToolExecutionError && isReconnectableMCPError(e) && !isUnsentRequestError(e)) {
17262
+ if (annotations?.readOnlyHint === true || annotations?.idempotentHint === true) {
17263
+ this.log("debug", `Retrying replay-safe tool ${tool.name} after a transport error`, { error: errorKind(e) });
17264
+ if (this.transport !== failedTransport || this.reconnectPromise) await this.reconnectAfterTransportFailure(failedTransport, lifecycleGeneration);
17265
+ return await executeToolCall();
17266
+ }
17267
+ this.log("error", `Lost the connection during tool ${tool.name}; it may have run`, { error: errorKind(e) });
17268
+ throw new _mastra_core_error.MastraError({
17269
+ id: "MCP_CLIENT_TOOL_CALL_OUTCOME_UNKNOWN",
17270
+ domain: _mastra_core_error.ErrorDomain.MCP,
17271
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
17272
+ text: `The connection to MCP server "${this.name}" failed before tool "${tool.name}" returned a result. The tool may have run on the server, so it was not called again.`,
17273
+ details: {
17274
+ toolName: tool.name,
17275
+ serverName: this.name
17276
+ }
17277
+ }, e);
17278
+ }
17279
+ if (!isToolExecutionError && isReconnectableMCPError(e)) {
17232
17280
  this.log("debug", `Transport error detected for tool ${tool.name}, attempting reconnection...`, { error: errorKind(e) });
17233
17281
  try {
17234
17282
  await this.reconnectAfterTransportFailure(failedTransport, lifecycleGeneration);
@@ -17670,12 +17718,12 @@ async function createOAuthCallbackServer(options) {
17670
17718
  },
17671
17719
  close() {
17672
17720
  settle({ error: /* @__PURE__ */ new Error("OAuth callback server closed before receiving an authorization code") });
17673
- server.closeIdleConnections();
17674
17721
  return new Promise((resolve, reject) => {
17675
17722
  server.close((error) => {
17676
17723
  if (error && error.code !== "ERR_SERVER_NOT_RUNNING") reject(error);
17677
17724
  else resolve();
17678
17725
  });
17726
+ server.closeAllConnections();
17679
17727
  });
17680
17728
  }
17681
17729
  };
@@ -22951,6 +22999,136 @@ function stepAt(workflow, path) {
22951
22999
  }
22952
23000
  return step;
22953
23001
  }
23002
+ // src/server/uri-template.ts
23003
+ /**
23004
+ * Whether `uri` is an RFC 6570 expansion of `template`. Decides membership only,
23005
+ * without extracting variables.
23006
+ *
23007
+ * The SDK's `UriTemplate.match` is a backtracking regex that takes minutes on a
23008
+ * few KB of client URI when variables are adjacent (`x://{a}-{b}-{c}-{d}`); this
23009
+ * walks the URI once per template segment instead.
23010
+ *
23011
+ * Plain `{x}` and `{+x}` must be non-empty, since an empty path segment never
23012
+ * names a resource. Outside `{+x}` and `{#x}`, values must be percent-encoded:
23013
+ * an ASCII reserved character other than `,` ends the value. Raw non-ASCII
23014
+ * characters are accepted because clients often send them unencoded.
23015
+ */
23016
+ function matchesUriTemplate(template, uri) {
23017
+ return advance(uri, /* @__PURE__ */ new Set([0]), parse(template)).has(uri.length);
23018
+ }
23019
+ const encoded = (char) => /[\w\-.~,]/.test(char) || char.charCodeAt(0) > 127;
23020
+ const anything = () => true;
23021
+ const literal = (text) => ({
23022
+ kind: "literal",
23023
+ text
23024
+ });
23025
+ const run = (allowed, min = 0) => ({
23026
+ kind: "run",
23027
+ allowed,
23028
+ min
23029
+ });
23030
+ const optional = (...segments) => ({
23031
+ kind: "optional",
23032
+ segments
23033
+ });
23034
+ const repeat = (...segments) => ({
23035
+ kind: "repeat",
23036
+ segments
23037
+ });
23038
+ const choice = (options) => ({
23039
+ kind: "choice",
23040
+ options
23041
+ });
23042
+ function parse(template) {
23043
+ const segments = [];
23044
+ let i = 0;
23045
+ while (i < template.length) {
23046
+ const open = template.indexOf("{", i);
23047
+ const close = open === -1 ? -1 : template.indexOf("}", open);
23048
+ if (close === -1) {
23049
+ segments.push(literal(template.slice(i)));
23050
+ break;
23051
+ }
23052
+ if (open > i) segments.push(literal(template.slice(i, open)));
23053
+ segments.push(...expression(template.slice(open + 1, close)));
23054
+ i = close + 1;
23055
+ }
23056
+ return segments;
23057
+ }
23058
+ function expression(body) {
23059
+ const operator = "+#./;?&".includes(body[0] ?? "") ? body[0] : "";
23060
+ const specs = body.slice(operator.length).split(",").map((spec) => ({
23061
+ name: spec.replace(/[*]|:\d*$/g, "").trim(),
23062
+ exploded: spec.includes("*")
23063
+ })).filter((spec) => spec.name);
23064
+ switch (operator) {
23065
+ case "": return [run(encoded, 1)];
23066
+ case "+": return [run(anything, 1)];
23067
+ case "#": return [optional(literal("#"), run(anything))];
23068
+ case "/": return specs.map(({ exploded }) => exploded ? repeat(literal("/"), run(encoded)) : optional(literal("/"), run(encoded)));
23069
+ case ".": return specs.map(() => optional(literal("."), run(encoded)));
23070
+ case ";": return specs.map(({ name, exploded }) => (exploded ? repeat : optional)(literal(`;${name}`), optional(literal("="), run(encoded))));
23071
+ default: {
23072
+ const pair = ({ name, exploded }, separator) => {
23073
+ const first = [literal(`${separator}${name}=`), run(encoded)];
23074
+ return exploded ? [...first, repeat(literal(`&${name}=`), run(encoded))] : first;
23075
+ };
23076
+ const firstPresent = specs.map((spec, i) => [...pair(spec, operator), ...specs.slice(i + 1).map((later) => optional(...pair(later, "&")))]);
23077
+ return [optional(choice(firstPresent))];
23078
+ }
23079
+ }
23080
+ }
23081
+ /** Every URI offset reachable after matching `segments` from any offset in `from`. */
23082
+ function advance(uri, from, segments) {
23083
+ let positions = from;
23084
+ for (const segment of segments) {
23085
+ if (positions.size === 0) break;
23086
+ positions = step(uri, positions, segment);
23087
+ }
23088
+ return positions;
23089
+ }
23090
+ function step(uri, positions, segment) {
23091
+ const next = /* @__PURE__ */ new Set();
23092
+ switch (segment.kind) {
23093
+ case "literal":
23094
+ for (const p of positions) if (uri.startsWith(segment.text, p)) next.add(p + segment.text.length);
23095
+ return next;
23096
+ case "choice":
23097
+ for (const option of segment.options) for (const p of advance(uri, positions, option)) next.add(p);
23098
+ return next;
23099
+ case "run": {
23100
+ let end = 0;
23101
+ for (const p of [...positions].sort((a, b) => a - b)) {
23102
+ const covered = p < end && next.has(p);
23103
+ if (segment.min === 0) next.add(p);
23104
+ if (covered) continue;
23105
+ end = p;
23106
+ for (let width = charWidth(uri, end, segment.allowed); width > 0; width = charWidth(uri, end, segment.allowed)) next.add(end += width);
23107
+ }
23108
+ return next;
23109
+ }
23110
+ case "optional":
23111
+ for (const p of positions) next.add(p);
23112
+ for (const p of advance(uri, positions, segment.segments)) next.add(p);
23113
+ return next;
23114
+ case "repeat": {
23115
+ for (const p of positions) next.add(p);
23116
+ let frontier = positions;
23117
+ while (frontier.size > 0) {
23118
+ const reached = [...advance(uri, frontier, segment.segments)].filter((p) => !next.has(p));
23119
+ for (const p of reached) next.add(p);
23120
+ frontier = new Set(reached);
23121
+ }
23122
+ return next;
23123
+ }
23124
+ }
23125
+ }
23126
+ /** Characters a run consumes at `i`: a whole `%HH` triplet, one allowed character, or none. */
23127
+ function charWidth(uri, i, allowed) {
23128
+ if (i >= uri.length) return 0;
23129
+ if (uri[i] === "%") return /^%[0-9A-Fa-f]{2}/.test(uri.slice(i, i + 3)) ? 3 : 0;
23130
+ return allowed(uri[i]) ? 1 : 0;
23131
+ }
22954
23132
  // src/server/server.ts
22955
23133
  const EMPTY_OBJECT_SCHEMA = {
22956
23134
  type: "object",
@@ -24323,10 +24501,24 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
24323
24501
  extra: toToolExecutionContext(ctx, this.name).extra,
24324
24502
  requestContext
24325
24503
  }) ?? []];
24504
+ const findResource = async (uri, ctx, requestContext) => {
24505
+ const listed = (await listResources(ctx, requestContext)).find((r) => r.uri === uri);
24506
+ if (listed || !options?.resourceTemplates) return listed;
24507
+ const template = (await options.resourceTemplates({
24508
+ extra: toToolExecutionContext(ctx, this.name).extra,
24509
+ requestContext
24510
+ })).find((t) => matchesUriTemplate(t.uriTemplate, uri));
24511
+ return template && {
24512
+ uri,
24513
+ name: template.name,
24514
+ mimeType: template.mimeType,
24515
+ _meta: template._meta
24516
+ };
24517
+ };
24326
24518
  this.setTracedHandler(server, "resources/list", async (_request, ctx, trace) => ({ resources: await listResources(ctx, trace.requestContext) }));
24327
24519
  this.setTracedHandler(server, "resources/read", async (request, ctx, trace) => {
24328
24520
  const uri = request.params.uri;
24329
- const resource = (await listResources(ctx, trace.requestContext)).find((r) => r.uri === uri);
24521
+ const resource = await findResource(uri, ctx, trace.requestContext);
24330
24522
  if (!resource) throw new _modelcontextprotocol_server.ProtocolError(_modelcontextprotocol_server.ProtocolErrorCode.InvalidParams, `Resource not found: ${uri}`);
24331
24523
  const html = this.appResourceHtml.get(uri);
24332
24524
  if (html !== void 0) return { contents: [this.resourceContents(resource, { text: html })] };