自托管OAuth MCP服务器
一个自托管的MCP(模型上下文协议)服务器,使用Keycloak作为身份提供者进行OAuth 2.0身份验证。此设置允许您完全在自己的基础设施上运行受OAuth保护的MCP服务器,并支持动态URL(ngrok、反向代理等)。
特性
- 使用PKCE的完整OAuth 2.0授权代码流
- 动态客户端注册(DCR)支持
- 适用于ngrok和其他反向代理
- Keycloak作为身份提供者
- FastMCP发行JWT代币
- 预配置的测试用户和客户端
建筑
┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Client │─────▶│ nginx (:9000) │─────▶│ mcp-server │
│ │ │ │ │ (:8007) │
└─────────────┘ │ /mcp ──────────┼─────▶│ FastMCP + OAuth │
│ │ └──────────────────┘
│ /realms/* ─────┼─────▶┌──────────────────┐
│ /authorize │ │ Keycloak │
│ /token │ │ (:9090) │
└─────────────────┘ └──────────────────┘- 引擎X:端口9000上的反向代理,路由MCP流量和OAuth端点
- 钥匙斗篷:OAuth 2.0/OpenID连接身份提供者
- mcp服务器:带有DynamicOIDCProxy的FastMCP服务器,用于URL感知身份验证
先决条件
- Docker和Docker Compose
- Python 3.11+(用于本地开发/测试)
- 紫外线 (Python包管理器)
快速开始
- 启动服务:
docker compose up -d- 等待初始化 (大约需要30秒):
docker compose logs -f keycloak-initinit服务会自动配置Keycloak并将凭据写入 .env.
- 使用预先生成的令牌进行测试:
uv run python client.py- 使用完整的OAuth流程进行测试:
uv run python oauth_client.py http://localhost:9000配置
默认凭据
| 设置 | 值 |
|---|---|
| 领域 | mcp |
| 客户端ID | mcp-server |
| 测试用户 | user / password |
| 管理控制台 | http://localhost:9090 (admin / admin) |
环境变量
这 keycloak-init 服务创建了一个 .env 文件包含:
| 变量 | 描述 |
|---|---|
KEYCLOAK_CLIENT_ID | OAuth客户端ID |
KEYCLOAK_CLIENT_SECRET | OAuth客户端密钥(自动生成) |
MCP_TOKEN | 预先生成的JWT访问令牌用于测试 |
服务器环境变量
| 变量 | 默认值 | 描述 |
|---|---|---|
KEYCLOAK_URL | http://localhost:9090 | JWKS的内部密钥斗篷URL |
KEYCLOAK_PUBLIC_URL | http://nginx:80 | 通过nginx创建密钥斗篷URL |
KEYCLOAK_REALM | mcp | Keycloak领域名称 |
KEYCLOAK_CLIENT_ID | mcp-server | OAuth客户端ID |
用法
选项1:直接令牌身份验证
使用来自的预生成令牌 .env:
import asyncio
from fastmcp import Client
from dotenv import load_dotenv
import os
load_dotenv()
async def main():
async with Client(
"http://localhost:9000/mcp",
auth=os.getenv("MCP_TOKEN"),
) as client:
tools = await client.list_tools()
print("Tools:", [t.name for t in tools])
result = await client.call_tool("hello", {"name": "World"})
print(result.content[0].text)
asyncio.run(main())选项2:完整的OAuth流程
运行执行完整授权代码流的OAuth客户端:
uv run python oauth_client.py http://localhost:9000这将:
- 发现OAuth端点
- 注册为动态客户端
- 打开浏览器进行用户登录(使用
user/password) - 代币的交换授权码
- 使用访问令牌调用MCP工具
接触互联网(ngrok)
要与外部客户端一起使用或测试来自不同来源的OAuth流:
- 开始ngrok:
ngrok http 9000- 使用ngrok URL进行测试:
uv run python oauth_client.py https://your-subdomain.ngrok-free.app服务器自动检测来自的公共URL X-Forwarded-* 并相应地调整所有OAuth URL。
端点
| 端点 | 描述 |
|---|---|
/mcp | MCP服务器(可流式传输http) |
/.well-known/oauth-authorization-server | OAuth服务器元数据 |
/.well-known/oauth-protected-resource/mcp | 受保护的资源元数据 |
/authorize | OAuth授权端点 |
/token | OAuth令牌端点 |
/register | 动态客户端注册 |
/realms/mcp/... | Keycloak OIDC端点(代理) |
/debug | 显示请求标头的调试端点 |
MCP工具
服务器提供了两个示例工具:
- 你好(姓名:str):返回问候语(
hello, {name}) - add(a:int,b:int):将两个数字相加
项目结构
.
├── docker-compose.yml # Service orchestration
├── Dockerfile # MCP server container
├── nginx.conf # Reverse proxy configuration
├── server.py # FastMCP server with DynamicOIDCProxy
├── client.py # Simple test client (uses pre-generated token)
├── oauth_client.py # Full OAuth flow test client
├── setup_keycloak.py # Keycloak configuration script
├── realm-export.json # Keycloak realm import config
├── pyproject.toml # Python dependencies
└── .env # Generated credentials (gitignored)发展
本地设置
# Install dependencies
uv sync
# Run server locally (requires Keycloak running in Docker)
uv run python server.py重建MCP服务器
docker compose build mcp-server
docker compose up -d mcp-server查看日志
# All services
docker compose logs -f
# Specific service
docker compose logs -f mcp-server
docker compose logs -f keycloak重置所有内容
docker compose down -v
rm -f .env
docker compose up -d运作原理
动态OIDC代理
服务器使用自定义 DynamicOIDCProxy 扩展FastMCP的类 OIDCProxy 要支持动态URL,请执行以下操作:
- 动态URL检测:从中提取公共URL
X-Forwarded-Proto和X-Forwarded-Host标头 - URL重写中间件:在响应中将内部URL(nginx、keycloak、localhost)重写为公共URL
- 动态代币交换:使用检测到的公共URL进行OAuth令牌交换redirect_uri
- 灵活的令牌验证:跳过发行者验证,以支持使用不同公共URL发行的令牌
OAuth流
- 客户端从以下位置发现OAuth端点
/.well-known/oauth-authorization-server - 客户端通过以下方式动态注册
/register - 客户端将用户重定向到
/authorizePKCE挑战 - 用户使用Keycloak进行身份验证
- Keycloak重定向回MCP服务器
/auth/callback - MCP服务器与Keycloak交换代码并发出FastMCP JWT
- 客户端通过以下方式交换FastMCP令牌的授权码
/token - 客户端使用访问令牌调用MCP工具
故障排除
“不记名代币被拒绝”
- 检查Keycloak是否正常:
docker compose ps - 查看令牌验证日志:
docker compose logs mcp-server - 服务器跳过颁发者验证以获得动态URL支持
“无效的redirect_uri”
- 确保realm-export.json具有
"redirectUris": ["*"] - 更改后重建:
docker compose down -v && docker compose up -d
“连接被拒绝”
- 等待所有服务启动:
docker compose ps - 检查Keycloak健康状况:
curl http://localhost:9090/health
与ngrok的代币交换失败
- 确保nginx正在转发X-Forwarded标头
- 检查调试终结点:
curl https://your-ngrok-url/debug
安全说明
- 默认配置使用开发设置(无SSL,简单密码)
- 生产:
- 通过nginx启用HTTPS - 使用强凭据 - 考虑使用固定的公共URL启用颁发者验证 - 查看Keycloak安全设置
- 这
.env文件包含机密,不应提交
许可证
麻省理工学院
