用于在Cloudflare Workers上构建受OAuth保护的远程MCP服务器的SDK,支持可插拔的身份验证适配器(Supabase已实现)。
简而言之:如果你正在构建一个需要用户身份验证的MCP服务器/代理,但你的身份提供商尚未提供OAuth 2.1流程(例如,截至2025年10月的Supabase),此SDK可以帮助你在反向代理基础上运行OAuth流程,并将其部署为Cloudflare Worker。 使用场景:
createOAuthProviderWithMCP, createAuthProxySupabaseAuthAdapterAppConfig, AuthAdapter, CoreBindings, TokenExchangeResult
AppConfig.loginPath? 可选登录路由(默认为"/auth/login")npm install @famma/mcp-auth
你的MCP代理应为类型agents/mcp(扩展或兼容McpAgent)。
// src/worker.ts
import { createOAuthProviderWithMCP, SupabaseAuthAdapter, type AppConfig } from "@famma/mcp-auth";
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
class MyMCP extends McpAgent {
server = new McpServer({ name: "Demo", version: "1.0.0" });
async init() {
this.server.tool("whoami", async () => ({
content: [{ type: "text", text: String(this.props?.userEmail ?? "未知用户") }],
}));
}
}
let provider: ReturnType<typeof createOAuthProviderWithMCP> | undefined;
export default {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
if (!provider) {
const appConfig: AppConfig = {
logoUrl: env.LOGO_URL ?? "https://example.com/logo.png",
companyName: env.COMPANY_NAME ?? "示例公司",
proxyTargetUrl: env.PROXY_TARGET_URL,
// 可选:自定义代理挂载的登录路由(默认为"/auth/login")
loginPath: env.LOGIN_PATH ?? "/auth/login",
};
const authAdapter = new SupabaseAuthAdapter({
supabaseUrl: env.SUPABASE_URL,
supabaseAnonKey: env.SUPABASE_ANON_KEY,
});
provider = createOAuthProviderWithMCP({
mcpAgentClass: MyMCP,
authAdapter,
appConfig,
});
}
return provider.fetch(request, env, ctx);
},
};
首先,创建一个KV命名空间用于存储令牌:
npx wrangler kv namespace create OAUTH_KV
复制输出中的id值。
创建或更新你的wrangler.jsonc,并添加上一步中的KV ID。确保绑定名称为OAUTH_KV:
{
"name": "mcp-worker",
"main": "src/worker.ts",
"compatibility_date": "2025-03-10",
"compatibility_flags": ["nodejs_compat"],
"kv_namespaces": [
{ "binding": "OAUTH_KV", "id": "<your-kv-id>" }
],
"vars": {
"COMPANY_NAME": "示例公司",
"LOGO_URL": "https://example.com/logo.png",
"PROXY_TARGET_URL": "https://your-login-host.example.com"
}
}
为了本地开发,在项目根目录中创建一个.dev.vars文件:
# .dev.vars(仅用于本地开发 - 不要提交到git)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key-here
PROXY_TARGET_URL=http://localhost:3000
注意:.dev.vars已经包含在.gitignore中,以防止意外提交敏感凭证。
对于生产部署,使用Wrangler配置所有环境变量为秘密:
# 必需的秘密
npx wrangler secret put SUPABASE_URL
npx wrangler secret put SUPABASE_ANON_KEY
npx wrangler secret put PROXY_TARGET_URL
# 本地开发(使用.dev.vars)
npx wrangler dev
# 部署到生产(使用wrangler秘密)
npx wrangler deploy
使用npx @modelcontextprotocol/inspector或npx @mcpjam/inspector@latest连接并测试你的MCP服务器。
查看examples/supabase/以获取一个完整的可运行Worker示例,其中包括:
env动态构造运行时适配器实现AuthAdapter接口,并将你的适配器传递给createOAuthProviderWithMCP。
关键职责:
getUser(c):当认证成功时返回{ id, email },否则返回null。getSession(c):返回{ accessToken, refreshToken, ... }或null。getAuthorizationProps(c, user, session):返回与OAuth令牌一起持久化的属性。包括未来刷新所需的任何内容(例如,API基础URL,客户端ID/密钥,租户)。tokenExchangeCallback({ grantType, props })(可选):执行刷新流程并返回更新的令牌。最小骨架:
import type { Context } from 'hono';
import type {
AuthAdapter,
AuthUser,
AuthSession,
CoreBindings,
TokenExchangeResult,
} from '@famma/mcp-auth';
export interface MyBindings extends CoreBindings {
// 如果你的提供商需要额外的环境绑定,请在此处添加
}
export class HeaderAuthAdapter implements AuthAdapter<MyBindings> {
async getUser(c: Context<{ Bindings: MyBindings }>): Promise<AuthUser | null> {
// 示例:从headers/cookies/session中推导用户
const userId = c.req.header('x-user-id');
const userEmail = c.req.header('x-user-email');
if (!userId) return null;
return { id: userId, email: userEmail ?? null };
}
async getSession(c: Context<{ Bindings: MyBindings }>): Promise<AuthSession | null> {
// 示例:从header/cookie中获取访问令牌;刷新令牌可选
const accessToken = c.req.header('x-access-token');
const refreshToken = c.req.header('x-refresh-token') ?? '';
if (!accessToken) return null;
return { accessToken, refreshToken };
}
async getAuthorizationProps(
_c: Context<{ Bindings: MyBindings }>,
user: AuthUser,
session: AuthSession,
): Promise<Record<string, any>> {
return {
userEmail: user.email ?? '',
userId: user.id,
accessToken: session.accessToken,
refreshToken: session.refreshToken,
// 添加未来刷新所需的提供商特定属性
providerBaseUrl: 'https://api.example.com',
clientId: 'your-client-id',
};
}
// 可选:实现刷新流程
async tokenExchangeCallback({ grantType, props }: { grantType: string; props: Record<string, any> }): Promise<TokenExchangeResult | void> {
if (grantType !== 'refresh_token') return;
const rt = props?.refreshToken as string | undefined;
if (!rt) return;
// 在此处执行你的提供商的刷新请求
// const resp = await fetch('https://api.example.com/oauth/token', { ... });
// const json = await resp.json();
const newAccess = 'NEW_ACCESS_TOKEN';
const newRefresh = rt; // 或旋转的新令牌
return {
accessTokenProps: { ...props, accessToken: newAccess },
newProps: { ...props, accessToken: newAccess, refreshToken: newRefresh },
// accessTokenTTL: json.expires_in,
};
}
}
将其集成到Worker中:
import { createOAuthProviderWithMCP, type AppConfig } from '@famma/mcp-auth';
import { McpAgent } from 'agents/mcp';
import { HeaderAuthAdapter } from './header-auth-adapter';
class MyMCP extends McpAgent { /* ...工具... */ }
export default {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
const appConfig: AppConfig = {
logoUrl: env.LOGO_URL,
companyName: env.COMPANY_NAME,
proxyTargetUrl: env.PROXY_TARGET_URL,
// 可选:自定义登录路由(默认为"/auth/login")
loginPath: env.LOGIN_PATH ?? "/auth/login",
};
const authAdapter = new HeaderAuthAdapter();
return createOAuthProviderWithMCP({
mcpAgentClass: MyMCP,
authAdapter,
appConfig,
}).fetch(request, env, ctx);
}
}
完整的可运行示例在examples/custom-adapter/中。
import {
createOAuthProviderWithMCP,
createAuthProxy,
SupabaseAuthAdapter,
type SupabaseAdapterConfig,
type SupabaseBindings,
type AppConfig,
type AuthAdapter,
type CoreBindings,
type TokenExchangeResult,
} from "@famma/mcp-auth";
createOAuthProviderWithMCP({ mcpAgentClass, authAdapter, appConfig, tokenExchangeCallback? })
OAuthProvider处理器。默认使用authAdapter.tokenExchangeCallback。createAuthProxy(authAdapter, appConfig)
/authorize, /approve, loginPath(默认为"/auth/login")和反向代理的Hono应用。SupabaseAuthAdapter(config: SupabaseAdapterConfig)
supabaseUrl, supabaseAnonKey。interface AuthAdapter<TBindings = any> {
getUser(c): Promise<AuthUser | null>;
getSession(c): Promise<AuthSession | null>;
getAuthorizationProps(c, user, session): Promise<Record<string, any>>;
tokenExchangeCallback?: (args: { grantType: string; props: Record<string, any> }) => Promise<TokenExchangeResult | void>;
}
Supabase适配器实现了tokenExchangeCallback以通过Supabase轮换refresh_token。
process.env;在fetch中从env读取运行时配置。OAUTH_KV。2025-03-10或更新nodejs_compat标志)OAUTH_KV命名空间用于令牌存储注意:示例中的环境变量是可选的;你可以硬编码值,或者在Wrangler中使用vars/秘密进行生产部署。
需求:Node 18+, npm, Wrangler。
开发:
npm install
npm run build
# 示例Worker(开发)
cd examples/supabase
npx wrangler dev
# 格式化/检查
npm run format
npm run lint:fix
请在GitHub上打开问题或PR。
仓库:https://github.com/famma-ai/mcp-auth
基于Josh Warwick关于构建远程MCP服务器的全面指南,此SDK扩展了他的工作,形成了一种可重用、可插拔的适配器架构。
MIT © 2.025 Famma. 查看LICENSE以获取详细信息。