作为Docebo的OAuth2授权服务器代理的多租户MCP(模型上下文协议)服务器。类似于mcp.zapier.com,此服务器允许MCP客户端通过单一端点发现并验证多个Docebo租户。
MCP客户端(Claude、ChatGPT、MCP Inspector等)
↓
↓ 1. 发现:GET /mcp/riccardo-lr-test/.well-known/oauth-authorization-server
↓
mcp.docebosaas.com(此服务器)
↓ 返回:特定租户的端点
↓ 授权端点:/mcp/riccardo-lr-test/oauth2/authorize
↓ 令牌端点:/mcp/riccardo-lr-test/oauth2/token
↓
↓ 2. OAuth流程:GET /mcp/riccardo-lr-test/oauth2/authorize
↓ 服务器从URL路径中提取租户
↓ 转向:
↓
riccardo-lr-test.docebosaas.com/oauth2/authorize
↓
↓ 3. 用户授权,回调代码
↓
↓ 4. 令牌交换:POST /mcp/riccardo-lr-test/oauth2/token
↓ 服务器从URL路径中提取租户
↓ 使用租户配置值覆盖redirect_uri
↓ 注入租户凭据(client_id,client_secret)
↓ 代理到:
↓
riccardo-lr-test.docebosaas.com/oauth2/token
↓ 返回访问令牌
↓
↓ 5. MCP调用:POST /mcp/riccardo-lr-test
↓ 带有:Authorization: Bearer <token>
↓ 服务器调用:
↓
riccardo-lr-test.docebosaas.com/manage/v1/*
↓ 返回数据给客户端
npm install
复制示例环境文件:
cp .env.example .env
编辑.env:
# 服务器公共URL(使用ngrok URL进行本地测试)
SERVER_PUBLIC_URL=https://mcp.docebosaas.com
# 或者用于本地测试:
# SERVER_PUBLIC_URL=https://abc123.ngrok.io
PORT=3000
ALLOWED_ORIGINS=*
ALLOW_LOCAL_DEV=true
为每个Docebo租户添加凭据:
# 格式:TENANT_{UPPERCASE_TENANT_ID}_CLIENT_ID
# TENANT_{UPPERCASE_TENANT_ID}_CLIENT_SECRET
# TENANT_{UPPERCASE_TENANT_ID}_REDIRECT_URI
# 示例:租户 "riccardo-lr-test"
TENANT_RICCARDO_LR_TEST_CLIENT_ID=my-mcp-server
TENANT_RICCARDO_LR_TEST_CLIENT_SECRET=abc123secret...
TENANT_RICCARDO_LR_TEST_REDIRECT_URI=https://mcp.docebosaas.com/oauth/callback
# 示例:租户 "acme-corp"
TENANT_ACME_CORP_CLIENT_ID=acme-oauth-app
TENANT_ACME_CORP_CLIENT_SECRET=xyz789secret...
TENANT_ACME_CORP_REDIRECT_URI=https://mcp.docebosaas.com/oauth/callback
重要:REDIRECT_URI必须与在Docebo OAuth2应用中注册的一致。服务器会在令牌交换期间用此值覆盖客户端的redirect_uri,以确保与MCP客户端兼容。
注意:租户ID格式转换:
riccardo-lr-testRICCARDO_LR_TEST对于每个租户,在Docebo中创建一个OAuth2应用:
https://mcp.docebosaas.com/callback(或您的公共URL)api.env文件中⚠️ 警告:这是仅供测试的概念验证实现。不适合生产使用。
此服务器实现了虚拟DCR层,允许OAuth客户端(如MCP Inspector、ChatGPT、Claude Desktop)动态“注册”自己,即使Docebo本身不支持RFC 7591动态客户端注册。
POST /mcp/<租户>/oauth2/register,携带客户端元数据client_id(UUID),并将映射存储在virtual-clients.txt中client_id和client_secret给客户端# 1. 注册新客户端
curl -X POST 'https://abc123.ngrok.io/mcp/riccardo-lr-test/oauth2/register' \
-H 'Content-Type: application/json' \
-d '{
"client_name": "我的MCP客户端",
"redirect_uris": ["http://localhost:8080/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}'
# 响应:
{
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"client_secret": "abc123...",
"client_id_issued_at": 1234567890,
...
}
# 2. 在OAuth流程中使用虚拟client_id
# 服务器将自动翻译为实际租户凭据
虚拟客户端映射存储在virtual-clients.txt(纯文本,已git忽略)中:
# 格式:虚拟client_id|租户id|创建时间|客户端名称|重定向URI
550e8400-e29b-41d4-a716-446655440000|riccardo-lr-test|2025-10-22T10:00:00Z|我的MCP客户端|http://localhost:8080/callback
**不要在生产环境中使用此实现。**它存在严重的安全限制:
要在生产环境中使用DCR,您需要:
Docebo不支持RFC 7591动态客户端注册。某些MCP客户端(如ChatGPT、Claude Desktop)可能期望支持DCR。此概念验证允许您在本地测试这些客户端,而无需修改它们,即使Docebo本身不支持DCR。
对于生产部署,请考虑:
启动ngrok隧道:
ngrok http 3000
更新.env中的ngrok URL:
SERVER_PUBLIC_URL=https://abc123.ngrok.io
启动开发服务器:
npm run dev
服务器将在以下地址可用:
https://abc123.ngrok.io(公开)http://localhost:3000(本地)配置MCP客户端(例如,MCP Inspector)指向您服务器的特定租户端点:
# MCP Inspector
npx @modelcontextprotocol/inspector \
--transport http \
--server-url https://abc123.ngrok.io/mcp/riccardo-lr-test
关键点:
/mcp/<租户id>(非查询字符串)/mcp/<租户id>/.well-known/oauth-authorization-server自动发现OAuth2端点注意:Claude Desktop目前仅支持stdio传输(不支持HTTP),因此还不能连接到此服务器。使用MCP Inspector进行测试。
所有端点都是特定于租户的,并且包含URL路径中的租户ID。
GET /mcp/<租户id>/.well-known/oauth-authorization-server(RFC 8414)返回特定租户的OAuth2授权服务器元数据:
{
"issuer": "https://mcp.docebosaas.com/mcp/riccardo-lr-test",
"authorization_endpoint": "https://mcp.docebosaas.com/mcp/riccardo-lr-test/oauth2/authorize",
"token_endpoint": "https://mcp.docebosaas.com/mcp/riccardo-lr-test/oauth2/token",
"scopes_supported": ["api"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token", "password"],
"code_challenge_methods_supported": ["S256"]
}
GET /mcp/<租户id>/.well-known/oauth-protected-resource(RFC 9728)返回特定租户的受保护资源元数据:
{
"resource": "https://mcp.docebosaas.com/mcp/riccardo-lr-test",
"authorization_servers": ["https://mcp.docebosaas.com/mcp/-riccardo-lr-test"]
}
GET /mcp/<租户id>/oauth2/authorize代理OAuth2授权请求到Docebo租户。
参数:
client_id,response_type,redirect_uri,scope,state,code_challenge等流程:
https://{租户}.docebosaas.com/oauth2/authorizecode回传POST /mcp/<租户id>/oauth2/token代理OAuth2令牌请求到Docebo租户。
正文参数(application/x-www-form-urlencoded):
grant_type:authorization_code,refresh_token或passwordcode:授权码(对于authorization_code授权)redirect_uri:客户端的重定向URI(服务器用租户配置值覆盖)code_verifier:PKCE验证器(可选)流程:
redirect_uriclient_id和client_secrethttps://{租户}.docebosaas.com/oauth2/token重要:服务器自动用租户配置的REDIRECT_URI覆盖redirect_uri参数,以确保与Docebo注册一致。
POST /mcp/<租户id>特定租户的MCP JSON-RPC端点。
头信息:
Authorization: Bearer <access_token>Content-Type: application/json示例请求:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "docebo.list_users",
"arguments": {
"page_size": 10
}
}
}
docebo.list_users列出Docebo LMS中的用户。
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
page | number | 页码(从1开始) |
page_size | number | 每页用户数(默认:200,最大:200) |
sort_attr | string | 排序属性(例如,“user_id”,“username”) |
sort_dir | string | 排序方向:“asc”或“desc” |
search_text | string | 用户名或电子邮件搜索过滤器 |
curl https://abc123.ngrok.io/.well-known/oauth-authorization-server | jq .
测试发现端点:
curl https://abc123.ngrok.io/mcp/riccardo-lr-test/.well-known/oauth-authorization-server | jq .
启动授权(将此URL粘贴到浏览器中):
https://abc123.ngrok.io/mcp/riccardo-lr-test/oauth2/authorize?client_id=test&response_type=code&redirect_uri=https://abc123.ngrok.io/oauth/callback&scope=api&code_challenge=CHALLENGE&code_challenge_method=S256
交换授权码获取令牌(授权后):
curl -X POST https://abc123.ngrok.io/mcp/riccardo-lr-test/oauth2/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=authorization_code" \
-d "code=<授权码>" \
-d "code_verifier=<验证器>" \
-d "redirect_uri=https://abc123.ngrok.io/oauth/callback"
调用MCP端点:
TOKEN="<步骤3中的访问令牌>"
curl -X POST "https://abc123.ngrok.io/mcp/riccardo-lr-test" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "docebo.list_users",
"arguments": {"page_size": 5}
}
}'
SERVER_PUBLIC_URL=https://mcp.docebosaas.com
PORT=3000
ALLOWED_ORIGINS=*
ALLOW_LOCAL_DEV=false
# 添加所有租户凭据
TENANT_<ID>_CLIENT_ID=...
TENANT_<ID>_CLIENT_SECRET=...
npm run build
npm start
ALLOW_LOCAL_DEV=false