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í:
- El usuario hace una pregunta en lenguaje natural.
- Claude Desktop es el cliente MCP. Elige una herramienta en función de la pregunta y la invoca.
- Nuestro servidor
FastMCPexponeget_featureystore_featurecomo herramientas MCP. - 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:
database.pygestiona las operaciones de SQLite.featurestore_server.pydefine 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).

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.pytiene 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:
- Claude ve las herramientas disponibles (
get_feature,list_features, etc.). - Decide que la pregunta necesita datos del feature store.
- Construye una llamada a herramienta y la envía a tu servidor.
- El servidor ejecuta la función Python y devuelve el resultado.
- Claude usa ese resultado para redactar la respuesta final.
5.3. Consultas de ejemplo
Reinicia Claude Desktop y prueba algunos prompts:
-
"List all available features."

-
"Get the feature vector for user_123."

-
"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.logen macOS. - Confirma que la configuración usa una ruta absoluta, no relativa.
- Confirma que
uvestá en elPATHde Claude Desktop. Si no lo está, apunta a la ruta completa del binario (which uvte dirá dónde está).
- Comprueba los logs en
-
"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.dbse está creando junto adatabase.py. Si tu directorio de trabajo cambia, la ruta relativa al script enget_db_path()es lo que te salva.
- Reprodúcelo en Inspector con
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.