<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Opentelemetry on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/opentelemetry/</link><description>Recent content in Opentelemetry on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Wed, 20 May 2026 06:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/opentelemetry/index.xml" rel="self" type="application/rss+xml"/><item><title>MCP por dentro y su observabilidad profunda: el LSP de los agentes IA y cómo verlo todo con OpenTelemetry</title><link>https://blog.lo0.es/posts/mcp-observability-otel/</link><pubDate>Wed, 20 May 2026 06:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/mcp-observability-otel/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>&lt;a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)&lt;/a> es el estándar que Anthropic publicó a finales de 2024 y que se ha convertido en 2026 en &lt;strong>el protocolo dominante para conectar agentes IA con herramientas y datos externos&lt;/strong>. Su valor —el motivo por el que toda la industria lo ha adoptado en menos de 18 meses— es que &lt;strong>resuelve un problema combinatorio&lt;/strong>: antes de MCP, integrar M apps IA con N herramientas requería M×N integraciones ad-hoc; con MCP, M + N. Es el mismo movimiento que hizo el &lt;a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol&lt;/a> en 2016 para los editores de código. La arquitectura es tres roles bien definidos —&lt;strong>Host&lt;/strong> (la app IA), &lt;strong>Cliente&lt;/strong> (la conexión, uno por servidor) y &lt;strong>Servidor&lt;/strong> (la pieza que expone capacidades)—; las primitivas son seis —tres del lado servidor (Tools, Resources, Prompts), tres del lado cliente (Sampling, Roots, Elicitation)—; el protocolo es JSON-RPC sobre dos transportes —stdio para procesos locales, Streamable HTTP para remoto—. El reto operacional aparece cuando hay 10-20 servers MCP corriendo simultáneamente, cada uno con varias tools, conectados a un agente que encadena llamadas multistep: &lt;strong>observar qué pasa, dónde fallan las cosas, cuánto cuesta cada tool, qué tenant invoca qué&lt;/strong> se vuelve crítico. La respuesta del ecosistema en 2026: las nuevas &lt;strong>OpenTelemetry GenAI semantic conventions for MCP&lt;/strong> (ya estables), trace context propagation vía &lt;code>params._meta&lt;/code> (porque JSON-RPC no lo trae nativo), FastMCP con instrumentación OTel built-in, MCP Gateways como capa centralizada (Traefik Hub, MintMCP, OpenObserve), y MCP Inspector para debugging interactivo. Este artículo recorre la arquitectura desde fuera hacia dentro, sitúa cada concepto en su lugar exacto, y baja al detalle de la observabilidad: trazas, métricas RED, casos de uso reales y trampas.&lt;/p>
&lt;blockquote>
&lt;p>Este es el &lt;strong>tercer post de la serie post-tracing&lt;/strong>. Posts previos: &lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals&lt;/a> y &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails&lt;/a>. Aquí bajamos al protocolo que conecta agentes con herramientas, y cómo verlo en producción.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-analogía-maestra-en-tres-versiones">La analogía maestra (en tres versiones)&lt;/h2>
&lt;p>MCP es un protocolo de comunicación. Como cualquier protocolo, se entiende mejor con la analogía adecuada. Voy a darte tres porque cada una ilumina una faceta distinta y la combinación te deja entendiéndolo mejor que cualquier definición técnica.&lt;/p>
&lt;h3 id="versión-1--el-usb-c-de-las-apps-ia-la-oficial">Versión 1 — El USB-C de las apps IA (la oficial)&lt;/h3>
&lt;p>Es la analogía que Anthropic adoptó al presentarlo. Antes de USB-C, cada dispositivo electrónico tenía su propio conector. Tu móvil llevaba microUSB o Lightning, tu portátil un puerto propietario para alimentación, tus auriculares un jack 3.5mm, tu disco externo USB-A en una punta y mini-USB en la otra. Resultado: tres cajas llenas de cables específicos que se perdían, ninguno servía para dos cosas, comprar un dispositivo nuevo significaba comprar accesorios nuevos.&lt;/p>
&lt;p>USB-C cambió eso. &lt;strong>Un único conector físico que muchos protocolos atraviesan&lt;/strong>: datos (USB 3, USB 4, Thunderbolt), vídeo (DisplayPort), alimentación (Power Delivery), audio. Conectas cualquier cosa a cualquier cosa y funciona; los protocolos negocian arriba.&lt;/p>
&lt;p>MCP juega el mismo rol para apps IA. Antes de MCP, &lt;strong>cada aplicación que quería integrar herramientas con un LLM&lt;/strong> —Claude Desktop, Cursor, Continue, custom agents propios— &lt;strong>inventaba su propia forma de hacerlo&lt;/strong>. Cada vendor de tools tenía que escribir N integraciones distintas, una por app. Resultado: fragmentación masiva, mucho código duplicado, integraciones que se rompían cuando una app cambiaba su API interna.&lt;/p>
&lt;p>Con MCP, el conector es uno: cualquier app que hable MCP puede usar cualquier herramienta MCP. Igual que tu USB-C habla a impresoras, monitores y discos sin que la impresora &amp;ldquo;sepa&amp;rdquo; que el cable está conectado a un Mac o a un Linux.&lt;/p>
&lt;h3 id="versión-2--el-lsp-de-los-editores-de-código-la-más-técnicamente-precisa">Versión 2 — El LSP de los editores de código (la más técnicamente precisa)&lt;/h3>
&lt;p>Esta es mi preferida porque la analogía es &lt;strong>estructuralmente idéntica&lt;/strong>, no solo metafórica.&lt;/p>
&lt;p>Hasta 2016, si querías que tu editor de código soportara un lenguaje nuevo —Rust, Go, TypeScript— alguien tenía que escribir un plugin específico para tu editor concreto. VSCode tenía su plugin de Rust, IntelliJ otro distinto, Vim otro, Emacs otro. Cada feature decente (go-to-definition, autocompletado, refactoring) era una implementación duplicada N veces. &lt;strong>M editores × N lenguajes = M·N integraciones&lt;/strong>.&lt;/p>
&lt;p>Microsoft propuso en 2016 el &lt;strong>Language Server Protocol (LSP)&lt;/strong>: cada lenguaje implementa &lt;strong>un único&lt;/strong> &amp;ldquo;language server&amp;rdquo; (un proceso que entiende ese lenguaje); cada editor implementa &lt;strong>un único&lt;/strong> cliente LSP; cuando trabajas con código Rust en VSCode, VSCode lanza rust-analyzer como subproceso y le habla LSP por stdio. Cualquier editor LSP + cualquier servidor LSP = funciona. &lt;strong>M + N&lt;/strong>.&lt;/p>
&lt;p>MCP es &lt;strong>literalmente&lt;/strong> este patrón, trasladado de &amp;ldquo;editor + language server&amp;rdquo; a &amp;ldquo;app IA + tool provider&amp;rdquo;. Y comparte hasta el detalle técnico: ambos pasan &lt;strong>JSON-RPC sobre stdio&lt;/strong> (entre otros transportes). Cuando Anthropic diseñó MCP, miraron a LSP. Quien venga del mundo de editores e IDEs encontrará MCP familiar.&lt;/p>
&lt;h3 id="versión-3--el-driver-del-sistema-operativo-la-operativa">Versión 3 — El driver del sistema operativo (la operativa)&lt;/h3>
&lt;p>Por último, una analogía que ayuda a entender &lt;strong>lo que hace&lt;/strong> un MCP server concreto.&lt;/p>
&lt;p>Un sistema operativo no sabe directamente cómo hablar con tu impresora HP LaserJet específica. Lo que sabe es &lt;strong>una interfaz genérica&lt;/strong>: &amp;ldquo;imprimir documento&amp;rdquo;, &amp;ldquo;consultar estado&amp;rdquo;, &amp;ldquo;cancelar tarea&amp;rdquo;. El driver de impresora es la pieza que traduce esa interfaz genérica a los comandos propietarios de tu impresora específica.&lt;/p>
&lt;p>Un MCP server hace exactamente lo mismo:&lt;/p>
&lt;ul>
&lt;li>Tu agente IA sabe &lt;strong>una interfaz genérica&lt;/strong>: invocar una tool con un schema definido, leer un resource por URI, pedir un prompt template por nombre.&lt;/li>
&lt;li>El &lt;strong>MCP server&lt;/strong> es el driver: traduce esas operaciones genéricas a las API concretas del sistema underlying —tu base de datos PostgreSQL, tu filesystem, tu API GitHub, tu Stripe—.&lt;/li>
&lt;/ul>
&lt;p>Esto deja al agente IA libre de saber cómo se autentica con GitHub, qué SQL exacto usa PostgreSQL, qué endpoints tiene Stripe. Habla MCP; el server se encarga de los detalles.&lt;/p>
&lt;p>Con las tres analogías combinadas: &lt;strong>MCP es la capa entre el LLM y el mundo, un USB-C estándar implementado como LSP en JSON-RPC, con cada server actuando de driver para un sistema underlying concreto&lt;/strong>.&lt;/p>
&lt;h2 id="qué-problema-concreto-resuelve-mcp">Qué problema concreto resuelve MCP&lt;/h2>
&lt;p>Antes de bajar a la arquitectura, conviene fijar &lt;strong>el problema específico&lt;/strong> que MCP resuelve, porque sin eso muchas decisiones de diseño parecen arbitrarias.&lt;/p>
&lt;p>El problema es &lt;strong>el coste cuadrático de las integraciones&lt;/strong>.&lt;/p>
&lt;p>Imagina que tienes M aplicaciones que usan LLMs (Claude Desktop, Cursor, Continue, ChatGPT Desktop, tu propio agente custom, &amp;hellip;) y N herramientas externas que esos LLMs podrían usar (filesystem, GitHub, Slack, PostgreSQL, Jira, Notion, &amp;hellip;). Sin un estándar:&lt;/p>
&lt;ul>
&lt;li>Cada par (aplicación, herramienta) requiere &lt;strong>una integración específica&lt;/strong>.&lt;/li>
&lt;li>Cada vez que la aplicación cambia su API interna, hay que actualizar N integraciones.&lt;/li>
&lt;li>Cada vez que la herramienta cambia su API, hay que actualizar M.&lt;/li>
&lt;li>Para que tu herramienta nueva sea adoptada, tienes que escribir M integraciones.&lt;/li>
&lt;li>Para que tu aplicación nueva soporte el ecosistema, tienes que escribir N.&lt;/li>
&lt;/ul>
&lt;p>Resultado real en 2023-2024: &lt;strong>fragmentación masiva&lt;/strong>. Function calling de OpenAI no era compatible con tool use de Anthropic; cada framework (LangChain, LlamaIndex, dspy) tenía su propio wrapper; los plugins de Claude Desktop no funcionaban en Cursor; etc.&lt;/p>
&lt;p>MCP rompe la cuadratura. &lt;strong>Cada aplicación implementa el protocolo una vez&lt;/strong>; &lt;strong>cada herramienta implementa el protocolo una vez&lt;/strong>; cualquier par funciona. M + N.&lt;/p>
&lt;p>Es exactamente lo que pasó con USB-C, con LSP, con SQL (antes había APIs propietarias por base de datos), con POSIX (antes había APIs propietarias por sistema operativo). El patrón se repite porque resuelve siempre el mismo tipo de problema.&lt;/p>
&lt;h2 id="la-arquitectura-tres-roles-situados-con-claridad">La arquitectura: tres roles, situados con claridad&lt;/h2>
&lt;p>Vamos a fijar dónde vive cada cosa, porque mezclar los roles es la fuente número uno de confusión en MCP.&lt;/p>
&lt;div class="diagram" style="max-width:720px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 720 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Arquitectura MCP: Host, Cliente, Servidor">
&lt;style>.title{font:600 13px sans-serif;fill:#222}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#555}.box{stroke:#444;stroke-width:1.4}.host{fill:#ffe9d6}.llm{fill:#ffd6d6}.client{fill:#d6eaff}.server{fill:#d9f5d6}.sys{fill:#eee;stroke-dasharray:4 2}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#mh)}.bidi{stroke:#888;stroke-width:1.2;fill:none}&lt;/style>
&lt;defs>&lt;marker id="mh" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="360" y="20" text-anchor="middle" class="title">Arquitectura MCP: dónde vive cada pieza&lt;/text>
&lt;rect x="30" y="40" width="280" height="280" rx="8" class="box host"/>
&lt;text x="170" y="60" text-anchor="middle" class="lbl">HOST&lt;/text>
&lt;text x="170" y="76" text-anchor="middle" class="sm">app IA: Claude Desktop, Cursor, agente propio&lt;/text>
&lt;rect x="55" y="95" width="230" height="50" rx="6" class="box llm"/>
&lt;text x="170" y="116" text-anchor="middle" class="lbl">LLM (motor de razonamiento)&lt;/text>
&lt;text x="170" y="132" text-anchor="middle" class="sm">decide qué tools llamar, qué resources leer&lt;/text>
&lt;rect x="55" y="160" width="100" height="36" rx="6" class="box client"/>
&lt;text x="105" y="183" text-anchor="middle" class="lbl">Cliente 1&lt;/text>
&lt;rect x="160" y="160" width="120" height="36" rx="6" class="box client"/>
&lt;text x="220" y="183" text-anchor="middle" class="lbl">Cliente 2&lt;/text>
&lt;rect x="55" y="210" width="100" height="36" rx="6" class="box client"/>
&lt;text x="105" y="233" text-anchor="middle" class="lbl">Cliente 3&lt;/text>
&lt;rect x="160" y="210" width="120" height="36" rx="6" class="box client"/>
&lt;text x="220" y="233" text-anchor="middle" class="lbl">Cliente N&lt;/text>
&lt;text x="170" y="275" text-anchor="middle" class="sm">un cliente MCP por cada servidor conectado&lt;/text>
&lt;text x="170" y="295" text-anchor="middle" class="sm">cada cliente es una conexión 1:1&lt;/text>
&lt;rect x="380" y="60" width="200" height="70" rx="6" class="box server"/>
&lt;text x="480" y="82" text-anchor="middle" class="lbl">Server: filesystem-mcp&lt;/text>
&lt;text x="480" y="100" text-anchor="middle" class="sm">stdio (proceso local)&lt;/text>
&lt;text x="480" y="116" text-anchor="middle" class="sm">tools: read, write, list, search&lt;/text>
&lt;rect x="380" y="140" width="200" height="70" rx="6" class="box server"/>
&lt;text x="480" y="162" text-anchor="middle" class="lbl">Server: github-mcp&lt;/text>
&lt;text x="480" y="180" text-anchor="middle" class="sm">Streamable HTTP (remoto)&lt;/text>
&lt;text x="480" y="196" text-anchor="middle" class="sm">tools: create_issue, get_pr, ...&lt;/text>
&lt;rect x="380" y="220" width="200" height="70" rx="6" class="box server"/>
&lt;text x="480" y="242" text-anchor="middle" class="lbl">Server: postgres-mcp&lt;/text>
&lt;text x="480" y="260" text-anchor="middle" class="sm">stdio (proceso local)&lt;/text>
&lt;text x="480" y="276" text-anchor="middle" class="sm">tools: query, schema; resources: tablas&lt;/text>
&lt;rect x="610" y="60" width="80" height="70" rx="6" class="box sys"/>
&lt;text x="650" y="92" text-anchor="middle" class="sm">FS local&lt;/text>
&lt;text x="650" y="108" text-anchor="middle" class="sm">↕&lt;/text>
&lt;rect x="610" y="140" width="80" height="70" rx="6" class="box sys"/>
&lt;text x="650" y="172" text-anchor="middle" class="sm">GitHub API&lt;/text>
&lt;text x="650" y="188" text-anchor="middle" class="sm">↕&lt;/text>
&lt;rect x="610" y="220" width="80" height="70" rx="6" class="box sys"/>
&lt;text x="650" y="252" text-anchor="middle" class="sm">PostgreSQL&lt;/text>
&lt;text x="650" y="268" text-anchor="middle" class="sm">↕&lt;/text>
&lt;path class="bidi" d="M155,178 L380,95"/>
&lt;path class="bidi" d="M280,178 L380,175"/>
&lt;path class="bidi" d="M155,228 L380,255"/>
&lt;path class="bidi" d="M580,95 L610,95"/>
&lt;path class="bidi" d="M580,175 L610,175"/>
&lt;path class="bidi" d="M580,255 L610,255"/>
&lt;text x="170" y="340" text-anchor="middle" class="sm">los clientes dentro del host hablan MCP a los servers; los servers traducen al sistema&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Tres roles. Vamos a fijar qué hace cada uno y dónde vive físicamente.&lt;/p>
&lt;h3 id="host-la-aplicación-ia">Host: la aplicación IA&lt;/h3>
&lt;p>El &lt;strong>Host&lt;/strong> es la aplicación que el usuario abre. Claude Desktop, Cursor, Continue, ChatGPT Desktop, un agente custom que tu equipo construye, una extensión de VSCode. Lo que el usuario percibe como &amp;ldquo;el producto&amp;rdquo;.&lt;/p>
&lt;p>El Host es el responsable de:&lt;/p>
&lt;ul>
&lt;li>Decidir &lt;strong>qué servidores MCP&lt;/strong> conectar (configurados por el usuario en un archivo o vía UI).&lt;/li>
&lt;li>Lanzar o conectar con cada servidor MCP.&lt;/li>
&lt;li>Crear &lt;strong>un Cliente MCP por servidor&lt;/strong> (es 1:1, no comparten).&lt;/li>
&lt;li>Embeber el &lt;strong>LLM&lt;/strong> (o llamarlo vía API) que toma las decisiones de qué herramientas usar.&lt;/li>
&lt;li>Mediar la &lt;strong>autorización&lt;/strong> del usuario para acciones sensibles (mostrarle al humano &amp;ldquo;el agente quiere ejecutar X tool, ¿permites?&amp;rdquo;).&lt;/li>
&lt;/ul>
&lt;p>Importante: &lt;strong>el LLM vive dentro del Host&lt;/strong>, no en los servidores. Los servidores son tontos; ejecutan operaciones cuando se les pide. El razonamiento (&amp;quot;¿debería llamar a esta tool ahora?&amp;quot;) vive en el LLM del host.&lt;/p>
&lt;h3 id="cliente-la-conexión-una-por-servidor">Cliente: la conexión, una por servidor&lt;/h3>
&lt;p>Un &lt;strong>Cliente MCP&lt;/strong> es una &lt;strong>conexión específica&lt;/strong> entre el Host y un Servidor. Si tu Host tiene 5 servidores MCP configurados, tiene &lt;strong>5 clientes&lt;/strong>, no uno compartido. Cada cliente:&lt;/p>
&lt;ul>
&lt;li>Mantiene su socket o stdio pipe con el servidor.&lt;/li>
&lt;li>Negocia capacidades en el handshake inicial (qué versión del protocolo, qué primitivas soportan ambos).&lt;/li>
&lt;li>Serializa requests JSON-RPC al servidor y deserializa respuestas.&lt;/li>
&lt;li>Es el punto donde &lt;strong>el Host invoca operaciones&lt;/strong> del servidor.&lt;/li>
&lt;/ul>
&lt;p>La separación 1:1 cliente-servidor es importante porque permite que cada server tenga su propio estado de sesión, sus permisos específicos y su contexto autenticado independiente. No hay multiplexación en el cliente.&lt;/p>
&lt;h3 id="servidor-la-pieza-que-expone-capacidades">Servidor: la pieza que expone capacidades&lt;/h3>
&lt;p>El &lt;strong>Servidor MCP&lt;/strong> es la pieza que implementa el lado tool-provider del protocolo. Recibe JSON-RPC del cliente, lo procesa, ejecuta la acción contra el sistema underlying y devuelve respuesta.&lt;/p>
&lt;p>Hay dos sabores físicamente:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Servidor local&lt;/strong>: arranca como subproceso del Host, comunica por stdio. Su ciclo de vida es el del Host (cuando cierras Claude Desktop, los servidores locales mueren). Modelo típico: tu Host lanza &lt;code>node filesystem-mcp-server.js&lt;/code> como hijo.&lt;/li>
&lt;li>&lt;strong>Servidor remoto&lt;/strong>: corre como servicio independiente, accesible por HTTP. Multi-tenant, autenticado, escalable. Modelo típico: una empresa publica &lt;code>https://mcp.acme.com/v1&lt;/code> y muchos hosts se conectan.&lt;/li>
&lt;/ul>
&lt;p>Esta diferencia tiene consecuencias enormes en observabilidad (volveremos en breve).&lt;/p>
&lt;h3 id="resumen-del-lugar-de-cada-cosa">Resumen del lugar de cada cosa&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Componente&lt;/th>
&lt;th>Vive en&lt;/th>
&lt;th>Hay cuántos&lt;/th>
&lt;th>Habla qué con quién&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Host&lt;/td>
&lt;td>Máquina del usuario&lt;/td>
&lt;td>1 (la app abierta)&lt;/td>
&lt;td>UI con usuario; lanza clientes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLM&lt;/td>
&lt;td>Embebido en Host (o cloud API)&lt;/td>
&lt;td>1 (el principal)&lt;/td>
&lt;td>Razona; pide tools&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cliente&lt;/td>
&lt;td>Host&lt;/td>
&lt;td>1 por servidor&lt;/td>
&lt;td>JSON-RPC con su servidor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Servidor local&lt;/td>
&lt;td>Subproceso del Host&lt;/td>
&lt;td>1 por integración local&lt;/td>
&lt;td>stdio con su cliente&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Servidor remoto&lt;/td>
&lt;td>Servicio externo&lt;/td>
&lt;td>1 por servicio&lt;/td>
&lt;td>HTTP/SSE con sus clientes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sistema underlying&lt;/td>
&lt;td>Externo&lt;/td>
&lt;td>Depende&lt;/td>
&lt;td>API/DB/FS, no MCP&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Si te confundes en discusión, vuelve a esta tabla. La fuente número uno de errores en MCP es decir &amp;ldquo;el servidor&amp;rdquo; cuando se quiere decir &amp;ldquo;el host&amp;rdquo;.&lt;/p>
&lt;h2 id="las-dos-capas-del-protocolo">Las dos capas del protocolo&lt;/h2>
&lt;p>MCP separa &lt;strong>data layer&lt;/strong> y &lt;strong>transport layer&lt;/strong>. Esta separación es la que permite que el protocolo funcione por stdio local y por HTTP remoto &lt;strong>sin cambiar nada&lt;/strong> en las primitivas.&lt;/p>
&lt;h3 id="data-layer-json-rpc-con-extensiones-mcp">Data Layer: JSON-RPC con extensiones MCP&lt;/h3>
&lt;p>La capa de datos define el &lt;strong>vocabulario de los mensajes&lt;/strong>. Es &lt;strong>JSON-RPC 2.0&lt;/strong>. Cada mensaje es un JSON con &lt;code>jsonrpc: &amp;quot;2.0&amp;quot;&lt;/code>, un &lt;code>method&lt;/code> (eg &lt;code>tools/call&lt;/code>, &lt;code>resources/read&lt;/code>), &lt;code>params&lt;/code>, e &lt;code>id&lt;/code> para correlar request con response.&lt;/p>
&lt;p>Encima de JSON-RPC, MCP añade:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Lifecycle&lt;/strong>: el handshake inicial (&lt;code>initialize&lt;/code>, &lt;code>initialized&lt;/code>) que negocia capacidades.&lt;/li>
&lt;li>&lt;strong>Las primitivas&lt;/strong> (siguiente sección): &lt;code>tools/*&lt;/code>, &lt;code>resources/*&lt;/code>, &lt;code>prompts/*&lt;/code>, &lt;code>sampling/*&lt;/code>, etc.&lt;/li>
&lt;li>&lt;strong>Notifications&lt;/strong>: mensajes sin respuesta (eg &lt;code>notifications/cancelled&lt;/code> para abortar una tool en curso).&lt;/li>
&lt;li>&lt;strong>Meta-information&lt;/strong>: el campo &lt;code>params._meta&lt;/code> por convención lleva metadata transversal (trace context, request IDs).&lt;/li>
&lt;/ul>
&lt;h3 id="transport-layer-cómo-se-mueven-los-mensajes">Transport Layer: cómo se mueven los mensajes&lt;/h3>
&lt;p>La capa de transporte define &lt;strong>cómo viajan&lt;/strong> los mensajes JSON-RPC. Dos transportes oficiales:&lt;/p>
&lt;p>&lt;strong>stdio&lt;/strong>: el cliente lanza el servidor como subproceso y se comunican por sus stdin/stdout/stderr con JSON-RPC. Un mensaje por línea, separados por newline. Sin red, sin handshake TLS, sin auth (la confianza se hereda del propio sistema operativo: si lanzas el subproceso, le confías). Latencia mínima (~100 μs round-trip), ancho de banda máximo (memcpy, no socket).&lt;/p>
&lt;p>Caso de uso: &lt;strong>servidores locales&lt;/strong> que viven en la misma máquina que el host. La mayoría de servidores MCP que ves en directorios públicos son stdio.&lt;/p>
&lt;p>&lt;strong>Streamable HTTP&lt;/strong>: el cliente envía POST a un endpoint HTTP del servidor; el servidor responde con JSON, opcionalmente abre un stream Server-Sent Events para enviar notificaciones asíncronas o respuestas largas. Auth por bearer token, API key o headers custom.&lt;/p>
&lt;p>Introducido en la spec de &lt;strong>noviembre 2025&lt;/strong>, sustituye al transporte SSE puro de versiones anteriores que tenía limitaciones de bidireccionalidad. Caso de uso: &lt;strong>servidores remotos&lt;/strong> que sirven a muchos clientes simultáneos, con autenticación y multi-tenancy.&lt;/p>
&lt;p>Importante: las &lt;strong>primitivas son las mismas&lt;/strong> en ambos transportes. Un &lt;code>tools/call&lt;/code> es idéntico en stdio y en HTTP. El transport es accidental, no fundamental.&lt;/p>
&lt;h2 id="las-seis-primitivas-situadas-en-la-arquitectura">Las seis primitivas: situadas en la arquitectura&lt;/h2>
&lt;p>Aquí está la chicha. Hay seis primitivas en MCP. Suelen confundirse porque varias parecen hacer cosas similares. La clasificación clave: &lt;strong>tres viven del lado servidor&lt;/strong> (server expone, cliente consume) y &lt;strong>tres del lado cliente&lt;/strong> (cliente expone, servidor consume).&lt;/p>
&lt;h3 id="server-side-lo-que-el-servidor-le-da-al-host">Server-side: lo que el servidor le da al host&lt;/h3>
&lt;p>&lt;strong>Tools&lt;/strong> son &lt;strong>acciones&lt;/strong> que el servidor expone. Cada tool tiene un schema (parámetros tipados, descripción) y una implementación. Cuando el LLM del host decide invocar una tool, el cliente envía &lt;code>tools/call&lt;/code> al servidor, este la ejecuta y devuelve resultado.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: el server &lt;code>github-mcp&lt;/code> expone &lt;code>create_issue(repo, title, body)&lt;/code>. El LLM del host decide &amp;ldquo;voy a crear un issue&amp;rdquo;, llama esta tool, github-mcp habla a la API de GitHub, devuelve el issue ID al LLM.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el servidor las expone, el LLM las consume&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Resources&lt;/strong> son &lt;strong>datos contextuales&lt;/strong> que el servidor expone, direccionables por URI. No son acciones; son lecturas de contenido. Un resource tiene URI (&lt;code>file:///path/to/doc.md&lt;/code>, &lt;code>postgres://table/users&lt;/code>), metadata y un endpoint para leer contenido.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: el server &lt;code>filesystem-mcp&lt;/code> expone como resources los archivos de los directorios autorizados. El LLM pide &lt;code>resources/read&lt;/code> con URI &lt;code>file:///docs/api.md&lt;/code> y obtiene el texto.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el servidor las expone, el host las lee (y opcionalmente las pasa al LLM como contexto)&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>Diferencia clave Tools vs Resources: &lt;strong>Tools son verbos&lt;/strong> (ejecutan, modifican estado, tienen side effects); &lt;strong>Resources son sustantivos&lt;/strong> (existen, se leen, son idempotentes). Si tienes algo que es &amp;ldquo;buscar texto en archivos&amp;rdquo; → probablemente Tool (acción). Si es &amp;ldquo;este archivo concreto&amp;rdquo; → Resource. La distinción importa para auditoría y permisos: tools requieren más control.&lt;/p>
&lt;p>&lt;strong>Prompts&lt;/strong> son &lt;strong>plantillas de prompt parametrizadas&lt;/strong> que el servidor expone. El usuario o el host puede invocarlas para inyectar un patrón conversacional al modelo.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: un server &lt;code>code-review-mcp&lt;/code> expone un prompt &lt;code>review_diff(diff_text, style=&amp;quot;strict&amp;quot;)&lt;/code> que devuelve un prompt completo bien escrito para pedirle al LLM que revise código.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el servidor las expone, el usuario o el host las invoca, el LLM las recibe como input&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>Los prompts son la primitiva menos usada de las tres; muchos servers ni los implementan. Pero permiten que un equipo publique buenos prompts como librería reutilizable, separados del agente.&lt;/p>
&lt;h3 id="client-side-lo-que-el-host-le-da-al-servidor">Client-side: lo que el host le da al servidor&lt;/h3>
&lt;p>Aquí es donde MCP se diferencia de protocolos como HTTP REST: &lt;strong>el servidor también puede pedir cosas al host&lt;/strong>, no es solo una vía. Tres primitivas viajan en esa dirección.&lt;/p>
&lt;p>&lt;strong>Sampling&lt;/strong>: el servidor pide al host que ejecute una generación con su LLM. Es decir, &lt;strong>el servidor toma prestado el LLM del host&lt;/strong> para razonar.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: el server &lt;code>search-mcp&lt;/code> recibe una query del agente, busca en su corpus, encuentra 50 resultados y necesita resumirlos antes de devolver. En vez de tener su propio LLM, manda un &lt;code>sampling/createMessage&lt;/code> al cliente; el host pasa esto a su LLM, ejecuta la generación con permisos del usuario, devuelve el resumen al servidor.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el servidor lo pide, el host (con su LLM y la autorización del usuario) lo cumple&lt;/strong>.&lt;/li>
&lt;li>Por qué importa: el usuario controla qué modelo se usa, qué coste se paga, qué permisos aplican. El servidor no necesita su propia API key de OpenAI.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Roots&lt;/strong>: el host le dice al servidor &lt;strong>dónde mirar&lt;/strong>. Roots son URIs (directorios, repositorios, namespaces) que el host autoriza al servidor a explorar.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: tu Claude Desktop arranca &lt;code>filesystem-mcp&lt;/code> con roots &lt;code>[file:///Users/yo/proyectos]&lt;/code>. El servidor sabe que solo debe operar dentro de esa carpeta, no en &lt;code>/etc/passwd&lt;/code>.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el host las declara en el handshake, el servidor las respeta&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Elicitation&lt;/strong>: el servidor pide al host &lt;strong>información adicional al usuario humano&lt;/strong> vía UI estructurada.&lt;/p>
&lt;ul>
&lt;li>Ejemplo: el server &lt;code>stripe-mcp&lt;/code> está a punto de procesar un refund de 5000€. Antes de ejecutar, manda &lt;code>elicitation/createMessage&lt;/code> al cliente; el host muestra al usuario &amp;ldquo;Confirma este refund de €5000&amp;rdquo; con un botón; cuando el usuario confirma, devuelve OK al server, que entonces procede.&lt;/li>
&lt;li>Lugar arquitectónico: &lt;strong>el servidor pide, el host muestra al usuario, el usuario decide, la respuesta vuelve al servidor&lt;/strong>.&lt;/li>
&lt;li>Es la primitiva clave para human-in-the-loop en acciones sensibles.&lt;/li>
&lt;/ul>
&lt;h3 id="visualización-del-flujo-de-las-seis-primitivas">Visualización del flujo de las seis primitivas&lt;/h3>
&lt;pre tabindex="0">&lt;code> HOST SERVIDOR
│ │
Server-side ─────┼─────────────────────────────────────┤
│ │
tools/list ──────┼────── pregunta qué tools hay ──────▶│
│◀────── devuelve lista ──────────────│
│ │
tools/call ──────┼────── ejecuta esta tool ───────────▶│
│◀────── resultado ──────────────────│
│ │
resources/read ──┼────── lee este URI ────────────────▶│
│◀────── contenido ─────────────────│
│ │
prompts/get ─────┼────── dame este prompt ────────────▶│
│◀────── prompt compilado ──────────│
│ │
Client-side ─────┼─────────────────────────────────────┤
│ │
sampling ────────│◀────── necesito una generación ─────│
│── usa mi LLM ───┐ │
│── devuelve ─────▼──────────────────▶│
│ │
roots ───────────┼─── declarados en handshake ────────▶│
│ │
elicitation ─────│◀────── pregunta al usuario X ───────│
│── muestra UI ──┐ │
│── confirma ────▼───────────────────▶│
&lt;/code>&lt;/pre>&lt;h2 id="el-json-rpc-en-acción-un-ejemplo-concreto">El JSON-RPC en acción: un ejemplo concreto&lt;/h2>
&lt;p>Para que la teoría se materialice, una conversación MCP real entre cliente y servidor &lt;code>filesystem-mcp&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-jsonc" data-lang="jsonc">// 1. Handshake inicial (cliente → servidor)
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 1, &amp;#34;method&amp;#34;: &amp;#34;initialize&amp;#34;,
&amp;#34;params&amp;#34;: {
&amp;#34;protocolVersion&amp;#34;: &amp;#34;2026-03-01&amp;#34;,
&amp;#34;capabilities&amp;#34;: {
&amp;#34;sampling&amp;#34;: {}, // este cliente soporta sampling
&amp;#34;roots&amp;#34;: { &amp;#34;listChanged&amp;#34;: true }
},
&amp;#34;clientInfo&amp;#34;: { &amp;#34;name&amp;#34;: &amp;#34;ClaudeDesktop&amp;#34;, &amp;#34;version&amp;#34;: &amp;#34;1.2.0&amp;#34; }
}
}
// 2. Server responde con sus capabilities
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 1, &amp;#34;result&amp;#34;: {
&amp;#34;protocolVersion&amp;#34;: &amp;#34;2026-03-01&amp;#34;,
&amp;#34;capabilities&amp;#34;: {
&amp;#34;tools&amp;#34;: { &amp;#34;listChanged&amp;#34;: true },
&amp;#34;resources&amp;#34;: { &amp;#34;subscribe&amp;#34;: true, &amp;#34;listChanged&amp;#34;: true },
&amp;#34;prompts&amp;#34;: {}
},
&amp;#34;serverInfo&amp;#34;: { &amp;#34;name&amp;#34;: &amp;#34;filesystem-mcp&amp;#34;, &amp;#34;version&amp;#34;: &amp;#34;0.5.2&amp;#34; }
}
}
// 3. Cliente pide listado de tools
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 2, &amp;#34;method&amp;#34;: &amp;#34;tools/list&amp;#34;
}
// 4. Server devuelve sus tools con schema
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 2, &amp;#34;result&amp;#34;: {
&amp;#34;tools&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;read_file&amp;#34;,
&amp;#34;description&amp;#34;: &amp;#34;Read a file from the filesystem&amp;#34;,
&amp;#34;inputSchema&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;object&amp;#34;,
&amp;#34;properties&amp;#34;: { &amp;#34;path&amp;#34;: { &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34; } },
&amp;#34;required&amp;#34;: [&amp;#34;path&amp;#34;]
}
},
{ &amp;#34;name&amp;#34;: &amp;#34;write_file&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;...&amp;#34;, &amp;#34;inputSchema&amp;#34;: {} },
{ &amp;#34;name&amp;#34;: &amp;#34;list_directory&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;...&amp;#34;, &amp;#34;inputSchema&amp;#34;: {} }
]
}
}
// 5. El LLM decide llamar read_file; cliente envía tools/call
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 3, &amp;#34;method&amp;#34;: &amp;#34;tools/call&amp;#34;,
&amp;#34;params&amp;#34;: {
&amp;#34;name&amp;#34;: &amp;#34;read_file&amp;#34;,
&amp;#34;arguments&amp;#34;: { &amp;#34;path&amp;#34;: &amp;#34;/Users/yo/proyectos/notas.md&amp;#34; },
&amp;#34;_meta&amp;#34;: { // ← extensión donde irá trace context
&amp;#34;traceparent&amp;#34;: &amp;#34;00-abc123...-def456-01&amp;#34;
}
}
}
// 6. Server devuelve contenido del archivo
{
&amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;, &amp;#34;id&amp;#34;: 3, &amp;#34;result&amp;#34;: {
&amp;#34;content&amp;#34;: [
{ &amp;#34;type&amp;#34;: &amp;#34;text&amp;#34;, &amp;#34;text&amp;#34;: &amp;#34;# Mis notas\n\n...&amp;#34; }
]
}
}
&lt;/code>&lt;/pre>&lt;p>Lo importante a notar: &lt;strong>&lt;code>params._meta&lt;/code>&lt;/strong>. Ese es el bag donde MCP convencionalmente pasa metadata transversal, incluyendo trace context. Volveremos en breve.&lt;/p>
&lt;h2 id="el-problema-de-observabilidad-por-qué-tracing-tradicional-no-basta">El problema de observabilidad: por qué tracing tradicional no basta&lt;/h2>
&lt;p>Hasta aquí la teoría. Bajemos al problema operacional: en un cluster de producción 2026, un agente típico tiene &lt;strong>5-15 servidores MCP&lt;/strong> conectados simultáneamente, cada uno con &lt;strong>5-20 tools&lt;/strong>, y cada conversación con el agente puede generar &lt;strong>decenas de llamadas a tools&lt;/strong> encadenadas. Sin observabilidad, depurar incidencias es imposible.&lt;/p>
&lt;p>Por qué el tracing genérico (Hubble, OTel sin convenciones MCP) no es suficiente:&lt;/p>
&lt;p>&lt;strong>Stdio no se ve en la red&lt;/strong>. Los servidores locales hablan por pipes del SO. Tu Hubble o tu Datadog APM no ven nada; no hay paquetes que capturar. AgentSight (visto en el &lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">post anterior de la serie eBPF&lt;/a>) con &lt;code>stdiocap&lt;/code> lo captura pero da el JSON-RPC en crudo, sin contexto semántico (qué tool es, qué resource, qué prompt).&lt;/p>
&lt;p>&lt;strong>HTTP genérico tampoco entiende MCP&lt;/strong>. Si trazas el HTTP a un servidor MCP remoto sin convenciones MCP, ves un POST a &lt;code>/v1&lt;/code> con un body JSON-RPC opaco. Pierdes &amp;ldquo;qué tool se invocó&amp;rdquo;, &amp;ldquo;qué argumentos&amp;rdquo;, &amp;ldquo;fue elicitation o sampling&amp;rdquo;. Métricas RED por endpoint no te sirven; necesitas RED &lt;strong>por tool&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>JSON-RPC no propaga trace context nativo&lt;/strong>. A diferencia de HTTP (W3C traceparent header) o gRPC (metadata), JSON-RPC no tiene un campo estándar para trace context. Si no propagas, cada llamada al servidor empieza un trace nuevo desconectado del trace del agente.&lt;/p>
&lt;p>&lt;strong>Multistep multi-server es muy difícil de seguir&lt;/strong>. Una sola conversación del usuario puede traducirse en: 1) call a github-mcp &lt;code>get_pr&lt;/code>; 2) call a filesystem-mcp &lt;code>read_file&lt;/code> para varios archivos; 3) llamada al LLM principal con todo el contexto; 4) call a postgres-mcp &lt;code>query&lt;/code>; 5) call a slack-mcp &lt;code>send_message&lt;/code>. Sin trace context propagado, son cinco traces inconexos. Con propagación, es un árbol.&lt;/p>
&lt;p>La solución: &lt;strong>OpenTelemetry semantic conventions for MCP&lt;/strong>, ya &lt;strong>estables&lt;/strong> en 2026.&lt;/p>
&lt;h2 id="opentelemetry-semantic-conventions-for-mcp">OpenTelemetry semantic conventions for MCP&lt;/h2>
&lt;p>Las &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/">GenAI MCP semantic conventions&lt;/a> son el set de atributos estandarizados para spans y métricas relacionados con MCP. Se publicaron como parte del subgrupo GenAI de OpenTelemetry SIG y son la primera parte de las semantic conventions GenAI que llegó a estable.&lt;/p>
&lt;h3 id="por-qué-semantic-conventions-específicas">Por qué semantic conventions específicas&lt;/h3>
&lt;p>Antes de tenerlas, los equipos instrumentaban MCP con las &lt;strong>RPC semantic conventions&lt;/strong> genéricas (las que usarías para gRPC o XML-RPC). Funcionaba a medias. Las conventions MCP-específicas añaden:&lt;/p>
&lt;ul>
&lt;li>Atributos para identificar &lt;strong>qué primitiva&lt;/strong> se ejecutó (&lt;code>mcp.method.name = &amp;quot;tools/call&amp;quot;&lt;/code>).&lt;/li>
&lt;li>Atributos para identificar &lt;strong>qué tool/resource/prompt&lt;/strong> concreto se tocó (&lt;code>mcp.tool.name&lt;/code>, &lt;code>mcp.resource.uri&lt;/code>, &lt;code>mcp.prompt.name&lt;/code>).&lt;/li>
&lt;li>Atributos para el flujo bidireccional (sampling/elicitation requests del servidor al cliente).&lt;/li>
&lt;li>Atributos para el handshake (&lt;code>mcp.protocol.version&lt;/code>, &lt;code>mcp.client.name&lt;/code>, &lt;code>mcp.server.name&lt;/code>).&lt;/li>
&lt;li>Métricas RED estandarizadas por tool (&lt;code>mcp.tool.call.duration&lt;/code>, &lt;code>mcp.tool.call.errors&lt;/code>).&lt;/li>
&lt;/ul>
&lt;h3 id="los-atributos-canónicos">Los atributos canónicos&lt;/h3>
&lt;p>Los atributos que cualquier instrumentación MCP-aware debería emitir:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Atributo&lt;/th>
&lt;th>Significado&lt;/th>
&lt;th>Ejemplo&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>mcp.method.name&lt;/code>&lt;/td>
&lt;td>Método JSON-RPC&lt;/td>
&lt;td>&lt;code>&amp;quot;tools/call&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.tool.name&lt;/code>&lt;/td>
&lt;td>Nombre de la tool&lt;/td>
&lt;td>&lt;code>&amp;quot;read_file&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.resource.uri&lt;/code>&lt;/td>
&lt;td>URI del resource&lt;/td>
&lt;td>&lt;code>&amp;quot;file:///docs/api.md&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.prompt.name&lt;/code>&lt;/td>
&lt;td>Nombre del prompt&lt;/td>
&lt;td>&lt;code>&amp;quot;code_review&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.session.id&lt;/code>&lt;/td>
&lt;td>ID de sesión MCP&lt;/td>
&lt;td>&lt;code>&amp;quot;sess-abc123&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.protocol.version&lt;/code>&lt;/td>
&lt;td>Versión del protocolo&lt;/td>
&lt;td>&lt;code>&amp;quot;2026-03-01&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.client.name&lt;/code>&lt;/td>
&lt;td>Identidad del cliente&lt;/td>
&lt;td>&lt;code>&amp;quot;ClaudeDesktop/1.2.0&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.server.name&lt;/code>&lt;/td>
&lt;td>Identidad del servidor&lt;/td>
&lt;td>&lt;code>&amp;quot;filesystem-mcp/0.5.2&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.transport&lt;/code>&lt;/td>
&lt;td>Transporte usado&lt;/td>
&lt;td>&lt;code>&amp;quot;stdio&amp;quot;&lt;/code> o &lt;code>&amp;quot;http&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mcp.error.code&lt;/code>&lt;/td>
&lt;td>JSON-RPC error code&lt;/td>
&lt;td>&lt;code>-32602&lt;/code> (Invalid params)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gen_ai.usage.input_tokens&lt;/code>&lt;/td>
&lt;td>Tokens consumidos (si sampling)&lt;/td>
&lt;td>&lt;code>1240&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gen_ai.usage.output_tokens&lt;/code>&lt;/td>
&lt;td>Tokens generados (si sampling)&lt;/td>
&lt;td>&lt;code>512&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Los dos últimos vienen de las semantic conventions GenAI genéricas y se aplican cuando la llamada MCP involucra sampling (servidor usando el LLM del cliente).&lt;/p>
&lt;h3 id="métricas-red-por-tool">Métricas RED por tool&lt;/h3>
&lt;p>Más allá de los spans, las semantic conventions definen tres métricas core:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>mcp.tool.call.duration&lt;/code>&lt;/strong> (histograma): latencia de cada invocación.&lt;/li>
&lt;li>&lt;strong>&lt;code>mcp.tool.call.count&lt;/code>&lt;/strong> (counter): número total de invocaciones.&lt;/li>
&lt;li>&lt;strong>&lt;code>mcp.tool.call.errors&lt;/code>&lt;/strong> (counter): errores por tool.&lt;/li>
&lt;/ul>
&lt;p>Etiquetadas con &lt;code>mcp.tool.name&lt;/code>, &lt;code>mcp.server.name&lt;/code>, &lt;code>mcp.client.name&lt;/code>. Pivotables en Grafana para responder &amp;ldquo;qué tool es la más lenta&amp;rdquo;, &amp;ldquo;qué tool falla más&amp;rdquo;, &amp;ldquo;qué cliente carga más a qué server&amp;rdquo;.&lt;/p>
&lt;h2 id="trace-context-propagation-el-truco-del-params_meta">Trace context propagation: el truco del &lt;code>params._meta&lt;/code>&lt;/h2>
&lt;p>JSON-RPC no tiene cabeceras como HTTP, así que MCP no puede usar &lt;code>traceparent&lt;/code> header de W3C directamente. La solución que el ecosistema ha consensuado: &lt;strong>propagar trace context en &lt;code>params._meta&lt;/code>&lt;/strong>.&lt;/p>
&lt;p>Cuando el cliente MCP envía un &lt;code>tools/call&lt;/code>, su instrumentación OTel hace:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">opentelemetry.propagate&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">inject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">carrier&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">carrier&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># rellena con traceparent/tracestate del span activo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;read_file&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;arguments&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/notas.md&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;_meta&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">carrier&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># ← propaga trace context&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cuando el servidor recibe, hace lo simétrico:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">opentelemetry.propagate&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">extract&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_meta&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{}))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">tracer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start_as_current_span&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;tools/call&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># esta span es hija de la del cliente&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">execute_tool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Resultado: el span del servidor es &lt;strong>hijo&lt;/strong> del span del cliente en el árbol de traces. Cuando ves la trace en Tempo o Phoenix, ves toda la cadena: usuario → host → cliente → server → ejecución → respuesta → cliente → host → respuesta al usuario.&lt;/p>
&lt;p>Esto requiere que &lt;strong>ambos extremos&lt;/strong> instrumenten consistentemente. Si el server no extrae el contexto, ves spans desconectados pero al menos tienes traceability del lado cliente.&lt;/p>
&lt;h2 id="patrones-de-instrumentación">Patrones de instrumentación&lt;/h2>
&lt;p>Hay tres caminos para instrumentar MCP, en orden creciente de esfuerzo:&lt;/p>
&lt;h3 id="1-fastmcp-con-opentelemetry-built-in">1. FastMCP con OpenTelemetry built-in&lt;/h3>
&lt;p>&lt;a href="https://gofastmcp.com/">FastMCP&lt;/a> es uno de los frameworks Python más usados para construir servidores MCP. Trae &lt;strong>instrumentación OpenTelemetry built-in&lt;/strong>: cada tool, resource template, prompt operation genera spans automáticamente con las conventions MCP correctas.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastmcp&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastMCP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">opentelemetry.sdk.trace.export&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OTLPSpanExporter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">mcp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastMCP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;my-server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">otel_endpoint&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://otel-collector:4318&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@mcp.tool&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">search_docs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Search the corpus for matching documents.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># esto genera automáticamente un span con&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># mcp.tool.name=search_docs, mcp.method.name=tools/call, etc.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">run_search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cero código de instrumentación. Spans con conventions correctas. Es el patrón recomendado si arrancas un servidor MCP en Python desde cero.&lt;/p>
&lt;h3 id="2-opentelemetry-sdk-manual">2. OpenTelemetry SDK manual&lt;/h3>
&lt;p>Para servidores ya existentes o en otros lenguajes (TypeScript, Go), la opción es instrumentar manualmente con el SDK estándar OTel + emitir los atributos MCP convencionales:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">opentelemetry&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">trace&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tracer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">trace&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_tracer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">handle_tools_call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">req&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">JSONRPCRequest&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extract_trace_context&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tracer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start_as_current_span&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mcp.tools.call&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">span&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mcp.method.name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;tools/call&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mcp.tool.name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">req&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mcp.server.name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;filesystem-mcp&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">execute_tool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">req&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mcp.error.code&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">32603&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">record_exception&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Más boilerplate pero funciona con cualquier servidor existente.&lt;/p>
&lt;h3 id="3-mcp-inspector-para-debugging-interactivo">3. MCP Inspector para debugging interactivo&lt;/h3>
&lt;p>&lt;a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector&lt;/a> (oficial) es una herramienta de &lt;strong>debugging interactivo a nivel protocolo&lt;/strong>. Lanza un proxy local (puerto 6277) entre tu cliente y el servidor, y abre una UI web (puerto 6274) donde ves cada mensaje JSON-RPC ida y vuelta en tiempo real.&lt;/p>
&lt;p>No es observabilidad de producción —es desarrollo y depuración—. Pero es &lt;strong>insustituible&lt;/strong> durante el bring-up de un servidor nuevo: ves exactamente qué requests llegan, qué responses se devuelven, qué errores se producen. Ahorra horas de logging ad-hoc.&lt;/p>
&lt;h2 id="mcp-gateways-la-pieza-centralizada-para-enterprise">MCP Gateways: la pieza centralizada para enterprise&lt;/h2>
&lt;p>Cuando tu organización tiene &lt;strong>muchos agentes&lt;/strong> conectándose a &lt;strong>muchos servidores MCP&lt;/strong>, gestionar la matriz de conexiones se vuelve operacionalmente serio. La pregunta natural —&amp;quot;¿puede haber un proxy delante de todos los MCP servers que centralice auth, rate limiting, logging y observabilidad?&amp;quot;— ya tiene respuesta: &lt;strong>MCP Gateways&lt;/strong>.&lt;/p>
&lt;p>Un Gateway MCP es un proxy que:&lt;/p>
&lt;ul>
&lt;li>Acepta conexiones MCP de los hosts/agentes.&lt;/li>
&lt;li>Las enruta a los servers MCP backend correspondientes.&lt;/li>
&lt;li>Aplica &lt;strong>autenticación y autorización&lt;/strong> centralizada (qué agente puede llamar qué tool).&lt;/li>
&lt;li>Aplica &lt;strong>rate limiting&lt;/strong> por agente, por tool, por tenant.&lt;/li>
&lt;li>&lt;strong>Observa&lt;/strong>: emite métricas OTel de cada operación pasante.&lt;/li>
&lt;li>&lt;strong>Propaga identidad&lt;/strong> del agente al servidor backend (con varios modelos: token forwarding, token exchange, impersonación).&lt;/li>
&lt;/ul>
&lt;p>Las opciones que se han establecido en 2026:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://doc.traefik.io/traefik-hub/mcp-gateway/">Traefik Hub MCP Gateway&lt;/a>&lt;/strong> — del equipo de Traefik. Configuración declarativa, integración nativa con el ecosistema Kubernetes/Helm de Traefik.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.mintmcp.com/">MintMCP&lt;/a>&lt;/strong> — gateway con foco en observabilidad y multi-tenancy. SaaS y self-host.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://openobserve.ai/blog/mcp-gateway-guide/">OpenObserve MCP Gateway&lt;/a>&lt;/strong> — integrado con la plataforma de observabilidad OpenObserve.&lt;/li>
&lt;/ul>
&lt;p>Para deployments pequeños (un equipo, pocos agentes) un Gateway puede ser overkill. Para enterprise (decenas de agentes, decenas de servers, compliance regulado), es prácticamente obligatorio.&lt;/p>
&lt;h2 id="casos-de-uso-reales-de-la-observabilidad-mcp">Casos de uso reales de la observabilidad MCP&lt;/h2>
&lt;p>Vamos a aterrizar con cinco casos donde la observabilidad MCP propiamente instrumentada da valor inmediato:&lt;/p>
&lt;h3 id="1-audit-por-tool-por-tenant-por-agente">1. Audit por tool, por tenant, por agente&lt;/h3>
&lt;p>Pregunta: &amp;ldquo;¿quién ejecutó la tool &lt;code>delete_repo&lt;/code> el mes pasado?&amp;rdquo;. Sin observabilidad MCP, imposible. Con conventions OTel + propagación de identidad: query en tu backend de traces filtrando por &lt;code>mcp.tool.name=&amp;quot;delete_repo&amp;quot;&lt;/code>, agrupando por &lt;code>mcp.client.name&lt;/code> o por user_id propagado en &lt;code>_meta&lt;/code>. Compliance feliz.&lt;/p>
&lt;h3 id="2-coste-por-tool-y-por-tenant">2. Coste por tool y por tenant&lt;/h3>
&lt;p>Pregunta: &amp;ldquo;¿cuánto cuesta cada tool?&amp;rdquo;. Si las tools invocan APIs externas (Stripe, OpenAI sampling) o consumen recursos significativos (GPU para una tool de inferencia), saber su coste agregado importa. Con &lt;code>mcp.tool.call.duration&lt;/code> + &lt;code>gen_ai.usage.*&lt;/code> agregadas por tool y tenant, se construyen dashboards de cost accountability sin instrumentar nada extra.&lt;/p>
&lt;h3 id="3-debug-de-cadenas-multistep-que-fallan">3. Debug de cadenas multistep que fallan&lt;/h3>
&lt;p>Pregunta: &amp;ldquo;el agente falló al completar esta tarea, ¿dónde fue?&amp;rdquo;. El trace propagado conecta: span del usuario → span del LLM con su CoT → spans de cada tool invocada → span del LLM final. Si la cadena se rompió en la tercera tool, en Tempo se ve el span rojo con el mensaje de error específico. Reproducir el fallo es trivial.&lt;/p>
&lt;h3 id="4-latencia-y-degradación-de-tools">4. Latencia y degradación de tools&lt;/h3>
&lt;p>Pregunta: &amp;ldquo;¿qué tool está degradando?&amp;rdquo;. Métricas RED por tool en Grafana muestran latencia p95/p99 a lo largo del tiempo. Cuando una tool empieza a subir de 200ms a 800ms (porque el servicio underlying se está colapsando), lo ves antes de que los usuarios se quejen.&lt;/p>
&lt;h3 id="5-detección-de-loops-y-anomalías-agentic">5. Detección de loops y anomalías agentic&lt;/h3>
&lt;p>Pregunta: &amp;ldquo;¿algún agente está atascado en bucle?&amp;rdquo;. Si un agente llama &lt;code>tools/call read_file&lt;/code> 80 veces en 30 segundos para el mismo path, claramente algo está mal. Alerta sobre &lt;code>mcp.tool.call.count&lt;/code> agrupado por (session_id, tool_name) detecta esto. Combinado con detección de loops a nivel de razonamiento, cierra el círculo.&lt;/p>
&lt;h2 id="trampas-operativas">Trampas operativas&lt;/h2>
&lt;h3 id="falta-de-identity-propagation">Falta de identity propagation&lt;/h3>
&lt;p>Tu Gateway autentica al agente, pero pasa requests al backend sin propagar identidad. Resultado: los logs del backend dicen &amp;ldquo;service-account&amp;rdquo; en todo, imposible auditar quién invocó qué. &lt;strong>Elige una estrategia de propagación temprano&lt;/strong>: token forwarding (sencillo, expone tokens al backend), token exchange (más seguro), o impersonación con logging cruzado.&lt;/p>
&lt;h3 id="servidores-stdio-que-no-aparecen-en-tu-apm">Servidores stdio que no aparecen en tu APM&lt;/h3>
&lt;p>Es la trampa nº1 del campo. Tu agente Cursor usa filesystem-mcp como stdio; no ves nada en Datadog porque no hay tráfico de red. Solución: instrumentar el servidor stdio con OTel SDK que exporta por OTLP a tu collector (vía gRPC o HTTP, OTel collector puede recibir aunque el server hable stdio con su cliente). O usar AgentSight &lt;code>stdiocap&lt;/code> para capturar el JSON-RPC en crudo y procesarlo offline.&lt;/p>
&lt;h3 id="múltiples-versiones-de-protocolo-en-producción">Múltiples versiones de protocolo en producción&lt;/h3>
&lt;p>Diferentes clientes usan distintas versiones de MCP simultáneamente. Tu metrics dashboard mezcla peras y manzanas. Etiqueta SIEMPRE con &lt;code>mcp.protocol.version&lt;/code> y filtra/agrupa por ella.&lt;/p>
&lt;h3 id="_meta-perdido-al-pasar-por-proxy">&lt;code>_meta&lt;/code> perdido al pasar por proxy&lt;/h3>
&lt;p>Tu Gateway acepta el request del cliente, lo reescribe para el backend, y se olvida de copiar &lt;code>params._meta&lt;/code>. Resultado: trace roto en el Gateway, dos traces inconexos. Asegúrate de que tu Gateway &lt;strong>preserva o re-inyecta&lt;/strong> trace context en cada hop.&lt;/p>
&lt;h3 id="volumen-de-trazas-con-servers-chatty">Volumen de trazas con servers chatty&lt;/h3>
&lt;p>Algunos servers MCP emiten muchas pequeñas operaciones (filesystem listings, partial reads). Sin sampling, llenan tu backend de trazas inútiles. Aplica &lt;strong>tail-based sampling&lt;/strong> que conserve sesiones completas o solo conserve traces con errores/latencia alta.&lt;/p>
&lt;h3 id="cardinalidad-en-métricas">Cardinalidad en métricas&lt;/h3>
&lt;p>&lt;code>mcp.tool.call.duration&lt;/code> con &lt;code>mcp.session.id&lt;/code> como label explota la cardinalidad. &lt;strong>No incluyas IDs únicos por sesión en labels&lt;/strong>; mantén la cardinalidad bajo control con labels que toman pocos valores discretos (tool name, server name, client name, error code).&lt;/p>
&lt;h3 id="confundir-spans-del-cliente-y-del-servidor">Confundir spans del cliente y del servidor&lt;/h3>
&lt;p>Cuando ves el árbol, distingue: el cliente ve &lt;strong>latencia total desde su perspectiva&lt;/strong> (incluye network); el servidor ve &lt;strong>solo su trabajo&lt;/strong>. Si miras solo el span del servidor para depurar latencia percibida por el usuario, te pierdes el RTT. Usa ambos.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto">Lo que no hemos cubierto&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>MCP transport WebSocket experimental&lt;/strong>: alternativa a Streamable HTTP, aún no estándar.&lt;/li>
&lt;li>&lt;strong>Servidores MCP en cloud-native deployments con sidecars&lt;/strong>: patrón emergente de desplegar MCP servers como sidecars de pods.&lt;/li>
&lt;li>&lt;strong>MCP federation&lt;/strong>: composición de varios servers como uno solo (similar a GraphQL federation).&lt;/li>
&lt;li>&lt;strong>eBPF + MCP&lt;/strong>: cómo &lt;code>stdiocap&lt;/code> de AgentSight y los hooks de Cilium se complementan con la instrumentación nativa.&lt;/li>
&lt;li>&lt;strong>MCP testing y contract tests&lt;/strong>: cómo validar que tu servidor cumple la spec.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>Especificación y conceptos:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://modelcontextprotocol.io/">Model Context Protocol — sitio oficial&lt;/a> — entrada canónica.&lt;/li>
&lt;li>&lt;a href="https://modelcontextprotocol.io/docs/learn/architecture">MCP architecture overview&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://modelcontextprotocol.info/docs/concepts/transports/">Transports — MCP docs&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector (GitHub)&lt;/a> — debugging interactivo.&lt;/li>
&lt;/ul>
&lt;p>OpenTelemetry GenAI MCP:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/">Semantic conventions for Model Context Protocol — OpenTelemetry&lt;/a> — referencia normativa.&lt;/li>
&lt;li>&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/269">Adding OpenTelemetry Trace Support to MCP (Discussion #269)&lt;/a> — historia de la propuesta.&lt;/li>
&lt;li>&lt;a href="https://oneuptime.com/blog/post/2026-03-26-how-to-instrument-mcp-servers-with-opentelemetry/view">How to Instrument MCP Servers with OpenTelemetry (OneUptime)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.elastic.co/observability-labs/blog/mcp-tracing-opentelemetry-elastic-apm">How to trace MCP server tool calls with OpenTelemetry and Elastic APM&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://signoz.io/blog/mcp-observability-with-otel/">MCP Observability with OpenTelemetry (SigNoz)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://developers.redhat.com/articles/2026/04/06/distributed-tracing-agentic-workflows-opentelemetry">Distributed tracing for agentic workflows (Red Hat Developer)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.mintmcp.com/blog/opentelemetry-ai-agents">OpenTelemetry for AI Agents in MCP Workflows (MintMCP)&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Frameworks y gateways:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://gofastmcp.com/servers/telemetry">FastMCP OpenTelemetry&lt;/a> — instrumentación built-in.&lt;/li>
&lt;li>&lt;a href="https://doc.traefik.io/traefik-hub/mcp-gateway/">Traefik Hub MCP Gateway&lt;/a> — gateway de Traefik.&lt;/li>
&lt;li>&lt;a href="https://www.mintmcp.com/">MintMCP&lt;/a> — gateway con foco en observabilidad.&lt;/li>
&lt;li>&lt;a href="https://openobserve.ai/blog/mcp-gateway-guide/">OpenObserve MCP Gateway guide&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://dev.to/composiodev/what-is-an-mcp-gateway-and-why-do-enterprise-ai-teams-need-one-in-2026-1lie">What is an MCP Gateway (DEV Community)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/traceloop/opentelemetry-mcp-server">OpenTelemetry MCP Server (Traceloop)&lt;/a> — el patrón inverso: usar MCP para que agentes consulten traces OTel.&lt;/li>
&lt;/ul>
&lt;p>Cross-references:&lt;/p>
&lt;ul>
&lt;li>Post anterior: &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/agentsight-tracing-llm/">AgentSight y el nuevo tracing de LLMs&lt;/a> — donde se introdujo &lt;code>stdiocap&lt;/code> para capturar stdio de servidores MCP locales.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/evals-llm-la-capa-despues-de-tracing/">Evals: la capa después del tracing&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>AgentSight y el nuevo tracing de LLMs: zero-instrumentation con eBPF frente a Langfuse, LangSmith, Phoenix y compañía</title><link>https://blog.lo0.es/posts/agentsight-tracing-llm/</link><pubDate>Tue, 19 May 2026 18:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/agentsight-tracing-llm/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Observar un agente de LLM en producción en 2026 se divide en dos enfoques con filosofías opuestas. El &lt;strong>instrumentado&lt;/strong>, dominante hasta 2025, vive en herramientas como &lt;a href="https://langfuse.com/">Langfuse&lt;/a>, &lt;a href="https://www.langchain.com/langsmith">LangSmith&lt;/a>, &lt;a href="https://phoenix.arize.com/">Arize Phoenix&lt;/a>, &lt;a href="https://www.helicone.ai/">Helicone&lt;/a>, &lt;a href="https://www.traceloop.com/openllmetry">OpenLLMetry/Traceloop&lt;/a> o &lt;a href="https://pydantic.dev/logfire">Pydantic Logfire&lt;/a>: instalas un SDK, decoras tus llamadas, emites spans con la convención &lt;strong>OpenTelemetry GenAI&lt;/strong> (&lt;code>gen_ai.request.model&lt;/code>, &lt;code>gen_ai.usage.input_tokens&lt;/code>, etc.) y los exportas a un backend. Profundidad altísima cuando controlas el código; cero visibilidad cuando el agente es un binario opaco que ejecutas sin instrumentar. El &lt;strong>zero-instrumentation&lt;/strong>, que &lt;a href="https://github.com/eunomia-bpf/agentsight">AgentSight&lt;/a> ha popularizado en la segunda mitad de 2025, gira la perspectiva 180º: pone hooks &lt;strong>eBPF en las uprobes de las bibliotecas SSL/TLS&lt;/strong> y captura el plaintext de cada petición HTTPS antes del cifrado, &lt;strong>sin tocar el código de la app&lt;/strong>, con menos del &lt;strong>3% de overhead&lt;/strong> y la garantía de ser &lt;strong>tamper-proof&lt;/strong> (el agente no puede falsificar lo que se ve en el kernel). Combinado con captura BPF de stdio para servidores MCP locales, AgentSight te da observabilidad completa de cualquier agente —incluyendo binarios cerrados como Claude Code, Gemini CLI o Cursor— en un cluster Kubernetes. Las dos familias no son enemigas: la pila de referencia 2026 combina ambas (instrumented para apps propias con LangChain, eBPF para binarios opacos y compliance de tamper-proof) sobre &lt;strong>OpenTelemetry GenAI semantic conventions&lt;/strong> como vocabulario común que el ecosistema está estabilizando este año.&lt;/p>
&lt;blockquote>
&lt;p>Este es el &lt;strong>cuarto y último post de la serie sobre eBPF&lt;/strong>. Parte 1: &lt;a href="https://blog.lo0.es/posts/ebpf-cilium-tcp-ip-bypass/">eBPF de cero a Cilium&lt;/a>. Parte 2: &lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon: seguridad de runtime&lt;/a>. Parte 3: &lt;a href="https://blog.lo0.es/posts/hubble-observabilidad-ebpf/">Hubble: observabilidad de red&lt;/a>. Aquí cerramos el círculo con la dimensión &lt;strong>semántica&lt;/strong> —qué hace un agente IA, no solo qué red abre o qué syscalls emite—.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-analogía-apm-tradicional-vs-sniffer-de-red">La analogía: APM tradicional vs sniffer de red&lt;/h2>
&lt;p>Quien haya operado aplicaciones empresariales conoce las dos tribus del monitoring. La tribu &lt;strong>APM&lt;/strong> (New Relic, AppDynamics, Datadog APM): instalas un agente o un SDK en cada aplicación, marcas spans, recoges traces con profundidad enorme dentro de cada proceso —líneas de código, queries SQL, métodos de Java—. La tribu &lt;strong>wire-level&lt;/strong> (sniffers de red, herramientas tipo SolarWinds NPM, NetFlow): no toca la aplicación; observa el cable, ve protocolos, latencias, retransmisiones, identifica problemas que la app no sabe que tiene.&lt;/p>
&lt;p>Cada una ve cosas distintas y las dos sirven. Quien ha vivido un incidente serio donde APM decía &amp;ldquo;todo verde&amp;rdquo; mientras los usuarios sufrían sabe que el wire-level habría detectado el problema (un middlebox saturado, un MTU mal configurado, un timeout de TCP). Quien ha intentado debuggear un memory leak con sniffers sabe que sin APM era imposible.&lt;/p>
&lt;p>La observabilidad de agentes LLM en 2026 está exactamente en este punto. El &lt;strong>APM-style&lt;/strong> lleva un par de años montado: Langfuse, LangSmith, Phoenix, OpenLLMetry. Profundidad enorme, requiere instrumentar la app. El &lt;strong>wire-level con eBPF&lt;/strong> acaba de llegar: AgentSight es el primer proyecto que lo lleva a productivo. Profundidad menor en el interior del agente, pero ve cualquier agente sin tocar nada y es &lt;strong>tamper-proof&lt;/strong>. Los dos sirven. La industria está en plena coexistencia.&lt;/p>
&lt;h2 id="por-qué-observar-agentes-llm-es-distinto">Por qué observar agentes LLM es distinto&lt;/h2>
&lt;p>Antes de entrar en herramientas, vale la pena detenerse en qué hace específicos a los agentes LLM como sujetos de observabilidad:&lt;/p>
&lt;p>&lt;strong>No-determinismo.&lt;/strong> El mismo input puede producir outputs distintos. Reproducir un incidente requiere capturar &lt;strong>exactamente&lt;/strong> la conversación, el modelo, los parámetros y, idealmente, la seed. Una métrica agregada &amp;ldquo;latencia p95&amp;rdquo; se queda corta; lo que necesitas es replay de la traza individual.&lt;/p>
&lt;p>&lt;strong>Cadena de invocaciones externas.&lt;/strong> Un agente típico llama LLM → herramientas (tool calling) → MCP servers → otras APIs → vuelta a LLM. Una sesión de chat puede generar &lt;strong>decenas de llamadas encadenadas&lt;/strong> que hay que correlar por trace_id para entender la decisión.&lt;/p>
&lt;p>&lt;strong>Coste lineal en tokens.&lt;/strong> Cada llamada se paga en tokens. Sin trazar input/output tokens por petición, no puedes asignar coste a tenant ni equipo, ni detectar bucles que se comen tu presupuesto en una hora.&lt;/p>
&lt;p>&lt;strong>Riesgo semántico.&lt;/strong> Prompt injection (un user input que contiene instrucciones para manipular al modelo), jailbreaks, leakage de secretos via tool calls. Es un tipo de problema que no aparece en aplicaciones tradicionales y la observabilidad debe verlo.&lt;/p>
&lt;p>&lt;strong>Binarios opacos.&lt;/strong> En 2026, muchos equipos despliegan &lt;strong>agentes de terceros&lt;/strong> —Claude Code, Cursor agent, Aider, Gemini CLI, Codex CLI— como herramientas internas. No son aplicaciones propias; son binarios cerrados que llaman a la API del vendor. Instrumentarlos es imposible. Observarlos requiere otra cosa.&lt;/p>
&lt;p>&lt;strong>Multi-agent y orquestación.&lt;/strong> Cada vez más arquitecturas tienen agentes que invocan a otros agentes (planner → executor → critic). La observabilidad debe entender la topología, no solo el span individual.&lt;/p>
&lt;p>Con estos cinco puntos en mente, las herramientas que vamos a ver se diferencian principalmente en &lt;strong>qué partes&lt;/strong> del problema cubren bien y &lt;strong>qué partes&lt;/strong> dejan ciegas.&lt;/p>
&lt;h2 id="el-enfoque-instrumentado-cómo-funciona">El enfoque instrumentado: cómo funciona&lt;/h2>
&lt;p>El modelo es directo y conocido:&lt;/p>
&lt;ol>
&lt;li>Tu código llama al LLM o a herramientas usando una librería oficial: &lt;code>openai&lt;/code>, &lt;code>anthropic&lt;/code>, &lt;code>langchain&lt;/code>, &lt;code>llama_index&lt;/code>, &lt;code>dspy&lt;/code>.&lt;/li>
&lt;li>Instalas un SDK del tracer (Langfuse, LangSmith, OpenLLMetry, Logfire) que &lt;strong>wrappea&lt;/strong> o &lt;strong>monkey-patcha&lt;/strong> esas librerías.&lt;/li>
&lt;li>Cada llamada emite un &lt;strong>span OpenTelemetry&lt;/strong> con atributos estandarizados: modelo usado, tokens input/output, latencia, parámetros, mensajes, herramienta invocada, resultado.&lt;/li>
&lt;li>Los spans se exportan vía OTLP a un backend que los muestra como un árbol de traces.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Ejemplo típico con OpenLLMetry + cualquier SDK&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">traceloop.sdk&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Traceloop&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Traceloop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">app_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;my-agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">api_endpoint&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://otel-collector:4318&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAI&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># este call emite automáticamente un span con&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># gen_ai.request.model, gen_ai.usage.input_tokens, etc.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">resp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chat&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">completions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-4.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lo que ves después: un dashboard con cada conversación como un trace, cada llamada como un span, los prompts y completions completos (si optas in), el coste calculado, latencias por span, errores marcados.&lt;/p>
&lt;h3 id="opentelemetry-genai-semantic-conventions-el-vocabulario-común">OpenTelemetry GenAI semantic conventions: el vocabulario común&lt;/h3>
&lt;p>La fragmentación del campo se está mitigando con &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">&lt;strong>OpenTelemetry GenAI Semantic Conventions&lt;/strong>&lt;/a>. Es el esfuerzo de la CNCF para que &lt;strong>todas&lt;/strong> las herramientas emitan spans con los mismos nombres de atributos:&lt;/p>
&lt;ul>
&lt;li>&lt;code>gen_ai.system&lt;/code> — el proveedor (openai, anthropic, vertex_ai, etc.).&lt;/li>
&lt;li>&lt;code>gen_ai.request.model&lt;/code> — modelo solicitado (&lt;code>gpt-4.1&lt;/code>, &lt;code>claude-3-5-sonnet&lt;/code>).&lt;/li>
&lt;li>&lt;code>gen_ai.response.model&lt;/code> — modelo realmente usado (a veces difiere, eg fallbacks).&lt;/li>
&lt;li>&lt;code>gen_ai.usage.input_tokens&lt;/code> y &lt;code>gen_ai.usage.output_tokens&lt;/code> — contadores.&lt;/li>
&lt;li>&lt;code>gen_ai.request.temperature&lt;/code>, &lt;code>gen_ai.request.top_p&lt;/code>, etc. — parámetros.&lt;/li>
&lt;li>&lt;code>gen_ai.response.finish_reasons&lt;/code> — por qué terminó (stop, length, content_filter).&lt;/li>
&lt;li>&lt;code>gen_ai.operation.name&lt;/code> — el tipo de operación (chat, embedding, completion).&lt;/li>
&lt;/ul>
&lt;p>A principios de 2026, los &lt;strong>client spans&lt;/strong> salieron de experimental a estable. El resto (server spans, multi-agent events) sigue en desarrollo. El significado operacional: si tu SDK emite estos atributos, &lt;strong>cualquier backend que entienda OTel GenAI&lt;/strong> puede consumirlos. Cambiar de Langfuse a Phoenix a Helicone no implica re-instrumentar, solo cambiar el exporter.&lt;/p>
&lt;p>La SIG está activamente desarrollando &lt;strong>conventions for multi-agent systems&lt;/strong>: agent teams, tasks, actions, memory, artifact tracking. Esto es lo que falta para que las arquitecturas de agentes complejas tengan vocabulario común. En 2026 está experimental; se espera estabilización a finales de año o principios de 2027.&lt;/p>
&lt;h3 id="herramientas-instrumentadas-el-panorama-2026">Herramientas instrumentadas: el panorama 2026&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Herramienta&lt;/th>
&lt;th>Licencia&lt;/th>
&lt;th>Self-host&lt;/th>
&lt;th>Foco&lt;/th>
&lt;th>Donde brilla&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Langfuse&lt;/strong>&lt;/td>
&lt;td>MIT&lt;/td>
&lt;td>&lt;strong>Sí&lt;/strong>&lt;/td>
&lt;td>LLM observability + evals + prompt mgmt&lt;/td>
&lt;td>Mejor balance OSS, suite completa&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LangSmith&lt;/strong>&lt;/td>
&lt;td>Comercial&lt;/td>
&lt;td>No&lt;/td>
&lt;td>LangChain/LangGraph nativo&lt;/td>
&lt;td>Si usas LangChain, integración cero-config&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Arize Phoenix&lt;/strong>&lt;/td>
&lt;td>ELv2 (OSS)&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>OTel-native, RAG fuerte&lt;/td>
&lt;td>Vector DBs, retrieval, embeddings&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Helicone&lt;/strong>&lt;/td>
&lt;td>Comercial + OSS lite&lt;/td>
&lt;td>Sí (lite)&lt;/td>
&lt;td>Proxy simple&lt;/td>
&lt;td>Setup minutos, OpenAI-only&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>OpenLLMetry / Traceloop&lt;/strong>&lt;/td>
&lt;td>Apache 2.0&lt;/td>
&lt;td>Sí&lt;/td>
&lt;td>SDK OTel para LLMs&lt;/td>
&lt;td>Vendor-neutral, exporta a cualquier OTel backend&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Pydantic Logfire&lt;/strong>&lt;/td>
&lt;td>Comercial&lt;/td>
&lt;td>No&lt;/td>
&lt;td>App + LLM unificado&lt;/td>
&lt;td>Si usas Pydantic AI, integración nativa&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Weights &amp;amp; Biases Weave&lt;/strong>&lt;/td>
&lt;td>Comercial&lt;/td>
&lt;td>Limitado&lt;/td>
&lt;td>Experimentación + producción&lt;/td>
&lt;td>Si ya usas W&amp;amp;B para training&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Laminar / Braintrust&lt;/strong>&lt;/td>
&lt;td>Comercial&lt;/td>
&lt;td>No / Sí&lt;/td>
&lt;td>Evals + tracing&lt;/td>
&lt;td>Más recientes, foco en evaluación&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="deep-dive-langfuse">Deep dive: Langfuse&lt;/h3>
&lt;p>Merece detenerse en &lt;a href="https://langfuse.com/">Langfuse&lt;/a> porque es, en 2026, &lt;strong>la elección por defecto entre las opciones open-source&lt;/strong> y la que más equipos han adoptado este año. Es proyecto de &lt;a href="https://github.com/langfuse/langfuse">YC W23&lt;/a>, licencia &lt;strong>MIT&lt;/strong>, y lleva un ritmo de release sostenido con cambios arquitectónicos serios entre versiones.&lt;/p>
&lt;p>&lt;strong>Cuatro pilares declarados&lt;/strong>: observability (tracing), evaluations, prompt management, playground/datasets. Cada uno por separado tiene productos comerciales completos detrás; Langfuse los integra en una sola plataforma con un solo backend.&lt;/p>
&lt;h4 id="el-sdk-v4-otel-native-no-un-sustituto">El SDK v4: OTEL-native, no un sustituto&lt;/h4>
&lt;p>El gran cambio operacional reciente es el &lt;strong>SDK v4&lt;/strong>, una capa fina sobre el cliente oficial de OpenTelemetry. La elección es deliberada: en lugar de mantener un cliente propio que se atrase respecto a las primitives OTel, Langfuse usa el SDK estándar y &lt;strong>enriquece&lt;/strong> los spans con atributos y helpers específicos para LLM. La consecuencia: cualquier código que ya esté instrumentado con OpenTelemetry vainilla (&lt;code>@opentelemetry/sdk-node&lt;/code>, &lt;code>opentelemetry-sdk&lt;/code> en Python) &lt;strong>puede exportar a Langfuse sin cambios mayores&lt;/strong>, y al revés, si mañana quieres migrar de Langfuse a otro backend OTel, los spans son portables.&lt;/p>
&lt;p>En Python el decorador idiomático es &lt;code>@observe&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langfuse&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">observe&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_client&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">langfuse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_client&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@observe&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">buscar_documentos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># cualquier llamada interna también se traza&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">vector_store&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">similarity_search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">as_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;generation&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">llamar_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># marcada como &amp;#34;generation&amp;#34; para que aparezca con metadata LLM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">openai_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chat&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">completions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@observe&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">pipeline_rag&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pregunta&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">docs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buscar_documentos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pregunta&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">llamar_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">build_prompt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pregunta&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">docs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El árbol de llamadas se captura automáticamente: la traza muestra &lt;code>pipeline_rag&lt;/code> como root span, con &lt;code>buscar_documentos&lt;/code> y &lt;code>llamar_llm&lt;/code> como hijos, anidados. Sin escribir un solo &lt;code>with tracer.start_as_current_span(...)&lt;/code> a mano.&lt;/p>
&lt;p>En TypeScript el equivalente es modular: instalas &lt;code>@langfuse/tracing&lt;/code>, &lt;code>@langfuse/otel&lt;/code> y &lt;code>@opentelemetry/sdk-node&lt;/code>, y puedes usar decoradores TS, context managers o spans manuales —los tres modelos interoperan—. La consecuencia: bibliotecas terceras que emiten spans OTel (&lt;code>openai&lt;/code>, &lt;code>@anthropic-ai/sdk&lt;/code>, instrumentaciones de Vercel AI SDK) se ven en Langfuse sin trabajo adicional.&lt;/p>
&lt;h4 id="arquitectura-self-host-pensada-para-producción-seria">Arquitectura self-host: pensada para producción seria&lt;/h4>
&lt;p>La arquitectura del backend Langfuse tiene &lt;strong>dos decisiones explícitas&lt;/strong> que distinguen su despliegue self-host:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Persistencia primero en S3/Blob Storage&lt;/strong>. Cuando un evento de tracing entra, &lt;strong>se persiste en object storage antes de tocar la base de datos&lt;/strong>. Solo cuando el procesado posterior confirma OK se inserta en Postgres/Clickhouse. Si la DB cae temporalmente, los eventos &lt;strong>no se pierden&lt;/strong>; quedan en S3 esperando reproceso. Para producción donde perder traces de un incidente equivale a perder evidencia, esto es load-bearing.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Migraciones largas como background jobs&lt;/strong>. Los upgrades de schema que en otras plataformas implican ventana de downtime, en Langfuse se ejecutan en background mientras la aplicación sigue sirviendo. El downtime de upgrade se reduce drásticamente.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Los modos de despliegue soportados oficialmente:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Docker Compose&lt;/strong>: para desarrollo y POCs. Un comando, todo arriba.&lt;/li>
&lt;li>&lt;strong>VM&lt;/strong>: un único nodo, contenedores, sin orquestación. Para entornos pequeños.&lt;/li>
&lt;li>&lt;strong>Kubernetes con Helm&lt;/strong>: el modo recomendado para producción. Chart oficial mantenido. Soporta external Postgres, external Clickhouse, external S3, HPA.&lt;/li>
&lt;/ul>
&lt;p>Las dependencias externas en producción típica: &lt;strong>Postgres&lt;/strong> (metadata, prompts, configuración), &lt;strong>Clickhouse&lt;/strong> (eventos de tracing, queries de alta cardinalidad), &lt;strong>S3 o blob compatible&lt;/strong> (eventos pendientes), &lt;strong>Redis&lt;/strong> (cola entre componentes). Sí, son varias piezas; es lo que sostiene la durabilidad y la escala.&lt;/p>
&lt;h4 id="prompt-management-como-ciudadano-de-primera-clase">Prompt management como ciudadano de primera clase&lt;/h4>
&lt;p>Lo que diferencia a Langfuse de las plataformas centradas solo en tracing es que &lt;strong>los prompts viven en Langfuse&lt;/strong>, no en el repo de la aplicación o en hojas de cálculo. Cada prompt tiene:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Nombre y versión&lt;/strong> (v1, v2, v3&amp;hellip;). Cambiar el prompt no requiere redeploy de la app: la app pide el prompt al SDK, que lo cachea y refresca cuando hay versión nueva.&lt;/li>
&lt;li>&lt;strong>Variables tipadas&lt;/strong>: &lt;code>{{user_input}}&lt;/code>, &lt;code>{{context}}&lt;/code>. Render con validación.&lt;/li>
&lt;li>&lt;strong>Tags y labels&lt;/strong>: por entorno (&lt;code>production&lt;/code>, &lt;code>staging&lt;/code>), por equipo, por experimento.&lt;/li>
&lt;li>&lt;strong>Cache cliente y servidor&lt;/strong>: el SDK cachea localmente con TTL configurable, evita roundtrip a Langfuse en cada llamada.&lt;/li>
&lt;li>&lt;strong>Linkage con traces&lt;/strong>: cada trace recoge qué versión exacta de qué prompt se usó. Investigar &amp;ldquo;esta respuesta salió mal&amp;rdquo; lleva al prompt versión Y, no a &amp;ldquo;alguna versión del prompt en algún momento&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langfuse&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_client&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">langfuse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_client&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">langfuse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_prompt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;rag-system-prompt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># o por label: langfuse.get_prompt(&amp;#34;rag-system-prompt&amp;#34;, label=&amp;#34;production&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">docs_text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">user_input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">question&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#39;compiled&amp;#39; es el string final, listo para mandar al LLM&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Para equipos que iteran sobre prompts a diario, esto es lo que evita el caos de &amp;ldquo;qué versión del prompt está corriendo realmente en producción ahora mismo&amp;rdquo;.&lt;/p>
&lt;h4 id="evaluations-cuatro-modelos-de-evaluación-combinables">Evaluations: cuatro modelos de evaluación combinables&lt;/h4>
&lt;p>Langfuse cubre los cuatro patrones de evaluación de respuestas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>LLM-as-a-judge&lt;/strong>: configuras un modelo (típicamente GPT-4 o Claude) con una rúbrica y evalúa cada respuesta. Resultado: score numérico (0-1) y justificación. Aplicable a tracing automático (todas las respuestas) o batch (selección de dataset).&lt;/li>
&lt;li>&lt;strong>User feedback&lt;/strong>: la app permite al usuario marcar respuesta como buena/mala. El feedback se asocia al trace y al prompt version, lo que permite ver qué versiones tienen peor rate.&lt;/li>
&lt;li>&lt;strong>Manual labeling&lt;/strong>: una UI donde labelers humanos puntúan respuestas. Útil para datasets dorados y para evaluar el judge.&lt;/li>
&lt;li>&lt;strong>Custom evaluators vía API/SDK&lt;/strong>: evals propios (un test unitario, una métrica de negocio) reportan score vía API. Se integran con CI.&lt;/li>
&lt;/ul>
&lt;p>Combinadas, dan &lt;strong>regression testing&lt;/strong> del prompt: cambias de v3 a v4, evalúas el dataset dorado con LLM-as-judge, comparas; si v4 empeora en alguno de los segmentos, el merge falla.&lt;/p>
&lt;h4 id="integraciones">Integraciones&lt;/h4>
&lt;p>Langfuse no compite con OpenLLMetry, LangChain o LiteLLM: los &lt;strong>integra&lt;/strong>. Las que están testeadas y documentadas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenTelemetry&lt;/strong>: cualquier instrumentación OTel emite a Langfuse vía OTLP.&lt;/li>
&lt;li>&lt;strong>LangChain y LangGraph&lt;/strong>: callback nativo que captura toda la cadena.&lt;/li>
&lt;li>&lt;strong>LlamaIndex&lt;/strong>: callback nativo.&lt;/li>
&lt;li>&lt;strong>OpenAI SDK&lt;/strong> (Python y TS): wrapper que añade tracing automáticamente.&lt;/li>
&lt;li>&lt;strong>LiteLLM&lt;/strong>: integración como callback, lo que cubre 100+ proveedores via LiteLLM.&lt;/li>
&lt;li>&lt;strong>OpenLLMetry / Traceloop&lt;/strong>: emiten a Langfuse como cualquier backend OTel.&lt;/li>
&lt;li>&lt;strong>MLflow&lt;/strong>: vía exporter OTel desde MLflow a Langfuse.&lt;/li>
&lt;li>&lt;strong>Vercel AI SDK&lt;/strong>: instrumentación nativa.&lt;/li>
&lt;/ul>
&lt;p>La estrategia es clara: &lt;strong>Langfuse es backend, no SDK&lt;/strong>. Tu equipo elige cómo instrumenta; Langfuse acepta cualquier camino. La consecuencia operativa: cambiar de Langfuse a otro backend OTel mañana es viable.&lt;/p>
&lt;h4 id="cuándo-langfuse-no-es-la-respuesta">Cuándo Langfuse no es la respuesta&lt;/h4>
&lt;p>Para no presentarlo como bala de plata:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Si solo usas LangChain y no tienes recursos para self-host&lt;/strong>: LangSmith te dará integración más fluida (es el mismo equipo).&lt;/li>
&lt;li>&lt;strong>Si tu única necesidad es proxy con cost tracking sin evals&lt;/strong>: Helicone es más simple.&lt;/li>
&lt;li>&lt;strong>Si quieres una solución vendor commercial integrada&lt;/strong>: Datadog LLM Observability, New Relic AI Monitoring o Dynatrace AI son alternativas Enterprise con soporte 24/7.&lt;/li>
&lt;li>&lt;strong>Si tu carga es batch puro de inferencia masiva sin agentes&lt;/strong>: probablemente no necesitas tracing semántico; Prometheus + Grafana con métricas OTel basta.&lt;/li>
&lt;/ul>
&lt;p>Para todo lo demás —apps propias con tracing serio, multi-tenant con cuotas, equipos que iteran prompts a diario, RAG con evaluación continua—, Langfuse es la apuesta segura.&lt;/p>
&lt;p>&lt;strong>Resumen de elección rápido&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>LangChain → LangSmith&lt;/strong> (cero esfuerzo, instrumentación automática).&lt;/li>
&lt;li>&lt;strong>Aplicaciones propias multi-framework con OSS → Langfuse&lt;/strong> (MIT, self-host, completo).&lt;/li>
&lt;li>&lt;strong>RAG con vector stores → Arize Phoenix&lt;/strong> (mejor visibilidad de retrieval).&lt;/li>
&lt;li>&lt;strong>Proxy simple, presupuesto bajo → Helicone&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Vendor neutrality estricta → OpenLLMetry/Traceloop&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Pydantic AI → Logfire&lt;/strong> (mismo equipo).&lt;/li>
&lt;/ul>
&lt;h3 id="fortalezas-y-debilidades-del-modelo-instrumentado">Fortalezas y debilidades del modelo instrumentado&lt;/h3>
&lt;p>&lt;strong>Fortalezas&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Profundidad enorme&lt;/strong>: spans anidados con todo el contexto (chain steps, retrieval, embeddings, tool calls).&lt;/li>
&lt;li>&lt;strong>Vocabulario semántico&lt;/strong>: SDK conoce el dominio (LLM, vector store, agent).&lt;/li>
&lt;li>&lt;strong>Madurez&lt;/strong>: tres años de evolución, ecosistema rico, dashboards listos.&lt;/li>
&lt;li>&lt;strong>Evals integradas&lt;/strong>: las plataformas top combinan tracing con evaluación (judge LLM, datasets, regression).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Debilidades&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Requiere control del código&lt;/strong>: si no puedes instrumentar, no funciona.&lt;/li>
&lt;li>&lt;strong>Trust en la app&lt;/strong>: si la app reporta mal o tiene un bug, la traza también. No es tamper-proof.&lt;/li>
&lt;li>&lt;strong>Acoplamiento al SDK&lt;/strong>: cambios de versión de una librería pueden romper la instrumentación.&lt;/li>
&lt;li>&lt;strong>Cobertura desigual&lt;/strong>: SDKs de Python están maduros; Go, Rust, JS más jóvenes.&lt;/li>
&lt;/ul>
&lt;h2 id="el-enfoque-zero-instrumentation-agentsight">El enfoque zero-instrumentation: AgentSight&lt;/h2>
&lt;p>&lt;a href="https://github.com/eunomia-bpf/agentsight">AgentSight&lt;/a> es el proyecto del grupo &lt;code>eunomia-bpf&lt;/code> que abandera el enfoque opuesto. Su &lt;a href="https://arxiv.org/abs/2508.02736">paper en arxiv (2508.02736)&lt;/a>, presentado en el &lt;em>Workshop on Practical Adoption Challenges of ML for Systems&lt;/em>, formaliza la propuesta. La premisa es directa:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Instead of instrumenting the agent, observe it at the system boundary.&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>Y &amp;ldquo;system boundary&amp;rdquo; significa &lt;strong>el límite del kernel&lt;/strong>: el último punto antes de que un dato salga del proceso hacia la red o el filesystem. Ahí, con eBPF, se ven las cosas tal como son, sin que la aplicación pueda cooperar para esconderlas.&lt;/p>
&lt;h3 id="arquitectura-tres-planos">Arquitectura: tres planos&lt;/h3>
&lt;p>AgentSight monta tres capas:&lt;/p>
&lt;p>&lt;strong>Plano 1 — SSL/TLS uprobes&lt;/strong>. eBPF puede atar programas a funciones de &lt;strong>bibliotecas userspace&lt;/strong> (uprobes). Las funciones objetivo son las de cifrado: &lt;code>SSL_write&lt;/code>, &lt;code>SSL_read&lt;/code> de OpenSSL/BoringSSL, equivalentes en Rustls. AgentSight les pone hooks que &lt;strong>capturan los argumentos&lt;/strong>: el buffer &lt;strong>plaintext&lt;/strong> que la app pasa para que sea cifrado, justo antes de que TLS lo procese. En la recepción, hace lo simétrico: hook después de &lt;code>SSL_read&lt;/code> con el plaintext recién descifrado. Resultado: AgentSight ve el contenido completo de cualquier petición HTTPS que la app haga &lt;strong>sin necesidad de man-in-the-middle ni certificados ni descifrar tráfico&lt;/strong>. El payload es plaintext porque se capturó &lt;strong>antes&lt;/strong> de cifrarse.&lt;/p>
&lt;p>Esto funciona porque las uprobes son baratas (~100 ns por invocación) y porque las apps usan bibliotecas de TLS comunes. Las pocas apps que implementan su propio TLS (raras en producción) escapan a este hook; para esas hace falta un kprobe diferente o instrumentación manual.&lt;/p>
&lt;p>&lt;strong>Plano 2 — Kernel events&lt;/strong>. Paralelamente, AgentSight observa syscalls relevantes a través de tracepoints: &lt;code>execve&lt;/code> (qué procesos arrancan), &lt;code>connect&lt;/code>/&lt;code>accept&lt;/code> (red), &lt;code>read&lt;/code>/&lt;code>write&lt;/code> con file descriptors (filesystem y stdio), &lt;code>unlink&lt;/code>, &lt;code>clone&lt;/code>. Cualquier acción del agente que tenga efecto fuera del proceso pasa por aquí. Esto cubre, entre otros, &lt;strong>comandos shell ejecutados por el agente&lt;/strong> —si un agente Claude Code decide ejecutar &lt;code>rm -rf&lt;/code> para &amp;ldquo;limpiar el proyecto&amp;rdquo;, el &lt;code>execve&lt;/code> se ve aunque la API LLM no lo reporte—.&lt;/p>
&lt;p>&lt;strong>Plano 3 — Correlation engine&lt;/strong>. Los dos planos anteriores producen streams de eventos asíncronos. AgentSight tiene un componente en userspace que los &lt;strong>correlaciona causalmente cross-process&lt;/strong>: una petición HTTP saliente con &lt;code>bash -c rm -rf&lt;/code> puede ser correlada con la respuesta LLM previa que la sugirió, vía PIDs, tiempos y heurísticas. El paper menciona el uso opcional de &lt;strong>un LLM secundario&lt;/strong> (Anthropic Claude por ejemplo) que analiza la secuencia de eventos y produce alertas semánticas: &amp;ldquo;el agente respondió con una tool call que no estaba en la whitelist&amp;rdquo;, &amp;ldquo;la cadena de reasoning lleva 47 iteraciones sin converger&amp;rdquo;.&lt;/p>
&lt;h3 id="stdiocap-capturar-stdio-de-servidores-mcp-locales">&lt;code>stdiocap&lt;/code>: capturar stdio de servidores MCP locales&lt;/h3>
&lt;p>Una pieza específica que merece mención propia es &lt;code>stdiocap&lt;/code>, una herramienta BPF separada incluida en el repo. El &lt;strong>Model Context Protocol (MCP)&lt;/strong>, popularizado por Anthropic en 2024 y mainstream en 2025-2026, tiene dos modos de transport: HTTP/SSE (red) y &lt;strong>stdio&lt;/strong> (entre el cliente y el server que arranca como subproceso). Los servidores MCP locales —los que corren en la misma máquina y son arrancados por el cliente como hijos vía pipes— comunican por stdin/stdout/stderr con JSON-RPC.&lt;/p>
&lt;p>&lt;code>stdiocap&lt;/code> engancha &lt;code>read&lt;/code>/&lt;code>write&lt;/code>/&lt;code>dup&lt;/code> sobre los file descriptors de stdin/stdout/stderr de un proceso target y &lt;strong>registra todo el tráfico JSON-RPC&lt;/strong> entre cliente y server MCP. Es la misma idea que la captura SSL pero para stdio: observas la conversación sin que ni el cliente ni el server lo sepan. Caso de uso típico: ver qué tools del MCP server &lt;code>filesystem-mcp&lt;/code> ha invocado un agente Claude Code en la última hora, qué argumentos pasó, qué errores recibió. Imposible con instrumentación clásica (los servers MCP suelen ser binarios de terceros).&lt;/p>
&lt;h3 id="garantías-tamper-proof-kernel-safety-3-overhead">Garantías: tamper-proof, kernel safety, &amp;lt;3% overhead&lt;/h3>
&lt;p>Tres propiedades hacen a AgentSight interesante para producción:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tamper-proof&lt;/strong>: la observación ocurre en el kernel (uprobes, syscalls). Una aplicación maliciosa o comprometida no puede falsificar lo que se ve. Comparar con instrumentación: si el agente decide no emitir el span de su acción, no aparece en Langfuse. Aquí no tiene elección.&lt;/li>
&lt;li>&lt;strong>Kernel safety&lt;/strong>: eBPF verifica formalmente que los programas terminen y respeten bounds checks. No puede crashear el kernel. Igual que en el resto de la serie eBPF.&lt;/li>
&lt;li>&lt;strong>&amp;lt;3% CPU overhead&lt;/strong> medido sobre cargas reales de agentes (paper). El número compara favorablemente con instrumentación SDK que típicamente añade 5-10% en aplicaciones intensas.&lt;/li>
&lt;/ul>
&lt;h3 id="lo-que-detecta-out-of-the-box">Lo que detecta out of the box&lt;/h3>
&lt;p>El paper y la documentación destacan tres clases de detección:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Prompt injection en tiempo real&lt;/strong>: el correlation engine puede aplicar reglas o un modelo de detección sobre el plaintext capturado por las uprobes SSL. Si el prompt contiene patrones sospechosos —&amp;ldquo;ignore all previous instructions&amp;rdquo;, system prompt embebido en un user input, instrucciones para exfiltrar datos—, marca alerta.&lt;/li>
&lt;li>&lt;strong>Reasoning loops que gastan recursos&lt;/strong>: agentes que entran en bucles infinitos llamando a herramientas sin progresar. Detectables porque la cadena causal no converge a &amp;ldquo;respuesta final&amp;rdquo; y los tokens se acumulan. El correlation engine los marca.&lt;/li>
&lt;li>&lt;strong>Bottlenecks en multi-agent&lt;/strong>: cuando varios agentes coordinan, AgentSight ve la matriz de comunicaciones entre todos y puede detectar agentes que se bloquean esperando, deadlocks, fan-out excesivo.&lt;/li>
&lt;/ul>
&lt;h2 id="el-choque-y-la-coexistencia">El choque y la coexistencia&lt;/h2>
&lt;p>Las dos familias parecen competir, pero en realidad ven cosas distintas y se complementan en producción.&lt;/p>
&lt;h3 id="lo-que-solo-el-instrumentado-ve">Lo que solo el instrumentado ve&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Variables internas del agente&lt;/strong> que no salen al cable: el estado intermedio de un chain LangChain, los valores antes de pasarlos a una herramienta, el cómo se construye un prompt a partir de un template con vars internos.&lt;/li>
&lt;li>&lt;strong>Spans semánticos profundos&lt;/strong>: &lt;code>retrieval &amp;gt; embed &amp;gt; vector_search &amp;gt; rerank &amp;gt; format_context &amp;gt; prompt_template &amp;gt; llm&lt;/code>. AgentSight ve solo la llamada final al LLM; el camino para construirla es invisible.&lt;/li>
&lt;li>&lt;strong>Evaluaciones&lt;/strong>: scoring de respuestas, judge LLMs, regresión de calidad. Esto vive solo en plataformas instrumentadas.&lt;/li>
&lt;/ul>
&lt;h3 id="lo-que-solo-ebpf-ve">Lo que solo eBPF ve&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Binarios opacos&lt;/strong>: Claude Code, Cursor, Gemini CLI, agentes de terceros. No tienes el código; no puedes instrumentarlos. Solo eBPF los ve.&lt;/li>
&lt;li>&lt;strong>Acciones a nivel sistema&lt;/strong>: el agente decide ejecutar &lt;code>git push --force&lt;/code> o &lt;code>kubectl delete&lt;/code>. La acción se ve en el &lt;code>execve&lt;/code>. La instrumentación del agente puede no reportarla (especialmente si fue un comando que el agente generó como output sin pasar por una &amp;ldquo;tool&amp;rdquo; explícita).&lt;/li>
&lt;li>&lt;strong>Tamper-proof audit&lt;/strong>: para compliance regulatorio (HIPAA, SOC2, NIS2), tener observación que la app no puede burlar tiene valor formal. eBPF lo da.&lt;/li>
&lt;li>&lt;strong>MCP servers locales con stdio&lt;/strong>: invisibles para instrumentación clásica salvo que cada server emita sus propios spans (raro).&lt;/li>
&lt;/ul>
&lt;h3 id="lo-que-ambos-ven-complementariamente">Lo que ambos ven, complementariamente&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Prompts y completions&lt;/strong>: instrumentado los emite con metadata rica; eBPF los captura del cable. Cross-check perfecto para detectar discrepancias.&lt;/li>
&lt;li>&lt;strong>Llamadas a APIs externas&lt;/strong>: APM lo marca; eBPF lo confirma a nivel kernel.&lt;/li>
&lt;li>&lt;strong>Latencia&lt;/strong>: APM por span; eBPF mide RTT a nivel TCP y conectividad red.&lt;/li>
&lt;/ul>
&lt;h3 id="matriz-de-decisión">Matriz de decisión&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Caso&lt;/th>
&lt;th>Instrumentado&lt;/th>
&lt;th>eBPF (AgentSight)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>App propia con LangChain&lt;/td>
&lt;td>&lt;strong>Sí, primero&lt;/strong>&lt;/td>
&lt;td>Opcional&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>App propia multi-framework&lt;/td>
&lt;td>&lt;strong>Sí&lt;/strong>&lt;/td>
&lt;td>Opcional&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Binario de terceros (Claude Code, Cursor)&lt;/td>
&lt;td>&lt;strong>No funciona&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Sí, único camino&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cumplimiento normativo tamper-proof&lt;/td>
&lt;td>Insuficiente&lt;/td>
&lt;td>&lt;strong>Sí, requerido&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Multi-tenant zero-trust&lt;/td>
&lt;td>Insuficiente&lt;/td>
&lt;td>&lt;strong>Sí, requerido&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Servidores MCP locales (stdio)&lt;/td>
&lt;td>Difícil&lt;/td>
&lt;td>&lt;strong>Sí, con stdiocap&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Evaluación de calidad de respuestas&lt;/td>
&lt;td>&lt;strong>Sí, requerido&lt;/strong>&lt;/td>
&lt;td>No (fuera de scope)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Profundidad de chain interno&lt;/td>
&lt;td>&lt;strong>Sí, requerido&lt;/strong>&lt;/td>
&lt;td>No (caja negra para AgentSight)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reasoning loop detection&lt;/td>
&lt;td>Posible con plumbing&lt;/td>
&lt;td>&lt;strong>Sí, integrado&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt injection en tiempo real&lt;/td>
&lt;td>Posible (post-procesado)&lt;/td>
&lt;td>&lt;strong>Sí, en stream&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La conclusión natural: &lt;strong>para apps propias, instrumentado; para binarios opacos o compliance, eBPF; para todo lo importante, ambos&lt;/strong>.&lt;/p>
&lt;h2 id="arquitectura-de-referencia-2026">Arquitectura de referencia 2026&lt;/h2>
&lt;p>Cuatro recetas que cubren el grueso de los casos reales:&lt;/p>
&lt;h3 id="setup-a--aplicación-propia-con-langchain-o-similar">Setup A — Aplicación propia con LangChain o similar&lt;/h3>
&lt;p>Necesidades: profundidad, evals, equipo cómodo con SDKs.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Langfuse self-host&lt;/strong> o &lt;strong>LangSmith cloud&lt;/strong> como backend.&lt;/li>
&lt;li>&lt;strong>OpenLLMetry SDK&lt;/strong> o &lt;strong>LangSmith SDK&lt;/strong> instrumentando el código.&lt;/li>
&lt;li>&lt;strong>OpenTelemetry Collector&lt;/strong> entre la app y el backend para flexibilidad de routing (a Langfuse + Tempo + Loki por ejemplo).&lt;/li>
&lt;li>&lt;strong>Hubble&lt;/strong> para la capa de red en el cluster (latencia inter-pod, drop attribution).&lt;/li>
&lt;/ul>
&lt;h3 id="setup-b--productivizar-un-binario-opaco-claude-code-gemini-cli">Setup B — Productivizar un binario opaco (Claude Code, Gemini CLI)&lt;/h3>
&lt;p>Necesidades: observar sin tocar, auditar, controlar coste.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>AgentSight&lt;/strong> desplegado como DaemonSet sobre el cluster (o standalone en el nodo).&lt;/li>
&lt;li>&lt;strong>Grafana con dashboards&lt;/strong> alimentados por las métricas de AgentSight.&lt;/li>
&lt;li>&lt;strong>Exportador OTLP&lt;/strong> de AgentSight a un backend OTel (Tempo, Jaeger). Los spans usarán las semantic conventions GenAI cuando se estandaricen del todo.&lt;/li>
&lt;li>&lt;strong>Tetragon&lt;/strong> opcional para política sobre qué puede ejecutar el agente (Sigkill si intenta &lt;code>rm -rf&lt;/code> o similar).&lt;/li>
&lt;/ul>
&lt;h3 id="setup-c--plataforma-multi-tenant-zero-trust">Setup C — Plataforma multi-tenant zero-trust&lt;/h3>
&lt;p>Necesidades: agentes de distintos clientes corriendo en el mismo cluster, auditoría obligatoria, ninguno confía en el otro.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>AgentSight&lt;/strong> como capa de auditoría tamper-proof. Compliance lo requiere.&lt;/li>
&lt;li>&lt;strong>Langfuse multi-tenant&lt;/strong> para los clientes que sí instrumentan.&lt;/li>
&lt;li>&lt;strong>Tetragon&lt;/strong> con &lt;code>TracingPolicyNamespaced&lt;/code> por tenant (políticas distintas por namespace).&lt;/li>
&lt;li>&lt;strong>Hubble&lt;/strong> con flow logs persistentes para forensics.&lt;/li>
&lt;li>&lt;strong>Cilium NetworkPolicy&lt;/strong> para aislar tenants entre sí en red.&lt;/li>
&lt;/ul>
&lt;h3 id="setup-d--servidor-mcp-local-en-una-workstation">Setup D — Servidor MCP local en una workstation&lt;/h3>
&lt;p>Necesidades: ver qué hace un agente con un MCP server stdio.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>AgentSight stdiocap&lt;/strong> apuntando al PID del cliente o del server.&lt;/li>
&lt;li>Captura JSON-RPC completo a fichero o a un endpoint OTLP.&lt;/li>
&lt;li>Visualización: Grafana o simplemente &lt;code>jq&lt;/code> sobre el log.&lt;/li>
&lt;/ul>
&lt;p>Caso de uso real: si estás integrando un MCP server propio y quieres ver qué tool calls hace un agente Claude Code o Cursor a tu server, &lt;code>stdiocap&lt;/code> es la forma más limpia. No necesitas modificar ni cliente ni server.&lt;/p>
&lt;h2 id="trampas-operativas">Trampas operativas&lt;/h2>
&lt;h3 id="datos-sensibles-en-prompts-instrumentado">Datos sensibles en prompts (instrumentado)&lt;/h3>
&lt;p>Por defecto, Langfuse, LangSmith y similares capturan &lt;strong>el contenido completo&lt;/strong> de prompts y completions. Si tu app procesa PII, secretos, datos médicos, &lt;strong>eso va a tu backend de observabilidad&lt;/strong>. Configurar &lt;strong>redacción&lt;/strong> o &lt;strong>content-opt-out&lt;/strong> antes de pasar a producción es obligado. OTel GenAI tiene flags específicos (&lt;code>OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=false&lt;/code>) para evitarlo.&lt;/p>
&lt;h3 id="datos-sensibles-en-prompts-agentsight">Datos sensibles en prompts (AgentSight)&lt;/h3>
&lt;p>Mismo problema, peor: AgentSight captura &lt;strong>literalmente lo que va al cable&lt;/strong>, plaintext. Si el agente conversó con &lt;code>api.openai.com&lt;/code> con un prompt que contenía datos sensibles, AgentSight tiene ese plaintext. Hay que cifrar o redactar antes de almacenar.&lt;/p>
&lt;h3 id="certificados-pinned-o-tls-no-estándar">Certificados pinned o TLS no estándar&lt;/h3>
&lt;p>Algunas apps de seguridad alta hacen certificate pinning o usan implementaciones de TLS no convencionales (Go&amp;rsquo;s &lt;code>crypto/tls&lt;/code>, BoringSSL custom). En esos casos, las uprobes a &lt;code>libssl&lt;/code> no las cubren. AgentSight detecta cuándo no puede observar y reporta gap; igual hay que añadir hooks específicos al SDK alternativo.&lt;/p>
&lt;h3 id="volumen-de-tokens-y-storage">Volumen de tokens y storage&lt;/h3>
&lt;p>Una aplicación con tráfico medio puede generar &lt;strong>millones de tokens al día&lt;/strong>. Si los almacenas todos en Langfuse o Phoenix con retención largos, la base de datos crece deprisa. Estrategias: sampling agresivo, retención corta para sesiones normales y larga solo para errores/anomalías, redaction de contenido y guardar solo metadata.&lt;/p>
&lt;h3 id="tracing-con-sampling-y-consistencia">Tracing con sampling y consistencia&lt;/h3>
&lt;p>Para reducir coste, muchas instalaciones samplean: solo 1 de cada N traces se persiste. &lt;strong>Cuidado con el sampling no consistente&lt;/strong>: un trace puede llevar varios spans en múltiples servicios, y si la decisión de samplear se toma per-span, acabas con traces incompletos. OTel tiene &lt;strong>head sampling&lt;/strong> (en el SDK al principio) que es consistente, y &lt;strong>tail sampling&lt;/strong> (en el collector al final) que permite reglas más finas. Para LLM, el tail sampling es ideal: muestrea todo, descarta solo las traces &amp;ldquo;normales&amp;rdquo; y conserva las que tienen errores, latencia alta o cost alto.&lt;/p>
&lt;h3 id="multi-agent-y-trace-propagation">Multi-agent y trace propagation&lt;/h3>
&lt;p>Cuando agente A llama a agente B, hay que &lt;strong>propagar el trace context&lt;/strong> (W3C Trace Context headers) para que se vea como un árbol único. Si no lo haces, ves dos traces inconexos. Las plataformas modernas lo hacen automáticamente con &lt;code>inject&lt;/code>/&lt;code>extract&lt;/code>, pero si tu transport entre agentes es custom (vía Redis pub/sub, vía DB), tienes que propagar a mano.&lt;/p>
&lt;h3 id="coste-de-las-uprobes-en-bibliotecas-críticas">Coste de las uprobes en bibliotecas críticas&lt;/h3>
&lt;p>Hookear &lt;code>libssl&lt;/code> añade ~100 ns por invocación. En cargas de tráfico TLS extremo (decenas de miles de conexiones/s por core), eso suma. AgentSight lo mantiene por debajo de 3% en cargas típicas de agentes (que son chatty pero no networking-intensive). Si tu uso fuese sniffing de todo el HTTPS del nodo, podría doler más.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próxima-serie">Lo que no hemos cubierto (próxima serie)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Evals&lt;/strong>: la siguiente capa después de tracing. Phoenix, Langfuse, LangSmith y compañía ofrecen evaluación de respuestas (judge LLM, datasets, regression). Es un mundo aparte.&lt;/li>
&lt;li>&lt;strong>Guardrails y safety&lt;/strong>: NeMo Guardrails, Llama Guard, Llama Prompt Guard, evaluadores específicos para prompt injection y jailbreaks.&lt;/li>
&lt;li>&lt;strong>MCP server observability profunda&lt;/strong>: cómo OpenTelemetry GenAI conventions están extendiéndose a MCP servers para trace-aware tools.&lt;/li>
&lt;li>&lt;strong>eBPF + on-device inference&lt;/strong>: cuando el LLM corre localmente vía vLLM o llama.cpp, las uprobes pueden ver la cola tokens-output ANTES de que vayan al cliente. Territorio nuevo.&lt;/li>
&lt;li>&lt;strong>Análisis estadístico de flows de agentes&lt;/strong>: detectar drift, outliers, patrones que indican degradación.&lt;/li>
&lt;/ul>
&lt;h2 id="cerrando-la-serie-ebpf">Cerrando la serie eBPF&lt;/h2>
&lt;p>Esta serie de cuatro artículos ha recorrido eBPF desde el primer principio hasta la frontera 2026:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://blog.lo0.es/posts/ebpf-cilium-tcp-ip-bypass/">eBPF de cero a Cilium&lt;/a> — qué es eBPF, hooks de networking, cómo Cilium se salta la pila TCP/IP, BGP Control Plane v2.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon: seguridad de runtime&lt;/a> — observabilidad y enforcement de procesos en el kernel.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/hubble-observabilidad-ebpf/">Hubble: observabilidad de red&lt;/a> — flow logs L3-L7 y la frontera con los agentes IA.&lt;/li>
&lt;li>&lt;strong>Este&lt;/strong> — AgentSight, tracing de LLMs, instrumentado vs zero-instrumentation.&lt;/li>
&lt;/ol>
&lt;p>Si has llegado hasta aquí tienes el mapa para sentarte con un equipo de plataforma, de seguridad o de IA en 2026 y reconocer qué hace cada pieza, qué problema resuelve y por dónde empezar. Toda esa pila —Cilium para CNI y BGP, Tetragon para seguridad de runtime, Hubble para observabilidad de red, AgentSight para agentes IA— compartiendo eBPF como sustrato común, gobernanza Cloud Native y vocabulario OpenTelemetry. Es la arquitectura limpia que la industria pidió hace una década y por fin existe.&lt;/p>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>AgentSight:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/eunomia-bpf/agentsight">AgentSight GitHub (eunomia-bpf)&lt;/a> — el proyecto.&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/abs/2508.02736">AgentSight: System-Level Observability for AI Agents Using eBPF (arxiv 2508.02736)&lt;/a> — paper formal.&lt;/li>
&lt;li>&lt;a href="https://dl.acm.org/doi/10.1145/3766882.3767169">AgentSight ACM workshop publication&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://eunomia.dev/blog/2025/08/26/agentsight-keeping-your-ai-agents-under-control-with-ebpf-powered-system-observability/">AgentSight blog post (eunomia.dev)&lt;/a> — descripción accesible.&lt;/li>
&lt;/ul>
&lt;p>OpenTelemetry GenAI semantic conventions:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry — Semantic conventions for generative AI systems&lt;/a> — referencia oficial.&lt;/li>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/">Semantic conventions for generative client AI spans&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/">Semantic conventions for generative AI metrics&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://opentelemetry.io/blog/2026/genai-observability/">Inside the LLM Call: GenAI Observability with OpenTelemetry (OTel blog 2026)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/open-telemetry/semantic-conventions/issues/2664">Multi-agent Semantic Conventions (GitHub issue #2664)&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Plataformas instrumentadas:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://langfuse.com/">Langfuse&lt;/a> — MIT, self-host + cloud.&lt;/li>
&lt;li>&lt;a href="https://www.langchain.com/langsmith">LangSmith&lt;/a> — LangChain team.&lt;/li>
&lt;li>&lt;a href="https://phoenix.arize.com/">Arize Phoenix&lt;/a> — OSS, OTel-native.&lt;/li>
&lt;li>&lt;a href="https://www.helicone.ai/">Helicone&lt;/a> — proxy simple.&lt;/li>
&lt;li>&lt;a href="https://github.com/traceloop/openllmetry">OpenLLMetry (Traceloop)&lt;/a> — Apache 2.0, SDK OTel.&lt;/li>
&lt;li>&lt;a href="https://pydantic.dev/docs/logfire/get-started/ai-observability/">Pydantic Logfire — AI observability&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Comparativas 2026:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.braintrust.dev/articles/langfuse-alternatives-2026">Langfuse alternatives 2026 (Braintrust)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.braintrust.dev/articles/best-llm-tracing-tools-2026">7 best LLM tracing tools for multi-agent AI systems (2026)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://medium.com/@kanerika/llmops-observability-langsmith-vs-arize-vs-langfuse-vs-w-b-f1baeabd1bbf">LLMOps Observability: LangSmith vs Arize vs Langfuse vs W&amp;amp;B&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.firecrawl.dev/blog/best-llm-observability-tools">Best LLM Observability Tools in 2026 (Firecrawl)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.spheron.network/blog/llm-observability-gpu-cloud-langfuse-arize-phoenix-helicone/">LLM Observability on GPU Cloud (Spheron 2026 guide)&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Cross-references de la serie:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/ebpf-cilium-tcp-ip-bypass/">eBPF de cero a Cilium&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon: seguridad de runtime&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/hubble-observabilidad-ebpf/">Hubble: observabilidad de red&lt;/a>.&lt;/li>
&lt;li>Serie de inferencia LLM: &lt;a href="https://blog.lo0.es/posts/kv-cache-fundamentos/">KV cache&lt;/a>, &lt;a href="https://blog.lo0.es/posts/vllm-kubernetes/">vLLM en Kubernetes&lt;/a>, &lt;a href="https://blog.lo0.es/posts/pagedattention-deep-dive/">PagedAttention&lt;/a>, &lt;a href="https://blog.lo0.es/posts/operators-llm-kubernetes/">Operators LLM K8s&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>