Cómo crear un servidor MCP en local y conectarlo a Claude (con código que funciona)
Crea un servidor MCP local desde una carpeta vacía: instala el SDK de TypeScript, registra dos herramientas que funcionen, pruébalas en el MCP Inspector y conecta el servidor a Claude Desktop y Claude Code. Incluye archivos de configuración, correcciones de rutas en Windows y una lista de comprobación para los fallos que rompen la mayoría de las configuraciones.
La mayoría de los tutoriales de MCP se quedan en el "hola mundo" y te dejan mirando una insignia roja de Disconnected. Este termina con un servidor ejecutándose en tu propio equipo, Claude llamando a sus herramientas y una lista breve con los fallos que más afectan a la gente. Escribirás unas 70 líneas de TypeScript, las probarás en un inspector que funciona en el navegador y conectarás el resultado tanto a Claude Desktop como a Claude Code.
El servidor es una pequeña herramienta de notas: Claude puede guardar una nota en un archivo JSON de tu disco y buscarla después. Es deliberadamente sencillo, porque la infraestructura es la misma tanto si tus herramientas leen un archivo de notas, consultan una base de datos o llaman a un modelo de imagen. He compilado y ejecutado el archivo del servidor que aparece más abajo con la versión 1.32 del SDK de TypeScript, así que el código se compila exactamente como se muestra.
Qué estás construyendo realmente
MCP en dos párrafos
El Model Context Protocol (MCP) es un estándar abierto, presentado por Anthropic en noviembre de 2024, que permite a una aplicación de IA comunicarse con herramientas externas de una forma coherente. En lugar de que cada aplicación invente su propio formato de plugins, un servidor MCP expone capacidades y un cliente MCP, como Claude Desktop o Claude Code, las encuentra y las llama. Los mensajes son JSON-RPC 2.0 simple, así que un servidor puede escribirse en cualquier lenguaje.
Un servidor puede ofrecer tres tipos de cosas:
Tools (herramientas): funciones que el modelo puede llamar, como "guardar una nota" o "ejecutar una consulta".
Resources (recursos): datos de solo lectura que la aplicación puede cargar como contexto, como un archivo o un registro de una base de datos.
Prompts: plantillas de prompt reutilizables que el usuario activa a propósito.
Este tutorial se centra en las herramientas, porque son las más sencillas de probar y las más útiles desde el primer día.
Por qué ejecutarlo en local
Un servidor local se ejecuta como proceso hijo del cliente, en tu equipo, con tus archivos y tus permisos. No queda expuesto a internet, no hay factura de alojamiento y la iteración es rápida: editas un archivo, compilas y reinicias. El transporte que usa es stdio: el cliente lanza tu programa y se comunica con él a través de la entrada y la salida estándar.
stdio (local)
Streamable HTTP (remoto)
Dónde se ejecuta
Proceso hijo en tu equipo
Un servidor web que alojas tú u otra persona
Quién puede acceder
Solo la aplicación que lo lanzó
Cualquiera con la URL y las credenciales
Autenticación
Ninguna, hereda tu cuenta de usuario
Obligatoria (OAuth o tokens)
Ideal para
Herramientas personales, acceso a archivos, desarrollo
Herramientas de equipo compartidas, integraciones SaaS
💡 Nota: Streamable HTTP sustituyó al transporte HTTP+SSE anterior en la revisión de la especificación 2025-03-26. Y como un navegador no puede lanzar un proceso en tu equipo, un servidor stdio no se puede añadir a claude.ai en el navegador. Ahí solo funcionan los servidores remotos.
Prepara el proyecto
Lo que necesitas instalado
Herramienta
Versión
Cómo comprobarlo
Node.js
20 LTS o más reciente
node --version
npm
Incluido con Node
npm --version
Claude Desktop
Última versión, macOS o Windows
Ajustes, luego Desarrollador
Claude Code (opcional)
Última versión
claude --version
Claude Desktop está disponible para macOS y Windows. En Linux, usa la ruta de Claude Code de la sección de conexión más abajo; el servidor es exactamente el mismo.
⚠️ Cuidado: TypeScript 7, la versión que npm instala hoy, ya no carga por sí solo los paquetes @types. Sin la línea "types": ["node"] obtendrás Cannot find name 'process' y otros errores similares en cada importación de Node.
Escribe tus dos primeras herramientas
El archivo completo del servidor
Guárdalo como src/index.ts. Expone save_note y search_notes, y guarda todo en un único archivo JSON.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
const NOTES_DIR = process.env.NOTES_DIR ?? path.join(os.homedir(), "mcp-notes");
const NOTES_FILE = path.join(NOTES_DIR, "notes.json");
type Note = { id: number; title: string; body: string; createdAt: string };
async function readNotes(): Promise<Note[]> {
try {
return JSON.parse(await fs.readFile(NOTES_FILE, "utf8"));
} catch {
return [];
}
}
const server = new McpServer({ name: "local-notes", version: "1.0.0" });
server.registerTool(
"save_note",
{
title: "Save note",
description: "Save a short note with a title and a body to the local notes file.",
inputSchema: {
title: z.string().min(1).max(120).describe("Short title for the note"),
body: z.string().min(1).describe("The text of the note"),
},
},
async ({ title, body }) => {
const notes = await readNotes();
const note: Note = {
id: notes.length + 1,
title,
body,
createdAt: new Date().toISOString(),
};
await fs.mkdir(NOTES_DIR, { recursive: true });
await fs.writeFile(NOTES_FILE, JSON.stringify([...notes, note], null, 2));
return { content: [{ type: "text", text: `Saved note #${note.id}: ${title}` }] };
}
);
server.registerTool(
"search_notes",
{
title: "Search notes",
description: "Find saved notes whose title or body contains a word or phrase.",
inputSchema: {
query: z.string().min(1).describe("Word or phrase to look for"),
},
},
async ({ query }) => {
const q = query.toLowerCase();
const hits = (await readNotes()).filter((n) =>
`${n.title} ${n.body}`.toLowerCase().includes(q)
);
if (hits.length === 0) {
return { content: [{ type: "text", text: `No notes match "${query}".` }] };
}
const text = hits.map((n) => `#${n.id} ${n.title}\n${n.body}`).join("\n\n");
return { content: [{ type: "text", text }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("local-notes MCP server running on stdio");
Qué hace cada parte
McpServer es la clase de alto nivel. El name y el version que le pasas aparecen en la lista de servidores del cliente y en los registros.
registerTool recibe el nombre de una herramienta, un objeto de configuración (title, description, inputSchema) y un manejador asíncrono.
El esquema de Zod se valida antes de que se ejecute tu manejador y después se convierte a JSON Schema para que Claude lo pueda leer. Cada cadena .describe() llega al modelo.
El valor de retorno es siempre { content: [...] }. El texto plano es el tipo de contenido más sencillo; también se admiten imágenes y enlaces a recursos.
StdioServerTransport lee las peticiones de stdin y escribe las respuestas en stdout.
💡 Tip: Escribe las descripciones para el modelo, no para las personas. "Busca notas guardadas cuyo título o cuerpo contenga una palabra o frase" le indica a Claude cuándo llamar a la herramienta. "Buscar" no. Claude elige las herramientas sobre todo por sus nombres y descripciones.
Como search_notes nunca modifica nada, márcalo así en su configuración: annotations: { readOnlyHint: true }. Las pistas son orientativas, y cada cliente decide por sí mismo cuánto confiar en ellas, pero permiten que los clientes bien diseñados traten las herramientas de solo lectura con más suavidad.
Nunca imprimas en stdout
Con stdio, stdout es el canal del protocolo. Un console.log("started") fuera de lugar introduce en el flujo una línea que no es JSON, y la mayoría de los clientes cortará la conexión o marcará el servidor como fallido. Recuerda esta regla y evitarás el fallo más común del primer día:
Sí:console.error("message"), que escribe en stderr, donde los clientes recogen los registros.
No:console.log(...) ni process.stdout.write(...) en ningún punto de tu servidor, incluidas las bibliotecas que importes.
Pruébalo antes que Claude
Ejecuta el MCP Inspector
El MCP Inspector es la herramienta oficial de depuración. Lanza tu servidor de la misma forma que lo haría un cliente y te ofrece botones en lugar de prompts.
npm run build
npx @modelcontextprotocol/inspector node build/index.js
Se abre una página en tu navegador. Haz clic en Connect, abre la pestaña Tools y pulsa List Tools. Deberías ver save_note y search_notes con sus esquemas. Ejecuta save_note con un título y un cuerpo, y luego ejecuta search_notes con una palabra de ese cuerpo. La primera llamada devuelve Saved note #1: Standup, y el archivo de notas aparece en una carpeta mcp-notes dentro de tu directorio personal (o en NOTES_DIR si lo configuraste).
Envía mensajes JSON-RPC en bruto
Si quieres ver el protocolo en sí, stdio usa un mensaje JSON por línea. Pon estas tres líneas en requests.jsonl:
La segunda respuesta enumera las dos herramientas con sus JSON Schemas. Ese intercambio es todo lo que hace Claude al conectarse: handshake, listar herramientas, llamar a herramientas.
Conéctalo a Claude
Edita la configuración de Claude Desktop
En Claude Desktop, abre Ajustes, luego Desarrollador, luego Editar configuración. Eso revela claude_desktop_config.json:
Añade tu servidor en mcpServers. Usa rutas absolutas, porque Claude Desktop lanza tu proceso desde su propio directorio de trabajo, no desde la carpeta de tu proyecto.
En macOS, la ruta tiene este aspecto: /Users/you/projects/local-notes-mcp/build/index.js. Guarda el archivo, luego cierra por completo Claude Desktop (en Windows, desde la bandeja del sistema, no solo con el botón de cerrar de la ventana) y vuelve a abrirlo. Tus herramientas aparecen en el menú de herramientas del campo de chat, y Claude pide permiso antes de ejecutar una.
Añádelo a Claude Code
Claude Code no necesita editar archivos. Un solo comando registra el servidor:
Todo lo que va después del doble guion es el comando que lanza tu servidor. Compruébalo con claude mcp list, o escribe /mcp dentro de una sesión para ver su estado. Un parámetro de ámbito decide quién tiene acceso al servidor:
Ámbito
Se guarda en
Quién lo ve
local (por defecto)
Tus ajustes privados para este proyecto
Solo tú, en este proyecto
project (--scope project)
.mcp.json en el repositorio
Todos los que lo clonen, después de aprobarlo
user (--scope user)
Tu configuración de usuario
Solo tú, en todos los proyectos
Prueba un prompt real
Pídele a Claude algo que obligue a llamar a una herramienta:
Guarda una nota titulada "Standup" que diga "Publicar el post de MCP el viernes". Luego busca en mis notas "viernes".
Claude llama a save_note, después a search_notes, y te cita el resultado. Abre notes.json para confirmar que los datos se han guardado en tu disco. Si es así, tienes un servidor MCP local funcionando.
Corrige los fallos y protégelo
Corrige los fallos más comunes
Síntoma
Causa probable
Solución
El servidor aparece como fallido o desconectado
Salida por stdout, o un fallo al arrancar
Ejecuta node build/index.js a mano y lee stderr; elimina todos los console.log
No hay herramientas después de editar la configuración
El cliente sigue abierto, o JSON no válido
Ciérralo del todo; revisa las comas finales
spawn node ENOENT
La aplicación no encuentra node en su PATH
Usa la ruta absoluta del binario node como command
Funciona en el Inspector, falla en Claude
Rutas relativas o variables de entorno que faltan
Rutas absolutas, y pon las variables en env
Los cambios en el código no tienen efecto
No compilaste ni reiniciaste
Ejecuta npm run build y luego reinicia el cliente
Dos detalles de Windows causan la mitad de los problemas restantes. Las barras invertidas dentro de las cadenas JSON deben duplicarse (C:\\Users\\you\\...), o puedes usar simplemente barras normales, como en el ejemplo de arriba. Y en Windows nativo, los servidores lanzados mediante npx suelen necesitar un envoltorio cmd /c en command; un comando node simple no basta.
Cuando algo sigue fallando, lee los registros. Claude Desktop escribe un registro por servidor, en ~/Library/Logs/Claude en macOS y en %APPDATA%\Claude\logs en Windows. Tus propias líneas de console.error acaban ahí.
Valores seguros que conviene mantener
Un servidor stdio hereda tus permisos, así que trata cada herramienta como código que puede actuar en tu nombre.
Limita el alcance del daño. Mantén el acceso a archivos dentro de una sola carpeta. Si una herramienta acepta una ruta, resuélvela y rechaza cualquier cosa fuera del directorio permitido.
Valida cada entrada. Las reglas min, max y enum de Zod no cuestan nada y bloquean las llamadas mal formadas antes de que se ejecute tu manejador.
Mantén los secretos fuera del código. Pon los tokens en el bloque env de tu configuración, y mantén ese archivo fuera del control de versiones.
Lee antes de instalar. Añade solo servidores de terceros cuyo código fuente hayas revisado. Se ejecutan con el acceso de tu cuenta.
Trata la salida de las herramientas como no confiable. El texto que tu herramienta obtiene de páginas web o correos puede contener instrucciones dirigidas al modelo. Devuélvelo como datos y mantén las acciones de escritura detrás de una confirmación.
Pasar de stdio a HTTP
Cuando tus compañeros necesiten las mismas herramientas, cambia el transporte. El SDK incluye StreamableHTTPServerTransport, que sirve las mismas McpServer por HTTP detrás de tu propia autenticación. Tus llamadas registerTool no cambian. Solo cambian el transporte y el registro en el cliente, por ejemplo claude mcp add --transport http notes https://your-host/mcp.
Ya puedes ver este patrón en la práctica. El conector de PicassoIA en claude.ai es un servidor MCP remoto que enumera la generación de imágenes, la edición de imágenes y la generación de video como herramientas, y Claude las llama sin ningún proceso local.
Redacta especificaciones de herramientas en PicassoIA
Las buenas herramientas empiezan por buenos nombres y buenas descripciones, y un modelo de lenguaje es una forma rápida de redactarlos antes de escribir código. PicassoIA aloja 75 modelos de texto en su categoría Large Language Models, entre ellos Claude Sonnet 5, pensado para tareas de programación. Así se usa para diseñar herramientas.
Describe la herramienta en lenguaje sencillo: qué hace, qué recibe, qué devuelve y si modifica algo.
Pide un formato de salida fijo: un nombre de herramienta en snake_case, una descripción de máximo dos frases escrita para un modelo, un esquema de Zod con .describe() en cada campo y tres casos límite que deberían fallar la validación.
Pega el resultado en una llamada a registerTool, compila y pruébalo en el Inspector.
Itera sobre la descripción, no sobre el esquema, cuando Claude elija la herramienta equivocada. El problema suele estar en la redacción.
Un prompt que funciona bien:
Estoy creando una herramienta MCP llamada list_overdue_tasks. Lee tasks.json, devuelve las tareas cuyo dueDate sea anterior a hoy y no cambia nada. Escribe el nombre de la herramienta, una descripción de dos frases para un modelo de IA, un esquema de entrada de Zod con un describe() en cada campo y tres entradas no válidas que debería rechazar.
Para refactorizaciones más grandes, como dividir un servidor de 600 líneas en módulos, prueba Claude Fable 5 o Claude Opus 4.7 pegando tu archivo completo.
Haz tu primera imagen en PicassoIA
Tu servidor de notas es una plantilla. Sustituye el archivo JSON por una llamada a un modelo de imagen y Claude podrá generar imágenes cuando se lo pidas. No hace falta que construyas eso primero para ver el resultado. Cada foto de este artículo se generó con P-Image, uno de los modelos de texto a imagen de Picasso IA.
Abre Picasso IA, escribe una frase que describa una escena y genera. Luego prueba tres experimentos:
Cambia la lente. Reescribe el mismo prompt con "35mm" y después con "85mm" y compara el encuadre.
Cambia la luz. Sustituye "morning window light" por "warm desk lamp" y observa cómo cambia el ambiente.
Cambia el ángulo. Pide una toma cenital y luego una toma desde abajo del mismo sujeto.
Construye una herramienta que te gustaría que Claude tuviera, conéctala con los pasos de arriba y dedica diez minutos a Picasso IA para crear las imágenes de tu proyecto. El servidor lleva una tarde. Las imágenes, segundos.