ISE Read-Only
A read-only Model Context Protocol server for Cisco Identity Services Engine
(ISE) 3.1 and newer, built on the ISE OpenAPI. Every query is an HTTP
GET, no writes possible, by construction.
What This Server Does
ISE Read-Only exposes a curated set of GET-only
tools that wrap the Cisco ISE 3.1+ OpenAPI. Inspect policy sets, authorization
rules, TrustSec mappings, deployment topology, certificates, repositories, and
patch state, without any path that could mutate ISE configuration.
The server is deliberately scoped to the OpenAPI surface. ERS endpoints
(/ers/config/…) are excluded by design, those would be served by
a separate MCP server if needed. See §5 below for the
full list of ERS-only domains this server intentionally does not cover.
Ships as a Docker or Podman container, wired in through the Docker MCP gateway or launched directly by your MCP client over stdio.
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 trusted certificates that expire in the next 90 days."
- "Show me the network access policy sets in evaluation order."
- "What authorization rules reference TrustSec security group tags?"
- "Are all ISE nodes running their assigned personas, or is anything degraded?"
- "What patches are installed across the deployment, is anything out of sync?"
- "Show me the configured file repositories and which protocols they use."
38 Read-Only Tools Across 6 Domains
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. Both keep your ISE
credentials out of the client's config file. Expand your runtime below.
Docker Docker Desktop · MCP Gateway ›
Clone the repository. The Docker backend lives in docker/.
$ git clone https://github.com/rosarion97/ise-readonly-mcp-server-public.git $ cd ise-readonly-mcp-server-public
Build from the repo root with docker/ as the context. The tag must match the image: field in custom-catalog.yaml.
$ docker build -t ise-readonly-mcp-server:latest docker/
The Docker MCP gateway injects these as env vars at launch, they never touch the client config.
$ docker mcp secret set ISE_HOST="ise-ppan.corp.local" $ docker mcp secret set ISE_USERNAME="apiuser" $ docker mcp secret set ISE_PASSWORD="<password>" $ docker mcp secret set ISE_VERIFY_SSL="yes"
Optional: ISE_MAX_RESPONSE_BYTES (default 120000) can be set the same way.
$ mkdir -p ~/.docker/mcp/catalogs $ cp docker/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:
ise-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 ISE image on demand, reads your catalog and registry, and resolves your ISE_* 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 ISE_* 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 ISE_PASSWORD, 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 # ise-readonly → enabled $ docker mcp tools list # lists the get_* tools
.env for smoke-tests only. A
docker run --env-file invocation works but loses the managed
gateway integration and leaves the value in a plaintext file. For an
env-file-first workflow, use the rootless Podman backend below, it's designed
around it.
Podman Rootless · stdio ›
Clone the repository. The Podman backend lives in podman/.
$ git clone https://github.com/rosarion97/ise-readonly-mcp-server-public.git $ cd ise-readonly-mcp-server-public
On macOS / Windows, start a Podman machine first (podman machine init && podman machine start); Linux runs natively.
$ podman build -t ise-readonly-mcp:latest podman/
Two ways to hand the container its credentials. Option A keeps your key in Podman's secret store and is recommended; Option B writes a plaintext .env for quick local testing. Either way, never inline -e ISE_PASSWORD=… in the client config.
$ printf '%s' 'your-ise-password' | podman secret create ise_password - $ printf '%s' 'apiuser' | podman secret create ise_username - $ printf '%s' 'ise-ppan.corp.local' | podman secret create ise_host -
At minimum keep ISE_PASSWORD in a secret; non-sensitive values like ISE_VERIFY_SSL can stay as plain -e flags.
chmod 600 podman/.env, never commit it (it's
already gitignored).
$ cp podman/.env.example podman/.env $ chmod 600 podman/.env # then edit: ISE_HOST / ISE_USERNAME / ISE_PASSWORD
Add the entry matching your Step 3 choice. Option A injects each secret with type=env,target=NAME, the values stay in Podman. Option B reads them from your .env.
{
"mcpServers": {
"ise-readonly": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--secret", "ise_host,type=env,target=ISE_HOST",
"--secret", "ise_username,type=env,target=ISE_USERNAME",
"--secret", "ise_password,type=env,target=ISE_PASSWORD",
"-e", "ISE_VERIFY_SSL=yes",
"ise-readonly-mcp:latest"
]
}
}
}
{
"mcpServers": {
"ise-readonly": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/podman/.env",
"ise-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 ISE tools appear there too. Only the file's
name and location change; the server entry is identical.
Prefer the CLI? Claude Code takes the same invocation:
$ claude mcp add ise-readonly \
podman run --rm -i \
--secret ise_host,type=env,target=ISE_HOST \
--secret ise_username,type=env,target=ISE_USERNAME \
--secret ise_password,type=env,target=ISE_PASSWORD \
-e ISE_VERIFY_SSL=yes \
ise-readonly-mcp:latest
Run the same command your client will run, it starts and waits on stdin for JSON-RPC. Press Ctrl+C to exit.
$ podman run --rm -i \
--secret ise_host,type=env,target=ISE_HOST \
--secret ise_username,type=env,target=ISE_USERNAME \
--secret ise_password,type=env,target=ISE_PASSWORD \
-e ISE_VERIFY_SSL=yes \
ise-readonly-mcp:latest
The -i flag is required, MCP stdio needs stdin attached, and any --env-file path must be absolute.
If your ISE PAN uses a self-signed certificate (common in lab and many production deployments), set ISE_VERIFY_SSL=no to suppress urllib3 warnings. For production, install the ISE CA into your container trust store and keep ISE_VERIFY_SSL=yes.
Before You Start
- Docker Desktop with the MCP Toolkit feature, or Podman on
$PATH. Pick one; the step-by-step for each is above. - ISE 3.1+ Primary Administration Node reachable from the host running Podman.
- An ISE admin account (or dedicated API user) with Super Admin, or ERS Admin + OpenAPI roles.
- Open API enabled on the ISE node: Administration → System → Settings → API Settings → Open API. Without this, every tool call returns
403. - An MCP-capable client, Claude Desktop, Claude Code, or any client speaking MCP over stdio.
What This Server Doesn't Cover
The following ISE domains are intentionally not exposed, they only exist under the ERS API (/ers/config/…), which this server excludes by design. A separate ERS-focused MCP server would be needed.
- Network devices & groups,
/ers/config/networkdevice/ - Internal users,
/ers/config/internaluser/ - Endpoint identities,
/ers/config/endpoint/ - Identity groups,
/ers/config/identitygroup/ - Security groups (SGTs),
/ers/config/sgt/ - SGACLs,
/ers/config/sgacl/ - TrustSec egress matrix,
/ers/config/egressmatrixcell/
Read-Only by Construction
GET against the ISE OpenAPI. The server
contains no code path that issues a write verb, so it is incapable of modifying
any ISE configuration regardless of what an LLM or user asks for. Not affiliated
with or endorsed by Cisco.