SecureMCP
Authenticates and authorizes every MCP tool call using Keycloak-issued JWTs. Verifies token signature, issuer, audience, and expiry, and enforces role-based access control (viewer/admin) for each tool, with all allowed and denied attempts logged to an audit trail.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SecureMCPshow me all pending orders"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SecureMCP
An MCP (Model Context Protocol) server where every tool call is authenticated and authorized by Keycloak.
AI agents are getting direct access to business data through MCP tools. SecureMCP shows how to put a real identity provider in front of that access: the agent must present a Keycloak-issued JWT, each tool is gated by a role, and every attempt — allowed or denied — is written to an audit log.
Stack: Node.js · TypeScript · @modelcontextprotocol/sdk (Streamable HTTP) · Keycloak · jose · SQLite (better-sqlite3) · Vitest · Docker Compose
Features
🔐 No token, no access. Every request to
/mcpmust carry a validAuthorization: Bearer <jwt>. Signature (RS256 via Keycloak's JWKS), issuer, audience and expiry are all verified; otherwise the server answers401with aWWW-Authenticateheader.🧑⚖️ Role-based tools. Realm roles from the token (
realm_access.roles) decide which tools a caller may use.🚫 Clear
Forbiddenerrors. A disallowed call returns an MCP tool error the agent can read, e.g.Forbidden: user 'viewer' (roles: viewer) is not allowed to call 'update_order_status' — requires role: admin.📜 Audit trail. Every tool call is stored in
audit_log(user, tool, args, allowed/denied, reason, timestamp) and is readable by admins through a tool.🔁 Stateless. Each HTTP request gets a fresh MCP server instance, so every request is re-authenticated — no session can outlive its token.
Tools
Tool | Arguments |
|
|
|
| ✅ | ✅ |
|
| ✅ | ✅ |
|
| ❌ | ✅ |
|
| ❌ | ✅ |
Order statuses: pending, paid, shipped, delivered, cancelled. The database is seeded with 20 orders on first start.
Related MCP server: order-management-mcp
Auth flow
sequenceDiagram
autonumber
actor User
participant Agent as AI agent / MCP client
participant KC as Keycloak<br/>(realm: securemcp)
participant MCP as SecureMCP server
participant DB as SQLite
User->>KC: Log in (viewer / admin)
KC-->>Agent: Access token (JWT, aud=mcp-server, realm roles)
Agent->>MCP: POST /mcp tools/call<br/>Authorization: Bearer <JWT>
MCP->>KC: GET /protocol/openid-connect/certs (JWKS, cached)
KC-->>MCP: Public signing keys
MCP->>MCP: Verify signature, iss, aud, exp
alt Token missing or invalid
MCP-->>Agent: 401 Unauthorized + WWW-Authenticate
else Token valid
MCP->>MCP: Check role against tool policy
alt Role not allowed
MCP->>DB: INSERT audit_log (denied)
MCP-->>Agent: Tool error: "Forbidden: … requires role: admin"
else Role allowed
MCP->>DB: INSERT audit_log (allowed)
MCP->>DB: Run tool query
DB-->>MCP: Rows
MCP-->>Agent: Tool result
end
endProject structure
SecureMCP/
├── src/
│ ├── index.ts # Entry point: config → DB → HTTP server
│ ├── app.ts # Express app, /mcp endpoint (stateless Streamable HTTP)
│ ├── auth.ts # Bearer-token middleware, JWT verification with jose
│ ├── tools.ts # Tool definitions, role policy, authorization + audit wrapper
│ ├── db.ts # SQLite schema, seed data and queries
│ └── config.ts # Environment configuration
├── tests/ # Vitest suites (local JWKS, no Keycloak needed)
├── keycloak/
│ └── realm-export.json # Auto-imported realm: client, roles, test users
├── scripts/
│ └── get-token.sh # Fetch a token for a test user
├── docker-compose.yml # Keycloak + MCP server
├── Dockerfile
└── .env.exampleGetting started
Prerequisites
Docker + Docker Compose
Node.js 22+ (only to run locally outside Docker or to run the tests)
curl
1. Start everything
git clone https://github.com/okba-bouziane/SecureMCP.git
cd SecureMCP
docker compose up --buildThis starts:
Service | URL | Notes |
Keycloak | Admin console login: | |
SecureMCP | Waits until Keycloak is healthy |
The securemcp realm is imported automatically with:
client
mcp-server(public, with an audience mapper so tokens carryaud: mcp-server)realm roles
viewerandadminusers
viewer/viewerandadmin/admin
Port 3000 already in use? Run
MCP_PORT=3100 docker compose up --build.
2. Get a token
TOKEN=$(./scripts/get-token.sh viewer) # or: ./scripts/get-token.sh adminTokens are valid for 15 minutes.
3. Call a tool
curl -s http://localhost:3000/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_order","arguments":{"id":1}}}'Try the same call without the header (→ 401), or call update_order_status with the viewer token (→ Forbidden).
Run the server without Docker
docker compose up -d keycloak
cp .env.example .env
npm install
npm run build
node --env-file=.env dist/index.jsConnect an MCP client
MCP Inspector
npx @modelcontextprotocol/inspectorTransport type:
Streamable HTTPURL:
http://localhost:3000/mcpAuthentication: header
Authorization, valueBearer <paste output of ./scripts/get-token.sh admin>Click Connect, open Tools → List Tools, and run them.
Use a viewer token to see the Forbidden responses.
Claude Desktop
Claude Desktop launches local MCP servers over stdio, so use mcp-remote as a bridge that adds the Bearer header. Edit claude_desktop_config.json
(macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):
{
"mcpServers": {
"securemcp": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:3000/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer <paste output of ./scripts/get-token.sh admin>"
}
}
}
}Restart Claude Desktop, then ask something like "List the pending orders and mark order 4 as shipped." Tokens expire after 15 minutes — fetch a new one and restart when that happens.
Screenshots
MCP Inspector — tools list (admin) |
|
Viewer blocked from |
|
401 without a token |
|
|
|
Claude Desktop using SecureMCP |
|
Placeholders — add images to
docs/screenshots/.
Tests
npm install
npm testThe test suite starts the real Express app on a random port and signs tokens with a locally generated RSA key (served as an in-memory JWKS), so no running Keycloak is needed. It covers:
✅ valid token → tools are listed and callable
🚫 missing token / non-Bearer header →
401⌛ expired token →
401🔑 token signed by an untrusted key, wrong issuer, wrong audience, malformed token →
401🧑⚖️ viewer blocked from
update_order_statusandget_audit_log(order left unchanged)📜 audit log records allowed and denied calls, readable via
get_audit_log
Configuration
Variable | Default | Description |
|
| HTTP port |
|
| Bind address |
|
| SQLite file (created and seeded on first start) |
|
| Expected |
|
| Where signing keys are fetched |
|
| Expected |
|
| Host port published by Docker Compose |
In Docker, the server fetches keys from http://keycloak:8080 on the internal network while still expecting iss=http://localhost:8080/...; Keycloak is pinned to that public hostname with KC_HOSTNAME.
Security notes
This is a demo, not a production deployment:
scripts/get-token.shuses the password grant for convenience. Real agents should use Authorization Code + PKCE (themcp-serverclient already allows it for the Inspector's redirect URI) or client credentials.Keycloak runs in
start-devmode over plain HTTP with default passwords. Use TLS, a real database and secret management in production.Tool arguments are stored verbatim in the audit log — redact sensitive fields if your tools accept them.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Identity, authorization, audit trails, and revocable permissions for AI agents accessing MCP tools.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Let AI agents query data and act across all your business apps via MCP.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables role-based order management through MCP tools, allowing users to view and refund orders according to their permissions (USER, MANAGER, ADMIN) with audit logging and JWT authentication handled by a FastAPI client.-
- FlicenseNot gradedqualityCmaintenanceEnables role-based order management operations such as viewing orders, checking refund eligibility, issuing refunds, deleting orders, and managing users through MCP tools with audit logging and permission checks.-
- FlicenseNot gradedqualityCmaintenanceMCP server that enforces role-based access control for order management, allowing users to view, refund, or delete orders based on their role (USER, MANAGER, ADMIN) with a JWT-authenticated FastAPI frontend.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to find stale unshipped orders, retrieve order details, and mark orders as shipped by wrapping the orders REST API as MCP tools.5MIT




