Skip to content

Conversation

@felixweinberger
Copy link
Contributor

Motivation and Context

How Has This Been Tested?

Breaking Changes

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

LucaButBoring and others added 30 commits October 22, 2025 14:47
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 26, 2025

Open in StackBlitz

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

commit: 4128d4d

LucaButBoring and others added 5 commits November 26, 2025 10:18
Responses and errors were incorrectly going through the original stream path instead of being queued. Also, extra.sendRequest was not setting the input_required status. These issues have been fixed and tests have been added/updated for them.

Sequence diagram of the intended flow:

```mermaid
sequenceDiagram
    participant C as Client Protocol
    participant CT as Client Transport
    participant ST as Server Transport
    participant S as Server Protocol
    participant TQ as TaskMessageQueue
    participant TS as TaskStore
    participant H as Async Handler

    Note over C,H: Phase 1: Task Creation
    activate C
    C->>CT: tools/call { task: { ttl: 60000 } }
    activate CT
    CT->>ST: HTTP POST
    activate ST
    ST->>S: _onrequest()
    activate S
    S->>TS: createTask()
    activate TS
    TS-->>S: Task { taskId, status: 'working' }
    deactivate TS
    S--)H: Start async handler (non-blocking)
    activate H
    S-->>ST: CreateTaskResult { task }
    deactivate S
    ST-->>CT: HTTP Response
    deactivate ST
    CT-->>C: CreateTaskResult
    deactivate CT
    deactivate C

    Note over C,H: Phase 2: Server Queues Elicitation Request
    H->>S: extra.sendRequest(elicitation, { relatedTask })
    activate S
    S->>TQ: enqueue({ type: 'request', message: elicitation })
    activate TQ
    TQ-->>S: OK
    deactivate TQ
    S->>S: Store resolver in _requestResolvers
    Note over S: Promise waiting...
    deactivate S
    H->>TS: updateTaskStatus('input_required')
    activate TS
    TS-->>H: OK
    deactivate TS
    Note over H: Blocked awaiting elicitation response

    Note over C,H: Phase 3: Client Polls Status
    activate C
    C->>CT: tasks/get { taskId }
    activate CT
    CT->>ST: HTTP POST
    activate ST
    ST->>S: _onrequest(GetTask)
    activate S
    S->>TS: getTask(taskId)
    activate TS
    TS-->>S: Task { status: 'input_required' }
    deactivate TS
    S-->>ST: Task
    deactivate S
    ST-->>CT: HTTP Response
    deactivate ST
    CT-->>C: Task { status: 'input_required' }
    deactivate CT
    deactivate C

    Note over C,H: Phase 4: Client Fetches Queued Messages
    activate C
    C->>CT: tasks/result { taskId }
    activate CT
    CT->>ST: HTTP POST
    activate ST
    ST->>S: _onrequest(GetTaskPayload)
    activate S
    S->>TQ: dequeue(taskId)
    activate TQ
    TQ-->>S: { type: 'request', message: elicitation }
    deactivate TQ
    S->>ST: send(elicitation, { relatedRequestId })
    ST-->>CT: SSE Event: elicitation request
    Note over S: Handler blocks (task not terminal)

    Note over C,H: Phase 5: Client Handles & Responds
    CT->>C: _onrequest(elicitation)
    activate C
    Note over C: Extract relatedTaskId from _meta
    C->>C: Call ElicitRequestSchema handler
    C->>C: Check: relatedTaskId && _taskMessageQueue
    Note over C: _taskMessageQueue is undefined
    C->>CT: transport.send(response)
    CT->>ST: HTTP POST (elicitation response)
    deactivate C

    Note over C,H: Phase 6: Server Receives Response, Resolves Promise
    ST->>S: _onresponse(elicitation response)
    S->>S: Lookup resolver in _requestResolvers
    S->>S: resolver(response)
    Note over S: Promise resolves
    S-->>H: Elicitation result { action: 'accept', content }
    Note over H: Resumes execution

    Note over C,H: Phase 7: Task Completes
    H->>TS: storeTaskResult('completed', finalResult)
    activate TS
    TS-->>H: OK
    deactivate TS
    deactivate H
    Note over S: GetTaskPayload handler wakes up
    S->>TS: getTask(taskId)
    activate TS
    TS-->>S: Task { status: 'completed' }
    deactivate TS
    S->>TS: getTaskResult(taskId)
    activate TS
    TS-->>S: CallToolResult
    deactivate TS
    S-->>ST: Return final result
    deactivate S
    ST-->>CT: SSE Event: CallToolResult
    deactivate ST
    CT-->>C: CallToolResult { content: [...] }
    deactivate CT
    deactivate C
```
@felixweinberger felixweinberger changed the title Fweinberger/tasks experimental isolation refactor: isolate tasks implementation in an experimental Nov 26, 2025
@felixweinberger felixweinberger force-pushed the fweinberger/tasks-experimental-isolation branch 3 times, most recently from f0a6f60 to 797e23e Compare November 26, 2025 22:31
@felixweinberger felixweinberger added the needs more work Not ready to be merged yet, needs additional follow-up from the author(s). label Nov 26, 2025
Phase 1-2 of tasks experimental isolation:
- Create src/experimental/tasks/ directory structure
- Move TaskStore, TaskMessageQueue, and related interfaces to experimental/tasks/interfaces.ts
- Add experimental/tasks/types.ts for re-exporting spec types
- Update shared/task.ts to re-export from experimental for backward compatibility
- Add barrel exports for experimental module

All tests pass (1399 tests).
Restore callTool() to its original implementation instead of delegating
to experimental.tasks.callToolStream(). This aligns with Python SDK's
approach where call_tool() is task-unaware and call_tool_as_task() is
the explicit experimental method.

Changes:
- Add guard for taskSupport: 'required' tools with clear error message
- Restore original output schema validation logic
- Add _cachedRequiredTaskTools to track required-only task tools
- Remove unused takeResult import

Tools with taskSupport: 'optional' work normally with callTool() since
the server returns CallToolResult. Only 'required' tools need the
experimental API.
@felixweinberger felixweinberger force-pushed the fweinberger/tasks-experimental-isolation branch from 6993f64 to 4128d4d Compare November 27, 2025 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs more work Not ready to be merged yet, needs additional follow-up from the author(s).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants