AI Engineering Academy · Lektion

Exponera databaseresurser via MCP

Skapa MCP-resurser som dynamiskt tillhandahåller databasinnehåll, exponera frågeverktyg som modellen kan anropa och implementera paginering för stora resultatmängder.

Lektion 3 av 413 steg

Exponera databaseresurser via MCP är en gratis lektion i AI Engineering Academy på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för AI Engineering Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i AI Engineering Academy innehåller totalt 4 lektioner.

Varför databaser behöver MCP

Databaser hör till de mest värdefulla datakällorna för AI-assistenter – men det är för riskabelt att ge en LLM direkt åtkomst till en rå databas. En MCP-server fungerar som en kontrollerad proxy mellan AI:n och databasen och exponerar endast de data och åtgärder som du uttryckligen tillåter, med inbyggd validering, hastighetsbegränsning och granskning.

Utforma MCP-schemat för databasen

Bestäm vad AI:n ska kunna se och göra innan du skriver kod. Definiera vilka tabeller som får läsas, vilka verktyg som eventuellt får utföra skrivåtgärder, vilka data som måste filtreras utifrån användarens kontext och vilka kolumner som ska döljas, till exempel lösenord och personligt identifierbar information (PII). Den här designfasen förhindrar oavsiktlig dataexponering.

  • Läsverktyg: list_records, get_record, search_records
  • Skrivverktyg (valfritt, begränsat): create_record, update_record
  • Förbjudet: körning av rå SQL, ändringar av schemat, systemtabeller

Konfigurera databasanslutningen

Använd asyncpg (PostgreSQL) eller aiosqlite (SQLite) för asynkron databasåtkomst i MCP-servern. Skapa en anslutningspool vid uppstart så att varje verktygsanrop inte behöver öppna en ny anslutning. Lagra databasens URL i en miljövariabel, aldrig i koden.

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 logger

Visa poster som ett verktyg

Ett verktyg som heter list_records och har filtrering och paginering låter AI:n bläddra i databasinnehåll utan att köra godtycklig SQL. Ta emot filterparametrar, bygg en parametriserad fråga och använd alltid en begränsning av antalet rader. Returnera resultaten som en formaterad sträng som modellen kan läsa och hänvisa till i sitt svar.

@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))]

Exponera databasrader som resurser

Enskilda dataposter kan exponeras som MCP-resurser med hjälp av strukturerade URI:er som db://products/42. Resurser gör det möjligt för AI-klienten att cacha och hänvisa till specifika poster utan att anropa ett verktyg varje gång. Implementera list_resources för att returnera de mest relevanta posterna och read_resource för att hämta en post via dess URI.

@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}')

Paginering för stora resultatmängder

Databastabeller innehåller ofta tusentals rader. Implementera markörbaserad paginering i verktygen så att AI:n kan begära nästa resultatsida. Använd last_id som en stabil markör. Det är mer tillförlitligt än offsetbaserad paginering, som kan hoppa över rader när poster infogas.

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'

Verktyg för fulltextsökning

Lägg till ett sökverktyg som använder PostgreSQLs fulltextsökning, så att AI:n kan hitta poster med naturliga språkfrågor i stället för exakta fältmatchningar. Det är särskilt användbart för produktkataloger, dokumentation och kunskapsbaser.

@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))]

Skrivåtgärder med bekräftelse

Om MCP-servern tillåter skrivåtgärder bör du införa ett bekräftelseförfarande i två steg. Det första verktygsanropet returnerar en förhandsvisning av vad som kommer att ändras. Ett andra verktyg, confirm_action(action_id), genomför ändringen. På så sätt förhindras AI:n från att utföra oåterkalleliga skrivåtgärder utifrån en enda instruktion utan att användaren är medveten om det.

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}')]

Filtrera känsliga data

Exponera aldrig känsliga kolumner som lösenord, API-nycklar eller PII om servern inte uttryckligen behöver dem för det aktuella användningsfallet. Använd SELECT-satser som utesluter känsliga kolumner och tillämpa filtrering på radnivå utifrån den autentiserade användarens behörigheter. MCP-servern är den sista försvarslinjen innan data når AI:n.

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 {}

Granskningsloggning för databasåtkomst

All databasåtkomst via MCP-servern bör loggas av säkerhets- och efterlevnadsskäl. Registrera vilket verktyg som anropades, vilka argument som skickades, vilka rader som returnerades, tidsstämpeln samt eventuell tillgänglig sessions- eller användarkontext. Detta granskningsspår gör det möjligt att upptäcka avvikande mönster och uppfylla efterlevnadskrav.

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 result

Testa MCP-servern för databasen

Testa MCP-servern för databasen på tre nivåer: enhetstester för enskilda frågefunktioner med hjälp av en testdatabas eller mockar, integrationstester som startar hela MCP-servern och anropar verktyg via SDK-klienten samt end-to-end-tester som verifierar att Claude Desktop ser verktygen och anropar dem korrekt. Automatiserade tester upptäcker regressioner innan de når AI:n.

Snabbtest

Testa dina kunskaper om att exponera databasresurser via MCP.

Sammanfattning av lektionen

I den här lektionen har du lärt dig att MCP-databasservrar fungerar som kontrollerade proxyservrar med uttryckliga frågeverktyg och skrivskyddade åtgärder som standard, att databasrader kan exponeras som MCP-resurser med strukturerade URI:er som AI:n kan hänvisa till samt att skrivåtgärder bör använda ett förhandsgransknings- och bekräftelseförfarande för att förhindra oavsiktliga ändringar. Nästa steg är att säkra MCP-servern med OAuth 2.0-autentisering och indatavalidering.

Gratis att börja

Lär dig Python med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
30
Lektioner
120

Vanliga frågor

Är lektionen ”Exponera databaseresurser via MCP” gratis?

Ja – hela texten till ”Exponera databaseresurser via MCP” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i AI Engineering Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i AI Engineering Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Exponera databaseresurser via MCP”?

Skapa MCP-resurser som dynamiskt tillhandahåller databasinnehåll, exponera frågeverktyg som modellen kan anropa och implementera paginering för stora resultatmängder. Ni övar på AI Engineering Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig AI Engineering Academy?

Du behöver inga förkunskaper. Utbildningen i AI Engineering Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Exponera databaseresurser via MCP”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här AI Engineering Academy-lektionen?

Ja. Varje AI Engineering Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Vad är MCP och varför är det viktigt
  2. Bygg er första MCP-server
  3. Exponera databaseresurser via MCP
  4. MCP-säkerhet och autentisering
← Tillbaka till AI Engineering Academy