Aller au contenu

Traduction automatique

Cet article a été traduit automatiquement depuis la version originale en anglais.

Tutoriel de serveur MCP avec uv et FastMCP : créer un serveur FeatureStoreLite

Ce tutoriel sur les serveurs MCP construit un petit serveur de feature store avec FastMCP, l'exécute avec uv et l'intègre à Claude Desktop.


Qu'est-ce qu'un serveur MCP ?

Le Model Context Protocol (MCP) est un standard ouvert pour connecter des assistants IA comme Claude à des données et outils externes. Un seul protocole, un seul ensemble de conventions, au lieu d'une nouvelle intégration pour chaque outil que vous voulez exposer.

Dans ce tutoriel, nous allons construire un serveur MCP FeatureStoreLite. Il s'intercale entre un LLM et un feature store (une base de données de features ML précalculées), et expose des outils pour interroger et écrire des vecteurs de features indexés par utilisateur, produit ou document.

Pourquoi construire ça ?

Vous êtes ingénieur ML en train de déboguer un pipeline. Au lieu de passer en SQL ou d'écrire un script rapide pour vérifier des valeurs de features, vous demandez directement à Claude : « Quel est le vecteur de features pour user_123 ? » ou « Montre-moi les métadonnées pour product_abc. » C'est le serveur MCP qui rend cela possible.

Pourquoi utiliser uv ?

Nous utiliserons uv, un installateur de paquets et gestionnaire de projet Python bien plus rapide que pip, qui gère aussi la résolution des dépendances et les environnements virtuels dans un seul outil. L'autre raison : uv run --with mcp[cli] ... nous permet de déclarer les dépendances inline plus tard dans la configuration de Claude Desktop, donc il n'y a pas de venv séparé à maintenir en phase.

Vue d'ensemble de l'architecture

Les quatre composants et leur articulation :

Architecture MCP

  1. L'utilisateur pose une question en langage naturel.
  2. Claude Desktop est le client MCP. Il choisit un outil en fonction de la question et l'appelle.
  3. Notre serveur FastMCP expose get_feature et store_feature comme outils MCP.
  4. SQLite est le stockage sous-jacent des vecteurs de features.

2. Configuration et installation

2.1. Installer uv

Si vous n'avez pas encore uv, installez-le. La suite du tutoriel suppose qu'il est disponible dans votre PATH.

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

# Or via Homebrew
brew install uv

2.2. Initialiser le projet

Créez un nouveau répertoire et initialisez un projet Python. uv init crée un pyproject.toml pour vous.

# 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. Construire le serveur

Deux fichiers, séparés par responsabilité :

  1. database.py gère les opérations SQLite.
  2. featurestore_server.py définit le serveur MCP.

3.1. La couche base de données (database.py)

Ce module gère la connexion SQLite et quelques helpers. Nous l'initialisons avec deux lignes d'exemple pour que le serveur ait quelque chose à renvoyer dès la première requête.

Créez 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!")

Initialisez la base de données :

uv run python database.py

3.2. Le serveur MCP (featurestore_server.py)

FastMCP fait l'essentiel du travail. Décorez une fonction Python standard et elle est enregistrée comme outil ou ressource MCP. La docstring devient la description vue par le LLM, donc rédigez-la pour un LLM, pas seulement pour des humains.

Créez 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. Tester avec MCP Inspector

Avant de connecter cela à Claude, vérifiez rapidement le serveur avec MCP Inspector. C'est une petite interface web pour appeler directement des outils et lire des ressources.

uv run mcp dev featurestore_server.py

La commande démarre le serveur et ouvre Inspector dans votre navigateur (généralement à l'adresse http://localhost:5173).

Inspector

Appelez get_feature avec key="user_123". S'il renvoie le JSON de la ligne d'initialisation, le serveur fonctionne.


5. Connexion à Claude Desktop

Une fois qu'Inspector confirme que le serveur fonctionne, enregistrez-le dans Claude Desktop.

5.1. Configurer Claude

Modifiez votre fichier de configuration Claude Desktop :

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

Ajoutez votre serveur à l'objet mcpServers :

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

Important : le chemin vers featurestore_server.py doit être absolu. Un chemin relatif échouera silencieusement, car Claude Desktop s'exécute depuis un répertoire de travail différent.

5.2. Comment l'interaction fonctionne

Voici ce qui s'exécute de bout en bout quand Claude a besoin d'une recherche de features :

Workflow MCP

  1. Claude voit les outils disponibles (get_feature, list_features, etc.).
  2. Il décide que la question nécessite des données du feature store.
  3. Il construit un appel d'outil et l'envoie à votre serveur.
  4. Le serveur exécute la fonction Python et renvoie le résultat.
  5. Claude utilise ce résultat pour rédiger la réponse finale.

5.3. Exemples de requêtes

Redémarrez Claude Desktop et essayez quelques prompts :

  1. "List all available features." Question 2

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

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


6. Dépannage

Quelques modes de panne utiles à connaître :

  • "Server connection failed" :

    • Vérifiez les logs dans ~/Library/Logs/Claude/mcp.log sur macOS.
    • Confirmez que la configuration utilise un chemin absolu, pas relatif.
    • Confirmez que uv est dans le PATH de Claude Desktop. Si ce n'est pas le cas, pointez vers le chemin complet du binaire (which uv vous indiquera où il se trouve).
  • "Tool execution error" :

    • Reproduisez-le dans Inspector avec uv run mcp dev featurestore_server.py. Inspector affiche l'erreur brute, que Claude Desktop masque généralement.
    • Vérifiez que features.db est bien créé à côté de database.py. Si votre répertoire de travail change, c'est le chemin relatif au script dans get_db_path() qui vous sauve.

7. Conclusion

C'est tout : un serveur FastMCP, un stockage SQLite en backend, et une configuration Claude Desktop qui pointe vers une commande uv run. La même structure fonctionne pour tout ce que vous pouvez encapsuler dans une fonction Python. Remplacez les appels SQLite par un vrai feature store, une API interne ou un model registry, et le serveur reste compact.

Références