Local MCP Server en 15 minutos (y qué hacer con él después)

typescript dev.to

87% de los developers que mencionan MCP en Twitter nunca escribieron un servidor propio. Lo leí en una encuesta informal de un thread de Hacker News y tuve que releer dos veces. Porque yo era parte de ese 87% hasta hace tres semanas.

MCP — Model Context Protocol — lleva meses en todas las conversaciones sobre IA. Anthropic lo publicó, los editores lo adoptaron, Claude Desktop lo usa por defecto. Todos hablan de él. Pocos lo tocaron de verdad. Decidí ser de los que lo tocan.

El resultado fue raro: funcionó demasiado rápido. Y eso me dejó en un lugar incómodo que vale la pena explorar.

Qué es un MCP server local y por qué importa ahora

MCP es un protocolo que le permite a un modelo de lenguaje comunicarse con herramientas externas de manera estandarizada. La idea central es simple: en vez de que cada integración de IA invente su propia forma de llamar funciones, existe un contrato común. Un servidor MCP expone herramientas, el cliente (Claude, Cursor, cualquier LLM compatible) las descubre y las usa.

Pensar en esto como una API REST para contexto de IA no está muy lejos. Pero hay una diferencia importante: MCP está diseñado para ser bidireccional y stateful. El servidor puede mantener estado entre llamadas. El cliente puede negociar capacidades. Es más parecido a un protocolo de lenguaje (como LSP para editores) que a un endpoint HTTP simple.

Localmenete, esto significa que puedo tener un proceso corriendo en mi máquina que le da a Claude acceso a mis archivos, mis bases de datos, mis APIs internas — sin mandar nada a ningún servicio externo. Eso, para ciertos casos de uso, es enorme.

La especificación está en modelcontextprotocol.io. El SDK oficial de TypeScript está en npm. La documentación es sorprendentemente buena para algo tan nuevo.

Levantando el servidor: los 12 minutos reales

Usé el SDK oficial de TypeScript. Node 20, un proyecto nuevo, tres dependencias.

# Inicializar proyecto
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Enter fullscreen mode Exit fullscreen mode

El servidor más simple posible — una herramienta que lee un directorio:

// src/server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { z } from 'zod';

// Crear instancia del servidor con metadatos
const server = new Server(
  {
    name: 'juanchi-local-tools',
    version: '0.1.0',
  },
  {
    capabilities: {
      tools: {}, // Este servidor expone herramientas
    },
  }
);

// Definir qué herramientas están disponibles
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'leer_directorio',
        description: 'Lista los archivos de un directorio local',
        inputSchema: {
          type: 'object',
          properties: {
            ruta: {
              type: 'string',
              description: 'Ruta absoluta del directorio a leer',
            },
          },
          required: ['ruta'],
        },
      },
      {
        name: 'leer_archivo',
        description: 'Lee el contenido de un archivo de texto',
        inputSchema: {
          type: 'object',
          properties: {
            ruta: {
              type: 'string',
              description: 'Ruta absoluta del archivo',
            },
          },
          required: ['ruta'],
        },
      },
    ],
  };
});

// Manejar llamadas a las herramientas
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'leer_directorio') {
    // Validar input con zod
    const { ruta } = z.object({ ruta: z.string() }).parse(args);

    try {
      const archivos = await readdir(ruta, { withFileTypes: true });
      const lista = archivos.map((f) =>
        `${f.isDirectory() ? '[DIR]' : '[FILE]'}${f.name}`
      );

      return {
        content: [
          {
            type: 'text',
            text: lista.join('\n'),
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: 'text', text: `Error: ${error}` }],
        isError: true,
      };
    }
  }

  if (name === 'leer_archivo') {
    const { ruta } = z.object({ ruta: z.string() }).parse(args);

    try {
      const contenido = await readFile(ruta, 'utf-8');
      return {
        content: [{ type: 'text', text: contenido }],
      };
    } catch (error) {
      return {
        content: [{ type: 'text', text: `Error: ${error}` }],
        isError: true,
      };
    }
  }

  // Herramienta no encontrada
  throw new Error(`Herramienta desconocida: ${name}`);
});

// Conectar usando transporte stdio (estándar para MCP local)
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('MCP Server corriendo en stdio');
}

main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Configuración mínima en tsconfig.json:

{"compilerOptions":{"target":"ES2022","module":"Node16","moduleResolution":"Node16","outDir":"./dist","strict":true},"include":["src"]}
Enter fullscreen mode Exit fullscreen mode

Para conectarlo a Claude Desktop, editás ~/Library/Application Support/Claude/claude_desktop_config.json en Mac:

{"mcpServers":{"juanchi-local":{"command":"npx","args":["tsx","/ruta/absoluta/a/tu/proyecto/src/server.ts"]}}}
Enter fullscreen mode Exit fullscreen mode

Reiniciás Claude Desktop. Aparece un ícono de herramientas. Las herramientas están disponibles. Funcionó.

Minuto 12.

El minuto 13: el problema real con MCP server local

Ahí estaba yo. Claude Desktop con mi servidor conectado. Las herramientas apareciendo correctamente. Todo verde.

Y no supe qué preguntarle.

Esto es lo que nadie dice en los tutoriales de MCP: el protocolo en sí no es la parte difícil. El caso de uso es la parte difícil.

Podés leer directorios, sí. ¿Pero para qué? Claude ya sabe leer archivos si se los pegás en el contexto. Podés conectar una base de datos. ¿Pero cuándo necesitás que un LLM haga queries de forma autónoma en tu máquina local?

Empecé a entender que MCP no es una solución buscando un problema — es infraestructura para cuando ya tenés el problema claro. Y la mayoría de los tutoriales lo enseñan al revés: primero el protocolo, después el contexto.

Me pasó algo parecido cuando leí sobre sistemas multi-agente y sus problemas de condiciones de carrera: la arquitectura era elegante, pero la complejidad real aparecía cuando tratás de aplicarla a algo concreto. El "15 minutos" del tutorial es real. Lo que viene después requiere pensamiento.

Los errores que sí encontré (gotchas reales)

El transporte stdio no es obvio. MCP local usa stdin/stdout para comunicarse. Eso significa que si usás console.log en tu servidor, rompés el protocolo porque estás escribiendo en stdout. Todo el logging tiene que ir a console.error (stderr). Perdí 20 minutos por esto.

Las rutas absolutas son obligatorias en la config de Claude Desktop. Rutas relativas no funcionan. El proceso se levanta desde un directorio distinto al que esperás.

El servidor se reinicia con cada conversación. No tenés estado persistente entre chats a menos que lo implementes explícitamente (base de datos, archivos, etc.). Eso cambia cómo diseñás las herramientas.

Los errores no son verbosos por defecto. Si algo falla en la conexión, Claude Desktop simplemente muestra que las herramientas no están disponibles. Para debuggear, necesitás revisar los logs en ~/Library/Logs/Claude/ en Mac.

Zod es prácticamente obligatorio. El inputSchema es JSON Schema puro, pero validar el input de los argumentos manualmente es un horror. Zod hace eso elegante. No lo skipees.

Esto me recordó al momento en que entendí que los LLMs pueden encontrar vulnerabilidades reales: la capacidad técnica es impresionante, pero el contexto en el que la aplicás cambia todo.

FAQ: MCP server local herramientas IA

¿Qué diferencia hay entre MCP y una Function Calling API normal?
Function Calling (OpenAI, Anthropic) es específico de cada proveedor y generalmente stateless por request. MCP es un protocolo abierto, estandarizado, que puede mantener estado y funciona con cualquier cliente compatible. La analogía más precisa: Function Calling es como un endpoint REST, MCP es como un protocolo de transporte completo. Si hoy usás Claude, mañana migrás a otro modelo compatible y tus servidores MCP siguen funcionando igual.

¿Es seguro darle a un LLM acceso a mi sistema de archivos local?
Depende de cómo lo implementes. El servidor MCP corre con tus permisos de sistema. Si le das acceso a /, podría leer (o escribir, si lo implementás) cualquier cosa. La práctica recomendada es limitar las rutas explícitamente en el servidor, no confiar en que el modelo no va a explorar de más, y nunca exponer herramientas de escritura/ejecución sin confirmación humana en el loop. Esto aplica también si conectás herramientas que ejecutan comandos.

¿Funciona con otros clientes además de Claude Desktop?
Sí. Cursor tiene soporte nativo para MCP. Continue.dev también. Cualquier cliente que implemente la spec puede conectarse. Eso es precisamente el valor del protocolo — escribís el servidor una vez, funciona en múltiples clientes. El ecosistema está creciendo rápido; vale la pena revisar mcp.so para ver servidores ya construidos.

¿Puedo usar MCP para conectar una base de datos PostgreSQL local?
Sí, y es uno de los casos de uso más potentes. Hay un servidor oficial @modelcontextprotocol/server-postgres que podés configurar en minutos. Le das acceso a tu instancia local y el modelo puede hacer queries, explorar el schema, analizar datos. Donde esto realmente brilla es en tareas de análisis exploratorio donde no sabés de antemano qué queries necesitás — el modelo las construye dinámicamente.

¿Qué tan estable es la spec? ¿Vale la pena invertir tiempo ahora?
La spec está en versión 2025-03-26 al momento de escribir esto. Cambió bastante entre 2024 y principios de 2025. Mi opinión: si estás construyendo algo productivo crítico, esperá un poco más. Si estás explorando y aprendiendo, ahora es el momento — el ecosistema está en ese punto dulce donde hay suficiente documentación y ejemplos pero todavía podés entender la spec completa en una tarde.

¿Tiene sentido usar MCP para un proyecto personal o es overkill?
Depende del proyecto. Si tenés workflows repetitivos donde un LLM necesita acceder a datos locales tuyo — notas, código, bases de datos personales, logs — MCP local es una solución limpia. Si tu caso de uso es "quiero que Claude me ayude a escribir código", Cursor o Claude Projects con archivos son más simples. MCP brilla cuando necesitás que el modelo acceda a fuentes de datos que no pueden vivir en el contexto del chat.

El protocolo que todos usan sin entender: mi conclusión

Treinta años mirando tecnologías me enseñaron a distinguir hype de infraestructura real. MCP tiene la forma de infraestructura real. No es un producto, no tiene una pantalla de marketing — es un protocolo con una spec pública, SDKs open source, y adopción genuina en múltiples ecosistemas.

Lo que me quedó del experimento no es el código — ese fue simple. Lo que me quedó es la pregunta del minuto 13: ¿para qué lo usás?

Tengo algunas ideas que empiezan a tomar forma. Un servidor que le da acceso a mis proyectos de Railway a Claude. Un servidor conectado a la base de datos de métricas del experimento de sonificación de colectivos para poder hacer preguntas exploratorias sobre los datos en tiempo real. Un servidor que indexa mi vault de Obsidian y permite búsqueda semántica desde el chat.

Ninguno de esos casos los podría haber imaginado antes de levantar el servidor. Eso también es parte del proceso: a veces tenés que construir la infraestructura para descubrir para qué sirve.

Me pasó con Docker cuando lo aprendí. Me pasó con el runtime de Rust para TypeScript: primero entendés la mecánica, después aparece el caso de uso natural. La tecnología que dura es la que no te impone el problema — te da las herramientas para resolverlo cuando lo encontrás.

MCP me parece eso. No sé todavía si estoy en lo correcto. Pero el minuto 13 ya no me parece un fracaso — me parece el comienzo de la parte interesante.

Si vos ya tenés un caso de uso claro y querés explorar la spec más a fondo, arrancá por modelcontextprotocol.io. Si todavía estás en el minuto 13 como estaba yo, está bien. Construí el servidor, dejalo correr, y esperá a que el problema llegue a vos.


Este artículo fue publicado originalmente en juanchi.dev

Source: dev.to

arrow_back Back to Tutorials