ljppanda/mcp-longjobs
FreeMaintainedDurable, resumable operations for MCP servers via a client-agnostic facade: background tool execution with progress polling, mid-flight input, cooperative cance
About
Durable, resumable operations for MCP servers via a client-agnostic facade: background tool execution with progress polling, mid-flight input, cooperative cancellation, plus chunked file transfers with resume cursors and sha256 verification. Works on every MCP client today — no Tasks-extension support required.
README
Durable, resumable operations for MCP — long-running tasks and large files that survive timeouts, disconnects, and client restarts. On every client, today.
The problem
Three things break every MCP server that does real work:
- Long-running tool calls time out. Clients impose per-call timeouts (often 10–60s). A crawl, a build, a batch job fails — and the model's "retry" restarts the whole operation from scratch.
- Failures are unrepairable. A failed call returns a freeform error, so the model guesses: retry blindly, or give up. It can't fix one parameter and resume.
- Large files have no transfer story. Binary content is base64-in-JSON (33% overhead, hard message-size caps) or a bare URL with zero conventions — no chunking, no resume, no integrity checks.
The 2026-07-28 MCP spec added Tasks — async execution with mid-flight input and durable handles. But no client supports it yet, and the spec requires servers to refuse tasks for clients that didn't opt in. Every long-running server therefore needs a fallback path that works on today's clients. That is this package.
What you get
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { JsonFileSessionStore, withTasks, withFileTransfer, asToolRegistrar } from "mcp-longjobs";
const mcp = new McpServer({ name: "my-server", version: "1.0.0" });
const registrar = asToolRegistrar(mcp);
const store = new JsonFileSessionStore("./state/sessions.json");
const tasks = withTasks(registrar, { store });
tasks.taskTool("crawl-site", {
description: "Crawl a site and produce a report (takes minutes)",
inputSchema: { url: z.string(), maxPages: z.number().default(50) },
}, async (args, ctx) => {
for (const page of pages) {
if (ctx.signal.aborted) throw new Error("cancelled");
await ctx.progress(`Crawled ${page.url}`, done / total);
if (needsConfirmation(page)) {
const answer = await ctx.needInput({ prompt: `Include ${page.url}?`, choices: ["yes", "no"] });
if (answer === "no") continue;
}
}
return { summary, reportPath }; // small result for the model; big artifacts go through file transfer
});
withFileTransfer(registrar, { store, storageDir: "./state/blobs" });
What the model experiences on today's clients (no Tasks support required):
crawl-sitereturns instantly with ataskIdand instructions to polldurable_task_get— no more timeouts.- Polls show live progress:
{ "status": "working", "progress": { "message": "Crawled /pricing", "fraction": 0.4 } }. - Mid-flight questions pause the task as
input_required; the model answers viadurable_task_respondand the task continues where it stopped. - Client crash? New session?
durable_task_getwith the sametaskIdstill works — state lives in the store, not in the connection. durable_task_cancelaborts the work cooperatively at its next checkpoint.
Failures are data, not protocol errors — a structured envelope the model can repair in one round-trip:
{
"status": "failed",
"error": {
"code": "offset_mismatch",
"message": "Expected offset 131072, got 0.",
"retryable": true,
"recoveryHint": "Do NOT resend the whole file. Re-send this chunk starting at offset 131072.",
"partial": { "cursor": 131072 }
}
}
Packages (subpath exports)
| Import | Purpose |
|---|---|
mcp-longjobs/tasks |
withTasks() + the durable_task_* facade: background execution, progress, mid-flight input, cooperative cancellation |
mcp-longjobs/files |
withFileTransfer(): chunked upload/download, resume cursor, sha256 verification, path-safety roots |
mcp-longjobs/core |
Session model, pluggable stores (memory, JSON file), structured error envelope |
Design notes
- Bytes never flow through the model. The model sees metadata only: handle, size, sha256, progress. Chunks through tool calls are for small-to-medium payloads; large files should move out-of-band (TUS endpoint planned) with the model verifying integrity.
- The model is the director, not the courier. Facade tool results carry their own instructions ("call
durable_task_getwith this id", "resume at offset N"), so any capable model can drive the protocol with zero host-side support. - Failures are repairable data. Every failure carries
code,retryable,recoveryHint, andpartial.cursor— what went wrong, whether a retry can work, what to do instead, and what already succeeded. - Lifecycle vocabulary matches the spec.
working / input_required / completed / failed / cancelled, so the native adapter can slot in later without breaking changes.
Status
| Component | Status |
|---|---|
| Tasks fallback facade (progress / input / cancel) | ✅ implemented |
| Durable session stores (memory, JSON file, SQLite) | ✅ implemented |
| Session TTL expiry (lazy, repairable envelope) | ✅ implemented |
| Chunked file transfer with resume + checksums | ✅ implemented |
Native ext-tasks adapter (CreateTaskResult / tasks/get) |
🔜 tracks the SDK's experimental Tasks API |
| TUS 1.0 out-of-band endpoint for large files | 🔜 planned — see mcp#189 |
| Redis store, Python port | 🔜 planned |
Quickstart
git clone https://github.com/ljppanda/mcp-longjobs
cd mcp-longjobs
npm install && npm run build
node dist/examples/report-generator.js
(Once published to npm, the same server runs with a single command: npx mcp-longjobs.)
Point your client at it (stdio):
{
"mcpServers": {
"report-generator": {
"command": "node",
"args": ["/absolute/path/to/mcp-longjobs/dist/examples/report-generator.js"]
}
}
}
Then ask: "Generate a report on EV batteries with 3 sections." Watch the model start the job, poll durable_task_get, and pick up the result. Kill the client mid-run, restart it, and ask for the same taskId — it resumes.
Development
npm install
npm test # vitest
npm run build # tsc -> dist/
npm run example # build + run the demo server
Contributing
PRs welcome — especially: store backends (SQLite/Redis), the native ext-tasks adapter, and the TUS endpoint. Please open an issue first for anything larger.
License
Installing ljppanda/mcp-longjobs
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/ljppanda/mcp-longjobsFAQ
Is ljppanda/mcp-longjobs MCP free?
Yes, ljppanda/mcp-longjobs MCP is free — one-click install via Unyly at no cost.
Does ljppanda/mcp-longjobs need an API key?
No, ljppanda/mcp-longjobs runs without API keys or environment variables.
Is ljppanda/mcp-longjobs hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install ljppanda/mcp-longjobs in Claude Desktop, Claude Code or Cursor?
Open ljppanda/mcp-longjobs on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare ljppanda/mcp-longjobs with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
