Content
# 🚀 C64 Model Context Protocol (MCP)
[](https://docs.datacontexthub.com/)

[](LICENSE)
---
MCP resource server that:
- Authenticates against Keycloak
- Discovers available views, functions, and agent systems
- Registers selected items as MCP tools
- Serves them over FastMCP (`streamable-http` or `sse`)
---
## 📋 Requirements
- Python `>=3.12.9`
- Network access to:
- Auth server (`AUTH_HOST` / realm)
- Linked Data API
- Graph Builder Services API
- M4AI API
## 🔧 Installation
```bash
uv sync
```
## ⚙️ Configuration
Configuration is loaded from environment variables (and `.env` in the repo root).
| Variable | Default | Description |
|---|---|---|
| `MCP_HOST` | `localhost` | MCP server bind host |
| `MCP_PORT` | `8001` | MCP server bind port |
| `MCP_SSL_ENABLED` | `false` | If `true`, MCP server URL is built as `https://...`; otherwise `http://...` |
| `TRANSPORT` | `streamable-http` | MCP transport (`streamable-http` or `sse`) |
| `AUTH_HOST` | `https://auth.example.com/auth` | Keycloak base host used to construct OIDC endpoints for server-side auth metadata/introspection |
| `AUTH_REALM` | `your-realm` | Keycloak realm used with `AUTH_HOST` |
| `OAUTH_CLIENT_ID` | `your-oauth-client-id` | OAuth client id used by MCP resource-server auth settings/introspection verifier |
| `OAUTH_CLIENT_SECRET` | `your-oauth-client-secret` | OAuth client secret paired with `OAUTH_CLIENT_ID` |
| `OAUTH_STRICT` | `false` | OAuth strict mode flag |
| `KEYCLOAK_TOKEN_URL` | `${AUTH_HOST}/realms/${AUTH_REALM}/protocol/openid-connect/token` | Token endpoint used by the CLI login flow (password + refresh grants) |
| `KEYCLOAK_CLIENT_ID` | `your-keycloak-client-id` | Client id sent by the CLI login/token client |
| `KEYCLOAK_SCOPE` | `email profile offline_access` | Scope requested by the CLI login/token client |
| `KEYCLOAK_GRANT_TYPE` | `password` | Grant type used for username/password login |
| `DCH_USERNAME` | none | Default username for startup authentication when `--username` is not provided |
| `DCH_PASSWORD` | none | Default password for startup authentication when `--password` is not provided |
| `DCH_VIEWS` | none | Comma-separated view ids for non-interactive selection |
| `DCH_FUNCTIONS` | none | Comma-separated function ids for non-interactive selection |
| `DCH_AGENT_SYSTEMS` | none | Comma-separated agent-system ids for non-interactive selection |
| `GRAPH_BUILDER_SERVICES_BASE_URL` | `https://api.example.com/gbs` | Base URL for Graph Builder Services API |
| `LINKED_DATA_API_BASE_URL` | `https://api.example.com/linked-data-api` | Base URL for Linked Data API |
| `M4AI_BASE_URL` | `https://api.example.com/m4ai` | Base URL for M4AI API |
### 💡 `AUTH_*` vs `KEYCLOAK_*`
- `AUTH_*` controls how the MCP server exposes and validates OAuth/OIDC integration (issuer/introspection/authorization metadata). It is server-facing auth configuration.
- `KEYCLOAK_*` controls how this script logs in a user and refreshes tokens during startup tool discovery (`KeycloakAuthClient`). It is client-facing token acquisition configuration.
- In many setups these point to the same Keycloak realm, but they can use different client IDs/secrets depending on your security model.
Example `.env`:
```env
MCP_HOST=localhost
MCP_PORT=8001
MCP_SSL_ENABLED=false
TRANSPORT=streamable-http
AUTH_HOST=https://auth.example.com/auth
AUTH_REALM=your-realm
OAUTH_CLIENT_ID=your-client-id
OAUTH_CLIENT_SECRET=your-secret
OAUTH_STRICT=false
KEYCLOAK_TOKEN_URL=https://auth.example.com/auth/realms/your-realm/protocol/openid-connect/token
KEYCLOAK_CLIENT_ID=your-keycloak-client-id
KEYCLOAK_SCOPE="email profile offline_access"
KEYCLOAK_GRANT_TYPE=password
DCH_USERNAME=your.username
DCH_PASSWORD=your-password
DCH_VIEWS=view-id-123,view-id-456
DCH_FUNCTIONS=77,81
DCH_AGENT_SYSTEMS=14,21
GRAPH_BUILDER_SERVICES_BASE_URL=https://api.example.com/gbs
LINKED_DATA_API_BASE_URL=https://api.example.com/linked-data-api
M4AI_BASE_URL=https://api.example.com/m4ai
```
## 🗝️ Configure Keycloak Client (for MCP Authentication)
Minimum setup to get credentials needed by this MCP server:
1. Ensure you have admin access to Keycloak.
2. Select the target realm.
3. Go to `Clients` and click `Create client`.
4. Set:
- Client type: `OpenID Connect`
- Client ID: your client id
- Client name: your client name
- Description: optional
5. Click `Next`.
6. Enable `Client authentication` (toggle ON).
7. In authentication flow, enable `Standard flow`.
8. Click `Next`.
9. Set `Valid redirect URIs` to where the client is available, usually `http://localhost:*`.
10. Create/save the client.
11. Open the created client, go to `Credentials`, and copy/create the client secret.
At this point you have the minimum required values:
- Client ID
- Client secret
- Realm
- Auth host
Use those values in `.env`:
```env
AUTH_HOST=https://<your-keycloak-host>/auth
AUTH_REALM=<your-realm>
OAUTH_CLIENT_ID=<client-id>
OAUTH_CLIENT_SECRET=<client-secret>
KEYCLOAK_CLIENT_ID=<client-id>
KEYCLOAK_TOKEN_URL=https://<your-keycloak-host>/auth/realms/<your-realm>/protocol/openid-connect/token
```
## ▶️ Running
Basic run:
```bash
uv run python main.py
```
With explicit credentials:
```bash
uv run python main.py --username my.user --password '***'
```
With credentials from environment:
```bash
export DCH_USERNAME=my.user
export DCH_PASSWORD='***'
uv run python main.py
```
With credential caching enabled:
```bash
uv run python main.py --cache-credentials
```
With direct selection from CLI:
```bash
uv run python main.py \
--views view-id-123,view-id-456 \
--functions 77,81 \
--agent-systems 14,21
```
With paging/limits:
```bash
uv run python main.py \
--views-limit 50 \
--functions-page-size 50 \
--agent-systems-page-size 50
```
These values control interactive selection page sizes. Additional pages are fetched lazily when you move between pages in the selector UI.
## 💻 Development
Run Ruff lint checks:
```bash
uv run ruff check .
```
Auto-format the codebase with Ruff:
```bash
uv run ruff format .
```
## 🤖 Claude Desktop Integration
Add this MCP server entry to your `claude_desktop_config.json` under `mcpServers`.
Use your own local values for binary path, MCP URL, OAuth client id, and client secret.
```json
{
"mcpServers": {
"c64-mcp": {
"command": "/path/to/npx",
"args": [
"mcp-remote",
"<mcp_server_url>",
"--static-oauth-client-info",
"{\"client_id\":\"your-client-id\",\"client_secret\":\"your-client-secret\"}"
]
}
},
...
}
```
## 📝 CLI Options
```text
--username
--password
--views
--functions
--agent-systems
--cache-credentials
--views-limit
--functions-page-size
--agent-systems-page-size
--selection-config
--output-selection-as-config
```
`--selection-config` is optional. If omitted, the script uses interactive selection.
`--cache-credentials` is optional. If omitted, `.credentials_cache` is not read or written.
Environment credentials are also supported via `DCH_USERNAME` and `DCH_PASSWORD`.
Selection can also be provided directly through `--views`, `--functions`, `--agent-systems` or `DCH_VIEWS`, `DCH_FUNCTIONS`, `DCH_AGENT_SYSTEMS`.
## 🔐 Authentication and Token Cache
Refresh token cache file: `.credentials_cache` (next to `main.py`).
Startup auth flow with `--cache-credentials`:
1. Try cached refresh token.
2. If invalid/missing, authenticate using `--username` / `--password`, then `DCH_USERNAME` / `DCH_PASSWORD`, then prompt if still needed.
3. Save new refresh token when available.
4. Continue with view/function/agent-system discovery.
Startup auth flow without `--cache-credentials`:
1. Skip reading `.credentials_cache`.
2. Authenticate using `--username` / `--password`, then `DCH_USERNAME` / `DCH_PASSWORD`, then prompt if still needed.
3. Do not write `.credentials_cache`.
4. Continue with view/function/agent-system discovery.
During API calls, if access token expires (`401`), the script refreshes and retries.
Important behavior:
- If credential caching is enabled and no cached token exists, login is prompted **before** reading `--selection-config`.
- CLI flags take precedence over `DCH_USERNAME` and `DCH_PASSWORD`.
- For direct selection, CLI args take precedence over `DCH_VIEWS`, `DCH_FUNCTIONS`, and `DCH_AGENT_SYSTEMS`.
## 🧰 Tool Selection Modes
### 1) Interactive mode (default)
When no selection config, direct selection CLI args, or direct selection env vars are provided:
- Script fetches selection pages from APIs on demand
- Shows checkbox-style selectors for:
- Views
- Functions
- Agent systems
- Left/right arrow keys or `a`/`d` move between pages in TTY mode
- `n` and `p` move between pages in prompt mode
- Item numbers are global across pages, so the displayed number stays stable as you page through results
- Selected items are registered as MCP tools
### 2) Direct id selection (`--views`, `--functions`, `--agent-systems`)
Provide comma-separated ids directly on the command line:
```bash
uv run python main.py \
--views view-id-123,view-id-456 \
--functions 77,81 \
--agent-systems 14,21
```
Or through environment variables:
```bash
export DCH_VIEWS=view-id-123,view-id-456
export DCH_FUNCTIONS=77,81
export DCH_AGENT_SYSTEMS=14,21
uv run python main.py
```
These use the same id-only resolution rules as JSON config mode. Missing categories default to an empty selection.
### 3) JSON config mode (`--selection-config`)
Provide a JSON file that defines what to select.
Example:
```json
{
"views": [
"view-id-123",
"view-id-456"
],
"functions": [
77,
81
],
"agent_systems": [
14,
21
]
}
```
Also accepted:
- `agentSystems` (camelCase) instead of `agent_systems`
### 📋 Config rules
Each entry in:
- `views` must be a view id
- `functions` must be a function id
- `agent_systems` must be an agent-system id
When `--selection-config` is used, the server fetches each selected entity directly by id and uses the fetched details to build the MCP tools in the same order as the config file.
If any id cannot be resolved, startup fails with a clear CLI error.
Run with selection file:
```bash
uv run python main.py --selection-config ./selection.json
```
### 📤 Export terminal selection to config
When running in interactive mode, you can export your selected items:
```bash
uv run python main.py --output-selection-as-config ./selection.json
```
This writes a JSON file with `views`, `functions`, and `agent_systems` based on your terminal selection.
The exported config contains only ids.
Constraint:
- `--selection-config` and `--output-selection-as-config` are mutually exclusive.
- `--selection-config` cannot be combined with `--views`, `--functions`, or `--agent-systems`.
- `--output-selection-as-config` only works in interactive mode.
## 💡 What Gets Registered
For each selected entity, the script fetches details and registers a tool:
- Views: executes via `execute_view`
- Functions: executes via `execute_function`
- Agent systems: executes over a stream, reconstructs chunked output, and returns the final last-layer result
Parameter names are normalized to valid Python identifiers for tool signatures.
## 🧩 Example Workflows
Interactive:
```bash
uv run python main.py
```
Non-interactive with predefined selections:
```bash
uv run python main.py \
--selection-config ./selection.json \
--views-limit 100 \
--functions-page-size 100 \
--agent-systems-page-size 100
```
## 🧯 Troubleshooting
- `Configuration error: Invalid transport ...`
- Set `TRANSPORT` to `streamable-http` or `sse`.
- `Authentication failed ...`
- Check username/password and Keycloak client settings.
- `Failed fetching views/functions/agent systems ...`
- Verify API connectivity and token scope/permissions.
- `Selection config contains unknown ...`
- Ensure the ids in JSON exist and are accessible by the detail endpoints.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available