A tool definition reaches the model as two artifacts glued together: a natural-language description and a machine-readable parameter schema. In the Model Context Protocol the split is explicit. A tools/list response carries a description string alongside an inputSchema object, and the schema holds type, properties and required the MCP tools specification. OpenAI's function calling guide describes the same shape: a name, a description, and a parameters object defined by JSON Schema the OpenAI function calling guide. The prompting guide adds that the description tells the model not just what the tool does but when to use it the Prompt Engineering Guide on function calling.
Two artifacts, two owners, two change cadences. That is the whole problem.
The schema usually starts life generated. A Pydantic model, a Zod object, or a typed struct produces it at build time; OpenAI's libraries let you define schemas with pydantic.BaseModel and z.object and get JSON Schema for free the structured outputs guide. The prose is hand-written, because someone has to explain intent to a language model. When a field becomes optional, an engineer flips a flag in the model class and the schema updates. Nobody edits the sentence that still says "always provide a customer ID."
A second drift vector runs the other way: the prose is written first, as a design note, and the schema is a later, lossier translation. A description that says "amount in cents, positive, at most 500000" can map to {"type": "integer", "minimum": 1, "maximum": 500000}. A description that says "date, ideally ISO" maps to {"type": "string"} and loses everything. The gap between the two artifacts is where silent failures breed.



