Skip to main content
Glama
weijianzhg

mCP 2.0

by weijianzhg

无状态 MCP,有状态应用

一个最小的 JavaScript 项目,演示 MCP 2026-07-28 的主要请求/响应模式,使用 MCP TypeScript SDK v2。

该演示涵盖:

  • 无 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" },
    },
  },
);

因此,存储在单个 McpServer 实例内的状态会在该请求结束后消失。调用演示的临时计数器两次,会从两个不同的服务器实例各产生 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 重试原始请求,因此服务器不会在操作流上发起第二个 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. 请求范围的进度

进度仍然附着在发起工作的请求上。并发操作只接收各自的更新,而不是共享一个全局事件流。

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. 可缓存性

可缓存的响应包含新鲜度生命周期和共享策略。当一个代理连接到许多 MCP 服务器时,这减少了重复的发现流量。

此服务器将其工具目录标记为可复用五分钟:

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