MCP Fusion/Protocol and runtime/Architecture du runtime

Architecture du runtime

Demandez à l’IA à propos de Vinkius

Comment MCP Fusion fonctionne réellement : compilation au build, routage des requêtes en O(1), contexte par requête, la monade Result, le pipeline d’exécution et les transports.

Cette page décrit le moteur sous-jacent de l’API fluide documentée dans Tools. C’est la référence technique approfondie du cycle de vie d’une requête, compilée dans packages/core.

Deux couches : compilez une fois, servez beaucoup

MCP Fusion sépare le travail qui relève du build time de celui qui doit se produire à chaque requête.

Build time. registry.register(builder) appelle builder.buildToolDefinition() immédiatement. Cette passe de compilation produit tout ce dont le serveur a besoin pour la vie du processus :

  • le schéma d’entrée fusionné, avec le champ discriminant et les annotations obligatoires par action
  • les schémas de validation prêts pour <tool_error>, un schéma Zod strict par action
  • la chaîne de middleware précompilée, une closure par action, le middleware global le plus externe et le middleware par action le plus interne
  • la carte des actions, une Map O(1) de la clé d’action vers le contexte compilé

Après buildToolDefinition() le builder est gelé : Object.freeze sur les actions, et une garde sur chaque méthode mutante. Vous ne pouvez pas modifier par accident un tool construit. mergeActions() est le seul moyen autorisé d’ajouter des actions, et il dégèle explicitement pour reconstruire.

Request time. Le chemin d’exécution est fixe et n’a aucune étape d’assemblage :

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

Chaque étape lit dans une structure précompilée. Aucun schéma reconstruit, aucune chaîne composée, aucune recherche parcourue sur le chemin de la requête.

Registry, noms de tools et actions

Un nom de builder avec un point n’est pas un nom de tool à plat. f.query('billing.get_invoice') enregistre l’action get_invoice sur le tool billing. Deux noms avec un point au build time est une erreur ; l’imbrication utilise f.router('support.tickets'). Les builders qui partagent un namespace fusionnent : trois fichiers exportant compliance.scan, compliance.report et compliance.status deviennent un seul tool compliance à trois actions, vous pouvez donc organiser un connecteur par répertoire et l’agent voit toujours une surface cohérente.

contextFactory : l’entrée par requête

attachToServer(server, { contextFactory }) exécute contextFactory(extra) une fois par requête, avant le routage. extra est le contexte de requête brut du SDK MCP : l’id de session, l’objet _meta (jeton de progression), l’AbortSignal pour l’annulation et le canal sendRequest pour l’élicitation. Renvoyez un objet neuf ; le framework le mute en place au fil de l’enrichissement par le middleware. C’est ici que les handles de base de données, les ids de tenant et le signal d’abort entrent dans ctx, donc un déploiement stateless n’a aucune fuite inter-requêtes par construction.

Si vous omettez contextFactory, toucher ctx lève une erreur explicite au lieu d’échouer mystérieusement.

Les transports

startServer() prend en charge trois transports :

TransportSessionUsage
stdioaucuneclients locaux, les applications de bureau, mcpfusion dev
httpid de session UUID, réapisseur de TTL, plafond de corps, token bucket par sessionun serveur MCP HTTP persistant
statelessaucunemode MCP 2.0 : un Server neuf par requête, derrière n’importe quel load balancer

Stateless est le choix par défaut pour scalier : pas de handshake initialize, les requêtes sont routées par les en-têtes Mcp-Method et Mcp-Name, et les adaptateurs Vercel et Cloudflare servent des réponses JSON sans SSE et sans état de session. Le Vinkius Edge utilise ce modèle : votre StartServerOptions.state est sérialisé avant que l’isolate soit détruit, puis restauré de façon transparente à la prochaine requête à froid.

Monade Result : le contrôle de flux sans exceptions

En interne, le pipeline repose sur Result : Success ou Failure, chaque étape s’interrompt sur Failure. Les handlers ne voient pas Result ; ils renvoient des données ou succeed()/toolError(). Ce que Result apporte, c’est la prévisibilité : la validation, l’analyse du discriminant et le dispatch renvoient des valeurs, pas des exceptions levées, donc rien ne se perd dans une stack trace. Un ToolResponse levé passe sans modification (donc throw toolError('NOT_FOUND', ...) fonctionne) ; toute autre levée est enveloppée comme INTERNAL_ERROR avec une récupération du type « ne rejouez pas à l’aveugle ».

Gardes autour du handler

Trois gardes indépendantes enveloppent l’exécution et ont un coût nul quand elles ne sont pas configurées :

  • Concurrence : un sémaphore avec file bornée. Au-delà de la capacité, renvoie SERVER_BUSY avec une indication de retry plutôt que de dégrader.
  • Sérialisation des mutations : une action destructive reçoit automatiquement un mutex FIFO par action, deux agents ne peuvent donc pas entrer en concurrence sur la même écriture.
  • Limite d’égress : un budget d’octets sur la réponse. Le texte trop volumineux est tronqué avec un message système demandant au modèle de paginer. Le structuredContent structuré est préservé ; isError n’est jamais inversé.

Voir Security pipeline pour les gardes côté entrée et State sync pour les signaux de cache et d’obsolescence.

Hooks d’observabilité

registry.enableDebug(observer), enableTracing(tracer) et enableTelemetry(sink) se propagent à chaque builder via la même interface duck-typée. Chaque hook est inerte tant qu’il n’est pas défini : pas de timer, pas de wrapper, pas d’allocation quand un sous-système est silencieux. Les trois mêmes sinks alimentent mcpfusion inspect, le pipeline OTel et la preuve de couverture du middleware dans le lockfile.

Prochaines étapes