Skip to main content
Glama
weijianzhg

mCP 2.0

by weijianzhg

MCP без состояния, приложение с состоянием

Минимальный JavaScript-проект, демонстрирующий основные паттерны запросов/ответов MCP 2026-07-28 с MCP TypeScript SDK v2.

Демо охватывает:

  • HTTP-запросы без сохранения состояния без Mcp-Session-Id.

  • Состояние, управляемое приложением, адресуемое явными дескрипторами.

  • Многоэтапные запросы для подтверждения пользователем.

  • Обновления прогресса, ограниченные отдельной операцией.

  • Кэшируемое обнаружение инструментов с 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