mcp服务器网桥
配置驱动的OAuth 2.1网桥,用于在任何OAuth 2.0提供程序上构建MCP服务器。
提供一个描述提供者OAuth端点的单一配置对象,您将获得一个完全兼容的 模型上下文协议 具有PKCE、动态客户端注册、每个用户凭据隔离、自动令牌刷新和结构化错误处理的服务器——没有样板。
运作原理
+-----------+ +------------------+ +--------------+
|MCP Client | |mcp-server-bridge | | Provider API |
+-----+-----+ +--------+---------+ +------+-------+
| | |
| (A) Authorization | |
| | |
| --- authorize (PKCE) -----> | |
| | --- redirect to login ----> |
| | |
| | | |
| | --- authenticated req ----> |
| | {
try {
const data = await client.request(
'/crm/v3/objects/contacts',
{ limit: String(limit || 10) },
);
return { content: [{ type: 'text', text: JSON.stringify(data.results) }] };
} catch (err) {
return formatToolError(err);
}
},
);
return server;
}3.启动服务器
// src/index.ts
import { createBridgeServer } from 'mcp-server-bridge';
import { config } from './provider.config.js';
import { createServer } from './server.js';
const { start } = createBridgeServer({
config,
createMcpServer: (client) => createServer(client),
});
start();三个文件,你就有了一个生产就绪的MCP服务器。
提供者配置参考
| 字段 | 类型 | 必填 | 描述 | |
|---|---|---|---|---|
name | string | 是 | 人类可读的提供者名称 | |
auth.authorizeUrl | string | 是 | 提供商的OAuth授权端点 | |
auth.tokenUrl | string | 是 | 提供商的OAuth令牌端点 | |
auth.scopes | string[] | 是 | 请求范围 | |
auth.scopeDelimiter | string | 无 | 用于在授权URL中加入作用域的分隔符(默认值: ' ' 根据RFC 6749第3.3节)。设置为 ',' 对于像Zoho这样使用逗号分隔作用域的提供者。 | |
auth.tokenContentType | `'form' \ | 'json'` | 否 | 令牌交换请求的内容类型(默认值: 'form').设置为 'json' 对于像Notion和Linear这样需要JSON的提供商。 |
auth.clientAuthMethod | `'body' \ | 'basic'` | 否 | 如何在令牌交换中发送客户端凭据(默认值: 'body').设置为 'basic' 对于需要HTTP基本身份验证的提供商(例如Stripe)。 |
auth.extraAuthorizeParams | Record | 否 | 用于授权重定向的额外查询参数(例如。 { access_type: 'offline' }) | |
env.clientId | string | Yes | 包含客户端ID的env变量的名称 | |
env.clientSecret | string | Yes | 持有客户端机密的env变量的名称 | |
callbackPathSegment | string | 是 | 回调路由的URL段("zoho" → /oauth/zoho/callback) | |
apiBaseUrl | string | 是 | 提供商的API基础URL | |
fetchUserIdentity | (accessToken: string) => Promise | 是 | 在OAuth交换后获取用户信息 | |
authorizeUser | `(identity: UserIdentity) => string \ | null` | 否 | 返回null表示允许,返回错误消息表示拒绝 |
tokenNeverExpires | boolean | 否 | 设置为 true 对于具有非到期令牌的提供商(请参见 提供商兼容性) | |
refreshTokenUrl | string | 否 | 令牌刷新端点(如果不同) tokenUrl | |
mcpServer | { name: string; version: string } | 否 | MCP服务器元数据 | |
m2m | { getProviderCredentials, scopes? } | 否 | 机器到机器配置(请参阅 M2M认证) |
提供商兼容性
OAuth提供者以可预测的方式偏离规范。该桥通过配置选项处理常见的变化:
非到期代币
ClickUp、Notion、Linear、Todoist、Figma和Slack等提供商发行永不过期的访问令牌,并且不提供刷新令牌。集 tokenNeverExpires: true 要处理此问题:
{
tokenNeverExpires: true,
// The callback handler will accept responses without a refresh_token.
// Tokens are stored with a far-future expiry.
// 401 responses throw ProviderAuthError instead of attempting refresh.
}JSON令牌交换
Notion和Linear等提供商要求令牌交换体为JSON,而不是 application/x-www-form-urlencoded:
{
auth: {
tokenContentType: 'json',
// ...
},
}令牌交换的基本身份验证
像Stripe这样的提供商通过HTTP Basic auth标头而不是请求正文发送客户端凭据:
{
auth: {
clientAuthMethod: 'basic',
// ...
},
}自定义范围分隔符
大多数提供者根据RFC 6749使用空格分隔的作用域。Zoho使用逗号:
{
auth: {
scopeDelimiter: ',',
scopes: ['ZohoCRM.modules.ALL', 'ZohoCRM.settings.ALL'],
// ...
},
}额外授权参数
一些提供商要求在授权重定向上添加其他参数(例如谷歌的 access_type: 'offline' 获取刷新令牌):
{
auth: {
extraAuthorizeParams: { access_type: 'offline', prompt: 'consent' },
// ...
},
}机器对机器身份验证
对于非交互式客户端(CI管道、无头代理),网桥支持 client_credentials 授权类型。使用配置 m2m 选项:
const config: ProviderConfig = {
// ... standard config ...
m2m: {
// Return provider credentials for M2M access.
// These might come from a service account, a stored token, etc.
async getProviderCredentials() {
return {
accessToken: process.env.SERVICE_ACCOUNT_TOKEN!,
refreshToken: process.env.SERVICE_ACCOUNT_REFRESH!,
expiresIn: 3600,
};
},
// Optional: restrict M2M clients to a subset of scopes
scopes: ['read'],
},
};M2M客户端通过POSTing进行身份验证 /token 和 grant_type=client_credentials:
curl -X POST https://your-bridge.example.com/token \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET注: MCP SDK的令牌处理程序不支持client_credentials服务器端,因此网桥安装了一个瘦中间件,在SDK处理程序运行之前拦截此授权类型。所有其他补助类型(authorization_code,refresh_token)以不变的方式传递到SDK。
自定义存储后端
默认情况下,网桥使用具有JSON文件持久性的内存映射(通过配置 OAUTH_STORE_PATH).对于需要横向扩展或持久性的生产部署,您可以注入自己的存储:
import { createBridgeServer } from 'mcp-server-bridge';
import type { ClientsStore, TokenStore } from 'mcp-server-bridge';
const myClientsStore: ClientsStore = { /* your Redis/Postgres/DynamoDB impl */ };
const myTokenStore: TokenStore = { /* your Redis/Postgres/DynamoDB impl */ };
const { start } = createBridgeServer({
config,
createMcpServer: (client) => createServer(client),
stores: { clientsStore: myClientsStore, tokenStore: myTokenStore },
});这 ClientsStore 和 TokenStore 接口从包中导出。这 ClientsStore 接口扩展了MCP SDK OAuthRegisteredClientsStore,因此实现与SDK的注册和身份验证处理程序直接兼容。
记录类型(AuthCodeRecord, AccessTokenRecord, RefreshTokenRecord, PendingAuthRecord)还导出以用于自定义存储实现。
API客户端
每个工具处理程序都会收到一个 ProviderApiClientInterface 有四种方法用于向提供者API发出经过身份验证的请求:
client.request(endpoint, params?) // GET
client.create(endpoint, body) // POST
client.update(endpoint, body) // PUT
client.remove(endpoint, params?) // DELETE所有方法自动执行:
- 包含承载授权标头
- 刷新401响应上的访问令牌(一次透明重试)
- 对429个速率限制(1s、2s、4s)实施指数回退
- 抛出类型错误类(见下文)
错误处理
该桥提供类型化错误类,因此工具可以返回AI代理可以推理的结构化错误:
| 类 | 代码 | 属性 | 抛出时 | |
|---|---|---|---|---|
ProviderAuthError | PROVIDER_AUTH_ERROR | -- | 令牌交换或刷新失败 | |
ProviderRateLimitError | PROVIDER_RATE_LIMIT | `retryAfter: number \ | null` | 429在所有重试尝试均已尝试完毕后 |
ProviderApiError | PROVIDER_API_ERROR | statusCode, responseBody | 来自提供商的非-2xx响应 | |
ProviderNetworkError | PROVIDER_NETWORK_ERROR | -- | 网络连接失败 |
使用 formatToolError() 在您的工具捕获块中返回符合MCP的错误响应:
import { formatToolError } from 'mcp-server-bridge';
server.tool('my_tool', 'Does something', {}, async () => {
try {
const data = await client.request('/endpoint');
return { content: [{ type: 'text', text: JSON.stringify(data) }] };
} catch (err) {
return formatToolError(err);
}
});服务器路由
网桥服务器会自动挂载这些路由:
| 路线 | 方法 | 目的 |
|---|---|---|
/.well-known/oauth-authorization-server | GET | OAuth 2.1元数据发现(启用CORS) |
/.well-known/oauth-protected-resource/mcp | GET | 受保护的资源元数据(启用CORS) |
/register | POST | 动态客户端注册(速率受限) |
/authorize | GET | 授权(重定向到提供商,速率受限) |
/token | POST | 令牌端点(启用CORS,速率受限) |
/revoke | POST | 令牌吊销(启用CORS,速率受限) |
/oauth/{provider}/callback | GET | 提供程序OAuth回调 |
/health | GET | 健康检查 |
/mcp | POST | MCP传输(受承载令牌保护) |
所有OAuth端点都通过MCP SDK的内置处理程序包含速率限制和CORS支持。这 /authorize 处理程序验证重定向URI 《联邦法规》第8252条第7.3款,允许环回地址的任何端口支持本地MCP客户端。
OAuth 2.1合规性
该桥实现了OAuth 2.1和相关规范要求:
- PKCE(S256) --对所有授权代码流强制执行
- 动态客户端注册 — RFC 7591
- 代币轮换 --每次使用刷新令牌时都会轮换
- 资源指标 — RFC 8707 通过完整的身份验证流程
- 令牌撤销 — RFC 7009
- 授权服务器元数据 — RFC 8414
- 受保护的资源元数据 — RFC 9728
- 重定向URI验证 — 《联邦法规》第8252条第7.3款 具有环回端口灵活性
- 令牌响应的范围 — RFC 6749§5.1
- 提供商令牌生命周期 --MCP令牌刷新验证上游凭据是否仍然存在
环境变量
| 变量 | 必填 | 描述 |
|---|---|---|
{PROVIDER}_CLIENT_ID | 是 | OAuth应用程序客户端ID(配置中设置的var名称) |
{PROVIDER}_CLIENT_SECRET | 是 | OAuth应用程序客户端密钥(配置中设置了var名称) |
MCP_OAUTH_ISSUER | 是 | 您的服务器的公共URL(例如。 https://my-mcp.example.com) |
OAUTH_STORE_PATH | 否 | 令牌存储文件路径(默认: /data/oauth-store.json).提供定制商店时不使用。 |
PORT | 无 | 服务器端口(默认值: 3000) |
部署
服务器是一个标准的Express应用程序。在Node.js运行的任何地方部署它——铁路、Fly.io、VPS等。
要求:
- HTTPS正在生产中(OAuth 2.1要求)
MCP_OAUTH_ISSUER必须是公共HTTPS URL- 提供商OAuth应用程序的重定向URI必须与您的回调URL匹配
反向代理后面:
const { start } = createBridgeServer({
config,
createMcpServer: (client) => createServer(client),
trustProxy: 1, // Trust one level of proxy (Railway, Nginx, etc.)
});测试
npm test # Run all tests
npm run test:watch # Watch mode测试套件包括OAuth令牌存储、提供程序流、API客户端重试逻辑、令牌管理器缓存和服务器集成(元数据、注册、令牌交换、M2M、承载身份验证)。
许可证
麻省理工学院
