-
Notifications
You must be signed in to change notification settings - Fork 0
feat: MCP async task support for long-running ingest operations #485
Description
Summary
The MCP November 2025 spec update added first-class async task support. libscope's MCP server currently runs all operations synchronously — ingest, re-index, and connector sync block the client for their full duration. For large document sets or slow embedding models, this times out or degrades MCP client UX. Adopting async tasks makes these operations non-blocking and observable.
Why
- Indexing a large document or running a connector sync can take 10–60+ seconds with a local embedding model. Blocking the MCP transport for this is a bad experience.
- The MCP spec now provides
tasks/create,tasks/get, andtasks/cancelprimitives for exactly this use case. - Async ingest would also enable progress reporting (chunks processed, embeddings queued) which is currently invisible to MCP clients.
- This is spec compliance work, not a new concept — the design is prescribed by the protocol.
Affected MCP tools
These tools should become async-capable:
| Tool | Current | Target |
|---|---|---|
index_document |
sync | async (returns task ID immediately) |
reindex_library |
sync | async |
sync_connector |
sync | async |
install_pack |
sync | async |
Tools that are inherently fast (search, get_document, list_documents, rate_chunk) remain synchronous.
Proposed design
Task lifecycle
client calls index_document(url, content)
→ MCP server returns { taskId: "task_abc123", status: "queued" }
→ client polls tasks/get(taskId) or subscribes to task events
→ server streams progress: { status: "running", progress: { chunks: 12, total: 45 } }
→ server completes: { status: "done", result: { documentId: "doc_xyz", chunkCount: 45 } }
Implementation approach
- Add a lightweight in-memory task queue (
src/mcp/tasks.ts) tracking status, progress, and result per task ID. - Wrap long-running tool handlers to enqueue work and return a task reference immediately.
- Register
tasks/getandtasks/cancelMCP handlers. - Emit progress updates via MCP notifications during indexing (hook into
src/core/indexing.tsprogress callbacks).
Task store interface
interface Task {
id: string;
tool: string;
status: "queued" | "running" | "done" | "failed" | "cancelled";
progress?: { current: number; total: number; message?: string };
result?: unknown;
error?: string;
createdAt: string;
updatedAt: string;
}Tasks are in-memory only (no persistence across server restarts). TTL: 1 hour after completion.
Backward compatibility
Async behavior should be opt-in at the tool call level via a parameter:
{ "tool": "index_document", "arguments": { "url": "...", "async": true } }Default remains synchronous (async: false) so existing integrations aren't broken.
Acceptance criteria
-
src/mcp/tasks.ts— in-memory task store with TTL cleanup -
tasks/getMCP handler: returns task status and progress -
tasks/cancelMCP handler: cancels queued/running tasks -
index_documentsupportsasync: trueparameter — returns task reference -
reindex_librarysupportsasync: true -
sync_connectorsupportsasync: true -
install_packsupportsasync: true - Progress events emitted during indexing (chunks embedded, total expected)
- Existing synchronous behavior unchanged when
asyncis not set orfalse - Unit tests for task store (create, progress update, complete, cancel, TTL cleanup)
- Integration test: async index_document returns task ID, polling resolves to completion
References
- MCP November 2025 spec update (Dave Patten)
- MCP 2026 roadmap
- Existing MCP tools:
src/mcp/tools/ - Indexing pipeline:
src/core/indexing.ts