Skip to main content
Glama
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