MMilunaCloud
FinTechSistemas DistribuidosMCPAIPython

Optimizando la operativa de producción con FastMCP y Agentes

Escrito por Miguel Angel Luna
Publicado el

Son las 18:30 de un viernes y te encuentras con 50 eventos atrapados en la Dead Letter Queue (DLQ). Cada uno representa dinero bloqueado o un cliente esperando una confirmación de pago. En arquitecturas orientadas a eventos, descifrar qué ha fallado suele significar cruzar cinco modelos de datos distintos y lidiar con llamadas asíncronas durante horas o días.

Con la llegada de los agentes de IA y el protocolo Model Context Protocol (MCP), podemos acortar radicalmente estos tiempos de resolución. La clave no es dejar que la IA opere a ciegas en producción, sino darle herramientas deterministas y mantener siempre al analista humano con las manos en el volante.

Optimización de operaciones con FastMCP y agentes

La pesadilla de la investigación forense

El verdadero cuello de botella de estas incidencias no es el volumen de eventos, sino la fricción cognitiva de la investigación. Para entender por qué falló una sola transacción, el analista debe reconstruir su ciclo de vida saltando entre herramientas dispares:

  • Rastrear trazas en CloudWatch o Datadog para identificar excepciones silenciosas.
  • Lanzar queries SQL en Athena o réplicas de lectura para contrastar el estado en base de datos.
  • Invocar APIs internas con Postman o cURL para comprobar si el servicio aguas abajo llegó a enterarse.

Multiplica este proceso por cada evento de la cola. El análisis manual consume jornadas enteras y el desgaste mental pasa factura: pasar por alto un campo en un JSON kilométrico o descuidar la comprobación en uno de los microservicios lleva a conclusiones erróneas y dispara el Mean Time to Resolution (MTTR).

El primer approach: Scripting tradicional

Cuando nos enfrentamos a un error recurrente, la primera reacción suele ser escribir un script en Python o Bash. Es una solución rápida y funciona bien si la comprobación es siempre la misma.

Sin embargo, en una DLQ de producción es habitual que esos 50 eventos respondan a 5 o 6 causas raíz completamente distintas (timeouts, estados intermedios inconsistentes, payloads con campos faltantes). Mantener scripts para cada variante se traduce en generar lotes manuales, bifurcar código continuamente y perder la agilidad que buscábamos.

La solución: FastMCP + Skill de Agente

Al implementar un servidor con FastMCP, exponemos herramientas que clientes de IA como Cursor o Windsurf pueden orquestar. El servidor MCP encapsula la lógica determinista contra nuestras APIs internas, mientras que el modelo de lenguaje aporta la capacidad de razonamiento contextual para evaluar las discrepancias caso a caso.

Arquitectura de ejecución y seguridad

Dado que se trata de operativa manual crítica sobre producción, la arquitectura sigue dos principios esenciales:

  1. Autenticación mediante variables de entorno: El token de acceso a las APIs internas o bases de datos se inyecta directamente al proceso del servidor MCP mediante variables de entorno locales (OPERATIONS_API_TOKEN), sin exponer credenciales al LLM ni en el historial de conversación.
  2. Human-in-the-loop vía Stdio: El servidor MCP se ejecuta en local comunicándose por stdin/stdout con el IDE (Cursor / Windsurf). El analista tiene visibilidad total del plan y es quien autoriza explícitamente cada llamada a la herramienta de modificación (patch_transaction) en la interfaz.

Implementación del servidor MCP

En este ejemplo exponemos dos herramientas: una de lectura para inspeccionar la transacción y otra de escritura para aplicar correcciones. Fíjate en que no es necesario tipar rígidamente la respuesta devuelta por la API: el modelo interpreta el diccionario de salida sin dificultad. En cambio, el input de modificación sí se modela estrictamente con Pydantic para actuar como barrera de seguridad (guardrail):

import os
from decimal import Decimal
from typing import Optional
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field

# Inicialización del servidor MCP
mcp = FastMCP("transactions-mcp")

# El cliente consume las credenciales inyectadas por variable de entorno
auth_token = os.environ.get("OPERATIONS_API_TOKEN")
client = TransactionsClient(token=auth_token)

class PatchTransactionRequest(BaseModel):
    id: str = Field(description="Transaction unique identifier")
    status: Optional[str] = Field(None, description="New status (COMPLETED, CANCELLED)")
    amount: Optional[Decimal] = Field(None, description="Transaction amount")
    sent_at: Optional[str] = Field(None, description="ISO timestamp when the event was dispatched")

@mcp.tool()
async def get_transaction(id: str) -> dict:
    """
    Retrieves the transaction details across microservices.
    """
    return await client.get_transaction_by_id(id)

@mcp.tool()
async def patch_transaction(request: PatchTransactionRequest):
    """
    Applies a corrective patch to a transaction.
    Operation is idempotent and creates an immutable audit trail entry (analyst ID, timestamp, diff).
    """
    return await client.patch_transaction(request)

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

Nota de diseño e idempotencia: La herramienta patch_transaction debe ser estrictamente idempotente. Si el analista confirma el parche o se produce un reintento por timeout de red, aplicar dos veces la misma mutación no debe duplicar efectos colaterales. Además, el backend registra automáticamente un log de auditoría inmutable vinculando la sesión del analista, la fecha y los campos modificados.

Conectando el servidor al IDE con uv

Para conectar nuestro servidor local a Cursor o Windsurf, configuramos el archivo mcp.json correspondiente. Utilizar uv para lanzar el proceso es la opción más limpia y reproducible: gestiona el entorno virtual al vuelo, instala dependencias sin fricción y garantiza un arranque instantáneo vía stdio:

{
  "mcpServers": {
    "transactions-mcp": {
      "command": "uv",
      "args": ["run", "mcp"],
      "env": {
        "OPERATIONS_API_TOKEN": "sec_live_ops_8f3a92bc..."
      }
    }
  }
}

Documentando las herramientas (La Skill)

Una pregunta habitual es: ¿realmente hace falta documentar las herramientas en una Skill si el propio IDE ya escanea los schemas del MCP?

Técnicamente, clientes como Cursor, Claude Desktop o Windsurf realizan introspección automática: invocan tools/list del protocolo MCP y parsean los tipos de Pydantic y los docstrings que hemos escrito en Python. Para operaciones triviales, eso es suficiente.

Sin embargo, en operativas complejas o críticas, definir una Skill en Markdown aporta dos ventajas clave:

  1. Contexto de negocio frente a tipos primitivos: El schema del MCP le dice al modelo que status es un str, pero la Skill le explica qué implicación operativa tiene pasar una transacción a COMPLETED o por qué CANCELLED no debe usarse si hubo movimiento contable.
  2. Heurística de uso: Permite guiar al agente sobre qué herramientas consultar primero, cuándo abstenerse de mutar datos y cómo interpretar respuestas ambiguas sin tener que sobrecargar los docstrings del código backend.
# MCP Skill: Transaction Operations Guide

This is an explanation for the `transactions-mcp` toolset in order to work with transactions.

## **MCP Tool: get_transaction**
- **Tool ID:** `get_transaction`
- **Description:** Retrieves the details of a specific transaction across microservices.
- **Inputs:**
  - `id` (string, required): Transaction unique identifier.
- **Output:**
  - `dict`: Transaction details including status, amount, timestamps, etc.

## **MCP Tool: patch_transaction**
- **Tool ID:** `patch_transaction`
- **Description:** Applies a corrective patch to a transaction, updating fields to the given values.
- **Inputs:**
  - `id` (string, required): Transaction unique identifier.
  - `status` (string, optional): New status (COMPLETED, CANCELLED).
  - `amount` (Decimal, optional): New amount.
  - `sent_at` (string, optional): Timestamp when the event was sent.
- **Output:**
  - Acknowledgement of the update.

## **Operational Heuristics & Business Rules**
- **Legitimate Errors (Do NOT patch):** Failures such as `INSUFFICIENT_FUNDS`, `CARD_EXPIRED` or `FRAUD_BLOCKED` are terminal business outcomes, not technical glitches. Do not patch or retry these transactions; flag them to the analyst as legitimate rejects.
- **Technical Inconsistencies (Patch candidates):** If ledger charges exist but the status remained `PENDING`, or a `COMPLETED` transaction has no dispatch timestamp, these represent message loss or event timeouts that require state reconciliation.

Orquestación en lenguaje natural (El Workflow)

Por último, definimos el workflow operativo. Este archivo sustituye a nuestros antiguos scripts monolíticos: en lugar de programar código rígido para cada bifurcación, expresamos las reglas de negocio en lenguaje natural. El agente evalúa las condiciones y propone las acciones pertinentes:

# Transaction analyst workflow

You are a transaction analyst specialized in payment operations. Use the available tools to analyze and resolve transaction issues.

## Execution plan

For the given list of transaction ids, perform the following steps:

1. Retrieve transaction details using the `get_transaction` tool.
2. Verify and plan patches based on transaction statuses:
   - If status is "PENDING" but amount is present: the dispatch event was missed. Prepare a patch to update status to "COMPLETED".
   - If status is "COMPLETED" but `sent_at` is null: the event was lost. Prepare a patch with status "COMPLETED" and `sent_at` set to the current ISO timestamp.
3. Present the proposed action plan to the analyst for confirmation.
4. Once authorized, execute `patch_transaction` for each transaction.
5. Verify the updated state by invoking `get_transaction` once again.

La interacción en vivo: Razonamiento y confirmación

Cuando le pasas al agente un lote de IDs desde el IDE, la ejecución no es una caja negra. Gracias al workflow definido, el modelo primero inspecciona el estado y formula una propuesta estructurada:

Agente:
“He analizado las 3 transacciones indicadas:

    • tx_90123: Estado PENDING, pero el pago de 150.00 EUR ya fue capturado en el ledger (evento de confirmación no emitido). Propongo parchar a COMPLETED.*
    • tx_90124: Estado COMPLETED, pero el campo sent_at está vacío (evento perdido). Propongo actualizar sent_at a 2026-09-26T18:32:00Z.*
    • tx_90125: Error por fondos insuficientes legítimo. No requiere intervención.*

¿Deseas que ejecute patch_transaction para tx_90123 y tx_90124?“

En este punto, el analista solo tiene que hacer clic en Approve en su IDE para que el parche se aplique en segundos, o rechazarlo si detecta alguna anomalía de negocio.

Resultados: Equilibrando el inflow y outflow de incidencias

El verdadero cuello de botella de estos incidentes suele ser la disparidad entre el inflow (ritmo al que entran eventos a la DLQ o incidentes al backlog) y el outflow (la capacidad del equipo de analizarlos, contrastar sistemas y subsanarlos).

Implementando este enfoque con FastMCP en local y flujos en lenguaje natural:

  • Inflow vs. Outflow equilibrado: El tiempo de triaje y resolución por lote de incidencias pasó de varios días de trabajo fragmentado a resolverse en cuestión de un par de horas. El outflow aumentó drásticamente sin necesidad de ampliar el equipo de guardia.
  • Control y auditoría continua: Al ejecutarse vía stdio desde el IDE del analista, cada mutación de datos requiere aprobación explícita. Eliminamos el riesgo de “alucinaciones destructivas” sin renunciar a la velocidad analítica del LLM.
  • Flexibilidad sin refactorizar scripts: Cuando aparece una variante imprevista en el fallo de la transacción, ajustar la regla en el prompt del workflow toma 30 segundos, frente al tiempo que requeriría modificar, probar y validar un script tradicional.