Skip to content

feat(mcp): add HTTP with OAuth token forwarding#8460

Draft
Joel-hanson wants to merge 1 commit into
Apicurio:mainfrom
Joel-hanson:feat/mcp-http-oauth
Draft

feat(mcp): add HTTP with OAuth token forwarding#8460
Joel-hanson wants to merge 1 commit into
Apicurio:mainfrom
Joel-hanson:feat/mcp-http-oauth

Conversation

@Joel-hanson

@Joel-hanson Joel-hanson commented Jul 4, 2026

Copy link
Copy Markdown

Summary

This PR adds an HTTP transport to the Apicurio Registry MCP server, secured with inbound OIDC and per-caller bearer-token forwarding to Registry.

Today, MCP only works as a local process (stdio) that authenticates to Registry with a shared service account. After this change, you can also run MCP as a long-lived HTTP service: clients log in with their own Keycloak user, and Registry applies that user’s RBAC (same as the UI).

Stdio mode is unchanged and remains the default for local desktop clients.

Fixes: #8394


Mental model

Before (stdio only)

┌─────────────┐   spawn process    ┌──────────────┐   client credentials   ┌──────────┐
│ MCP client  │ ─────────────────► │ MCP server   │ ─────────────────────► │ Registry │
│ (desktop)   │   stdin / stdout   │ (one-shot)   │   admin-client token   │          │
└─────────────┘                    └──────────────┘                        └──────────┘

• One shared service account for all tool calls
• Every user effectively has the same Registry permissions
• Works well for local Claude Desktop / Cursor command configs

After (stdio or HTTP)

MODE A — stdio (unchanged)
┌─────────────┐   spawn process    ┌──────────────┐   client credentials   ┌──────────┐
│ MCP client  │ ─────────────────► │ MCP server   │ ─────────────────────► │ Registry │
└─────────────┘                    └──────────────┘                        └──────────┘

MODE B — HTTP + token forwarding (new)
┌─────────────┐   HTTPS + Bearer   ┌──────────────┐   same Bearer token    ┌──────────┐
│ MCP client  │ ─────────────────► │ MCP server   │ ─────────────────────► │ Registry │
│ (remote /   │   POST /mcp        │ (long-lived) │   per-user RBAC        │          │
│  desktop)   │                    └──────┬───────┘                        └──────────┘
└──────┬──────┘                           │
       │                                  │ validates token
       │         OAuth login              ▼
       └──────────────────────────► ┌──────────┐
                                    │ Keycloak │
                                    └──────────┘

• Caller’s own Keycloak token is forwarded to Registry
• sr-readonly cannot create groups; sr-admin can — same as the UI
• Suitable for MCP Inspector, remote agents, shared deployments

Runtime safety gate

HTTP transport is compiled into the MCP image at build time, but /mcp is not exposed unless you opt in at runtime:

quarkus.mcp.server.http.enabled=true     ← build time (always in published images)
apicurio.mcp.http.enabled=false          ← runtime default

                    ┌─ apicurio.mcp.http.enabled=false ──► /mcp returns 404
Request to /mcp ───┤
                    └─ apicurio.mcp.http.enabled=true
                       + Quarkus OIDC configured ─────────► /mcp requires Bearer token

Enforced by McpHttpGateFilter (see McpHttpDisabledByDefaultTest).


User journeys

Journey 1 — Local desktop client (stdio) — no change required

  1. Point your MCP client at the MCP Docker image / JAR (as today).
  2. If Registry is secured, set APICURIO_MCP_AUTH_* client credentials.
  3. Keep HTTP mode off (APICURIO_MCP_HTTP_ENABLED=false, QUARKUS_OIDC_TENANT_ENABLED=false).

Example: examples/mcp-keycloak/stdio.example.json

Journey 2 — HTTP MCP with OAuth (new)

  1. Run a secured Registry + Keycloak (or use the example stack below).
  2. Start the MCP server as an HTTP service with HTTP mode + OIDC enabled.
  3. Configure your MCP client with url: http://<host>:8082/mcp and OAuth client apicurio-mcp.
  4. Client completes OAuth login; MCP validates the token and forwards it to Registry.
  5. Tool calls succeed or fail based on that user’s Registry roles.

Example: examples/mcp-keycloak/http.example.json


What users need to set

Stdio mode (MCP → Registry)

Setting Env var Example
Registry URL REGISTRY_URL http://localhost:8081
Enable OAuth2 APICURIO_MCP_AUTH_ENABLED true
Token endpoint APICURIO_MCP_AUTH_TOKEN_ENDPOINT http://localhost:8080/realms/registry/protocol/openid-connect/token
Client ID APICURIO_MCP_AUTH_CLIENT_ID admin-client
Client secret APICURIO_MCP_AUTH_CLIENT_SECRET test1
Disable inbound OIDC QUARKUS_OIDC_TENANT_ENABLED false
Disable HTTP mode APICURIO_MCP_HTTP_ENABLED false

HTTP mode (remote / OAuth clients)

Apicurio settings:

Setting Env var Example
Enable HTTP mode APICURIO_MCP_HTTP_ENABLED true
Forward caller token APICURIO_MCP_HTTP_FORWARD_TOKEN true
Registry URL REGISTRY_URL http://apicurio-registry:8080

Quarkus companion settings (required when HTTP mode is on):

Setting Env var Example
Enable HTTP transport QUARKUS_MCP_SERVER_HTTP_ENABLED true
Disable stdio QUARKUS_MCP_SERVER_STDIO_ENABLED false
Enable OIDC QUARKUS_OIDC_TENANT_ENABLED true
Application type QUARKUS_OIDC_APPLICATION_TYPE service
Auth server QUARKUS_OIDC_AUTH_SERVER_URL http://keycloak:8080/realms/registry
Secure /mcp QUARKUS_HTTP_AUTH_PERMISSION_AUTHENTICATED_PATHS /mcp
Require auth QUARKUS_HTTP_AUTH_PERMISSION_AUTHENTICATED_POLICY authenticated

Optional (OAuth discovery / CORS for Inspector-style clients): see examples/mcp-keycloak/docker-compose.yml.

Full reference: MCP server integration guide


What’s in the code

Area Change
RegistryClientResolver Per-request client: forward inbound Bearer token, or fall back to client credentials / anonymous
McpHttpGateFilter /mcp returns 404 unless apicurio.mcp.http.enabled=true
McpHttpAuthValidator Fail-fast at startup if HTTP mode is on without OIDC / transport
Java SDK RegistryClientOptions.bearerToken(), AuthType.BEARER, JDK adapter support
Utils.java Clearer ProblemDetails / 401–403 messages (roles matter now that tokens are per-user)
examples/mcp-keycloak Local Keycloak + Registry + MCP HTTP stack
Docs HTTP/OAuth section in the integration guide

Why Utils.java error formatting is in this PR

With token forwarding, tool calls fail based on the caller’s Keycloak role. Returning a clear 401/403 (with role hints) is part of making HTTP mode usable for agents and users.

Quarkus OIDC

@github-actions github-actions Bot added the lifecycle/new PR awaiting triage label Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Thanks for opening this PR!

A maintainer will review and accept it shortly. In the meantime, you can
continue pushing changes.

Available commands:

Command Description
/ready Mark as ready for review (after a maintainer accepts the PR)
/disable-tests Disable smoke tests during development
/enable-tests Re-enable smoke tests
/unstale Remove the stale label
/retry Re-run the lifecycle orchestrator and retry failed tests

Maintainer commands:

Command Description
/accept Accept the PR and transition to WIP
/reject [reason] Reject and close the PR
/skip-review Skip review requirement for small changes (tests still required)
/auto-merge Toggle auto-merge
/merge Merge the PR

Note (fork PR): Review label updates may not apply automatically. A maintainer can use /retry after reviewing to update the labels.

Enable optional HTTP MCP transport with inbound Quarkus OIDC and
forward caller JWTs to Registry via Java SDK bearerToken() support.
Stdio transport and client-credentials mode remain the default.

Adds Keycloak example, docs, and integration tests.

Fixes Apicurio#8394

Signed-off-by: Joel Hanson <17215044+Joel-hanson@users.noreply.github.com>
@Joel-hanson Joel-hanson force-pushed the feat/mcp-http-oauth branch from 01164d6 to dd544fe Compare July 4, 2026 05:28
@sonarqubecloud

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lifecycle/new PR awaiting triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server: OAuth-authenticated HTTP transport for remote AI agents

1 participant