IA · MCP

Cómo construir y usar servidores MCP que de verdad ayuden a usuarios no técnicos.

MCP hizo fácil poner los datos de la empresa al alcance de usuarios no técnicos, pero tener acceso no significó obtener buenas respuestas. Después de construir varios servidores MCP, aprendí que la solución está en los dos lados: el servidor necesita la misma disciplina de ingeniería que cualquier buena API, y las personas que lo usan necesitan algunos hábitos sencillos. Este artículo cubre ambos, con los patrones y el código que más diferencia hicieron para mí.

En resumen

Un servidor MCP es una capa delgada sobre una API, así que todas las buenas prácticas de APIs siguen aplicando, y además tiene que proteger la ventana de contexto del modelo. Diseña pocas herramientas orientadas a tareas, filtra y pagina por defecto, usa caché en Redis, maneja los 429 dentro del servidor y devuelve errores que el modelo pueda corregir. Luego enseña a quienes preguntan a empezar con preguntas acotadas, pedir primero un resumen y limitar el número de llamadas a herramientas.

La promesa y la realidad

El Model Context Protocol permite que un asistente de IA llame herramientas que leen sistemas reales: un CRM, una herramienta de tickets, una base de datos de ventas. Para personas que nunca escribieron una consulta, eso pareció un gran avance.

En la práctica, vi repetirse el mismo patrón. Los datos aparecían, el usuario hacía una pregunta y la IA lanzaba decenas de llamadas a herramientas. Las respuestas se volvían lentas, los resultados llegaban incompletos o truncados, saltaban los límites de peticiones y la conversación se desviaba. Muchas de esas sesiones terminaban igual: el usuario se rendía y le pedía ayuda a un ingeniero.

MCP envuelve una API; no reemplaza la ingeniería de APIs

Un servidor MCP es una capa delgada sobre una API, así que todas las buenas prácticas de APIs siguen aplicando dentro de él. La caché, la paginación, los filtros, el manejo de límites de peticiones y los errores claros no desaparecen porque quien llama sea una IA. Importan más, porque una IA reintenta de forma agresiva y nunca se cansa.

MCP además agrega una restricción nueva: la ventana de contexto del modelo. Cada definición de herramienta y cada respuesta se cargan en esa ventana. Un servidor con 80 herramientas y respuestas JSON extensas la llena rápido, y el modelo se vuelve más lento, más caro y menos preciso.

Por eso un buen diseño MCP protege dos recursos escasos a la vez:

  • El backend, de avalanchas de peticiones y de llamadas idénticas repetidas.
  • El contexto del modelo, de demasiadas herramientas y de respuestas demasiado grandes.

Una petición que respeta ambos fluye así:

  1. El usuario hace una pregunta en lenguaje natural.
  2. La IA elige una herramienta orientada a la tarea y bien descrita.
  3. El servidor MCP revisa su caché antes de llamar a la API.
  4. Si no está en caché, llama a la API con filtros por defecto y un tamaño de página pequeño, y reintenta ante un 429 con espera progresiva.
  5. Devuelve un resumen corto y filtrado, con un cursor para pedir más, y guarda el resultado en caché.

Construir el servidor: las prácticas que hicieron la diferencia

Las mayores mejoras vinieron de cinco hábitos en el servidor: herramientas orientadas a tareas, filtros y paginación por defecto, una caché en Redis, manejo de 429 incorporado y errores sobre los que el modelo pueda actuar.

1. Diseña herramientas alrededor de tareas, no de endpoints

Replicar cada endpoint REST como una herramienta obliga al modelo a encadenar muchas llamadas. Una herramienta como get_open_orders_summary reemplaza tres o cuatro llamadas de bajo nivel y se lee como la pregunta que haría un usuario. Mantén corta la lista de herramientas y escribe cada descripción como si fuera para un colega nuevo: qué devuelve, cuándo usarla y cuáles son sus límites por defecto.

2. Filtra y pagina por defecto

Nunca dejes que una herramienta devuelva "todo" a menos que se lo pidan. Usa por defecto un rango de fechas reciente, un tamaño de página pequeño y solo los campos que una persona leería. Devuelve un cursor para que el modelo pida más solo cuando lo necesite.

3. Usa caché en Redis

Las IA se repiten. La misma consulta suele ejecutarse varias veces en una conversación, y entre distintos usuarios. Una caché de corta duración en Redis, con la clave formada por el nombre de la herramienta y sus argumentos, absorbe la mayor parte de esa carga.

4. Maneja los 429 dentro del servidor

El modelo nunca debería ver errores crudos de límite de peticiones, porque tiende a reintentar de inmediato. El servidor debe reintentar con espera exponencial, respetar el encabezado Retry-After y rendirse con un mensaje claro.

5. Devuelve errores que el modelo pueda corregir

"Fecha inválida: usa AAAA-MM-DD" le permite al modelo corregirse en un paso. Un stack trace lleva a reintentos a ciegas.

Ejemplo: una herramienta que aplica las cinco

Este esquema usa el SDK oficial de MCP para Python (FastMCP), redis.asyncio y httpx. La API de pedidos es un ejemplo ficticio. Las interfaces de los SDK cambian, así que verifica nombres y firmas en la documentación actual antes de construir sobre él.

import asyncio, hashlib, json
from datetime import date, timedelta

import httpx
import redis.asyncio as redis
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("orders")
cache = redis.Redis.from_url("redis://localhost:6379/0")
http = httpx.AsyncClient(base_url="https://api.example.com", timeout=10)

CACHE_TTL_SECONDS = 300
MAX_PAGE_SIZE = 50

async def get_with_retry(path: str, params: dict, attempts: int = 4) -> dict:
    """Llama a la API y reintenta ante un 429 con espera progresiva y Retry-After."""
    for attempt in range(attempts):
        resp = await http.get(path, params=params)
        if resp.status_code != 429:
            resp.raise_for_status()
            return resp.json()
        retry_after = resp.headers.get("Retry-After")
        wait = float(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt
        await asyncio.sleep(wait)
    raise RuntimeError("La API de pedidos limitó las peticiones. Intenta de nuevo en un minuto.")

@mcp.tool()
async def get_open_orders_summary(
    status: str = "open",
    since: str | None = None,
    page_size: int = 20,
    cursor: str | None = None,
) -> dict:
    """Resume los pedidos por estado. Por defecto: pedidos abiertos de los
    últimos 30 días, 20 por página. Usa el `cursor` de un resultado anterior
    para ver más. `since` debe tener el formato AAAA-MM-DD."""
    try:
        since = since or (date.today() - timedelta(days=30)).isoformat()
        date.fromisoformat(since)
    except ValueError:
        return {"error": "Fecha 'since' inválida. Usa AAAA-MM-DD, por ejemplo 2026-09-01."}

    params = {"status": status, "since": since,
              "limit": min(page_size, MAX_PAGE_SIZE), "cursor": cursor}
    params = {k: v for k, v in params.items() if v is not None}  # no envía un cursor vacío
    key = "orders:" + hashlib.sha256(json.dumps(params, sort_keys=True).encode()).hexdigest()

    if cached := await cache.get(key):
        return json.loads(cached)

    data = await get_with_retry("/orders", params)
    result = {
        "orders": [  # solo los campos que lee una persona
            {"id": o["id"], "customer": o["customer_name"],
             "total": o["total"], "created": o["created_at"]}
            for o in data["items"]
        ],
        "next_cursor": data.get("next_cursor"),
        "note": "Se muestra una página. Pide más con next_cursor.",
    }
    await cache.set(key, json.dumps(result), ex=CACHE_TTL_SECONDS)
    return result

if __name__ == "__main__":
    mcp.run()

Elige el TTL según el tipo de dato: minutos para pedidos en vivo, horas para datos de referencia como catálogos de productos. Nunca compartas la caché entre usuarios cuando los resultados dependen de permisos; incluye el usuario o el cliente (tenant) en la clave.

Caché en el front end

Si construyes tu propia interfaz alrededor del servidor MCP, una caché en el navegador ahorra viajes de ida y vuelta en vistas repetidas. Solo aplica a tu propia aplicación; clientes como los asistentes de IA de escritorio manejan su propio estado.

Usa cada almacenamiento del navegador para lo que hace bien:

AlmacenamientoSirve paraEvítalo para
CookiesBanderas y preferencias pequeñas, IDs de sesión (HttpOnly)Datos en caché: el tamaño es limitado (normalmente unos 4 KB por cookie) y las cookies viajan en cada petición
localStorageResultados pequeños y no sensibles, con TTLTokens o datos personales: cualquier script de la página puede leerlo
IndexedDBConjuntos de datos más grandes en cachéCualquier cosa que deba mantenerse en secreto

Las cookies funcionan bien para preferencias pequeñas, pero guardar datos en caché en cookies agrega bytes a cada petición, lo que puede costar más de lo que ahorra la caché. Toma los tamaños de la tabla como aproximados; los límites varían según el navegador, así que revisa la documentación actual de cada uno.

Un envoltorio simple con TTL evita que las entradas de localStorage queden desactualizadas:

const TTL_MS = 5 * 60 * 1000;

function cacheGet(key) {
  try {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const { value, expires } = JSON.parse(raw);
    if (Date.now() > expires) {
      localStorage.removeItem(key);
      return null;
    }
    return value;
  } catch {
    return null; // almacenamiento no disponible o dañado: se usa el servidor
  }
}

function cacheSet(key, value, ttl = TTL_MS) {
  try {
    localStorage.setItem(key, JSON.stringify({ value, expires: Date.now() + ttl }));
  } catch {
    // cuota excedida o almacenamiento bloqueado: no se guarda en caché
  }
}

Mantén el TTL del navegador igual o menor que el del servidor, para que las dos capas nunca estén en desacuerdo por mucho tiempo.

Usar bien MCP: hábitos para quienes preguntan

Un servidor bien construido igual tiene problemas cuando todas las preguntas son abiertas. Algunos hábitos convirtieron sesiones largas y fallidas en sesiones cortas y útiles.

  • Conecta solo lo que la tarea necesita. Cada servidor conectado agrega definiciones de herramientas al contexto del modelo. Algunos clientes de IA pueden cargar herramientas bajo demanda; revisa si el tuyo lo hace.
  • Empieza acotado. Indica el rango de fechas, el estado y los campos que te interesan.
  • Pide primero un resumen y después los detalles. Los totales y los elementos principales rara vez necesitan más de una llamada.
  • Limita el trabajo. Dile a la IA cuántas llamadas o páginas puede usar antes de consultarte.
  • Pide confirmación antes de las consultas grandes. Haz que la IA confirme antes de traer más de una página.
  • Reutiliza resultados anteriores. Pide a la IA que trabaje con lo que ya obtuvo en la conversación.

Patrones de prompt que funcionaron

Un prompt vago provoca una avalancha de llamadas:

Muéstrame todo sobre nuestros pedidos.

Un prompt acotado obtiene la respuesta en una o dos llamadas:

Con la herramienta de pedidos, resume los pedidos abiertos de los últimos 30 días:
cantidad total, valor total y los 5 pedidos más grandes.
Usa como máximo 2 llamadas a herramientas. Si necesitas más datos, pregúntame primero.

Para equipos, una instrucción permanente corta (en un prompt de sistema, en las instrucciones del proyecto o en una plantilla compartida) establece el hábito una sola vez:

Al usar herramientas de datos:
- Empieza con la consulta más pequeña que responda la pregunta.
- Usa los filtros por defecto y una página de resultados, salvo que pida más.
- No repitas una llamada a una herramienta que ya hiciste en esta conversación.
- Si una herramienta devuelve un error, léelo y corrige la entrada antes de reintentar.
- Si la respuesta necesita más de 3 llamadas a herramientas, explica tu plan y pregunta primero.

Qué cambió, y una lista de verificación

Con estas prácticas en los dos lados, las sesiones se acortaron, las respuestas llegaron más completas y menos preguntas terminaron en una solicitud al equipo de ingeniería.

Al construir un servidor MCP

  • Las herramientas están orientadas a tareas, son pocas y están bien descritas
  • Toda herramienta que devuelve listas filtra y pagina por defecto, con un cursor
  • Las respuestas devuelven solo los campos que lee una persona
  • Caché en Redis con clave por herramienta, argumentos y usuario o cliente, con TTL según el tipo de dato
  • Los 429 se reintentan dentro del servidor con espera progresiva y Retry-After
  • Los errores le dicen al modelo exactamente cómo corregir la entrada

Al usar un servidor MCP

  • Solo están conectados los servidores necesarios
  • Las preguntas indican un rango de fechas, filtros y campos
  • Primero el resumen, los detalles cuando se piden
  • Un límite de llamadas a herramientas, con una consulta antes de las extracciones grandes
  • Una instrucción permanente compartida por el equipo

MCP les abre a los usuarios no técnicos una puerta a sus datos. Una buena ingeniería en el servidor y algunos hábitos de quienes lo usan son lo que hace que valga la pena cruzarla.

La especificación de MCP y sus SDK evolucionan rápido, así que revisa la documentación oficial en modelcontextprotocol.io para conocer funciones actuales como la paginación y la carga de herramientas antes de construir.

Erwin MonguiDirector de Tecnología en Cedar Planters e ingeniero certificado en Inteligencia Artificial, en Toronto.

Recibe mis próximos casos y artículos en la Búsqueda de Google.