Abre un terminal vacío. Al terminar este tutorial, ese directorio tendrá un servidor MCP en TypeScript que ofrece evidencias de CI a Claude Code y Codex sin conceder acceso genérico al sistema de archivos. Vas a compilar el proyecto, llamar sus tools con MCP Inspector y conectar el mismo proceso a los dos agentes.
El ejemplo corre localmente y solo lee datos. Consulta un archivo JSON con fallos del pipeline, valida cada argumento con Zod y recorta las respuestas grandes antes de que ocupen la ventana de contexto. El código es pequeño para poder entenderlo, pero resuelve una integración real que puedes adaptar a una API interna.
Resultado del tutorial
- Un servidor MCP por stdio con dos tools tipadas.
- Datos restringidos a un archivo conocido dentro del workspace.
- Respuestas compactas y errores que el agente puede interpretar.
- Verificación en Inspector antes de habilitar el servidor para el agente.
Qué vas a construir
El servidor lee .mcp-data/ci-failures.json y expone dos operaciones. consultar_falhas_ci filtra una lista corta, mientras que ler_falha_ci recupera un registro por su identificador. Ninguna tool escribe archivos, ejecuta shell o acepta una ruta arbitraria.
Por ejemplo, ler_falha_ci rechaza un identificador mal formado antes de leer el archivo. La validación pertenece a la frontera de la tool, no al prompt del agente.
Esa superficie estrecha es intencional. MCP organiza las integraciones mediante tools, recursos y prompts, y el tutorial oficial usa el SDK para registrar tools con schemas de entrada (Model Context Protocol, "Build an MCP server", consultado el 23/07/2026). Un primer servidor enfocado resulta más fácil de entender, probar y revisar.
El recorrido de una petición es sencillo:
- El cliente inicia el proceso Node.js por stdio.
- El servidor anuncia sus dos tools.
- El agente envía argumentos que pasan por el schema Zod.
- El handler lee el archivo conocido, reduce el resultado y devuelve texto estructurado.
- Inspector o el agente muestra la respuesta.
Este tutorial lleva la política de allowlists MCP para agentes de código a una implementación pequeña que puedes ejecutar.
Requisitos previos
Necesitas una versión LTS mantenida de Node.js, npm y un terminal. El código usa módulos ESM y la API nativa de archivos de Node. Claude Code o Codex son útiles para el último paso, pero Inspector permite completar la verificación del protocolo sin instalar ninguno de los dos agentes.
Comprueba el entorno:
node --version
npm --version
Crea un directorio descartable para el tutorial:
mkdir mcp-evidencias-ci
cd mcp-evidencias-ci
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript @types/node
mkdir -p src .mcp-data
El SDK oficial cambia con el protocolo. Antes de adaptar el ejemplo a producción, consulta el quickstart y las notas de versión del SDK TypeScript de Model Context Protocol. El artículo no presupone que una versión concreta del paquete vaya a seguir vigente.
Configura TypeScript y los scripts del proyecto
Reemplaza package.json por esta configuración:
{
"name": "mcp-evidencias-ci",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"start": "node build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.0.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
"typescript": "^5.0.0"
}
}
Después crea tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "build",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}
Ejecuta npm install otra vez para alinear el lockfile con el manifiesto editado:
npm install
npm run build
El primer build todavía no encuentra src/index.ts. Ese error es normal en este punto. El próximo paso crea el servidor.
Crea una fuente de evidencias reproducible
Guarda estos datos en .mcp-data/ci-failures.json:
[
{
"id": "ci-1042",
"suite": "auth",
"test": "session_contract",
"file": "tests/auth/session_contract.test.ts",
"message": "cookie de sessão ausente após renovação"
},
{
"id": "ci-1043",
"suite": "billing",
"test": "webhook_idempotency",
"file": "tests/billing/webhook_idempotency.test.ts",
"message": "evento repetido criou uma segunda cobrança"
}
]
El archivo representa un artefacto que CI podría producir después de quitar secretos y reducir los logs. El servidor no recibe la ruta en el tool call. Resuelve una ubicación conocida a partir de MCP_WORKSPACE, lo que elimina una oportunidad sencilla de path traversal.
Mantén el artefacto pequeño en un sistema real. Claude Code avisa cuando una tool MCP genera una respuesta grande y permite configurar un máximo (Claude Code Docs, "MCP output limits and warnings", consultado el 23/07/2026). Limitar los datos en origen es más seguro y barato que pedir al cliente que los recorte después.
Implementa el servidor MCP en TypeScript
Crea src/index.ts con el servidor completo:
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
type CiFailure = {
id: string;
suite: string;
test: string;
file: string;
message: string;
};
const workspace = resolve(process.env.MCP_WORKSPACE ?? process.cwd());
const dataFile = resolve(workspace, ".mcp-data", "ci-failures.json");
const server = new McpServer({
name: "evidencias-ci",
version: "1.0.0",
});
async function loadFailures(): Promise<CiFailure[]> {
const raw = await readFile(dataFile, "utf8");
const parsed: unknown = JSON.parse(raw);
return z
.array(
z.object({
id: z.string(),
suite: z.string(),
test: z.string(),
file: z.string(),
message: z.string(),
}),
)
.parse(parsed);
}
function asText(value: unknown) {
return {
content: [
{
type: "text" as const,
text: JSON.stringify(value, null, 2),
},
],
};
}
server.registerTool(
"consultar_falhas_ci",
{
description:
"Lista poucas falhas de CI por suite ou termo, sem executar comandos.",
inputSchema: {
termo: z.string().trim().max(80).optional(),
limite: z.number().int().min(1).max(10).default(5),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async ({ termo, limite }) => {
try {
const failures = await loadFailures();
const needle = termo?.toLocaleLowerCase("pt-BR");
const selected = failures
.filter((failure) => {
if (!needle) return true;
return [failure.id, failure.suite, failure.test, failure.message]
.join(" ")
.toLocaleLowerCase("pt-BR")
.includes(needle);
})
.slice(0, limite);
return asText({
total_retornado: selected.length,
falhas: selected,
});
} catch (error) {
return {
...asText({
erro: "nao_foi_possivel_ler_evidencias",
detalhe: error instanceof Error ? error.message : "erro desconhecido",
}),
isError: true,
};
}
},
);
server.registerTool(
"ler_falha_ci",
{
description: "Lê uma falha de CI pelo identificador exato.",
inputSchema: {
id: z.string().regex(/^ci-[0-9]+$/),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async ({ id }) => {
try {
const failures = await loadFailures();
const failure = failures.find((item) => item.id === id);
if (!failure) {
return {
...asText({ erro: "falha_nao_encontrada", id }),
isError: true,
};
}
return asText(failure);
} catch (error) {
return {
...asText({
erro: "nao_foi_possivel_ler_evidencias",
detalhe: error instanceof Error ? error.message : "erro desconhecido",
}),
isError: true,
};
}
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Servidor MCP de evidências de CI pronto em stdio");
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
inputSchema impide que el agente envíe objetos arbitrarios. Las annotations declaran intención de lectura, pero no sustituyen los permisos del cliente. El límite máximo evita que una consulta amplia vuelque todo el artefacto en el contexto.
Hay otro detalle fácil de pasar por alto: un servidor stdio no debe usar console.log() para diagnóstico. Stdout transporta los mensajes JSON-RPC; cualquier texto suelto puede romper la conexión. El tutorial oficial dirige los logs a stderr con console.error() (Model Context Protocol, "Logging in MCP Servers", consultado el 23/07/2026).
Compila y prueba con MCP Inspector
Compila el servidor:
npm run build
El comando debe crear build/index.js sin errores de TypeScript. Ahora abre Inspector con el workspace definido de forma explícita:
MCP_WORKSPACE="$PWD" npx @modelcontextprotocol/inspector node build/index.js
La documentación oficial presenta Inspector como una herramienta interactiva para comprobar conexión, negociación de capacidades, schemas y ejecución de tools (Model Context Protocol, "MCP Inspector", consultado el 23/07/2026). En la interfaz:
- Conecta el proceso local.
- Abre la pestaña de tools.
- Selecciona
consultar_falhas_ci. - Envía
{"termo":"auth","limite":2}. - Confirma que la respuesta solo contiene
ci-1042. - Llama
ler_falha_cicon{"id":"ci-1043"}. - Prueba
{"id":"archivo-secreto"}y confirma que el schema rechaza la entrada.

Inspector debe ir antes que el agente. Si la tool falla de forma aislada, añadir un modelo solo encarece el diagnóstico y lo vuelve menos determinista.
Conecta el servidor a Codex
Codex admite servidores locales por stdio y comparte la configuración MCP entre CLI y la extensión del IDE. La documentación oficial permite añadir el proceso con codex mcp add o configurarlo en config.toml (OpenAI, "Model Context Protocol", consultado el 23/07/2026).
Ejecuta dentro del directorio del proyecto:
codex mcp add evidencias-ci \
--env MCP_WORKSPACE="$PWD" \
-- node "$PWD/build/index.js"
Comprueba la configuración:
codex mcp list
Dentro de Codex, abre /mcp y confirma que se descubrieron las dos tools. Empieza con una petición estrecha: "consulta los fallos de la suite auth y no modifiques archivos".
Para un entorno compartido, la configuración de proyecto puede habilitar solo las tools de lectura y pedir aprobación por defecto. Codex ofrece allowlists, denylists y modos de aprobación por servidor y por tool. Es una segunda barrera además de las annotations del SDK.
Conecta el mismo servidor a Claude Code
Claude Code también inicia servidores locales por stdio. Su documentación recomienda este transporte para scripts y tools locales y ofrece alcances local, de proyecto y de usuario (Claude Code Docs, "Connect Claude Code to tools via MCP", consultado el 23/07/2026).
Añade el servidor solo a este proyecto:
claude mcp add evidencias-ci \
--transport stdio \
--scope project \
--env MCP_WORKSPACE="$PWD" \
-- node "$PWD/build/index.js"
Después ejecuta:
claude mcp get evidencias-ci
claude mcp list
Abre /mcp dentro de Claude Code y comprueba la conexión. Las configuraciones de proyecto piden aprobación antes de usarse, algo adecuado para un servidor versionado con el repositorio.
Los loops largos de reparación plantean un segundo problema: conservar la evidencia útil sin reenviar todo el historial. Yo uso RemoteCode para extender flujos agentic de Claude Code y Codex con menos contexto repetido en esa situación. Es una herramienta propia, y la relación es directa: una salida MCP compacta y una menor repetición de contexto atacan el mismo coste desde lados distintos.
Errores comunes y diagnóstico
| Síntoma | Causa probable | Solución |
|---|---|---|
spawn node ENOENT |
El cliente no encuentra el ejecutable. | Usa la ruta absoluta que devuelve which node. |
| La conexión se cierra al iniciar | Algún log llegó a stdout. | Cambia console.log() por console.error(). |
| El servidor conecta, pero no muestra tools | El build está desactualizado o se inició otro archivo. | Ejecuta npm run build y revisa build/index.js. |
ENOENT durante el tool call |
MCP_WORKSPACE apunta a otro directorio. |
Añade de nuevo el servidor con la ruta absoluta correcta. |
| La respuesta crece demasiado | El handler no limita registros o campos. | Mantén un límite, recorta campos y pagina en origen. |
| Funciona en Inspector, pero no en el agente | La configuración o la política del cliente bloquea el servidor. | Revisa /mcp, codex mcp list o claude --debug mcp. |
La guía de diagnóstico de Claude Code identifica las rutas relativas como causa común y recomienda claude --debug mcp para leer stderr (Claude Code Docs, "Debug your configuration", consultado el 23/07/2026). El mismo orden sirve en Codex: comprueba comando, argumentos, directorio y variables antes de investigar el modelo.
Registra estos eventos como parte de la observabilidad de agentes de código. Tool, duración, tamaño de respuesta y error ofrecen una evidencia mejor que el resumen libre del agente.
Límites antes de producción
Este proyecto prueba el flujo local. No implementa autenticación, aislamiento multiusuario, rate limit, auditoría persistente ni transporte remoto. No publiques el proceso en la red cambiando únicamente stdio por HTTP.
Para un servicio remoto, usa Streamable HTTP, autentica cada cliente, valida audience y scope, aplica timeouts y separa las tools de lectura de las mutaciones. Claude Code marca SSE como legado y recomienda HTTP para servidores remotos actuales (Claude Code Docs, "Add a remote HTTP server", consultado el 23/07/2026).
Tampoco trates las annotations como control de acceso. Describen comportamiento al cliente. La autorización debe existir en el servidor y en la política del agente. La siguiente capa de seguridad es un gate de allowlist MCP en CI.
Si el caso crece hasta convertirse en búsqueda semántica del repositorio, no amplíes esta tool hasta que lea todo. Separa recuperación, reranking y presupuesto de respuesta como explica RAG de codebase para agentes.
Revisa el árbol de dependencias y sus avisos de seguridad antes del despliegue. Un build limpio de TypeScript demuestra compatibilidad de tipos, no la ausencia de vulnerabilidades transitivas.
Comprueba el resultado
El tutorial funcionó cuando se cumplen estas condiciones:
npm run buildtermina sin error.- Inspector descubre exactamente dos tools.
- La búsqueda de
authdevuelve un fallo. - Un identificador fuera del formato es rechazado por el schema.
- Codex o Claude Code muestra el servidor como conectado.
- Ninguna tool acepta una ruta, un comando de shell o una operación de escritura.
El mismo ejemplo también se verificó sin la interfaz de Inspector. Los bloques de código se instalaron en un directorio limpio, se compilaron y se llamaron desde un cliente MCP por stdio. El cliente descubrió las dos tools, y la consulta de auth devolvió únicamente ci-1042.
Pide al agente que explique un fallo usando solo el servidor MCP y compara su respuesta con el JSON de origen. Es un eval pequeño, pero mide algo útil: la evidencia debe permanecer fiel y el agente no debe inventar logs ausentes.
Para incorporar la verificación al pipeline, conecta el tool call con evals de PR para agentes en CI. MCP entrega la evidencia; el eval juzga si el agente la usó bien.
Preguntas frecuentes
¿Debo usar stdio o Streamable HTTP?
Usa stdio para un servidor local iniciado por el cliente. Usa Streamable HTTP cuando varios clientes necesiten un servicio remoto. No expongas este ejemplo sin autenticación, autorización, límites y auditoría.
¿Puedo añadir una tool que corrija el fallo?
Sí, pero conviértela en otra tool con permisos y aprobación propios. Separar lectura y mutación impide que una consulta de evidencia herede la capacidad de editar código o reiniciar un pipeline.
¿Por qué usar Inspector si el agente ya muestra las tools?
Inspector separa el protocolo y el handler del comportamiento del modelo. Permite validar schemas, entradas inválidas, errores y respuestas de manera repetible. Eso reduce el espacio de investigación cuando falla la integración.
¿Cómo evito que un servidor MCP consuma demasiado contexto?
Limita registros y campos en el servidor, ofrece filtros explícitos y pagina los conjuntos grandes. Un presupuesto de contexto para agentes de código ayuda a medir el coste combinado de tools, instrucciones e historial.