Pabawi includes an embedded Model Context Protocol (MCP) server that exposes read-only (by default) infrastructure tools over Streamable HTTP. This lets AI assistants like Claude Desktop, Claude Code, Kiro, Cursor, Gemini CLI, OpenAI Codex CLI, and other MCP-compatible clients query your infrastructure data directly.
Add to backend/.env:
MCP_ENABLED=trueRestart the backend. You should see in the logs:
MCP server enabled, initializing...
Provisioning MCP service user
MCP server initialized, /mcp endpoint registered
The MCP endpoint is available at POST http://<host>:<port>/mcp.
When enabled, Pabawi:
- Creates a dedicated
mcp-servicesystem user with a random password (cannot be used for login) - Creates an
MCP Servicebuilt-in role with allreadpermissions - Assigns the role to the service user
- Starts the MCP server and registers the
/mcpendpoint
All MCP endpoints require JWT authentication — the same Authorization: Bearer <token> header used by the REST API. Once authenticated, tool calls go through the same RBAC permission system as the REST API. The MCP server calls services directly (no HTTP round-trips) since it runs inside the backend process.
The MCP endpoint requires authentication. Two methods are supported:
Set MCP_AUTH_TOKEN in backend/.env:
MCP_AUTH_TOKEN=your-secure-random-token-hereGenerate a strong token:
openssl rand -hex 32Use this token as the Authorization: Bearer <token> header in your MCP client configuration. It does not expire and is scoped exclusively to the /mcp endpoint — it cannot be used to access other API routes.
Alternatively, any valid Pabawi JWT (obtained via POST /api/auth/login) is accepted on the MCP endpoint. This is useful for browser-based or short-lived integrations but requires periodic token renewal.
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"pabawi": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer <MCP_AUTH_TOKEN>"
}
}
}
}Add to .kiro/settings/mcp.json in your workspace:
{
"mcpServers": {
"pabawi": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer <MCP_AUTH_TOKEN>"
}
}
}
}Add to your Cursor MCP settings:
{
"mcpServers": {
"pabawi": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer <MCP_AUTH_TOKEN>"
}
}
}
}Run from your project directory:
claude mcp add --transport http pabawi http://localhost:3000/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"This stores the server in your local Claude Code config. To verify it's connected, run /mcp inside Claude Code.
Add to ~/.gemini/settings.json:
{
"mcpServers": {
"pabawi": {
"httpUrl": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer <MCP_AUTH_TOKEN>"
}
}
}
}Restart Gemini CLI and run /mcp to verify the connection.
Add to ~/.codex/config.toml:
[mcp_servers.pabawi]
url = "http://localhost:3000/mcp"
[mcp_servers.pabawi.headers]
Authorization = "Bearer <MCP_AUTH_TOKEN>"Run codex mcp list to verify the server is configured.
The endpoint accepts standard MCP Streamable HTTP requests at POST /mcp. All requests require a bearer token in the Authorization header — either the static MCP_AUTH_TOKEN or a valid JWT from POST /api/auth/login.
| Tool | Description | Parameters |
|---|---|---|
inventory_list |
List nodes from all active integrations | search? — filter by name or certname |
facts_get |
Get system facts for a node | certname — node certname |
facts_bulk |
Get specific facts across all nodes in one query | fact_names — array of top-level fact names, include_all? |
reports_query |
Query Puppet run reports | certname?, limit?, status? |
catalogs_get |
Get compiled Puppet catalog for a node | certname — node certname |
hiera_lookup |
Look up a Hiera key value for a node | key, node? (certname for hierarchy resolution), environment? (default: production) |
executions_list |
List execution history | limit?, status?, tool? |
integrations_list |
List integrations and health status | (none) |
journal_query |
Search journal entries | nodeId?, eventType?, limit? |
monitoring_services_get |
Get live Checkmk service status for a node | nodeId — node hostname |
monitoring_events_get |
Get Checkmk state-change events for a node | nodeId — node hostname, limit? (1-1000, default 200) |
All tools are read-only. Each tool checks RBAC permissions before executing.
Once connected, you can ask your AI assistant things like:
- "List all nodes in my infrastructure"
- "Search for nodes with 'web' in the name"
- "Which integrations are healthy right now?"
- "Show me all nodes managed by Puppet"
- "What are the facts for node web-01.example.com?"
- "What OS is db-primary running?"
- "Show me the Puppet catalog for web-01"
- "Look up the ntp::servers Hiera key in production"
- "Show me the last 10 Puppet reports"
- "Are there any failed Puppet runs in the last reports?"
- "What executions have been run recently?"
- "Show me journal entries for node web-01"
- "Check if all integrations are connected and healthy"
- "Show me failed Puppet reports for the last 5 runs"
- "What changed on node db-primary according to the journal?"
- "Look up the Hiera value for profile::base::packages in staging"
- "List all nodes, then get the facts for the first one"
- "Check which integrations are unhealthy and show me recent journal entries"
- "Find all web servers and show me their latest Puppet reports"
The MCP Service role is created with all read permissions by default. To customize what the MCP server can access:
- Go to the Role Management page in the Pabawi UI
- Find the
MCP Servicerole - Add or remove permissions as needed
The mcp-service user and MCP Service role are visible in the Users and Roles management pages like any other user/role.
Returns aggregated node inventory from all active integrations (Bolt, PuppetDB, Ansible, SSH, Proxmox, AWS, Azure).
search: "web" → returns only nodes with "web" in name or certname
search: omitted → returns all nodes
Returns facts gathered from all sources for a specific node. Facts are keyed by source (bolt, puppetdb, ansible, etc.).
certname: "web-01.example.com"
Returns specific facts across all nodes in a single PuppetDB query. Much more efficient than calling facts_get per node when you need the same facts for many nodes.
fact_names: ["os", "networking", "memory"] → get OS, networking, and memory facts for all nodes
include_all: false → filter to essential sub-keys only
Returns Puppet run reports. Requires PuppetDB integration.
certname: "web-01" → reports for a specific node
limit: 5 → last 5 reports
status: "changed" → only reports with changes
Returns the compiled Puppet catalog for a node. Requires PuppetDB integration.
certname: "web-01.example.com"
Resolves a Hiera key for a node. When a node is provided, the service fetches the node's facts from PuppetDB and uses them to resolve hierarchy paths (e.g. nodes/%{facts.networking.fqdn}.yaml, os/%{facts.os.family}.yaml). Without a node, only static hierarchy levels like common.yaml are checked.
key: "ntp::servers"
node: "web01.example.com" → resolve using this node's facts (optional)
environment: "staging" → defaults to "production" if omitted
Returns execution history (commands, tasks, Puppet runs).
limit: 10 → last 10 executions
status: "success" → only successful executions
tool: "bolt" → only Bolt executions
Returns all configured integrations with their current health status. No parameters.
Returns journal entries (events, actions, notes).
nodeId: "web-01" → entries for a specific node
eventType: "puppet_run" → only Puppet run events
limit: 20 → last 20 entries
Returns live Checkmk service monitoring status for a node. Requires the Checkmk integration to be configured. Returns an MCP error if the plugin is disabled or the node is unknown.
nodeId: "web-01" → service status for this node
Each service includes: description, state (OK/WARN/CRIT/UNKNOWN), plugin output, and last check timestamp.
Returns Checkmk state-change events for a node. Events come from Livestatus when configured, otherwise derived from the REST API. Returns an MCP error if the plugin is disabled or the node is unknown.
nodeId: "web-01" → events for this node
limit: 50 → last 50 events (default: 200, max: 1000)
Each event includes: timestamp, service description, state transition, and output text.
The MCP endpoint (/mcp) is served on the same port as the rest of the API (default 3000). No additional port mapping is needed — if the Pabawi UI is reachable, so is MCP.
To enable MCP in Docker, add to your .env file (or the env_file referenced by docker-compose):
MCP_ENABLED=true
MCP_AUTH_TOKEN=<generate-with-openssl-rand-hex-32>The MCP client URL from outside the container is:
http://<docker-host>:<mapped-port>/mcp
For example, with the default docker-compose.yml mapping 3000:3000:
http://localhost:3000/mcp
The MCP Streamable HTTP transport uses long-lived SSE connections on GET /mcp. If you place a reverse proxy (nginx, Traefik, HAProxy) or Kubernetes ingress in front of Pabawi:
- Disable response buffering for the
/mcppath — SSE requires unbuffered streaming - Increase idle/read timeouts to at least 300s (the default 60s in nginx will drop MCP sessions)
- Disable request body size limits or set them generously for
/mcpPOST (MCP messages can be large) - Preserve headers — the
mcp-session-idheader must pass through unmodified
Example nginx location block:
location /mcp {
proxy_pass http://pabawi:3000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}Example Kubernetes ingress annotation (nginx ingress controller):
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"When running Pabawi in a pod:
- The
/mcpendpoint is part of the same container — expose it via the same Service/Ingress as the UI - Set
MCP_ENABLED=trueandMCP_AUTH_TOKENin your ConfigMap/Secret - If using horizontal pod autoscaling, note that MCP sessions are in-memory and not shared across replicas — a client must hit the same pod for the duration of a session (use sticky sessions or session affinity)
- Verify
MCP_ENABLED=trueis set inbackend/.env - Restart the backend after changing the setting
- Check logs for "MCP server initialized" message
- If using
MCP_AUTH_TOKEN: verify the token in your client config matches the value inbackend/.envexactly - If using JWT: tokens expire — re-authenticate via
POST /api/auth/loginto get a fresh token - Ensure the
Authorization: Bearer <token>header is being sent - Verify the user account is not locked or disabled (JWT path only)
- The
MCP Servicerole may be missing the required permission - Go to Role Management → MCP Service → add the missing permission
- Permission format:
<resource>/<action>(e.g.,puppetdb/read)
- The
mcp-serviceuser is only created whenMCP_ENABLED=true - Check logs for "Provisioning MCP service user" or errors during provisioning
- Tools like
reports_query,catalogs_get, andhiera_lookuprequire their respective integrations to be enabled - If PuppetDB is not configured, PuppetDB-dependent tools will return "service not available"