E3 · Kit instalado
5 minEjecuta el bloque instalar.
Listo cuando: npm ls --depth=0 → muestra @modelcontextprotocol/server@2.3.1 y @modelcontextprotocol/client@2.3.1.
Clase 2 · 20 min
La forma más sencilla de conectar un servicio a un agente es stdio: el client inicia tu programa y conversa por stdin y stdout. En esta clase instalas el kit con versiones fijas, lees el servidor de grupos y llamas a la tool de dos maneras: con un client de prueba y con el MCP Inspector en modo línea de comandos.
Comenzar la lección
Objetivo: levantar un servidor MCP por stdio con el SDK oficial v2 y llamarlo con un client de prueba y con el Inspector, sin ningún host.
Haz clic en cada etapa.
Una sola vez. Necesitas Node 20 o más nuevo y git. Los bloques de las próximas clases parten de esta carpeta.
git clone https://github.com/inematds/formacao-webmcp-mcp-v72 ~/projetos/formacao-webmcp-mcp-v72 cd ~/projetos/formacao-webmcp-mcp-v72/kit npm ci # instala las versiones del package-lock.json npm ls --depth=0
Cloning into '~/projetos/formacao-webmcp-mcp-v72'... added 19 packages, and audited 20 packages in 3s 2 packages are looking for funding run `npm fund` for details found 0 vulnerabilities kit-servidor-mcp@1.0.0 ~/projetos/formacao-webmcp-mcp-v72/kit ├── @modelcontextprotocol/client@2.3.1 ├── @modelcontextprotocol/ext-apps@2.0.3 ├── @modelcontextprotocol/node@2.1.1 ├── @modelcontextprotocol/server@2.3.1 └── zod@4.6.7
En la captura, la carpeta personal fue reemplazada por ~; en tu terminal aparece la ruta completa.
Lee antes de ejecutar. Las dos tools, el resource y el prompt están en la misma fábrica; el stdio es solo la puerta de entrada.
// servidor.mjs — el servidor MCP de ejemplo del curso (servicio ficticio de turmas).
// SDK oficial v2 (@modelcontextprotocol/server 2.3.1, del 05/10/2026), que sirve la
// revisión 2026-07-28 del MCP y, con el mismo código, a clientes de la revisión 2025-11-25.
// No llama a ningún modelo ni servicio de pago: los datos quedan en la memoria del proceso.
import { McpServer, acceptedContent, inputRequired, inputResponse, requireScopes } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
export const TURMAS = [
{ id: 'html-01', titulo: 'HTML semântico para agentes', vagas: 3 },
{ id: 'mcp-01', titulo: 'Servidor MCP em uma tarde', vagas: 1 },
{ id: 'mcp-02', titulo: 'MCP com autorização', vagas: 0 }
];
const Turma = z.object({ id: z.string(), titulo: z.string(), vagas: z.int() });
const Confirma = z.object({ confirmar: z.boolean().meta({ title: 'Confirmar a reserva' }) });
export function createServer() {
const server = new McpServer({ name: 'turmas', version: '1.0.0' });
// 1) Tool de solo lectura: el cliente puede aprobarla sin preguntar.
server.registerTool(
'buscar_turmas',
{
title: 'Buscar turmas',
description: 'Lista turmas cujo título contém o termo. Só leitura: não reserva nada.',
inputSchema: z.object({
termo: z.string().min(2).describe('Parte do título, ex.: "mcp"'),
limite: z.int().min(1).max(20).optional().describe('Máximo de turmas (padrão 5)')
}),
outputSchema: z.object({ turmas: z.array(Turma) }),
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
},
async ({ termo, limite }) => {
const t = termo.toLowerCase();
const turmas = TURMAS.filter(x => x.titulo.toLowerCase().includes(t)).slice(0, limite ?? 5);
return {
content: [{ type: 'text', text: turmas.map(x => `${x.id} · ${x.titulo} · ${x.vagas} vagas`).join('\n') || 'nenhuma turma' }],
structuredContent: { turmas }
};
}
);
// 2) Tool con consecuencia: pide confirmación al usuario a mitad de la llamada (MRTR).
server.registerTool(
'reservar_vaga',
{
title: 'Reservar vaga',
description: 'Reserva uma vaga na turma para o e-mail informado. Tem consequência: pede confirmação antes.',
inputSchema: z.object({
turma_id: z.string().describe('Id da turma, ex.: "mcp-01"'),
email: z.email().describe('E-mail de quem vai fazer a turma')
}),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
// Con token (http-auth.mjs), exige el scope turmas:reservar; sin token (stdio), no se aplica.
scopeChallenge: requireScopes('turmas:reservar')
},
async ({ turma_id, email }, ctx) => {
const turma = TURMAS.find(x => x.id === turma_id);
if (!turma) return { content: [{ type: 'text', text: `turma ${turma_id} não existe` }], isError: true };
if (turma.vagas < 1) return { content: [{ type: 'text', text: `turma ${turma_id} sem vagas` }], isError: true };
const resposta = inputResponse(ctx.mcpReq.inputResponses, 'confirmar');
if (resposta.kind === 'elicit' && resposta.action !== 'accept') {
return { content: [{ type: 'text', text: 'reserva cancelada pelo usuário' }], isError: true };
}
const ok = acceptedContent(ctx.mcpReq.inputResponses, 'confirmar', Confirma);
if (ok?.confirmar !== true) {
return inputRequired({
inputRequests: {
confirmar: inputRequired.elicit({ message: `Reservar 1 vaga em "${turma.titulo}" para ${email}?`, requestedSchema: Confirma })
}
});
}
turma.vagas -= 1;
return { content: [{ type: 'text', text: `reservado: ${turma_id} para ${email} (restam ${turma.vagas})` }] };
}
);
// 3) Resource: dato que el cliente lee cuando quiera (no es una acción).
server.registerResource(
'catalogo',
'turmas://catalogo',
{ title: 'Catálogo de turmas', description: 'Todas as turmas em JSON', mimeType: 'application/json' },
async uri => ({ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(TURMAS) }] })
);
// 4) Prompt: plantilla de pedido que el usuario elige en el cliente.
server.registerPrompt(
'convite_turma',
{
title: 'Convite para a turma',
description: 'Monta o pedido de um convite curto para uma turma',
argsSchema: z.object({ turma_id: z.string() })
},
async ({ turma_id }) => ({
messages: [{ role: 'user', content: { type: 'text', text: `Escreva um convite de 3 linhas para a turma ${turma_id}, usando o recurso turmas://catalogo.` } }]
})
);
return server;
}// stdio.mjs — sirve el servidor de turmas por stdio (el cliente inicia este proceso).
// stdout es el canal del protocolo: todo log va a stderr (console.error).
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { createServer } from './servidor.mjs';
serveStdio(createServer);
console.error('turmas: servidor MCP no stdio');La prueba hace el papel de Claude Code: inicia node stdio.mjs, negocia la revisión y llama a buscar_turmas.
cd ~/projetos/formacao-webmcp-mcp-v72/kit node teste-stdio.mjs; echo "saída: $?"
// Uso: node teste-stdio.mjs [arquivo do servidor, padrão stdio.mjs]
// teste-stdio.mjs — hace el papel del cliente (Claude Code, Codex…): inicia "node stdio.mjs",
// negocia la revisión del protocolo y llama a una tool por el stdin/stdout del proceso.
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
const cliente = new Client({ name: 'teste-stdio', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await cliente.connect(new StdioClientTransport({ command: 'node', args: [process.argv[2] ?? 'stdio.mjs'] }));
const { tools } = await cliente.listTools();
const r = await cliente.callTool({ name: 'buscar_turmas', arguments: { termo: 'mcp' } });
console.log(`era: ${cliente.getProtocolEra()} · ferramentas: ${tools.map(t => t.name).join(', ')}`);
console.log(`buscar_turmas → ${r.structuredContent.turmas.length} turmas`);
await cliente.close();cd "$(mktemp -d)" && cp ~/projetos/formacao-webmcp-mcp-v72/kit/*.mjs ~/projetos/formacao-webmcp-mcp-v72/kit/package.json .
node teste-stdio.mjs 2>&1 | grep -m1 -o "ERR_MODULE_NOT_FOUND\|Cannot find package '[^']*'"; echo "saída: ${PIPESTATUS[0]}"turmas: servidor MCP no stdio era: modern · ferramentas: buscar_turmas, reservar_vaga buscar_turmas → 2 turmas saída: 0
La primera línea vino del stderr del servidor; el resto, del cliente.
ERR_MODULE_NOT_FOUND saída: 1
Capturado con el bloque "erro comum" de arriba, que copia los archivos a una carpeta vacía, sin node_modules. Solución: npm ci dentro de kit/.
El Inspector 2.11.0 tiene modo línea de comandos. La versión está fijada en npx para que el resultado sea el mismo de la captura.
cd ~/projetos/formacao-webmcp-mcp-v72/kit npx -y @modelcontextprotocol/inspector@2.11.0 --cli node stdio.mjs \ --method tools/call --tool-name buscar_turmas --tool-arg termo=html echo "saída: $?"
turmas: servidor MCP no stdio
{
"content": [
{
"type": "text",
"text": "html-01 · HTML semântico para agentes · 3 vagas"
}
],
"structuredContent": {
"turmas": [
{
"id": "html-01",
"titulo": "HTML semântico para agentes",
"vagas": 3
}
]
}
}
saída: 0Sin --cli, el mismo comando abre la interfaz web del Inspector, donde te conectas y pruebas las tools haciendo clic.
En stdio, cada línea de stdout se lee como un mensaje JSON-RPC. Por eso el kit registra los logs con console.error (stderr). La documentación del SDK avisa que un console.log estropea el canal y el host puede cerrar la conexión.
En nuestra prueba del 19/10/2026, el cliente del SDK 2.3.1 y el Inspector 2.11.0 se saltaron una línea sucia y siguieron. No cuentes con eso: otro host puede no perdonarlo. Los logs, siempre en stderr.
¿Prefieres Python? El paquete del SDK oficial mcp 2.3.0 (PyPI, descargado el 19/10/2026) contiene server/discover e InputRequiredResult en el código; revisa las notas de versión del python-sdk antes de usarlo. Este curso sigue en Node; los conceptos valen igual.
Ensayo en el kit y un cambio tuyo.
Ejecuta el bloque instalar.
Listo cuando: npm ls --depth=0 → muestra @modelcontextprotocol/server@2.3.1 y @modelcontextprotocol/client@2.3.1.
Ejecuta la prueba stdio y el Inspector --cli.
Listo cuando: node teste-stdio.mjs → era: modern · ferramentas: buscar_turmas, reservar_vaga y saída: 0; el Inspector devuelve un JSON con "structuredContent".
Agrega un grupo en TURMAS dentro de servidor.mjs con la palabra mcp en el título y ejecuta la prueba de nuevo.
Listo cuando: node teste-stdio.mjs → buscar_turmas → 3 turmas.
Pregunta 1 de 4
En el transporte stdio, ¿quién inicia el proceso del servidor?competencia
Pregunta 2 de 4
¿Por qué el kit usa npm ci en vez de npm install?competencia
npm ci garantiza esas versiones. npm install puede subir a una versión que se comporta distinto.npm ci garantiza esas versiones. npm install puede subir a una versión que se comporta distinto.Repasar: Instala el kitPregunta 3 de 4
¿Dónde debe escribir los logs el servidor stdio?competencia
Pregunta 4 de 4
La prueba muestra era: modern. ¿Qué garantiza eso?
mode: 'auto' y el servidor ofreció 2026-07-28. Un cliente antiguo sigue siendo atendido por el mismo servidor (clase 4).mode: 'auto' y el servidor ofreció 2026-07-28. Un cliente antiguo sigue siendo atendido por el mismo servidor (clase 4).Repasar: Instala el kitElige una alternativa. La explicación aparece después de tu intento.