netlify/context-and-tools/netlify-mcp-servers
Claude Code and agent skill by netlify/context-and-tools
- community
Checking that the SKILL.md file is still in the repository.
Build, deploy, and secure Model Context Protocol (MCP) servers on Netlify. Use whenever the task involves creating an MCP server, exposing an app or API to AI agents as MCP tools, letting Claude / Cursor / Claude Code call a custom remote server, or adding MCP tools to an existing Netlify site. Covers the MCP SDK + Streamable HTTP transport on a Netlify Function, authentication (single shared secret vs per-user API keys with Netlify Identity), read/write safety, file uploads, and connecting clients. Use even when the user just says "MCP", "tool server for an agent", or "let an AI use my API".
Install
npx vskill@latest install netlify/context-and-tools/netlify-mcp-serversSKILL.md
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.
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.
Stack
Use the official MCP SDK with its Web-standard Streamable HTTP transport, running statelessly inside a Netlify Function.
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.
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:
That's a complete, deployable server. Everything else is tools, auth, and safety.
Browser-based clients and 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:
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.
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.
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:
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.
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
zodschemas do this) and log every tool call so you can see what the agent did —console.infoshows up in Netlify function logs.
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:
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.
File uploads
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/netlify-mcp-servers/SKILL.md
Popularity
- Repository stars
- 33
- Repository forks
- 7
- 7-day trend score
- 18
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/netlify/context-and-tools/netlify-mcp-servers)