summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/mcp-client.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/tools/mcp-client.ts')
-rw-r--r--packages/core/src/tools/mcp-client.ts68
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,