netlify-mcp-servers について
netlify-mcp-servers は、Netlify Functions 上で独自の MCP サーバーを構築し、アプリのツールやデータを AI エージェントに提供する開発者向けのスキルです。公式 SDK を使うステートレスな HTTP サーバー、認証、権限、安全対策、クライアント接続を扱い、Netlify がホストするプラットフォーム管理用サーバーとは区別しています。
主な機能
- 01公式 MCP SDK と HTTP トランスポートの利用
- 02Netlify Function でのツール定義
- 03共有シークレットまたはユーザー API キーの設定
- 04入力検証とレート制限
- 05最小権限の適用とツール呼び出しの記録
- 06クライアントからのリモート MCP サーバー接続
活用シーン
- 01AI エージェントへのアプリのツールやデータの提供
- 02Netlify Function としての MCP エンドポイントのデプロイ
- 03MCP 呼び出しの認証とレート制限の設定
MCP 対応
HTTP 経由で MCP クライアントにアプリのツールを公開するリモート MCP サーバーの構築と、クライアントの接続方法を説明します。対象は独自サーバーであり、Netlify プラットフォーム管理用のホスト型サーバーではありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ netlify/ context-and-tools --skill netlify-mcp-serversインストール後、Claude Code で/netlify-mcp-serversを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/authentication.mdconnecting-clients.mdfile-uploads.md詳細
Netlify MCP サーバー
MCP サーバーは、AI クライアント(Claude Desktop、Claude Code、Cursor など)が呼び出せるツール(および任意でリソース/プロンプト)を公開します。Netlify では、リモート MCP サーバーは HTTP 経由で MCP プロトコルをやり取りする1 つの Netlify Functionです。このスキルを使うと、動作する安全なサーバーを構築し、クライアントを接続できます。
「Netlify MCP」は 2 つの異なるものを指します。正しいほうを構築しているか確認してください。 Netlify は、AI クライアントがユーザーに代わってNetlify プラットフォームを操作し、プロジェクトの作成、デプロイの実行、環境変数やインフラの管理を Netlify アカウント経由で行える、独自のホスト型 MCP サーバーを公開しています。こちらを自分で書く必要はありません。Netlify の MCP サーバーのドキュメントに従って、クライアントを Netlify のホスト型 MCP サーバーに接続してください(サイト上でエージェントを実行する方法については、netlify-agent-runnerスキルも参照してください)。このスキルが扱うのは、もう一方のものです。Netlify Function 上でホストし、エージェントに自分のアプリのツールやデータを公開する、自分専用の MCP サーバーを構築します。「エージェントに自分の Netlify サイト/デプロイ/環境変数を管理させたい」という場合は、ホスト型の Netlify MCP サーバーを使うのであって、自分で Function を書くのではありません。
同じセットアップを 2 通りの方法で利用できます。
- スタンドアロンサーバー — MCP エンドポイント(たとえばサードパーティ API のラッパー)だけを目的とするリポジトリです。
- 既存アプリへの追加 — サイトと並ぶもう 1 つの Function として追加します。ロジックが重複しないよう、ツールから、UI や REST ルートがすでに使っている同じサービス/データレイヤーを呼び出してください。
構築を始める前に
認証コードの構成に影響するため、まず次の 1 点を決めてください。
- このサーバーを呼び出すのは誰ですか? 自分だけ(個人用/シングルユーザーサーバー)→ 単一の共有シークレットを使います。複数の人がそれぞれ自分として操作する → Netlify Identity によるユーザーごとの API キーを使います。認証を参照してください。
迷った場合は、まず単一の共有シークレットから始めてください。数行で設定でき、後からユーザーごとのキーを追加できます。別の指定がなければ、こちらをデフォルトにします。
技術スタック
公式の MCP SDK と Web 標準の Streamable HTTP トランスポートを使用し、Netlify Function 内でステートレスに実行します。
npm install @modelcontextprotocol/sdk zod
Netlify Function は Web プラットフォームに対応しており、Requestを受け取り、Responseを返します。SDK には、まさにこれらのプリミティブを基盤とするトランスポートWebStandardStreamableHTTPServerTransportが用意されています(SDK が内部で使用するものと同じコアであり、Cloudflare Workers/Deno/Bun でも使われています)。Requestを渡して、生成されたResponseを返すだけで済みます。アダプターもバージョンの固定も不要です。古いガイドでは、Node 向けのStreamableHTTPServerTransportとfetch-to-nodeブリッジを使い、トランスポートが要求する Node のreq/resオブジェクトを合成する方法が紹介されています。Netlify ではそのどちらも不要です。使わないほうがシンプルで、この構成で動作することも確認済みです。
これとは別に、注意点が 1 つあります。Acceptヘッダーにapplication/jsonとtext/event-streamの両方が含まれていない POST に対し、トランスポートはHTTP 406を返します。これは MCP 仕様上の要件であり、満たす必要があるのはクライアントです。406 が返された場合は、サーバーではなくクライアントのAcceptヘッダーを修正してください。また、SDK にプロトコル処理を任せれば、JSON-RPC のフレーミングやプロトコルバージョンのハンドシェイクを自分で保守する必要もありません。
サーバー Function
Web 標準のトランスポートを使えば、数行で実装できます。古いガイドに記載されている内容の大半は、不要な Node ブリッジです。netlify/functions/mcp.tsに配置してください。
import type { Config, Context } from "@netlify/functions";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { z } from "zod";
import { checkBearer } from "../lib/mcp/bearer"; // see Authentication
function buildServer() {
const server = new McpServer({ name: "my-mcp", version: "0.1.0" });
server.tool(
"get_item",
"Fetch a single item by id. Read-only.",
{ id: z.string().describe("The item's unique id") },
async ({ id }) => ({
content: [{ type: "text", text: JSON.stringify(await getItem(id)) }],
}),
);
return server;
}
export default async (req: Request, _context: Context) => {
if (!checkBearer(req)) return new Response("Unauthorized", { status: 401 });
// Stateless JSON server: it only does request/response over POST. Reject other
// methods — a GET makes the transport open an SSE stream that never closes, which
// a serverless function can't serve (you'll get a 502).
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
// Fresh server + transport per request, no session to persist. enableJsonResponse
// returns one application/json body instead of an SSE stream — the right fit here.
const server = buildServer();
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
// Hand over the Web Request, return the Web Response. The transport owns JSON-RPC
// framing, body parsing (a malformed body comes back as a clean 400), and the handshake.
await server.connect(transport);
return transport.handleRequest(req);
};
export const config: Config = { path: "/mcp" };
これでデプロイ可能なサーバーが完成です。あとはツール、認証、安全対策を追加します。
ブラウザベースのクライアントと CORS
Netlify Functions は CORS ヘッダーを自動で追加しません。また、上記のサーバーは OPTIONS を含む、POST 以外のすべてのメソッドに 405 を返します。ブラウザは OPTIONS をプリフライトとして送信します。通常のケースでは問題ありません。ネイティブの MCP クライアント(Claude Code、Cursor、Claude Desktop、mcp-remote ブリッジ)はブラウザではなく、同一オリジンポリシーを適用しないため、CORS は一切必要ありません。そのため、ブラウザからの呼び出しが失敗しても、これらのクライアントは動作します。
問題になるのは、MCP クライアントがブラウザ内で実行される場合、つまりウェブアプリがクロスオリジンでサーバーを呼び出す場合だけです。その場合、レスポンスに Access-Control-Allow-Origin が含まれていないとブラウザがリクエストをブロックします。また、ブラウザは最初に OPTIONS プリフライトを送信するため、Access-Control-Allow-Methods(POST を含む)と Access-Control-Allow-Headers(Authorization と Content-Type を含む)を付けて 2xx を返す必要があります。ブラウザーのコンソールに「CORS ポリシーによってブロックされました:Access-Control-Allow-Origin ヘッダーがありません」というエラーが表示されるのはこのためです。サーバーの故障やプラットフォームのバグではありません。関数内で、405 のチェックより前にプリフライトに応答し、POST のレスポンスにも CORS ヘッダーを付けてください。
const CORS = {
"Access-Control-Allow-Origin": Netlify.env.get("MCP_ALLOWED_ORIGIN") ?? "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Authorization, Content-Type, Mcp-Session-Id",
};
// In the handler, before the 405 check:
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
// ...then reject other non-POST methods with 405, and add CORS to the transport's Response.
関数自身がこれらのヘッダーを設定する必要があります。ブラウザーの CORS エラーを Netlify にエスカレーションしたり、認証を緩めて回避したりしないでください。
ツールの定義
各ツールは name、1 行の description、zod 入力スキーマ、および { content: [...] } を返すハンドラーで構成されます。モデルが見るのは説明文とパラメーターの .describe() テキストだけです。エージェント向けの API ドキュメントとして、ツールの機能、使用するタイミング、取り消せない操作があればそれを明記してください。
ツールが増えてきたら、各ツールを個別のモジュールに分け、buildServer() で登録してください。ツールが多数あるサーバーでは、レジストリ({ name, description, inputSchema, handler } の配列)を用意し、tools/list と tools/call を一度だけ接続することがよくあります。どちらの場合も、上記のトランスポート設定は同じです。
認証
MCP クライアントは、サーバーを呼び出す権限があることを証明する必要があります。すべてのリクエストに Authorization: Bearer <token> を含め、これ以外はすべて 401 で拒否してください。
共有シークレット 1 つ(個人利用/単一ユーザー向け)。環境変数 1 つを使用し、定数時間で比較します。これを netlify/lib/mcp/bearer.ts に記述してください。
import { timingSafeEqual } from "node:crypto";
export function checkBearer(req: Request): boolean {
const expected = Netlify.env.get("MCP_BEARER_TOKEN");
if (!expected) return false;
const match = req.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i);
if (!match) return false;
const a = Buffer.from(match[1]);
const b = Buffer.from(expected);
// Length check first because timingSafeEqual throws (RangeError) on unequal-length
// buffers. The token is fixed-length, so the early return leaks nothing useful.
return a.length === b.length && timingSafeEqual(a, b);
}
openssl rand -hex 32 でトークンを生成し、シークレット環境変数として保存してください。
ユーザーごとの API キー(複数ユーザー向け)。Netlify Identity でウェブ UI を保護し、各ユーザーが独自のキーを発行できるようにします。各キーのハッシュのみを保存し(平文は決して保存しません)、そのユーザーに紐付けます。リクエストごとにキーからユーザーを特定し、そのユーザーをツールハンドラーに渡して、適切なユーザーとしてツールが動作するようにします。スキーマ、生成、ハッシュ化、失効、ユーザーの特定を含む完全なパターンについては、認証を参照してください。
スコープはシンプルに始めましょう。 最もシンプルなモデルは、すべて許可するか、すべて拒否するかです。有効なキーであれば、そのキーに紐付いたユーザーとしてすべてのツールを呼び出せます。通常は、ここから始めるのが適切です。具体的なニーズ(例:読み取り専用キー)が出てきたらキーごとのスコープを追加し、アプリに本当に必要であれば、ツールごとのスコープやロール階層に拡張してください。より包括的な RBAC 設計が求められた場合は、完全な階層を最初から必須とするのではなく、シンプルな基本設計を示し、その上にスコープを重ねてください。
安全性と権限
ツールは自律型エージェントに渡される公開 API です。慎重に扱ってください。
- 必要な機能だけを公開してください。 読み取りと書き込みを分け、破壊的なツールを公開する前によく検討してください。一般的で安全な選択肢は、削除ツールを完全に省き、破壊的な操作は人間が操作する UI に限定することです。
- 取り消せない操作や公開される操作にはガードを設けてください。 ツールの説明に「投稿する前に、正確な文面をユーザーに見せて確認を取ってください」など、明示的な指示を記載します。これはモデルレベルのソフトなガードなので、すぐに無効化できるトークンという実際のキルスイッチも用意してください。
- クライアントの認証情報はバックエンドの認証情報と分けてください。 クライアントはサーバーに対して認証し(ベアラー/API キー)、サーバーはデータベースやサードパーティ API に対してサーバー独自のシークレットで認証します。バックエンドの強力なキーをクライアントに渡してはいけません。
- 最小権限のバックエンド認証情報を使用してください。 アカウントレベルの認証情報ではなく、アプリパスワードやスコープを限定したトークンを使用すれば、漏えい時の影響を抑え、無効化できます。
- 入力を検証し(
zodスキーマで検証できます)、すべてのツール呼び出しをログに記録してください。 エージェントの操作内容は、Netlify 関数のログに出力されるconsole.infoで確認できます。
レート制限
MCP サーバーは、自律型エージェントが短い間隔で繰り返し呼び出せる公開エンドポイントです。上限を設けてください。Netlify Functions には宣言型のレート制限が組み込まれているため、独自にカウンターを実装する必要はありません(インスタンスごとのインメモリカウンターでは、関数インスタンスをまたいで機能しません。次のセクションを参照してください)。関数の config エクスポートに rateLimit ブロックを追加します。
export const config: Config = {
path: "/mcp",
rateLimit: {
windowSize: 60, // time window in seconds; capped at 180
windowLimit: 100, // max requests per window
aggregateBy: ["ip", "domain"], // group by ip, domain, or both
},
};
上限を超えると、プラットフォームはデフォルトで HTTP 429 を返します(または action: "rewrite" と to パスを設定し、超過したトラフィックを専用ページに送信できます)。関数のレート制限は関数の config エクスポートでのみ設定できます。netlify.toml では定義できません。
ファイルのアップロード
ツールでエージェントにファイル(投稿する画像や添付するドキュメントなど)を渡してもらう必要がある場合、ツール呼び出しでバイト列を Base64 として送信しないでください。モデルのコンテキストが肥大化し、ペイロードの上限にも達する可能性があります。代わりに、エージェントに有効期間が短く一度だけ使える署名付き URLを渡し、その URL に PUT で生のバイト列を送信させて、ファイルをNetlify Blobsに保存し、ほかのツールからは安定したキーでファイルを参照してください。アップロード ID、コンテンツタイプ、サイズ、有効期限を対象に、シークレット環境変数を鍵としてHMAC-SHA256で URL に署名し、定数時間で検証してください。署名自体が認可となるため、PUT にベアラートークンは必要ありません。アップロードエンドポイントでは、宣言されたコンテンツタイプとサイズを強制し、リプレイを拒否してください。コードを含む 3 ステップのフロー全体(prepare_upload → PUT → finalize_upload)については、ファイルのアップロードを参照してください。
リクエスト間で状態は保持されません
リクエストごとに新しいサーバーとトランスポートが構築され、呼び出しは毎回、別の関数インスタンス、またはコールドスタートした関数インスタンスに到達する可能性があります。モジュールレベルのメモリはインスタンス間で共有されず、コールドスタートをまたいで永続化されません。そのため、呼び出し間で保持する必要がある状態を、モジュールスコープの Set/Map/変数に保存することはできません。これは、上記の署名付きアップロードの一度限りの使用/リプレイ追跡、冪等性キー、「この ID は処理済み」というガード、手動で追跡するユーザーごとのカウンターなどを指します。インメモリのガードは、ローカル環境や 1 つのウォーム状態のインスタンスでは正しく動作しているように見えますが、別のインスタンスがリクエストを処理した瞬間、リプレイされたアップロードを通したり(または呼び出しを二重処理したり)してしまいます。その状態は、アップロード/リクエスト ID をキーとして、Netlify Blobs やデータベースなどの永続ストアに保存し、そこで確認してから記録してください。(これが、サーバー自体を sessionIdGenerator: undefined にしてステートレスに実行する理由でもあります。)
クライアントの接続
ネイティブのリモート MCP 対応が標準になっています。mcp-remote ブリッジは、フォールバックとしてのみ使用してください。
- Claude Code —
claude mcp add --transport http my-mcp https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>" - Cursor — URL と
Authorizationヘッダーを指定して、サーバーをmcp.jsonに追加します。 - Claude Desktop / claude.ai — カスタムコネクター(設定 → コネクター)を追加します。コネクターは OAuth を前提としているため、静的ベアラートークンを使うサーバーには
mcp-remoteブリッジが確実な方法です。 - フォールバック(古いクライアント/stdio 専用クライアント) —
npx mcp-remote https://<site>.netlify.app/mcp --header "Authorization: Bearer <token>"
クライアントの全一覧と OAuth/カスタムコネクターの詳細については、クライアントの接続を参照してください。
ローカル開発とデプロイ
- 実行:
netlify devを実行すると、関数がhttp://localhost:8888/mcpで提供されます。 - テスト: MCP Inspector(
npx @modelcontextprotocol/inspector)を使い、Streamable HTTP 経由でAuthorization: Bearerヘッダーを付けて URL に接続し、ツールを一覧表示/呼び出します。または、ローカルホストの URL をclaude mcp add --transport httpに指定します。 - 注意事項: Netlify Identity は
netlify devでは動作しないため、ユーザーごとのキー認証はデプロイプレビューでテストする必要があります。netlify-identity スキルを参照してください。 - デプロイ: Git にプッシュするか、
netlify deploy --build --prodを実行します。 - シークレット: トークン/キーは環境変数として設定します(
netlify env:set MCP_BEARER_TOKEN <value> --secret)。コードに記述してはいけません。
共通ルール
- シークレットをハードコードしないでください。トークン、API キー、署名用シークレットは Netlify の環境変数として保存し、シークレットとして指定してください。漏えいのリスクに加えて、ソースコード(またはビルドで公開されるファイル)にベアラートークンや署名用シークレットを記述すると、ほかのビルドが正常に完了していても Netlify のシークレットスキャンが検出し、デプロイに失敗します。この場合は、シークレット環境変数に移して実行時に
Netlify.env.get(...)で読み取ってください。コミット済みの場合はトークンをローテーションしてください。スキャナーを無効にしてはいけません。スキャンの制御については netlify-deploy を参照してください。 - 関数内では、
process.envではなくNetlify.env.get("VAR")で環境変数を読み取ります。 .gitignoreに.netlifyを追加します。
関連スキルとリファレンス
- 認証 — 単一のシークレットとユーザーごとの API キー(Identity)の違いについて詳しく説明します。
- クライアントの接続 — クライアントの全一覧、OAuth、カスタムコネクターについて説明します。
- ファイルのアップロード — 署名付き URL を介して、エージェントが Netlify Blobs に画像/ファイルをアップロードできるようにします。
- netlify-functions — 関数の構文、ルーティング、制限。netlify-identity — Identity の設定。netlify-database/netlify-blobs — キーとファイルの保存先。netlify-deploy — デプロイ。netlify-config — 環境変数。