Skip to main content
Glama
weijianzhg

mCP 2.0

by weijianzhg

ステートレス MCP、ステートフルなアプリケーション

MCP TypeScript SDK v2 を使用して、MCP 2026-07-28 の主要なリクエスト/レスポンスパターンを示す最小限の JavaScript プロジェクトです。

このデモでは以下を扱います:

  • Mcp-Session-Id のないステートレスな HTTP リクエスト。

  • 明示的なハンドルで参照されるアプリケーション管理の状態。

  • ユーザー確認のための複数回のラウンドトリップリクエスト。

  • 個々の操作にスコープされた進捗更新。

  • TTL と共有スコープを備えたキャッシュ可能なツールディスカバリ。

実行方法

Node.js 20 以降が必要です。

npm install
npm run demo

このコマンドは、利用可能なローカルポートでサーバーを起動し、すべてのシナリオを実行して結果を出力し、サーバーをシャットダウンします。削除の例では、インメモリの仮想ファイルセットを使用し、ディスク上のファイルには一切触れません。

統合テストを実行するには:

npm test

別の MCP クライアントのためにサーバーを起動したままにするには:

npm run server

エンドポイントは http://127.0.0.1:3000/mcp です。PORT を設定するとポートを上書きできます。

Related MCP server: MCP RC Long-Running Task Prototype

1. ステートレスリクエスト

MCP 2026-07-28 はプロトコルレベルの HTTP セッションを廃止します。このプロジェクトでは、リクエストごとに新しい McpServer を作成するため、どのリクエストも任意のサーバーインスタンスに到達できます:

const mcpHandler = createMcpHandler(
  () => createStateServer(demoFiles),
  {
    legacy: "reject",
    onerror: (error) => console.error("MCP error:", error),
  },
);

クライアントは明示的に最新のプロトコルリビジョンを選択します:

const client = new Client(
  { name: "state-demo-client", version: "0.1.0" },
  {
    capabilities: { elicitation: { form: {} } },
    versionNegotiation: {
      mode: { pin: "2026-07-28" },
    },
  },
);

したがって、1 つの McpServer インスタンス内に保存された状態は、そのリクエストの後に消えます。デモの一時的なカウンターを 2 回呼び出すと、2 つの異なるサーバーインスタンスから 1 が生成されます。

2. ステートフルなアプリケーションデータ

ステートレスな MCP は、ステートレスなアプリケーションを要求しません。永続的な状態は、リクエストごとの MCP サーバーの外部にあり、明示的なハンドルを使用して選択されます:

const countersById = new Map();

server.registerTool(
  "create-counter",
  {
    outputSchema: z.object({ counterId: z.uuid(), value: z.number() }),
  },
  async () => {
    const counterId = randomUUID();
    countersById.set(counterId, 0);
    const structuredContent = { counterId, value: 0 };

    return {
      content: [{ type: "text", text: JSON.stringify(structuredContent) }],
      structuredContent,
    };
  },
);

クライアントは、独立した呼び出しの間でハンドルを保持します:

const created = await client.callTool({ name: "create-counter" });
const { counterId } = created.structuredContent;

await client.callTool({
  name: "increment-counter",
  arguments: { counterId },
});

インメモリの Map は単なる代用です。本番サーバーは、データベースまたは共有ストアを使用し、ハンドルを認証済みプリンシパルにバインドし、すべてのルックアップで認可と有効期限を強制する必要があります。

3. 複数回のラウンドトリップ確認

追加の入力が必要なツールは input_required を返します。クライアントは回答を取得し、inputResponses を使用して元のリクエストを再試行するため、サーバーは操作ストリーム上で 2 番目の JSON-RPC リクエストを開始しません。

server.registerTool(
  "delete-files",
  {
    inputSchema: z.object({ files: z.array(z.string()).min(1) }),
    annotations: { destructiveHint: true },
  },
  async ({ files }, ctx) => {
    const confirmation = acceptedContent(
      ctx.mcpReq.inputResponses,
      "confirm",
      confirmationSchema,
    );

    if (confirmation === undefined) {
      return inputRequired({
        inputRequests: {
          confirm: inputRequired.elicit({
            message: `Delete ${files.length} virtual files?`,
            requestedSchema: confirmationSchema,
          }),
        },
      });
    }

    if (!confirmation.confirm) {
      return {
        content: [{ type: "text", text: "Cancelled" }],
        structuredContent: { status: "cancelled", deleted: [] },
      };
    }

    const deleted = files.filter((file) => demoFiles.delete(file));
    return {
      content: [{ type: "text", text: `Deleted: ${deleted.join(", ")}` }],
      structuredContent: { status: "deleted", deleted },
    };
  },
);

SDK は、通常の引き出しハンドラーを使用してリクエストを満たし、自動的に再試行できます:

client.setRequestHandler("elicitation/create", async (request) => {
  const confirm = await askUser(request.params.message);
  return {
    action: "accept",
    content: { confirm },
  };
});

確認はユーザーエクスペリエンスであり、認可ではありません。サーバーは依然として呼び出し元を認証し、ファイルを削除する権限を独立して強制する必要があります。

4. リクエストスコープの進捗

進捗は、作業を開始したリクエストに付随したままです。並行操作は、1 つのグローバルイベントストリームを共有するのではなく、自分自身の更新のみを受け取ります。

server.registerTool(
  "run-work",
  { inputSchema: z.object({ job: z.string() }) },
  async ({ job }, ctx) => {
    const progressToken = ctx.mcpReq._meta?.progressToken;

    for (const progress of [10, 30, 70]) {
      if (progressToken !== undefined) {
        await ctx.mcpReq.notify({
          method: "notifications/progress",
          params: {
            progressToken,
            progress,
            total: 100,
            message: `${job}: ${progress}%`,
          },
        });
      }
    }

    return {
      content: [{ type: "text", text: `${job}: complete` }],
      structuredContent: { job, status: "complete" },
    };
  },
);

各クライアント呼び出しは、独自の進捗コールバックを提供します:

await Promise.all([
  client.callTool(
    { name: "run-work", arguments: { job: "alpha" } },
    { onprogress: (update) => alphaProgress.push(update) },
  ),
  client.callTool(
    { name: "run-work", arguments: { job: "beta" } },
    { onprogress: (update) => betaProgress.push(update) },
  ),
]);

5. キャッシュ可能性

キャッシュ可能なレスポンスには、鮮度の有効期間と共有ポリシーが含まれます。これにより、1 つのエージェントが多くの MCP サーバーに接続する際の繰り返しのディスカバリトラフィックが削減されます。

このサーバーは、ツールカタログを 5 分間再利用可能としてマークします:

const server = new McpServer(
  { name: "mcp-state-demo", version: "0.1.0" },
  {
    cacheHints: {
      "tools/list": {
        ttlMs: 300_000,
        cacheScope: "public",
      },
    },
  },
);

クライアントは新しいエントリを自動的に使用します:

await client.listTools(); // network request; stores the result
await client.listTools(); // cache hit; no network request

await client.listTools(undefined, {
  cacheMode: "refresh",
}); // forces a network request and updates the cache

これらのフィールドは、tools/list、prompts/list、resources/list、resources/templates/list、および resources/read の結果に適用されます。

  • public は、クライアントと共有仲介者が結果をユーザー間で再利用することを許可します。

  • private は、再利用をリクエスト元の認可コンテキストに制限します。キャッシュストアが共有されている場合、クライアントの cachePartition を安定したプリンシパル識別子に設定します。

TTL は鮮度の見積もりです。リスト変更通知は、TTL が期限切れになる前にキャッシュされたカタログを無効化できます。

プロジェクト構造

src/server.js       MCP server and tool implementations
src/demo.js         Client exercising all five scenarios
test/state.test.js  Integration tests for every scenario

MCP パッケージは 2.0.0 に固定されており、ここで使用される inputRequired、acceptedContent、キャッシュ、およびリクエストスコープの進捗 API が含まれています。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A reference implementation demonstrating proper MCP server patterns with HTTP transport, featuring session management, progress notifications, and example tools for testing server functionality. Serves as a clean template for building MCP servers with streamable responses and comprehensive error handling.
    7
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Demonstrates MCP 2026-07-28 behavior for long-running tool calls, task lifecycle (get, update, cancel), and elicitation clarification during async tasks.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Educational MCP server demonstrating the 2026-07-28 stateless protocol with raw Starlette, no SDK, featuring tools, request state handles, MRTR elicitation, and subscriptions.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Demo MCP server for ACEL, a runtime verification middleware that blocks a rule-violating tool call before it executes. 5 tools (authenticate, read/validate/delete records, send payment) showing ACEL enforcing call ordering and state preconditions live via the official MCP SDK's middleware hook.
    1
    MIT