- A retry is a new request, not proof that the previous side effect failed.
- Idempotency must be enforced by the handler and its downstream operation, not only advertised as a hint.
- Non-repeatable tools need confirmation, reconciliation, or a safe status lookup before retrying.
- Server-boundary telemetry can reveal repeated calls and outcomes, but not a private client retry policy or downstream settlement without an emitted signal.
Retries are normal in distributed systems, and MCP servers sit inside a distributed system. A client can retry after a timeout even when the server completed the operation. An agent can call again after receiving an error it did not understand. A proxy or job runner can replay a request. The danger is not the existence of retries. The danger is allowing a duplicate state-changing call to look like a harmless read.
Idempotent does not mean successful
An idempotent operation can be applied more than once with the same intended effect. It can still fail, time out, or return an error. Idempotency is a property of repeated execution, not a promise that the first call will finish. For example, deleting an already-deleted record may be safely repeatable but should still report whether the requested state was achieved.
The MCP specification exposes tool annotations that can describe behavior such as read-only or idempotent, but the specification treats annotations as hints and clients must not blindly trust them. The server remains responsible for enforcing authorization, validating arguments, and making the handler safe for the behavior it advertises.
Classify tools by side effect
- Read-only tools fetch or compute information without changing external state.
- Conditionally mutating tools change state only when a precondition or version matches.
- Create-style tools can often accept an idempotency key and return the existing result for a duplicate key.
- Non-repeatable tools, such as sending an email or charging a payment method, need a stronger boundary than a client retry loop.
Design the idempotency key at the application boundary
An idempotency key is useful only if the handler and its downstream operation honor it. Generate or accept a key that represents one intended operation, store the key with the result for a bounded retention period, and return the original result when the same authorized caller repeats it. Do not use a timestamp alone if two requests can arrive in the same time window, and do not make a key global when tenant or user scope is part of the authorization decision.
const result = await createInvoice({
customerId,
amount,
idempotencyKey: request.headers["x-idempotency-key"],
});
// The database constraint or downstream API must enforce
// one effect for one authorized key, not just the handler.
return result;Timeouts create the ambiguous result problem
Suppose a client sends create_ticket, the server commits the ticket, and the response is lost. The client sees a timeout and retries. If the second call creates another ticket, the protocol was available but the product outcome is wrong. The server needs a way to connect both attempts to one intended operation, or it must make the action safe to query and reconcile before creating again.
This is also why a retry count is not proof of a failed operation. It is evidence that a caller sent another request. A useful incident investigation combines retry metadata, request timing, the tool's side-effect policy, downstream identifiers, and the final application outcome.
Return errors that support safe recovery
- Tell the client whether the operation may have been applied before the error occurred.
- Return a stable error class and a human-readable correction when the input is invalid.
- Include a safe operation identifier that can be used for status lookup, not a secret or bearer token.
- Do not suggest retrying a non-idempotent operation unless the server has a reconciliation path.
How to test retry behavior
Write tests that force a response timeout after the side effect, retry the exact request, send two concurrent requests with the same key, reuse a key with different arguments, and retry after the key retention window. Test authorization scope too. The same idempotency key must not let one user retrieve another user's result.
At production scale, inspect the rate of repeated calls by tool, client, error class, and deployment. TrackMCP can show observed calls, durations, retry metadata when supplied, and tool-level errors at the server boundary. It does not know whether a client was about to retry, whether a host displayed the error, or whether a downstream payment processor settled a request unless your server emits that outcome.
Should every MCP tool be idempotent?
No. Read operations are often repeatable, but some useful actions have unavoidable side effects. Those tools need explicit confirmation, idempotency or reconciliation, and careful error semantics.
Does an MCP idempotentHint guarantee safe retries?
No. It is a behavioral hint. Clients must treat server-provided annotations as untrusted, and the server must make its implementation match the behavior it advertises.
Is a duplicate call always a retry?
No. It may be a new workflow step, a user request repeated intentionally, or a client recovery attempt. Use timing, request context, retry metadata, and application identifiers rather than call count alone.
About the publisher
TrackMCP, also written Track MCP
TrackMCP helps teams understand which clients connect to their MCP servers, which tools agents use, and where workflows fail. Learn more about Track MCP.
See this on your own server
TrackMCP turns your MCP server's calls into adoption, workflows, and outcomes. One line to install.