Skip to main content
Glama
weijianzhg

mCP 2.0

by weijianzhg

MCP sin estado, aplicación con estado

Un proyecto mínimo de JavaScript que demuestra los principales patrones de solicitud/respuesta de MCP 2026-07-28 con el SDK TypeScript de MCP v2.

La demo cubre:

  • Solicitudes HTTP sin estado, sin Mcp-Session-Id.

  • Estado gestionado por la aplicación y direccionado mediante identificadores explícitos.

  • Solicitudes de varios viajes de ida y vuelta para la confirmación del usuario.

  • Actualizaciones de progreso limitadas a una operación individual.

  • Descubrimiento de herramientas almacenable en caché con TTL y ámbito de compartición.

Cómo ejecutarlo

Requiere Node.js 20 o posterior.

npm install
npm run demo

El comando inicia un servidor en un puerto local disponible, recorre todos los escenarios, imprime los resultados y apaga el servidor. El ejemplo de eliminación usa un conjunto de archivos virtuales en memoria y nunca toca archivos en el disco.

Ejecuta las pruebas de integración con:

npm test

Para dejar el servidor en ejecución para otro cliente MCP:

npm run server

El endpoint es http://127.0.0.1:3000/mcp. Define PORT para cambiar el puerto.

Related MCP server: MCP RC Long-Running Task Prototype

1. Solicitudes sin estado

MCP 2026-07-28 elimina las sesiones HTTP a nivel de protocolo. Este proyecto crea un McpServer nuevo para cada solicitud, de modo que cualquier solicitud puede llegar a cualquier instancia del servidor:

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

El cliente selecciona explícitamente la revisión moderna del protocolo:

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

Por tanto, el estado almacenado dentro de una instancia de McpServer desaparece después de esa solicitud. Llamar dos veces al contador efímero de la demo produce 1 desde dos instancias de servidor diferentes.

2. Datos de aplicación con estado

Un MCP sin estado no requiere una aplicación sin estado. El estado duradero pertenece fuera de la instancia de MCP de cada solicitud y se selecciona mediante un identificador explícito:

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

El cliente transporta el identificador entre llamadas que por lo demás son independientes:

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

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

El Map en memoria es solo un marcador provisional. Un servidor de producción debería usar una base de datos o un almacén compartido, vincular los identificadores al principal autenticado y aplicar autorización y caducidad en cada búsqueda.

3. Confirmación de varios viajes de ida y vuelta

Una herramienta que necesita más información devuelve input_required. El cliente obtiene la respuesta e repite la solicitud original con inputResponses, de modo que el servidor no inicia una segunda petición JSON-RPC en el flujo de operaciones.

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

El SDK puede atender la solicitud y reintentarla automáticamente mediante el controlador normal de elicitación:

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

La confirmación es una experiencia de usuario, no una autorización. El servidor debe seguir autenticando al llamador y hacer cumplir de forma independiente el permiso para eliminar archivos.

4. Progreso con ámbito de solicitud

El progreso permanece asociado a la solicitud que inició el trabajo. Las operaciones concurrentes reciben solo sus propias actualizaciones en lugar de compartir un único flujo global de eventos.

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

Cada llamada del cliente proporciona su propia función de retorno de progreso:

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. Capacidad de almacenamiento en caché

Las respuestas almacenables en caché incluyen una duración de frescura y una política de compartición. Esto reduce el tráfico repetido de descubrimiento cuando un agente se conecta a muchos servidores MCP.

Este servidor marca su catálogo de herramientas como reutilizable durante cinco minutos:

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

El cliente usa las entradas frescas automáticamente:

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

Los campos se aplican a los resultados de tools/list, prompts/list, resources/list, resources/templates/list y resources/read.

  • public permite que clientes e intermediarios compartidos reutilicen el resultado entre usuarios.

  • private restringe la reutilización al contexto de autorización de la solicitud. Cuando un almacén de caché esté compartido, define el cachePartition del cliente como un identificador estable del principal.

Un TTL es una estimación de frescura. Las notificaciones de cambios en las listas pueden invalidar catálogos almacenados en caché antes de que expire su TTL.

Estructura del proyecto

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

Los paquetes MCP están fijados en 2.0.0, incluidas las API de inputRequired, acceptedContent, de caché y de progreso con ámbito de solicitud que se usan aquí.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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
    C
    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.
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weijianzhg/mcp-2-0'

If you have feedback or need assistance with the MCP directory API, please join our Discord server