Biblioteca · Conexiones: API, MCP y funcionar 24/7

MCP Builder: crear tu propio servidor MCP

Ingeniero85 minActualizado: octubre de 2026
25 de 105 en la biblioteca

Tiempo: unos 25 min de teoría + 60 min de práctica

Los comandos y paquetes de esta lección se revisaron con la documentación oficial a octubre de 2026. El SDK y Claude Code se actualizan seguido: si un comando no funciona, compáralo con la documentación de MCP en Claude Code y con modelcontextprotocol.io. Versiones actuales: Lo vigente.


Lo esencial

MCP es como un puerto USB para Claude. El USB es un conector estándar: conectas un mouse, una memoria, un micrófono, una impresora, y la computadora los reconoce. MCP es un protocolo estándar: conectas tu CRM, tu base de datos, una API corporativa, el sistema de archivos, y Claude los ve como herramientas. Hoy vas a escribir tu propio servidor MCP: 30 a 50 líneas de código, y Claude gana capacidades nuevas que antes no tenía.

🎨 Imagínalo así: MCP = un conector USB para la IA. Anthropic hizo el estándar, tú haces el dispositivo. El usuario solo lo conecta. Todo el ecosistema funciona porque el conector es el mismo.


Conceptos clave

  • MCP (Model Context Protocol): el estándar abierto de Anthropic para conectar la IA con herramientas externas (modelcontextprotocol.io)
  • Servidor MCP: un programa que le ofrece a Claude tools, resources y prompts
  • Transportes: stdio (local), HTTP (remoto, el recomendado), SSE (remoto, obsoleto), WebSocket (solo vía configuración JSON)
  • Tres tipos de objetos: tools (acciones), resources (datos), prompts (plantillas)
  • Tres alcances (scopes): local (por defecto, privado), project (con .mcp.json, para el equipo), user (todos los proyectos)
  • Instalación: claude mcp add (CLI), .mcp.json (archivo) o mediante un plugin
  • Economía de tokens: aviso cuando pasa de 10,000 tokens, límite por defecto de 25,000 tokens por llamada
  • SDK de TypeScript: @modelcontextprotocol/server / SDK de Python: pip install "mcp[cli]" (el paquete viejo de TypeScript @modelcontextprotocol/sdk todavía aparece en ejemplos)

Teoría

Arquitectura de MCP

Código
Claude Code (Client)
       │
       │  Protocolo MCP estándar (JSON-RPC 2.0)
       │  por stdio o HTTP (SSE está obsoleto)
       ▼
MCP Server (tu código)
       │
       ├── tools    → funciones que Claude puede llamar
       ├── resources → datos que Claude puede leer
       └── prompts  → plantillas para tareas repetitivas
       │
       ▼
Sistema externo (CRM, BD, API, archivos...)

🎨 Imagínalo así: un servidor MCP es como el maestro de obras. Siempre está ahí mientras se trabaja. Claude le llama al maestro de obras ("búscame un contacto"), él va al CRM y vuelve con la respuesta. Claude no sabe cómo está hecho el CRM, solo sabe que el maestro de obras sabe trabajar con él.

Lo clave: un servidor MCP es un programa común y corriente. Arranca cuando Claude Code se abre en el proyecto y sigue activo mientras la sesión está abierta. Claude llama a las tools mediante solicitudes JSON-RPC y el servidor responde con resultados.

Qué se puede hacer con servidores MCP conectados (según la documentación oficial):

  • Implementar una función desde el issue tracker: "Haz la función de JIRA ENG-4521 y crea un PR en GitHub"
  • Analizar el monitoreo: "Revisa Sentry y muéstrame los errores de las últimas 24 horas"
  • Consultar bases de datos: "Encuentra a los usuarios que usaron la función X"
  • Integrar diseños: "Actualiza la plantilla según los nuevos diseños de Figma"
  • Automatizar: "Crea borradores de correo para estos 10 usuarios"

Tres formas de instalar servidores MCP

Forma 1: servidor HTTP remoto (la recomendada para servicios en la nube)

bash
claude mcp add --transport http notion https://mcp.notion.com/mcp

Forma 2: servidor SSE remoto (obsoleto, usa HTTP)

bash
claude mcp add --transport sse asana https://mcp.asana.com/sse

Forma 3: servidor stdio local

bash
claude mcp add --transport stdio --env AIRTABLE_API_KEY=YOUR_KEY airtable \
  -- npx -y airtable-mcp-server

Importante: todas las opciones (--transport, --env, --scope) van antes del nombre del servidor. -- separa el nombre del comando de arranque.

Tres alcances (scope)

Scope Dónde se guarda Quién lo ve Cuándo usarlo
local (default) ~/.claude.json Solo tú, solo en este proyecto Servidores personales, experimentos
project .mcp.json en la raíz del proyecto Todo el equipo (vía git) Herramientas compartidas del proyecto
user ~/.claude.json Tú, en todos los proyectos Utilidades personales para todos tus proyectos
bash
# Agregar con scope project (para el equipo)
claude mcp add --transport http --scope project sentry https://mcp.sentry.dev/mcp

Si hay conflicto de nombres, la prioridad es: local > project > user > plugin > claude.ai connectors. Los servidores definidos por el administrador de la organización tienen prioridad sobre todos.

Administrar los servidores

bash
claude mcp list              # Lista de todos los servidores
claude mcp get github        # Detalles de un servidor
claude mcp remove github     # Eliminar un servidor
/mcp                         # Dentro de Claude Code: estado de los servidores y reconexión

Economía de tokens de MCP (importante para el negocio)

Cada servidor MCP consume tokens de la ventana de contexto. Esto pesa mucho en el costo:

  • Aviso cuando una sola llamada devuelve más de 10,000 tokens
  • Límite por defecto: 25,000 tokens por respuesta de una tool de MCP
  • Ajustar el límite: MAX_MCP_OUTPUT_TOKENS=50000 claude
  • Tiempo de espera al arrancar: MCP_TIMEOUT=10000 claude (10 segundos)

🎨 Imagínalo así: cada servidor MCP es un pasajero en el camión del contexto. Antes, las descripciones de todas sus herramientas ocupaban lugar desde el principio. Ahora, por defecto, funciona el tool search: al arrancar solo se cargan los nombres y los detalles se traen cuando Claude los necesita. Pero los resultados de las llamadas sí ocupan contexto. Sube solo a los que de verdad necesitas.

Reconexión automática: si un servidor HTTP/SSE se desconecta, Claude Code se reconecta solo con espera exponencial (hasta 5 intentos). Los servidores stdio locales no se reconectan solos: reinícialos desde /mcp.


Tres tipos de objetos MCP

🎨 Imagínalo así: las tools son un desarmador (acciones). Los resources son un plano (datos para leer). Los prompts son un instructivo de armado (plantilla). En la mayoría de los proyectos solo hace falta el desarmador.

Tools (herramientas): acciones que Claude puede ejecutar:

  • get_contact: obtener un contacto del CRM
  • create_task: crear una tarea
  • send_message: enviar un mensaje
  • query_database: ejecutar una consulta a la BD

Resources (recursos): datos que Claude puede leer:

  • crm://contacts/list: lista de contactos
  • db://reports/monthly: reporte mensual
  • file://config/settings: configuración de la aplicación

Prompts (plantillas): instrucciones listas para tareas típicas:

  • analyze_deal: plantilla para analizar un trato
  • write_followup: plantilla de correo de seguimiento

A la mayoría de los proyectos les bastan las tools.


Servidor MCP mínimo: Hello World

Instalamos el SDK:

bash
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
mkdir src

En package.json agrega "type": "module". Al lado pon un tsconfig.json:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "types": ["node"],
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Creamos src/server.ts:

typescript
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

// Creamos el servidor
const server = new McpServer({
  name: "my-first-mcp",
  version: "1.0.0",
});

// Agregamos una tool: una función simple
server.registerTool(
  "get_weather",                              // Nombre de la tool
  {
    description: "Obtener el clima de una ciudad",       // Descripción para Claude
    inputSchema: z.object({                              // Parámetros (esquema Zod)
      city: z.string().describe("Nombre de la ciudad"),
    }),
  },
  async ({ city }) => {
    // Aquí va la lógica real: llamada a una API, consulta a la BD, etc.
    // Para el ejemplo, un valor fijo
    return {
      content: [{
        type: "text",
        text: `Clima en ${city}: +22°C, nublado`
      }]
    };
  }
);

// Conectamos el transporte stdio y arrancamos
const transport = new StdioServerTransport();
await server.connect(transport);

Importante: un servidor stdio se comunica con Claude por la salida estándar, así que no puedes imprimir logs con console.log. Para los logs usa console.error.

Compilamos y arrancamos:

bash
npx tsc
node build/server.js

Ejemplo real: un servidor MCP para un CRM

Un servidor completo que Claude Code usa para trabajar con un CRM ficticio mediante una API REST:

typescript
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

const CRM_API_URL = process.env.CRM_API_URL || "https://api.mycrm.com";
const CRM_API_KEY = process.env.CRM_API_KEY || "";

const server = new McpServer({
  name: "crm-mcp-server",
  version: "1.0.0",
});

// Tool 1: obtener un contacto por nombre o email
server.registerTool(
  "get_contact",
  {
    description: "Buscar un contacto en el CRM por nombre o dirección de email",
    inputSchema: z.object({
      query: z.string().describe("Nombre o email a buscar"),
    }),
  },
  async ({ query }) => {
    const response = await fetch(
      `${CRM_API_URL}/contacts/search?q=${encodeURIComponent(query)}`,
      { headers: { "X-API-Key": CRM_API_KEY } }
    );
    const data = await response.json();

    if (!data.contacts?.length) {
      return { content: [{ type: "text", text: `No se encontró el contacto "${query}"` }] };
    }

    const contact = data.contacts[0];
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          id: contact.id,
          name: contact.full_name,
          email: contact.email,
          company: contact.company,
          deal_stage: contact.deal_stage,
          last_contact: contact.last_contact_date,
        }, null, 2)
      }]
    };
  }
);

// Tool 2: crear una tarea
server.registerTool(
  "create_task",
  {
    description: "Crear una tarea en el CRM ligada a un contacto",
    inputSchema: z.object({
      contact_id: z.string().describe("ID del contacto"),
      title: z.string().describe("Nombre de la tarea"),
      due_date: z.string().describe("Fecha límite en formato YYYY-MM-DD"),
      priority: z.enum(["low", "medium", "high"]).describe("Prioridad de la tarea"),
    }),
  },
  async ({ contact_id, title, due_date, priority }) => {
    const response = await fetch(`${CRM_API_URL}/tasks`, {
      method: "POST",
      headers: {
        "X-API-Key": CRM_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ contact_id, title, due_date, priority }),
    });
    const task = await response.json();

    return {
      content: [{
        type: "text",
        text: `Tarea creada. ID: ${task.id}. Fecha límite: ${due_date}. Prioridad: ${priority}.`
      }]
    };
  }
);

// Tool 3: actualizar la etapa del trato
server.registerTool(
  "update_deal_stage",
  {
    description: "Actualizar la etapa del trato de un contacto",
    inputSchema: z.object({
      contact_id: z.string().describe("ID del contacto"),
      stage: z.enum(["lead", "qualified", "proposal", "negotiation", "closed_won", "closed_lost"])
             .describe("Nueva etapa del trato"),
      note: z.string().optional().describe("Nota sobre el cambio de etapa"),
    }),
  },
  async ({ contact_id, stage, note }) => {
    await fetch(`${CRM_API_URL}/contacts/${contact_id}`, {
      method: "PATCH",
      headers: {
        "X-API-Key": CRM_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ deal_stage: stage, stage_note: note }),
    });

    return {
      content: [{
        type: "text",
        text: `Etapa del trato actualizada: ${stage}${note ? `. Nota: ${note}` : ""}`
      }]
    };
  }
);

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

Registrarlo en el proyecto: .mcp.json

Para que Claude Code arranque tu servidor MCP automáticamente al abrir el proyecto, creas .mcp.json en la raíz del proyecto:

json
{
  "mcpServers": {
    "crm": {
      "command": "node",
      "args": ["./mcp-servers/crm/build/server.js"],
      "env": {
        "CRM_API_URL": "https://api.mycrm.com",
        "CRM_API_KEY": "${CRM_API_KEY}"
      }
    }
  }
}

A partir de ahí, cuando abres Claude Code en esa carpeta, el servidor arranca solo. Claude ve las tools get_contact, create_task y update_deal_stage como capacidades integradas.

Variables de entorno en .mcp.json (función oficial):

Se admite la sintaxis ${VAR} y ${VAR:-default} en los campos command, args, env, url y headers:

json
{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      }
    }
  }
}

Así puedes subir .mcp.json a git sin secretos: cada desarrollador pone sus propias variables de entorno.

Registro por CLI (alternativa):

bash
# Con scope user (disponible en todos los proyectos)
claude mcp add --transport stdio --scope user crm -- node ./server.js

# Agregar desde JSON
claude mcp add-json crm '{"command":"node","args":["./server.js"],"env":{"CRM_API_KEY":"${CRM_API_KEY}"}}'

Importar desde Claude Desktop (si ya lo tienes configurado; funciona en macOS y WSL):

bash
claude mcp add-from-claude-desktop

Pruebas: MCP Inspector

🎨 Imagínalo así: MCP Inspector es como probar un auto en un estacionamiento vacío. Antes de salir a la carretera (Claude Code), revisas que el volante funcione, que frene y que el motor no cascabelee. Te ahorra horas de depuración en el uso real.

El proyecto MCP ofrece un inspector interactivo para probar servidores sin Claude (requiere Node 22.19 o más nuevo):

bash
npx @modelcontextprotocol/inspector node build/server.js

Se abre una interfaz web donde puedes:

  • Ver todas las tools registradas
  • Llamar cada tool a mano con parámetros
  • Ver qué responde el servidor
  • Depurar errores

También hay un modo de consola: npx @modelcontextprotocol/inspector --cli node build/server.js --method tools/list muestra la lista de tools y termina. Es mucho más rápido que probar a través de Claude Code.


Claude Code como servidor MCP

Claude Code puede funcionar él mismo como servidor MCP para otras aplicaciones:

bash
claude mcp serve

Conexión desde Claude Desktop:

json
{
  "mcpServers": {
    "claude-code": {
      "type": "stdio",
      "command": "claude",
      "args": ["mcp", "serve"],
      "env": {}
    }
  }
}

Esto les da a otras aplicaciones de IA acceso a las herramientas de Claude Code (Read, Edit, Bash y otras).


Ligar servidores MCP a subagentes

🎨 Imagínalo así: ligar un MCP a un subagente es como darle herramientas solo a la cuadrilla que las necesita. El plomero no carga las herramientas del electricista, solo las suyas. La conversación principal no se llena de herramientas que solo hacen falta mientras trabaja un subagente.

Puedes ligar servidores MCP a un subagente concreto con el campo mcpServers del frontmatter:

Escribe esto en el chat
---
name: browser-tester
description: Tests features in a real browser using Playwright
mcpServers:
  - playwright:
      type: stdio
      command: npx
      args: ["-y", "@playwright/mcp@latest"]
---

Los servidores en línea se conectan cuando arranca el subagente y se desconectan cuando termina. La conversación principal no ve esas herramientas, y eso ahorra contexto.


La versión en Python: FastMCP

Si prefieres Python, hay una forma más declarativa: el SDK arma la descripción de la tool a partir de las anotaciones de tipo y el docstring. En la documentación actual la clase se llama MCPServer (en ejemplos viejos aparece FastMCP):

python
# pip install "mcp[cli]"   o   uv add "mcp[cli]"
from mcp.server import MCPServer

mcp = MCPServer("crm-server")

@mcp.tool()
def get_contact(query: str) -> str:
    """Buscar un contacto en el CRM por nombre o email"""
    # Lógica de búsqueda
    return f"Contacto encontrado: {query}"

@mcp.tool()
def create_task(contact_id: str, title: str, due_date: str) -> str:
    """Crear una tarea en el CRM"""
    # Lógica para crear la tarea
    return f"Tarea creada: {title} para {contact_id}"

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

Registro en .mcp.json:

json
{
  "mcpServers": {
    "crm-python": {
      "command": "python",
      "args": ["./mcp-servers/crm_server.py"]
    }
  }
}

Publicar en npm

Si quieres compartir tu servidor MCP o usarlo en varios proyectos:

bash
# package.json
{
  "name": "@yourname/mcp-crm",
  "version": "1.0.0",
  "type": "module",
  "bin": { "mcp-crm": "./server.js" },
  "main": "./server.js"
}

# Publicación
npm publish --access public

Una vez publicado, cualquiera puede usarlo:

json
{
  "mcpServers": {
    "crm": {
      "command": "npx",
      "args": ["-y", "@yourname/mcp-crm"]
    }
  }
}

Recursos de MCP: menciones con @

Los servidores MCP pueden ofrecer resources que mencionas con @:

Escribe esto en el chat
Analiza @github:issue://123 y propón una corrección
Revisa la documentación @docs:file://api/authentication

Los recursos aparecen en el autocompletado, junto a los archivos, cuando escribes @.

Actualización dinámica de herramientas

Claude Code admite las notificaciones list_changed de los servidores MCP. Si un servidor agrega o quita tools, Claude Code actualiza la lista solo, sin reconectar.


Práctica

Tarea: un servidor MCP para trabajar con archivos de notas locales

  1. Crea la carpeta my-notes-mcp/ e inicializa el proyecto con los pasos de la sección Hello World de arriba: npm init -y, npm install @modelcontextprotocol/server zod, npm install -D @types/node typescript, "type": "module" y tsconfig.json
  2. Crea src/server.ts con tres tools:
    • list_notes: lista de archivos en la carpeta ~/Notes/ (o la que tú quieras)
    • read_note: leer un archivo por su nombre
    • create_note: crear un archivo nuevo con una nota
  3. Compila: npx tsc
  4. Pruébalo con MCP Inspector: npx @modelcontextprotocol/inspector node build/server.js
  5. Regístralo en el .mcp.json del proyecto
  6. Reinicia Claude Code y comprueba que aparecieron las tools
  7. Pídele a Claude: "Crea una nota sobre la reunión de hoy"; debería usar tu tool
  8. Bonus: agrega una tool search_notes que busque en el contenido de las notas con grep

Objetivo: escribir un servidor MCP que funcione desde cero, registrarlo y probarlo en Claude Code.


Herramientas y recursos

  • @modelcontextprotocol/server: npm install @modelcontextprotocol/server, el SDK oficial de TypeScript
  • mcp: pip install "mcp[cli]", el SDK de Python (clase MCPServer, antes FastMCP)
  • zod: npm install zod, tipado de los parámetros de las tools (obligatorio con el SDK de TypeScript)
  • MCP Inspector: npx @modelcontextprotocol/inspector, para probar sin Claude
  • Documentación: modelcontextprotocol.io, la especificación del protocolo
  • Página oficial de MCP en Claude Code: https://code.claude.com/docs/en/mcp
  • GitHub: github.com/modelcontextprotocol/servers, cientos de servidores listos
  • Comandos de CLI:
    • claude mcp add: agregar un servidor
    • claude mcp list: lista de servidores
    • claude mcp get <name>: detalles de un servidor
    • claude mcp remove <name>: eliminar
    • claude mcp add-from-claude-desktop: importar desde Claude Desktop
    • claude mcp add-json <name> '<json>': agregar desde JSON
    • claude mcp serve: correr Claude Code como servidor MCP
    • /mcp: estado de los servidores dentro de Claude Code (incluida la autenticación OAuth)

Ideas clave

Un servidor MCP es un programa común en TypeScript o Python. De 30 a 50 líneas de código le dan a Claude herramientas nuevas. La complejidad solo crece con la complejidad de tu lógica, no por el protocolo MCP.

Transportes: stdio (local), HTTP (remoto, el recomendado), SSE (obsoleto), WebSocket (vía configuración JSON). Tres scopes: local (por defecto), project (.mcp.json para el equipo), user (todos los proyectos).

Economía de tokens: los resultados de las llamadas MCP consumen contexto (las descripciones de las herramientas, por defecto, se cargan cuando hacen falta). Aviso con más de 10,000 tokens de salida. Límite por defecto de 25,000 tokens. Se ajusta con MAX_MCP_OUTPUT_TOKENS.

Prueba con MCP Inspector antes de conectarlo a Claude: te ahorra tiempo de depuración.

Puedes ligar servidores MCP a subagentes con el campo mcpServers del frontmatter: el servidor solo se conecta mientras trabaja el subagente y no llena el contexto de la conversación principal.

.mcp.json admite variables de entorno (${VAR}, ${VAR:-default}): puedes subirlo a git sin secretos.


Siguiente lección

→ Portafolio y casos de éxito: cómo mostrar tu valor

La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso