AI Engineering Academy · Aula

Disponibilizando recursos de banco de dados via MCP

Crie recursos MCP que disponibilizem dinamicamente o conteúdo do banco de dados, exponha ferramentas de consulta que o modelo possa chamar e implemente paginação para grandes conjuntos de resultados.

Aula 3 de 413 etapas

Disponibilizando recursos de banco de dados via MCP é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.

Por que os bancos de dados precisam do MCP

Os bancos de dados estão entre as fontes de dados mais valiosas para assistentes de IA — mas o acesso direto ao banco de dados é perigoso demais para ser fornecido diretamente a um LLM. Um servidor MCP atua como um proxy controlado entre a IA e seu banco de dados, expondo apenas os dados e as operações que você autorizar explicitamente, com validação, limitação de taxa e auditoria incorporadas.

Projetando o esquema MCP do banco de dados

Antes de escrever o código, decida o que a IA poderá ver e fazer. Defina: quais tabelas poderão ser lidas, quais ferramentas realizarão operações de escrita, se houver, quais dados deverão ser filtrados pelo contexto do usuário e quais colunas deverão ser ocultadas, como senhas e PII. Essa etapa de projeto evita a exposição acidental de dados.

  • Ferramentas de leitura: list_records, get_record, search_records
  • Ferramentas de escrita (opcionais e restritas): create_record, update_record
  • Proibido: execução de SQL bruto, alterações de esquema e tabelas do sistema

Configurando a conexão com o banco de dados

Use asyncpg (PostgreSQL) ou aiosqlite (SQLite) para o acesso assíncrono ao banco de dados no seu servidor MCP. Crie um pool de conexões na inicialização para que cada chamada de ferramenta não precise abrir uma nova conexão. Armazene a URL do banco de dados em uma variável de ambiente, nunca no código.

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

Listando registros como uma ferramenta

Uma ferramenta list_records com filtragem e paginação permite que a IA navegue pelo conteúdo do banco de dados sem executar SQL arbitrário. Aceite parâmetros de filtro, crie uma consulta parametrizada e sempre aplique um limite de linhas. Retorne os resultados como uma string formatada que o modelo possa ler e usar como referência em sua resposta.

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

Expondo linhas do banco de dados como recursos

Registros individuais do banco de dados podem ser expostos como recursos MCP usando URIs estruturadas, como db://products/42. Os recursos permitem que o cliente de IA armazene em cache e faça referência a registros específicos sem chamar uma ferramenta a cada vez. Implemente list_resources para retornar os registros mais relevantes e read_resource para buscar um registro por 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}')

Paginação para grandes conjuntos de resultados

As tabelas de bancos de dados frequentemente têm milhares de linhas. Implemente paginação baseada em cursor nas suas ferramentas para que a IA possa solicitar a próxima página de resultados. Use last_id como um cursor estável, mais confiável do que a paginação baseada em deslocamento, que pode ignorar linhas quando há inserções.

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'

Ferramenta de pesquisa de texto completo

Adicione uma ferramenta de pesquisa que use a pesquisa de texto completo do PostgreSQL, permitindo que a IA encontre registros por meio de consultas em linguagem natural, em vez de correspondências exatas de campos. Isso é especialmente útil para catálogos de produtos, documentação e bases de conhecimento.

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

Operações de escrita com confirmação

Se o seu servidor MCP permitir operações de escrita, implemente um padrão de confirmação em duas etapas. A primeira chamada da ferramenta retorna uma prévia do que será alterado. Uma segunda ferramenta confirm_action(action_id) executa a alteração. Isso impede que a IA faça alterações irreversíveis a partir de um único prompt sem o conhecimento do usuário.

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

Filtrando dados confidenciais

Nunca exponha colunas confidenciais, como senhas, chaves de API ou PII, a menos que o servidor precise explicitamente delas para o caso de uso empresarial. Use cláusulas SELECT que excluam colunas confidenciais e aplique filtragem em nível de linha com base nas permissões do usuário autenticado. Seu servidor MCP é a última linha de defesa antes que os dados cheguem à IA.

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

Registro de auditoria do acesso ao banco de dados

Todo acesso ao banco de dados por meio do seu servidor MCP deve ser registrado para fins de segurança e conformidade. Registre a ferramenta chamada, os argumentos fornecidos, as linhas retornadas, o horário e qualquer contexto de sessão ou usuário disponível. Essa trilha de auditoria permite detectar padrões anômalos e atender aos requisitos de conformidade.

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

Testando seu servidor MCP de banco de dados

Teste seu servidor MCP de banco de dados em três níveis: testes unitários para funções de consulta individuais, usando um banco de dados de teste ou simulações; testes de integração que iniciem o servidor MCP completo e chamem ferramentas por meio do cliente do SDK; e testes de ponta a ponta que verifiquem se o Claude Desktop vê as ferramentas e as chama corretamente. Os testes automatizados detectam regressões antes que elas cheguem à IA.

Verificação rápida

Teste sua compreensão sobre a exposição de recursos de banco de dados por meio do MCP.

Recapitulação da lição

Nesta lição, você aprendeu que: os servidores MCP de banco de dados atuam como proxies controlados, com ferramentas de consulta explícitas e operações somente de leitura por padrão; as linhas do banco de dados podem ser expostas como recursos MCP com URIs estruturadas para que a IA faça referência a elas; e as operações de escrita devem usar um padrão de prévia seguida de confirmação para impedir alterações não intencionais. A seguir, protegeremos nosso servidor MCP com autenticação OAuth 2.0 e validação de entradas.

Grátis para começar

Aprenda Python com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
30
Aulas
120

Perguntas Frequentes

A aula “Disponibilizando recursos de banco de dados via MCP” é grátis?

Sim — o texto completo de “Disponibilizando recursos de banco de dados via MCP” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.

O que vou aprender em “Disponibilizando recursos de banco de dados via MCP”?

Crie recursos MCP que disponibilizem dinamicamente o conteúdo do banco de dados, exponha ferramentas de consulta que o modelo possa chamar e implemente paginação para grandes conjuntos de resultados. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar AI Engineering Academy?

Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Disponibilizando recursos de banco de dados via MCP”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de AI Engineering Academy?

Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. O que é MCP e por que ele importa
  2. Criando seu primeiro servidor MCP
  3. Disponibilizando recursos de banco de dados via MCP
  4. Segurança e autenticação no MCP
← Voltar para AI Engineering Academy