toolcall() ← all concepts

// concept · MCP

Your tool asked a question. Now it runs twice.

A tool that needs one more detail can answer with a question instead of a result. What surprises people is what comes back: not a reply to that call, but a whole new call — and your handler starting again from the top.

// a result, or a request

Since the 2026-07-28 line, a tool call has two shapes of success. It can complete, or it can come back asking for something:

Servers MAY respond to tools/call with an InputRequiredResult to indicate that additional input is needed before the tool call can be completed.

The result carries resultType: "input_required", a map of inputRequests — each one an elicitation/create with a message and a schema for the answer — and an opaque requestState.

# the tool's answer is a question resultType: "input_required" inputRequests: { github_login: { …message, requestedSchema… } } requestState: "eyJsb2NhdGlvbiI6…"

So far this reads like a pause. It is not one.

The retry is a new call

The client does not reply to the request that asked. It issues a fresh tools/call — same tool name, the same arguments re-sent verbatim — with the user's answers and the echoed state attached.

# call 1 name: get_weather arguments: { location: "New York" } # call 2 — the retry name: get_weather arguments: { location: "New York" } inputResponses: { github_login: { action: "accept", … } } requestState: "eyJsb2NhdGlvbiI6…"

And then the giveaway, stated outright in the spec:

Note that the JSON-RPC id MUST be different between the initial request and the retry.

A different id is a different request. Nothing is suspended server-side waiting to continue — the first call completed, with an answer that happened to be a question. That is also why the protocol works at all with a stateless core: everything the retry needs travels in its own payload, so any instance of your server can pick it up.

Which means your handler runs again

Follow that through to your own code. Your tool function is entered a second time, with the same arguments it had the first time. Whatever it did before it asked, it is about to do again.

# run 1 take_payment(40) ← charged ask_for_login() ← returns input_required, call ends # run 2 — same arguments, from the top take_payment(40) ← charged AGAIN finish()
The protocol is not charging you twice. It re-issues the call; what that costs is a property of how the handler is written. That distinction matters — the fix lives entirely in your code, and nothing in the spec will do it for you.

The fix, and the cheap version of it

Two ways out, and the first is usually enough.

order the work ask FIRST, then do the thing that costs or resume use requestState to skip what is already done

Putting the side effect after the question is free and needs no bookkeeping: on the first run the tool asks and does nothing else; on the retry the answer is present, so it runs straight through. Reconstructing from requestState is the option when the work genuinely has to happen before you can even formulate the question.

If your tools are already idempotent you have most of this for free — a repeated call with the same arguments should already be safe. This is a good argument for that discipline rather than a separate one: the protocol will now re-issue calls on its own, not only when a client retries after a timeout.

Two things it doesn't mean

Your server cannot interrupt people out of nowhere. Under SEP-2260, server-initiated requests may only be issued while the server is actively processing a client request. Earlier revisions recommended it; it is now required, so every question traces back to something the user started.

And this is not the security story. requestState is a mechanism for carrying context across the two calls, not a credential — hardening it against replay belongs with the broader point that an id is not a permission, which comes from the same spec revision and is its own subject.

Sources: Model Context Protocol specification, draft — Server / Tools §Input Required Tool Results, and §Multi Round-Trip Requests (SEP-2322, SEP-2260) · MCP blog — the 2026-07-28 release candidate. Verified 2026-09-02.

One concept a week. Free.

The deeper, copy-paste version of each ToolCall short — in your inbox.

// total: 0.00 · spam: void · unsubscribe: one click