Tool arguments: descriptions, limits and choices
A model writes a tool's arguments from its schema alone. Descriptions, limits and fixed choices make right arguments easier and wrong ones impossible.
ARTICLES = {
"Where is my order?": "orders",
"Changing an order": "orders",
"How refunds work": "refunds",
"Refunds for damaged items": "refunds",
"Resetting your password": "account",
}@mcp.tool()
def search_help(
query: Annotated[str, Field(description="Words to look for in the help articles.")],
topic: Literal["orders", "refunds", "account"] | None = None,
limit: Annotated[int, Field(ge=1, le=5)] = 3,
) -> str:
"""Search the help centre and return matching article titles."""
found = [title for title, t in ARTICLES.items() if query.lower() in title.lower() and topic in (None, t)]
return "; ".join(found[:limit]) or "No articles found."The imports gain Annotated, Literal and Pydantic's Field. Annotated[str, Field(description=...)] is still a str, with a description attached for the model. Field(ge=1, le=5) limits limit to 1 to 5. Literal[...] allows only those three topics, and | None = None makes the topic optional.
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
schema = tools.tools[1].input_schema
print(schema["properties"]["query"])
print(schema["properties"]["topic"])
print(schema["properties"]["limit"])
print(schema["required"])
asyncio.run(main())Every piece landed in the schema: the description, an enum of topics, minimum and maximum, and a default. Only query is required.
A call that breaks the rules
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
good = await client.call_tool("search_help", {"query": "refund", "topic": "refunds"})
print(good.content[0].text)
bad = await client.call_tool("search_help", {"query": "refund", "limit": 50})
print(bad.is_error)
print(bad.content[0].text)
asyncio.run(main())limit=50 never reached your function. The SDK checked the arguments against the schema, and the result came back as an error whose text says exactly what was wrong. A model reads that text as the tool's answer and can try again with a valid value, so a limit you write once also teaches the model.
- Call
search_helpwith"topic": "billing". - Remove the
Fieldfromqueryand compare its schema entry. - Call it with
"limit": "two".
This is what real progress feels like.