MCP Fusion/Protocol and runtime/Arquitectura de runtime

Arquitectura de runtime

Pregunta a la IA sobre Vinkius

Cómo se ejecuta realmente MCP Fusion: compilación en build time, enrutamiento de solicitudes O(1), contexto por solicitud, la mónada Result, el pipeline de ejecución y los transports.

Esta página describe el motor que hay debajo de la API fluida documentada en Tools. Es la referencia técnica profunda del ciclo de vida de una solicitud, compilada en packages/core.

Dos capas: compila una vez, sirve muchas

MCP Fusion separa el trabajo que pertenece al build time del trabajo que debe ocurrir en cada solicitud.

Build time. registry.register(builder) llama a builder.buildToolDefinition() de inmediato. Ese paso de compilación produce todo lo que el servidor necesita durante toda la vida del proceso:

  • el schema de entrada combinado, con el campo discriminador y las anotaciones obligatorias por acción
  • los schemas de validación listos para <tool_error>, un schema Zod strict por acción
  • la cadena de middleware precompilada, un closure por acción, el middleware global más externo y el de cada acción más interno
  • el mapa de acciones, un Map O(1) de la clave de acción al contexto compilado

Después de buildToolDefinition() el builder queda congelado: Object.freeze sobre las acciones y una guarda en cada método modificante. No puedes cambiar una tool construida por accidente. mergeActions() es la única vía autorizada para añadir acciones, y descongela explícitamente para reconstruir.

Request time. La ruta de runtime es fija y no tiene paso de ensamblaje:

contextFactory → discriminator parse → action resolve (Map)
  → arg validation (cached strict schema) → middleware chain (precompiled)
  → handler → Presenter (postProcess) → guards → response

Cada paso lee de una estructura precompilada. No se reconstruye ningún schema, no se compone ninguna cadena, no se recorre ninguna búsqueda en la ruta de la solicitud.

Registry, nombres de tools y acciones

Un nombre de builder con punto no es un nombre plano de tool. f.query('billing.get_invoice') registra la acción get_invoice en la tool billing. Dos nombres con un punto en build time son un error; el anidamiento usa f.router('support.tickets'). Los builders que comparten un namespace se fusionan: tres archivos que exportan compliance.scan, compliance.report y compliance.status se convierten en una sola tool compliance con tres acciones, así que puedes organizar un conector por directorio y el agente sigue viendo una superficie coherente.

contextFactory: la entrada por solicitud

attachToServer(server, { contextFactory }) ejecuta contextFactory(extra) una vez por solicitud, antes del enrutamiento. extra es el contexto de solicitud crudo del SDK de MCP: el id de sesión, el objeto _meta (token de progreso), el AbortSignal para cancelación y el canal sendRequest para elicitación. Devuelve un objeto nuevo; el framework lo muta en el sitio mientras el middleware lo enriquece. Ahí es donde los handles de base de datos, los ids de tenant y la señal de abort entran en ctx, así que un despliegue stateless no tiene filtración entre solicitudes por construcción.

Si omites contextFactory, tocar ctx lanza un error explícito en lugar de fallar de forma misteriosa.

Los transports

startServer() admite tres transports:

TransportSesiónUso
stdioningunaclientes locales, las apps de escritorio, mcpfusion dev
httpid de sesión UUID, reaper de TTL, tope de cuerpo, token bucket por sesiónun servidor MCP HTTP persistente
statelessningunamodo MCP 2.0: un Server nuevo por solicitud, detrás de cualquier load balancer

Stateless es el valor por defecto para escalar: sin handshake initialize, las solicitudes se enrutan por las cabeceras Mcp-Method y Mcp-Name, y los adaptadores Vercel y Cloudflare sirven respuestas JSON sin SSE y sin estado de sesión. El Vinkius Edge usa este modelo: tu StartServerOptions.state se serializa antes de que el isolate se descarte y se restaura de forma transparente en la siguiente solicitud en frío.

Mónada Result: control de flujo sin excepciones

Internamente el pipeline se basa en Result: Success o Failure, cada paso se corta en Failure. Los handlers no ven Result; devuelven datos o succeed()/toolError(). Lo que Result aporta es previsibilidad: la validación, el análisis del discriminador y el dispatch devuelven valores, no excepciones lanzadas, así nada se pierde en una stack trace. Un ToolResponse lanzado pasa intacto (así que throw toolError('NOT_FOUND', ...) funciona); cualquier otro throw se envuelve como INTERNAL_ERROR con una recuperación de "no reintentes a ciegas".

Guardas alrededor del handler

Tres guardas independientes envuelven la ejecución y tienen coste cero cuando no están configuradas:

  • Concurrencia: un semáforo con cola acotada. Por encima de la capacidad devuelve SERVER_BUSY con una pista de retry en lugar de degradar.
  • Serialización de mutaciones: a una acción destructiva se le asigna automáticamente un mutex FIFO por acción, así dos agentes no compiten por la misma escritura.
  • Límite de egreso: un presupuesto de bytes en la respuesta. El texto excesivo se trunca con un mensaje de sistema que indica al modelo paginar. El structuredContent estructurado se conserva; isError nunca se invierte.

Mira Security pipeline para las guardas del lado de entrada y State sync para señales de caché y obsolescencia.

Hooks de observabilidad

registry.enableDebug(observer), enableTracing(tracer) y enableTelemetry(sink) se propagan a cada builder a través de la misma interfaz duck-typed. Cada hook es inerte hasta que se define: sin timer, sin wrapper, sin asignación cuando un subsistema está callado. Los mismos tres sinks alimentan mcpfusion inspect, el pipeline OTel y la evidencia de cobertura de middleware del lockfile.

Próximos pasos