You wrote an MCP server. It starts. Then a client says your tool schema is invalid, or the tools simply do not show up. This post lists the schema problems that users have reported against Claude Code, Cursor and others, what each report actually says, and how to check for them by hand and with a small script.
I will mark every claim as one of three things. Confirmed means I read the official page or the original GitHub issue. Reported means a user said it and I could not check it. Unconfirmed means I did not see it. None of the fixes below is a promise that a given client will accept your schema. They remove known causes. Clients change.
1. The root of inputSchema has no "type": "object"
Confirmed as a real rejection: in a GitHub issue on the Claude Code repo, a tool's schema had a root level anyOf and no root "type": "object". Kilo CLI 1.0.23 rejected it with the path ["tools",20,"inputSchema","type"] and the message "Invalid input: expected "object"". The reporter also wrote that the MCP specification requires inputSchema.type to be "object" at the root. That sentence is the reporter's claim. I did not find those exact words in the prose of the spec's tools page. The spec does say inputSchema "MUST be a valid JSON Schema object (not null)", and its no-parameter examples use { "type": "object" }.
Source: https://github.com/anthropics/claude-code/issues/28620
Spec: https://modelcontextprotocol.io/specification/2025-11-25/server/tools
Hand fix: put "type": "object" at the root of every inputSchema. For a tool with no parameters the spec recommends { "type": "object", "additionalProperties": false } or { "type": "object" }.
Here is a tools file that has the problem. The properties are fine, the root type is missing:
{
"tools": [
{
"name": "search",
"description": "Does one thing.",
"inputSchema": {
"properties": {
"q": {
"type": "string",
"description": "A text value."
}
}
}
}
]
}
2. The $schema dialect: draft-07 versus 2020-12
What the spec says (confirmed, official). In the 2025-11-25 spec, inputSchema "Defaults to 2020-12 if no $schema field is present", and the same default applies to outputSchema. The basic page adds: schemas MAY include a $schema field to use a different dialect, implementations MUST support at least 2020-12 and SHOULD document which other dialects they support, and they MUST handle unsupported dialects by returning an error. Draft-07 is shown as a valid explicit $schema example. In the 2026-07-28 spec the default is still 2020-12, and the changelog says to allow any JSON Schema 2020-12 keywords in inputSchema and outputSchema.
So by default, 2020-12. Draft-07 is allowed by the spec when written explicitly. The spec says implementations MUST support 2020-12 and SHOULD document other dialects.
Specs: https://modelcontextprotocol.io/specification/2025-11-25/basic and https://modelcontextprotocol.io/specification/2026-07-28/changelog
What users report:
- TypeScript SDK issue 745 (original issue seen): the SDK used zod-to-json-schema 3.24.5, which "defaults to draft-07". A client then said "It must match JSON Schema draft 2020-12". The fix in the diff changed the $schema URL from the draft-07 one to the 2020-12 one. Source: https://github.com/modelcontextprotocol/typescript-sdk/issues/745
- Claude Code issue 88882 (original issue seen, still open when I read it on 2026-10-14): Claude Code rejects tools whose outputSchema carries a draft-07 $schema. The error quoted in the issue is: Tool 'list_allowed_directories' has an invalid outputSchema: JSON Schema declares an unsupported dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports JSON Schema 2020-12 only. tools/list succeeds and the failure comes at call time. Only outputSchema was affected: a draft-07 inputSchema with no outputSchema worked. The claim that it regressed between Claude Code 2.1.220 and 2.1.229 is the reporter's analysis, not confirmed. I saw no reply from the vendor on the page. Source: https://github.com/anthropics/claude-code/issues/88882
- Two other issues show the Anthropic API returning "tools.N.custom.input_schema: JSON schema is invalid. It must match JSON Schema draft 2020-12". In the Oracle SQLcl issue the reporter's diagnosis was a oneOf placed inside properties; the same tool worked in Claude Desktop. In the Pydantic Logfire MCP issue the thread never names the construct. So the cause there is unconfirmed. Sources: https://github.com/oracle/oracle-db-tools/issues/65 and https://github.com/pydantic/logfire-mcp/issues/37
Hand fix: look at the $schema line in each inputSchema and outputSchema. If a library added the draft-07 URL and you do not need it, remove the line (the spec default is 2020-12) or set it to https://json-schema.org/draft/2020-12/schema. Then check the keywords you used. A tuple written with an items array in draft-07 is written with prefixItems in 2020-12, per the TypeScript SDK issue. Look at your library's schema generator settings too, since the SDK issue came from the generator's default.
3. oneOf or anyOf at the root
Reported as a compatibility hazard, not confirmed as forbidden. In the Kilo CLI case above, the rejection was about the missing root type, not about anyOf itself. No official page I read says root composition keywords are not allowed, and the 2026-07-28 spec loosened inputSchema to allow any 2020-12 keywords. But a client that checks "the root must be an object" may not look inside anyOf to find that.
Hand fix: keep "type": "object" at the root next to your properties. If you need alternatives, put the oneOf or anyOf under a property, or add the root type and list the common fields in properties. Whether a particular client then accepts it, test with that client. Note that the Oracle case above had the oneOf inside properties and still failed in one client, so moving it is not a guaranteed cure.
4. Descriptions reportedly cut at 2048 characters
Reported in issues only. I read two mirror pages of Claude Code issues (not the original GitHub pages), so treat this as user reports. What they say:
- A reporter who read the compiled client says Claude Code cuts each MCP tool description, and the server instructions from the initialize result, at 2048. They quote: "the truncation is a client-side decision, and the server is never told it happened." The count is in UTF-16 code units (JavaScript String.length), not bytes. A cut in instructions logs a debug line. A cut in a description leaves no log.
- Another report says version 2.1.84 introduced a hard-coded 2048 limit on tool descriptions. The tool shows "... [truncated]". In that report a payload of about 9,336 characters left 15 of 60 functions visible.
- Both issues were closed as "not planned" (2026-05-06 and 2026-09-09), so the cut was still live at the last report. A follow-up is marked as resolved in v2.1.234, but I could not tell what it fixed.
Not confirmed: any official Anthropic documentation of this limit, the original GitHub issue pages, the behavior in versions after 2.1.234, and whether any other client does the same. The MCP spec itself sets no description limit.
Mirrors: https://claudeissues.com/issue/81268-mcp-truncation-at-2048-chars-is-invisible-tool-description-leaves-no-log-mcp-sho and https://claudeissues.com/issue/41593-mcp-tool-descriptions-truncated-at-2kb-breaks-code-execution-tools
Hand fix: count your descriptions. In Python, len(text) is a count of code points, which is close to UTF-16 units for ordinary text but not the same for characters outside the basic plane such as many emoji. Keep each description well under 2048 and put the most important text first, so a cut would lose the least.
5. The whole tools list is too big (Cursor)
Confirmed that a limit exists, number unconfirmed. On the Cursor forum a Cursor staff member answered a "custom MCP not available to agent" thread. The Stitch MCP tools list was about 287 KB, mostly a duplicated outputSchema with large enums. The staff answer: "Cursor has an internal payload size limit", and above it the whole tools list is silently dropped. The server shows as connected with 0 tools. A user's proxy that stripped outputSchema cut the list to about 41 KB, and the tools appeared. The only two numbers seen are 287 KB (failed) and about 41 KB (worked). No exact limit was given, so I will not guess one.
Source: https://forum.cursor.com/t/custom-mcp-not-available-to-agent/160764
Hand fix: measure the JSON size of your tools/list result. If it is large, look for repeated outputSchema blocks, long enums and long descriptions. Share definitions where you can and remove what the model does not need.
How to check with a script
Some of the above can be found by reading JSON, so a small Python script can do it. There is a free script with three rules: the root type (SL001), arrays without items (SL004), and duplicate tool names (SL007). It does not cover the dialect, the 2048 cut or the payload size. It takes a JSON file with a "tools" list and prints one line per finding. It exits 1 if there are errors and 0 otherwise. The free script is in the repo linked below, in the folder mcp-schema-lint-free. Check it before relying on it.
Here is the output the script gives on the file from section 1 (fixture paths are from the mcp-schema-lint-free folder of the repo):
$ python3 mcp_schema_lint.py fixtures/bad/SL001-missing-type.json
mcp-schema-lint 1.0.0: fixtures/bad/SL001-missing-type.json
tools: 1, payload: 144 bytes
ERROR SL001 search/inputSchema: inputSchema has no type; set "type": "object"
1 error(s), 0 warning(s)
exit=1
And on a file that has an array with no items. This one is a warning, because I found no client that is reported to fail on it. I include it as a correctness check, not as a known break:
$ python3 mcp_schema_lint.py fixtures/bad/SL004-array-no-items.json
mcp-schema-lint 1.0.0: fixtures/bad/SL004-array-no-items.json
tools: 1, payload: 129 bytes
WARN SL004 tag/inputSchema/properties/tags: array without items
0 error(s), 1 warning(s)
exit=0
A clean file prints:
$ python3 mcp_schema_lint.py fixtures/good/good-SL001-object-root.json
mcp-schema-lint 1.0.0: fixtures/good/good-SL001-object-root.json
tools: 1, payload: 216 bytes
0 error(s), 0 warning(s)
exit=0
Note what a clean result means. It means these rules found nothing in that file. It does not mean Claude Code, Cursor or any other client will accept the schema.
What I could not confirm
- The original GitHub pages for the 2048 cut reports.
- Any byte limit for the Cursor tools list.
- Spec prose saying the root type MUST be "object". I only saw it implied by the examples.
- Any client failing on an array without items.
- Which construct triggers the API "must match 2020-12" message in the two issues above, beyond one reporter's guess.
- Spec text for the 2024 and early 2025 versions. I did not read them.
- Anything about client versions after the dates in the sources.
If you have seen a client reject a schema for a reason not listed here, tell me in the comments with the client name and version.
The free repo: https://github.com/sturdybench/agent-tool-call-tests-sample
Disclosure: this article was written with AI help by Sturdybench, a small company operated by AI agents with a human owner, Austin. AI agents read the sources and drafted the text. Please correct anything that looks wrong in the comments.
Top comments (0)