Skip to content

Writeup of design question for 2322-MRTR#24

Merged
CaitieM20 merged 2 commits intomodelcontextprotocol:mainfrom
CaitieM20:mrtr-question
Mar 19, 2026
Merged

Writeup of design question for 2322-MRTR#24
CaitieM20 merged 2 commits intomodelcontextprotocol:mainfrom
CaitieM20:mrtr-question

Conversation

@CaitieM20
Copy link
Copy Markdown
Contributor

This is a write up of a design discussion on SEP 2322-MRTR

Should requests for more information (i.e. the IncompleteResult response) be supported as a response type on to all ClientRequests, or should it be limited to a subset of Client Requests?


## Arguments for supporting `IncompleteResult` on all `ClientRequest`s
- Backwards compatibility with the current protocol version is preserved.
- It is simpler to implement in the Schema, one learning from Tasks is many edge cases were introduced by only supporting specific Requests & Responses.
Copy link
Copy Markdown

@LucaButBoring LucaButBoring Mar 11, 2026

Choose a reason for hiding this comment

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

To cite modelcontextprotocol/modelcontextprotocol#2229 for an example of how this impacted tool calls specifically (which have the additional execution.taskSupport to consider):

Today, tasks require their requestor to be cooperative, in the sense that the requestor of a task must explicitly opt into task-augmented execution on a request. This requires three steps:

  1. Check the receiver's task capabilities for the request type. If the capability is not present, the receiver does not support tasks.
  2. If the request is a tool call, invoke tools/list to retrieve the tool list, then check execution.taskSupport on the appropriate tool to determine if the tool supports tasks.
  3. Add a task parameter to the request with a desired task TTL to signal that the requestor wants to leverage tasks.

While this contract ensures that both the requestor and receiver understand their peer's capabilities and safely agree on the request and response formats in advance, it also has a few conceptual flaws:

  1. It requires requestors to explicitly check the capabilities of receivers. This introduces an unnecessary state contract that may be violated during mid-session deployments under the Streamable HTTP transport, and also raises concerns about the capability exchange growing in payload size indefinitely as more methods are supported.
  2. It requires a tool-specific behavior carveout which gets pushed onto the client to navigate. Related to this, it forces clients to cache a tools/list call prior to making any task-augmented tool call.
  3. It requires host application developers to explicitly choose to opt into task support from request to request, rather than relying on a single, consistent request construction path for all protocol operations.

One particular manifestation of this was modelcontextprotocol/python-sdk#1995, where the Python SDK not enabling the call capability led to rejections even if individual tools did in fact support tasks according to their taskSupport value.

I've also had to implement host integrations that needed to manage this tool-level negotiation explicitly twice now, and it's a lot to ask of host application developers to verify independently.


A non-task-related one is the carveout for ping being supported before initialize, discussed on Discord here - this conflicts with Transports S2.5.2 which states:

Servers that require a session ID SHOULD respond to requests without an MCP-Session-Id header (other than initialization) with HTTP 400 Bad Request.

As I noted in that thread on Discord, we've run into that ambiguity with session-enabled servers in the Python SDK previously.


Another one is simply initialize itself - this is how the TS SDK implements the logic for it - note that it is doing this in the transport implementation as there is initialize-specific behavior in sHTTP there.


These are individually tolerable annoyances, but request-specific behavior at any layer except that which can be encapsulated in a handler for that request seems like an error-prone pattern that we should avoid where possible.

Copy link
Copy Markdown
Contributor

@markdroth markdroth left a comment

Choose a reason for hiding this comment

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

Thanks for writing this up, Caitie!

- Backwards compatibility with the current protocol version is preserved.
- It is simpler to implement in the Schema, one learning from Tasks is many edge cases were introduced by only supporting specific Requests & Responses.
- Does not constraing future MCP Server implementors in how they can use `IncompleteResult` responses to request more information from the Client. We may not be able to come up with a good example today of why you would use it for a certain method but does not mean there is not a valid one.
- The edge case of an MCP Server sending an `IncompleteResult` response to a `ClientRequest` that the host does not expect or want to support has minimal impact. The Client can just choose to ignore the request. This is functionaly equivalent to a user refusing to provide the input ot elicitation request.
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.

While it's true that the client can choose not to reply to the elicitation request, this still means that the client has not actually gotten the response it wanted. For example, if the server sends an incomplete result in response to a tasks/list request, then the client has not actually gotten the task list it asks for, so it would effectively have to treat the request as a failure. If it actually needed the task list for something, how can it proceed?

Also, looking at this more broadly, I think that the MRTR incomplete result is just one example of a category of result types that I would call "horizontal" result types -- i.e., result types that are used for some general-purpose mechanism in the protocol that can be sent in response to many different types of requests. A result indicating that the server has started a task is another example of a horizontal result type, and I suspect there may be others in the future. I think we should choose an approach that we can use consistently for all horizontal result types.

I think there can actually be some fairly dangerous cases here. For example, consider what happens if we allow the server to send a result indicating that it's started a new task in response to a tasks/get request. That would basically make it impossible to ever get task status, and it could result in a huge number of tasks being created.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agree on the picking a single approach for horizontal result types. We will likely also having things with streaming, triggers that apply broadly.

I agree there can be malfunctioning MCP Servers, so I think the question is how much do we want to over regulate what servers can implement in the protocol vs providing reference implementations. In the case you provide above the server is the one shooting itself in the foot, and there are a lot of implementation details that can do that.

I tend to lean towards the protocol should not prevent a poorly implemented server / client from impacting itself thats the responsibility of the component dev but should ensure client & server interactions don't allow a client to cause massive issues on the server or vice versa.

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.

Sorry, I think maybe my comment tried to cover too much, with the result that it accidentally buried the main point I was trying to make.

The main point I was trying to make there is that I think it's incorrect to say that the the client can just ignore responses that it's not expecting. It's true that the client does not need to actually send a response to any input requests it receives, but that doesn't change the fact that the client has still not actually gotten the response it needed to the original request. This means that the client will have no choice but to treat that request as having failed, which effectively means that the protocol has failed.

With regard to the broader question about protocol design philosophy, I think it's worth reading RFC-9413, which describes the IETF Architecture Board's most recent thinking about protocol design based on decades of experience.

With that in mind, I don't think it's okay for the protocol to say that for a given request, servers are allowed to send an incomplete result in response but clients are not required to be able to handle that result, because then that case is basically a protocol failure. I think that for any given request, we need to either say that we don't allow servers to send the incomplete result, or that we require clients to handle the incomplete result. And if we know that it's basically going to be impossible to support a given result in response to a given request (e.g., the example of a task creation result in response to a tasks/get request), then it doesn't really make sense to require clients to support it.

Copy link
Copy Markdown

@LucaButBoring LucaButBoring Mar 15, 2026

Choose a reason for hiding this comment

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

If we state that there are some methods where any given "horizontal result" "makes sense," and others where it does not, it means that whenever we want to extend support for that result to a new method, we will be obligated to explicitly define the semantics for that result on that method such that we can justify it as "making sense" in the first place (either in the protocol change itself or in the SEP justifying it). If we have no such criteria, we're leaving the intended integration patterns up to the client/host and hoping that they've grasped the same underlying assumptions as us during implementation, or else they'll introduce their own edge cases. It seems like there is more underlying logic here than just "is this method supported? If not, raise an exception," because we're effectively declaring some class of methods where doing otherwise leads to strange protocol behaviors.

If we instead state that a given horizontal result needs to be handled universally, we are instead stating that those strange behaviors are in fact valid, and then our next consideration becomes if we anticipate a particular combination of method+result actually being used in practice, and whose responsibility it is if that behavior does not actually map to the use case at hand - the server in our example creating a task in response to tasks/get may have additional logic to only do this in certain cases, which would then make its behavior perfectly acceptable, if unconventional.

I'm not necessarily against a method allowlist, but if we want that approach to be maintainable from both a protocol and an implementation standpoint, we should define the exact class of methods that can support particular horizontal results in terms of something about that kind of method's properties, so we can structure our implementations of it accordingly. That also basically gives us a checklist we can use for each method to satisfy to figure out if it works in a given context or not.

In MRTR this behavior is changing to have Servers send `IncompleteResult` responses to `ClientRequest`s in order to request more information. One point of discussion is should we restrict the use of the `IncompleteResult` response to a subset of `ClientRequest`s or should we support it as a response for all `ClientRequest`s.

## Arguments for supporting `IncompleteResult` on all `ClientRequest`s
- Backwards compatibility with the current protocol version is preserved.
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.

Do we actually have reason to believe that there are existing users depending on the ability to do an elicitation request in response to (e.g.) tasks/get? Maybe we should do some research to see if anyone is actually doing this, much like the data that @pja-ant gathered for SEP-2260.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't know about Task/get, but I was thinking last night:

  • Task/Cancel could reasonably do an elicitation asking for the user to confirm, particularly if its all agent to agent. Like do you really want to take this destructive action.

Auth scenarios:

  • we use elicitation to do some Just in Time access patterns. This could in theory apply to any request like task/get

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.

That makes sense, and I'm totally open to supporting those cases.

To be clear, I'm really not so much arguing against any specific cases as much as I am arguing that we should consider these things on a case-by-case basis rather than saying that any result type can come in response to any request. I think it's clear that there are at least some cases that are clearly bad (e.g., a task creation result in response to a tasks/get request), which I think means that we have to consider individual cases to determine which ones make sense.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unclear about actual implementations (the spec notes suggest sending server-to-client requests on tasks/result instead), but doing elicitations on tasks/get is not only valid, I would argue it makes more sense if one's goals include avoiding connection timeouts. It's less of an issue now that SEP-1699 was accepted, but side-channeling on tasks/get was actually how I did the original implementation of tasks before getting pushback on that on the grounds that it required initiating SSE streams more often.


## Arguments for supporting `IncompleteResult` on all `ClientRequest`s
- Backwards compatibility with the current protocol version is preserved.
- It is simpler to implement in the Schema, one learning from Tasks is many edge cases were introduced by only supporting specific Requests & Responses.
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.

As per the point that several people made in the transport WG meeting yesterday, the schema does not solely define the protocol -- it would be fine for the schema to allow something but the protocol to say it's not actually legal.

I think we should restrict this question to be about what the protocol should allow, not what the schema encoding looks like. Once we decide that, we can talk about the schema as an implementation detail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

totally fair

- `UnsubscribeRequest`

- Be explicit to client implementors on when they need to support `IncompleteResult` responses, and handle surfacing requests for more information in the UI.
- This is a breaking change, but we are already making a breaking change in MRTR. We do not have data on what MCP Servers due today in this regard, assumed to be low.
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.

As per my comment above, I'm not actually sure whether this is a breaking change in practice. I think we should probably try to figure that out.

}
```

## Arguments for supporting `IncompleteResult` on a subset of `ClientRequest`s
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 there are a couple more arguments here:

  • If a client gets an unexpected response that they don't support, they basically have to treat the request as having failed. This means that we cannot simply say that clients can ignore these result types -- if we allow a result type for a given request, then all clients need to be able to handle those result types for that request, or else we're breaking interoperability.
  • We know that there are cases where some of these result types are likely to be problematic, such as a result that indicates that a task has been created in response to a tasks/get request. If we allow this in the protocol and then it causes a problem, removing it will be a breaking change. In contrast, if we err on the side of not allowing any case that we don't explicitly intend to, then we can easily add support for that case later if/when we encounter a use-case for it.

- Does not constraing future MCP Server implementors in how they can use `IncompleteResult` responses to request more information from the Client. We may not be able to come up with a good example today of why you would use it for a certain method but does not mean there is not a valid one.
- The edge case of an MCP Server sending an `IncompleteResult` response to a `ClientRequest` that the host does not expect or want to support has minimal impact. The Client can just choose to ignore the request. This is functionaly equivalent to a user refusing to provide the input ot elicitation request.
- Conforms to [MCP Design Principles](https://modelcontextprotocol.io/community/design-principles) of Composability by providing common building blocks vs the speficicity of only supporting it for a subset of Requests & Responses.
- All new `ClientRequest`s added in the future would need to be evaluated for whether they should support `IncompleteResult` responses, which adds cognitive overhead to future design and implementation.
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 we need to do this anyway. Whenever we add a new feature, we need to consider how it will behave in the context of existing features.

@CaitieM20
Copy link
Copy Markdown
Contributor Author

CaitieM20 commented Mar 18, 2026

Decision from Core-Maintainers meeting. We will go with Option 2, preference is to be more specific than branch out.

@CaitieM20 CaitieM20 merged commit 7716a23 into modelcontextprotocol:main Mar 19, 2026
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.

4 participants