Skip to main content

Wombat MCP Bridge

Wombat logo

A secure Model Context Protocol server that exposes Wombat (Wombat backend) REST endpoints as MCP tools for AI assistants.

Architecture

sequenceDiagram
    actor User
    participant AI_Client as AI Client (MCP Host)
    participant MCP as wombat-mcp (this bridge)
    participant IdP as Keycloak / IdP
    participant API as wombat-server

    User->>IdP: Authenticate (OIDC/SAML)
    IdP-->>User: IdP tokens
    User->>API: Exchange IdP tokens for Wombat JWT
    API-->>User: Wombat access token

    User->>AI_Client: Start AI session
    AI_Client->>MCP: SSE /sse with Authorization: Bearer <Wombat token>
    MCP->>API: Introspect token (GET /auth/me) or verify HS256 locally
    API-->>MCP: User profile {id, username, role, is_active}

    alt Token invalid
        MCP-->>AI_Client: 401 Unauthorized
    else Token valid
        AI_Client->>MCP: tools/list
        MCP-->>AI_Client: Tools filtered by user's role

        AI_Client->>MCP: tools/call (e.g. list runs)
        MCP->>MCP: Validate arguments against OpenAPI schema
        MCP->>API: Proxy request with same Bearer token
        API-->>MCP: Backend response (row-level auth applied)
        MCP-->>AI_Client: MCP TextContent(result)
    end

Security model

  • IdP-agnostic: The bridge does not talk to Keycloak/Entra/Okta directly. It trusts tokens issued by the upstream wombat-server, which handles the actual OIDC/SAML flows.
  • OIDC pass-through: The AI client inherits the user's Wombat SSO context by presenting the same Bearer token.
  • Least privilege: tools/list is filtered by the authenticated user's role. Unauthorized tools are invisible.
  • Context propagation: Every backend call forwards the original Authorization: Bearer header so Wombat can apply endpoint- and row-level authorization. When a caller sends X-Mandant-Context on the SSE connection, it is forwarded as well so holding/group admins can narrow into one mandate.
  • Strict validation: Tool arguments are validated against Pydantic models generated from the Wombat OpenAPI spec.

Quick start

Python (local)

# 1. Install dependencies
pip install -e ".[dev]"

# 2. Configure
cp .env.example .env
# Edit .env to point at your Wombat backend and choose token validation mode.

# 3. Start the bridge
python -m wombat_mcp.main

The SSE endpoint is available at http://localhost:3001/sse.

Docker Compose (local development)

For local development the bridge can also be built from its Dockerfile and started alongside the rest of the Wombat stack. From the repository root:

docker compose up -d wombat-mcp

The service builds from ./wombat-mcp, depends on wombat-server, and exposes port 3001. Override WOMBAT_TOKEN_MODE or WOMBAT_JWT_SECRET via .env if you are not using the default introspection mode.

Release artifact: wombat-mcp is released as a Python package on PyPI. The Dockerfile exists only for local convenience in docker-compose.yml; the release workflow does not publish a Docker image.

First-time setup

This guide covers installing the bridge, creating a Wombat token, and connecting it to Claude Desktop. For most local use the recommended transport is stdio; an HTTPS-based HTTP/SSE alternative is documented below.

1. Install the bridge

git clone https://github.com/wombat-gov/wombat-mcp.git
cd wombat-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

This creates three console scripts in .venv/bin:

  • wombat-mcp — runs the HTTP/SSE bridge.
  • wombat-mcp-stdio — runs the stdio bridge for Claude Desktop.
  • wombat-mcp-configure — interactive login that stores credentials in the user's config directory.

2. Start the upstream Wombat server

The bridge needs a running wombat-server on http://localhost:8000. Follow the setup in ../wombat-server/README.md.

For local development, seed a bootstrap admin in ../wombat-server/.env:

WOMBAT_ADMIN_USERNAME=admin
WOMBAT_ADMIN_PASSWORD=changeme123
WOMBAT_ADMIN_ROLE=admin

The server creates this user automatically on first startup.

Run the interactive helper:

wombat-mcp-configure

It prompts for the Wombat API URL, username, and password, logs in, and stores the access token, refresh token, and URL in the user's config directory. The file is created with restrictive permissions (0o600) where the OS supports it.

After wombat-mcp-configure succeeds, wombat-mcp-stdio can start without any environment variables. It reads the stored config by default and refreshes the access token automatically before it expires.

Access tokens expire after WOMBAT_JWT_EXPIRE_MINUTES (default 60). Keeping the refresh token in the stored config lets the stdio bridge refresh the access token automatically.

Alternative: manual token creation

If you prefer not to store credentials, or you are configuring a headless/CI environment, create a token pair manually and pass it through environment variables:

RESP=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"changeme123"}')

export ACCESS_TOKEN=$(echo "$RESP" | jq -r .access_token)
export REFRESH_TOKEN=$(echo "$RESP" | jq -r .refresh_token)

echo "Access:  $ACCESS_TOKEN"
echo "Refresh: $REFRESH_TOKEN"

If you don't have jq, inspect the raw JSON response and copy the two token values manually.

The best way to use the bridge locally with Claude Desktop is to run it as a stdio command. This avoids HTTPS certificates, open ports, and TLS termination entirely.

Because credentials are now stored by wombat-mcp-configure, the Claude Desktop config only needs the command path:

{
  "mcpServers": {
    "wombat": {
      "command": "/Users/lewandos/git/waf++/wafpass-mcp/.venv/bin/wombat-mcp-stdio"
    }
  }
}

Place the file in Claude Desktop's MCP config location and restart Claude Desktop:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

The exact path can be opened from Claude Desktop via Settings → Developer → Edit Config.

After restarting, open a new chat and look for the Wombat tools in the tool picker (or ask Claude to list them). You should see tools like list_runs_runs_get, get_run_runs__run_id__get, etc., filtered by the token's role.

Overriding the stored config with environment variables

Environment variables take precedence over the stored config. This is useful for testing, CI, or switching backends without re-running wombat-mcp-configure:

{
  "mcpServers": {
    "wombat": {
      "command": "/Users/lewandos/git/waf++/wafpass-mcp/.venv/bin/wombat-mcp-stdio",
      "env": {
        "WOMBAT_ACCESS_TOKEN": "<Wombat_TOKEN_HERE>",
        "WOMBAT_REFRESH_TOKEN": "<Wombat_REFRESH_TOKEN_HERE>",
        "WOMBAT_API_BASE_URL": "http://localhost:8000",
        "WOMBAT_TOKEN_MODE": "introspection",
        "WOMBAT_REFRESH_THRESHOLD_SECONDS": "300"
      }
    }
  }
}

5. Alternative: HTTPS setup with mkcert

If your MCP client requires HTTP/SSE over https:// instead of stdio, you can run the bridge behind a trusted local certificate generated with mkcert.

Install and trust the local CA

macOS:

brew install mkcert
mkcert -install

The -install step adds the local CA to the macOS system and login keychains; most browsers and HTTP clients trust it immediately.

Windows:

# Use Chocolatey or download the binary from GitHub
choco install mkcert
mkcert -install

You may need to restart browsers or add the generated CA certificate to the Windows certificate store manually. See the mkcert README for the latest Windows instructions.

Linux:

# Install from your distribution or download the binary
curl -JLO https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert-v1.4.4-linux-amd64
sudo mv mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert
mkcert -install

On Linux the CA is installed under $HOME/.local/share/mkcert; some browsers need it imported manually.

Generate a certificate for localhost

mkcert localhost 127.0.0.1 ::1
# Produces: localhost.pem (cert) and localhost-key.pem (key)

Run the bridge with TLS

uvicorn wombat_mcp.main:app \
  --host 0.0.0.0 \
  --port 3001 \
  --ssl-keyfile ./localhost-key.pem \
  --ssl-certfile ./localhost.pem

The SSE endpoint is now https://localhost:3001/sse. The certificate files are already ignored by .gitignore so they will not be committed.

Notes for stdio mode

  • In stdio mode, credentials are read from the stored config file by default. Environment variables override the stored values, so WOMBAT_ACCESS_TOKEN is only required when no config has been saved.
  • WOMBAT_REFRESH_TOKEN is optional but strongly recommended so the bridge can refresh the access token before it expires.
  • The bridge logs to stderr; stdout is reserved for the MCP protocol.
  • Token refresh only happens in stdio mode. HTTP/SSE clients must provide a fresh access token with every SSE connection.

Alternative: HTTPS setup for HTTP/SSE clients

If you prefer to keep using the HTTP/SSE transport (for remote AI hosts, browser-based clients, or because your MCP client does not support stdio), you need a TLS termination layer in front of the bridge because most HTTPS-only MCP clients refuse plain http:// URLs. Below are three working approaches for local development and testing.

Option A: ngrok (quickest public HTTPS URL)

ngrok gives you an HTTPS tunnel to localhost:3001 without touching certificates.

  1. Install ngrok and authenticate (ngrok authtoken <token>).
  2. Start the bridge normally:
    python -m wombat_mcp.main
    
  3. In another terminal expose it:
    ngrok http 3001
    
  4. Copy the generated https:// URL (e.g. https://xxxx.ngrok-free.app).
  5. Configure your MCP client to use that URL for the SSE endpoint, and keep sending Authorization: Bearer <Wombat token>:
    https://xxxx.ngrok-free.app/sse
    

The free ngrok tier gives a random URL on every restart. For a stable address use a paid/ngrok-static domain or switch to Cloudflare Tunnel.

Option B: mkcert + uvicorn with TLS (local trusted certificate)

mkcert creates a certificate that your OS and browsers trust locally. This is the cleanest setup for repeated local testing with Claude Desktop.

  1. Install mkcert and create a local CA:
    mkcert -install
    
  2. Generate a certificate for localhost (and any local DNS you use):
    mkcert localhost 127.0.0.1 ::1
    # Produces: localhost.pem (cert) and localhost-key.pem (key)
    
  3. Start uvicorn directly with the certificate files:
    uvicorn wombat_mcp.main:app \
      --host 0.0.0.0 \
      --port 3001 \
      --ssl-keyfile ./localhost-key.pem \
      --ssl-certfile ./localhost.pem
    
  4. The SSE endpoint is now available at:
    https://localhost:3001/sse
    
  5. Keep the key and certificate files out of git (they are already ignored by the default .gitignore patterns for *.pem).

The built-in python -m wombat_mcp.main entry point does not expose uvicorn's TLS flags. Use uvicorn directly, or set MCP_PORT / other settings via environment variables as usual.

Option C: Caddy reverse proxy (automatic local HTTPS)

Caddy can obtain and serve a local HTTPS certificate automatically.

  1. Create a Caddyfile in the repo root:
    localhost:3443 {
        reverse_proxy localhost:3001
    }
    
  2. Start the bridge on its default port:
    python -m wombat_mcp.main
    
  3. Run Caddy in another terminal:
    caddy run
    
  4. Connect your MCP client to:
    https://localhost:3443/sse
    

Caddy will handle TLS for the client and proxy plain HTTP to the bridge.

Updating the test scripts for HTTPS

The example scripts in scripts/ use http://localhost:3001. When testing an HTTPS endpoint, change the URLs in those scripts (or pass them as arguments) to the HTTPS address, e.g.:

SSE_URL = "https://localhost:3001/sse"
post_url = f"https://localhost:3001{endpoint}"

HTTPS/SSE client configuration example

Once you have an HTTPS endpoint, add it to an MCP host that supports HTTP/SSE (e.g. a remote Claude Code instance or another MCP client):

{
  "mcpServers": {
    "wombat": {
      "url": "https://localhost:3001/sse",
      "headers": {
        "Authorization": "Bearer <Wombat_TOKEN>"
      }
    }
  }
}

Replace <Wombat_TOKEN> with a real token obtained from your Wombat backend. Automatic refresh is only available in stdio mode; HTTP/SSE clients must provide a fresh token with every SSE connection.

Configuration

Settings can come from two places. wombat-mcp-configure writes them to the user's config directory; environment variables override the stored values.

Variable Default Description
WOMBAT_API_BASE_URL http://localhost:8000 Upstream Wombat API
WOMBAT_TOKEN_MODE introspection introspection (call /auth/me) or jwt_secret (local HS256)
WOMBAT_JWT_SECRET (empty) Required for jwt_secret mode; must match backend secret
WOMBAT_ACCESS_TOKEN (empty) Wombat token for stdio mode; optional if stored by wombat-mcp-configure
WOMBAT_REFRESH_TOKEN (empty) Opaque refresh token for automatic access-token refresh in stdio mode
WOMBAT_REFRESH_THRESHOLD_SECONDS 300 Refresh the access token if it expires within this many seconds
MCP_HOST 0.0.0.0 Bridge bind host (HTTP/SSE mode)
MCP_PORT 3001 Bridge bind port (HTTP/SSE mode)
LOG_LEVEL INFO Logging level

The stored config file location depends on the OS:

  • macOS: ~/Library/Application Support/wombat-mcp/settings.json
  • Linux: ~/.config/wombat-mcp/settings.json
  • Windows: %APPDATA%\wombat-mcp\settings.json

Token validation modes

  • introspection (recommended): The bridge calls GET /auth/me on Wombat for every new SSE connection. This is IdP-agnostic, works with any backend secret rotation, and lets Wombat revoke tokens instantly.
  • jwt_secret: The bridge verifies the HS256 signature locally. Faster but requires sharing the secret and does not detect token revocation.

Tool registration and role filtering

At startup the bridge fetches http://<WOMBAT_API_BASE_URL>/openapi.json and converts each safe operation into an MCP tool:

  • Tool names use the OpenAPI operationId when present (e.g. list_runs_runs_get, get_run_runs__run_id__get), otherwise a generated name like get_health.
  • Path parameters become required tool arguments.
  • Query parameters become optional tool arguments.
  • Request bodies become top-level tool arguments.
  • Operations in SKIP_OPERATIONS (login, OIDC callbacks, etc.) are never exposed.
  • ROLE_MAP assigns a minimum required role per endpoint. The list_tools handler removes tools the caller's role cannot execute.

Example tool call flow

Authenticated as an engineer:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

The response includes list_runs_runs_get, get_run_runs__run_id__get, get_runs_id_findings_get, etc., but not admin-only tools like get_sso_config_sso_config_get.

Verified against the live ../docker-compose.yml stack: the bridge loads 113 tools for admin, 102 tools for clevel, and intermediate counts for higher roles.

Calling list_runs_runs_get:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "list_runs_runs_get",
    "arguments": {"limit": 5}
  }
}

The bridge proxies this to GET /runs?limit=5 with the user's Bearer token. Wombat applies group-based row filtering and returns only the runs the user may see.

Both the SSE GET /sse and every POST /messages/ request must carry the same Authorization: Bearer <Wombat token> header.

Development

pytest
ruff check wombat_mcp tests scripts
mypy wombat_mcp tests scripts

These same checks run in GitHub Actions:

  • .github/workflows/ci.yml — runs on every pull request and push to main.
  • .github/workflows/release.yml — builds, runs lint/type/tests, publishes to PyPI, and creates a GitHub release on every push to main.

Test scripts

scripts/ contains standalone MCP-over-SSE clients for manual end-to-end checks:

# list all tools visible to the token's role
python scripts/mcp_list_tools.py <Wombat_TOKEN>

# call one tool and print the backend response
python scripts/mcp_call_tool_test.py <Wombat_TOKEN>

# minimal client that prints init + first 10 tools
python scripts/mcp_client_test.py <Wombat_TOKEN>

Deployment notes

  • Run behind a TLS-terminating reverse proxy in production.
  • Prefer WOMBAT_TOKEN_MODE=introspection so the bridge does not need to store the JWT secret.
  • Keep the bridge on a separate network path from the IdP; it only needs outbound access to Wombat.

Contributing and security

  • CONTRIBUTING.md — how to set up local development, run tests, and open pull requests.
  • docs/mcp-desktop-setup.md — step-by-step Claude Desktop installation and login guide.
  • TECH.md — architecture, request lifecycle, OpenAPI mapping, and token validation details.
  • SECURITY.md — supported versions, vulnerability reporting, and security-sensitive configuration.
  • CODE_OF_CONDUCT.md — community standards and enforcement.
  • LICENSE — Apache License 2.0.

Metadata

Release files for wombat-mcp 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wombat-mcp 0.2.0
File Size Uploaded Provenance Actions
wombat_mcp-0.2.0.tar.gz 338.7 kB With provenance Details for wombat_mcp-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for wombat-mcp 0.2.0
File Interpreter ABI Platform Actions
wombat_mcp-0.2.0-py3-none-any.whl Python 3 none any Details for wombat_mcp-0.2.0-py3-none-any.whl

Total release size: 661.0 kB

Release files / wombat_mcp-0.2.0.tar.gz

Download URL wombat_mcp-0.2.0.tar.gz
Size 338.7 kB
Tags Source
SHA-256 checksum
How to use checksums
516aa4816345eab0ee7d8f58b4cbf664943aa31f6dbde06a99bde5fba1aa58f0
BLAKE2b-256 checksum
How to use checksums
44d94fe962a7ae63b8a4c1d0937b40d2e1163b325a783844299437d95ec7e3cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / wombat_mcp-0.2.0-py3-none-any.whl

Download URL wombat_mcp-0.2.0-py3-none-any.whl
Size 322.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cd13cef3ad8e9083943dd8a90b7f4dbb74ef8761eb5510d8021fc45ce200deaa
BLAKE2b-256 checksum
How to use checksums
d7cc5b17795538ac244b315bf6aeb65cc27836af9e2d6d60708a129dd24683cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for wombat-mcp 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for wombat_mcp-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page