Inteligencia Artificial

Cómo crear un servidor MCP en TypeScript paso a paso

Autorangel cruz
Publicado
Lectura7 min de lectura
Actualizado
Cómo crear un servidor MCP en TypeScript paso a paso

Crear un servidor MCP es más sencillo de lo que parece: con el SDK oficial de TypeScript necesitas un archivo, un par de dependencias y unas 30 líneas de código para tener una herramienta que Claude o Cursor pueden invocar. En esta guía construimos uno desde cero, lo probamos con el MCP Inspector y lo conectamos a tus editores. Todo el código está completo y funciona tal cual.

Si todavía no tienes claro qué es MCP, lo expliqué a fondo en el post de Context7, el servidor MCP de documentación. En resumen: el Model Context Protocol es un estándar abierto que estandariza cómo un asistente de IA se conecta a herramientas y fuentes de datos externas. Un servidor MCP es justo eso: un proceso que expone capacidades (tools, resources, prompts) que el modelo puede usar.

Aquí nos centramos en tools, que es el 90% de los casos de uso: funciones que el modelo llama con argumentos y que devuelven un resultado.

Qué vas a necesitar

  • Node.js 18 o superior (lo verificas con node --version).
  • Un editor (Cursor, VS Code, lo que uses).
  • Claude Code o Cursor para la conexión final (opcional, pero es lo divertido).

No necesitas ninguna API key ni cuenta de pago. El servidor corre en tu máquina.

Paso 1: Inicializa el proyecto

Crea la carpeta e instala las dependencias. Usamos el SDK oficial (@modelcontextprotocol/sdk) y zod para validar los argumentos de entrada:

mkdir mi-servidor-mcp && cd mi-servidor-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

El SDK habla MCP por ti (el protocolo JSON-RPC, el handshake, la negociación de capacidades). Tú solo escribes la lógica de tus tools.

Edita el package.json para que use módulos ES y tenga un script de build:

{
	"name": "mi-servidor-mcp",
	"version": "1.0.0",
	"type": "module",
	"bin": {
		"mi-servidor-mcp": "./build/index.js"
	},
	"scripts": {
		"build": "tsc"
	},
	"dependencies": {
		"@modelcontextprotocol/sdk": "^1.29.0",
		"zod": "^3.24.0"
	},
	"devDependencies": {
		"@types/node": "^22.0.0",
		"typescript": "^5.6.0"
	}
}

"type": "module" es obligatorio. El SDK es ESM puro; sin esta línea verás errores de import al ejecutar.

Crea un tsconfig.json:

{
	"compilerOptions": {
		"target": "ES2022",
		"module": "Node16",
		"moduleResolution": "Node16",
		"outDir": "./build",
		"rootDir": "./src",
		"strict": true,
		"esModuleInterop": true,
		"skipLibCheck": true
	},
	"include": [
		"src/**/*.ts"
	]
}

Paso 2: Escribe el servidor (tu primer tool)

Crea src/index.ts. Empezamos con un tool mínimo para entender la forma: una calculadora que suma dos números.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
 
// 1. Crea la instancia del servidor
const server = new McpServer({
	name: "mi-servidor-mcp",
	version: "1.0.0",
});
 
// 2. Registra un tool
server.registerTool(
	"calcular",
	{
		description: "Suma dos números y devuelve el resultado",
		inputSchema: {
			a: z.number().describe("Primer número"),
			b: z.number().describe("Segundo número"),
		},
	},
	async ({ a, b }) => ({
		content: [{ type: "text", text: `El resultado es ${a + b}` }],
	})
);
 
// 3. Conecta el servidor por stdio
async function main() {
	const transport = new StdioServerTransport();
	await server.connect(transport);
	console.error("Servidor MCP en marcha (stdio)");
}
 
main().catch((error) => {
	console.error("Error fatal:", error);
	process.exit(1);
});

Hay tres piezas y conviene entenderlas:

  1. McpServer es la API de alto nivel. Le das un nombre y una versión.
  2. registerTool recibe el nombre del tool, sus metadatos (description + inputSchema) y el handler. El inputSchema es un objeto de campos Zod —no un z.object(...)—; el SDK lo convierte automáticamente a JSON Schema para que el modelo sepa qué argumentos pasar. Los .describe() ayudan al modelo a usar el tool correctamente.
  3. StdioServerTransport comunica el servidor con el cliente vía stdin/stdout. Es el transporte para integraciones locales: el editor lanza tu servidor como proceso hijo y hablan por esa tubería.

Regla de oro del stdio: nunca uses console.log en un servidor stdio. Stdout es el canal del protocolo; si escribes ahí corrompes los mensajes. Para logs usa siempre console.error (va a stderr).

Compílalo:

npm run build

Esto genera build/index.js. Ya tienes un servidor MCP funcional.

Paso 3: Pruébalo con el MCP Inspector

Antes de conectarlo a nada, pruébalo de forma aislada. El MCP Inspector es una herramienta oficial que levanta una UI web para invocar tus tools a mano:

npx @modelcontextprotocol/inspector node build/index.js

Abre la URL que imprime en consola, verás tu servidor conectado. En la pestaña Tools aparece calcular: pulsa List Tools, selecciona el tool, introduce a y b, y ejecuta. Si devuelve "El resultado es...", todo funciona.

Este paso te ahorra horas: si algo falla, lo ves aquí sin el ruido de un editor de por medio.

Paso 4: Un tool que sirve de verdad

Sumar números no impresiona a nadie. La gracia de MCP es darle al modelo acceso a datos que no tiene. Añadamos un tool que consulta la API pública de GitHub y devuelve las estrellas y el lenguaje de un repositorio. Agrégalo en src/index.ts, antes de main():

server.registerTool(
	"info_repo",
	{
		description:
			"Obtiene estrellas, lenguaje y descripción de un repositorio público de GitHub",
		inputSchema: {
			owner: z.string().describe("Usuario u organización dueña del repo"),
			repo: z.string().describe("Nombre del repositorio"),
		},
	},
	async ({ owner, repo }) => {
		const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
			headers: { "User-Agent": "mi-servidor-mcp" },
		});
 
		if (!res.ok) {
			return {
				content: [
					{ type: "text", text: `No encontré ${owner}/${repo} (HTTP ${res.status}).` },
				],
				isError: true,
			};
		}
 
		const data = await res.json();
		const resumen = [
			`Repositorio: ${data.full_name}`,
			`Descripción: ${data.description ?? "—"}`,
			`Lenguaje: ${data.language ?? "—"}`,
			`Estrellas: ${data.stargazers_count}`,
			`Forks: ${data.forks_count}`,
		].join("\n");
 
		return { content: [{ type: "text", text: resumen }] };
	}
);

Dos detalles importantes:

  • Manejo de errores con isError: true. Cuando algo sale mal, devuelve el error dentro del content y marca isError. Así el modelo entiende que falló y puede reaccionar (reintentar, avisarte), en vez de recibir una excepción opaca.
  • Devuelve texto claro y estructurado. El modelo lee la respuesta como contexto; cuanto más legible, mejor la usa.

Recompila (npm run build) y vuelve a probar info_repo en el Inspector con, por ejemplo, owner: upstash y repo: context7.

Paso 5: Conéctalo a Claude Code

La forma más limpia de registrar el servidor en Claude Code es con el comando claude mcp add. No tienes que editar JSON a mano: el comando escribe la configuración por ti en ~/.claude.json (el archivo de config en tu directorio home).

Para un servidor stdio local, pasa el comando que lo lanza después de --. Usa la ruta absoluta al build/index.js:

claude mcp add mi-servidor --scope user -- node /ruta/absoluta/a/mi-servidor-mcp/build/index.js

Un par de detalles:

  • Todo lo que va después de -- es el comando que ejecuta tu servidor (node build/index.js); lo de antes son opciones de Claude.
  • --scope user guarda el servidor en ~/.claude.json y lo deja disponible en todos tus proyectos. Sin ese flag se usa el scope local (por defecto), que también vive en ~/.claude.json pero solo se carga en el proyecto actual.

Verifica que quedó registrado:

claude mcp list

Dentro de una sesión de Claude Code, el comando /mcp te muestra los servidores conectados y sus tools. Pídele algo como "usa info_repo para ver cuántas estrellas tiene upstash/context7" y llamará a tu servidor.

Paso 6: Conéctalo a Claude Desktop

Claude Desktop tiene dos vías. Para un servidor que acabas de programar, lo más directo es editar su configuración desde la propia app (no busques el archivo a mano):

  1. Abre el menú Claude en la barra de menús del sistema —no los ajustes de dentro de la ventana— y elige Settings….
  2. Ve a la pestaña Developer y pulsa Edit Config. Eso crea (o abre) el archivo claude_desktop_config.json:
    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json
  3. Añade tu servidor dentro de mcpServers, con la ruta absoluta al build/index.js:
{
	"mcpServers": {
		"mi-servidor": {
			"command": "node",
			"args": [
				"/ruta/absoluta/a/mi-servidor-mcp/build/index.js"
			]
		}
	}
}

Guarda y reinicia Claude Desktop por completo (ciérralo del todo, no solo la ventana). Al reabrirlo verás el indicador de MCP en la esquina inferior derecha del cuadro de mensaje; púlsalo para ver tus tools.

Si en vez de un servidor propio quieres instalar uno ya empaquetado, Claude Desktop también soporta Desktop Extensions (archivos .mcpb) desde Settings → Extensions, sin tocar JSON.

Paso 7: Conéctalo a Cursor

Cursor usa el mismo formato que Claude Desktop. Crea .cursor/mcp.json en tu proyecto (o ~/.cursor/mcp.json para que esté disponible en todos):

{
	"mcpServers": {
		"mi-servidor": {
			"command": "node",
			"args": [
				"/ruta/absoluta/a/mi-servidor-mcp/build/index.js"
			]
		}
	}
}

En Settings → MCP lo verás listado. A partir de ahí, el agente de Cursor puede invocar tus tools dentro del chat.

Próximos pasos

Ya tienes la base. Desde aquí puedes:

  • Añadir más tools repitiendo el patrón de registerTool. Cada uno es una capacidad nueva.
  • Exponer resources (datos de solo lectura que el modelo puede leer, como archivos o registros) y prompts (plantillas reutilizables).
  • Pasar a transporte HTTP (StreamableHTTPServerTransport) si quieres un servidor remoto en vez de local.
  • Empaquetarlo y compartirlo. Si ya trabajas con skills, te interesa cómo crear un plugin para Claude Code, que incluye conectar un MCP remoto por OAuth.

Y si lo que buscas es no escribir tools de documentación tú mismo, Context7 ya es un servidor MCP listo que inyecta docs actualizadas de más de 1000 librerías.

Preguntas frecuentes

¿Qué es un servidor MCP?

Es un proceso que expone capacidades —tools, resources y prompts— a un asistente de IA a través del Model Context Protocol, un estándar abierto. Permite que modelos como Claude o el agente de Cursor ejecuten funciones y accedan a datos externos de forma estandarizada.

¿Necesito una API key para crear un servidor MCP?

No. Un servidor MCP local corre en tu máquina y se comunica con el editor por stdio. Solo necesitarías credenciales si tus tools consumen una API externa que las requiera.

¿Puedo escribir un servidor MCP en otro lenguaje?

Sí. Existe SDK oficial para TypeScript, Python y otros lenguajes. En esta guía usamos el de TypeScript porque encaja con el ecosistema de Cursor, VS Code y Node, pero el protocolo es el mismo en todos.

¿Por qué no debo usar console.log en un servidor MCP?

Porque con el transporte stdio la salida estándar (stdout) es el canal del protocolo. Cualquier console.log corrompe los mensajes JSON-RPC y rompe la comunicación. Para depurar usa console.error, que escribe en stderr.

¿Cómo pruebo un servidor MCP sin conectarlo a un editor?

Con el MCP Inspector: npx @modelcontextprotocol/inspector node build/index.js. Levanta una interfaz web donde puedes listar e invocar tus tools a mano antes de integrarlo en Claude o Cursor.