mcp-queue-doctor について
mcp-queue-doctorは、pg-bossとgraphile-workerのPostgreSQLジョブキューを診断するMCPサーバーです。根拠を示した調査結果と復旧案を返し、読み取り専用でキューの変更を防ぎます。
主な機能
- 01リトライストームと停滞ジョブの検出
- 02スケジュール漏れと期限超過の検出
- 03キュー状態、失敗、ジョブ詳細の確認
- 04エラー種別による障害分類
- 05バックエンドの能力に応じた診断
- 06読み取り専用クエリと復旧案の提示
活用シーン
- 01繰り返し発生するジョブ失敗の共通原因を探る
- 02長時間アクティブなジョブを調査する
- 03スケジュール済みジョブの実行時刻を確認する
- 04本番データを変更せずに復旧方法を検討する
MCP 対応
stdio経由でMCPクライアントにキュー診断ツールを提供し、設定されたPostgreSQLデータベースへ直接接続します。クエリは読み取り専用トランザクションで実行され、復旧操作は提案のみで実行されません。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mcp-queue-doctor
Postgres ジョブキュー(pg-boss と graphile-worker)を診断するMCP サーバーです。リトライの嵐、ワーカーの停止、スケジュールの実行漏れ、期限超過など、何が壊れているのか、なぜなのか、最も安全に復旧する方法を示します。
❌ "3 jobs in enrichment/corpus-fill are in state 'failed'."
✅ "enrichment/corpus-fill failed 140 times over 3m, peaking at 50 failures in a
single minute. 91% share one error, which looks like an upstream rate limit.
This is one fault reproduced many times, not many separate faults — so the fix
belongs at the source, and retrying the jobs individually will reproduce it.
Recovery, safest first:
1. Stop enqueuing to this queue — every new job feeds the same failure.
2. Confirm when the upstream quota resets; treat that as the time to resume.
3. Add a cooldown gate after N consecutive 429s.
⚠ Do NOT bulk-retry yet — the upstream is still limited.
Evidence: 140 failures, 91% 'HTTP 429 Too Many Requests (daily quota
exceeded)', peak 50/min, busiest minutes [...], 12 other errors [...]"
2つ目の回答こそが、この製品の価値です。各指摘には根拠となった証拠が付いているため、あなたやエージェントは推論を鵜呑みにせず、確認できます。
ヒューリスティックの由来
ルールは、2026年4月以降、約30の cron キューを稼働させる pg-boss インスタンスに対して本番環境で毎日実行されている、朝のヘルスチェックから抽出したものです。すべてのしきい値は、実際の誤検知または実際に見逃した障害を受けて調整されています。以下の各ルールには、そのきっかけとなったインシデントが記されています。この出所こそが重要です。README のために考案したヒューリスティックではありません。
インストール
npm install -g mcp-queue-doctor
{
"mcpServers": {
"queue-doctor": {
"command": "mcp-queue-doctor",
"env": {
"QUEUE_DOCTOR_DATABASE_URL": "postgres://readonly:pw@localhost:5432/app"
}
}
}
}
次のように尋ねてみてください。「ジョブキューに何か問題はありますか?」
まず動作を確認したいですか? examples/demo を使うと、使い捨ての Postgres を起動し、約1分で7つの障害を作り出します。また、同じデータベースに graphile-worker のインスタンスも配置されます。そこで7つのルールのうち4つが意図的に機能しないため、機能の宣言によって実際に何が得られるのかを最も明確に確認できます。
接続方法
このサーバーは stdio で通信するため、すべての MCP クライアントがサブプロセスとして起動します。異なるのは設定の保存場所と、そのプロセスからデータベースに接続できるかどうかだけです。
Claude Desktop — macOS では ~/Library/Application Support/Claude/claude_desktop_config.json、Windows では %APPDATA%\Claude\claude_desktop_config.json です。上記の mcpServers ブロックを使ってから、アプリを再起動してください。
Desktop はログインシェルではなくアプリバンドルからサブプロセスを起動するため、PATH が最小限で、mcp-queue-doctor や npx をそのまま指定しても解決できないことがよくあります。絶対パスを指定してください。グローバルインストール後に which mcp-queue-doctor で確認するか、npx の絶対パスを指定し、引数として ["-y", "mcp-queue-doctor"] を渡します。
Claude Code — ファイルを編集せずに、コマンドを1つ実行するだけです。
claude mcp add queue-doctor -e QUEUE_DOCTOR_DATABASE_URL=postgres://... -- npx -y mcp-queue-doctor
個人用設定ではなく、リポジトリのルートに共有対象の .mcp.json を作成するには、-s project を追加してください。そうすると、そのリポジトリで作業する全員がツールを利用できます。
クラウド/リモートセッション(Claude Code on the web など、その他のヘッドレスランナー)— 承認プロンプトに応答する人がいないため、共有対象の .mcp.json だけが利用可能な方法です。接続文字列はコミットせず、参照するようにしてください。Claude Code は .mcp.json 内の ${VAR} と ${VAR:-default} を展開します。
{
"mcpServers": {
"queue-doctor": {
"command": "npx",
"args": ["-y", "mcp-queue-doctor"],
"env": { "QUEUE_DOCTOR_DATABASE_URL": "${QUEUE_DOCTOR_DATABASE_URL}" }
}
}
}
プロジェクトスコープのサーバーは、起動前に信頼する必要があります。ヘッドレスセッションでは対話型の承認が行われないため、リポジトリの .claude/settings.json で enableAllProjectMcpServers: true を設定します。
本当の制約は設定ではなく、到達可能性です。 サーバーはクライアントが実行される場所で動き、Postgres に直接接続します。間にホスト型のコンポーネントはありません。そのため、クラウドサンドボックスで診断できるのは、そのサンドボックス内のデータベースだけです。デモ環境か、セッション自身が起動した開発環境が対象になります。自分のホストでループバックにバインドされている本番キューには、サンドボックスからまったく接続できません。接続可能にするために公開するのは、代償に見合いません。
すでに本番環境への経路があるマシン上のクライアントから診断してください。たとえば、SSH トンネルを使ってノート PC から接続します。
ssh -N -L 5432:127.0.0.1:5432 prod-host
そのうえで QUEUE_DOCTOR_DATABASE_URL を 127.0.0.1:5432 に設定します。トンネルがアクセス許可となり、ターミナルを開いている間だけ有効です。認証情報がマシンの外に出ることもありません。
ツール
| ツール | 内容 |
|---|---|
diagnose | まずはこちらから。 ルールカタログ全体を実行し、根拠と復旧手順を添えて、優先度順に診断結果を返します |
queue_overview | キューごとの状態別件数、スタックしたジョブ、および各キューの有効期限/保持/再試行の設定 |
failed_jobs | 指定期間内の失敗とエラーメッセージに加え、キューごとのエラー発生頻度の内訳 |
stuck_jobs | しきい値を超えてアクティブなジョブと、その経過時間、有効期限、ハートビートの遅延状況 |
missed_schedules | 最新の実行時刻が cron 式から想定される時刻より古い cron キュー |
schedule_status | 登録済みスケジュールすべての cron、タイムゾーン、最終実行時刻、次回の予定時刻 |
job_detail | 1 件のジョブの完全な記録:状態、所要時間、再試行回数、ペイロード、出力 |
server_info | 接続状況、検出されたスキーマ、適合する方言、制限された機能 |
スケジュールの実行予定は、各 cron 式を解析して pg-boss 独自の schedule
テーブルから導出されるため、通常は設定不要です。この機能の元となったヘルスチェックには、
手作業で管理する期待ジョブの一覧があり、誰も追加し忘れたジョブはいつの間にか対象外になっていました。
ルールカタログ
| ルール | 発生条件 | きっかけとなったインシデント |
|---|---|---|
retry-storm | 多数の失敗が短期間に集中し、その大半が同一のエラーである場合 | 1 日あたりの API クォータ超過により、一晩で 875 件のコーパス補完ジョブが失敗しました。件数からは 875 件の問題があるように見えましたが、実態は 1 件でした |
expiry-overrun | 失敗までの所要時間がキューの有効期限付近に集中する場合 | 上流のスロットリングで処理が遅くなり、コーパス全体のスイープが 30 分の有効期限内に完了できなくなりました。毎晩ジョブ失敗として報告されていましたが、内部の実時間予算を設けることで解決しました |
stuck-jobs | ジョブが長時間アクティブなまま、またはハートビートが停止した場合 | ワーカーが正常終了せずに強制終了されると、メンテナンスによって回収されるまで行がアクティブなまま残ります |
missed-schedule | 最新の実行時刻が最後に予定されていた実行時刻より前の場合 | 「一度も実行されていない」(スケジューラーが起動していない)状態と「実行が停止した」状態を区別します |
duplicate-registration | 1 回の実行時に cron キューが二重にエンキューされた場合 | インストルメンテーションフックがプロセスごとにジョブ登録を 2 回呼び出していたため、数週間にわたりすべての cron が二重実行されていました |
retention-window | 失敗行の件数が期間指定で集計した件数と一致しない場合 | バグ修正後も数日間ヘルスメールが黄色のままで、数日前に失敗した行を数えていました |
dead-queue | かなり前に登録され、スケジュールされておらず、何も保持していない場合 | 停止したプロデューサー、またはリファクタリングで削除された登録 |
失敗は分類されます(rate_limit、transient_transport、auth、
not_found)。分類によって推奨される対処が変わるためです。429 が相次ぐ場合の適切な対応は、
接続リセットが発生した場合の適切な対応とはほぼ正反対です。
バックエンド
| バックエンド | サポート | 検証済みバージョン |
|---|---|---|
| pg-boss v11+ | 完全対応 | 11.1.2(スキーマ 26)、12.27.0(スキーマ 37) |
| pg-boss v10 | 認識しますが、拒否します — 下記を参照 | 10.4.2(スキーマ 24) |
| pg-boss v9 以前 | 認識しますが、拒否します | — |
| graphile-worker 0.17 | 部分対応、機能を宣言 | 0.17.3 |
QUEUE_DOCTOR_BACKEND=pgboss(デフォルト)または graphile を指定して選択します。スキーマの
デフォルト値はバックエンドに従います。
ゼロではなく、機能の有無
バックエンドは、単に列の名前が異なるだけではありません。仕事のモデルそのものが異なります。 graphile-worker はジョブが成功すると削除し、ジョブごとの有効期限も、ワーカーのハートビートもありません。また、cron 式はデータベースではなくファイルに保存されます。そのため、「過去 1 日に完了した件数」は、どんな方法を使っても答えられません。
そのため、各バックエンドは回答できる項目を宣言します。不足しているデータに依存するルールは、ゼロを報告するのではなく何も報告しません。ゼロは測定値のように見えるためです。
| ルール | pg-boss v11+ | graphile-worker |
|---|---|---|
retry-storm | ✅ | ✅ |
stuck-jobs | ✅(ハートビートあり) | ✅(経過時間のみ) |
expiry-overrun | ✅ | — 有効期限なし |
missed-schedule | ✅ | — cron はファイルに保存 |
duplicate-registration | ✅ | — 実行履歴なし |
retention-window | ✅ | — 保持されるデータなし |
dead-queue | ✅ | — キューレジストリなし |
server_info は機能の一覧を報告し、それぞれの制限を明示します。
pg-boss のバージョンに対応
pg-boss のテーブルは安定した API ではありません。バージョンによって、すべてのタイムスタンプ列の名前が変更され(createdon → created_on)、テーブル全体が削除され(archive は v11 で削除)、期間の型が interval から整数に変更され(expire_in → expire_seconds)、ジョブテーブルがパーティション化され、診断内容を大きく左右する列(heartbeat_on)が追加されてきました。
1 つの形状をハードコードしたツールは、次のアップグレードで動かなくなります。不運な場合は、気づかないままです。この問題をまさに示す例として、ここから抽出されたヘルスチェックは、pgboss.archive の消失によって実際には SQLSTATE 42P01 が発生していたにもかかわらず、自信たっぷりに誤った「スケジュール未実行」警告を何週間も出し続けていました。
そのため、スキーマの知識はデータとして 1 つのファイル src/pgboss/dialect.ts にまとめられています。
- すべてのリレーション名と列名は方言で宣言されています。 クエリビルダーはそこから識別子を出力するため、新しい pg-boss のレイアウトへの対応はこのファイルを編集するだけです。ほかの場所にある SQL で pg-boss のテーブル名を直接指定することはありません。
- 方言はバージョン番号ではなく、実際に確認されたスキーマの形状に基づいて照合されます。 pg-boss のリリースとスキーマバージョンの対応関係は契約として公開されておらず、推測による対応付けでは、この仕組みが防ごうとしている問題が再発します。バージョン番号は読み取られ、報告され、「検証済みの範囲を超える新しいバージョンです」と伝えるために使われますが、どの SQL を実行するかの判断には使われません。
- オプションの列は機能検出されます。
heartbeat_onがない場合、スタックしたジョブの検出は経過時間ベースに切り替わり、その旨を伝えます。エラーにはなりません。 - 不明なレイアウトは、名前を挙げて拒否されます。 v10 より前のスキーマは明確に認識され、その理由とともに拒否されます。最新のクエリで診断すると、アーカイブ済みのジョブをすべて気づかないまま見落とすためです。誤った診断をするより、拒否するほうが適切です。
server_info は、照合された方言、スキーマバージョン、そのバージョンが実際の pg-boss に対して検証済みかどうか、および機能が制限されている場合はその内容を報告します。
これは理論上の懸念ではありません。実際に発生したバグをすでに検出しています。当初、この方言は、v10 で archive テーブルが削除されたと考えて、最低対応バージョンをv10としていました。pg-boss 10.4.2 を起動してみると、archive テーブルはまだ存在し、有効期限も引き続き expire_in interval でした。そのため、この方言は v10 を全面的に拒否し、何も照合できていませんでした。実際の最低対応バージョンはv11です。現在、v10 には専用の方言があり、認識したうえで名前を挙げて拒否します。v10 ではジョブテーブルだけを読むと、すでにアーカイブされたデータをすべて気づかないまま見落とすためです。
CI によって、この対応の正しさを維持しています。統合テストスイートは pg-boss 10、11、12 をそれぞれ別のスキーマに起動し、確認されたスキーマバージョンが方言の検証済みリストに含まれていることをアサートします。そのため、将来の pg-boss でスキーマが変更された場合、未検証の SQL が実行されるのではなく、明確に失敗します。
構造上、読み取り専用
すべてのクエリは、statement_timeout と行数上限が設定された BEGIN READ ONLY トランザクション内で実行され、必ずロールバックされます。復旧アクションは正確なコマンドとともに推奨されるだけで、実行されることはありません。データベース自体が書き込みを拒否するため、混乱したエージェントがキューを消去することはできません。
本番キューへの書き込みを防ぐという目的に対し、独立した保証を3つ設けています。
- すべてのトランザクションで
BEGIN READ ONLYを使用 - 接続レベルで
default_transaction_read_only=onを設定 - 最小権限のロールで接続するようドキュメントで案内 — このコードが正しいことに依存しない唯一の保証です
タイムアウトはSQLに埋め込むのではなく、set_config(..., is_local => true) を使ってパラメーターとしてバインドします。バインドパラメーターにできない唯一の識別子であるスキーマ名は、識別子の文法に照らして検証し、引用符で囲みます。
ログの相関(任意)
キューの状態から分かるのはジョブが失敗したことであり、通常、その理由はアプリケーションログに記録されています。サーバーにログバックエンドを指定すると、調査結果に障害の原因となったログ行が引用されます。
QUEUE_DOCTOR_AXIOM_TOKEN=xapt-... # read-capable PAT
QUEUE_DOCTOR_AXIOM_DATASET=app-prod
QUEUE_DOCTOR_AXIOM_ORG_ID=your-org
QUEUE_DOCTOR_AXIOM_QUEUE_FIELD=job # field carrying the queue name
意図的に任意とし、何かに影響が及ぶこともないようにしています。ログバックエンドが停止していても、正常な診断が失敗に変わることはありません。また、「確認していない」状態と「確認したが何も見つからなかった」状態は区別されます。そうしないと、ログ行がないことが、存在しない証拠として受け取られてしまうためです。設定が一部だけ行われている場合は、黙って機能を縮小するのではなく、起動時エラーになります。
接続できないデータベースへのアクセス
本番キューは、診断が最も必要でありながら、最もアクセスしにくい対象であることがよくあります。Postgres がループバックアドレスにバインドされ、ポートフォワーディングもなく、手前にあるアプリケーションだけが公開されている、といった状況です。診断ツールが接続できるようデータベースをネットワークに公開するのは、割に合わない選択です。権限付与は永続的で、必要な範囲をはるかに超えるためです。
そのため、代わりにサーバーから読み取り専用のSQLエンドポイントへHTTPSでSQLを実行できます。
QUEUE_DOCTOR_HTTP_SQL_URL=https://your-app.example/api/admin/sql
QUEUE_DOCTOR_HTTP_SQL_TOKEN=...
これらを設定すれば接続文字列は不要です。両方を設定するとHTTPトランスポートが優先されるため、環境に設定された DATABASE_URL が知らないうちに接続先になることもありません。エンドポイントは {"query": "...", "params": [...]} を受け取り、{"rows": [...], "truncated": bool} を返す必要があります。リファレンス実装:showbook の /api/admin/sql。
安全性を担保する仕組みは接続先に移りますが、これは妥協ではなく改善です。エンドポイントは独自の読み取り専用トランザクションを開始し、独自のタイムアウトと行数上限を適用し、レート制限やすべてのクエリのログ記録も行えます。また、アプリケーション自体よりも権限を絞ったロールで運用できます。これらはすべて、このクライアントが正しく動作することに依存しません。利用者側で変わるのは、エンドポイントの statement_timeout と行数上限が QUEUE_DOCTOR_STATEMENT_TIMEOUT_MS と QUEUE_DOCTOR_MAX_ROWS より優先されること、行数を切り詰めるエンドポイントでは単に短い結果としてではなく truncated として報告されること、そして1回の diagnose で、適用中のレート制限に対しておよそ12件のリクエストが発生することです。
バインドパラメーターは必須であり、任意ではありません。読み取り専用エンドポイントへ接続するために、クライアントがリテラルを自分で埋め込まざるを得ないようでは、その接続のためにインジェクションの温床を作ることになります。
設定
| 変数 | デフォルト | 用途 |
|---|---|---|
QUEUE_DOCTOR_DATABASE_URL / DATABASE_URL | — | HTTP トランスポートを使用しない場合は必須です。Postgres 接続文字列 |
QUEUE_DOCTOR_HTTP_SQL_URL | — | 接続の代わりにクエリを実行する読み取り専用 SQL エンドポイント |
QUEUE_DOCTOR_HTTP_SQL_TOKEN | — | そのエンドポイント用のベアラートークン |
QUEUE_DOCTOR_BACKEND | pgboss | pgboss または graphile |
QUEUE_DOCTOR_SCHEMA | バックエンドごと | キューがインストールされたスキーマ |
QUEUE_DOCTOR_STATEMENT_TIMEOUT_MS | 5000 | クエリごとのタイムアウト(100~120000) |
QUEUE_DOCTOR_MAX_ROWS | 500 | クエリごとの行数上限(1~10000) |
QUEUE_DOCTOR_LOG_LEVEL | info | debug/info/warn/error/silent(stderr) |
QUEUE_DOCTOR_THRESHOLDS | — | ルールのしきい値を上書きする JSON オブジェクト(以下を参照) |
.env.example を参照してください。Node.js ≥ 20.11 が必要です。
ルールの調整
しきい値は、これらのルールの抽出元となったキューに合わせて調整されています。 これは妥当な出発点ですが、万能の答えではありません。不安定な上流サービスに対して 1 時間に 50 回正当に失敗するキューにリトライストームは発生していません。それなのに 毎回そう告げられれば、やがて結果を読まなくなってしまいます。
JSON オブジェクトで任意のしきい値を上書きできます。変更されるのは、設定したキーだけです。
QUEUE_DOCTOR_THRESHOLDS='{"stormMinFailures":50,"idleQueueSeconds":2592000}'
| キー | デフォルト | 対象 |
|---|---|---|
stormMinFailures | 20 | バーストがストームと判定されるまでの失敗数 |
stormDominantShare | 0.5 | エラーが支配的と判定されるために占めるべき割合 |
stormPeakPerMinute | 5 | 断続的な失敗ではなくバーストと判定される、1 分あたりの失敗数 |
stormCriticalFailures | 100 | これを超えると、ストームは警告ではなく重大と判定されます |
expiryProximity | 0.95 | 失敗ではなく強制終了に見える、有効期限に対する割合 |
expiryMinJobs | 3 | パターンと判定されるまでに有効期限を迎えるジョブ数 |
heartbeatMissedMultiplier | 3 | ワーカーが停止したと判定されるまでのハートビート未受信回数 |
missedScheduleCriticalSeconds | 86400 | これを超える遅延は重大と判定されます |
retentionMismatchMin | 5 | 保持期間の問題を指摘する、余分に残った古い失敗行の数 |
duplicateTickMin | 2 | 二重登録を疑う、重複実行があるティックの数 |
idleQueueSeconds | 604800 | 空のキューに言及する価値があると判断される経過時間 |
correlatedLogSample | 5 | 検出結果の根拠として添付されるログ行数 |
未知のキーは警告ではなく起動エラーになります。入力ミスによってデフォルト値が
黙って適用される事態を防ぐためです。server_info は有効な値と、設定によって変更された
値を報告するため、上書きが適用されたことを確認できます。
MCP レジストリへの公開
server.json はレジストリのマニフェストです。そのバージョンと、参照先の npm バージョンは
どちらも npm version によって同期されます(scripts/sync-version.mjs を参照)。両者に
ずれがあるとテストが失敗します。npm に存在しないバージョンを指定するレジストリエントリは
クライアントを 404 に誘導するため、古いエントリよりも深刻です。
所有権は公開済みの package.json にある mcpName フィールドで証明されるため、
先に npm へ公開する必要があります。
npm version patch # syncs src/version.ts and server.json
npm publish # the registry reads mcpName off this
mcp-publisher login github # device auth as the io.github.<user> namespace owner
mcp-publisher publish
このツールでできないこと
- キューブラウザーではありません。 ジョブをページ送りして確認するには、
psqlのほうが適しています。 - ダッシュボードではありません。 これはエージェント向けのインフラです。UI には MCP クライアントを使用します。
- Redis キューツールではありません。 対応する 2 つのバックエンドはいずれも Postgres ネイティブです。これによって初めて読み取り専用トランザクションを保証できます。 BullMQ や Celery では、別の安全性対策が必要になります。
- 書き込みを行いません。 再試行、キャンセル、パージは行いません。
ロードマップ
- 読み取り専用データベースレイヤー、スキーマプローブ、CI
- 読み取り専用のツール群
- 診断エンジン
- chaos worker を含む
docker compose upデモ - CI で実際の pg-boss 10/11/12 を使った統合テスト
- ログ相関により、検出結果の根拠としてアプリケーションログを提示
- 2 つ目のアダプター(graphile-worker)
- 明示的なフラグで有効化するオプトイン方式の書き込みツール(
retry_job、cancel_job) - 設定可能なルールのしきい値
開発
npm install
npm run verify # lint + typecheck + test + build
ユニットテストスイートでは、スクリプト化された偽のクライアントを通じてデータベース層をテストし、各問題の発生原因を再現するフィクスチャを使ってルールをテストします。そのため、npm test は Postgres もコンテナもネットワークも使わずに実行できます。
統合テストスイートでは、実際の Postgres に対して、本物の pg-boss(v10、v11、v12)と graphile-worker を起動します。
docker run -d -p 55432:5432 -e POSTGRES_USER=qd -e POSTGRES_PASSWORD=qd \
-e POSTGRES_DB=qd postgres:16-alpine
QUEUE_DOCTOR_TEST_DATABASE_URL=postgres://qd:[email protected]:55432/qd \
npm run test:integration
この変数が設定されていない場合、統合テストは自動的にスキップされるため、Postgres がないコントリビューターの作業が妨げられることはありません。実際に試す場合は、examples/demo を使用してください。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。