[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-netlify-netlify-mcp-servers":3,"mdc--k0rnuw-key":33,"related-repo-netlify-netlify-mcp-servers":3940,"related-org-netlify-netlify-mcp-servers":4044},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":23,"repoUrl":24,"updatedAt":25,"license":26,"forks":27,"topics":28,"repo":29,"sourceUrl":31,"mdContent":32},"netlify-mcp-servers","build and deploy MCP servers on Netlify","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 \u002F Cursor \u002F 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\u002Fwrite 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\".",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"netlify","Netlify","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fnetlify.png",[12,14,17,20],{"name":9,"slug":8,"type":13},"tag",{"name":15,"slug":16,"type":13},"MCP","mcp",{"name":18,"slug":19,"type":13},"Agents","agents",{"name":21,"slug":22,"type":13},"API Development","api-development",24,"https:\u002F\u002Fgithub.com\u002Fnetlify\u002Fcontext-and-tools","2026-07-17T05:30:15.342368",null,5,[],{"repoUrl":24,"stars":23,"forks":27,"topics":30,"description":26},[],"https:\u002F\u002Fgithub.com\u002Fnetlify\u002Fcontext-and-tools\u002Ftree\u002FHEAD\u002Fskills\u002Fnetlify-mcp-servers","---\nname: netlify-mcp-servers\ndescription: 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 \u002F Cursor \u002F 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\u002Fwrite 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\".\n---\n\n# Netlify MCP Servers\n\nAn MCP server exposes **tools** (and optionally resources\u002Fprompts) 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.\n\n**\"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\u002Fdeploys\u002Fenv vars,\" that's the hosted Netlify MCP server, not a function you write.\n\nThe same setup works two ways:\n\n- **Standalone server** — a repo whose only job is the MCP endpoint (e.g. wrapping a third-party API).\n- **Added to an existing app** — one more function alongside your site. Have its tools call the **same service\u002Fdata layer your UI and REST routes already use**, so logic isn't duplicated.\n\n## Before you build\n\nDecide one thing up front, because it shapes the auth code:\n\n- **Who calls this server?** Just you (a personal\u002Fsingle-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\u002Fauthentication.md).\n\nIf 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.\n\n## Stack\n\nUse the official MCP SDK with its Web-standard Streamable HTTP transport, running statelessly inside a Netlify Function.\n\n```bash\nnpm install @modelcontextprotocol\u002Fsdk zod\n```\n\nA 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 \u002F Deno \u002F 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`\u002F`res` objects it expects; on Netlify you need neither, and skipping them is both simpler and what's verified to work here.\n\nOne gotcha, independent of all this: the transport returns **HTTP 406** to any POST whose `Accept` header lacks *both* `application\u002Fjson` and `text\u002Fevent-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.\n\n## The server function\n\nWith 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\u002Ffunctions\u002Fmcp.ts`:\n\n```typescript\nimport type { Config, Context } from \"@netlify\u002Ffunctions\";\nimport { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002FwebStandardStreamableHttp.js\";\nimport { z } from \"zod\";\nimport { checkBearer } from \"..\u002Flib\u002Fmcp\u002Fbearer\"; \u002F\u002F see Authentication\n\nfunction buildServer() {\n  const server = new McpServer({ name: \"my-mcp\", version: \"0.1.0\" });\n\n  server.tool(\n    \"get_item\",\n    \"Fetch a single item by id. Read-only.\",\n    { id: z.string().describe(\"The item's unique id\") },\n    async ({ id }) => ({\n      content: [{ type: \"text\", text: JSON.stringify(await getItem(id)) }],\n    }),\n  );\n\n  return server;\n}\n\nexport default async (req: Request, _context: Context) => {\n  if (!checkBearer(req)) return new Response(\"Unauthorized\", { status: 401 });\n\n  \u002F\u002F Stateless JSON server: it only does request\u002Fresponse over POST. Reject other\n  \u002F\u002F methods — a GET makes the transport open an SSE stream that never closes, which\n  \u002F\u002F a serverless function can't serve (you'll get a 502).\n  if (req.method !== \"POST\") return new Response(\"Method not allowed\", { status: 405 });\n\n  \u002F\u002F Fresh server + transport per request, no session to persist. enableJsonResponse\n  \u002F\u002F returns one application\u002Fjson body instead of an SSE stream — the right fit here.\n  const server = buildServer();\n  const transport = new WebStandardStreamableHTTPServerTransport({\n    sessionIdGenerator: undefined,\n    enableJsonResponse: true,\n  });\n\n  \u002F\u002F Hand over the Web Request, return the Web Response. The transport owns JSON-RPC\n  \u002F\u002F framing, body parsing (a malformed body comes back as a clean 400), and the handshake.\n  await server.connect(transport);\n  return transport.handleRequest(req);\n};\n\nexport const config: Config = { path: \"\u002Fmcp\" };\n```\n\nThat's a complete, deployable server. Everything else is tools, auth, and safety.\n\n## Browser-based clients and CORS\n\nNetlify 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.\n\nIt 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:\n\n```typescript\nconst CORS = {\n  \"Access-Control-Allow-Origin\": Netlify.env.get(\"MCP_ALLOWED_ORIGIN\") ?? \"*\",\n  \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Authorization, Content-Type, Mcp-Session-Id\",\n};\n\n\u002F\u002F In the handler, before the 405 check:\nif (req.method === \"OPTIONS\") return new Response(null, { status: 204, headers: CORS });\n\u002F\u002F ...then reject other non-POST methods with 405, and add CORS to the transport's Response.\n```\n\nThe 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.\n\n## Defining tools\n\nEach 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.\n\nAs 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\u002Flist` + `tools\u002Fcall` once — the transport setup above is identical either way.\n\n## Authentication\n\nThe MCP client must prove it's allowed to call your server. Every request carries `Authorization: Bearer \u003Ctoken>`; reject anything else with a 401.\n\n**Single shared secret** (personal \u002F single-user). One env var, compared in constant time. Put this in `netlify\u002Flib\u002Fmcp\u002Fbearer.ts`:\n\n```typescript\nimport { timingSafeEqual } from \"node:crypto\";\n\nexport function checkBearer(req: Request): boolean {\n  const expected = Netlify.env.get(\"MCP_BEARER_TOKEN\");\n  if (!expected) return false;\n  const match = req.headers.get(\"authorization\")?.match(\u002F^Bearer\\s+(.+)$\u002Fi);\n  if (!match) return false;\n  const a = Buffer.from(match[1]);\n  const b = Buffer.from(expected);\n  \u002F\u002F Length check first because timingSafeEqual throws (RangeError) on unequal-length\n  \u002F\u002F buffers. The token is fixed-length, so the early return leaks nothing useful.\n  return a.length === b.length && timingSafeEqual(a, b);\n}\n```\n\nGenerate the token with `openssl rand -hex 32` and store it as a secret env var.\n\n**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](references\u002Fauthentication.md).\n\n**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.\n\n## Safety and permissions\n\nTools are a public API handed to an autonomous agent. Be deliberate:\n\n- **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.\n- **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.\n- **Keep the client's credential separate from your backend's.** The client authenticates to your server (bearer\u002FAPI 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.\n- **Use least-privilege backend credentials** — app passwords or scoped tokens, not account-level ones, so a leak is contained and revocable.\n- **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.\n\n## Rate limiting\n\nAn 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:\n\n```typescript\nexport const config: Config = {\n  path: \"\u002Fmcp\",\n  rateLimit: {\n    windowSize: 60,               \u002F\u002F time window in seconds; capped at 180\n    windowLimit: 100,             \u002F\u002F max requests per window\n    aggregateBy: [\"ip\", \"domain\"], \u002F\u002F group by ip, domain, or both\n  },\n};\n```\n\nOver 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`.\n\n## File uploads\n\nWhen 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](references\u002Ffile-uploads.md).\n\n## State doesn't survive between requests\n\nEvery 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`\u002F`Map`\u002Fvariable: single-use \u002F 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\u002Frequest id, and check-and-mark it there. (This is also why the server itself runs stateless, with `sessionIdGenerator: undefined`.)\n\n## Connecting a client\n\nNative remote-MCP support is now the norm; reach for the `mcp-remote` bridge only as a fallback.\n\n- **Claude Code** — `claude mcp add --transport http my-mcp https:\u002F\u002F\u003Csite>.netlify.app\u002Fmcp --header \"Authorization: Bearer \u003Ctoken>\"`\n- **Cursor** — add the server to `mcp.json` with the URL and an `Authorization` header.\n- **Claude Desktop \u002F 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.\n- **Fallback (older \u002F stdio-only clients)** — `npx mcp-remote https:\u002F\u002F\u003Csite>.netlify.app\u002Fmcp --header \"Authorization: Bearer \u003Ctoken>\"`\n\nFull client matrix and the OAuth \u002F Custom Connector deep-dive: [connecting clients](references\u002Fconnecting-clients.md).\n\n## Local dev and deploy\n\n- **Run it:** `netlify dev` serves the function at `http:\u002F\u002Flocalhost:8888\u002Fmcp`.\n- **Test it:** the MCP Inspector — `npx @modelcontextprotocol\u002Finspector` — connect via Streamable HTTP to your URL with an `Authorization: Bearer` header and list\u002Fcall tools. Or point `claude mcp add --transport http` at the localhost URL.\n- **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.\n- **Deploy:** push to Git, or `netlify deploy --build --prod`.\n- **Secrets:** set tokens\u002Fkeys as env vars (`netlify env:set MCP_BEARER_TOKEN \u003Cvalue> --secret`) — never in code.\n\n## Cross-cutting rules\n\n- 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.\n- Inside functions, read env vars with `Netlify.env.get(\"VAR\")`, not `process.env`.\n- Add `.netlify` to `.gitignore`.\n\n## Related skills and references\n\n- [authentication](references\u002Fauthentication.md) — single-secret vs per-user API keys (Identity) in depth.\n- [connecting clients](references\u002Fconnecting-clients.md) — full client matrix, OAuth, and Custom Connectors.\n- [file uploads](references\u002Ffile-uploads.md) — letting an agent upload images\u002Ffiles via presigned URLs to Netlify Blobs.\n- **netlify-functions** — function syntax, routing, limits. **netlify-identity** — Identity setup. **netlify-database** \u002F **netlify-blobs** — where to store keys and files. **netlify-deploy** — deploys. **netlify-config** — env vars.\n",{"data":34,"body":35},{"name":4,"description":6},{"type":36,"children":37},"root",[38,46,67,120,125,157,164,169,205,210,216,221,261,336,393,399,412,1799,1804,1810,1844,1923,2250,2255,2261,2305,2342,2347,2360,2377,2928,2941,2963,2973,2979,2984,3072,3078,3106,3314,3371,3377,3468,3474,3529,3535,3547,3630,3642,3648,3774,3780,3855,3861,3934],{"type":39,"tag":40,"props":41,"children":42},"element","h1",{"id":4},[43],{"type":44,"value":45},"text","Netlify MCP Servers",{"type":39,"tag":47,"props":48,"children":49},"p",{},[50,52,58,60,65],{"type":44,"value":51},"An MCP server exposes ",{"type":39,"tag":53,"props":54,"children":55},"strong",{},[56],{"type":44,"value":57},"tools",{"type":44,"value":59}," (and optionally resources\u002Fprompts) that an AI client — Claude Desktop, Claude Code, Cursor — can call. On Netlify, a remote MCP server is just ",{"type":39,"tag":53,"props":61,"children":62},{},[63],{"type":44,"value":64},"one Netlify Function",{"type":44,"value":66}," that speaks the MCP protocol over HTTP. This skill gets you a working, secure server and connects a client to it.",{"type":39,"tag":47,"props":68,"children":69},{},[70,75,77,83,85,90,92,97,99,104,106,111,113,118],{"type":39,"tag":53,"props":71,"children":72},{},[73],{"type":44,"value":74},"\"Netlify MCP\" means two different things — make sure you're building the right one.",{"type":44,"value":76}," Netlify publishes its ",{"type":39,"tag":78,"props":79,"children":80},"em",{},[81],{"type":44,"value":82},"own",{"type":44,"value":84}," hosted MCP server that lets an AI client operate the ",{"type":39,"tag":53,"props":86,"children":87},{},[88],{"type":44,"value":89},"Netlify platform",{"type":44,"value":91}," 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 ",{"type":39,"tag":53,"props":93,"children":94},{},[95],{"type":44,"value":96},"netlify-agent-runner",{"type":44,"value":98}," skill for running agents against your site). This skill is the ",{"type":39,"tag":78,"props":100,"children":101},{},[102],{"type":44,"value":103},"other",{"type":44,"value":105}," thing: building ",{"type":39,"tag":53,"props":107,"children":108},{},[109],{"type":44,"value":110},"your own",{"type":44,"value":112}," MCP server — an endpoint that exposes ",{"type":39,"tag":78,"props":114,"children":115},{},[116],{"type":44,"value":117},"your",{"type":44,"value":119}," app's tools and data to an agent — hosted on a Netlify Function. If the ask is \"let my agent manage my Netlify sites\u002Fdeploys\u002Fenv vars,\" that's the hosted Netlify MCP server, not a function you write.",{"type":39,"tag":47,"props":121,"children":122},{},[123],{"type":44,"value":124},"The same setup works two ways:",{"type":39,"tag":126,"props":127,"children":128},"ul",{},[129,140],{"type":39,"tag":130,"props":131,"children":132},"li",{},[133,138],{"type":39,"tag":53,"props":134,"children":135},{},[136],{"type":44,"value":137},"Standalone server",{"type":44,"value":139}," — a repo whose only job is the MCP endpoint (e.g. wrapping a third-party API).",{"type":39,"tag":130,"props":141,"children":142},{},[143,148,150,155],{"type":39,"tag":53,"props":144,"children":145},{},[146],{"type":44,"value":147},"Added to an existing app",{"type":44,"value":149}," — one more function alongside your site. Have its tools call the ",{"type":39,"tag":53,"props":151,"children":152},{},[153],{"type":44,"value":154},"same service\u002Fdata layer your UI and REST routes already use",{"type":44,"value":156},", so logic isn't duplicated.",{"type":39,"tag":158,"props":159,"children":161},"h2",{"id":160},"before-you-build",[162],{"type":44,"value":163},"Before you build",{"type":39,"tag":47,"props":165,"children":166},{},[167],{"type":44,"value":168},"Decide one thing up front, because it shapes the auth code:",{"type":39,"tag":126,"props":170,"children":171},{},[172],{"type":39,"tag":130,"props":173,"children":174},{},[175,180,182,187,189,194,196,203],{"type":39,"tag":53,"props":176,"children":177},{},[178],{"type":44,"value":179},"Who calls this server?",{"type":44,"value":181}," Just you (a personal\u002Fsingle-user server) → use a ",{"type":39,"tag":53,"props":183,"children":184},{},[185],{"type":44,"value":186},"single shared secret",{"type":44,"value":188},". Multiple people, each acting as themselves → use ",{"type":39,"tag":53,"props":190,"children":191},{},[192],{"type":44,"value":193},"per-user API keys",{"type":44,"value":195}," backed by Netlify Identity. See ",{"type":39,"tag":197,"props":198,"children":200},"a",{"href":199},"references\u002Fauthentication.md",[201],{"type":44,"value":202},"authentication",{"type":44,"value":204},".",{"type":39,"tag":47,"props":206,"children":207},{},[208],{"type":44,"value":209},"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.",{"type":39,"tag":158,"props":211,"children":213},{"id":212},"stack",[214],{"type":44,"value":215},"Stack",{"type":39,"tag":47,"props":217,"children":218},{},[219],{"type":44,"value":220},"Use the official MCP SDK with its Web-standard Streamable HTTP transport, running statelessly inside a Netlify Function.",{"type":39,"tag":222,"props":223,"children":228},"pre",{"className":224,"code":225,"language":226,"meta":227,"style":227},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npm install @modelcontextprotocol\u002Fsdk zod\n","bash","",[229],{"type":39,"tag":230,"props":231,"children":232},"code",{"__ignoreMap":227},[233],{"type":39,"tag":234,"props":235,"children":238},"span",{"class":236,"line":237},"line",1,[239,245,251,256],{"type":39,"tag":234,"props":240,"children":242},{"style":241},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[243],{"type":44,"value":244},"npm",{"type":39,"tag":234,"props":246,"children":248},{"style":247},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[249],{"type":44,"value":250}," install",{"type":39,"tag":234,"props":252,"children":253},{"style":247},[254],{"type":44,"value":255}," @modelcontextprotocol\u002Fsdk",{"type":39,"tag":234,"props":257,"children":258},{"style":247},[259],{"type":44,"value":260}," zod\n",{"type":39,"tag":47,"props":262,"children":263},{},[264,266,272,274,280,282,288,290,295,297,302,304,310,312,318,320,326,328,334],{"type":44,"value":265},"A Netlify Function already speaks the web platform — it receives a ",{"type":39,"tag":230,"props":267,"children":269},{"className":268},[],[270],{"type":44,"value":271},"Request",{"type":44,"value":273}," and returns a ",{"type":39,"tag":230,"props":275,"children":277},{"className":276},[],[278],{"type":44,"value":279},"Response",{"type":44,"value":281},". The SDK ships a transport built on exactly those primitives, ",{"type":39,"tag":230,"props":283,"children":285},{"className":284},[],[286],{"type":44,"value":287},"WebStandardStreamableHTTPServerTransport",{"type":44,"value":289}," (the same core the SDK runs on internally, and what Cloudflare Workers \u002F Deno \u002F Bun use): you hand it the ",{"type":39,"tag":230,"props":291,"children":293},{"className":292},[],[294],{"type":44,"value":271},{"type":44,"value":296}," and return the ",{"type":39,"tag":230,"props":298,"children":300},{"className":299},[],[301],{"type":44,"value":279},{"type":44,"value":303}," it produces — no adapter, no version pin. Older guides reach for the Node-flavored ",{"type":39,"tag":230,"props":305,"children":307},{"className":306},[],[308],{"type":44,"value":309},"StreamableHTTPServerTransport",{"type":44,"value":311}," plus a ",{"type":39,"tag":230,"props":313,"children":315},{"className":314},[],[316],{"type":44,"value":317},"fetch-to-node",{"type":44,"value":319}," bridge to synthesize the Node ",{"type":39,"tag":230,"props":321,"children":323},{"className":322},[],[324],{"type":44,"value":325},"req",{"type":44,"value":327},"\u002F",{"type":39,"tag":230,"props":329,"children":331},{"className":330},[],[332],{"type":44,"value":333},"res",{"type":44,"value":335}," objects it expects; on Netlify you need neither, and skipping them is both simpler and what's verified to work here.",{"type":39,"tag":47,"props":337,"children":338},{},[339,341,346,348,354,356,361,363,369,371,377,379,384,386,391],{"type":44,"value":340},"One gotcha, independent of all this: the transport returns ",{"type":39,"tag":53,"props":342,"children":343},{},[344],{"type":44,"value":345},"HTTP 406",{"type":44,"value":347}," to any POST whose ",{"type":39,"tag":230,"props":349,"children":351},{"className":350},[],[352],{"type":44,"value":353},"Accept",{"type":44,"value":355}," header lacks ",{"type":39,"tag":78,"props":357,"children":358},{},[359],{"type":44,"value":360},"both",{"type":44,"value":362}," ",{"type":39,"tag":230,"props":364,"children":366},{"className":365},[],[367],{"type":44,"value":368},"application\u002Fjson",{"type":44,"value":370}," and ",{"type":39,"tag":230,"props":372,"children":374},{"className":373},[],[375],{"type":44,"value":376},"text\u002Fevent-stream",{"type":44,"value":378},". That's an MCP-spec requirement the ",{"type":39,"tag":78,"props":380,"children":381},{},[382],{"type":44,"value":383},"client",{"type":44,"value":385}," must satisfy — a 406 means fix the client's ",{"type":39,"tag":230,"props":387,"children":389},{"className":388},[],[390],{"type":44,"value":353},{"type":44,"value":392}," 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.",{"type":39,"tag":158,"props":394,"children":396},{"id":395},"the-server-function",[397],{"type":44,"value":398},"The server function",{"type":39,"tag":47,"props":400,"children":401},{},[402,404,410],{"type":44,"value":403},"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 ",{"type":39,"tag":230,"props":405,"children":407},{"className":406},[],[408],{"type":44,"value":409},"netlify\u002Ffunctions\u002Fmcp.ts",{"type":44,"value":411},":",{"type":39,"tag":222,"props":413,"children":417},{"className":414,"code":415,"language":416,"meta":227,"style":227},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import type { Config, Context } from \"@netlify\u002Ffunctions\";\nimport { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002FwebStandardStreamableHttp.js\";\nimport { z } from \"zod\";\nimport { checkBearer } from \"..\u002Flib\u002Fmcp\u002Fbearer\"; \u002F\u002F see Authentication\n\nfunction buildServer() {\n  const server = new McpServer({ name: \"my-mcp\", version: \"0.1.0\" });\n\n  server.tool(\n    \"get_item\",\n    \"Fetch a single item by id. Read-only.\",\n    { id: z.string().describe(\"The item's unique id\") },\n    async ({ id }) => ({\n      content: [{ type: \"text\", text: JSON.stringify(await getItem(id)) }],\n    }),\n  );\n\n  return server;\n}\n\nexport default async (req: Request, _context: Context) => {\n  if (!checkBearer(req)) return new Response(\"Unauthorized\", { status: 401 });\n\n  \u002F\u002F Stateless JSON server: it only does request\u002Fresponse over POST. Reject other\n  \u002F\u002F methods — a GET makes the transport open an SSE stream that never closes, which\n  \u002F\u002F a serverless function can't serve (you'll get a 502).\n  if (req.method !== \"POST\") return new Response(\"Method not allowed\", { status: 405 });\n\n  \u002F\u002F Fresh server + transport per request, no session to persist. enableJsonResponse\n  \u002F\u002F returns one application\u002Fjson body instead of an SSE stream — the right fit here.\n  const server = buildServer();\n  const transport = new WebStandardStreamableHTTPServerTransport({\n    sessionIdGenerator: undefined,\n    enableJsonResponse: true,\n  });\n\n  \u002F\u002F Hand over the Web Request, return the Web Response. The transport owns JSON-RPC\n  \u002F\u002F framing, body parsing (a malformed body comes back as a clean 400), and the handshake.\n  await server.connect(transport);\n  return transport.handleRequest(req);\n};\n\nexport const config: Config = { path: \"\u002Fmcp\" };\n","typescript",[418],{"type":39,"tag":230,"props":419,"children":420},{"__ignoreMap":227},[421,487,529,571,613,661,671,697,797,805,828,851,872,943,982,1093,1110,1123,1131,1148,1157,1165,1230,1331,1338,1347,1356,1365,1474,1482,1491,1500,1528,1561,1579,1602,1619,1627,1636,1645,1684,1721,1730,1738],{"type":39,"tag":234,"props":422,"children":423},{"class":236,"line":237},[424,430,435,441,447,452,457,462,467,472,477,482],{"type":39,"tag":234,"props":425,"children":427},{"style":426},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[428],{"type":44,"value":429},"import",{"type":39,"tag":234,"props":431,"children":432},{"style":426},[433],{"type":44,"value":434}," type",{"type":39,"tag":234,"props":436,"children":438},{"style":437},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[439],{"type":44,"value":440}," {",{"type":39,"tag":234,"props":442,"children":444},{"style":443},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[445],{"type":44,"value":446}," Config",{"type":39,"tag":234,"props":448,"children":449},{"style":437},[450],{"type":44,"value":451},",",{"type":39,"tag":234,"props":453,"children":454},{"style":443},[455],{"type":44,"value":456}," Context",{"type":39,"tag":234,"props":458,"children":459},{"style":437},[460],{"type":44,"value":461}," }",{"type":39,"tag":234,"props":463,"children":464},{"style":426},[465],{"type":44,"value":466}," from",{"type":39,"tag":234,"props":468,"children":469},{"style":437},[470],{"type":44,"value":471}," \"",{"type":39,"tag":234,"props":473,"children":474},{"style":247},[475],{"type":44,"value":476},"@netlify\u002Ffunctions",{"type":39,"tag":234,"props":478,"children":479},{"style":437},[480],{"type":44,"value":481},"\"",{"type":39,"tag":234,"props":483,"children":484},{"style":437},[485],{"type":44,"value":486},";\n",{"type":39,"tag":234,"props":488,"children":490},{"class":236,"line":489},2,[491,495,499,504,508,512,516,521,525],{"type":39,"tag":234,"props":492,"children":493},{"style":426},[494],{"type":44,"value":429},{"type":39,"tag":234,"props":496,"children":497},{"style":437},[498],{"type":44,"value":440},{"type":39,"tag":234,"props":500,"children":501},{"style":443},[502],{"type":44,"value":503}," McpServer",{"type":39,"tag":234,"props":505,"children":506},{"style":437},[507],{"type":44,"value":461},{"type":39,"tag":234,"props":509,"children":510},{"style":426},[511],{"type":44,"value":466},{"type":39,"tag":234,"props":513,"children":514},{"style":437},[515],{"type":44,"value":471},{"type":39,"tag":234,"props":517,"children":518},{"style":247},[519],{"type":44,"value":520},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js",{"type":39,"tag":234,"props":522,"children":523},{"style":437},[524],{"type":44,"value":481},{"type":39,"tag":234,"props":526,"children":527},{"style":437},[528],{"type":44,"value":486},{"type":39,"tag":234,"props":530,"children":532},{"class":236,"line":531},3,[533,537,541,546,550,554,558,563,567],{"type":39,"tag":234,"props":534,"children":535},{"style":426},[536],{"type":44,"value":429},{"type":39,"tag":234,"props":538,"children":539},{"style":437},[540],{"type":44,"value":440},{"type":39,"tag":234,"props":542,"children":543},{"style":443},[544],{"type":44,"value":545}," WebStandardStreamableHTTPServerTransport",{"type":39,"tag":234,"props":547,"children":548},{"style":437},[549],{"type":44,"value":461},{"type":39,"tag":234,"props":551,"children":552},{"style":426},[553],{"type":44,"value":466},{"type":39,"tag":234,"props":555,"children":556},{"style":437},[557],{"type":44,"value":471},{"type":39,"tag":234,"props":559,"children":560},{"style":247},[561],{"type":44,"value":562},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002FwebStandardStreamableHttp.js",{"type":39,"tag":234,"props":564,"children":565},{"style":437},[566],{"type":44,"value":481},{"type":39,"tag":234,"props":568,"children":569},{"style":437},[570],{"type":44,"value":486},{"type":39,"tag":234,"props":572,"children":574},{"class":236,"line":573},4,[575,579,583,588,592,596,600,605,609],{"type":39,"tag":234,"props":576,"children":577},{"style":426},[578],{"type":44,"value":429},{"type":39,"tag":234,"props":580,"children":581},{"style":437},[582],{"type":44,"value":440},{"type":39,"tag":234,"props":584,"children":585},{"style":443},[586],{"type":44,"value":587}," z",{"type":39,"tag":234,"props":589,"children":590},{"style":437},[591],{"type":44,"value":461},{"type":39,"tag":234,"props":593,"children":594},{"style":426},[595],{"type":44,"value":466},{"type":39,"tag":234,"props":597,"children":598},{"style":437},[599],{"type":44,"value":471},{"type":39,"tag":234,"props":601,"children":602},{"style":247},[603],{"type":44,"value":604},"zod",{"type":39,"tag":234,"props":606,"children":607},{"style":437},[608],{"type":44,"value":481},{"type":39,"tag":234,"props":610,"children":611},{"style":437},[612],{"type":44,"value":486},{"type":39,"tag":234,"props":614,"children":615},{"class":236,"line":27},[616,620,624,629,633,637,641,646,650,655],{"type":39,"tag":234,"props":617,"children":618},{"style":426},[619],{"type":44,"value":429},{"type":39,"tag":234,"props":621,"children":622},{"style":437},[623],{"type":44,"value":440},{"type":39,"tag":234,"props":625,"children":626},{"style":443},[627],{"type":44,"value":628}," checkBearer",{"type":39,"tag":234,"props":630,"children":631},{"style":437},[632],{"type":44,"value":461},{"type":39,"tag":234,"props":634,"children":635},{"style":426},[636],{"type":44,"value":466},{"type":39,"tag":234,"props":638,"children":639},{"style":437},[640],{"type":44,"value":471},{"type":39,"tag":234,"props":642,"children":643},{"style":247},[644],{"type":44,"value":645},"..\u002Flib\u002Fmcp\u002Fbearer",{"type":39,"tag":234,"props":647,"children":648},{"style":437},[649],{"type":44,"value":481},{"type":39,"tag":234,"props":651,"children":652},{"style":437},[653],{"type":44,"value":654},";",{"type":39,"tag":234,"props":656,"children":658},{"style":657},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[659],{"type":44,"value":660}," \u002F\u002F see Authentication\n",{"type":39,"tag":234,"props":662,"children":664},{"class":236,"line":663},6,[665],{"type":39,"tag":234,"props":666,"children":668},{"emptyLinePlaceholder":667},true,[669],{"type":44,"value":670},"\n",{"type":39,"tag":234,"props":672,"children":674},{"class":236,"line":673},7,[675,681,687,692],{"type":39,"tag":234,"props":676,"children":678},{"style":677},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[679],{"type":44,"value":680},"function",{"type":39,"tag":234,"props":682,"children":684},{"style":683},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[685],{"type":44,"value":686}," buildServer",{"type":39,"tag":234,"props":688,"children":689},{"style":437},[690],{"type":44,"value":691},"()",{"type":39,"tag":234,"props":693,"children":694},{"style":437},[695],{"type":44,"value":696}," {\n",{"type":39,"tag":234,"props":698,"children":700},{"class":236,"line":699},8,[701,706,711,716,721,725,731,736,741,745,749,754,758,762,767,771,775,780,784,788,793],{"type":39,"tag":234,"props":702,"children":703},{"style":677},[704],{"type":44,"value":705},"  const",{"type":39,"tag":234,"props":707,"children":708},{"style":443},[709],{"type":44,"value":710}," server",{"type":39,"tag":234,"props":712,"children":713},{"style":437},[714],{"type":44,"value":715}," =",{"type":39,"tag":234,"props":717,"children":718},{"style":437},[719],{"type":44,"value":720}," new",{"type":39,"tag":234,"props":722,"children":723},{"style":683},[724],{"type":44,"value":503},{"type":39,"tag":234,"props":726,"children":728},{"style":727},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[729],{"type":44,"value":730},"(",{"type":39,"tag":234,"props":732,"children":733},{"style":437},[734],{"type":44,"value":735},"{",{"type":39,"tag":234,"props":737,"children":738},{"style":727},[739],{"type":44,"value":740}," name",{"type":39,"tag":234,"props":742,"children":743},{"style":437},[744],{"type":44,"value":411},{"type":39,"tag":234,"props":746,"children":747},{"style":437},[748],{"type":44,"value":471},{"type":39,"tag":234,"props":750,"children":751},{"style":247},[752],{"type":44,"value":753},"my-mcp",{"type":39,"tag":234,"props":755,"children":756},{"style":437},[757],{"type":44,"value":481},{"type":39,"tag":234,"props":759,"children":760},{"style":437},[761],{"type":44,"value":451},{"type":39,"tag":234,"props":763,"children":764},{"style":727},[765],{"type":44,"value":766}," version",{"type":39,"tag":234,"props":768,"children":769},{"style":437},[770],{"type":44,"value":411},{"type":39,"tag":234,"props":772,"children":773},{"style":437},[774],{"type":44,"value":471},{"type":39,"tag":234,"props":776,"children":777},{"style":247},[778],{"type":44,"value":779},"0.1.0",{"type":39,"tag":234,"props":781,"children":782},{"style":437},[783],{"type":44,"value":481},{"type":39,"tag":234,"props":785,"children":786},{"style":437},[787],{"type":44,"value":461},{"type":39,"tag":234,"props":789,"children":790},{"style":727},[791],{"type":44,"value":792},")",{"type":39,"tag":234,"props":794,"children":795},{"style":437},[796],{"type":44,"value":486},{"type":39,"tag":234,"props":798,"children":800},{"class":236,"line":799},9,[801],{"type":39,"tag":234,"props":802,"children":803},{"emptyLinePlaceholder":667},[804],{"type":44,"value":670},{"type":39,"tag":234,"props":806,"children":808},{"class":236,"line":807},10,[809,814,818,823],{"type":39,"tag":234,"props":810,"children":811},{"style":443},[812],{"type":44,"value":813},"  server",{"type":39,"tag":234,"props":815,"children":816},{"style":437},[817],{"type":44,"value":204},{"type":39,"tag":234,"props":819,"children":820},{"style":683},[821],{"type":44,"value":822},"tool",{"type":39,"tag":234,"props":824,"children":825},{"style":727},[826],{"type":44,"value":827},"(\n",{"type":39,"tag":234,"props":829,"children":831},{"class":236,"line":830},11,[832,837,842,846],{"type":39,"tag":234,"props":833,"children":834},{"style":437},[835],{"type":44,"value":836},"    \"",{"type":39,"tag":234,"props":838,"children":839},{"style":247},[840],{"type":44,"value":841},"get_item",{"type":39,"tag":234,"props":843,"children":844},{"style":437},[845],{"type":44,"value":481},{"type":39,"tag":234,"props":847,"children":848},{"style":437},[849],{"type":44,"value":850},",\n",{"type":39,"tag":234,"props":852,"children":854},{"class":236,"line":853},12,[855,859,864,868],{"type":39,"tag":234,"props":856,"children":857},{"style":437},[858],{"type":44,"value":836},{"type":39,"tag":234,"props":860,"children":861},{"style":247},[862],{"type":44,"value":863},"Fetch a single item by id. Read-only.",{"type":39,"tag":234,"props":865,"children":866},{"style":437},[867],{"type":44,"value":481},{"type":39,"tag":234,"props":869,"children":870},{"style":437},[871],{"type":44,"value":850},{"type":39,"tag":234,"props":873,"children":875},{"class":236,"line":874},13,[876,881,886,890,894,898,903,907,911,916,920,924,929,933,938],{"type":39,"tag":234,"props":877,"children":878},{"style":437},[879],{"type":44,"value":880},"    {",{"type":39,"tag":234,"props":882,"children":883},{"style":727},[884],{"type":44,"value":885}," id",{"type":39,"tag":234,"props":887,"children":888},{"style":437},[889],{"type":44,"value":411},{"type":39,"tag":234,"props":891,"children":892},{"style":443},[893],{"type":44,"value":587},{"type":39,"tag":234,"props":895,"children":896},{"style":437},[897],{"type":44,"value":204},{"type":39,"tag":234,"props":899,"children":900},{"style":683},[901],{"type":44,"value":902},"string",{"type":39,"tag":234,"props":904,"children":905},{"style":727},[906],{"type":44,"value":691},{"type":39,"tag":234,"props":908,"children":909},{"style":437},[910],{"type":44,"value":204},{"type":39,"tag":234,"props":912,"children":913},{"style":683},[914],{"type":44,"value":915},"describe",{"type":39,"tag":234,"props":917,"children":918},{"style":727},[919],{"type":44,"value":730},{"type":39,"tag":234,"props":921,"children":922},{"style":437},[923],{"type":44,"value":481},{"type":39,"tag":234,"props":925,"children":926},{"style":247},[927],{"type":44,"value":928},"The item's unique id",{"type":39,"tag":234,"props":930,"children":931},{"style":437},[932],{"type":44,"value":481},{"type":39,"tag":234,"props":934,"children":935},{"style":727},[936],{"type":44,"value":937},") ",{"type":39,"tag":234,"props":939,"children":940},{"style":437},[941],{"type":44,"value":942},"},\n",{"type":39,"tag":234,"props":944,"children":946},{"class":236,"line":945},14,[947,952,957,962,967,972,977],{"type":39,"tag":234,"props":948,"children":949},{"style":677},[950],{"type":44,"value":951},"    async",{"type":39,"tag":234,"props":953,"children":954},{"style":437},[955],{"type":44,"value":956}," ({",{"type":39,"tag":234,"props":958,"children":960},{"style":959},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[961],{"type":44,"value":885},{"type":39,"tag":234,"props":963,"children":964},{"style":437},[965],{"type":44,"value":966}," })",{"type":39,"tag":234,"props":968,"children":969},{"style":677},[970],{"type":44,"value":971}," =>",{"type":39,"tag":234,"props":973,"children":974},{"style":727},[975],{"type":44,"value":976}," (",{"type":39,"tag":234,"props":978,"children":979},{"style":437},[980],{"type":44,"value":981},"{\n",{"type":39,"tag":234,"props":983,"children":985},{"class":236,"line":984},15,[986,991,995,1000,1004,1008,1012,1016,1020,1024,1028,1033,1037,1042,1046,1051,1055,1060,1065,1069,1074,1079,1084,1089],{"type":39,"tag":234,"props":987,"children":988},{"style":727},[989],{"type":44,"value":990},"      content",{"type":39,"tag":234,"props":992,"children":993},{"style":437},[994],{"type":44,"value":411},{"type":39,"tag":234,"props":996,"children":997},{"style":727},[998],{"type":44,"value":999}," [",{"type":39,"tag":234,"props":1001,"children":1002},{"style":437},[1003],{"type":44,"value":735},{"type":39,"tag":234,"props":1005,"children":1006},{"style":727},[1007],{"type":44,"value":434},{"type":39,"tag":234,"props":1009,"children":1010},{"style":437},[1011],{"type":44,"value":411},{"type":39,"tag":234,"props":1013,"children":1014},{"style":437},[1015],{"type":44,"value":471},{"type":39,"tag":234,"props":1017,"children":1018},{"style":247},[1019],{"type":44,"value":44},{"type":39,"tag":234,"props":1021,"children":1022},{"style":437},[1023],{"type":44,"value":481},{"type":39,"tag":234,"props":1025,"children":1026},{"style":437},[1027],{"type":44,"value":451},{"type":39,"tag":234,"props":1029,"children":1030},{"style":727},[1031],{"type":44,"value":1032}," text",{"type":39,"tag":234,"props":1034,"children":1035},{"style":437},[1036],{"type":44,"value":411},{"type":39,"tag":234,"props":1038,"children":1039},{"style":443},[1040],{"type":44,"value":1041}," JSON",{"type":39,"tag":234,"props":1043,"children":1044},{"style":437},[1045],{"type":44,"value":204},{"type":39,"tag":234,"props":1047,"children":1048},{"style":683},[1049],{"type":44,"value":1050},"stringify",{"type":39,"tag":234,"props":1052,"children":1053},{"style":727},[1054],{"type":44,"value":730},{"type":39,"tag":234,"props":1056,"children":1057},{"style":426},[1058],{"type":44,"value":1059},"await",{"type":39,"tag":234,"props":1061,"children":1062},{"style":683},[1063],{"type":44,"value":1064}," getItem",{"type":39,"tag":234,"props":1066,"children":1067},{"style":727},[1068],{"type":44,"value":730},{"type":39,"tag":234,"props":1070,"children":1071},{"style":443},[1072],{"type":44,"value":1073},"id",{"type":39,"tag":234,"props":1075,"children":1076},{"style":727},[1077],{"type":44,"value":1078},")) ",{"type":39,"tag":234,"props":1080,"children":1081},{"style":437},[1082],{"type":44,"value":1083},"}",{"type":39,"tag":234,"props":1085,"children":1086},{"style":727},[1087],{"type":44,"value":1088},"]",{"type":39,"tag":234,"props":1090,"children":1091},{"style":437},[1092],{"type":44,"value":850},{"type":39,"tag":234,"props":1094,"children":1096},{"class":236,"line":1095},16,[1097,1102,1106],{"type":39,"tag":234,"props":1098,"children":1099},{"style":437},[1100],{"type":44,"value":1101},"    }",{"type":39,"tag":234,"props":1103,"children":1104},{"style":727},[1105],{"type":44,"value":792},{"type":39,"tag":234,"props":1107,"children":1108},{"style":437},[1109],{"type":44,"value":850},{"type":39,"tag":234,"props":1111,"children":1113},{"class":236,"line":1112},17,[1114,1119],{"type":39,"tag":234,"props":1115,"children":1116},{"style":727},[1117],{"type":44,"value":1118},"  )",{"type":39,"tag":234,"props":1120,"children":1121},{"style":437},[1122],{"type":44,"value":486},{"type":39,"tag":234,"props":1124,"children":1126},{"class":236,"line":1125},18,[1127],{"type":39,"tag":234,"props":1128,"children":1129},{"emptyLinePlaceholder":667},[1130],{"type":44,"value":670},{"type":39,"tag":234,"props":1132,"children":1134},{"class":236,"line":1133},19,[1135,1140,1144],{"type":39,"tag":234,"props":1136,"children":1137},{"style":426},[1138],{"type":44,"value":1139},"  return",{"type":39,"tag":234,"props":1141,"children":1142},{"style":443},[1143],{"type":44,"value":710},{"type":39,"tag":234,"props":1145,"children":1146},{"style":437},[1147],{"type":44,"value":486},{"type":39,"tag":234,"props":1149,"children":1151},{"class":236,"line":1150},20,[1152],{"type":39,"tag":234,"props":1153,"children":1154},{"style":437},[1155],{"type":44,"value":1156},"}\n",{"type":39,"tag":234,"props":1158,"children":1160},{"class":236,"line":1159},21,[1161],{"type":39,"tag":234,"props":1162,"children":1163},{"emptyLinePlaceholder":667},[1164],{"type":44,"value":670},{"type":39,"tag":234,"props":1166,"children":1168},{"class":236,"line":1167},22,[1169,1174,1179,1184,1188,1192,1196,1201,1205,1210,1214,1218,1222,1226],{"type":39,"tag":234,"props":1170,"children":1171},{"style":426},[1172],{"type":44,"value":1173},"export",{"type":39,"tag":234,"props":1175,"children":1176},{"style":426},[1177],{"type":44,"value":1178}," default",{"type":39,"tag":234,"props":1180,"children":1181},{"style":677},[1182],{"type":44,"value":1183}," async",{"type":39,"tag":234,"props":1185,"children":1186},{"style":437},[1187],{"type":44,"value":976},{"type":39,"tag":234,"props":1189,"children":1190},{"style":959},[1191],{"type":44,"value":325},{"type":39,"tag":234,"props":1193,"children":1194},{"style":437},[1195],{"type":44,"value":411},{"type":39,"tag":234,"props":1197,"children":1198},{"style":241},[1199],{"type":44,"value":1200}," Request",{"type":39,"tag":234,"props":1202,"children":1203},{"style":437},[1204],{"type":44,"value":451},{"type":39,"tag":234,"props":1206,"children":1207},{"style":959},[1208],{"type":44,"value":1209}," _context",{"type":39,"tag":234,"props":1211,"children":1212},{"style":437},[1213],{"type":44,"value":411},{"type":39,"tag":234,"props":1215,"children":1216},{"style":241},[1217],{"type":44,"value":456},{"type":39,"tag":234,"props":1219,"children":1220},{"style":437},[1221],{"type":44,"value":792},{"type":39,"tag":234,"props":1223,"children":1224},{"style":677},[1225],{"type":44,"value":971},{"type":39,"tag":234,"props":1227,"children":1228},{"style":437},[1229],{"type":44,"value":696},{"type":39,"tag":234,"props":1231,"children":1233},{"class":236,"line":1232},23,[1234,1239,1243,1248,1253,1257,1261,1265,1270,1274,1279,1283,1287,1292,1296,1300,1304,1309,1313,1319,1323,1327],{"type":39,"tag":234,"props":1235,"children":1236},{"style":426},[1237],{"type":44,"value":1238},"  if",{"type":39,"tag":234,"props":1240,"children":1241},{"style":727},[1242],{"type":44,"value":976},{"type":39,"tag":234,"props":1244,"children":1245},{"style":437},[1246],{"type":44,"value":1247},"!",{"type":39,"tag":234,"props":1249,"children":1250},{"style":683},[1251],{"type":44,"value":1252},"checkBearer",{"type":39,"tag":234,"props":1254,"children":1255},{"style":727},[1256],{"type":44,"value":730},{"type":39,"tag":234,"props":1258,"children":1259},{"style":443},[1260],{"type":44,"value":325},{"type":39,"tag":234,"props":1262,"children":1263},{"style":727},[1264],{"type":44,"value":1078},{"type":39,"tag":234,"props":1266,"children":1267},{"style":426},[1268],{"type":44,"value":1269},"return",{"type":39,"tag":234,"props":1271,"children":1272},{"style":437},[1273],{"type":44,"value":720},{"type":39,"tag":234,"props":1275,"children":1276},{"style":683},[1277],{"type":44,"value":1278}," Response",{"type":39,"tag":234,"props":1280,"children":1281},{"style":727},[1282],{"type":44,"value":730},{"type":39,"tag":234,"props":1284,"children":1285},{"style":437},[1286],{"type":44,"value":481},{"type":39,"tag":234,"props":1288,"children":1289},{"style":247},[1290],{"type":44,"value":1291},"Unauthorized",{"type":39,"tag":234,"props":1293,"children":1294},{"style":437},[1295],{"type":44,"value":481},{"type":39,"tag":234,"props":1297,"children":1298},{"style":437},[1299],{"type":44,"value":451},{"type":39,"tag":234,"props":1301,"children":1302},{"style":437},[1303],{"type":44,"value":440},{"type":39,"tag":234,"props":1305,"children":1306},{"style":727},[1307],{"type":44,"value":1308}," status",{"type":39,"tag":234,"props":1310,"children":1311},{"style":437},[1312],{"type":44,"value":411},{"type":39,"tag":234,"props":1314,"children":1316},{"style":1315},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[1317],{"type":44,"value":1318}," 401",{"type":39,"tag":234,"props":1320,"children":1321},{"style":437},[1322],{"type":44,"value":461},{"type":39,"tag":234,"props":1324,"children":1325},{"style":727},[1326],{"type":44,"value":792},{"type":39,"tag":234,"props":1328,"children":1329},{"style":437},[1330],{"type":44,"value":486},{"type":39,"tag":234,"props":1332,"children":1333},{"class":236,"line":23},[1334],{"type":39,"tag":234,"props":1335,"children":1336},{"emptyLinePlaceholder":667},[1337],{"type":44,"value":670},{"type":39,"tag":234,"props":1339,"children":1341},{"class":236,"line":1340},25,[1342],{"type":39,"tag":234,"props":1343,"children":1344},{"style":657},[1345],{"type":44,"value":1346},"  \u002F\u002F Stateless JSON server: it only does request\u002Fresponse over POST. Reject other\n",{"type":39,"tag":234,"props":1348,"children":1350},{"class":236,"line":1349},26,[1351],{"type":39,"tag":234,"props":1352,"children":1353},{"style":657},[1354],{"type":44,"value":1355},"  \u002F\u002F methods — a GET makes the transport open an SSE stream that never closes, which\n",{"type":39,"tag":234,"props":1357,"children":1359},{"class":236,"line":1358},27,[1360],{"type":39,"tag":234,"props":1361,"children":1362},{"style":657},[1363],{"type":44,"value":1364},"  \u002F\u002F a serverless function can't serve (you'll get a 502).\n",{"type":39,"tag":234,"props":1366,"children":1368},{"class":236,"line":1367},28,[1369,1373,1377,1381,1385,1390,1395,1399,1404,1408,1412,1416,1420,1424,1428,1432,1437,1441,1445,1449,1453,1457,1462,1466,1470],{"type":39,"tag":234,"props":1370,"children":1371},{"style":426},[1372],{"type":44,"value":1238},{"type":39,"tag":234,"props":1374,"children":1375},{"style":727},[1376],{"type":44,"value":976},{"type":39,"tag":234,"props":1378,"children":1379},{"style":443},[1380],{"type":44,"value":325},{"type":39,"tag":234,"props":1382,"children":1383},{"style":437},[1384],{"type":44,"value":204},{"type":39,"tag":234,"props":1386,"children":1387},{"style":443},[1388],{"type":44,"value":1389},"method",{"type":39,"tag":234,"props":1391,"children":1392},{"style":437},[1393],{"type":44,"value":1394}," !==",{"type":39,"tag":234,"props":1396,"children":1397},{"style":437},[1398],{"type":44,"value":471},{"type":39,"tag":234,"props":1400,"children":1401},{"style":247},[1402],{"type":44,"value":1403},"POST",{"type":39,"tag":234,"props":1405,"children":1406},{"style":437},[1407],{"type":44,"value":481},{"type":39,"tag":234,"props":1409,"children":1410},{"style":727},[1411],{"type":44,"value":937},{"type":39,"tag":234,"props":1413,"children":1414},{"style":426},[1415],{"type":44,"value":1269},{"type":39,"tag":234,"props":1417,"children":1418},{"style":437},[1419],{"type":44,"value":720},{"type":39,"tag":234,"props":1421,"children":1422},{"style":683},[1423],{"type":44,"value":1278},{"type":39,"tag":234,"props":1425,"children":1426},{"style":727},[1427],{"type":44,"value":730},{"type":39,"tag":234,"props":1429,"children":1430},{"style":437},[1431],{"type":44,"value":481},{"type":39,"tag":234,"props":1433,"children":1434},{"style":247},[1435],{"type":44,"value":1436},"Method not allowed",{"type":39,"tag":234,"props":1438,"children":1439},{"style":437},[1440],{"type":44,"value":481},{"type":39,"tag":234,"props":1442,"children":1443},{"style":437},[1444],{"type":44,"value":451},{"type":39,"tag":234,"props":1446,"children":1447},{"style":437},[1448],{"type":44,"value":440},{"type":39,"tag":234,"props":1450,"children":1451},{"style":727},[1452],{"type":44,"value":1308},{"type":39,"tag":234,"props":1454,"children":1455},{"style":437},[1456],{"type":44,"value":411},{"type":39,"tag":234,"props":1458,"children":1459},{"style":1315},[1460],{"type":44,"value":1461}," 405",{"type":39,"tag":234,"props":1463,"children":1464},{"style":437},[1465],{"type":44,"value":461},{"type":39,"tag":234,"props":1467,"children":1468},{"style":727},[1469],{"type":44,"value":792},{"type":39,"tag":234,"props":1471,"children":1472},{"style":437},[1473],{"type":44,"value":486},{"type":39,"tag":234,"props":1475,"children":1477},{"class":236,"line":1476},29,[1478],{"type":39,"tag":234,"props":1479,"children":1480},{"emptyLinePlaceholder":667},[1481],{"type":44,"value":670},{"type":39,"tag":234,"props":1483,"children":1485},{"class":236,"line":1484},30,[1486],{"type":39,"tag":234,"props":1487,"children":1488},{"style":657},[1489],{"type":44,"value":1490},"  \u002F\u002F Fresh server + transport per request, no session to persist. enableJsonResponse\n",{"type":39,"tag":234,"props":1492,"children":1494},{"class":236,"line":1493},31,[1495],{"type":39,"tag":234,"props":1496,"children":1497},{"style":657},[1498],{"type":44,"value":1499},"  \u002F\u002F returns one application\u002Fjson body instead of an SSE stream — the right fit here.\n",{"type":39,"tag":234,"props":1501,"children":1503},{"class":236,"line":1502},32,[1504,1508,1512,1516,1520,1524],{"type":39,"tag":234,"props":1505,"children":1506},{"style":677},[1507],{"type":44,"value":705},{"type":39,"tag":234,"props":1509,"children":1510},{"style":443},[1511],{"type":44,"value":710},{"type":39,"tag":234,"props":1513,"children":1514},{"style":437},[1515],{"type":44,"value":715},{"type":39,"tag":234,"props":1517,"children":1518},{"style":683},[1519],{"type":44,"value":686},{"type":39,"tag":234,"props":1521,"children":1522},{"style":727},[1523],{"type":44,"value":691},{"type":39,"tag":234,"props":1525,"children":1526},{"style":437},[1527],{"type":44,"value":486},{"type":39,"tag":234,"props":1529,"children":1531},{"class":236,"line":1530},33,[1532,1536,1541,1545,1549,1553,1557],{"type":39,"tag":234,"props":1533,"children":1534},{"style":677},[1535],{"type":44,"value":705},{"type":39,"tag":234,"props":1537,"children":1538},{"style":443},[1539],{"type":44,"value":1540}," transport",{"type":39,"tag":234,"props":1542,"children":1543},{"style":437},[1544],{"type":44,"value":715},{"type":39,"tag":234,"props":1546,"children":1547},{"style":437},[1548],{"type":44,"value":720},{"type":39,"tag":234,"props":1550,"children":1551},{"style":683},[1552],{"type":44,"value":545},{"type":39,"tag":234,"props":1554,"children":1555},{"style":727},[1556],{"type":44,"value":730},{"type":39,"tag":234,"props":1558,"children":1559},{"style":437},[1560],{"type":44,"value":981},{"type":39,"tag":234,"props":1562,"children":1564},{"class":236,"line":1563},34,[1565,1570,1574],{"type":39,"tag":234,"props":1566,"children":1567},{"style":727},[1568],{"type":44,"value":1569},"    sessionIdGenerator",{"type":39,"tag":234,"props":1571,"children":1572},{"style":437},[1573],{"type":44,"value":411},{"type":39,"tag":234,"props":1575,"children":1576},{"style":437},[1577],{"type":44,"value":1578}," undefined,\n",{"type":39,"tag":234,"props":1580,"children":1582},{"class":236,"line":1581},35,[1583,1588,1592,1598],{"type":39,"tag":234,"props":1584,"children":1585},{"style":727},[1586],{"type":44,"value":1587},"    enableJsonResponse",{"type":39,"tag":234,"props":1589,"children":1590},{"style":437},[1591],{"type":44,"value":411},{"type":39,"tag":234,"props":1593,"children":1595},{"style":1594},"--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC",[1596],{"type":44,"value":1597}," true",{"type":39,"tag":234,"props":1599,"children":1600},{"style":437},[1601],{"type":44,"value":850},{"type":39,"tag":234,"props":1603,"children":1605},{"class":236,"line":1604},36,[1606,1611,1615],{"type":39,"tag":234,"props":1607,"children":1608},{"style":437},[1609],{"type":44,"value":1610},"  }",{"type":39,"tag":234,"props":1612,"children":1613},{"style":727},[1614],{"type":44,"value":792},{"type":39,"tag":234,"props":1616,"children":1617},{"style":437},[1618],{"type":44,"value":486},{"type":39,"tag":234,"props":1620,"children":1622},{"class":236,"line":1621},37,[1623],{"type":39,"tag":234,"props":1624,"children":1625},{"emptyLinePlaceholder":667},[1626],{"type":44,"value":670},{"type":39,"tag":234,"props":1628,"children":1630},{"class":236,"line":1629},38,[1631],{"type":39,"tag":234,"props":1632,"children":1633},{"style":657},[1634],{"type":44,"value":1635},"  \u002F\u002F Hand over the Web Request, return the Web Response. The transport owns JSON-RPC\n",{"type":39,"tag":234,"props":1637,"children":1639},{"class":236,"line":1638},39,[1640],{"type":39,"tag":234,"props":1641,"children":1642},{"style":657},[1643],{"type":44,"value":1644},"  \u002F\u002F framing, body parsing (a malformed body comes back as a clean 400), and the handshake.\n",{"type":39,"tag":234,"props":1646,"children":1648},{"class":236,"line":1647},40,[1649,1654,1658,1662,1667,1671,1676,1680],{"type":39,"tag":234,"props":1650,"children":1651},{"style":426},[1652],{"type":44,"value":1653},"  await",{"type":39,"tag":234,"props":1655,"children":1656},{"style":443},[1657],{"type":44,"value":710},{"type":39,"tag":234,"props":1659,"children":1660},{"style":437},[1661],{"type":44,"value":204},{"type":39,"tag":234,"props":1663,"children":1664},{"style":683},[1665],{"type":44,"value":1666},"connect",{"type":39,"tag":234,"props":1668,"children":1669},{"style":727},[1670],{"type":44,"value":730},{"type":39,"tag":234,"props":1672,"children":1673},{"style":443},[1674],{"type":44,"value":1675},"transport",{"type":39,"tag":234,"props":1677,"children":1678},{"style":727},[1679],{"type":44,"value":792},{"type":39,"tag":234,"props":1681,"children":1682},{"style":437},[1683],{"type":44,"value":486},{"type":39,"tag":234,"props":1685,"children":1687},{"class":236,"line":1686},41,[1688,1692,1696,1700,1705,1709,1713,1717],{"type":39,"tag":234,"props":1689,"children":1690},{"style":426},[1691],{"type":44,"value":1139},{"type":39,"tag":234,"props":1693,"children":1694},{"style":443},[1695],{"type":44,"value":1540},{"type":39,"tag":234,"props":1697,"children":1698},{"style":437},[1699],{"type":44,"value":204},{"type":39,"tag":234,"props":1701,"children":1702},{"style":683},[1703],{"type":44,"value":1704},"handleRequest",{"type":39,"tag":234,"props":1706,"children":1707},{"style":727},[1708],{"type":44,"value":730},{"type":39,"tag":234,"props":1710,"children":1711},{"style":443},[1712],{"type":44,"value":325},{"type":39,"tag":234,"props":1714,"children":1715},{"style":727},[1716],{"type":44,"value":792},{"type":39,"tag":234,"props":1718,"children":1719},{"style":437},[1720],{"type":44,"value":486},{"type":39,"tag":234,"props":1722,"children":1724},{"class":236,"line":1723},42,[1725],{"type":39,"tag":234,"props":1726,"children":1727},{"style":437},[1728],{"type":44,"value":1729},"};\n",{"type":39,"tag":234,"props":1731,"children":1733},{"class":236,"line":1732},43,[1734],{"type":39,"tag":234,"props":1735,"children":1736},{"emptyLinePlaceholder":667},[1737],{"type":44,"value":670},{"type":39,"tag":234,"props":1739,"children":1741},{"class":236,"line":1740},44,[1742,1746,1751,1756,1760,1764,1768,1772,1777,1781,1785,1790,1794],{"type":39,"tag":234,"props":1743,"children":1744},{"style":426},[1745],{"type":44,"value":1173},{"type":39,"tag":234,"props":1747,"children":1748},{"style":677},[1749],{"type":44,"value":1750}," const",{"type":39,"tag":234,"props":1752,"children":1753},{"style":443},[1754],{"type":44,"value":1755}," config",{"type":39,"tag":234,"props":1757,"children":1758},{"style":437},[1759],{"type":44,"value":411},{"type":39,"tag":234,"props":1761,"children":1762},{"style":241},[1763],{"type":44,"value":446},{"type":39,"tag":234,"props":1765,"children":1766},{"style":437},[1767],{"type":44,"value":715},{"type":39,"tag":234,"props":1769,"children":1770},{"style":437},[1771],{"type":44,"value":440},{"type":39,"tag":234,"props":1773,"children":1774},{"style":727},[1775],{"type":44,"value":1776}," path",{"type":39,"tag":234,"props":1778,"children":1779},{"style":437},[1780],{"type":44,"value":411},{"type":39,"tag":234,"props":1782,"children":1783},{"style":437},[1784],{"type":44,"value":471},{"type":39,"tag":234,"props":1786,"children":1787},{"style":247},[1788],{"type":44,"value":1789},"\u002Fmcp",{"type":39,"tag":234,"props":1791,"children":1792},{"style":437},[1793],{"type":44,"value":481},{"type":39,"tag":234,"props":1795,"children":1796},{"style":437},[1797],{"type":44,"value":1798}," };\n",{"type":39,"tag":47,"props":1800,"children":1801},{},[1802],{"type":44,"value":1803},"That's a complete, deployable server. Everything else is tools, auth, and safety.",{"type":39,"tag":158,"props":1805,"children":1807},{"id":1806},"browser-based-clients-and-cors",[1808],{"type":44,"value":1809},"Browser-based clients and CORS",{"type":39,"tag":47,"props":1811,"children":1812},{},[1813,1815,1820,1822,1828,1830,1836,1838,1842],{"type":44,"value":1814},"Netlify Functions do ",{"type":39,"tag":53,"props":1816,"children":1817},{},[1818],{"type":44,"value":1819},"not",{"type":44,"value":1821}," add CORS headers for you, and the server above returns 405 to every non-POST method — including the ",{"type":39,"tag":230,"props":1823,"children":1825},{"className":1824},[],[1826],{"type":44,"value":1827},"OPTIONS",{"type":44,"value":1829}," preflight a browser sends. That's fine for the normal case: native MCP clients (Claude Code, Cursor, Claude Desktop, the ",{"type":39,"tag":230,"props":1831,"children":1833},{"className":1832},[],[1834],{"type":44,"value":1835},"mcp-remote",{"type":44,"value":1837}," bridge) are ",{"type":39,"tag":53,"props":1839,"children":1840},{},[1841],{"type":44,"value":1819},{"type":44,"value":1843}," 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.",{"type":39,"tag":47,"props":1845,"children":1846},{},[1847,1849,1854,1856,1862,1864,1869,1871,1877,1879,1885,1887,1892,1894,1900,1901,1907,1908,1914,1916,1921],{"type":44,"value":1848},"It only matters when your MCP client runs ",{"type":39,"tag":53,"props":1850,"children":1851},{},[1852],{"type":44,"value":1853},"in a browser",{"type":44,"value":1855}," — a web app calling the server cross-origin. Then the browser blocks the request unless the response carries ",{"type":39,"tag":230,"props":1857,"children":1859},{"className":1858},[],[1860],{"type":44,"value":1861},"Access-Control-Allow-Origin",{"type":44,"value":1863},", and it first sends an ",{"type":39,"tag":230,"props":1865,"children":1867},{"className":1866},[],[1868],{"type":44,"value":1827},{"type":44,"value":1870}," preflight that must come back ",{"type":39,"tag":230,"props":1872,"children":1874},{"className":1873},[],[1875],{"type":44,"value":1876},"2xx",{"type":44,"value":1878}," with ",{"type":39,"tag":230,"props":1880,"children":1882},{"className":1881},[],[1883],{"type":44,"value":1884},"Access-Control-Allow-Methods",{"type":44,"value":1886}," (including ",{"type":39,"tag":230,"props":1888,"children":1890},{"className":1889},[],[1891],{"type":44,"value":1403},{"type":44,"value":1893},") and ",{"type":39,"tag":230,"props":1895,"children":1897},{"className":1896},[],[1898],{"type":44,"value":1899},"Access-Control-Allow-Headers",{"type":44,"value":1886},{"type":39,"tag":230,"props":1902,"children":1904},{"className":1903},[],[1905],{"type":44,"value":1906},"Authorization",{"type":44,"value":370},{"type":39,"tag":230,"props":1909,"children":1911},{"className":1910},[],[1912],{"type":44,"value":1913},"Content-Type",{"type":44,"value":1915},"). 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, ",{"type":39,"tag":53,"props":1917,"children":1918},{},[1919],{"type":44,"value":1920},"before",{"type":44,"value":1922}," the 405 check, and echo the CORS headers on the POST response too:",{"type":39,"tag":222,"props":1924,"children":1926},{"className":414,"code":1925,"language":416,"meta":227,"style":227},"const CORS = {\n  \"Access-Control-Allow-Origin\": Netlify.env.get(\"MCP_ALLOWED_ORIGIN\") ?? \"*\",\n  \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Authorization, Content-Type, Mcp-Session-Id\",\n};\n\n\u002F\u002F In the handler, before the 405 check:\nif (req.method === \"OPTIONS\") return new Response(null, { status: 204, headers: CORS });\n\u002F\u002F ...then reject other non-POST methods with 405, and add CORS to the transport's Response.\n",[1927],{"type":39,"tag":230,"props":1928,"children":1929},{"__ignoreMap":227},[1930,1952,2038,2074,2110,2117,2124,2132,2242],{"type":39,"tag":234,"props":1931,"children":1932},{"class":236,"line":237},[1933,1938,1943,1948],{"type":39,"tag":234,"props":1934,"children":1935},{"style":677},[1936],{"type":44,"value":1937},"const",{"type":39,"tag":234,"props":1939,"children":1940},{"style":443},[1941],{"type":44,"value":1942}," CORS ",{"type":39,"tag":234,"props":1944,"children":1945},{"style":437},[1946],{"type":44,"value":1947},"=",{"type":39,"tag":234,"props":1949,"children":1950},{"style":437},[1951],{"type":44,"value":696},{"type":39,"tag":234,"props":1953,"children":1954},{"class":236,"line":489},[1955,1960,1964,1968,1972,1977,1981,1986,1990,1995,1999,2003,2008,2012,2016,2021,2025,2030,2034],{"type":39,"tag":234,"props":1956,"children":1957},{"style":437},[1958],{"type":44,"value":1959},"  \"",{"type":39,"tag":234,"props":1961,"children":1962},{"style":727},[1963],{"type":44,"value":1861},{"type":39,"tag":234,"props":1965,"children":1966},{"style":437},[1967],{"type":44,"value":481},{"type":39,"tag":234,"props":1969,"children":1970},{"style":437},[1971],{"type":44,"value":411},{"type":39,"tag":234,"props":1973,"children":1974},{"style":443},[1975],{"type":44,"value":1976}," Netlify",{"type":39,"tag":234,"props":1978,"children":1979},{"style":437},[1980],{"type":44,"value":204},{"type":39,"tag":234,"props":1982,"children":1983},{"style":443},[1984],{"type":44,"value":1985},"env",{"type":39,"tag":234,"props":1987,"children":1988},{"style":437},[1989],{"type":44,"value":204},{"type":39,"tag":234,"props":1991,"children":1992},{"style":683},[1993],{"type":44,"value":1994},"get",{"type":39,"tag":234,"props":1996,"children":1997},{"style":443},[1998],{"type":44,"value":730},{"type":39,"tag":234,"props":2000,"children":2001},{"style":437},[2002],{"type":44,"value":481},{"type":39,"tag":234,"props":2004,"children":2005},{"style":247},[2006],{"type":44,"value":2007},"MCP_ALLOWED_ORIGIN",{"type":39,"tag":234,"props":2009,"children":2010},{"style":437},[2011],{"type":44,"value":481},{"type":39,"tag":234,"props":2013,"children":2014},{"style":443},[2015],{"type":44,"value":937},{"type":39,"tag":234,"props":2017,"children":2018},{"style":437},[2019],{"type":44,"value":2020},"??",{"type":39,"tag":234,"props":2022,"children":2023},{"style":437},[2024],{"type":44,"value":471},{"type":39,"tag":234,"props":2026,"children":2027},{"style":247},[2028],{"type":44,"value":2029},"*",{"type":39,"tag":234,"props":2031,"children":2032},{"style":437},[2033],{"type":44,"value":481},{"type":39,"tag":234,"props":2035,"children":2036},{"style":437},[2037],{"type":44,"value":850},{"type":39,"tag":234,"props":2039,"children":2040},{"class":236,"line":531},[2041,2045,2049,2053,2057,2061,2066,2070],{"type":39,"tag":234,"props":2042,"children":2043},{"style":437},[2044],{"type":44,"value":1959},{"type":39,"tag":234,"props":2046,"children":2047},{"style":727},[2048],{"type":44,"value":1884},{"type":39,"tag":234,"props":2050,"children":2051},{"style":437},[2052],{"type":44,"value":481},{"type":39,"tag":234,"props":2054,"children":2055},{"style":437},[2056],{"type":44,"value":411},{"type":39,"tag":234,"props":2058,"children":2059},{"style":437},[2060],{"type":44,"value":471},{"type":39,"tag":234,"props":2062,"children":2063},{"style":247},[2064],{"type":44,"value":2065},"POST, OPTIONS",{"type":39,"tag":234,"props":2067,"children":2068},{"style":437},[2069],{"type":44,"value":481},{"type":39,"tag":234,"props":2071,"children":2072},{"style":437},[2073],{"type":44,"value":850},{"type":39,"tag":234,"props":2075,"children":2076},{"class":236,"line":573},[2077,2081,2085,2089,2093,2097,2102,2106],{"type":39,"tag":234,"props":2078,"children":2079},{"style":437},[2080],{"type":44,"value":1959},{"type":39,"tag":234,"props":2082,"children":2083},{"style":727},[2084],{"type":44,"value":1899},{"type":39,"tag":234,"props":2086,"children":2087},{"style":437},[2088],{"type":44,"value":481},{"type":39,"tag":234,"props":2090,"children":2091},{"style":437},[2092],{"type":44,"value":411},{"type":39,"tag":234,"props":2094,"children":2095},{"style":437},[2096],{"type":44,"value":471},{"type":39,"tag":234,"props":2098,"children":2099},{"style":247},[2100],{"type":44,"value":2101},"Authorization, Content-Type, Mcp-Session-Id",{"type":39,"tag":234,"props":2103,"children":2104},{"style":437},[2105],{"type":44,"value":481},{"type":39,"tag":234,"props":2107,"children":2108},{"style":437},[2109],{"type":44,"value":850},{"type":39,"tag":234,"props":2111,"children":2112},{"class":236,"line":27},[2113],{"type":39,"tag":234,"props":2114,"children":2115},{"style":437},[2116],{"type":44,"value":1729},{"type":39,"tag":234,"props":2118,"children":2119},{"class":236,"line":663},[2120],{"type":39,"tag":234,"props":2121,"children":2122},{"emptyLinePlaceholder":667},[2123],{"type":44,"value":670},{"type":39,"tag":234,"props":2125,"children":2126},{"class":236,"line":673},[2127],{"type":39,"tag":234,"props":2128,"children":2129},{"style":657},[2130],{"type":44,"value":2131},"\u002F\u002F In the handler, before the 405 check:\n",{"type":39,"tag":234,"props":2133,"children":2134},{"class":236,"line":699},[2135,2140,2145,2149,2154,2159,2163,2167,2171,2175,2179,2183,2187,2191,2196,2200,2204,2208,2213,2217,2222,2226,2230,2234,2238],{"type":39,"tag":234,"props":2136,"children":2137},{"style":426},[2138],{"type":44,"value":2139},"if",{"type":39,"tag":234,"props":2141,"children":2142},{"style":443},[2143],{"type":44,"value":2144}," (req",{"type":39,"tag":234,"props":2146,"children":2147},{"style":437},[2148],{"type":44,"value":204},{"type":39,"tag":234,"props":2150,"children":2151},{"style":443},[2152],{"type":44,"value":2153},"method ",{"type":39,"tag":234,"props":2155,"children":2156},{"style":437},[2157],{"type":44,"value":2158},"===",{"type":39,"tag":234,"props":2160,"children":2161},{"style":437},[2162],{"type":44,"value":471},{"type":39,"tag":234,"props":2164,"children":2165},{"style":247},[2166],{"type":44,"value":1827},{"type":39,"tag":234,"props":2168,"children":2169},{"style":437},[2170],{"type":44,"value":481},{"type":39,"tag":234,"props":2172,"children":2173},{"style":443},[2174],{"type":44,"value":937},{"type":39,"tag":234,"props":2176,"children":2177},{"style":426},[2178],{"type":44,"value":1269},{"type":39,"tag":234,"props":2180,"children":2181},{"style":437},[2182],{"type":44,"value":720},{"type":39,"tag":234,"props":2184,"children":2185},{"style":683},[2186],{"type":44,"value":1278},{"type":39,"tag":234,"props":2188,"children":2189},{"style":443},[2190],{"type":44,"value":730},{"type":39,"tag":234,"props":2192,"children":2193},{"style":437},[2194],{"type":44,"value":2195},"null,",{"type":39,"tag":234,"props":2197,"children":2198},{"style":437},[2199],{"type":44,"value":440},{"type":39,"tag":234,"props":2201,"children":2202},{"style":727},[2203],{"type":44,"value":1308},{"type":39,"tag":234,"props":2205,"children":2206},{"style":437},[2207],{"type":44,"value":411},{"type":39,"tag":234,"props":2209,"children":2210},{"style":1315},[2211],{"type":44,"value":2212}," 204",{"type":39,"tag":234,"props":2214,"children":2215},{"style":437},[2216],{"type":44,"value":451},{"type":39,"tag":234,"props":2218,"children":2219},{"style":727},[2220],{"type":44,"value":2221}," headers",{"type":39,"tag":234,"props":2223,"children":2224},{"style":437},[2225],{"type":44,"value":411},{"type":39,"tag":234,"props":2227,"children":2228},{"style":443},[2229],{"type":44,"value":1942},{"type":39,"tag":234,"props":2231,"children":2232},{"style":437},[2233],{"type":44,"value":1083},{"type":39,"tag":234,"props":2235,"children":2236},{"style":443},[2237],{"type":44,"value":792},{"type":39,"tag":234,"props":2239,"children":2240},{"style":437},[2241],{"type":44,"value":486},{"type":39,"tag":234,"props":2243,"children":2244},{"class":236,"line":799},[2245],{"type":39,"tag":234,"props":2246,"children":2247},{"style":657},[2248],{"type":44,"value":2249},"\u002F\u002F ...then reject other non-POST methods with 405, and add CORS to the transport's Response.\n",{"type":39,"tag":47,"props":2251,"children":2252},{},[2253],{"type":44,"value":2254},"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.",{"type":39,"tag":158,"props":2256,"children":2258},{"id":2257},"defining-tools",[2259],{"type":44,"value":2260},"Defining tools",{"type":39,"tag":47,"props":2262,"children":2263},{},[2264,2266,2272,2274,2280,2282,2287,2289,2295,2297,2303],{"type":44,"value":2265},"Each tool is a ",{"type":39,"tag":230,"props":2267,"children":2269},{"className":2268},[],[2270],{"type":44,"value":2271},"name",{"type":44,"value":2273},", a one-line ",{"type":39,"tag":230,"props":2275,"children":2277},{"className":2276},[],[2278],{"type":44,"value":2279},"description",{"type":44,"value":2281},", a ",{"type":39,"tag":230,"props":2283,"children":2285},{"className":2284},[],[2286],{"type":44,"value":604},{"type":44,"value":2288}," input schema, and a handler that returns ",{"type":39,"tag":230,"props":2290,"children":2292},{"className":2291},[],[2293],{"type":44,"value":2294},"{ content: [...] }",{"type":44,"value":2296},". The description and parameter ",{"type":39,"tag":230,"props":2298,"children":2300},{"className":2299},[],[2301],{"type":44,"value":2302},".describe()",{"type":44,"value":2304}," 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.",{"type":39,"tag":47,"props":2306,"children":2307},{},[2308,2310,2316,2318,2324,2326,2332,2334,2340],{"type":44,"value":2309},"As the count grows, give each tool its own module and register them in ",{"type":39,"tag":230,"props":2311,"children":2313},{"className":2312},[],[2314],{"type":44,"value":2315},"buildServer()",{"type":44,"value":2317},". Servers with many tools often keep a registry (an array of ",{"type":39,"tag":230,"props":2319,"children":2321},{"className":2320},[],[2322],{"type":44,"value":2323},"{ name, description, inputSchema, handler }",{"type":44,"value":2325},") and wire ",{"type":39,"tag":230,"props":2327,"children":2329},{"className":2328},[],[2330],{"type":44,"value":2331},"tools\u002Flist",{"type":44,"value":2333}," + ",{"type":39,"tag":230,"props":2335,"children":2337},{"className":2336},[],[2338],{"type":44,"value":2339},"tools\u002Fcall",{"type":44,"value":2341}," once — the transport setup above is identical either way.",{"type":39,"tag":158,"props":2343,"children":2344},{"id":202},[2345],{"type":44,"value":2346},"Authentication",{"type":39,"tag":47,"props":2348,"children":2349},{},[2350,2352,2358],{"type":44,"value":2351},"The MCP client must prove it's allowed to call your server. Every request carries ",{"type":39,"tag":230,"props":2353,"children":2355},{"className":2354},[],[2356],{"type":44,"value":2357},"Authorization: Bearer \u003Ctoken>",{"type":44,"value":2359},"; reject anything else with a 401.",{"type":39,"tag":47,"props":2361,"children":2362},{},[2363,2368,2370,2376],{"type":39,"tag":53,"props":2364,"children":2365},{},[2366],{"type":44,"value":2367},"Single shared secret",{"type":44,"value":2369}," (personal \u002F single-user). One env var, compared in constant time. Put this in ",{"type":39,"tag":230,"props":2371,"children":2373},{"className":2372},[],[2374],{"type":44,"value":2375},"netlify\u002Flib\u002Fmcp\u002Fbearer.ts",{"type":44,"value":411},{"type":39,"tag":222,"props":2378,"children":2380},{"className":414,"code":2379,"language":416,"meta":227,"style":227},"import { timingSafeEqual } from \"node:crypto\";\n\nexport function checkBearer(req: Request): boolean {\n  const expected = Netlify.env.get(\"MCP_BEARER_TOKEN\");\n  if (!expected) return false;\n  const match = req.headers.get(\"authorization\")?.match(\u002F^Bearer\\s+(.+)$\u002Fi);\n  if (!match) return false;\n  const a = Buffer.from(match[1]);\n  const b = Buffer.from(expected);\n  \u002F\u002F Length check first because timingSafeEqual throws (RangeError) on unequal-length\n  \u002F\u002F buffers. The token is fixed-length, so the early return leaks nothing useful.\n  return a.length === b.length && timingSafeEqual(a, b);\n}\n",[2381],{"type":39,"tag":230,"props":2382,"children":2383},{"__ignoreMap":227},[2384,2425,2432,2478,2539,2576,2699,2734,2791,2835,2843,2851,2921],{"type":39,"tag":234,"props":2385,"children":2386},{"class":236,"line":237},[2387,2391,2395,2400,2404,2408,2412,2417,2421],{"type":39,"tag":234,"props":2388,"children":2389},{"style":426},[2390],{"type":44,"value":429},{"type":39,"tag":234,"props":2392,"children":2393},{"style":437},[2394],{"type":44,"value":440},{"type":39,"tag":234,"props":2396,"children":2397},{"style":443},[2398],{"type":44,"value":2399}," timingSafeEqual",{"type":39,"tag":234,"props":2401,"children":2402},{"style":437},[2403],{"type":44,"value":461},{"type":39,"tag":234,"props":2405,"children":2406},{"style":426},[2407],{"type":44,"value":466},{"type":39,"tag":234,"props":2409,"children":2410},{"style":437},[2411],{"type":44,"value":471},{"type":39,"tag":234,"props":2413,"children":2414},{"style":247},[2415],{"type":44,"value":2416},"node:crypto",{"type":39,"tag":234,"props":2418,"children":2419},{"style":437},[2420],{"type":44,"value":481},{"type":39,"tag":234,"props":2422,"children":2423},{"style":437},[2424],{"type":44,"value":486},{"type":39,"tag":234,"props":2426,"children":2427},{"class":236,"line":489},[2428],{"type":39,"tag":234,"props":2429,"children":2430},{"emptyLinePlaceholder":667},[2431],{"type":44,"value":670},{"type":39,"tag":234,"props":2433,"children":2434},{"class":236,"line":531},[2435,2439,2444,2448,2452,2456,2460,2464,2469,2474],{"type":39,"tag":234,"props":2436,"children":2437},{"style":426},[2438],{"type":44,"value":1173},{"type":39,"tag":234,"props":2440,"children":2441},{"style":677},[2442],{"type":44,"value":2443}," function",{"type":39,"tag":234,"props":2445,"children":2446},{"style":683},[2447],{"type":44,"value":628},{"type":39,"tag":234,"props":2449,"children":2450},{"style":437},[2451],{"type":44,"value":730},{"type":39,"tag":234,"props":2453,"children":2454},{"style":959},[2455],{"type":44,"value":325},{"type":39,"tag":234,"props":2457,"children":2458},{"style":437},[2459],{"type":44,"value":411},{"type":39,"tag":234,"props":2461,"children":2462},{"style":241},[2463],{"type":44,"value":1200},{"type":39,"tag":234,"props":2465,"children":2466},{"style":437},[2467],{"type":44,"value":2468},"):",{"type":39,"tag":234,"props":2470,"children":2471},{"style":241},[2472],{"type":44,"value":2473}," boolean",{"type":39,"tag":234,"props":2475,"children":2476},{"style":437},[2477],{"type":44,"value":696},{"type":39,"tag":234,"props":2479,"children":2480},{"class":236,"line":573},[2481,2485,2490,2494,2498,2502,2506,2510,2514,2518,2522,2527,2531,2535],{"type":39,"tag":234,"props":2482,"children":2483},{"style":677},[2484],{"type":44,"value":705},{"type":39,"tag":234,"props":2486,"children":2487},{"style":443},[2488],{"type":44,"value":2489}," expected",{"type":39,"tag":234,"props":2491,"children":2492},{"style":437},[2493],{"type":44,"value":715},{"type":39,"tag":234,"props":2495,"children":2496},{"style":443},[2497],{"type":44,"value":1976},{"type":39,"tag":234,"props":2499,"children":2500},{"style":437},[2501],{"type":44,"value":204},{"type":39,"tag":234,"props":2503,"children":2504},{"style":443},[2505],{"type":44,"value":1985},{"type":39,"tag":234,"props":2507,"children":2508},{"style":437},[2509],{"type":44,"value":204},{"type":39,"tag":234,"props":2511,"children":2512},{"style":683},[2513],{"type":44,"value":1994},{"type":39,"tag":234,"props":2515,"children":2516},{"style":727},[2517],{"type":44,"value":730},{"type":39,"tag":234,"props":2519,"children":2520},{"style":437},[2521],{"type":44,"value":481},{"type":39,"tag":234,"props":2523,"children":2524},{"style":247},[2525],{"type":44,"value":2526},"MCP_BEARER_TOKEN",{"type":39,"tag":234,"props":2528,"children":2529},{"style":437},[2530],{"type":44,"value":481},{"type":39,"tag":234,"props":2532,"children":2533},{"style":727},[2534],{"type":44,"value":792},{"type":39,"tag":234,"props":2536,"children":2537},{"style":437},[2538],{"type":44,"value":486},{"type":39,"tag":234,"props":2540,"children":2541},{"class":236,"line":27},[2542,2546,2550,2554,2559,2563,2567,2572],{"type":39,"tag":234,"props":2543,"children":2544},{"style":426},[2545],{"type":44,"value":1238},{"type":39,"tag":234,"props":2547,"children":2548},{"style":727},[2549],{"type":44,"value":976},{"type":39,"tag":234,"props":2551,"children":2552},{"style":437},[2553],{"type":44,"value":1247},{"type":39,"tag":234,"props":2555,"children":2556},{"style":443},[2557],{"type":44,"value":2558},"expected",{"type":39,"tag":234,"props":2560,"children":2561},{"style":727},[2562],{"type":44,"value":937},{"type":39,"tag":234,"props":2564,"children":2565},{"style":426},[2566],{"type":44,"value":1269},{"type":39,"tag":234,"props":2568,"children":2569},{"style":1594},[2570],{"type":44,"value":2571}," false",{"type":39,"tag":234,"props":2573,"children":2574},{"style":437},[2575],{"type":44,"value":486},{"type":39,"tag":234,"props":2577,"children":2578},{"class":236,"line":663},[2579,2583,2588,2592,2597,2601,2606,2610,2614,2618,2622,2627,2631,2635,2640,2645,2649,2653,2658,2663,2668,2672,2677,2682,2686,2691,2695],{"type":39,"tag":234,"props":2580,"children":2581},{"style":677},[2582],{"type":44,"value":705},{"type":39,"tag":234,"props":2584,"children":2585},{"style":443},[2586],{"type":44,"value":2587}," match",{"type":39,"tag":234,"props":2589,"children":2590},{"style":437},[2591],{"type":44,"value":715},{"type":39,"tag":234,"props":2593,"children":2594},{"style":443},[2595],{"type":44,"value":2596}," req",{"type":39,"tag":234,"props":2598,"children":2599},{"style":437},[2600],{"type":44,"value":204},{"type":39,"tag":234,"props":2602,"children":2603},{"style":443},[2604],{"type":44,"value":2605},"headers",{"type":39,"tag":234,"props":2607,"children":2608},{"style":437},[2609],{"type":44,"value":204},{"type":39,"tag":234,"props":2611,"children":2612},{"style":683},[2613],{"type":44,"value":1994},{"type":39,"tag":234,"props":2615,"children":2616},{"style":727},[2617],{"type":44,"value":730},{"type":39,"tag":234,"props":2619,"children":2620},{"style":437},[2621],{"type":44,"value":481},{"type":39,"tag":234,"props":2623,"children":2624},{"style":247},[2625],{"type":44,"value":2626},"authorization",{"type":39,"tag":234,"props":2628,"children":2629},{"style":437},[2630],{"type":44,"value":481},{"type":39,"tag":234,"props":2632,"children":2633},{"style":727},[2634],{"type":44,"value":792},{"type":39,"tag":234,"props":2636,"children":2637},{"style":437},[2638],{"type":44,"value":2639},"?.",{"type":39,"tag":234,"props":2641,"children":2642},{"style":683},[2643],{"type":44,"value":2644},"match",{"type":39,"tag":234,"props":2646,"children":2647},{"style":727},[2648],{"type":44,"value":730},{"type":39,"tag":234,"props":2650,"children":2651},{"style":437},[2652],{"type":44,"value":327},{"type":39,"tag":234,"props":2654,"children":2655},{"style":426},[2656],{"type":44,"value":2657},"^",{"type":39,"tag":234,"props":2659,"children":2660},{"style":247},[2661],{"type":44,"value":2662},"Bearer\\s",{"type":39,"tag":234,"props":2664,"children":2665},{"style":437},[2666],{"type":44,"value":2667},"+(",{"type":39,"tag":234,"props":2669,"children":2670},{"style":247},[2671],{"type":44,"value":204},{"type":39,"tag":234,"props":2673,"children":2674},{"style":437},[2675],{"type":44,"value":2676},"+)",{"type":39,"tag":234,"props":2678,"children":2679},{"style":426},[2680],{"type":44,"value":2681},"$",{"type":39,"tag":234,"props":2683,"children":2684},{"style":437},[2685],{"type":44,"value":327},{"type":39,"tag":234,"props":2687,"children":2688},{"style":1315},[2689],{"type":44,"value":2690},"i",{"type":39,"tag":234,"props":2692,"children":2693},{"style":727},[2694],{"type":44,"value":792},{"type":39,"tag":234,"props":2696,"children":2697},{"style":437},[2698],{"type":44,"value":486},{"type":39,"tag":234,"props":2700,"children":2701},{"class":236,"line":673},[2702,2706,2710,2714,2718,2722,2726,2730],{"type":39,"tag":234,"props":2703,"children":2704},{"style":426},[2705],{"type":44,"value":1238},{"type":39,"tag":234,"props":2707,"children":2708},{"style":727},[2709],{"type":44,"value":976},{"type":39,"tag":234,"props":2711,"children":2712},{"style":437},[2713],{"type":44,"value":1247},{"type":39,"tag":234,"props":2715,"children":2716},{"style":443},[2717],{"type":44,"value":2644},{"type":39,"tag":234,"props":2719,"children":2720},{"style":727},[2721],{"type":44,"value":937},{"type":39,"tag":234,"props":2723,"children":2724},{"style":426},[2725],{"type":44,"value":1269},{"type":39,"tag":234,"props":2727,"children":2728},{"style":1594},[2729],{"type":44,"value":2571},{"type":39,"tag":234,"props":2731,"children":2732},{"style":437},[2733],{"type":44,"value":486},{"type":39,"tag":234,"props":2735,"children":2736},{"class":236,"line":699},[2737,2741,2746,2750,2755,2759,2764,2768,2772,2777,2782,2787],{"type":39,"tag":234,"props":2738,"children":2739},{"style":677},[2740],{"type":44,"value":705},{"type":39,"tag":234,"props":2742,"children":2743},{"style":443},[2744],{"type":44,"value":2745}," a",{"type":39,"tag":234,"props":2747,"children":2748},{"style":437},[2749],{"type":44,"value":715},{"type":39,"tag":234,"props":2751,"children":2752},{"style":443},[2753],{"type":44,"value":2754}," Buffer",{"type":39,"tag":234,"props":2756,"children":2757},{"style":437},[2758],{"type":44,"value":204},{"type":39,"tag":234,"props":2760,"children":2761},{"style":683},[2762],{"type":44,"value":2763},"from",{"type":39,"tag":234,"props":2765,"children":2766},{"style":727},[2767],{"type":44,"value":730},{"type":39,"tag":234,"props":2769,"children":2770},{"style":443},[2771],{"type":44,"value":2644},{"type":39,"tag":234,"props":2773,"children":2774},{"style":727},[2775],{"type":44,"value":2776},"[",{"type":39,"tag":234,"props":2778,"children":2779},{"style":1315},[2780],{"type":44,"value":2781},"1",{"type":39,"tag":234,"props":2783,"children":2784},{"style":727},[2785],{"type":44,"value":2786},"])",{"type":39,"tag":234,"props":2788,"children":2789},{"style":437},[2790],{"type":44,"value":486},{"type":39,"tag":234,"props":2792,"children":2793},{"class":236,"line":799},[2794,2798,2803,2807,2811,2815,2819,2823,2827,2831],{"type":39,"tag":234,"props":2795,"children":2796},{"style":677},[2797],{"type":44,"value":705},{"type":39,"tag":234,"props":2799,"children":2800},{"style":443},[2801],{"type":44,"value":2802}," b",{"type":39,"tag":234,"props":2804,"children":2805},{"style":437},[2806],{"type":44,"value":715},{"type":39,"tag":234,"props":2808,"children":2809},{"style":443},[2810],{"type":44,"value":2754},{"type":39,"tag":234,"props":2812,"children":2813},{"style":437},[2814],{"type":44,"value":204},{"type":39,"tag":234,"props":2816,"children":2817},{"style":683},[2818],{"type":44,"value":2763},{"type":39,"tag":234,"props":2820,"children":2821},{"style":727},[2822],{"type":44,"value":730},{"type":39,"tag":234,"props":2824,"children":2825},{"style":443},[2826],{"type":44,"value":2558},{"type":39,"tag":234,"props":2828,"children":2829},{"style":727},[2830],{"type":44,"value":792},{"type":39,"tag":234,"props":2832,"children":2833},{"style":437},[2834],{"type":44,"value":486},{"type":39,"tag":234,"props":2836,"children":2837},{"class":236,"line":807},[2838],{"type":39,"tag":234,"props":2839,"children":2840},{"style":657},[2841],{"type":44,"value":2842},"  \u002F\u002F Length check first because timingSafeEqual throws (RangeError) on unequal-length\n",{"type":39,"tag":234,"props":2844,"children":2845},{"class":236,"line":830},[2846],{"type":39,"tag":234,"props":2847,"children":2848},{"style":657},[2849],{"type":44,"value":2850},"  \u002F\u002F buffers. The token is fixed-length, so the early return leaks nothing useful.\n",{"type":39,"tag":234,"props":2852,"children":2853},{"class":236,"line":853},[2854,2858,2862,2866,2871,2876,2880,2884,2888,2893,2897,2901,2905,2909,2913,2917],{"type":39,"tag":234,"props":2855,"children":2856},{"style":426},[2857],{"type":44,"value":1139},{"type":39,"tag":234,"props":2859,"children":2860},{"style":443},[2861],{"type":44,"value":2745},{"type":39,"tag":234,"props":2863,"children":2864},{"style":437},[2865],{"type":44,"value":204},{"type":39,"tag":234,"props":2867,"children":2868},{"style":443},[2869],{"type":44,"value":2870},"length",{"type":39,"tag":234,"props":2872,"children":2873},{"style":437},[2874],{"type":44,"value":2875}," ===",{"type":39,"tag":234,"props":2877,"children":2878},{"style":443},[2879],{"type":44,"value":2802},{"type":39,"tag":234,"props":2881,"children":2882},{"style":437},[2883],{"type":44,"value":204},{"type":39,"tag":234,"props":2885,"children":2886},{"style":443},[2887],{"type":44,"value":2870},{"type":39,"tag":234,"props":2889,"children":2890},{"style":437},[2891],{"type":44,"value":2892}," &&",{"type":39,"tag":234,"props":2894,"children":2895},{"style":683},[2896],{"type":44,"value":2399},{"type":39,"tag":234,"props":2898,"children":2899},{"style":727},[2900],{"type":44,"value":730},{"type":39,"tag":234,"props":2902,"children":2903},{"style":443},[2904],{"type":44,"value":197},{"type":39,"tag":234,"props":2906,"children":2907},{"style":437},[2908],{"type":44,"value":451},{"type":39,"tag":234,"props":2910,"children":2911},{"style":443},[2912],{"type":44,"value":2802},{"type":39,"tag":234,"props":2914,"children":2915},{"style":727},[2916],{"type":44,"value":792},{"type":39,"tag":234,"props":2918,"children":2919},{"style":437},[2920],{"type":44,"value":486},{"type":39,"tag":234,"props":2922,"children":2923},{"class":236,"line":874},[2924],{"type":39,"tag":234,"props":2925,"children":2926},{"style":437},[2927],{"type":44,"value":1156},{"type":39,"tag":47,"props":2929,"children":2930},{},[2931,2933,2939],{"type":44,"value":2932},"Generate the token with ",{"type":39,"tag":230,"props":2934,"children":2936},{"className":2935},[],[2937],{"type":44,"value":2938},"openssl rand -hex 32",{"type":44,"value":2940}," and store it as a secret env var.",{"type":39,"tag":47,"props":2942,"children":2943},{},[2944,2949,2951,2956,2958,2962],{"type":39,"tag":53,"props":2945,"children":2946},{},[2947],{"type":44,"value":2948},"Per-user API keys",{"type":44,"value":2950}," (multi-user). Netlify Identity gates a web UI where each user mints their own keys; you store only a ",{"type":39,"tag":53,"props":2952,"children":2953},{},[2954],{"type":44,"value":2955},"hash",{"type":44,"value":2957}," 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 ",{"type":39,"tag":197,"props":2959,"children":2960},{"href":199},[2961],{"type":44,"value":202},{"type":44,"value":204},{"type":39,"tag":47,"props":2964,"children":2965},{},[2966,2971],{"type":39,"tag":53,"props":2967,"children":2968},{},[2969],{"type":44,"value":2970},"Start simple with scoping.",{"type":44,"value":2972}," 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.",{"type":39,"tag":158,"props":2974,"children":2976},{"id":2975},"safety-and-permissions",[2977],{"type":44,"value":2978},"Safety and permissions",{"type":39,"tag":47,"props":2980,"children":2981},{},[2982],{"type":44,"value":2983},"Tools are a public API handed to an autonomous agent. Be deliberate:",{"type":39,"tag":126,"props":2985,"children":2986},{},[2987,3004,3014,3030,3040],{"type":39,"tag":130,"props":2988,"children":2989},{},[2990,2995,2997,3002],{"type":39,"tag":53,"props":2991,"children":2992},{},[2993],{"type":44,"value":2994},"Expose the least that does the job.",{"type":44,"value":2996}," Separate reads from writes, and think hard before exposing destructive tools. A common, sound choice is to ",{"type":39,"tag":53,"props":2998,"children":2999},{},[3000],{"type":44,"value":3001},"omit delete tools entirely",{"type":44,"value":3003}," and keep destructive actions in a human-operated UI.",{"type":39,"tag":130,"props":3005,"children":3006},{},[3007,3012],{"type":39,"tag":53,"props":3008,"children":3009},{},[3010],{"type":44,"value":3011},"Guard irreversible or public actions",{"type":44,"value":3013}," 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.",{"type":39,"tag":130,"props":3015,"children":3016},{},[3017,3022,3024,3028],{"type":39,"tag":53,"props":3018,"children":3019},{},[3020],{"type":44,"value":3021},"Keep the client's credential separate from your backend's.",{"type":44,"value":3023}," The client authenticates to your server (bearer\u002FAPI key); your server authenticates to the database or third-party API with its ",{"type":39,"tag":78,"props":3025,"children":3026},{},[3027],{"type":44,"value":82},{"type":44,"value":3029}," secret. Never pass your backend god-key out to the client.",{"type":39,"tag":130,"props":3031,"children":3032},{},[3033,3038],{"type":39,"tag":53,"props":3034,"children":3035},{},[3036],{"type":44,"value":3037},"Use least-privilege backend credentials",{"type":44,"value":3039}," — app passwords or scoped tokens, not account-level ones, so a leak is contained and revocable.",{"type":39,"tag":130,"props":3041,"children":3042},{},[3043,3048,3050,3055,3057,3062,3064,3070],{"type":39,"tag":53,"props":3044,"children":3045},{},[3046],{"type":44,"value":3047},"Validate inputs",{"type":44,"value":3049}," (your ",{"type":39,"tag":230,"props":3051,"children":3053},{"className":3052},[],[3054],{"type":44,"value":604},{"type":44,"value":3056}," schemas do this) and ",{"type":39,"tag":53,"props":3058,"children":3059},{},[3060],{"type":44,"value":3061},"log every tool call",{"type":44,"value":3063}," so you can see what the agent did — ",{"type":39,"tag":230,"props":3065,"children":3067},{"className":3066},[],[3068],{"type":44,"value":3069},"console.info",{"type":44,"value":3071}," shows up in Netlify function logs.",{"type":39,"tag":158,"props":3073,"children":3075},{"id":3074},"rate-limiting",[3076],{"type":44,"value":3077},"Rate limiting",{"type":39,"tag":47,"props":3079,"children":3080},{},[3081,3083,3088,3090,3096,3098,3104],{"type":44,"value":3082},"An MCP server is a public endpoint an autonomous agent can hit in a tight loop — cap it. Netlify Functions have ",{"type":39,"tag":53,"props":3084,"children":3085},{},[3086],{"type":44,"value":3087},"built-in declarative rate limiting",{"type":44,"value":3089},", 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 ",{"type":39,"tag":230,"props":3091,"children":3093},{"className":3092},[],[3094],{"type":44,"value":3095},"rateLimit",{"type":44,"value":3097}," block to the function's ",{"type":39,"tag":230,"props":3099,"children":3101},{"className":3100},[],[3102],{"type":44,"value":3103},"config",{"type":44,"value":3105}," export:",{"type":39,"tag":222,"props":3107,"children":3109},{"className":414,"code":3108,"language":416,"meta":227,"style":227},"export const config: Config = {\n  path: \"\u002Fmcp\",\n  rateLimit: {\n    windowSize: 60,               \u002F\u002F time window in seconds; capped at 180\n    windowLimit: 100,             \u002F\u002F max requests per window\n    aggregateBy: [\"ip\", \"domain\"], \u002F\u002F group by ip, domain, or both\n  },\n};\n",[3110],{"type":39,"tag":230,"props":3111,"children":3112},{"__ignoreMap":227},[3113,3144,3172,3188,3214,3240,3299,3307],{"type":39,"tag":234,"props":3114,"children":3115},{"class":236,"line":237},[3116,3120,3124,3128,3132,3136,3140],{"type":39,"tag":234,"props":3117,"children":3118},{"style":426},[3119],{"type":44,"value":1173},{"type":39,"tag":234,"props":3121,"children":3122},{"style":677},[3123],{"type":44,"value":1750},{"type":39,"tag":234,"props":3125,"children":3126},{"style":443},[3127],{"type":44,"value":1755},{"type":39,"tag":234,"props":3129,"children":3130},{"style":437},[3131],{"type":44,"value":411},{"type":39,"tag":234,"props":3133,"children":3134},{"style":241},[3135],{"type":44,"value":446},{"type":39,"tag":234,"props":3137,"children":3138},{"style":437},[3139],{"type":44,"value":715},{"type":39,"tag":234,"props":3141,"children":3142},{"style":437},[3143],{"type":44,"value":696},{"type":39,"tag":234,"props":3145,"children":3146},{"class":236,"line":489},[3147,3152,3156,3160,3164,3168],{"type":39,"tag":234,"props":3148,"children":3149},{"style":727},[3150],{"type":44,"value":3151},"  path",{"type":39,"tag":234,"props":3153,"children":3154},{"style":437},[3155],{"type":44,"value":411},{"type":39,"tag":234,"props":3157,"children":3158},{"style":437},[3159],{"type":44,"value":471},{"type":39,"tag":234,"props":3161,"children":3162},{"style":247},[3163],{"type":44,"value":1789},{"type":39,"tag":234,"props":3165,"children":3166},{"style":437},[3167],{"type":44,"value":481},{"type":39,"tag":234,"props":3169,"children":3170},{"style":437},[3171],{"type":44,"value":850},{"type":39,"tag":234,"props":3173,"children":3174},{"class":236,"line":531},[3175,3180,3184],{"type":39,"tag":234,"props":3176,"children":3177},{"style":727},[3178],{"type":44,"value":3179},"  rateLimit",{"type":39,"tag":234,"props":3181,"children":3182},{"style":437},[3183],{"type":44,"value":411},{"type":39,"tag":234,"props":3185,"children":3186},{"style":437},[3187],{"type":44,"value":696},{"type":39,"tag":234,"props":3189,"children":3190},{"class":236,"line":573},[3191,3196,3200,3205,3209],{"type":39,"tag":234,"props":3192,"children":3193},{"style":727},[3194],{"type":44,"value":3195},"    windowSize",{"type":39,"tag":234,"props":3197,"children":3198},{"style":437},[3199],{"type":44,"value":411},{"type":39,"tag":234,"props":3201,"children":3202},{"style":1315},[3203],{"type":44,"value":3204}," 60",{"type":39,"tag":234,"props":3206,"children":3207},{"style":437},[3208],{"type":44,"value":451},{"type":39,"tag":234,"props":3210,"children":3211},{"style":657},[3212],{"type":44,"value":3213},"               \u002F\u002F time window in seconds; capped at 180\n",{"type":39,"tag":234,"props":3215,"children":3216},{"class":236,"line":27},[3217,3222,3226,3231,3235],{"type":39,"tag":234,"props":3218,"children":3219},{"style":727},[3220],{"type":44,"value":3221},"    windowLimit",{"type":39,"tag":234,"props":3223,"children":3224},{"style":437},[3225],{"type":44,"value":411},{"type":39,"tag":234,"props":3227,"children":3228},{"style":1315},[3229],{"type":44,"value":3230}," 100",{"type":39,"tag":234,"props":3232,"children":3233},{"style":437},[3234],{"type":44,"value":451},{"type":39,"tag":234,"props":3236,"children":3237},{"style":657},[3238],{"type":44,"value":3239},"             \u002F\u002F max requests per window\n",{"type":39,"tag":234,"props":3241,"children":3242},{"class":236,"line":663},[3243,3248,3252,3256,3260,3265,3269,3273,3277,3282,3286,3290,3294],{"type":39,"tag":234,"props":3244,"children":3245},{"style":727},[3246],{"type":44,"value":3247},"    aggregateBy",{"type":39,"tag":234,"props":3249,"children":3250},{"style":437},[3251],{"type":44,"value":411},{"type":39,"tag":234,"props":3253,"children":3254},{"style":443},[3255],{"type":44,"value":999},{"type":39,"tag":234,"props":3257,"children":3258},{"style":437},[3259],{"type":44,"value":481},{"type":39,"tag":234,"props":3261,"children":3262},{"style":247},[3263],{"type":44,"value":3264},"ip",{"type":39,"tag":234,"props":3266,"children":3267},{"style":437},[3268],{"type":44,"value":481},{"type":39,"tag":234,"props":3270,"children":3271},{"style":437},[3272],{"type":44,"value":451},{"type":39,"tag":234,"props":3274,"children":3275},{"style":437},[3276],{"type":44,"value":471},{"type":39,"tag":234,"props":3278,"children":3279},{"style":247},[3280],{"type":44,"value":3281},"domain",{"type":39,"tag":234,"props":3283,"children":3284},{"style":437},[3285],{"type":44,"value":481},{"type":39,"tag":234,"props":3287,"children":3288},{"style":443},[3289],{"type":44,"value":1088},{"type":39,"tag":234,"props":3291,"children":3292},{"style":437},[3293],{"type":44,"value":451},{"type":39,"tag":234,"props":3295,"children":3296},{"style":657},[3297],{"type":44,"value":3298}," \u002F\u002F group by ip, domain, or both\n",{"type":39,"tag":234,"props":3300,"children":3301},{"class":236,"line":673},[3302],{"type":39,"tag":234,"props":3303,"children":3304},{"style":437},[3305],{"type":44,"value":3306},"  },\n",{"type":39,"tag":234,"props":3308,"children":3309},{"class":236,"line":699},[3310],{"type":39,"tag":234,"props":3311,"children":3312},{"style":437},[3313],{"type":44,"value":1729},{"type":39,"tag":47,"props":3315,"children":3316},{},[3317,3319,3325,3327,3333,3335,3341,3343,3348,3350,3355,3357,3362,3364,3370],{"type":44,"value":3318},"Over the limit the platform returns HTTP ",{"type":39,"tag":230,"props":3320,"children":3322},{"className":3321},[],[3323],{"type":44,"value":3324},"429",{"type":44,"value":3326}," by default (or set ",{"type":39,"tag":230,"props":3328,"children":3330},{"className":3329},[],[3331],{"type":44,"value":3332},"action: \"rewrite\"",{"type":44,"value":3334}," with a ",{"type":39,"tag":230,"props":3336,"children":3338},{"className":3337},[],[3339],{"type":44,"value":3340},"to",{"type":44,"value":3342}," path to send excess traffic to a dedicated page). Function rate limits live ",{"type":39,"tag":53,"props":3344,"children":3345},{},[3346],{"type":44,"value":3347},"only",{"type":44,"value":3349}," in the function's ",{"type":39,"tag":230,"props":3351,"children":3353},{"className":3352},[],[3354],{"type":44,"value":3103},{"type":44,"value":3356}," export — they ",{"type":39,"tag":53,"props":3358,"children":3359},{},[3360],{"type":44,"value":3361},"cannot",{"type":44,"value":3363}," be defined in ",{"type":39,"tag":230,"props":3365,"children":3367},{"className":3366},[],[3368],{"type":44,"value":3369},"netlify.toml",{"type":44,"value":204},{"type":39,"tag":158,"props":3372,"children":3374},{"id":3373},"file-uploads",[3375],{"type":44,"value":3376},"File uploads",{"type":39,"tag":47,"props":3378,"children":3379},{},[3380,3382,3387,3389,3395,3397,3402,3404,3409,3411,3416,3418,3423,3425,3430,3432,3437,3439,3445,3447,3452,3453,3459,3461,3467],{"type":44,"value":3381},"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 ",{"type":39,"tag":53,"props":3383,"children":3384},{},[3385],{"type":44,"value":3386},"presigned URL",{"type":44,"value":3388}," to ",{"type":39,"tag":230,"props":3390,"children":3392},{"className":3391},[],[3393],{"type":44,"value":3394},"PUT",{"type":44,"value":3396}," the raw bytes to, store them in ",{"type":39,"tag":53,"props":3398,"children":3399},{},[3400],{"type":44,"value":3401},"Netlify Blobs",{"type":44,"value":3403},", and reference the file by a stable key from your other tools. Sign the URL with an ",{"type":39,"tag":53,"props":3405,"children":3406},{},[3407],{"type":44,"value":3408},"HMAC-SHA256",{"type":44,"value":3410}," over the upload id, content-type, size, and expiry, keyed by a ",{"type":39,"tag":53,"props":3412,"children":3413},{},[3414],{"type":44,"value":3415},"secret env var",{"type":44,"value":3417},", and ",{"type":39,"tag":53,"props":3419,"children":3420},{},[3421],{"type":44,"value":3422},"verify it in constant time",{"type":44,"value":3424}," — the signature ",{"type":39,"tag":78,"props":3426,"children":3427},{},[3428],{"type":44,"value":3429},"is",{"type":44,"value":3431}," the authorization, so the ",{"type":39,"tag":230,"props":3433,"children":3435},{"className":3434},[],[3436],{"type":44,"value":3394},{"type":44,"value":3438}," carries no bearer token. On the upload endpoint, enforce the declared content-type and size and reject replays. Full three-step flow (",{"type":39,"tag":230,"props":3440,"children":3442},{"className":3441},[],[3443],{"type":44,"value":3444},"prepare_upload",{"type":44,"value":3446}," → ",{"type":39,"tag":230,"props":3448,"children":3450},{"className":3449},[],[3451],{"type":44,"value":3394},{"type":44,"value":3446},{"type":39,"tag":230,"props":3454,"children":3456},{"className":3455},[],[3457],{"type":44,"value":3458},"finalize_upload",{"type":44,"value":3460},") with code: ",{"type":39,"tag":197,"props":3462,"children":3464},{"href":3463},"references\u002Ffile-uploads.md",[3465],{"type":44,"value":3466},"file uploads",{"type":44,"value":204},{"type":39,"tag":158,"props":3469,"children":3471},{"id":3470},"state-doesnt-survive-between-requests",[3472],{"type":44,"value":3473},"State doesn't survive between requests",{"type":39,"tag":47,"props":3475,"children":3476},{},[3477,3479,3484,3486,3490,3492,3498,3499,3505,3507,3512,3514,3519,3521,3527],{"type":44,"value":3478},"Every request builds a fresh server and transport, and any invocation may land on a ",{"type":39,"tag":53,"props":3480,"children":3481},{},[3482],{"type":44,"value":3483},"different",{"type":44,"value":3485}," — 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 ",{"type":39,"tag":53,"props":3487,"children":3488},{},[3489],{"type":44,"value":3361},{"type":44,"value":3491}," live in a module-scoped ",{"type":39,"tag":230,"props":3493,"children":3495},{"className":3494},[],[3496],{"type":44,"value":3497},"Set",{"type":44,"value":327},{"type":39,"tag":230,"props":3500,"children":3502},{"className":3501},[],[3503],{"type":44,"value":3504},"Map",{"type":44,"value":3506},"\u002Fvariable: single-use \u002F 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 ",{"type":39,"tag":78,"props":3508,"children":3509},{},[3510],{"type":44,"value":3511},"looks",{"type":44,"value":3513}," 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 ",{"type":39,"tag":53,"props":3515,"children":3516},{},[3517],{"type":44,"value":3518},"durable store",{"type":44,"value":3520}," — Netlify Blobs or your database — keyed by the upload\u002Frequest id, and check-and-mark it there. (This is also why the server itself runs stateless, with ",{"type":39,"tag":230,"props":3522,"children":3524},{"className":3523},[],[3525],{"type":44,"value":3526},"sessionIdGenerator: undefined",{"type":44,"value":3528},".)",{"type":39,"tag":158,"props":3530,"children":3532},{"id":3531},"connecting-a-client",[3533],{"type":44,"value":3534},"Connecting a client",{"type":39,"tag":47,"props":3536,"children":3537},{},[3538,3540,3545],{"type":44,"value":3539},"Native remote-MCP support is now the norm; reach for the ",{"type":39,"tag":230,"props":3541,"children":3543},{"className":3542},[],[3544],{"type":44,"value":1835},{"type":44,"value":3546}," bridge only as a fallback.",{"type":39,"tag":126,"props":3548,"children":3549},{},[3550,3566,3591,3615],{"type":39,"tag":130,"props":3551,"children":3552},{},[3553,3558,3560],{"type":39,"tag":53,"props":3554,"children":3555},{},[3556],{"type":44,"value":3557},"Claude Code",{"type":44,"value":3559}," — ",{"type":39,"tag":230,"props":3561,"children":3563},{"className":3562},[],[3564],{"type":44,"value":3565},"claude mcp add --transport http my-mcp https:\u002F\u002F\u003Csite>.netlify.app\u002Fmcp --header \"Authorization: Bearer \u003Ctoken>\"",{"type":39,"tag":130,"props":3567,"children":3568},{},[3569,3574,3576,3582,3584,3589],{"type":39,"tag":53,"props":3570,"children":3571},{},[3572],{"type":44,"value":3573},"Cursor",{"type":44,"value":3575}," — add the server to ",{"type":39,"tag":230,"props":3577,"children":3579},{"className":3578},[],[3580],{"type":44,"value":3581},"mcp.json",{"type":44,"value":3583}," with the URL and an ",{"type":39,"tag":230,"props":3585,"children":3587},{"className":3586},[],[3588],{"type":44,"value":1906},{"type":44,"value":3590}," header.",{"type":39,"tag":130,"props":3592,"children":3593},{},[3594,3599,3601,3606,3608,3613],{"type":39,"tag":53,"props":3595,"children":3596},{},[3597],{"type":44,"value":3598},"Claude Desktop \u002F claude.ai",{"type":44,"value":3600}," — add a ",{"type":39,"tag":53,"props":3602,"children":3603},{},[3604],{"type":44,"value":3605},"Custom Connector",{"type":44,"value":3607}," (Settings → Connectors). Connectors are OAuth-oriented; for a static-bearer server the ",{"type":39,"tag":230,"props":3609,"children":3611},{"className":3610},[],[3612],{"type":44,"value":1835},{"type":44,"value":3614}," bridge is the reliable path.",{"type":39,"tag":130,"props":3616,"children":3617},{},[3618,3623,3624],{"type":39,"tag":53,"props":3619,"children":3620},{},[3621],{"type":44,"value":3622},"Fallback (older \u002F stdio-only clients)",{"type":44,"value":3559},{"type":39,"tag":230,"props":3625,"children":3627},{"className":3626},[],[3628],{"type":44,"value":3629},"npx mcp-remote https:\u002F\u002F\u003Csite>.netlify.app\u002Fmcp --header \"Authorization: Bearer \u003Ctoken>\"",{"type":39,"tag":47,"props":3631,"children":3632},{},[3633,3635,3641],{"type":44,"value":3634},"Full client matrix and the OAuth \u002F Custom Connector deep-dive: ",{"type":39,"tag":197,"props":3636,"children":3638},{"href":3637},"references\u002Fconnecting-clients.md",[3639],{"type":44,"value":3640},"connecting clients",{"type":44,"value":204},{"type":39,"tag":158,"props":3643,"children":3645},{"id":3644},"local-dev-and-deploy",[3646],{"type":44,"value":3647},"Local dev and deploy",{"type":39,"tag":126,"props":3649,"children":3650},{},[3651,3675,3709,3739,3756],{"type":39,"tag":130,"props":3652,"children":3653},{},[3654,3659,3660,3666,3668,3674],{"type":39,"tag":53,"props":3655,"children":3656},{},[3657],{"type":44,"value":3658},"Run it:",{"type":44,"value":362},{"type":39,"tag":230,"props":3661,"children":3663},{"className":3662},[],[3664],{"type":44,"value":3665},"netlify dev",{"type":44,"value":3667}," serves the function at ",{"type":39,"tag":230,"props":3669,"children":3671},{"className":3670},[],[3672],{"type":44,"value":3673},"http:\u002F\u002Flocalhost:8888\u002Fmcp",{"type":44,"value":204},{"type":39,"tag":130,"props":3676,"children":3677},{},[3678,3683,3685,3691,3693,3699,3701,3707],{"type":39,"tag":53,"props":3679,"children":3680},{},[3681],{"type":44,"value":3682},"Test it:",{"type":44,"value":3684}," the MCP Inspector — ",{"type":39,"tag":230,"props":3686,"children":3688},{"className":3687},[],[3689],{"type":44,"value":3690},"npx @modelcontextprotocol\u002Finspector",{"type":44,"value":3692}," — connect via Streamable HTTP to your URL with an ",{"type":39,"tag":230,"props":3694,"children":3696},{"className":3695},[],[3697],{"type":44,"value":3698},"Authorization: Bearer",{"type":44,"value":3700}," header and list\u002Fcall tools. Or point ",{"type":39,"tag":230,"props":3702,"children":3704},{"className":3703},[],[3705],{"type":44,"value":3706},"claude mcp add --transport http",{"type":44,"value":3708}," at the localhost URL.",{"type":39,"tag":130,"props":3710,"children":3711},{},[3712,3717,3719,3723,3725,3730,3732,3737],{"type":39,"tag":53,"props":3713,"children":3714},{},[3715],{"type":44,"value":3716},"Identity caveat:",{"type":44,"value":3718}," Netlify Identity does ",{"type":39,"tag":53,"props":3720,"children":3721},{},[3722],{"type":44,"value":1819},{"type":44,"value":3724}," work under ",{"type":39,"tag":230,"props":3726,"children":3728},{"className":3727},[],[3729],{"type":44,"value":3665},{"type":44,"value":3731},", so per-user-key auth must be tested on a deploy preview. See the ",{"type":39,"tag":53,"props":3733,"children":3734},{},[3735],{"type":44,"value":3736},"netlify-identity",{"type":44,"value":3738}," skill.",{"type":39,"tag":130,"props":3740,"children":3741},{},[3742,3747,3749,3755],{"type":39,"tag":53,"props":3743,"children":3744},{},[3745],{"type":44,"value":3746},"Deploy:",{"type":44,"value":3748}," push to Git, or ",{"type":39,"tag":230,"props":3750,"children":3752},{"className":3751},[],[3753],{"type":44,"value":3754},"netlify deploy --build --prod",{"type":44,"value":204},{"type":39,"tag":130,"props":3757,"children":3758},{},[3759,3764,3766,3772],{"type":39,"tag":53,"props":3760,"children":3761},{},[3762],{"type":44,"value":3763},"Secrets:",{"type":44,"value":3765}," set tokens\u002Fkeys as env vars (",{"type":39,"tag":230,"props":3767,"children":3769},{"className":3768},[],[3770],{"type":44,"value":3771},"netlify env:set MCP_BEARER_TOKEN \u003Cvalue> --secret",{"type":44,"value":3773},") — never in code.",{"type":39,"tag":158,"props":3775,"children":3777},{"id":3776},"cross-cutting-rules",[3778],{"type":44,"value":3779},"Cross-cutting rules",{"type":39,"tag":126,"props":3781,"children":3782},{},[3783,3816,3836],{"type":39,"tag":130,"props":3784,"children":3785},{},[3786,3788,3793,3795,3801,3803,3807,3809,3814],{"type":44,"value":3787},"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 ",{"type":39,"tag":53,"props":3789,"children":3790},{},[3791],{"type":44,"value":3792},"Netlify's secrets scanning and fails the deploy",{"type":44,"value":3794}," even after an otherwise-green build — the fix is to move it to a secret env var and read it at runtime with ",{"type":39,"tag":230,"props":3796,"children":3798},{"className":3797},[],[3799],{"type":44,"value":3800},"Netlify.env.get(...)",{"type":44,"value":3802},", and rotate the token if it was committed, ",{"type":39,"tag":78,"props":3804,"children":3805},{},[3806],{"type":44,"value":1819},{"type":44,"value":3808}," to disable the scanner. See ",{"type":39,"tag":53,"props":3810,"children":3811},{},[3812],{"type":44,"value":3813},"netlify-deploy",{"type":44,"value":3815}," for the scan controls.",{"type":39,"tag":130,"props":3817,"children":3818},{},[3819,3821,3827,3829,3835],{"type":44,"value":3820},"Inside functions, read env vars with ",{"type":39,"tag":230,"props":3822,"children":3824},{"className":3823},[],[3825],{"type":44,"value":3826},"Netlify.env.get(\"VAR\")",{"type":44,"value":3828},", not ",{"type":39,"tag":230,"props":3830,"children":3832},{"className":3831},[],[3833],{"type":44,"value":3834},"process.env",{"type":44,"value":204},{"type":39,"tag":130,"props":3837,"children":3838},{},[3839,3841,3847,3848,3854],{"type":44,"value":3840},"Add ",{"type":39,"tag":230,"props":3842,"children":3844},{"className":3843},[],[3845],{"type":44,"value":3846},".netlify",{"type":44,"value":3388},{"type":39,"tag":230,"props":3849,"children":3851},{"className":3850},[],[3852],{"type":44,"value":3853},".gitignore",{"type":44,"value":204},{"type":39,"tag":158,"props":3856,"children":3858},{"id":3857},"related-skills-and-references",[3859],{"type":44,"value":3860},"Related skills and references",{"type":39,"tag":126,"props":3862,"children":3863},{},[3864,3873,3882,3891],{"type":39,"tag":130,"props":3865,"children":3866},{},[3867,3871],{"type":39,"tag":197,"props":3868,"children":3869},{"href":199},[3870],{"type":44,"value":202},{"type":44,"value":3872}," — single-secret vs per-user API keys (Identity) in depth.",{"type":39,"tag":130,"props":3874,"children":3875},{},[3876,3880],{"type":39,"tag":197,"props":3877,"children":3878},{"href":3637},[3879],{"type":44,"value":3640},{"type":44,"value":3881}," — full client matrix, OAuth, and Custom Connectors.",{"type":39,"tag":130,"props":3883,"children":3884},{},[3885,3889],{"type":39,"tag":197,"props":3886,"children":3887},{"href":3463},[3888],{"type":44,"value":3466},{"type":44,"value":3890}," — letting an agent upload images\u002Ffiles via presigned URLs to Netlify Blobs.",{"type":39,"tag":130,"props":3892,"children":3893},{},[3894,3899,3901,3905,3907,3912,3914,3919,3921,3925,3927,3932],{"type":39,"tag":53,"props":3895,"children":3896},{},[3897],{"type":44,"value":3898},"netlify-functions",{"type":44,"value":3900}," — function syntax, routing, limits. ",{"type":39,"tag":53,"props":3902,"children":3903},{},[3904],{"type":44,"value":3736},{"type":44,"value":3906}," — Identity setup. ",{"type":39,"tag":53,"props":3908,"children":3909},{},[3910],{"type":44,"value":3911},"netlify-database",{"type":44,"value":3913}," \u002F ",{"type":39,"tag":53,"props":3915,"children":3916},{},[3917],{"type":44,"value":3918},"netlify-blobs",{"type":44,"value":3920}," — where to store keys and files. ",{"type":39,"tag":53,"props":3922,"children":3923},{},[3924],{"type":44,"value":3813},{"type":44,"value":3926}," — deploys. ",{"type":39,"tag":53,"props":3928,"children":3929},{},[3930],{"type":44,"value":3931},"netlify-config",{"type":44,"value":3933}," — env vars.",{"type":39,"tag":3935,"props":3936,"children":3937},"style",{},[3938],{"type":44,"value":3939},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":3941,"total":984},[3942,3959,3973,3986,4002,4019,4030],{"slug":3943,"name":3943,"fn":3944,"description":3945,"org":3946,"tags":3947,"stars":23,"repoUrl":24,"updatedAt":3958},"netlify-access-control","manage Netlify site access control","Use when the task involves controlling who can reach a Netlify site, or telling Netlify Identity apart from Secure Access. Trigger whenever the user wants to lock a site or deploy to their company\u002Fteam, restrict access to employees only, build an internal or employees-only app, set up password protection, SSO, or SAML, asks \"who can access my site\", or is confused about Netlify Identity vs Secure Access vs team login vs OAuth providers. Routes the request to the right layer — app-level Identity, site-visitor Password Protection, the Auth0 extension, or Team\u002FOrg SAML SSO — and explains the two-layer (perimeter + in-app identity) pattern and its double-login tradeoff. For building the app-level auth itself, use the netlify-identity skill.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3948,3951,3952,3955],{"name":3949,"slug":3950,"type":13},"Access Control","access-control",{"name":9,"slug":8,"type":13},{"name":3953,"slug":3954,"type":13},"Permissions","permissions",{"name":3956,"slug":3957,"type":13},"Security","security","2026-07-14T05:40:29.082149",{"slug":96,"name":96,"fn":3960,"description":3961,"org":3962,"tags":3963,"stars":23,"repoUrl":24,"updatedAt":3972},"run AI agent tasks on Netlify","Run AI agent tasks remotely on Netlify using Claude, Codex, or Gemini. Use when the user wants to run an AI agent on their site, get a second opinion from another model, or delegate development tasks to run remotely against their repo.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3964,3965,3968,3971],{"name":18,"slug":19,"type":13},{"name":3966,"slug":3967,"type":13},"Automation","automation",{"name":3969,"slug":3970,"type":13},"LLM","llm",{"name":9,"slug":8,"type":13},"2026-07-17T05:30:19.462913",{"slug":3974,"name":3974,"fn":3975,"description":3976,"org":3977,"tags":3978,"stars":23,"repoUrl":24,"updatedAt":3985},"netlify-ai-gateway","route AI requests via Netlify AI Gateway","Reference for Netlify AI Gateway — the managed proxy that routes calls to OpenAI, Anthropic, and Google Gemini SDKs without provider API keys. Use this skill any time the user wants to add AI on a Netlify site (chat, completion, reasoning, image generation, image-to-image edit\u002Fstylize), choose or change a model, wire up the OpenAI \u002F Anthropic \u002F @google\u002Fgenai SDK, decide which provider to use for an image-gen feature (it's Gemini-only on the gateway), or debug \"model not found\" \u002F \"API key missing\" against the gateway. Required reading before pinning a model — the gateway exposes a curated subset, not every provider model.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3979,3982,3983,3984],{"name":3980,"slug":3981,"type":13},"AI Infrastructure","ai-infrastructure",{"name":21,"slug":22,"type":13},{"name":3969,"slug":3970,"type":13},{"name":9,"slug":8,"type":13},"2026-07-17T05:30:13.14555",{"slug":3918,"name":3918,"fn":3987,"description":3988,"org":3989,"tags":3990,"stars":23,"repoUrl":24,"updatedAt":4001},"manage file storage with Netlify Blobs","Guide for using Netlify Blobs for file and asset storage — images, documents, uploads, exports, cached binary artifacts. Covers getStore(), CRUD operations, metadata, listing, deploy-scoped vs site-scoped stores, and local development. Do NOT use Blobs as a dynamic data store — use Netlify Database for that.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3991,3994,3997,3998],{"name":3992,"slug":3993,"type":13},"Backend","backend",{"name":3995,"slug":3996,"type":13},"File Storage","file-storage",{"name":9,"slug":8,"type":13},{"name":3999,"slug":4000,"type":13},"Storage","storage","2026-07-17T05:30:16.503306",{"slug":4003,"name":4003,"fn":4004,"description":4005,"org":4006,"tags":4007,"stars":23,"repoUrl":24,"updatedAt":4018},"netlify-caching","configure CDN caching on Netlify","Guide for controlling caching on Netlify's CDN. Use when configuring cache headers, setting up stale-while-revalidate, implementing on-demand cache purge, or understanding Netlify's CDN caching behavior. Covers Cache-Control, Netlify-CDN-Cache-Control, cache tags, durable cache, and framework-specific caching patterns.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4008,4011,4014,4015],{"name":4009,"slug":4010,"type":13},"Caching","caching",{"name":4012,"slug":4013,"type":13},"Deployment","deployment",{"name":9,"slug":8,"type":13},{"name":4016,"slug":4017,"type":13},"Performance","performance","2026-07-14T05:40:20.066717",{"slug":3931,"name":3931,"fn":4020,"description":4021,"org":4022,"tags":4023,"stars":23,"repoUrl":24,"updatedAt":4029},"configure Netlify site settings","Reference for netlify.toml configuration and site environment variables. Use when configuring build settings, redirects, rewrites, headers, deploy contexts, the `[dev]` block that controls `netlify dev` (command, port, targetPort, framework), or any site-level configuration — and when managing environment variables or secrets with the Netlify CLI, including scoping values to specific deploy contexts. Covers the complete netlify.toml syntax including redirects with splats\u002Fconditions, headers, deploy contexts, functions config, edge functions config, and the `[dev]` block (including when `framework` must be `\"#custom\"` — required when both a custom `command` and `targetPort` are set).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4024,4027,4028],{"name":4025,"slug":4026,"type":13},"Configuration","configuration",{"name":4012,"slug":4013,"type":13},{"name":9,"slug":8,"type":13},"2026-07-17T05:30:21.284801",{"slug":3911,"name":3911,"fn":4031,"description":4032,"org":4033,"tags":4034,"stars":23,"repoUrl":24,"updatedAt":4043},"provision and manage Netlify Database","Guide for using Netlify Database — the GA managed Postgres product built into Netlify. Use when a project needs any kind of dynamic, structured, or relational data. Covers provisioning via @netlify\u002Fdatabase, Drizzle ORM (@beta) setup, migrations, preview branching, and safe production data handling. Blobs is only for file\u002Fasset storage — any dynamic data belongs in the database.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4035,4036,4039,4040],{"name":3992,"slug":3993,"type":13},{"name":4037,"slug":4038,"type":13},"Database","database",{"name":9,"slug":8,"type":13},{"name":4041,"slug":4042,"type":13},"PostgreSQL","postgresql","2026-07-20T05:58:58.934045",{"items":4045,"total":1112},[4046,4060,4072,4079,4086,4093,4100,4107,4113,4120,4132,4147],{"slug":4047,"name":4047,"fn":4048,"description":4049,"org":4050,"tags":4051,"stars":1502,"repoUrl":4058,"updatedAt":4059},"configure-axis","configure AXIS agent evaluation scenarios","Author AXIS (Agent Experience Index Score) scenarios and axis.config.json for a project. Use when the user asks to set up AXIS, add a scenario, write or edit axis.config.json, or evaluate an AI agent with AXIS.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4052,4053,4054,4057],{"name":18,"slug":19,"type":13},{"name":4025,"slug":4026,"type":13},{"name":4055,"slug":4056,"type":13},"Evals","evals",{"name":9,"slug":8,"type":13},"https:\u002F\u002Fgithub.com\u002Fnetlify\u002Faxis","2026-07-20T05:59:47.468757",{"slug":4061,"name":4061,"fn":4062,"description":4063,"org":4064,"tags":4065,"stars":1502,"repoUrl":4058,"updatedAt":4071},"using-axis","run and interpret AXIS reports","Run AXIS, read its reports, navigate its project layout, and interpret scores. Use when the user asks to run AXIS, invoke the CLI, compare runs, explain a score, find a regression, manage baselines, or understand where AXIS writes its files.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4066,4069,4070],{"name":4067,"slug":4068,"type":13},"CLI","cli",{"name":4055,"slug":4056,"type":13},{"name":9,"slug":8,"type":13},"2026-07-14T05:40:13.443461",{"slug":3943,"name":3943,"fn":3944,"description":3945,"org":4073,"tags":4074,"stars":23,"repoUrl":24,"updatedAt":3958},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4075,4076,4077,4078],{"name":3949,"slug":3950,"type":13},{"name":9,"slug":8,"type":13},{"name":3953,"slug":3954,"type":13},{"name":3956,"slug":3957,"type":13},{"slug":96,"name":96,"fn":3960,"description":3961,"org":4080,"tags":4081,"stars":23,"repoUrl":24,"updatedAt":3972},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4082,4083,4084,4085],{"name":18,"slug":19,"type":13},{"name":3966,"slug":3967,"type":13},{"name":3969,"slug":3970,"type":13},{"name":9,"slug":8,"type":13},{"slug":3974,"name":3974,"fn":3975,"description":3976,"org":4087,"tags":4088,"stars":23,"repoUrl":24,"updatedAt":3985},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4089,4090,4091,4092],{"name":3980,"slug":3981,"type":13},{"name":21,"slug":22,"type":13},{"name":3969,"slug":3970,"type":13},{"name":9,"slug":8,"type":13},{"slug":3918,"name":3918,"fn":3987,"description":3988,"org":4094,"tags":4095,"stars":23,"repoUrl":24,"updatedAt":4001},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4096,4097,4098,4099],{"name":3992,"slug":3993,"type":13},{"name":3995,"slug":3996,"type":13},{"name":9,"slug":8,"type":13},{"name":3999,"slug":4000,"type":13},{"slug":4003,"name":4003,"fn":4004,"description":4005,"org":4101,"tags":4102,"stars":23,"repoUrl":24,"updatedAt":4018},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4103,4104,4105,4106],{"name":4009,"slug":4010,"type":13},{"name":4012,"slug":4013,"type":13},{"name":9,"slug":8,"type":13},{"name":4016,"slug":4017,"type":13},{"slug":3931,"name":3931,"fn":4020,"description":4021,"org":4108,"tags":4109,"stars":23,"repoUrl":24,"updatedAt":4029},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4110,4111,4112],{"name":4025,"slug":4026,"type":13},{"name":4012,"slug":4013,"type":13},{"name":9,"slug":8,"type":13},{"slug":3911,"name":3911,"fn":4031,"description":4032,"org":4114,"tags":4115,"stars":23,"repoUrl":24,"updatedAt":4043},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4116,4117,4118,4119],{"name":3992,"slug":3993,"type":13},{"name":4037,"slug":4038,"type":13},{"name":9,"slug":8,"type":13},{"name":4041,"slug":4042,"type":13},{"slug":3813,"name":3813,"fn":4121,"description":4122,"org":4123,"tags":4124,"stars":23,"repoUrl":24,"updatedAt":4131},"deploy and host projects on Netlify","Deploy, host, and publish web projects on Netlify with the Netlify CLI. Use when the user wants to deploy a site or repository to Netlify, link a local project to a Netlify site, ship a production or preview\u002Fdraft deploy, set up Git-based continuous deployment, run a manual or local deploy, configure CI deploys, or troubleshoot a failed or misconfigured deploy. Also use to view or tail a deployed site's runtime logs — function and edge-function output, deploy logs — via `netlify logs` when debugging what a live site is doing (recent errors, recent activity, streaming output).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4125,4126,4127,4128],{"name":4067,"slug":4068,"type":13},{"name":4012,"slug":4013,"type":13},{"name":9,"slug":8,"type":13},{"name":4129,"slug":4130,"type":13},"Web Development","web-development","2026-07-17T05:30:14.218977",{"slug":4133,"name":4133,"fn":4134,"description":4135,"org":4136,"tags":4137,"stars":23,"repoUrl":24,"updatedAt":4146},"netlify-edge-functions","write Netlify Edge Functions","Guide for writing Netlify Edge Functions. Use when building middleware, geolocation-based logic, request\u002Fresponse manipulation, authentication checks, A\u002FB testing, or any low-latency edge compute. Covers Deno runtime, context.next() middleware pattern, geolocation, and when to choose edge vs serverless.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4138,4141,4144,4145],{"name":4139,"slug":4140,"type":13},"Edge Functions","edge-functions",{"name":4142,"slug":4143,"type":13},"Middleware","middleware",{"name":9,"slug":8,"type":13},{"name":4016,"slug":4017,"type":13},"2026-07-14T05:40:34.147865",{"slug":4148,"name":4148,"fn":4149,"description":4150,"org":4151,"tags":4152,"stars":23,"repoUrl":24,"updatedAt":4161},"netlify-forms","handle HTML forms with Netlify","Guide for using Netlify Forms for HTML form handling. Use when adding contact forms, feedback forms, file upload forms, or any form that should be collected by Netlify. Covers the data-netlify attribute, spam filtering, AJAX submissions, file uploads, notifications, and the submissions API.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[4153,4156,4159,4160],{"name":4154,"slug":4155,"type":13},"Forms","forms",{"name":4157,"slug":4158,"type":13},"HTML","html",{"name":9,"slug":8,"type":13},{"name":4129,"slug":4130,"type":13},"2026-07-14T05:40:16.075367"]