安全配置展示服务器

一个独立的展示项目,演示如何通过Keycloak OIDC承载令牌认证来保护Camel JBang MCP服务器,并展示如何在Claude Code、MCP Inspector等编码代理中注册该服务器。

  • by oscerd
  • 本地部署
  • 更新于
在 GitHub 查看 oscerd/camel-jbang-mcp-security-conf

使用说明

Camel MCP Server - Secure Configuration Showcase

A standalone showcase demonstrating how to secure the existing Camel JBang MCP server with Keycloak OIDC bearer-token authentication over HTTP/SSE transport, and how to register it in Claude Code, MCP Inspector, and other coding agents.

The upstream camel-jbang-mcp runs in STDIO-only mode with no authentication. This project repackages it as an HTTP/SSE server secured by Keycloak, exposing the same tools (catalog, transform, validate, harden, kamelets, versions) behind OIDC bearer-token auth.

Prerequisites

  • Java 17+
  • Maven 3.9+
  • Docker & Docker Compose (for Keycloak)
  • curl and jq (for testing)
  • camel-jbang-mcp installed in local Maven repo (mvn install from the Camel source tree)

Quick Start (Dev Mode)

Quarkus Dev Services can auto-start a Keycloak container:

mvn quarkus:dev

This starts the MCP server on port 8080 and automatically provisions a Keycloak instance on port 8180 with the pre-configured realm.

Production Mode

1. Start Keycloak

docker-compose up -d

Wait for Keycloak to be ready (admin console at http://localhost:8180, credentials: admin/admin).

2. Build and run the MCP server

mvn clean package -DskipTests
java -jar target/camel-mcp-secure-demo-1.0.0-SNAPSHOT-runner.jar

The server starts on http://localhost:8080.

Obtaining Access Tokens

Password grant (as alice - mcp-user role)

export TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=mcp-client" \
  -d "username=alice" \
  -d "password=alice" | jq -r '.access_token')

Password grant (as bob - mcp-admin role)

export TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=mcp-client" \
  -d "username=bob" \
  -d "password=bob" | jq -r '.access_token')

Client credentials grant (service-to-service)

export TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=mcp-server" \
  -d "client_secret=mcp-server-secret" | jq -r '.access_token')

Inspect a token

echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .

Testing with curl

Verify 401 without token

curl -v http://localhost:8080/mcp/sse
# Returns 401 Unauthorized

Establish SSE connection with token

curl -N -H "Authorization: Bearer $TOKEN" http://localhost:8080/mcp/sse

Invoke a tool via MCP protocol

# First establish an SSE connection to get the messages endpoint,
# then POST a tool call:
curl -X POST http://localhost:8080/mcp/messages/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "camel_catalog_components",
      "arguments": {
        "filter": "kafka",
        "label": "",
        "limit": 5,
        "runtime": "main",
        "camelVersion": ""
      }
    }
  }'

Registering with Claude Code

Option 1: CLI with SSE transport

# Get a token (access token lifespan is 1 hour in this demo)
export TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&client_id=mcp-client&username=alice&password=alice" \
  | jq -r '.access_token')

claude mcp add -t sse \
  camel-mcp-secure http://localhost:8080/mcp/sse \
  -H "Authorization: Bearer $TOKEN"

Option 2: CLI with Streamable HTTP transport

claude mcp add -t http \
  camel-mcp-secure http://localhost:8080/mcp \
  -H "Authorization: Bearer $TOKEN"

Option 3: .mcp.json (project-level)

Create a .mcp.json file in your project root:

{
  "mcpServers": {
    "camel-mcp-secure": {
      "type": "sse",
      "url": "http://localhost:8080/mcp/sse",
      "headers": {
        "Authorization": "Bearer <YOUR_TOKEN_HERE>"
      }
    }
  }
}

Option 4: Claude Desktop via mcp-remote bridge

For Claude Desktop, which only supports stdio transport, use the mcp-remote npm package:

{
  "mcpServers": {
    "camel-mcp-secure": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "http://localhost:8080/mcp/sse",
        "--header",
        "Authorization: Bearer <YOUR_TOKEN_HERE>"
      ]
    }
  }
}

Testing token revocation with Claude Code

Once the MCP server is registered, you can verify that revoking the token causes Claude Code to lose access:

# 1. Register with a valid token (see options above)

# 2. Verify it works — run Claude Code and invoke a tool, e.g.:
#    "list camel components related to kafka"
#    Claude Code should successfully call camel_catalog_components

# 3. Revoke the token
curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=mcp-client" \
  -d "token=$TOKEN"

# 4. Re-run Claude Code and try the same tool — it should fail with 401

# 5. To restore access, get a new token and re-register:
export TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/camel-mcp/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&client_id=mcp-client&username=alice&password=alice" \
  | jq -r '.access_token')

claude mcp remove camel-mcp-secure
claude mcp add -t sse \
  camel-mcp-secure http://localhost:8080/mcp/sse \
  -H "Authorization: Bearer $TOKEN"

Note: Quarkus OIDC validates JWT tokens offline using cached JWKS keys by default. If revocation does not take effect immediately, you can force it by logging out all sessions for the user via the Keycloak admin API:

# Get admin token
ADMIN_TOKEN=$(curl -s -X POST \
  http://localhost:8180/realms/master/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&client_id=admin-cli&username=admin&password=admin" \
  | jq -r '.access_token')

# Find alice's user ID and logout all sessions
ALICE_ID=$(curl -s \
  http://localhost:8180/admin/realms/camel-mcp/users?username=alice \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[0].id')

curl -s -X POST \
  http://localhost:8180/admin/realms/camel-mcp/users/$ALICE_ID/logout \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Registering with MCP Inspector

  1. Open MCP Inspector
  2. Select SSE transport type
  3. Enter URL: http://localhost:8080/mcp/sse
  4. In the Headers section, add:
    • Key: Authorization
    • Value: Bearer <YOUR_TOKEN_HERE>
  5. Click Connect

Available Tools

These are the existing tools from camel-jbang-mcp, now secured behind OIDC auth:

Tool Description
camel_catalog_components List Camel components (filter by name, label, runtime)
camel_catalog_component_doc Get detailed docs for a specific component
camel_catalog_dataformats List Camel data formats
camel_catalog_dataformat_doc Get detailed docs for a data format
camel_catalog_languages List Camel expression languages
camel_catalog_language_doc Get detailed docs for a language
camel_catalog_eips List Camel EIPs (Enterprise Integration Patterns)
camel_catalog_eip_doc Get detailed docs for an EIP
camel_catalog_kamelets List Camel Kamelets
camel_catalog_kamelet_doc Get detailed docs for a Kamelet
camel_validate_route Validate a Camel endpoint URI or YAML route
camel_validate_yaml_dsl Validate YAML DSL against the JSON schema
camel_transform_route Transform routes between YAML, XML, and Java DSL
camel_route_context Get enriched context (component + EIP docs) for a route
camel_route_harden_context Get security hardening analysis for a route
camel_version_list List available Camel versions by runtime

Users and Roles

User Password Roles Notes
alice alice mcp-user Standard user
bob bob mcp-admin Admin user

Keycloak admin console: http://localhost:8180 (credentials: admin/admin).

Security Flow

Client                    MCP Server (Quarkus)              Keycloak
  |                             |                              |
  |-- POST /token ------------->|                              |
  |                             |                              |
  |<--- access_token -----------|                              |
  |                             |                              |
  |-- GET /mcp/sse ------------>|                              |
  |   Authorization: Bearer xxx |                              |
  |                             |-- validate token ----------->|
  |                             |<-- token claims -------------|
  |                             |                              |
  |                             |-- check @Authenticated       |
  |                             |                              |
  |<--- SSE stream ------------|                              |
  |                             |                              |
  |-- POST /mcp/messages/1 --->|                              |
  |   Authorization: Bearer xxx |                              |
  |   { tool call }             |-- validate token ----------->|
  |                             |<-- token claims -------------|
  |                             |                              |
  |<--- tool result ------------|                              |

How It Works

This project depends on org.apache.camel:camel-jbang-mcp as a library. The existing MCP tool classes (CatalogTools, TransformTools, HardenTools, KameletTools, VersionTools, ExplainTools) are @ApplicationScoped CDI beans with @Tool annotations — Quarkus discovers them automatically from the dependency.

The key configuration changes from the upstream:

Setting Upstream (STDIO) This Showcase (HTTP + OIDC)
quarkus.http.host-enabled false true (via POM property)
Transport quarkus-mcp-server-stdio quarkus-mcp-server-http
Authentication None Keycloak OIDC bearer tokens
HTTP auth policies None /mcp/sse, /mcp/messages/*, /mcp
Port N/A 8080
Config format application.properties application.yaml

Why YAML? The upstream camel-jbang-mcp jar bundles its own application.properties at the classpath root. In a Quarkus uber-jar, this can shadow our file. Using application.yaml avoids the filename collision entirely — Quarkus loads both files and YAML properties take precedence.

The build-time property quarkus.http.host-enabled=true is set in the POM <properties> section, which has the highest precedence during Quarkus augmentation.

Project Structure

camel-jbang-mcp-secure-conf/
├── .gitignore
├── README.md
├── docker-compose.yml           # Keycloak 26.0 on port 8180
├── keycloak/
│   └── camel-mcp-realm.json     # Pre-configured realm, clients, users, roles
├── pom.xml                      # Depends on camel-jbang-mcp + quarkus-oidc
└── src/main/resources/
    └── application.yaml         # HTTP + OIDC config (YAML to avoid collision)

No custom Java code is needed — the tools come from the camel-jbang-mcp dependency.