AI Engineering Academy · Pelajaran

Mendedahkan Sumber Pangkalan Data melalui MCP

Cipta sumber MCP yang menyediakan kandungan pangkalan data secara dinamik, dedahkan alat pertanyaan yang boleh dipanggil oleh model dan laksanakan penomboran untuk set hasil yang besar.

Pelajaran 3 daripada 413 langkah

Mendedahkan Sumber Pangkalan Data melalui MCP ialah pelajaran AI Engineering Academy percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran AI Engineering Academy, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus AI Engineering Academy merangkumi sejumlah 4 pelajaran.

Mengapa Pangkalan Data Memerlukan MCP

Pangkalan data merupakan antara sumber data paling bernilai untuk pembantu AI — tetapi memberikan akses pangkalan data mentah secara terus kepada LLM terlalu berbahaya. Pelayan MCP bertindak sebagai proksi terkawal antara AI dengan pangkalan data anda, dan hanya mendedahkan data serta operasi yang anda benarkan secara jelas, dengan pengesahan, pengehadan kadar dan pengauditan terbina dalam.

Mereka Bentuk Skema MCP Pangkalan Data

Sebelum menulis kod, tentukan perkara yang patut boleh dilihat dan dilakukan oleh AI. Tentukan: jadual yang boleh dibaca, alat yang menjalankan operasi tulis (jika ada), data yang mesti ditapis mengikut konteks pengguna, serta lajur yang patut disembunyikan (kata laluan, PII). Fasa reka bentuk ini menghalang pendedahan data secara tidak sengaja.

  • Alat baca: list_records, get_record, search_records
  • Alat tulis (pilihan, terhad): create_record, update_record
  • Dilarang: pelaksanaan SQL mentah, perubahan skema, jadual sistem

Menyediakan Sambungan Pangkalan Data

Gunakan asyncpg (PostgreSQL) atau aiosqlite (SQLite) untuk akses pangkalan data tak segerak dalam pelayan MCP anda. Cipta kumpulan sambungan semasa permulaan supaya setiap panggilan alat tidak membuka sambungan baharu. Simpan URL pangkalan data dalam pemboleh ubah persekitaran, jangan sekali-kali dalam kod.

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

Menyenaraikan Rekod sebagai Alat

Alat list_records dengan penapisan dan penomboran halaman membolehkan AI menyemak kandungan pangkalan data tanpa menjalankan SQL sewenang-wenangnya. Terima parameter penapis, bina pertanyaan berparameter dan sentiasa kenakan had bilangan baris. Pulangkan hasil sebagai rentetan berformat yang boleh dibaca oleh 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))]

Mendedahkan Baris Pangkalan Data sebagai Resource

Rekod pangkalan data individu boleh didedahkan sebagai resource MCP menggunakan URI berstruktur seperti db://products/42. Resource membolehkan klien AI menyimpan cache dan merujuk rekod tertentu tanpa memanggil alat setiap kali. Laksanakan list_resources untuk memulangkan rekod paling berkaitan dan read_resource untuk mendapatkan satu rekod mengikut 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}')

Penomboran Halaman untuk Set Hasil Besar

Jadual pangkalan data sering mengandungi ribuan baris. Laksanakan penomboran halaman berasaskan kursor dalam alat anda supaya AI boleh meminta halaman hasil yang seterusnya. Gunakan last_id sebagai kursor stabil (lebih boleh dipercayai berbanding penomboran berasaskan offset yang boleh melangkau baris apabila rekod disisipkan).

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 Carian Teks Penuh

Tambahkan alat carian yang menggunakan carian teks penuh PostgreSQL supaya AI boleh mencari rekod berdasarkan pertanyaan bahasa semula jadi, bukannya padanan medan yang tepat. Ciri ini amat berkuasa untuk katalog produk, dokumentasi dan pangkalan 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 Pengesahan

Jika pelayan MCP anda membenarkan operasi tulis, bina corak pengesahan dua langkah. Panggilan alat pertama memulangkan pratonton perkara yang akan berubah. Alat confirm_action(action_id) yang kedua melaksanakan perubahan tersebut. Ini menghalang AI daripada melakukan penulisan yang tidak boleh dibuat asal melalui satu prompt tanpa pengetahuan 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}')]

Menapis Data Sensitif

Jangan sekali-kali mendedahkan lajur sensitif seperti kata laluan, kunci API atau PII, kecuali pelayan anda benar-benar memerlukannya untuk kes penggunaan perniagaan. Gunakan klausa SELECT yang mengecualikan lajur sensitif, dan gunakan penapisan peringkat baris berdasarkan keizinan pengguna yang disahkan. Pelayan MCP anda ialah benteng pertahanan terakhir sebelum data sampai kepada 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 {}

Pencatatan Audit untuk Akses Pangkalan Data

Setiap akses pangkalan data melalui pelayan MCP anda hendaklah dicatat untuk tujuan keselamatan dan pematuhan. Rekodkan alat yang dipanggil, argumen yang dihantar, baris yang dipulangkan, cap masa serta sebarang konteks sesi/pengguna yang tersedia. Jejak audit ini membolehkan anda mengesan corak luar biasa dan memenuhi keperluan pematuhan.

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 Pelayan MCP Pangkalan Data Anda

Uji pelayan MCP pangkalan data anda pada tiga tahap: ujian unit untuk fungsi pertanyaan individu (menggunakan pangkalan data ujian atau objek palsu), ujian integrasi yang memulakan pelayan MCP penuh dan memanggil alat melalui klien SDK, serta ujian hujung ke hujung yang mengesahkan Claude Desktop melihat alat tersebut dan memanggilnya dengan betul. Ujian automatik mengesan regresi sebelum masalah sampai kepada AI.

Semakan Pantas

Uji pemahaman anda tentang pendedahan resource pangkalan data melalui MCP.

Imbas Kembali Pelajaran

Dalam pelajaran ini, anda telah mempelajari bahawa: pelayan pangkalan data MCP bertindak sebagai proksi terkawal dengan alat pertanyaan yang jelas dan operasi baca sahaja secara lalai, baris pangkalan data boleh didedahkan sebagai resource MCP dengan URI berstruktur untuk dirujuk oleh AI, dan operasi tulis hendaklah menggunakan corak pratonton kemudian pengesahan bagi menghalang perubahan yang tidak diingini. Seterusnya, kita akan melindungi pelayan MCP dengan pengesahan OAuth 2.0 dan pengesahan input.

Percuma untuk bermula

Pelajari Python dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
30
Pelajaran
120

Soalan Lazim

Adakah pelajaran “Mendedahkan Sumber Pangkalan Data melalui MCP” percuma?

Ya — teks penuh “Mendedahkan Sumber Pangkalan Data melalui MCP” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus AI Engineering Academy, tingkat taraf kepada CoddyKit PRO. Kursus AI Engineering Academy merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Mendedahkan Sumber Pangkalan Data melalui MCP”?

Cipta sumber MCP yang menyediakan kandungan pangkalan data secara dinamik, dedahkan alat pertanyaan yang boleh dipanggil oleh model dan laksanakan penomboran untuk set hasil yang besar. Anda berlatih AI Engineering Academy menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan AI Engineering Academy?

Tiada pengalaman terdahulu diperlukan. Pembelajaran AI Engineering Academy di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.

Berapa lamakah pelajaran “Mendedahkan Sumber Pangkalan Data melalui MCP” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran AI Engineering Academy ini?

Ya. Setiap pelajaran AI Engineering Academy menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Apakah MCP dan Mengapa Ia Penting
  2. Membina Pelayan MCP Pertama Anda
  3. Mendedahkan Sumber Pangkalan Data melalui MCP
  4. Keselamatan dan Pengesahan MCP
← Kembali ke AI Engineering Academy