mcp-oauth-test
FastMCP OIDC Server
这是一个使用 FastMCP 编写的、通过 OIDC 登录保护的 MCP server。它使用 FastMCP 的 OIDCProxy:MCP 客户端通过服务端暴露的 OAuth 元数据完成认证,实际登录和 token exchange 转发到 QA OIDC provider。
当前已接入 QA MGT OpenAPI,生成 trainee、订单、商品、客户、校园招聘和国际招聘相关 MCP tools。
OIDC discovery 地址已默认配置为:
https://auth-qa.drillinsight.com/.well-known/openid-configuration认证准备
先在 auth-qa.drillinsight.com 注册 OAuth 应用,并将以下回调地址加入白名单:
http://localhost:8000/auth/callback部署到其他地址时,将 http://localhost:8000 替换为 BASE_URL 的值。回调地址必须与 FastMCP 的 BASE_URL 完全匹配。
本地运行
cp .env.example .env
# 编辑 .env,至少填写 OIDC_CLIENT_ID 和 OIDC_CLIENT_SECRET
uv sync
uv run mcp-oidc-server也可以直接运行模块:
uv run python -m oidc_mcp_server.server服务默认监听 http://127.0.0.1:8000。如果 MCP 客户端运行在其他机器或使用容器,请设置可被客户端访问的 BASE_URL 和合适的 HOST(例如 0.0.0.0)。
Claude Code 插件
仓库包含一个私有 Claude Code marketplace 和 MCP 插件:
.claude-plugin/marketplace.json
└── plugins/mcp-oauth-test/
├── .claude-plugin/plugin.json
├── .mcp.json
└── README.md插件只负责把 Claude Code 连接到已经部署的远程 MCP 服务,不会在本地启动 Python 服务。开发测试时可以直接加载:
claude --plugin-dir ./plugins/mcp-oauth-test插件已固定连接到开发环境 MCP Server:
https://api-mcp-oauth-dev.beaconfireinc.com/mcp也可以从私有 marketplace 安装:
/plugin marketplace add /path/to/mcp-oauth-test
/plugin install mcp-oauth-test@authsome-internal当前 marketplace 根目录就是仓库根目录。请保持该 marketplace 在公司私有 GitHub 仓库中,不要提交到公开 marketplace。共享环境应使用 HTTPS 地址,并在公司 IdP 和 MCP Server 侧限制公司用户访问。
为什么使用 OIDCProxy
上游 auth-qa.drillinsight.com 不需要支持 DCR 或 CIMD。OIDCProxy 正是用于这种场景:
ChatGPT ── MCP OAuth / CIMD ──> FastMCP OIDCProxy
│
└── 固定 client_id/client_secret ──> auth-qa.drillinsight.com需要在上游提前注册的只有 FastMCP 这个 OAuth 应用,并配置 ${BASE_URL}/auth/callback。ChatGPT 使用的 CIMD 由 FastMCP 代理层处理,不会转发给上游 OAuth server。
ChatGPT CIMD 配置
在 ChatGPT 创建自定义 MCP 时,OAuth 高级设置中的“客户端注册”请选择:
客户端标识元数据文档(CIMD)当前 ChatGPT 连接器生成的信息为:
CIMD Client ID / 客户端元数据 URL:
https://chatgpt.com/oauth/0Buhw3sHVv1-/client.json
ChatGPT Callback URL:
https://chatgpt.com/connector/oauth/0Buhw3sHVv1-CIMD URL 本身就是 ChatGPT 访问 FastMCP OAuth 代理时使用的 client_id。它不需要、也不应该注册到上游 auth-qa.drillinsight.com。
本项目存在两层不同的 OAuth Client ID:
OAuth 链路 |
| 配置位置 |
ChatGPT → FastMCP OIDCProxy |
| ChatGPT 自动提供,选择 CIMD 后无需手动填写 |
FastMCP OIDCProxy → |
| MCP Server 的 |
对应的数据流为:
ChatGPT
│ client_id=https://chatgpt.com/oauth/0Buhw3sHVv1-/client.json
▼
FastMCP OIDCProxy
│ client_id=app_74a4b555-5b87-4212-9dda-d584fa78caf8
▼
auth-qa.drillinsight.comMCP Server 的环境变量配置:
OIDC_CLIENT_ID=app_74a4b555-5b87-4212-9dda-d584fa78caf8
OIDC_CLIENT_SECRET=<上游 OAuth Server 颁发的客户端密钥>上游 OAuth Server 只需为该 app_... 应用配置 FastMCP 的回调地址:
https://heroic-verbally-crawdad.ngrok-free.app/auth/callback不要将 ChatGPT 的回调地址 https://chatgpt.com/connector/oauth/... 配置到上游 OAuth Server;该地址由 FastMCP 代理层在完成认证后使用。
认证开始时,正常日志应先出现 ChatGPT 的 CIMD Client ID:
CIMD document fetched and validated
GET /authorize?client_id=https://chatgpt.com/oauth/.../client.json ... 302随后 FastMCP 才会使用 app_74a4b555-... 跳转到上游 OAuth Server。
MCP 客户端配置
将 MCP 地址配置为:
http://localhost:8000/mcpFastMCP 会提供以下认证发现地址:
http://localhost:8000/.well-known/oauth-authorization-server
http://localhost:8000/.well-known/oauth-protected-resource/mcp客户端应自动读取这些 MCP/OAuth discovery endpoint。登录成功后可调用两个受保护工具:
ping:健康检查。who_am_i:返回 FastMCP 从当前认证 token 中提取的client_id、scope 和 claims。
配置项
环境变量 | 必需 | 默认值 | 说明 |
| 是 | - | 上游 OIDC 客户端 ID |
| 二选一 | - | confidential client secret |
| 二选一 | - | public PKCE client 或生产环境的 FastMCP token 签名密钥 |
| 否 | QA discovery URL | OIDC discovery 地址 |
| 否 |
| MCP server 公网地址 |
| 否 |
| OAuth 授权请求/默认广告的 scope;不用于 access token scope 校验 |
| 否 | OIDC discovery issuer | JWT |
| 否 |
| 自定义 token issuer 的 JWKS 地址 |
| 否 | - | 可选 JWT |
| 否 |
| 监听地址 |
| 否 |
| 监听端口 |
| 否 | QA MGT 地址 | MGT API 实际调用 Base URL |
| 否 |
| 本地 OpenAPI spec 路径 |
生产环境请显式设置随机的 JWT_SIGNING_KEY,并使用 HTTPS 的 BASE_URL。不要把 .env 或任何 client secret 提交到 Git。
自定义 Token Issuer
如果 token 的 iss 不是 OIDC discovery 返回的 issuer,而是由 auth middleware 改写成租户地址,例如:
实际 token iss:
https://api-authsome-qa.drillinsight.com/auth-middleware/t_adecdb63-afab-4346-a1aa-b50bbbae7aee/设置:
OIDC_TOKEN_ISSUER=https://api-authsome-qa.drillinsight.com/auth-middleware/t_adecdb63-afab-4346-a1aa-b50bbbae7aee/
OIDC_JWKS_URI=https://auth-qa.drillinsight.com/oauth/jwksOIDC_CONFIG_URL 仍用于 OAuth 登录和授权端点发现;OIDC_TOKEN_ISSUER 只用于 JWT access token 的 iss 校验。两者可以不同。OIDC_TOKEN_ISSUER 必须与 token 中的 iss 完全一致,包括末尾 /。
当前项目不会校验 access token 的 scope 或 scp claim,因为 MGT 历史 Token 使用的是非标准 scope 格式。OIDC_REQUIRED_SCOPES 仍用于 OAuth 授权请求,但不会阻止缺少标准 scope claim 的有效 Token。签名、issuer、audience、过期时间和 JWKS 校验仍然保留。
QA MGT OpenAPI 接入
QA OpenAPI spec 已固定保存到:
specs/mgt-qa-openapi.jsonMCP 不会在运行时访问线上 /api-docs,因此未来生产环境不开放 API 文档也不影响运行。通过 MGT_API_BASE_URL 切换实际 API 地址即可。Docker 镜像会将 specs/mgt-qa-openapi.json 复制到 /app/specs/mgt-qa-openapi.json,并自动设置 MGT_OPENAPI_SPEC_PATH。
第一版暴露的 API 范围:
/api/v1/user/current
/course/list
/batch/list
/batch/trainee/list
/equity/userequity/give
/api/v1/order/**
/api/v1/item/**
/api/v1/open/getSku*
/api/v1/customers
/api/v1/campus-recruitment/**(排除 export)
/api/v1/recruitment-info/**(排除 export)订单支付链接接口按当前需求接入:
/api/v1/order/queryPayLink
/api/v1/order/reGenaratePayLink仍然排除退款、支付回调和客户数据导出接口:
/mall/v1/order/refund
/alipay/**
/stripe/**
/weixin/refund/**
/api/v1/customers/export
/api/v1/campus-recruitment/export
/api/v1/recruitment-info/export每次调用 MGT 时,OpenAPI client 会从当前 FastMCP 请求中取得用户的上游 OAuth access token,并发送:
Authorization: Bearer <user access token>
X-Application-Id: <token.app_id>其中 X-Application-Id 不需要额外配置,直接从已验证 JWT 的 app_id claim 读取。没有 app_id 的 Token 会被拒绝,避免向 MGT 发送不完整的请求。
因此 MGT 必须信任 auth-qa.drillinsight.com 签发的用户 Token,并按照用户身份执行权限控制。
ChatGPT CIMD timeout 排查
如果日志包含:
CIMD fetch failed for https://chatgpt.com/.../client.json: Timeout fetching
Unregistered client_id=https://chatgpt.com/.../client.json说明 FastMCP 无法直接访问 ChatGPT 托管的客户端元数据。若当前机器必须通过受信任的出站代理访问外网,请配置:
FASTMCP_SSRF_TRUST_PROXY=true
HTTPS_PROXY=http://127.0.0.1:7897然后彻底停止并重新启动服务。程序会在导入 FastMCP 之前自动加载项目根目录的 .env;FastMCP 默认会对 CIMD/JWKS 请求进行 DNS 校验和 IP 固定,因此不会自动使用普通代理环境变量。开启该选项后,会把 SSRF 防护责任交给指定代理,并忽略 NO_PROXY。仅可对可信代理开启。
日志中的首次 POST /mcp 401 是客户端在认证前探测受保护资源;对多个 /.well-known/... 地址的 404 也是 ChatGPT 的兼容性探测。只要 /.well-known/oauth-authorization-server 返回 200,它们不是本次失败原因。
如果日志显示 Unregistered client_id=app_... 或其他非 URL client ID,说明 ChatGPT 缓存了已从服务端存储中丢失的旧 DCR 注册。固定 JWT_SIGNING_KEY、重启服务,然后在 ChatGPT 中删除并重新创建该自定义 MCP,使其重新调用 /register。仅重试登录不会恢复服务端未知的旧 client ID。
测试
uv run pytestThis server cannot be deployed
Maintenance
Related MCP Connectors
- mcp-serverOAuthio.klokin
MCP server exposing klokin time-tracking operations (employees, time entries, stores) to AI clients.
MCP server for AI access to Swagger by SmartBear.
MCP server for Product Management
Read-only MCP server for ClassQuill, a tutoring-business-management platform.