Stable MCP Tool Contracts and Error Semantics
Snapshot
~85 sec
A stable MCP tool contract performs one operation with typed input, bounded output, and distinguishable failure. Safe retry needs real server behavior, such as an operation key plus a precondition and recorded outcome. An idempotency annotation is only a hint. Dry-run, expected diff, and post-change evidence make writes reviewable.
You will learn
- Design one-operation input and output contracts with bounded results.
- Separate protocol errors from tool execution results marked isError.
- Require preconditions and operation-key tracking before treating a write retry as idempotent.
Target outcome
You can review an MCP tool contract for stable semantics, honest retry behavior, and evidence-rich failure handling.
Visual walkthrough
Visual walkthrough
~7 min
Six contract commitments
One operation
The tool name and description declare one stable responsibility.
Typed input
Required fields, allowed values, identifiers, limits, and preconditions are machine-checkable.
Bounded output
Result size, pagination, coverage, and expected fields are explicit.
Real idempotency
The server recognizes an operation key, checks preconditions, and returns the recorded outcome instead of repeating a write.
Error layer
Protocol failure and completed tool execution with isError have different meanings.
Untrusted annotation
Hints help a client present risk, but the client must not treat them as proof of behavior.
Ambiguous mutation versus stable contract
Approach Operation scope Retry behavior Before-write evidence Failure meaning fix_sceneA broad tool chooses edits, saves the scene, and returns free-form text. The same input can rename, delete, add, or save unrelated invented objects. A repeated call may apply another mutation because no operation key or precondition is tracked. The caller cannot review an expected diff before authority is granted. A paragraph does not distinguish malformed protocol, rejected input, partial execution, or project validation failure. rename_scene_objectOne exact invented object moves from an expected current name to one proposed name. Stable object ID, expectedName, and newName bound one property change. The server records operationKey and outcome; the same key returns that outcome without reapplying. dryRun returns expectedDiff with applied false before a write approval. Unknown tool or invalid protocol stays separate from an executed tool result that reports isError. Make idempotency server-observable
A write becomes safely repeatable only when the server can distinguish a new operation from a duplicate and enforce the same precondition and outcome.
Receive request
Typed target, expected current value, proposed value, operation key, and dry-run mode.
firstLook up operation key
Check a durable operation record before touching project state.
if newCheck precondition
Confirm the exact invented object still has expectedName.
if currentExecute once
Apply the one approved rename only for a new valid operation.
thenRecord outcome
Persist operation key, target, resulting state, and evidence before returning.
supports retryReturn known outcome
A duplicate key returns the recorded result without a second rename.
A write becomes safely repeatable only when the server can distinguish a new operation from a duplicate and enforce the same precondition and outcome. Reading order
- Receive request
Typed target, expected current value, proposed value, operation key, and dry-run mode.
- Look up operation key
Check a durable operation record before touching project state.
- Check precondition
Confirm the exact invented object still has expectedName.
- Execute once
Apply the one approved rename only for a new valid operation.
- Record outcome
Persist operation key, target, resulting state, and evidence before returning.
- Return known outcome
A duplicate key returns the recorded result without a second rename.
Connection explanations
- Receive request → Look up operation key (first)
The operation key is checked before any mutation, not used only as a log label afterward.
- Look up operation key → Check precondition (if new)
A new key still needs a current-state precondition so stale writes are rejected.
- Check precondition → Execute once (if current)
Only a matching before state permits the one declared operation.
- Execute once → Record outcome (then)
The server records enough evidence to distinguish completion from an unknown outcome.
- Record outcome → Return known outcome (supports retry)
A retry with the same key receives the known result rather than executing again.
Preview the exact diff before authority
Invented conceptual request and result. A real server implementation must validate the schema, enforce the operation key and precondition, and produce the result from current project state.
Languagejson { "tool": "rename_scene_object", "input": { "objectId": "target-dummy-01", "expectedName": "TargetDummy", "newName": "PracticeTarget", "operationKey": "rename-2026-07-26-001", "dryRun": true }, "result": { "status": "preview", "expectedDiff": { "objectId": "target-dummy-01", "name": ["TargetDummy", "PracticeTarget"] }, "applied": false } }Code annotations
Lines 3-5
Bind target and before state
Stable object ID and expectedName identify both what may change and the state that must still be true.
Effect: A stale or substituted target is rejected before mutation.
Lines 6-7
Request preview under one operation key
The key identifies the logical operation; dryRun requests evidence without applying it.
Effect: The caller can review the same bounded operation before changing authority.
Lines 9-16
Return structured expected diff
Status, target, before and after values, and applied false are separate fields.
Effect: Approval can name the exact proposed change rather than rely on prose.
Judge a timed-out write
An invented rename tool times out after sending a write. Its metadata says idempotent, but the request has no operation key and the server stores no outcome record. Should the client retry automatically?
Expected reasoning
No. The annotation is only a hint and the outcome is unknown. A retry could apply another or conflicting mutation. First inspect current target state. Redesign the tool with a stable target, expected before value, operation key, durable outcome lookup, dry-run expected diff, and post-change evidence. Retry the same logical write only when the server can distinguish duplicate from new execution.
Go deeper
- MCP 2025-11-25 specification: ToolsCurrent discovery, input schema, structured result, tool error, and human-in-the-loop contract.
- MCP 2025-11-25 specification: SchemaAuthoritative protocol types and JSON Schema definitions for compatible implementations.
- MCP 2025-11-25 specification: CancellationCurrent notification semantics for requests that should stop without inventing a second success or error result.