netlify-mcp-servers
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNetlify 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 zodA Netlify Function already speaks the web platform — it receives a and returns a . The SDK ships a transport built on exactly those primitives, (the same core the SDK runs on internally, and what Cloudflare Workers / Deno / Bun use): you hand it the and return the it produces — no adapter, no version pin. Older guides reach for the Node-flavored plus a bridge to synthesize the Node / objects it expects; on Netlify you need neither, and skipping them is both simpler and what's verified to work here.
RequestResponseWebStandardStreamableHTTPServerTransportRequestResponseStreamableHTTPServerTransportfetch-to-nodereqresOne gotcha, independent of all this: the transport returns HTTP 406 to any POST whose header lacks both and . That's an MCP-spec requirement the client must satisfy — a 406 means fix the client's 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.
Acceptapplication/jsontext/event-streamAccept使用官方MCP SDK及其基于Web标准的可流式HTTP传输,以无状态方式运行在Netlify Function中。
bash
npm install @modelcontextprotocol/sdk zodNetlify Function本身已支持Web平台——接收并返回。SDK提供了基于这些原生对象构建的传输层(与Cloudflare Workers / Deno / Bun使用的核心一致):只需传入并返回其生成的即可,无需适配器或版本锁定。旧指南会使用Node风格的加上桥接来生成Node的/对象,但在Netlify上你无需这些操作,跳过它们不仅更简单,也是经验证可行的方案。
RequestResponseWebStandardStreamableHTTPServerTransportRequestResponseStreamableHTTPServerTransportfetch-to-nodereqres需要注意一个问题:任何头同时缺少和的POST请求,传输层都会返回HTTP 406。这是MCP规范对客户端的要求——出现406错误意味着需要修复客户端的头,而非修改服务器。让SDK负责协议处理,也意味着你无需手动维护JSON-RPC框架或协议版本握手逻辑。
Acceptapplication/jsontext/event-streamAcceptThe 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.tstypescript
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.tstypescript
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 preflight a browser sends. That's fine for the normal case: native MCP clients (Claude Code, Cursor, Claude Desktop, the 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.
OPTIONSmcp-remoteIt 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 , and it first sends an preflight that must come back with (including ) and (including and ). 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:
Access-Control-Allow-OriginOPTIONS2xxAccess-Control-Allow-MethodsPOSTAccess-Control-Allow-HeadersAuthorizationContent-Typetypescript
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错误——包括浏览器发送的预检请求。这在常规情况下并无问题:原生MCP客户端(Claude Code、Cursor、Claude Desktop、桥接工具)并非浏览器,不遵循同源策略,因此完全不需要CORS——这也是这些客户端能正常工作而浏览器请求无法运行的原因。
OPTIONSmcp-remote只有当你的MCP客户端在浏览器中运行时(跨域调用服务器的Web应用),CORS才会成为问题。此时浏览器会阻止请求,除非响应包含头,且浏览器会先发送预检请求,该请求必须返回状态码,并携带(包含)和(包含和)头。浏览器控制台中出现“blocked by CORS policy: No Access-Control-Allow-Origin header”错误即为此类问题——并非服务器故障或平台bug。需在函数中405检查之前处理预检请求,并在POST响应中也添加CORS头:
Access-Control-Allow-OriginOPTIONS2xxAccess-Control-Allow-MethodsPOSTAccess-Control-Allow-HeadersAuthorizationContent-Typetypescript
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 , a one-line , a input schema, and a handler that returns . The description and parameter 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.
namedescriptionzod{ content: [...] }.describe()As the count grows, give each tool its own module and register them in . Servers with many tools often keep a registry (an array of ) and wire + once — the transport setup above is identical either way.
buildServer(){ name, description, inputSchema, handler }tools/listtools/call每个工具包含、单行、输入模式,以及返回的处理函数。描述和参数的文本是模型唯一能看到的内容——请像编写Agent的API文档一样撰写:说明工具功能、适用场景,并标注任何不可逆操作。
namedescriptionzod{ content: [...] }.describe()随着工具数量增加,可为每个工具创建独立模块,并在中注册。包含大量工具的服务器通常会维护一个注册表(数组),并一次性配置 + ——上述传输层设置无需更改。
buildServer(){ name, description, inputSchema, handler }tools/listtools/callAuthentication
身份验证
The MCP client must prove it's allowed to call your server. Every request carries ; reject anything else with a 401.
Authorization: Bearer <token>Single shared secret (personal / single-user). One env var, compared in constant time. Put this in :
netlify/lib/mcp/bearer.tstypescript
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 and store it as a secret env var.
openssl rand -hex 32Per-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客户端必须证明自己有权调用你的服务器。每个请求需携带;否则返回401拒绝请求。
Authorization: Bearer <token>单一共享密钥(个人/单用户场景)。只需一个环境变量,通过常量时间比较验证。将代码放在中:
netlify/lib/mcp/bearer.tstypescript
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 schemas do this) and log every tool call so you can see what the agent did —
zodshows up in Netlify function logs.console.info
工具是提供给自主Agent的公开API,请谨慎设计:
- 最小权限原则。区分读操作与写操作,在暴露破坏性工具前需深思熟虑。一个常见且合理的选择是完全省略删除工具,将破坏性操作保留在人工操作的UI中。
- 保护不可逆或公开操作,在工具描述中添加明确说明——例如“向用户显示确切文本并获取确认后再发布”。这是一种软约束、模型级别的防护,因此需配合真正的应急开关:可立即吊销的令牌。
- 将客户端凭据与后端凭据分离。客户端向你的服务器进行身份验证(使用Bearer/API密钥);你的服务器使用自身的机密凭据向数据库或第三方API进行身份验证。切勿将后端的超级密钥传递给客户端。
- 使用最小权限的后端凭据——应用密码或范围受限的令牌,而非账户级凭据,这样即使泄露也能控制影响范围并可吊销。
- 验证输入(你的模式已实现此功能)并记录每次工具调用,以便查看Agent的操作——
zod输出会显示在Netlify函数日志中。console.info
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 block to the function's export:
rateLimitconfigtypescript
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 by default (or set with a path to send excess traffic to a dedicated page). Function rate limits live only in the function's export — they cannot be defined in .
429action: "rewrite"toconfignetlify.tomlMCP服务器是自主Agent可能频繁调用的公开端点——需设置调用上限。Netlify Functions内置声明式速率限制,因此无需手动实现计数器(单实例内存计数器无法跨函数实例共享——请见下一节)。在函数的导出中添加块:
configrateLimittypescript
export const config: Config = {
path: "/mcp",
rateLimit: {
windowSize: 60, // 时间窗口(秒);最大为180
windowLimit: 100, // 时间窗口内的最大请求数
aggregateBy: ["ip", "domain"], // 按IP、域名或两者分组
},
};超出限制时,平台默认返回HTTP (也可设置并指定路径,将超额流量导向专用页面)。函数速率限制仅可在函数的导出中定义——无法在中配置。
429action: "rewrite"toconfignetlify.tomlFile 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 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 carries no bearer token. On the upload endpoint, enforce the declared content-type and size and reject replays. Full three-step flow ( → → ) with code: file uploads.
PUTPUTprepare_uploadPUTfinalize_upload当工具需要Agent提供文件(例如要发布的图片、要附加的文档)时,请勿将字节以base64格式通过工具调用传递——这会占用模型的上下文空间并触发 payload 限制。相反,应为Agent提供一个短期、一次性的预签名URL用于原始字节,将文件存储在Netlify Blobs中,并在其他工具中通过稳定密钥引用该文件。使用机密环境变量作为密钥,通过HMAC-SHA256对上传ID、内容类型、大小和有效期进行签名,并通过常量时间验证——签名即为授权凭证,因此请求无需携带Bearer令牌。在上传端点中,需强制验证声明的内容类型和大小,并拒绝重放请求。完整的三步流程( → → )及代码请见:文件上传。
PUTPUTprepare_uploadPUTfinalize_uploadState 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 //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 .)
SetMapsessionIdGenerator: undefined每个请求都会创建新的服务器和传输层,且每个调用可能会分配到不同的——或冷启动的——函数实例。模块级内存不会在实例间共享,也无法在冷启动后持久化。因此,需要在调用间持久化的状态不能存储在模块作用域的//变量中:例如上述预签名上传的一次性/重放跟踪、幂等键、“已处理该ID”防护、手动跟踪的每用户计数器。内存中的防护在本地和单个热实例上看似正确,但当其他实例处理请求时,会静默允许重放上传(或重复处理调用)。需将此类状态存储在持久化存储中——Netlify Blobs或你的数据库——以上传/请求ID为键,并在其中进行检查和标记。(这也是服务器采用无状态运行、设置的原因。)
SetMapsessionIdGenerator: undefinedConnecting a client
客户端连接
Native remote-MCP support is now the norm; reach for the bridge only as a fallback.
mcp-remote- Claude Code —
claude mcp add --transport http my-mcp https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>" - Cursor — add the server to with the URL and an
mcp.jsonheader.Authorization - Claude Desktop / claude.ai — add a Custom Connector (Settings → Connectors). Connectors are OAuth-oriented; for a static-bearer server the bridge is the reliable path.
mcp-remote - 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支持现已成为主流;仅在必要时才使用桥接工具作为 fallback。
mcp-remote- Claude Code ——
claude mcp add --transport http my-mcp https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>" - Cursor —— 在中添加服务器的URL和
mcp.json头。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: serves the function at
netlify dev.http://localhost:8888/mcp - Test it: the MCP Inspector — — connect via Streamable HTTP to your URL with an
npx @modelcontextprotocol/inspectorheader and list/call tools. Or pointAuthorization: Bearerat the localhost URL.claude mcp add --transport http - Identity caveat: Netlify Identity does not work under , so per-user-key auth must be tested on a deploy preview. See the netlify-identity skill.
netlify dev - Deploy: push to Git, or .
netlify deploy --build --prod - Secrets: set tokens/keys as env vars () — never in code.
netlify env:set MCP_BEARER_TOKEN <value> --secret
- 运行:会在
netlify dev提供函数服务。http://localhost:8888/mcp - 测试:使用MCP Inspector————通过可流式HTTP连接到你的URL并携带
npx @modelcontextprotocol/inspector头,即可列出/调用工具。也可将Authorization: Bearer指向本地URL。claude mcp add --transport http - Identity注意事项:Netlify Identity无法在下工作,因此每用户密钥身份验证必须在部署预览中测试。请参考netlify-identity方案。
netlify dev - 部署:推送到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 , and rotate the token if it was committed, not to disable the scanner. See netlify-deploy for the scan controls.
Netlify.env.get(...) - Inside functions, read env vars with , not
Netlify.env.get("VAR").process.env - Add to
.netlify..gitignore
- 切勿硬编码机密信息。将令牌、API密钥和签名机密存储为Netlify环境变量(标记为机密)。除泄露风险外,写入源代码(或构建过程中发布的任何文件)的Bearer令牌或签名机密会触发Netlify的机密扫描并导致部署失败,即使构建本身无其他问题——解决方法是将其移至机密环境变量并在运行时通过读取,若令牌已提交则需轮换,不要禁用扫描器。请参考netlify-deploy方案了解扫描控制。
Netlify.env.get(...) - 在函数中,使用读取环境变量,而非
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——环境变量配置。