queue-aiops 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/CHANGELOG.md +5 -0
  2. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/PKG-INFO +61 -12
  3. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/README.md +60 -11
  4. queue_aiops-0.2.0/RELEASE_NOTES.md +48 -0
  5. queue_aiops-0.2.0/docs/VERIFICATION.md +200 -0
  6. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/_shared.py +1 -1
  7. queue_aiops-0.2.0/mcp_server/server.py +76 -0
  8. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/rabbit_reads.py +16 -0
  9. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/redis_reads.py +12 -0
  10. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/undo.py +13 -2
  11. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/pyproject.toml +1 -1
  12. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/rabbit_cmds.py +28 -4
  13. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/redis_cmds.py +14 -2
  14. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/__init__.py +5 -1
  15. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/decorators.py +12 -0
  16. queue_aiops-0.2.0/queue_aiops/governance/readonly.py +49 -0
  17. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/sanitize.py +25 -0
  18. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/_util.py +17 -1
  19. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/analysis.py +13 -10
  20. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/rabbit_reads.py +45 -24
  21. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/redis_reads.py +45 -17
  22. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/writes.py +8 -8
  23. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/server.json +3 -3
  24. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/skills/queue-aiops/SKILL.md +106 -37
  25. queue_aiops-0.2.0/skills/queue-aiops/references/agent-guardrails.md +109 -0
  26. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/skills/queue-aiops/references/capabilities.md +8 -1
  27. queue_aiops-0.2.0/tests/test_cli_rabbit_writes.py +164 -0
  28. queue_aiops-0.2.0/tests/test_cli_reads.py +249 -0
  29. queue_aiops-0.2.0/tests/test_cli_secret.py +111 -0
  30. queue_aiops-0.2.0/tests/test_connection.py +241 -0
  31. queue_aiops-0.2.0/tests/test_gov_audit.py +292 -0
  32. queue_aiops-0.2.0/tests/test_gov_decorators.py +452 -0
  33. queue_aiops-0.2.0/tests/test_gov_patterns.py +417 -0
  34. queue_aiops-0.2.0/tests/test_gov_policy.py +415 -0
  35. queue_aiops-0.2.0/tests/test_mcp_reads.py +211 -0
  36. queue_aiops-0.2.0/tests/test_optional_fields.py +184 -0
  37. queue_aiops-0.2.0/tests/test_readonly.py +142 -0
  38. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_reads.py +2 -1
  39. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/uv.lock +1 -1
  40. queue_aiops-0.1.0/RELEASE_NOTES.md +0 -33
  41. queue_aiops-0.1.0/mcp_server/server.py +0 -38
  42. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/.github/workflows/mcp-publish.yml +0 -0
  43. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/.github/workflows/publish.yml +0 -0
  44. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/.gitignore +0 -0
  45. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/LICENSE +0 -0
  46. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/SECURITY.md +0 -0
  47. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/__init__.py +0 -0
  48. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/__init__.py +0 -0
  49. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/analysis.py +0 -0
  50. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/overview.py +0 -0
  51. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/mcp_server/tools/writes.py +0 -0
  52. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/__init__.py +0 -0
  53. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/__init__.py +0 -0
  54. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/_common.py +0 -0
  55. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/_root.py +0 -0
  56. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/analyze.py +0 -0
  57. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/doctor.py +0 -0
  58. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/init.py +0 -0
  59. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/overview.py +0 -0
  60. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/secret.py +0 -0
  61. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/cli/undo.py +0 -0
  62. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/config.py +0 -0
  63. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/connection.py +0 -0
  64. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/doctor.py +0 -0
  65. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/audit.py +0 -0
  66. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/budget.py +0 -0
  67. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/paths.py +0 -0
  68. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/patterns.py +0 -0
  69. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/policy.py +0 -0
  70. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/governance/undo.py +0 -0
  71. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/__init__.py +0 -0
  72. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/ops/overview.py +0 -0
  73. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/platform.py +0 -0
  74. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/queue_aiops/secretstore.py +0 -0
  75. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/skills/queue-aiops/references/cli-reference.md +0 -0
  76. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/skills/queue-aiops/references/setup-guide.md +0 -0
  77. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/smithery.yaml +0 -0
  78. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/conftest.py +0 -0
  79. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/fakes.py +0 -0
  80. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_analysis.py +0 -0
  81. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_cli_writes.py +0 -0
  82. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_doctor.py +0 -0
  83. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_governance_persistence.py +0 -0
  84. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_init.py +0 -0
  85. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_platform.py +0 -0
  86. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_secretstore.py +0 -0
  87. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_smoke.py +0 -0
  88. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_undo_executor.py +0 -0
  89. {queue_aiops-0.1.0 → queue_aiops-0.2.0}/tests/test_writes.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.1.1 — 2026-07-17
4
+
5
+ ### Fixed
6
+ - Added the MCP Registry ownership marker (mcp-name) to the README so the server publishes to the MCP Registry.
7
+
3
8
  ## v0.1.0 — 2026-07-17
4
9
 
5
10
  Initial preview release.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queue-aiops
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Governed AI-ops for redis + rabbitmq: memory-pressure/latency/queue-backlog/connection-churn RCAs, slowlog and SCAN-budgeted big-key sampling, queues/policies/nodes, and governed writes (config set, client kill, purge/delete queue, policies) with a built-in governance harness (audit, budget, undo, risk tiers)
5
5
  Author-email: wei <zhouwei008@gmail.com>
6
6
  License-Expression: MIT
@@ -15,7 +15,9 @@ Requires-Dist: rich<16.0,>=13.0
15
15
  Requires-Dist: typer<1.0,>=0.12
16
16
  Description-Content-Type: text/markdown
17
17
 
18
- # Queue AIops (preview)
18
+ <!-- mcp-name: io.github.AIops-tools/queue-aiops -->
19
+
20
+ # Queue AIops
19
21
 
20
22
  **Governed AI-ops for redis + rabbitmq.** queue-aiops is for the team running
21
23
  their own cache and message broker — a redis that "suddenly eats memory", a
@@ -31,9 +33,10 @@ by the built-in governance harness.
31
33
  > with, endorsed by, or sponsored by the Redis or RabbitMQ projects or their
32
34
  > respective owners.** Redis and RabbitMQ are trademarks of their respective owners.
33
35
  >
34
- > **Preview / mock-only**: not yet validated against production brokers. Both
35
- > redis and rabbitmq are free/self-hostable (one lab container or package install each), so a lab
36
- > check is easy — `queue-aiops doctor` is the fastest live probe.
36
+ > **Verification**: behaviour is covered by a mock-based test suite; not yet validated
37
+ > against live brokers. Both redis and rabbitmq are free/self-hostable (one lab container
38
+ > or package install each), so a lab check is easy — `queue-aiops doctor` is the fastest
39
+ > live probe, and [docs/VERIFICATION.md](docs/VERIFICATION.md) is the checklist.
37
40
 
38
41
  ## Quick start
39
42
 
@@ -56,6 +59,46 @@ queue-aiops redis bigkeys # SCAN-budgeted big-key sample (never KEYS *)
56
59
  queue-aiops rabbitmq queues # deepest backlog first
57
60
  ```
58
61
 
62
+ ## Security: read-only mode
63
+
64
+ This tool is meant to be handed to an AI agent, so its safety story is enforced
65
+ by the server rather than requested in a prompt:
66
+
67
+ ```bash
68
+ export QUEUE_READ_ONLY=1
69
+ ```
70
+
71
+ With that set, the **8 write tools are never registered**. An MCP client
72
+ lists **20 tools instead of 28** — the writes are not hidden, not
73
+ gated behind a flag, and not merely refused when called. They are absent from
74
+ the session. A model cannot invoke a tool it was never offered, and cannot be
75
+ argued into one.
76
+
77
+ That distinction is the whole point. A tool that exists but refuses still invites
78
+ retry loops and "I'll describe the call instead" behaviour from smaller models,
79
+ and it leaves a reviewer trusting a promise. An absent tool is a fact you can
80
+ check: connect, list the tools, and see that the writes are not there.
81
+
82
+ Enforcement is two layers deep, so the switch cannot be sidestepped by changing
83
+ entry point:
84
+
85
+ | Layer | What it does | Covers |
86
+ |---|---|---|
87
+ | `@governed_tool` harness | refuses every non-read operation outright | MCP, CLI, and in-process callers |
88
+ | MCP registration | write tools are removed from `list_tools()` | anything speaking MCP |
89
+
90
+ Read operations are unaffected, and every call is still audited to
91
+ `~/.queue-aiops/audit.db`.
92
+
93
+ > The read/write split is derived from each tool's declared `risk_level`, and a
94
+ > test asserts that this never disagrees with the `[READ]`/`[WRITE]` tag in the
95
+ > tool's own documentation — so a write can't quietly present itself as a read.
96
+
97
+ Running a smaller / local model? See
98
+ [agent-guardrails.md](skills/queue-aiops/references/agent-guardrails.md) — it lists
99
+ the guardrails this tool now enforces for you (so you don't spend prompt budget
100
+ restating them) and gives a ready-made system prompt for what's left.
101
+
59
102
  ## Support scope
60
103
 
61
104
  | Platform | Protocol | Coverage |
@@ -63,7 +106,7 @@ queue-aiops rabbitmq queues # deepest backlog first
63
106
  | **redis** (5.x–7.x wire protocol via `redis` Python client) | RESP, password optional, TLS optional | INFO (server/memory/clients/stats/persistence/keyspace), SLOWLOG, CLIENT LIST/KILL, CONFIG GET/SET, MEMORY STATS/USAGE, SCAN-budgeted big-key sampling, DBSIZE, PING |
64
107
  | **rabbitmq** (management plugin HTTP API) | HTTP(S), Basic auth | /api/overview, /api/queues (+ per-vhost, detail, purge, declare, delete), /api/connections, /api/channels, /api/consumers, /api/policies (get/set/delete), /api/nodes |
65
108
 
66
- **26 MCP tools** — 19 reads (incl. 4 flagship RCAs) + 7 governed writes.
109
+ **28 MCP tools** — 20 reads (incl. 4 flagship RCAs) + 8 governed writes.
67
110
 
68
111
  | Group | Tools | R/W |
69
112
  |-------|-------|:---:|
@@ -73,6 +116,7 @@ queue-aiops rabbitmq queues # deepest backlog first
73
116
  | Flagship RCAs | redis_memory_pressure_rca, redis_latency_rca, rabbitmq_queue_backlog_rca, connection_churn_analysis | read |
74
117
  | Writes (medium) | redis_config_set, redis_kill_client, declare_queue, set_policy, delete_policy | write |
75
118
  | Writes (**high**) | purge_queue, delete_queue | write |
119
+ | Undo | undo_list, undo_apply | read / write |
76
120
 
77
121
  The four RCAs are transparent heuristics that report their numbers — thresholds
78
122
  are named constants, every finding carries its evidence, never a black-box
@@ -148,12 +192,17 @@ queue-aiops analyze memory|latency|backlog|churn
148
192
 
149
193
  ## Verification status
150
194
 
151
- Preview / mock-only: the full test suite runs against mocked clients (no live
152
- broker needed), and the REST paths / INFO fields are modelled from the public
153
- docs of both platforms. Not yet validated against live production brokers —
154
- both are trivially self-hostable, so `queue-aiops doctor` against a
155
- local lab redis instance / rabbitmq broker (management plugin enabled) is
156
- the quickest live check.
195
+ **Mock-validated; not yet run against live brokers.** The full test suite runs against
196
+ mocked clients (no live broker needed): every module imports, every MCP tool carries the
197
+ governance marker, the four flagship RCAs are unit-tested against synthetic telemetry,
198
+ and reversible writes are asserted to record the correct inverse undo descriptor. The
199
+ REST paths and `INFO` field names are modelled from the public docs of both platforms
200
+ and have not been exercised against a real broker.
201
+
202
+ Both platforms are trivially self-hostable, so `queue-aiops doctor` against a local lab
203
+ redis instance / rabbitmq broker (management plugin enabled) is the quickest live check.
204
+ [docs/VERIFICATION.md](docs/VERIFICATION.md) is the full checklist a live run must
205
+ satisfy.
157
206
 
158
207
  ## Contributing
159
208
 
@@ -1,4 +1,6 @@
1
- # Queue AIops (preview)
1
+ <!-- mcp-name: io.github.AIops-tools/queue-aiops -->
2
+
3
+ # Queue AIops
2
4
 
3
5
  **Governed AI-ops for redis + rabbitmq.** queue-aiops is for the team running
4
6
  their own cache and message broker — a redis that "suddenly eats memory", a
@@ -14,9 +16,10 @@ by the built-in governance harness.
14
16
  > with, endorsed by, or sponsored by the Redis or RabbitMQ projects or their
15
17
  > respective owners.** Redis and RabbitMQ are trademarks of their respective owners.
16
18
  >
17
- > **Preview / mock-only**: not yet validated against production brokers. Both
18
- > redis and rabbitmq are free/self-hostable (one lab container or package install each), so a lab
19
- > check is easy — `queue-aiops doctor` is the fastest live probe.
19
+ > **Verification**: behaviour is covered by a mock-based test suite; not yet validated
20
+ > against live brokers. Both redis and rabbitmq are free/self-hostable (one lab container
21
+ > or package install each), so a lab check is easy — `queue-aiops doctor` is the fastest
22
+ > live probe, and [docs/VERIFICATION.md](docs/VERIFICATION.md) is the checklist.
20
23
 
21
24
  ## Quick start
22
25
 
@@ -39,6 +42,46 @@ queue-aiops redis bigkeys # SCAN-budgeted big-key sample (never KEYS *)
39
42
  queue-aiops rabbitmq queues # deepest backlog first
40
43
  ```
41
44
 
45
+ ## Security: read-only mode
46
+
47
+ This tool is meant to be handed to an AI agent, so its safety story is enforced
48
+ by the server rather than requested in a prompt:
49
+
50
+ ```bash
51
+ export QUEUE_READ_ONLY=1
52
+ ```
53
+
54
+ With that set, the **8 write tools are never registered**. An MCP client
55
+ lists **20 tools instead of 28** — the writes are not hidden, not
56
+ gated behind a flag, and not merely refused when called. They are absent from
57
+ the session. A model cannot invoke a tool it was never offered, and cannot be
58
+ argued into one.
59
+
60
+ That distinction is the whole point. A tool that exists but refuses still invites
61
+ retry loops and "I'll describe the call instead" behaviour from smaller models,
62
+ and it leaves a reviewer trusting a promise. An absent tool is a fact you can
63
+ check: connect, list the tools, and see that the writes are not there.
64
+
65
+ Enforcement is two layers deep, so the switch cannot be sidestepped by changing
66
+ entry point:
67
+
68
+ | Layer | What it does | Covers |
69
+ |---|---|---|
70
+ | `@governed_tool` harness | refuses every non-read operation outright | MCP, CLI, and in-process callers |
71
+ | MCP registration | write tools are removed from `list_tools()` | anything speaking MCP |
72
+
73
+ Read operations are unaffected, and every call is still audited to
74
+ `~/.queue-aiops/audit.db`.
75
+
76
+ > The read/write split is derived from each tool's declared `risk_level`, and a
77
+ > test asserts that this never disagrees with the `[READ]`/`[WRITE]` tag in the
78
+ > tool's own documentation — so a write can't quietly present itself as a read.
79
+
80
+ Running a smaller / local model? See
81
+ [agent-guardrails.md](skills/queue-aiops/references/agent-guardrails.md) — it lists
82
+ the guardrails this tool now enforces for you (so you don't spend prompt budget
83
+ restating them) and gives a ready-made system prompt for what's left.
84
+
42
85
  ## Support scope
43
86
 
44
87
  | Platform | Protocol | Coverage |
@@ -46,7 +89,7 @@ queue-aiops rabbitmq queues # deepest backlog first
46
89
  | **redis** (5.x–7.x wire protocol via `redis` Python client) | RESP, password optional, TLS optional | INFO (server/memory/clients/stats/persistence/keyspace), SLOWLOG, CLIENT LIST/KILL, CONFIG GET/SET, MEMORY STATS/USAGE, SCAN-budgeted big-key sampling, DBSIZE, PING |
47
90
  | **rabbitmq** (management plugin HTTP API) | HTTP(S), Basic auth | /api/overview, /api/queues (+ per-vhost, detail, purge, declare, delete), /api/connections, /api/channels, /api/consumers, /api/policies (get/set/delete), /api/nodes |
48
91
 
49
- **26 MCP tools** — 19 reads (incl. 4 flagship RCAs) + 7 governed writes.
92
+ **28 MCP tools** — 20 reads (incl. 4 flagship RCAs) + 8 governed writes.
50
93
 
51
94
  | Group | Tools | R/W |
52
95
  |-------|-------|:---:|
@@ -56,6 +99,7 @@ queue-aiops rabbitmq queues # deepest backlog first
56
99
  | Flagship RCAs | redis_memory_pressure_rca, redis_latency_rca, rabbitmq_queue_backlog_rca, connection_churn_analysis | read |
57
100
  | Writes (medium) | redis_config_set, redis_kill_client, declare_queue, set_policy, delete_policy | write |
58
101
  | Writes (**high**) | purge_queue, delete_queue | write |
102
+ | Undo | undo_list, undo_apply | read / write |
59
103
 
60
104
  The four RCAs are transparent heuristics that report their numbers — thresholds
61
105
  are named constants, every finding carries its evidence, never a black-box
@@ -131,12 +175,17 @@ queue-aiops analyze memory|latency|backlog|churn
131
175
 
132
176
  ## Verification status
133
177
 
134
- Preview / mock-only: the full test suite runs against mocked clients (no live
135
- broker needed), and the REST paths / INFO fields are modelled from the public
136
- docs of both platforms. Not yet validated against live production brokers —
137
- both are trivially self-hostable, so `queue-aiops doctor` against a
138
- local lab redis instance / rabbitmq broker (management plugin enabled) is
139
- the quickest live check.
178
+ **Mock-validated; not yet run against live brokers.** The full test suite runs against
179
+ mocked clients (no live broker needed): every module imports, every MCP tool carries the
180
+ governance marker, the four flagship RCAs are unit-tested against synthetic telemetry,
181
+ and reversible writes are asserted to record the correct inverse undo descriptor. The
182
+ REST paths and `INFO` field names are modelled from the public docs of both platforms
183
+ and have not been exercised against a real broker.
184
+
185
+ Both platforms are trivially self-hostable, so `queue-aiops doctor` against a local lab
186
+ redis instance / rabbitmq broker (management plugin enabled) is the quickest live check.
187
+ [docs/VERIFICATION.md](docs/VERIFICATION.md) is the full checklist a live run must
188
+ satisfy.
140
189
 
141
190
  ## Contributing
142
191
 
@@ -0,0 +1,48 @@
1
+ # Release notes — queue-aiops 0.2.0
2
+
3
+ Previous release: 0.1.1.
4
+
5
+ ## Headline: read-only mode
6
+
7
+ ```bash
8
+ export QUEUE_READ_ONLY=1
9
+ ```
10
+
11
+ With this set the **8 write tools are never registered** — an MCP
12
+ client lists **20 tools instead of 28**. The writes are not hidden
13
+ behind a flag and not merely refused on call: they are absent from the session,
14
+ so a model cannot invoke one and cannot be argued into one. For a reviewer this
15
+ is checkable rather than promised — connect, list the tools, and the writes are
16
+ not there.
17
+
18
+ Enforcement is two layers deep: the `@governed_tool` harness refuses every
19
+ non-read operation (covering the CLI and in-process callers too), and the MCP
20
+ server removes write tools from `list_tools()`. Changing entry point does not
21
+ get around it.
22
+
23
+ ## BREAKING — return shapes changed
24
+
25
+ This release changes payloads that callers may be parsing. Both changes exist
26
+ to stop a result from misrepresenting itself:
27
+
28
+ 1. **Absent fields are now `null`, not `""`.** A missing value and an empty value
29
+ were previously indistinguishable, which invited consumers to invent the
30
+ difference. Keys are still always present — only the value may be null.
31
+ 2. **Anything with a `limit` now returns an envelope** —
32
+ `{"<items>": [...], "returned": N, "limit": L, "truncated": bool}`. Truncation is
33
+ *measured* (one extra row is fetched), never inferred from the page happening to
34
+ be full. Where a genuine pre-cap total is knowable it is reported as `total`;
35
+ where it isn't, `total` is deliberately omitted rather than echoing `returned`.
36
+
37
+ ## Also in this release
38
+
39
+ - **`docs/VERIFICATION.md`** — what the mock suite actually guarantees, a live
40
+ verification checklist, and the criteria for claiming this tool verified.
41
+ - **`skills/queue-aiops/references/agent-guardrails.md`** — for driving this tool with a
42
+ smaller / local model: which guardrails are now enforced for you, and a
43
+ ready-made system prompt for the rest.
44
+ - Expanded operator playbooks in the skill documentation.
45
+ - The advertised tool count now matches what an MCP client actually lists
46
+ (it includes `undo_list` / `undo_apply`), and a release gate keeps it honest.
47
+ - The `(preview)` label has been dropped. It never meant unreleased; verification
48
+ status now lives in `docs/VERIFICATION.md` where it can be specific.
@@ -0,0 +1,200 @@
1
+ # Live verification — redis / rabbitmq
2
+
3
+ `queue-aiops` is exercised by a **mock-only** test suite (`uv run pytest`, no real
4
+ broker). It has **not** yet been validated end-to-end against a live redis instance or
5
+ rabbitmq broker. This document says exactly what the mock suite already guarantees, and
6
+ what a live run has to prove before anyone may describe this tool as verified against a
7
+ real broker.
8
+
9
+ It is deliberately checklist-shaped so the result is reproducible and auditable — not a
10
+ subjective "seems fine".
11
+
12
+ ## What the mock suite already guarantees
13
+
14
+ - Every module imports; the CLI builds; **all 28 MCP tools** carry the `@governed_tool`
15
+ harness marker (`tests/test_smoke.py`, which also asserts the tool count and that
16
+ `__version__` matches `pyproject.toml`).
17
+ - The four flagship analyses (`redis_memory_pressure_rca`, `redis_latency_rca`,
18
+ `rabbitmq_queue_backlog_rca`, `connection_churn_analysis`) are unit-tested against
19
+ synthetic telemetry: each cause classification fires on the right signal
20
+ (noeviction-near-limit vs active eviction vs fragmentation vs swapping; O(N) command
21
+ patterns and fork/AOF stalls; no-consumers vs unacked pile-up vs rate deficit),
22
+ findings cite the measured number, and partial/missing fields do not crash.
23
+ - The **platform registry** resolves each tool to the correct redis (RESP, via the
24
+ `redis` Python client) or rabbitmq (management HTTP API, HTTP Basic) shape.
25
+ - **Safety invariants** are tested: the redis surface is a typed command **allow-list**
26
+ with no generic passthrough; big-key sampling is `SCAN`-based under a hard budget and
27
+ **never** issues `KEYS *`; and rabbitmq path segments (queue/policy names, and the
28
+ default vhost `/` → `%2F`) are percent-encoded centrally.
29
+ - Reversible writes record a faithful **inverse** undo descriptor built from a fetched
30
+ before-state (`redis_config_set` restores the prior value from `CONFIG GET`;
31
+ `set_policy` / `delete_policy` restore the captured definition; `declare_queue`'s undo
32
+ deletes it only when newly created). `purge_queue` declares **no** undo;
33
+ `delete_queue`'s undo re-declares the captured definition and the tests assert that
34
+ **messages are not claimed to be restored**.
35
+ - Governance persistence is tested against a real on-disk SQLite audit DB: calls land as
36
+ rows, failures record `status=error` and no undo, and the secure-by-default approver
37
+ gate refuses high-risk ops when no `rules.yaml` exists.
38
+
39
+ What it does **not** guarantee: that the concrete `INFO` field names, `SLOWLOG` entry
40
+ shape, and rabbitmq management-API JSON match a real broker build. Those are modelled
41
+ from each project's public documentation and are the **largest verification debt in this
42
+ repo** — `INFO` field names in particular have shifted across redis 5/6/7.
43
+
44
+ ## Prerequisites for a live run
45
+
46
+ Both platforms are free and trivially containerised:
47
+
48
+ ```bash
49
+ docker run -d --name redis-verify -p 6379:6379 redis:7 \
50
+ redis-server --maxmemory 128mb --maxmemory-policy noeviction
51
+ docker run -d --name rabbit-verify -p 5672:5672 -p 15672:15672 rabbitmq:3-management
52
+ ```
53
+
54
+ The rabbitmq **management plugin** must be enabled (the `-management` image does this),
55
+ and the API user needs the `monitoring` or `management` tag.
56
+
57
+ Use a **throwaway broker with throwaway data**. The checklist purges and deletes queues,
58
+ kills clients, and changes the running config — `purge_queue` destroys real messages and
59
+ records **no undo**. Never point it at a broker carrying traffic you care about.
60
+
61
+ Set the lab up so the reads have something to find:
62
+
63
+ - Fill redis past 80% of a small `maxmemory` and leave the policy at `noeviction`, so the
64
+ most dangerous finding is reachable.
65
+ - Create a few deliberately oversized keys (a large hash and a large list).
66
+ - Run some `KEYS`/`SMEMBERS` calls so the SLOWLOG has genuine O(N) entries.
67
+ - On rabbitmq, create three queues: one with **no consumer** and messages published, one
68
+ with a consumer that **never acks**, and one healthy — the three backlog causes.
69
+
70
+ ```bash
71
+ uv tool install queue-aiops
72
+ queue-aiops init # wizard: pick platform, optional encrypted secret
73
+ ```
74
+
75
+ Record the exact versions tested (e.g. "redis 7.4, rabbitmq 3.13") — a tick is only
76
+ meaningful with the build it was ticked against.
77
+
78
+ ## Verification checklist
79
+
80
+ Tick every box, **per platform**. A box that cannot be ticked is a verification gap —
81
+ record it, do not silently pass.
82
+
83
+ ### 1. Connectivity (the fastest live gate)
84
+ - [ ] `queue-aiops doctor` → green on each target: config parsed, secret store unlocks,
85
+ and a real `PING` / `/api/overview` returns.
86
+ - [ ] `queue-aiops doctor --skip-auth` → passes offline (config/secret checks only).
87
+ - [ ] An **auth-less** lab redis connects with no stored secret (this is a supported
88
+ configuration and a common mock-vs-reality divergence).
89
+ - [ ] One config spanning both platforms works with `--target` switching.
90
+
91
+ ### 2. Reads return real, well-shaped data
92
+ - [ ] `queue-aiops overview` → real version/role, memory posture, client counts, ops/sec,
93
+ hit rate and key count (redis); version, queue/message totals, connection/channel/
94
+ consumer counts and node alarms (rabbitmq). Confirm a **partial** failure degrades
95
+ into the `errors` list rather than failing the whole call.
96
+ - [ ] `queue-aiops redis info` / `redis memory` → fields match `redis-cli INFO`
97
+ **on each of redis 6 and 7**, since field names have moved between versions.
98
+ - [ ] `queue-aiops redis clients` → matches `CLIENT LIST`; `redis keyspace` matches
99
+ `DBSIZE` per database.
100
+ - [ ] `queue-aiops redis slowlog --limit 50` → matches `SLOWLOG GET`, with timings
101
+ parsed correctly.
102
+ - [ ] `queue-aiops redis config-get maxmemory*` → glob patterns work and match
103
+ `CONFIG GET`.
104
+ - [ ] `queue-aiops redis bigkeys --count 500` → finds the oversized keys you planted, and
105
+ reports a **coverage %** consistent with the keyspace size. Confirm with
106
+ `MONITOR` or the slowlog that it issued `SCAN`, **never** `KEYS *` — this is the
107
+ single most important safety check in the file.
108
+ - [ ] `queue-aiops rabbitmq queues` / `queue <name>` / `connections` / `channels` /
109
+ `policies` / `nodes` → match the management UI, including ready-vs-unacked splits.
110
+ - [ ] A queue and a policy whose **names need percent-encoding** (a `/`, a space, a `%`)
111
+ are read correctly, and the default vhost `/` resolves as `%2F`.
112
+
113
+ ### 3. The analyses are right, not just non-crashing
114
+ - [ ] With redis near `maxmemory` under `noeviction`,
115
+ `queue-aiops analyze memory --used-pct 85` raises the **noeviction-near-limit**
116
+ finding and cites the real used/max numbers. Switch to `allkeys-lru`, drive
117
+ evictions, and confirm the finding changes to active eviction.
118
+ - [ ] Fragmentation vs swapping are distinguished correctly (do not accept "flagged
119
+ something" — check it names the right one).
120
+ - [ ] `queue-aiops analyze latency --slow-us 10000` digests the planted O(N) entries by
121
+ **command pattern** and suggests the incremental variant (`KEYS` → `SCAN`).
122
+ - [ ] Trigger a background save on a large dataset; the fork/AOF stall signal appears.
123
+ - [ ] `queue-aiops analyze backlog --top 20` classifies each of your three planted
124
+ queues correctly: **no consumers**, **unacked pile-up**, and healthy.
125
+ - [ ] Drive the node past its memory watermark; the alarm is reported as blocking **all**
126
+ publishers, and outranks the per-queue findings.
127
+ - [ ] `queue-aiops analyze churn` on both platforms pins reconnect churn to the right
128
+ client source.
129
+
130
+ ### 4. A reversible write + its undo (governance closes the loop)
131
+ - [ ] `queue-aiops redis config-set maxmemory-policy allkeys-lru --dry-run` → prints the
132
+ call, `CONFIG GET` shows the value unchanged.
133
+ - [ ] `queue-aiops redis config-set maxmemory-policy allkeys-lru` → the running config
134
+ changes, the result carries an `_undo_id`, and a row lands in
135
+ `~/.queue-aiops/audit.db`.
136
+ - [ ] `queue-aiops undo list` shows it; `undo apply <id>` restores the **prior** policy —
137
+ verify starting from a non-default value, where a naive undo would wrongly set
138
+ `noeviction`.
139
+ - [ ] `queue-aiops rabbitmq set-policy <name> <pattern> '{"max-length":1000}'` over an
140
+ **existing** policy, then `undo apply` → the prior definition returns intact (not
141
+ a deletion).
142
+ - [ ] `queue-aiops rabbitmq declare-queue <new> --dry-run` then for real, then
143
+ `undo apply` → the newly created queue is deleted. Repeat against a queue that
144
+ **already existed**: the undo must **not** delete it.
145
+
146
+ ### 5. Irreversible / asymmetric writes behave as declared
147
+ - [ ] `queue-aiops rabbitmq purge <queue> --dry-run` → reports the message count about to
148
+ be destroyed; the real run destroys exactly that many and records **no** undo.
149
+ - [ ] `queue-aiops rabbitmq delete-queue <queue>` on a queue **with messages**, then
150
+ `undo apply` → the queue is re-declared with the right durable/auto-delete flags,
151
+ and the **messages are gone**. Confirm the tool never claimed otherwise.
152
+ - [ ] `queue-aiops redis kill-client --addr <ip:port>` drops that connection, records no
153
+ undo, and is audited.
154
+
155
+ ### 6. Governance actually gates
156
+ - [ ] With no `~/.queue-aiops/rules.yaml`, `purge_queue` and `delete_queue` are
157
+ **refused** unless `QUEUE_AUDIT_APPROVED_BY` is set (secure-by-default); with it
158
+ plus `QUEUE_AUDIT_RATIONALE`, both appear in the audit row.
159
+ - [ ] A tight poll loop trips the runaway budget guard rather than hammering the broker.
160
+ - [ ] A failed call (nonexistent queue) is audited `status=error` and records no undo.
161
+ - [ ] Attempting a redis command outside the typed allow-list is rejected **before** the
162
+ client sends anything — confirm against the live server that nothing executes.
163
+
164
+ ### 7. Cleanup
165
+ - [ ] Restore every config value you changed, delete the throwaway queues and policies,
166
+ and flush the lab redis.
167
+ - [ ] `queue-aiops overview` matches the baseline you captured before starting.
168
+ - [ ] Skim `~/.queue-aiops/audit.db` — every write is there with the right risk tier.
169
+
170
+ ## Criteria to consider it live-verified
171
+
172
+ All of the following must hold:
173
+
174
+ 1. Every box above is ticked against **both** a real redis and a real rabbitmq, with the
175
+ exact builds recorded (e.g. "redis 7.4 + rabbitmq 3.13"), and the redis reads
176
+ re-checked on at least **two major redis versions** given the `INFO` field drift.
177
+ 2. Every field-name or API-shape mismatch found is **fixed and covered by a regression
178
+ test**, so the mock suite would now catch it.
179
+ 3. Section 4 passed, including the asymmetric `declare_queue` case (undo must delete only
180
+ a queue it created). Recording an undo descriptor is not the same as the undo
181
+ working, and this product line has shipped broken undo pairs before.
182
+ 4. The big-key sampler was confirmed **live** to issue `SCAN` under budget and never
183
+ `KEYS *` — a mock cannot prove this, and getting it wrong stalls a production redis.
184
+ 5. The run is written up in the release notes / product-line memory with the date and
185
+ package version, matching how the line records its other live-verified tools.
186
+
187
+ Until then, this repo says only what is true: mock-validated, live-unverified. Claiming
188
+ otherwise would break that promise.
189
+
190
+ ## Notes for maintainers
191
+
192
+ - `queue-aiops doctor` is the single fastest live entry point; start there.
193
+ - Weight the run toward **redis `INFO` field names** and **rabbitmq percent-encoded path
194
+ segments** — those are the two places the mocks are least able to model.
195
+ - Managed cloud queue services (ElastiCache, Amazon MQ, CloudAMQP, …) are a **separate**
196
+ verification target: wire-compatible reads may work, but `CONFIG SET` and
197
+ `CLIENT KILL` are commonly restricted. Do not infer managed support from a self-hosted
198
+ run.
199
+ - Add this tool's result to the product-line verification ledger once green, so the
200
+ central "verification debt" list stays accurate.
@@ -75,7 +75,7 @@ def tool_errors(shape: str = "dict") -> Callable:
75
75
  mcp = FastMCP(
76
76
  "queue-aiops",
77
77
  instructions=(
78
- "Queue/cache broker operations (preview) over redis and rabbitmq: for a "
78
+ "Queue/cache broker operations over redis and rabbitmq: for a "
79
79
  "redis target — server/memory/keyspace reads, clients, slowlog, config, "
80
80
  "and a SCAN-budgeted big-key sample (never KEYS *); for a rabbitmq "
81
81
  "target — management-API overview, queues, connections, channels, "
@@ -0,0 +1,76 @@
1
+ """MCP server wrapping queue-aiops operations (stdio transport).
2
+
3
+ Thin adapter layer: each ``@mcp.tool()`` function (in ``mcp_server/tools/``)
4
+ delegates to the ``queue_aiops`` ops package and is wrapped with the
5
+ queue-aiops ``@governed_tool`` harness (audit / budget / undo / risk-tier).
6
+
7
+ Standalone, self-governed broker operations over redis and rabbitmq:
8
+ redis server/memory/clients/slowlog/config/keyspace/big-key reads, rabbitmq
9
+ overview/queues/connections/channels/policies/node reads, four flagship
10
+ analyses, and governed writes (redis config set + client kill; rabbitmq queue
11
+ purge/delete/declare and policy set/delete).
12
+
13
+ Source: https://github.com/AIops-tools/Queue-AIops
14
+ License: MIT
15
+ """
16
+
17
+ import logging
18
+
19
+ from mcp_server._shared import _safe_error, mcp, tool_errors
20
+
21
+ # Importing the tool modules registers every @mcp.tool() onto the shared
22
+ # `mcp` instance. Order does not matter; each module is self-contained.
23
+ from mcp_server.tools import ( # noqa: F401 — side effects
24
+ analysis,
25
+ overview,
26
+ rabbit_reads,
27
+ redis_reads,
28
+ undo,
29
+ writes,
30
+ )
31
+ from queue_aiops.governance import READ_ONLY_ENV, is_read_only
32
+
33
+ __all__ = ["mcp", "main", "_safe_error", "tool_errors", "apply_read_only"]
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ def apply_read_only() -> list[str]:
39
+ """Unregister every write tool when read-only mode is on.
40
+
41
+ The ``@governed_tool`` harness already refuses writes in this mode, so this
42
+ is a second layer — and the one that matters for smaller local models:
43
+ a tool absent from ``list_tools()`` cannot be hallucinated into a call,
44
+ whereas a tool that exists but refuses invites retry loops. It also gives a
45
+ compliance reviewer something checkable — the write tools are simply not
46
+ exposed.
47
+
48
+ ``risk_level == "low"`` is the read/write discriminator; a smoke test
49
+ asserts it stays in agreement with each tool's ``[READ]``/``[WRITE]``
50
+ docstring tag so the two can never drift apart silently.
51
+
52
+ Returns the names that were removed (empty when not in read-only mode).
53
+ """
54
+ if not is_read_only():
55
+ return []
56
+ registry = mcp._tool_manager._tools
57
+ dropped = [
58
+ name
59
+ for name, tool in registry.items()
60
+ if getattr(getattr(tool, "fn", None), "_risk_level", "low") != "low"
61
+ ]
62
+ for name in dropped:
63
+ del registry[name]
64
+ if dropped:
65
+ logger.info(
66
+ "%s is set — read-only mode: %d write tool(s) not exposed",
67
+ READ_ONLY_ENV, len(dropped),
68
+ )
69
+ return dropped
70
+
71
+
72
+ def main() -> None:
73
+ """Run the MCP server over stdio."""
74
+ logging.basicConfig(level=logging.INFO)
75
+ apply_read_only()
76
+ mcp.run(transport="stdio")
@@ -28,6 +28,10 @@ def list_queues(vhost: Optional[str] = None, target: Optional[str] = None) -> di
28
28
  Args:
29
29
  vhost: Restrict to one vhost (the default vhost is '/'); omit for all.
30
30
  target: rabbitmq target name from config; omit for the default.
31
+
32
+ Returns an envelope: {"queues": [...], "returned": N, "limit": L,
33
+ "truncated": bool}. When "truncated" is true there is more than was
34
+ returned — do not treat this as the complete set.
31
35
  """
32
36
  return ops.list_queues(_get_connection(target), vhost)
33
37
 
@@ -54,6 +58,10 @@ def list_connections(target: Optional[str] = None) -> dict:
54
58
 
55
59
  Args:
56
60
  target: rabbitmq target name from config; omit for the default.
61
+
62
+ Returns an envelope: {"connections": [...], "returned": N, "limit": L,
63
+ "truncated": bool}. When "truncated" is true there is more than was
64
+ returned — do not treat this as the complete set.
57
65
  """
58
66
  return ops.list_connections(_get_connection(target))
59
67
 
@@ -66,6 +74,10 @@ def list_channels(target: Optional[str] = None) -> dict:
66
74
 
67
75
  Args:
68
76
  target: rabbitmq target name from config; omit for the default.
77
+
78
+ Returns an envelope: {"channels": [...], "returned": N, "limit": L,
79
+ "truncated": bool}. When "truncated" is true there is more than was
80
+ returned — do not treat this as the complete set.
69
81
  """
70
82
  return ops.list_channels(_get_connection(target))
71
83
 
@@ -79,6 +91,10 @@ def list_policies(vhost: Optional[str] = None, target: Optional[str] = None) ->
79
91
  Args:
80
92
  vhost: Restrict to one vhost (the default vhost is '/'); omit for all.
81
93
  target: rabbitmq target name from config; omit for the default.
94
+
95
+ Returns an envelope: {"policies": [...], "returned": N, "limit": L,
96
+ "truncated": bool}. When "truncated" is true there is more than was
97
+ returned — do not treat this as the complete set.
82
98
  """
83
99
  return ops.list_policies(_get_connection(target), vhost)
84
100
 
@@ -39,6 +39,10 @@ def redis_clients(target: Optional[str] = None) -> dict:
39
39
 
40
40
  Args:
41
41
  target: redis target name from config; omit for the default.
42
+
43
+ Returns an envelope: {"clients": [...], "returned": N, "limit": L,
44
+ "truncated": bool}. When "truncated" is true there is more than was
45
+ returned — do not treat this as the complete set.
42
46
  """
43
47
  return ops.list_clients(_get_connection(target))
44
48
 
@@ -52,6 +56,10 @@ def redis_slowlog(count: int = 128, target: Optional[str] = None) -> dict:
52
56
  Args:
53
57
  count: Max entries to pull (capped at 128).
54
58
  target: redis target name from config; omit for the default.
59
+
60
+ Returns an envelope: {"entries": [...], "returned": N, "limit": L,
61
+ "truncated": bool}. When "truncated" is true there is more than was
62
+ returned — do not treat this as the complete set.
55
63
  """
56
64
  return ops.slowlog(_get_connection(target), count)
57
65
 
@@ -94,5 +102,9 @@ def redis_big_keys(top: int = 20, target: Optional[str] = None) -> dict:
94
102
  Args:
95
103
  top: How many key rows to return, largest first (default 20).
96
104
  target: redis target name from config; omit for the default.
105
+
106
+ Returns an envelope: {"topKeys": [...], "returned": N, "limit": L,
107
+ "truncated": bool}. When "truncated" is true there is more than was
108
+ returned — do not treat this as the complete set.
97
109
  """
98
110
  return ops.big_key_sample(_get_connection(target), top)