Meraki Read-Only
A production-grade, read-only Model Context Protocol server for the Cisco Meraki Dashboard API v1. Launches directly from your MCP client over stdio, in a Docker or Podman container.
What This Server Does
Meraki Read-Only exposes a curated set of GET-only
tools that wrap the Cisco Meraki Dashboard API v1. It's designed to be safe in
production environments: no tool will ever issue a POST,
PUT, PATCH, or DELETE against the Meraki
cloud.
The server is pinned to a single organization at startup via the
MERAKI_ORG_ID environment variable. Org-scoped tools take no
org_id parameter at all, there is no surface for the model to
target an unrelated org the API key can also see.
Ships as both a Docker and a Podman container
from the same server.py, pick whichever runtime you already run.
What to Ask, in Plain English
A handful of real questions you can put to your AI client once this server is wired in. The model figures out which tools to call, you describe the outcome.
- "List Meraki networks that have any APs reporting offline."
- "Which clients failed to associate to wireless today, and where?"
- "Show channel utilization across all APs in network L_123456, flag any above 70%."
- "What IDS events fired on the security appliance in the last 24 hours?"
- "Pull the AutoVPN reachability matrix and call out any tunnels that are down."
- "What configuration changes happened in the last 24 hours, and who made them?"
63 Read-Only Tools Across the Meraki Stack
Build & Configure
Two supported runtimes, pick the one you already run. The docker/ and
podman/ backends ship an identical server.py; only the
container file and the way the client is wired differ. This server reads its two
credentials as plain environment variables only, there is no
file-mounted secret mode. Expand your runtime below.
Docker Docker Desktop · MCP Gateway ›
Clone the repository. The Docker backend lives in docker/ and ships a custom-catalog.yaml alongside the Dockerfile.
$ git clone https://github.com/rosarion97/meraki-readonly-mcp-server-public.git $ cd meraki-readonly-mcp-server-public/docker
The image tag must match the image: field in custom-catalog.yaml (meraki-readonly-mcp:latest).
$ docker build -t meraki-readonly-mcp:latest .
You need two values, both validated at startup: MERAKI_API_KEY and MERAKI_ORG_ID. Grab the org ID with your key:
$ curl -H "Authorization: Bearer $MERAKI_API_KEY" \ https://api.meraki.com/api/v1/organizations
Option A, MCP Toolkit secret (recommended). Store each value in Docker Desktop's encrypted store; the gateway injects them as env vars at launch. With Option A, continue through Steps 4–7 below.
$ docker mcp secret set MERAKI_API_KEY $ docker mcp secret set MERAKI_ORG_ID $ docker mcp secret ls # values are masked
chmod 600 .env, never commit it
(it's gitignored and excluded from the image). This path skips the gateway:
skip Steps 4–6, point your client straight at docker run --env-file
(below), then jump to Step 7.
$ cp .env.example .env $ chmod 600 .env # then set MERAKI_API_KEY and MERAKI_ORG_ID
{
"mcpServers": {
"meraki": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/docker/.env",
"meraki-readonly-mcp:latest"
]
}
}
}
Steps 4–6 are the Option A (secret store) path. Used Option B? Skip to Step 7.
$ mkdir -p ~/.docker/mcp/catalogs $ cp custom-catalog.yaml ~/.docker/mcp/catalogs/custom.yaml
Add the entry under the single top-level registry: key in ~/.docker/mcp/registry.yaml. Don't overwrite the file if it already exists.
registry:
meraki-readonly:
catalog: custom
enabled: true
Add the gateway block to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). The gateway spawns the Meraki image on demand, reads your catalog and registry, and resolves your MERAKI_* secrets at request time. Replace <your-username> with your macOS username (run whoami to check):
{
"mcpServers": {
"mcp-toolkit-gateway": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", "/Users/<your-username>/.docker/mcp:/mcp",
"-v", "/Users/<your-username>/Library/Caches/docker-secrets-engine/engine.sock:/root/.cache/docker-secrets-engine/engine.sock",
"docker/mcp-gateway:latest",
"--catalog=/mcp/catalogs/custom.yaml",
"--registry=/mcp/registry.yaml",
"--transport=stdio"
]
}
}
}
All three bind-mounts are required: the Docker socket lets the gateway spawn the server container; ~/.docker/mcp is where it reads your catalog and registry; the docker-secrets-engine socket is the resolver Docker Desktop exposes for the secret store, without it your MERAKI_* URLs resolve to empty strings and the server never starts. On Linux Docker Desktop the path is ~/.docker/desktop/secrets-engine/engine.sock; find it with find ~ -name engine.sock.
claude mcp add -s user mcp-toolkit-gateway -- docker run …; Codex reads the same shape from ~/.codex/config.toml as [mcp_servers.mcp-toolkit-gateway]. Any MCP-capable client, Google's Gemini included, reads an mcpServers block from its own settings file; only the location changes, the entry is identical.
Then quit and reopen the client. claude_desktop_config.json never contains the API key, the gateway resolves it from Docker's secret store at request time. As a shortcut, docker mcp client connect claude-desktop (or MCP Toolkit → Clients in Docker Desktop) writes a similar block automatically; the explicit JSON above survives Docker Desktop updates that may rewrite the auto-managed entry.
$ docker mcp server list # meraki-readonly → enabled $ docker mcp tools list
Or run the image directly, it starts and waits silently on stdin (correct for an MCP stdio server). Press Ctrl+C to exit; a missing value fails fast on stderr.
$ docker run --rm -i --env-file .env meraki-readonly-mcp:latest
Podman Rootless · stdio ›
Clone the repository. The Podman backend lives in podman/.
$ git clone https://github.com/rosarion97/meraki-readonly-mcp-server-public.git $ cd meraki-readonly-mcp-server-public/podman
On macOS / Windows, start a Podman machine first (podman machine init && podman machine start); Linux runs natively.
$ podman build -t meraki-readonly-mcp:latest .
Two required values: MERAKI_API_KEY and MERAKI_ORG_ID (one container serves one org). Option 1 keeps them in Podman's encrypted secret store and is recommended; Option 2 writes a plaintext .env.
$ printf '%s' 'YOUR_MERAKI_API_KEY' | podman secret create meraki_api_key - $ printf '%s' 'YOUR_ORG_ID' | podman secret create meraki_org_id - $ podman secret ls # confirm both (values not shown)
chmod 600 .env, never commit it (it's gitignored).
$ cp .env.example .env $ chmod 600 .env # then set MERAKI_API_KEY and MERAKI_ORG_ID
Add the entry matching your Step 3 choice. Option 1 injects each secret with type=env,target=NAME; Option 2 reads them from your .env.
{
"mcpServers": {
"meraki": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--secret", "meraki_api_key,type=env,target=MERAKI_API_KEY",
"--secret", "meraki_org_id,type=env,target=MERAKI_ORG_ID",
"meraki-readonly-mcp:latest"
]
}
}
}
{
"mcpServers": {
"meraki": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/podman/.env",
"meraki-readonly-mcp:latest"
]
}
}
}
mcpServers block from its own settings.json,
Google's Gemini included. Drop this same entry into that client's
settings.json and the Meraki tools appear there too. Only the file's
name and location change; the server entry is identical.
Claude Code uses the same shape in .claude/settings.json (project) or ~/.claude/settings.json (global). -i is required; paths must be absolute.
Run the image manually, it starts and waits on stdin. Press Ctrl+C to exit.
$ podman run --rm -i \
--secret meraki_api_key,type=env,target=MERAKI_API_KEY \
--secret meraki_org_id,type=env,target=MERAKI_ORG_ID \
meraki-readonly-mcp:latest
One container serves one organization, run a second container with its own credentials to serve another org. --rm cleans up the container after each session.
Three Variables
- MERAKI_API_KEY, required. Bearer token used for every API call. Generate it from the Meraki Dashboard under My Profile → API access.
- MERAKI_ORG_ID, required. Pins this instance to a single organization. Run a second container with a different
.envto serve another org. - MERAKI_MAX_RESPONSE_BYTES, optional. Caps the JSON size of any one tool response. Default
120000(~30k tokens). Raise for big-window models, lower for small ones.
Before You Start
- Docker Engine 24+ / Docker Desktop 4.27+ (MCP Toolkit for the secret path), or Podman 4.x+ on
$PATH. Pick one; the step-by-step for each is above. - A Cisco Meraki Dashboard API key, generated from My Profile → API access → Generate new API key.
- A read-only admin bound to that API key. The server enforces read-only at the protocol layer, but a read-only admin role is defense-in-depth.
- An MCP-capable client, Claude Desktop, Claude Code, Gemini CLI, or any client speaking MCP over stdio.
Read-Only by Construction
GET against
api.meraki.com/api/v1. The server contains no code path that issues
a write verb, so it is incapable of modifying any Meraki configuration regardless
of what an LLM or user asks for. Pull requests that add additional read-only
endpoints are welcome; any change that introduces a write-capable verb against the
Dashboard API will be rejected.