The foundation of any MCP Tool is its schema. When a client connects to your server, it requests a list of available tools. Your server responds with a definition that describes the function signature in a format the Large Language Model (LLM) can parse and understand. This definition dictates how the model structures its requests. If the schema is loose or poorly defined, the model will struggle to execute the tool correctly or may attempt to invoke it with malformed arguments.The Anatomy of a Tool DefinitionThe MCP specification mandates that every tool definition must contain three primary components: a unique name, a functional description, and an input schema. These components allow the host application to present the tool to the model as an available function.The name field serves as the identifier for the tool. It acts similarly to a function name in Python or TypeScript. Best practices suggest using snake_case or camelCase strings that clearly categorize the action, such as database_query or file_write. This name is what the model will reference in its decision output when it chooses to call the tool.The description field is significantly different from standard code comments. While comments are for developers, this description is part of the prompt context sent to the LLM. It serves as the instruction manual for the AI. A high-quality description explains not only what the tool does but also under what circumstances it should be used. For example, instead of simply stating "Gets weather," a better description would be "Retrieves current weather conditions for a specific city. Use this when the user asks about temperature, rain, or forecast."The inputSchema defines the parameters the tool accepts. MCP relies on the JSON Schema standard for this purpose. This allows you to define strict types for every argument, ensuring the server receives data it can process without immediate errors.digraph ToolStructure { rankdir=TB; node [shape=box, style=filled, fontname="Arial", fontsize=12, margin=0.2]; Tool [label="Tool Definition", fillcolor="#a5d8ff", color="#1c7ed6"]; Name [label="Name\n(Identifier)", fillcolor="#e9ecef", color="#868e96"]; Desc [label="Description\n(LLM Instructions)", fillcolor="#e9ecef", color="#868e96"]; Schema [label="Input Schema\n(JSON Schema)", fillcolor="#b2f2bb", color="#2f9e44"]; Props [label="Properties\n(Arguments)", fillcolor="#ffffff", color="#adb5bd"]; Req [label="Required Fields\n(Constraints)", fillcolor="#ffffff", color="#adb5bd"]; Tool -> Name [color="#adb5bd"]; Tool -> Desc [color="#adb5bd"]; Tool -> Schema [color="#adb5bd"]; Schema -> Props [color="#8ce99a"]; Schema -> Req [color="#8ce99a"]; }Structure of an MCP Tool Definition showing the relationship between the identifier, the instructions for the model, and the strict data validation layer.Understanding the Input SchemaThe inputSchema property must be a JSON object describing the expected arguments. This object follows the JSON Schema draft standard. At the top level, the type is almost always object, representing the dictionary of arguments the function expects.Within this object, you define properties. Each key in the properties object corresponds to an argument name. For each argument, you must specify a type, such as string, integer, boolean, or array. This typing provides the first line of defense against hallucinated inputs. If a tool expects an integer for a port number, the JSON Schema validation ensures the model does not pass a string like "eighty-eighty".Consider a tool designed to search a product database. The JSON structure sent over the MCP protocol would look like this:{ "name": "search_products", "description": "Search for products in the catalog by name or category. Returns price and stock status.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "The search term, e.g., 'wireless headphones'" }, "category": { "type": "string", "enum": ["electronics", "clothing", "home"], "description": "Filter by product category" }, "max_price": { "type": "integer", "description": "Maximum price in USD" } }, "required": ["query"] } }In this example, query is the only mandatory argument. The category argument uses the enum keyword to restrict input to a specific set of allowed strings. This reduces the chance of the model inventing categories that do not exist in your database.Describing Arguments for the ModelJust as the tool itself needs a description, each property within the schema benefits from a description field. This is where you perform micro-prompt engineering. The description for an argument tells the model exactly what information to extract from the user's conversation history to populate that field.If you have an argument named sql_query, a description like "The SQL command to execute" is helpful. However, a description like "A read-only SELECT statement to query the users table. Do not use DROP or DELETE" acts as both instruction and a safety rail. The model incorporates these instructions into its reasoning process before generating the tool call.Required vs Optional ParametersThe required field in the JSON Schema is a list of strings indicating which properties must be present. The distinction between required and optional parameters significantly influences model behavior.When a parameter is listed in required, the model will prioritize finding or generating a value for it. If the information is missing from the conversation context, a well-trained model (like Claude) will often halt execution and ask the user for clarification rather than guessing. For example, if city is required for a weather tool and the user asks "What is the weather?", the model will respond "Which city?" instead of calling the tool with a hallucinated location.Optional parameters allow the model to be flexible. If max_results is optional, the model can omit it, and your server implementation should handle the default value logic.Schema Complexity and PerformanceWhile JSON Schema supports deep nesting and complex validation logic (like oneOf or allOf), it is advisable to keep tool schemas as flat as possible. Deeply nested structures increase the token count and cognitive load for the LLM, which can lead to higher latency or parsing errors.If a tool requires a complex object hierarchy, consider breaking it down into simpler arguments or splitting the functionality into multiple, more specific tools. The goal is to minimize the ambiguity in the interface so the model can map user intent to function arguments with high reliability.Validating the SchemaIn the MCP architecture, the ListToolsRequest is the mechanism used to transport these definitions from Server to Client.$$R_{tools} = { T_1, T_2, \dots, T_n }$$Where each $$T$$ is a complete tool definition object. When the client receives this list, it does not execute the code. It simply registers the capability. The validation of the schema happens twice:Client-Side: The LLM provider often validates that the generated JSON matches the schema before sending the request back to your server.Server-Side: Your server must validate the incoming CallToolRequest against the schema you defined.This dual-validation ensures that your implementation logic inside the tool handler receives clean, typed data, allowing you to focus on the business logic rather than extensive error checking for basic data types. In the following section, we will explore how to automate this validation using Pydantic models in Python.