
Mcp Queue Doctor
io.github.ethanasm由 GitHub 账号 ethanasm 发布v0.2.2更新于 Oct 24, 2026
Diagnoses pg-boss and graphile-worker job queues: retry storms, stuck workers, missed schedules.
概览
只读的 MCP 服务器,用于诊断 Postgres 作业队列(pg-boss、graphile-worker),并带证据解释重试风暴、卡住的 worker 和漏掉的定时任务。
- 可以做什么
- 运行完整规则目录,返回按严重程度排序、附带证据与恢复建议的发现
- 查看队列概况:各状态计数、卡住的任务,以及过期、保留和重试配置
- 查看时间窗口内的失败任务、错误信息与按队列的错误频率分布
- 检查定时任务:最新触发晚于 cron 预期的队列,以及每个计划的 cron、时区、上次与下次触发
- 查看单个任务的完整记录(状态、耗时、重试、负载、输出),并检查连接、识别到的 schema 与受限能力
- 适用场景
- 适合在 Postgres 上运行 pg-boss 或 graphile-worker 队列、希望让助手排查失败、重试风暴、卡住的 worker 或漏跑的定时任务,并按最安全的顺序给出恢复建议的场景。
- 运行要求
- 以本地 Node.js 进程运行(需 Node.js 20.11 及以上),从 npm 安装并通过 stdio 启动。需要 QUEUE_DOCTOR_DATABASE_URL 或 DATABASE_URL 提供 Postgres 连接串,或改用 QUEUE_DOCTOR_HTTP_SQL_URL 与 QUEUE_DOCTOR_HTTP_SQL_TOKEN 指向只读 SQL 端点。后端与 schema 由 QUEUE_DOCTOR_BACKEND、QUEUE_DOCTOR_SCHEMA 指定。该进程必须能访问数据库。
安装
在 SourceWeft 中
- 打开 控制台中的 Mcp Queue Doctor,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
mcp-queue-doctor
An MCP server that diagnoses Postgres job queues — pg-boss and graphile-worker. Retry storms, stuck workers, missed schedules, expiry overruns: what is broken, why, and the safest way to recover.
The second answer is the product. Every finding carries the evidence it was drawn from, so you — or an agent — can check the reasoning instead of trusting it.
Where the heuristics come from
The rules are extracted from a morning health check that has run daily in production since April 2026 against a pg-boss instance driving ~30 cron queues. Every threshold was tuned by a real false positive or a real missed failure, and each rule below names the incident that motivated it. That provenance is the point: these are not heuristics invented for a README.
Install
Then ask: "Is anything wrong with my job queue?"
Want to see it work first? examples/demo spins up a
throwaway Postgres and manufactures seven failures in about a minute. It also
plants a graphile-worker instance in the same database, where four of the seven
rules go deliberately silent — the clearest way to see what capability
declaration actually buys you.
Connecting it
The server speaks stdio, so every MCP client starts it as a subprocess. The only thing that varies is where the config lives — and whether that process can reach your database.
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json
on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows. Use the
mcpServers block above, then restart the app.
Desktop launches its subprocesses from the app bundle, not a login shell, so
PATH is minimal and a bare mcp-queue-doctor or npx often fails to resolve.
Give it an absolute path — which mcp-queue-doctor after a global install, or
the absolute path to npx with ["-y", "mcp-queue-doctor"] as its args.
Claude Code — one command, no file editing:
Add -s project to write a checked-in .mcp.json at the repo root instead of
your personal config, so everyone working in that repo gets the tool.
Cloud / remote sessions (Claude Code on the web, and any other headless
runner) — a checked-in .mcp.json is the only mechanism that works, because
nobody is there to answer an approval prompt. Reference the connection string
rather than committing it; Claude Code expands ${VAR} and ${VAR:-default}
in .mcp.json:
Project-scoped servers still need to be trusted before they start. In a
headless session that means setting enableAllProjectMcpServers: true in the
repo's .claude/settings.json, since the interactive approval never arrives.
Reachability is the real constraint, not the config. The server runs wherever the client runs, and it connects to Postgres directly — there is no hosted component in between. A cloud sandbox can therefore only diagnose a database inside that sandbox: the demo stack, or a dev stack the session brought up itself. A production queue bound to loopback on your own host is not reachable from a sandbox at all, and exposing it to make it reachable is the wrong trade.
Diagnose production from a client on a machine that already has a route to it — your laptop, over an SSH tunnel:
and point QUEUE_DOCTOR_DATABASE_URL at 127.0.0.1:5432. The tunnel is the
access grant, it lasts exactly as long as the terminal stays open, and the
credentials never leave your machine.
Tools
Schedule expectations are derived from pg-boss's own schedule table by parsing
each cron expression, so the common case needs no configuration. The health
check this was extracted from carried a hand-maintained list of expected jobs
that silently stopped covering whatever nobody remembered to add.
The rule catalog
Failures are classified (rate_limit, transient_transport, auth,
not_found) because the class changes the advice: the right response to a storm
of 429s is close to the opposite of the right response to connection resets.
Backends
Select with QUEUE_DOCTOR_BACKEND=pgboss (default) or graphile; the schema
default follows the backend.
Capabilities, not zeros
Backends don't just name columns differently — they model work differently. graphile-worker deletes a job when it succeeds, has no per-job expiry, no worker heartbeats, and keeps cron expressions in a file rather than the database. So "how many completed in the last day" has no answer there at any price.
Every backend therefore declares what it can answer, and rules that depend on missing data stay silent rather than reporting a zero — a zero reads like a measurement.
server_info reports the capability set and spells out each limitation.
Versioned against pg-boss
pg-boss's tables are not a stable API. Across versions it has renamed every
timestamp column (createdon → created_on), dropped a whole table (archive,
removed in v11), changed a duration from an interval to an integer (expire_in
→ expire_seconds), partitioned the job table, and added columns
(heartbeat_on) that materially change what can be diagnosed.
A tool that hard-codes one shape breaks on the next upgrade — silently, if it is
unlucky. That is exactly how the health check this is extracted from spent weeks
emitting a confident, wrong "missed schedules" warning that was really SQLSTATE
42P01 after pgboss.archive disappeared.
So schema knowledge lives in one file, src/pgboss/dialect.ts, as data:
- Every relation and column name is declared in a dialect. Query builders emit identifiers from it, so supporting a new pg-boss layout is an edit to that file — no SQL elsewhere mentions a pg-boss table by name.
- Dialects are matched on observed shape, not on a version number. pg-boss's release→schema-version mapping is not published as a contract, and a guessed mapping would reintroduce the very failure this guards against. The version integer is read, reported, and used to say "this is newer than anything we have verified" — but it never decides which SQL runs.
- Optional columns are feature-detected. No
heartbeat_on? Stuck-job detection degrades to age-based and says so, instead of failing. - Unknown layouts are refused, by name. A pre-v10 schema is recognised specifically and rejected with the reason, because diagnosing it against modern queries would silently miss every archived job. A wrong diagnosis is worse than a refusal.
server_info reports the matched dialect, the schema version, whether that
version has been verified against real pg-boss, and any reduced capabilities.
This is not a theoretical concern — it has already caught a real bug. The
dialect originally claimed a v10 floor, on the belief that v10 removed the
archive table. Booting pg-boss 10.4.2 showed the archive table still present
and expiry still an expire_in interval, so the dialect was rejecting v10
outright and matching nothing at all for it. The real floor is v11, and
v10 now has its own dialect: recognised, and refused by name, because reading
the job table alone on v10 silently misses everything already archived.
CI keeps this honest. The integration suite boots pg-boss 10, 11 and 12 into separate schemas and asserts that the observed schema version appears in the dialect's verified list — so a future pg-boss that changes the schema fails loudly rather than running unverified SQL.
Read-only, by construction
Every query runs inside a BEGIN READ ONLY transaction with a
statement_timeout and a row cap, and is always rolled back. Recovery actions
are recommended, with exact commands — never executed. A confused agent cannot
purge your queue, because the database itself refuses the write.
Three independent guarantees, because the failure being guarded against is writing to someone's production queue:
BEGIN READ ONLYon every transactiondefault_transaction_read_only=onat connection level- The docs tell you to connect as a least-privilege role — the only guarantee that does not depend on this code being correct
Timeouts bind as parameters via set_config(..., is_local => true) rather than
being interpolated into SQL. The schema name — the one identifier that cannot be
a bind parameter — is validated against an identifier grammar and quoted.
Log correlation (optional)
Queue state says that a job failed; application logs usually say why. Point the server at a log backend and findings quote the lines behind a failure.
Deliberately optional, and deliberately unable to break anything: a dead log backend never turns a working diagnosis into a failed one, and "we did not look" stays distinguishable from "we looked and found nothing" — otherwise an absent log line reads as evidence of absence. Half-configured settings are a startup error rather than a silent downgrade.
Reaching a database you cannot connect to
Production queues are often the ones you most want diagnosed and least able to reach: Postgres bound to loopback, no port forwarding, only the application in front of it exposed. Opening the database to the network so a diagnostic can connect is a poor trade — the grant is permanent and far wider than the need.
So the server can run its SQL over HTTPS against a read-only SQL endpoint instead:
Set these and no connection string is needed; set both and the HTTP transport
wins, so an ambient DATABASE_URL cannot quietly become the target. The
endpoint must accept {"query": "...", "params": [...]} and answer with
{"rows": [...], "truncated": bool}. Reference implementation:
showbook's /api/admin/sql.
The safety properties move to the far end, which is an improvement rather than
a compromise. The endpoint opens its own read-only transaction, enforces its
own timeout and row cap, can rate-limit, can log every query, and can be backed
by a role with narrower grants than the application's own — none of which
depend on this client being correct. What changes for you: the endpoint's
statement_timeout and row cap win over QUEUE_DOCTOR_STATEMENT_TIMEOUT_MS
and QUEUE_DOCTOR_MAX_ROWS, a truncating endpoint is reported as truncated
rather than silently short, and one diagnose costs roughly a dozen requests
against whatever rate limit is in force.
Bind parameters are required, not optional: a client forced to inline its own literals to reach a read-only endpoint would be building an injection sink to get there.
Configuration
See .env.example. Requires Node.js ≥ 20.11.
Tuning the rules
The thresholds are tuned to the queue these rules were extracted from. That is a defensible starting point and a poor universal answer: a queue that legitimately fails fifty times an hour against a flaky upstream does not have a retry storm, and being told it does every time teaches you to stop reading.
Override any of them with a JSON object — only the keys you set change:
An unknown key is a startup error, not a warning — a typo that silently
leaves the default in place is the failure this prevents. server_info reports
the effective values and which ones you set, so you can confirm an override
took.
Publishing to the MCP registry
server.json is the registry manifest. Its version and the npm version it
points at are both synced by npm version (see scripts/sync-version.mjs), and
a test fails if they drift — a registry entry naming a version that is not on
npm sends clients to a 404, which is worse than a stale entry.
Ownership is proved by the mcpName field in the published package.json,
so npm must be published first:
What this is not
- Not a queue browser. To page through jobs,
psqlis better. - Not a dashboard. This is agent infrastructure; your MCP client is the UI.
- Not a Redis queue tool. Both supported backends are Postgres-native, which is what makes the read-only transaction guarantee possible at all. BullMQ and Celery would need a different safety story.
- Not a writer. It will not retry, cancel, or purge anything.
Roadmap
- Read-only database layer, schema probe, CI
- The read-only tool surface
- The diagnosis engine
- A
docker compose updemo with a chaos worker - Integration tests against real pg-boss 10/11/12 in CI
- Log correlation, so findings can cite application logs
- A second adapter (graphile-worker)
- Opt-in write tools (
retry_job,cancel_job) behind an explicit flag - Configurable rule thresholds
Development
The unit suite drives the database layer through a scripted fake client and the
rules through fixtures reconstructing each motivating incident, so npm test
runs with no Postgres, no containers, and no network.
The integration suite boots real pg-boss (v10, v11, v12) and real graphile-worker against a live Postgres:
It skips itself when that variable is unset, so a contributor without Postgres
is never blocked. For a hands-on run, use examples/demo.
License
MIT
来源:README.md,提交 2d1ab06
工具
0版本历史
1- v0.2.2最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分