Skip to content

Conversation

@felixweinberger
Copy link
Contributor

@felixweinberger felixweinberger commented Nov 27, 2025

Fixes a bug discovered during manual testing of Tasks (SEP-1686):

requestStream() polled tasks/get but never called tasks/result during non-terminal states, so side-channel messages (elicitation, sampling) were never delivered to the client, causing interactive tools to hang indefinitely at input_required state.

Motivation and Context

After merging #1041, manual testing revealed that interactive tools (elicitation/sampling) would hang indefinitely because the client never received the elicitation request messages.

How Has This Been Tested?

  • Manual testing with simpleTaskInteractive.ts server and client examples
  • Added integration test callToolStream with elicitation in taskLifecycle.test.ts
  • All tests pass

With the example clients added here:

Before After
CleanShot 2025-11-27 at 20 18 28 CleanShot 2025-11-27 at 20 32 02

Breaking Changes

None

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The fix adds input_required handling to the polling loop. When the task status is input_required, we call tasks/result which delivers queued messages via SSE and blocks until the task reaches a terminal state. This preserves the polling loop's ability to yield intermediate status updates while ensuring side-channel messages are delivered.

@felixweinberger felixweinberger requested a review from a team as a code owner November 27, 2025 20:13
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 27, 2025

Open in StackBlitz

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/sdk@1185

commit: b95b381

@felixweinberger felixweinberger force-pushed the fweinberger/tasks-experimental-isolation branch from 436d05b to 0601671 Compare November 27, 2025 20:15
@felixweinberger felixweinberger changed the title fix: deliver side-channel messages via callToolStream fix: call tasks/result to deliver side-channel messages Nov 27, 2025
Copy link

@He-Pin He-Pin left a comment

Choose a reason for hiding this comment

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

This PR needs to update the server side to make it behave as the spec just said, the client should and will see an input_required asap the server requires it to make the task progress.

…ssages

Add input_required handling to the polling loop in requestStream().
When task status is input_required, call tasks/result to deliver
queued messages (elicitation, sampling) via SSE and block until terminal.
@felixweinberger felixweinberger force-pushed the fweinberger/tasks-experimental-isolation branch from 0601671 to 9608c03 Compare November 27, 2025 20:29
@felixweinberger
Copy link
Contributor Author

This PR needs to update the server side to make it behave as the spec just said, the client should and will see an input_required asap the server requires it to make the task progress.

Updated the PR and tested it again with the 2 examples:

// server
npx tsx src/examples/server/simpleTaskInteractive.ts

// client
npx tsx src/examples/client/simpleTaskInteractiveClient.ts

Which results in:

CleanShot 2025-11-27 at 20 32 02

@He-Pin
Copy link

He-Pin commented Nov 27, 2025

btw, your implementations in your work probably all use databases, right? To be honest, I haven't fully implemented the server-side logic yet... It's much more complex than I imagined, because I also have various connectors as tools.

const result = await this.getTaskResult({ taskId }, resultSchema, options);
yield { type: 'result', result };
return;
}
Copy link

Choose a reason for hiding this comment

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

cool

@felixweinberger
Copy link
Contributor Author

@LucaButBoring would be great to get your thoughts on this one.

@LucaButBoring
Copy link
Contributor

LucaButBoring commented Nov 27, 2025

Looks good, sorry about that — there was an existing integ test that checked for the input_required state change, but I didn't make it use requestStream (it explicitly called tasks/result) so it didn't catch this.

@felixweinberger
Copy link
Contributor Author

Looks good, sorry about that — there was an existing integ test that checked for the input_required state change, but I didn't make it use requestStream (it explicitly called tasks/result) so it didn't catch this.

Should we be doing anything in particular in the working state? I assume in the working state we don't need to call tasks/result preemptively and can just continue polling?

@LucaButBoring
Copy link
Contributor

That is correct — working means to just keep polling

@He-Pin
Copy link

He-Pin commented Nov 27, 2025

only swith to tasks/result when the state changed to failed, completed, input_required

@He-Pin
Copy link

He-Pin commented Nov 27, 2025

Does the inspector depends on this sdk?I want to use it asap

@felixweinberger felixweinberger merged commit 42a2f41 into main Nov 28, 2025
10 checks passed
@felixweinberger felixweinberger deleted the fweinberger/tasks-experimental-isolation branch November 28, 2025 11:46
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.

5 participants