fix(mcp): sync required array with properties in tool schemas (#754)

* fix(mcp): sync required array with properties in tool schemas

MCP servers can emit schemas where the required array contains keys
not present in properties. This causes API 400 errors:
"Extra required key 'X' supplied."

- Add sanitizeSchemaRequired() to filter required arrays
- Apply it to MCP tool inputJSONSchema before sending to API
- Also fix filterSwarmFieldsFromSchema to update required after
  removing properties

Fixes #525

* test: add MCP schema required sanitization test
This commit is contained in:
Kagura
2026-04-19 06:44:25 +08:00
committed by GitHub
parent 3d1979ff06
commit 002a8f1f6d
2 changed files with 75 additions and 1 deletions

View File

@@ -111,11 +111,60 @@ function filterSwarmFieldsFromSchema(
delete filteredProps[field]
}
filtered.properties = filteredProps
// Keep `required` in sync after removing properties
if (Array.isArray(filtered.required)) {
filtered.required = filtered.required.filter(
(key: string) => key in filteredProps,
)
}
}
return filtered
}
/**
* Ensure `required` only lists keys present in `properties`.
* MCP servers may emit schemas where these are out of sync, causing
* API 400 errors ("Extra required key supplied").
* Recurses into nested object schemas.
*/
function sanitizeSchemaRequired(
schema: Anthropic.Tool.InputSchema,
): Anthropic.Tool.InputSchema {
if (!schema || typeof schema !== 'object') {
return schema
}
const result = { ...schema }
const props = result.properties as Record<string, unknown> | undefined
if (props && Array.isArray(result.required)) {
result.required = result.required.filter(
(key: string) => key in props,
)
}
// Recurse into nested object properties
if (props) {
const sanitizedProps = { ...props }
for (const [key, value] of Object.entries(sanitizedProps)) {
if (
value &&
typeof value === 'object' &&
(value as Record<string, unknown>).type === 'object'
) {
sanitizedProps[key] = sanitizeSchemaRequired(
value as Anthropic.Tool.InputSchema,
)
}
}
result.properties = sanitizedProps
}
return result
}
export async function toolToAPISchema(
tool: Tool,
options: {
@@ -156,7 +205,7 @@ export async function toolToAPISchema(
// Use tool's JSON schema directly if provided, otherwise convert Zod schema
let input_schema = (
'inputJSONSchema' in tool && tool.inputJSONSchema
? tool.inputJSONSchema
? sanitizeSchemaRequired(tool.inputJSONSchema as Anthropic.Tool.InputSchema)
: zodToJsonSchema(tool.inputSchema)
) as Anthropic.Tool.InputSchema