Skip to content
Deployment
Skill

/netlify-mcp-servers

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

From plugin
netlify-skills
3715 skills1 MCP
Install
$ npx -y skills add netlify/context-and-tools --skill netlify-mcp-servers --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/netlify-mcp-servers

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

netlify-mcp-servers.SKILL.md
name: netlify-mcp-servers
description: 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".

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](references/authentication.md).

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.

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.

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`:

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.

Browser-based clients and CORS

Netlify F

Read more
Ships withnetlify-skills

Public Netlify skills for AI coding agents. Each skill is a focused, factual reference for a Netlify platform primitive — designed to help agents build correctly on Netlify without needing to search docs.

Get the whole plugin

Other skills on netlify-skills.