mcp-keycloak
by ndigrazia
README.md
# MCP Keycloak Resource Server (`mcp-keycloak`)
This project implements a **Model Context Protocol (MCP)** protected Resource Server secured by an OAuth 2.0 Authorization Server (**Keycloak**) using **Token Introspection (RFC 7662)**.
The server is built with the Python `mcp` SDK using the `FastMCP` framework, showcasing how to restrict access to MCP tools by requiring a valid Bearer token.
---
## Architecture Overview
```
+--------------------+ 1. Request Token +--------------------+
| | ------------------------------> | |
| MCP Client | | Keycloak |
| (e.g., test_client)| <------------------------------ | (Auth Server, :8080)
| | 2. Access Token +--------------------+
+--------------------+ ^
| |
| 3. Call Tool (with Bearer Token) |
v | 4. Introspect
+--------------------+ | Token
| MCP Server | ------------------------------------------+
| (Resource, :3000) | <------------------------------------------
+--------------------+ 5. Active: True/False
```
1. **Keycloak (Authorization Server)**: Serves on port `8080`. It handles client credential grants and token introspection. A pre-configured database is included in the project directory (`keycloak_data/`) to make spin-up seamless.
2. **MCP Resource Server (Resource Server)**: Built with `FastMCP`, running on port `3000`. When a client calls a protected tool, the server intercepts the request and validates the `Authorization: Bearer <token>` header against Keycloak's introspection endpoint.
3. **Token Verifier (`IntrospectionTokenVerifier`)**: Implements standard OAuth 2.0 token introspection (`RFC 7662`). It validates token activity, resource/audience restrictions (`aud`), scope limits, and expiration times.
---
## Prerequisites
- **Python**: Version `3.12` or higher (compatible with [`uv`](https://github.com/astral-sh/uv))
- **Docker & Docker Compose**: To run Keycloak
---
## Pre-Configured Keycloak Clients
The embedded Keycloak database is pre-configured with three OAuth 2.0 clients within the `master` realm. The full, exact JSON configuration of these clients is exported and available in the [`keycloak_config.json`](./keycloak_config.json) file.
These clients use the Client Credentials Grant flow (via Service Accounts) and are configured with specific client secrets, scopes, and audience mappers:
### 1. `mcp-client` (Primary Testing Client)
* **Client ID**: `mcp-client`
* **Client Secret**: `64gq3p8y3siZKZoKH6X9Bq2oqIPfukBZYMnmyCVCJw1QgjfNzdympB0eaZ1aXtFl0yNUWzGr3S7Gov3gR4kLfe`
* **Authentication Flow**: Client Credentials (Service Account enabled)
* **Assigned Scopes**: `mcp:tools`
* **Audience Mappers**:
* `mcp-server`
* `http://localhost:3000` (derived resource server URL)
### 2. `mcp-client-2` (Alternative Testing Client)
* **Client ID**: `mcp-client-2`
* **Client Secret**: `FebOB24OaG3F4J9dMZPoZ7qhzQvUKQ5HgJ6IbQ6yXQBd5tg88f8tPSEA0aUGK9AuEebxHRBwsPdLuDjQpiLFhR`
* **Authentication Flow**: Client Credentials (Service Account enabled)
* **Assigned Scopes**: `mcp:tools`
* **Audience Mappers**:
* `mcp-server`
* `http://localhost:3000`
### 3. `mcp-server` (Resource Server Introspection)
* **Client ID**: `mcp-server`
* **Client Secret**: `5XflSzJJrvQD8iNz4t7RMI4i9rNvdnLQs5PSo8EMGkfUDKr02SjaPs4fKA9kWNO1G06nhDzTHMeHrnz9H4h5ut`
* **Authentication Flow**: Client Credentials (Service Account enabled) — used by the Resource Server to authenticate introspection requests.
* **Assigned Scopes**: `mcp:tools`
* **Audience Mappers**:
* `mcp-server`
* `http://localhost:3000`
---
## Installation & Setup
### 1. Start Keycloak
Run the Keycloak container in development mode using the provided `docker-compose.yml`:
```bash
docker compose up -d
```
Keycloak starts on `http://127.0.0.1:8080`. Admin credentials are:
- **Username**: `admin`
- **Password**: `admin`
*Note: The container mounts `./keycloak_data`, preserving the pre-configured realm, clients (`mcp-server`, `mcp-client`, and `mcp-client-2`), and settings.*
### 2. Install Project Dependencies
If you are using `uv`:
```bash
uv sync
```
Alternatively, standard `pip` can be used:
```bash
pip install -e .
```
---
## Running the Server
Start the MCP Resource Server:
```bash
uv run main.py
```
By default, the server starts on `http://localhost:3000` using the `streamable-http` transport (supporting Streamable HTTP MCP communication).
### Server Configuration
Configuration options can be customized via environment variables defined in `config.py`:
| Environment Variable | Default Value | Description |
|----------------------|---------------|-------------|
| `HOST` | `localhost` | MCP Server hostname |
| `PORT` | `3000` | MCP Server port |
| `AUTH_HOST` | `localhost` | Keycloak server host |
| `AUTH_PORT` | `8080` | Keycloak server port |
| `AUTH_REALM` | `master` | Keycloak Realm to use |
| `OAUTH_CLIENT_ID` | `mcp-server` | Client ID the Resource Server uses for Introspection |
| `OAUTH_CLIENT_SECRET`| `5XflSzJJrv...` | Client Secret for Introspection client |
| `MCP_SCOPE` | `mcp:tools` | Scope required to invoke the MCP tools |
| `OAUTH_STRICT` | `false` | Enable strict OAuth validations |
| `TRANSPORT` | `streamable-http` | MCP Transport protocol (`streamable-http` or `sse`) |
---
## Secure MCP Tools Provided
The server registers two secure arithmetic tools:
1. **`add_numbers`**
- **Arguments**: `a: float`, `b: float`
- **Operation**: Adds two numbers together.
- **Output**: Returns JSON containing parameters, sum result, and timestamp.
2. **`multiply_numbers`**
- **Arguments**: `x: float`, `y: float`
- **Operation**: Multiplies two numbers.
- **Output**: Returns JSON containing parameters, product result, and timestamp.
---
## Testing with the Test Client
A CLI-based test client (`test_client.py`) is included to simulate MCP client interactions.
### 1. View Help Options
To see all available CLI flags:
```bash
uv run test_client.py --help
```
### 2. Automatically Fetch Token & List Secure Tools
To retrieve a client token via Keycloak's client credentials flow (using client credentials configured for `mcp-client`) and list the available tools on the MCP server:
```bash
uv run test_client.py --fetch-token
```
To use the alternative `mcp-client-2` client, specify the client credentials flags:
```bash
uv run test_client.py --fetch-token --client-id mcp-client-2 --client-secret FebOB24OaG3F4J9dMZPoZ7qhzQvUKQ5HgJ6IbQ6yXQBd5tg88f8tPSEA0aUGK9AuEebxHRBwsPdLuDjQpiLFhR
```
### 3. Call a Protected Tool
To execute a secure tool using an automatically fetched token:
#### Addition Example:
```bash
uv run test_client.py --fetch-token --call add_numbers --args '{"a": 15.5, "b": 24.5}'
```
#### Multiplication Example:
```bash
uv run test_client.py --fetch-token --call multiply_numbers --args '{"x": 6.0, "y": 7.0}'
```
---
## Technical Details
### Token Validation Rules (`token_verifier.py`)
When an incoming MCP request is received, `IntrospectionTokenVerifier.verify_token(token)` validates:
- **HTTP status**: The introspection request successfully responds with an HTTP `200` status.
- **Activity**: The response JSON includes `"active": true`.
- **Audience (`aud`)**: The audience claim matches the resource server URL (derived dynamically, e.g., `http://localhost:3000/`).
- **Scopes**: Validated to ensure the client has the required `mcp:tools` scope.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues