netlify-mcp-servers

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Netlify MCP Servers

Netlify MCP Servers

An MCP server exposes tools (and optionally resources/prompts) that an AI client — Claude Desktop, Claude Code, Cursor — can call. On Netlify, a remote MCP server is just one Netlify Function that speaks the MCP protocol over HTTP. This skill gets you a working, secure server and connects a client to it.
"Netlify MCP" means two different things — make sure you're building the right one. Netlify publishes its own hosted MCP server that lets an AI client operate the Netlify platform on your behalf — create projects, trigger deploys, manage env vars and infrastructure through your Netlify account. You don't write that one; you point your client at Netlify's hosted MCP server per Netlify's MCP-server docs (and see the netlify-agent-runner skill for running agents against your site). This skill is the other thing: building your own MCP server — an endpoint that exposes your app's tools and data to an agent — hosted on a Netlify Function. If the ask is "let my agent manage my Netlify sites/deploys/env vars," that's the hosted Netlify MCP server, not a function you write.
The same setup works two ways:
  • Standalone server — a repo whose only job is the MCP endpoint (e.g. wrapping a third-party API).
  • Added to an existing app — one more function alongside your site. Have its tools call the same service/data layer your UI and REST routes already use, so logic isn't duplicated.
MCP服务器会暴露工具(可选包含资源/提示词)供AI客户端——Claude Desktop、Claude Code、Cursor——调用。在Netlify上,远程MCP服务器本质上就是一个通过HTTP协议实现MCP规范的Netlify Function。本方案将为你搭建一个可用且安全的服务器,并完成与客户端的连接。
“Netlify MCP”有两种不同含义,请确保你构建的是正确的那一种。 Netlify官方提供了托管式MCP服务器,可让AI客户端代表你操作Netlify平台——创建项目、触发部署、管理环境变量和基础设施。这种服务器无需你编写,只需按照Netlify的MCP服务器文档指引,将客户端指向其托管服务器即可(若要让Agent操作你的站点,可参考netlify-agent-runner方案)。而本方案则是另一种情况:构建你自己的MCP服务器——一个将你的应用工具和数据暴露给Agent的端点,托管在Netlify Function上。如果需求是“让我的Agent管理我的Netlify站点/部署/环境变量”,那对应的是Netlify托管式MCP服务器,而非需要你编写的函数。
本搭建方案支持两种使用方式:
  • 独立服务器——仅作为MCP端点的仓库(例如包装第三方API)。
  • 集成到现有应用——作为站点的额外函数存在。让其工具调用你的UI和REST路由已使用的同一服务/数据层,避免逻辑重复。

Before you build

构建前准备

Decide one thing up front, because it shapes the auth code:
  • Who calls this server? Just you (a personal/single-user server) → use a single shared secret. Multiple people, each acting as themselves → use per-user API keys backed by Netlify Identity. See authentication.
If you're not sure, start with the single shared secret — it's a few lines and you can layer per-user keys on later. I'll default to that unless you say otherwise.
请提前确定一件事,因为它会影响身份验证代码的编写:
  • 谁会调用该服务器? 仅你自己(个人/单用户服务器)→ 使用单一共享密钥。多用户且各自独立操作→ 使用基于Netlify Identity的每用户API密钥。详情请见身份验证
若不确定,建议从单一共享密钥开始——仅需几行代码,后续可再扩展为每用户密钥。除非你特别说明,否则默认采用该方案。

Stack

技术栈

Use the official MCP SDK with its Web-standard Streamable HTTP transport, running statelessly inside a Netlify Function.
bash
npm install @modelcontextprotocol/sdk zod
A Netlify Function already speaks the web platform — it receives a
Request
and returns a
Response
. The SDK ships a transport built on exactly those primitives,
WebStandardStreamableHTTPServerTransport
(the same core the SDK runs on internally, and what Cloudflare Workers / Deno / Bun use): you hand it the
Request
and return the
Response
it produces — no adapter, no version pin. Older guides reach for the Node-flavored
StreamableHTTPServerTransport
plus a
fetch-to-node
bridge to synthesize the Node
req
/
res
objects it expects; on Netlify you need neither, and skipping them is both simpler and what's verified to work here.
One gotcha, independent of all this: the transport returns HTTP 406 to any POST whose
Accept
header lacks both
application/json
and
text/event-stream
. That's an MCP-spec requirement the client must satisfy — a 406 means fix the client's
Accept
header, not the server. Letting the SDK own the protocol also means you don't hand-maintain JSON-RPC framing or the protocol-version handshake.
使用官方MCP SDK及其基于Web标准的可流式HTTP传输,以无状态方式运行在Netlify Function中。
bash
npm install @modelcontextprotocol/sdk zod
Netlify Function本身已支持Web平台——接收
Request
并返回
Response
。SDK提供了基于这些原生对象构建的传输层
WebStandardStreamableHTTPServerTransport
(与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框架或协议版本握手逻辑。

The server function

服务器函数

With the Web-standard transport this is a few lines — most of what older guides show was the Node bridge, which you don't need. Put it in
netlify/functions/mcp.ts
:
typescript
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" };
That's a complete, deployable server. Everything else is tools, auth, and safety.
使用Web标准传输层只需几行代码——旧指南中大部分内容都是Node桥接代码,你无需使用。将代码放在
netlify/functions/mcp.ts
中:
typescript
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"; // 请参考身份验证部分

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

  server.tool(
    "get_item",
    "根据ID获取单个条目。只读操作。",
    { id: z.string().describe("条目的唯一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 });

  // 无状态JSON服务器:仅通过POST处理请求/响应。拒绝其他请求方法
  // ——GET请求会让传输层打开一个永不关闭的SSE流,而无服务器函数无法提供此类服务(会返回502错误)。
  if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });

  // 每个请求创建新的服务器和传输层,无需持久化会话。enableJsonResponse
  // 返回单个application/json响应体而非SSE流——这是此处的最佳选择。
  const server = buildServer();
  const transport = new WebStandardStreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,
  });

  // 传入Web Request,返回Web Response。传输层负责JSON-RPC
  // 框架、请求体解析(格式错误的请求体将返回清晰的400错误)以及握手逻辑。
  await server.connect(transport);
  return transport.handleRequest(req);
};

export const config: Config = { path: "/mcp" };
这就是一个完整的可部署服务器。其余内容均围绕工具、身份验证和安全展开。

Browser-based clients and CORS

基于浏览器的客户端与CORS

Netlify Functions do not add CORS headers for you, and the server above returns 405 to every non-POST method — including the
OPTIONS
preflight a browser sends. That's fine for the normal case: native MCP clients (Claude Code, Cursor, Claude Desktop, the
mcp-remote
bridge) are not browsers and don't enforce the same-origin policy, so they need no CORS at all — which is why those clients work while a browser call doesn't.
It only matters when your MCP client runs in a browser — a web app calling the server cross-origin. Then the browser blocks the request unless the response carries
Access-Control-Allow-Origin
, and it first sends an
OPTIONS
preflight that must come back
2xx
with
Access-Control-Allow-Methods
(including
POST
) and
Access-Control-Allow-Headers
(including
Authorization
and
Content-Type
). A "blocked by CORS policy: No Access-Control-Allow-Origin header" error in the browser console is this — not a broken server or a platform bug. Answer the preflight in the function itself, before the 405 check, and echo the CORS headers on the POST response too:
typescript
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.
The function must set these headers itself — don't treat a browser CORS error as something to escalate to Netlify or route around by loosening auth.
Netlify Functions不会自动添加CORS头,且上述服务器会对所有非POST方法返回405错误——包括浏览器发送的
OPTIONS
预检请求。这在常规情况下并无问题:原生MCP客户端(Claude Code、Cursor、Claude Desktop、
mcp-remote
桥接工具)并非浏览器,不遵循同源策略,因此完全不需要CORS——这也是这些客户端能正常工作而浏览器请求无法运行的原因。
只有当你的MCP客户端在浏览器中运行时(跨域调用服务器的Web应用),CORS才会成为问题。此时浏览器会阻止请求,除非响应包含
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”错误即为此类问题——并非服务器故障或平台bug。需在函数中405检查之前处理预检请求,并在POST响应中也添加CORS头:
typescript
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",
};

// 在处理函数中,405检查之前添加:
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
// ...然后拒绝其他非POST方法并返回405,同时为传输层的Response添加CORS头。
函数必须自行设置这些头——请勿将浏览器CORS错误视为需要上报给Netlify或通过放宽身份验证来规避的问题。

Defining tools

工具定义

Each tool is a
name
, a one-line
description
, a
zod
input schema, and a handler that returns
{ content: [...] }
. The description and parameter
.describe()
text are the only thing the model sees — write them like API docs for an agent: say what the tool does, when to use it, and call out anything irreversible.
As the count grows, give each tool its own module and register them in
buildServer()
. Servers with many tools often keep a registry (an array of
{ name, description, inputSchema, handler }
) and wire
tools/list
+
tools/call
once — the transport setup above is identical either way.
每个工具包含
name
、单行
description
zod
输入模式,以及返回
{ content: [...] }
的处理函数。描述和参数的
.describe()
文本是模型唯一能看到的内容——请像编写Agent的API文档一样撰写:说明工具功能、适用场景,并标注任何不可逆操作。
随着工具数量增加,可为每个工具创建独立模块,并在
buildServer()
中注册。包含大量工具的服务器通常会维护一个注册表(
{ name, description, inputSchema, handler }
数组),并一次性配置
tools/list
+
tools/call
——上述传输层设置无需更改。

Authentication

身份验证

The MCP client must prove it's allowed to call your server. Every request carries
Authorization: Bearer <token>
; reject anything else with a 401.
Single shared secret (personal / single-user). One env var, compared in constant time. Put this in
netlify/lib/mcp/bearer.ts
:
typescript
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);
}
Generate the token with
openssl rand -hex 32
and store it as a secret env var.
Per-user API keys (multi-user). Netlify Identity gates a web UI where each user mints their own keys; you store only a hash of each key (never the plaintext) tied to that user, resolve the key to a user on every request, and flow that user into your tool handlers so tools act as the right person. Full pattern — schema, generation, hashing, revocation, resolving the user — in authentication.
Start simple with scoping. The simplest model is all-or-nothing: a valid key can call every tool as the user it belongs to — usually the right starting point. Add per-key scopes when a concrete need appears (e.g. a read-only key), and grow into per-tool scopes or role tiers if the app genuinely calls for them. If a fuller RBAC design is requested, lead with the simple baseline and layer scopes on top of it, rather than treating the full hierarchy as required up front.
MCP客户端必须证明自己有权调用你的服务器。每个请求需携带
Authorization: Bearer <token>
;否则返回401拒绝请求。
单一共享密钥(个人/单用户场景)。只需一个环境变量,通过常量时间比较验证。将代码放在
netlify/lib/mcp/bearer.ts
中:
typescript
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);
  // 先检查长度,因为timingSafeEqual会对长度不等的缓冲区抛出RangeError。令牌长度固定,因此提前返回不会泄露有用信息。
  return a.length === b.length && timingSafeEqual(a, b);
}
使用
openssl rand -hex 32
生成令牌,并将其存储为机密环境变量。
每用户API密钥(多用户场景)。Netlify Identity提供Web UI,用户可自行生成密钥;你只需存储每个密钥的哈希值(绝不存储明文)并与用户关联,在每次请求时将密钥解析为用户,并将用户信息传入工具处理函数,确保工具以正确用户身份执行操作。完整实现方案——模式、生成、哈希、吊销、用户解析——请见身份验证
从简单的权限范围开始。最简单的模型是全有或全无:有效密钥可调用该用户名下的所有工具——这通常是合适的起点。当出现具体需求时再添加密钥级权限范围(例如只读密钥),若应用确实需要,可进一步扩展为工具级权限范围或角色层级。若用户要求更完整的RBAC设计,请先从简单基线开始,再逐步添加权限范围,而非一开始就采用完整的层级结构。

Safety and permissions

安全与权限

Tools are a public API handed to an autonomous agent. Be deliberate:
  • Expose the least that does the job. Separate reads from writes, and think hard before exposing destructive tools. A common, sound choice is to omit delete tools entirely and keep destructive actions in a human-operated UI.
  • Guard irreversible or public actions by putting explicit instructions in the tool's description — e.g. "show the user the exact text and get confirmation before posting." This is a soft, model-level guard, so back it with a real kill switch: a token you can revoke instantly.
  • Keep the client's credential separate from your backend's. The client authenticates to your server (bearer/API key); your server authenticates to the database or third-party API with its own secret. Never pass your backend god-key out to the client.
  • Use least-privilege backend credentials — app passwords or scoped tokens, not account-level ones, so a leak is contained and revocable.
  • Validate inputs (your
    zod
    schemas do this) and log every tool call so you can see what the agent did —
    console.info
    shows up in Netlify function logs.
工具是提供给自主Agent的公开API,请谨慎设计:
  • 最小权限原则。区分读操作与写操作,在暴露破坏性工具前需深思熟虑。一个常见且合理的选择是完全省略删除工具,将破坏性操作保留在人工操作的UI中。
  • 保护不可逆或公开操作,在工具描述中添加明确说明——例如“向用户显示确切文本并获取确认后再发布”。这是一种软约束、模型级别的防护,因此需配合真正的应急开关:可立即吊销的令牌。
  • 将客户端凭据与后端凭据分离。客户端向你的服务器进行身份验证(使用Bearer/API密钥);你的服务器使用自身的机密凭据向数据库或第三方API进行身份验证。切勿将后端的超级密钥传递给客户端。
  • 使用最小权限的后端凭据——应用密码或范围受限的令牌,而非账户级凭据,这样即使泄露也能控制影响范围并可吊销。
  • 验证输入(你的
    zod
    模式已实现此功能)并记录每次工具调用,以便查看Agent的操作——
    console.info
    输出会显示在Netlify函数日志中。

Rate limiting

速率限制

An MCP server is a public endpoint an autonomous agent can hit in a tight loop — cap it. Netlify Functions have built-in declarative rate limiting, so don't hand-roll a counter (a per-instance in-memory counter wouldn't hold across function instances anyway — see the next section). Add a
rateLimit
block to the function's
config
export:
typescript
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
  },
};
Over the limit the platform returns HTTP
429
by default (or set
action: "rewrite"
with a
to
path to send excess traffic to a dedicated page). Function rate limits live only in the function's
config
export — they cannot be defined in
netlify.toml
.
MCP服务器是自主Agent可能频繁调用的公开端点——需设置调用上限。Netlify Functions内置声明式速率限制,因此无需手动实现计数器(单实例内存计数器无法跨函数实例共享——请见下一节)。在函数的
config
导出中添加
rateLimit
块:
typescript
export const config: Config = {
  path: "/mcp",
  rateLimit: {
    windowSize: 60,               // 时间窗口(秒);最大为180
    windowLimit: 100,             // 时间窗口内的最大请求数
    aggregateBy: ["ip", "domain"], // 按IP、域名或两者分组
  },
};
超出限制时,平台默认返回HTTP
429
(也可设置
action: "rewrite"
并指定
to
路径,将超额流量导向专用页面)。函数速率限制可在函数的
config
导出中定义——无法
netlify.toml
中配置。

File uploads

文件上传

When a tool needs the agent to supply a file (an image to post, a doc to attach), don't push the bytes through the tool call as base64 — it bloats the model's context and runs into payload limits. Instead hand the agent a short-lived, single-use presigned URL to
PUT
the raw bytes to, store them in Netlify Blobs, and reference the file by a stable key from your other tools. Sign the URL with an HMAC-SHA256 over the upload id, content-type, size, and expiry, keyed by a secret env var, and verify it in constant time — the signature is the authorization, so the
PUT
carries no bearer token. On the upload endpoint, enforce the declared content-type and size and reject replays. Full three-step flow (
prepare_upload
PUT
finalize_upload
) with code: file uploads.
当工具需要Agent提供文件(例如要发布的图片、要附加的文档)时,请勿将字节以base64格式通过工具调用传递——这会占用模型的上下文空间并触发 payload 限制。相反,应为Agent提供一个短期、一次性的预签名URL用于
PUT
原始字节,将文件存储在Netlify Blobs中,并在其他工具中通过稳定密钥引用该文件。使用机密环境变量作为密钥,通过HMAC-SHA256对上传ID、内容类型、大小和有效期进行签名,并通过常量时间验证——签名即为授权凭证,因此
PUT
请求无需携带Bearer令牌。在上传端点中,需强制验证声明的内容类型和大小,并拒绝重放请求。完整的三步流程(
prepare_upload
PUT
finalize_upload
)及代码请见:文件上传

State doesn't survive between requests

请求间状态无法持久化

Every request builds a fresh server and transport, and any invocation may land on a different — or cold-started — function instance. Module-level memory is not shared between instances and not durable across cold starts. So state you need to persist between calls cannot live in a module-scoped
Set
/
Map
/variable: single-use / replay tracking for the presigned uploads above, idempotency keys, "already processed this id" guards, per-user counters you track by hand. An in-memory guard looks correct locally and on one warm instance, then silently lets a replayed upload through (or double-processes a call) the moment another instance serves the request. Keep that state in a durable store — Netlify Blobs or your database — keyed by the upload/request id, and check-and-mark it there. (This is also why the server itself runs stateless, with
sessionIdGenerator: undefined
.)
每个请求都会创建新的服务器和传输层,且每个调用可能会分配到不同的——或冷启动的——函数实例。模块级内存不会在实例间共享,也无法在冷启动后持久化。因此,需要在调用间持久化的状态不能存储在模块作用域的
Set
/
Map
/变量中:例如上述预签名上传的一次性/重放跟踪、幂等键、“已处理该ID”防护、手动跟踪的每用户计数器。内存中的防护在本地和单个热实例上看似正确,但当其他实例处理请求时,会静默允许重放上传(或重复处理调用)。需将此类状态存储在持久化存储中——Netlify Blobs或你的数据库——以上传/请求ID为键,并在其中进行检查和标记。(这也是服务器采用无状态运行、设置
sessionIdGenerator: undefined
的原因。)

Connecting a client

客户端连接

Native remote-MCP support is now the norm; reach for the
mcp-remote
bridge only as a fallback.
  • Claude Code
    claude mcp add --transport http my-mcp https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"
  • Cursor — add the server to
    mcp.json
    with the URL and an
    Authorization
    header.
  • Claude Desktop / claude.ai — add a Custom Connector (Settings → Connectors). Connectors are OAuth-oriented; for a static-bearer server the
    mcp-remote
    bridge is the reliable path.
  • Fallback (older / stdio-only clients)
    npx mcp-remote https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"
Full client matrix and the OAuth / Custom Connector deep-dive: connecting clients.
原生远程MCP支持现已成为主流;仅在必要时才使用
mcp-remote
桥接工具作为 fallback。
  • 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
    桥接工具是可靠的解决方案。
  • Fallback(旧版/仅支持stdio的客户端) ——
    npx mcp-remote https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"
完整的客户端矩阵及OAuth/自定义连接器详解请见:客户端连接

Local dev and deploy

本地开发与部署

  • Run it:
    netlify dev
    serves the function at
    http://localhost:8888/mcp
    .
  • Test it: the MCP Inspector —
    npx @modelcontextprotocol/inspector
    — connect via Streamable HTTP to your URL with an
    Authorization: Bearer
    header and list/call tools. Or point
    claude mcp add --transport http
    at the localhost URL.
  • Identity caveat: Netlify Identity does not work under
    netlify dev
    , so per-user-key auth must be tested on a deploy preview. See the netlify-identity skill.
  • Deploy: push to Git, or
    netlify deploy --build --prod
    .
  • Secrets: set tokens/keys as env vars (
    netlify env:set MCP_BEARER_TOKEN <value> --secret
    ) — never in code.
  • 运行
    netlify dev
    会在
    http://localhost:8888/mcp
    提供函数服务。
  • 测试:使用MCP Inspector——
    npx @modelcontextprotocol/inspector
    ——通过可流式HTTP连接到你的URL并携带
    Authorization: Bearer
    头,即可列出/调用工具。也可将
    claude mcp add --transport http
    指向本地URL。
  • Identity注意事项:Netlify Identity无法
    netlify dev
    下工作,因此每用户密钥身份验证必须在部署预览中测试。请参考netlify-identity方案。
  • 部署:推送到Git,或使用
    netlify deploy --build --prod
  • 机密信息:将令牌/密钥设置为环境变量(
    netlify env:set MCP_BEARER_TOKEN <value> --secret
    )——绝不要写入代码。

Cross-cutting rules

通用规则

  • Never hardcode secrets. Store tokens, API keys, and signing secrets as Netlify environment variables (mark them secret). Beyond the leak risk, a bearer token or signing secret written into source (or any file the build publishes) trips Netlify's secrets scanning and fails the deploy even after an otherwise-green build — the fix is to move it to a secret env var and read it at runtime with
    Netlify.env.get(...)
    , and rotate the token if it was committed, not to disable the scanner. See netlify-deploy for the scan controls.
  • Inside functions, read env vars with
    Netlify.env.get("VAR")
    , not
    process.env
    .
  • Add
    .netlify
    to
    .gitignore
    .
  • 切勿硬编码机密信息。将令牌、API密钥和签名机密存储为Netlify环境变量(标记为机密)。除泄露风险外,写入源代码(或构建过程中发布的任何文件)的Bearer令牌或签名机密会触发Netlify的机密扫描并导致部署失败,即使构建本身无其他问题——解决方法是将其移至机密环境变量并在运行时通过
    Netlify.env.get(...)
    读取,若令牌已提交则需轮换,不要禁用扫描器。请参考netlify-deploy方案了解扫描控制。
  • 在函数中,使用
    Netlify.env.get("VAR")
    读取环境变量,而非
    process.env
  • .netlify
    添加到
    .gitignore

Related skills and references

相关方案与参考

  • authentication — single-secret vs per-user API keys (Identity) in depth.
  • connecting clients — full client matrix, OAuth, and Custom Connectors.
  • file uploads — letting an agent upload images/files via presigned URLs to Netlify Blobs.
  • netlify-functions — function syntax, routing, limits. netlify-identity — Identity setup. netlify-database / netlify-blobs — where to store keys and files. netlify-deploy — deploys. netlify-config — env vars.
  • 身份验证——深入介绍单一密钥与每用户API密钥(Identity)方案。
  • 客户端连接——完整的客户端矩阵、OAuth及自定义连接器。
  • 文件上传——让Agent通过预签名URL将图片/文件上传到Netlify Blobs。
  • netlify-functions——函数语法、路由、限制。netlify-identity——Identity设置。netlify-database / netlify-blobs——密钥与文件存储方案。netlify-deploy——部署方案。netlify-config——环境变量配置。