OAuth身份验证SDK

用于在Cloudflare Workers上构建具有可插拔身份验证适配器的OAuth保护远程MCP服务器的SDK。

  • by famma-ai
  • TypeScript
  • 本地部署
  • 26 星标
  • 更新于
在 GitHub 查看 famma-ai/mcp-auth

使用说明

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, 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 ?? "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"),以及反向代理。
  • 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 了解详情。