Skip to main content
Glama
README.md
# rq-mcp

[![CI](https://github.com/DawidBlonski/rq-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/DawidBlonski/rq-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server for
[RQ](https://python-rq.org) (Redis Queue), letting AI agents inspect,
debug, and operate RQ queues, jobs, and workers.

## Status

Phase 1 (read-only), Phase 2 (write: enqueue/requeue/cancel), and Phase 3
(admin: delete/purge/worker shutdown) are all complete — see the "Modes"
section below and "Available tools" for the full surface.

## Requirements

- Python 3.14+
- [uv](https://docs.astral.sh/uv/)
- A running Redis instance (a `docker-compose.yml` is provided for local dev)
- [Docker](https://docs.docker.com/get-docker/) — only needed if you use the
  provided `docker-compose.yml` instead of your own Redis

## Installation

```bash
uv sync
```

## Configuration

All configuration is via environment variables.

| Variable | Default | Purpose |
|---|---|---|
| `REDIS_URL` | `redis://localhost:6379/0` | Redis connection string |
| `RQ_MCP_MODE` | `readonly` | `readonly`, `write`, or `admin` — see "Modes" below |
| `RQ_MCP_WORKER_TTL_S` | `420` | `health_check`'s `WORKER_STALE_HEARTBEAT` threshold (matches RQ's own default worker TTL) |
| `RQ_MCP_OLDEST_JOB_THRESHOLD_S` | `300` | `health_check`'s `OLDEST_JOB_TOO_OLD` threshold |
| `RQ_MCP_HIGH_FAILURE_THRESHOLD` | `50` | `health_check`'s `HIGH_FAILURE_COUNT` threshold |
| `RQ_MCP_TASK_WHITELIST` | unset | Path to a TOML file listing functions `enqueue_task` may enqueue (`write` mode) — see [`examples/task_whitelist.toml`](examples/task_whitelist.toml). Unset/missing/unparseable all mean "nothing is enqueueable" (fails closed). |

```bash
export REDIS_URL="redis://localhost:6379/0"
```

### Modes

`RQ_MCP_MODE` gates which tools get registered: `readonly` (default)
exposes only inspection tools (12); `write` adds enqueue/requeue/cancel
tools (6 more, 18 total); `admin` adds delete/purge/worker-shutdown tools
(6 more, 24 total). Modes are cumulative — `admin` gets `write` and
`readonly` tools too.

**`admin` mode grants genuinely destructive capabilities** — permanently
deleting jobs, wiping entire status registries, emptying queues, shutting
down or globally suspending workers. Don't set `RQ_MCP_MODE=admin` against
a Redis instance you don't want an AI agent able to disrupt.

> [!WARNING]
> `suspend_workers` is **global**, not scoped to a queue or a single
> worker: it stops *every* RQ worker sharing that Redis connection from
> picking up new jobs, regardless of which queue(s) they listen to. This
> is RQ's own suspend mechanism (`rq.suspension`), not something rq-mcp
> narrows down. `resume_workers` undoes it (also globally); an optional
> `ttl` on `suspend_workers` can auto-expire the suspension.

## Quickstart: trying it out locally

This spins up Redis, puts demo data on a couple of queues, and lets you call
the MCP tools interactively through the
[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) — no
AI client required.

```bash
# 1. Start Redis
docker compose up -d

# 2. Seed demo data across every job/queue/worker state
uv run python scripts/seed_demo_data.py

# 3. Launch the server with the MCP Inspector (opens a browser UI)
uv run mcp dev src/rq_mcp/server.py
```

`scripts/seed_demo_data.py` (see "Local dev / example data" below) seeds
queued, scheduled, deferred, finished, and failed jobs (two different
exception types, so `summarize_failures` has something to group), plus a
fake stale worker entry so `health_check` reports a finding out of the box.
For a lighter touch — just a couple of queued jobs — use
`examples/seed_queues.py` instead.

In the Inspector UI, connect and use "List Tools" to see the tools for
your current `RQ_MCP_MODE` (see "Available tools" below), and "List
Prompts" for the 5 guided workflows. `mcp dev` requires the `cli` extra,
which is already included as a dev dependency.

`list_jobs`/`summarize_failures`/`list_scheduled_jobs` are how you discover
job IDs to pass to `get_job`/`get_job_result` — none of the seed scripts
print them directly.

Optionally, run a real worker in another terminal so jobs keep getting
processed live, and so you have a live worker to inspect with
`list_workers`/`get_worker` (this is on top of `seed_demo_data.py`'s
built-in one-shot processing — a live worker keeps running afterward):

```bash
uv run python examples/worker.py
```

See [`examples/`](examples/) and [`scripts/`](scripts/) for the job
definitions and seed scripts.

When you're done:

```bash
docker compose down -v
```

## Running the server

Outside of the Inspector, the server communicates over stdio, so it's
normally launched by an MCP client (e.g. Claude Desktop, Claude Code) rather
than run standalone. To start it directly:

```bash
uv run rq-mcp
# or
uv run python -m rq_mcp
```

## Available tools

### Read-only (`readonly` mode and above)

All 12 of these are read-only (`readOnlyHint: true`).

**System**
- `get_rq_info` — Redis connection status, Redis/RQ versions, discovered
  queue names, and worker count.
- `redis_info` — selected Redis `INFO` fields (`used_memory`, `maxmemory`,
  `maxmemory_policy`, `evicted_keys`, `connected_clients`, per-database key
  counts), by section (defaults to memory/clients/stats/keyspace).

**Queues**
- `list_queues` — per queue: job counts by status (queued, started,
  finished, failed, deferred, scheduled, canceled) and worker count.
- `get_queue` — the same per-queue detail plus the oldest queued job's age
  in seconds and `default_timeout`.

**Jobs**
- `get_job` — full details for a single job by ID (status, origin queue,
  function name, redacted/truncated args/kwargs/meta, timeout, ttl,
  dependency IDs, worker name, lifecycle timestamps), or `null` if the job
  doesn't exist.
- `get_job_result` — a job's result or exception info, truncated to
  `max_bytes` (default 2000), or `null` if the job doesn't exist.
- `list_jobs` — jobs in a queue filtered by status, paginated
  (`offset`/`limit`, max 100 per page), with a total count.
- `summarize_failures` — a queue's failed jobs grouped by function name +
  exception type: count, an example job ID, first/last failure time.
- `list_scheduled_jobs` — a queue's scheduled jobs: ID, function name,
  scheduled time, paginated.

**Workers**
- `list_workers` — every registered worker (optionally filtered by queue):
  name, state, queues, current job ID, heartbeat + age, job counters.
- `get_worker` — full details for a single worker by name (state, hostname,
  IP, PID, queues, current job ID, timestamps, job counters, total working
  time), or `null` if it isn't currently registered.

**Health**
- `health_check` — runs six diagnostics across queues/workers/Redis and
  returns `{"ok": bool, "findings": [...]}`. Finding codes:
  `QUEUE_NO_WORKERS`, `WORKER_STALE_HEARTBEAT`, `ORPHANED_STARTED_JOB`,
  `OLDEST_JOB_TOO_OLD`, `REDIS_EVICTION_RISK`, `HIGH_FAILURE_COUNT`.

### Write (`write` mode and above)

Mutate job state, but nothing here deletes data or touches workers.

- `list_task_functions` — the enqueue whitelist loaded from
  `RQ_MCP_TASK_WHITELIST` (name, description, args per entry).
- `enqueue_task` — enqueue a whitelisted function by dotted name
  (`func_name` must be in `list_task_functions`'s output — nothing else
  can be enqueued), immediately or scheduled via `at`/`in_seconds`.
- `requeue_job` — requeue a single failed job.
- `requeue_failed` — bulk-requeue a queue's failed jobs, optionally
  filtered by function name/exception type; `dry_run=True` by default.
- `cancel_job` — cancel a queued/scheduled/deferred job.
- `stop_running_job` — send a stop signal to a currently-executing job.

### Admin (`admin` mode only)

**Destructive.** `delete_job`/`clear_registry`/`empty_queue`/
`shutdown_worker`/`suspend_workers` all require `confirm=True` to
actually act (or `dry_run=True` to preview the affected count first,
which needs no confirmation). `resume_workers` needs neither.

- `delete_job` — permanently delete a single job.
- `clear_registry` — permanently delete every job in one queue's status
  registry (started/finished/failed/deferred/scheduled/canceled).
- `empty_queue` — permanently delete every queued job in a queue.
- `shutdown_worker` — send a shutdown command to one named worker.
- `suspend_workers` — **global**: stop every worker on this Redis from
  picking up new jobs (see the warning above).
- `resume_workers` — undo `suspend_workers` (also global); no
  confirmation needed.

## Available prompts

Five guided read-only diagnostic workflows, each naming which tools above
to call, in order, and what to conclude from the results:

- `diagnose_queue(queue_name)`
- `triage_failed_jobs(queue_name, limit)`
- `why_is_job_stuck(job_id)`
- `capacity_check()`
- `post_deploy_check()`

## Running tests

```bash
uv run pytest
```

Two layers of tests, neither requiring a running Redis instance:

- `tests/test_rq_service.py`, `tests/test_config.py`, `tests/test_helpers.py`,
  `tests/test_audit.py`, `tests/test_prompts.py` — mock the Redis
  connection (`unittest.mock`); fast, cover most branches directly.
- `tests/test_integration.py` — runs real `RQService` methods against a
  real `redis`/`rq` API surface backed by an in-memory
  [`fakeredis`](https://github.com/cunla/fakeredis-py) instance (using
  RQ's non-forking `SimpleWorker` so job execution stays visible to the
  test process). Covers scenarios that are awkward to fully exercise with
  mocks: an orphaned started-job registry entry (real composite-key
  format), mixed-exception grouping, and pagination boundaries.

## Linting and formatting

```bash
uv run ruff check .
uv run ruff format --check .
```

## Architecture

Simple layered `src` layout, no unnecessary abstractions. See
[`ARCHITECTURE.md`](ARCHITECTURE.md) for the full layer breakdown, request
lifecycle, and shared helpers (pagination, redaction, truncation).

## Local dev / example data

- `docker-compose.yml` — a local Redis 7 instance for development, not used
  in production or in tests.
- `examples/` — minimal scripts (`tasks.py`, `seed_queues.py`,
  `worker.py`) for a quick two-queue smoke test. Not part of the
  installable package.
- `scripts/seed_demo_data.py` (+ `seed_tasks.py`) — a richer seed covering
  every job status, two distinct exception types, and a hand-crafted stale
  worker entry, so every Phase 1 tool (including `health_check`'s findings
  and `summarize_failures`'s grouping) has something real to show. Not
  part of the installable package.

## RQ gotchas worth knowing

A few things discovered while building this that aren't obvious from RQ's
own docs:

- **`Queue`'s `default_timeout` isn't persisted to Redis.** It's a
  Python-side constructor default (180s) that only exists in the process
  that created the `Queue` object — `get_queue`'s `default_timeout` field
  reflects *this server's* default, not necessarily what any given
  producer used (each job's own `.timeout`, exposed via `get_job`, is the
  real source of truth per job).
- **`worker.queue_names()` is a method, not a property** in `rq==2.12.0` —
  easy to get wrong since many other worker fields are plain attributes.
- **`worker.get_current_job_id()` does a live Redis round trip** every
  call; it isn't populated by `Worker.all()`'s bulk `refresh()`.
- **RQ doesn't expose a structured exception class**, only the raw
  traceback text (`Result.exc_string`) — `summarize_failures` parses the
  traceback's last line by convention (`ExceptionType: message`).
- **`StartedJobRegistry.add()` raises `NotImplementedError`** in this RQ
  version (an unimplemented base-class stub); entries are written directly
  via `ZADD` with a `"{job_id}:{execution_id}"` composite key internally by
  the worker. Relevant if you ever need to hand-craft a started-registry
  entry (as `scripts/seed_demo_data.py`'s tests do).
- **`fakeredis` doesn't implement the `INFO` command** — `get_info()` and
  `redis_info()` degrade gracefully against it (their existing
  `RedisError` handling), but don't expect real memory/stats data from
  integration tests.
- **A non-forking `SimpleWorker` burst-processing several jobs in one
  long-lived process intermittently failed to re-resolve later jobs'
  function imports** in this environment (Python 3.14 / rq 2.12.0) when
  the task module was only importable via a bare `sys.path[0]`-relative
  script directory (not a proper package). Not fully root-caused; worked
  around by using the regular forking `Worker` wherever multiple real
  jobs need to run outside of `fakeredis`-backed tests (see
  `scripts/seed_demo_data.py`).

TDQS

A4.2/5.0

Scored across 12 tools

Disambiguation5/5

Each tool targets a distinct RQ resource or view: environment info, Redis internals, queues, jobs, results, workers, failures, and health. The only close pair is list_jobs with status=scheduled and list_scheduled_jobs, but their return shapes and descriptions make the intended use clear.

Naming Consistency4/5

The naming is mostly predictable with get_ for single entities and list_ for collections, all in snake_case. summarize_failures and health_check deviate slightly from the get_/list_ pattern but remain readable and consistent in style.

Tool Count5/5

Twelve tools is well-scoped for an RQ monitoring and inspection server. Each tool covers a meaningful part of the domain without unnecessary redundancy or bloat.

Completeness5/5

The tool surface covers the major RQ monitoring areas: environment, Redis health, queues, jobs by status, individual job details and results, scheduled jobs, workers, failure aggregation, and health diagnostics. For a read-only monitoring server, there are no obvious dead ends or missing core operations.

Maintenance

ActivityMaintained
ResponsivenessNo issues