Claude y el Model Context Protocol: conecta la IA con tus sistemas reales

Claude puede razonar sobre casi cualquier problema técnico. Pero razonar no es lo mismo que resolver. Para que un modelo de IA sea útil en el contexto de trabajo real de un equipo de desarrollo, necesita poder consultar la base de datos, leer ficheros, ejecutar queries, actualizar registros y llamar a APIs internas. Eso es exactamente lo que resuelve el Model Context Protocol.
MCP es un estándar abierto creado por Anthropic y adoptado por otros modelos y clientes. Define cómo los sistemas de IA se comunican con herramientas externas de forma segura, con un protocolo unificado. En 2026, MCP ya no es experimental: es la forma estándar de extender los agentes de IA con capacidades reales.
Qué es MCP y cómo funciona
MCP sigue una arquitectura cliente-servidor. El host (Claude Desktop, Claude Code, Cursor, o tu propio cliente) actúa como intermediario entre el usuario y los servidores MCP. Cada servidor expone capacidades en tres categorías:
- Resources: datos que el modelo lee bajo demanda: ficheros, resultados de queries, documentos
- Tools: funciones que el modelo ejecuta: insertar en BD, enviar emails, llamar APIs
- Prompts: plantillas de prompt reutilizables para tareas concretas
El modelo nunca tiene acceso directo a tus sistemas. Siempre pasa por el servidor MCP, que tú controlas y donde defines exactamente qué operaciones están permitidas.
Construir un servidor MCP para Supabase
npm install @modelcontextprotocol/sdk @supabase/supabase-js// src/server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const server = new Server(
{ name: 'portfolio-db', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'listar_posts',
description:
'Lista los posts del blog. Filtra por estado (published/draft) y limita resultados.',
inputSchema: {
type: 'object',
properties: {
estado: { type: 'string', enum: ['published', 'draft'] },
limite: { type: 'number', default: 10 },
},
},
},
{
name: 'actualizar_seo',
description:
'Actualiza seo_title y meta_description de un post. Para optimizaciones masivas de SEO.',
inputSchema: {
type: 'object',
properties: {
id: { type: 'number' },
seo_title: { type: 'string', maxLength: 60 },
meta_description: { type: 'string', maxLength: 160 },
},
required: ['id'],
},
},
{
name: 'publicar_post',
description: 'Cambia el estado de un post de draft a published.',
inputSchema: {
type: 'object',
properties: { id: { type: 'number' } },
required: ['id'],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args } = req.params;
switch (name) {
case 'listar_posts': {
let query = supabase
.from('posts')
.select('id, slug, title, status, published_at, seo_title, meta_description')
.order('published_at', { ascending: false })
.limit(args?.limite ?? 10);
if (args?.estado) query = query.eq('status', args.estado);
const { data, error } = await query;
if (error) throw new Error(error.message);
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
}
case 'actualizar_seo': {
const { id, ...campos } = args as { id: number; seo_title?: string; meta_description?: string };
const { error } = await supabase
.from('posts')
.update({ ...campos, updated_at: new Date().toISOString() })
.eq('id', id);
if (error) throw new Error(error.message);
return { content: [{ type: 'text', text: `Post ${id} actualizado.` }] };
}
case 'publicar_post': {
const { error } = await supabase
.from('posts')
.update({ status: 'published', published_at: new Date().toISOString() })
.eq('id', args.id)
.eq('status', 'draft');
if (error) throw new Error(error.message);
return { content: [{ type: 'text', text: `Post ${args.id} publicado.` }] };
}
default:
throw new Error(`Herramienta desconocida: ${name}`);
}
});
const transport = new StdioServerTransport();
await server.connect(transport);Configurar Claude Desktop
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"portfolio-db": {
"command": "node",
"args": ["/ruta/al/servidor/dist/server.js"],
"env": {
"SUPABASE_URL": "https://tu-proyecto.supabase.co",
"SUPABASE_SERVICE_ROLE_KEY": "tu-service-role-key"
}
}
}
}Tras reiniciar Claude Desktop, puedes pedirle directamente:
- "Lista los posts en borrador que tienen meta_description vacía"
- "Actualiza el seo_title de todos los posts publicados para incluir el año 2026"
- "Publica el post con id 12 y confírmame el resultado"
Resources: exponer schema y documentación
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'db://schema/posts',
name: 'Schema tabla posts',
mimeType: 'application/json',
},
{
uri: 'file:///docs/arquitectura.md',
name: 'Arquitectura del proyecto',
mimeType: 'text/markdown',
},
],
}));Con esto, Claude puede leer el schema de tu BD antes de sugerir queries, o la documentación de arquitectura antes de proponer cambios en el código.
Errores comunes al implementar MCP
Descripciones de herramientas vagas. La descripción es lo que Claude usa para decidir qué herramienta llamar. "Obtiene datos" no sirve. "Devuelve los últimos N posts publicados ordenados por fecha, opcionalmente filtrados por estado" sí sirve.
Sin validación de argumentos. Si el modelo pasa un argumento incorrecto y el handler no lo valida, puedes corromper datos. Valida tipos y rangos antes de ejecutar operaciones de escritura.
Service role key expuesta. Si el config de Claude Desktop está en un directorio compartido, la service role key queda expuesta. Usa variables de entorno del sistema operativo.
Herramientas destructivas sin confirmación. Para operaciones como borrar registros, considera un paso previo que muestra lo que se va a eliminar antes de ejecutarlo.
Conclusión
MCP es el puente entre los modelos de IA y los sistemas reales. La diferencia entre "Claude me explica cómo mejorar el SEO de mis posts" y "Claude mejora el SEO de todos mis posts directamente" es la diferencia entre una herramienta de consulta y una herramienta de trabajo. El servidor MCP de base de datos es el punto de entrada más natural. Desde ahí, puedes añadir herramientas para tu sistema de archivos, tu gestor de tareas o cualquier sistema con una API. La barrera de entrada es baja y el retorno en productividad es inmediato.