Icono del sitio Profile Software Services

MCP (Model Context Protocol): guía completa para conectar la IA con herramientas externas

MCP Model Context Protocol

En los últimos años han aparecido cientos de modelos de lenguaje capaces de razonar, generar código, analizar documentos, planificar tareas y un largo etcétera. Sin embargo, para mí el gran avance se encuentra en dotar a todos ellos de la posibilidad de actuar. Un modelo puede pensar qué consulta lanzar contra una base de datos, pero si no puede ejecutarla, se queda en un simple pensamiento. Para solucionar esto, surgió Model Context Protocol. MCP es el estándar que permite a cualquier asistente de IA ejecutar herramientas externas sin necesidad de intermediarios: consultar bases de datos, leer logs… todo ello sin requerir de acción humana. Es, por tanto, el sistema nervioso que conecta el cerebro de la IA con el mundo real. Lo que permite que los modelos no solo piensen, sino que hagan.

Hasta ahora, el foco ha estado en lo grande: agentes autónomos, pipelines de agentes de IA en desarrollo de software, integraciones empresariales. Pero MCP también es útil para las cosas más pequeñas, las de nuestro día a día como desarrolladores. En mi caso, he encontrado en MCP un gran aliado para la depuración de incidencias.

Cansado de necesitar consultar DynamoDB manualmente y todo lo que conlleva: abrir la terminal, pelearme con las comillas escapadas, interpretar un JSON tipado ilegible, lidiar con el cambio de contexto… decidí construir dynamodb-explorer, un servidor MCP de solo lectura para DynamoDB.

El problema: los clientes oficiales de AWS y la fricción de contexto

Aunque DynamoDB es una base de datos potente, su experiencia de uso en el día a día deja bastante que desear. Y no es culpa de DynamoDB en sí, sino de las herramientas de las que disponemos para interactuar con ella.

DynamoDB y sus herramientas: misma fricción, distinta ventana

DynamoDB usa un JSON tipado pensado para serialización, pero no para el ojo humano. Un string no es "valor": es {"S": "valor"}. Un número no es 42: es {"N": "42"}. Esto no es un detalle menor, es el mayor de los inconvenientes, ya que da igual la herramienta que uses, CLI, consola, NoSQL Workbench, PartiQL, todas devuelven los datos en este formato. Por ello, cada consulta se convierte también en un trabajo mental de descodificación, con la carga que supone.

La CLI de AWS agrava el problema con una sintaxis críptica: --expression-attribute-values--projection-expression--key-condition-expression. Una comilla mal puesta y el comando falla. La consola web no lo resuelve: entre login, región, menús y pantallas de carga, llegar a una consulta simple se convierte en un tedioso camino. Y herramientas como NoSQL Workbench o PartiQL mejoran la sintaxis, sí, pero sigues atado a otra ventana y al mismo DynamoDB JSON en las respuestas.

Además, independientemente de la herramienta elegida, el patrón es siempre el mismo: salir de tu entorno de trabajo para entrar en una herramienta que no habla tu idioma.

Lo que de verdad necesitas no es otra herramienta de consulta. Es poder preguntar desde donde ya estás trabajando:

«¿Cuántas notificaciones sin enviar tiene el usuario 12345?»

«¿Hay registros huérfanos en la tabla de producción?»

«¿Qué entidades existen y cómo se relacionan?»

Y que alguien, o algo, traduzca esa pregunta a las queries correctas, las ejecute, deserialice el resultado y devuelva una respuesta en JSON limpio. Sin salir del chat. Sin recordar sintaxis, ni procesar mentalmente estructuras de DynamoDB JSON

Ese «algo» es justo lo que permite Model Context Protocol. Y eso fue lo que me llevó a construir el explorador.

Qué es MCP (Model Context Protocol)

A nivel técnico, MCP (Model Context Protocol) funciona sobre JSON-RPC 2.0: el cliente y el servidor intercambian mensajes estructurados en JSON a través de un canal de transporte. El ciclo es simple: el cliente inicia la conexión, negocia capacidades con el servidor, descubre las tools disponibles (tools/list) y luego las invoca bajo demanda (tools/call). Cada tool recibe argumentos tipados y devuelve contenido estructurado.

Model Context Protocol es un protocolo abierto, creado y liberado inicialmente por Anthropic, que permite a los modelos de lenguaje interactuar con herramientas externas de forma estructurada. Básicamente: una forma estándar de conectar tus herramientas con tu asistente de código.

Piensa en ello como un USB-C para herramientas de IA. Antes de MCP, cada integración requería su propio adaptador, su propia lógica de parsing… MCP estandariza esa conexión.

En una integración local típica, la arquitectura se ve así:

Cliente MCP (Claude Code / Cursor / Codex...) ←→ JSON-RPC sobre stdin/stdout ←→ Servidor MCP (tu código)

A nivel de transporte, stdio es el óptimo para herramientas locales porque Claude Code lanza el servidor como proceso hijo y se comunica con él por entrada y salida estándar. Sin puertos, sin autenticación HTTP, sin CORS. MCP también soporta otros transportes como SSE (Server-Sent Events, para herramientas remotas) y Streamable HTTP, pensados para escenarios donde el servidor no vive en la misma máquina que el cliente.

Un servidor MCP puede exponer tres tipos de capacidades:

En mi caso me centré en tools, porque el objetivo era permitir consultas y análisis sobre DynamoDB. Pero el protocolo da para más.

La solución: un explorador de DynamoDB vía MCP (Model Context Protocol)

Diseñé dynamodb-explorer, un servidor MCP que expone herramientas diseñadas para consultar DynamoDB desde el chat de Claude Code.

Stack

Para integrarlo en Claude Code, la configuración es así de simple:

{
  "mcpServers": {
    "dynamodb-explorer": {
      "command": "npx",
      "args": ["tsx", "/ruta/dynamodb-explorer/src/index.ts"],
      "env": {
        "AWS_PROFILE": "dev",
        "AWS_REGION": "eu-west-1",
        "DYNAMODB_TABLE_NAME": "mi-tabla"
      }
    }
  }
}

Usando AWS_PROFILE, se pueden separar credenciales por entorno y evitar que el servidor use accidentalmente permisos más amplios de los necesarios. En local apunta a staging; en producción, solo cuando es imprescindible y con límites más agresivos.

Read-only por construcción: menos riesgo, no cero riesgo

Algo que tuve claro desde el minuto uno: el servidor no escribe. Solo importa GetItemCommandQueryCommandScanCommandDescribeTableCommand y ListTablesCommand. No hay rastro de PutItemCommandUpdateItemCommand ni DeleteItemCommand en el código.

Eso elimina el riesgo de modificar datos accidentalmente. El asistente puede explorar sin miedo, pero teniendo claro que read-only no es sinónimo de inofensivo. El riesgo cambia de naturaleza, ya que no solo se trata de la integridad (no puedes romper datos) sino también de la confidencialidad (qué datos puedes leer, cuántos, y a dónde llegan).

Un servidor mal configurado podría leer PII sin filtro, ejecutar scans costosos o devolver más información de la necesaria al modelo. Por eso apliqué varias capas:

{
  "Effect": "Allow",
  "Action": [
    "dynamodb:ListTables",
    "dynamodb:DescribeTable",
    "dynamodb:GetItem",
    "dynamodb:Query",
    "dynamodb:Scan"
  ],
  "Resource": [
    "arn:aws:dynamodb:eu-west-1:123456789012:table/mi-tabla"
  ]
}

Además apliqué límites de resultados por defecto en cada tool, separación de credenciales por entorno, logs de invocación y redacción de campos sensibles antes de devolver la respuesta.

Las herramientas, organizadas por capacidad

En lugar de exponer un genérico query(table, expression) , que para eso ya está la CLI, diseñé herramientas que encapsulan operaciones compuestas, agrupándolas en tres capas:

Exploración básica — lo primero que necesitas al abrir una tabla:

Análisis de modelo — para entender la forma de los datos:

Auditoría y debugging — para detectar lo que falla:

Si tuviera que quedarme con tres: analyze_table_schema para aterrizar en una tabla desconocida, read_entity para el día a día, y find_database_inconsistencies para auditoría.

Cada tool devuelve JSON plano legible. Nada de {"S": "valor"}. El servidor hace la deserialización antes de devolver la respuesta, así que el modelo recibe datos que entiende a la primera. De esta forma es capaz de interpretar y responderte en lenguaje natural.

Un ejemplo real de salida

Lo importante no es solo que la tool encuentre problemas: es cómo los devuelve. Cada issue tiene severidad, tipo y un mensaje pensado para que el asistente razone, no para que regurgite un Scan en crudo.

Así se ve una respuesta de find_database_inconsistencies:

{
  "summary": {
    "totalIssues": 12,
    "high": 3,
    "medium": 5,
    "low": 4
  },
  "issues": [
    {
      "severity": "high",
      "type": "orphan_reference",
      "entity": "ORDER#a1b2c3d4",
      "detail": "Referencia a USER#5678 que no existe en la tabla."
    },
    {
      "severity": "medium",
      "type": "mixed_attribute_type",
      "attribute": "created_at",
      "entity": "PRODUCT",
      "detail": "El atributo aparece con tipos S y N en entidades del mismo tipo."
    },
    {
      "severity": "low",
      "type": "expired_ttl",
      "entity": "SESSION#xyz",
      "detail": "TTL expirado hace 3 días. DynamoDB aún no lo ha eliminado."
    }
  ]
}

Esta estructura es lo que permite que el asistente no se limite a volcar datos, puede priorizar problemas, explicar impacto y sugerir siguientes pasos. Esa es justo la diferencia entre envolver una API y construir una herramienta de verdad.

En mi flujo diario, consultas que antes me llevaban uno o dos minutos entre CLI, JSON y cambios de contexto pasaron a resolverse en unos segundos desde el chat.

Construye tu propio servidor MCP (Model Context Protocol) en 10 minutos

Si quieres integrar tu propia herramienta, montar un servidor MCP es sorprendentemente rápido. Siguiendo con el ejemplo del artículo, aquí tienes una tool que consulta DynamoDB y devuelve un JSON limpio.

1. Inicializa el proyecto

mkdir mi-mcp && cd mi-mcp
npm init -y
npm install @modelcontextprotocol/sdk @aws-sdk/client-dynamodb
npm install -D tsx typescript @types/node

2. Crea el servidor con una tool real

Este esqueleto expone leer_registro, que recibe una PK, una SK y devuelve el ítem en JSON plano, sin DynamoDB JSON tipado:

import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
import type { AttributeValue } from "@aws-sdk/client-dynamodb";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const dynamo = new DynamoDBClient({ region: process.env.AWS_REGION || "eu-west-1" });
const TABLE = process.env.DYNAMODB_TABLE_NAME || "mi-tabla";

const server = new Server(
  { name: "dynamodb-light", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Deserializar DynamoDB JSON → JSON plano
function attrToJs(value: AttributeValue | undefined): unknown {
  if (!value) return undefined;
  if ("S" in value) return value.S;
  if ("N" in value) return Number(value.N);
  if ("BOOL" in value) return value.BOOL;
  if ("NULL" in value) return null;
  if ("L" in value) return (value.L ?? []).map(attrToJs);
  if ("M" in value) {
    return Object.fromEntries(
      Object.entries(value.M ?? {}).map(([k, v]) => [k, attrToJs(v)])
    );
  }
  return value;
}

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "leer_registro",
      description: "Lee un ítem de DynamoDB por PK y SK y lo devuelve en JSON plano.",
      inputSchema: {
        type: "object",
        properties: {
          PK: { type: "string", description: "Partition key exacta." },
          SK: { type: "string", description: "Sort key exacta." }
        },
        required: ["PK", "SK"]
      }
    }
  ]
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "leer_registro") {
    const args = request.params.arguments as Record<string, unknown> | undefined;
    const PK = args?.PK as string;
    const SK = args?.SK as string;

    const result = await dynamo.send(
      new GetItemCommand({
        TableName: TABLE,
        Key: { PK: { S: PK }, SK: { S: SK } }
      })
    );

    const item = result.Item
      ? Object.fromEntries(
          Object.entries(result.Item).map(([k, v]) => [k, attrToJs(v)])
        )
      : null;

    return {
      content: [{ type: "text", text: JSON.stringify({ PK, SK, item }, null, 2) }]
    };
  }
  throw new Error(Tool desconocida: ${request.params.name});
});

const transport = new StdioServerTransport();
await server.connect(transport);

Presta atención a attrToJs, ya que esta pequeña función recursiva es lo que convierte {"S": "valor"} en "valor". Puede parecer una tontería, pero es justo lo que elimina la fricción que describí al principio. La versión real maneja más tipos de DynamoDB (binarios, sets de strings, sets de números), pero esta ya cubre el 95% de los casos. La idea es que veas el patrón, no que copies un deserializador de producción.

3. Conéctalo a Claude Code

En Claude Code, añades el servidor a tu archivo .mcp.json en la raíz del proyecto:

{
  "mcpServers": {
    "dynamodb-light": {
      "command": "npx",
      "args": ["tsx", "/ruta/a/mi-mcp/index.ts"],
      "env": {
        "AWS_PROFILE": "dev",
        "AWS_REGION": "eu-west-1",
        "DYNAMODB_TABLE_NAME": "mi-tabla"
      }
    }
  }
}

También puedes usar claude mcp add dynamodb-light -- npx tsx /ruta/a/mi-mcp/index.ts desde la terminal y Claude Code lo registra automáticamente.

¿Y si usas otra herramienta?

La configuración cambia ligeramente según el cliente MCP, pero el concepto es idéntico en todas:

En todos los casos, el servidor es el mismo. Solo cambia dónde declaras el bloque JSON y cómo el cliente lanza el proceso hijo. El estándar MCP garantiza que tu servidor funcione en cualquiera de ellos sin modificaciones.

El patrón se repite para cualquier integración: registras tools en ListToolsRequestSchema, implementas su lógica en CallToolRequestSchema y conectas por stdio. Añadir más herramientas es cuestión de sumar casos al switch.

Lecciones que aprendí

El valor está en el conocimiento de dominio, no en el wrapper

Un buen servidor MCP no envuelve una API: encapsula conocimiento de dominio. read_entity no es un query genérico: sabe qué campos son claves, cómo se relacionan las entidades, y compone varias consultas para devolver una respuesta coherente. Esa es la diferencia entre un wrapper y una herramienta de verdad.

Cosas que tuve que ajustar después de usarlo

Dejarlo correctamente configurado y adaptado a mis necesidades fue fruto de iteraciones de refinamiento. Algunos de los problemas en los que tuve que trabajar fueron:

Cuándo usar MCP (Model Context Protocol) y cuándo no

A pesar de todos los beneficios y capacidades que nos otorga MCP, debemos tener claro que no en todos los escenarios encaja como un guante.

Ideal para

No lo usaría para

Bajo mi punto de vista, el punto dulce de MCP (Model Context Protocol) está justo donde estaba mi problema inicial: tareas exploratorias, repetitivas y con suficiente lógica de dominio como para que un aws dynamodb query no baste.

Conclusión

Al final, dynamodb-explorer es solo un ejemplo. Lo que de verdad importa es el patrón: identificar esas pequeñas fricciones diarias como consultar logs, revisar PRs, comprobar el estado de un deploy, buscar en la documentación interna… y convertirlas en tools que tu asistente ejecute sin que tú cambies de ventana.

MCP (Model Context Protocol) es la pieza que faltaba para que esta revolución industrial de la IA aterrice en nuestro día a día como developers. No hace falta un proyecto de meses ni un equipo dedicado. Con cincuenta líneas de TypeScript y el SDK montas un servidor que te ahorra horas de contexto perdido cada semana.

Yo empecé con DynamoDB porque era mi mayor fricción en el día a día. Tú tendrás la tuya. Lo que sea que te haga salir del chat varias veces al día: logs, incidencias, métricas, tickets de Jira… Construye un MCP para eso. La inversión es mínima y el retorno en fluidez es inmediato.

La revolución ya está aquí. MCP es la herramienta para no quedarte mirándola desde la barrera.

¿Quieres seguir al tanto de lo último en Inteligencia Artificial y desarrollo de software? Permanece atento/a a nuestras Redes Sociales y Canal de YouTube.

Salir de la versión móvil