返回市场
HTTP-OAuth-MCP服务器

HTTP-OAuth-MCP服务器

作者:NapthaAI97 星标更新:2025-05-08

项目介绍

🌊 支持OAuth的HTTP + SSE MCP服务器

简介

此仓库提供了一个参考实现,用于创建支持流式HTTP和SSE传输的远程MCP服务器,并通过基于OAuth的身份验证来授权,符合MCP规范。

请注意,本仓库中的MCP服务器在逻辑上与处理报告SSE+HTTP传输的应用程序以及OAuth是分开的。

因此,您可以轻松地分叉这个仓库,并插入您自己的MCP服务器和OAuth凭证,以构建一个具有您自己功能的运行中的SSE/HTTP+OAuth MCP服务器。

但是,为什么?

非常好的问题!MCP规范于2025年3月25日添加了基于OAuth的授权规范。截至2025年5月1日:

  • TypeScript SDK包含了大量用于实现带有流式HTTP的OAuth授权MCP服务器的构建模块,但没有关于如何构建此类服务器的文档或教程
  • Python SDK既没有实现流式HTTP传输,也没有实现TypeScript SDK中存在的OAuth构建模块
  • 流式HTTP传输在诸如Cursor和Claude桌面这样的MCP主机应用程序中普遍不受支持,尽管它可以直接集成到使用JS/TS SDK的StreamableHttpClientTransport类编写的代理中

Naptha AI,我们确实想构建一个基于流式HTTP传输的OAuth授权MCP服务器,但找不到任何参考实现,所以我们决定自己构建一个!

依赖项

Bun,一种快速的一体化JavaScript运行时,是推荐的运行时和包管理器。对npm+tsc进行了有限的兼容性测试。

概览

此仓库提供了以下内容:

  1. 一个MCP服务器,您可以轻松替换为您自己的服务器。
  2. 一个express.js应用程序,该应用程序管理SSE和流式HTTP传输以及OAuth授权。

这个express应用程序就是您插入凭证和MCP服务器的地方。

请注意,虽然此express应用实现了所需的OAuth端点,包括/authorize和授权服务器元数据端点(RFC8414),但它不实现OAuth授权服务器

此示例将OAuth代理到支持动态客户端注册的上游OAuth服务器(RFC7591)。要使用此示例,您需要自带授权服务器。我们建议使用Auth0;请参阅下面的"设置OAuth"部分

配置您的服务器

关于OAuth及动态客户端注册的注意事项

要使用此示例,您需要一个OAuth授权服务器。**不要自行实现!**为了创建我们的演示,我们使用了Auth0——这是一个很好的选择,尽管还有许多其他选项。

MCP规范要求支持一种不常见的OAuth特性,即RFC7591,动态客户端注册。MCP规范规定,MCP客户端和服务器应支持动态客户端注册协议,以便MCP客户端(无论客户端传输位于何处)可以在无需用户注册的情况下获取客户端ID。这允许新客户端(代理、应用程序等)自动注册到新的服务器。更多细节可以在MCP规范的授权部分找到,这意味着不幸的是,您不能简单地直接代理到Google或GitHub这样的提供商,因为它们不支持动态客户端注册(它们要求您在其UI中注册客户端)。

这给您留下了两个选项:

  1. 选择像Auth0这样的上游OAuth提供商,它允许您使用如Google和GitHub这样的OIDC IDP进行身份验证,并且确实支持动态客户端注册,或者
  2. 在应用程序本身中实现动态客户端注册(即,express应用程序不仅是一个简单的OAuth代理,而是一个完整的或部分完整的OAuth服务器)。Cloudflare为他们的Workers OAuth MCP服务器实现了类似的东西,我们可能会在此项目中扩展它。您可以在这里找到它。

为了简化,我们选择了前者,使用Auth0。

[!NOTE] 由于此实现代理了上游OAuth服务器,默认情况下将OAuth服务器的访问令牌转发给客户端会暴露用户的上游访问令牌给下游客户端及MCP主机。这对于许多用例来说不合适,因此这种方法重新实现了某些@modelcontextprotocol/typescript-sdk类来解决这个问题。

请注意,虽然我们在代理上游授权服务器,但我们不会将最终用户的认证令牌返回给MCP客户端/主机——相反,我们会发出我们自己的令牌,并允许客户端/主机使用该令牌与我们的服务器进行授权。这可以防止恶意客户端或主机滥用令牌,或者如果令牌泄露被滥用。

使用Auth0设置OAuth

开始使用Auth0:

  1. Auth0.com创建一个Auth0账户。
  2. 至少创建一个连接到IDP(如Google或GitHub)的连接。您可以在这里学习如何操作
  3. 将连接提升为域级连接。由于每个MCP客户端都会注册新的OAuth客户端,您无法按应用程序/客户端配置您的IDP连接。这意味着您的连接需要对您域内的所有应用程序可用。您可以在这里学习如何操作
  4. 启用动态客户端注册(Auth0也称之为“动态应用程序注册”)。您可以在这里学习如何操作

一旦所有这些都已设置好,您将需要以下信息:

  • 您的Auth0客户端ID
  • 您的Auth0客户端密钥
  • 您的Auth0租户域名

确保将这些信息填入您的.env。复制.env.template并更新值以匹配您的配置和密钥。

运行服务器

此仓库包括两个独立的服务器:

  • src/app.stateless.ts处实现的无状态流式HTTP服务器。仅支持流式HTTP传输,并且(理论上)适合无服务器部署。
  • src/app.stateful.ts处实现的有状态SSE和流式HTTP。此应用程序提供两种传输方式,但在使用redis存储策略时仍会维护内存状态(连接必须在内存中持久化),因此不适合无服务器部署或简单的水平扩展。

您可以使用bun运行其中任何一个:

bun run src/app.stateless.ts
# 或,
bun run src/app.stateful.ts

综合测试

要测试我们的支持流式HTTP和OAuth的MCP服务器,您有几个选项。

如上所述,Python MCP SDK不支持这些功能,因此目前您可以将我们的远程服务器插入到类似于Cursor或Claude Desktop的MCP主机中,或者直接插入到TypeScript/JavaScript应用程序中——但不能插入到Python应用程序中。

将您的服务器插入MCP主机(Cursor / Claude)

由于大多数MCP主机都不支持流式HTTP(在多个方面优于SSE)或OAuth,我们建议使用mcp-remotenpm包,该包将处理OAuth授权,并将远程传输桥接到主机的STDIO传输。

命令如下所示:

bunx mcp-remote --transport http-first https://some-domain.server.com/mcp
# 或,
npx mcp-remote --transport http-first https://some-domain.server.com/mcp

您有几个选项用于--transport选项:

  • http-first(默认):首先尝试HTTP传输,如果HTTP因404错误失败,则回退到SSE
  • sse-first:首先尝试SSE传输,如果SSE因405错误失败,则回退到HTTP
  • http-only:仅使用HTTP传输,如果服务器不支持则失败
  • sse-only:仅使用SSE传输,如果服务器不支持则失败

[!NOTE] 如果您使用src/app.stateless.ts启动无状态版本的服务器,SSE传输不可用,因此您应该使用--transport http-only。如果您使用此入口点,不应期望SSE传输能正常工作。

将您的服务器插入代理

您可以使用StreamableHTTPClientTransport将您的流式HTTP服务器插入到JS/TS代理中。然而,这不会与受OAuth保护的服务器一起工作。相反,您应在客户端侧使用Authorization头,并在服务器侧使用有效的访问令牌。

您可以使用客户端凭据、API密钥或其他方法实现这一点。这种模式在此仓库中未得到支持,但使用Vercel AI SDK可以这样实现:

import { openai } from '@ai-sdk/openai';
import { experimental_createMCPClient as createMcpClient, generateText } from 'ai';
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const mcpClient = await createMcpClient({
  transport: new StreamableHTTPClientTransport(
    new URL("http://localhost:5050/mcp"), {
      requestInit: {
        headers: {
          Authorization: "Bearer YOUR TOKEN HERE",
      }, 
    },
    // TODO 添加OAuth客户端提供商,如果您想要的话
    authProvider: undefined,
  }),
});

const tools = await mcpClient.tools();
await generateText({
  model: openai("gpt-4o"),
  prompt: "Hello, world!",
  tools: {
    ...(await mcpClient.tools())
  }
});