Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Proposal: non-blocking elicitations piggybacking on Tasks #1869

@robertyates

Description

@robertyates

would be great to get feedback on this, we will likely do this as a workaround to blocking elicitations calls

Problem

At the moment elicitations are blocking requests where the tool call is blocked client side. Tasks gets close to addressing, the tool call is no longer blocking but when Tasks go into the input_needed state they again becoming client and server blocking requests waiting on user input. While this works for stdio and dedicated user processes, it does not work when done at scale server side.

Proposal

This came out of a conversation I had with Claude on how to address

Non-Blocking Elicitations in MCP Tasks: Implementation Specification

Solution Overview

Use the _meta extension point in the MCP specification to embed elicitation requests directly in task responses, enabling fully non-blocking user input collection.

Architecture

  1. Capability Negotiation
    Server declares task support for tools:
{
  "capabilities": {
    "tasks": {
      "requests": {
        "tools.call": true
      }
    }
  }
}

Server indicates which tools should use tasks:

{
  "tools": [
    {
      "name": "book_dinner",
      "description": "Book a dinner reservation",
      "inputSchema": { ... },
      "annotations": {
        "taskHint": "always"
      }
    }
  ]
}
  1. Initial Tool Call with Task Augmentation
    Client sends task-augmented tool call:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "book_dinner",
    "arguments": {
      "date": "2025-11-22",
      "time": "19:00"
    },
    "task": {}
  }
}
  1. Server Returns CreateTaskResult with Embedded Elicitation
    Server responds immediately (non-blocking) with elicitation in _meta:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "taskId": "task-dinner-123",
    "status": "input_required",
    "createdAt": "2025-11-21T10:00:00Z",
    "_meta": {
      "io.modelcontextprotocol/related-task": {
        "taskId": "task-dinner-123"
      },
      "io.modelcontextprotocol/pending-elicitations": [
        {
          "elicitationId": "elicit-partysize-456",
          "mode": "form",
          "message": "How many people will be dining?",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "partySize": {
                "type": "number",
                "minimum": 1,
                "maximum": 20,
                "title": "Number of guests"
              }
            },
            "required": ["partySize"]
          }
        }
      ],
      "io.modelcontextprotocol/model-immediate-response": "Checking availability for your reservation..."
    }
  }
}

Key Points:

Server returns immediately, no blocking
Status is input_required
Elicitation embedded in _meta under custom key io.modelcontextprotocol/pending-elicitations
Optional model-immediate-response provides context for the LLM while waiting

  1. Client Presents Elicitation to User
    Client extracts elicitation from _meta and displays form/prompt to user. No blocking calls needed.
  2. Client Submits Elicitation Response via tasks/get
    Client sends elicitation response embedded in tasks/get:
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tasks/get",
  "params": {
    "taskId": "task-dinner-123",
    "_meta": {
      "io.modelcontextprotocol/elicitation-response": {
        "elicitationId": "elicit-partysize-456",
        "action": "accept",
        "content": {
          "partySize": 4
        }
      }
    }
  }
}

Why tasks/get?

Already part of the polling pattern clients use
Single round-trip: submit response + get updated status
Server can choose to acknowledge the response in the same call
Non-blocking: doesn't wait for task completion

  1. Server Processes Response and Completes Task
    Server responds with updated task status:
    Option A - Task still working:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "taskId": "task-dinner-123",
    "status": "working",
    "createdAt": "2025-11-21T10:00:00Z",
    "_meta": {
      "io.modelcontextprotocol/related-task": {
        "taskId": "task-dinner-123"
      }
    }
  }
}

Option B - Task completed immediately:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "taskId": "task-dinner-123",
    "status": "completed",
    "createdAt": "2025-11-21T10:00:00Z",
    "_meta": {
      "io.modelcontextprotocol/related-task": {
        "taskId": "task-dinner-123"
      }
    }
  }
}
  1. Client Retrieves Final Result
    If task is completed, client calls tasks/result:
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tasks/result",
  "params": {
    "taskId": "task-dinner-123"
  }
}

Server returns the tool result:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "✓ Reservation confirmed for 4 people on November 22nd at 7:00 PM at Chez Claude. Confirmation #: RES-2024-789"
      }
    ]
  }
}

Complete Flow Diagram

1. Client → Server: tools/call with task augmentation
2. Server → Client: CreateTaskResult (status=input_required) with elicitation in _meta
   ✓ Server is NON-BLOCKING, can handle thousands of waiting tasks

3. [User interacts with elicitation form]

4. Client → Server: tasks/get with elicitation response in _meta
5. Server → Client: Task (status=working or completed)
   ✓ Still NON-BLOCKING

6. [If status=completed]
   Client → Server: tasks/result
7. Server → Client: Tool result

Total round-trips: 3 (vs. 4+ with traditional blocking elicitation)
Extension Keys
Custom _meta Keys Used

Compatibility

Spec-compliant: Uses only standard MCP task features + _meta extensions
Graceful degradation: Clients that don't understand custom _meta keys can still poll with tasks/get and call tasks/result when status is input_required (falls back to blocking behavior)
No protocol changes: Doesn't require changes to the MCP specification itself

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions