Build
Put the rules in the types
Put the rules in the types, not the docstring. The schema then tells Claude exactly what's allowed, and bad input is rejected before your code runs.
Rules hidden in prose
@mcp.tool()def search_issues(repo: str, state: str, labels: str, limit: int) -> str: """Search issues. repo: owner/name, e.g. octo/app state: one of open, closed, all labels: comma-separated label names limit: max results, 1 to 50 Returns a JSON string of issues."""The schema only says “string” and “int”. Claude has to read and obey the prose, and nothing checks it.
Rules in the types
from typing import Annotated, Literalfrom pydantic import BaseModel, Field class Issue(BaseModel): number: int title: str url: str @mcp.tool()def search_issues( repo: Annotated[str, Field(description="owner/name")], state: Literal["open", "closed", "all"] = "open", labels: list[str] = [], limit: Annotated[int, Field(ge=1, le=50)] = 10,) -> list[Issue]: """Search issues in a GitHub repository."""Literal
Becomes an
enum: only valid values.Field(ge, le)
Becomes
minimum and maximum, with descriptions.Pydantic model
Validated input or output. A return type becomes an
outputSchema.TypedDict
Same schema, lighter: types only, no runtime validation.