Abra um terminal vazio. Ao fim deste tutorial, esse diretório terá um servidor MCP em TypeScript que oferece evidências de CI ao Claude Code e ao Codex sem dar acesso genérico ao sistema de arquivos. Você vai compilar o projeto, chamar as ferramentas pelo MCP Inspector e conectar o mesmo processo aos dois agentes.
O exemplo é local e somente leitura. Ele consulta um arquivo JSON com falhas de pipeline, valida cada argumento com Zod e corta respostas grandes antes que elas ocupem a janela de contexto. Isso deixa o código curto o bastante para entender e útil o bastante para adaptar a uma API interna.
Resultado do tutorial
- Um servidor MCP por stdio com duas ferramentas tipadas.
- Dados restritos a um arquivo conhecido dentro do diretório de trabalho.
- Respostas compactas e erros que o agente consegue interpretar.
- Verificação no Inspector antes de liberar o servidor ao agente.
O que você vai construir
O servidor lê .mcp-data/ci-failures.json e expõe duas operações: consultar_falhas_ci, para filtrar uma lista curta, e ler_falha_ci, para recuperar um registro pelo identificador. Nenhuma ferramenta escreve arquivos, executa comandos no shell ou aceita um caminho arbitrário.
Por exemplo, ler_falha_ci rejeita um identificador fora do formato antes de consultar o arquivo. A validação fica na fronteira da ferramenta, não no prompt do agente.
Essa escolha é deliberada. A especificação do MCP organiza integrações em ferramentas, recursos e prompts, enquanto o tutorial oficial usa o SDK para registrar ferramentas com esquemas de entrada (Model Context Protocol, "Build an MCP server", consultado em 23/07/2026). Para um primeiro servidor, uma superfície estreita facilita entender o protocolo e revisar o risco.
O fluxo será este:
- O cliente inicia o processo Node.js por stdio.
- O servidor anuncia as duas ferramentas.
- O agente envia argumentos que passam pelo esquema Zod.
- A função manipuladora lê o arquivo conhecido, reduz o resultado e devolve texto estruturado.
- O Inspector ou o agente mostra a resposta.
Esse tutorial complementa o desenho de MCP com allowlist para agentes de código. Aqui, a política aparece dentro de uma implementação pequena que você pode executar.
Pré-requisitos
Você precisa de Node.js em uma versão LTS mantida, npm e um terminal. O código usa módulos ESM e a API nativa de arquivos do Node. Também é útil ter Claude Code ou Codex instalado para o último passo, mas o Inspector permite concluir toda a verificação sem eles.
Confirme o ambiente:
node --version
npm --version
Use um diretório descartável para acompanhar o 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
O SDK oficial muda com o protocolo. Antes de levar o exemplo para produção, confira o tutorial inicial e as notas de versão do SDK TypeScript do Model Context Protocol. O tutorial evita fixar uma versão no texto porque a instalação deve resolver uma versão atual e compatível no momento da execução.
Configure TypeScript e os scripts do projeto
Substitua o conteúdo de package.json por esta configuração:
{
"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"
}
}
Depois crie tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "build",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}
Execute npm install mais uma vez para alinhar o lockfile ao manifesto editado:
npm install
npm run build
A primeira compilação ainda não encontra src/index.ts. Esse erro é esperado neste ponto. O próximo passo cria o servidor.
Crie uma fonte de evidências reproduzível
Salve este conteúdo em .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"
}
]
O arquivo representa um artefato que seu CI poderia produzir depois de limpar segredos e reduzir logs. O servidor não recebe o caminho na chamada da ferramenta. Ele resolve um local conhecido a partir de MCP_WORKSPACE, o que elimina uma classe simples de travessia de diretórios.
Em um sistema real, mantenha esse artefato pequeno. A documentação do Claude Code alerta quando uma ferramenta MCP retorna saída muito grande e permite configurar o limite máximo (Claude Code Docs, "MCP output limits and warnings", consultado em 23/07/2026). É melhor limitar na origem do que depender do cliente para cortar depois.
Implemente o servidor MCP em TypeScript
Crie src/index.ts com o código 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);
});
O inputSchema impede que o agente envie objetos arbitrários. As annotations declaram a intenção de leitura, mas não substituem permissões no cliente. O limite máximo reduz o risco de uma consulta ampla despejar o arquivo inteiro no contexto.
Há outro detalhe fácil de perder: um servidor stdio não pode usar console.log() para diagnóstico. O stdout carrega as mensagens JSON-RPC; qualquer texto solto pode corromper a conexão. O tutorial oficial recomenda escrever logs em stderr com console.error() (Model Context Protocol, "Logging in MCP Servers", consultado em 23/07/2026).
Compile e teste com o MCP Inspector
Compile o servidor:
npm run build
O comando deve criar build/index.js sem erros de TypeScript. Em seguida, abra o Inspector com o diretório de trabalho explicitamente definido:
MCP_WORKSPACE="$PWD" npx @modelcontextprotocol/inspector node build/index.js
A documentação oficial apresenta o Inspector como uma ferramenta interativa para verificar conexão, negociação de capacidades, schemas e execução de ferramentas (Model Context Protocol, "MCP Inspector", consultado em 23/07/2026). No painel:
- Conecte ao processo local.
- Abra a aba de ferramentas.
- Selecione
consultar_falhas_ci. - Envie
{"termo":"auth","limite":2}. - Confirme que a resposta contém apenas
ci-1042. - Chame
ler_falha_cicom{"id":"ci-1043"}. - Teste
{"id":"arquivo-secreto"}e confirme que o esquema rejeita a entrada.

O Inspector deve vir antes do agente. Se a ferramenta falha isoladamente, adicionar um modelo só torna o diagnóstico mais caro e menos determinístico.
Conecte o servidor ao Codex
O Codex aceita servidores locais por stdio e compartilha a configuração entre CLI e extensão. A documentação oficial permite adicionar o processo com codex mcp add ou configurar uma tabela em config.toml (OpenAI, "Model Context Protocol", consultado em 23/07/2026).
Dentro do diretório do projeto, execute:
codex mcp add evidencias-ci \
--env MCP_WORKSPACE="$PWD" \
-- node "$PWD/build/index.js"
Verifique a configuração:
codex mcp list
Na interface do Codex, use /mcp para confirmar que consultar_falhas_ci e ler_falha_ci foram descobertas. Comece com um pedido estreito: "consulte as falhas da suite auth e não altere arquivos".
Para equipes, uma configuração de projeto pode habilitar somente as ferramentas de leitura e pedir aprovação por padrão. A documentação atual do Codex oferece enabled_tools, disabled_tools e modos de aprovação por servidor e por ferramenta. Isso cria uma segunda barreira além das annotations do SDK.
Conecte o mesmo servidor ao Claude Code
O Claude Code também inicia servidores locais por stdio. A documentação recomenda esse transporte para scripts e ferramentas que precisam de acesso local e oferece escopos local, de projeto e de usuário (Claude Code Docs, "Connect Claude Code to tools via MCP", consultado em 23/07/2026).
Adicione o servidor apenas ao projeto:
claude mcp add evidencias-ci \
--transport stdio \
--scope project \
--env MCP_WORKSPACE="$PWD" \
-- node "$PWD/build/index.js"
Depois rode:
claude mcp get evidencias-ci
claude mcp list
Dentro do Claude Code, abra /mcp e confira a conexão. Configurações de projeto exigem aprovação antes do uso, o que é adequado para um servidor versionado com o repositório.
Quando um loop atravessa muitas falhas e tentativas, o custo passa a ser manter a evidência útil sem reenviar o histórico inteiro. Nesses casos, uso RemoteCode para estender fluxos agentic de Claude Code e Codex com menos repetição de contexto. É uma ferramenta minha, citada aqui porque a saída curta do MCP e a economia de tokens resolvem partes do mesmo problema.
Erros comuns e diagnóstico
| Sintoma | Causa provável | Correção |
|---|---|---|
spawn node ENOENT |
O cliente não encontra o executável. | Use o caminho absoluto de node, obtido com which node. |
| A conexão encerra ao iniciar | Algum log foi escrito em stdout. | Troque console.log() por console.error(). |
| O servidor conecta, mas não mostra ferramentas | A compilação está desatualizada ou o arquivo errado foi iniciado. | Rode npm run build e confira o caminho de build/index.js. |
ENOENT ao chamar a ferramenta |
MCP_WORKSPACE aponta para outro diretório. |
Readicione o servidor com o caminho absoluto correto. |
| A resposta cresce demais | A função não limita itens ou campos. | Mantenha limite, corte campos e pagine na origem. |
| Funciona no Inspector, mas não no agente | A configuração ou a política do cliente bloqueia o servidor. | Inspecione /mcp, codex mcp list ou claude --debug mcp. |
A documentação de diagnóstico do Claude Code observa que caminhos relativos são uma causa comum de falha e sugere claude --debug mcp para ver stderr (Claude Code Docs, "Debug your configuration", consultado em 23/07/2026). O mesmo princípio vale no Codex: primeiro confirme comando, argumentos, diretório e variáveis; só depois investigue o modelo.
Esse registro também deve entrar na observabilidade de agentes de código. Chamada de ferramenta, duração, tamanho da resposta e erro formam uma trilha melhor que o resumo livre do agente.
Limites antes de levar para produção
Este servidor prova o fluxo local. Ele não implementa autenticação, isolamento multiusuário, limitação de requisições, auditoria persistente nem transporte remoto. Não publique o processo na rede apenas trocando stdio por HTTP.
Para um servidor remoto, escolha Streamable HTTP, autentique cada cliente, valide o audience e o escopo, aplique um tempo limite e mantenha ferramentas de leitura separadas das mutações. Claude Code marca SSE como transporte legado e recomenda HTTP para servidores remotos atuais (Claude Code Docs, "Add a remote HTTP server", consultado em 23/07/2026).
Também não confie nas annotations como controle de acesso. Elas descrevem comportamento ao cliente. A autorização precisa existir no servidor e na política do agente. O próximo passo de segurança está em barreiras de allowlist para MCP no CI.
Se o caso de uso virar busca semântica de repositório, não aumente esta ferramenta até ela ler tudo. Separe recuperação, reranking e orçamento de resposta como descrito em RAG de codebase para agentes.
Revise a árvore de dependências e os avisos de segurança antes da implantação. Uma compilação limpa de TypeScript prova compatibilidade de tipos, não ausência de vulnerabilidades transitivas.
Como verificar que o tutorial funcionou
O resultado está correto quando estas condições são verdadeiras:
npm run buildtermina sem erro.- O Inspector descobre exatamente duas ferramentas.
- A consulta por
authdevolve uma falha. - Um identificador fora do formato é rejeitado pelo esquema.
- Codex ou Claude Code mostra o servidor como conectado.
- Nenhuma ferramenta aceita caminho, comando de shell ou operação de escrita.
Este mesmo exemplo também foi verificado sem a interface do Inspector. O código dos blocos foi instalado em um diretório limpo, compilado e chamado por um cliente MCP via stdio. O cliente descobriu as duas ferramentas, e a consulta por auth devolveu somente ci-1042.
Depois, peça ao agente para explicar uma falha usando somente o MCP. Compare a resposta com o JSON de origem. Esse é um eval pequeno, mas útil: a evidência deve permanecer fiel e o agente não deve inventar logs ausentes.
Para ampliar a verificação, conecte a chamada da ferramenta a evals de PR para agentes no CI. O MCP fornece a evidência; o eval decide se o agente a usou corretamente.
Perguntas frequentes
Devo usar stdio ou Streamable HTTP?
Use stdio para um servidor local iniciado pelo próprio cliente. Use Streamable HTTP quando vários clientes precisam acessar um serviço remoto. Não exponha o exemplo local na rede sem autenticação, autorização, limites e auditoria.
Posso adicionar uma ferramenta que corrige a falha?
Pode, mas ela deve ser outra ferramenta, com permissão e aprovação próprias. Mantenha leitura e mutação separadas. Assim, consultar evidência não ganha por acidente a capacidade de editar código ou reiniciar um pipeline.
Por que testar no Inspector se o agente já mostra as ferramentas?
O Inspector separa o protocolo e a função manipuladora do comportamento do modelo. Ele permite validar esquema, entrada inválida, erro e resposta de forma repetível. Isso reduz o espaço de investigação quando a integração falha.
Como evitar que um MCP consuma contexto demais?
Limite resultados e campos no servidor, ofereça filtros explícitos e pagine conjuntos grandes. O orçamento de contexto para agentes de código ajuda a medir o custo total de ferramentas, instruções e histórico.