You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On the tested MCP SDK path, a mutating tools/call commits its external
effect and then loses its response; the client perceives a timeout. When the
application retries with a fresh JSON-RPC request ID, the tool executes again
and a second external effect occurs. MCP 2025-11-25 defines no normative
retry or duplicate-effect semantics for tools/call, so this is
spec-permitted, implementation-specific behavior — not a protocol defect.
Question for maintainers: "When a mutating tools/call may have completed
but its response is lost, should the MCP specification explicitly describe
the call outcome as ambiguous and warn clients that retrying can re-execute
external effects unless the tool/application provides its own stable
deduplication mechanism?"
Minimal reproduction
Mutating tool, one ledger row per execution (baseline tool is intentionally non-idempotent — no dedup, so the ledger authoritatively
counts executions): (1) deterministic fault hook drops the attempt-1 tools/call response after the handler returned (effect committed first);
(2) client perceives a timeout via read_timeout_seconds (MCPError) — the
SDK does not automatically retry, so the retry is application policy:
exactly one retry, identical arguments, fresh JSON-RPC id, as the spec
requires (request IDs "MUST NOT have been previously used by the requestor
within the same session"); (3) count ledger rows.
Observed behavior
Attempt 1 (jsonrpc_id=2): effect committed, response dropped; client sends notifications/cancelled for the timed-out id. Attempt 2 (jsonrpc_id=3,
application retry): tool re-executes, second effect committed. Ledger: 2 external effects for 1 logical operation, deterministic (20/20 on
clean checkouts).
Positive control
Same sequence with a stable logical-operation key and dedup at the
application effect layer: attempt 2 returns deduped, 1 external effect
recorded. Application-level idempotency successfully mitigated the
reproduced behavior. MCP did not dedup; the application effect layer did —
one mitigation, not a prescribed solution.
Specification context
server/tools (2025-11-25): tools/call execution semantics
(execute-once, dedup, effect guarantees), retry after a timeout, and
duplicate detection are all UNDEFINED — the section is normative about
wire shapes, not about what a completed tool call did.
idempotentHint (since 2025-03-26, PR ToolAnnotations #185) is NON-NORMATIVE: a tool
description, not a retry protocol; no RFC 2119 obligation either side.
basic/utilities/cancellation covers races only (MUST handle gracefully;
SHOULD stop processing; MAY ignore already-completed) — not
retry-after-loss.
The spec's fresh-ID requirement actively prevents ID reuse by which a
server might otherwise correlate a retry — the retry is structurally
unrecognizable to the server as a duplicate.
Why this matters
The tested MCP SDK path permits a retrying application to execute a
mutating tool more than once after an ambiguous lost-response outcome. tools/call currently does not define normative retry or duplicate-effect
semantics for this case. Applications can mitigate this using stable
idempotency at the effect boundary. The experiment motivates clarifying the
expected responsibility split between client, server, tool, and downstream
effect provider, so independent implementations converge on documented
expectations rather than ad-hoc retry conventions. Related prior art
(reference only, not revival): PR #3182 (closed 2026-08-23 on policy
grounds, not merits) proposed a normative idempotencyKey; this issue asks
only whether the spec should document the responsibility boundary.
Possible documentation-conformance options
Which vehicle would maintainers prefer, if any?
Non-normative spec guidance (e.g. on server/tools): the call
outcome after a lost response is ambiguous; retrying MAY re-execute
external effects; stable deduplication lives outside the protocol.
Candidate text: "The protocol defines no idempotency,
duplicate-detection, or exactly-once semantics for tools/call. A
client that retries a tool call after a timeout or lost response MAY
cause the tool to execute more than once; the server is not required to
recognize a retry as a duplicate. Implementations and application layers
that require bounded effect multiplicity should coordinate retry
identity and duplicate suppression outside the protocol (e.g.,
application-level idempotency keys or durable result lookup)."
Tool-author guidance on declaring and handling retry-relevant
properties.
Client retry guidance for effectful calls (caution before retrying a
timed-out mutating call).
A conformance vector: lost response after effect commit → retry with
new request ID → count effects; positive control with a stable
application-level key.
Future normative retry semantics — explicitly out of scope here.
Environment
mcp Python SDK 2.2.0 / mcp-types 2.2.0, real ClientSession → MCPServer over memory streams, negotiated protocolVersion 2025-11-25,
Python 3.12.3.
Reproduction link-commands
Self-contained scripts (local, not posted), ~7 s per run:
Problem-question
On the tested MCP SDK path, a mutating
tools/callcommits its externaleffect and then loses its response; the client perceives a timeout. When the
application retries with a fresh JSON-RPC request ID, the tool executes again
and a second external effect occurs. MCP 2025-11-25 defines no normative
retry or duplicate-effect semantics for
tools/call, so this isspec-permitted, implementation-specific behavior — not a protocol defect.
Question for maintainers: "When a mutating tools/call may have completed
but its response is lost, should the MCP specification explicitly describe
the call outcome as ambiguous and warn clients that retrying can re-execute
external effects unless the tool/application provides its own stable
deduplication mechanism?"
Minimal reproduction
Mutating tool, one ledger row per execution (baseline tool is
intentionally non-idempotent — no dedup, so the ledger authoritatively
counts executions): (1) deterministic fault hook drops the attempt-1
tools/callresponse after the handler returned (effect committed first);(2) client perceives a timeout via
read_timeout_seconds(MCPError) — theSDK does not automatically retry, so the retry is application policy:
exactly one retry, identical arguments, fresh JSON-RPC id, as the spec
requires (request IDs "MUST NOT have been previously used by the requestor
within the same session"); (3) count ledger rows.
Observed behavior
Attempt 1 (
jsonrpc_id=2): effect committed, response dropped; client sendsnotifications/cancelledfor the timed-out id. Attempt 2 (jsonrpc_id=3,application retry): tool re-executes, second effect committed. Ledger:
2 external effects for 1 logical operation, deterministic (20/20 on
clean checkouts).
Positive control
Same sequence with a stable logical-operation key and dedup at the
application effect layer: attempt 2 returns
deduped, 1 external effectrecorded. Application-level idempotency successfully mitigated the
reproduced behavior. MCP did not dedup; the application effect layer did —
one mitigation, not a prescribed solution.
Specification context
server/tools(2025-11-25):tools/callexecution semantics(execute-once, dedup, effect guarantees), retry after a timeout, and
duplicate detection are all UNDEFINED — the section is normative about
wire shapes, not about what a completed tool call did.
idempotentHint(since 2025-03-26, PR ToolAnnotations #185) is NON-NORMATIVE: a tooldescription, not a retry protocol; no RFC 2119 obligation either side.
basic/utilities/cancellationcovers races only (MUST handle gracefully;SHOULD stop processing; MAY ignore already-completed) — not
retry-after-loss.
server might otherwise correlate a retry — the retry is structurally
unrecognizable to the server as a duplicate.
Why this matters
The tested MCP SDK path permits a retrying application to execute a
mutating tool more than once after an ambiguous lost-response outcome.
tools/callcurrently does not define normative retry or duplicate-effectsemantics for this case. Applications can mitigate this using stable
idempotency at the effect boundary. The experiment motivates clarifying the
expected responsibility split between client, server, tool, and downstream
effect provider, so independent implementations converge on documented
expectations rather than ad-hoc retry conventions. Related prior art
(reference only, not revival): PR #3182 (closed 2026-08-23 on policy
grounds, not merits) proposed a normative
idempotencyKey; this issue asksonly whether the spec should document the responsibility boundary.
Possible documentation-conformance options
Which vehicle would maintainers prefer, if any?
server/tools): the calloutcome after a lost response is ambiguous; retrying MAY re-execute
external effects; stable deduplication lives outside the protocol.
Candidate text: "The protocol defines no idempotency,
duplicate-detection, or exactly-once semantics for
tools/call. Aclient that retries a tool call after a timeout or lost response MAY
cause the tool to execute more than once; the server is not required to
recognize a retry as a duplicate. Implementations and application layers
that require bounded effect multiplicity should coordinate retry
identity and duplicate suppression outside the protocol (e.g.,
application-level idempotency keys or durable result lookup)."
properties.
timed-out mutating call).
new request ID → count effects; positive control with a stable
application-level key.
Environment
mcpPython SDK 2.2.0 /mcp-types2.2.0, realClientSession→MCPServerover memory streams, negotiated protocolVersion2025-11-25,Python 3.12.3.
Reproduction link-commands
Self-contained scripts (local, not posted), ~7 s per run:
Baseline prints
authoritative effect count: 2; the control prints1(see
evidence-summary.mdfor wire excerpts and frozen evidence hashes).Limitations
after commit); other fault shapes not tested.
SDK performs no automatic retry.
path.
This is implementation evidence plus a spec-clarification question. No
normative change is proposed; no option above is prescribed.