diff options
Diffstat (limited to 'packages/core/src/tools/mcp-client.ts')
| -rw-r--r-- | packages/core/src/tools/mcp-client.ts | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 26244d9e..9a35b84e 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -417,6 +417,65 @@ export async function connectAndDiscover( } /** + * Recursively validates that a JSON schema and all its nested properties and + * items have a `type` defined. + * + * @param schema The JSON schema to validate. + * @returns `true` if the schema is valid, `false` otherwise. + * + * @visiblefortesting + */ +export function hasValidTypes(schema: unknown): boolean { + if (typeof schema !== 'object' || schema === null) { + // Not a schema object we can validate, or not a schema at all. + // Treat as valid as it has no properties to be invalid. + return true; + } + + const s = schema as Record<string, unknown>; + + if (!s.type) { + // These keywords contain an array of schemas that should be validated. + // + // If no top level type was given, then they must each have a type. + let hasSubSchema = false; + const schemaArrayKeywords = ['anyOf', 'allOf', 'oneOf']; + for (const keyword of schemaArrayKeywords) { + const subSchemas = s[keyword]; + if (Array.isArray(subSchemas)) { + hasSubSchema = true; + for (const subSchema of subSchemas) { + if (!hasValidTypes(subSchema)) { + return false; + } + } + } + } + + // If the node itself is missing a type and had no subschemas, then it isn't valid. + if (!hasSubSchema) return false; + } + + if (s.type === 'object' && s.properties) { + if (typeof s.properties === 'object' && s.properties !== null) { + for (const prop of Object.values(s.properties)) { + if (!hasValidTypes(prop)) { + return false; + } + } + } + } + + if (s.type === 'array' && s.items) { + if (!hasValidTypes(s.items)) { + return false; + } + } + + return true; +} + +/** * Discovers and sanitizes tools from a connected MCP client. * It retrieves function declarations from the client, filters out disabled tools, * generates valid names for them, and wraps them in `DiscoveredMCPTool` instances. @@ -448,6 +507,15 @@ export async function discoverTools( continue; } + if (!hasValidTypes(funcDecl.parametersJsonSchema)) { + console.warn( + `Skipping tool '${funcDecl.name}' from MCP server '${mcpServerName}' ` + + `because it has missing types in its parameter schema. Please file an ` + + `issue with the owner of the MCP server.`, + ); + continue; + } + discoveredTools.push( new DiscoveredMCPTool( mcpCallableTool, |
