使用说明
Famma AI-MCP认证
用于在Cloudflare Workers上构建OAuth保护的远程MCP服务器的SDK,带有可插拔的身份验证适配器(已实现Supabase)。
这是给谁的?
TL;DR:如果您正在构建需要用户身份验证的MCP服务器/代理,但您的身份提供商尚未提供OAuth 2.1流(例如,截至2025年10月的Supabase),则此SDK可帮助您在基于反向代理的OAuth流后面运行MCP,并将其部署为Cloudflare Worker。 在以下情况下使用此选项:
- 您有一个MCP代理/服务器,每个用户都需要经过身份验证的访问权限。
- 您的IdP缺少适合您当前用例的OAuth 2.1流。
- 您需要一个带有可插拔身份验证适配器(包括Supabase)的Cloudflare Workers部署。
接口
- 导出的图元:
createOAuthProviderWithMCP,createAuthProxy - 适配器:
SupabaseAuthAdapter - 类型:
AppConfig,AuthAdapter,CoreBindings,TokenExchangeResultAppConfig.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 ?? "Unknown user") }],
}));
}
}
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 ?? "Example Co",
proxyTargetUrl: env.PROXY_TARGET_URL,
// Optional: customize the login route mounted by the proxy (default "/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);
},
};
设置和部署
1.创建KV命名空间
首先,为令牌存储创建一个KV命名空间:
npx wrangler kv namespace create OAUTH_KV
复制 id 输出值。
2.配置wrangler.json
创建或更新您的 wrangler.jsonc 使用前一步骤中的KV ID。确保保留 binding 名为 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": "Example Co",
"LOGO_URL": "https://example.com/logo.png",
"PROXY_TARGET_URL": "https://your-login-host.example.com"
}
}
3.为本地开发创建.dev.vars
为了地方发展,创建一个 .dev.vars 项目根目录中的文件:
# .dev.vars (for local development only - DO NOT commit to git)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key-here
PROXY_TARGET_URL=http://localhost:3000
备注: .dev.vars 已包含在 .gitignore 以防止敏感凭据的意外提交。
4.为生产设定秘密
对于生产部署,使用Wrangler将所有环境变量配置为机密:
# Required secrets
npx wrangler secret put SUPABASE_URL
npx wrangler secret put SUPABASE_ANON_KEY
npx wrangler secret put PROXY_TARGET_URL
5.本地运行或部署
# Local development (uses .dev.vars)
npx wrangler dev
# Deploy to production (uses wrangler secrets)
npx wrangler deploy
使用 npx @modelcontextprotocol/inspector 或 npx @mcpjam/inspector@latest 连接并测试您的MCP服务器。
示例项目
看 examples/supabase/ 对于具备以下条件的完整工作人员:
- MCP代理示例
- 您的Supabase身份验证提供者
- 运行时适配器构造来自
env - 开发变量模板和Wrangler配置
构建自定义身份验证提供程序(非Supabase)
实施 AuthAdapter 接口并将适配器传递给 createOAuthProviderWithMCP.
主要职责:
getUser(c):返回{ id, email }当经过认证时,或null.getSession(c):返回{ accessToken, refreshToken, ... }或null.getAuthorizationProps(c, user, session):返回属性以与OAuth令牌一起持久化。包括将来刷新所需的任何内容(例如,API基本URL、客户端id/secret、租户)。tokenExchangeCallback({ grantType, props })(可选):执行刷新流并返回更新的令牌。
最小骨架:
import type { Context } from 'hono';
import type {
AuthAdapter,
AuthUser,
AuthSession,
CoreBindings,
TokenExchangeResult,
} from '@famma/mcp-auth';
export interface MyBindings extends CoreBindings {
// Add any additional env bindings if your provider needs them
}
export class HeaderAuthAdapter implements AuthAdapter<MyBindings> {
async getUser(c: Context<{ Bindings: MyBindings }>): Promise<AuthUser | null> {
// Example: derive user from 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> {
// Example: access token from header/cookie; refresh token optional
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,
// Add provider-specific props needed for future refresh
providerBaseUrl: 'https://api.example.com',
clientId: 'your-client-id',
};
}
// Optional: implement refresh flow
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;
// Perform your provider's refresh request here
// const resp = await fetch('https://api.example.com/oauth/token', { ... });
// const json = await resp.json();
const newAccess = 'NEW_ACCESS_TOKEN';
const newRefresh = rt; // or a rotated token
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 { /* ...tools... */ }
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,
// Optional: customize login route (default "/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/.
API
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与Worker兼容的处理程序。用途authAdapter.tokenExchangeCallback默认情况下。
- 返回一个
createAuthProxy(authAdapter, appConfig)- 返回实现的Hono应用程序
/authorize,/approve,loginPath(默认值"/auth/login"),以及反向代理。
- 返回实现的Hono应用程序
SupabaseAuthAdapter(config: SupabaseAdapterConfig)- 要求:
supabaseUrl,supabaseAnonKey.
- 要求:
AuthAdapter合同
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 旋转 refresh_token 通过Supabase。
备注
- Cloudflare Workers不使用
process.env;从以下位置读取运行时配置env在fetch. - OAuth提供者需要
OAUTH_KV在Wrangler中配置。
兼容性和要求
- Cloudflare Workers:兼容性_日期
2025-03-10或更新 - 牧马人:v4.42+(带
nodejs_compat标志已启用) - KV:
OAUTH_KV令牌存储所需的命名空间 - Node.js:18+用于本地开发/构建工具
- TypeScript:5.9+
注:环境变量在示例中是可选的;您可以硬编码值或使用 vars/Wrangler的生产秘密。
贡献
需求:Node 18+,npm,牧马人。
发展:
npm install
npm run build
# Example worker (dev)
cd examples/supabase
npx wrangler dev
# Formatting / lint
npm run format
npm run lint:fix
请在GitHub上打开问题或PR。
链接
代表:https://github.com/famma-ai/mcp-auth
- MCP:https://modelcontextprotocol.io
- Cloudflare Workers OAuth提供程序:https://developers.cloudflare.com/workers/
- npm:https://www.npmjs.com/package/@famma/mcp认证
鸣谢
建在上面 Josh Warwick关于构建远程MCP服务器的综合指南,这个SDK将他的工作扩展到可重用、可插拔的适配器架构中。
许可证
麻省 理工© 2025年。看 LICENSE 了解详情。