Conversation
| * **Proposed Stateless Flow:** Mark presented a new pattern where the server | ||
| immediately terminates the initial request with a response indicating "more | ||
| information needed." The client then sends a completely new request containing | ||
| the original information plus the elicitation response. |
There was a problem hiding this comment.
Did anyone discussed about how this would be implemented? This doesn't seem easy to implement gracefully.
As a developer, I want to write my code once, with a certain logical flow, and if there's an elicitation, I expect to resume on that line. But with this flow, how is the code supposed to happen? Halt the execution at the point, and continue on that point on a different process doesn't seem a good idea.
There was a problem hiding this comment.
This would be a pretty fundamental change to the tool authoring model for all developers, and would almost certainly be a big breaking change (I also brought this up last month when it was discussed in SFO). With that said, I think it might be possible to shim existing code onto it by essentially "replaying" tool calls on each re-attempt.
Say you have something like this in a tool:
1: Begin tool call
2: Validate arguments
3: Send elicitation
4: Receive elicitation results
5: Send tool call result
If any step yields to the client, the entire sequence needs to be repeated from the beginning on the next tool call, because we assume (in general) that servers retain no state. The client makes request 1, and we get to step 3 (+ means the line was executed):
+ 1: Begin tool call
+ 2: Validate arguments
+ 3: Send elicitation
4: Receive elicitation results
5: Send tool call result
...and then the server rejects with (some equivalent of) an elicitation request. Then, the client makes request 2 (with an elicitation response squashed into the request this time), and we execute steps 1-3 again, except that at step 3, the server already has the elicitation response and continues to step 5:
++ 1: Begin tool call
++ 2: Validate arguments
++ 3: Send elicitation
+ 4: Receive elicitation results
+ 5: Send tool call result
This isn't perfect, though, and it'd be safer with a state store of some kind to be able to somehow snapshot the request state after request 1. In my example above (which is not representative of what Mark has in mind), I'm assuming that everything before sending the elicitation is idempotent, however. In reality, it may not be.
There was a problem hiding this comment.
Though actually, I think that my own idea here would conflict directly with how Tasks work, which could complicate things. In general, almost nothing you would want to wrap in a Task is naturally-idempotent like this, unless you choose to front-load elicitation/sampling and have all non-idempotent logic occur after receiving the responses (and typically sampling is used at the end of an operation).
There was a problem hiding this comment.
Your speculation is what I've thought, which doesn't seem a good idea. And as you said in your own message at the end, the user may have a different execution by their own logic, so replying the steps will not be safe.
| * **Overlap Analysis:** The group identified that the "Tasks" mechanism | ||
| currently duplicates some functionality discussed in the multi-roundtrip | ||
| proposal, specifically regarding polling and state retention. | ||
| * **Unified Vision:** Caitie McCaffrey noted that Tasks and Tool Calls are | ||
| similar but necessary; she described Tasks as a "promise over a network" and | ||
| suggested that the handling of elicitation responses in both systems needs to | ||
| align. |
There was a problem hiding this comment.
The tricky piece here (I think) is that it becomes ambiguous how the "more information needed" rejection works when something is fragmented across multiple discrete polling requests. It makes sense that (for example) if an elicitation is required when creating a task, it would be handled exactly like a normal tool call and the creation step is what would "pause," but if an elicitation is required after that it would need to be delivered some other way.
It should technically be fine to do the rejection on a polling request, but then that becomes stateful in an awkward orthogonal way to how multi-roundtrip is intended to work. In particular, you can't just replay or snapshot an entire Task's state (consider that it's often offloaded to another service altogether), so this puts you in a tangled state situation where it's unclear where the elicitation/sampling result actually needs to go. I'll need to work through some examples to figure this out, I think.
| the client to make a completely independent request that must be manually | ||
| linked back to the original context by the server. | ||
| * **Proposed Stateless Flow:** Mark presented a new pattern where the server | ||
| immediately terminates the initial request with a response indicating "more |
There was a problem hiding this comment.
I need to think through this more, but this execution path could also be an answer to both the ask for supporting immediate result acceptance in Tasks (modelcontextprotocol/modelcontextprotocol#1905) and being able to "surprise" a client with a Task - implementing this flow would potentially require some sort of dynamic response handling in a way that Tasks don't currently require, in that the client needs to not be waiting for a specific result type. Not certain yet though, just throwing the idea out there right now.
add 2026-01-21 weekly meeting notes