0Pricing
AI Engineering Academy · Pelajaran

Menyediakan Sumber Daya Basis Data melalui MCP

Buat sumber daya MCP yang menyediakan konten basis data secara dinamis, sediakan alat kueri yang dapat dipanggil model, dan terapkan pembagian halaman untuk kumpulan hasil yang besar.

Menyediakan Sumber Daya Basis Data melalui MCP adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 3 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.

Mengapa Basis Data Membutuhkan MCP

Basis data adalah salah satu sumber data paling berharga bagi asisten AI—tetapi akses basis data mentah terlalu berbahaya jika diberikan langsung kepada LLM. Server MCP bertindak sebagai proksi terkendali antara AI dan basis data Anda, dengan hanya mengekspos data serta operasi yang secara eksplisit Anda izinkan, lengkap dengan validasi, pembatasan laju, dan audit bawaan.

Merancang Skema MCP Basis Data

Sebelum menulis kode, tentukan hal-hal yang boleh dilihat dan dilakukan AI. Tentukan: tabel mana yang dapat dibaca, alat mana yang melakukan operasi tulis jika ada, data apa yang harus difilter berdasarkan konteks pengguna, serta kolom mana yang harus disembunyikan seperti kata sandi dan PII. Tahap perancangan ini mencegah terbukanya data secara tidak sengaja.

  • Alat baca: list_records, get_record, search_records
  • Alat tulis (opsional, dibatasi): create_record, update_record
  • Dilarang: eksekusi SQL mentah, perubahan skema, tabel sistem

Menyiapkan Koneksi Basis Data

Gunakan asyncpg (PostgreSQL) atau aiosqlite (SQLite) untuk akses basis data asinkron di server MCP Anda. Buat kumpulan koneksi saat startup agar setiap pemanggilan alat tidak membuka koneksi baru. Simpan URL basis data dalam variabel lingkungan, jangan pernah menuliskannya di dalam kode.

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

Menyediakan Record sebagai Alat

Alat list_records dengan pemfilteran dan paginasi memungkinkan AI menjelajahi isi basis data tanpa menjalankan SQL arbitrer. Terima parameter filter, buat kueri berparameter, dan selalu terapkan batas jumlah baris. Kembalikan hasil sebagai string berformat yang dapat dibaca model dan dirujuk dalam jawabannya.

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

Mengekspos Baris Basis Data sebagai Resource

Record basis data individual dapat diekspos sebagai resource MCP menggunakan URI terstruktur seperti db://products/42. Resource memungkinkan klien AI menyimpan dalam tembolok dan merujuk record tertentu tanpa memanggil alat setiap kali. Implementasikan list_resources untuk mengembalikan record yang paling relevan dan read_resource untuk mengambil satu record berdasarkan 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}')

Paginasi untuk Kumpulan Hasil Besar

Tabel basis data sering memiliki ribuan baris. Implementasikan paginasi berbasis kursor dalam alat Anda agar AI dapat meminta halaman hasil berikutnya. Gunakan last_id sebagai kursor stabil, yang lebih andal daripada paginasi berbasis offset karena baris dapat terlewati saat penyisipan dilakukan.

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'

Alat Pencarian Teks Lengkap

Tambahkan alat pencarian yang menggunakan pencarian teks lengkap PostgreSQL agar AI dapat menemukan record berdasarkan kueri bahasa alami, bukan hanya kecocokan kolom yang persis. Fitur ini sangat berguna terutama untuk katalog produk, dokumentasi, dan basis pengetahuan.

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

Operasi Tulis dengan Konfirmasi

Jika server MCP Anda mengizinkan operasi tulis, terapkan pola konfirmasi dua langkah. Pemanggilan alat pertama mengembalikan pratinjau perubahan yang akan dilakukan. Alat confirm_action(action_id) kedua menjalankan perubahan tersebut. Dengan demikian, AI tidak dapat melakukan penulisan yang tidak dapat dibatalkan hanya dari satu perintah tanpa sepengetahuan pengguna.

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

Memfilter Data Sensitif

Jangan pernah mengekspos kolom sensitif seperti kata sandi, kunci API, atau PII, kecuali server Anda memang membutuhkannya untuk keperluan bisnis. Gunakan klausa SELECT yang mengecualikan kolom sensitif, lalu terapkan pemfilteran tingkat baris berdasarkan izin pengguna yang telah diautentikasi. Server MCP Anda adalah garis pertahanan terakhir sebelum data mencapai AI.

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

Logging Audit untuk Akses Basis Data

Setiap akses basis data melalui server MCP Anda harus dicatat demi keamanan dan kepatuhan. Catat alat yang dipanggil, argumen yang diteruskan, baris yang dikembalikan, stempel waktu, serta konteks sesi atau pengguna yang tersedia. Jejak audit ini memungkinkan Anda mendeteksi pola anomali dan memenuhi persyaratan kepatuhan.

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

Menguji Server MCP Basis Data

Uji server MCP basis data Anda pada tiga tingkat: pengujian unit untuk setiap fungsi kueri menggunakan basis data pengujian atau tiruan, pengujian integrasi yang memulai server MCP lengkap lalu memanggil alat melalui klien SDK, serta pengujian menyeluruh yang memastikan Claude Desktop melihat alat dan memanggilnya dengan benar. Pengujian otomatis menemukan regresi sebelum perubahan tersebut mencapai AI.

Pemeriksaan Cepat

Uji pemahaman Anda tentang pengeksposan resource basis data melalui MCP.

Ringkasan Pelajaran

Dalam pelajaran ini, Anda mempelajari bahwa: server MCP basis data bertindak sebagai proksi terkendali dengan alat kueri eksplisit dan operasi hanya-baca secara bawaan, baris basis data dapat diekspos sebagai resource MCP dengan URI terstruktur untuk dirujuk AI, dan operasi tulis sebaiknya menggunakan pola pratinjau lalu konfirmasi untuk mencegah perubahan yang tidak diinginkan. Selanjutnya, kita akan mengamankan server MCP dengan autentikasi OAuth 2.0 dan validasi input.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Menyediakan Sumber Daya Basis Data melalui MCP” gratis?

Ya — teks lengkap “Menyediakan Sumber Daya Basis Data melalui MCP” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Menyediakan Sumber Daya Basis Data melalui MCP”?

Buat sumber daya MCP yang menyediakan konten basis data secara dinamis, sediakan alat kueri yang dapat dipanggil model, dan terapkan pembagian halaman untuk kumpulan hasil yang besar. Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?

Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 3 dari 4.

Berapa lama pelajaran “Menyediakan Sumber Daya Basis Data melalui MCP” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?

Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Apa Itu MCP dan Mengapa Penting
  2. Membangun Server MCP Pertama Anda
  3. Menyediakan Sumber Daya Basis Data melalui MCP
  4. Keamanan dan Autentikasi MCP
← Kembali ke AI Engineering Academy