{"record":{"id":"bb81f45a26fbafc5","repo":"shareAI-lab/learn-claude-code","slug":"invalid-input-schema-for-origin","errorCode":null,"errorMessage":"Invalid input schema for {origin}","messagePattern":"Invalid input schema for (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s14_mcp_plugin/code.py","lineNumber":340,"sourceCode":"    }\n\n    for server_name, server in mcp_clients.items():\n        safe_server = normalize_mcp_name(server_name)\n        for tool_def in server.tools:\n            raw_name = tool_def[\"name\"]\n            safe_tool = normalize_mcp_name(raw_name)\n            prefixed = f\"mcp__{safe_server}__{safe_tool}\"\n            if len(prefixed) > 64:\n                raise ValueError(f\"MCP tool name is longer than 64 characters: {prefixed}\")\n            origin = f\"MCP tool {server_name!r}/{raw_name!r}\"\n            if prefixed in origins:\n                raise ValueError(\n                    \"MCP tool name collision after normalization: \"\n                    f\"{prefixed!r} maps both {origins[prefixed]} and {origin}\"\n                )\n            schema = tool_def.get(\"inputSchema\", {})\n            if not isinstance(schema, dict) or schema.get(\"type\", \"object\") != \"object\":\n                raise ValueError(f\"Invalid input schema for {origin}\")\n            origins[prefixed] = origin\n            tools.append({\n                \"name\": prefixed,\n                \"description\": tool_def.get(\"description\", \"\"),\n                \"input_schema\": schema,\n            })\n            handlers[prefixed] = (\n                lambda *, client=server, tool=raw_name, **kwargs:\n                client.call_tool(tool, kwargs)\n            )\n            policies[prefixed] = MCP_HOST_POLICY.get(\n                (server_name, raw_name), \"confirm\"\n            )\n\n    mcp_tool_policies = policies\n    return tools, handlers\n\n","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s14_mcp_plugin/code.py#L322-L358","documentation":"Raised while registering tools from an MCP server: each tool's advertised inputSchema must be a JSON object schema (a dict whose 'type' is 'object', explicitly or by default). The harness enforces this because it forwards the schema verbatim to the model as the tool's input contract, and a non-object or non-dict schema would produce an invalid tool definition. The offending server/tool is named in {origin} as 'MCP tool <server>/<rawname>'.","triggerScenarios":"Calling the MCP plugin's registration loop (build of mcp__<server>__<tool> handlers) where a tool_def's 'inputSchema' key is missing, is a list/string/None, or has \"type\": \"array\" / \"string\" / non-'object'. Typical of hand-rolled MCP servers or test doubles that omit inputSchema or emit JSON-Schema draft-style arrays.","commonSituations":"A custom in-process MCP server registered with tool dicts lacking 'inputSchema'; a server copied from docs that uses 'parameters' instead of 'inputSchema'; a proxy that stringifies the schema; upstream server version change renaming the field.","solutions":["Fix the offending MCP server so each tool definition includes inputSchema as a dict with \"type\": \"object\" (or omit 'type' so the default 'object' applies).","If you cannot change the server, wrap its tool_defs and inject {\"type\": \"object\"} as a default inputSchema before registration.","Inspect the server's tools/list response (e.g. via MCPClient.call_tool or a raw client) to identify exactly which tool advertises the bad schema named in {origin}."],"exampleFix":"// before\ntool_defs = [{\"name\": \"echo\", \"description\": \"echo text\"}]\nserver.register(tool_defs, handlers)  // raises: Invalid input schema for MCP tool ...\n\n// after\ntool_defs = [{\n    \"name\": \"echo\",\n    \"description\": \"echo text\",\n    \"inputSchema\": {\"type\": \"object\", \"properties\": {\"text\": {\"type\": \"string\"}}},\n}]\nserver.register(tool_defs, handlers)","handlingStrategy":"validation","validationCode":"def valid_tool_defs(tool_defs):\n    for t in tool_defs:\n        s = t.get(\"inputSchema\")\n        if not isinstance(s, dict) or s.get(\"type\", \"object\") != \"object\":\n            return False\n    return True\n\nif not valid_tool_defs(tool_defs):\n    tool_defs = [{**t, \"inputSchema\": t.get(\"inputSchema\") if isinstance(t.get(\"inputSchema\"), dict) and t[\"inputSchema\"].get(\"type\", \"object\") == \"object\" else {\"type\": \"object\"}} for t in tool_defs]","typeGuard":"def is_object_schema(schema) -> bool:\n    return isinstance(schema, dict) and schema.get(\"type\", \"object\") == \"object\"","tryCatchPattern":"try:\n    register_mcp_tools(server, tool_defs)\nexcept ValueError as e:\n    if \"Invalid input schema\" in str(e):\n        log.warning(\"skipping %s: bad inputSchema\", e)\n    else:\n        raise","preventionTips":["Always emit inputSchema: {\"type\": \"object\", ...} from custom MCP servers.","Validate server tool lists with a small schema check before registration in tests.","Prefer forwarding real tools/list payloads rather than hand-writing tool dicts."],"tags":["mcp","schema","validation","tool-registration"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}