← Back to Build

Schema Design

Well-typed schemas reduce agent errors, prevent hallucinated arguments, and make your server predictable.

Why This Matters

Every tool's inputSchema is a contract between your server and the AI agent. When the schema is precise, the model generates valid arguments on the first try. When it's vague or missing constraints, the model guesses — and guesses wrong. Schema quality directly impacts tool call success rates.

Key insight: Each tool definition consumes context window tokens. Sloppy schemas waste tokens and confuse the model. Tight schemas are both cheaper and more reliable.

The Spec Foundation

MCP uses JSON Schema (Draft 2020-12) for inputSchema and outputSchema. The top-level type is always "object", with parameters defined as properties.

What the Spec Defines

FieldRequiredPurpose
inputSchemaYesJSON Schema describing the tool's parameters
outputSchemaNoJSON Schema describing structured return data (added June 2025)
structuredContentConditionalMust be present when outputSchema is declared

Core Principles

1. Flatten Your Schemas

Flat schemas (top-level primitives) dramatically outperform nested objects for LLM comprehension. Deep nesting increases token overhead and reduces adherence.

ApproachExample
Flat { "email": "...", "status": "shipped", "limit": 20 }
Nested { "filters": { "user": { "email": "..." }, "order": { "status": "..." } } }

2. Use Enums for Categorical Fields

Never use free-text strings for fields that have a known set of valid values. Enums prevent hallucination and give strong runtime validation.

  • "status": { "type": "string", "enum": ["pending", "shipped", "delivered"] }
  • "priority": { "type": "string", "enum": ["low", "medium", "high", "critical"] }

3. Constrain Numeric Fields

Always set bounds on numbers. Without minimum/maximum, a model may generate absurd values.

  • "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
  • "page": { "type": "integer", "minimum": 1 }

4. Mark Required Fields Explicitly

The required array tells the model which parameters are mandatory. Omitting it implies everything is optional, which is rarely what you want.

5. Describe Every Property

Add a description to every property in your schema. This is where you put format expectations, default behavior, and usage guidance.

6. Use additionalProperties: false

When strict validation is needed, set additionalProperties: false to reject unexpected fields. This catches model hallucinations at the protocol level.

Output Schemas (June 2025+)

The 2025-06-18 spec added outputSchema as a first-class field. When declared:

  1. Servers MUST populate structuredContent conforming to the schema
  2. For backward compatibility, also include the same data as a TextContent block
  3. Clients SHOULD validate structuredContent against the schema

This enables machine-to-machine tool chaining — downstream tools can consume structured output without parsing text.

SDK Patterns

TypeScript (Zod)

The TypeScript SDK uses Zod for schema definition. Use .describe() on every field:

server.tool(
  "search_orders",
  {
    email: z.string().email()
      .describe("Customer email address"),
    status: z.enum(["pending", "shipped", "delivered"])
      .describe("Filter by order status"),
    limit: z.number().int().min(1).max(100).default(20)
      .describe("Max results to return")
  },
  async (args) => { /* handler */ }
);
Compatibility note: MCP SDK v1.17.5 has known issues with Zod v4. If you upgrade Zod, check the compatibility issue first.

Python (Pydantic / FastMCP)

FastMCP auto-generates schemas from type hints and docstrings. Use Field(description=...) and Literal types:

from fastmcp import FastMCP
from pydantic import Field
from typing import Literal

mcp = FastMCP("My Server")

@mcp.tool()
def search_orders(
    email: str = Field(description="Customer email"),
    status: Literal["pending", "shipped", "delivered"]
      = Field(description="Filter by order status"),
    limit: int = Field(
      default=20, ge=1, le=100,
      description="Max results to return"
    ),
) -> list[dict]:
    """Search customer orders by email and filters.

    Use when a user asks about order history or wants
    to find specific orders.
    """
    ...

FastMCP automatically dereferences $ref entries (inlining $defs) for clients that don't support JSON Schema references.

Pagination Pattern

Any tool that returns a list should support pagination. The standard MCP pattern:

  • Accept limit (default 20–50) and optional cursor as input parameters
  • Return next_cursor and has_more in the response
  • Never return unbounded result sets — they flood the context window

Handling Complex Inputs

When you need structured inputs (arrays of objects, nested configs), consider these strategies:

  1. Break it up. Split complex operations into multiple simpler tools rather than one tool with many optional branches.
  2. Accept JSON strings. For unavoidably complex inputs, accept a JSON string and parse internally. This avoids deeply nested schema definitions.
  3. Limit tool count. Keep 5–15 tools per server. Beyond 15, split into domain-specific servers.
  4. Lazy-load schemas. For very large schemas, consider the lazy-loading pattern where the full schema is fetched on demand.

Testing Your Schemas

  • Validate example payloads against your inputSchema in CI to catch accidental breaking changes
  • Use the MCP Inspector to verify your tool definitions look correct
  • Test with real LLMs — have an agent call your tools and check argument quality
  • Verify outputSchema matches actual structuredContent responses

Quick Checklist

  • Every tool has an inputSchema with "type": "object"
  • All required parameters are listed in the required array
  • Every property has a description
  • Categorical fields use enum constraints, not free text
  • Numeric fields have minimum/maximum bounds
  • Schemas are flat — no unnecessary nesting
  • List-returning tools accept limit and cursor parameters
  • Optional fields have sensible default values
  • additionalProperties: false is set where strict validation is needed
  • outputSchema is declared for tools with machine-readable output

Essential Resources

Official Specification

SDKs & Frameworks

Schema Design Guides

Structured Output & outputSchema

JSON Schema for LLMs

SDK-Specific Patterns