mcpField
N
netlify-mcp-servers

netlify-mcp-servers

github.com/netlify开发473.1k 次安装6 天前更新
代码仓库

关于 netlify-mcp-servers

netlify-mcp-servers 是一项指导开发者在 Netlify Functions 上构建自有 MCP 服务的技能,供希望向 AI Agent 开放应用工具和数据的团队使用。它介绍基于官方 SDK 的无状态 HTTP 服务、认证、权限、安全及客户端连接;并区分自建服务与 Netlify 托管的平台管理服务。

主要功能

  1. 01使用官方 MCP SDK 和 HTTP 传输
  2. 02在 Netlify Function 中定义工具
  3. 03配置共享密钥或用户 API 密钥
  4. 04验证输入并设置速率限制
  5. 05采用最小权限和工具调用日志
  6. 06通过客户端连接远程 MCP 服务

适用场景

  1. 01向 AI Agent 提供应用工具和数据
  2. 02将 MCP 端点部署为 Netlify Function
  3. 03为 MCP 调用配置认证和请求限流

MCP 相关

指导创建通过 HTTP 向 MCP 客户端暴露应用工具的远程 MCP 服务,并说明客户端连接方式。内容针对自建服务,而非 Netlify 托管的 Netlify 平台管理服务。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/netlify/context-and-tools --skill netlify-mcp-servers

安装后在 Claude Code 中输入/netlify-mcp-servers确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
authentication.md
connecting-clients.md
file-uploads.md

详细介绍

Netlify MCP 服务

MCP 服务会公开工具(以及可选的资源/提示词),供 AI 客户端(Claude Desktop、Claude Code、Cursor)调用。在 Netlify 上,远程 MCP 服务就是一个通过 HTTP 使用 MCP 协议的 Netlify Function。本指南将帮助你搭建一个可用且安全的服务,并将客户端连接到它。

“Netlify MCP”指两种不同的东西——请确保你要构建的是正确的那一种。 Netlify 发布了自有的托管 MCP 服务,允许 AI 客户端代表你操作 Netlify 平台——通过你的 Netlify 帐户创建项目、触发部署、管理环境变量和基础设施。你无需编写这个服务;只需按照 Netlify 的 MCP 服务文档,将客户端指向 Netlify 托管的 MCP 服务(如需让 Agent 在你的网站上运行,请参阅 netlify-agent-runner 技能)。本指南讲的是另一种:构建你自己的 MCP 服务——在 Netlify Function 上托管一个端点,向 Agent 公开你自己的应用工具和数据。如果你的需求是“让我的 Agent 管理我的 Netlify 站点/部署/环境变量”,那就应该使用 Netlify 托管的 MCP 服务,而不是自己编写一个 Function。

同一套方案有两种用法:

  • 独立服务——仓库唯一的用途就是提供 MCP 端点(例如封装第三方 API)。
  • 添加到现有应用——在站点旁再添加一个 Function。让它的工具调用UI 和 REST 路由已经使用的同一服务/数据层,避免重复实现逻辑。

开始构建之前

请先确定一件事,因为它会影响身份验证代码:

  • 谁会调用这个服务? 只有你(个人/单用户服务)→ 使用单个共享密钥。多人各自以自己的身份操作 → 使用由 Netlify Identity 支持的每用户 API 密钥。请参阅身份验证。

如果不确定,就从单个共享密钥开始——只需几行代码,之后也可以再添加每用户密钥。除非你另有说明,否则我会默认采用这种方式。

技术栈

使用官方 MCP SDK 及其符合 Web 标准的 Streamable HTTP 传输,在 Netlify Function 中以无状态方式运行。

npm install @modelcontextprotocol/sdk zod

Netlify Function 已经支持 Web 平台——它接收一个 Request 并返回一个 Response。SDK 提供的传输基于完全相同的基本对象构建,即 WebStandardStreamableHTTPServerTransport(这是 SDK 内部运行所用的同一核心,也是 Cloudflare Workers/Deno/Bun 使用的核心):将 Request 交给它,再返回它生成的 Response 即可——无需适配器,也无需固定版本。较早的指南会使用 Node 风格的 StreamableHTTPServerTransport,再搭配 fetch-to-node 桥接层来模拟它所需的 Node req/res 对象;在 Netlify 上,这两者都不需要,而且省去它们不仅更简单,也已验证可行。

有一点需要注意,与上述内容无关:对于任何 Accept 标头中缺少 application/json 和 text/event-stream 两者的 POST 请求,传输都会返回 HTTP 406。这是 MCP 规范的要求,必须由客户端满足——收到 406 表示需要修正客户端的 Accept 标头,而不是服务器。让 SDK 负责协议,也意味着你无需手动维护 JSON-RPC 分帧或协议版本握手。

服务 Function

使用符合 Web 标准的传输只需几行代码——较早指南中的大部分内容都是 Netlify 不需要的 Node 桥接层。将它放在 netlify/functions/mcp.ts 中:

import type { Config, Context } from "@netlify/functions";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { z } from "zod";
import { checkBearer } from "../lib/mcp/bearer"; // see Authentication

function buildServer() {
  const server = new McpServer({ name: "my-mcp", version: "0.1.0" });

  server.tool(
    "get_item",
    "Fetch a single item by id. Read-only.",
    { id: z.string().describe("The item's unique id") },
    async ({ id }) => ({
      content: [{ type: "text", text: JSON.stringify(await getItem(id)) }],
    }),
  );

  return server;
}

export default async (req: Request, _context: Context) => {
  if (!checkBearer(req)) return new Response("Unauthorized", { status: 401 });

  // Stateless JSON server: it only does request/response over POST. Reject other
  // methods — a GET makes the transport open an SSE stream that never closes, which
  // a serverless function can't serve (you'll get a 502).
  if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });

  // Fresh server + transport per request, no session to persist. enableJsonResponse
  // returns one application/json body instead of an SSE stream — the right fit here.
  const server = buildServer();
  const transport = new WebStandardStreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,
  });

  // Hand over the Web Request, return the Web Response. The transport owns JSON-RPC
  // framing, body parsing (a malformed body comes back as a clean 400), and the handshake.
  await server.connect(transport);
  return transport.handleRequest(req);
};

export const config: Config = { path: "/mcp" };

这就是一个完整且可部署的服务。其余工作是添加工具、身份验证和安全措施。

基于浏览器的客户端与 CORS

Netlify Functions 不会替你添加 CORS 标头,而且上面的服务会对所有非 POST 方法返回 405——包括浏览器发送的 OPTIONS 预检请求。通常情况下这没问题:原生 MCP 客户端(Claude Code、Cursor、Claude Desktop、mcp-remote 桥接程序)不是浏览器,不会执行相同的源策略,因此完全不需要 CORS——这就是这些客户端能够正常工作,而浏览器请求却不行的原因。

只有当 MCP 客户端运行在浏览器中时,CORS 才会产生影响——例如 Web 应用跨源调用服务。此时,除非响应携带 Access-Control-Allow-Origin,否则浏览器会阻止请求;并且浏览器会先发送 OPTIONS 预检请求,该请求必须返回 2xx,且包含 Access-Control-Allow-Methods(包括 POST)和 Access-Control-Allow-Headers(包括 Authorization 和 Content-Type)。浏览器控制台中出现“blocked by CORS policy: No Access-Control-Allow-Origin header”错误,原因就在这里——这并不表示服务损坏或平台存在问题。请在函数中、检查 405 之前处理预检请求,并在 POST 响应中也回显 CORS 标头:

const CORS = {
  "Access-Control-Allow-Origin": Netlify.env.get("MCP_ALLOWED_ORIGIN") ?? "*",
  "Access-Control-Allow-Methods": "POST, OPTIONS",
  "Access-Control-Allow-Headers": "Authorization, Content-Type, Mcp-Session-Id",
};

// In the handler, before the 405 check:
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
// ...then reject other non-POST methods with 405, and add CORS to the transport's Response.

函数必须自行设置这些标头——不要把浏览器 CORS 错误当作需要上报给 Netlify 的问题,也不要通过放宽身份验证来绕开它。

定义工具

每个工具都包含一个 name、一行 description、一个 zod 输入架构,以及一个返回 { content: [...] } 的处理程序。模型能看到的只有描述和参数的 .describe() 文本——请将它们写成面向 Agent 的 API 文档:说明工具的功能、适用场景,并指出任何不可逆操作。

随着工具数量增加,请为每个工具创建独立模块,并在 buildServer() 中注册。包含许多工具的服务通常会维护一个注册表(由 { name, description, inputSchema, handler } 组成的数组),并统一接入 tools/list 和 tools/call——无论采用哪种方式,上面的传输设置都相同。

身份验证

MCP 客户端必须证明自己有权调用你的服务。每个请求都携带 Authorization: Bearer <token>;其他情况一律拒绝,并返回 401。

单个共享密钥(个人/单用户)。使用一个环境变量,并通过恒定时间比较进行校验。将其放入 netlify/lib/mcp/bearer.ts:

import { timingSafeEqual } from "node:crypto";

export function checkBearer(req: Request): boolean {
  const expected = Netlify.env.get("MCP_BEARER_TOKEN");
  if (!expected) return false;
  const match = req.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i);
  if (!match) return false;
  const a = Buffer.from(match[1]);
  const b = Buffer.from(expected);
  // Length check first because timingSafeEqual throws (RangeError) on unequal-length
  // buffers. The token is fixed-length, so the early return leaks nothing useful.
  return a.length === b.length && timingSafeEqual(a, b);
}

使用 openssl rand -hex 32 生成令牌,并将其作为机密环境变量存储。

每用户 API 密钥(多用户)。Netlify Identity 为 Web UI 提供访问控制,每位用户都可以在其中生成自己的密钥;你只存储每个密钥的哈希值(绝不存储明文),并将其与对应用户关联;每次请求时,根据密钥解析出用户,并将该用户传递给工具处理程序,以便工具以正确用户的身份执行操作。完整模式——包括架构、生成、哈希、撤销和用户解析——请参阅身份验证。

从简单的作用域控制开始。 最简单的模型是非此即彼:有效密钥可以代表其所属用户调用所有工具——这通常是合适的起点。出现具体需求时(例如只读密钥),再添加逐密钥作用域;如果应用确实需要,再扩展为逐工具作用域或角色层级。如果有人要求更完整的 RBAC 设计,请先介绍简单的基线方案,再在此基础上添加作用域,而不要一开始就把完整层级视为必需。

安全与权限

工具是提供给自主 Agent 的公共 API。请谨慎设计:

  • 只开放完成任务所需的最少权限。 将读取与写入分开,并仔细考虑是否要开放破坏性工具。一个常见且稳妥的选择是完全不提供删除工具,并将破坏性操作保留在由人工操作的界面中。
  • **对不可逆或公开的操作设置防护,**在工具描述中加入明确说明,例如:“发布前向用户展示确切文本并征得确认。”这是模型层面的软性防护,因此还应配备真正的紧急关闭机制:可随时立即撤销的令牌。
  • 将客户端凭证与后端凭证分开。 客户端向你的服务进行身份验证(bearer/API 密钥);你的服务则使用自己的密钥向数据库或第三方 API 进行身份验证。切勿将后端的超级密钥交给客户端。
  • 为后端凭证设置最小权限——使用应用密码或限定范围的令牌,而不是账户级凭证,这样即使泄露,影响也有限且可以撤销。
  • 验证输入(你的 zod 架构会执行此操作),并记录每次工具调用,以便查看 Agent 执行了什么操作——console.info 的内容会显示在 Netlify 函数日志中。

速率限制

MCP 服务是自主 Agent 可以短时间内循环调用的公共端点——务必限制调用频率。Netlify Functions 内置了声明式速率限制,因此不要自行编写计数器(每个实例的内存计数器无法跨函数实例工作——请参阅下一节)。在函数的 config 导出中添加 rateLimit 区块:

export const config: Config = {
  path: "/mcp",
  rateLimit: {
    windowSize: 60,               // time window in seconds; capped at 180
    windowLimit: 100,             // max requests per window
    aggregateBy: ["ip", "domain"], // group by ip, domain, or both
  },
};

超过限制时,平台默认返回 HTTP 429(也可以设置 action: "rewrite",并指定 to 路径,将超出的流量发送到专用页面)。函数速率限制只能配置在函数的 config 导出中,不能定义在 netlify.toml 中。

文件上传

如果工具需要 Agent 提供文件(例如要发布的图片或要附加的文档),不要通过工具调用以 base64 格式传输字节数据——这会占用大量模型上下文,并可能超出载荷限制。应向 Agent 提供一个短期有效、仅可使用一次的预签名 URL,让它通过 PUT 上传原始字节,将文件存储在 Netlify Blobs 中,并在其他工具中通过稳定的键引用文件。使用密钥环境变量作为密钥,对上传 id、content-type、大小和过期时间计算 HMAC-SHA256 签名,并以恒定时间验证签名——签名本身就是授权,因此 PUT 请求无需携带 bearer 令牌。在上传端点上,强制校验声明的 content-type 和大小,并拒绝重放。完整的三步流程(prepare_upload → PUT → finalize_upload)及代码见:文件上传。

状态无法在请求之间保留

每个请求都会新建服务和传输,而且任何一次调用都可能被分配到不同的函数实例,或分配到刚刚冷启动的实例。模块级内存不会在实例之间共享,也无法在冷启动后持久保留。因此,需要在调用之间持久保存的状态不能放在模块作用域的 Set/Map/变量中,例如上述预签名上传的单次使用/重放跟踪、幂等键、“此 id 已处理”的防护,以及手动跟踪的每用户计数器。内存防护在本地和单个温实例上看起来正确,但一旦请求由另一个实例处理,就会悄无声息地放过重放的上传(或对调用进行重复处理)。请将此类状态保存在持久化存储中——Netlify Blobs 或你的数据库——并以上传/请求 id 为键,在其中进行检查并标记。(这也是服务本身以无状态方式运行并设置 sessionIdGenerator: undefined 的原因。)

连接客户端

原生远程 MCP 支持现已成为常态;只有在备用情况下才使用 mcp-remote 桥接工具。

  • Claude Code — claude mcp add --transport http my-mcp https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"
  • Cursor — 将服务添加到 mcp.json,并设置 URL 和 Authorization 标头。
  • Claude Desktop / claude.ai — 添加自定义连接器(设置 → 连接器)。连接器面向 OAuth;对于静态 bearer 服务,mcp-remote 桥接工具是可靠的选择。
  • 备用方案(较旧的 / 仅支持 stdio 的客户端) — npx mcp-remote https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"

完整的客户端对照表以及 OAuth / 自定义连接器详解:连接客户端。

本地开发与部署

  • 运行:netlify dev 会在 http://localhost:8888/mcp 提供该函数。
  • **测试:**使用 MCP Inspector — npx @modelcontextprotocol/inspector — 通过 Streamable HTTP 连接到你的 URL,设置 Authorization: Bearer 标头,然后列出/调用工具。或者将 claude mcp add --transport http 指向 localhost URL。
  • 身份验证注意事项:Netlify Identity 在 netlify dev 下无法使用,因此必须在部署预览中测试按用户密钥进行的身份验证。请参阅 netlify-identity 技能。
  • **部署:**推送到 Git,或运行 netlify deploy --build --prod。
  • **密钥:**将令牌/密钥设为环境变量(netlify env:set MCP_BEARER_TOKEN <value> --secret)——绝不要写入代码。

通用规则

  • 绝不要将机密硬编码。将令牌、API 密钥和签名密钥存储为 Netlify 环境变量(并标记为机密)。除了泄露风险之外,将 bearer 令牌或签名密钥写入源代码(或任何会由构建发布的文件),都会触发 Netlify 的机密扫描,并导致部署失败,即使构建其他部分已成功也不例外——解决方法是将其移入机密环境变量,并在运行时通过 Netlify.env.get(...) 读取;如果令牌已提交,则应轮换令牌,而不是禁用扫描器。扫描控制选项请参阅 netlify-deploy。
  • 在函数内部,使用 Netlify.env.get("VAR") 读取环境变量,而不是 process.env。
  • 将 .netlify 添加到 .gitignore。

相关技能和参考资料

  • 身份验证 — 深入介绍单个机密与按用户 API 密钥(Identity)。
  • 连接客户端 — 完整的客户端对照表、OAuth 和自定义连接器。
  • 文件上传 — 让 Agent 通过预签名 URL 将图片/文件上传到 Netlify Blobs。
  • netlify-functions — 函数语法、路由和限制。netlify-identity — Identity 设置。netlify-database / netlify-blobs — 密钥和文件的存储位置。netlify-deploy — 部署。netlify-config — 环境变量。

评论

支持 Markdown