Exposición de recursos de bases de datos mediante MCP
Cree recursos MCP que proporcionen contenido de bases de datos dinámicamente, exponga herramientas de consulta que el modelo pueda llamar e implemente la paginación para conjuntos de resultados grandes.
Exposición de recursos de bases de datos mediante MCP es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.
Por qué las bases de datos necesitan MCP
Las bases de datos se encuentran entre las fuentes de datos más valiosas para los asistentes de IA, pero es demasiado peligroso proporcionar acceso directo a una base de datos a un LLM. Un servidor MCP actúa como un proxy controlado entre la IA y su base de datos, y expone únicamente los datos y las operaciones que usted permite explícitamente, con validación, limitación de velocidad y auditoría integradas.
Diseñar el esquema MCP de su base de datos
Antes de escribir código, decida qué debería poder ver y hacer la IA. Defina qué tablas se pueden leer, qué herramientas realizan operaciones de escritura (si las hay), qué datos deben filtrarse según el contexto del usuario y qué columnas deben ocultarse (contraseñas, información de identificación personal). Esta fase de diseño evita la exposición accidental de datos.
- Herramientas de lectura:
list_records,get_record,search_records - Herramientas de escritura (opcionales y restringidas):
create_record,update_record - Prohibido: ejecutar SQL sin procesar, modificar el esquema, acceder a tablas del sistema
Configurar la conexión a la base de datos
Use asyncpg (PostgreSQL) o aiosqlite (SQLite) para acceder de forma asíncrona a la base de datos desde su servidor MCP. Cree un grupo de conexiones al iniciar el servidor para que cada llamada a una herramienta no abra una conexión nueva. Almacene la URL de la base de datos en una variable de entorno, nunca en el 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 loggerMostrar registros como herramienta
Una herramienta list_records con filtrado y paginación permite a la IA explorar el contenido de la base de datos sin ejecutar SQL arbitrario. Acepte parámetros de filtrado, construya una consulta parametrizada y aplique siempre un límite de filas. Devuelva los resultados como una cadena con formato que el modelo pueda leer y utilizar como referencia en su respuesta.
@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))]Exponer filas de la base de datos como recursos
Los registros individuales de la base de datos se pueden exponer como recursos MCP mediante URI estructurados, como db://products/42. Los recursos permiten al cliente de IA almacenar en caché y hacer referencia a registros específicos sin llamar a una herramienta cada vez. Implemente list_resources para devolver los registros más relevantes y read_resource para obtener uno mediante su 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}')Paginación para conjuntos de resultados grandes
Las tablas de bases de datos suelen tener miles de filas. Implemente una paginación basada en cursores en sus herramientas para que la IA pueda solicitar la página siguiente de resultados. Use last_id como cursor estable, ya que es más fiable que la paginación basada en desplazamientos, que puede omitir filas cuando se insertan registros.
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'Herramienta de búsqueda de texto completo
Añada una herramienta de búsqueda que utilice la búsqueda de texto completo de PostgreSQL, lo que permite a la IA encontrar registros mediante consultas en lenguaje natural en lugar de coincidencias exactas de campos. Esto resulta especialmente eficaz para catálogos de productos, documentación y bases de conocimiento.
@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))]Operaciones de escritura con confirmación
Si su servidor MCP permite operaciones de escritura, incorpore un patrón de confirmación en dos pasos. La primera llamada a la herramienta devuelve una vista previa de los cambios. Una segunda herramienta, confirm_action(action_id), ejecuta el cambio. Esto evita que la IA realice escrituras irreversibles a partir de un solo prompt sin que el usuario sea consciente de ello.
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}')]Filtrar datos confidenciales
No exponga nunca columnas confidenciales, como contraseñas, claves de API o información de identificación personal, a menos que su servidor lo necesite explícitamente para el caso de uso empresarial. Use cláusulas SELECT que excluyan las columnas confidenciales y aplique filtros por fila basados en los permisos del usuario autenticado. Su servidor MCP es la última línea de defensa antes de que los datos lleguen a la 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 auditoría para el acceso a la base de datos
Todo acceso a la base de datos a través de su servidor MCP debe registrarse por motivos de seguridad y cumplimiento normativo. Registre la herramienta llamada, los argumentos proporcionados, las filas devueltas, la marca de tiempo y cualquier contexto de sesión o usuario disponible. Este registro de auditoría le permite detectar patrones anómalos y cumplir los requisitos normativos.
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 resultProbar su servidor MCP de base de datos
Pruebe su servidor MCP de base de datos en tres niveles: pruebas unitarias para funciones de consulta individuales (mediante una base de datos de prueba o mocks), pruebas de integración que inicien el servidor MCP completo y llamen a las herramientas mediante el cliente del SDK, y pruebas de extremo a extremo que verifiquen que Claude Desktop ve las herramientas y las llama correctamente. Las pruebas automatizadas detectan regresiones antes de que lleguen a la IA.
Comprobación rápida
Compruebe su comprensión de la exposición de recursos de bases de datos mediante MCP.
Resumen de la lección
En esta lección aprendió que: los servidores MCP de bases de datos actúan como proxies controlados con herramientas de consulta explícitas y operaciones de solo lectura de forma predeterminada, las filas de la base de datos se pueden exponer como recursos MCP con URI estructurados para que la IA haga referencia a ellos, y las operaciones de escritura deben usar un patrón de vista previa y confirmación para evitar cambios no deseados. A continuación, protegeremos nuestro servidor MCP con autenticación OAuth 2.0 y validación de entradas.
Aprende Python con un tutor de IA — gratis
Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.
- Cursos
- 30
- Lecciones
- 120
Preguntas frecuentes
¿La lección «Exposición de recursos de bases de datos mediante MCP» es gratis?
Sí — el texto completo de «Exposición de recursos de bases de datos mediante MCP» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Exposición de recursos de bases de datos mediante MCP»?
Cree recursos MCP que proporcionen contenido de bases de datos dinámicamente, exponga herramientas de consulta que el modelo pueda llamar e implemente la paginación para conjuntos de resultados grand… Practicas AI Engineering Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar AI Engineering Academy?
No se requiere experiencia previa. AI Engineering Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Exposición de recursos de bases de datos mediante MCP»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de AI Engineering Academy?
Sí. Cada lección de AI Engineering Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Qué es MCP y por qué es importante
- Creación de su primer servidor MCP
- Exposición de recursos de bases de datos mediante MCP
- Seguridad y autenticación en MCP