Skip to main content

WAF++ MCP Bridge

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

Architecture

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

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

    User->>AI_Client: Start AI session
    AI_Client->>MCP: SSE /sse with Authorization: Bearer <WAF++ 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 wafpass-server, which handles the actual OIDC/SAML flows.
  • OIDC pass-through: The AI client inherits the user's WAF++ 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 WAFpass can apply endpoint- and row-level authorization.
  • Strict validation: Tool arguments are validated against Pydantic models generated from the WAFpass 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 WAFpass backend and choose token validation mode.

# 3. Start the bridge
python -m wafpass_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 WAF++ stack. From the repository root:

docker compose up -d wafpass-mcp

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

Release artifact: wafpass-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.

Configuration

Variable Default Description
WAFPASS_API_BASE_URL http://localhost:8000 Upstream WAFpass API
WAFPASS_TOKEN_MODE introspection introspection (call /auth/me) or jwt_secret (local HS256)
WAFPASS_JWT_SECRET (empty) Required for jwt_secret mode; must match backend secret
MCP_HOST 0.0.0.0 Bridge bind host
MCP_PORT 3001 Bridge bind port
LOG_LEVEL INFO Logging level

Token validation modes

  • introspection (recommended): The bridge calls GET /auth/me on WAFpass for every new SSE connection. This is IdP-agnostic, works with any backend secret rotation, and lets WAFpass 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://<WAFPASS_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. WAFpass 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 <WAF++ token> header.

Development

pytest
ruff check wafpass_mcp tests scripts
mypy wafpass_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 <WAF++_TOKEN>

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

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

Deployment notes

  • Run behind a TLS-terminating reverse proxy in production.
  • Prefer WAFPASS_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 WAFpass.

Contributing and security

  • CONTRIBUTING.md — how to set up local development, run tests, and open pull requests.
  • 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wafpass_mcp-0.1.1.tar.gz (27.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wafpass_mcp-0.1.1-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file wafpass_mcp-0.1.1.tar.gz.

File metadata

  • Download URL: wafpass_mcp-0.1.1.tar.gz
  • Upload date:
  • Size: 27.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for wafpass_mcp-0.1.1.tar.gz
Algorithm Hash digest
SHA256 e2e46041967864ca57eef61a12683464f265c4bb36f75e079673faf850a3ffa4
MD5 2878a496335b984d2eb6dbf0f51c3ff8
BLAKE2b-256 e20aea7665ccc0d2cda0de5d470b34baa7309d78364a1a1d89fd015325857bb8

See more details on using hashes here.

Provenance

The following attestation bundles were made for wafpass_mcp-0.1.1.tar.gz:

Publisher: release.yml on WAF2p/wafpass-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wafpass_mcp-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: wafpass_mcp-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for wafpass_mcp-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c9edbef27d5fd03395a7df05168d857b955fe5a1a1ee70d9f76dd852762a2b0c
MD5 b8fba52b1be431e213026b47b69abf3d
BLAKE2b-256 1550b6270c1ff6c3ebd2ee7c062d5a70a2974b547abb19682ddb8b2057ee3d2b

See more details on using hashes here.

Provenance

The following attestation bundles were made for wafpass_mcp-0.1.1-py3-none-any.whl:

Publisher: release.yml on WAF2p/wafpass-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page