Cómo construir servidores MCP con FastMCP (con una herramienta real respaldada por Crawl)
TL;DR
FastMCP es la forma más rápida de convertir una función de Python en una herramienta MCP. Un único decorador @mcp.tool en una función tipada genera el esquema JSON que un cliente MCP necesita, sin necesidad de manejar manualmente el protocolo.
Instala con un comando: pip install fastmcp. Este tutorial instaló y verificó FastMCP 3.4.7 directamente desde PyPI.
Un servidor mínimo necesita tres líneas de código real. Crea una instancia de FastMCP, decora una función con @mcp.tool y llama a mcp.run().
Los servidores FastMCP son probables sin un proceso de cliente separado. La clase fastmcp.Client puede conectarse directamente a un objeto servidor en el mismo proceso de Python, que este tutorial utilizó para verificar que cada ejemplo realmente se ejecuta.
Una herramienta que solo suma dos números no justifica construir un servidor. El ejemplo trabajado en este tutorial conecta una herramienta FastMCP a Nstproxy Crawl para que un cliente MCP pueda entregar una URL y recibir de vuelta Markdown limpio.
Llamar a una API externa real desde una herramienta significa manejar modos de falla reales. Las credenciales faltantes, las respuestas no-200 y los errores de red necesitan manejo explícito: este tutorial muestra la salida real de error capturada cuando una clave API no está configurada.
FastMCP es un superconjunto del SDK oficial de MCP para Python, no un competidor. FastMCP 1.0 se fusionó con el SDK oficial; el paquete fastmcp activamente desarrollado en PyPI es FastMCP 2.x, que agrega la capa de experiencia del desarrollador de la que este tutorial depende.
Introducción: por qué FastMCP es el punto de entrada práctico a los servidores MCP
FastMCP convierte una función de Python tipada en una herramienta que un agente de IA puede llamar, sin requerir que escribas manualmente el manejo de mensajes JSON-RPC. El Protocolo de Contexto de Modelo (MCP) define cómo una aplicación de IA — un "cliente MCP" como Claude Desktop, un asistente de IDE, o un agente personalizado — descubre y llama herramientas, recursos y avisos expuestos por un proceso de "servidor MCP" separado. Implementar ese protocolo a mano significa escribir generación de esquemas, enrutamiento de mensajes y plomería de transporte antes de que una sola herramienta haga algo útil.
FastMCP elimina esa plomería. Escribes una función de Python normal, agregas sugerencias de tipo, la decoras con @mcp.tool, y FastMCP genera el esquema de la herramienta, maneja el bucle de mensajes JSON-RPC y lo expone a través del transporte que elijas. La documentación oficial del Protocolo de Contexto de Modelo describe el MCP en sí como "un estándar de código abierto para conectar aplicaciones de IA a sistemas externos": FastMCP es el marco de Python que hace que construir el lado del servidor de esa conexión sea rápido, y su código fuente está publicado en el repositorio oficial de FastMCP en GitHub bajo una licencia Apache-2.0.
Este tutorial instala FastMCP de verdad, ejecuta un servidor mínimo y luego construye algo que un agente realmente necesitaría: una herramienta MCP que obtiene una URL a través de la API de Nstproxy Crawl y devuelve Markdown limpio al agente que llama. Cada bloque de código a continuación se ejecutó en un entorno Python real; donde un paso necesitaba una credencial que este artículo no puede proporcionar, esa brecha se revela en lugar de cubrirse con una respuesta fabricada.
Instalar FastMCP
Instale FastMCP con pip install fastmcp (o uv add fastmcp si su proyecto utiliza uv) dentro de un entorno de Python 3.10+. Este tutorial se ejecutó:
en un entorno Linux aislado y confirmó la instalación con pip show fastmcp, que reportó:
Name: fastmcp
Version: 3.4.7
Summary: La forma rápida y pythonica de construir servidores y clientes MCP.
Esa versión coincide con lo que la página de PyPI de FastMCP enumeraba como actual en el momento de escribir. requests se instala junto con FastMCP porque el ejemplo trabajado más adelante en este tutorial hace una llamada HTTP saliente a la API de Nstproxy Crawl. Estado: ejecutado-en-vivo — esta es la salida real del entorno aislado utilizado para escribir este artículo, no un número de versión copiado.
Configurar un servidor FastMCP mínimo
Un servidor FastMCP empieza a partir de una única instancia de FastMCP que nombra el servidor y contiene todas las herramientas que registre en él. Cree un archivo llamado hello_server.py:
from fastmcp import FastMCP
mcp = FastMCP("Servidor MCP Hola")@mcp.tooldefgreet(name:str)->str:"""Saludar a un usuario por su nombre."""returnf"Hola, {name}!"if __name__ =="__main__": mcp.run()
El decorador @mcp.tool lee las sugerencias de tipo de la función (name: str en, str out) y su docstring, luego construye el esquema JSON que un cliente MCP utiliza para saber cómo llamar a greet y qué esperar de vuelta — nunca escribes ese esquema a mano. mcp.run() sin argumentos inicia el servidor a través del transporte stdio, el transporte predeterminado que los clientes MCP como Claude Desktop utilizan para lanzar un servidor local como un subproceso. Estado: solo-configuración para esta cerca por sí sola — se ejercita realmente en la próxima sección.
Implementación básica: ejecutando y llamando al servidor
Ejecutar python hello_server.py inicia el servidor y bloquea, esperando a que un cliente MCP se conecte a través de stdio — no hay salida visible en ese modo por diseño, por lo que el útil paso de verificación es llamarlo con un cliente en lugar de mirar una terminal bloqueada. La propia clase Client de FastMCP puede conectarse a un objeto servidor directamente dentro del mismo proceso de Python, que es la forma más rápida de confirmar que una herramienta realmente funciona antes de conectar un cliente MCP real:
import asyncio
from fastmcp import Client
from hello_server import mcp
asyncdefmain():asyncwith Client(mcp)as client: tools =await client.list_tools()print("HERRAMIENTAS:",[t.name for t in tools]) result =await client.call_tool("greet",{"name":"Nstproxy"})print("RESULTADO:", result.data)if __name__ =="__main__": asyncio.run(main())
Estado: ejecutado-en-vivo — este es el stdout real capturado de la ejecución de ambos archivos juntos, confirmando que el registro de herramientas, la generación de esquemas y el camino de llamada funcionan de extremo a extremo antes de que cualquier API externa entre en la imagen.
Para ejecutar el mismo servidor como un proceso independiente en lugar de un cliente en proceso, simplemente llámelo directamente (python hello_server.py, transporte stdio) o inícielo a través de HTTP para acceso remoto:
mcp.run(transport="http", port=8000)
La interfaz de línea de comandos FastMCP ofrece la misma opción sin editar el archivo: fastmcp run hello_server.py:mcp para stdio, o fastmcp run hello_server.py:mcp --transport http --port 8000 para HTTP. La CLI importa el objeto del servidor directamente y no ejecuta el bloque if __name__ == "__main__":, por lo que esta protección es opcional cuando solo se inicia a través de la CLI. Estado: ilustrativo — sintaxis de transporte documentada del inicio rápido oficial de FastMCP, no ejecutada separadamente del camino de stdio ya verificado anteriormente.
Patrones avanzados: darle al servidor una herramienta que valga la pena llamar
Una herramienta que suma dos números demuestra que el decorador funciona, pero no le da a un agente una razón para ejecutar este servidor en lugar de simplemente hacer aritmética por sí mismo. El escenario que realmente necesita un servidor MCP es entregarle a un agente algo que no puede hacer por su cuenta: acceder a una API externa, recuperar una página o leer un sistema de archivos al que de otro modo no tiene acceso. El ejemplo trabajado de este tutorial es una herramienta crawl_url que llama al endpoint de raspado de una sola página de Nstproxy Crawl y devuelve el contenido de la página como Markdown, por lo que cualquier cliente MCP que se conecte a este servidor puede entregar una URL y recibir de vuelta texto que un LLM puede leer directamente.
El endpoint de raspado de una sola página de Nstproxy Crawl se encuentra en POST https://api.nstproxy.com/api/v1/crawl/scrape, autenticado con un encabezado x-api-key. Llamado sin parámetros adicionales, ese endpoint devuelve un ID de tarea inmediatamente con status: "processing" para sondeo asincrónico; agregar el parámetro de consulta async=true hace que el mismo endpoint espere y devuelva el resultado en una sola respuesta en su lugar, que es lo que necesita una llamada de herramienta MCP sincrónica. El cuerpo de la solicitud toma url, un array formats (markdown, html, rawData, screenshot, pdf), un timeout opcional en milisegundos, y onlyMainContent para eliminar la navegación y el contenido redundante. La respuesta incluye un campo markdown con el contenido limpio, o un token markdownRef en su lugar cuando el resultado es demasiado grande para incluirse en línea — resuelto por separado a través de GET /api/v1/crawl/storage/read?st={ref}. Estos detalles se confirman directamente contra la documentación de la API de Nstproxy Crawl en lugar de asumirse por el nombre del endpoint.
import os
import requests
from fastmcp import FastMCP
mcp = FastMCP("Servidor MCP de Nstproxy Crawl")NSTPROXY_API_KEY = os.environ.get("NSTPROXY_API_KEY","YOUR_API_KEY")CRAWL_ENDPOINT ="https://api.nstproxy.com/api/v1/crawl/scrape"@mcp.tooldefcrawl_url(url:str)->str:"""Recupera una URL a través de Nstproxy Crawl y devuelve Markdown limpio.
Requiere que NSTPROXY_API_KEY esté configurado. Llama al endpoint
de raspado de una sola página de Nstproxy Crawl con async=true para
obtener una respuesta inmediata y de estilo sincrónico.
"""try: response = requests.post( CRAWL_ENDPOINT, params={"async":"true"}, headers={"x-api-key": NSTPROXY_API_KEY,"Content-Type":"application/json",}, json={"url": url,"formats":["markdown"],"onlyMainContent":True,"timeout":60000,}, timeout=65,)except requests.RequestException as exc:returnf"La solicitud falló antes de recibir una respuesta: {exc}"if response.status_code !=200:return(f"Nstproxy Crawl devolvió HTTP {response.status_code}: "f"{response.text[:500]}") body = response.json()ifnot body.get("success",False):returnf"La solicitud de rastreo no tuvo éxito: {body}"return body.get("data",{}).get("markdown","(no se devolvió campo markdown)")if __name__ =="__main__": mcp.run()
Necesitarás tu propia clave de API de Nstproxy Crawl configurada como la variable de entorno NSTPROXY_API_KEY antes de que crawl_url pueda devolver contenido real de la página; sin una, la función aún se ejecuta, se registra como una herramienta con un esquema correcto y aún realiza una solicitud saliente real, pero esa solicitud no puede autenticarse. Conectar un Client de FastMCP a este servidor y listar sus herramientas funcionó exactamente de la misma manera que lo hizo el ejemplo de hello-world:
TOOLS: ['crawl_url']
Llamar a crawl_url contra https://example.com en el sandbox de este artículo — que no tiene configurada una clave de API de Nstproxy y también tiene acceso de red saliente restringido — produjo este error real capturado en lugar de una respuesta de éxito fabricada:
RESULTADO: La solicitud falló antes de que se recibiera una respuesta: HTTPSConnectionPool(host='api.nstproxy.com', port=443): Se superaron los máximos reintentos con la url: /api/v1/crawl/scrape?async=true (Causado por ProxyError('No se puede conectar al proxy', OSError('Falló la conexión a través del túnel: 403 Prohibido')))
Estado: brecha de requisito para la llamada de Crawl saliente específicamente: el registro de herramientas y la generación del esquema para crawl_url se ejecutaron en vivo, pero la recuperación de la página real no pudo completarse en este entorno porque no había una clave API real disponible para probar, y este artículo no fabricó una respuesta JSON para hacer que el ejemplo se viera más terminado de lo que es. En un despliegue normal con un NSTPROXY_API_KEY válido y acceso de red abierto, el mismo camino de código devuelve el campo markdown de una respuesta de Crawl de Nstproxy exitosa en lugar de esta cadena de error.
Límites honestos
FastMCP maneja la generación de esquemas, el transporte y el ciclo de solicitud/respuesta, pero no maneja lo que sucede dentro de la función de su herramienta; eso es Python común, con modos de fallo ordinarios. crawl_url arriba devuelve una cadena simple en cada camino, incluidos los caminos de fallo, porque los resultados de la herramienta MCP están destinados a ser legibles por el modelo que llama; elevar una excepción no capturada en su lugar se presentaría como un fallo genérico de llamada a la herramienta al cliente sin ninguno de los detalles de diagnóstico en la cadena. Una versión de producción de esta herramienta también debería limitar url a los esquemas esperados, establecer un tiempo de espera de solicitud más corto que la propia paciencia del cliente y decidir explícitamente si un 403/404 del sitio objetivo (ambos cobrables bajo la tarificación por cada recuperación de Crawl, ya que la recuperación en sí se completó) debería reintentarse o devolverse al llamador tal como está.
FastMCP en sí no gestiona límites de tasa de API, reintentos o autenticación para cualquier servicio externo que su herramienta llame; todo eso pertenece al propio código de su herramienta, exactamente como se muestra arriba. Tampoco valida el contenido de lo que una herramienta devuelve más allá de coincidir con su tipo declarado, por lo que una herramienta que promete -> str y devuelve Markdown mal formado aún pasará las verificaciones de FastMCP; validar la calidad de la salida es trabajo del autor de la herramienta.
Solución de problemas
Un servidor que se ejecuta pero informa cero herramientas generalmente significa que la función nunca fue decorada, o fue decorada en una instancia diferente de FastMCP a la que se pasó a mcp.run(); verifique que cada @mcp.tool esté directamente encima de una función y que solo exista una instancia de FastMCP() por archivo. Un cliente que puede listar herramientas pero falla en cada llamada es a menudo un desajuste de tipo de pista: si el esquema promete un int y el cliente envía una cadena que no se puede convertir, FastMCP rechazará la llamada antes de que el cuerpo de su función se ejecute. Cuando una herramienta que llama a una API externa no devuelve nada útil, verifique el estado y el cuerpo de la respuesta por separado, como lo hace crawl_url arriba; una respuesta de Crawl de Nstproxy puede llegar como un HTTP 200 normal mientras su campo success es false, y el código que solo comprueba el código de estado pasará por alto eso.
Conclusión
Todo el valor de FastMCP radica en colapsar la distancia entre una función de Python que funciona y una herramienta que un agente de IA puede llamar; este tutorial pasó de un directorio vacío a un servidor con una llamada a una API externa real en dos archivos. El camino desde pip install fastmcp hasta una herramienta registrada y validada por esquema toma minutos; la parte más difícil y valiosa es lo que la herramienta realmente hace una vez que un agente la llama, por lo que el ejemplo trabajado aquí alcanza una API externa en lugar de detenerse en la aritmética.
Para una herramienta cuyo único trabajo es convertir una URL en contenido legible para el agente, Nstproxy Crawl está construido precisamente para esa transición. Es una API de rastreo web centrada en IA que toma una URL y devuelve una salida limpia y estructurada: Markdown, HTML limpio, enlaces, capturas de pantalla o PDF — con renderizado de JavaScript y acceso respaldado por proxy de Nstproxy manejado detrás de la única llamada a la API, en lugar de requerir que usted ejecute un navegador sin cabeza y un grupo de proxies usted mismo dentro de la función de la herramienta. Se adapta naturalmente detrás de una herramienta MCP como crawl_url arriba porque ambas están resolviendo el mismo problema: obtener un modelo algo que pueda leer sin hacer que el código del agente sea responsable de la automatización del navegador.
Salida de Markdown por llamada única: una solicitud POST devuelve el contenido de la página ya convertido a Markdown, por lo que la función de la herramienta MCP solo necesita verificar success y devolver el campo markdown, no ejecutar su propia conversión de HTML a texto.
Renderizado de JavaScript incluido: las páginas que construyen su contenido del lado del cliente se renderizan completamente antes de que se capture la respuesta, por lo que una herramienta que llama a Crawl no necesita una dependencia separada de navegador sin cabeza junto a FastMCP.
Rastreo a nivel de sitio para herramientas de múltiples páginas: más allá del ejemplo de crawl_url de una sola página aquí, la misma cuenta puede llamar a POST /api/v1/crawl para rastrear muchas páginas bajo un trabajo (con límites explícitos de maxDepth y maxPages), útil para una herramienta que necesita entregar a un agente toda una sección de un sitio en lugar de una URL.
Reintentos y almacenamiento de resultados grandes gestionados en el servidor — los fetches fallidos se reintentan automáticamente, y las cargas de Markdown/HTML/capturas de pantalla de gran tamaño regresan como un token de referencia resuelto a través de una llamada separada de lectura de almacenamiento, por lo que una función de herramienta no necesita su propio bucle de reintento o gestión del tamaño de los blobs.
P: ¿Es FastMCP lo mismo que el SDK oficial de MCP para Python?
No — FastMCP 1.0 fue fusionado en el SDK oficial de MCP para Python, pero el paquete fastmcp activamente mantenido en PyPI es FastMCP 2.x, un marco superconjunto construido sobre esa base con características adicionales de experiencia para desarrolladores como el Client en proceso utilizado en este tutorial, ayudantes de autenticación y un CLI dedicado. Instalar fastmcp desde PyPI te obtiene la línea 2.x, no el código que fue absorbido en el SDK base.
P: ¿Necesito una clave API de pago para seguir este tutorial?
Solo para la segunda mitad. El ejemplo hello_server.py funciona completamente gratis sin ninguna cuenta externa, y la propia salida de este tutorial TOOLS: ['greet'] / RESULT: Hello, Nstproxy! fue capturada sin ninguna credencial. El ejemplo crawl_url necesita una clave API de Nstproxy Crawl para devolver contenido de página — sin una, la herramienta aún se registra y aún intenta la solicitud, pero la llamada falla, exactamente como se muestra en la salida de error capturada de este artículo.
P: ¿Puedo usar un servidor FastMCP con Claude Desktop u otro cliente MCP?
Sí — cualquier cliente MCP que soporte lanzar un servidor local a través de stdio, incluyendo Claude Desktop, puede ejecutar un servidor FastMCP de la misma manera que ejecuta cualquier otro servidor MCP, apuntando su configuración al archivo de Python y dejando que el cliente gestione el proceso. Este tutorial verificó el comportamiento de llamada de herramienta con la propia clase Client de FastMCP en su lugar, que es más rápida para iterar mientras se escriben y prueban herramientas antes de conectar un cliente de escritorio completo.
P: ¿Qué sucede si mi función de herramienta lanza una excepción en lugar de devolver una cadena?
Una excepción no capturada dentro de una función de herramienta se manifiesta al cliente MCP que llama como un fallo genérico en la llamada a la herramienta, sin el detalle diagnóstico específico que una cadena de error capturada y devuelta puede llevar. El ejemplo crawl_url en este tutorial deliberadamente captura requests.RequestException y verifica el estado de la respuesta y el campo success explícitamente, devolviendo una cadena descriptiva en cada camino en lugar de dejar que una excepción se propague.
P: ¿FastMCP maneja limitaciones de tasa o reintentos para API externas que llama mi herramienta?
No — FastMCP gestiona la capa del protocolo MCP (generación de esquema, enrutamiento de mensajes, transporte), no los internos de lo que hace tu función de herramienta. La limitación de tasa, la lógica de reintentos y el manejo de tiempos de espera para una API externa como Nstproxy Crawl pertenecen al propio código de tu herramienta, de la misma manera que crawl_url arriba establece su propio tiempo de espera de solicitud y verifica el cuerpo de la respuesta en lugar de asumir que FastMCP lo maneja.
P: ¿Es legal construir una herramienta MCP que rastree sitios web?
Rastrear páginas accesibles públicamente a las que estás autorizado a acceder es una práctica estándar, pero aún eres responsable de seguir los términos de servicio del sitio objetivo, respetar robots.txt donde aplique, y no usar una herramienta de rastreo para extraer contenido no público o controlado por acceso. Nstproxy Crawl está construido en torno a la recolección legítima y permitida de datos web públicos en lugar de eludir autenticaciones o muros de pago, y la misma responsabilidad se aplica a lo que tu propia función de herramienta haga con los datos que recupera.
Compara BeautifulSoup y Scrapy para la extracción de datos web en Python: arquitectura, rendimiento, características y una guía de decisión respaldada por código en vivo y fuentes verificadas.
Marcus Chen
Aug. 24th 2026
110M+ IP reales con 99.9% de acceso exitoso
Respuesta media ultrarrapida ~0.5s para tareas de alta concurrencia
Desde solo $0.1/GB
Acceso inmediato a pools premium de proxies residenciales, datacenter, IPv6 e ISP.