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.
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.claude-plugin/plugin.json +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.mcp.json +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/CHANGELOG.md +25 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/PKG-INFO +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/RELEASE_NOTES.md +3 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/SECURITY.md +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/openclaw.plugin.json +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/package.json +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/pyproject.toml +4 -4
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/_root.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/rabbit_cmds.py +5 -5
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/redis_cmds.py +2 -2
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/undo.py +3 -3
- queue_aiops-0.11.0/queue_aiops/mcp_server/_annotations.py +66 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/_shared.py +2 -2
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/server.py +8 -3
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/analysis.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/overview.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/rabbit_reads.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/redis_reads.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/undo.py +4 -4
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/writes.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/server.json +2 -2
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/agent-guardrails.md +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_analysis.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_audited.py +3 -2
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_governed_exit.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_rabbit_writes.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_writes.py +3 -3
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_audit.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_decorators.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_patterns.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_governance_persistence.py +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_mcp_reads.py +12 -12
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_optional_fields.py +1 -1
- queue_aiops-0.11.0/tests/test_package_layout.py +62 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_self_lockout.py +4 -4
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_smoke.py +9 -9
- queue_aiops-0.11.0/tests/test_tool_annotations.py +139 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_undo_executor.py +5 -5
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_writes.py +7 -7
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/uv.lock +1 -1
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.github/workflows/mcp-publish.yml +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.github/workflows/publish.yml +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/.gitignore +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/Dockerfile +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/LICENSE +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/README.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/docs/VERIFICATION.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/glama.json +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/_common.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/analyze.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/doctor.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/init.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/overview.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/cli/secret.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/config.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/connection.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/doctor.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/audit.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/budget.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/decorators.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/outcome.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/paths.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/patterns.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/policy.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/sanitize.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/governance/undo.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/__init__.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/_util.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/analysis.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/overview.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/rabbit_reads.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/redis_reads.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/ops/writes.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/platform.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/queue_aiops/secretstore.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/SKILL.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/capabilities.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/cli-reference.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/skills/queue-aiops/references/setup-guide.md +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/smithery.yaml +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/conftest.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/fakes.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_reads.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_cli_secret.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_connection.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_doctor.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_gov_policy.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_init.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_int_fidelity.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_platform.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_reads.py +0 -0
- {queue_aiops-0.9.4 → queue_aiops-0.11.0}/tests/test_secretstore.py +0 -0
- {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.
|
|
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",
|
|
@@ -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.
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "queue-aiops"
|
|
7
|
-
version = "0.
|
|
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"
|
|
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"]
|
|
@@ -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.
|
|
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.
|
|
10
|
+
"version": "0.11.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "queue-aiops",
|
|
15
|
-
"version": "0.
|
|
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
|
-
|
|
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"))
|
|
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)
|
|
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"})
|