Skip to content

🤖 ESTÁNDAR DE HERRAMIENTAS Y SERVIDORES MCP (MODEL CONTEXT PROTOCOL)

Objetivo: Definir cómo construir, integrar y extender servidores MCP (Model Context Protocol) para conectar asistentes de IA (como Claude Code o Claude Desktop) de forma segura con nuestras bases de datos, APIs de pago y servicios cloud.


🎯 LAS 4 REGLAS INQUEBRANTABLES DE MCP

[REQUIRED] MCP-001: Todo MCP Server DEBE validar sus inputs con Zod. NUNCA ejecutar consultas o comandos con argumentos sin validar.

Por qué: un servidor MCP ejecuta acciones con argumentos que propone un modelo, no una persona. Un modelo puede alucinar un argumento o repetir uno inyectado en el contenido que acaba de leer, así que la entrada es tan poco confiable como la de un endpoint público y se valida igual (S-001).

[REQUIRED] MCP-002: NUNCA exponer credenciales ni secretos en el código del servidor MCP. Los tokens y claves API se leen de variables de entorno del proceso.

Por qué: el código de un servidor MCP se comparte, se versiona y se pega en conversaciones con un modelo. Un secreto ahí escapa por más vías de las habituales, incluida la de acabar dentro del contexto de un LLM.

[REQUIRED] MCP-003: Principio de Privilegio Mínimo. Un servidor MCP que consulta la base de datos debe usar un rol con permisos acotados o funciones RPC específicas — NUNCA acceso ilimitado de superusuario.

Por qué: un servidor MCP amplifica cualquier permiso que le des, porque quien decide qué invocar es un modelo. Con service_role sobre la base de datos, una petición mal interpretada es un borrado masivo; con un rol acotado o funciones RPC concretas, es un error recuperable.

[REQUIRED] MCP-004: Todo MCP Server DEBE incluir Rate Limiting y límites de respuesta. Evita que la IA realice cientos de peticiones involuntarias o devuelva payloads de gigabytes que saturen el contexto.

Por qué: un modelo en bucle puede lanzar cientos de llamadas en segundos sin intención de hacer daño, y una respuesta enorme agota la ventana de contexto además de la cuota. El límite protege del uso accidental, que aquí es mucho más probable que el malicioso.


🌐 1. ¿QUÉ ES MCP (MODEL CONTEXT PROTOCOL)?

El Model Context Protocol (MCP) es un estándar abierto que permite a los modelos de lenguaje (LLMs) interactuar de forma segura con herramientas externas, recursos de datos y APIs locales o remotas a través de una interfaz cliente-servidor JSON-RPC 2.0.

┌────────────────────────────────┐         JSON-RPC         ┌────────────────────────────────┐
│   Cliente MCP (Claude Code /   │ ───────────────────────► │   Servidor MCP (TypeScript)    │
│   Claude Desktop / Antigravity)│ ◄─────────────────────── │  (Supabase / Stripe / R2 API)  │
└────────────────────────────────┘      Stdio / SSE         └────────────────────────────────┘

💻 2. ESTRUCTURA Y CÓDIGO DE UN MCP SERVER COMPLETO

A continuación se muestra la implementación de referencia de un servidor MCP en TypeScript usando el SDK oficial @modelcontextprotocol/sdk.

typescript
// mcp-servers/supabase-server/src/index.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-supabase-js'
import { z } from 'zod'

// 1. Validar entorno
const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY

if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
  console.error('Error: Faltan las variables SUPABASE_URL o SUPABASE_SERVICE_ROLE_KEY')
  process.exit(1)
}

const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)

// 2. Inicializar el Servidor MCP
const server = new Server(
  {
    name: 'supabase-mcp-server',
    version: '1.0.0'
  },
  {
    capabilities: {
      tools: {}
    }
  }
)

// 3. Schemas de Zod para validación estricta de inputs (MCP-001)
const QueryTableSchema = z.object({
  table: z.string().regex(/^[a-zA-Z0-9_]+$/, 'Nombre de tabla inválido'),
  columns: z.string().default('id, created_at'),
  limit: z.number().min(1).max(100).default(20)
})

const ExecuteRpcSchema = z.object({
  function_name: z.string().regex(/^[a-zA-Z0-9_]+$/),
  args: z.record(z.unknown()).optional()
})

// 4. Registrar la lista de herramientas disponibles
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'query_table',
      description: 'Consulta filas de una tabla permitida en Supabase con columnas explícitas.',
      inputSchema: {
        type: 'object',
        properties: {
          table: { type: 'string', description: 'Nombre de la tabla (ej. documents, teams)' },
          columns: { type: 'string', description: 'Columnas a seleccionar separadas por comas (NUNCA *)' },
          limit: { type: 'number', description: 'Cantidad máxima de registros (1-100)' }
        },
        required: ['table']
      }
    },
    {
      name: 'execute_rpc',
      description: 'Ejecuta una función almacenada segura (RPC) en la base de datos.',
      inputSchema: {
        type: 'object',
        properties: {
          function_name: { type: 'string', description: 'Nombre de la función RPC' },
          args: { type: 'object', description: 'Argumentos clave-valor para la función' }
        },
        required: ['function_name']
      }
    }
  ]
}))

// 5. Manejar la ejecución de las herramientas (tools/call)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params

  try {
    switch (name) {
      case 'query_table': {
        const validated = QueryTableSchema.parse(args)
        
        const { data, error } = await supabase
          .from(validated.table)
          .select(validated.columns)
          .limit(validated.limit)

        if (error) throw error

        return {
          content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
        }
      }

      case 'execute_rpc': {
        const validated = ExecuteRpcSchema.parse(args)

        const { data, error } = await supabase.rpc(
          validated.function_name,
          validated.args ?? {}
        )

        if (error) throw error

        return {
          content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
        }
      }

      default:
        throw new Error(`Herramienta desconocida: ${name}`)
    }
  } catch (err: unknown) {
    const message = err instanceof Error ? err.message : String(err)
    return {
      isError: true,
      content: [{ type: 'text', text: `Error ejecutando ${name}: ${message}` }]
    }
  }
})

// 6. Arrancar el servidor usando transporte stdio
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
  console.error('Supabase MCP Server corriendo en stdio...')
}

main().catch(console.error)

⚙️ 3. CONFIGURACIÓN EN CLAUDE DESKTOP Y ANTHROPIC CLI

Para registrar servidores MCP en tu entorno de desarrollo, edita el archivo de configuración global:

  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
json
{
  "mcpServers": {
    "supabase": {
      "command": "node",
      "args": ["C:/Users/usuario/projects/mcp-servers/supabase-server/dist/index.js"],
      "env": {
        "SUPABASE_URL": "https://xxx.supabase.co",
        "SUPABASE_SERVICE_ROLE_KEY": "sb-xxx-secret"
      }
    },
    "stripe": {
      "command": "npx",
      "args": ["-y", "@stripe/mcp-server-stripe"],
      "env": {
        "STRIPE_SECRET_KEY": "sk_test_xxx"
      }
    },
    "cloudflare": {
      "command": "node",
      "args": ["C:/Users/usuario/projects/mcp-servers/cloudflare-server/dist/index.js"],
      "env": {
        "CLOUDFLARE_API_TOKEN": "cf_token_xxx",
        "CLOUDFLARE_ACCOUNT_ID": "cf_account_xxx"
      }
    }
  }
}

📚 4. CATÁLOGO RECOMENDADO DE SERVIDORES MCP

Servidor MCPPropósitoPrioridadVariables requeridas
supabase-mcpConsultas SQL asistidas, esquemas de tablas, verificación de RLS🔴 CríticoSUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY
stripe-mcpConsultar clientes, eventos de pago, webhooks y productos🔴 CríticoSTRIPE_SECRET_KEY
resend-mcpProbar plantillas React Email y estado de entregabilidad🟡 ImportanteRESEND_API_KEY
cloudflare-mcpInspeccionar logs de Workers, namespaces KV y buckets R2🟡 ImportanteCLOUDFLARE_API_TOKEN, ACCOUNT_ID
github-mcpCrear PRs, revisar issues y consultar workflows de CI/CD🟢 RecomendadoGITHUB_TOKEN
playwright-mcpAutomatización E2E e inspección visual de UI en navegadores🟢 RecomendadoN/A

🛡️ 5. SEGURIDAD Y AUDITORÍA DE EJECUCIÓN MCP

Petición de la IA (Llamar a herramienta query_table)


Validación de Schema Zod (MCP-001)  ──→ [Si falla: Retorna Error al LLM sin ejecutar]


Verificación de Máximo de Resultados (limit <= 100)


Ejecución en Supabase con Service Role / RPC acotado


Respuesta Sanitizada (sin revelar tokens ni hashes de contraseñas)
  1. Respuestas Acotadas: Las herramientas que retornan listados deben truncar arrays grandes a un máximo de 100 objetos para no agotar la ventana de contexto de la IA.
  2. Audit Logging: El servidor MCP imprime en stderr (no en stdout, ya que stdout se usa para el transporte JSON-RPC) cada invocación con su timestamp y argumentos.

171 documentos indexados · generado desde INDEX.json