fix(everything): use relatedTask option to enable elicitation over HTTP (#3228)#3816
Conversation
|
@claude review |
|
Claude finished @cliffhall's task in 1m 33s —— View job Code Review
Overall: The fix is correct, minimal, and well-motivated. The one-line change to
|
a207af5 to
67a71f1
Compare
|
Hi @cliffhall, I’ve reviewed Claude’s comments. The only issue he suggested fixing is: However, this pattern is commonly used in other tests, so I don’t think it needs to be changed: Please let me know if you have a different opinion. |
cliffhall
left a comment
There was a problem hiding this comment.
Review — relatedTask for elicitation over HTTP
I pulled this PR down, rebased it onto current main, and verified it end-to-end. The approach is correct and the fix is the right one. Details and a few non-blocking observations below.
Verification
- Rebased onto current
main(the branch was ~25 commits behind, including the zod v4 / SDK^1.29.0upgrade). It rebased cleanly, no conflicts. npm run buildclean;vitest61 passing, including the two new tests here.- End-to-end over Streamable HTTP (SDK 1.29 client): task goes
working → input_required, theelicitation/createis delivered, the client'sacceptresolves, and the final report shows the real "After receiving clarification..." path — not the "unavailable" fallback. - End-to-end over stdio: same result, so adding
relatedTaskdoes not regress the transport that already worked. - Confirmed in Inspector V2, which handles
input_requiredcorrectly (as does Inspector V1 via the author's #1174).
The mechanism is right
The PR description nails it: the issue (#3228) assumed the new streaming elicitInputStream API (SDK #1210) was required, but { relatedTask: { taskId } } on the existing sendRequest is sufficient and simpler. For reference, both are now available in SDK 1.29 (#1210 merged 2026-02-11), but relatedTask is the lighter-weight, spec-aligned choice here — it enqueues the request and delivers it through the tasks/result stream on the client's poll, matching the spec's input_required sequence. No reason to reach for elicitInputStream for this.
The input_required → (elicit) → working status transitions are spec-compliant, and the comment/report-text updates accurately describe the new behavior.
Non-blocking: capability check is now too loose for form-mode
This is pre-existing (not introduced by this PR), but it becomes more relevant now that the elicitation path actually runs on HTTP:
const clientSupportsElicitation = clientCapabilities.elicitation !== undefined;
...
ambiguous: validatedArgs.ambiguous && clientSupportsElicitation,Under SDK ≥1.29's getSupportedElicitationModes, a client that declares elicitation: { url: {} } (URL-mode only) but not form is treated as not supporting form mode — so this tool's form-mode elicitation/create is rejected with -32602 Client does not support form-mode elicitation requests, and the try/catch degrades to the default interpretation. It won't crash, but such a client silently won't get the disambiguation prompt. Consider gating on form-mode support specifically (the SDK exports getSupportedElicitationModes(capabilities.elicitation)) rather than "any elicitation capability." Reasonable as a follow-up rather than a blocker.
Nits (all optional)
- Tests assert the contract, not the delivery. The two new tests mock
sendRequestand assertrelatedTaskis passed (good — that's the actual code change) and that non-ambiguous calls don't elicit. They don't exercise real cross-transport delivery, which is the point of the fix — that's inherently the SDK's job, and I covered it with the e2e runs above. Might be worth a line in the PR noting manual HTTP verification. - Stronger assertion available. The first test could also assert the accepted clarification propagates into the stored result (e.g.
storeTaskResultreceives a report containing the chosen interpretation), locking in the accept-handling path, not just the request shape. - Fallback string. The catch hardcodes
"technical (default - elicitation unavailable)"; minor, and consistent with the existing decline/cancel strings.
Verdict
LGTM on substance — correct mechanism, verified on stdio + Streamable HTTP, spec-compliant flow. The only thing it strictly needs is a rebase onto current main (clean, as noted). The form-mode capability gating is worth a follow-up but isn't a blocker for the demo's happy path.
|
Thanks for this, @galagaevdc |
Summary
Fixes #3228 — elicitation in
simulate-research-querynow works on all transports (STDIO, SSE, Streamable HTTP).The original issue assumed
elicitInputStream(a new SDK API) was required for HTTP support. It turns out the existing SDK already has the mechanism: passing{ relatedTask: { taskId } }tosendRequest.Root Cause
When
sendRequestis called from the backgroundrunResearchProcesswithoutrelatedTask, the SDK sends the message directly through the transport using therelatedRequestIdof the originaltools/callrequest. On HTTP, that response stream is already closed (theCreateTaskResultwas already returned), so the elicitation silently fails.Fix
Pass
{ relatedTask: { taskId } }as the third argument tosendRequestwhen sendingelicitation/create. WhenrelatedTaskis set, the SDK:_taskMessageQueueinstead of sending via transport directlytasks/result(as the spec says it SHOULD upon seeinginput_required), the SDK dequeues and delivers the message through thetasks/resultSSE streamsendRequestresolvesThis matches the spec's sequence diagram for the
input_requiredflow: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#input-required-statusChanges
simulate-research-query.ts: add{ relatedTask: { taskId } }to thesendRequestcall; update comments and report text to remove the outdated "fails on HTTP" messagingtools.test.ts: add two tests verifying thatsendRequestreceivesrelatedTaskfor ambiguous queries, and is not called for non-ambiguous queriesTransport support after fix
Note
There is a bug in MCP inspector, so it will be impossible to test using it. I've already prepared fix for MCP inspector, but it's not merged yet modelcontextprotocol/inspector#1174