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.
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.
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:
Funciona para uso puntual, pero no como solución a largo plazo.
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 serviciosGET /api/v1/services/:name/status — consultar el estado de un servicioPOST /api/v1/services/:name/deploy — lanzar un despliegueGET /api/v1/services/:name/logs — ver los logs de despliegue recientesPrimero, 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
{
"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).
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
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:
| 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 |
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.
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.
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.
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.
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.