Modern Celery task monitoring with MCP integration. No UI, just data.
- MCP-first: Query and manage tasks, workers, and queues via the Model Context Protocol
- Event sourcing: Append-only event log for a complete audit trail and state reconstruction
- Real-time monitoring: Capture Celery events as they happen
- Task actions: Revoke, retry, and recover orphaned tasks
- Worker management: List, inspect, scale, and shut down workers
- Alerts: Slack-compatible webhook notifications on failures, slow tasks, and offline workers
- Prometheus metrics: Scrape task and worker telemetry via
/metrics - PostgreSQL backend: Production-ready, async throughout
- Broker-agnostic: RabbitMQ, LavinMQ, Redis, or any Celery/kombu broker
- Python 3.14+
- PostgreSQL 14+
- A Celery broker (RabbitMQ, LavinMQ, Redis, ...)
- uv
git clone https://github.com/KalvadTech/taskowl.git
cd taskowl
make install
export DATABASE_URL="postgresql+asyncpg://user:pass@localhost:5432/taskowl"
export CELERY_BROKER_URL="amqp://guest:guest@localhost:5672//"
make migrateRun the three processes (separate terminals):
make api # REST API on :8000
make consume # Celery event consumer
make mcp # MCP server on :8001taskowl listens to Celery's events stream, which workers emit only if enabled. Add this to your Celery application so taskowl can see your tasks and workers:
# celery_app.py
from celery import Celery
app = Celery("myapp", broker="amqp://guest:guest@localhost:5672//")
# Workers emit task/worker events (sent, received, started, succeeded, failed, ...)
app.conf.worker_send_task_events = True
# Emit a 'task-sent' event when a task is published
app.conf.task_send_sent_event = True
# How often workers send a heartbeat (default: 2s). Higher values
# increase the worker-offline detection delay.
app.conf.worker_heartbeat_interval = 2Alternatively, start your worker with the -E flag, which is equivalent to
worker_send_task_events = True:
celery -A myapp worker -E --loglevel=infoNote: If events are not enabled, taskowl simply sees nothing — no tasks, no workers. Enabling events is the one integration required.
The MCP server runs on http://localhost:8001/mcp (Streamable HTTP).
Add a remote MCP server to your opencode.json:
{
"mcp": {
"taskowl": {
"type": "remote",
"url": "http://localhost:8001/mcp",
"enabled": true,
"oauth": false
}
}
}Point your MCP client at the Streamable HTTP endpoint http://localhost:8001/mcp.
If authentication is enabled (see below), send the taskowl API key as
Authorization: Bearer <key> with each request.
| Category | Tools |
|---|---|
| Tasks | list_tasks, get_task, get_task_timeline, get_task_chain, get_task_summary, list_task_types, list_orphaned_tasks |
| Task actions | revoke_task, retry_task, execute_task |
| Workers | get_worker_status, list_workers, get_worker_stats, shutdown_worker, scale_worker_pool, restart_worker_pool, get_active_tasks, get_scheduled_tasks, get_reserved_tasks |
| Queues | list_queues |
Total: 20 tools
list_tasks supports exact filters (state, name, worker, since), a partial
case-insensitive search on the task name, offset for pagination, and sort_by
(timestamp [default, newest-first], name, state, worker).
Questions you can ask your AI assistant when the MCP server is connected:
| Question | Tools used |
|---|---|
| "Show me failed tasks from the last hour" | list_tasks |
| "Which task types are running?" | list_task_types |
| "Which tasks are orphaned?" | list_orphaned_tasks |
| "Show me the timeline for task abc" | get_task_timeline |
| "What's the retry chain for task abc?" | get_task_chain |
| "What's the task success rate in the last 30 minutes?" | get_task_summary |
| "Which workers are online?" | get_worker_status, list_workers |
| "How many messages are in each queue?" | list_queues |
| "Shutdown worker celery@worker1" | shutdown_worker |
| "Restart the pool on celery@worker1" | restart_worker_pool |
| "What's scheduled to run next?" | get_scheduled_tasks, get_reserved_tasks |
| "Retry task abc" | retry_task |
| "Run myapp.tasks.process now" | execute_task |
Celery workers ──events──▶ Broker ──▶ taskowl consumer ──▶ PostgreSQL
│
REST API ◀───────────────────────────┘
▲
│ HTTP
MCP server ──▶ LLM / MCP client
- Consumer (separate process) captures Celery events and appends them to
PostgreSQL (
task_events,worker_events). - REST API serves queries and actions over the event-sourcing tables.
- MCP server is a thin wrapper that calls the REST API for LLM access.
All configuration is via environment variables:
| Variable | Description | Default | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgresql+asyncpg://localhost:5432/taskowl |
Yes |
CELERY_BROKER_URL |
Celery broker URL (RabbitMQ, Redis, etc.) | amqp://guest:guest@localhost:5672// |
Yes |
TASKOWL_HOST |
FastAPI server host | 0.0.0.0 |
No |
TASKOWL_PORT |
FastAPI server port | 8000 |
No |
MCP_HOST |
MCP server host | 0.0.0.0 |
No |
MCP_PORT |
MCP server port | 8001 |
No |
LOG_LEVEL |
Logging level (DEBUG, INFO, WARNING, ERROR) | INFO |
No |
API_KEY |
API key for authentication (optional) | None (disabled) | No |
ORPHAN_GRACE_SECONDS |
Wait after task started before flagging as orphan | 60 |
No |
WORKER_OFFLINE_TIMEOUT_SECONDS |
No heartbeat for this long means worker is offline | 30 |
No |
ALERT_WEBHOOK_URL |
Slack webhook URL to post alerts to (disabled if unset) | None | No |
ALERT_ON_TASK_FAILED |
Enable task-failed alerts | true |
No |
ALERT_ON_WORKER_OFFLINE |
Enable worker-offline alerts | true |
No |
ALERT_SLOW_TASK_SECONDS |
Alert when a succeeded task exceeds this runtime | None | No |
ALERT_WORKER_CHECK_SECONDS |
Interval for the periodic stale-worker check | 30 |
No |
taskowl works with any Celery/kombu broker via CELERY_BROKER_URL:
export CELERY_BROKER_URL="amqp://guest:guest@localhost:5672//" # RabbitMQ / LavinMQ
export CELERY_BROKER_URL="redis://localhost:6379/0" # RedisSet ALERT_WEBHOOK_URL to a Slack incoming webhook to receive notifications on
task failures, offline workers, and slow tasks. Alerting is off by default.
export ALERT_WEBHOOK_URL="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"Conditions:
ALERT_ON_TASK_FAILED=true(default) — notify when a task failsALERT_ON_WORKER_OFFLINE=true(default) — notify when a worker goes offline (via anworker-offlineevent or a stale heartbeat detected everyALERT_WORKER_CHECK_SECONDS)ALERT_SLOW_TASK_SECONDS=30— notify when a succeeded task exceeds 30s
Payloads are Slack-formatted and contain task metadata only (name, task ID, worker, error, runtime) — args, kwargs, and results are never sent.
Scrape task and worker telemetry from the API server:
curl http://localhost:8000/metricsscrape_configs:
- job_name: taskowl
metrics_path: /metrics
scrape_interval: 15s
static_configs:
- targets: ["localhost:8000"]| Metric | Type | Labels |
|---|---|---|
taskowl_task_events_total |
Counter | event_type, task_name, worker |
taskowl_task_execution_duration_seconds |
Histogram | task_name |
taskowl_worker_status |
Gauge (1 = online, 0 = offline) | worker |
taskowl_worker_active_tasks |
Gauge | worker |
taskowl_worker_processed_total |
Counter | worker |
Security:
/metricsis intentionally unauthenticated so Prometheus can scrape it without the taskowl API key. Only expose it to trusted networks or behind a reverse proxy.
Optional API key authentication protects the REST API and MCP server. Set
API_KEY to enable it; all requests must then include
Authorization: Bearer <key>.
export API_KEY="your-secret-key-here"
curl -H "Authorization: Bearer your-secret-key-here" http://localhost:8000/api/tasksWhen authentication is disabled, all endpoints are open. /health, /, and
/metrics remain open regardless.
The API server (port 8000) exposes a REST API for tasks, workers, orphans, retries, and metrics. Interactive docs are available at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - Raw OpenAPI schema:
http://localhost:8000/openapi.json
| Area | Endpoints |
|---|---|
| Tasks | GET /api/tasks, GET /api/tasks/{id}, GET /api/tasks/{id}/timeline, GET /api/tasks/{id}/chain, GET /api/tasks/summary, GET /api/tasks/types, GET /api/tasks/orphaned |
| Task actions | POST /api/tasks/{id}/revoke, POST /api/tasks/{id}/retry, POST /api/tasks/execute |
| Workers | GET /api/workers, GET /api/workers/list, GET /api/workers/{name}/stats, GET /api/workers/active-tasks, GET /api/workers/scheduled, GET /api/workers/reserved |
| Worker actions | POST /api/workers/{name}/shutdown, POST /api/workers/{name}/scale, POST /api/workers/{name}/restart |
| Queues | GET /api/queues |
| Ops | GET /health, GET /metrics |
The /openapi.json schema is the authoritative reference — this README lists
only endpoint groups.
- Verify workers emit events — start with
-Eor setworker_send_task_events. - Check the consumer connected:
make consume 2>&1 | grep "Connected to Celery broker"
- Verify events reach the broker:
celery -A your_app events --dump
- Check event counts in the database:
SELECT COUNT(*) FROM task_events; SELECT COUNT(*) FROM worker_events;
pg_isreadyto confirm PostgreSQL is up.- Check
DATABASE_URLformat:postgresql+asyncpg://user:pass@host:port/dbname. - Verify the role has access to the database.
rabbitmqctl status(or your broker's health check) to confirm it's running.- Check
CELERY_BROKER_URLformat and credentials.
- API:
lsof -i :8000/ MCP:lsof -i :8001 - Kill the offending process:
kill $(lsof -t -i :8001)
Open an issue with the error message, environment details, steps to reproduce, and relevant (sanitized) logs: https://github.com/KalvadTech/taskowl/issues
See CONTRIBUTING.md for development setup, code style, testing, and the pull request process.
MIT — see LICENSE for details.
