Long-running MCP calls (tasks)

Call Liyagent's own /mcp tools as MCP tasks, poll and cancel them, and answer the questions a task asks while it runs.

After reading this page you will be able to:
  • Start a tool call as a task
  • Poll, collect and cancel it
  • Answer an input_required task
  • Explain who can see a task

Liyagent serves its own tools at /mcp (Streamable HTTP), for MCP clients such as Claude Desktop, Cursor or your own agents. Some calls take minutes. The longest is ask_agent, which runs a whole agent. Liyagent supports the tasks utility from MCP 2025-11-25 for these calls: the client gets a task at once, then polls for it and collects the result when it is ready.

A call without a task is answered exactly as before. Clients that do not use tasks see no change.

Start a task

initialize advertises "tasks": {"list": {}, "cancel": {}, "requests": {"tools": {"call": {}}}}, and every tool in tools/list carries "execution": {"taskSupport": "optional"}. To run a call as a task, add task to its params:

{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "ask_agent", "arguments": {"agent": "it-helpdesk", "message": "..."},
            "task": {"ttl": 3600000}}}

The answer is a CreateTaskResult: {"task": {"taskId", "status": "working", "createdAt", "lastUpdatedAt", "ttl", "pollInterval"}}. The call has already passed the same checks as any other call: kill switch, rate limits, Cedar, budget, data policies and guardrails. They apply to the work the task runs.

MethodWhat it does
tasks/getThe task's status: working, input_required, completed, failed or cancelled
tasks/resultThe tool's result once the task ends, tagged _meta["io.modelcontextprotocol/related-task"]. With Accept: text/event-stream it streams until then, including any question the task asks. Without a stream it waits up to mcp_task_result_wait_s (30 s)
tasks/listYour tasks, 50 at a time (nextCursor)
tasks/cancelEnds the task as cancelled and stops the agent run it wraps at its next step. A task that already ended answers -32602

A tool result with isError ends the task as failed.

Answer a question mid-task

When a server that the asked agent calls needs something from the user (an MCP elicitation/create), the task moves to input_required. Call tasks/result with Accept: text/event-stream. The stream carries the request:

{"jsonrpc": "2.0", "id": "tiq-task-<taskId>-1", "method": "elicitation/create",
 "params": {"mode": "form", "message": "Which cost centre?", "requestedSchema": {...},
            "_meta": {"io.modelcontextprotocol/related-task": {"taskId": "<taskId>"}}}}

POST your answer back to /mcp as a JSON-RPC response with the same id: {"jsonrpc": "2.0", "id": "tiq-task-<taskId>-1", "result": {"action": "accept", "content": {...}}}. The task then continues and the stream ends with the result.

The same rules apply as when a person answers in the console. A form that asks for a password, token, key or card number is declined without being shown to you. A URL must be public HTTPS. Your answer is checked against the form before the server sees it, and an answer that does not fit is a decline. No answer within mcp_task_input_timeout_s (10 minutes) is also a decline.

Who can see a task

A task belongs to the caller that started it: the agent credential, or the app and the person signed in to it, in their tenant. Any other caller gets -32602 for that task id, as if it did not exist, and cannot answer its questions. tasks/list lists only your own.

Limits and durability

  • Tasks are stored in the database, so any replica answers tasks/get. The work runs in the process that accepted the call and reports that it is alive every 10 seconds. If that process stops, the task is reported as failed ("interrupted"), not left working.
  • mcp_task_max_ttl_ms caps how long a task is kept (default 24 hours). mcp_task_max_open caps a caller's open tasks (default 20). A call over the cap is refused with refused_by: task_limit.
  • mcp_tasks_enabled turns tasks off.
  • The audit log records mcp.task.created, .completed, .failed, .cancelled, .input_required and .input_answered under the caller's name. Answers are never recorded.