meraki-mcp
by reshif
README.md
# meraki-mcp
A **read-only, production-shaped** Model Context Protocol server for the Cisco
Meraki Dashboard, built for infra/delivery teams (MR / MS / MX). It exposes a
small, high-signal set of tools an LLM can select reliably — the tool design
rationale lives in [`../docs/`](../docs/README.md); the code architecture is in
[`ARCHITECTURE.md`](ARCHITECTURE.md).
Built on the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) (`FastMCP`)
and the official async [`meraki`](https://github.com/meraki/dashboard-api-python) library.
## What makes it enterprise-shaped
- **One response contract.** Every tool returns `ToolResult{summary, data, meta, error}`. Nobody hand-rolls output; tools project Meraki payloads to declared fields (no raw blobs) and cap arrays. `meta` carries `scope`, `count`, `truncated`, `elapsed_ms`, `partial`/`warnings` (a partially-failed call *declares* what's missing), and `note`.
- **Response-size budget.** The registrar runs `enforce_budget` on every result: if it serializes past `MERAKI_MCP_MAX_RESPONSE_CHARS` (~15k tokens) the largest arrays are shrunk to fit and the result is flagged (`meta.truncated` + `meta.note`) — so a response can never overflow the Copilot/agent context.
- **One lifecycle owner.** `register_tool` wraps every tool with timeout, exception→`ToolResult` normalization, `elapsed_ms`, and structured logging. A raw exception never crosses the MCP boundary.
- **One SDK seam.** All Meraki calls flow through `MerakiGateway` (concurrency limit, per-call timeout, error normalization, logging) — applied once, not per tool.
- **Names in, IDs never invented.** Tools accept "Rome DC"; the cached `Resolver` turns it into an ID (ambiguity returns the candidate list for the model to pick).
- **Testable by construction.** Tools depend on the gateway *interface*, so `tests/fakes.FakeGateway` exercises real tool logic offline, no API key.
- **Read-only by default.** The sole non-GET tool (`live_diagnostics`) is behind a flag and annotated `readOnlyHint=false`.
## Layout
```
src/meraki_mcp/
server.py FastMCP app + lifespan (builds gateway, resolver, settings once)
config.py env Settings (validated; timeouts, concurrency, feature flags)
context.py AppContext handed to every tool (gateway, resolver, settings)
core/
gateway.py the ONE Meraki SDK seam: concurrency + timeout + error normalization
response.py ToolResult / Meta / Collector / Response (the output contract)
errors.py ResolutionError + GatewayError hierarchies (each carries an ErrorKind)
resolver.py name/serial/MAC -> IDs, TTL-cached
observability.py logging setup
pagination.py aggregate (all pages) vs firehose (capped)
formatting.py small trim helpers
tools/
_registry.py register_tool: the single lifecycle wrapper (ctx->app, timeout, errors, logging)
_shared.py one-import toolkit for tool modules
<domain>.py pure handlers + one-line register() each
optional/ reports.py (P2) + live.py (non-GET, flagged)
prompts/playbooks.py 13 packaged playbooks (register_prompts)
tests/
fakes.py FakeGateway/FakeSection — the DIP payoff
test_core.py test_gateway.py test_response.py test_selection.py
```
## Implementation status
**All 18 tools are implemented** — 13 core (`find`, `health_overview`,
`list_problems`, `site_status`, `device_status`, `client_status`,
`troubleshoot_client`, `triage_site`, `site_turnup_readiness`, `wireless_status`,
`switch_status`, `security_events`, `config_review`) plus 5 optional/off-by-default
(`change_audit`, `firmware_compliance`, `license_inventory_report`, `usage_summary`,
and `live_diagnostics` — the only non-GET tool, a create→poll job runner). Also
**3 resources** (`meraki://organizations`, `meraki://networks`, `meraki://devices`)
and **13 prompts**. Multi-org is supported: `MERAKI_ORG_ID` is an optional default,
every tool takes an optional `org`, and `find()` lists orgs/networks for discovery.
> Field-shape parsing is based on the Meraki OpenAPI spec and unit-tested with a
> `FakeGateway`, but **not yet run against a live org** — do one smoke test with a
> read-only key before production (see REVIEW.md B2).
## Run
```bash
pip install -e ".[dev]"
cp .env.example .env # set a READ-ONLY MERAKI_DASHBOARD_API_KEY (+ MERAKI_ORG_ID for single-org)
meraki-mcp # transport from .env (streamable-http by default)
```
## How to test it
1. **Unit tests (offline, no key):** `pytest -q` — 36 tests (gateway, response/budget, resolver, all tools via FakeGateway). Lint: `ruff check src tests scripts`.
2. **Live smoke (needs a read-only key in `.env`):** `python scripts/smoke_test.py` — runs `find`/`health_overview`/`list_problems` against your real org and prints each summary + any `meta.warnings` (field-shape/licensing issues). Add a site name to also exercise `site_status`/`wireless_status`: `python scripts/smoke_test.py "Rome DC"`.
3. **In VS Code / Copilot (stdio):** `.vscode/mcp.json` (in the workspace root) launches this server for VS Code's MCP client — open it and start the `meraki` server.
4. **Over HTTP (Copilot Studio / MCP Inspector):**
```bash
MERAKI_MCP_TRANSPORT=streamable-http meraki-mcp # serves http://127.0.0.1:8000/mcp
```
or containerised: `docker build -t meraki-mcp . && docker run --rm -p 8000:8000 -e MERAKI_DASHBOARD_API_KEY=... -e MERAKI_ORG_ID=... meraki-mcp`. Expose via a dev tunnel and register the `https://…/mcp` URL in Copilot Studio (**No authentication** for now — see `CONNECT-COPILOT-STUDIO.md`).
> **Never commit a real key.** `.env` is git-ignored; `.env.example` holds a placeholder only.
# meraki-mcp
# meraki-mcp
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues