Skip to content

mcp: client multi-round-trip middleware mutates caller params, replaying inputResponses across calls #1144

Description

@yhxlele

What did you do?

Called ClientSession.CallTool against a tool whose handler gates on an elicitation InputRequest (SEP-2322), reusing one *CallToolParams struct for a second call:

params := &mcp.CallToolParams{Name: "delete", Arguments: args}
res1, _ := cs.CallTool(ctx, params) // elicitation fires, user accepts, tool runs
res2, _ := cs.CallTool(ctx, params) // same struct, second call

The server handler uses the gating idiom from the SDK's own tests and examples:

if len(req.Params.InputResponses) == 0 {
    return &mcp.CallToolResult{
        InputRequests: mcp.InputRequestMap{"confirm": &mcp.ElicitParams{Message: "Sure?"}},
        RequestState:  state,
    }, nil, nil
}

What did you see?

After the first call returns, the caller's params struct has been mutated: params.InputResponses holds the first call's ElicitResult and params.RequestState holds the first call's state. setMultiRoundTripRetryParams (mcp/mrtr.go) writes both fields through the caller's pointer during the middleware's retry loop and never clears them.

The second call therefore goes out with the first call's answer pre-attached. The server sees len(InputResponses) > 0, treats the call as already answered, and the client's ElicitationHandler never fires. For a confirmation gate on a destructive tool, the second action runs with no user confirmation at all.

What did you expect to see?

CallTool / GetPrompt / ReadResource should not mutate caller-owned params. Each fresh call should round-trip its own input requests; retry bookkeeping belongs to the middleware, not the caller's struct.

What version of the Go MCP SDK are you using?

v1.7.0; the behavior is unchanged at head (7256941).

What version of Go are you using?

go1.26.5 darwin/arm64

I have a fix ready (carry the retry responses/state on a shallow copy of the params instead of mutating the original) and will send a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues, valuable feature requests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions