queue-aiops 0.10.0__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.10.0 → queue_aiops-0.11.0}/.claude-plugin/plugin.json +1 -1
  2. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/.mcp.json +1 -1
  3. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/CHANGELOG.md +13 -0
  4. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/PKG-INFO +1 -1
  5. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/SECURITY.md +1 -1
  6. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/openclaw.plugin.json +1 -1
  7. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/package.json +1 -1
  8. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/pyproject.toml +4 -4
  9. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/_root.py +1 -1
  10. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/rabbit_cmds.py +5 -5
  11. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/redis_cmds.py +2 -2
  12. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/undo.py +3 -3
  13. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/_shared.py +2 -2
  14. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/server.py +4 -4
  15. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/analysis.py +1 -1
  16. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/overview.py +1 -1
  17. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/rabbit_reads.py +1 -1
  18. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/redis_reads.py +1 -1
  19. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/undo.py +4 -4
  20. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/writes.py +1 -1
  21. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/server.json +2 -2
  22. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_analysis.py +1 -1
  23. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_audited.py +3 -2
  24. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_governed_exit.py +1 -1
  25. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_rabbit_writes.py +1 -1
  26. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_writes.py +3 -3
  27. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_gov_audit.py +1 -1
  28. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_gov_decorators.py +1 -1
  29. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_gov_patterns.py +1 -1
  30. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_governance_persistence.py +1 -1
  31. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_mcp_reads.py +12 -12
  32. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_optional_fields.py +1 -1
  33. queue_aiops-0.11.0/tests/test_package_layout.py +62 -0
  34. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_self_lockout.py +4 -4
  35. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_smoke.py +9 -9
  36. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_tool_annotations.py +17 -6
  37. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_undo_executor.py +5 -5
  38. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_writes.py +7 -7
  39. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/uv.lock +1 -1
  40. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/.github/workflows/mcp-publish.yml +0 -0
  41. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/.github/workflows/publish.yml +0 -0
  42. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/.gitignore +0 -0
  43. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/Dockerfile +0 -0
  44. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/LICENSE +0 -0
  45. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/README.md +0 -0
  46. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/RELEASE_NOTES.md +0 -0
  47. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/docs/VERIFICATION.md +0 -0
  48. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/glama.json +0 -0
  49. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/__init__.py +0 -0
  50. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/__init__.py +0 -0
  51. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/_common.py +0 -0
  52. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/analyze.py +0 -0
  53. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/doctor.py +0 -0
  54. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/init.py +0 -0
  55. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/overview.py +0 -0
  56. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/cli/secret.py +0 -0
  57. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/config.py +0 -0
  58. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/connection.py +0 -0
  59. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/doctor.py +0 -0
  60. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/__init__.py +0 -0
  61. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/audit.py +0 -0
  62. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/budget.py +0 -0
  63. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/decorators.py +0 -0
  64. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/outcome.py +0 -0
  65. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/paths.py +0 -0
  66. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/patterns.py +0 -0
  67. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/policy.py +0 -0
  68. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/sanitize.py +0 -0
  69. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/governance/undo.py +0 -0
  70. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/__init__.py +0 -0
  71. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/_annotations.py +0 -0
  72. {queue_aiops-0.10.0 → queue_aiops-0.11.0/queue_aiops}/mcp_server/tools/__init__.py +0 -0
  73. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/__init__.py +0 -0
  74. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/_util.py +0 -0
  75. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/analysis.py +0 -0
  76. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/overview.py +0 -0
  77. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/rabbit_reads.py +0 -0
  78. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/redis_reads.py +0 -0
  79. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/ops/writes.py +0 -0
  80. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/platform.py +0 -0
  81. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/queue_aiops/secretstore.py +0 -0
  82. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/skills/queue-aiops/SKILL.md +0 -0
  83. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/skills/queue-aiops/references/agent-guardrails.md +0 -0
  84. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/skills/queue-aiops/references/capabilities.md +0 -0
  85. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/skills/queue-aiops/references/cli-reference.md +0 -0
  86. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/skills/queue-aiops/references/setup-guide.md +0 -0
  87. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/smithery.yaml +0 -0
  88. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/conftest.py +0 -0
  89. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/fakes.py +0 -0
  90. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_reads.py +0 -0
  91. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_cli_secret.py +0 -0
  92. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_connection.py +0 -0
  93. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_doctor.py +0 -0
  94. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_gov_policy.py +0 -0
  95. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_init.py +0 -0
  96. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_int_fidelity.py +0 -0
  97. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_platform.py +0 -0
  98. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_reads.py +0 -0
  99. {queue_aiops-0.10.0 → queue_aiops-0.11.0}/tests/test_secretstore.py +0 -0
  100. {queue_aiops-0.10.0 → 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.10.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",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "queue-aiops==0.10.0",
8
+ "queue-aiops==0.11.0",
9
9
  "queue-aiops-mcp"
10
10
  ]
11
11
  }
@@ -1,5 +1,18 @@
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
+
3
16
  ## v0.10.0 — 2026-10-15
4
17
 
5
18
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: queue-aiops
3
- Version: 0.10.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
@@ -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.10.0",
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.10.0",
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.10.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", "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
@@ -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,12 +17,12 @@ License: MIT
17
17
  import logging
18
18
  import os
19
19
 
20
- from mcp_server._annotations import apply_risk_annotations
21
- 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
22
22
 
23
23
  # Importing the tool modules registers every @mcp.tool() onto the shared
24
24
  # `mcp` instance. Order does not matter; each module is self-contained.
25
- from mcp_server.tools import ( # noqa: F401 — side effects
25
+ from queue_aiops.mcp_server.tools import ( # noqa: F401 — side effects
26
26
  analysis,
27
27
  overview,
28
28
  rabbit_reads,
@@ -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.0",
10
+ "version": "0.11.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "queue-aiops",
15
- "version": "0.10.0",
15
+ "version": "0.11.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -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"})
@@ -61,7 +61,7 @@ def wire_redis(monkeypatch):
61
61
  for mod in ("analysis", "redis_reads", "overview"):
62
62
  import importlib
63
63
 
64
- m = importlib.import_module(f"mcp_server.tools.{mod}")
64
+ m = importlib.import_module(f"queue_aiops.mcp_server.tools.{mod}")
65
65
  monkeypatch.setattr(m, "_get_connection", lambda target=None, _c=conn: _c)
66
66
  return conn
67
67
 
@@ -71,7 +71,7 @@ def wire_redis(monkeypatch):
71
71
 
72
72
  @pytest.mark.unit
73
73
  def test_redis_read_wrappers_hit_expected_commands(wire_redis):
74
- from mcp_server.tools import redis_reads as t
74
+ from queue_aiops.mcp_server.tools import redis_reads as t
75
75
 
76
76
  assert t.redis_server_info()["version"] == "7.2.5"
77
77
  assert t.redis_memory_stats()["usedPctOfMax"] == 92.0
@@ -84,7 +84,7 @@ def test_redis_read_wrappers_hit_expected_commands(wire_redis):
84
84
 
85
85
  @pytest.mark.unit
86
86
  def test_rabbit_read_wrappers_hit_expected_endpoints(monkeypatch):
87
- from mcp_server.tools import rabbit_reads as t
87
+ from queue_aiops.mcp_server.tools import rabbit_reads as t
88
88
 
89
89
  conn = rabbit_conn({
90
90
  ("GET", "/api/overview"): {"rabbitmq_version": "3.13.2",
@@ -113,7 +113,7 @@ def test_rabbit_read_wrappers_hit_expected_endpoints(monkeypatch):
113
113
 
114
114
  @pytest.mark.unit
115
115
  def test_queue_overview_wrapper_dispatches_by_platform(wire_redis):
116
- from mcp_server.tools import overview as t
116
+ from queue_aiops.mcp_server.tools import overview as t
117
117
 
118
118
  out = t.queue_overview()
119
119
  assert out["platform"] == "redis"
@@ -125,7 +125,7 @@ def test_queue_overview_wrapper_dispatches_by_platform(wire_redis):
125
125
 
126
126
  @pytest.mark.unit
127
127
  def test_memory_pressure_rca_pulls_live_and_flags_noeviction_oom(wire_redis):
128
- from mcp_server.tools import analysis as t
128
+ from queue_aiops.mcp_server.tools import analysis as t
129
129
 
130
130
  out = t.redis_memory_pressure_rca()
131
131
  assert out["pressure"] is True
@@ -137,7 +137,7 @@ def test_memory_pressure_rca_pulls_live_and_flags_noeviction_oom(wire_redis):
137
137
 
138
138
  @pytest.mark.unit
139
139
  def test_latency_rca_pulls_live_slowlog_and_stalls(wire_redis):
140
- from mcp_server.tools import analysis as t
140
+ from queue_aiops.mcp_server.tools import analysis as t
141
141
 
142
142
  out = t.redis_latency_rca()
143
143
  top = out["slowlogPatterns"][0]
@@ -149,7 +149,7 @@ def test_latency_rca_pulls_live_slowlog_and_stalls(wire_redis):
149
149
 
150
150
  @pytest.mark.unit
151
151
  def test_memory_rca_injected_telemetry_skips_live_pull():
152
- from mcp_server.tools import analysis as t
152
+ from queue_aiops.mcp_server.tools import analysis as t
153
153
 
154
154
  out = t.redis_memory_pressure_rca(telemetry={
155
155
  "memory": {"usedPctOfMax": 20.0, "maxmemoryBytes": 1000,
@@ -161,7 +161,7 @@ def test_memory_rca_injected_telemetry_skips_live_pull():
161
161
 
162
162
  @pytest.mark.unit
163
163
  def test_backlog_rca_pulls_live_queues_and_classifies_no_consumers(monkeypatch):
164
- from mcp_server.tools import analysis as t
164
+ from queue_aiops.mcp_server.tools import analysis as t
165
165
 
166
166
  conn = rabbit_conn({
167
167
  ("GET", "/api/queues"): [
@@ -178,7 +178,7 @@ def test_backlog_rca_pulls_live_queues_and_classifies_no_consumers(monkeypatch):
178
178
 
179
179
  @pytest.mark.unit
180
180
  def test_churn_analysis_pulls_live_redis_snapshot_and_flags_reconnects(wire_redis):
181
- from mcp_server.tools import analysis as t
181
+ from queue_aiops.mcp_server.tools import analysis as t
182
182
 
183
183
  out = t.connection_churn_analysis()
184
184
  assert out["platform"] == "redis"
@@ -189,7 +189,7 @@ def test_churn_analysis_pulls_live_redis_snapshot_and_flags_reconnects(wire_redi
189
189
 
190
190
  @pytest.mark.unit
191
191
  def test_rca_tool_sanitises_broker_failure_into_error_envelope(monkeypatch):
192
- from mcp_server.tools import analysis as t
192
+ from queue_aiops.mcp_server.tools import analysis as t
193
193
 
194
194
  def boom(_target=None):
195
195
  raise ValueError("redis down: 10.0.0.1:6379 unreachable")
@@ -202,8 +202,8 @@ def test_rca_tool_sanitises_broker_failure_into_error_envelope(monkeypatch):
202
202
 
203
203
  @pytest.mark.unit
204
204
  def test_read_and_rca_tools_are_low_risk():
205
- from mcp_server.tools import analysis as a
206
- from mcp_server.tools import redis_reads as r
205
+ from queue_aiops.mcp_server.tools import analysis as a
206
+ from queue_aiops.mcp_server.tools import redis_reads as r
207
207
 
208
208
  for fn in (a.redis_memory_pressure_rca, a.redis_latency_rca,
209
209
  a.rabbitmq_queue_backlog_rca, a.connection_churn_analysis,
@@ -159,7 +159,7 @@ def test_queue_list_carries_the_standard_envelope():
159
159
 
160
160
  @pytest.mark.unit
161
161
  def test_undo_list_envelope_measures_truncation(monkeypatch):
162
- from mcp_server.tools import undo as undo_tools
162
+ from queue_aiops.mcp_server.tools import undo as undo_tools
163
163
 
164
164
  rows = [
165
165
  {
@@ -0,0 +1,62 @@
1
+ """The wheel ships exactly one top-level package, and nothing imports a shared one.
2
+
3
+ Every AIops tool used to install a top-level ``mcp_server`` package beside its
4
+ own. Two tools in one environment then merged into a single ``mcp_server``:
5
+ one tool's ``-mcp`` entry point started the other tool's server, and its CLI
6
+ ``undo`` wrote audit rows into the other tool's home. The server now lives at
7
+ ``<pkg>.mcp_server``. This file is identical across the AIops line.
8
+ """
9
+
10
+ import ast
11
+ import tomllib
12
+ from pathlib import Path
13
+
14
+ ROOT = Path(__file__).resolve().parents[1]
15
+ PYPROJECT = tomllib.loads((ROOT / "pyproject.toml").read_text("utf-8"))
16
+ WHEEL_PACKAGES = PYPROJECT["tool"]["hatch"]["build"]["targets"]["wheel"]["packages"]
17
+ PKG = WHEEL_PACKAGES[0]
18
+
19
+
20
+ def test_wheel_ships_only_the_tool_package():
21
+ assert WHEEL_PACKAGES == [PKG]
22
+ assert PKG != "mcp_server"
23
+
24
+
25
+ def test_no_top_level_mcp_server_directory():
26
+ assert not (ROOT / "mcp_server").exists()
27
+ assert (ROOT / PKG / "mcp_server" / "server.py").is_file()
28
+
29
+
30
+ def test_mcp_entry_point_starts_this_tools_server():
31
+ entries = [v for k, v in PYPROJECT["project"]["scripts"].items() if k.endswith("-mcp")]
32
+ assert entries == [f"{PKG}.mcp_server.server:main"]
33
+
34
+
35
+ def _imported_modules(path: Path) -> list[str]:
36
+ names: list[str] = []
37
+ for node in ast.walk(ast.parse(path.read_text("utf-8"))):
38
+ if isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
39
+ names.append(node.module)
40
+ elif isinstance(node, ast.Import):
41
+ names.extend(alias.name for alias in node.names)
42
+ return names
43
+
44
+
45
+ def test_nothing_imports_a_top_level_mcp_server():
46
+ files = [p for d in (ROOT / PKG, ROOT / "tests") for p in d.rglob("*.py")]
47
+ assert files, "no source files found"
48
+ offenders = sorted(
49
+ f"{p.relative_to(ROOT)}: {name}"
50
+ for p in files
51
+ if "__pycache__" not in p.parts
52
+ for name in _imported_modules(p)
53
+ if name == "mcp_server" or name.startswith("mcp_server.")
54
+ )
55
+ assert not offenders, offenders
56
+
57
+
58
+ def test_server_module_resolves_inside_the_package():
59
+ import importlib
60
+
61
+ server = importlib.import_module(f"{PKG}.mcp_server.server")
62
+ assert Path(server.__file__).resolve().is_relative_to((ROOT / PKG).resolve())
@@ -115,7 +115,7 @@ def test_self_lockout_is_a_valueerror():
115
115
 
116
116
  @pytest.mark.unit
117
117
  def test_dry_run_of_a_denylisted_parameter_is_refused(monkeypatch):
118
- from mcp_server.tools import writes as t
118
+ from queue_aiops.mcp_server.tools import writes as t
119
119
 
120
120
  conn = redis_conn(config={"requirepass": "current-password"})
121
121
  monkeypatch.setattr(t, "_get_connection", lambda target=None: conn)
@@ -130,7 +130,7 @@ def test_dry_run_of_a_denylisted_parameter_is_refused(monkeypatch):
130
130
  @pytest.mark.unit
131
131
  def test_dry_run_of_an_ordinary_parameter_still_previews(monkeypatch):
132
132
  """The dry-run guard must be exact, not a blanket refusal of every preview."""
133
- from mcp_server.tools import writes as t
133
+ from queue_aiops.mcp_server.tools import writes as t
134
134
 
135
135
  conn = redis_conn(config={"maxmemory-policy": "noeviction"})
136
136
  monkeypatch.setattr(t, "_get_connection", lambda target=None: conn)
@@ -159,8 +159,8 @@ def _cli_dry_run(monkeypatch, tmp_path, argv, conn):
159
159
  import queue_aiops.governance.audit as audit_mod
160
160
  import queue_aiops.governance.policy as policy_mod
161
161
  import queue_aiops.governance.undo as undo_mod
162
- from mcp_server.tools import writes as gov
163
162
  from queue_aiops.cli import app
163
+ from queue_aiops.mcp_server.tools import writes as gov
164
164
 
165
165
  monkeypatch.setenv("QUEUE_AIOPS_HOME", str(tmp_path))
166
166
  audit_mod.reset_engine()
@@ -210,7 +210,7 @@ def test_the_refusal_reaches_the_agent_intact_through_the_mcp_layer(monkeypatch)
210
210
  than replaced — but it is truncated. The route back sits at the END of the
211
211
  message, so an over-long refusal loses exactly the part the caller acts on.
212
212
  """
213
- from mcp_server.tools import writes as t
213
+ from queue_aiops.mcp_server.tools import writes as t
214
214
 
215
215
  conn = redis_conn(config={"requirepass": "current-password"})
216
216
  monkeypatch.setattr(t, "_get_connection", lambda target=None: conn)
@@ -12,7 +12,7 @@ import importlib
12
12
  import pytest
13
13
  from typer.testing import CliRunner
14
14
 
15
- # Kept in sync with mcp_server/server.py (the full registered tool surface).
15
+ # Kept in sync with queue_aiops/mcp_server/server.py (the full registered tool surface).
16
16
  EXPECTED_TOOLS = {
17
17
  # overview
18
18
  "queue_overview",
@@ -45,10 +45,10 @@ def test_all_modules_import():
45
45
  "queue_aiops.cli.redis_cmds", "queue_aiops.cli.rabbit_cmds",
46
46
  "queue_aiops.cli.analyze", "queue_aiops.cli.overview",
47
47
  "queue_aiops.cli.doctor",
48
- "mcp_server.server", "mcp_server._shared",
49
- "mcp_server.tools.redis_reads", "mcp_server.tools.rabbit_reads",
50
- "mcp_server.tools.analysis", "mcp_server.tools.writes",
51
- "mcp_server.tools.overview",
48
+ "queue_aiops.mcp_server.server", "queue_aiops.mcp_server._shared",
49
+ "queue_aiops.mcp_server.tools.redis_reads", "queue_aiops.mcp_server.tools.rabbit_reads",
50
+ "queue_aiops.mcp_server.tools.analysis", "queue_aiops.mcp_server.tools.writes",
51
+ "queue_aiops.mcp_server.tools.overview",
52
52
  ):
53
53
  importlib.import_module(name)
54
54
 
@@ -102,7 +102,7 @@ def test_cli_leaf_help_triggers_lazy_imports():
102
102
 
103
103
  @pytest.mark.unit
104
104
  def test_mcp_list_tools_exposes_expected_tools():
105
- from mcp_server.server import mcp
105
+ from queue_aiops.mcp_server.server import mcp
106
106
 
107
107
  tools = asyncio.run(mcp.list_tools())
108
108
  names = {t.name for t in tools}
@@ -111,7 +111,7 @@ def test_mcp_list_tools_exposes_expected_tools():
111
111
 
112
112
  @pytest.mark.unit
113
113
  def test_every_mcp_tool_is_governed_by_harness():
114
- from mcp_server import _shared
114
+ from queue_aiops.mcp_server import _shared
115
115
 
116
116
  tool_objs = _shared.mcp._tool_manager._tools
117
117
  assert EXPECTED_TOOLS <= set(tool_objs), "tool registry incomplete"
@@ -123,7 +123,7 @@ def test_every_mcp_tool_is_governed_by_harness():
123
123
 
124
124
  @pytest.mark.unit
125
125
  def test_tool_count_is_expected():
126
- from mcp_server import _shared
126
+ from queue_aiops.mcp_server import _shared
127
127
 
128
128
  assert len(_shared.mcp._tool_manager._tools) == 28
129
129
 
@@ -166,7 +166,7 @@ def test_risk_level_agrees_with_read_write_docstring_tag():
166
166
  16 such mislabels line-wide once, so it is kept even though read-only mode
167
167
  (its original motivation) is gone.
168
168
  """
169
- from mcp_server import server
169
+ from queue_aiops.mcp_server import server
170
170
 
171
171
  untagged, mismatched = [], []
172
172
  for name, tool in server.mcp._tool_manager._tools.items():
@@ -6,18 +6,29 @@ here too. This file is identical across the AIops line.
6
6
  """
7
7
 
8
8
  import asyncio
9
+ import importlib
9
10
  import re
11
+ from pathlib import Path
10
12
  from types import SimpleNamespace
11
13
 
12
14
  import pytest
13
15
  from mcp.server import MCPServer
14
16
 
15
- import mcp_server.server as server
16
- from mcp_server._annotations import (
17
- RISK_META_KEY,
18
- annotations_for,
19
- apply_risk_annotations,
20
- )
17
+
18
+ def _server_package() -> str:
19
+ """``<pkg>.mcp_server``, found on disk so this file needs no per-tool name."""
20
+ root = Path(__file__).resolve().parents[1]
21
+ found = sorted(p.parent.parent.name for p in root.glob("*/mcp_server/server.py"))
22
+ assert len(found) == 1, f"expected exactly one <pkg>/mcp_server/server.py, found {found}"
23
+ return f"{found[0]}.mcp_server"
24
+
25
+
26
+ SERVER_PACKAGE = _server_package()
27
+ server = importlib.import_module(f"{SERVER_PACKAGE}.server")
28
+ _annotations = importlib.import_module(f"{SERVER_PACKAGE}._annotations")
29
+ RISK_META_KEY = _annotations.RISK_META_KEY
30
+ annotations_for = _annotations.annotations_for
31
+ apply_risk_annotations = _annotations.apply_risk_annotations
21
32
 
22
33
 
23
34
  @pytest.fixture(scope="module")
@@ -16,9 +16,9 @@ import pytest
16
16
  import queue_aiops.governance.audit as audit_mod
17
17
  import queue_aiops.governance.policy as policy_mod
18
18
  import queue_aiops.governance.undo as undo_mod
19
- from mcp_server._shared import mcp
20
- from mcp_server.tools import undo as gov
21
19
  from queue_aiops.governance import governed_tool
20
+ from queue_aiops.mcp_server._shared import mcp
21
+ from queue_aiops.mcp_server.tools import undo as gov
22
22
 
23
23
  _CALLS: list[dict] = []
24
24
  _TARGETS: list = []
@@ -211,7 +211,7 @@ def test_undo_apply_audits_both_wrapper_and_inverse(gov_home):
211
211
  @pytest.mark.unit
212
212
  def test_resolve_tool_loads_full_registry_under_cli_only_import():
213
213
  """Regression (live-found 2026-07-31): CLI ``undo apply`` runs in a process
214
- that imports only ``mcp_server.tools.undo`` — every write tool is imported
214
+ that imports only ``queue_aiops.mcp_server.tools.undo`` — every write tool is imported
215
215
  lazily inside its own CLI command, so the inverse was "not registered" and
216
216
  CLI-initiated undo failed for every write tool. ``_resolve_tool`` must force
217
217
  a full server load on a miss.
@@ -225,8 +225,8 @@ def test_resolve_tool_loads_full_registry_under_cli_only_import():
225
225
 
226
226
  script = textwrap.dedent(
227
227
  """
228
- from mcp_server._shared import mcp
229
- import mcp_server.tools.undo as u
228
+ from queue_aiops.mcp_server._shared import mcp
229
+ import queue_aiops.mcp_server.tools.undo as u
230
230
  before = set(mcp._tool_manager._tools)
231
231
  # A miss must trigger the full-registry load, not just return None.
232
232
  u._resolve_tool("__definitely_not_a_tool__")
@@ -215,8 +215,8 @@ def test_delete_policy_captures_prior_and_teaches_when_missing():
215
215
 
216
216
  @pytest.mark.unit
217
217
  def test_governed_config_set_records_undo_token(monkeypatch):
218
- from mcp_server.tools import writes as t
219
218
  from queue_aiops.governance.undo import get_undo_store
219
+ from queue_aiops.mcp_server.tools import writes as t
220
220
 
221
221
  conn = redis_conn(config={"maxmemory-policy": "noeviction"})
222
222
  monkeypatch.setattr(t, "_get_connection", lambda target=None: conn)
@@ -238,8 +238,8 @@ def test_governed_delete_queue_undo_replays_through_declare_queue(monkeypatch):
238
238
  """THE replay test: delete_queue's recorded undo, fed back into the
239
239
  declare_queue tool with exactly its recorded params, must re-issue the PUT
240
240
  with the captured definition."""
241
- from mcp_server.tools import writes as t
242
241
  from queue_aiops.governance.undo import get_undo_store
242
+ from queue_aiops.mcp_server.tools import writes as t
243
243
 
244
244
  conn = rabbit_conn({
245
245
  ("GET", "/api/queues/%2F/orders"): _Q,
@@ -267,8 +267,8 @@ def test_governed_delete_queue_undo_replays_through_declare_queue(monkeypatch):
267
267
 
268
268
  @pytest.mark.unit
269
269
  def test_governed_set_policy_undo_replays_prior_policy(monkeypatch):
270
- from mcp_server.tools import writes as t
271
270
  from queue_aiops.governance.undo import get_undo_store
271
+ from queue_aiops.mcp_server.tools import writes as t
272
272
 
273
273
  conn = rabbit_conn({
274
274
  ("GET", "/api/policies/%2F/lim"): _POLICY,
@@ -287,7 +287,7 @@ def test_governed_set_policy_undo_replays_prior_policy(monkeypatch):
287
287
 
288
288
  @pytest.mark.unit
289
289
  def test_undo_descriptor_shapes_invert_correctly():
290
- from mcp_server.tools import writes as t
290
+ from queue_aiops.mcp_server.tools import writes as t
291
291
 
292
292
  cfg = t._config_set_undo({"parameter": "maxmemory"}, {"priorState": {"value": "100mb"}})
293
293
  assert cfg["tool"] == "redis_config_set"
@@ -332,7 +332,7 @@ def test_undo_descriptor_shapes_invert_correctly():
332
332
 
333
333
  @pytest.mark.unit
334
334
  def test_write_risk_tiers():
335
- from mcp_server.tools import writes as t
335
+ from queue_aiops.mcp_server.tools import writes as t
336
336
 
337
337
  assert t.purge_queue._risk_level == "high"
338
338
  assert t.delete_queue._risk_level == "high"
@@ -350,7 +350,7 @@ _REDIS_MUTATORS = {"config_set", "client_kill_filter"}
350
350
  @pytest.mark.unit
351
351
  def test_dry_run_previews_do_not_mutate(monkeypatch):
352
352
  """The invariant is: a dry_run MAY read, it must never WRITE."""
353
- from mcp_server.tools import writes as t
353
+ from queue_aiops.mcp_server.tools import writes as t
354
354
 
355
355
  rconn = redis_conn(config={"maxmemory": "0"}, client_id=11)
356
356
  qconn = rabbit_conn({("GET", "/api/queues/%2F/orders"): _Q})
@@ -372,8 +372,8 @@ def test_dry_run_previews_do_not_mutate(monkeypatch):
372
372
 
373
373
  @pytest.mark.unit
374
374
  def test_irreversible_writes_record_no_undo(monkeypatch):
375
- from mcp_server.tools import writes as t
376
375
  from queue_aiops.governance.undo import get_undo_store
376
+ from queue_aiops.mcp_server.tools import writes as t
377
377
 
378
378
  conn = rabbit_conn({
379
379
  ("GET", "/api/queues/%2F/orders"): _Q,
@@ -812,7 +812,7 @@ wheels = [
812
812
 
813
813
  [[package]]
814
814
  name = "queue-aiops"
815
- version = "0.10.0"
815
+ version = "0.11.0"
816
816
  source = { editable = "." }
817
817
  dependencies = [
818
818
  { name = "cryptography" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes