fabric-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.
- fabric_aiops-0.1.0/.github/workflows/publish.yml +26 -0
- fabric_aiops-0.1.0/.gitignore +7 -0
- fabric_aiops-0.1.0/CHANGELOG.md +55 -0
- fabric_aiops-0.1.0/LICENSE +21 -0
- fabric_aiops-0.1.0/PKG-INFO +123 -0
- fabric_aiops-0.1.0/README.md +107 -0
- fabric_aiops-0.1.0/RELEASE_NOTES.md +51 -0
- fabric_aiops-0.1.0/SECURITY.md +80 -0
- fabric_aiops-0.1.0/fabric_aiops/__init__.py +10 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/__init__.py +9 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/_common.py +81 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/_root.py +64 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/client.py +65 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/device.py +75 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/doctor.py +21 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/health.py +47 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/init.py +99 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/network.py +72 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/org.py +75 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/overview.py +16 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/remediate.py +195 -0
- fabric_aiops-0.1.0/fabric_aiops/cli/secret.py +103 -0
- fabric_aiops-0.1.0/fabric_aiops/config.py +151 -0
- fabric_aiops-0.1.0/fabric_aiops/connection.py +214 -0
- fabric_aiops-0.1.0/fabric_aiops/doctor.py +84 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/__init__.py +40 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/audit.py +377 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/budget.py +225 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/decorators.py +474 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/paths.py +23 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/patterns.py +378 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/policy.py +411 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/sanitize.py +39 -0
- fabric_aiops-0.1.0/fabric_aiops/governance/undo.py +218 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/__init__.py +1 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/_util.py +74 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/clients.py +62 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/devices.py +93 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/health.py +283 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/networks.py +82 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/organizations.py +93 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/overview.py +41 -0
- fabric_aiops-0.1.0/fabric_aiops/ops/remediation.py +145 -0
- fabric_aiops-0.1.0/fabric_aiops/platform.py +148 -0
- fabric_aiops-0.1.0/fabric_aiops/secretstore.py +302 -0
- fabric_aiops-0.1.0/mcp_server/__init__.py +1 -0
- fabric_aiops-0.1.0/mcp_server/_shared.py +100 -0
- fabric_aiops-0.1.0/mcp_server/server.py +35 -0
- fabric_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- fabric_aiops-0.1.0/mcp_server/tools/clients.py +67 -0
- fabric_aiops-0.1.0/mcp_server/tools/devices.py +83 -0
- fabric_aiops-0.1.0/mcp_server/tools/health.py +104 -0
- fabric_aiops-0.1.0/mcp_server/tools/networks.py +75 -0
- fabric_aiops-0.1.0/mcp_server/tools/organizations.py +101 -0
- fabric_aiops-0.1.0/mcp_server/tools/remediation.py +310 -0
- fabric_aiops-0.1.0/pyproject.toml +59 -0
- fabric_aiops-0.1.0/server.json +21 -0
- fabric_aiops-0.1.0/skills/fabric-aiops/SKILL.md +106 -0
- fabric_aiops-0.1.0/skills/fabric-aiops/references/capabilities.md +84 -0
- fabric_aiops-0.1.0/skills/fabric-aiops/references/cli-reference.md +75 -0
- fabric_aiops-0.1.0/skills/fabric-aiops/references/setup-guide.md +82 -0
- fabric_aiops-0.1.0/smithery.yaml +9 -0
- fabric_aiops-0.1.0/tests/test_health.py +138 -0
- fabric_aiops-0.1.0/tests/test_platform.py +142 -0
- fabric_aiops-0.1.0/tests/test_reads.py +169 -0
- fabric_aiops-0.1.0/tests/test_secretstore.py +99 -0
- fabric_aiops-0.1.0/tests/test_smoke.py +262 -0
- fabric_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,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to fabric-aiops are documented here. Format loosely follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/); this project uses semantic
|
|
5
|
+
versioning (currently 0.x preview — the API may change).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-07-13
|
|
8
|
+
|
|
9
|
+
Initial preview release: governed AI-ops for network fabrics managed through the
|
|
10
|
+
**Cisco Meraki Dashboard API** (organizations → networks → devices) with a
|
|
11
|
+
bundled governance harness. **Mock-validated only — not verified against a live
|
|
12
|
+
Meraki organization.** Community-maintained; not affiliated with Cisco/Meraki.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Multi-platform connection layer** — a `platform` registry (`fabric_aiops.platform`)
|
|
17
|
+
keyed by name so future controllers (Catalyst Center, Arista CVP) can register a
|
|
18
|
+
descriptor without touching the ops/CLI/MCP layers. Only **meraki** is
|
|
19
|
+
registered in v0.1. Handles `Authorization: Bearer` / `X-Cisco-Meraki-API-Key`
|
|
20
|
+
auth, `Link`-header pagination, and injection-safe response normalisation.
|
|
21
|
+
- **32 governed MCP tools**, every one wrapped with `@governed_tool`:
|
|
22
|
+
- **Overview** — `overview` (org networks + device status/product rollup).
|
|
23
|
+
- **Organizations** — `org_list`, `org_get`, `org_licensing`, `org_admins`,
|
|
24
|
+
`org_device_statuses`, `org_api_requests`.
|
|
25
|
+
- **Networks** — `network_list`, `network_get`, `network_vlans`,
|
|
26
|
+
`network_alerts`, `network_traffic`.
|
|
27
|
+
- **Devices** — `device_inventory` (by model MX/MS/MR/MV/MG), `device_status`,
|
|
28
|
+
`device_uplinks`, `switch_ports`, `wireless_ssids`.
|
|
29
|
+
- **Clients** — `client_list`, `client_get`, `client_usage`,
|
|
30
|
+
`client_connectivity`.
|
|
31
|
+
- **Health (flagship)** — `uplink_loss_and_latency_rca`, `network_health_score`,
|
|
32
|
+
`config_template_drift`.
|
|
33
|
+
- **Remediation (writes)** — `reboot_device` (high), `blink_device_leds` (low),
|
|
34
|
+
`update_device` (medium), `update_network_vlan` (medium),
|
|
35
|
+
`claim_devices_into_network` (high), `remove_device_from_network` (high),
|
|
36
|
+
`bind_network_to_template` (high), `unbind_network_from_template` (high).
|
|
37
|
+
- **Guarded writes** — every write supports a `dry_run` preview and (at the CLI)
|
|
38
|
+
double confirmation. Mutating/reversible writes fetch the **real before-state**
|
|
39
|
+
and record a faithful inverse undo descriptor.
|
|
40
|
+
- **Bundled governance harness** (`fabric_aiops.governance`) — audit log, policy
|
|
41
|
+
engine, token/runaway budget guard, undo-token recording, graduated risk tiers,
|
|
42
|
+
prompt-injection `sanitize`. State under `~/.fabric-aiops/` (relocatable via
|
|
43
|
+
`FABRIC_AIOPS_HOME`).
|
|
44
|
+
- **Encrypted secret store** — Meraki API keys in `~/.fabric-aiops/secrets.enc`
|
|
45
|
+
(Fernet + scrypt); legacy `FABRIC_<TARGET>_APIKEY` env fallback + `secret
|
|
46
|
+
migrate`.
|
|
47
|
+
- **CLI** — `init` wizard, `overview`, `org`, `network`, `device`, `client`,
|
|
48
|
+
`health`, `remediate`, `secret`, `doctor`, `mcp`.
|
|
49
|
+
|
|
50
|
+
### Known limitations
|
|
51
|
+
|
|
52
|
+
- Preview / mock-only: Dashboard API paths need live verification.
|
|
53
|
+
- Coverage is a curated subset of the Dashboard API; open an issue/PR for gaps.
|
|
54
|
+
|
|
55
|
+
[0.1.0]: https://github.com/AIops-tools/Fabric-AIops/releases/tag/v0.1.0
|
|
@@ -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,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fabric-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Governed AI-ops for Cisco Meraki network fabrics: uplink RCA, health scoring & config-drift 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/fabric-aiops -->
|
|
18
|
+
|
|
19
|
+
# Fabric AIops (preview)
|
|
20
|
+
|
|
21
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Cisco, Meraki, or any network-controller vendor.** "Cisco" and "Meraki" and all product/trademark names belong to their respective owners. MIT licensed.
|
|
22
|
+
|
|
23
|
+
Governed AI-ops for **network fabrics** managed through a cloud controller —
|
|
24
|
+
starting with the **Cisco Meraki Dashboard API** (organizations → networks →
|
|
25
|
+
devices) — with a **built-in governance harness**: unified audit log, policy
|
|
26
|
+
engine, token/runaway budget guard, undo-token recording, and graduated-autonomy
|
|
27
|
+
risk tiers. **Multi-platform by construction**: a registry keyed by `platform`
|
|
28
|
+
means Catalyst Center or Arista CVP can be added later as additional platforms
|
|
29
|
+
without touching the ops/CLI/MCP layers — **Meraki is the one platform shipped in
|
|
30
|
+
v0.1**. **Preview — mock-validated only, not verified against a live Meraki org.**
|
|
31
|
+
|
|
32
|
+
## What it does
|
|
33
|
+
|
|
34
|
+
Three flagship signature analyses, plus the guarded reads and writes around them:
|
|
35
|
+
|
|
36
|
+
- **Uplink loss & latency RCA** — pull MX WAN uplink loss + latency across an
|
|
37
|
+
org, rank the worst uplinks by a composite of average loss and latency, and
|
|
38
|
+
map each degraded uplink to a likely cause + recommended action. Every ranking
|
|
39
|
+
carries its numbers, not a black-box verdict.
|
|
40
|
+
- **Network health score** — a composite 0-100 score per network from device
|
|
41
|
+
online %, uplink health %, and an alert-severity penalty (weighted 0.5/0.3/0.2),
|
|
42
|
+
with every component returned so the number is explainable.
|
|
43
|
+
- **Config template drift** — for networks bound to a config template, list the
|
|
44
|
+
settings that have drifted from the template (expected vs actual).
|
|
45
|
+
|
|
46
|
+
## What works
|
|
47
|
+
|
|
48
|
+
- **CLI** (`fabric-aiops ...`): `init`, `overview`, `org`, `network`, `device`, `client`, `health`, `remediate`, `secret`, `doctor`, `mcp`.
|
|
49
|
+
- **MCP server** (`fabric-aiops mcp` or `fabric-aiops-mcp`): **32 tools** (24 read, 8 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
50
|
+
- **Encrypted credentials**: the Meraki API key lives in an encrypted store `~/.fabric-aiops/secrets.enc` (Fernet + scrypt) — **never plaintext on disk**. Unlock with a master password from `FABRIC_AIOPS_MASTER_PASSWORD` (MCP/CI) or an interactive prompt (CLI).
|
|
51
|
+
- **Reversibility**: mutating writes fetch the **real before-state first** and record a faithful inverse (`update_device`/`update_network_vlan` restore prior values; `claim`↔`remove`; `bind`↔`unbind`/rebind). Irreversible ops (`reboot_device`, `blink_device_leds`) record the prior state for audit but declare no undo.
|
|
52
|
+
- **Safety**: every state-changing CLI op supports `--dry-run` and requires double confirmation; every write MCP tool takes a `dry_run` preview.
|
|
53
|
+
|
|
54
|
+
## Capability matrix (32 MCP tools)
|
|
55
|
+
|
|
56
|
+
| Domain | Tools | Count | R/W |
|
|
57
|
+
|--------|-------|:-----:|:---:|
|
|
58
|
+
| **Overview** | `overview` | 1 | read |
|
|
59
|
+
| **Organizations** | `org_list`, `org_get`, `org_licensing`, `org_admins`, `org_device_statuses`, `org_api_requests` | 6 | read |
|
|
60
|
+
| **Networks** | `network_list`, `network_get`, `network_vlans`, `network_alerts`, `network_traffic` | 5 | read |
|
|
61
|
+
| **Devices** | `device_inventory`, `device_status`, `device_uplinks`, `switch_ports`, `wireless_ssids` | 5 | read |
|
|
62
|
+
| **Clients** | `client_list`, `client_get`, `client_usage`, `client_connectivity` | 4 | read |
|
|
63
|
+
| **Health (flagship)** | `uplink_loss_and_latency_rca`, `network_health_score`, `config_template_drift` | 3 | read |
|
|
64
|
+
| **Remediation** | `reboot_device`, `claim_devices_into_network`, `remove_device_from_network`, `bind_network_to_template`, `unbind_network_from_template` | 5 | write (high) |
|
|
65
|
+
| | `update_device`, `update_network_vlan` | 2 | write (medium) |
|
|
66
|
+
| | `blink_device_leds` | 1 | write (low) |
|
|
67
|
+
|
|
68
|
+
`network_health_score` and `config_template_drift` are injected-only (they score
|
|
69
|
+
data you already hold); `uplink_loss_and_latency_rca` accepts injected `records`
|
|
70
|
+
for offline analysis or pulls live from a configured target. Device models carry
|
|
71
|
+
a product-type prefix: **MX** appliance, **MS** switch, **MR** wireless AP, **MV**
|
|
72
|
+
camera, **MG** cellular gateway.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv tool install fabric-aiops # or: pipx install fabric-aiops
|
|
78
|
+
fabric-aiops init # wizard: add a target + store the Meraki API key (encrypted)
|
|
79
|
+
fabric-aiops doctor # verify config, secrets, connectivity
|
|
80
|
+
fabric-aiops overview # one-shot fabric fleet health
|
|
81
|
+
fabric-aiops health uplink-rca # rank worst MX WAN uplinks + cause/action
|
|
82
|
+
fabric-aiops device inventory --model MS # switches in the org
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Run as an MCP server (stdio):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export FABRIC_AIOPS_MASTER_PASSWORD=... # unlock secrets non-interactively
|
|
89
|
+
fabric-aiops-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Governance
|
|
93
|
+
|
|
94
|
+
Every MCP tool passes through the bundled `@governed_tool` harness:
|
|
95
|
+
|
|
96
|
+
- **Audit** — every call (params, result, status, duration, risk tier, approver,
|
|
97
|
+
rationale) is logged to `~/.fabric-aiops/audit.db` (relocatable via
|
|
98
|
+
`FABRIC_AIOPS_HOME`).
|
|
99
|
+
- **Budget / runaway guard** — token and call budgets trip a circuit breaker.
|
|
100
|
+
- **Risk tiers** — graduated autonomy; high-risk ops can require a named approver
|
|
101
|
+
(`ENDPOINT_AUDIT_APPROVED_BY` / `ENDPOINT_AUDIT_RATIONALE` — the env-var names
|
|
102
|
+
the bundled harness reads).
|
|
103
|
+
- **Undo recording** — reversible writes record an inverse descriptor built from
|
|
104
|
+
the fetched before-state.
|
|
105
|
+
|
|
106
|
+
## Scope
|
|
107
|
+
|
|
108
|
+
This is the **network-fabric / controller** member of the AIops-tools family
|
|
109
|
+
(governed AI-ops with audit + budget + undo + risk tiers). Do **NOT** use it for
|
|
110
|
+
OT / industrial edge (Modbus, OPC-UA, PROFINET) — see the separate
|
|
111
|
+
`industrial-aiops` line — nor for device-level CLI/SSH network automation.
|
|
112
|
+
|
|
113
|
+
## Missing a capability?
|
|
114
|
+
|
|
115
|
+
Coverage is intentionally a curated subset of the Meraki Dashboard API. Missing a
|
|
116
|
+
call, a device family, or want another controller platform (Catalyst Center,
|
|
117
|
+
Arista CVP)? **Open an issue or PR** — contributions welcome.
|
|
118
|
+
|
|
119
|
+
## Status
|
|
120
|
+
|
|
121
|
+
**Preview — mock-validated only, not verified against a live Meraki org.** The
|
|
122
|
+
Dashboard API paths are modelled from the public API shape and need live
|
|
123
|
+
verification. `fabric-aiops doctor` is the fastest live check.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/fabric-aiops -->
|
|
2
|
+
|
|
3
|
+
# Fabric AIops (preview)
|
|
4
|
+
|
|
5
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Cisco, Meraki, or any network-controller vendor.** "Cisco" and "Meraki" and all product/trademark names belong to their respective owners. MIT licensed.
|
|
6
|
+
|
|
7
|
+
Governed AI-ops for **network fabrics** managed through a cloud controller —
|
|
8
|
+
starting with the **Cisco Meraki Dashboard API** (organizations → networks →
|
|
9
|
+
devices) — with a **built-in governance harness**: unified audit log, policy
|
|
10
|
+
engine, token/runaway budget guard, undo-token recording, and graduated-autonomy
|
|
11
|
+
risk tiers. **Multi-platform by construction**: a registry keyed by `platform`
|
|
12
|
+
means Catalyst Center or Arista CVP can be added later as additional platforms
|
|
13
|
+
without touching the ops/CLI/MCP layers — **Meraki is the one platform shipped in
|
|
14
|
+
v0.1**. **Preview — mock-validated only, not verified against a live Meraki org.**
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
Three flagship signature analyses, plus the guarded reads and writes around them:
|
|
19
|
+
|
|
20
|
+
- **Uplink loss & latency RCA** — pull MX WAN uplink loss + latency across an
|
|
21
|
+
org, rank the worst uplinks by a composite of average loss and latency, and
|
|
22
|
+
map each degraded uplink to a likely cause + recommended action. Every ranking
|
|
23
|
+
carries its numbers, not a black-box verdict.
|
|
24
|
+
- **Network health score** — a composite 0-100 score per network from device
|
|
25
|
+
online %, uplink health %, and an alert-severity penalty (weighted 0.5/0.3/0.2),
|
|
26
|
+
with every component returned so the number is explainable.
|
|
27
|
+
- **Config template drift** — for networks bound to a config template, list the
|
|
28
|
+
settings that have drifted from the template (expected vs actual).
|
|
29
|
+
|
|
30
|
+
## What works
|
|
31
|
+
|
|
32
|
+
- **CLI** (`fabric-aiops ...`): `init`, `overview`, `org`, `network`, `device`, `client`, `health`, `remediate`, `secret`, `doctor`, `mcp`.
|
|
33
|
+
- **MCP server** (`fabric-aiops mcp` or `fabric-aiops-mcp`): **32 tools** (24 read, 8 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
34
|
+
- **Encrypted credentials**: the Meraki API key lives in an encrypted store `~/.fabric-aiops/secrets.enc` (Fernet + scrypt) — **never plaintext on disk**. Unlock with a master password from `FABRIC_AIOPS_MASTER_PASSWORD` (MCP/CI) or an interactive prompt (CLI).
|
|
35
|
+
- **Reversibility**: mutating writes fetch the **real before-state first** and record a faithful inverse (`update_device`/`update_network_vlan` restore prior values; `claim`↔`remove`; `bind`↔`unbind`/rebind). Irreversible ops (`reboot_device`, `blink_device_leds`) record the prior state for audit but declare no undo.
|
|
36
|
+
- **Safety**: every state-changing CLI op supports `--dry-run` and requires double confirmation; every write MCP tool takes a `dry_run` preview.
|
|
37
|
+
|
|
38
|
+
## Capability matrix (32 MCP tools)
|
|
39
|
+
|
|
40
|
+
| Domain | Tools | Count | R/W |
|
|
41
|
+
|--------|-------|:-----:|:---:|
|
|
42
|
+
| **Overview** | `overview` | 1 | read |
|
|
43
|
+
| **Organizations** | `org_list`, `org_get`, `org_licensing`, `org_admins`, `org_device_statuses`, `org_api_requests` | 6 | read |
|
|
44
|
+
| **Networks** | `network_list`, `network_get`, `network_vlans`, `network_alerts`, `network_traffic` | 5 | read |
|
|
45
|
+
| **Devices** | `device_inventory`, `device_status`, `device_uplinks`, `switch_ports`, `wireless_ssids` | 5 | read |
|
|
46
|
+
| **Clients** | `client_list`, `client_get`, `client_usage`, `client_connectivity` | 4 | read |
|
|
47
|
+
| **Health (flagship)** | `uplink_loss_and_latency_rca`, `network_health_score`, `config_template_drift` | 3 | read |
|
|
48
|
+
| **Remediation** | `reboot_device`, `claim_devices_into_network`, `remove_device_from_network`, `bind_network_to_template`, `unbind_network_from_template` | 5 | write (high) |
|
|
49
|
+
| | `update_device`, `update_network_vlan` | 2 | write (medium) |
|
|
50
|
+
| | `blink_device_leds` | 1 | write (low) |
|
|
51
|
+
|
|
52
|
+
`network_health_score` and `config_template_drift` are injected-only (they score
|
|
53
|
+
data you already hold); `uplink_loss_and_latency_rca` accepts injected `records`
|
|
54
|
+
for offline analysis or pulls live from a configured target. Device models carry
|
|
55
|
+
a product-type prefix: **MX** appliance, **MS** switch, **MR** wireless AP, **MV**
|
|
56
|
+
camera, **MG** cellular gateway.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv tool install fabric-aiops # or: pipx install fabric-aiops
|
|
62
|
+
fabric-aiops init # wizard: add a target + store the Meraki API key (encrypted)
|
|
63
|
+
fabric-aiops doctor # verify config, secrets, connectivity
|
|
64
|
+
fabric-aiops overview # one-shot fabric fleet health
|
|
65
|
+
fabric-aiops health uplink-rca # rank worst MX WAN uplinks + cause/action
|
|
66
|
+
fabric-aiops device inventory --model MS # switches in the org
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Run as an MCP server (stdio):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export FABRIC_AIOPS_MASTER_PASSWORD=... # unlock secrets non-interactively
|
|
73
|
+
fabric-aiops-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Governance
|
|
77
|
+
|
|
78
|
+
Every MCP tool passes through the bundled `@governed_tool` harness:
|
|
79
|
+
|
|
80
|
+
- **Audit** — every call (params, result, status, duration, risk tier, approver,
|
|
81
|
+
rationale) is logged to `~/.fabric-aiops/audit.db` (relocatable via
|
|
82
|
+
`FABRIC_AIOPS_HOME`).
|
|
83
|
+
- **Budget / runaway guard** — token and call budgets trip a circuit breaker.
|
|
84
|
+
- **Risk tiers** — graduated autonomy; high-risk ops can require a named approver
|
|
85
|
+
(`ENDPOINT_AUDIT_APPROVED_BY` / `ENDPOINT_AUDIT_RATIONALE` — the env-var names
|
|
86
|
+
the bundled harness reads).
|
|
87
|
+
- **Undo recording** — reversible writes record an inverse descriptor built from
|
|
88
|
+
the fetched before-state.
|
|
89
|
+
|
|
90
|
+
## Scope
|
|
91
|
+
|
|
92
|
+
This is the **network-fabric / controller** member of the AIops-tools family
|
|
93
|
+
(governed AI-ops with audit + budget + undo + risk tiers). Do **NOT** use it for
|
|
94
|
+
OT / industrial edge (Modbus, OPC-UA, PROFINET) — see the separate
|
|
95
|
+
`industrial-aiops` line — nor for device-level CLI/SSH network automation.
|
|
96
|
+
|
|
97
|
+
## Missing a capability?
|
|
98
|
+
|
|
99
|
+
Coverage is intentionally a curated subset of the Meraki Dashboard API. Missing a
|
|
100
|
+
call, a device family, or want another controller platform (Catalyst Center,
|
|
101
|
+
Arista CVP)? **Open an issue or PR** — contributions welcome.
|
|
102
|
+
|
|
103
|
+
## Status
|
|
104
|
+
|
|
105
|
+
**Preview — mock-validated only, not verified against a live Meraki org.** The
|
|
106
|
+
Dashboard API paths are modelled from the public API shape and need live
|
|
107
|
+
verification. `fabric-aiops doctor` is the fastest live check.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Fabric AIops v0.1.0 — preview
|
|
2
|
+
|
|
3
|
+
Governed AI-ops for **network fabrics** managed through the **Cisco Meraki
|
|
4
|
+
Dashboard API** (organizations → networks → devices) for AI agents, with a
|
|
5
|
+
built-in governance harness (audit, policy, token/runaway budget, undo-token
|
|
6
|
+
recording, graduated risk tiers) and an encrypted credential store. Standalone —
|
|
7
|
+
no external skill-family dependency. Multi-platform by construction; Meraki is
|
|
8
|
+
the one platform shipped in v0.1.
|
|
9
|
+
|
|
10
|
+
> **Preview / mock-only.** All behaviour is validated against mocked Dashboard
|
|
11
|
+
> API responses; it has not been run against a live Meraki organization. The
|
|
12
|
+
> fastest live check is `fabric-aiops doctor`.
|
|
13
|
+
>
|
|
14
|
+
> Community-maintained; **not affiliated with or endorsed by Cisco/Meraki.**
|
|
15
|
+
> Trademarks belong to their owners.
|
|
16
|
+
|
|
17
|
+
## Highlights
|
|
18
|
+
|
|
19
|
+
- **32 MCP tools** (24 read, 8 write), every one wrapped with `@governed_tool`.
|
|
20
|
+
- Read: fleet `overview`; organizations (6); networks (5); devices (5);
|
|
21
|
+
clients (4); and three flagship analyses.
|
|
22
|
+
- Write: `reboot_device`/`claim_devices_into_network`/`remove_device_from_network`/
|
|
23
|
+
`bind_network_to_template`/`unbind_network_from_template` (high),
|
|
24
|
+
`update_device`/`update_network_vlan` (medium, capture before-state),
|
|
25
|
+
`blink_device_leds` (low).
|
|
26
|
+
- **Three signature analyses** — `uplink_loss_and_latency_rca` (rank worst MX WAN
|
|
27
|
+
uplinks + cause/action), `network_health_score` (composite per-network health),
|
|
28
|
+
and `config_template_drift` (settings drifted from a bound template).
|
|
29
|
+
- **Encrypted API key store** (`~/.fabric-aiops/secrets.enc`, Fernet + scrypt) —
|
|
30
|
+
never plaintext on disk; legacy `FABRIC_<TARGET>_APIKEY` env fallback.
|
|
31
|
+
- **CLI** with an `init` onboarding wizard, `secret` management, and `doctor`.
|
|
32
|
+
- **Multi-platform connection layer** — a `platform` registry (`Authorization:
|
|
33
|
+
Bearer` or `X-Cisco-Meraki-API-Key` auth, `Link`-header pagination) with
|
|
34
|
+
teaching error translation (`FabricApiError`); only Meraki registered in v0.1.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install fabric-aiops
|
|
40
|
+
fabric-aiops init
|
|
41
|
+
fabric-aiops doctor
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Caveats
|
|
45
|
+
|
|
46
|
+
- The Dashboard API paths are modelled from the public API shape and need live
|
|
47
|
+
verification against a real organization.
|
|
48
|
+
- Out of scope by design: full org/network provisioning workflows, firmware
|
|
49
|
+
upgrade orchestration, and any bulk destructive operation.
|
|
50
|
+
- Missing a capability or want another controller platform (Catalyst Center,
|
|
51
|
+
Arista CVP)? Open an issue or PR.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Disclaimer
|
|
4
|
+
|
|
5
|
+
Community-maintained open-source project. **Not affiliated with, endorsed by, or
|
|
6
|
+
sponsored by Cisco, Meraki, or any network-controller vendor.** "Cisco",
|
|
7
|
+
"Meraki", and all product/trademark names belong to their respective owners.
|
|
8
|
+
Source is publicly auditable under the MIT license.
|
|
9
|
+
|
|
10
|
+
## Reporting Vulnerabilities
|
|
11
|
+
|
|
12
|
+
Report privately via a GitHub Security Advisory on
|
|
13
|
+
[github.com/AIops-tools/Fabric-AIops](https://github.com/AIops-tools/Fabric-AIops/security/advisories)
|
|
14
|
+
or email zhouwei008@gmail.com. Please do not open public issues for security
|
|
15
|
+
reports.
|
|
16
|
+
|
|
17
|
+
## Security Design
|
|
18
|
+
|
|
19
|
+
### Credential Management
|
|
20
|
+
- Per-target Meraki API keys live **encrypted** in `~/.fabric-aiops/secrets.enc`
|
|
21
|
+
(Fernet/AES-128 + scrypt-derived key; chmod 600), never in `config.yaml` and
|
|
22
|
+
never in source. The master password is never stored — only a per-store random
|
|
23
|
+
salt and the ciphertext are on disk.
|
|
24
|
+
- A legacy plaintext env var `FABRIC_<TARGET_NAME_UPPER>_APIKEY` is still honoured
|
|
25
|
+
as a fallback with a deprecation warning (migrate with `fabric-aiops secret
|
|
26
|
+
migrate`).
|
|
27
|
+
- The API key is sent in the platform auth header (`Authorization: Bearer` by
|
|
28
|
+
default, or `X-Cisco-Meraki-API-Key`) at request time and held only in memory.
|
|
29
|
+
Keys are never logged or echoed; the config file holds only platform, base URL,
|
|
30
|
+
default org id, and TLS settings.
|
|
31
|
+
|
|
32
|
+
### Governed Operations
|
|
33
|
+
Every MCP tool runs through the bundled `@governed_tool` harness
|
|
34
|
+
(`fabric_aiops.governance`):
|
|
35
|
+
- **Audit** — every call logged to a local SQLite DB under `~/.fabric-aiops/`
|
|
36
|
+
(relocatable via `FABRIC_AIOPS_HOME`), agent-attributed, secret-redacted.
|
|
37
|
+
- **Token/runaway budget** — hard ceilings (`ENDPOINT_MAX_TOOL_CALLS` /
|
|
38
|
+
`ENDPOINT_MAX_TOOL_SECONDS` — the env-var names the bundled harness reads) plus
|
|
39
|
+
an on-by-default guard that trips a tight poll/retry loop, preventing unbounded
|
|
40
|
+
Dashboard API consumption (and the associated rate-limit exposure).
|
|
41
|
+
- **Graduated risk tiers** — `~/.fabric-aiops/rules.yaml` `risk_tiers` gate writes
|
|
42
|
+
by environment/tag; the highest tiers require a recorded approver
|
|
43
|
+
(`ENDPOINT_AUDIT_APPROVED_BY` / `ENDPOINT_AUDIT_RATIONALE`).
|
|
44
|
+
- **Undo-token recording** — mutating/reversible writes fetch the **real
|
|
45
|
+
before-state first** and record a faithful inverse descriptor (e.g.
|
|
46
|
+
`update_device` restores the prior attributes; `bind`↔`unbind`; `claim`↔`remove`).
|
|
47
|
+
|
|
48
|
+
### State-Changing Operations
|
|
49
|
+
Every write supports `--dry-run` (CLI) / `dry_run=True` (MCP) and requires double
|
|
50
|
+
confirmation at the CLI layer. Destructive or fleet-affecting ops (`reboot_device`,
|
|
51
|
+
`claim_devices_into_network`, `remove_device_from_network`,
|
|
52
|
+
`bind_network_to_template`, `unbind_network_from_template`) are `risk_level=high`;
|
|
53
|
+
mutating reversible ops (`update_device`, `update_network_vlan`) are `medium`;
|
|
54
|
+
`blink_device_leds` (a locator aid, no config change) is `low`. Irreversible ops
|
|
55
|
+
capture the before-state for the audit record but record no undo token.
|
|
56
|
+
|
|
57
|
+
### SSL/TLS Verification
|
|
58
|
+
`verify_ssl` defaults to true; disable only for a self-signed on-prem controller
|
|
59
|
+
proxy.
|
|
60
|
+
|
|
61
|
+
### Prompt-Injection Protection
|
|
62
|
+
All controller-returned text (network names, device names, client descriptions,
|
|
63
|
+
tags, settings) is passed through a `sanitize()` truncate + control-character
|
|
64
|
+
strip before reaching the agent.
|
|
65
|
+
|
|
66
|
+
### Network Scope
|
|
67
|
+
No webhooks, no telemetry, no outbound calls beyond the configured controller REST
|
|
68
|
+
API base URL. No post-install scripts or background services.
|
|
69
|
+
|
|
70
|
+
## Static Analysis
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uvx bandit -r fabric_aiops/ mcp_server/
|
|
74
|
+
uv run ruff check .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Supported Versions
|
|
78
|
+
|
|
79
|
+
The latest released version receives security fixes. This is a preview (0.x);
|
|
80
|
+
pin a version in production.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""fabric-aiops — governed Cisco Meraki network-fabric operations 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 ``fabric_aiops.governance`` — this package has no external
|
|
6
|
+
skill-family dependency. Multi-platform by construction (see
|
|
7
|
+
``fabric_aiops.platform``); v0.1 ships Meraki. Preview: not yet full-coverage.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Shared helpers for fabric-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
|
+
OrgOption = Annotated[
|
|
21
|
+
str | None, typer.Option("--org-id", "-o", help="Meraki organization id (else target default)")
|
|
22
|
+
]
|
|
23
|
+
DryRunOption = Annotated[
|
|
24
|
+
bool, typer.Option("--dry-run", help="Print the API call without executing")
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _cli_error_types() -> tuple[type[BaseException], ...]:
|
|
29
|
+
"""Exceptions translated to a one-line teaching error instead of a traceback."""
|
|
30
|
+
from fabric_aiops.connection import FabricApiError
|
|
31
|
+
|
|
32
|
+
return (FabricApiError, KeyError, OSError, ValueError)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cli_errors(fn: Callable) -> Callable:
|
|
36
|
+
"""Translate known exceptions into one red line + exit code 1."""
|
|
37
|
+
|
|
38
|
+
@functools.wraps(fn)
|
|
39
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
40
|
+
try:
|
|
41
|
+
return fn(*args, **kwargs)
|
|
42
|
+
except (typer.Exit, typer.Abort):
|
|
43
|
+
raise
|
|
44
|
+
except _cli_error_types() as e:
|
|
45
|
+
message = str(e)
|
|
46
|
+
if isinstance(e, KeyError):
|
|
47
|
+
message = f"Missing required key or environment variable: {message}"
|
|
48
|
+
console.print(f"[red]Error: {message}[/]")
|
|
49
|
+
raise typer.Exit(1) from e
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_connection(target: str | None, config_path: Path | None = None):
|
|
55
|
+
"""Return a (conn, config) tuple for the given target."""
|
|
56
|
+
from fabric_aiops.config import load_config
|
|
57
|
+
from fabric_aiops.connection import ConnectionManager
|
|
58
|
+
|
|
59
|
+
cfg = load_config(config_path)
|
|
60
|
+
mgr = ConnectionManager(cfg)
|
|
61
|
+
return mgr.connect(target), cfg
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def dry_run_print(*, operation: str, api_call: str, parameters: dict | None = None) -> None:
|
|
65
|
+
"""Print a dry-run preview of the API call that would be made."""
|
|
66
|
+
console.print("\n[bold magenta][DRY-RUN] No changes will be made.[/]")
|
|
67
|
+
console.print(f"[magenta] Operation: {operation}[/]")
|
|
68
|
+
console.print(f"[magenta] API Call: {api_call}[/]")
|
|
69
|
+
for k, v in (parameters or {}).items():
|
|
70
|
+
console.print(f"[magenta] Param: {k} = {v}[/]")
|
|
71
|
+
console.print("[magenta] Run without --dry-run to execute.[/]\n")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def double_confirm(action: str, resource: str) -> None:
|
|
75
|
+
"""Require two confirmations for a destructive operation."""
|
|
76
|
+
console.print(f"[bold yellow]⚠️ About to: {action} '{resource}'[/]")
|
|
77
|
+
typer.confirm(f"Confirm 1/2: {action} '{resource}'?", abort=True)
|
|
78
|
+
typer.confirm(
|
|
79
|
+
f"Confirm 2/2: really {action} '{resource}'? This may be irreversible.",
|
|
80
|
+
abort=True,
|
|
81
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
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 fabric_aiops.cli._common import cli_errors
|
|
8
|
+
from fabric_aiops.cli.client import client_app
|
|
9
|
+
from fabric_aiops.cli.device import device_app
|
|
10
|
+
from fabric_aiops.cli.doctor import doctor_cmd
|
|
11
|
+
from fabric_aiops.cli.health import health_app
|
|
12
|
+
from fabric_aiops.cli.init import init_cmd
|
|
13
|
+
from fabric_aiops.cli.network import network_app
|
|
14
|
+
from fabric_aiops.cli.org import org_app
|
|
15
|
+
from fabric_aiops.cli.overview import overview_cmd
|
|
16
|
+
from fabric_aiops.cli.remediate import remediate_app
|
|
17
|
+
from fabric_aiops.cli.secret import secret_app
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(
|
|
20
|
+
name="fabric-aiops",
|
|
21
|
+
help="Governed AI-ops for Cisco Meraki network fabrics (organizations / networks / devices).",
|
|
22
|
+
no_args_is_help=True,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app.add_typer(org_app, name="org")
|
|
26
|
+
app.add_typer(network_app, name="network")
|
|
27
|
+
app.add_typer(device_app, name="device")
|
|
28
|
+
app.add_typer(client_app, name="client")
|
|
29
|
+
app.add_typer(health_app, name="health")
|
|
30
|
+
app.add_typer(remediate_app, name="remediate")
|
|
31
|
+
app.add_typer(secret_app, name="secret")
|
|
32
|
+
app.command("init")(init_cmd)
|
|
33
|
+
app.command("overview")(overview_cmd)
|
|
34
|
+
app.command("doctor")(doctor_cmd)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("mcp")
|
|
38
|
+
@cli_errors
|
|
39
|
+
def mcp_cmd() -> None:
|
|
40
|
+
"""Start the MCP server (stdio transport).
|
|
41
|
+
|
|
42
|
+
Single-command entry point for MCP clients (does not go through uvx/PyPI
|
|
43
|
+
resolution at launch):
|
|
44
|
+
fabric-aiops mcp
|
|
45
|
+
"""
|
|
46
|
+
import sys
|
|
47
|
+
|
|
48
|
+
if sys.version_info < (3, 11):
|
|
49
|
+
typer.echo(
|
|
50
|
+
f"ERROR: fabric-aiops requires Python >= 3.11 "
|
|
51
|
+
f"(got {sys.version_info.major}.{sys.version_info.minor}).\n"
|
|
52
|
+
f"Fix: uv python install 3.12 && "
|
|
53
|
+
f"uv tool install --python 3.12 --force fabric-aiops",
|
|
54
|
+
err=True,
|
|
55
|
+
)
|
|
56
|
+
raise typer.Exit(2)
|
|
57
|
+
|
|
58
|
+
from mcp_server.server import main as _mcp_main
|
|
59
|
+
|
|
60
|
+
_mcp_main()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
app()
|