Módulo 7: Testing, Debugging e Integración
Troubleshooting y Errores Comunes
Troubleshooting y Errores Comunes
Descripción de la cápsula
Esta cápsula es tu guía de referencia. Cuando algo falle — y va a fallar — vienes aquí. Cada error tiene el mismo formato: síntomas (qué ves), diagnóstico (cómo investigar), causa (por qué pasa), y solución (cómo arreglarlo). Sin ambigüedad, sin rodeos.
Los errores están organizados por categoría: conexión, transport, tools, resources, permisos, y configuración. Al final, hay un mini-proyecto que integra todo lo aprendido en este módulo: construir un test suite completo para uno de tus MCP servers.
Errores de conexión
Error 1: "Server failed to start"
Síntomas: Claude Code muestra "disconnected" en /mcp. MCP Inspector no conecta.
Diagnóstico:
# Ejecuta el server directamente
node dist/index.js < /dev/null 2>&1
echo $? # exit code distinto de 0 = error
# Para Python
python server.py < /dev/null 2>&1
Causas y soluciones:
| Causa | Solución |
|---|---|
| Build no actualizado | npm run build |
| Dependencia faltante | npm install |
| Path incorrecto en settings | Verifica el path absoluto en ~/.claude/settings.json |
| Puerto en uso (HTTP transport) | Cambia el puerto o mata el proceso existente |
| Error de sintaxis en el código | Revisa la salida de tsc --noEmit |
Error 2: "Connection refused"
Síntomas: El client intenta conectar pero recibe "connection refused."
Diagnóstico:
# Verifica que el server está ejecutándose
ps aux | grep "node dist/index.js"
# Verifica el puerto (para HTTP transport)
lsof -i :3000
Causa: El server no está escuchando o está escuchando en un puerto/interface diferente.
Solución:
// Verifica que el transport se inicia correctamente
const transport = new StdioServerTransport();
await server.connect(transport);
// Para stdio, no hay puerto — el problema es que el server no arranca
Error 3: "ENOENT: no such file or directory"
Síntomas: Claude Code no puede encontrar el ejecutable del server.
Diagnóstico:
# Verifica que el archivo existe
ls -la ~/path/to/dist/index.js
# Verifica que el comando existe
which node
which python
Solución: Usa paths absolutos en la configuración:
{
"mcpServers": {
"my-server": {
"command": "/usr/local/bin/node",
"args": ["/Users/tu-usuario/proyectos/my-server/dist/index.js"]
}
}
}
Errores de transport
Error 4: "Unexpected token in JSON"
Síntomas: El server arranca pero los requests fallan con "parse error."
Diagnóstico: Busca output no-JSON en stdout:
node dist/index.js < /dev/null 2>/dev/null
# Si ves texto que NO es JSON → ese es el problema
Causa: console.log() en un server con stdio transport. Cualquier texto en stdout que no sea JSON-RPC rompe el parser del client.
Solución:
// ❌ Rompe stdio
console.log("Server iniciado");
// ✅ Usa stderr para logs
console.error("[INFO] Server iniciado");
Revisa todo tu código buscando console.log y cámbialo a console.error. Incluye dependencias que podrían usar console.log internamente.
Error 5: "Timeout waiting for response"
Síntomas: El request se envía pero nunca llega la respuesta.
Diagnóstico:
// Agrega timeout explícito en tu tool
async ({ filePath }) => {
console.error(`[DEBUG] read_file inicio: ${filePath}`);
const start = Date.now();
try {
const content = await fs.readFile(filePath, "utf-8");
console.error(`[DEBUG] read_file completado en ${Date.now() - start}ms`);
return { content: [{ type: "text" as const, text: content }] };
} catch (error) {
console.error(`[ERROR] read_file falló después de ${Date.now() - start}ms`);
throw error;
}
}
Causas comunes:
| Causa | Solución |
|---|---|
| Operación I/O que nunca resuelve | Agrega timeout con Promise.race |
| Deadlock en código async | Revisa que no hay awaits circulares |
| Server procesando request anterior | Verifica que no hay operaciones bloqueantes |
| Network call a servicio caído | Agrega timeout a requests HTTP |
Error 6: "Protocol versión mismatch"
Síntomas: "Unsupported protocol versión" en la respuesta de inicialización.
Causa: Versiones incompatibles del SDK entre client y server.
Solución:
# Actualiza el SDK
npm install @modelcontextprotocol/sdk@latest
# Verifica la versión instalada
npm list @modelcontextprotocol/sdk
Errores de Tools
Error 7: "Tool not found"
Síntomas: Claude Code intenta usar un tool pero recibe "tool not found."
Diagnóstico:
# Verifica con MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
# ¿El tool aparece en la lista?
Causas y soluciones:
| Causa | Solución |
|---|---|
| Tool no registrado | Verifica que server.tool(...) se ejecuta |
| Nombre del tool con typo | Compara el nombre en el código vs el que Claude Code usa |
| Registro condicional que falla | Revisa que la condición se cumple |
| Build desactualizado | npm run build |
Error 8: "Invalid arguments"
Síntomas: "Validation error" o "Invalid arguments" al invocar un tool.
Diagnóstico: Revisa qué argumentos envía Claude Code vs qué espera tu schema:
En MCP Inspector → Panel de mensajes:
Request: { "name": "read_file", "arguments": { "path": "/tmp/test.txt" } }
^^^^
Tu schema espera "filePath", no "path"
Causa: Mismatch entre lo que Claude Code envía y lo que tu schema Zod espera.
Solución:
// Verifica que tu descripción guía a Claude correctamente
server.tool(
"read_file",
"Lee un archivo. El parámetro filePath debe ser la ruta completa al archivo.",
{ filePath: z.string().describe("Ruta completa al archivo (e.g., /home/user/file.txt)") },
// ...
);
Error 9: "Tool execution failed"
Síntomas: El tool se invoca pero retorna un error genérico.
Diagnóstico: Agrega logging detallado en el handler:
async ({ filePath }) => {
try {
// ... tu lógica
} catch (error) {
console.error("[ERROR] Tool failed:", error);
return {
content: [{ type: "text" as const, text: `Error: ${(error as Error).message}\nStack: ${(error as Error).stack}` }],
isError: true,
};
}
}
Causa: Excepción no capturada en el handler del tool. Sin try/catch, el error se propaga como un error genérico del protocolo.
Errores de Resources
Error 10: "Resource not found"
Síntomas: Intentar leer un resource retorna "resource not found."
Diagnóstico:
# Verifica que el resource está registrado
# En MCP Inspector, panel de Resources → ¿aparece el URI?
Causa: El URI del request no coincide exactamente con el URI registrado.
Solución:
// Verifica la coincidencia exacta
server.resource(
"project-status",
"status://project", // ← Este URI exacto
{ description: "..." },
async () => { ... }
);
// El client debe usar exactamente "status://project"
// No "status://project/" (trailing slash)
// No "Status://project" (case sensitive)
Error 11: "Resource returned invalid data"
Síntomas: El resource retorna datos pero el client no puede procesarlos.
Causa: El formato de la respuesta no cumple el schema de MCP.
Solución: Asegúrate de retornar el formato correcto:
// ✅ Formato correcto
async () => ({
contents: [{
uri: "status://project",
text: JSON.stringify({ status: "ok" }),
mimeType: "application/json",
}],
})
// ❌ Formato incorrecto (falta uri o mimeType)
async () => ({
contents: [{
text: "algo",
}],
})
Errores de permisos y configuración
Error 12: "Permission denied"
Síntomas: Claude Code se niega a invocar un tool.
Causa: Los permisos de MCP no están configurados o fueron denegados.
Solución:
# Verifica permisos en Claude Code
/mcp
# Si el tool aparece pero no se puede usar, revisa los permisos del filesystem
ls -la /ruta/que/el/tool/accede
Error 13: "JSON parse error en settings"
Síntomas: Claude Code no reconoce ningún MCP server.
Diagnóstico:
# Valida el JSON del settings file
python3 -c "import json; json.load(open('$HOME/.claude/settings.json'))"
Causas comunes: Comma trailing, comillas simples en vez de dobles, o comments (JSON no soporta comments).
// ❌ Errores comunes de JSON
{
"mcpServers": {
"server": {
"command": "node",
"args": ["index.js"], // ← trailing comma si es el último campo
}
}
}
// ✅ JSON válido
{
"mcpServers": {
"server": {
"command": "node",
"args": ["index.js"]
}
}
}
Tabla de referencia rápida
| Error | Causa más probable | Fix rápido |
|---|---|---|
| Server disconnected | Server crashea al iniciar | npm run build && node dist/index.js < /dev/null |
| Connection refused | Server no está ejecutándose | Verificar proceso y path |
| ENOENT | Path incorrecto en settings | Usar paths absolutos |
| JSON parse error (transport) | console.log en stdio server | Cambiar a console.error |
| Timeout | Operación I/O bloqueante | Agregar timeout con Promise.race |
| Tool not found | Tool no registrado o build viejo | npm run build, verificar con Inspector |
| Invalid arguments | Schema mismatch | Verificar nombre de parámetros en Zod |
| Resource not found | URI mismatch (case, trailing slash) | Comparar URI exacto |
| Permission denied | Permisos no aceptados | /mcp para verificar estado |
| Settings parse error | JSON inválido | Validar con python -c "import json; ..." |
Mini-proyecto: Test suite completo para un MCP server
Objetivo
Construye un test suite completo para uno de los MCP servers que creaste en los módulos 4 o 5. El test suite debe cubrir las tres capas de testing y servir como template para el proyecto del Módulo 8.
Requisitos
Tu test suite debe incluir:
- Al menos 3 unit tests de tools — happy path, error handling, edge cases
- Al menos 2 unit tests de resources — datos válidos, formato correcto
- Al menos 3 integration tests — capabilities, protocolo, flujo completo
- Al menos 2 tests de error handling — inputs inválidos, servicios caídos
- Setup y teardown — archivos temporales, cleanup, fixtures
- Logging — el server debe tener logging a stderr
Estructura del proyecto
my-mcp-server/
├── src/
│ ├── index.ts # Server con createServer() exportado
│ └── logger.ts # Logger a stderr
├── tests/
│ ├── tools.test.ts # Unit tests de tools
│ ├── resources.test.ts # Unit tests de resources
│ └── server.test.ts # Integration tests
├── vitest.config.ts
├── tsconfig.json
└── package.json
Para Python:
my-mcp-server-python/
├── server.py
├── logger.py
├── tests/
│ ├── __init__.py
│ ├── test_tools.py
│ ├── test_resources.py
│ └── test_server.py
└── pyproject.toml
Paso 1: Setup del test framework
TypeScript:
npm install -D vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
testTimeout: 10000,
},
});
Python:
pip install pytest pytest-asyncio
# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
Paso 2: Helper de testing
// tests/helpers.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createServer } from "../src/index.js";
export async function createTestClient(): Promise<{ client: Client; cleanup: () => Promise<void> }> {
const server = createServer();
const client = new Client({ name: "test-client", version: "1.0.0" });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
return {
client,
cleanup: async () => { await client.close(); },
};
}
Paso 3: Escribir los tests
Usa los patrones de la cápsula 02 como referencia. Aquí va un template mínimo:
// tests/tools.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { createTestClient } from "./helpers.js";
import fs from "fs/promises";
import os from "os";
import path from "path";
let client: Client;
let cleanup: () => Promise<void>;
let tmpDir: string;
beforeAll(async () => {
({ client, cleanup } = await createTestClient());
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "mcp-test-"));
// Crea archivos de prueba según tus tools
});
afterAll(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
await cleanup();
});
describe("Tool: [nombre de tu tool]", () => {
it("happy path — retorna resultado correcto", async () => {
const result = await client.callTool({
name: "tu_tool",
arguments: { /* parámetros válidos */ },
});
expect(result.isError).toBeUndefined();
// Verifica el contenido del resultado
});
it("error handling — input inválido", async () => {
const result = await client.callTool({
name: "tu_tool",
arguments: { /* parámetros inválidos */ },
});
expect(result.isError).toBe(true);
});
it("edge case — [describe el edge case]", async () => {
// Test para un caso límite específico de tu tool
});
});
// tests/server.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { createTestClient } from "./helpers.js";
let client: Client;
let cleanup: () => Promise<void>;
beforeAll(async () => {
({ client, cleanup } = await createTestClient());
});
afterAll(async () => { await cleanup(); });
describe("Server integration", () => {
it("lista todos los tools registrados", async () => {
const { tools } = await client.listTools();
expect(tools.length).toBeGreaterThan(0);
// Verifica que tus tools específicos aparecen
});
it("cada tool tiene descripción y schema", async () => {
const { tools } = await client.listTools();
for (const tool of tools) {
expect(tool.description).toBeTruthy();
expect(tool.inputSchema).toBeDefined();
}
});
it("resources están accesibles", async () => {
const { resources } = await client.listResources();
for (const resource of resources) {
const result = await client.readResource({ uri: resource.uri });
expect(result.contents).toHaveLength(1);
}
});
});
Paso 4: Ejecutar y verificar
# TypeScript
npm test
# Python
pytest -v
# Output esperado:
# ✓ Tool: [nombre] > happy path
# ✓ Tool: [nombre] > error handling
# ✓ Tool: [nombre] > edge case
# ✓ Resource: [nombre] > datos válidos
# ✓ Resource: [nombre] > formato correcto
# ✓ Server > lista tools
# ✓ Server > tools con descripción
# ✓ Server > resources accesibles
# ✓ Error handling > input inválido
# ✓ Error handling > servicio caído
#
# 10 tests passed
Criterios de éxito del mini-proyecto
□ Al menos 10 tests en total
□ Cubre tools (happy path + error + edge case)
□ Cubre resources (datos + formato)
□ Cubre integration (capabilities + protocolo)
□ Setup/teardown funciona (archivos temporales se limpian)
□ Todos los tests pasan en verde
□ El server tiene logging a stderr
□ npm test / pytest ejecuta todo con un solo comando
Resumen del módulo
A lo largo de las 5 cápsulas de este módulo, aprendiste:
- Cápsula 01: La transición de "funciona" a "funciona de forma confiable" — por qué testing es inversión, no burocracia
- Cápsula 02: Tests automatizados con Vitest y pytest — unit tests, integration tests, InMemoryTransport
- Cápsula 03: Debugging con MCP Inspector, logging a stderr, tracing de performance
- Cápsula 04: Configuración de Claude Code — settings, scopes, permisos, verificación paso a paso
- Cápsula 05: Guía de troubleshooting para los 13 errores más comunes + mini-proyecto de test suite
Lo que ahora puedes hacer
- ✅ Escribir tests automatizados para cualquier MCP server
- ✅ Debuggear problemas con MCP Inspector y logging
- ✅ Configurar MCP servers en Claude Code con confianza
- ✅ Diagnosticar y resolver errores comunes sin buscar en Google
- ✅ Construir un test suite completo como base para proyectos reales
Lo que viene
El Módulo 8 es el proyecto final. Todo converge: construyes un MCP server production-ready conectado a datos reales, con test suite completo, documentación, y demo end-to-end en Claude Code. Los patrones de testing, debugging, y configuración que aprendiste aquí son la base directa del proyecto.
La transición es: "Ya sabes construir, testear, debuggear y conectar MCP servers. Ahora construye uno real de principio a fin."
Recursos adicionales
- Vitest Documentation — Framework de testing para TypeScript
- pytest Documentation — Framework de testing para Python
- MCP Inspector — Herramienta de debugging visual
- Claude Code MCP Documentation — Configuración oficial
- MCP Specification — Error Handling — Manejo de errores en el protocolo
- MCP TypeScript SDK — SDK oficial con ejemplos
- MCP Python SDK — SDK oficial para Python
- Node.js Debugging Guide — Debugging de aplicaciones Node.js