Expose Celery tasks as async MCP tools with automatic lifecycle management
Project description
mcp-celery
Expose Celery tasks as asynchronous MCP tools — with automatic lifecycle management and no boilerplate.
Surfacing a long-running Celery task to an MCP client normally means hand-writing start / status / result / cancel tools for every task. mcp-celery generates that surface for you: register a task once and the right MCP tools appear, from a single synchronous call to full polling, depending on the strategy you pick.
Status: Alpha. The four stable strategies use only ratified MCP primitives and work on any MCP client today.
Installation
pip install mcp-celery
Requires Python ≥ 3.10, mcp >= 1.0, and Celery with a broker and result backend (e.g. Redis).
Quick start
from celery import Celery
from mcp_celery import AsyncToolServer
celery_app = Celery(
"myapp",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
@celery_app.task
def generate_report(user_id: str) -> dict:
... # slow work
return {"user_id": user_id, "revenue": 42000}
server = AsyncToolServer("my-server", celery_app=celery_app)
server.register_async_tool(generate_report, description="Generate a report")
if __name__ == "__main__":
server.run()
That single registration generates the MCP tools. The default AdaptiveExposureStrategy returns the result inline when the task is fast, and hands back a token plus generate_report_status / _result / _cancel tools when it runs long.
To define and register a task in one step, use the decorator:
@server.async_tool(cancel=True, result_ttl=300)
def train_model(dataset: str) -> dict:
...
Choosing a strategy
The exposure strategy decides how a task is surfaced over MCP. Pick by task duration and client constraints; see docs/strategy-stability.md for full guidance.
| Strategy | Tools/task | Best for | Stability |
|---|---|---|---|
AdaptiveExposureStrategy (default) |
1 + polling fallback | Mixed or unknown durations — inline if fast, polling if slow; streams progress while blocking | stable |
BlockingExposureStrategy |
1 | Short tasks — one synchronous call, result inline | stable |
SingleToolExposureStrategy |
1 (action-driven) | Full lifecycle in one tool — minimal tool budget | stable |
PollingExposureStrategy |
4 (_start / _status / _result / _cancel) |
Long tasks, or explicit lifecycle control | stable |
OperationResourceStrategy |
1 + MCP task/resource API | MCP-native async tasks (SEP-1391) | experimental |
from mcp_celery.exposure import BlockingExposureStrategy
server = AsyncToolServer(
"my-server",
celery_app=celery_app,
strategy=BlockingExposureStrategy(max_wait=15.0),
)
Every stable strategy relies only on MCP tools, so it works on any client. OperationResourceStrategy depends on the un-ratified SEP-1391 Task primitive and emits an ExperimentalFeatureWarning on use.
Architecture
AsyncToolServer sits on two swappable layers:
- Backend (
AbstractBackend) — how a task runs.CeleryBackendis the only implementation today. - Exposure strategy (
AbstractExposureStrategy) — how the task is surfaced over MCP (the table above).
Task state is normalised to a single TaskStatus enum (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED, RETRYING). The token a client carries across calls is the Celery task id. Transport (stdio, SSE, streamable-HTTP) is delegated to FastMCP via server.run().
Attach to a FastMCP server you already configured with
AsyncToolServer.from_fastmcp(existing_mcp, celery_app=...).
Examples
Runnable end-to-end examples live in examples/, each driven by a single script that handles Redis, the virtualenv, and the Celery worker:
bash examples/run.sh adaptive # AdaptiveExposureStrategy (default)
bash examples/run.sh blocking # BlockingExposureStrategy
bash examples/run.sh single_tool # SingleToolExposureStrategy
bash examples/run.sh with_package # PollingExposureStrategy
bash examples/run.sh approach3 # OperationResourceStrategy (experimental)
bash examples/run.sh baseline # hand-written MCP tools, for comparison
Requires python3, bash, and Docker (only to auto-start Redis).
Limitations
- Token validity. Celery cannot tell an unknown or expired task id from a pending one — both report
PENDING. A polling strategy given a bad or TTL-expired token keeps reportingPENDINGrather than erroring. - Cancellation is best-effort.
cancelissuesrevoke(terminate=True)and reports the current state, which is often stillRUNNINGimmediately after, since revocation is asynchronous. OperationResourceStrategyis experimental. It depends on the un-ratified MCP SEP-1391 Task primitive; most clients do not understand the returnedtask://URI.
Development
pip install -e ".[dev]"
pytest # unit tests use an in-memory backend — no Redis or worker needed
ruff check src tests
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_celery-0.2.0.tar.gz.
File metadata
- Download URL: mcp_celery-0.2.0.tar.gz
- Upload date:
- Size: 41.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52e530e60640b7ab8cf4b9c7a7ebfaaf13d5c93a23869aa700d4973bc4d2f5f1
|
|
| MD5 |
4626722ca5503a4507ecfdf42bbaffd5
|
|
| BLAKE2b-256 |
b7bb53709a8a44af1921fd44842f70d7860f7a3399453d8c43e787eb31c5e119
|
File details
Details for the file mcp_celery-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp_celery-0.2.0-py3-none-any.whl
- Upload date:
- Size: 33.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64f5afe3842259ea84355635d16afe1d565c54ea8e53ac4b429885e738eb3114
|
|
| MD5 |
a69906115d664ff177a3a5fa4484f65d
|
|
| BLAKE2b-256 |
5f6d2c98df7deaacdfbfaf44e869994ecd42978d89f1eb59950797b97199173a
|