Datenbankressourcen über MCP bereitstellen
Erstellen Sie MCP-Ressourcen, die Datenbankinhalte dynamisch bereitstellen, stellen Sie Abfrage-Tools bereit, die das Modell aufrufen kann, und implementieren Sie eine Seitennavigation für große Ergebnismengen.
Datenbankressourcen über MCP bereitstellen ist eine kostenlose AI Engineering Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Engineering Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.
Warum Datenbanken MCP benötigen
Datenbanken gehören zu den wertvollsten Datenquellen für KI-Assistenten – ein direkter Zugriff auf die Rohdatenbank ist für ein LLM jedoch zu gefährlich. Ein MCP-Server fungiert als kontrollierter Proxy zwischen der KI und Ihrer Datenbank. Er stellt nur die Daten und Operationen bereit, die Sie ausdrücklich erlauben, und verfügt über integrierte Validierung, Ratenbegrenzung und Protokollierung.
Datenbankschema für MCP entwerfen
Entscheiden Sie vor dem Schreiben des Codes, was die KI sehen und tun können soll. Definieren Sie, welche Tabellen lesbar sind, welche Tools Schreiboperationen ausführen dürfen (falls überhaupt), welche Daten nach Benutzerkontext gefiltert werden müssen und welche Spalten verborgen bleiben sollen (Passwörter, personenbezogene Daten). Diese Entwurfsphase verhindert eine versehentliche Offenlegung von Daten.
- Lese-Tools:
list_records,get_record,search_records - Schreib-Tools (optional, eingeschränkt):
create_record,update_record - Verboten: Ausführung von rohem SQL, Änderungen am Schema, Systemtabellen
Datenbankverbindung einrichten
Verwenden Sie asyncpg (PostgreSQL) oder aiosqlite (SQLite) für den asynchronen Datenbankzugriff in Ihrem MCP-Server. Erstellen Sie beim Start einen Verbindungspool, damit nicht jeder Tool-Aufruf eine neue Verbindung öffnen muss. Speichern Sie die Datenbank-URL in einer Umgebungsvariablen, niemals im Code.
import asyncpg
import os
from mcp.server import Server
from mcp import types
import asyncio
app = Server('database-mcp-server')
pool = None # Global connection pool
async def get_pool():
global pool
if pool is None:
pool = await asyncpg.create_pool(
os.environ['DATABASE_URL'],
min_size=2,
max_size=10,
command_timeout=30
)
return pool
# Initialize pool at startup
async def startup():
await get_pool()
print('Database pool initialized', flush=False) # stderr only via loggerDatensätze als Tool auflisten
Mit einem list_records-Tool samt Filterung und Seitennummerierung kann die KI Datenbankinhalte durchsuchen, ohne beliebiges SQL auszuführen. Akzeptieren Sie Filterparameter, erstellen Sie eine parametrisierte Abfrage und wenden Sie immer eine Zeilenbegrenzung an. Geben Sie die Ergebnisse als formatierten String zurück, den das Modell lesen und in seiner Antwort referenzieren kann.
@app.list_tools()
async def list_tools():
return [
types.Tool(
name='list_products',
description='List products from the catalog with optional filtering by category and price range.',
inputSchema={
'type': 'object',
'properties': {
'category': {'type': 'string', 'description': 'Filter by product category.'},
'max_price': {'type': 'number', 'description': 'Maximum price in USD.'},
'limit': {'type': 'integer', 'default': 20, 'maximum': 100}
},
'required': []
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == 'list_products':
return await list_products_handler(arguments)
async def list_products_handler(args: dict):
pool = await get_pool()
query = 'SELECT id, name, category, price, stock FROM products WHERE 1=1'
params = []
if 'category' in args:
params.append(args['category'])
query += f' AND category = ${len(params)}'
if 'max_price' in args:
params.append(args['max_price'])
query += f' AND price <= ${len(params)}'
limit = min(args.get('limit', 20), 100)
params.append(limit)
query += f' ORDER BY name LIMIT ${len(params)}'
async with pool.acquire() as conn:
rows = await conn.fetch(query, *params)
if not rows:
return [types.TextContent(type='text', text='No products found matching your criteria.')]
lines = ['id | name | category | price | stock']
lines += [f'{r["id"]} | {r["name"]} | {r["category"]} | ${r["price"]} | {r["stock"]}' for r in rows]
return [types.TextContent(type='text', text='\n'.join(lines))]Datenbankzeilen als Ressourcen bereitstellen
Einzelne Datenbankdatensätze können mithilfe strukturierter URIs wie db://products/42 als MCP-Ressourcen bereitgestellt werden. Ressourcen ermöglichen es dem KI-Client, bestimmte Datensätze zwischenzuspeichern und zu referenzieren, ohne jedes Mal ein Tool aufzurufen. Implementieren Sie list_resources, um die relevantesten Datensätze zurückzugeben, und read_resource, um einen Datensatz anhand seiner URI abzurufen.
@app.list_resources()
async def list_resources():
pool = await get_pool()
async with pool.acquire() as conn:
# List most recently updated products as resources
rows = await conn.fetch(
'SELECT id, name FROM products ORDER BY updated_at DESC LIMIT 50'
)
return [
types.Resource(
uri=f'db://products/{row["id"]}',
name=row['name'],
description=f'Product record for {row["name"]}',
mimeType='application/json'
)
for row in rows
]
@app.read_resource()
async def read_resource(uri: str) -> str:
import json
if uri.startswith('db://products/'):
product_id = int(uri.split('/')[-1])
pool = await get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow(
'SELECT id, name, category, price, description, stock FROM products WHERE id = $1',
product_id
)
if row is None:
raise ValueError(f'Product {product_id} not found')
return json.dumps(dict(row), default=str)
raise ValueError(f'Unknown resource URI: {uri}')Seitennummerierung für große Ergebnismengen
Datenbanktabellen enthalten häufig Tausende von Zeilen. Implementieren Sie in Ihren Tools eine cursorbasierte Seitennummerierung, damit die KI die nächste Ergebnisseite anfordern kann. Verwenden Sie last_id als stabilen Cursor. Das ist zuverlässiger als eine offsetbasierte Seitennummerierung, bei der durch Einfügungen Zeilen übersprungen werden können.
async def paginated_list(table: str, limit: int = 20, after_id: int = 0) -> tuple:
'''Returns (rows, has_more, next_cursor).'''
pool = await get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch(
f'SELECT * FROM {table} WHERE id > $1 ORDER BY id LIMIT $2',
after_id,
limit + 1 # Fetch one extra to detect if more pages exist
)
has_more = len(rows) > limit
rows = rows[:limit]
next_cursor = rows[-1]['id'] if rows else None
return rows, has_more, next_cursor
# In your tool result, include pagination info:
# 'Showing 20 products. To see the next page, call list_products with after_id=120'Volltextsuche als Tool
Fügen Sie ein Suchtool hinzu, das die PostgreSQL-Volltextsuche verwendet. So kann die KI Datensätze anhand natürlichsprachlicher Suchanfragen statt anhand exakter Feldübereinstimmungen finden. Das ist besonders leistungsfähig für Produktkataloge, Dokumentationen und Wissensdatenbanken.
@app.list_tools()
async def list_tools():
return [
types.Tool(
name='search_products',
description='Full-text search across product names and descriptions. Use for finding products by natural language, not by category filter.',
inputSchema={
'type': 'object',
'properties': {
'query': {'type': 'string', 'description': 'Natural language search query.'},
'limit': {'type': 'integer', 'default': 10, 'maximum': 50}
},
'required': ['query']
}
)
]
async def search_products_handler(args: dict):
pool = await get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch(
'''SELECT id, name, price, ts_rank(search_vector, query) AS rank
FROM products, plainto_tsquery('english', $1) AS query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT $2''',
args['query'],
args.get('limit', 10)
)
if not rows:
return [types.TextContent(type='text', text=f'No products found matching "{args["query"]}". ')]
lines = [f'{r["id"]}: {r["name"]} — ${r["price"]}' for r in rows]
return [types.TextContent(type='text', text='\n'.join(lines))]Schreiboperationen mit Bestätigung
Wenn Ihr MCP-Server Schreiboperationen zulässt, implementieren Sie ein zweistufiges Bestätigungsmuster. Der erste Tool-Aufruf gibt eine Vorschau der geplanten Änderungen zurück. Ein zweites Tool confirm_action(action_id) führt die Änderung aus. So verhindern Sie, dass die KI aufgrund eines einzelnen Prompts irreversible Änderungen vornimmt, ohne dass der Benutzer davon weiß.
import uuid
pending_actions = {} # In-memory store; use Redis in production
async def create_order_preview(args: dict) -> list:
action_id = str(uuid.uuid4())[:8]
pending_actions[action_id] = {'type': 'create_order', 'data': args}
return [types.TextContent(
type='text',
text=f'Preview: Create order for customer {args["customer_id"]} with {len(args["items"])} items, '
f'total ${args["total"]}. Confirm with: confirm_action(action_id="{action_id}")')
]
async def confirm_action_handler(args: dict) -> list:
action_id = args.get('action_id')
action = pending_actions.pop(action_id, None)
if not action:
return [types.TextContent(type='text', text='Action expired or not found.')]
# Execute the pending action
result = await execute_action(action)
return [types.TextContent(type='text', text=f'Done: {result}')]Sensible Daten filtern
Stellen Sie sensible Spalten wie Passwörter, API-Schlüssel oder personenbezogene Daten niemals bereit, es sei denn, Ihr Server benötigt sie ausdrücklich für den jeweiligen Geschäftszweck. Verwenden Sie SELECT-Klauseln, die sensible Spalten ausschließen, und wenden Sie eine Filterung auf Zeilenebene an, die auf den Berechtigungen des authentifizierten Benutzers basiert. Ihr MCP-Server ist die letzte Schutzbarriere, bevor Daten die KI erreichen.
async def get_safe_customer(customer_id: int) -> dict:
'''Fetch customer data with sensitive fields excluded.'''
pool = await get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow(
'''SELECT id, name, created_at, country, tier
FROM customers
WHERE id = $1
-- NEVER select: password_hash, api_key, full_address, payment_method_id
''',
customer_id
)
return dict(row) if row else {}Audit-Logging für Datenbankzugriffe
Jeder Datenbankzugriff über Ihren MCP-Server sollte aus Sicherheits- und Compliance-Gründen protokolliert werden. Erfassen Sie das aufgerufene Tool, die übergebenen Argumente, die zurückgegebenen Zeilen, den Zeitstempel sowie verfügbaren Sitzungs- oder Benutzerkontext. Anhand dieser Prüfspur können Sie ungewöhnliche Muster erkennen und Compliance-Anforderungen erfüllen.
import logging
import sys
import time
audit_logger = logging.getLogger('mcp.audit')
audit_logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stderr)
audit_logger.addHandler(handler)
async def audited_tool_call(name: str, arguments: dict, session_id: str = None) -> list:
start = time.time()
result = await call_tool(name, arguments)
elapsed_ms = round((time.time() - start) * 1000)
audit_logger.info({
'event': 'tool_call',
'tool': name,
'args': {k: v for k, v in arguments.items() if k != 'password'},
'result_count': len(result),
'elapsed_ms': elapsed_ms,
'session_id': session_id
})
return resultDatenbank-MCP-Server testen
Testen Sie Ihren Datenbank-MCP-Server auf drei Ebenen: Unit-Tests für einzelne Abfragefunktionen (mit einer Testdatenbank oder Mocks), Integrationstests, die den vollständigen MCP-Server starten und Tools über den SDK-Client aufrufen, sowie End-to-End-Tests, die überprüfen, ob Claude Desktop die Tools sieht und korrekt aufruft. Automatisierte Tests erkennen Regressionen, bevor diese die KI erreichen.
Kurzer Test
Testen Sie Ihr Verständnis davon, wie Datenbankressourcen über MCP bereitgestellt werden.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: MCP-Datenbankserver fungieren als kontrollierte Proxys mit ausdrücklich definierten Abfragetools und standardmäßig schreibgeschützten Operationen, Datenbankzeilen können als MCP-Ressourcen mit strukturierten URIs bereitgestellt werden, auf die die KI verweisen kann und Schreiboperationen sollten dem Muster Vorschau, dann Bestätigung folgen, um unbeabsichtigte Änderungen zu verhindern. Als Nächstes sichern wir unseren MCP-Server mit OAuth-2.0-Authentifizierung und Eingabevalidierung.
Häufig gestellte Fragen
Ist die Lektion „Datenbankressourcen über MCP bereitstellen“ kostenlos?
Ja — der vollständige Text von „Datenbankressourcen über MCP bereitstellen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Engineering Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Datenbankressourcen über MCP bereitstellen“?
Erstellen Sie MCP-Ressourcen, die Datenbankinhalte dynamisch bereitstellen, stellen Sie Abfrage-Tools bereit, die das Modell aufrufen kann, und implementieren Sie eine Seitennavigation für große Erge… Du übst AI Engineering Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um AI Engineering Academy zu starten?
Keine Vorkenntnisse erforderlich. AI Engineering Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „Datenbankressourcen über MCP bereitstellen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser AI Engineering Academy-Lektion Code schreiben und ausführen?
Ja. Jede AI Engineering Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Was ist MCP und warum ist es wichtig?
- Ihren ersten MCP-Server entwickeln
- Datenbankressourcen über MCP bereitstellen
- Sicherheit und Authentifizierung in MCP