Spec MCP 2025-11-25 —

Tutorial MCP: crea tu primer servidor desde cero

El Model Context Protocol (MCP) es el estándar abierto que conecta aplicaciones LLM con herramientas y datos externos. En este tutorial construirás un servidor MCP funcional paso a paso: instalarás el SDK en Python o TypeScript, definirás tools y resources, lo conectarás a Claude Desktop y probará un ejemplo completo que consulta una base de datos SQLite. Al terminar tendrás una base reutilizable para exponer cualquier API, servicio o fuente de datos a modelos de lenguaje.

Lo más importante de este tutorial
  • El SDK oficial de MCP existe en Python (mcp) y TypeScript (@modelcontextprotocol/sdk). Ambos producen servidores 100% compatibles con la spec 2025-11-25.
  • FastMCP en Python permite definir herramientas con un simple decorador @mcp.tool() sobre cualquier función Python normal.
  • Un servidor MCP stdio se conecta a Claude Desktop editando un único fichero JSON de configuración. No hay puertos ni servidores web necesarios en local.
  • El mismo servidor funciona con Claude Desktop, Claude Code, Cursor, VS Code + Copilot y cualquier otro cliente que implemente la spec.
  • Para producción remota, la spec exige Streamable HTTP como transporte y autenticación OAuth 2.1 con PKCE.
Conceptos base

Arquitectura MCP: host, client y server

Antes de escribir código conviene tener clara la separación de responsabilidades que define la spec.

La arquitectura MCP separa tres roles bien definidos. El host es la aplicación que el usuario ve: Claude Desktop, VS Code, Cursor. El client es el objeto protocolario que el host crea por cada servidor al que se conecta; gestiona el ciclo de vida de la conexión y la negociación de capacidades. El server es el proceso que expones tú: declara herramientas, recursos y prompts, y responde a las peticiones del cliente.

Diagrama de la arquitectura del protocolo MCP con los tres roles: Host con MCP Client embebido, protocolo JSON-RPC, y MCP Server con Tools, Resources y Prompts
Arquitectura MCP segun la spec 2025-11-25. El servidor expone tres primitivas: tools, resources y prompts.

Lo que tú construyes en este tutorial es el servidor. El host (Claude Desktop u otro cliente) se encarga de descubrir tus herramientas, presentarlas al modelo y enrutar las llamadas. Tu servidor solo necesita responder a las peticiones que llegan por el transporte que elijas: stdio para local, Streamable HTTP para remoto.

Tools
Acciones que el LLM puede invocar para producir un efecto o consultar un sistema externo. Ejemplo: buscar_productos, crear_ticket.
Resources
Fuentes de datos que el host puede leer y añadir al contexto. Son típicamente de solo lectura. Ejemplo: archivo://config.json.
Prompts
Plantillas de instrucciones reutilizables que el servidor declara. El usuario puede seleccionarlas desde la interfaz del host.
Paso 1

Instalar el SDK de MCP

El SDK oficial de Python se instala con pip o con uv. El tutorial usa Python 3.10+ y el paquete mcp[cli].

Instalación con pip (recomendado para empezar)

# Crear entorno virtual
python -m venv .venv
source .venv/bin/activate    # Linux / macOS
# .venv\Scripts\activate     # Windows

# Instalar el SDK con extras de CLI y FastMCP
pip install "mcp[cli]"

# Verificar la instalación
python -c "import mcp; print(mcp.__version__)"

Instalación con uv (alternativa rápida)

La documentación oficial de MCP recomienda uv como gestor de paquetes por su velocidad. Si ya lo tienes instalado:

# Crear proyecto con uv
uv init mi-servidor-mcp
cd mi-servidor-mcp

# Añadir dependencia MCP
uv add "mcp[cli]"

# Ejecutar con uv run (gestiona el entorno automáticamente)
uv run python server.py
Que incluye mcp[cli]
Documentación oficial de MCP en modelcontextprotocol.io/docs/develop/build-server
El extra [cli] instala FastMCP, las utilidades de línea de comandos mcp dev y mcp install, y todas las dependencias de transporte (stdio y Streamable HTTP). Es el punto de partida recomendado para nuevos servidores.
Paso 2

El servidor más simple posible

Un servidor MCP mínimo funcional en cinco líneas de Python usando FastMCP.

FastMCP es la clase de alto nivel del SDK de Python. Gestiona el protocolo, el ciclo de vida y el transporte. Tú solo necesitas definir las herramientas como funciones normales con tipos Python y un docstring.

Guarda esto como server.py:

from mcp.server.fastmcp import FastMCP

# Nombre del servidor (visible en los clientes)
mcp = FastMCP("mi-primer-servidor")

@mcp.tool()
def sumar(a: int, b: int) -> int:
    """Suma dos numeros enteros y devuelve el resultado."""
    return a + b

@mcp.tool()
def saludar(nombre: str) -> str:
    """Genera un saludo personalizado para la persona indicada."""
    return f"Hola, {nombre}! Soy tu servidor MCP."

if __name__ == "__main__":
    mcp.run(transport="stdio")

Para probar que el servidor arranca correctamente:

# Deberia arrancar sin errores (Ctrl+C para salir)
python server.py

# O con uv
uv run python server.py

El servidor espera conexiones por stdin/stdout. En este modo no verás nada en la terminal hasta que un cliente se conecte. Eso es correcto: el transporte stdio espera JSON-RPC por stdin. El servidor está listo para conectarse a Claude Desktop o a cualquier otro cliente.

Paso 3

Definir tools: la primitiva principal

Las tools son las acciones que el LLM puede invocar. FastMCP infiere el esquema JSON automáticamente desde los tipos Python.

Anatomía de una tool MCP

Cada tool tiene cuatro elementos: nombre (el nombre de la función), descripción (el docstring), esquema de entrada (inferido de los tipos de argumentos) y la lógica de ejecución (el cuerpo de la función). FastMCP genera el JSON Schema automáticamente desde los type hints de Python.

from mcp.server.fastmcp import FastMCP
from typing import Optional

mcp = FastMCP("catalogo-tools")

@mcp.tool()
def buscar_productos(
    termino: str,
    categoria: Optional[str] = None,
    precio_max: Optional[float] = None,
) -> list[dict]:
    """
    Busca productos en el catalogo por termino y filtros opcionales.

    Args:
        termino: Texto a buscar en el nombre y descripcion del producto.
        categoria: Categoria opcional para filtrar (electronica, ropa, hogar...).
        precio_max: Precio maximo en euros. Si no se indica, sin limite.

    Returns:
        Lista de productos encontrados con id, nombre y precio.
    """
    # Logica real aqui: consulta a base de datos, API, etc.
    # Ejemplo simulado:
    productos = [
        {"id": 1, "nombre": "Teclado mecanico", "precio": 89.99, "categoria": "electronica"},
        {"id": 2, "nombre": "Raton ergonomico", "precio": 49.99, "categoria": "electronica"},
    ]

    resultados = [p for p in productos if termino.lower() in p["nombre"].lower()]

    if categoria:
        resultados = [p for p in resultados if p["categoria"] == categoria]

    if precio_max is not None:
        resultados = [p for p in resultados if p["precio"] <= precio_max]

    return resultados


@mcp.tool()
async def obtener_precio_actual(simbolo: str) -> dict:
    """
    Consulta el precio actual de un ticker de bolsa.
    Usa un simbolo como AAPL, MSFT o GOOGL.
    """
    import httpx
    # Ejemplo con llamada HTTP real (simulada aqui)
    # async with httpx.AsyncClient() as client:
    #     resp = await client.get(f"https://api.ejemplo.com/price/{simbolo}")
    #     return resp.json()
    return {"simbolo": simbolo, "precio": 150.25, "moneda": "USD"}


if __name__ == "__main__":
    mcp.run(transport="stdio")

Reglas para buenas tools MCP

Docstrings claros y completos

El docstring es la descripción que el LLM usa para decidir si invocar la tool. Cuanto más precisa sea la descripción de qué hace, cuándo usarla y qué devuelve, mejor elegirá el modelo cuándo usarla.

Tipos Python estrictos

FastMCP convierte los type hints en JSON Schema. Usa tipos precisos: str, int, float, bool, list[str], Optional[str]. Evita Any o tipos sin anotar.

Funciones sync y async

FastMCP soporta tanto funciones síncronas como async def. Usa async cuando hagas llamadas HTTP, operaciones de IO o consultas a bases de datos para no bloquear el event loop.

Errores con excepciones Python normales

Si la tool falla, lanza una excepción Python normal. FastMCP la convierte en un error MCP correcto que el cliente puede manejar. No devuelvas mensajes de error como si fueran resultados exitosos.

Paso 4

Definir resources: datos para el contexto

Los resources exponen fuentes de datos que el host puede leer y añadir al contexto del modelo. A diferencia de las tools, no se invocan por el LLM sino por el host.

Un resource en MCP tiene una URI (como config://app o archivo://README.md) y devuelve texto o datos binarios cuando el cliente lo lee. FastMCP usa el decorador @mcp.resource() con la URI como patrón.

from mcp.server.fastmcp import FastMCP
from pathlib import Path

mcp = FastMCP("servidor-con-resources")

# Resource estatico: siempre devuelve el mismo contenido
@mcp.resource("config://aplicacion")
def config_aplicacion() -> str:
    """Configuracion actual de la aplicacion en formato texto."""
    return """
version: 1.2.0
entorno: produccion
base_de_datos: postgresql://localhost:5432/miapp
max_conexiones: 100
"""

# Resource dinamico: lee un archivo del sistema
@mcp.resource("archivo://{nombre_archivo}")
def leer_archivo(nombre_archivo: str) -> str:
    """
    Lee el contenido de un archivo del directorio de datos.
    Usa rutas relativas como datos/config.json o logs/app.log.
    """
    ruta = Path("datos") / nombre_archivo

    if not ruta.exists():
        raise FileNotFoundError(f"Archivo no encontrado: {nombre_archivo}")

    if not ruta.is_relative_to(Path("datos")):
        raise PermissionError("Solo se pueden leer archivos del directorio datos/")

    return ruta.read_text(encoding="utf-8")

# Resource que lista elementos disponibles
@mcp.resource("lista://usuarios")
async def lista_usuarios() -> str:
    """Lista de usuarios activos en el sistema en formato JSON."""
    import json
    # Aqui iria la consulta real a la base de datos
    usuarios = [
        {"id": 1, "nombre": "Ana Garcia", "rol": "admin"},
        {"id": 2, "nombre": "Carlos Lopez", "rol": "usuario"},
    ]
    return json.dumps(usuarios, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    mcp.run(transport="stdio")

Los resources aparecen en el panel de contexto de Claude Desktop (el icono de clip). El usuario puede seleccionarlos para incluirlos en la conversación. El host decide qué resources añadir al contexto; el LLM no puede invocarlos directamente como hace con las tools.

Paso 5

Conectar el servidor a Claude Desktop

Una vez que el servidor funciona localmente, conectarlo a Claude Desktop requiere editar un único fichero de configuración JSON.

Localizar el fichero de configuración

Claude Desktop lee la configuración de servidores MCP desde:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Si el fichero no existe, créalo. Si ya existe, añade tu servidor dentro de mcpServers.

Configuración para un servidor Python local

{
  "mcpServers": {
    "mi-servidor": {
      "command": "/ruta/absoluta/a/tu/.venv/bin/python",
      "args": ["/ruta/absoluta/a/tu/server.py"]
    }
  }
}

El campo command debe apuntar al Python del entorno virtual donde instalaste el SDK, no al Python del sistema. Si usas Windows:

{
  "mcpServers": {
    "mi-servidor": {
      "command": "C:\\ruta\\a\\tu\\.venv\\Scripts\\python.exe",
      "args": ["C:\\ruta\\a\\tu\\server.py"]
    }
  }
}

Configuración con variables de entorno

Si tu servidor necesita credenciales o configuración por entorno, pásalas en el campo env:

{
  "mcpServers": {
    "mi-servidor": {
      "command": "/ruta/.venv/bin/python",
      "args": ["/ruta/server.py"],
      "env": {
        "DATABASE_URL": "postgresql://localhost:5432/miapp",
        "API_KEY": "tu-clave-api-aqui"
      }
    }
  }
}

Instalar usando el CLI de MCP (alternativa)

Si instalaste el SDK con el extra [cli], puedes usar el comando mcp install para registrar el servidor automáticamente en Claude Desktop:

# Instalar el servidor en Claude Desktop
mcp install server.py --name "mi-servidor"

# Verificar que aparece en la configuracion
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json

Verificar la conexión en Claude Desktop

Después de editar el fichero de configuración, reinicia Claude Desktop. Si el servidor se conecta correctamente:

  1. Veras el icono del martillo (herramientas) en el panel de composición.
  2. Al hacer clic mostrara las tools que tu servidor declara.
  3. Puedes hacer una pregunta que requiera una tool para probar que el flujo funciona de extremo a extremo.
Ejemplo completo

Servidor MCP que consulta una base de datos SQLite

Un ejemplo funcional y completo: un servidor MCP que expone herramientas para leer y escribir en una base de datos SQLite local. Puedes probarlo sin dependencias externas.

Guarda el siguiente código como servidor_bd.py. Incluye inicialización de la base de datos, tres tools (listar tablas, ejecutar consultas SELECT y insertar registros) y un resource que expone el esquema completo.

"""
Servidor MCP que expone una base de datos SQLite a traves del protocolo MCP.
Herramientas:
  - listar_tablas: devuelve las tablas de la base de datos
  - consultar: ejecuta una query SELECT y devuelve los resultados como JSON
  - insertar_tarea: anade una tarea a la tabla 'tareas'
Resources:
  - schema://base-de-datos: esquema SQL de la base de datos
"""

import json
import sqlite3
from pathlib import Path
from mcp.server.fastmcp import FastMCP

# Inicializar FastMCP
mcp = FastMCP("servidor-sqlite")

# Ruta de la base de datos (relativa al script)
DB_PATH = Path(__file__).parent / "datos.db"

def get_conn() -> sqlite3.Connection:
    """Abre una conexion a SQLite con row_factory para devolver dicts."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

def inicializar_db() -> None:
    """Crea las tablas si no existen y anade datos de ejemplo."""
    conn = get_conn()
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS tareas (
            id      INTEGER PRIMARY KEY AUTOINCREMENT,
            titulo  TEXT NOT NULL,
            hecho   INTEGER DEFAULT 0,
            creado  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );

        CREATE TABLE IF NOT EXISTS notas (
            id        INTEGER PRIMARY KEY AUTOINCREMENT,
            contenido TEXT NOT NULL,
            creado    TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );

        INSERT OR IGNORE INTO tareas (id, titulo, hecho)
            VALUES (1, 'Revisar documentacion MCP', 0);
        INSERT OR IGNORE INTO tareas (id, titulo, hecho)
            VALUES (2, 'Construir primer servidor MCP', 0);
        INSERT OR IGNORE INTO notas (id, contenido)
            VALUES (1, 'MCP usa JSON-RPC 2.0 sobre stdio o HTTP.');
    """)
    conn.commit()
    conn.close()


# ─── TOOLS ────────────────────────────────────────────────────────────────────

@mcp.tool()
def listar_tablas() -> list[str]:
    """
    Devuelve los nombres de todas las tablas de la base de datos.
    Util para explorar la estructura antes de hacer consultas.
    """
    conn = get_conn()
    rows = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
    ).fetchall()
    conn.close()
    return [row["name"] for row in rows]


@mcp.tool()
def consultar(sql: str, limite: int = 50) -> str:
    """
    Ejecuta una consulta SQL SELECT en la base de datos y devuelve los resultados.

    Solo se permiten sentencias SELECT. Usa LIMIT para no cargar demasiados datos.
    Devuelve los resultados como JSON.

    Args:
        sql: Sentencia SELECT. Ejemplo: SELECT * FROM tareas WHERE hecho = 0
        limite: Numero maximo de filas a devolver (por defecto 50, maximo 200).
    """
    # Validacion de seguridad: solo SELECT
    sql_limpio = sql.strip().upper()
    if not sql_limpio.startswith("SELECT"):
        raise ValueError("Solo se permiten sentencias SELECT.")

    limite = min(limite, 200)

    # Inyectar LIMIT si no lo tiene la query
    if "LIMIT" not in sql_limpio:
        sql = f"{sql.rstrip(';')} LIMIT {limite}"

    conn = get_conn()
    try:
        rows = conn.execute(sql).fetchall()
        resultados = [dict(row) for row in rows]
        return json.dumps(resultados, ensure_ascii=False, indent=2, default=str)
    except sqlite3.Error as e:
        raise ValueError(f"Error en la query SQL: {e}") from e
    finally:
        conn.close()


@mcp.tool()
def insertar_tarea(titulo: str) -> dict:
    """
    Anade una nueva tarea a la base de datos.

    Args:
        titulo: Titulo de la tarea. Debe ser descriptivo y no estar vacio.

    Returns:
        Diccionario con el id asignado y el titulo de la tarea creada.
    """
    if not titulo.strip():
        raise ValueError("El titulo de la tarea no puede estar vacio.")

    conn = get_conn()
    cursor = conn.execute(
        "INSERT INTO tareas (titulo) VALUES (?)",
        (titulo.strip(),),
    )
    conn.commit()
    nuevo_id = cursor.lastrowid
    conn.close()

    return {"id": nuevo_id, "titulo": titulo.strip(), "hecho": False}


@mcp.tool()
def marcar_tarea_hecha(id_tarea: int) -> str:
    """
    Marca una tarea como completada dado su ID.

    Args:
        id_tarea: ID numerico de la tarea a marcar como hecha.
    """
    conn = get_conn()
    conn.execute("UPDATE tareas SET hecho = 1 WHERE id = ?", (id_tarea,))
    conn.commit()
    conn.close()
    return f"Tarea {id_tarea} marcada como completada."


# ─── RESOURCES ────────────────────────────────────────────────────────────────

@mcp.resource("schema://base-de-datos")
def esquema_bd() -> str:
    """
    Devuelve el esquema SQL completo de la base de datos.
    Util para que el LLM conozca la estructura antes de hacer consultas.
    """
    conn = get_conn()
    rows = conn.execute(
        "SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL ORDER BY name"
    ).fetchall()
    conn.close()
    schemas = [row["sql"] for row in rows]
    return "\n\n".join(schemas)


# ─── ARRANQUE ─────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    inicializar_db()
    mcp.run(transport="stdio")

Configuración en Claude Desktop para este ejemplo

{
  "mcpServers": {
    "base-de-datos-local": {
      "command": "/ruta/a/.venv/bin/python",
      "args": ["/ruta/a/servidor_bd.py"]
    }
  }
}

Una vez conectado, puedes preguntarle a Claude cosas como: "Lístame las tareas pendientes", "Crea una tarea llamada Revisar logs de producción" o "Muestra todas las notas". Claude invocará las tools del servidor automáticamente.

Alternativa

Versión TypeScript del mismo servidor

El SDK oficial de TypeScript (@modelcontextprotocol/sdk) produce servidores 100% compatibles. El patrón es equivalente al de Python.

Instalación del SDK TypeScript

# Inicializar proyecto Node.js
mkdir mi-servidor-mcp-ts && cd mi-servidor-mcp-ts
npm init -y

# Instalar dependencias
npm install @modelcontextprotocol/sdk zod

# TypeScript (opcional pero recomendado)
npm install -D typescript @types/node tsx

Servidor MCP mínimo en TypeScript

Guarda como server.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as z from "zod";

// Instanciar el servidor MCP
const server = new McpServer({
  name: "mi-servidor-ts",
  version: "1.0.0",
});

// Registrar una tool: sumar
server.registerTool(
  "sumar",
  {
    description: "Suma dos numeros enteros y devuelve el resultado.",
    inputSchema: z.object({
      a: z.number().int().describe("Primer numero"),
      b: z.number().int().describe("Segundo numero"),
    }),
  },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }],
  }),
);

// Registrar otra tool: buscar en la base de conocimiento
server.registerTool(
  "buscar_kb",
  {
    description:
      "Busca un termino en la base de conocimiento y devuelve articulos relacionados.",
    inputSchema: z.object({
      termino: z.string().describe("Texto a buscar"),
      limite: z.number().int().min(1).max(20).default(5),
    }),
  },
  async ({ termino, limite }) => {
    // Logica real de busqueda aqui
    const articulos = [
      { id: 1, titulo: "Introduccion a MCP", relevancia: 0.95 },
      { id: 2, titulo: "Servidores MCP en produccion", relevancia: 0.82 },
    ]
      .filter((a) => a.titulo.toLowerCase().includes(termino.toLowerCase()))
      .slice(0, limite);

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(articulos, null, 2),
        },
      ],
    };
  },
);

// Nota CRITICA para stdio: usa console.error(), NUNCA console.log()
// console.log() escribe en stdout y corrompe el canal JSON-RPC

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Servidor MCP arrancado y esperando conexiones...");
}

main().catch((err) => {
  console.error("Error fatal:", err);
  process.exit(1);
});

Ejecutar el servidor TypeScript

# Con tsx (sin compilar, recomendado para desarrollo)
npx tsx server.ts

# Compilar y ejecutar con node
npx tsc
node dist/server.js
Regla critica en TypeScript + stdio
Nunca uses console.log() en un servidor MCP con transporte stdio. El stdout es el canal JSON-RPC exclusivo del protocolo. Cualquier texto que escribas en stdout corrompe el flujo y el cliente no podrá parsear los mensajes. Usa siempre console.error() para logs y depuración: escribe en stderr, que el host gestiona por separado.
Depuración

Probar y depurar el servidor MCP

El SDK incluye herramientas para probar el servidor sin necesitar Claude Desktop. También hay un inspector visual oficial.

MCP Inspector: interfaz visual para probar tools

El MCP Inspector es una herramienta web oficial que se conecta a tu servidor y permite invocar tools y leer resources desde una interfaz gráfica. Es el método más rápido para depurar sin necesitar Claude Desktop.

# Ejecutar el inspector con tu servidor (Python)
npx @modelcontextprotocol/inspector python server.py

# Con TypeScript
npx @modelcontextprotocol/inspector npx tsx server.ts

# El inspector abre en http://localhost:5173 por defecto

Prueba desde línea de comandos con mcp dev

# El modo dev del SDK arranca el servidor con hot-reload
mcp dev server.py

# O con uv
uv run mcp dev server.py

Errores comunes y cómo resolverlos

Errores frecuentes al construir servidores MCP
Error Causa probable Solución
El servidor no aparece en Claude Desktop Ruta incorrecta en claude_desktop_config.json Verifica que la ruta al Python del virtualenv es absoluta y correcta. Reinicia Claude Desktop después de cada cambio en el JSON.
Las tools no se muestran El servidor arranca pero falla silenciosamente Ejecuta el servidor manualmente desde la terminal. Revisa los logs de Claude Desktop en ~/Library/Logs/Claude/ (macOS) o %APPDATA%\Claude\logs\ (Windows).
Error de parseo JSON en el cliente print() o console.log() en el servidor En Python, no uses print() para logs; usa sys.stderr.write(). En TypeScript, usa console.error() en lugar de console.log().
ModuleNotFoundError al arrancar SDK instalado en el Python del sistema, no en el virtualenv Activa el entorno virtual antes de instalar: source .venv/bin/activate. Usa la ruta absoluta al Python del venv en la configuración de Claude Desktop.
Tool no se invoca aunque el prompt la requiere Descripción de la tool poco clara o ambigua Mejora el docstring: específica exactamente qué hace la tool, cuándo usarla y qué datos devuelve. El LLM usa la descripción para decidir si invocarla.
Preguntas frecuentes

Preguntas frecuentes sobre el tutorial MCP

Con FastMCP en Python, un servidor MCP con dos o tres herramientas tarda menos de 30 minutos desde cero: instalar el SDK, escribir las funciones con decoradores @mcp.tool() y conectarlo a Claude Desktop. El tiempo aumenta si el servidor necesita acceso a bases de datos, APIs externas o autenticación OAuth.

Las tools son acciones que el LLM puede invocar para obtener un resultado o producir un efecto (consultar una API, ejecutar una query SQL, enviar un mensaje). Los resources son fuentes de datos que el host puede leer y añadir al contexto del modelo (archivos, registros de base de datos, documentos). La diferencia principal es que las tools tienen efectos secundarios y los resources son típicamente de solo lectura.

Sí. Anthropic mantiene un SDK oficial de TypeScript en github.com/modelcontextprotocol/typescript-sdk. El patrón es equivalente al de Python: creas una instancia de McpServer, registras herramientas con server.registerTool() usando esquemas Zod, y conectas un StdioServerTransport. Ambos SDKs son oficialmente mantenidos y producen servidores 100% compatibles con la spec MCP 2025-11-25.

El primer paso es ejecutar el servidor manualmente desde la terminal para verificar que arranca sin errores. Después, revisa los logs de Claude Desktop en ~/Library/Logs/Claude/ (macOS) o %APPDATA%\Claude\logs\ (Windows). El error más común es una ruta incorrecta en claude_desktop_config.json: la clave "command" debe apuntar al ejecutable Python del entorno virtual donde instalaste el SDK, no al Python del sistema.

Para servidores locales (en la misma máquina que el cliente), stdio es el transporte recomendado: simple, sin puertos abiertos y seguro por defecto. Para servidores remotos accesibles por red, la spec 2025-11-25 define Streamable HTTP como el transporte estándar, con soporte opcional para SSE legacy para compatibilidad con clientes antiguos. En producción remota es obligatorio implementar autenticación OAuth 2.1.

Sí. Cualquier servidor MCP correcto es compatible con todos los clientes que implementen la spec: Claude Code, VS Code con GitHub Copilot, Cursor, Continue, Zed y otros. La configuración cambia según el cliente (en Cursor editas ~/.cursor/mcp.json, en VS Code editas settings.json) pero el servidor en sí no necesita ningún cambio. Consulta la página de clientes MCP para ver la configuración específica de cada uno.

Descubre todos los clientes MCP disponibles

Tu servidor MCP funciona con Claude Desktop, Claude Code, Cursor, VS Code y muchos más. Consulta la guía de clientes para ver cómo configurar cada uno y comparar su nivel de soporte de la spec.

Ver clientes MCP
Guía gratuita

Crea tu primer agente de IA, paso a paso

Descarga la guía en PDF: 12 secciones, ejemplos reales y datos de 2026. Gratis.