MCP API

github-webhook-server can optionally expose a Model Context Protocol (MCP) endpoint at /mcp. It runs on the same FastAPI server as the normal webhook API, but it is a separate interface: GitHub still sends events to /webhook_server, while MCP clients connect to /mcp.

When enabled, the server builds the MCP layer from the existing FastAPI app instead of maintaining a separate MCP-only API. That keeps the MCP surface aligned with the routes you already run.

Note: /mcp is hidden from the autogenerated FastAPI schema. The route is registered with include_in_schema=False, so you will not see it in Swagger or other OpenAPI-based docs.

Enable and connect

MCP is disabled unless the process starts with ENABLE_MCP_SERVER=true. The same pattern is used for the log server feature, which matters because most of the useful agent-facing operations are log and workflow analysis routes.

LOG_SERVER_ENABLED: bool = os.environ.get("ENABLE_LOG_SERVER") == "true"
MCP_SERVER_ENABLED: bool = os.environ.get("ENABLE_MCP_SERVER") == "true"

Tip: Use the exact lowercase string true. Values such as True, 1, or yes do not enable these features in this codebase.

The example Compose file ships with MCP off by default:

environment:
  - ENABLE_LOG_SERVER=true
  - ENABLE_MCP_SERVER=false

To enable MCP:

  1. Set ENABLE_MCP_SERVER=true.
  2. Set ENABLE_LOG_SERVER=true if you want agents to query logs, PR flow data, or workflow step timelines.
  3. Restart the server.
  4. Point your MCP client at http://<host>:5000/mcp, or the HTTPS equivalent for your deployment.

With the default container setup, port 5000 is the expected default.

Note: Enabling MCP does not change your GitHub webhook URL. GitHub should continue posting to /webhook_server.

How /mcp is implemented

The MCP transport is mounted as a single streamable HTTP endpoint at /mcp. It is created from the main FASTAPI_APP, and routes tagged mcp_exclude are filtered out.

# MCP Integration - Only register if ENABLE_MCP_SERVER=true
if MCP_SERVER_ENABLED:
    # Create MCP instance with the main app
    # NOTE: No authentication configured - MCP server runs without auth
    # ⚠️ SECURITY WARNING: Deploy only on trusted networks (VPN, internal)
    # Never expose to public internet - use reverse proxy with auth for external access
    mcp = FastApiMCP(FASTAPI_APP, exclude_tags=["mcp_exclude"])

    # Create stateless HTTP transport to avoid session management issues
    # Override with stateless session manager
    http_transport = FastApiHttpSessionManager(
        mcp_server=mcp.server,
        event_store=None,  # No event store needed for stateless mode
        json_response=True,
    )
    # Manually patch to use stateless mode
    http_transport._session_manager = None  # Force recreation with stateless=True

    # Register the HTTP endpoint manually
    @FASTAPI_APP.api_route("/mcp", methods=["GET", "POST", "DELETE"], include_in_schema=False, operation_id="mcp_http")
    async def handle_mcp_streamable_http(request: Request) -> Response:
        # Session manager is initialized in lifespan
        if http_transport is None or http_transport._session_manager is None:
            LOGGER.error("MCP session manager not initialized")
            raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="MCP server not initialized")

        return await http_transport.handle_fastapi_request(request)

During startup, the app initializes the underlying MCP session manager in stateless mode:

http_transport._session_manager = StreamableHTTPSessionManager(
    app=mcp.server,
    event_store=http_transport.event_store,
    json_response=True,
    stateless=True,  # Enable stateless mode - no session management required
)

In practice, this means:

  • /mcp is either mounted or not mounted at all.
  • It uses one HTTP endpoint instead of a separate URL per capability.
  • It is stateless on the server side.
  • Proxies and gateways in front of it must allow GET, POST, and DELETE to /mcp.

What agents can access

Because the MCP layer is built from the FastAPI app, the operation IDs in webhook_server/app.py are the best guide to what an agent can use.

These routes are part of the app surface:

@FASTAPI_APP.get(f"{APP_URL_ROOT_PATH}/healthcheck", operation_id="healthcheck")
def healthcheck() -> dict[str, Any]:
    return {"status": requests.codes.ok, "message": "Alive"}
@FASTAPI_APP.get(
    "/logs/api/entries",
    operation_id="get_log_entries",
    dependencies=[Depends(require_log_server_enabled)],
)
@FASTAPI_APP.get(
    "/logs/api/export",
    operation_id="export_logs",
    dependencies=[Depends(require_log_server_enabled)],
)
@FASTAPI_APP.get(
    "/logs/api/pr-flow/{hook_id}",
    operation_id="get_pr_flow_data",
    dependencies=[Depends(require_log_server_enabled)],
)
@FASTAPI_APP.get(
    "/logs/api/workflow-steps/{hook_id}",
    operation_id="get_workflow_steps",
    dependencies=[Depends(require_log_server_enabled)],
)
@FASTAPI_APP.get(
    "/logs/api/step-logs/{hook_id}/{step_name}",
    operation_id="get_step_logs",
    dependencies=[Depends(require_log_server_enabled), Depends(require_trusted_network)],
)

The main capabilities this gives to AI agents are:

  • Health checks through healthcheck
  • Filtered webhook log search through get_log_entries
  • Log exports through export_logs
  • PR workflow analysis through get_pr_flow_data
  • Step-by-step execution timelines through get_workflow_steps
  • Time-correlated step log inspection through get_step_logs

If ENABLE_LOG_SERVER is still off, the log-related operations are not usable and return the same "log server is disabled" behavior as the regular HTTP API.

The webhook receiver itself is intentionally excluded from the MCP surface:

@FASTAPI_APP.post(
    APP_URL_ROOT_PATH,
    operation_id="process_webhook",
    dependencies=[Depends(gate_by_allowlist_ips_dependency)],
    tags=["mcp_exclude"],
)
async def process_webhook(request: Request) -> JSONResponse:

That is an important design choice. Agents can inspect server state and workflow data, but they do not get an MCP path to submit synthetic GitHub webhooks through the main ingestion endpoint.

Tip: If you do not want AI agents to inspect logs or workflow history, leave ENABLE_LOG_SERVER=false even if you enable MCP.

Logging and configuration

MCP traffic uses its own log file instead of sharing the main webhook server log. The example config includes the relevant settings:

log-file: webhook-server.log
mcp-log-file: mcp_server.log
logs-server-log-file: logs_server.log
mask-sensitive-data: true

A few practical details matter here:

  • mcp-log-file defaults to mcp_server.log.
  • Relative log file names are resolved under ${WEBHOOK_SERVER_DATA_DIR}/logs/.
  • If you do not set WEBHOOK_SERVER_DATA_DIR, the default data directory is /home/podman/data, so the default MCP log path becomes /home/podman/data/logs/mcp_server.log.
  • MCP logging is configured during application startup, so plan to restart the server after changing ENABLE_MCP_SERVER or mcp-log-file.
  • The MCP logger reuses the same masking setting as the rest of the app, so mask-sensitive-data: true also applies to MCP logs.

Security and deployment expectations

Warning: The MCP endpoint has no built-in authentication. Treat /mcp as an internal or administrative interface.

The code is explicit about this:

# NOTE: No authentication configured - MCP server runs without auth
# ⚠️ SECURITY WARNING: Deploy only on trusted networks (VPN, internal)
# Never expose to public internet - use reverse proxy with auth for external access
mcp = FastApiMCP(FASTAPI_APP, exclude_tags=["mcp_exclude"])

Use these expectations when deploying it:

  • Keep /mcp on a trusted private network, VPN, or localhost-only deployment whenever possible.
  • If it must be reachable through a shared edge, put it behind a reverse proxy or load balancer that enforces authentication and TLS.
  • Do not assume the GitHub IP allowlist protects /mcp. That allowlist is attached to the webhook receiver, not to the MCP transport.
  • Be extra careful if ENABLE_LOG_SERVER=true. In that mode, agents can query operational data such as hook IDs, repository names, GitHub usernames, workflow timings, error details, and token-spend metadata.
  • Treat mask-sensitive-data: true as a helpful safeguard, not as a security boundary.
  • If you run behind a reverse proxy, verify client IP handling carefully before relying on network-based restrictions.
  • Keep the whole MCP endpoint off the public internet unless you have strong compensating controls in front of it.

Warning: get_step_logs is the most restricted log route in the HTTP app because it requires both require_log_server_enabled and require_trusted_network. That extra check is useful, but it is still not a replacement for proper network isolation and authentication in front of /mcp.