<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mcp on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/mcp/</link><description>Recent content in Mcp 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/mcp/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>Hubble: observabilidad de red en eBPF, estado del arte 2026 y la nueva frontera con los agentes IA</title><link>https://blog.lo0.es/posts/hubble-observabilidad-ebpf/</link><pubDate>Tue, 19 May 2026 06:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/hubble-observabilidad-ebpf/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>&lt;a href="https://github.com/cilium/hubble">Hubble&lt;/a> es &lt;strong>la observabilidad de red nativa de Cilium&lt;/strong>, construida sobre los mismos programas eBPF que Cilium usa para enforcement. No duplica datapath ni instrumenta el kernel a su manera: &lt;strong>escucha&lt;/strong> los hooks que Cilium ya tiene y produce &lt;strong>flow logs estructurados&lt;/strong> con contexto Kubernetes incluido —pod, namespace, labels, service, verdict de policy, payload L7 cuando aplica—. Es lo que pasa cuando alguien decide que &lt;code>tcpdump&lt;/code> con &lt;code>grep&lt;/code> no escala a 10 000 pods y construye un sistema distribuido propio (Hubble server por nodo + Hubble Relay como agregador + CLI + UI) con overhead &lt;strong>prácticamente cero&lt;/strong> porque la captura ya estaba ocurriendo. En 2026 está en &lt;strong>versión 1.19.3&lt;/strong> (abril 2026), con Cilium 1.19 marcando el décimo aniversario del proyecto; ha llegado el &lt;strong>tracing por IP options&lt;/strong>, el filtrado por estado de cifrado, el &lt;strong>drop event taggeado con la NetworkPolicy exacta que lo causó&lt;/strong> (atribución directa), el &lt;strong>field mask API&lt;/strong> estabilizado, y la primera oleada de &lt;strong>anomaly detection con ML aplicado a flows&lt;/strong> para predictive security en clusters IoT/5G. Y, lo más interesante para 2026: aparece una frontera nueva donde el mismo eBPF observa &lt;strong>agentes de IA&lt;/strong> —Claude Code, Gemini CLI, agentes MCP— interceptando SSL/TLS y stdio sin instrumentar el código, lo que convierte el stack Cilium + Hubble + Tetragon + &lt;strong>AgentSight&lt;/strong> en una pila completa para entender qué hace un sistema agentic dentro de un cluster.&lt;/p>
&lt;blockquote>
&lt;p>Este artículo es la &lt;strong>parte 3 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: cómo el kernel aprendió a saltarse su propia pila TCP/IP&lt;/a>. Parte 2: &lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon: el primo de seguridad de Cilium que ve cada syscall en el kernel&lt;/a>. Aquí completamos el cuadrante de observabilidad: &lt;strong>red&lt;/strong> con Hubble, &lt;strong>proceso&lt;/strong> con Tetragon, &lt;strong>agente IA&lt;/strong> con AgentSight.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-analogía-tcpdump-que-habla-kubernetes">La analogía: tcpdump que habla Kubernetes&lt;/h2>
&lt;p>Si has administrado redes los últimos veinte años, &lt;code>tcpdump&lt;/code> y Wireshark han sido el pan nuestro de cada día. Capturan paquetes en una interfaz, los parsean, te dejan filtrar con &lt;code>tcp.port == 443 and host 10.0.0.5&lt;/code>. Funcionan, llevan funcionando desde los 90, y son lo primero que abres cuando algo huele raro.&lt;/p>
&lt;p>Ahora pega &lt;code>tcpdump&lt;/code> a un cluster Kubernetes de 10 000 pods. Los problemas saltan en orden:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Una sesión &lt;code>tcpdump&lt;/code> por nodo&lt;/strong>. Querías &amp;ldquo;ver el tráfico entre el frontend y la API&amp;rdquo;; necesitas SSH a cada nodo, tcpdump por cada NIC, sincronizar timestamps, agregar a mano.&lt;/li>
&lt;li>&lt;strong>No hay contexto K8s&lt;/strong>. Ves un paquete de &lt;code>10.244.5.7&lt;/code> a &lt;code>10.244.8.42&lt;/code>. ¿Qué pod era? ¿Qué namespace? ¿Qué label? Te toca correlar con &lt;code>kubectl get pod -A -o wide&lt;/code> cada vez.&lt;/li>
&lt;li>&lt;strong>Sin entender L7&lt;/strong>. Ves un POST a HTTPS, no puedes saber qué método y path porque está cifrado en el cable. Si hay mTLS entre pods, peor.&lt;/li>
&lt;li>&lt;strong>Coste alto&lt;/strong>: captura completa de paquetes con copia a userspace ralentiza el datapath. En tráfico denso, lo notas.&lt;/li>
&lt;/ol>
&lt;p>Hubble es &lt;strong>tcpdump rediseñado para todo eso&lt;/strong>. Reutiliza los programas eBPF que &lt;strong>ya están&lt;/strong> procesando cada paquete (Cilium los pone ahí para enforcement) y, mientras toman su decisión de allow/deny, &lt;strong>emiten un evento de flow&lt;/strong> con todo el contexto: identidad del pod origen y destino, namespace, labels, protocolo, verdict, y —si Cilium ha hecho parsing L7 vía Envoy— método HTTP, path, status code, DNS query, Kafka topic. Ese evento viaja por un ringbuffer a userspace, lo recibe el &lt;strong>Hubble server&lt;/strong> que vive dentro del agent Cilium del nodo, y lo expone vía gRPC. Un servicio aparte, &lt;strong>Hubble Relay&lt;/strong>, agrega los streams de todos los nodos y te da una única API cluster-wide. Por encima de eso: una CLI (&lt;code>hubble&lt;/code>) y una UI web con grafo de servicios en tiempo real.&lt;/p>
&lt;p>Cero copia adicional. Cero parsing duplicado. Y el resultado lo entiende cualquiera que sepa qué es un Pod.&lt;/p>
&lt;h2 id="arquitectura-cuatro-piezas-que-se-ven-desde-fuera">Arquitectura: cuatro piezas que se ven desde fuera&lt;/h2>
&lt;p>Hubble se compone de cuatro componentes lógicos, todos opcionales según lo que quieras hacer:&lt;/p>
&lt;h3 id="1-hubble-server-embedded-en-cada-agent-cilium">1. Hubble Server (embedded en cada agent Cilium)&lt;/h3>
&lt;p>Vive &lt;strong>dentro del proceso del agent Cilium&lt;/strong> (no es un binario aparte). Cada nodo expone localmente un endpoint gRPC en el socket Unix &lt;code>/var/run/cilium/hubble.sock&lt;/code>. El server escucha los eventos que los programas eBPF emiten al ringbuffer, los enriquece con metadata Kubernetes (que el agent ya tiene en memoria), y los pone disponibles para consumidores.&lt;/p>
&lt;p>Activación: &lt;code>--set hubble.enabled=true&lt;/code> en el chart Helm de Cilium. Por defecto, el server &lt;strong>solo es accesible localmente&lt;/strong>; si quieres consumirlo desde otro nodo, hace falta exponerlo (lo que hace Hubble Relay).&lt;/p>
&lt;h3 id="2-hubble-relay-agregador">2. Hubble Relay (agregador)&lt;/h3>
&lt;p>Es un Deployment aparte (típicamente 1 réplica, escalable) que &lt;strong>se conecta a todos los Hubble servers del cluster&lt;/strong> y &lt;strong>agrega sus streams en una única API&lt;/strong>. Cuando tu CLI o UI pide &amp;ldquo;los últimos 1000 flows del cluster&amp;rdquo;, la Relay los recoge en paralelo de todos los nodos y devuelve la unión.&lt;/p>
&lt;p>Activación: &lt;code>--set hubble.relay.enabled=true&lt;/code>. Sin la Relay, solo ves el tráfico del nodo donde estás conectado, lo que es útil para debug local pero no para visión cluster-wide.&lt;/p>
&lt;h3 id="3-hubble-cli-hubble">3. Hubble CLI (&lt;code>hubble&lt;/code>)&lt;/h3>
&lt;p>Un binario en Go que habla gRPC con la Relay (o con un Hubble server local). Soporta dos modos principales:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>hubble observe&lt;/code>&lt;/strong>: stream de flows en tiempo real, con filtros muy expresivos (por namespace, pod, port, verdict, protocolo, label).&lt;/li>
&lt;li>&lt;strong>&lt;code>hubble status&lt;/code>&lt;/strong>: estado del cluster Hubble (cuántos nodos conectados, lag, flow rate).&lt;/li>
&lt;/ul>
&lt;p>Y el equivalente a &lt;code>tcpdump&lt;/code>&amp;rsquo;s pcap dump: &lt;code>hubble observe --output jsonpb &amp;gt; flows.json&lt;/code> para procesar a posteriori con &lt;code>jq&lt;/code> u otras herramientas.&lt;/p>
&lt;h3 id="4-hubble-ui">4. Hubble UI&lt;/h3>
&lt;p>Frontend web que se conecta a Hubble Relay y muestra:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Grafo de servicios&lt;/strong> en tiempo real (qué Pod habla con qué Service, qué protocolos usa, qué verdict).&lt;/li>
&lt;li>&lt;strong>Lista de flows&lt;/strong> filtrable.&lt;/li>
&lt;li>&lt;strong>Detalles L7&lt;/strong> cuando los hay (HTTP method/path/status, DNS query/response).&lt;/li>
&lt;/ul>
&lt;p>Activación: &lt;code>--set hubble.ui.enabled=true&lt;/code>. Útil para presentaciones a equipos no-CLI; no sustituye a la CLI para debug serio.&lt;/p>
&lt;div class="diagram" style="max-width:720px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 720 290" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Arquitectura de Hubble">
&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}.k{fill:#ffe9d6}.s{fill:#d6eaff}.r{fill:#d9f5d6}.c{fill:#e9d6f5}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#hh)}&lt;/style>
&lt;defs>&lt;marker id="hh" 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="18" text-anchor="middle" class="title">Arquitectura Hubble: 4 piezas, eBPF como única fuente de datos&lt;/text>
&lt;rect x="30" y="40" width="200" height="50" rx="6" class="box k"/>
&lt;text x="130" y="60" text-anchor="middle" class="lbl">eBPF (kernel)&lt;/text>
&lt;text x="130" y="78" text-anchor="middle" class="sm">programs de Cilium&lt;/text>
&lt;rect x="30" y="115" width="200" height="50" rx="6" class="box s"/>
&lt;text x="130" y="135" text-anchor="middle" class="lbl">Hubble Server (nodo)&lt;/text>
&lt;text x="130" y="153" text-anchor="middle" class="sm">grpc local, dentro del agent&lt;/text>
&lt;rect x="270" y="115" width="180" height="50" rx="6" class="box s"/>
&lt;text x="360" y="135" text-anchor="middle" class="lbl">Hubble Server (nodo N)&lt;/text>
&lt;text x="360" y="153" text-anchor="middle" class="sm">uno por nodo&lt;/text>
&lt;rect x="490" y="115" width="200" height="50" rx="6" class="box s"/>
&lt;text x="590" y="135" text-anchor="middle" class="lbl">Hubble Server (nodo …)&lt;/text>
&lt;text x="590" y="153" text-anchor="middle" class="sm">N agents = N servers&lt;/text>
&lt;rect x="220" y="190" width="280" height="50" rx="6" class="box r"/>
&lt;text x="360" y="210" text-anchor="middle" class="lbl">Hubble Relay (Deployment)&lt;/text>
&lt;text x="360" y="228" text-anchor="middle" class="sm">agrega streams gRPC de todos los nodos&lt;/text>
&lt;rect x="80" y="245" width="160" height="35" rx="6" class="box c"/>
&lt;text x="160" y="266" text-anchor="middle" class="lbl">Hubble CLI&lt;/text>
&lt;rect x="290" y="245" width="160" height="35" rx="6" class="box c"/>
&lt;text x="370" y="266" text-anchor="middle" class="lbl">Hubble UI&lt;/text>
&lt;rect x="500" y="245" width="180" height="35" rx="6" class="box c"/>
&lt;text x="590" y="266" text-anchor="middle" class="lbl">Prometheus / OTLP&lt;/text>
&lt;path class="arr" d="M130,90 L130,115"/>
&lt;path class="arr" d="M130,165 L290,190"/>
&lt;path class="arr" d="M360,165 L360,190"/>
&lt;path class="arr" d="M590,165 L430,190"/>
&lt;path class="arr" d="M340,240 L200,245"/>
&lt;path class="arr" d="M360,240 L370,245"/>
&lt;path class="arr" d="M380,240 L560,245"/>
&lt;/svg>
&lt;/div>
&lt;h2 id="qué-se-ve-el-flow-log-de-hubble-por-dentro">Qué se ve: el flow log de Hubble por dentro&lt;/h2>
&lt;p>Un flow de Hubble en formato JSON tiene aproximadamente esta forma (simplificado):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;time&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2026-05-19T03:12:45.182Z&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="nt">&amp;#34;verdict&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;FORWARDED&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="nt">&amp;#34;source&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="nt">&amp;#34;ID&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5482&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;identity&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">24871&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;namespace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;prod-api&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="nt">&amp;#34;labels&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;app=checkout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;team=payments&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="nt">&amp;#34;pod_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;checkout-7c9f-x8j2&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="nt">&amp;#34;workloads&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;checkout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;kind&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Deployment&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;destination&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="nt">&amp;#34;ID&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">12041&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;identity&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">18356&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;namespace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;prod-db&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="nt">&amp;#34;labels&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;app=postgres&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;tier=primary&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="nt">&amp;#34;pod_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;postgres-0&amp;#34;&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;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;L3_L4&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="nt">&amp;#34;l4&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="nt">&amp;#34;TCP&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="nt">&amp;#34;source_port&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">41982&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;destination_port&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5432&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;flags&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;SYN&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&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;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;node_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;rke2-worker-03&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="nt">&amp;#34;Summary&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;TCP Flags: SYN&amp;#34;&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 hay parsing L7 activo (vía Envoy embebido o el parser ligero de Hubble), el mismo flujo añade:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;l7&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;REQUEST&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="nt">&amp;#34;http&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="nt">&amp;#34;code&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;method&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;GET&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="nt">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/api/v1/cart/items&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="nt">&amp;#34;protocol&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HTTP/1.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="nt">&amp;#34;headers&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="nt">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user-agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;checkout/1.4.2&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;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>Los protocolos soportados nativamente para parsing L7:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>HTTP/1.1 y HTTP/2&lt;/strong> (incluyendo gRPC sobre HTTP/2).&lt;/li>
&lt;li>&lt;strong>DNS&lt;/strong> (queries y responses, con domains, tipos, response codes).&lt;/li>
&lt;li>&lt;strong>Kafka&lt;/strong> (topics, API keys).&lt;/li>
&lt;li>&lt;strong>TLS handshake&lt;/strong> (SNI, no el payload cifrado por defecto).&lt;/li>
&lt;li>&lt;strong>MySQL, Cassandra&lt;/strong> (con módulos opcionales).&lt;/li>
&lt;/ul>
&lt;p>Para HTTP y gRPC, Cilium puede activar el proxy Envoy embebido para los flujos que quieras inspeccionar (no todos; selectivo via &lt;code>CiliumNetworkPolicy&lt;/code> con reglas L7). Sin Envoy hay parsing ligero pero menos detallado.&lt;/p>
&lt;h2 id="verdict-y-atribución-de-drops">Verdict y atribución de drops&lt;/h2>
&lt;p>Cada flow tiene un &lt;code>verdict&lt;/code>: &lt;code>FORWARDED&lt;/code>, &lt;code>DROPPED&lt;/code>, &lt;code>ERROR&lt;/code>, &lt;code>AUDIT&lt;/code>, &lt;code>REDIRECTED&lt;/code>, &lt;code>TRACED&lt;/code>, &lt;code>TRANSLATED&lt;/code>. Para el caso &lt;code>DROPPED&lt;/code>, Hubble incluye una &lt;strong>razón estructurada&lt;/strong> (&lt;code>drop_reason&lt;/code>) y, desde Cilium 1.19, &lt;strong>la NetworkPolicy exacta&lt;/strong> que lo causó.&lt;/p>
&lt;p>Esto último cambia la operativa. Antes, cuando un pod no podía hablar con otro, el flujo de debug era:&lt;/p>
&lt;ol>
&lt;li>Ver el flow dropeado en Hubble.&lt;/li>
&lt;li>Mirar todas las CiliumNetworkPolicy del namespace.&lt;/li>
&lt;li>Razonar a mano cuál de ellas, con cuáles labels, lo está bloqueando.&lt;/li>
&lt;/ol>
&lt;p>Con la atribución de Cilium 1.19, el campo &lt;code>policy_match_info&lt;/code> te dice directamente &amp;ldquo;lo dropeó la policy &lt;code>frontend-egress&lt;/code>, regla 3&amp;rdquo;. Pasas de &amp;ldquo;Sherlock Holmes durante 20 minutos&amp;rdquo; a &amp;ldquo;kubectl get -o yaml de esa policy concreta&amp;rdquo;.&lt;/p>
&lt;h2 id="métricas-prometheus-y-dashboards-grafana">Métricas Prometheus y dashboards Grafana&lt;/h2>
&lt;p>Hubble también expone &lt;strong>métricas agregadas&lt;/strong> en formato Prometheus, separadas del stream gRPC de flows. Activación: &lt;code>--set hubble.metrics.enabled=true&lt;/code> (Helm) y enumeración del set que quieres exportar.&lt;/p>
&lt;p>Los grupos de métricas habituales:&lt;/p>
&lt;ul>
&lt;li>&lt;code>flow&lt;/code>: total flows por verdict, source/dest, protocolo.&lt;/li>
&lt;li>&lt;code>http&lt;/code>: requests por método, código de respuesta, latencia (histograma).&lt;/li>
&lt;li>&lt;code>dns&lt;/code>: queries, response codes, dominios top-N.&lt;/li>
&lt;li>&lt;code>tcp&lt;/code>: handshakes, retransmisiones, ventana congestion.&lt;/li>
&lt;li>&lt;code>drop&lt;/code>: drops por razón, con NetworkPolicy attribution.&lt;/li>
&lt;li>&lt;code>port-distribution&lt;/code>: histograma de ports activos.&lt;/li>
&lt;li>&lt;code>policy&lt;/code>: hits por policy y verdict.&lt;/li>
&lt;/ul>
&lt;p>Estas métricas tienen labels K8s ricos (&lt;code>source_workload&lt;/code>, &lt;code>destination_workload&lt;/code>, &lt;code>namespace&lt;/code>, etc.) que las hacen pivotables en Grafana. Hay &lt;a href="https://grafana.com/grafana/dashboards/?search=hubble">dashboards prebuilt en Grafana Labs&lt;/a> que cubren los casos comunes; importar uno y tener visión inmediata cuesta cinco minutos.&lt;/p>
&lt;p>Coste: las métricas con muchos labels K8s pueden &lt;strong>explotar la cardinalidad&lt;/strong> en Prometheus. Para clusters grandes (&amp;gt;1 000 pods), conviene revisar qué set exportas y usar &lt;strong>drop rules&lt;/strong> en Prometheus para limitar.&lt;/p>
&lt;h2 id="despliegue-helm-en-una-pantalla">Despliegue: Helm en una pantalla&lt;/h2>
&lt;p>Instalación canónica de Cilium con Hubble completo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># values.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">hubble&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metrics&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">dns:query;ignoreAAAA&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">drop&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">tcp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">flow&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">port-distribution&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">icmp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">serviceMonitor&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># auto-discover por kube-prometheus-stack&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">relay&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rollOutPods&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ui&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rollOutPods&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ingress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">className&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cilium&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hosts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">hubble.example.local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Y la instalación:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">helm upgrade --install cilium cilium/cilium -n kube-system -f values.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Tras la instalación, valida con &lt;code>cilium status&lt;/code> (CLI de Cilium) que la sección Hubble muestra OK, y prueba el primer flow con:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cilium hubble observe --namespace prod-api --pod checkout-7c9f-x8j2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="estado-del-arte-en-2026">Estado del arte en 2026&lt;/h2>
&lt;p>&lt;a href="https://www.infoq.com/news/2026/02/cilium-119/">Cilium 1.19 se publicó en febrero de 2026&lt;/a> marcando el décimo aniversario del proyecto. Hubble alcanzó la versión 1.19.3 el 22 de abril de 2026. Las novedades relevantes:&lt;/p>
&lt;h3 id="atribución-directa-de-drops-a-networkpolicy">Atribución directa de drops a NetworkPolicy&lt;/h3>
&lt;p>Ya cubierta arriba; es probablemente el cambio operacional más valioso del release. Cualquier flow dropeado lleva el nombre, namespace y regla específica de la policy responsable. Aplicable también vía métricas Prometheus, lo que permite alertas tipo &amp;ldquo;policy X está dropping &amp;gt;N peticiones/segundo&amp;rdquo;.&lt;/p>
&lt;h3 id="tracing-con-ip-options">Tracing con IP options&lt;/h3>
&lt;p>Hubble puede ahora &lt;strong>trazar paquetes individuales con IP options&lt;/strong> activado. Es un mecanismo similar al traceroute pero a nivel L3: pones una marca en el paquete y Cilium la reporta cada vez que el paquete atraviesa un nodo o una decisión de eBPF. Útil para debug de paths multi-cluster, fabric mesh, o NetworkPolicy que se aplican en distintas capas.&lt;/p>
&lt;h3 id="filtrado-por-estado-de-cifrado">Filtrado por estado de cifrado&lt;/h3>
&lt;p>Nuevo flag en CLI: &lt;code>hubble observe --encryption-status=encrypted&lt;/code> (o &lt;code>unencrypted&lt;/code>). Útil para validar despliegues con WireGuard o IPsec activado pod-a-pod: confirmas que el tráfico que &lt;strong>debería&lt;/strong> estar cifrado lo está, y detectas regresiones rápidamente.&lt;/p>
&lt;h3 id="hubble-field-mask-api-estabilizado">Hubble field mask API estabilizado&lt;/h3>
&lt;p>El &lt;code>field_mask&lt;/code> permite pedir solo las partes del flow que te interesan, reduciendo enormemente el ancho de banda y el procesamiento cuando solo necesitas, por ejemplo, source/dest y verdict. Antes era experimental, ahora está estable y es &lt;strong>default-on&lt;/strong> en la CLI.&lt;/p>
&lt;h3 id="ai-driven-anomaly-detection-predictive-security">AI-driven anomaly detection (predictive security)&lt;/h3>
&lt;p>Esta es la incorporación más comentada de 2026. Cilium 1.19 añade hooks para que un consumer externo —típicamente un sistema ML— procese los flows en streaming y detecte anomalías estadísticas: pods que de pronto hablan con destinos nuevos, picos de latencia en una API, secuencias raras de DNS. La parte de &lt;strong>detección&lt;/strong> ocurre fuera del agent Cilium (no se quiere ML pesado en el datapath), pero Cilium expone los flows con las features pre-calculadas que el modelo necesita. Los casos de uso publicados se enfocan en &lt;strong>IoT y 5G&lt;/strong> donde el tráfico es alto en volumen y bajo en variedad, condiciones ideales para anomaly detection.&lt;/p>
&lt;h3 id="escala-a-10-000-pods">Escala a 10 000+ pods&lt;/h3>
&lt;p>Cilium 1.19 ha hecho trabajo serio en escalabilidad: Hubble Relay puede ahora agregar streams de cientos de nodos sin saturar; el field_mask por defecto reduce el ancho de banda inter-nodo; y los flows pueden samplearse en alta carga si tu uso es análisis estadístico (no debug forense).&lt;/p>
&lt;h3 id="cilium-120-en-desarrollo">Cilium 1.20 en desarrollo&lt;/h3>
&lt;p>&lt;a href="https://docs.cilium.io/en/latest/operations/upgrade/">Cilium 1.20&lt;/a> está en branch de desarrollo. Lo más relevante para Hubble:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Unificación de &lt;code>preferIpv6&lt;/code>&lt;/strong>: el flag &lt;code>hubble.preferIpv6&lt;/code> se deprecó en favor del global &lt;code>preferIpv6&lt;/code> aplicable a todos los componentes Cilium.&lt;/li>
&lt;li>&lt;strong>&lt;code>tetragon-python&lt;/code> SDK&lt;/strong>: aunque es de Tetragon, no de Hubble, marca tendencia: políticas eBPF escritas en Python en lugar de YAML. Probablemente Hubble seguirá camino similar.&lt;/li>
&lt;/ul>
&lt;h2 id="la-nueva-frontera-ebpf-y-los-agentes-de-ia">La nueva frontera: eBPF y los agentes de IA&lt;/h2>
&lt;p>Hasta aquí el contenido clásico de Hubble. Pero hay un giro 2026 que merece la pena cubrir porque cierra el círculo con la otra serie de este blog.&lt;/p>
&lt;p>Cuando un cluster Kubernetes empieza a ejecutar &lt;strong>agentes de IA&lt;/strong> —Claude Code, Gemini CLI, agentes basados en LangGraph que llaman APIs y MCP servers—, el problema de observabilidad cambia de forma. Ya no basta con saber &amp;ldquo;qué pod habló con qué pod&amp;rdquo; (eso es Hubble) ni &amp;ldquo;qué proceso ejecutó qué&amp;rdquo; (eso es Tetragon). Necesitas saber:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>A qué APIs externas está llamando el agente&lt;/strong> y con qué prompts.&lt;/li>
&lt;li>&lt;strong>Qué herramientas MCP está invocando&lt;/strong>, con qué argumentos.&lt;/li>
&lt;li>&lt;strong>Cuántos tokens consume&lt;/strong>, qué modelo elige, cuánto cuesta.&lt;/li>
&lt;li>&lt;strong>Si el agente se desvía&lt;/strong> del comportamiento esperado (out-of-policy queries, intentos de jailbreak, leakage de secretos).&lt;/li>
&lt;/ul>
&lt;p>Las soluciones tradicionales —instrumentar el código del agente con OpenTelemetry, parsear logs estructurados— no funcionan bien cuando el agente es un binario de terceros (Claude Code de Anthropic, Gemini CLI de Google) o cuando los MCP servers viven en otros lenguajes con stdio como transport.&lt;/p>
&lt;h3 id="agentsight-zero-instrumentation-para-agentes-llm">AgentSight: zero-instrumentation para agentes LLM&lt;/h3>
&lt;p>&lt;a href="https://github.com/eunomia-bpf/agentsight">&lt;strong>AgentSight&lt;/strong>&lt;/a> (proyecto del grupo &lt;code>eunomia-bpf&lt;/code>, mismo ecosistema de varios runtimes eBPF de alto perfil) ataca este problema con la misma filosofía que Hubble: &lt;strong>no instrumentes; escucha&lt;/strong>. Pone hooks eBPF en dos puntos críticos:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>uprobes en bibliotecas SSL/TLS&lt;/strong> (&lt;code>libssl&lt;/code>, &lt;code>boringssl&lt;/code>, &lt;code>rustls&lt;/code>). Captura el plaintext &lt;strong>antes&lt;/strong> del cifrado en send y &lt;strong>después&lt;/strong> del descifrado en recv. Para una llamada HTTP a &lt;code>https://api.anthropic.com/v1/messages&lt;/code>, AgentSight ve el JSON completo del prompt y la respuesta &lt;strong>sin descifrar nada en transit&lt;/strong>, simplemente porque ha llegado al nivel del syscall antes de que la TLS layer haga su trabajo.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>stdiocap&lt;/code> BPF&lt;/strong>: captura &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. Esto es lo que permite observar &lt;strong>MCP servers que hablan stdio&lt;/strong> con su cliente —el patrón habitual de los servers MCP locales—. Capturas el JSON-RPC que va y viene sin que ni el cliente ni el server lo sepan.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Sobrecarga reportada: &lt;strong>&amp;lt;3% CPU&lt;/strong>, comparable a Hubble en su régimen.&lt;/p>
&lt;h3 id="cómo-encaja-con-hubble-y-tetragon">Cómo encaja con Hubble y Tetragon&lt;/h3>
&lt;p>Los tres se complementan limpiamente:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hubble&lt;/strong> te dice: &amp;ldquo;el pod del agente abrió conexión TCP a &lt;code>api.anthropic.com:443&lt;/code> con verdict ALLOW&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Tetragon&lt;/strong> te dice: &amp;ldquo;el proceso &lt;code>claude-code&lt;/code> con PID 1843 hizo &lt;code>connect()&lt;/code> a esa IP&amp;rdquo; (más el binario, los argumentos, el namespace de pod).&lt;/li>
&lt;li>&lt;strong>AgentSight&lt;/strong> te dice: &amp;ldquo;el contenido HTTPS de esa conexión era un prompt &lt;code>messages=[{role:'user', content:'analyze this repo and modify the firewall config'}]&lt;/code> y la respuesta incluyó una tool call a &lt;code>read_file&lt;/code> con argument &lt;code>/etc/passwd&lt;/code>&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;p>Es la diferencia entre &lt;strong>flujo, proceso y semántica&lt;/strong>. Para un equipo de seguridad que quiera vigilar agentes de IA en producción, los tres son necesarios. Para alguien que quiera entender el coste, los tres son útiles (Hubble para latencia de red, Tetragon para uso de recursos, AgentSight para tokens y modelo elegido).&lt;/p>
&lt;h3 id="casos-de-uso-emergentes">Casos de uso emergentes&lt;/h3>
&lt;p>Los patrones que se están consolidando en 2026:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Audit trail de agentes&lt;/strong>: registrar cada llamada a LLM y cada tool call para compliance (sobre todo en sectores regulados).&lt;/li>
&lt;li>&lt;strong>Detección de jailbreak y prompt injection&lt;/strong>: aplicar reglas sobre los prompts capturados por AgentSight (similar a las TracingPolicy de Tetragon, pero sobre contenido semántico).&lt;/li>
&lt;li>&lt;strong>Cost accountability&lt;/strong>: ver qué team/agente consume qué tokens, sin instrumentar.&lt;/li>
&lt;li>&lt;strong>Replay y debug&lt;/strong>: reproducir el reasoning de un agente en producción sin pedirle que vuelva a ejecutar (que es no-determinístico).&lt;/li>
&lt;/ul>
&lt;p>Es un campo joven —AgentSight tiene meses, no años— pero el patrón &amp;ldquo;eBPF como observabilidad zero-instrumentation&amp;rdquo; está clarísimamente extendiéndose más allá de red y proceso. El próximo año va a ver consolidación y, probablemente, integración nativa con Hubble.&lt;/p>
&lt;h2 id="casos-de-uso-habituales-de-hubble">Casos de uso habituales de Hubble&lt;/h2>
&lt;p>Volviendo a Hubble propiamente, los casos en los que cualquier organización lo despliega:&lt;/p>
&lt;h3 id="1-debug-de-networkpolicy">1. Debug de NetworkPolicy&lt;/h3>
&lt;p>El uso clásico: &amp;ldquo;este pod no llega a este Service&amp;rdquo;. Sin Hubble, tocaba SSH, tcpdump, comparar reglas. Con Hubble:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">hubble observe --from-pod prod-api/checkout --to-pod prod-db/postgres --verdict DROPPED
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Si hay drops, ves la policy responsable (Cilium 1.19+). Si no hay drops, el problema no es policy: es DNS, routing o el target service.&lt;/p>
&lt;h3 id="2-audit-de-comunicación-inter-namespace">2. Audit de comunicación inter-namespace&lt;/h3>
&lt;p>Para compliance: validar que namespaces aislados no están comunicándose contra lo declarado.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">hubble observe --from-namespace prod-payments --to-namespace &lt;span class="s1">&amp;#39;NOT prod-db&amp;#39;&lt;/span> --output json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-detección-de-exfiltración">3. Detección de exfiltración&lt;/h3>
&lt;p>Tráfico saliente a destinos públicos sospechosos. Hubble los detecta por IP/SNI, no por payload (que está cifrado):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">hubble observe --to-fqdn &lt;span class="s1">&amp;#39;NOT *.example.com&amp;#39;&lt;/span> --to-fqdn &lt;span class="s1">&amp;#39;NOT *.internal&amp;#39;&lt;/span> --protocol tcp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Combinado con métricas Prometheus y alertas en Grafana, esto da un radar de exfiltración a coste cero.&lt;/p>
&lt;h3 id="4-slo-de-servicio-en-tiempo-real">4. SLO de servicio en tiempo real&lt;/h3>
&lt;p>Métricas &lt;code>hubble:http:response_time_seconds&lt;/code> con labels &lt;code>source_workload&lt;/code>, &lt;code>destination_workload&lt;/code>, &lt;code>method&lt;/code>, &lt;code>status_code&lt;/code> permiten dashboards SLO sin necesidad de instrumentar las apps. El SRE ve la latencia p95 de &lt;code>checkout → catalog&lt;/code> directamente.&lt;/p>
&lt;h3 id="5-performance-debugging">5. Performance debugging&lt;/h3>
&lt;p>&lt;code>hubble:tcp:retransmissions_total&lt;/code> y &lt;code>hubble:tcp:flags_total{flag=&amp;quot;RST&amp;quot;}&lt;/code> son señales tempranas de problemas de red. Una subida correlada con regresión de latencia te apunta a algo en infraestructura (NIC, switch, MTU) antes de bajar a investigar la app.&lt;/p>
&lt;h3 id="6-forensics-post-incidente">6. Forensics post-incidente&lt;/h3>
&lt;p>Configurar Hubble para exportar flows a almacenamiento persistente (vía OTLP a Tempo/Loki, o &lt;code>hubble observe --output jsonpb&lt;/code> a S3) te da capacidad forense: si en T+30 días detectas que algo iba mal en T, puedes reconstruir el tráfico.&lt;/p>
&lt;h2 id="hubble-y-el-resto-del-stack-de-observabilidad">Hubble y el resto del stack de observabilidad&lt;/h2>
&lt;p>Hubble no reemplaza Prometheus, Loki, Tempo ni Jaeger; los complementa:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Prometheus&lt;/strong>: recibe las métricas agregadas de Hubble. Hubble exporta endpoint Prometheus nativo.&lt;/li>
&lt;li>&lt;strong>Loki&lt;/strong>: recibe los flow logs estructurados si los exportas como logs. Hubble no tiene exporter nativo a Loki, pero un Fluent Bit con plugin OTLP o uno custom hace el puente fácilmente.&lt;/li>
&lt;li>&lt;strong>Tempo / Jaeger&lt;/strong>: el Cilium Operator tiene exportador OTLP de flows en formato traces (cada flujo HTTP/gRPC es un span). Integra con Tempo o cualquier otro tracing backend OTLP.&lt;/li>
&lt;li>&lt;strong>Grafana&lt;/strong>: ya hay dashboards públicos de Hubble. Combinados con Prometheus, Loki y Tempo, te dan un panel unificado: métricas, logs, traces, todo correlado por labels K8s.&lt;/li>
&lt;/ul>
&lt;p>La pila full-stack que se ve en producción 2026 (descrita en &lt;a href="https://dev.to/x4nent/building-a-production-ebpf-observability-security-stack-for-kubernetes-in-2026-5051">Building a Production eBPF Observability &amp;amp; Security Stack for Kubernetes in 2026&lt;/a>):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Datos&lt;/strong>: Cilium + Hubble (red), Tetragon (proceso), AgentSight (agente IA).&lt;/li>
&lt;li>&lt;strong>Pipeline&lt;/strong>: OTLP Collector como router único.&lt;/li>
&lt;li>&lt;strong>Almacenamiento&lt;/strong>: Prometheus (métricas), Loki (logs), Tempo (traces).&lt;/li>
&lt;li>&lt;strong>UI&lt;/strong>: Grafana con dashboards específicos por dominio.&lt;/li>
&lt;li>&lt;strong>Alerting&lt;/strong>: AlertManager con reglas sobre las métricas Hubble + Tetragon.&lt;/li>
&lt;/ul>
&lt;h2 id="comparativa-con-alternativas">Comparativa con alternativas&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sistema&lt;/th>
&lt;th>Capa&lt;/th>
&lt;th>Foco&lt;/th>
&lt;th>Modelo&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Hubble&lt;/strong>&lt;/td>
&lt;td>L3-L7 red&lt;/td>
&lt;td>Cluster K8s con Cilium&lt;/td>
&lt;td>eBPF, pull metrics, push flows gRPC&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GKE Dataplane v2 obs&lt;/strong>&lt;/td>
&lt;td>L3-L7 red&lt;/td>
&lt;td>GKE managed&lt;/td>
&lt;td>eBPF (Cilium-based, gestionado)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tigera Calico Whisker&lt;/strong>&lt;/td>
&lt;td>L3-L7 red&lt;/td>
&lt;td>Cluster con Calico&lt;/td>
&lt;td>eBPF + pcap, UI propia&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tetragon&lt;/strong>&lt;/td>
&lt;td>Proceso/syscall&lt;/td>
&lt;td>Cluster K8s&lt;/td>
&lt;td>eBPF, push events gRPC&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Falco&lt;/strong>&lt;/td>
&lt;td>Proceso/syscall&lt;/td>
&lt;td>Cluster K8s&lt;/td>
&lt;td>eBPF en userspace o módulo kernel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AgentSight&lt;/strong>&lt;/td>
&lt;td>Agente LLM&lt;/td>
&lt;td>Sistemas agentic&lt;/td>
&lt;td>eBPF (SSL uprobes + stdio)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Beyla&lt;/strong> (Grafana)&lt;/td>
&lt;td>Aplicación&lt;/td>
&lt;td>App L7 + tracing&lt;/td>
&lt;td>eBPF (uprobes en libs)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Pixie&lt;/strong>&lt;/td>
&lt;td>App + sistema&lt;/td>
&lt;td>Visibilidad cluster amplia&lt;/td>
&lt;td>eBPF + script PXL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Parca&lt;/strong>&lt;/td>
&lt;td>Profiling CPU/mem&lt;/td>
&lt;td>Performance&lt;/td>
&lt;td>eBPF profile sampling&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Si tu CNI es Cilium, &lt;strong>Hubble es el punto de entrada natural&lt;/strong> y no compite con los demás: complementa. Para clusters Calico, Whisker es el equivalente. Para profiling, Parca. Para agentes IA, AgentSight. La era del &amp;ldquo;una herramienta para todo&amp;rdquo; está pasando: la pila moderna combina varias piezas especializadas, todas basadas en eBPF, expuestas vía OTLP.&lt;/p>
&lt;h2 id="trampas-operativas">Trampas operativas&lt;/h2>
&lt;h3 id="cardinalidad-en-prometheus">Cardinalidad en Prometheus&lt;/h3>
&lt;p>Las métricas Hubble con todos los labels K8s pueden explotar Prometheus. &lt;strong>Mide la cardinalidad antes de exportar todo&lt;/strong>. Las métricas más prolíficas son &lt;code>flow&lt;/code> y &lt;code>httpV2&lt;/code>; empieza por &lt;code>drop&lt;/code> y &lt;code>port-distribution&lt;/code> y añade el resto incrementalmente.&lt;/p>
&lt;h3 id="l7-visibility-cuesta-cpu">L7 visibility cuesta CPU&lt;/h3>
&lt;p>Activar parsing L7 vía Envoy embebido añade carga al agent (no al datapath base, pero sí al envoy proxy del nodo). Para tráfico HTTP intenso, mide. Para flujos donde solo necesitas L4, deja Envoy desactivado.&lt;/p>
&lt;h3 id="hubble-relay-sin-ha">Hubble Relay sin HA&lt;/h3>
&lt;p>Una sola réplica de Relay es un single point of failure para CLI y UI (no para el agent local, que sigue funcionando). Para producción, deploy con &lt;code>replicas: 2+&lt;/code> y &lt;code>topologySpreadConstraints&lt;/code> para que no caigan ambas.&lt;/p>
&lt;h3 id="encryption-status-reporting-depende-de-cilium-config">Encryption status reporting depende de Cilium config&lt;/h3>
&lt;p>El nuevo filtro &lt;code>--encryption-status&lt;/code> solo da datos reales si Cilium tiene encryption activado (WireGuard o IPsec). Sin esto, todo es &lt;code>unencrypted&lt;/code> y el filtro no aporta.&lt;/p>
&lt;h3 id="ui-expuesta-sin-auth">UI expuesta sin auth&lt;/h3>
&lt;p>Hubble UI no tiene auth nativa. Si la expones por Ingress, &lt;strong>delante tiene que haber autenticación&lt;/strong>: OIDC vía oauth2-proxy, mTLS, IP allowlist. No es opcional.&lt;/p>
&lt;h3 id="storage-no-escalado">Storage no escalado&lt;/h3>
&lt;p>Si guardas flows durante días para forensics, el volumen es serio. Para un cluster de 100 pods activos, fácilmente 1-10 GB/día de flow logs. Plantea el ciclo de vida (compactación, retención, cold storage) antes de habilitarlo.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto">Lo que no hemos cubierto&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Mesh / multi-cluster Hubble&lt;/strong>: agregar flows de varios clusters Cilium en una sola Relay. Caso de uso: visión cross-cluster, debug de service mesh distribuido.&lt;/li>
&lt;li>&lt;strong>&lt;code>hubble export&lt;/code>&lt;/strong>: persistencia local en disco del agent para forensics con baja retención.&lt;/li>
&lt;li>&lt;strong>Anomaly detection con modelos propios&lt;/strong>: cómo conectar el stream gRPC a un consumer ML personalizado.&lt;/li>
&lt;li>&lt;strong>AgentSight en profundidad&lt;/strong>: el proyecto merece su propio artículo. Próxima entrega.&lt;/li>
&lt;li>&lt;strong>eBPF para profiling de LLM serving&lt;/strong>: cómo medir TTFT, TPOT y throughput de vLLM sin instrumentar, usando uprobes en libcudart.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;p>Hubble y Cilium:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/cilium/hubble">Hubble GitHub&lt;/a> — repo principal.&lt;/li>
&lt;li>&lt;a href="https://docs.cilium.io/en/stable/observability/hubble/">Hubble — Network Observability (Cilium docs)&lt;/a> — referencia oficial.&lt;/li>
&lt;li>&lt;a href="https://www.infoq.com/news/2026/02/cilium-119/">Cilium 1.19 release notes (InfoQ, feb 2026)&lt;/a> — décimo aniversario y novedades 1.19.&lt;/li>
&lt;li>&lt;a href="https://github.com/cilium/cilium/releases">Cilium releases&lt;/a> — todos los releases.&lt;/li>
&lt;li>&lt;a href="https://grafana.com/grafana/dashboards/19423-hubble-l7-http-metrics-by-workload/">Hubble L7 HTTP Metrics — Grafana dashboard 19423&lt;/a> — listo para importar.&lt;/li>
&lt;li>&lt;a href="https://cloud-cod.com/index.php/2026/03/03/end-to-end-l7-visibility-with-cilium-hubble/">End‑to‑end L7 Visibility with Cilium Hubble (cloud-cod.com, mar 2026)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.youngju.dev/blog/cilium/cilium_hubble_observability.en">Cilium Hubble Observability Platform Internal Analysis (Young-ju)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://johal.in/ciliumnetworkpolicy-python-hubble-l7-visibility-2026/">CiliumNetworkPolicy Python Hubble: L7 Visibility 2026&lt;/a> — uno de los hilos del SDK Python.&lt;/li>
&lt;/ul>
&lt;p>Estado del arte 2026 y stack completo:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/x4nent/building-a-production-ebpf-observability-security-stack-for-kubernetes-in-2026-5051">Building a Production eBPF Observability &amp;amp; Security Stack for Kubernetes in 2026 (DEV)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.cloudraft.io/blog/ebpf-based-network-observability-using-cilium-hubble">eBPF-Based Network Observability: Exploring Cilium Hubble and Alternatives (CloudRaft)&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>eBPF + agentes IA:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/eunomia-bpf/agentsight">AgentSight (GitHub eunomia-bpf)&lt;/a> — el proyecto referenciado.&lt;/li>
&lt;li>&lt;a href="https://klizosolutions.medium.com/harnessing-ebpf-for-high-performance-llm-workloads-a-cloud-native-guide-efb7d73e19ed">Harnessing eBPF for High‑Performance LLM Workloads (Klizo Solutions)&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Cross-references:&lt;/p>
&lt;ul>
&lt;li>Parte 1: &lt;a href="https://blog.lo0.es/posts/ebpf-cilium-tcp-ip-bypass/">eBPF de cero a Cilium&lt;/a>.&lt;/li>
&lt;li>Parte 2: &lt;a href="https://blog.lo0.es/posts/tetragon-runtime-security/">Tetragon: el primo de seguridad de Cilium&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 K8s&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> — donde el tráfico que Hubble observa lleva los prompts que AgentSight inspecciona.&lt;/li>
&lt;/ul></description></item></channel></rss>