When Claude gets a list of tools from an MCP server, it doesn't know what those tools do through magic — it reads the schema you defined. The tool schema is the contract between your server and Claude: it describes what the tool does, what parameters it accepts, which ones are required, and what values are valid. Write a good schema and Claude calls your tool correctly. Write a vague one and Claude guesses — and guesses wrong.
What the Tool Schema Is
When a client calls tools/list on your MCP server, your server returns an array of tool definitions. Each definition is a JSON object that Claude reads to understand the tool. This object is the tool schema.
The schema serves two distinct purposes:
- For Claude's reasoning: The
nameanddescriptionfields are used to select the right tool for the task. Claude reads them as natural language. - For parameter construction: The
inputSchemadefines exactly what arguments to pass when calling the tool — types, constraints, defaults, which are required.
Both matter. A perfectly structured schema with a useless description produces bad tool calls just as reliably as a broken schema.
Anatomy of a Tool Definition
A tool definition object has four top-level fields:
name— the tool's identifier, used intools/callrequests. Must be unique within the server. Use snake_case or camelCase consistently.description— a natural language description Claude reads to decide when to use the tool. This is arguably the most important field.inputSchema— a JSON Schema (draft 7) object describing the tool's parameters.annotations— optional metadata about the tool's behavior (read-only, destructive, idempotent). Does not affect execution, only informs clients.
Here is the complete example we'll unpack throughout this article — a search_files tool:
{
"name": "search_files",
"description": "Searches files in a directory for content matching the query. Returns matching file paths and the line numbers where the match appears. Does not modify any files.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The text or regex pattern to search for"
},
"path": {
"type": "string",
"description": "Directory to search in. Defaults to the root allowed directory.",
"default": "/"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return (1-100)",
"minimum": 1,
"maximum": 100,
"default": 20
},
"case_sensitive": {
"type": "boolean",
"description": "Whether the search is case-sensitive",
"default": false
}
},
"required": ["query"]
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true
}
}
The name Field
Tool names must be unique within a server and are used as the identifier in tools/call requests. Keep them concise, descriptive, and in a consistent format. Good names: search_files, create_issue, get_weather. Avoid abbreviations Claude might not understand: srch_f, ci, gw.
If you have multiple similar tools, the name helps Claude distinguish them: read_file vs write_file is immediately clear. file_op_1 vs file_op_2 is not.
The description Field
This is where most developers underinvest. Claude reads your tool description as part of its reasoning about which tool fits the user's request. A weak description produces tool misuse even when the implementation is perfect.
A good tool description does three things:
- States what the tool does. Be specific. "Searches files" is weak. "Searches the content of files in a directory and returns matching file paths and line numbers" is better.
- Notes side effects or lack thereof. If the tool modifies data, say so. If it's read-only, say that too. "Does not modify any files" is a signal Claude uses to prefer this tool in contexts where modification would be inappropriate.
- Guides when NOT to use it. If there's a common misuse scenario, address it. "Use read_file to get the full contents of a single file; use search_files when you don't know which file contains the information."
The inputSchema Field
The inputSchema is a JSON Schema draft 7 object. It must be of "type": "object" at the top level (since MCP tool arguments are always a JSON object). Inside it, you define properties and required.
Properties
Each key in properties is a parameter name. Its value is a JSON Schema that describes that parameter. Key sub-fields for each property:
type—"string","integer","number","boolean","array", or"object"description— what this parameter means and what values are valid. Claude uses this to decide what value to pass.default— the value to use if this parameter is not provided. Document it clearly so Claude knows it can omit the parameter when the default is appropriate.enum— an array of the only valid values. Useful for parameters with a fixed set of options, like"enum": ["asc", "desc"].minimum/maximum— for numeric types, the valid range.pattern— a regex the string value must match.
required
The required array lists the parameter names Claude must always provide. Any parameter not in required is optional. If you omit the required array entirely, Claude treats all parameters as optional — which means required ones get skipped, causing tool call failures.
{
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Absolute path to the file to read. The file must exist."
},
"encoding": {
"type": "string",
"description": "File encoding to use when reading",
"enum": ["utf-8", "utf-16", "ascii"],
"default": "utf-8"
}
},
"required": ["file_path"]
}
Why Parameter Descriptions Matter So Much
Claude doesn't have access to your server's implementation. When constructing a tool call, it only has the parameter name and description to determine what value to pass. Compare these two descriptions for the same parameter:
- Weak:
"description": "The path"— Claude doesn't know if this is relative or absolute, whether the path must exist, or what it refers to. - Strong:
"description": "Absolute path to the target file. Must exist. Example: /home/user/documents/report.pdf"— Claude knows exactly what format to use and what precondition applies.
The stronger description directly reduces wrong-format tool calls and eliminates a class of errors entirely. This is the highest-leverage optimization you can make to a tool definition.
How Claude Sends a Tool Call
After reading the schema via tools/list, when Claude decides to use a tool it sends a tools/call request. The call includes the tool name and an arguments object that must conform to the inputSchema. Using our example:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search_files",
"arguments": {
"query": "TODO",
"path": "/Users/name/projects/myapp/src",
"max_results": 50
}
},
"id": 7
}
Your server receives this over the JSON-RPC transport and executes the tool with the provided arguments. The result is returned as a content array in the response.
Tool Annotations
The annotations field is optional metadata that describes the tool's behavioral characteristics without changing how it works. MCP clients and hosts use annotations to make safety decisions — for example, requiring user confirmation before calling a destructive tool.
The three main annotations are:
readOnlyHint—trueif the tool never modifies any state. Clients may use this to auto-approve read-only tool calls.destructiveHint—trueif the tool could permanently delete or alter data. Clients may require confirmation before calling.idempotentHint—trueif calling the tool multiple times with the same arguments produces the same result. Useful for retry logic.
For more detail on how clients use these, see the MCP tool annotations article. For a broader overview of tools alongside resources and prompts, see MCP primitives compared.
Common Mistakes
Missing the required Array
Without a required array, Claude treats everything as optional. If your tool requires a parameter to function (a query string, a file path, a user ID), put it in required. Without this, Claude will sometimes omit required parameters, your tool will fail, and the error will seem mysterious.
Vague Top-Level Description
Putting a one-sentence vague description like "Manages files" and relying on parameter names to communicate everything is a recipe for misuse. The description is what Claude reads when deciding which tool to pick — make it count.
Abbreviations in Parameter Names
Parameter names like q, p, n, or max_r force Claude to infer meaning from context rather than the name itself. Use full words: query, path, count, max_results. Parameter descriptions compensate somewhat, but clear names are still better.
No Default Values Documented
If a parameter has a default value and you want Claude to be able to omit it when the default is appropriate, declare the default in the schema. Without it, Claude may always include the parameter (wasting tokens) or never include it (missing the non-default case).
Frequently Asked Questions
MCP uses JSON Schema draft 7 for the inputSchema field in tool definitions. This means you can use draft 7 keywords including type, properties, required, enum, minimum, maximum, default, pattern, and allOf/anyOf/oneOf. Some newer JSON Schema features from draft 2019-09 or 2020-12 may not be recognized by all MCP clients, so stick to draft 7 vocabulary for maximum compatibility.
Claude reads the name and description fields of every tool returned by tools/list and uses them as its primary signal for tool selection. It matches the user's intent against tool descriptions using the same language understanding it applies to everything else. This is why descriptions matter so much: if two tools have vague or similar descriptions, Claude may choose the wrong one. Be explicit in your descriptions about what each tool does AND what it doesn't do, and consider adding guidance on when to prefer one tool over another.
Yes. A tool with no parameters should still include an inputSchema field, but with an empty properties object and an empty required array: {"type": "object", "properties": {}}. Omitting inputSchema entirely may cause errors in some MCP clients. Tools with no parameters are common for actions that don't need input — like get_current_time or list_available_databases.
The MCP server receives the tool call arguments and should validate them against the inputSchema before executing. If a parameter is the wrong type, out of range, or missing when required, the server should return an error result rather than proceeding. Claude will see the error and typically try to correct the call. Most MCP SDKs provide schema validation helpers so you don't have to write the validation logic yourself — just define the schema accurately and let the SDK handle rejection.