Conectar Claude Code a herramientas internas: integra tus sistemas con MCP

Conecta los sistemas internos de tu empresa a Claude Code mediante MCP: desde un servidor fetch genérico hasta escribir un MCP Server dedicado, cubriendo plataformas de despliegue, monitorización, tickets y principios de diseño de herramientas.


En el artículo anterior hablamos de conectar bases de datos. Las bases de datos tienen protocolos estándar, así que la integración es relativamente directa. Pero la mayoría de los equipos también dependen de un montón de sistemas internos en su día a día: plataformas de despliegue, paneles de monitoreo, sistemas de tickets, APIs internas, servicios de configuración centralizada.

Estos sistemas normalmente no tienen un MCP Server listo para usar, pero casi todos exponen una API HTTP. En este artículo veremos cómo usar MCP para conectar estas herramientas internas a Claude Code, permitiéndole consultar métricas de monitoreo, revisar el estado de despliegues y gestionar tickets directamente.

Dos caminos

Hay dos formas de integrar herramientas internas:

Opción 1: Usar un MCP Server HTTP genérico
Existen MCP Servers genéricos en la comunidad que pueden envolver cualquier API REST como una herramienta MCP. Escribes un archivo de descripción de la API y el servidor lo convierte en herramientas que Claude puede invocar. Ideal para APIs con estructuras simples que no requieren lógica compleja.

Opción 2: Escribir tu propio MCP Server
Usa el SDK de MCP en TypeScript o Python para escribir un servidor dedicado, con control total sobre la definición de herramientas, validación de parámetros y manejo de errores. Ideal cuando necesitas combinar múltiples APIs, transformar datos o agregar lógica de negocio.

En este artículo cubrimos ambas opciones, empezando por la más sencilla.

Opción 1: Integración rápida con mcp-server-fetch

La solución más ligera es usar el paquete oficial @anthropic-ai/mcp-server-fetch, que permite a Claude hacer peticiones HTTP directamente. La configuración es mínima:

{
  "mcpServers": {
    "fetch": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/mcp-server-fetch"]
    }
  }
}

Una vez configurado, Claude puede llamar directamente a APIs internas:

Revisa el estado actual del servicio en la plataforma de despliegue https://deploy.internal.com/api/v1/services/user-service

Claude enviará una petición GET, obtendrá la respuesta y te la mostrará interpretada.

Pero este enfoque tiene limitaciones claras:

  • Cada vez hay que indicarle a Claude la URL completa y el formato de la petición
  • No hay validación de parámetros; Claude podría equivocarse en la ruta
  • La autenticación hay que pasarla cada vez o ponerla en el prompt (poco seguro)
  • No es posible combinar múltiples llamadas a APIs

Funciona para uso puntual, pero no como solución a largo plazo.

Opción 2: Escribir un MCP Server dedicado

Cuando necesitas usar un sistema interno de forma recurrente, escribir un MCP Server dedicado es la mejor opción. A continuación lo demostramos con un caso real: integrar la plataforma de despliegue de la empresa.

Supongamos que tu plataforma de despliegue expone las siguientes APIs:

  • GET /api/v1/services — listar todos los servicios
  • GET /api/v1/services/:name/status — consultar el estado de un servicio
  • POST /api/v1/services/:name/deploy — lanzar un despliegue
  • GET /api/v1/services/:name/logs — ver los logs de despliegue recientes

Escribir el MCP Server en TypeScript

Primero, inicializa el proyecto:

mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

Código principal en src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const API_BASE = process.env.DEPLOY_API_URL!;
const API_TOKEN = process.env.DEPLOY_API_TOKEN!;

async function api(path: string, method = "GET", body?: unknown) {
  const res = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) {
    throw new Error(`API error: ${res.status} ${await res.text()}`);
  }
  return res.json();
}

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

// Listar todos los servicios
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
  const data = await api("/api/v1/services");
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});

// Ver el estado de un servicio específico
server.tool(
  "service_status",
  "查看指定服务的当前部署状态、版本号和健康检查结果",
  { name: z.string().describe("服务名称,如 user-service") },
  async ({ name }) => {
    const data = await api(`/api/v1/services/${name}/status`);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

// Ver logs de despliegue
server.tool(
  "deploy_logs",
  "查看指定服务最近的部署日志",
  {
    name: z.string().describe("服务名称"),
    limit: z.number().optional().default(10).describe("返回条数,默认 10"),
  },
  async ({ name, limit }) => {
    const data = await api(`/api/v1/services/${name}/logs?limit=${limit}`);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

// Lanzar un despliegue
server.tool(
  "trigger_deploy",
  "触发指定服务的部署。这是一个写操作,会实际影响生产环境",
  {
    name: z.string().describe("服务名称"),
    version: z.string().describe("要部署的版本号或 git ref"),
  },
  async ({ name, version }) => {
    const data = await api(`/api/v1/services/${name}/deploy`, "POST", {
      version,
    });
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

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

Compilar:

npx tsc

Configurar Claude Code

{
  "mcpServers": {
    "deploy": {
      "command": "node",
      "args": ["/path/to/mcp-deploy/dist/index.js"],
      "env": {
        "DEPLOY_API_URL": "https://deploy.internal.com",
        "DEPLOY_API_TOKEN": "your-api-token"
      }
    }
  }
}

Coloca el token en .claude/settings.local.json (sin commitear a git), y la URL en .claude/settings.json (commiteado, compartido con el equipo).

En la práctica

Una vez configurado, la conversación se vuelve natural:

¿Cuál es el estado actual de user-service?

→ Claude llama a service_status("user-service")
→ Respuesta: en ejecución, versión v2.3.1, último despliegue hace 2 horas, health checks pasados
¿Alguno de los despliegues recientes falló?

→ Claude llama a deploy_logs("user-service", 20)
→ Analiza los logs y te indica que el tercer despliegue se revirtió por un timeout en el health check
Despliega user-service a la versión v2.3.2

→ Claude llama a trigger_deploy("user-service", "v2.3.2")
→ Como la descripción de la herramienta indica "operación de escritura", Claude te pedirá confirmación primero

¿Conviene integrar operaciones de escritura?

Esta es una pregunta que merece reflexión.

Las operaciones de lectura se pueden integrar sin problema. Consultar estados, ver logs, buscar tickets: estas operaciones no tienen efectos secundarios; si Claude se equivoca, no hay pérdida.

Las operaciones de escritura se dividen en dos categorías:

Las de bajo riesgo se pueden integrar, pero hay que señalarlo claramente en la descripción de la herramienta. Claude solicita confirmación automáticamente para operaciones marcadas con efectos secundarios. Por ejemplo: crear tickets, enviar mensajes, actualizar configuraciones.

Las de alto riesgo es mejor no integrarlas. Eliminar recursos, lanzar rollbacks, modificar permisos: estas operaciones tienen consecuencias graves e irreversibles; es más seguro hacerlas manualmente.

Si de todas formas decides integrar operaciones de escritura, al menos haz dos cosas:

  1. Indica claramente en la descripción de la herramienta «esta es una operación de escritura que afecta al entorno de producción»
  2. Añade las validaciones de seguridad necesarias en el MCP Server (por ejemplo, impedir despliegues al namespace de producción)

Ideas de integración para sistemas internos comunes

Sistema Herramientas expuestas Consideraciones
Plataforma de despliegue (K8s / Kamal) Consultar estado de servicios, ver logs, lanzar despliegues Confirmar operaciones de escritura
Monitoreo (Grafana / Datadog) Consultar métricas, ver historial de alertas Limitar el rango temporal de consultas para evitar exceso de datos
Tickets (Jira / Linear) Buscar tickets, crear tickets, actualizar estados Crear tickets es escritura, pero de bajo riesgo
Documentación interna (Notion / Confluence) Buscar documentos, leer contenido de páginas Cuidado con la paginación; no traer demasiado de una sola vez
Configuración centralizada (Consul / etcd) Leer configuraciones, comparar diferencias entre entornos Solo lectura; no integrar escritura
CI/CD (GitHub Actions / Jenkins) Ver estado de builds, lanzar builds Lanzar builds se considera escritura de riesgo medio

Principios de diseño de herramientas

Diseñar herramientas MCP no es lo mismo que diseñar APIs. Las APIs son para programadores; las herramientas son para la IA. Hay varios principios a tener en cuenta:

Los nombres deben ser descriptivos

✗ get_svc_stat     — Claude puede no adivinar el significado de la abreviación
✓ service_status   — queda claro de inmediato qué hace

Las descripciones deben estar pensadas para la IA

La descripción de una herramienta no es documentación para humanos; es lo que Claude usa para decidir cuándo invocarla. La descripción debe explicar: qué hace la herramienta, qué devuelve y cuándo debe usarse.

✗ "Obtener estado del servicio"
✓ "Consultar el estado de despliegue actual, número de versión y resultado de health checks de un servicio específico. Usar cuando el usuario pregunte si un servicio está funcionando correctamente"

Definir los parámetros con claridad usando zod

Los parámetros con .describe() son los que Claude entiende correctamente. Sin descripción, Claude solo puede adivinar por el nombre.

Devolver datos estructurados

Las herramientas MCP devuelven texto, pero conviene que sea JSON formateado. Claude procesa datos estructurados con mucha más precisión que texto plano.

Granularidad adecuada

No metas un flujo complejo en una sola herramienta. Tampoco dividas una consulta simple en tres herramientas. El principio es: una herramienta por operación independiente y con sentido propio.

Dónde colocar el MCP Server

El código del MCP Server puede ubicarse en varios lugares:

Dentro del repositorio del proyecto (recomendado para empezar)

your-project/
├── .claude/settings.json
├── mcp-servers/
│   └── deploy/
│       ├── src/index.ts
│       ├── package.json
│       └── tsconfig.json
└── ...

La ventaja es que el código y la configuración están juntos; el equipo solo necesita clonar e instalar dependencias para empezar a usarlo.

Repositorio independiente

Cuando el MCP Server se necesita en múltiples proyectos, ponlo en un repositorio separado y publícalo como paquete npm o imagen Docker.

{
  "mcpServers": {
    "deploy": {
      "command": "npx",
      "args": ["-y", "@yourcompany/mcp-deploy-server"]
    }
  }
}

Instalación global

Para MCP Servers de uso general en toda la empresa (por ejemplo, autenticación unificada o plataforma de logs centralizada), instálalos globalmente y configúralos en ~/.claude/settings.json.

Consejos de depuración

Los problemas más comunes al desarrollar un MCP Server son «Claude no invoca mi herramienta» o «la invoca pero da error».

Verificar que el servidor arrancó

Tras reiniciar Claude Code, escribe /mcp para ver la lista de MCP Servers conectados. Si tu servidor no aparece, revisa que el command y los args sean correctos.

Probar el servidor de forma aislada

Los MCP Servers se comunican por stdio, así que puedes probarlo directamente en la terminal:

echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js

Si devuelve la lista de herramientas, el servidor en sí funciona bien.

Revisar los logs de invocación de Claude

Claude Code muestra la entrada y salida de cada invocación de herramienta. Si los parámetros son incorrectos, normalmente se debe a que la descripción de la herramienta o la definición de parámetros no es suficientemente clara y Claude la interpretó mal.

Caso práctico: integrar Sentry para seguimiento de errores

Veamos un caso real completo. Supongamos que queremos integrar Sentry en Claude Code para que pueda consultar errores en producción directamente.

server.tool(
  "search_errors",
  "在 Sentry 中搜索最近的错误。用于排查线上问题、查看错误趋势",
  {
    query: z.string().describe("搜索关键词,如错误信息、函数名"),
    hours: z.number().optional().default(24).describe("查看最近多少小时的错误"),
  },
  async ({ query, hours }) => {
    const since = new Date(Date.now() - hours * 3600000).toISOString();
    const data = await api(
      `/api/0/projects/${ORG}/${PROJECT}/issues/?query=${encodeURIComponent(query)}&start=${since}&sort=date`
    );
    const summary = data.map((issue: any) => ({
      title: issue.title,
      count: issue.count,
      firstSeen: issue.firstSeen,
      lastSeen: issue.lastSeen,
      link: issue.permalink,
    }));
    return {
      content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
    };
  }
);

server.tool(
  "error_details",
  "查看 Sentry 中某个错误的详细信息,包括堆栈和最近一次事件",
  { issueId: z.string().describe("Sentry issue ID") },
  async ({ issueId }) => {
    const [issue, latest] = await Promise.all([
      api(`/api/0/issues/${issueId}/`),
      api(`/api/0/issues/${issueId}/events/latest/`),
    ]);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(
            {
              title: issue.title,
              count: issue.count,
              users: issue.userCount,
              stacktrace: latest.entries?.find(
                (e: any) => e.type === "exception"
              ),
            },
            null,
            2
          ),
        },
      ],
    };
  }
);

Una vez integrado, la depuración de problemas en producción se convierte en algo así:

¿Hubo errores 500 nuevos en las últimas 4 horas?

→ Claude busca en Sentry
→ Encuentra 3 issues nuevos, el más grave afecta a 120 usuarios
→ Obtiene automáticamente el stack trace y localiza una excepción de puntero nulo
→ Busca la ubicación correspondiente en el código y propone una solución

Desde la detección del problema hasta la localización en el código, todo el proceso se completa en una sola conversación.

Siguiente paso

En este artículo vimos cómo usar MCP para integrar herramientas internas. La idea central es: si el sistema interno tiene una API HTTP → escribes un MCP Server que la envuelva → Claude puede usarla directamente.

Todos los ejemplos de este artículo envuelven APIs existentes: tanto la plataforma de despliegue como Sentry ya tienen interfaces propias, y el MCP Server solo actúa como capa de traducción y adaptación. En el próximo artículo abordaremos un escenario diferente: qué hacer cuando la funcionalidad que necesitas no tiene una API disponible, y cómo construir un MCP Server desde cero, implementando la lógica, gestionando el estado y manejando interacciones complejas de múltiples pasos.