mcpField
F
FastMCP

FastMCP

ローカル
github.com/punkpeye開発ツール3.3k2 日前に更新

FastMCP について

FastMCP は、クライアントセッションを処理する MCP サーバーを構築するための TypeScript フレームワークです。公式 SDK の上に構築されており、低レベルの実装を意識せずに開発したい開発者を対象としています。ハンドシェイク方式の 2025-11-25 以前の MCP リビジョンに対応し、2026-07-28 のステートレス仕様には対応していません。

主な機能

  1. 01Tool、Resource、Prompt の簡潔な定義
  2. 02OpenAPI から MCP への変換
  3. 03SSE 互換の HTTP ストリーミング
  4. 04Cloudflare Workers などのエッジランタイム対応
  5. 05サーバーレス向けのステートレスモード
  6. 06ポートを使わないユニットテスト用のインメモリトランスポート

活用シーン

  1. 01TypeScript で MCP サーバーを素早く構築
  2. 02OpenAPI のエンドポイントを MCP ツールに変換
  3. 03MCP サーバーのエッジランタイムへのデプロイ

MCP 対応

FastMCP は MCP サーバーを構築し、Tool、Resource、Prompt を MCP クライアントに公開します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"fastmcp-punkpeye": {
"command": "npx",
"args": [
"-y",
"fastmcp"
]
}
}
}
起動コマンドnpx -y fastmcp

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
npmfastmcp

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

FastMCP

クライアントセッションを処理できるMCPサーバーを構築するためのTypeScriptフレームワークです。

重要

FastMCPは、レガシーなハンドシェイクベースのMCPリビジョン(2025-11-25以前)を実装しています。プロトコルをステートレスにした現行の仕様2026-07-28には対応していません。現行仕様を対象とするフレームワークには、ViteMCPを使用してください。

機能

公式 SDK ではなく FastMCP を使うのはどのような場合ですか?

FastMCP は公式 SDK の上に構築されています。

公式 SDK は MCP を構築するための基本的な構成要素を提供しますが、多くの実装の詳細はユーザーに委ねられています。

FastMCP は、以下を提供する独自の方針に基づいたフレームワークによって、こうした複雑さを解消します。

  • 定型処理をすべて自動で処理します
  • 一般的なタスク向けにシンプルで直感的な API を提供します
  • ベストプラクティスとエラー処理を標準で備えています
  • MCP の中核機能に集中できます

FastMCP を選ぶ場合: 低レベルの実装詳細を扱わずに、MCP サーバーをすばやく構築したい場合です。

公式 SDK を使う場合: 最大限の制御が必要な場合、または特定のアーキテクチャ要件がある場合です。この場合は、よくある落とし穴を避けるため、FastMCP の実装を参考にすることをおすすめします。

インストール

npm install fastmcp

クイックスタート

メモ

実際の環境で FastMCP を使用した事例が数多くあります。例についてはショーケースをご覧ください。

import { FastMCP } from "fastmcp";
import { z } from "zod"; // Or any validation library that supports Standard Schema

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  name: "add",
  description: "Add two numbers",
  parameters: z.object({
    a: z.number(),
    b: z.number(),
  }),
  execute: async (args) => {
    return String(args.a + args.b);
  },
});

server.start({
  transportType: "stdio",
});

以上です! MCP サーバーが動作するようになりました。

ターミナルで以下を実行して、サーバーをテストできます。

git clone https://github.com/punkpeye/fastmcp.git
cd fastmcp

pnpm install
pnpm build

# Test the addition server example using CLI:
npx fastmcp dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx fastmcp inspect src/examples/addition.ts

独自の MCP サーバーを構築するためのボイラープレートリポジトリをお探しの場合は、fastmcp-boilerplateをご覧ください。

リモートサーバーのオプション

FastMCP はリモート通信向けに複数のトランスポートオプションをサポートしており、リモートマシン上でホストされている MCP にネットワーク経由でアクセスできます。

HTTP ストリーミング

HTTP ストリーミングは、対応環境では SSE よりも効率的な代替手段となり、大きなペイロードではパフォーマンスが向上する可能性があります。

HTTP ストリーミングをサポートするサーバーは、次のように実行できます。

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

これによりサーバーが起動し、http://localhost:8080/mcp で HTTP ストリーミング接続を待ち受けます。

注: httpStream.endpoint オプションを使用して、エンドポイントのパスをカスタマイズすることもできます(デフォルトは /mcp です)。

注: HTTP ストリーミングと組み込みの OAuth ルートを発行者パス配下で提供するには、httpStream.basePath を設定します(例:/issuer1)。RFC 8414 に従い、/.well-known/oauth-authorization-server/issuer1 で認可サーバーのメタデータを公開します。

注: http://localhost:8080/sse で SSE サーバーも起動します。

適切なクライアントトランスポートを使用して、これらのサーバーに接続できます。

HTTP ストリーミング接続の場合:

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new StreamableHTTPClientTransport(
  new URL(`http://localhost:8080/mcp`),
);

await client.connect(transport);

SSE 接続の場合:

import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));

await client.connect(transport);
HTTPS サポート

FastMCP では、SSL 証明書のオプションを指定することで、セキュアな接続のための HTTPS をサポートしています。

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8443,
    sslCert: "./path/to/cert.pem",
    sslKey: "./path/to/key.pem",
    sslCa: "./path/to/ca.pem", // Optional: for client certificate authentication
  },
});

これにより、https://localhost:8443/mcp で HTTPS サーバーが起動します。

SSL オプション:

  • sslCert - SSL 証明書ファイルへのパス
  • sslKey - SSL 秘密鍵ファイルへのパス
  • sslCa - (任意)相互 TLS 認証用の CA 証明書へのパス

テストでは、自己署名証明書を生成できます。

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"

本番環境では、Let's Encrypt などの信頼できる CA から証明書を取得してください。

詳しい例については、https-server example を参照してください。

CORS の設定

デフォルトでは、FastMCP は標準の許可ヘッダーセットを使用して CORS を有効にします。cors オプションを渡すことで、CORS の動作をカスタマイズできます。

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
    cors: {
      origin: "http://localhost:3000",
      allowedHeaders: [
        "Content-Type",
        "Authorization",
        "Accept",
        "Mcp-Session-Id",
        "Mcp-Protocol-Version",
        "Last-Event-Id",
        "X-Custom-Header",
      ],
      credentials: true,
    },
  },
});

cors オプションには、次の値を指定できます。

  • true(デフォルト)- デフォルト設定で CORS を有効にします
  • false - CORS を完全に無効にします
  • 次のフィールドを持つオブジェクト:
    • origin - 文字列、文字列の配列、または関数 (origin: string) => boolean
    • allowedHeaders - 文字列または文字列の配列
    • methods - 許可する HTTP メソッドの配列
    • exposedHeaders - 公開するヘッダーの配列
    • credentials - 認証情報を許可するかどうかを指定するブール値
    • maxAge - プリフライトキャッシュの有効期間(秒)

CorsOptions 型は、利便性のために fastmcp からエクスポートされています。

カスタム HTTP ルート

FastMCP では、MCP エンドポイントと並行してカスタム HTTP ルートを追加できます。これにより、REST API、Webhook、管理インターフェースなどを含む包括的な HTTP サービスを、同じサーバープロセス内で構築できます。

const app = server.getApp();

// Add REST API endpoints with Hono's native API
app.get("/api/users", async (c) => {
  return c.json({ users: [] });
});

// Handle path parameters
app.get("/api/users/:id", async (c) => {
  return c.json({
    userId: c.req.param("id"),
    query: c.req.query(), // Access query parameters
  });
});

// Handle POST requests with body parsing
app.post("/api/users", async (c) => {
  const body = await c.req.json();
  return c.json({ created: body }, 201);
});

// Serve HTML content
app.get("/admin", async (c) => {
  return c.html("<html><body><h1>Admin Panel</h1></body></html>");
});

// Handle webhooks
app.post("/webhook/github", async (c) => {
  const payload = await c.req.json();
  const event = c.req.header("x-github-event");

  // Process webhook...
  return c.json({ received: true });
});

カスタムルートは、server.getApp() が返す基盤の Hono アプリを使用し、次の機能をサポートします。

  • Hono の HTTP メソッド:get、post、put、delete、patch、options など
  • パスパラメーター(:param)とワイルドカード(*)
  • クエリ文字列の解析
  • c.req による JSON、テキスト、フォームなどのボディーヘルパー
  • カスタムステータスコードとヘッダー
  • Hono を通じたミドルウェアとルートグループ

ルートは登録された順に照合されるため、すべてに一致するパターンより先に、特定のルートを定義できます。

パブリックルートと保護されたルート

独自のルートミドルウェアまたは認証チェックを追加しない限り、カスタム Hono ルートはパブリックです。保護されたカスタムルートを作成するには、再利用可能なヘルパーに認証ロジックを記述し、FastMCP の authenticate オプションと Hono のルートハンドラーの両方から呼び出してください。

import type { IncomingMessage } from "node:http";
import type { Context } from "hono";
import { FastMCP } from "fastmcp";

async function authenticateRequest(request: IncomingMessage) {
  const apiKey = request.headers["x-api-key"];
  return apiKey === "123" ? { userId: "123" } : undefined;
}

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: authenticateRequest,
});

const app = server.getApp();

async function requireAuth(c: Context) {
  const auth = await authenticateRequest(c.env.incoming);

  if (!auth) {
    return c.json({ error: "Authentication required" }, 401);
  }

  return auth;
}

// Public route - no authentication required
app.get("/.well-known/openid-configuration", async (c) => {
  return c.json({
    issuer: "https://example.com",
    authorization_endpoint: "https://example.com/auth",
    token_endpoint: "https://example.com/token",
  });
});

// Private route - requires authentication
app.get("/api/users", async (c) => {
  const auth = await requireAuth(c);
  if (auth instanceof Response) {
    return auth;
  }

  return c.json({ users: [] });
});

// Public static files
app.get("/public/*", async (c) => {
  return c.text(`File: ${c.req.path}`);
});

パブリックルートは、次の用途に最適です。

  • OAuth ディスカバリーエンドポイント(.well-known/*)
  • ヘルスチェックとステータスページ
  • 静的アセットとドキュメント
  • 外部サービスからの Webhook エンドポイント
  • ユーザー認証を必要としないパブリック API

詳しい例については、custom-routes example を参照してください。

エッジランタイムのサポート

FastMCP は Cloudflare Workers などのエッジランタイムをサポートしており、世界中で最小限のレイテンシーで MCP サーバーをエッジにデプロイできます。

FastMCP と EdgeFastMCP の選び方
ユースケースクラスインポート
Node.js、Express、BunFastMCPimport { FastMCP } from "fastmcp"
Cloudflare Workers、Deno DeployEdgeFastMCPimport { EdgeFastMCP } from "fastmcp/edge"
機能FastMCPEdgeFastMCP
ランタイムNode.jsEdge(V8 isolates)
起動方法server.start({ port })export default server
トランスポートstdio、httpStream、SSEHTTP Streamable のみ
セッションステートフルまたはステートレスステートレスのみ
ファイルシステムありなし
OAuth/認証組み込みの authenticate オプションHono ミドルウェアを使用(組み込みを予定)
カスタムルートserver.getApp()server.getApp()

注: EdgeFastMCP の組み込み認証は、今後のリリースで追加予定です。FastMCP と EdgeFastMCP はどちらも内部で Hono を使用しているため、技術的な障壁はありません。EdgeFastMCP は、FastMCP に OAuth が追加される前に作成されただけです。Node.js の http.IncomingMessage ではなく、Web の Request を受け取る authenticate オプションを追加する PR を歓迎します。

それまでは、Hono ミドルウェアを使用してください。

const app = server.getApp();
app.use("/api/*", async (c, next) => {
  if (c.req.header("authorization") !== "Bearer secret") {
    return c.json({ error: "Unauthorized" }, 401);
  }
  await next();
});
Cloudflare Workers

FastMCP を Cloudflare Workers にデプロイするには、/edge サブパスから EdgeFastMCP クラスを使用します。

import { EdgeFastMCP } from "fastmcp/edge";
import { z } from "zod";

const server = new EdgeFastMCP({
  name: "My Edge Server",
  version: "1.0.0",
  description: "MCP server running on Cloudflare Workers",
});

// Add tools, resources, prompts as usual
server.addTool({
  name: "greet",
  description: "Greet someone",
  parameters: z.object({
    name: z.string(),
  }),
  execute: async ({ name }) => {
    return `Hello, ${name}! Served from the edge.`;
  },
});

// Export the server as the default (required for Cloudflare Workers)
export default server;
Edge ランタイムの違い

Edge ランタイムで実行する場合:

  • デフォルトでステートレス:各リクエストは個別に処理されます
  • ファイルシステムにアクセス不可:外部データには fetch API を使用します
  • V8 Isolates:コールドスタートが速く、リソースを効率的に使用します
  • グローバルデプロイ:Edge ロケーションに自動で分散されます
Edge でのカスタムルート

基盤となる Hono アプリにアクセスして、カスタム HTTP ルートを追加できます。

const app = server.getApp();

// Add a landing page
app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>"));

// Add REST API endpoints
app.get("/api/status", (c) => c.json({ status: "ok" }));
デプロイ

wrangler.toml を設定します。

name = "my-mcp-server"
main = "src/index.ts"
compatibility_date = "2024-01-01"

次のコマンドでデプロイします。

wrangler deploy

詳しい説明は、edge-cloudflare-worker の例をご覧ください。

ステートレスモード

FastMCP は HTTP ストリーミングでのステートレス動作をサポートしています。このモードでは、永続的なセッションを維持せず、各リクエストを個別に処理します。サーバーレス環境、ロードバランスされたデプロイ、またはセッション状態が不要な場合に最適です。

ステートレスモードでは:

  • サーバー上でセッションは追跡されません
  • 各リクエストで一時的なセッションが作成され、応答後に破棄されます
  • メモリ使用量が減り、スケーラビリティが向上します
  • ステートレスなデプロイ環境に最適です

stateless: true オプションを追加すると、ステートレスモードを有効にできます。

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
    stateless: true,
  },
});

注: ステートレスモードは HTTP ストリーミングトランスポートでのみ利用できます。永続的なセッションに依存する機能(セッション固有の状態など)は、ステートレスモードでは利用できません。

CLI 引数または環境変数を使用して、ステートレスモードを有効にすることもできます。

# Via CLI argument
npx tsx src/server.ts --transport http-stream --port 8080 --stateless true

# Via environment variable
FASTMCP_TRANSPORT=httpStream FASTMCP_STATELESS=true npx tsx src/server.ts

/ready ヘルスチェックエンドポイントでは、サーバーがステートレスモードで実行中かどうかを確認できます。

{
  "mode": "stateless",
  "ready": 1,
  "status": "ready",
  "total": 1
}

コアコンセプト

ツール

MCP のツールを使うと、サーバーはクライアントから呼び出され、LLM がアクションを実行するために使用できる実行可能な関数を公開できます。

FastMCP は、ツールのパラメーターを定義するために Standard Schema 仕様を使用します。この仕様を実装していれば、Zod、ArkType、Valibot など、お好みのスキーマ検証ライブラリを使用できます。

Zod の例:

import { z } from "zod";

server.addTool({
  name: "fetch-zod",
  description: "Fetch the content of a url (using Zod)",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

ArkType の例:

import { type } from "arktype";

server.addTool({
  name: "fetch-arktype",
  description: "Fetch the content of a url (using ArkType)",
  parameters: type({
    url: "string",
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

Valibot の例:

Valibot にはピア依存関係の @valibot/to-json-schema が必要です。

import * as v from "valibot";

server.addTool({
  name: "fetch-valibot",
  description: "Fetch the content of a url (using Valibot)",
  parameters: v.object({
    url: v.string(),
  }),
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

プレーンな JSON Schema の例:

すでに JSON Schema をお持ちの場合(OpenAPI ドキュメント、設定ファイル、または別のサーバーから取得したものなど)、jsonSchemaAdapter を使ってラップすれば、スキーマライブラリを介さずにそのまま使用できます。

検証を行うピア依存関係の ajv が必要です。また、スキーマで email や uri などの format キーワードを使用する場合は、ajv-formats も必要です。どちらもツールが初めて呼び出されたときにインポートされるため、これらを使わないサーバーに追加の負荷はかかりません。

npm install ajv ajv-formats
import { jsonSchemaAdapter } from "fastmcp";

server.addTool({
  name: "fetch-json-schema",
  description: "Fetch the content of a url (using plain JSON Schema)",
  parameters: jsonSchemaAdapter({
    type: "object",
    properties: {
      url: { type: "string", format: "uri" },
    },
    required: ["url"],
  }),
  execute: async (args) => {
    const { url } = args as { url: string };
    return await fetchWebpageContent(url);
  },
});

outputSchema でも機能します。FastMCP は、スキーマの指定内容にかかわらず、入力スキーマに additionalProperties: false を設定して公開します。これは Zod と Valibot のスキーマにも同様に適用されます。例外は、追加プロパティのみで構成されるオブジェクトです。辞書(properties がなく、additionalProperties スキーマがあるもの。z.record() など)は値のスキーマを保持し、自由形式の引数({ type: "object" } または additionalProperties: true)はオープンなままです。出力スキーマは、スキーマで宣言した additionalProperties のルールをそのまま保持します。

上記のスキーマライブラリとは異なり、プレーンな JSON Schema には TypeScript の型が含まれないため、execute が受け取る引数の型は unknown です。自分でキャストするか、型を絞り込んでください。

パラメーターのないツール

パラメーターが不要なツールを作成する場合、次の 2 つの方法があります。

  1. parameters プロパティを完全に省略します。

    server.addTool({
      name: "sayHello",
      description: "Say hello",
      // No parameters property
      execute: async () => {
        return "Hello, world!";
      },
    });
    
  2. 空の parameters を明示的に定義します。

    import { z } from "zod";
    
    server.addTool({
      name: "sayHello",
      description: "Say hello",
      parameters: z.object({}), // Empty object
      execute: async () => {
        return "Hello, world!";
      },
    });
    

メモ

どちらの方法も、Cursor を含むすべての MCP クライアントと完全に互換性があります。FastMCP はどちらの場合も適切なスキーマを自動生成します。

構造化されたツール出力

ツールでは outputSchema を宣言し、構造化データを返すことができます。FastMCP はその値を MCP の structuredContent として公開すると同時に、テキストコンテンツのみを表示するクライアント向けに JSON テキストのフォールバックも返します。

server.addTool({
  name: "get-weather",
  description: "Get weather for a city",
  parameters: z.object({
    city: z.string(),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    humidity: z.number(),
  }),
  execute: async ({ city }) => {
    const weather = await getWeather(city);

    return {
      temperature: weather.temperature,
      humidity: weather.humidity,
    };
  },
});

明示的なテキストコンテンツと構造化コンテンツを一緒に返すこともできます。

server.addTool({
  name: "get-weather",
  description: "Get weather for a city",
  parameters: z.object({
    city: z.string(),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    humidity: z.number(),
  }),
  execute: async ({ city }) => {
    const weather = await getWeather(city);

    return {
      content: [
        {
          type: "text",
          text: `${city}: ${weather.temperature}F`,
        },
      ],
      structuredContent: {
        temperature: weather.temperature,
        humidity: weather.humidity,
      },
    };
  },
});

outputSchema が指定されている場合、FastMCP はツールの結果を送信する前に structuredContent を検証します。構造化出力が無効な場合、宣言されたスキーマに違反したまま送信されるのではなく、ツールエラーとしてクライアントに返されます。

ツールの認可

ツールの定義に省略可能な canAccess 関数を追加すると、認証済みユーザーが利用できるツールを制御できます。この関数は認証コンテキストを受け取り、ユーザーがツールへのアクセスを許可されている場合は true を返します。

server.addTool({
  name: "admin-tool",
  description: "An admin-only tool",
  canAccess: (auth) => auth?.role === "admin",
  execute: async () => "Welcome, admin!",
});

authenticate 関数のないサーバーには確認すべき認証情報がないため、すべてのセッションにすべてのツールが表示されます。authenticate 関数がある場合、認証情報を持たないままのセッション(stdio では、authenticate が例外をスローするか何も返さなくてもサーバーは処理を続行します)には、canAccess を持つツールは表示されません。

文字列を返す

execute は文字列を返すことができます。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return "Hello, world!";
  },
});

後者は、次の記述と同じです。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "text",
          text: "Hello, world!",
        },
      ],
    };
  },
});
リストを返す

メッセージのリストを返す場合は、content プロパティを持つオブジェクトを返します。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        { type: "text", text: "First message" },
        { type: "text", text: "Second message" },
      ],
    };
  },
});
画像を返す

画像のコンテンツオブジェクトを作成するには、imageContent を使用します。

import { imageContent } from "fastmcp";

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return imageContent({
      url: "https://example.com/image.png",
    });

    // or...
    // return imageContent({
    //   path: "/path/to/image.png",
    // });

    // or...
    // return imageContent({
    //   buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
    // });

    // or...
    // return {
    //   content: [
    //     await imageContent(...)
    //   ],
    // };
  },
});

imageContent 関数では、次のオプションを指定できます。

  • url:画像の URL です。
  • timeoutMs:URL からのダウンロードに対する省略可能なタイムアウト時間(ミリ秒)です(デフォルトは 30 秒)。
  • path:画像ファイルのパスです。
  • buffer:画像データのバッファーです。

url、path、buffer のうち、指定するのは 1 つだけです。

上記の例は、次の記述と同じです。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "image",
          data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
          mimeType: "image/png",
        },
      ],
    };
  },
});
設定可能な Ping の動作

FastMCP には、接続状態を維持するための設定可能な Ping 機構が含まれています。Ping の動作は、サーバーオプションでカスタマイズできます。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  ping: {
    // Explicitly enable or disable pings (defaults vary by transport)
    enabled: true,
    // Configure ping interval in milliseconds (default: 5000ms)
    intervalMs: 10000,
    // Set log level for ping-related messages (default: 'debug')
    logLevel: "debug",
  },
});

デフォルトでは、Ping の動作は各トランスポートタイプに合わせて最適化されています。

  • SSE および HTTP ストリーミング接続では有効です(キープアライブのメリットがあります)
  • stdio 接続では無効です(通常、Ping は不要です)

この設定可能な方式により、ログの出力量を抑え、さまざまな使用シナリオに合わせてパフォーマンスを最適化できます。

長時間実行されるツール呼び出しを維持する(streamKeepalive)

Ping は独立したサーバーからクライアントへのストリームを通じて送信されるため、個々のツール呼び出しの接続が開いた状態に保たれることはありません。また、stateless モードでは、このストリーム自体が存在しません。数分間出力を生成しないツールは、応答接続が無通信の状態となり、サーバーの手前にあるアイドル接続タイムアウト(AWS ALB のデフォルトは 60 秒)によって、結果が書き込まれる前に接続が閉じられます。

これは両方のセッションモードに当てはまります。ステートフルなデプロイでは、独立したストリームは Ping によって維持されますが、ツール呼び出しを運ぶ接続はアイドル状態になり、結局閉じられます。ステートレスモードでは、そもそも独立したストリームがありません。

streamKeepalive は、処理中のツール呼び出し自身の応答ストリームに定期的に書き込みます。これは、そうしなければ閉じられてしまう接続です。この機能はオプトインです。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  streamKeepalive: {
    // Opt in; disabled by default
    enabled: true,
    // Keep comfortably below the shortest idle timeout on the path
    intervalMs: 20000,
  },
});

各キープアライブは notifications/message で、レベルは logLevel で設定可能(デフォルトは debug)です。また、ロガー fastmcp-keepalive のタグが付き、実行中のツール呼び出しに関連付けられます。キープアライブはツールの実行開始時に始まり、リクエストがそのツールの結果を待たなくなった時点(成功、エラー、タイムアウト、クライアントによるキャンセル)で停止するため、アイドル接続は静かな状態に保たれます。メッセージはリクエストに関連付けられているため、ステートフルモードと stateless モードのどちらでも動作し、標準のログ通知以外にクライアント側のサポートは必要ありません。

知っておくべき制限事項:

  • httpStream.enableJsonResponse を使う場合は効果がありません。この設定では単一の JSON 応答がバッファリングされ、ストリーミングされないため、書き込み可能なストリームがありません。
  • 対象となるのはツール呼び出しのみです。その他の長時間実行リクエスト(resources/read、prompts/get)は、完了するまで何も書き込みません。
  • stdio では維持すべきプロキシがないため、この機能を有効にしても通知トラフィックが増えるだけです。
長時間実行されるツール呼び出しのキャンセル(context.signal)

すべての execute には、結果を誰にも届けられなくなった時点で発火する AbortSignal が渡されます。実際の処理を行う対象にこのシグナルを渡し、呼び出しの終了後も処理が続くのではなく、呼び出しの終了とともに処理を停止させてください。

server.addTool({
  name: "fetch_report",
  parameters: z.object({ url: z.string() }),
  timeoutMs: 30000,
  execute: async (args, { signal }) => {
    const response = await fetch(args.url, { signal });
    return await response.text();
  },
});

このシグナルは、次の3つのいずれかが発生すると中断されます。

  • クライアントが呼び出しをキャンセルした場合 — notifications/cancelled が送信されます。呼び出し元が自身のリクエストを中断したときに、MCP SDK が送信する通知です。
  • セッションが終了した場合 — トランスポートが閉じられた場合、またはセッションが明示的に閉じられた場合です。この場合、キャンセル通知は関係しません。
  • timeoutMs が経過した場合 — signal.reason は呼び出し元が受け取るものと同じ UserError です。そのため、ツールはタイムアウトとキャンセルを区別できます。

呼び出しが正常に完了した後にシグナルが中断されることはないため、シグナルにクリーンアップ処理を設定しても安全です。

知っておくべき制限事項:

  • 処理が自動的に強制終了されるわけではありません。 FastMCP はツールの結果を待つのをやめますが、execute が返した Promise は解決するまで実行され続けます。シグナルを無視するツールは最後まで実行されます。ただし、結果を報告する相手はいません。
  • リクエスト中に HTTP クライアントが切断されても検出されません。 MCP SDK がリクエスト自身のシグナルを中断するのは、明示的な notifications/cancelled があった場合のみです。また、StreamableHTTPServerTransport は放棄された応答ストリームをセッション終了とはみなしません。セッションを終了(DELETE)せずに切断した呼び出し元がいた場合、ツールは処理が完了するかタイムアウトするまで実行され続けます。切断の検出に頼るのではなく、負荷の高い処理には timeoutMs を設定してください。
  • load には渡されません。 リソース、リソーステンプレート、プロンプトには、signal のない簡略化されたコンテキストが渡されます。
ヘルスチェックエンドポイント

httpStream トランスポートで FastMCP を実行すると、ロードバランサーやコンテナオーケストレーションの稼働状態チェックに役立つ、プレーンテキストの応答を返すシンプルな HTTP エンドポイントを任意で公開できます。

サーバーオプションの health キーを使用して、エンドポイントを有効にするか、設定を変更します。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  health: {
    // Enable / disable (default: true)
    enabled: true,
    // Body returned by the endpoint (default: 'ok')
    message: "healthy",
    // Path that should respond (default: '/health')
    path: "/healthz",
    // HTTP status code to return (default: 200)
    status: 200,
  },
});

await server.start({
  transportType: "httpStream",
  httpStream: { port: 8080 },
});

これで、http://localhost:8080/healthz へのリクエストは次の内容を返します。

HTTP/1.1 200 OK
content-type: text/plain

healthy

サーバーが stdio トランスポートで起動された場合、エンドポイントは無視されます。

Roots の管理

FastMCP は Roots をサポートしています。これは、クライアントがファイルシステムのようなルートロケーションのセットを提供し、一覧表示や動的な更新を行えるようにする機能です。Roots 機能はサーバーオプションで設定または無効化できます。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  roots: {
    // Set to false to explicitly disable roots support
    enabled: false,
    // By default, roots support is enabled (true)
  },
});

これにより、次のメリットが得られます。

  • Roots をサポートしていない可能性のあるさまざまなクライアントとの互換性が向上します
  • roots 機能を実装していないクライアントへの接続時に、エラーログが減少します
  • MCP サーバーの機能をより明示的に制御できます
  • roots 機能を利用できない場合も、適切に機能を縮退できます

サーバーでルートの変更をリッスンできます。

server.on("connect", (event) => {
  const session = event.session;

  // Access the current roots
  console.log("Initial roots:", session.roots);

  // Listen for changes to the roots
  session.on("rootsChanged", (event) => {
    console.log("Roots changed:", event.roots);
  });
});

クライアントが roots をサポートしていない場合や、roots 機能が明示的に無効化されている場合でも、これらの操作はエラーをスローせずに適切に処理されます。

音声を返す

audioContent を使用して、音声のコンテンツオブジェクトを作成します。

import { audioContent } from "fastmcp";

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return audioContent({
      url: "https://example.com/audio.mp3",
    });

    // or...
    // return audioContent({
    //   path: "/path/to/audio.mp3",
    // });

    // or...
    // return audioContent({
    //   buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
    // });

    // or...
    // return {
    //   content: [
    //     await audioContent(...)
    //   ],
    // };
  },
});

audioContent 関数では、次のオプションを指定します。

  • url:音声の URL。
  • timeoutMs:URL からのダウンロードに対するタイムアウト(ミリ秒)。省略した場合のデフォルトは 30 秒です。
  • path:音声ファイルのパス。
  • buffer:バッファとしての音声データ。

url、path、buffer のうち、指定するのは 1 つだけです。

上記の例は、次のコードと同等です。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "audio",
          data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
          mimeType: "audio/mpeg",
        },
      ],
    };
  },
});
組み合わせ型を返す

さまざまな型をこのように組み合わせて、AI に返すことができます。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "text",
          text: "Hello, world!",
        },
        {
          type: "image",
          data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
          mimeType: "image/png",
        },
        {
          type: "audio",
          data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
          mimeType: "audio/mpeg",
        },
      ],
    };
  },

  // or...
  // execute: async (args) => {
  //   const imgContent = await imageContent({
  //     url: "https://example.com/image.png",
  //   });
  //   const audContent = await audioContent({
  //     url: "https://example.com/audio.mp3",
  //   });
  //   return {
  //     content: [
  //       {
  //         type: "text",
  //         text: "Hello, world!",
  //       },
  //       imgContent,
  //       audContent,
  //     ],
  //   };
  // },
});
カスタムロガー

FastMCP では、サーバーのメッセージロギングを制御するために、カスタムロガーの実装を指定できます。既存のロギングインフラストラクチャとの統合や、ログ形式のカスタマイズに役立ちます。

import { FastMCP, Logger } from "fastmcp";

class CustomLogger implements Logger {
  debug(...args: unknown[]): void {
    console.log("[DEBUG]", new Date().toISOString(), ...args);
  }

  error(...args: unknown[]): void {
    console.error("[ERROR]", new Date().toISOString(), ...args);
  }

  info(...args: unknown[]): void {
    console.info("[INFO]", new Date().toISOString(), ...args);
  }

  log(...args: unknown[]): void {
    console.log("[LOG]", new Date().toISOString(), ...args);
  }

  warn(...args: unknown[]): void {
    console.warn("[WARN]", new Date().toISOString(), ...args);
  }
}

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  logger: new CustomLogger(),
});

Winston、Pino、ファイルベースのロギングの例については、src/examples/custom-logger.ts を参照してください。

ロギング

ツールは、コンテキストオブジェクト内の log オブジェクトを使用して、クライアントにメッセージをログ出力できます。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args, { log }) => {
    log.info("Downloading file...", {
      url,
    });

    // ...

    log.info("Downloaded file");

    return "done";
  },
});

log オブジェクトには、次のメソッドがあります。

  • debug(message: string, data?: SerializableValue)
  • error(message: string, data?: SerializableValue)
  • info(message: string, data?: SerializableValue)
  • warn(message: string, data?: SerializableValue)

クライアントは logging/setLevel リクエストを使用して、必要な最小レベルを引き上げることができます。FastMCP はそれより重大度の低いメッセージを送信せずに破棄するため、logging/setLevel で error が設定されると、log.debug と log.info はどこにも届きません。クライアントからの要求がない間は、すべてのレベルが送信されます。現在有効なレベルは session.loggingLevel で確認できます。

各メッセージは、それをログ出力したリクエストのストリームを通じて送信されるため、stateless モードでもロギングは機能します。各リクエストに対して単一の JSON ボディで応答する httpStream.enableJsonResponse を使用した場合、リクエストの実行中にログ出力されたメッセージは破棄されます。

エラー

ユーザーに表示することを意図したエラーは、UserError インスタンスとしてスローしてください。

import { UserError } from "fastmcp";

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args) => {
    if (args.url.startsWith("https://example.com")) {
      throw new UserError("This URL is not allowed");
    }

    return "done";
  },
});
進捗

ツールは、コンテキストオブジェクト内で reportProgress を呼び出して進捗を報告できます。

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args, { reportProgress }) => {
    await reportProgress({
      progress: 0,
      total: 100,
    });

    // ...

    await reportProgress({
      progress: 100,
      total: 100,
    });

    return "done";
  },
});

reportProgress は数値フィールドに加えて、人が読める形式の省略可能な message を受け取ります。クライアントは進捗インジケーターの横にこのメッセージを表示できます。

await reportProgress({
  progress: 40,
  total: 100,
  message: "Downloading chunk 4 of 10…",
});

進捗通知が送信されるのは、ツール呼び出しで progressToken を指定してクライアントがオプトインした場合のみです。それ以外の場合、reportProgress は何も行いません。notifications/progress は MCP 仕様の一部であるため(message フィールドはリビジョン 2025-03-26 以降)、長時間実行されるツール呼び出し中に段階的な更新を送信するための、移植性の高い方法です。違いについては、以下のストリーミング出力を参照してください。

ストリーミング出力

FastMCP は、ツールの実行中に部分的な結果をストリーミングできるため、応答性の高い UI とリアルタイムのフィードバックを実現できます。これは特に次の場合に便利です。

  • コンテンツを段階的に生成する、実行時間の長い操作
  • テキスト、画像、その他のメディアの段階的な生成
  • 部分的な結果をすぐに確認できるとユーザーにとって有益な操作

重要

streamContent は FastMCP の拡張機能であり、MCP 仕様の一部ではありません。MCP 仕様では定義されていない notifications/tool/streamContent 通知を送信します。リビジョン 2025-11-25 の時点では、ツール出力をストリーミングする標準的な仕組みはありません(SEP-2998 は、その仕組みを追加するための進行中の提案です)。

クライアントは、ハンドラーが登録されていない通知をエラーなしで黙って破棄します。クライアントがストリーミングされたコンテンツを認識できるのは、そのメソッドのハンドラー(または fallbackNotificationHandler)を登録している場合のみであり、これをツール出力として表示することが知られているクライアントはありません。たとえば、MCP Inspector はフォールバックハンドラーを介して通知ペインに記録しますが、ツールの結果には引き続き execute が返した内容だけが表示されます。そのため、ストリーミングは主にクライアントも制御できる場合に便利です。詳しくは、以下のストリーミングされたコンテンツの受信を参照してください。どのクライアントでも機能する段階的な更新が必要な場合は、代わりに reportProgress と message を使用してください。

ツールからストリーミングするには、streamContent メソッドを使用します。

server.addTool({
  name: "generateText",
  description: "Generate text incrementally",
  parameters: z.object({
    prompt: z.string(),
  }),
  annotations: {
    streamingHint: true, // Advisory only; see below
    readOnlyHint: true,
  },
  execute: async (args, { streamContent }) => {
    // Send initial content immediately
    await streamContent({ type: "text", text: "Starting generation...\n" });

    // Simulate incremental content generation
    const words = "The quick brown fox jumps over the lazy dog.".split(" ");
    for (const word of words) {
      await streamContent({ type: "text", text: word + " " });
      await new Promise((resolve) => setTimeout(resolve, 300)); // Simulate delay
    }

    // Always return a final result. Returning nothing sends an empty tool
    // result, so clients that ignore the streamed notifications see no output
    // at all.
    return "The quick brown fox jumps over the lazy dog.";
  },
});

警告

execute から undefined を返すと、content が空のツール結果になります。すべてをストリーミングして何も返さない場合、通知を記録するクライアントも含め、何かが失われたことを示す情報がないまま、ツール呼び出しは空の結果で完了します。完全な結果も返し、ストリーミングされたコンテンツはあくまで段階的なレンダリングを補助するものとして扱ってください。

streamingHint アノテーションは参考情報のメタデータです。tools/list でそのままクライアントに転送されますが、streamContent を有効化または制限するものではなく、FastMCP 自体がこれを読み取ることもありません。現時点でこれに基づいて動作するクライアントは知られていませんが、SEP-2998 では、同じアノテーション名の標準化が提案されています。

ストリーミングされたコンテンツの受信

クライアントがこれらの通知を認識できるのは、そのメソッドのハンドラー(または fallbackNotificationHandler)を登録している場合のみです。

import { z } from "zod";

const StreamContentNotificationSchema = z.object({
  method: z.literal("notifications/tool/streamContent"),
  params: z.object({
    content: z.array(z.any()),
    toolName: z.string(),
  }),
});

client.setNotificationHandler(
  StreamContentNotificationSchema,
  (notification) => {
    const { content, toolName } = notification.params;
    // Render the partial content however you like.
  },
);

通知に含まれるのは toolName のみで、リクエストトークンや進捗トークンは含まれません。そのため、1 つのセッションで同じツールを同時に呼び出した場合、それぞれを区別できない点に注意してください。

ストリーミングはすべてのコンテンツタイプ(テキスト、画像、音声)で機能し、進捗報告と組み合わせることもできます。

server.addTool({
  name: "processData",
  description: "Process data with streaming updates",
  parameters: z.object({
    datasetSize: z.number(),
  }),
  annotations: {
    streamingHint: true,
  },
  execute: async (args, { streamContent, reportProgress }) => {
    const total = args.datasetSize;

    for (let i = 0; i < total; i++) {
      // Standard progress notification: reaches every spec-compliant client
      await reportProgress({
        progress: i,
        total,
        message: `Processed ${i} of ${total} items`,
      });

      // Richer partial content: only reaches clients that opt in
      if (i % 10 === 0) {
        await streamContent({
          type: "text",
          text: `Processed ${i} of ${total} items...\n`,
        });
      }

      await new Promise((resolve) => setTimeout(resolve, 50));
    }

    return "Processing complete!";
  },
});
情報提供の要求

ツールは、コンテキストオブジェクト内の elicit メソッドを使用して、実行中に 情報提供を要求 できます。クライアントは対応する elicitation 機能モードを通知する必要があります。フォームによる要求(デフォルト)の場合は elicitation: { form: {} }、URL による要求の場合は elicitation: { url: {} } を指定します。

server.addTool({
  name: "delete-file",
  description: "Delete a file",
  parameters: z.object({
    path: z.string(),
  }),
  execute: async (args, { elicit }) => {
    const response = await elicit({
      message: `Are you sure you want to delete ${args.path}?`,
      requestedSchema: {
        type: "object",
        properties: {
          confirmed: {
            type: "boolean",
          },
        },
        required: ["confirmed"],
      },
    });

    if (response.action !== "accept" || !response.content?.confirmed) {
      return "Deletion cancelled.";
    }

    // ...

    return `Deleted ${args.path}`;
  },
});

応答の action は "accept"、"decline"、または "cancel" です。承認された場合、content には requestedSchema に一致するユーザーの回答が格納されます。情報提供の要求は、ツール以外でも session.requestElicitation を通じて利用できます。

ツールのアノテーション

MCP Specification(2025-03-26)以降、ツールには、その動作に関するメタデータを追加して、より豊かなコンテキストと制御を提供するアノテーションを含めることができます。

server.addTool({
  name: "fetch-content",
  description: "Fetch content from a URL",
  parameters: z.object({
    url: z.string(),
  }),
  annotations: {
    title: "Web Content Fetcher", // Human-readable title for UI display
    readOnlyHint: true, // Tool doesn't modify its environment
    openWorldHint: true, // Tool interacts with external entities
  },
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

利用可能なアノテーションは次のとおりです。

アノテーション型デフォルト説明
titlestring-ツールの人が読みやすいタイトルです。UI 表示に役立ちます
readOnlyHintbooleanfalsetrue の場合、ツールが環境を変更しないことを示します
destructiveHintbooleantruetrue の場合、ツールが破壊的な更新を実行する可能性があることを示します(readOnlyHint が false の場合にのみ意味があります)
idempotentHintbooleanfalsetrue の場合、同じ引数でツールを繰り返し呼び出しても追加の効果がないことを示します(readOnlyHint が false の場合にのみ意味があります)
openWorldHintbooleantruetrue の場合、ツールが外部エンティティからなる「オープンワールド」とやり取りする可能性があることを示します

これらのアノテーションにより、クライアントと LLM は、ツールの使用方法や呼び出し時に想定されることをよりよく理解できます。

リソース

リソース は、MCP サーバーがクライアントに提供するあらゆる種類のデータを表します。次のようなものが含まれます。

  • ファイルの内容
  • スクリーンショットと画像
  • ログファイル
  • その他

各リソースは一意の URI で識別され、テキストデータまたはバイナリデータを含めることができます。

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load() {
    return {
      text: await readLogFile(),
    };
  },
});

メモ

load は複数のリソースを返すことができます。たとえば、ディレクトリを読み取る際に、そのディレクトリ内のファイル一覧を返すために使用できます。

async load() {
  return [
    {
      text: "First file content",
    },
    {
      text: "Second file content",
    },
  ];
}

load では、バイナリコンテンツを返すこともできます。

async load() {
  return {
    blob: 'base64-encoded-data'
  };
}

load は、第 2 引数として auth(authenticate 関数が返す値がある場合はその値)を、第 3 引数として context オブジェクトも受け取ります。context は、tool.execute で利用可能な client、log、session、sessionId フィールドを反映します(セッション ID とリクエスト ID の追跡を参照)。reportProgress と streamContent はツール呼び出しの進行状況トークンに紐づいているため含まれません。

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load(auth, context) {
    context.log.info("loading application logs", { requestedBy: auth?.userId });

    return {
      text: await readLogFile(),
    };
  },
});
リソース更新の購読

クライアントは、MCP の resources/subscribe メソッドでリソースを購読し、その内容が変更されるたびに通知を受け取れます。FastMCP は、リソースを公開するすべてのサーバーについて subscribe ケイパビリティを自動的に通知し、各クライアントの購読を追跡します。また、sendResourceUpdated を使って更新を通知できます。

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load() {
    return { text: await readLogFile() };
  },
});

// Whenever the underlying data changes, notify subscribed clients:
await server.sendResourceUpdated("file:///logs/app.log");

sendResourceUpdated は、指定された URI を購読しているクライアントにのみ通知するため、データが変更されるたびに安全に呼び出せます。FastMCP は、ツール、リソース、プロンプトについて listChanged ケイパビリティも通知し、実行時にツール、リソース、リソーステンプレート、プロンプトを追加または削除すると、notifications/tools/list_changed / notifications/resources/list_changed / notifications/prompts/list_changed を自動的に送信します。

あるセッションに対して、ネゴシエーション時に存在しなかった種類のプリミティブを後から表示することはできません。MCP では initialize 中にケイパビリティが確定し、セッションの存続期間中は変わらないため、サーバーにリソースがまったくない状態で接続したクライアントには、後から追加されたリソースは表示されません。FastMCP はケイパビリティ名を示す警告をログに記録し、そのセッションには何も行いません。start() の前に、追加する予定の種類ごとに少なくとも 1 つ登録するか、クライアントを再接続してください。後から接続するセッションにはすべてが表示されます。

リソーステンプレート

リソーステンプレートも定義できます。

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});

通常のリソースと同様に、load は第 2 引数と第 3 引数として auth と context も受け取ります(リソースを参照)。

{name} 式は RFC 6570 に従って 1 つのパスセグメント内で一致するため、たとえば file:///logs/a/b.log は上記のテンプレートに一致しません。複数のセグメントにまたがる値には、{+name} を使用します(例:file:///{+path})。値はパーセントデコードされた状態で渡されるため(file:///logs/..%2Fsecret.log は name: "../secret" を返します)、パスとして使用する前に検証してください。

リソーステンプレート引数の自動補完

リソーステンプレートの引数に complete 関数を指定すると、自動補完を有効にできます。

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
      complete: async (value) => {
        if (value === "Example") {
          return {
            values: ["Example Log"],
          };
        }

        return {
          values: [],
        };
      },
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});
埋め込みリソース

FastMCP には、ツールの応答にリソースを含める処理を簡素化する便利な embedded() メソッドがあります。この機能により、コードの重複が減り、ツール内からリソースを参照しやすくなります。

基本的な使い方
server.addTool({
  name: "get_user_data",
  description: "Retrieve user information",
  parameters: z.object({
    userId: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded(`user://profile/${args.userId}`),
        },
      ],
    };
  },
});
リソーステンプレートの使用

embedded() メソッドはリソーステンプレートでもシームレスに動作します。

// Define a resource template
server.addResourceTemplate({
  uriTemplate: "docs://project/{section}",
  name: "Project Documentation",
  mimeType: "text/markdown",
  arguments: [
    {
      name: "section",
      required: true,
    },
  ],
  async load(args) {
    const docs = {
      "getting-started": "# Getting Started\n\nWelcome to our project!",
      "api-reference": "# API Reference\n\nAuthentication is required.",
    };
    return {
      text: docs[args.section] || "Documentation not found",
    };
  },
});

// Use embedded resources in a tool
server.addTool({
  name: "get_documentation",
  description: "Retrieve project documentation",
  parameters: z.object({
    section: z.enum(["getting-started", "api-reference"]),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded(`docs://project/${args.section}`),
        },
      ],
    };
  },
});
直接定義したリソースの使用

直接定義したリソースでも動作します。

// Define a direct resource
server.addResource({
  uri: "system://status",
  name: "System Status",
  mimeType: "text/plain",
  async load() {
    return {
      text: "System operational",
    };
  },
});

// Use in a tool
server.addTool({
  name: "get_system_status",
  description: "Get current system status",
  parameters: z.object({}),
  execute: async () => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded("system://status"),
        },
      ],
    };
  },
});
プロンプト

プロンプトを使うと、サーバーで再利用可能なプロンプトテンプレートとワークフローを定義でき、クライアントからユーザーや LLM に簡単に提示できます。一般的な LLM とのやり取りを標準化して共有する強力な方法です。

server.addPrompt({
  name: "git-commit",
  description: "Generate a Git commit message",
  arguments: [
    {
      name: "changes",
      description: "Git diff or description of changes",
      required: true,
    },
  ],
  load: async (args) => {
    return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
  },
});

リソースと同様に、load は第 2 引数と第 3 引数として auth と context も受け取ります(リソースを参照)。

server.addPrompt({
  name: "git-commit",
  description: "Generate a Git commit message",
  arguments: [
    {
      name: "changes",
      description: "Git diff or description of changes",
      required: true,
    },
  ],
  load: async (args, auth, context) => {
    context.log.debug("generating git commit prompt", { user: auth?.userId });

    return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
  },
});
プロンプト引数の自動補完

プロンプトでは、引数の自動補完を提供できます。

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      complete: async (value) => {
        if (value === "Germ") {
          return {
            values: ["Germany"],
          };
        }

        return {
          values: [],
        };
      },
    },
  ],
});
enum を使用したプロンプト引数の自動補完

引数に enum 配列を指定すると、サーバーはその引数の補完候補を自動的に提供します。

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      enum: ["Germany", "France", "Italy"],
    },
  ],
});
OpenAPI

fromOpenAPI()(fastmcp/openapi から)は、OpenAPI 3.x ドキュメントを FastMCP サーバーに変換し、操作ごとに 1 つのツールを作成します。その際、外部の $ref(複数ファイルの仕様)、相対パスの servers[0].url の解決、パラメーターのフラット化による衝突にも対応します。

import { fromOpenAPI } from "fastmcp/openapi";

const server = await fromOpenAPI({
  spec: "https://petstore3.swagger.io/api/v3/openapi.json",
  include: (operation) => operation.tags.includes("pet"),
});

await server.start({ transportType: "stdio" });

オプションの詳細、認証、既知の制限事項については、OpenAPI から MCP へを参照してください。

認証

FastMCP は、事前設定済みプロバイダーによる OAuth 2.1 認証をサポートしているため、最小限の設定でサーバーを保護できます。

事前設定済みプロバイダーを使用した OAuth

プロバイダーとともに auth オプションを使用して、OAuth 認証を有効にします。

import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  canAccess: requireAuth,
  description: "Get user profile",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    const response = await fetch(
      "https://www.googleapis.com/oauth2/v2/userinfo",
      {
        headers: { Authorization: `Bearer ${accessToken}` },
      },
    );
    return JSON.stringify(await response.json());
  },
  name: "get-profile",
});

利用可能なプロバイダー:

プロバイダーインポート用途
GoogleProviderfastmcpGoogle OAuth
GitHubProviderfastmcpGitHub OAuth
AzureProviderfastmcpAzure/Entra ID
OAuthProviderfastmcp任意の OAuth 2.0 プロバイダー

汎用 OAuth プロバイダー(SAP、Auth0、Okta など):

import { FastMCP, OAuthProvider } from "fastmcp";

const server = new FastMCP({
  auth: new OAuthProvider({
    authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,
    baseUrl: "https://your-server.com",
    clientId: process.env.OAUTH_CLIENT_ID!,
    clientSecret: process.env.OAUTH_CLIENT_SECRET!,
    scopes: ["openid", "profile"],
    tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,
  }),
  name: "My Server",
  version: "1.0.0",
});
ツールの認可

組み込みヘルパー関数と canAccess プロパティを使用して、ツールへのアクセスを制御します。

import {
  requireAuth,
  requireScopes,
  requireRole,
  requireAll,
  requireAny,
  getAuthSession,
} from "fastmcp";

// Require any authenticated user
server.addTool({
  canAccess: requireAuth,
  name: "user-tool",
  // ...
});

// Require specific OAuth scopes
server.addTool({
  canAccess: requireScopes("read:user", "write:data"),
  name: "scoped-tool",
  // ...
});

// Require specific role
server.addTool({
  canAccess: requireRole("admin"),
  name: "admin-tool",
  // ...
});

// Combine with AND logic
server.addTool({
  canAccess: requireAll(requireAuth, requireRole("admin")),
  name: "admin-only",
  // ...
});

// Combine with OR logic
server.addTool({
  canAccess: requireAny(requireRole("admin"), requireRole("moderator")),
  name: "staff-tool",
  // ...
});

カスタム認可:

独自のロジックを使用する場合は、関数を直接渡します。

server.addTool({
  name: "custom-auth-tool",
  canAccess: (auth) =>
    auth?.role === "admin" && auth?.department === "engineering",
  execute: async () => "Access granted!",
});

セッションデータの取得:

ツールの execute 関数内で OAuth セッションに型安全にアクセスするには、getAuthSession を使用します。

import { getAuthSession, GoogleSession } from "fastmcp";

server.addTool({
  canAccess: requireAuth,
  name: "get-profile",
  execute: async (_args, { session }) => {
    // Type-safe destructuring (throws if not authenticated)
    const { accessToken } = getAuthSession(session);

    // Or with provider-specific typing:
    // const { accessToken } = getAuthSession<GoogleSession>(session);

    const response = await fetch("https://api.example.com/user", {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    return JSON.stringify(await response.json());
  },
});

注: session.accessToken に直接アクセスすることもできますが、session が undefined の場合を処理する必要があります。getAuthSession ヘルパーは、セッションが認証されていない場合に明確なエラーをスローするため、canAccess: requireAuth と併用するとより安全です。

カスタム認証

OAuth 以外のシナリオ(API キー、カスタムトークン)では、authenticate オプションを使用します。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: (request) => {
    const apiKey = request.headers["x-api-key"];

    if (apiKey !== "123") {
      throw new Response(null, {
        status: 401,
        statusText: "Unauthorized",
      });
    }

    return { id: 1, role: "user" };
  },
});

server.addTool({
  name: "sayHello",
  execute: async (args, { session }) => {
    return `Hello, ${session.id}!`;
  },
});
OAuth プロキシ

auth オプションは、MCP クライアントと上流の OAuth プロバイダーの間で安全な仲介役として機能する、FastMCP 組み込みの OAuth プロキシを使用します。このプロキシは、Dynamic Client Registration(DCR)、PKCE、同意管理、暗号化とトークンスワップパターンがデフォルトで有効なトークン管理など、OAuth 2.1 の認可フロー全体を処理します。

主な機能:

  • 🔐 デフォルトで安全:自動暗号化(AES-256-GCM)とトークンスワップパターン
  • 🚀 設定不要:キーを自動生成し、OAuth フローを自動的に処理
  • 🔌 事前設定済みプロバイダー:Google、GitHub、Azure を組み込みでサポート
  • 🎯 RFC 準拠:DCR(RFC 7591)、PKCE、OAuth 2.1 を実装
  • 🔑 オプションの JWKS:トークン検証で RS256/ES256 をサポート(オプションの jose 依存関係を使用)

クイックスタート:

import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  canAccess: requireAuth,
  name: "protected-tool",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    // Use accessToken to call upstream APIs
    return "Authenticated!";
  },
});

高度な設定:

OAuth の動作をさらに細かく制御するには、oauth オプションを直接使用します。

import { FastMCP } from "fastmcp";
import { GoogleProvider } from "fastmcp/auth";

const authProvider = new GoogleProvider({
  baseUrl: "https://your-server.com",
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  scopes: ["openid", "profile", "email"],
});

const server = new FastMCP({
  name: "My Server",
  oauth: {
    authorizationServer: authProvider
      .getProxy()
      .getAuthorizationServerMetadata(),
    enabled: true,
    proxy: authProvider.getProxy(),
  },
  version: "1.0.0",
});

ドキュメント:

OAuth 検出エンドポイント

FastMCP は、OAuth プロバイダーと直接統合するための OAuth 検出エンドポイントもサポートしており、MCP Specification 2025-03-26 と MCP Specification 2025-06-18 の両方に対応しています。これにより、RFC 8414(OAuth 2.0 Authorization Server Metadata)および RFC 9470(OAuth 2.0 Protected Resource Metadata)に準拠した標準の検出エンドポイントが提供されます。

import { FastMCP } from "fastmcp";
import buildGetJwks from "get-jwks";
import fastJwt, { type DecodedJwt } from "fast-jwt";

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  oauth: {
    enabled: true,
    authorizationServer: {
      issuer: "https://auth.example.com",
      authorizationEndpoint: "https://auth.example.com/oauth/authorize",
      tokenEndpoint: "https://auth.example.com/oauth/token",
      jwksUri: "https://auth.example.com/.well-known/jwks.json",
      responseTypesSupported: ["code"],
    },
    protectedResource: {
      resource: "mcp://my-server",
      authorizationServers: ["https://auth.example.com"],
    },
  },
  authenticate: async (request) => {
    const authHeader = request.headers.authorization;

    if (!authHeader?.startsWith("Bearer ")) {
      throw new Response(null, {
        status: 401,
        statusText: "Missing or invalid authorization header",
      });
    }

    const token = authHeader.slice(7); // Remove 'Bearer ' prefix

    // Validate OAuth JWT access token using OpenID Connect discovery
    try {
      // Create JWKS client for token verification
      const getJwks = buildGetJwks();

      // Create JWT verifier
      const verify = fastJwt.createVerifier({
        async key({ header }: DecodedJwt) {
          const publicKey = await getJwks.getPublicKey({
            kid: header.kid,
            alg: header.alg,
            domain: "https://auth.example.com",
          });
          return publicKey;
        },
        algorithms: ["RS256"],
      });

      // Verify the JWT token
      const payload = await verify(token);

      return {
        userId: payload.sub,
        scope: payload.scope,
        email: payload.email,
        // Include other claims as needed
      };
    } catch (error) {
      throw new Response(null, {
        status: 401,
        statusText: "Invalid OAuth token",
      });
    }
  },
});

MCP サーバーが発行者パスの下で公開されている場合は、HTTP ストリームのベースパスも設定してください。

server.start({
  transportType: "httpStream",
  httpStream: {
    basePath: "/issuer1",
    endpoint: "/mcp",
    port: 8080,
  },
});

この設定では、FastMCP は発行者パスの認可サーバーメタデータを /.well-known/oauth-authorization-server/issuer1 で提供し、保護対象リソースのメタデータは MCP エンドポイントの /.well-known/oauth-protected-resource/issuer1/mcp で引き続き利用できます。

この設定により、次の OAuth 検出エンドポイントが自動的に公開されます。

  • /.well-known/oauth-authorization-server - 認可サーバーメタデータ(RFC 8414)
  • /.well-known/oauth-authorization-server<basePath> - httpStream.basePath が設定されている場合の認可サーバーメタデータ(RFC 8414 Section 3)
  • /.well-known/oauth-protected-resource - 保護対象リソースのメタデータ(RFC 9728)
  • /.well-known/oauth-protected-resource<endpoint> - サブパスにある保護対象リソースのメタデータ(MCP 2025-11-25)

検出メカニズム(MCP Specification 2025-11-25):

クライアントは次の検索順序で保護対象リソースのメタデータを検出します。

  1. WWW-Authenticate ヘッダー - 主な方法(mcp-proxy が自動的に処理します)
  2. サブパスの well-known - /.well-known/oauth-protected-resource<endpoint>(例:/.well-known/oauth-protected-resource/mcp)
  3. ルートの well-known - /.well-known/oauth-protected-resource(フォールバック)

サブパスとルートの両方のエンドポイントは同一のメタデータを返すため、すべての MCP クライアント実装との互換性が確保されます。

JWT トークンを検証するには、OAuth JWT トークン用の get-jwks や fast-jwt などのライブラリを使用できます。

コンテキスト経由でのヘッダーの受け渡し

MCP サーバーを HTTP 経由で公開する場合、クライアントがヘッダーを使って機密キーを渡せるようにしたいことがあります。これらのキーをツールが利用する API に渡すことで、各クライアントが独自の API キーを指定できるようになります。これは、authenticate セクションで HTTP ヘッダーを取得してセッションに保存し、後でツールから参照できるようにすることで実現できます。

import { FastMCP } from "fastmcp";
import { IncomingHttpHeaders } from "http";

// Define the session data type
interface SessionData {
  headers: IncomingHttpHeaders;
  [key: string]: unknown; // Add index signature to satisfy Record<string, unknown>
}

// Create a server instance
const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: async (request: any): Promise<SessionData> => {
    // Authentication logic
    return {
      headers: request.headers,
    };
  },
});

// Tool to display HTTP headers
server.addTool({
  name: "headerTool",
  description: "Reads HTTP headers from the request",
  execute: async (args: any, context: any) => {
    const session = context.session as SessionData;
    const headers = session?.headers ?? {};

    const getHeaderString = (header: string | string[] | undefined) =>
      Array.isArray(header) ? header.join(", ") : (header ?? "N/A");

    const userAgent = getHeaderString(headers["user-agent"]);
    const authorization = getHeaderString(headers["authorization"]);
    return `User-Agent: ${userAgent}\nAuthorization: ${authorization}\nAll Headers: ${JSON.stringify(headers, null, 2)}`;
  },
});

// Start the server
server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

これに接続するクライアントは、次のようになります。

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

const transport = new StreamableHTTPClientTransport(
  new URL(`http://localhost:8080/mcp`),
  {
    requestInit: {
      headers: {
        Authorization: "Test 123",
      },
    },
  },
);

const client = new Client({
  name: "example-client",
  version: "1.0.0",
});

(async () => {
  await client.connect(transport);

  // Call a tool
  const result = await client.callTool({
    name: "headerTool",
    arguments: {
      arg1: "value",
    },
  });

  console.log("Tool result:", result);
})().catch(console.error);

クライアントの実行後にコンソールに表示される内容は、次のようになります。

Tool result: {
  content: [
    {
      type: 'text',
      text: 'User-Agent: node\n' +
        'Authorization: Test 123\n' +
        'All Headers: {\n' +
        '  "host": "localhost:8080",\n' +
        '  "connection": "keep-alive",\n' +
        '  "authorization": "Test 123",\n' +
        '  "content-type": "application/json",\n' +
        '  "accept": "application/json, text/event-stream",\n' +
        '  "accept-language": "*",\n' +
        '  "sec-fetch-mode": "cors",\n' +
        '  "user-agent": "node",\n' +
        '  "accept-encoding": "gzip, deflate",\n' +
        '  "content-length": "163"\n' +
        '}'
    }
  ]
}
セッション ID とリクエスト ID の追跡

FastMCP は、コンテキストパラメーターを通じて、セッション ID とリクエスト ID をツールハンドラーに自動的に公開します。これにより、セッションごとの状態管理とリクエストの追跡が可能になります。

セッション ID(context.sessionId):

  • HTTP ベースのトランスポート(HTTP Stream、SSE)でのみ利用できます
  • Mcp-Session-Id ヘッダーから抽出されます
  • 同じクライアントからの複数のリクエスト間で一定です
  • セッションごとの状態、カウンター、ユーザー固有のデータの維持に役立ちます

リクエスト ID(context.requestId):

  • クライアントによって提供された場合、すべてのトランスポートで利用できます
  • 個々のリクエストごとに一意です
  • リクエストのトレースとデバッグに役立ちます
import { FastMCP } from "fastmcp";
import { z } from "zod";

const server = new FastMCP({
  name: "Session Counter Server",
  version: "1.0.0",
});

// Per-session counter storage
const sessionCounters = new Map<string, number>();

server.addTool({
  name: "increment_counter",
  description: "Increment a per-session counter",
  parameters: z.object({}),
  execute: async (args, context) => {
    if (!context.sessionId) {
      return "Session ID not available (requires HTTP transport)";
    }

    const counter = sessionCounters.get(context.sessionId) || 0;
    const newCounter = counter + 1;
    sessionCounters.set(context.sessionId, newCounter);

    return `Counter for session ${context.sessionId}: ${newCounter}`;
  },
});

server.addTool({
  name: "show_ids",
  description: "Display session and request IDs",
  parameters: z.object({}),
  execute: async (args, context) => {
    return `Session ID: ${context.sessionId || "N/A"}
Request ID: ${context.requestId || "N/A"}`;
  },
});

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

ユースケース:

  • セッションごとの状態管理:各クライアントセッション固有のカウンター、キャッシュ、一時データを維持します
  • ユーザー認証と認可:リクエスト間で認証済みユーザーを追跡します
  • セッション固有のリソース管理:セッションごとにリソースを割り当て、管理します
  • マルチテナント実装:セッションごとにデータと操作を分離します
  • リクエストのトレース:デバッグと監視のために個々のリクエストを追跡します

例:

セッションベースのカウンター管理を実演する完全な例については、src/examples/session-id-counter.ts を参照してください。

注記:

  • セッション ID は MCP トランスポートレイヤーによって自動的に生成されます
  • ステートレスモードでは、セッション ID はリクエスト間で保持されません
  • stdio トランスポートでは HTTP セッションの概念がないため、sessionId は undefined になります
指示の提供

instructions オプションを使用して、サーバーに指示を提供できます。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  instructions:
    'Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.',
});
サーバーのアイコンとメタデータ

サーバーのアイコンと関連するメタデータを公開できます。アイコンに対応したクライアントは、UI にアイコンを表示できます。

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  websiteUrl: "https://example.com",
  icons: [
    {
      src: "https://example.com/icon.png",
      mimeType: "image/png",
      sizes: ["48x48"],
    },
  ],
});

省略可能な title も MCP の initialize(serverInfo)に渡されます。これは、サーバーの name よりも表示名を優先するクライアント向けです。

セッション

session オブジェクトは FastMCPSession のインスタンスで、アクティブなクライアントセッションを表します。

server.sessions;

クライアントとサーバー間の 1 対 1 の通信を可能にするため、クライアント接続ごとに新しいサーバーインスタンスを割り当てます。

型付きサーバーイベント

on メソッドを使用して、サーバーが発行するイベントをリッスンできます。

server.on("connect", (event) => {
  console.log("Client connected:", event.session);
});

server.on("disconnect", (event) => {
  console.log("Client disconnected:", event.session);
});

FastMCPSession

FastMCPSession はクライアントセッションを表し、クライアントとやり取りするためのメソッドを提供します。

FastMCPSession インスタンスの取得方法の例については、セッションを参照してください。

requestElicitation

requestElicitation は、クライアントを介してユーザーから追加情報を収集するためのエリシテーションリクエストを作成し、応答を返します。クライアントは、対応する elicitation 機能モードを通知する必要があります。フォームリクエスト(デフォルト)の場合は elicitation: { form: {} }、URL リクエストの場合は elicitation: { url: {} } です。

await session.requestElicitation({
  message: "What is your name?",
  requestedSchema: {
    type: "object",
    properties: {
      name: { type: "string" },
    },
    required: ["name"],
  },
});

ツール内では、コンテキストオブジェクトの elicit メソッドを使用してください。

requestSampling

requestSampling はサンプリングリクエストを作成し、応答を返します。

await session.requestSampling({
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: "What files are in the current directory?",
      },
    },
  ],
  systemPrompt: "You are a helpful file system assistant.",
  includeContext: "thisServer",
  maxTokens: 100,
});
オプション

requestSampling は、リクエストオプション用の省略可能な第 2 パラメーターを受け取ります。

await session.requestSampling(
  {
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: "What files are in the current directory?",
        },
      },
    ],
    systemPrompt: "You are a helpful file system assistant.",
    includeContext: "thisServer",
    maxTokens: 100,
  },
  {
    // Progress callback - called when progress notifications are received
    onprogress: (progress) => {
      console.log(`Progress: ${progress.progress}/${progress.total}`);
    },

    // Abort signal for cancelling the request
    signal: abortController.signal,

    // Request timeout in milliseconds (default: DEFAULT_REQUEST_TIMEOUT_MSEC)
    timeout: 30000,

    // Whether progress notifications reset the timeout (default: false)
    resetTimeoutOnProgress: true,

    // Maximum total timeout regardless of progress (no default)
    maxTotalTimeout: 60000,
  },
);

オプション:

  • onprogress?: (progress: Progress) => void - リモート側からの進捗通知用コールバック
  • signal?: AbortSignal - リクエストをキャンセルするための中断シグナル
  • timeout?: number - ミリ秒単位のリクエストタイムアウト
  • resetTimeoutOnProgress?: boolean - 進捗通知によってタイムアウトをリセットするかどうか
  • maxTotalTimeout?: number - 進捗通知にかかわらず適用される最大合計タイムアウト
clientCapabilities

clientCapabilities プロパティには、クライアントの機能が含まれます。

session.clientCapabilities;
loggingLevel

loggingLevel プロパティには、クライアントが設定したログレベルが格納されます。クライアントが logging/setLevel を送信するまでは info が設定されます。クライアントが設定したレベルより重大度の低いメッセージは送信されず、破棄されます。

session.loggingLevel;
roots

roots プロパティには、クライアントが設定したルートが格納されます。

session.roots;
server

server プロパティには、セッションに関連付けられた MCP サーバーのインスタンスが格納されます。

session.server;
型付きセッションイベント

on メソッドを使用して、セッションが発行するイベントをリッスンできます。

session.on("rootsChanged", (event) => {
  console.log("Roots changed:", event.roots);
});

session.on("error", (event) => {
  console.error("Error:", event.error);
});

サーバーの実行

インメモリトランスポートを使った単体テスト

server.connect(transport) は、start() にトランスポートを作成させる代わりに、自分で構築したトランスポートにサーバーを接続します。SDK の InMemoryTransport と組み合わせると、サーバーをプロセス内で実行できます。ポートのバインドもサブプロセスの起動も不要で、stdio サーバーをテストする場合に通常はこちらが適しています。

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";

async function createTestClient(server: FastMCP) {
  const [clientTransport, serverTransport] =
    InMemoryTransport.createLinkedPair();

  const client = new Client({ name: "test-client", version: "0.0.0" });

  const [session] = await Promise.all([
    server.connect(serverTransport),
    client.connect(clientTransport),
  ]);

  return { client, session };
}

test("adds two numbers", async () => {
  const { client } = await createTestClient(server);

  expect(
    await client.callTool({ arguments: { a: 2, b: 3 }, name: "add" }),
  ).toEqual({
    content: [{ text: "5", type: "text" }],
  });

  await client.close();
});

セッションは、インスタンスに登録されたツール、リソース、プロンプトから構築されます。これは start() がセッションを構築する方法とまったく同じです。そのため、テストでは canAccess によるフィルタリングや connect/disconnect イベントなど、実際のサーバーと同じ接続処理を検証できます。

セッション認証情報は第 2 引数として渡します。これは、authenticate 関数が返す値と同等です。

await server.connect(serverTransport, { id: 7, role: "admin" });

connect は FastMCPSession を返すため、session.clientCapabilities、session.roots などを検証できます。トランスポートのライフサイクルは呼び出し元が管理します。stop() は connect に渡されたトランスポートを閉じないため、テスト終了時にはクライアント(および disconnect イベントを発生させる必要がある場合はセッション)を閉じてください。

mcp-cli を使ったテスト

サーバーをテストおよびデバッグする最も手早い方法は、fastmcp dev を使うことです。

npx fastmcp dev server.js
npx fastmcp dev server.ts

これにより、ターミナルで MCP サーバーをテストおよびデバッグするために、mcp-cli を使ってサーバーが実行されます。

ツールを非対話形式で呼び出す場合(スクリプトや自動テストなど)は、--tool と、必要に応じて JSON の --args を渡します。

npx fastmcp dev server.ts --tool add --args '{"a":1,"b":2}'

これにより、対話型インスペクターを開く代わりに、ツールの結果が JSON として出力され、終了します。このモードでは、サーバーは 1 回の呼び出しのために起動するため、--watch は効果がありません。

MCP Inspector でのインスペクト

別の方法として、公式の MCP Inspector を使い、Web UI でサーバーをインスペクトできます。

npx fastmcp inspect server.ts

よくある質問

Claude Desktop で使用するには?

ガイド https://modelcontextprotocol.io/quickstart/user に従い、次の設定を追加してください。

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
      "env": {
        "YOUR_ENV_VAR": "value"
      }
    }
  }
}
FastMCP をプロキシの背後で実行するには?

FastMCP を express および http-proxy-middleware と併用する例については、このイシューを参照してください。

ショーケース

メモ

FastMCP を使用してサーバーを開発した方は、ここで紹介するためにプルリクエストを送信してください!

メモ

独自の MCP サーバーを構築するためのボイラープレートリポジトリをお探しの場合は、fastmcp-boilerplateをご覧ください。

  • apinetwork/piapi-mcp-server - Midjourney/Flux/Kling/LumaLabs/Udio/Chrip/Trellis を使用してメディアを生成します
  • domdomegg/computer-use-mcp - コンピューターを操作します
  • LiterallyBlah/Dradis-MCP – Dradis のプロジェクトと脆弱性を管理します
  • Meeting-Baas/meeting-mcp - 会議ボットの作成、文字起こしの検索、録音データの管理を行います
  • drumnation/unsplash-smart-mcp-server – AI エージェントが Unsplash のプロ品質のストックフォトをシームレスに検索、推薦、提供できるようにします
  • ssmanji89/halopsa-workflows-mcp - HaloPSA Workflows と AI アシスタントを連携します
  • aiamblichus/mcp-chat-adapter – LLM がチャット補完を利用するためのシンプルなインターフェースを提供します
  • eyaltoledano/claude-task-master – FastMCP を活用した高度な AI プロジェクト/タスク管理ツールです
  • cswkim/discogs-mcp-server - Discogs API に接続し、音楽コレクションを操作します
  • Panzer-Jack/feuse-mcp - フロントエンド向けの便利な MCP ツールです。API 連携とコード生成を自動化するための、ウェブ開発者に欠かせないユーティリティを提供します
  • sunra-ai/sunra-clients - Sunra.ai は開発者向けに構築された生成メディアプラットフォームで、高性能な AI モデルの推論機能を提供します。
  • foxtrottwist/shortcuts-mcp - Claude を macOS Shortcuts に接続し、システムの自動化、アプリ連携、インタラクティブなワークフローを実現します

謝辞

このプロジェクトは BrowserStack でテストされています。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。