Saltar a contenido

Traducción automática

Este artículo se tradujo automáticamente a partir de la versión original en inglés.

Tutorial de servidor MCP con uv y FastMCP: crea un servidor FeatureStoreLite

En este tutorial de servidor MCP construiremos un pequeño servidor de feature store con FastMCP, lo ejecutaremos con uv y lo conectaremos a Claude Desktop.


¿Qué es un servidor MCP?

El Model Context Protocol (MCP) es un estándar abierto para conectar asistentes de IA como Claude con datos y herramientas externos. Un protocolo, un conjunto de convenciones, en lugar de una integración nueva para cada herramienta que quieras exponer.

En este tutorial construiremos un servidor MCP FeatureStoreLite. Se sitúa entre un LLM y un feature store (una base de datos de features de ML precomputadas) y expone herramientas para consultar y escribir vectores de features indexados por usuario, producto o documento.

¿Por qué construir esto?

Eres un ingeniero de ML depurando un pipeline. En lugar de abrir SQL o escribir un script rápido para comprobar valores de features, le preguntas directamente a Claude: "¿Cuál es el vector de features para user_123?" o "Muéstrame los metadatos de product_abc." El servidor MCP es lo que hace posible eso.

¿Por qué usar uv?

Usaremos uv, un instalador de paquetes y gestor de proyectos de Python mucho más rápido que pip y que gestiona resolución de dependencias y virtualenvs en una sola herramienta. La otra razón: uv run --with mcp[cli] ... nos permite declarar dependencias inline en la configuración de Claude Desktop más adelante, así que no hay ningún venv separado que mantener sincronizado.

Resumen de la arquitectura

Las cuatro piezas y cómo encajan entre sí:

Arquitectura MCP

  1. El usuario hace una pregunta en lenguaje natural.
  2. Claude Desktop es el cliente MCP. Elige una herramienta en función de la pregunta y la invoca.
  3. Nuestro servidor FastMCP expone get_feature y store_feature como herramientas MCP.
  4. SQLite es el almacenamiento subyacente para los vectores de features.

2. Configuración e instalación

2.1. Instala uv

Si todavía no tienes uv, instálalo. El resto del tutorial asume que está en tu PATH.

# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or via Homebrew
brew install uv

2.2. Inicializa el proyecto

Crea un directorio nuevo e inicializa un proyecto de Python. uv init crea un pyproject.toml por ti.

# Create project directory
mkdir mcp-featurestore
cd mcp-featurestore

# Initialize Python project
uv init

# Add the MCP SDK with CLI tools
uv add "mcp[cli]"

3. Construcción del servidor

Dos archivos, separados por responsabilidad:

  1. database.py gestiona las operaciones de SQLite.
  2. featurestore_server.py define el servidor MCP.

3.1. La capa de base de datos (database.py)

Este módulo se encarga de la conexión SQLite y de un par de funciones auxiliares. Lo inicializamos con dos filas de ejemplo para que el servidor tenga algo que devolver en la primera consulta.

Crea database.py:

# database.py
import json
import os
import sqlite3


def get_db_path() -> str:
    """Get the database path - always in the script's directory"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(script_dir, "features.db")


def init_db() -> None:
    """Initialize the feature store database with table and sample data"""
    conn = sqlite3.connect(get_db_path())
    conn.execute("""
        CREATE TABLE IF NOT EXISTS features (
            key TEXT PRIMARY KEY,
            vector TEXT NOT NULL,
            metadata TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)

    # Sample data for experimentation
    example_features = [
        (
            "user_123",
            "[0.1, 0.2, -0.5, 0.8, 0.3, -0.1, 0.9, -0.4]",
            json.dumps({"type": "user", "id": 123, "segment": "premium"}),
        ),
        (
            "product_abc",
            "[0.7, -0.3, 0.4, 0.1, -0.8, 0.6, 0.2, -0.5]",
            json.dumps({"type": "product", "id": "abc", "category": "electronics"}),
        ),
    ]

    # Insert if not exists
    for key, vector, metadata in example_features:
        try:
            conn.execute(
                "INSERT INTO features (key, vector, metadata) VALUES (?, ?, ?)",
                (key, vector, metadata),
            )
        except sqlite3.IntegrityError:
            pass  # Already exists

    conn.commit()
    conn.close()


def get_db_connection() -> sqlite3.Connection:
    """Get a database connection"""
    return sqlite3.connect(get_db_path())


if __name__ == "__main__":
    init_db()
    print("✅ Database initialized successfully!")

Inicializa la base de datos:

uv run python database.py

3.2. El servidor MCP (featurestore_server.py)

FastMCP hace la mayor parte del trabajo. Decora una función Python normal y queda registrada como herramienta o recurso MCP. El docstring se convierte en la descripción que ve el LLM, así que escríbelo para un LLM, no solo para personas.

Crea featurestore_server.py:

# featurestore_server.py
import json
from mcp.server.fastmcp import FastMCP
from database import get_db_connection, init_db

# Initialize the MCP Server
mcp = FastMCP("FeatureStoreLite")

# Ensure DB is ready when server starts
init_db()


@mcp.resource("schema://main")
def get_schema() -> str:
    """
    Resource: Provide the database schema.
    Resources are passive data that LLMs can read like files.
    """
    conn = get_db_connection()
    try:
        schema = conn.execute(
            "SELECT sql FROM sqlite_master WHERE type='table'"
        ).fetchall()
        return "\n".join(sql[0] for sql in schema if sql[0]) or "No tables found."
    finally:
        conn.close()


@mcp.tool()
def store_feature(key: str, vector: str, metadata: str | None = None) -> str:
    """
    Tool: Store a feature vector.
    Tools are executable functions that LLMs can call to perform actions.
    """
    conn = get_db_connection()
    try:
        # Validate that vector is valid JSON
        json.loads(vector)

        conn.execute(
            "INSERT OR REPLACE INTO features (key, vector, metadata) VALUES (?, ?, ?)",
            (key, vector, metadata),
        )
        conn.commit()
        return f"Successfully stored feature '{key}'"
    except json.JSONDecodeError:
        return "Error: Vector must be a valid JSON array string (e.g., '[0.1, 0.2]')"
    except Exception as e:
        return f"Error: {str(e)}"
    finally:
        conn.close()


@mcp.tool()
def get_feature(key: str) -> str:
    """
    Tool: Retrieve a feature vector by key.
    """
    conn = get_db_connection()
    try:
        row = conn.execute(
            "SELECT vector, metadata FROM features WHERE key = ?", (key,)
        ).fetchone()

        if row:
            return json.dumps(
                {
                    "key": key,
                    "vector": json.loads(row[0]),
                    "metadata": json.loads(row[1]) if row[1] else None,
                },
                indent=2,
            )
        return f"Feature '{key}' not found."
    finally:
        conn.close()


@mcp.tool()
def list_features() -> str:
    """
    Tool: List all available feature keys.
    """
    conn = get_db_connection()
    try:
        rows = conn.execute("SELECT key FROM features").fetchall()
        return json.dumps([row[0] for row in rows])
    finally:
        conn.close()


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

4. Pruebas con MCP Inspector

Antes de conectarlo con Claude, valida el servidor con MCP Inspector. Es una pequeña interfaz web para invocar herramientas y leer recursos directamente.

uv run mcp dev featurestore_server.py

El comando inicia el servidor y abre Inspector en tu navegador (normalmente en http://localhost:5173).

Inspector

Invoca get_feature con key="user_123". Si devuelve el JSON de la fila inicial, el servidor está funcionando.


5. Conexión con Claude Desktop

Una vez que Inspector confirme que el servidor funciona, regístralo en Claude Desktop.

5.1. Configurar Claude

Edita el archivo de configuración de Claude Desktop:

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

Añade tu servidor al objeto mcpServers:

{
    "mcpServers": {
        "featurestore": {
            "command": "uv",
            "args": [
                "run",
                "--with",
                "mcp[cli]",
                "mcp",
                "run",
                "/ABSOLUTE/PATH/TO/mcp-featurestore/featurestore_server.py"
            ]
        }
    }
}

Importante: la ruta a featurestore_server.py tiene que ser absoluta. Una ruta relativa fallará sin avisar porque Claude Desktop se ejecuta desde un directorio de trabajo distinto.

5.2. Cómo funciona la interacción

Esto es lo que se ejecuta de extremo a extremo cuando Claude necesita consultar una feature:

Flujo de trabajo MCP

  1. Claude ve las herramientas disponibles (get_feature, list_features, etc.).
  2. Decide que la pregunta necesita datos del feature store.
  3. Construye una llamada a herramienta y la envía a tu servidor.
  4. El servidor ejecuta la función Python y devuelve el resultado.
  5. Claude usa ese resultado para redactar la respuesta final.

5.3. Consultas de ejemplo

Reinicia Claude Desktop y prueba algunos prompts:

  1. "List all available features." Pregunta 2

  2. "Get the feature vector for user_123." Pregunta 3

  3. "Store a new feature for 'new_item' with vector [0.5, 0.5] and metadata {'type': 'test'}."


6. Resolución de problemas

Algunos modos de fallo que conviene conocer:

  • "Server connection failed":

    • Comprueba los logs en ~/Library/Logs/Claude/mcp.log en macOS.
    • Confirma que la configuración usa una ruta absoluta, no relativa.
    • Confirma que uv está en el PATH de Claude Desktop. Si no lo está, apunta a la ruta completa del binario (which uv te dirá dónde está).
  • "Tool execution error":

    • Reprodúcelo en Inspector con uv run mcp dev featurestore_server.py. Inspector muestra el error en bruto, que Claude Desktop normalmente oculta.
    • Comprueba que features.db se está creando junto a database.py. Si tu directorio de trabajo cambia, la ruta relativa al script en get_db_path() es lo que te salva.

7. Conclusión

Eso es todo: un servidor FastMCP, un almacenamiento SQLite y una configuración de Claude Desktop que apunta a un comando uv run. La misma estructura sirve para cualquier cosa que puedas encapsular en una función Python. Sustituye las llamadas a SQLite por un feature store real, una API interna o un registro de modelos, y el servidor seguirá siendo pequeño.

Referencias