@mastra/mcp 2.4.0 → 2.4.1-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.js CHANGED
@@ -22220,7 +22220,7 @@ require_json_schema_traverse();
22220
22220
  const JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
22221
22221
  const JSON_SCHEMA_2019_09 = /^https?:\/\/json-schema\.org\/draft\/2019-09\/schema#?$/;
22222
22222
  /** Keywords whose value is a subschema (`items` may also be an array of subschemas). */
22223
- const SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
22223
+ const SCHEMA_KEYWORDS$1 = /* @__PURE__ */ new Set([
22224
22224
  "items",
22225
22225
  "additionalItems",
22226
22226
  "contains",
@@ -22234,13 +22234,13 @@ const SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
22234
22234
  "not"
22235
22235
  ]);
22236
22236
  /** Keywords whose value is an array of subschemas. */
22237
- const SCHEMA_ARRAY_KEYWORDS = /* @__PURE__ */ new Set([
22237
+ const SCHEMA_ARRAY_KEYWORDS$1 = /* @__PURE__ */ new Set([
22238
22238
  "allOf",
22239
22239
  "anyOf",
22240
22240
  "oneOf"
22241
22241
  ]);
22242
22242
  /** Keywords whose values map arbitrary names to subschemas. */
22243
- const SCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
22243
+ const SCHEMA_MAP_KEYWORDS$1 = /* @__PURE__ */ new Set([
22244
22244
  "properties",
22245
22245
  "patternProperties",
22246
22246
  "$defs",
@@ -22284,11 +22284,11 @@ function rewrite(schema, depth, budget, isRoot = false) {
22284
22284
  if ("$recursiveRef" in out || "$recursiveAnchor" in out) throw new Unconvertible();
22285
22285
  if ("unevaluatedItems" in out || "prefixItems" in out || "contentSchema" in out) throw new Unconvertible();
22286
22286
  if (typeof out.$ref === "string" && /\/(items\/\d+|additionalItems)(\/|$)/.test(out.$ref)) throw new Unconvertible();
22287
- for (const [key, child] of Object.entries(out)) if (SCHEMA_KEYWORDS.has(key)) out[key] = Array.isArray(child) ? (checkBudget(depth + 1, budget), child.map((item) => rewrite(item, depth + 2, budget))) : rewrite(child, depth + 1, budget);
22288
- else if (SCHEMA_ARRAY_KEYWORDS.has(key) && Array.isArray(child)) {
22287
+ for (const [key, child] of Object.entries(out)) if (SCHEMA_KEYWORDS$1.has(key)) out[key] = Array.isArray(child) ? (checkBudget(depth + 1, budget), child.map((item) => rewrite(item, depth + 2, budget))) : rewrite(child, depth + 1, budget);
22288
+ else if (SCHEMA_ARRAY_KEYWORDS$1.has(key) && Array.isArray(child)) {
22289
22289
  checkBudget(depth + 1, budget);
22290
22290
  out[key] = child.map((item) => rewrite(item, depth + 2, budget));
22291
- } else if (SCHEMA_MAP_KEYWORDS.has(key) && child && typeof child === "object" && !Array.isArray(child)) {
22291
+ } else if (SCHEMA_MAP_KEYWORDS$1.has(key) && child && typeof child === "object" && !Array.isArray(child)) {
22292
22292
  checkBudget(depth + 1, budget);
22293
22293
  out[key] = Object.fromEntries(Object.entries(child).map(([name, sub]) => [name, rewrite(sub, depth + 2, budget)]));
22294
22294
  }
@@ -22902,6 +22902,326 @@ function getMCPDiscoveryErrorDetails(error) {
22902
22902
  };
22903
22903
  }
22904
22904
  //#endregion
22905
+ //#region src/client/input-schema-meta-schema.ts
22906
+ /**
22907
+ * Structural JSON Schema meta-schema, used to check that a server-supplied tool input schema is
22908
+ * shaped like a JSON Schema before it is handed to a model provider.
22909
+ *
22910
+ * Based on https://json-schema.org/draft-07/schema (`$schema`/`$id` dropped so the validator does
22911
+ * not try to resolve them). Draft-07 is used because its meta-schema is a single self-contained
22912
+ * document; the 2020-12 meta-schema is split across vocabulary documents. Changes from upstream:
22913
+ * - `format`, `uniqueItems` and the non-empty `enum` rule are removed, so only wrong value types
22914
+ * and non-schemas in schema positions drop a tool, not regex dialects, non-URI ids or duplicates;
22915
+ * - the block at the end of `properties` adds the 2019-09/2020-12 subschema keywords.
22916
+ * Unknown keywords stay allowed, as in every dialect.
22917
+ */
22918
+ const INPUT_SCHEMA_META_SCHEMA = {
22919
+ title: "Core schema meta-schema",
22920
+ definitions: {
22921
+ schemaArray: {
22922
+ type: "array",
22923
+ minItems: 1,
22924
+ items: { $ref: "#" }
22925
+ },
22926
+ nonNegativeInteger: {
22927
+ type: "integer",
22928
+ minimum: 0
22929
+ },
22930
+ nonNegativeIntegerDefault0: { allOf: [{ $ref: "#/definitions/nonNegativeInteger" }, { default: 0 }] },
22931
+ simpleTypes: { enum: [
22932
+ "array",
22933
+ "boolean",
22934
+ "integer",
22935
+ "null",
22936
+ "number",
22937
+ "object",
22938
+ "string"
22939
+ ] },
22940
+ stringArray: {
22941
+ type: "array",
22942
+ items: { type: "string" },
22943
+ default: []
22944
+ }
22945
+ },
22946
+ type: ["object", "boolean"],
22947
+ properties: {
22948
+ $id: { type: "string" },
22949
+ $schema: { type: "string" },
22950
+ $ref: { type: "string" },
22951
+ $comment: { type: "string" },
22952
+ title: { type: "string" },
22953
+ description: { type: "string" },
22954
+ default: true,
22955
+ readOnly: {
22956
+ type: "boolean",
22957
+ default: false
22958
+ },
22959
+ writeOnly: {
22960
+ type: "boolean",
22961
+ default: false
22962
+ },
22963
+ examples: {
22964
+ type: "array",
22965
+ items: true
22966
+ },
22967
+ multipleOf: {
22968
+ type: "number",
22969
+ exclusiveMinimum: 0
22970
+ },
22971
+ maximum: { type: "number" },
22972
+ exclusiveMaximum: { type: "number" },
22973
+ minimum: { type: "number" },
22974
+ exclusiveMinimum: { type: "number" },
22975
+ maxLength: { $ref: "#/definitions/nonNegativeInteger" },
22976
+ minLength: { $ref: "#/definitions/nonNegativeIntegerDefault0" },
22977
+ pattern: { type: "string" },
22978
+ additionalItems: { $ref: "#" },
22979
+ items: {
22980
+ anyOf: [{ $ref: "#" }, { $ref: "#/definitions/schemaArray" }],
22981
+ default: true
22982
+ },
22983
+ maxItems: { $ref: "#/definitions/nonNegativeInteger" },
22984
+ minItems: { $ref: "#/definitions/nonNegativeIntegerDefault0" },
22985
+ uniqueItems: {
22986
+ type: "boolean",
22987
+ default: false
22988
+ },
22989
+ contains: { $ref: "#" },
22990
+ maxProperties: { $ref: "#/definitions/nonNegativeInteger" },
22991
+ minProperties: { $ref: "#/definitions/nonNegativeIntegerDefault0" },
22992
+ required: { $ref: "#/definitions/stringArray" },
22993
+ additionalProperties: { $ref: "#" },
22994
+ definitions: {
22995
+ type: "object",
22996
+ additionalProperties: { $ref: "#" },
22997
+ default: {}
22998
+ },
22999
+ properties: {
23000
+ type: "object",
23001
+ additionalProperties: { $ref: "#" },
23002
+ default: {}
23003
+ },
23004
+ patternProperties: {
23005
+ type: "object",
23006
+ additionalProperties: { $ref: "#" },
23007
+ default: {}
23008
+ },
23009
+ dependencies: {
23010
+ type: "object",
23011
+ additionalProperties: { anyOf: [{ $ref: "#" }, { $ref: "#/definitions/stringArray" }] }
23012
+ },
23013
+ propertyNames: { $ref: "#" },
23014
+ const: true,
23015
+ enum: {
23016
+ type: "array",
23017
+ items: true
23018
+ },
23019
+ type: { anyOf: [{ $ref: "#/definitions/simpleTypes" }, {
23020
+ type: "array",
23021
+ items: { $ref: "#/definitions/simpleTypes" },
23022
+ minItems: 1
23023
+ }] },
23024
+ format: { type: "string" },
23025
+ contentMediaType: { type: "string" },
23026
+ contentEncoding: { type: "string" },
23027
+ if: { $ref: "#" },
23028
+ then: { $ref: "#" },
23029
+ else: { $ref: "#" },
23030
+ allOf: { $ref: "#/definitions/schemaArray" },
23031
+ anyOf: { $ref: "#/definitions/schemaArray" },
23032
+ oneOf: { $ref: "#/definitions/schemaArray" },
23033
+ not: { $ref: "#" },
23034
+ $defs: {
23035
+ type: "object",
23036
+ additionalProperties: { $ref: "#" },
23037
+ default: {}
23038
+ },
23039
+ prefixItems: { $ref: "#/definitions/schemaArray" },
23040
+ dependentSchemas: {
23041
+ type: "object",
23042
+ additionalProperties: { $ref: "#" }
23043
+ },
23044
+ dependentRequired: {
23045
+ type: "object",
23046
+ additionalProperties: { $ref: "#/definitions/stringArray" }
23047
+ },
23048
+ unevaluatedItems: { $ref: "#" },
23049
+ unevaluatedProperties: { $ref: "#" },
23050
+ contentSchema: { $ref: "#" }
23051
+ },
23052
+ default: true
23053
+ };
23054
+ let inputSchemaMetaValidator;
23055
+ let inputSchemaMetaValidatorLoad;
23056
+ const SCHEMA_TYPES = /* @__PURE__ */ new Set([
23057
+ "array",
23058
+ "boolean",
23059
+ "integer",
23060
+ "null",
23061
+ "number",
23062
+ "object",
23063
+ "string"
23064
+ ]);
23065
+ const SCHEMA_MAP_KEYWORDS = [
23066
+ "$defs",
23067
+ "definitions",
23068
+ "properties",
23069
+ "patternProperties",
23070
+ "dependentSchemas",
23071
+ "dependencies"
23072
+ ];
23073
+ const SCHEMA_ARRAY_KEYWORDS = [
23074
+ "prefixItems",
23075
+ "allOf",
23076
+ "anyOf",
23077
+ "oneOf"
23078
+ ];
23079
+ const SCHEMA_KEYWORDS = [
23080
+ "additionalProperties",
23081
+ "unevaluatedProperties",
23082
+ "additionalItems",
23083
+ "unevaluatedItems",
23084
+ "contains",
23085
+ "propertyNames",
23086
+ "not",
23087
+ "if",
23088
+ "then",
23089
+ "else",
23090
+ "contentSchema"
23091
+ ];
23092
+ const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
23093
+ const isNumber = (value) => typeof value === "number";
23094
+ const isCount = (value) => Number.isInteger(value) && value >= 0;
23095
+ const isString = (value) => typeof value === "string";
23096
+ const isBoolean = (value) => typeof value === "boolean";
23097
+ /** Value types the meta-schema requires for the keywords that do not hold subschemas. */
23098
+ const KEYWORD_VALUE_CHECKS = {
23099
+ multipleOf: [(value) => isNumber(value) && value > 0, "a positive number"],
23100
+ ...Object.fromEntries([
23101
+ "maximum",
23102
+ "exclusiveMaximum",
23103
+ "minimum",
23104
+ "exclusiveMinimum"
23105
+ ].map((k) => [k, [isNumber, "a number"]])),
23106
+ ...Object.fromEntries([
23107
+ "maxLength",
23108
+ "minLength",
23109
+ "maxItems",
23110
+ "minItems",
23111
+ "maxProperties",
23112
+ "minProperties"
23113
+ ].map((k) => [k, [isCount, "a non-negative integer"]])),
23114
+ ...Object.fromEntries([
23115
+ "$id",
23116
+ "$schema",
23117
+ "$ref",
23118
+ "$comment",
23119
+ "title",
23120
+ "description",
23121
+ "pattern",
23122
+ "format",
23123
+ "contentMediaType",
23124
+ "contentEncoding"
23125
+ ].map((k) => [k, [isString, "a string"]])),
23126
+ ...Object.fromEntries([
23127
+ "readOnly",
23128
+ "writeOnly",
23129
+ "uniqueItems"
23130
+ ].map((k) => [k, [isBoolean, "a boolean"]])),
23131
+ examples: [Array.isArray, "an array"],
23132
+ enum: [Array.isArray, "an array"],
23133
+ required: [isStringArray, "an array of strings"]
23134
+ };
23135
+ /**
23136
+ * Fallback for runtimes where the cf-worker validator cannot be imported. Applies the same rules as
23137
+ * {@link INPUT_SCHEMA_META_SCHEMA}: every subschema is an object or boolean, and every known
23138
+ * keyword has a value of the right type.
23139
+ */
23140
+ function getStructuralInputSchemaError(schema) {
23141
+ const seen = /* @__PURE__ */ new Set();
23142
+ const stack = [{
23143
+ value: schema,
23144
+ path: "#"
23145
+ }];
23146
+ while (stack.length > 0) {
23147
+ const { value, path } = stack.pop();
23148
+ if (typeof value === "boolean") continue;
23149
+ if (!value || typeof value !== "object" || Array.isArray(value)) return `${path}: must be a JSON Schema object or boolean`;
23150
+ if (seen.has(value)) continue;
23151
+ seen.add(value);
23152
+ const record = value;
23153
+ const { type, dependentRequired } = record;
23154
+ const types = Array.isArray(type) ? type : [type];
23155
+ if (type !== void 0 && (types.length === 0 || !types.every((t) => SCHEMA_TYPES.has(t)))) return `${path}/type: must be a JSON Schema type name or a non-empty array of them`;
23156
+ for (const [keyword, [isValid, expected]] of Object.entries(KEYWORD_VALUE_CHECKS)) if (record[keyword] !== void 0 && !isValid(record[keyword])) return `${path}/${keyword}: must be ${expected}`;
23157
+ if (dependentRequired !== void 0 && (!dependentRequired || typeof dependentRequired !== "object" || Array.isArray(dependentRequired) || !Object.values(dependentRequired).every(isStringArray))) return `${path}/dependentRequired: must be an object of string arrays`;
23158
+ for (const keyword of SCHEMA_MAP_KEYWORDS) {
23159
+ const schemas = record[keyword];
23160
+ if (schemas === void 0) continue;
23161
+ if (!schemas || typeof schemas !== "object" || Array.isArray(schemas)) return `${path}/${keyword}: must be an object`;
23162
+ for (const [key, child] of Object.entries(schemas)) {
23163
+ if (keyword === "dependencies" && isStringArray(child)) continue;
23164
+ stack.push({
23165
+ value: child,
23166
+ path: `${path}/${keyword}/${key}`
23167
+ });
23168
+ }
23169
+ }
23170
+ for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
23171
+ const schemas = record[keyword];
23172
+ if (schemas === void 0) continue;
23173
+ if (!Array.isArray(schemas) || schemas.length === 0) return `${path}/${keyword}: must be a non-empty array`;
23174
+ schemas.forEach((child, index) => stack.push({
23175
+ value: child,
23176
+ path: `${path}/${keyword}/${index}`
23177
+ }));
23178
+ }
23179
+ if (Array.isArray(record.items)) {
23180
+ if (record.items.length === 0) return `${path}/items: must be a schema or a non-empty array`;
23181
+ record.items.forEach((child, index) => stack.push({
23182
+ value: child,
23183
+ path: `${path}/items/${index}`
23184
+ }));
23185
+ } else if (record.items !== void 0) stack.push({
23186
+ value: record.items,
23187
+ path: `${path}/items`
23188
+ });
23189
+ for (const keyword of SCHEMA_KEYWORDS) if (record[keyword] !== void 0) stack.push({
23190
+ value: record[keyword],
23191
+ path: `${path}/${keyword}`
23192
+ });
23193
+ }
23194
+ }
23195
+ /**
23196
+ * Loads the SDK's cf-worker validator, which interprets schemas instead of compiling them with
23197
+ * `new Function`, so it also runs on Cloudflare Workers. It is imported lazily because
23198
+ * `@cfworker/json-schema` builds a URL from `self.location` at import time, which throws in some
23199
+ * browser worker locations; importing `@mastra/mcp` must not fail there. Until it has loaded, or
23200
+ * if it cannot load, the structural fallback check is used instead.
23201
+ */
23202
+ function loadInputSchemaMetaValidator() {
23203
+ inputSchemaMetaValidatorLoad ??= import("@modelcontextprotocol/client/validators/cf-worker").then(({ CfWorkerJsonSchemaValidator }) => {
23204
+ inputSchemaMetaValidator = new CfWorkerJsonSchemaValidator({ draft: "7" }).getValidator(INPUT_SCHEMA_META_SCHEMA);
23205
+ }, () => {});
23206
+ return inputSchemaMetaValidatorLoad;
23207
+ }
23208
+ /**
23209
+ * Checks a server-supplied input schema against {@link INPUT_SCHEMA_META_SCHEMA}, falling back to
23210
+ * a structural check while the validator is not loaded. Callers must rule out oversized schemas
23211
+ * with getJsonSchemaComplexityError first.
23212
+ */
23213
+ function getInputSchemaError(schema) {
23214
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return "#: input schema must be a JSON object";
23215
+ if (!inputSchemaMetaValidator) return getStructuralInputSchemaError(schema);
23216
+ let result;
23217
+ try {
23218
+ result = inputSchemaMetaValidator(schema);
23219
+ } catch {
23220
+ return;
23221
+ }
23222
+ return result.valid ? void 0 : result.errorMessage.split("; ").at(-1);
23223
+ }
23224
+ //#endregion
22905
23225
  //#region src/client/url-policy.ts
22906
23226
  /**
22907
23227
  * Redirect status codes the manual redirect loop follows. Other 3xx statuses
@@ -23359,6 +23679,7 @@ var InternalMastraMCPClient = class extends MastraBase {
23359
23679
  */
23360
23680
  constructor({ name, version = "1.0.0", clientInfo, server, timeout = DEFAULT_REQUEST_TIMEOUT_MSEC }) {
23361
23681
  super({ name: "MastraMCPClient" });
23682
+ loadInputSchemaMetaValidator();
23362
23683
  this.name = name;
23363
23684
  this.timeout = timeout;
23364
23685
  this.logHandler = server.logger;
@@ -23991,14 +24312,7 @@ var InternalMastraMCPClient = class extends MastraBase {
23991
24312
  return this.jsonSchemaValidator;
23992
24313
  }
23993
24314
  convertInputSchema(inputSchema) {
23994
- const schema = withDefaultDialect("jsonSchema" in inputSchema ? inputSchema.jsonSchema : inputSchema);
23995
- const standardSchema = toStandardSchema$2(schema);
23996
- const complexityError = getJsonSchemaComplexityError(schema);
23997
- if (!complexityError) return standardSchema;
23998
- return { "~standard": {
23999
- ...standardSchema["~standard"],
24000
- validate: () => ({ issues: [{ message: complexityError }] })
24001
- } };
24315
+ return toStandardSchema$2(withDefaultDialect("jsonSchema" in inputSchema ? inputSchema.jsonSchema : inputSchema));
24002
24316
  }
24003
24317
  /**
24004
24318
  * Wraps the output schema with a validator that always succeeds. The tool's execute wrapper
@@ -24080,6 +24394,7 @@ var InternalMastraMCPClient = class extends MastraBase {
24080
24394
  async tools() {
24081
24395
  this.log("debug", `Requesting tools from MCP server`);
24082
24396
  const { tools } = await this.client.listTools({ _meta: this.requestMeta() }, { timeout: this.timeout });
24397
+ await loadInputSchemaMetaValidator();
24083
24398
  const toolsRes = {};
24084
24399
  for (const tool of tools) {
24085
24400
  const mastraTool = this.buildToolFromListEntry(tool, {
@@ -24095,6 +24410,25 @@ var InternalMastraMCPClient = class extends MastraBase {
24095
24410
  * tools behave identically to discovered ones.
24096
24411
  */
24097
24412
  buildToolFromListEntry(tool, serverMeta) {
24413
+ const rawInputSchema = tool.inputSchema;
24414
+ const inputSchema = rawInputSchema && typeof rawInputSchema === "object" && "jsonSchema" in rawInputSchema ? rawInputSchema.jsonSchema : rawInputSchema;
24415
+ const inputSchemaError = getJsonSchemaComplexityError(inputSchema) ?? getInputSchemaError(inputSchema);
24416
+ if (inputSchemaError) {
24417
+ const details = {
24418
+ serverName: this.name,
24419
+ toolName: tool.name,
24420
+ reason: inputSchemaError
24421
+ };
24422
+ this.log("warning", `Skipping MCP tool "${tool.name}" with an invalid input schema: ${inputSchemaError}`, details);
24423
+ if (serverMeta.connectFirst) throw new MastraError({
24424
+ id: "MCP_CLIENT_INVALID_TOOL_INPUT_SCHEMA",
24425
+ domain: ErrorDomain.MCP,
24426
+ category: ErrorCategory.THIRD_PARTY,
24427
+ text: `Invalid input schema for MCP tool "${tool.name}": ${inputSchemaError}`,
24428
+ details
24429
+ });
24430
+ return;
24431
+ }
24098
24432
  try {
24099
24433
  let requireApproval;
24100
24434
  let needsApprovalFn;
@@ -25908,7 +26242,9 @@ onListChanged: async (serverName, handler) => {
25908
26242
  */
25909
26243
  async toolFromDefinition({ serverName, definition }) {
25910
26244
  this.addToInstanceCache();
25911
- return (await this.getOrCreateClient(serverName, this.getServerConfig(serverName))).toolFromDefinition({ definition });
26245
+ const client = await this.getOrCreateClient(serverName, this.getServerConfig(serverName));
26246
+ await loadInputSchemaMetaValidator();
26247
+ return client.toolFromDefinition({ definition });
25912
26248
  }
25913
26249
  /**
25914
26250
  * Rebuilds an entire cached catalog into a namespaced tool map, without connecting.
@@ -25934,10 +26270,14 @@ onListChanged: async (serverName, handler) => {
25934
26270
  this.logger.warn("Skipping cached MCP tool definitions for a server that is no longer configured", { serverName });
25935
26271
  continue;
25936
26272
  }
25937
- for (const [toolName, definition] of Object.entries(definitions)) tools[`${serverName}_${toolName}`] = await this.toolFromDefinition({
25938
- serverName,
25939
- definition
25940
- });
26273
+ for (const [toolName, definition] of Object.entries(definitions)) try {
26274
+ tools[`${serverName}_${toolName}`] = await this.toolFromDefinition({
26275
+ serverName,
26276
+ definition
26277
+ });
26278
+ } catch (error) {
26279
+ if (!(error instanceof MastraError && error.id === "MCP_CLIENT_INVALID_TOOL_INPUT_SCHEMA")) throw error;
26280
+ }
25941
26281
  }
25942
26282
  return tools;
25943
26283
  }