Automatische vertaling
Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.
MCP Server-tutorial met uv en FastMCP: bouw een FeatureStoreLite-server
In deze MCP-servertutorial bouwen we een kleine feature-store-server met FastMCP, draaien we die met uv, en koppelen we hem aan Claude Desktop.
Wat is een MCP-server?
Het Model Context Protocol (MCP) is een open standaard om AI-assistenten zoals Claude te verbinden met externe data en tools. Eén protocol, één set conventies, in plaats van een nieuwe integratie voor elke tool die je wilt ontsluiten.
In deze tutorial bouwen we een FeatureStoreLite MCP-server. Die zit tussen een LLM en een feature store (een database met vooraf berekende ML-features), en stelt tools beschikbaar om featurevectoren op te vragen en weg te schrijven, gesleuteld op user, product of document.
Waarom dit bouwen?
Je bent een ML engineer die een pipeline debugt. In plaats van SQL in te duiken of snel een script te schrijven om featurewaarden te controleren, vraag je Claude direct: "Wat is de featurevector voor user_123?" of "Laat me de metadata zien voor product_abc.". De MCP-server maakt dat mogelijk.
Waarom uv gebruiken?
We gebruiken uv, een Python package installer en projectmanager die veel sneller is dan pip en dependency resolution en virtualenvs in één tool afhandelt. De andere reden: uv run --with mcp[cli] ... laat ons later dependencies inline declareren in de Claude Desktop-config, zodat er geen aparte venv is die je synchroon moet houden.
Architectuuroverzicht
De vier onderdelen en hoe ze in elkaar passen:
- De gebruiker stelt een vraag in natuurlijke taal.
- Claude Desktop is de MCP-client. Die kiest op basis van de vraag een tool en roept die aan.
- Onze
FastMCP-server steltget_featureenstore_featurebeschikbaar als MCP-tools. - SQLite is de achterliggende opslag voor de featurevectoren.
2. Setup en installatie
2.1. Installeer uv
Als je uv nog niet hebt, installeer het dan. De rest van deze tutorial gaat ervan uit dat het op je PATH staat.
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Or via Homebrew
brew install uv
2.2. Initialiseer het project
Maak een nieuwe directory aan en initialiseer een Python-project. uv init maakt een pyproject.toml voor je aan.
# 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. De server bouwen
Twee bestanden, gescheiden per verantwoordelijkheid:
database.pyhandelt SQLite-bewerkingen af.featurestore_server.pydefinieert de MCP-server.
3.1. De databaselaag (database.py)
Deze module beheert de SQLite-verbinding en een paar helpers. We vullen die met twee voorbeeldrijen, zodat de server bij de eerste query iets kan teruggeven.
Maak database.py aan:
# 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!")
Initialiseer de database:
uv run python database.py
3.2. De MCP-server (featurestore_server.py)
FastMCP doet het meeste werk. Voorzie een gewone Python-functie van een decorator en die wordt geregistreerd als MCP-tool of resource. De docstring wordt de beschrijving die de LLM ziet, dus schrijf die voor een LLM, niet alleen voor mensen.
Maak featurestore_server.py aan:
# 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. Testen met MCP Inspector
Voordat je dit aan Claude koppelt, controleer je de server eerst met de MCP Inspector. Dat is een kleine web-UI om tools aan te roepen en resources direct uit te lezen.
uv run mcp dev featurestore_server.py
Het commando start de server en opent de Inspector in je browser (meestal op http://localhost:5173).

Roep get_feature aan met key="user_123". Als die de JSON voor de seed-rij teruggeeft, werkt de server.
5. Verbinden met Claude Desktop
Zodra de Inspector bevestigt dat de server werkt, registreer je hem in Claude Desktop.
5.1. Claude configureren
Bewerk je Claude Desktop-configuratiebestand:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%/Claude/claude_desktop_config.json
Voeg je server toe aan het object mcpServers:
{
"mcpServers": {
"featurestore": {
"command": "uv",
"args": [
"run",
"--with",
"mcp[cli]",
"mcp",
"run",
"/ABSOLUTE/PATH/TO/mcp-featurestore/featurestore_server.py"
]
}
}
}
Belangrijk: het pad naar
featurestore_server.pymoet absoluut zijn. Een relatief pad faalt stilzwijgend omdat Claude Desktop vanuit een andere working directory draait.
5.2. Hoe de interactie werkt
Dit draait end-to-end wanneer Claude een feature lookup nodig heeft:
- Claude ziet de beschikbare tools (
get_feature,list_features, enzovoort). - Het beslist dat de vraag data uit de feature store nodig heeft.
- Het bouwt een tool call op en stuurt die naar je server.
- De server voert de Python-functie uit en retourneert het resultaat.
- Claude gebruikt dat resultaat om het definitieve antwoord te schrijven.
5.3. Voorbeeldqueries
Herstart Claude Desktop en probeer een paar prompts:
-
"Lijst alle beschikbare features op."

-
"Haal de featurevector op voor user_123."

-
"Sla een nieuwe feature op voor 'new_item' met vector [0.5, 0.5] en metadata {'type': 'test'}."
6. Problemen oplossen
Een paar foutmodi die handig zijn om te kennen:
-
"Server connection failed":
- Controleer de logs op
~/Library/Logs/Claude/mcp.logop macOS. - Bevestig dat de config een absoluut pad gebruikt, geen relatief pad.
- Bevestig dat
uvop dePATHvan Claude Desktop staat. Zo niet, verwijs dan naar het volledige binaire pad (which uvvertelt je waar het staat).
- Controleer de logs op
-
"Tool execution error":
- Reproduceer het in de Inspector met
uv run mcp dev featurestore_server.py. De Inspector toont de ruwe fout, die Claude Desktop meestal inslikt. - Controleer dat
features.dbnaastdatabase.pywordt aangemaakt. Als je working directory verschuift, is het script-relatieve pad inget_db_path()wat je redt.
- Reproduceer het in de Inspector met
7. Conclusie
Dat is het hele verhaal: een FastMCP-server, een SQLite-opslaglaag, en een Claude Desktop-config die verwijst naar een uv run-commando. Dezelfde vorm werkt voor alles wat je in een Python-functie kunt wrappen. Vervang de SQLite-calls door een echte feature store, een interne API of een model registry, en de server blijft klein.