MCP Server API Contracts for AI Agents

API spec contract structure for MCP servers serving AI agent clients
Spec Coding Editorial Team · Spec-driven development notes

The Model Context Protocol quietly turned every internal script, database query, and admin action into an API. The client for that API is not a developer reading your docs; it is a language model reading your tool descriptions at runtime and deciding, on its own, what to call and with what arguments. That client deserves the same thing every API consumer deserves, a contract, and it punishes the lack of one faster than any human integrator ever did.

Published on 2026-07-07 · 6 min read · Author: Spec Coding Editorial Team · Review policy: Editorial Policy

Draft the contract before you ship the server. Use the free generator to turn a tool inventory into a reviewable API spec with schemas, error codes, and side-effect notes.
Download matrix Generate API spec Copy the template

An MCP tool is an API with a probabilistic client

When a human developer integrates your API, ambiguity gets absorbed by their judgment. They read the docs twice, test in staging, and email you when the 409 makes no sense. An agent does none of that. It reads the tool's name, description, and input schema in its context window, forms a belief about what the tool does, and acts on that belief immediately, in production, possibly hundreds of times an hour.

This changes the economics of specification. Every ambiguity in a human-facing API costs you a support thread. Every ambiguity in an agent-facing tool costs you whatever the agent decided it meant, multiplied by every session that reaches the same fork. The fix is not better prompting on the client side; client prompts are the one thing you do not control. The fix is a contract on the server side: a spec that pins down inputs, outputs, errors, and side effects before any agent depends on them.

What changes when the consumer is an agent

Contract elementHuman developer clientAI agent client
DocumentationRead once during integrationRe-read from the schema every session; description is the docs
Validation errorsFixed during developmentMust name the failing field so the agent can self-correct
RetriesWritten deliberately, onceAttempted whenever an error looks transient; idempotency is survival
Destructive actionsGuarded by developer cautionGuarded only by what the contract flags and the client enforces
Breaking changesAnnounced in a changelogSilently misfire until a human notices agent behavior degraded

None of this is exotic. It is the same rigor a good OpenAPI spec already demands, applied with the assumption that the reader has perfect patience, zero context outside the schema, and no ability to ask a clarifying question. The agentic clients guide covers the design side in depth; this article focuses on the contract artifact itself.

The contract packet for an MCP server

Before implementation, write one spec document per server with six sections: the tool inventory with a one-line purpose per tool, an input schema per tool, an output contract per tool, a shared error taxonomy, a side-effect classification, and a versioning policy. The API spec generator produces this skeleton from a plain-language description of your tools, and the API spec template is the copy-ready version for teams that prefer to start from a file.

The inventory section earns its place by forcing one decision teams skip: which capabilities do not become tools. A server that exposes twenty granular tools pushes orchestration into the agent's context window, where it is re-derived badly on every session. Most servers do better exposing fewer, task-shaped tools with strict contracts than mirroring their internal API surface one endpoint at a time.

Input schemas: constrain, do not describe

JSON Schema is the enforcement layer of the contract, and agents respect it more reliably than they respect prose. Every constraint you can express in the schema, express in the schema: enums instead of free-text strings, formats and patterns for identifiers, explicit defaults, required fields kept to the true minimum. The description field then carries semantics the schema cannot: units, timezone assumptions, what happens when an optional field is omitted.

"amount_cents": {
  "type": "integer",
  "minimum": 1,
  "maximum": 500000,
  "description": "Refund amount in cents. Must not exceed
                  the invoice's remaining refundable balance;
                  the server rejects overdrafts with
                  REFUND_EXCEEDS_BALANCE."
}

The anti-pattern is the string-typed field with a paragraph of prose explaining the accepted values. Agents will eventually send the one variant your parser did not expect. If the values are enumerable, enumerate them; if they follow a format, pattern them; if a combination is invalid, say so in the description and reject it with a named error.

Output contracts: agents parse, they do not skim

An MCP tool that returns a friendly paragraph of prose forces every consuming agent to parse English. That parse is a probabilistic operation that will go wrong somewhere. The output contract should commit to structured content with stable field names, a machine-readable status, and identifiers the agent can feed into follow-up calls. Prose belongs in one designated summary field, not in the load-bearing structure.

{
  "status": "refunded",
  "refund_id": "rf_8104",
  "amount_cents": 4200,
  "invoice_state": "closed",
  "summary": "Refunded $42.00 against invoice inv_2231."
}

Treat output fields with the same backward-compatibility discipline as a public API response. Renaming refund_id to refundId is a breaking change even though no compiler will catch it; the agents consuming it will simply start failing at whatever step needed the identifier.

An error taxonomy agents can act on

Errors are where agent-facing contracts diverge most from habit. A human reads "Something went wrong, please try again later" and applies judgment. An agent needs the judgment encoded: every error the server can return should map to exactly one recovery action. The full method is in the API error taxonomy guide; the short version is a small, stable code set classified by what the caller should do next.

ClassExample codeAgent action
TransientUPSTREAM_TIMEOUTRetry with backoff, same idempotency key
Invalid inputINVALID_FIELD: amount_centsFix the named field, retry once
State conflictREFUND_EXCEEDS_BALANCERe-read state, re-plan, do not blind-retry
PermissionROLE_FORBIDDENStop and surface to the user
Not foundINVOICE_NOT_FOUNDVerify the identifier with a read tool first

Two rules keep the taxonomy honest. Codes are append-only: agents and their prompts calcify around them, so renames are breaking changes. And every validation error names the failing field in a structured detail, because "invalid request" sends an agent into a guess-and-retry loop that looks, from your logs, exactly like an attack.

Side effects: flag them or agents will find them

The contract's side-effect section classifies every tool three ways: read-only or mutating, idempotent or not, reversible or destructive. MCP has native annotations for exactly this, readOnlyHint, destructiveHint, and idempotentHint, and your spec should decide their values deliberately rather than defaulting them. Client applications use these hints to decide when to ask a human for confirmation; a mislabeled tool inherits whatever trust the label promised.

Any mutating tool an agent may retry needs idempotency, and the contract should say how: an idempotency_key input that makes duplicate calls return the original result is the pattern that survives real agent behavior. For genuinely destructive operations, deletion, irreversible sends, money movement, the strongest contract clause is a two-step shape: a dry_run or preview mode that returns what would happen, and an execute mode that requires the token the preview issued. That turns "the agent deleted the wrong thing" from an incident into a rejected call.

Worked example: a refund tool contract

tool: refund_invoice
purpose: refund a failed or disputed invoice, once
inputs: invoice_id (pattern inv_*), amount_cents (1..500000),
        idempotency_key (uuid, required), dry_run (bool, default true)
output: status, refund_id, amount_cents, invoice_state, summary
errors: INVALID_FIELD, INVOICE_NOT_FOUND, REFUND_EXCEEDS_BALANCE,
        ROLE_FORBIDDEN, UPSTREAM_TIMEOUT
side effects: mutating, idempotent via key, destructive
              (flags: readOnlyHint=false, destructiveHint=true,
               idempotentHint=true)
policy: dry_run=true returns preview + confirm_token;
        execution requires confirm_token from a preview
        issued within 10 minutes
version: v2 (v1 accepted amount as dollars; removed 2026-06)

Every line answers a question an agent would otherwise answer by guessing. The version line is not decoration: the dollars-to-cents change in v1 is precisely the kind of silent semantic shift that agent clients cannot detect and cannot survive.

Versioning and the review gate

MCP servers ship fast because they are small, which means their contracts drift fast too. Two process rules contain the drift. First, tool schemas and descriptions live in the repo as reviewable artifacts, and any change to them gets the same schema diff review an OpenAPI change would get: what breaks, who consumes it, what is the migration line. Second, semantic changes that the schema cannot express, a default flipping, a unit changing, an error code gaining a new meaning, require a version bump and a dated line in the contract, because no diff tool will flag them for you.

The uncomfortable truth about agent-facing APIs is that your real interface is not the code; it is the beliefs your contract induces in a language model. Write the contract first, and those beliefs are at least ones you chose.

Keywords: MCP server contract · API spec for AI agents · tool input schema · agent error taxonomy · idempotency key · destructive tool flags