The Model Context Protocol (MCP), open-sourced by Anthropic and rapidly adopted by tools like Cursor, Claude Desktop, and DevFlow agents, provides a standardized JSON-RPC protocol for connecting Large Language Models to local tools, databases, and context servers.
When LLMs interact with MCP servers, tool execution depends entirely on JSON Schema contracts defined in tools/list. If an MCP server provides ambiguous parameter schemas, unresolvable $defs references, missing required properties, or unconstrained string formats, LLMs frequently hallucinate invalid arguments, trigger runtime validation errors, or fail to call tools entirely.
This guide details how MCP tool schemas work, the top schema validation errors, and how to write rock-solid tool definitions for reliable LLM execution.
1. Anatomy of an MCP Tool Definition
Under the MCP specification, every tool exposed by an MCP server must return a name, description, and inputSchema:
{
"name": "query_database",
"description": "Execute a parameterized SQL read-only query against the application analytics warehouse.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The raw SELECT SQL statement to execute. Must not include mutations (INSERT, UPDATE, DELETE)."
},
"limit": {
"type": "integer",
"description": "Maximum number of rows to return.",
"default": 50,
"minimum": 1,
"maximum": 500
},
"format": {
"type": "string",
"enum": ["json", "csv"],
"description": "Desired tabular output serialization format."
}
},
"required": ["query"],
"additionalProperties": false
}
}
2. Top 5 MCP Schema Validation Pitfalls & Fixes
1. Missing type: "object" at the Schema Root
- The Bug: MCP clients and LLMs expect
inputSchemato be an object representing named function arguments. Providing a primitive type (e.g.{"type": "string"}) or an array schema at the root triggers MCP client validation failures. - The Fix: Always specify
"type": "object"and define parameters inside the"properties"dictionary.
2. Omitted required Fields & Undefined Arguments
- The Bug: If a parameter is strictly required by your server implementation but omitted from the
"required": [...]array, the LLM may treat it as optional and call the tool without it, causingTypeError: Cannot read properties of undefinedon the server. - The Fix: Explicitly list every mandatory parameter in the
"required"array at the appropriate schema level.
{
"type": "object",
"properties": {
"organizationId": { "type": "string" },
"filter": { "type": "string" }
},
"required": ["organizationId"]
}
3. Vague Descriptions Leading to Tool Hallucination
- The Bug: LLMs select and parameterize tools based on semantic reasoning over the
descriptionfields. A schema with"description": "Fetch data"gives the model zero guidance on expected ID formats, units, or ISO timestamps. - The Fix: Provide explicit format guidance, examples, and constraints in descriptions:
"dateFrom": {
"type": "string",
"format": "date-time",
"description": "Start timestamp in ISO-8601 UTC format (e.g. '2026-09-04T00:00:00Z')."
}
4. Unconstrained String Fields Instead of enum
- The Bug: When a tool parameter expects one of a finite set of values (e.g.
"status": "active" | "archived"), defining it as an unrestricted{"type": "string"}invites the model to guess variations like"ACTIVE","enabled", or"live". - The Fix: Restrict finite parameters using strict
enumarrays:
"status": {
"type": "string",
"enum": ["active", "archived", "pending"],
"description": "Filter items by status."
}
5. Dangling $ref and Unresolvable $defs
- The Bug: Reusable schema components defined with
$ref: "#/$defs/User"fail if the$defsobject is not co-located inside theinputSchemaor if the MCP client does not support external schema resolution. - The Fix: Keep MCP tool
inputSchemaself-contained and inline, or ensure$defsare defined within the rootinputSchemaobject.
3. Implementing Type-Safe MCP Tools with TypeScript & Zod
If you build MCP servers using the official @modelcontextprotocol/sdk, leverage zod-to-json-schema to generate strict, compliant input schemas automatically:
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const SearchParamsSchema = z.object({
query: z.string().min(1).describe('Search keyword or natural language query'),
maxResults: z.number().int().min(1).max(20).default(5).describe('Max results to return'),
category: z.enum(['docs', 'issues', 'pull-requests']).optional().describe('Filter target entity category'),
});
const server = new Server(
{ name: 'developer-docs-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
// Register tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'search_documentation',
description: 'Search internal engineering documentation and API reference specs.',
inputSchema: zodToJsonSchema(SearchParamsSchema, { target: 'jsonSchema7' }),
},
],
};
});
Frequently Asked Questions
What JSON Schema draft version does MCP use?
The MCP specification uses JSON Schema Draft 7 / Draft 2020-12 dialect conventions compatible with standard LLM function-calling APIs (Anthropic Tool Use, OpenAI Structured Outputs).
How do I validate MCP server schemas in my browser?
Use DevFlow's MCP Schema Validator to test and lint tool schemas, detect schema drift, verify argument validation rules, and simulate LLM tool calls.