自托管OAuth MCP服务器

一个自托管的MCP服务器,支持OAuth 2.0认证,使用Keycloak作为身份提供者,适用于需要自主控制基础设施的场景。

  • by OhadRubin
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 OhadRubin/self_hosted_oauth_mcp

使用说明

自托管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包管理器)

快速开始

  1. 启动服务:

    docker compose up -d
    
  2. 等待初始化 (大约需要30秒):

    docker compose logs -f keycloak-init
    

    init服务会自动配置Keycloak并将凭据写入 .env.

  3. 使用预先生成的令牌进行测试:

    uv run python client.py
    
  4. 使用完整的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

这将:

  1. 发现OAuth端点
  2. 注册为动态客户端
  3. 打开浏览器进行用户登录(使用 user / password)
  4. 代币的交换授权码
  5. 使用访问令牌调用MCP工具

接触互联网(ngrok)

要与外部客户端一起使用或测试来自不同来源的OAuth流:

  1. 开始ngrok:

    ngrok http 9000
    
  2. 使用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,请执行以下操作:

  1. 动态URL检测:从中提取公共URL X-Forwarded-Proto 和 X-Forwarded-Host 标头
  2. URL重写中间件:在响应中将内部URL(nginx、keycloak、localhost)重写为公共URL
  3. 动态代币交换:使用检测到的公共URL进行OAuth令牌交换redirect_uri
  4. 灵活的令牌验证:跳过发行者验证,以支持使用不同公共URL发行的令牌

OAuth流

  1. 客户端从以下位置发现OAuth端点 /.well-known/oauth-authorization-server
  2. 客户端通过以下方式动态注册 /register
  3. 客户端将用户重定向到 /authorize PKCE挑战
  4. 用户使用Keycloak进行身份验证
  5. Keycloak重定向回MCP服务器 /auth/callback
  6. MCP服务器与Keycloak交换代码并发出FastMCP JWT
  7. 客户端通过以下方式交换FastMCP令牌的授权码 /token
  8. 客户端使用访问令牌调用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 文件包含机密,不应提交

许可证

麻省理工学院