Skip to content

design: mrtr implementation proposal#950

Draft
yarolegovich wants to merge 1 commit intomainfrom
yarolegovich/mrtr
Draft

design: mrtr implementation proposal#950
yarolegovich wants to merge 1 commit intomainfrom
yarolegovich/mrtr

Conversation

@yarolegovich
Copy link
Copy Markdown
Member

@yarolegovich yarolegovich commented May 8, 2026

This PR presents a proposal for implementing Multi Round-Trip Requests (MRTR) as defined in SEP-2322.

In the new protocol version servers can't initiate requests to clients, but when a server requires additional input for completing tools/call, prompts/get, or resources/read it can return an incomplete result along with a set of inputRequests. The client fulfills them locally and retries the same call with inputResponses attached.

@yarolegovich yarolegovich marked this pull request as draft May 8, 2026 11:04
@maciej-kisiel
Copy link
Copy Markdown
Contributor

I think the main question we should ask ourselves is how we would design the API for this if there was no backwards compatibility requirement.

If we have an approach that will likely be chosen for v2, sometimes it may make sense to move toward its direction, even if it has some disadvantages if they would disappear with v2 release.

Comment thread design/mrtr.md
```

`InputRequests` and `RequestState` fields are added directly to `CallToolResult`, `GetPromptResult`, and `ReadResourceResult` as exported.
Result type discriminator (completed, input_required) is unexported so that SDK users don't need to set it to the correct constant in addition to setting either `Content` or `InputRequests`. Handler execution result is validated and augmented before marshaling:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would the error be propagated to the tool developer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. My instinct was to treat it similarly to json.Marshal failure on the write path, but there's however an interesting discrepancy in how it's handled depending on where in the stack it occurs:

  1. Here it's returned as an fmt.Errorf -> jsonrpc error to clients.
  2. Here it's just written to an error log through onInternalError handler. The result is not returned to clients.

(1) exposes some implementation details but is more "visible" and is likely harmless. My vote would be a combination - log an error and return a jsonrpc server error to clients.

Comment thread design/mrtr.md

An unexported middleware is installed in the client, which similarly to `urlElicitationMiddleware` will automatically invoke handlers for the corresponding methods on incomplete results and retry the original request. `ClientOptions` will be extended with configuration knobs:
```go
type MRTROptions struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I would introduce these knobs from the start. If the user need is real, someone will come to use and ask about it. We can always add them later in a backwards compatible manner.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't object. We can start with a reasonable default of max retries and add configurability on request

Comment thread design/mrtr.md

// RequireInput constructs a tool call, prompt or resource result with input requests set.
// mrtrResult provides methods for setting private fields on these types.
func RequireInput[T any, TP interface { *T; mrtrResult }](r InputRequiredResult) TP { ... }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always introduce this even with exported fields, as a "util" that guarantees correctness. But likely not from the start.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo at least with the current data model and resultType auto-populated the utility would be very shallow. I would not be considering it unless we get an inflow of issues where people try to set InputRequests together with Content or other fields

Comment thread design/mrtr.md

### Protocol version bridging

When a handler returns an input-required result to an old client the SDK could bridge by invoking `ServerSession.Elicit`/`CreateMessage`/`ListRoots` on the `ServerSession` and re-invoking the handler with the collected `inputResponses`:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this behavior would be valuable. Without it, the server creators are not incentivized to use the new MRTR format because migrating would mean they will stop working with all older clients. It would slow down the adoption of the new protocol version.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is whether we should build it from the start or add later if there's a demand for it?

Comment thread design/mrtr.md
@yarolegovich
Copy link
Copy Markdown
Member Author

I think the main question we should ask ourselves is how we would design the API for this if there was no backwards compatibility requirement.

I don't think my proposal would change significantly if we didn't need backward compatibility.

I would want a sealed interface return type for *Handler functions and separate types for complete and incomplete result. On the client side I would probably leave a handler registration API through ClientOptions.

If we make the client API return a sealed interface as well the "base case" becomes overly verbose - all clients need to unpack result to a specific type and handle it accordingly, even though most of the time retry is auto-handled by the middleware.

Now this breaks the proposed client side "manual handling" where if you disable MRTR through options you can just inspect *Result.InputRequests and take action. I don't think it's a big problem though because users can just disable the auto-middleware and write their own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants