queue-aiops 0.9.4__tar.gz → 0.11.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 (100) hide show
  1. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.claude-plugin/plugin.json +1 -1
  2. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.mcp.json +1 -1
  3. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/CHANGELOG.md +25 -0
  4. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/PKG-INFO +1 -1
  5. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/RELEASE_NOTES.md +3 -0
  6. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/SECURITY.md +1 -1
  7. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/openclaw.plugin.json +1 -1
  8. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/package.json +1 -1
  9. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/pyproject.toml +4 -4
  10. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/_root.py +1 -1
  11. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/rabbit_cmds.py +5 -5
  12. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/redis_cmds.py +2 -2
  13. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/undo.py +3 -3
  14. queue_aiops-0.11.0/queue_aiops/mcp_server/_annotations.py +66 -0
  15. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/_shared.py +2 -2
  16. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/server.py +8 -3
  17. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/analysis.py +1 -1
  18. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/overview.py +1 -1
  19. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/rabbit_reads.py +1 -1
  20. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/redis_reads.py +1 -1
  21. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/undo.py +4 -4
  22. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/writes.py +1 -1
  23. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/server.json +2 -2
  24. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/agent-guardrails.md +1 -1
  25. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_analysis.py +1 -1
  26. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_audited.py +3 -2
  27. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_governed_exit.py +1 -1
  28. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_rabbit_writes.py +1 -1
  29. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_writes.py +3 -3
  30. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_audit.py +1 -1
  31. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_decorators.py +1 -1
  32. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_patterns.py +1 -1
  33. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_governance_persistence.py +1 -1
  34. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_mcp_reads.py +12 -12
  35. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_optional_fields.py +1 -1
  36. queue_aiops-0.11.0/tests/test_package_layout.py +62 -0
  37. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_self_lockout.py +4 -4
  38. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_smoke.py +9 -9
  39. queue_aiops-0.11.0/tests/test_tool_annotations.py +139 -0
  40. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_undo_executor.py +5 -5
  41. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_writes.py +7 -7
  42. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/uv.lock +1 -1
  43. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.github/workflows/mcp-publish.yml +0 -0
  44. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.github/workflows/publish.yml +0 -0
  45. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.gitignore +0 -0
  46. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/Dockerfile +0 -0
  47. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/LICENSE +0 -0
  48. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/README.md +0 -0
  49. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/docs/VERIFICATION.md +0 -0
  50. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/glama.json +0 -0
  51. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/__init__.py +0 -0
  52. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/__init__.py +0 -0
  53. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/_common.py +0 -0
  54. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/analyze.py +0 -0
  55. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/doctor.py +0 -0
  56. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/init.py +0 -0
  57. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/overview.py +0 -0
  58. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/secret.py +0 -0
  59. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/config.py +0 -0
  60. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/connection.py +0 -0
  61. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/doctor.py +0 -0
  62. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/__init__.py +0 -0
  63. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/audit.py +0 -0
  64. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/budget.py +0 -0
  65. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/decorators.py +0 -0
  66. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/outcome.py +0 -0
  67. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/paths.py +0 -0
  68. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/patterns.py +0 -0
  69. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/policy.py +0 -0
  70. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/sanitize.py +0 -0
  71. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/undo.py +0 -0
  72. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/__init__.py +0 -0
  73. {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/__init__.py +0 -0
  74. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/__init__.py +0 -0
  75. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/_util.py +0 -0
  76. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/analysis.py +0 -0
  77. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/overview.py +0 -0
  78. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/rabbit_reads.py +0 -0
  79. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/redis_reads.py +0 -0
  80. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/writes.py +0 -0
  81. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/platform.py +0 -0
  82. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/secretstore.py +0 -0
  83. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/SKILL.md +0 -0
  84. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/capabilities.md +0 -0
  85. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/cli-reference.md +0 -0
  86. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/setup-guide.md +0 -0
  87. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/smithery.yaml +0 -0
  88. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/conftest.py +0 -0
  89. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/fakes.py +0 -0
  90. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_reads.py +0 -0
  91. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_secret.py +0 -0
  92. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_connection.py +0 -0
  93. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_doctor.py +0 -0
  94. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_policy.py +0 -0
  95. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_init.py +0 -0
  96. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_int_fidelity.py +0 -0
  97. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_platform.py +0 -0
  98. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_reads.py +0 -0
  99. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_secretstore.py +0 -0
  100. {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_self_kill.py +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "queue-aiops",
4
4
  "displayName": "Queue AIops",
5
- "version": "0.9.4",
5
+ "version": "0.11.0",
6
6
  "description": "Governed redis + rabbitmq ops: memory/latency/backlog/churn RCA, policies. 28 tools.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "queue-aiops==0.9.4",
8
+ "queue-aiops==0.11.0",
9
9
  "queue-aiops-mcp"
10
10
  ]
11
11
  }
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.11.0 — 2026-10-15
4
+
5
+ ### Fixed
6
+ - The MCP server moved from a top-level `mcp_server` package to
7
+ `queue_aiops.mcp_server`. Every AIops tool used to ship its own top-level
8
+ `mcp_server`, so installing two of them into one environment merged the two
9
+ into one package: `queue-aiops-mcp` could start another tool's server, and
10
+ `queue-aiops undo` wrote audit and undo rows into the other tool's home
11
+ directory. Installs through `uvx`, the Claude Code plugin or the OpenClaw
12
+ bundle were isolated and unaffected. The `queue-aiops-mcp` command is
13
+ unchanged; only code that imported `mcp_server` directly must now import
14
+ `queue_aiops.mcp_server`.
15
+
16
+ ## v0.10.0 — 2026-10-15
17
+
18
+ ### Added
19
+ - MCP tool annotations derived from each tool's governance `risk_level`, so
20
+ clients can gate approval from `tools/list` alone. `low` tools advertise
21
+ `readOnlyHint: true`; every other tool advertises `readOnlyHint: false` and
22
+ `destructiveHint: true`. The `medium` tier includes deletes and hard
23
+ power-offs, so a tool is never advertised as non-destructive on the strength
24
+ of its tier. `idempotentHint` only repeats an explicit declaration. The exact
25
+ level is published in `_meta` under `io.github.AIops-tools/riskLevel`. These
26
+ are hints for the client; audit and undo are unchanged.
27
+
3
28
  ## v0.9.4 — 2026-09-16
4
29
 
5
30
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: queue-aiops
3
- Version: 0.9.4
3
+ Version: 0.11.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 Zhou <zhouwei008@gmail.com>
6
6
  License-Expression: MIT
@@ -1,5 +1,8 @@
1
1
  # Release notes — queue-aiops 0.4.0
2
2
 
3
+ > Historical — this file covers the 0.4.0 release only and is not updated.
4
+ > For every release since, see [CHANGELOG.md](CHANGELOG.md) and the GitHub releases.
5
+
3
6
  Previous release: 0.3.0.
4
7
 
5
8
  ## BREAKING — the authorization layer is removed
@@ -87,7 +87,7 @@ background services.
87
87
  ## Static Analysis
88
88
 
89
89
  ```bash
90
- uvx bandit -r queue_aiops/ mcp_server/
90
+ uvx bandit -r queue_aiops/
91
91
  uv run ruff check .
92
92
  ```
93
93
 
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "queue-aiops==0.9.4",
14
+ "queue-aiops==0.11.0",
15
15
  "queue-aiops-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/queue-aiops",
3
- "version": "0.9.4",
3
+ "version": "0.11.0",
4
4
  "description": "Governed redis + rabbitmq ops: memory/latency/backlog/churn RCA, policies. 28 tools.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "queue-aiops"
7
- version = "0.9.4"
7
+ version = "0.11.0"
8
8
  description = "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)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -29,10 +29,10 @@ dev = [
29
29
 
30
30
  [project.scripts]
31
31
  queue-aiops = "queue_aiops.cli:app"
32
- queue-aiops-mcp = "mcp_server.server:main"
32
+ queue-aiops-mcp = "queue_aiops.mcp_server.server:main"
33
33
 
34
34
  [tool.hatch.build.targets.wheel]
35
- packages = ["queue_aiops", "mcp_server"]
35
+ packages = ["queue_aiops"]
36
36
 
37
37
  [tool.ruff]
38
38
  line-length = 100
@@ -48,7 +48,7 @@ select = ["E", "F", "I", "N", "W", "UP"]
48
48
  "queue_aiops/governance/*.py" = ["E501", "N818"]
49
49
  # MCP server files are reflected by FastMCP/Pydantic; PEP 604 unions (X | None)
50
50
  # crash on older mcp/pydantic, so they intentionally use Optional[X].
51
- "mcp_server/**/*.py" = ["UP007", "UP045"]
51
+ "queue_aiops/mcp_server/**/*.py" = ["UP007", "UP045"]
52
52
  # The MCP entry keeps an explicit Python-version guard even though the declared
53
53
  # minimum is already 3.11.
54
54
  "queue_aiops/cli/_root.py" = ["UP036"]
@@ -53,7 +53,7 @@ def mcp_cmd() -> None:
53
53
  )
54
54
  raise typer.Exit(2)
55
55
 
56
- from mcp_server.server import main as _mcp_main
56
+ from queue_aiops.mcp_server.server import main as _mcp_main
57
57
 
58
58
  _mcp_main()
59
59
 
@@ -155,7 +155,7 @@ def rabbit_purge(
155
155
  dry_run: DryRunOption = False,
156
156
  ) -> None:
157
157
  """Purge all ready messages from a queue (IRREVERSIBLE, risk=high)."""
158
- from mcp_server.tools import writes as gov
158
+ from queue_aiops.mcp_server.tools import writes as gov
159
159
 
160
160
  if dry_run:
161
161
  # Through the governed twin, so the preview is audited and any harness
@@ -178,7 +178,7 @@ def rabbit_delete_queue(
178
178
  dry_run: DryRunOption = False,
179
179
  ) -> None:
180
180
  """Delete a queue (risk=high; undo re-declares the captured definition)."""
181
- from mcp_server.tools import writes as gov
181
+ from queue_aiops.mcp_server.tools import writes as gov
182
182
 
183
183
  if dry_run:
184
184
  # Through the governed twin, so the preview is audited and any harness
@@ -207,7 +207,7 @@ def rabbit_declare_queue(
207
207
  dry_run: DryRunOption = False,
208
208
  ) -> None:
209
209
  """Declare (create) a queue (undo deletes it when newly created)."""
210
- from mcp_server.tools import writes as gov
210
+ from queue_aiops.mcp_server.tools import writes as gov
211
211
 
212
212
  if dry_run:
213
213
  # Through the governed twin, so the preview is audited and any harness
@@ -242,7 +242,7 @@ def rabbit_set_policy(
242
242
  dry_run: DryRunOption = False,
243
243
  ) -> None:
244
244
  """Create/update a policy (reversible — prior policy captured for undo)."""
245
- from mcp_server.tools import writes as gov
245
+ from queue_aiops.mcp_server.tools import writes as gov
246
246
 
247
247
  try:
248
248
  definition_obj = json.loads(definition)
@@ -276,7 +276,7 @@ def rabbit_delete_policy(
276
276
  dry_run: DryRunOption = False,
277
277
  ) -> None:
278
278
  """Delete a policy (reversible — its definition is captured for undo)."""
279
- from mcp_server.tools import writes as gov
279
+ from queue_aiops.mcp_server.tools import writes as gov
280
280
 
281
281
  if dry_run:
282
282
  # Through the governed twin, so the preview is audited and any harness
@@ -134,7 +134,7 @@ def redis_config_set(
134
134
  dry_run: DryRunOption = False,
135
135
  ) -> None:
136
136
  """Set one config parameter (reversible — prior value captured for undo)."""
137
- from mcp_server.tools import writes as gov
137
+ from queue_aiops.mcp_server.tools import writes as gov
138
138
 
139
139
  if dry_run:
140
140
  # Through the governed call: redis_config_set refuses the self-affecting
@@ -160,7 +160,7 @@ def redis_kill_client(
160
160
  dry_run: DryRunOption = False,
161
161
  ) -> None:
162
162
  """Disconnect one client (irreversible; its info is captured as priorState)."""
163
- from mcp_server.tools import writes as gov
163
+ from queue_aiops.mcp_server.tools import writes as gov
164
164
 
165
165
  who = str(client_id) if client_id else addr
166
166
  if dry_run:
@@ -1,7 +1,7 @@
1
1
  """``queue-aiops undo`` — list recorded undo tokens and apply them.
2
2
 
3
3
  Real execution is delegated to the ``@governed_tool``-wrapped functions in
4
- ``mcp_server.tools.undo`` so an applied undo is audited on the SAME governance
4
+ ``queue_aiops.mcp_server.tools.undo`` so an applied undo is audited on the SAME governance
5
5
  path as any other write (the inverse tool it dispatches is itself governed).
6
6
  """
7
7
 
@@ -38,7 +38,7 @@ def undo_list_cmd(
38
38
  target: TargetOption = None,
39
39
  ) -> None:
40
40
  """List recorded, not-yet-applied undo tokens."""
41
- from mcp_server.tools import undo as gov
41
+ from queue_aiops.mcp_server.tools import undo as gov
42
42
 
43
43
  console.print_json(json.dumps(gov.undo_list(limit=limit, target=target)))
44
44
 
@@ -49,7 +49,7 @@ def undo_apply_cmd(
49
49
  undo_id: UndoIdArg, target: TargetOption = None, dry_run: DryRunOption = False
50
50
  ) -> None:
51
51
  """Apply a recorded undo (dispatches its inverse tool; dry-run + confirm)."""
52
- from mcp_server.tools import undo as gov
52
+ from queue_aiops.mcp_server.tools import undo as gov
53
53
 
54
54
  if dry_run:
55
55
  # This path already routed through the governed call, but rendered the
@@ -0,0 +1,66 @@
1
+ """Derive MCP tool annotations from the governance risk level.
2
+
3
+ Every tool already declares ``risk_level`` on ``@governed_tool``; this module
4
+ publishes it to MCP clients as the standard ``ToolAnnotations`` hints plus the
5
+ exact level under ``_meta``, so a client can gate approval without reading our
6
+ docstrings.
7
+
8
+ Only the safe direction is ever asserted:
9
+
10
+ - ``low`` → ``readOnlyHint: true``. Backed by the smoke-test invariant that
11
+ ``risk_level == "low"`` exactly when the docstring says ``[READ]``.
12
+ - anything else → ``readOnlyHint: false, destructiveHint: true``. The
13
+ ``medium`` tier is NOT non-destructive — it holds deletes, hard power-offs,
14
+ state kills and stat resets — so claiming ``destructiveHint: false`` from the
15
+ tier would tell a client it may skip approval for a delete. Over-warning
16
+ costs a confirmation prompt; under-warning costs data.
17
+ - ``idempotentHint`` only echoes an explicit ``@governed_tool(idempotent=True)``
18
+ declaration; it is never inferred.
19
+ - ``openWorldHint`` is left unset (spec default ``true``): every tool talks to
20
+ an external system.
21
+
22
+ This file is identical across the AIops line; keep it that way.
23
+ """
24
+
25
+ from typing import Any
26
+
27
+ from mcp.types import ToolAnnotations
28
+
29
+ RISK_META_KEY = "io.github.AIops-tools/riskLevel"
30
+ RISK_LEVELS = frozenset({"low", "medium", "high", "critical"})
31
+
32
+
33
+ def annotations_for(risk_level: str, idempotent: bool) -> ToolAnnotations:
34
+ """Return the MCP hints that a tool of ``risk_level`` may honestly claim."""
35
+ if risk_level not in RISK_LEVELS:
36
+ raise ValueError(
37
+ f"unknown risk_level {risk_level!r}; expected one of {sorted(RISK_LEVELS)}"
38
+ )
39
+ if risk_level == "low":
40
+ return ToolAnnotations(read_only_hint=True)
41
+ return ToolAnnotations(
42
+ read_only_hint=False,
43
+ destructive_hint=True,
44
+ idempotent_hint=idempotent,
45
+ )
46
+
47
+
48
+ def apply_risk_annotations(server: Any) -> None:
49
+ """Replace every registered tool with a copy carrying its risk annotations.
50
+
51
+ Fails fast on a tool without ``@governed_tool`` metadata: an unannotated
52
+ tool would be advertised with the spec defaults, i.e. as a destructive
53
+ write with no stated risk level — wrong for a read, silent for a write.
54
+ """
55
+ tools = server._tool_manager._tools # same private registry undo.py relies on
56
+ for name, tool in list(tools.items()):
57
+ risk_level = getattr(tool.fn, "_risk_level", None)
58
+ if risk_level is None:
59
+ raise RuntimeError(f"tool {name!r} has no @governed_tool risk_level")
60
+ idempotent = bool(getattr(tool.fn, "_idempotent", False))
61
+ tools[name] = tool.model_copy(
62
+ update={
63
+ "annotations": annotations_for(risk_level, idempotent),
64
+ "meta": {**(tool.meta or {}), RISK_META_KEY: risk_level},
65
+ }
66
+ )
@@ -1,8 +1,8 @@
1
1
  """Shared MCP server primitives: the MCPServer instance, connection helper,
2
2
  error sanitisation, and the ``@tool_errors`` decorator.
3
3
 
4
- Tool modules under ``mcp_server/tools/`` import ``mcp`` from here and register
5
- their ``@mcp.tool()`` functions onto it. ``mcp_server/server.py`` then imports
4
+ Tool modules under ``queue_aiops/mcp_server/tools/`` import ``mcp`` from here and register
5
+ their ``@mcp.tool()`` functions onto it. ``queue_aiops/mcp_server/server.py`` then imports
6
6
  those modules and runs the server.
7
7
 
8
8
  Keep ``Optional[X]`` (never PEP 604 ``X | None``) in any reflected tool
@@ -1,6 +1,6 @@
1
1
  """MCP server wrapping queue-aiops operations (stdio transport).
2
2
 
3
- Thin adapter layer: each ``@mcp.tool()`` function (in ``mcp_server/tools/``)
3
+ Thin adapter layer: each ``@mcp.tool()`` function (in ``queue_aiops/mcp_server/tools/``)
4
4
  delegates to the ``queue_aiops`` ops package and is wrapped with the
5
5
  queue-aiops ``@governed_tool`` harness (audit / budget / undo / risk-tier).
6
6
 
@@ -17,11 +17,12 @@ License: MIT
17
17
  import logging
18
18
  import os
19
19
 
20
- from mcp_server._shared import _safe_error, mcp, tool_errors
20
+ from queue_aiops.mcp_server._annotations import apply_risk_annotations
21
+ from queue_aiops.mcp_server._shared import _safe_error, mcp, tool_errors
21
22
 
22
23
  # Importing the tool modules registers every @mcp.tool() onto the shared
23
24
  # `mcp` instance. Order does not matter; each module is self-contained.
24
- from mcp_server.tools import ( # noqa: F401 — side effects
25
+ from queue_aiops.mcp_server.tools import ( # noqa: F401 — side effects
25
26
  analysis,
26
27
  overview,
27
28
  rabbit_reads,
@@ -30,6 +31,10 @@ from mcp_server.tools import ( # noqa: F401 — side effects
30
31
  writes,
31
32
  )
32
33
 
34
+ # Publish each tool's governance risk level as MCP annotations. Runs after
35
+ # every tool module has registered, so no tool is advertised unannotated.
36
+ apply_risk_annotations(mcp)
37
+
33
38
  __all__ = ["mcp", "main", "_safe_error", "tool_errors"]
34
39
 
35
40
  logger = logging.getLogger(__name__)
@@ -2,8 +2,8 @@
2
2
 
3
3
  from typing import Any, Optional
4
4
 
5
- from mcp_server._shared import _get_connection, mcp, tool_errors
6
5
  from queue_aiops.governance import governed_tool
6
+ from queue_aiops.mcp_server._shared import _get_connection, mcp, tool_errors
7
7
  from queue_aiops.ops import analysis as ops
8
8
  from queue_aiops.ops._util import as_obj, num
9
9
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
- from mcp_server._shared import _get_connection, mcp, tool_errors
6
5
  from queue_aiops.governance import governed_tool
6
+ from queue_aiops.mcp_server._shared import _get_connection, mcp, tool_errors
7
7
  from queue_aiops.ops import overview as ops
8
8
 
9
9
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
- from mcp_server._shared import _get_connection, mcp, tool_errors
6
5
  from queue_aiops.governance import governed_tool
6
+ from queue_aiops.mcp_server._shared import _get_connection, mcp, tool_errors
7
7
  from queue_aiops.ops import rabbit_reads as ops
8
8
 
9
9
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
- from mcp_server._shared import _get_connection, mcp, tool_errors
6
5
  from queue_aiops.governance import governed_tool
6
+ from queue_aiops.mcp_server._shared import _get_connection, mcp, tool_errors
7
7
  from queue_aiops.ops import redis_reads as ops
8
8
 
9
9
 
@@ -16,26 +16,26 @@ design, inverses in this line key off ids/names, not credentials.
16
16
  import json
17
17
  from typing import Any, Optional
18
18
 
19
- from mcp_server._shared import mcp, tool_errors
20
19
  from queue_aiops.governance import governed_tool
21
20
  from queue_aiops.governance.undo import get_undo_store
21
+ from queue_aiops.mcp_server._shared import mcp, tool_errors
22
22
 
23
23
 
24
24
  def _resolve_tool(name: str) -> Any:
25
25
  """Return the governed callable registered under ``name`` (or None).
26
26
 
27
27
  The CLI reaches this through ``undo apply``, whose process only imports
28
- ``mcp_server.tools.undo`` — every other tool module is imported lazily inside
28
+ ``queue_aiops.mcp_server.tools.undo`` — every other tool module is imported lazily inside
29
29
  its own CLI command and so is absent here. Without forcing a full load,
30
30
  ``undo_apply`` would fail with "inverse tool not registered" for every write
31
31
  tool when driven from the CLI (the MCP entry point loads all modules, so it
32
- never hit this). If the first lookup misses, import ``mcp_server.server`` —
32
+ never hit this). If the first lookup misses, import ``queue_aiops.mcp_server.server`` —
33
33
  which registers every ``@mcp.tool()`` as an import side effect — and retry.
34
34
  Imported lazily to avoid the undo↔server cycle.
35
35
  """
36
36
  tool = mcp._tool_manager._tools.get(name)
37
37
  if tool is None:
38
- import mcp_server.server # noqa: F401 — registers all tools as a side effect
38
+ import queue_aiops.mcp_server.server # noqa: F401 — registers all tools as a side effect
39
39
 
40
40
  tool = mcp._tool_manager._tools.get(name)
41
41
  return getattr(tool, "fn", None) if tool else None
@@ -13,8 +13,8 @@ delete_policy = medium.
13
13
 
14
14
  from typing import Any, Optional
15
15
 
16
- from mcp_server._shared import _get_connection, mcp, tool_errors
17
16
  from queue_aiops.governance import governed_tool
17
+ from queue_aiops.mcp_server._shared import _get_connection, mcp, tool_errors
18
18
  from queue_aiops.ops import writes as ops
19
19
 
20
20
  # ── undo descriptors (built from the fetched before-state) ──────────────────
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/AIops-tools/Queue-AIops",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.9.4",
10
+ "version": "0.11.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "queue-aiops",
15
- "version": "0.9.4",
15
+ "version": "0.11.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -48,7 +48,7 @@ because fragmentation is checked before big keys. Do not let the model treat `fi
48
48
  as the headline — make it weigh every finding's `evidence` and say which one it acted on.
49
49
  (Earlier versions of this file claimed these lists were ranked worst-first. They never were.)
50
50
 
51
- Copy this into your agent's system prompt:
51
+ Recommended setup for a local model (adapt it to your agent client):
52
52
 
53
53
  ```text
54
54
  You operate a RabbitMQ broker or a Redis instance through the queue-aiops MCP
@@ -244,7 +244,7 @@ def _scanned_sources():
244
244
  by the drift gate, so it is not this repo's to edit.
245
245
  """
246
246
  root = Path(ops.__file__).parents[2]
247
- found = sorted(root.glob("queue_aiops/**/*.py")) + sorted(root.glob("mcp_server/**/*.py"))
247
+ found = sorted(root.glob("queue_aiops/**/*.py")) # includes queue_aiops/mcp_server
248
248
  files = [p for p in found if "governance" not in p.parts]
249
249
  assert len(files) > 10, f"source sweep found only {len(files)} modules — the glob is wrong"
250
250
  return files
@@ -52,11 +52,12 @@ def _chain(fn):
52
52
 
53
53
  def _calls_an_mcp_tool(fn) -> bool:
54
54
  source = inspect.getsource(fn)
55
- if "mcp_server" in source:
55
+ if "queue_aiops.mcp_server" in source:
56
56
  return True
57
57
  names = set()
58
58
  for node in ast.parse(open(inspect.getsourcefile(fn)).read()).body:
59
- if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("mcp_server"):
59
+ module = (node.module or "") if isinstance(node, ast.ImportFrom) else ""
60
+ if module.startswith("queue_aiops.mcp_server"):
60
61
  names |= {a.asname or a.name for a in node.names}
61
62
  used = {n.id for n in ast.walk(ast.parse(textwrap.dedent(source))) if isinstance(n, ast.Name)}
62
63
  return bool(names & used)
@@ -28,7 +28,7 @@ import typer
28
28
  from queue_aiops.cli._common import checked
29
29
 
30
30
  CLI_DIR = pathlib.Path(__file__).resolve().parents[1] / "queue_aiops" / "cli"
31
- TOOLS_DIR = pathlib.Path(__file__).resolve().parents[1] / "mcp_server" / "tools"
31
+ TOOLS_DIR = pathlib.Path(__file__).resolve().parents[1] / "queue_aiops" / "mcp_server" / "tools"
32
32
 
33
33
  #: Helpers that end the command non-zero when the payload reports a failure.
34
34
  CHECKERS = ['checked', 'dry_run_preview']
@@ -44,7 +44,7 @@ def gov_home(tmp_path, monkeypatch):
44
44
 
45
45
 
46
46
  def _wire(monkeypatch, routes):
47
- from mcp_server.tools import writes as gov
47
+ from queue_aiops.mcp_server.tools import writes as gov
48
48
 
49
49
  conn = rabbit_conn(routes)
50
50
  monkeypatch.setattr(gov, "_get_connection", lambda target=None: conn)
@@ -1,7 +1,7 @@
1
1
  """CLI confirmed-write path — past dry-run, through governance, onto disk.
2
2
 
3
3
  The CLI write commands delegate real execution to the ``@governed_tool``
4
- functions in ``mcp_server.tools``. These tests drive ``redis config-set`` and
4
+ functions in ``queue_aiops.mcp_server.tools``. These tests drive ``redis config-set`` and
5
5
  ``rabbitmq purge`` PAST the dry-run branch and the double-confirm prompts and
6
6
  assert the call really went through the governed path (audit row on disk) —
7
7
  the regression test for the "CLI writes were unaudited" line-wide fix.
@@ -35,7 +35,7 @@ def gov_home(tmp_path, monkeypatch):
35
35
  @pytest.fixture
36
36
  def cache_conn(monkeypatch):
37
37
  """A fake redis connection wired into the governed write module."""
38
- from mcp_server.tools import writes as gov
38
+ from queue_aiops.mcp_server.tools import writes as gov
39
39
 
40
40
  conn = redis_conn(config={"maxmemory-policy": "noeviction"})
41
41
  monkeypatch.setattr(gov, "_get_connection", lambda target=None: conn)
@@ -45,7 +45,7 @@ def cache_conn(monkeypatch):
45
45
  @pytest.fixture
46
46
  def broker_conn(monkeypatch):
47
47
  """A fake rabbitmq connection wired into the governed write module."""
48
- from mcp_server.tools import writes as gov
48
+ from queue_aiops.mcp_server.tools import writes as gov
49
49
 
50
50
  conn = rabbit_conn({
51
51
  ("GET", "/api/queues/%2F/orders"): {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Portable across the whole tool line: references ONLY
4
4
  ``queue_aiops.governance.audit`` and binds state to an isolated home via
5
- ``QUEUE_AIOPS_HOME``. No ops / cli / connection / mcp_server imports.
5
+ ``QUEUE_AIOPS_HOME``. No ops / cli / connection / queue_aiops.mcp_server imports.
6
6
 
7
7
  Exercises row writes with every column, query filters, stats aggregation,
8
8
  size-based rotation + archive cleanup, permission hardening, in-place column
@@ -3,7 +3,7 @@
3
3
  Portable across the whole tool line: references ONLY
4
4
  ``queue_aiops.governance.*`` and binds state to an isolated home via
5
5
  ``QUEUE_AIOPS_HOME``. Every governed function under test is SYNTHETIC and
6
- defined inline — no real ops / cli / connection / mcp_server tool is imported.
6
+ defined inline — no real ops / cli / connection / queue_aiops.mcp_server tool is imported.
7
7
 
8
8
  Exercises the async wrapper (audit parity with sync), the error-capture path
9
9
  with secret redaction, BudgetExceeded propagation, positional /
@@ -2,7 +2,7 @@
2
2
 
3
3
  Portable across the whole tool line: references ONLY
4
4
  ``queue_aiops.governance.patterns`` and binds state to an isolated home via
5
- ``QUEUE_AIOPS_HOME``. No ops / cli / connection / mcp_server imports.
5
+ ``QUEUE_AIOPS_HOME``. No ops / cli / connection / queue_aiops.mcp_server imports.
6
6
 
7
7
  Exercises pattern loading + signature validation, the armable preconditions,
8
8
  skill/tool/target matching, rate limiting, the circuit breaker's state
@@ -124,7 +124,7 @@ def test_real_write_tool_persists_priorstate_undo(gov_home, monkeypatch):
124
124
  """End-to-end through a REAL product write tool: redis_config_set must
125
125
  capture the parameter's prior value before setting and persist the
126
126
  restore-undo on disk."""
127
- from mcp_server.tools import writes as gov
127
+ from queue_aiops.mcp_server.tools import writes as gov
128
128
  from tests.fakes import redis_conn
129
129
 
130
130
  conn = redis_conn(config={"maxmemory-policy": "noeviction"})