container-host-aiops 0.1.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.
- container_host_aiops-0.1.0/.github/workflows/publish.yml +26 -0
- container_host_aiops-0.1.0/.gitignore +7 -0
- container_host_aiops-0.1.0/CHANGELOG.md +60 -0
- container_host_aiops-0.1.0/LICENSE +21 -0
- container_host_aiops-0.1.0/PKG-INFO +96 -0
- container_host_aiops-0.1.0/README.md +80 -0
- container_host_aiops-0.1.0/RELEASE_NOTES.md +59 -0
- container_host_aiops-0.1.0/SECURITY.md +83 -0
- container_host_aiops-0.1.0/container_host_aiops/__init__.py +9 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/__init__.py +9 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/_common.py +78 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/_root.py +70 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/analyze.py +55 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/container.py +85 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/doctor.py +21 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/image.py +61 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/init.py +126 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/manage.py +192 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/network.py +38 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/overview.py +16 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/secret.py +108 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/stack.py +48 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/system.py +60 -0
- container_host_aiops-0.1.0/container_host_aiops/cli/volume.py +48 -0
- container_host_aiops-0.1.0/container_host_aiops/config.py +198 -0
- container_host_aiops-0.1.0/container_host_aiops/connection.py +227 -0
- container_host_aiops-0.1.0/container_host_aiops/doctor.py +98 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/__init__.py +40 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/audit.py +377 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/budget.py +225 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/decorators.py +474 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/paths.py +23 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/patterns.py +378 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/policy.py +411 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/sanitize.py +39 -0
- container_host_aiops-0.1.0/container_host_aiops/governance/undo.py +218 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/__init__.py +1 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/_metrics.py +83 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/_util.py +90 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/analyses.py +311 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/containers.py +173 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/images.py +124 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/networks.py +53 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/overview.py +45 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/stacks.py +69 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/system.py +163 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/volumes.py +72 -0
- container_host_aiops-0.1.0/container_host_aiops/ops/writes.py +209 -0
- container_host_aiops-0.1.0/container_host_aiops/platform.py +146 -0
- container_host_aiops-0.1.0/container_host_aiops/secretstore.py +302 -0
- container_host_aiops-0.1.0/mcp_server/__init__.py +1 -0
- container_host_aiops-0.1.0/mcp_server/_shared.py +104 -0
- container_host_aiops-0.1.0/mcp_server/server.py +38 -0
- container_host_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- container_host_aiops-0.1.0/mcp_server/tools/analyses.py +93 -0
- container_host_aiops-0.1.0/mcp_server/tools/containers.py +88 -0
- container_host_aiops-0.1.0/mcp_server/tools/images.py +57 -0
- container_host_aiops-0.1.0/mcp_server/tools/networks.py +32 -0
- container_host_aiops-0.1.0/mcp_server/tools/stacks.py +50 -0
- container_host_aiops-0.1.0/mcp_server/tools/system.py +75 -0
- container_host_aiops-0.1.0/mcp_server/tools/volumes.py +44 -0
- container_host_aiops-0.1.0/mcp_server/tools/writes.py +260 -0
- container_host_aiops-0.1.0/pyproject.toml +59 -0
- container_host_aiops-0.1.0/server.json +21 -0
- container_host_aiops-0.1.0/skills/container-host-aiops/SKILL.md +109 -0
- container_host_aiops-0.1.0/skills/container-host-aiops/references/capabilities.md +93 -0
- container_host_aiops-0.1.0/skills/container-host-aiops/references/cli-reference.md +106 -0
- container_host_aiops-0.1.0/skills/container-host-aiops/references/setup-guide.md +108 -0
- container_host_aiops-0.1.0/smithery.yaml +9 -0
- container_host_aiops-0.1.0/tests/test_platform.py +144 -0
- container_host_aiops-0.1.0/tests/test_reads.py +305 -0
- container_host_aiops-0.1.0/tests/test_secretstore.py +99 -0
- container_host_aiops-0.1.0/tests/test_smoke.py +273 -0
- container_host_aiops-0.1.0/uv.lock +962 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Trusted Publishing (OIDC) — publishes from GitHub's runners with no API token,
|
|
4
|
+
# sidestepping the local-IP / account new-project rate limit. Configure a matching
|
|
5
|
+
# "trusted publisher" for this package on PyPI (see the repo release notes).
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC)
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Set up uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
- name: Build sdist + wheel
|
|
24
|
+
run: uv build
|
|
25
|
+
- name: Publish to PyPI (Trusted Publishing)
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to container-host-aiops are documented here. This project adheres
|
|
4
|
+
to [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — preview
|
|
7
|
+
|
|
8
|
+
Initial preview release: governed AI-ops for **non-orchestrator container hosts**
|
|
9
|
+
across the **Docker Engine API** (unix socket or TCP) and **Portainer** (its
|
|
10
|
+
management API, which also proxies Docker), with a bundled governance harness. One
|
|
11
|
+
config can span many hosts; a per-target `platform` field selects the API shape.
|
|
12
|
+
**Mock-validated only — not yet verified against a live Docker daemon or Portainer
|
|
13
|
+
server.**
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **34 MCP tools** (26 read, 8 write), every one wrapped with the bundled
|
|
18
|
+
`@governed_tool` harness (audit, policy, token/runaway budget, undo,
|
|
19
|
+
risk-tiers):
|
|
20
|
+
- **Overview** — `overview` (one-shot host health: version + container state
|
|
21
|
+
rollup + disk headline).
|
|
22
|
+
- **Containers (read)** — `list_containers`, `inspect_container`,
|
|
23
|
+
`container_logs` (tail N), `container_stats` (CPU%/mem%), `container_top`,
|
|
24
|
+
`container_restart_summary`.
|
|
25
|
+
- **Images (read)** — `list_images`, `inspect_image` (with history/layers),
|
|
26
|
+
`dangling_images`, `image_disk_usage`.
|
|
27
|
+
- **Volumes (read)** — `list_volumes`, `inspect_volume`, `dangling_volumes`.
|
|
28
|
+
- **Networks (read)** — `list_networks`, `inspect_network`.
|
|
29
|
+
- **System (read)** — `system_info`, `system_version`, `system_df`,
|
|
30
|
+
`system_events`.
|
|
31
|
+
- **Stacks (Portainer, read)** — `list_endpoints`, `list_stacks`,
|
|
32
|
+
`stack_detail`.
|
|
33
|
+
- **Flagship analyses (read)** — `restart_loop_rca` (crash-looping containers +
|
|
34
|
+
cause/action from exit code + log tail), `resource_pressure_analysis`
|
|
35
|
+
(CPU/memory vs limits + recommendation), `image_and_volume_bloat` (dangling
|
|
36
|
+
images + volumes + build cache → prune candidates with reclaimable bytes).
|
|
37
|
+
- **Writes** — `restart_container` (med), `stop_container` (med, undo→start),
|
|
38
|
+
`start_container` (med, undo→stop), `update_container` (med, undo restores
|
|
39
|
+
prior CPU/memory limits), `remove_container` (**high**, captures full inspect
|
|
40
|
+
first), `prune_images` (**high**, dry-run lists candidates), `prune_volumes`
|
|
41
|
+
(**high**, dry-run lists candidates), `recreate_stack` (**high**, Portainer).
|
|
42
|
+
- **Connection layer** — Docker over a unix socket (`httpx.HTTPTransport(uds=...)`)
|
|
43
|
+
or a TCP host; Portainer over HTTPS with an `X-API-Key` token that also proxies
|
|
44
|
+
the Docker API of a managed endpoint (`/api/endpoints/{id}/docker/...`).
|
|
45
|
+
- **Encrypted secret store** — the Portainer API token is stored encrypted in
|
|
46
|
+
`~/.container-host-aiops/secrets.enc` (Fernet + scrypt); never plaintext on disk.
|
|
47
|
+
A direct Docker socket needs no secret. Legacy `CONTAINER_HOST_<TARGET>_TOKEN`
|
|
48
|
+
env var honoured as a fallback.
|
|
49
|
+
- **CLI** (`container-host-aiops`) — `init` platform-picking wizard, `overview`,
|
|
50
|
+
`container`, `image`, `volume`, `network`, `system`, `stack`, `analyze`,
|
|
51
|
+
`manage` (guarded writes with `--dry-run` + double-confirm), `secret`
|
|
52
|
+
management, and a platform-aware `doctor`.
|
|
53
|
+
|
|
54
|
+
### Known limitations
|
|
55
|
+
|
|
56
|
+
- Preview / mock-only: the Docker Engine + Portainer API responses are mocked and
|
|
57
|
+
need live verification against a real daemon / server. `container-host-aiops
|
|
58
|
+
doctor` is the fastest live check.
|
|
59
|
+
- Single-host focus by design: cluster orchestrators, hypervisors, storage
|
|
60
|
+
appliances, and backup products are out of scope (separate AIops-tools).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei <zhouwei008@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: container-host-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Governed AI-ops for Docker + Portainer container hosts: container/image/volume/network/system reads, restart-loop and resource-pressure and bloat analyses, and guarded lifecycle + prune writes with a built-in governance harness (audit, budget, undo, risk tiers)
|
|
5
|
+
Author-email: wei <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: cryptography>=42.0
|
|
10
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
+
Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
12
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
13
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
14
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<!-- mcp-name: io.github.AIops-tools/container-host-aiops -->
|
|
18
|
+
|
|
19
|
+
# Container Host AIops (preview)
|
|
20
|
+
|
|
21
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Docker, Inc., Portainer.io, or any container-platform vendor.** "Docker", "Portainer" and all product/trademark names belong to their respective owners. MIT licensed.
|
|
22
|
+
|
|
23
|
+
Governed AI-ops for **non-orchestrator container hosts** — the **Docker Engine API** (over a local unix socket or a TCP host) and **Portainer** (its management API, which also proxies Docker) — with a **built-in governance harness**: unified audit log, policy engine, token/runaway budget guard, undo-token recording, and graduated-autonomy risk tiers. **Multi-platform by construction**: a registry keyed by `platform` means a per-target `platform` field (`docker` / `portainer`) selects the API shape, and another host family could be added later without touching the ops/CLI/MCP layers. **Preview — mock-validated only, not verified against a live Docker daemon or Portainer server.**
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
Three flagship signature analyses, plus the guarded reads and writes around them:
|
|
28
|
+
|
|
29
|
+
- **Restart-loop RCA** — inspect containers for restart count + exit code, flag the crash-looping ones (restartCount over threshold, or restarting/dead, or a non-zero exit), and map each to a likely cause + action from the exit code (137 OOM/SIGKILL, 143 SIGTERM, 139 segfault, 127 bad entrypoint, …), with a tail of logs. Every ranking carries its numbers, not a black-box verdict.
|
|
30
|
+
- **Resource-pressure analysis** — a one-shot CPU%/memory% sample per running container vs its configured limits, flagging each "near" (≥ 80% of a threshold) or "over", with a recommendation (raise a limit, set a missing memory limit, scale out).
|
|
31
|
+
- **Image & volume bloat** — dangling images + dangling volumes + build cache from `system/df`, totalled into prune candidates with reclaimable bytes.
|
|
32
|
+
|
|
33
|
+
## What works
|
|
34
|
+
|
|
35
|
+
- **CLI** (`container-host-aiops ...`): `init`, `overview`, `container`, `image`, `volume`, `network`, `system`, `stack`, `analyze`, `manage`, `secret`, `doctor`, `mcp`.
|
|
36
|
+
- **MCP server** (`container-host-aiops mcp` or `container-host-aiops-mcp`): **34 tools** (26 read, 8 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
37
|
+
- **Connection layer**: Docker over a **unix socket** (`httpx.HTTPTransport(uds=...)`) or a TCP host; Portainer over HTTPS with an `X-API-Key` token that also proxies the Docker API of a managed endpoint. A local Docker socket needs **no secret** — the socket's file permissions are the boundary.
|
|
38
|
+
- **Encrypted credentials**: the Portainer API token lives in an encrypted store `~/.container-host-aiops/secrets.enc` (Fernet + scrypt) — **never plaintext on disk**. Unlock with a master password from `CONTAINER_HOST_AIOPS_MASTER_PASSWORD` (MCP/CI) or an interactive prompt (CLI).
|
|
39
|
+
- **Reversibility**: mutating writes fetch the **real before-state first** and record a faithful inverse (`stop`↔`start`; `update_container` restores prior CPU/memory limits). Irreversible ops (`remove_container`, `prune_images`, `prune_volumes`, `recreate_stack`) capture the before-state for audit but declare no undo.
|
|
40
|
+
- **Safety**: every state-changing CLI op supports `--dry-run` and requires double confirmation; every write MCP tool takes a `dry_run` preview — and prune previews **list what would be removed + reclaimable bytes** before doing it.
|
|
41
|
+
|
|
42
|
+
## Capability matrix (34 MCP tools)
|
|
43
|
+
|
|
44
|
+
| Domain | Tools | Count | R/W |
|
|
45
|
+
|--------|-------|:-----:|:---:|
|
|
46
|
+
| **Overview** | `overview` | 1 | read |
|
|
47
|
+
| **Containers** | `list_containers`, `inspect_container`, `container_logs`, `container_stats`, `container_top`, `container_restart_summary` | 6 | read |
|
|
48
|
+
| **Images** | `list_images`, `inspect_image`, `dangling_images`, `image_disk_usage` | 4 | read |
|
|
49
|
+
| **Volumes** | `list_volumes`, `inspect_volume`, `dangling_volumes` | 3 | read |
|
|
50
|
+
| **Networks** | `list_networks`, `inspect_network` | 2 | read |
|
|
51
|
+
| **System** | `system_info`, `system_version`, `system_df`, `system_events` | 4 | read |
|
|
52
|
+
| **Stacks (Portainer)** | `list_endpoints`, `list_stacks`, `stack_detail` | 3 | read |
|
|
53
|
+
| **Analyses (flagship)** | `restart_loop_rca`, `resource_pressure_analysis`, `image_and_volume_bloat` | 3 | read |
|
|
54
|
+
| **Writes** | `remove_container`, `prune_images`, `prune_volumes`, `recreate_stack` | 4 | write (high) |
|
|
55
|
+
| | `restart_container`, `stop_container`, `start_container`, `update_container` | 4 | write (medium) |
|
|
56
|
+
|
|
57
|
+
The three analyses accept injected data for offline analysis, or pull live from a configured target. Stacks/endpoints require a `portainer` target.
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install container-host-aiops # or: pipx install container-host-aiops
|
|
63
|
+
container-host-aiops init # wizard: add a Docker socket or Portainer target
|
|
64
|
+
container-host-aiops doctor # verify config, secrets, connectivity
|
|
65
|
+
container-host-aiops overview # one-shot host health
|
|
66
|
+
container-host-aiops analyze restart-loop # crash-looping containers + cause/action
|
|
67
|
+
container-host-aiops container list --running # running containers
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run as an MCP server (stdio):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export CONTAINER_HOST_AIOPS_MASTER_PASSWORD=... # only needed for Portainer targets
|
|
74
|
+
container-host-aiops-mcp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Governance
|
|
78
|
+
|
|
79
|
+
Every MCP tool passes through the bundled `@governed_tool` harness:
|
|
80
|
+
|
|
81
|
+
- **Audit** — every call (params, result, status, duration, risk tier, approver, rationale) is logged to `~/.container-host-aiops/audit.db` (relocatable via `CONTAINER_HOST_AIOPS_HOME`).
|
|
82
|
+
- **Budget / runaway guard** — token and call budgets trip a circuit breaker.
|
|
83
|
+
- **Risk tiers** — graduated autonomy; high-risk ops (remove / prune / recreate) can require a named approver.
|
|
84
|
+
- **Undo recording** — reversible writes record an inverse descriptor built from the fetched before-state.
|
|
85
|
+
|
|
86
|
+
## Scope
|
|
87
|
+
|
|
88
|
+
This is the **container-host** member of the AIops-tools family (governed AI-ops with audit + budget + undo + risk tiers), for **single-host Docker / Portainer**. It is deliberately **NOT** for a cluster orchestrator, a hypervisor, a storage appliance, a backup product, or OT / industrial edge — those are separate tools/lines.
|
|
89
|
+
|
|
90
|
+
## Missing a capability?
|
|
91
|
+
|
|
92
|
+
Coverage is intentionally a curated subset of the Docker Engine + Portainer APIs. Missing a call, or want another container host family? **Open an issue or PR** — contributions welcome.
|
|
93
|
+
|
|
94
|
+
## Status
|
|
95
|
+
|
|
96
|
+
**Preview — mock-validated only, not verified against a live Docker daemon or Portainer server.** The API paths are modelled from the public API shape and need live verification. `container-host-aiops doctor` is the fastest live check.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/container-host-aiops -->
|
|
2
|
+
|
|
3
|
+
# Container Host AIops (preview)
|
|
4
|
+
|
|
5
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Docker, Inc., Portainer.io, or any container-platform vendor.** "Docker", "Portainer" and all product/trademark names belong to their respective owners. MIT licensed.
|
|
6
|
+
|
|
7
|
+
Governed AI-ops for **non-orchestrator container hosts** — the **Docker Engine API** (over a local unix socket or a TCP host) and **Portainer** (its management API, which also proxies Docker) — with a **built-in governance harness**: unified audit log, policy engine, token/runaway budget guard, undo-token recording, and graduated-autonomy risk tiers. **Multi-platform by construction**: a registry keyed by `platform` means a per-target `platform` field (`docker` / `portainer`) selects the API shape, and another host family could be added later without touching the ops/CLI/MCP layers. **Preview — mock-validated only, not verified against a live Docker daemon or Portainer server.**
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
Three flagship signature analyses, plus the guarded reads and writes around them:
|
|
12
|
+
|
|
13
|
+
- **Restart-loop RCA** — inspect containers for restart count + exit code, flag the crash-looping ones (restartCount over threshold, or restarting/dead, or a non-zero exit), and map each to a likely cause + action from the exit code (137 OOM/SIGKILL, 143 SIGTERM, 139 segfault, 127 bad entrypoint, …), with a tail of logs. Every ranking carries its numbers, not a black-box verdict.
|
|
14
|
+
- **Resource-pressure analysis** — a one-shot CPU%/memory% sample per running container vs its configured limits, flagging each "near" (≥ 80% of a threshold) or "over", with a recommendation (raise a limit, set a missing memory limit, scale out).
|
|
15
|
+
- **Image & volume bloat** — dangling images + dangling volumes + build cache from `system/df`, totalled into prune candidates with reclaimable bytes.
|
|
16
|
+
|
|
17
|
+
## What works
|
|
18
|
+
|
|
19
|
+
- **CLI** (`container-host-aiops ...`): `init`, `overview`, `container`, `image`, `volume`, `network`, `system`, `stack`, `analyze`, `manage`, `secret`, `doctor`, `mcp`.
|
|
20
|
+
- **MCP server** (`container-host-aiops mcp` or `container-host-aiops-mcp`): **34 tools** (26 read, 8 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
21
|
+
- **Connection layer**: Docker over a **unix socket** (`httpx.HTTPTransport(uds=...)`) or a TCP host; Portainer over HTTPS with an `X-API-Key` token that also proxies the Docker API of a managed endpoint. A local Docker socket needs **no secret** — the socket's file permissions are the boundary.
|
|
22
|
+
- **Encrypted credentials**: the Portainer API token lives in an encrypted store `~/.container-host-aiops/secrets.enc` (Fernet + scrypt) — **never plaintext on disk**. Unlock with a master password from `CONTAINER_HOST_AIOPS_MASTER_PASSWORD` (MCP/CI) or an interactive prompt (CLI).
|
|
23
|
+
- **Reversibility**: mutating writes fetch the **real before-state first** and record a faithful inverse (`stop`↔`start`; `update_container` restores prior CPU/memory limits). Irreversible ops (`remove_container`, `prune_images`, `prune_volumes`, `recreate_stack`) capture the before-state for audit but declare no undo.
|
|
24
|
+
- **Safety**: every state-changing CLI op supports `--dry-run` and requires double confirmation; every write MCP tool takes a `dry_run` preview — and prune previews **list what would be removed + reclaimable bytes** before doing it.
|
|
25
|
+
|
|
26
|
+
## Capability matrix (34 MCP tools)
|
|
27
|
+
|
|
28
|
+
| Domain | Tools | Count | R/W |
|
|
29
|
+
|--------|-------|:-----:|:---:|
|
|
30
|
+
| **Overview** | `overview` | 1 | read |
|
|
31
|
+
| **Containers** | `list_containers`, `inspect_container`, `container_logs`, `container_stats`, `container_top`, `container_restart_summary` | 6 | read |
|
|
32
|
+
| **Images** | `list_images`, `inspect_image`, `dangling_images`, `image_disk_usage` | 4 | read |
|
|
33
|
+
| **Volumes** | `list_volumes`, `inspect_volume`, `dangling_volumes` | 3 | read |
|
|
34
|
+
| **Networks** | `list_networks`, `inspect_network` | 2 | read |
|
|
35
|
+
| **System** | `system_info`, `system_version`, `system_df`, `system_events` | 4 | read |
|
|
36
|
+
| **Stacks (Portainer)** | `list_endpoints`, `list_stacks`, `stack_detail` | 3 | read |
|
|
37
|
+
| **Analyses (flagship)** | `restart_loop_rca`, `resource_pressure_analysis`, `image_and_volume_bloat` | 3 | read |
|
|
38
|
+
| **Writes** | `remove_container`, `prune_images`, `prune_volumes`, `recreate_stack` | 4 | write (high) |
|
|
39
|
+
| | `restart_container`, `stop_container`, `start_container`, `update_container` | 4 | write (medium) |
|
|
40
|
+
|
|
41
|
+
The three analyses accept injected data for offline analysis, or pull live from a configured target. Stacks/endpoints require a `portainer` target.
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv tool install container-host-aiops # or: pipx install container-host-aiops
|
|
47
|
+
container-host-aiops init # wizard: add a Docker socket or Portainer target
|
|
48
|
+
container-host-aiops doctor # verify config, secrets, connectivity
|
|
49
|
+
container-host-aiops overview # one-shot host health
|
|
50
|
+
container-host-aiops analyze restart-loop # crash-looping containers + cause/action
|
|
51
|
+
container-host-aiops container list --running # running containers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run as an MCP server (stdio):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export CONTAINER_HOST_AIOPS_MASTER_PASSWORD=... # only needed for Portainer targets
|
|
58
|
+
container-host-aiops-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Governance
|
|
62
|
+
|
|
63
|
+
Every MCP tool passes through the bundled `@governed_tool` harness:
|
|
64
|
+
|
|
65
|
+
- **Audit** — every call (params, result, status, duration, risk tier, approver, rationale) is logged to `~/.container-host-aiops/audit.db` (relocatable via `CONTAINER_HOST_AIOPS_HOME`).
|
|
66
|
+
- **Budget / runaway guard** — token and call budgets trip a circuit breaker.
|
|
67
|
+
- **Risk tiers** — graduated autonomy; high-risk ops (remove / prune / recreate) can require a named approver.
|
|
68
|
+
- **Undo recording** — reversible writes record an inverse descriptor built from the fetched before-state.
|
|
69
|
+
|
|
70
|
+
## Scope
|
|
71
|
+
|
|
72
|
+
This is the **container-host** member of the AIops-tools family (governed AI-ops with audit + budget + undo + risk tiers), for **single-host Docker / Portainer**. It is deliberately **NOT** for a cluster orchestrator, a hypervisor, a storage appliance, a backup product, or OT / industrial edge — those are separate tools/lines.
|
|
73
|
+
|
|
74
|
+
## Missing a capability?
|
|
75
|
+
|
|
76
|
+
Coverage is intentionally a curated subset of the Docker Engine + Portainer APIs. Missing a call, or want another container host family? **Open an issue or PR** — contributions welcome.
|
|
77
|
+
|
|
78
|
+
## Status
|
|
79
|
+
|
|
80
|
+
**Preview — mock-validated only, not verified against a live Docker daemon or Portainer server.** The API paths are modelled from the public API shape and need live verification. `container-host-aiops doctor` is the fastest live check.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Container Host AIops v0.1.0 — preview
|
|
2
|
+
|
|
3
|
+
Governed AI-ops for **non-orchestrator container hosts** across the **Docker
|
|
4
|
+
Engine API** (unix socket or TCP) and **Portainer** (its management API, which
|
|
5
|
+
also proxies Docker) for AI agents, with a built-in governance harness (audit,
|
|
6
|
+
policy, token/runaway budget, undo-token recording, graduated risk tiers) and an
|
|
7
|
+
encrypted credential store. Standalone — no external skill-family dependency. One
|
|
8
|
+
config can span many hosts; a per-target `platform` field selects the API shape.
|
|
9
|
+
|
|
10
|
+
> **Preview / mock-only.** All behaviour is validated against mocked Docker /
|
|
11
|
+
> Portainer responses; it has not been run against a live daemon or server. The
|
|
12
|
+
> fastest live check is `container-host-aiops doctor`.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- **34 MCP tools** (26 read, 8 write), every one wrapped with `@governed_tool`.
|
|
17
|
+
- **Overview** — `overview` (host version + container state rollup + disk).
|
|
18
|
+
- **Containers** — `list_containers`, `inspect_container`, `container_logs`,
|
|
19
|
+
`container_stats`, `container_top`, `container_restart_summary`.
|
|
20
|
+
- **Images** — `list_images`, `inspect_image`, `dangling_images`,
|
|
21
|
+
`image_disk_usage`.
|
|
22
|
+
- **Volumes / Networks** — `list_volumes`, `inspect_volume`, `dangling_volumes`;
|
|
23
|
+
`list_networks`, `inspect_network`.
|
|
24
|
+
- **System** — `system_info`, `system_version`, `system_df`, `system_events`.
|
|
25
|
+
- **Stacks (Portainer)** — `list_endpoints`, `list_stacks`, `stack_detail`.
|
|
26
|
+
- **Writes** — `restart_container`, `stop_container`/`start_container`,
|
|
27
|
+
`update_container`, `remove_container`, `prune_images`/`prune_volumes`,
|
|
28
|
+
`recreate_stack`.
|
|
29
|
+
- **Three flagship analyses** — `restart_loop_rca` (crash-looping containers +
|
|
30
|
+
cause/action from exit code + log tail), `resource_pressure_analysis` (CPU/memory
|
|
31
|
+
vs limits + recommendation), `image_and_volume_bloat` (prune candidates +
|
|
32
|
+
reclaimable bytes). Each accepts injected data for offline analysis or pulls live.
|
|
33
|
+
- **Connection layer** — Docker over a unix socket (`httpx.HTTPTransport(uds=...)`)
|
|
34
|
+
or a TCP host; Portainer over HTTPS with an `X-API-Key` token that proxies the
|
|
35
|
+
Docker API of a managed endpoint. A local Docker socket needs no secret.
|
|
36
|
+
- **Encrypted secret store** (`~/.container-host-aiops/secrets.enc`, Fernet + scrypt)
|
|
37
|
+
— the Portainer API token, never plaintext on disk; legacy
|
|
38
|
+
`CONTAINER_HOST_<TARGET>_TOKEN` env fallback.
|
|
39
|
+
- **Guarded writes** — destructive ops (`remove_container`, `prune_images`,
|
|
40
|
+
`prune_volumes`, `recreate_stack`) require dry-run + double-confirm; prune
|
|
41
|
+
previews list what would be removed + reclaimable bytes first. Reversible writes
|
|
42
|
+
capture before-state and record an inverse undo descriptor.
|
|
43
|
+
- **CLI** with an `init` platform-picking wizard, `secret` management, and a
|
|
44
|
+
platform-aware `doctor`.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install container-host-aiops
|
|
50
|
+
container-host-aiops init # pick platform (docker/portainer) + connect
|
|
51
|
+
container-host-aiops doctor
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Caveats
|
|
55
|
+
|
|
56
|
+
- Preview / mock-only: the Docker Engine + Portainer API responses are mocked and
|
|
57
|
+
need live verification.
|
|
58
|
+
- Single-host focus by design: cluster orchestrators, hypervisors, storage
|
|
59
|
+
appliances, and backup products are out of scope (separate AIops-tools).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Disclaimer
|
|
4
|
+
|
|
5
|
+
Community-maintained open-source project. **Not affiliated with, endorsed by, or
|
|
6
|
+
sponsored by Docker, Inc. or Portainer.io.** Product and trademark names (Docker,
|
|
7
|
+
Portainer) belong to their owners. Source is auditable under the MIT license.
|
|
8
|
+
|
|
9
|
+
## Reporting Vulnerabilities
|
|
10
|
+
|
|
11
|
+
Report privately via a GitHub Security Advisory on
|
|
12
|
+
[github.com/AIops-tools/Container-Host-AIops](https://github.com/AIops-tools/Container-Host-AIops/security/advisories)
|
|
13
|
+
or email zhouwei008@gmail.com. Please do not open public issues for security
|
|
14
|
+
reports.
|
|
15
|
+
|
|
16
|
+
## Security Design
|
|
17
|
+
|
|
18
|
+
### Credential Management
|
|
19
|
+
- The only secret is the **Portainer API token**. It lives **encrypted** in
|
|
20
|
+
`~/.container-host-aiops/secrets.enc` (Fernet/AES-128 + scrypt-derived key; chmod
|
|
21
|
+
600), never in `config.yaml` and never in source. The master password is never
|
|
22
|
+
stored — only a per-store random salt and the ciphertext are on disk.
|
|
23
|
+
- A **direct Docker socket** target needs no secret at all — the unix socket's
|
|
24
|
+
file permissions are the trust boundary. Treat access to `/var/run/docker.sock`
|
|
25
|
+
as root-equivalent on the host and scope it accordingly.
|
|
26
|
+
- A legacy plaintext env var `CONTAINER_HOST_<TARGET_NAME_UPPER>_TOKEN` is still
|
|
27
|
+
honoured as a fallback with a deprecation warning (migrate with
|
|
28
|
+
`container-host-aiops secret migrate`).
|
|
29
|
+
- The token is held only in memory and never logged or echoed; it is sent in the
|
|
30
|
+
`X-API-Key` request header at call time. The config file holds only platform,
|
|
31
|
+
socket path / host, port, endpoint id, and TLS settings.
|
|
32
|
+
|
|
33
|
+
### Governed Operations
|
|
34
|
+
Every MCP tool runs through the bundled `@governed_tool` harness
|
|
35
|
+
(`container_host_aiops.governance`):
|
|
36
|
+
- **Audit** — every call logged to a local SQLite DB under `~/.container-host-aiops/`
|
|
37
|
+
(relocatable via `CONTAINER_HOST_AIOPS_HOME`), agent-attributed, secret-redacted.
|
|
38
|
+
- **Token/runaway budget** — hard ceilings (`CONTAINER_HOST_MAX_TOOL_CALLS` /
|
|
39
|
+
`CONTAINER_HOST_MAX_TOOL_SECONDS` — the env-var names the bundled harness reads)
|
|
40
|
+
plus an on-by-default guard that trips a tight poll/retry loop, preventing
|
|
41
|
+
unbounded API consumption.
|
|
42
|
+
- **Graduated risk tiers** — `~/.container-host-aiops/rules.yaml` `risk_tiers` gate
|
|
43
|
+
writes by environment/tag; the highest tiers require a recorded approver.
|
|
44
|
+
- **Undo-token recording** — reversible writes capture the BEFORE state and
|
|
45
|
+
record an inverse descriptor (e.g. `stop_container`→`start_container`,
|
|
46
|
+
`update_container`→restore prior limits) so the change can be rolled back.
|
|
47
|
+
|
|
48
|
+
### State-Changing Operations
|
|
49
|
+
Destructive writes — `remove_container`, `prune_images`, `prune_volumes`,
|
|
50
|
+
`recreate_stack` — are `risk_level=high`, accept a `dry_run` preview (prune
|
|
51
|
+
previews **list what would be removed + reclaimable bytes** first), and (under
|
|
52
|
+
`risk_tiers`) require a recorded approver (`CONTAINER_HOST_AUDIT_APPROVED_BY` +
|
|
53
|
+
`CONTAINER_HOST_AUDIT_RATIONALE` — the env-var names the bundled harness reads).
|
|
54
|
+
Lifecycle writes (`restart_container`, `stop_container`, `start_container`,
|
|
55
|
+
`update_container`) are `risk_level=medium`; reversible ones capture before-state
|
|
56
|
+
and record an undo token. `remove_container` captures the full inspect JSON
|
|
57
|
+
before deletion for the audit trail.
|
|
58
|
+
|
|
59
|
+
### SSL/TLS Verification
|
|
60
|
+
`verify_ssl` defaults to true; disable only for a self-signed Portainer / TLS
|
|
61
|
+
Docker daemon in a lab. A unix-socket Docker target does not use TLS.
|
|
62
|
+
|
|
63
|
+
### Prompt-Injection Protection
|
|
64
|
+
All host-returned text (container names, image tags, log lines, event text,
|
|
65
|
+
volume/network names) is passed through a `sanitize()` truncate +
|
|
66
|
+
control-character strip before reaching the agent, bounded in depth and length.
|
|
67
|
+
|
|
68
|
+
### Network Scope
|
|
69
|
+
No webhooks, no telemetry, no outbound calls beyond the configured Docker socket /
|
|
70
|
+
TCP host or the Portainer API base URL. No post-install scripts or background
|
|
71
|
+
services.
|
|
72
|
+
|
|
73
|
+
## Static Analysis
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uvx bandit -r container_host_aiops/ mcp_server/
|
|
77
|
+
uv run ruff check .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Supported Versions
|
|
81
|
+
|
|
82
|
+
The latest released version receives security fixes. This is a preview (0.x);
|
|
83
|
+
pin a version in production.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""container-host-aiops — governed Docker + Portainer container-host ops for AI agents.
|
|
2
|
+
|
|
3
|
+
Standalone and self-contained: the governance harness (audit, token budget,
|
|
4
|
+
undo-token recording, graduated risk tiers, prompt-injection sanitize) is
|
|
5
|
+
bundled under ``container_host_aiops.governance`` — this package has no external
|
|
6
|
+
skill-family dependency. Preview: not yet full-coverage, mock-validated only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Shared helpers for container-host-aiops CLI sub-modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# ─── Shared Option types ───────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
TargetOption = Annotated[
|
|
18
|
+
str | None, typer.Option("--target", "-t", help="Target name from config")
|
|
19
|
+
]
|
|
20
|
+
DryRunOption = Annotated[
|
|
21
|
+
bool, typer.Option("--dry-run", help="Print the API call without executing")
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _cli_error_types() -> tuple[type[BaseException], ...]:
|
|
26
|
+
"""Exceptions translated to a one-line teaching error instead of a traceback."""
|
|
27
|
+
from container_host_aiops.connection import ContainerHostApiError
|
|
28
|
+
|
|
29
|
+
return (ContainerHostApiError, KeyError, OSError, ValueError)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def cli_errors(fn: Callable) -> Callable:
|
|
33
|
+
"""Translate known exceptions into one red line + exit code 1."""
|
|
34
|
+
|
|
35
|
+
@functools.wraps(fn)
|
|
36
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
37
|
+
try:
|
|
38
|
+
return fn(*args, **kwargs)
|
|
39
|
+
except (typer.Exit, typer.Abort):
|
|
40
|
+
raise
|
|
41
|
+
except _cli_error_types() as e:
|
|
42
|
+
message = str(e)
|
|
43
|
+
if isinstance(e, KeyError):
|
|
44
|
+
message = f"Missing required key or environment variable: {message}"
|
|
45
|
+
console.print(f"[red]Error: {message}[/]")
|
|
46
|
+
raise typer.Exit(1) from e
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_connection(target: str | None, config_path: Path | None = None):
|
|
52
|
+
"""Return a (conn, config) tuple for the given target."""
|
|
53
|
+
from container_host_aiops.config import load_config
|
|
54
|
+
from container_host_aiops.connection import ConnectionManager
|
|
55
|
+
|
|
56
|
+
cfg = load_config(config_path)
|
|
57
|
+
mgr = ConnectionManager(cfg)
|
|
58
|
+
return mgr.connect(target), cfg
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def dry_run_print(*, operation: str, api_call: str, parameters: dict | None = None) -> None:
|
|
62
|
+
"""Print a dry-run preview of the API call that would be made."""
|
|
63
|
+
console.print("\n[bold magenta][DRY-RUN] No changes will be made.[/]")
|
|
64
|
+
console.print(f"[magenta] Operation: {operation}[/]")
|
|
65
|
+
console.print(f"[magenta] API Call: {api_call}[/]")
|
|
66
|
+
for k, v in (parameters or {}).items():
|
|
67
|
+
console.print(f"[magenta] Param: {k} = {v}[/]")
|
|
68
|
+
console.print("[magenta] Run without --dry-run to execute.[/]\n")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def double_confirm(action: str, resource: str) -> None:
|
|
72
|
+
"""Require two confirmations for a destructive operation."""
|
|
73
|
+
console.print(f"[bold yellow]⚠️ About to: {action} '{resource}'[/]")
|
|
74
|
+
typer.confirm(f"Confirm 1/2: {action} '{resource}'?", abort=True)
|
|
75
|
+
typer.confirm(
|
|
76
|
+
f"Confirm 2/2: really {action} '{resource}'? This may be irreversible.",
|
|
77
|
+
abort=True,
|
|
78
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Top-level Typer app: assembles sub-apps and top-level commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from container_host_aiops.cli._common import cli_errors
|
|
8
|
+
from container_host_aiops.cli.analyze import analyze_app
|
|
9
|
+
from container_host_aiops.cli.container import container_app
|
|
10
|
+
from container_host_aiops.cli.doctor import doctor_cmd
|
|
11
|
+
from container_host_aiops.cli.image import image_app
|
|
12
|
+
from container_host_aiops.cli.init import init_cmd
|
|
13
|
+
from container_host_aiops.cli.manage import manage_app
|
|
14
|
+
from container_host_aiops.cli.network import network_app
|
|
15
|
+
from container_host_aiops.cli.overview import overview_cmd
|
|
16
|
+
from container_host_aiops.cli.secret import secret_app
|
|
17
|
+
from container_host_aiops.cli.stack import stack_app
|
|
18
|
+
from container_host_aiops.cli.system import system_app
|
|
19
|
+
from container_host_aiops.cli.volume import volume_app
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
name="container-host-aiops",
|
|
23
|
+
help="Governed AI-ops for Docker + Portainer container hosts: container / "
|
|
24
|
+
"image / volume / network / system reads, flagship analyses, and guarded "
|
|
25
|
+
"lifecycle + prune writes with a built-in governance harness.",
|
|
26
|
+
no_args_is_help=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
app.add_typer(container_app, name="container")
|
|
30
|
+
app.add_typer(image_app, name="image")
|
|
31
|
+
app.add_typer(volume_app, name="volume")
|
|
32
|
+
app.add_typer(network_app, name="network")
|
|
33
|
+
app.add_typer(system_app, name="system")
|
|
34
|
+
app.add_typer(stack_app, name="stack")
|
|
35
|
+
app.add_typer(analyze_app, name="analyze")
|
|
36
|
+
app.add_typer(manage_app, name="manage")
|
|
37
|
+
app.add_typer(secret_app, name="secret")
|
|
38
|
+
app.command("init")(init_cmd)
|
|
39
|
+
app.command("overview")(overview_cmd)
|
|
40
|
+
app.command("doctor")(doctor_cmd)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("mcp")
|
|
44
|
+
@cli_errors
|
|
45
|
+
def mcp_cmd() -> None:
|
|
46
|
+
"""Start the MCP server (stdio transport).
|
|
47
|
+
|
|
48
|
+
Single-command entry point for MCP clients (does not go through uvx/PyPI
|
|
49
|
+
resolution at launch):
|
|
50
|
+
container-host-aiops mcp
|
|
51
|
+
"""
|
|
52
|
+
import sys
|
|
53
|
+
|
|
54
|
+
if sys.version_info < (3, 11):
|
|
55
|
+
typer.echo(
|
|
56
|
+
f"ERROR: container-host-aiops requires Python >= 3.11 "
|
|
57
|
+
f"(got {sys.version_info.major}.{sys.version_info.minor}).\n"
|
|
58
|
+
f"Fix: uv python install 3.12 && "
|
|
59
|
+
f"uv tool install --python 3.12 --force container-host-aiops",
|
|
60
|
+
err=True,
|
|
61
|
+
)
|
|
62
|
+
raise typer.Exit(2)
|
|
63
|
+
|
|
64
|
+
from mcp_server.server import main as _mcp_main
|
|
65
|
+
|
|
66
|
+
_mcp_main()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
app()
|