{"id":831,"date":"2026-07-08T01:26:00","date_gmt":"2026-07-07T23:26:00","guid":{"rendered":"https:\/\/atlaszn.com\/blog\/?p=831"},"modified":"2026-07-06T02:40:45","modified_gmt":"2026-07-06T00:40:45","slug":"construir-mcp-server","status":"publish","type":"post","link":"https:\/\/atlaszn.com\/blog\/construir-mcp-server\/","title":{"rendered":"C\u00f3mo construir un MCP Server"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introducci\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El <a href=\"https:\/\/atlaszn.com\/blog\/model-context-protocol\/\" data-type=\"post\" data-id=\"713\">Model Context Protocol<\/a> (MCP) es un protocolo abierto que estandariza la comunicaci\u00f3n entre<br>LLMs y herramientas externas. En lugar de construir integraciones ad-hoc para cada agente,<br>MCP define una interfaz \u00fanica: el agente descubre tools, las invoca con JSON-RPC 2.0, y el<br>servidor ejecuta la l\u00f3gica.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este art\u00edculo explico c\u00f3mo construimos un MCP server que integra cuatro servicios<br>complementarios para proporcionar a cualquier LLM (Claude, GPT, Gemini, etc.) capacidades de b\u00fasqueda, navegaci\u00f3n web interactiva, extracci\u00f3n de contenido y OCR mediante una \u00fanica interfaz MCP:<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li><strong>SearXNG<\/strong>: metab\u00fasqueda privada y autogestionada<\/li>\n\n\n\n<li><strong>Firecrawl<\/strong>: scraping, crawling y extracci\u00f3n estructurada<\/li>\n\n\n\n<li><strong>Camofox<\/strong>: navegaci\u00f3n headless con anti-detecci\u00f3n<\/li>\n\n\n\n<li><strong>OCR iOS<\/strong>: reconocimiento de texto local v\u00eda Apple Vision Framework<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Arquitectura general<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El servidor MCP act\u00faa como un&nbsp;<strong>proxy unificado<\/strong>&nbsp;entre el LLM y los servicios. No expone<br>los servicios directamente, sino que ofrece tools que el LLM invoca seg\u00fan necesidad.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"683\" src=\"https:\/\/atlaszn.com\/blog\/wp-content\/uploads\/2026\/07\/diagrama_mcp-1024x683.webp\" alt=\"\" class=\"wp-image-832\" srcset=\"https:\/\/atlaszn.com\/blog\/wp-content\/uploads\/2026\/07\/diagrama_mcp-1024x683.webp 1024w, https:\/\/atlaszn.com\/blog\/wp-content\/uploads\/2026\/07\/diagrama_mcp-300x200.webp 300w, https:\/\/atlaszn.com\/blog\/wp-content\/uploads\/2026\/07\/diagrama_mcp-768x512.webp 768w, https:\/\/atlaszn.com\/blog\/wp-content\/uploads\/2026\/07\/diagrama_mcp.webp 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Cada tool del MCP server llama a su servicio v\u00eda HTTP. El LLM nunca ve los servicios<br>directamente, solo ve las tools con sus nombres, descripciones y par\u00e1metros.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Decisiones de dise\u00f1o <\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. FastMCP vs MCP SDK raw<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Usamos&nbsp;<code>FastMCP<\/code>&nbsp;del SDK oficial (<code>mcp.server.fastmcp<\/code>). FastMCP es una capa sobre el<br>protocolo MCP que elimina boilerplate: genera autom\u00e1ticamente el JSON Schema de cada tool<br>a partir de type hints y docstrings, maneja el lifecycle del protocolo, y soporta m\u00faltiples<br>transportes sin configuraci\u00f3n adicional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">FastMCP abstrae el protocolo, lo que es bueno para productividad pero limita<br>el control fino sobre mensajes JSON-RPC individuales. Para nuestro caso (tools simples sin<br>streaming) parece una decisi\u00f3n correcta.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Transporte: stdio vs Streamable HTTP<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Soportamos ambos.&nbsp;<code>stdio<\/code>&nbsp;es ideal cuando el cliente MCP (Claude Desktop, Cursor) lanza el<br>server como subproceso local.&nbsp;<code>streamable-http<\/code>&nbsp;es necesario cuando el server corre en<br>una m\u00e1quina remota.<\/p>\n\n\n<div class=\"acb-callout acb-callout--default\"><div class=\"acb-callout__content\"><strong>Tradeoff importante:<\/strong>\u00a0stdio\u00a0es m\u00e1s simple y seguro (no requiere puertos abiertos), pero<br>ata el ciclo de vida del server al del cliente. HTTP permite servir a m\u00faltiples clientes,<br>pero requiere configuraci\u00f3n de red, firewalls y posiblemente autenticaci\u00f3n.<\/div><\/div>\n\n\n<p class=\"wp-block-paragraph\">En una topolog\u00eda con el LLM en&nbsp;<code>10.0.1.5<\/code>, los servicios en&nbsp;<code>10.0.1.8<\/code>&nbsp;y el MCP server en otra m\u00e1quina, ambas opciones son viables. La elecci\u00f3n depende principalmente de la infraestructura disponible y de si el servidor ser\u00e1 utilizado por uno o varios clientes.<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li><strong>Opci\u00f3n A (SSH):<\/strong>&nbsp;el cliente en&nbsp;<code>10.0.1.5<\/code>&nbsp;lanza&nbsp;<code>ssh user@mcp-server python server.py<\/code><br>como comando stdio. No requiere abrir puertos.<\/li>\n\n\n\n<li><strong>Opci\u00f3n B (HTTP):<\/strong>&nbsp;el server corre con&nbsp;<code>--transport streamable-http --port 8001<\/code>&nbsp;y el cliente se conecta a&nbsp;<code>http:\/\/mcp-server:8001\/mcp<\/code>. Requiere el puerto accesible.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Cliente HTTP global vs por request<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Una de las primeras correcciones que hicimos fue centralizar el cliente HTTP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http_client = httpx.AsyncClient(\n    timeout=httpx.Timeout(connect=10.0, read=30.0, write=30.0, pool=30.0),\n    limits=httpx.Limits(max_keepalive_connections=20, max_connections=100),\n)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Antes<\/strong>&nbsp;cada request creaba su propio&nbsp;<code>AsyncClient<\/code>, lo que implicaba:<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li>Negociaci\u00f3n TCP en cada llamada (handshake + TLS)<\/li>\n\n\n\n<li>Sin reuso de conexiones<\/li>\n\n\n\n<li>Mayor latencia y carga en los servicios<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Despu\u00e9s<\/strong>&nbsp;con un cliente global con pool de conexiones keep-alive:<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li>Las conexiones se reusan entre requests<\/li>\n\n\n\n<li>El l\u00edmite&nbsp;<code>max_connections=100<\/code>&nbsp;permite controlar el n\u00famero m\u00e1ximo de conexiones simult\u00e1neas abiertas por el cliente HTTP. El valor adecuado depender\u00e1 de la capacidad de los servicios de destino y de la carga esperada.<\/li>\n\n\n\n<li>Se define un timeout granular por operaci\u00f3n (connect, read, write, pool)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">El cliente global complica el shutdown (hay que cerrarlo expl\u00edcitamente al<br>salir). Lo resolvemos con un&nbsp;<code>try\/finally<\/code>&nbsp;en el bloque&nbsp;<code>__main__<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. SSRF protecci\u00f3n resolviendo DNS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Aceptar URLs arbitrarias del LLM implica riesgo de Server-Side Request Forgery (SSRF).<br>Un LLM malicioso o comprometido podr\u00eda intentar acceder a&nbsp;<code>http:\/\/169.254.169.254\/latest\/meta-data\/<\/code><br>(metadata cloud) o a servicios internos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nuestra validaci\u00f3n:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Verifica esquema HTTP\/HTTPS solamente<\/li>\n\n\n\n<li>Resuelve el hostname a IP con&nbsp;<code>socket.gethostbyname()<\/code><\/li>\n\n\n\n<li>Bloquea IPs privadas, loopback y link-local con&nbsp;<code>ipaddress<\/code>&nbsp;module<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>def _es_ip_privada(host):\n    ip = ipaddress.ip_address(host)\n    return ip.is_private or ip.is_loopback or ip.is_link_local\n  \ndef _resolver_y_validar(host):\n    ip = socket.gethostbyname(host)\n    if _es_ip_privada(ip):\n        raise ValueError(f\"Host {host} resuelve a IP privada {ip}\")<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tradeoff:<\/strong>&nbsp;Resolver DNS en cada llamada a\u00f1ade una peque\u00f1a latencia adicional, cuyo impacto depender\u00e1 de la cach\u00e9 DNS del sistema operativo, la red y el resolvedor utilizado. Validar \u00fanicamente el formato del hostname no es suficiente frente a determinados escenarios como DNS rebinding. Resolver el nombre antes de comprobar la IP reduce significativamente la superficie de ataque frente a una validaci\u00f3n basada \u00fanicamente en el hostname, aunque no elimina todos los escenarios de SSRF (por ejemplo DNS rebinding o cambios de resoluci\u00f3n posteriores a la validaci\u00f3n).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. M\u00faltiples servicios unificados vs separados<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos haber creado 4 servidores MCP independientes (uno por servicio). En su lugar<br>los unificamos en un solo server con tools nombradas por prefijo&nbsp;<code>searxng_*<\/code>,&nbsp;<code>firecrawl_*<\/code>,<br><code>camofox_*<\/code>,&nbsp;<code>ocr_*<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ventajas de unificarlos:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li>Una sola configuraci\u00f3n para el usuario<\/li>\n\n\n\n<li>Un solo proceso que gestionar<\/li>\n\n\n\n<li>El LLM ve todas las tools disponibles sin cambiar de \u00abcontexto\u00bb<\/li>\n\n\n\n<li>Las tools pueden combinarse en un mismo flujo de razonamiento<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Desventajas:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li>Si un servicio falla (ej. Camofox ca\u00eddo), todas las tools aparecen como disponibles<br>pero pueden fallar<\/li>\n\n\n\n<li>Mayor acoplamiento: actualizar la l\u00f3gica de un servicio requiere tocar el mismo archivo<\/li>\n\n\n\n<li>Un solo punto de fallo<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">En despliegues donde distintos equipos gestionan cada servicio o donde se requiere aislamiento entre componentes, separar los servidores puede simplificar la operaci\u00f3n. En otros escenarios, especialmente para uso personal o equipos peque\u00f1os, un \u00fanico MCP resulta m\u00e1s sencillo de administrar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6. Retries autom\u00e1ticos<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Inicialmente configuramos&nbsp;<code>httpx.AsyncHTTPTransport(retries=2)<\/code>&nbsp;a nivel de transporte.<br>Lo eliminamos al descubrir que retry autom\u00e1tico en POST requests puede causar efectos<br>secundarios graves: doble crawl, doble extracci\u00f3n, doble navegaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Decisi\u00f3n final:<\/strong>&nbsp;sin retries autom\u00e1ticos. Cada tool maneja sus errores espec\u00edficos<br>(timeout, HTTP error, conexi\u00f3n) con mensajes claros para el LLM, que puede decidir<br>reintentar o no. Esto no significa que los retries sean siempre una mala idea. En operaciones idempotentes (por ejemplo, muchas peticiones GET) pueden mejorar la resiliencia del sistema. En nuestro caso preferimos deshabilitarlos globalmente para evitar efectos secundarios en operaciones que modifican estado.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7. OCR: URL vs Base64<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El servidor OCR expone un endpoint HTTP sencillo (<code>POST \/upload<\/code>) que recibe im\u00e1genes mediante una petici\u00f3n multipart.<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li><code>ocr_imagen_url<\/code>: descarga la imagen desde una URL y la reenv\u00eda al OCR<\/li>\n\n\n\n<li><code>ocr_imagen_base64<\/code>: recibe la imagen como base64 directamente<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">URL es m\u00e1s eficiente (no pasa datos binarios por MCP), pero requiere que<br>la imagen sea accesible p\u00fablicamente. Base64 funciona para cualquier imagen (capturas<br>locales, fotos reci\u00e9n tomadas, im\u00e1genes generadas por el LLM), pero puede saturar<br>el contexto si la imagen es grande. Nuestro l\u00edmite pr\u00e1ctico ser\u00e1n ~20MB.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8. Observabilidad<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cada herramienta envuelve sus llamadas HTTP con:<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li><strong>Request ID<\/strong>&nbsp;(UUID hex de 8 chars) para correlacionar logs<\/li>\n\n\n\n<li><strong>Timing<\/strong>&nbsp;con&nbsp;<code>time.monotonic()<\/code>&nbsp;para medir latencia<\/li>\n\n\n\n<li><strong>Logging estructurado<\/strong>&nbsp;con niveles (info para \u00e9xito, error para fallos)<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>log.info(\"&#91;%s] GET %s \u2192 %s (%dms)\", rid, url, resp.status_code, int(elapsed * 1000))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El logging detallado agrega overhead de I\/O. En nuestro escenario de uso, donde predominan pocas peticiones relativamente costosas (navegaci\u00f3n, OCR o scraping), el coste adicional del logging resulta peque\u00f1o frente al tiempo total de ejecuci\u00f3n. En sistemas de mayor volumen podr\u00edan ser preferibles estrategias como logging as\u00edncrono, muestreo o agregaci\u00f3n de eventos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9. Truncado de respuestas<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Aunque el protocolo MCP no impone un l\u00edmite estricto para las respuestas de las herramientas, en la pr\u00e1ctica el tama\u00f1o \u00fatil est\u00e1 condicionado por el cliente MCP, el modelo utilizado y la ventana de contexto disponible. Por ello implementamos dos estrategias de truncado:<\/p>\n\n\n\n<ul class=\"wp-block-list\" class=\"wp-block-list\">\n<li><strong>Por caracteres<\/strong>&nbsp;(<code>truncar_por_caracteres<\/code>): corta a 8000 chars. Ideal para texto<br>plano (OCR, contenido de p\u00e1ginas).<\/li>\n\n\n\n<li><strong>Por l\u00edneas<\/strong>&nbsp;(<code>truncar_por_lineas<\/code>): corta a 80 l\u00edneas. Ideal para listas de<br>resultados (b\u00fasquedas, mapas de sitio).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">El truncado incluye un mensaje \u00ab[Truncado \u2014 respuesta demasiado larga]\u00bb para que el LLM<br>sepa que la respuesta est\u00e1 incompleta y pueda pedir m\u00e1s si es necesario.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">10. Excepciones espec\u00edficas vs gen\u00e9ricas<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Evolucionamos de&nbsp;<code>except Exception as e<\/code>&nbsp;(que oculta bugs de l\u00f3gica y filtra informaci\u00f3n<br>interna al usuario) a manejo granular:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>except httpx.TimeoutException:\n    return \"Error: el servicio no respondi\u00f3 a tiempo\"\nexcept httpx.HTTPStatusError as e:\n    log.error(\"HTTP %s: %s\", e.response.status_code, e.response.text&#91;:300])\n    return \"Error: el servicio respondi\u00f3 con un c\u00f3digo de error\"\nexcept httpx.HTTPError as e:\n    log.error(\"Error de conexi\u00f3n: %s\", e)\n    return \"Error: no se pudo conectar con el servicio\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Principio:<\/strong>&nbsp;el usuario ve mensajes gen\u00e9ricos y accionables; los detalles t\u00e9cnicos<br>(IPs, stack traces, cuerpos de respuesta) van solo a los logs.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lecciones aprendidas<\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Nombrar el archivo&nbsp;<code>mcp.py<\/code>&nbsp;shadowea el paquete.<\/strong>&nbsp;Suena obvio, pero es el error<br>m\u00e1s com\u00fan al empezar con MCP en Python. El archivo debe llamarse cualquier otra cosa.<\/li>\n\n\n\n<li><strong>FastMCP tiene dos implementaciones habituales:<\/strong>&nbsp;la del SDK oficial (<code>mcp.server.fastmcp<\/code>) y la<br>standalone&nbsp;<code>fastmcp<\/code>&nbsp;de Prefect). Tienen APIs distintas. La oficial no tiene<br><code>on_shutdown<\/code>, la de Prefect s\u00ed.<\/li>\n\n\n\n<li><strong>El LLM no entiende de infraestructura.<\/strong>&nbsp;El modelo no dispone de conocimiento espec\u00edfico sobre la infraestructura de despliegue. Por ello las herramientas deben describirse en t\u00e9rminos funcionales (\u00abbuscar\u00bb, \u00abextraer\u00bb, \u00abnavegar\u00bb) y no mediante detalles internos de implementaci\u00f3n.<\/li>\n\n\n\n<li><strong>El contexto MCP es limitado.<\/strong>&nbsp;Las respuestas de tools deben ser concisas.<br>Un resultado de OCR con 5000 caracteres de texto es m\u00e1s \u00fatil que uno con 50000<br>que nunca llega al LLM porque excede el l\u00edmite del protocolo.<\/li>\n\n\n\n<li><strong>Protecci\u00f3n SSRF.<\/strong>&nbsp;Si un MCP server acepta URLs proporcionadas por el LLM, conviene incorporar protecci\u00f3n frente a SSRF desde el principio. Resolver el hostname antes de validar la IP evita una clase importante de ataques y representa una mejora significativa frente a validar \u00fanicamente el formato de la URL, aunque no sustituye otras medidas de defensa que puedan ser necesarias en entornos especialmente sensibles.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusi\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Construir un MCP server que integra b\u00fasqueda, navegaci\u00f3n web mediante un navegador real, extracci\u00f3n de contenido y OCR combina infraestructura, protocolos y dise\u00f1o de herramientas orientadas a agentes. La clave est\u00e1 en las decisiones de borde: c\u00f3mo truncar, c\u00f3mo autenticar, c\u00f3mo validar, cu\u00e1ndo retry, qu\u00e9 loggear.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El c\u00f3digo fuente completo est\u00e1 en el <a href=\"https:\/\/github.com\/FeR12J\/mcp\">repositorio<\/a>. Las tools expuestas son:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Servicio<\/th><th>Tools<\/th><\/tr><\/thead><tbody><tr><td>SearXNG<\/td><td><code>searxng_buscar<\/code><\/td><\/tr><tr><td>Firecrawl<\/td><td><code>firecrawl_obtener<\/code>,&nbsp;<code>firecrawl_buscar<\/code>,&nbsp;<code>firecrawl_mapa<\/code>,&nbsp;<code>firecrawl_rastrear<\/code>,&nbsp;<code>firecrawl_extraer<\/code><\/td><\/tr><tr><td>Camofox<\/td><td><code>camofox_navegar<\/code>,&nbsp;<code>camofox_obtener_instantanea<\/code>,&nbsp;<code>camofox_hacer_click<\/code>,&nbsp;<code>camofox_escribir<\/code>,&nbsp;<code>camofox_capturar_pantalla<\/code>,&nbsp;<code>camofox_transcribir_youtube<\/code>,&nbsp;<code>camofox_buscar<\/code><\/td><\/tr><tr><td>OCR iOS<\/td><td><code>ocr_imagen_url<\/code>,&nbsp;<code>ocr_imagen_base64<\/code>,&nbsp;<code>ocr_documento_url<\/code>,&nbsp;<code>ocr_documento_base64<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">En conjunto, las 16 herramientas proporcionan a cualquier LLM una interfaz unificada para buscar informaci\u00f3n, navegar por sitios web, interactuar con aplicaciones web, extraer contenido estructurado y realizar OCR, ampliando significativamente las capacidades del modelo para automatizar tareas sobre la web.<\/p>\n\n\n<div class=\"acb-callout acb-callout--default\"><div class=\"acb-callout__content\"><a href=\"https:\/\/atlaszn.com\/blog\/patrones-agenticos\/\">Patrones ag\u00e9nticos:<\/a> Una serie sobre los patrones de dise\u00f1o para construir sistemas inteligentes con modelos de lenguaje. Cada patr\u00f3n aborda un problema espec\u00edfico de arquitectura y se apoya en los anteriores.<\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>C\u00f3mo construir un servidor MCP que unifica SearXNG, Firecrawl, Camofox y OCR para dar a cualquier LLM acceso a b\u00fasqueda, scraping, navegaci\u00f3n web y reconocimiento de texto mediante una \u00fanica interfaz. Tambi\u00e9n detalla las principales decisiones de dise\u00f1o (arquitectura, seguridad, rendimiento y manejo de errores) y las lecciones aprendidas para crear un sistema robusto, seguro y f\u00e1cil de usar.<\/p>\n","protected":false},"author":1,"featured_media":834,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[135,50,6,24],"tags":[65,162,168,163,164,85,138,153,141,20,167,166,165],"class_list":["post-831","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-agentes","category-fundamentos","category-ia-automatizacion","category-ia-local","tag-agentes-de-ia","tag-arquitectura-de-software","tag-async-python","tag-backend","tag-firecrawl","tag-llm","tag-mcp","tag-model-context-protocol","tag-python","tag-searxng","tag-seguridad-en-apis","tag-ssrf","tag-web-scraping"],"_links":{"self":[{"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/posts\/831","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/comments?post=831"}],"version-history":[{"count":4,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/posts\/831\/revisions"}],"predecessor-version":[{"id":842,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/posts\/831\/revisions\/842"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/media\/834"}],"wp:attachment":[{"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/media?parent=831"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/categories?post=831"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/atlaszn.com\/blog\/wp-json\/wp\/v2\/tags?post=831"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}