Research-Stack/6-Documentation/wiki/Authentik-MCP-Bridge.md
2026-05-25 16:24:21 -05:00

321 lines
9.3 KiB
Markdown

# Authentik MCP Bridge Specification
> **Purpose**: Define a Model Context Protocol (MCP) server surface so any LLM can safely manage Authentik identities through structured tools.
> **Status**: Draft — not yet implemented
> **Last updated**: 2026-05-20
---
## 1. What is MCP?
[Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for exposing tools, resources, and prompts to LLMs. An MCP server provides a JSON-RPC interface that a client (Claude Desktop, Cursor, a custom agent harness) can discover and invoke.
For Authentik, an MCP bridge means:
- **No raw curl** in the LLM prompt. The LLM calls `create_agent("openclaw-7", ...)` and the bridge handles the REST API.
- **Type safety**: input schemas are JSON Schema; the bridge validates before touching Authentik.
- **Auditability**: every tool call is logged with arguments and results.
- **Rate limiting / circuit breaking**: the bridge can throttle or deny dangerous operations.
---
## 2. Bridge Architecture
```
┌─────────────────┐ MCP (stdio/sse) ┌──────────────────┐ REST + Bearer ┌─────────────┐
│ LLM Client │ ───────────────────────▶ │ Authentik MCP │ ───────────────────▶ │ Authentik │
│ (Claude / │ JSON-RPC 2.0 │ Bridge Server │ Token │ API │
│ Cursor / etc) │ │ (Python/Node) │ │ :9000 │
└─────────────────┘ └──────────────────┘ └─────────────┘
┌────────▼─────────┐
│ Audit Log │
│ (JSONL + S3) │
└─────────────────┘
```
> **Authentication required**: The bridge itself must be authenticated by Authentik. It reads `AUTHENTIK_TOKEN` at startup (from env var or the credential server). The token must belong to an Authentik account with admin-level privileges (e.g., `llm-agent-controller` in the `authentik Admins` group). Without a valid token, every tool call returns an authentication error.
### Transport options
| Transport | Use case | Endpoint |
|-----------|----------|----------|
| `stdio` | Local CLI tools, Claude Desktop | `authentik-mcp-bridge stdio` |
| `sse` | Remote/web clients, persistent connection | `authentik-mcp-bridge sse --port 8080` |
---
## 3. Tool Surface
### 3.1 `create_agent`
Create a service-account user in Authentik.
**Input schema**:
```json
{
"username": "openclaw-instance-7",
"name": "OpenClaw Instance 7",
"email": "openclaw-7@researchstack.info",
"group": "AgentManager"
}
```
**Output**:
```json
{
"pk": 42,
"username": "openclaw-instance-7",
"uuid": "...",
"group_assigned": true
}
```
**Implementation**: `POST /core/users/` then `POST /core/groups/{uuid}/add_user/`
---
### 3.2 `create_token`
Generate an API token for an existing agent.
**Input schema**:
```json
{
"user_pk": 42,
"identifier": "openclaw-7-token"
}
```
**Output**:
```json
{
"identifier": "openclaw-7-token",
"key": "keJxas4I...",
"warning": "Store immediately — shown once"
}
```
**Implementation**: `POST /core/tokens/` then `GET /core/tokens/{identifier}/view_key/`
---
### 3.3 `list_agents`
List all service-account users.
**Input schema**:
```json
{
"active_only": true
}
```
**Output**:
```json
{
"agents": [
{"pk": 7, "username": "llm-agent-controller", "is_active": true},
{"pk": 42, "username": "openclaw-instance-7", "is_active": true}
]
}
```
**Implementation**: `GET /core/users/` with `?type=service_account` filter
---
### 3.4 `suspend_agent`
Soft-delete (deactivate) an agent.
**Input schema**:
```json
{
"user_pk": 42
}
```
**Output**:
```json
{
"ok": true,
"pk": 42,
"is_active": false
}
```
**Implementation**: `PATCH /core/users/{pk}/` with `{"is_active": false}`
---
### 3.5 `revoke_agent`
Hard-delete an agent and all its tokens.
**Input schema**:
```json
{
"user_pk": 42
}
```
**Output**:
```json
{
"ok": true,
"pk": 42,
"status": "deleted"
}
```
**Implementation**: `DELETE /core/users/{pk}/`
---
### 3.6 `rotate_token`
Regenerate the key for an existing token.
**Input schema**:
```json
{
"identifier": "openclaw-7-token"
}
```
**Output**:
```json
{
"identifier": "openclaw-7-token",
"key": "newKey...",
"warning": "Old key invalidated immediately"
}
```
**Implementation**: `POST /core/tokens/{identifier}/set_key/`
---
### 3.7 `create_application`
Create an application that agents can be granted access to.
**Input schema**:
```json
{
"name": "OpenClaw Data Sink",
"slug": "openclaw-data-sink"
}
```
**Output**:
```json
{
"slug": "openclaw-data-sink",
"name": "OpenClaw Data Sink",
"meta_launch_url": ""
}
```
**Implementation**: `POST /core/applications/`
---
## 4. Safety Boundaries
| Boundary | MCP Bridge Rule |
|----------|-----------------|
| **Token exposure** | `create_token` returns the key exactly once. The bridge does not log the key. The LLM client must store it in SOPS/RDS. |
| **Scope** | The bridge authenticates with the `llm-controller-token`. It cannot escalate beyond what that token permits. |
| **Rate limit** | Configurable: max 10 `create_agent` calls per minute, max 3 `revoke_agent` calls per minute. |
| **Deny list** | The bridge refuses to operate on `akadmin` (pk=6) and `llm-agent-controller` (pk=7). |
| **Audit** | Every call writes a JSON line to `~/.cache/authentik-mcp-bridge.jsonl` and optionally to `s3://research-stack/agent-receipts/`. |
---
## 5. Reference Implementation (Python, stdio)
A minimal skeleton using the official `mcp` Python SDK:
```python
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "mcp>=1.0.0",
# "httpx",
# ]
# ///
import os
import httpx
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
AUTHENTIK_TOKEN = os.environ["AUTHENTIK_TOKEN"]
BASE_URL = "https://researchstack.info"
app = Server("authentik-mcp-bridge")
@app.list_tools()
async def list_tools():
return [
Tool(name="create_agent", description="Create a service account", inputSchema={...}),
Tool(name="create_token", description="Create an API token", inputSchema={...}),
Tool(name="list_agents", description="List agents", inputSchema={...}),
Tool(name="suspend_agent", description="Deactivate an agent", inputSchema={...}),
Tool(name="revoke_agent", description="Hard-delete an agent", inputSchema={...}),
Tool(name="rotate_token", description="Rotate a token key", inputSchema={...}),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
async with httpx.AsyncClient(timeout=30) as client:
headers = {"Authorization": f"Bearer {AUTHENTIK_TOKEN}"}
if name == "create_agent":
r = await client.post(f"{BASE_URL}/api/v3/core/users/", headers=headers, json={...})
return [TextContent(type="text", text=r.text)]
# ... etc
async def main():
async with stdio_server() as (read, write):
await app.run(read, write, app.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
```
---
## 6. Deployment Plan
| Step | Action | Owner |
|------|--------|-------|
| 1 | Implement the 7 tools in Python using `mcp` SDK | Future agent |
| 2 | Add JSONL audit log + optional S3 sink | Future agent |
| 3 | Add rate limiting + deny-list for protected accounts | Future agent |
| 4 | Deploy as systemd service on qfox-1 or nixos-laptop | Future agent |
| 5 | Register with Claude Desktop / Cursor / OpenClaw harness | User |
| 6 | Document per-LLM configuration (env vars, transport mode) | Future agent |
---
## 7. Relationship to Existing Components
| Component | How MCP Bridge Uses It |
|-----------|----------------------|
| `authentik_agent_manager` (Rust) | The bridge is a higher-level wrapper around the same REST calls. The Rust shim supports both individual CLI commands and full DAG execution. The bridge is for LLM tool use. |
| Credential Server (RDS) | The bridge reads `AUTHENTIK_TOKEN` from the credential server at runtime: `curl http://100.101.247.127:8444/credentials/authentik` |
| SOPS secrets | Source of truth for the token. The bridge can optionally load from `sops -d` if the credential server is down. |
| `Authentik-Agent-Management.md` | Operational docs — the bridge implements the API patterns documented there. |
---
## 8. Open Questions
- Should the bridge support **application entitlement management** (granting an agent access to a specific app)? This requires `POST /core/application_entitlements/` which is more complex.
- Should the bridge support **group creation** so LLMs can define new scopes dynamically?
- Should the bridge support **impersonation** (`POST /core/users/{pk}/impersonate/`) for testing agent permissions?