Publié 20 sept. 20265 min de lecture
MCP Fusion 5.1.0 : corréler les appels d'outils MCP dans la trace distribuée
Publiée le 20 septembre 2026. Les spans d'outil MCP étaient des îlots : chaque appel démarrait une nouvelle racine, et les backends qui ne parlent pas mcp.* ne pouvaient pas les interroger. La 5.1.0 ajoute à chaque span d'outil la corrélation du contexte W3C Trace et l'émission double GenAI/OpenInference, sans la moindre dépendance OpenTelemetry dans le core.

Par Renato Marinho
Founder · Vinkius
Les notes de version de la 5.1.0, publiée le 20 septembre 2026, listent deux lots de travail : G1, corrélation du parent W3C Trace Context, et G2, émission biconvention, et les deux arrivent dans un seul et même package (notes de version · MCP Fusion sur GitHub). Les quinze autres packages montent à 5.1.0 en lockstep sans une ligne de code : leur range ^5.0.0 sur le core est déjà satisfaite, donc pour eux, le release tient en une ligne de comptabilité semver. Ce qui a bougé est étroit, et il touche précisément la chose qui décide si vous pouvez voir les appels d'outils de votre agent dans votre backend.
Le waterfall mourait à la passerelle
Avant ce release, MCPFusionTracer.startSpan prenait deux arguments et les spans qu'il produisait étaient toujours des racines fraîches. Concrètement, la trace distribuée de l'agent s'arrêtait à la passerelle MCP : chaque appel d'outil apparaissait dans votre backend comme une trace orpheline à lui seul, et suivre agent → outil dans un seul waterfall était impossible. C'était aussi la cause d'une autre cécité : les backends sans connaissance du namespace privé mcp.* ne pouvaient pas répondre à « tous les appels de la dernière heure » ; cette requête ne courait que là où vous aviez enseigné le namespace à votre fournisseur.
Un handle de parent que le framework ne touche pas
Le nouveau type exporté MCPFusionSpanContext est un handle de parent structurellement opaque : traceId (32 caractères hex minuscules), spanId (16), flags (l'octet de flags W3C, 0x01 si échantillonné), plus les optionnels remote, traceState, baggage et un slot raw pour le contexte du tracer du host. Le host en construit un à partir d'une paire traceparent / tracestate / baggage reçue, en-têtes HTTP, ou params._meta du MCP, et le framework fait exactement une chose avec : il le transmet. Il ne l'inspecte jamais. Cette opacité est le design, pas un accident : comme un adaptateur côté host mappe traceId / spanId / raw sur SpanContext + Context d'OpenTelemetry, @mcpfusion/core garde zéro dépendance @opentelemetry/*. Un Tracer OTel brut n'est pas assignable à MCPFusionTracer (tableaux d'attributs immuables, plus la scission du Context), la frontière reste donc un adaptateur fin dans votre code, pas une dépendance de package.
Le parsing est strict par choix. Les helpers sans dépendance s'appuient sur crypto.randomUUID() : newTraceId(), newSpanId(), generateTraceparent(sampled = true), et parseTraceparent n'accepte que la version 00, des trace IDs de 32 hex minuscules non nuls, des span IDs de 16 hex minuscules non nuls et des flags de deux chiffres hexadécimaux ; toute déviation renvoie undefined, jamais un parent fabriqué. tracestate et baggage passent verbatim sous leurs limites W3C (512 caractères, 8192 octets), et extractW3CContext rend un traceparent valide obligatoire : pas d'en-tête valide, pas de contexte, et le span est une racine fraîche. Le format en octets est celui que SwarmGateway émet déjà ; les traces du monorepo s'interopèrent sans cérémonie.
La récupération est par requête. GroupedToolBuilder et ToolRegistry lisent une clé conventionnelle duck-typée, ctx.mcpTraceContext (même motif que ctx.handoffTraceparent), et la filent dans le span d'outil, et le span 404 d'outil inconnu que ToolRegistry.routeCall émet reçoit le même traitement, ce qui compte précisément parce que le 404 est souvent le moment où l'agent a perdu de vue quels outils existent. Une subtilité à connaître : sans contextFactory, attachToServer() installe un proxy de garde qui lève à tout accès de propriété ; le nouveau helper readMcpTraceContext mène sa lecture unique dans un try/catch et avale la levée, donc activer le tracing seul ne vous force jamais à écrire un factory. L'absence de contexte se lit comme « racine fraîche », jamais comme une erreur. Il y a une limite documentée : le framework ne tenant aucun Context OTel à l'exécution, les appels aval auto-instrumentés dans le handler (Prisma, clients HTTP, Redis) apparaissent comme frères du span MCP, pas enfants. Le parent entrant est honoré ; le span d'outil ne devient pas le contexte actif pour tout ce qui est en aval.
Un span, trois dialectes
Chaque span d'outil porte désormais, aux côtés des attributs natifs mcp.*, openinference.span.kind = "TOOL", gen_ai.operation.name = "tools/call" et tool.name, le span 404 inclus. Le même span est donc portable sur Datadog, Arize et Phoenix sans qu'aucun ait à comprendre mcp.* ; filtrez sur openinference.span.kind = TOOL n'importe où. Ce qui n'est pas émis pèse autant que ce qui l'est : aucun attribut de jeton ou de coût à la frontière de l'outil, car ceux-ci vivent sur le span LLM côté agent, et la 5.1.0 refuse de les inventer à la couche outil. La sémantique de statut suit le pipeline : UNSET pour les échecs côté IA (erreurs de validation, actions inconnues, outils inconnus ; le span 404 est explicitement UNSET avec message, pas ERROR), OK en succès et ERROR seulement si un handler lève une défaillance système. Un LLM qui appelle le mauvais outil ne déclenche pas votre équipe de garde.
Si vous exécutez déjà un tracer OpenTelemetry
Toute l'adoption tient en deux fragments. D'abord, enveloppez le tracer pour que l'argument de contexte optionnel atterrisse au bon endroit :
import { trace, type SpanOptions, type Context } from '@opentelemetry/api';
const otel = trace.getTracer('mcpfusion');
registry.attachToServer(server, {
contextFactory: createContext,
tracing: {
startSpan: (name, options, context) =>
otel.startSpan(name, options as SpanOptions, context?.raw as Context | undefined),
},
});
Puis, dans le context factory par requête, extrayez le contexte W3C et rangez-le sous la clé conventionnelle :
const ctx = {
...baseContext,
mcpTraceContext: extractW3CContext(request.headers),
};
À partir de là, chaque span d'outil, routage 404 inclus, se suspend à la trace de l'appelant, et tout backend qui parle les conventions GenAI/OpenInference le voit. Le bloc de vérification du release le soutient : build du core à zéro erreur TypeScript, les 16 packages satellites propres, la suite du core verte à 272 fichiers / 5 263 tests et zéro échec, et @mcpfusion/swarm à 158/158. Le nouveau Tracing.test.ts épingule la parité d'émission double entre le span d'outil et le span 404, les allers-retours W3C, le rejet des entrées malformées, la tolérance du proxy de garde et la propagation du contexte parent sur les deux chemins.
