Servicio MCP que actua como proxy seguro para acceder a las bases de datos de Pampling y a integraciones API externas (Odoo, Shopify) desde Claude Code y aplicaciones externas. Las credenciales nunca salen del servidor.
| Campo | Valor |
|---|---|
| URL publica | https://globaldb.pampl.ing |
| MCP endpoint (publico) | https://globaldb.pampl.ing/mcp |
| MCP endpoint (red interna Docker) | http://pampling-global-database:8000/mcp |
| REST API proxy (publico) | https://globaldb.pampl.ing/api/proxy |
| REST API proxy (red interna) | http://pampling-global-database:8000/api/proxy |
| Repositorio | git@bitbucket.org:pampling/pampling-global-database.git |
| App UUID (Coolify) | q179bkab6m4lsz7ogg31uzn5 |
| DB UUID (Coolify) | cdb2q3yuzinpx46foncf7uhq |
| Base de datos | globaldb (usuario: globaldb_user) |
| Alias de red Docker | pampling-global-database |
El contenedor tiene configurado un alias estable de red Docker: pampling-global-database. Cualquier otra app en la red coolify puede resolver ese hostname al contenedor actual aunque Coolify lo recree.
Configurado en Coolify via custom_network_aliases=pampling-global-database en la app. Esto hace que la URL interna sea estable y no cambie entre deploys.
Desde una app consumidora desplegada en Coolify:
PAMPLING_DB_MCP_CONTAINER_URL=http://pampling-global-database:8000/mcp
PAMPLING_DB_MCP_AUTHORIZATION=Bearer <token>
Ventajas sobre usar el dominio publico:
Detalle del patron y checklist para integrarlo en apps nuevas: guias/consumir-mcp-desde-apps-coolify (sigue vigente sin cambios estructurales).
Verificacion del alias:
docker inspect $(docker ps --filter "name=q179bkab6m4ls" --format '{{.Names}}') \
--format '{{json .NetworkSettings.Networks}}' | jq .
En Aliases debe aparecer pampling-global-database.
@modelcontextprotocol/sdk) — Streamable HTTP transportmysql2), PostgreSQL (pg)Claude Code (MCP) --token--> MCP Server --credenciales--> BD MySQL/PG
Dashboard (REST) --token--> | Odoo (JSON-RPC)
Claude.ai (OAuth) --token--> | Shopify (GraphQL)
+-- REST API (paneles web)
+-- REST Proxy (apps externas)
+-- OAuth 2.0 (clientes web)
+-- PostgreSQL interna (config, permisos, logs)
+-- Paneles admin/usuario (HTML/CSS/JS)
+-- Worker local (analisis IA, fuera del servidor)
El sistema separa conceptualmente tres tipos de capacidad:
connections): MySQL, PostgreSQL, MariaDB. Se consultan con SQL (tool query) o buscando en su esquema (search_schema).integrations): Odoo (lectura y escritura) y Shopify (solo lectura). Se consultan con tools especificos por tipo.Todos comparten: auth, token, grupos, logs, monitor.
Claude y las aplicaciones nunca reciben credenciales. El flujo:
read o write)kind='app')| Rol | Panel web | MCP / API | Permisos |
|---|---|---|---|
| admin | Panel completo | Acceso a todo | CRUD conexiones, integraciones, usuarios, grupos, permisos, workers, milestones; logs y monitor |
| user | Sus BD/integraciones y su historial | Solo los destinos asignados | Segun el access_level de cada conexion/integracion (ver mas abajo) |
Los usuarios se auto-registran en https://globaldb.pampl.ing/ (pestana Registro). Restriccion: solo emails @pampling.com.
Tras el registro, el usuario esta activo pero sin permisos. Un admin debe asignarlo a un grupo o darle permisos individuales.
kind='app')Ademas de los usuarios humanos (kind='human', por defecto), existen cuentas de servicio:
responsible_user_id que apunta al humano responsable de esa cuenta. Se usa en el analisis IA: si la app aparece con problemas (errores, uso anomalo), el informe referencia al responsable.Utiles para dar acceso a un servicio automatizado (un cron, otra app) sin usar el token de una persona.
access_levelCada connection y cada integration tiene una columna access_level (read | write) que define su techo absoluto de capacidad:
access_level='read', nadie puede escribir en ella, sin importar que permisos tenga un usuario o grupo.access_level='write', los usuarios/grupos autorizados pueden usar los tools/endpoints de escritura.Las columnas
access_levelenpermissions,group_connections,integration_permissionsygroup_integrationsse mantienen en el esquema por compatibilidad, pero ya no se consultan para autorizar. La autorizacion efectiva mira unicamente elaccess_levelde la integracion/conexion.
Esto simplifica el modelo: un admin marca una integracion como write y todos sus usuarios/grupos autorizados heredan esa capacidad; si es read, ninguno escribe aunque su grant historico diga write.
access_level de cada destinoTambien se pueden dar permisos directos usuario-destino.
Si un usuario tiene acceso por grupo y directo al mismo destino, gana el acceso (la capacidad real la limita siempre el access_level de la conexion/integracion, no el grant).
Cada usuario anade esto a su configuracion de Claude Code:
{
"mcpServers": {
"pampling-db": {
"type": "http",
"url": "https://globaldb.pampl.ing/mcp",
"headers": {
"Authorization": "Bearer TOKEN_DEL_USUARIO"
}
}
}
}
El token se obtiene en https://globaldb.pampl.ing/user/.
Al conectar, el cliente recibe en la respuesta initialize un campo instructions con un mini-manual del servidor. Clientes modernos (Claude Code, Claude.ai, Codex) lo muestran automaticamente.
whoami (siempre disponible)Devuelve en markdown un resumen personalizado: usuario, rol, BDs SQL accesibles (marcadas con 📖 si tienen guia de uso), integraciones accesibles (con tipo + access_level), y una cheat-sheet dinamica de que tools puede usar segun lo que tenga asignado. Es el punto de partida recomendado en cualquier sesion nueva.
| Tool | Descripcion |
|---|---|
list_databases |
Lista las BDs accesibles del usuario |
describe_database |
Esquema de una BD: tablas, columnas, tipos |
get_database_guide |
Devuelve la guia de uso (markdown) de una BD, si la tiene configurada |
search_schema |
Busqueda de tablas/columnas por texto libre en el esquema de una BD |
query |
Ejecuta SQL (read-only por defecto) |
query_history |
Historial de queries del usuario |
| Tool | Descripcion |
|---|---|
odoo_list_integrations |
Lista las integraciones Odoo accesibles |
odoo_list_models |
Lista los modelos Odoo disponibles (res.partner, sale.order, ...) |
odoo_describe_model |
Campos de un modelo (via fields_get) |
odoo_search_read |
Consulta registros con domain + fields + limit + offset + order |
odoo_create |
Crea 1 registro. Devuelve id |
odoo_write |
Actualiza registros por ids. Devuelve true |
odoo_unlink |
Borra registros (max 100 ids por llamada) |
Limites de odoo_search_read: default limit 50, max 500.
Gating por
access_level:odoo_create,odoo_writeyodoo_unlinksolo se exponen si el usuario tiene al menos una integracion Odoo conaccess_level='write'. Si todas sus integraciones Odoo son de solo lectura, estos tools ni siquiera aparecen en su catalogo — evita que un agente crea que puede escribir cuando no puede.
| Tool | Descripcion |
|---|---|
shopify_list_integrations |
Lista las integraciones Shopify accesibles |
shopify_list_resources |
Lista los recursos/tipos disponibles en el catalogo Shopify |
shopify_describe_resource |
Describe un recurso (campos, tipo) |
shopify_query |
Consulta via GraphQL (solo lectura) |
Solo lectura en v1. Estos tools solo se exponen si el usuario tiene al menos una integracion Shopify accesible (registro condicional, igual que Odoo).
notifications/tools/list_changed)Cuando el admin cambia integraciones, permisos o grupos, el servidor emite la notificacion spec-compliant notifications/tools/list_changed a todas las sesiones MCP activas. Los clientes modernos refrescan su catalogo de tools automaticamente, sin reiniciar sesion.
Eventos que la disparan:
access_level de una integracionNo se dispara con cambios de conexiones SQL (no afectan al catalogo de tools, solo a los datos accesibles con query).
Ademas del Bearer token estatico, el servidor implementa OAuth 2.0 con PKCE (RFC 6749 + RFC 7591 Dynamic Client Registration + RFC 7636), pensado para que Claude.ai (version web) se autentique sin pegar un token manualmente.
| Endpoint | Descripcion |
|---|---|
GET /.well-known/oauth-authorization-server |
Metadata del servidor de autorizacion |
GET /.well-known/oauth-protected-resource (+ variante /mcp) |
Metadata del recurso protegido |
/oauth/register |
Dynamic Client Registration |
/oauth/authorize |
Autorizacion (PKCE) |
/oauth/token |
Intercambio de code por token |
/oauth/revoke |
Revocacion de token |
oauth_clients — clientes registrados dinamicamenteoauth_codes — authorization codes (de un solo uso, con expiracion corta)oauth_tokens — access/refresh tokens emitidosLos access tokens emitidos por OAuth llevan el prefijo mcp_at_ y se resuelven en el mismo resolveTokenUser que los tokens estaticos (api_token) — para el resto del sistema son indistinguibles una vez autenticado.
Aplicaciones externas pueden consultar y (para Odoo, si el access_level lo permite) escribir en BDs e integraciones via REST sin credenciales directas.
Base URL publica: https://globaldb.pampl.ing/api/proxy
Base URL red interna: http://pampling-global-database:8000/api/proxy
Auth: Authorization: Bearer TOKEN_DEL_USUARIO
| Metodo | Path | Descripcion |
|---|---|---|
GET |
/databases |
Lista BDs accesibles |
GET |
/databases/:nombre/schema |
Esquema de una BD |
POST |
/query |
Ejecuta SQL (body: {database, sql}) |
| Metodo | Path | Descripcion |
|---|---|---|
GET |
/integrations |
Lista integraciones accesibles |
GET |
/integrations/:name/models |
Lista modelos Odoo (opcional ?filter=sale.) |
GET |
/integrations/:name/models/:model/fields |
Campos de un modelo |
POST |
/integrations/:name/search |
search_read (body: {model, domain, fields, limit, offset, order}) |
POST |
/integrations/:name/create |
Crea registro (body: {model, values}) → {id} — requiere access_level=write |
POST |
/integrations/:name/write |
Actualiza por ids (body: {model, ids, values}, max 500 ids) → {ok} — requiere access_level=write |
POST |
/integrations/:name/unlink |
Borra por ids (body: {model, ids}, max 100 ids) → {ok} — requiere access_level=write |
Los tres endpoints de escritura devuelven 403 si la integracion no tiene access_level=write.
import requests
BASE = "https://globaldb.pampl.ing/api/proxy"
headers = {"Authorization": "Bearer TU_TOKEN"}
# SQL
dbs = requests.get(f"{BASE}/databases", headers=headers).json()
result = requests.post(f"{BASE}/query", headers=headers, json={
"database": "pampling-production",
"sql": "SELECT * FROM orders LIMIT 100"
}).json()
# Odoo (lectura)
integrations = requests.get(f"{BASE}/integrations", headers=headers).json()
partners = requests.post(f"{BASE}/integrations/pampling-odoo/search", headers=headers, json={
"model": "res.partner",
"domain": [["is_company", "=", True]],
"fields": ["name", "email"],
"limit": 10
}).json()
# Odoo (escritura, requiere access_level=write en la integracion)
created = requests.post(f"{BASE}/integrations/pampling-odoo/create", headers=headers, json={
"model": "res.partner",
"values": {"name": "Nuevo contacto", "email": "contacto@ejemplo.com"}
}).json()
| Metodo | Path | Descripcion |
|---|---|---|
POST |
/api/auth/login |
Login -> JWT |
POST |
/api/auth/register |
Registro (solo @pampling.com) -> JWT |
GET |
/api/catalog |
Catalogo publico de BDs, integraciones y grupos |
GET |
/api/health |
Health check |
| Metodo | Path | Descripcion |
|---|---|---|
GET/POST/PATCH/DELETE |
/api/admin/users/* |
CRUD usuarios (incluye kind y responsible_user_id) |
GET/POST/PATCH/DELETE |
/api/admin/connections/* |
CRUD conexiones BD (incluye access_level, summary, usage_guide) |
GET/POST/PATCH/DELETE |
/api/admin/integrations/* |
CRUD integraciones API (Odoo, Shopify), incluye access_level |
POST |
/api/admin/integrations/:id/test |
Probar conexion (version + uid, o equivalente segun tipo) |
GET/POST/DELETE |
/api/admin/permissions/* |
Permisos individuales BD |
GET/POST/PATCH/DELETE |
/api/admin/groups/* |
CRUD grupos |
POST/DELETE |
/api/admin/groups/:id/connections |
Conexiones del grupo |
POST/DELETE |
/api/admin/groups/:id/integrations |
Integraciones del grupo |
POST/DELETE |
/api/admin/groups/:id/users |
Usuarios del grupo |
GET |
/api/admin/logs |
Logs unificados (SQL + integraciones) con filtro ?source=sql\|integration\|all |
GET |
/api/admin/stats/* |
Metricas de monitoreo (agregadas SQL + integraciones) |
GET |
/api/admin/metrics/timeseries |
Series temporales — parametros metric, range, group_by (ver Monitoreo) |
GET/POST/DELETE |
/api/admin/analyses/* |
CRUD de analisis IA + POST /generate para encolar uno nuevo |
GET |
/api/admin/analyses/jobs/:id |
Estado de un job de analisis |
GET |
/api/admin/analyses/jobs |
Ultimos 20 jobs (debug) |
GET/POST/DELETE |
/api/admin/worker-tokens/* |
Gestion de tokens del worker de analisis IA |
GET/POST/PATCH/DELETE |
/api/admin/milestones/* |
CRUD de hitos (lineas verticales en graficas de Tendencias) |
GET |
/api/admin/errors |
Ultimos N logs de error capturados |
GET |
/api/admin/errors/stats |
Info de ficheros de log de error |
GET |
/api/admin/errors/stdout |
Tail del stdout completo (util tras un SIGKILL/OOM) |
Bearer gdw_...)| Metodo | Path | Descripcion |
|---|---|---|
POST |
/api/worker/analyses/poll |
El worker local hace polling aqui cada 5s buscando jobs pendientes |
POST |
/api/worker/analyses/:id/result |
El worker sube el resultado del analisis |
| Metodo | Path | Descripcion |
|---|---|---|
GET |
/api/user/profile/me |
Perfil |
GET |
/api/user/profile/me/databases |
BDs accesibles |
GET |
/api/user/profile/me/integrations |
Integraciones accesibles |
GET |
/api/user/profile/me/groups |
Grupos del usuario |
GET |
/api/user/history |
Historial de queries |
Desde 2026-07-08 el analisis IA ya no usa la API de Anthropic (creditos prepagados). Se genera con un worker local que ejecuta
claude -p, aprovechando la suscripcion Claude Code de la cuenta empresarial y evitando el cobro por tokens de API.
/admin/monitor.html → pestana Analisis IA.POST /api/admin/analyses/generate:
analysis_job (status pending) y devuelve {status:'queued', job_id}.C:\Users\User\globaldb-worker\), hace polling a POST /api/worker/analyses/poll cada 5s con Authorization: Bearer gdw_....claude -p --model claude-haiku-4-5-20251001 pasando el prompt por stdin.POST /api/worker/analyses/:id/result. El servidor parsea el JSON, enriquece problematic_users (cruzando con responsible_user_id si el usuario problematico es una cuenta kind='app') y crea la fila en daily_analyses.GET /api/admin/analyses/jobs/:id y muestra el informe cuando el job pasa a done.| Tabla | Descripcion |
|---|---|
worker_tokens |
Tokens del worker (prefijo gdw_, guardados hasheados). Gestion en /admin/users.html → pestana Workers |
analysis_jobs |
Cola de jobs con status pending / claimed / done / error. Contiene el prompt y los datos crudos recolectados |
daily_analyses |
Analisis producidos (tabla ya existente, ampliada con la columna problematic_users_json) |
C:\Users\User\globaldb-worker\ — no forma parte del repo pampling-global-database.globaldb-worker.mjs, run.cmd, start-worker.vbs, README.md.start-worker.vbs se copia a %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\.GLOBALDB_WORKER_TOKEN (configurada con setx).worker.log en la propia carpeta del worker.Tabla milestones con CRUD en /api/admin/milestones. Se pintan como lineas verticales en las graficas de la pestana Tendencias del monitor, para poder correlacionar visualmente un cambio de comportamiento del sistema con un evento conocido (deploy, incidencia, cambio de configuracion).
| Tabla | Descripcion |
|---|---|
users |
Usuarios. Incluye kind (human/app) y responsible_user_id |
connections |
Conexiones BD (password_encrypted). Incluye access_level, summary, usage_guide |
permissions |
Permisos directos usuario-conexion |
groups |
Grupos de permisos |
group_connections |
Conexiones dentro de un grupo |
user_groups |
Usuarios en un grupo |
query_logs |
Registro de queries SQL |
| Tabla | Descripcion |
|---|---|
integrations |
Integraciones (Odoo, Shopify, ...), con config JSONB cifrado y columna access_level |
integration_permissions |
Permisos directos usuario-integracion |
group_integrations |
Integraciones dentro de un grupo |
integration_logs |
Registro de llamadas a integraciones |
| Tabla | Descripcion |
|---|---|
oauth_clients |
Clientes registrados via Dynamic Client Registration |
oauth_codes |
Authorization codes (un solo uso) |
oauth_tokens |
Access/refresh tokens emitidos (prefijo mcp_at_) |
| Tabla | Descripcion |
|---|---|
daily_analyses |
Analisis IA generados. Incluye problematic_users_json |
analysis_jobs |
Cola de jobs para el worker local |
worker_tokens |
Tokens del worker (prefijo gdw_) |
milestones |
Hitos temporales para las graficas de Tendencias |
Todos los secretos (passwords SQL, api_key de Odoo/Shopify, etc.) se almacenan cifrados con AES-256-GCM usando ENCRYPTION_KEY. Formato iv:tag:ciphertext en base64.
| Panel | URL | Descripcion |
|---|---|---|
| Login / Registro | / |
Acceso y registro @pampling.com |
| Catalogo publico | /catalog |
BDs e integraciones disponibles (sin auth) |
| Admin - Conexiones e integraciones | /admin/ |
Gestionar conexiones BD SQL e integraciones API (Odoo, Shopify) combinadas |
| Admin - Usuarios | /admin/users.html |
Usuarios, sus grupos y pestana Workers (tokens del worker de analisis IA) |
| Admin - Topologia | /admin/topology.html |
Grafo interactivo de usuarios / grupos / conexiones / integraciones |
| Admin - Explorador | /admin/explorer.html |
Explorador de esquemas de las BDs accesibles |
| Admin - Monitor | /admin/monitor.html |
Dashboard con 4 pestanas: Monitor, Logs, Analisis IA, Tendencias |
| Usuario - Panel | /user/ |
BDs, integraciones, grupos, token, config MCP |
| Usuario - Historial | /user/history.html |
Historial |
Correccion:
/admin/integrations.html,/admin/groups.htmly/admin/logs.htmlcomo paginas separadas no existen. Todo se gestiona desde/admin/(conexiones + integraciones combinadas) y/admin/users.html(usuarios + grupos + workers); logs y monitor viven dentro de las pestanas de/admin/monitor.html.
El panel /admin/monitor.html tiene 4 pestanas:
Metricas unificadas de BDs SQL e integraciones API:
Colores: verde < 100ms, amarillo < 500ms, rojo > 500ms. Auto-refresh cada 30s (con boton para pausar).
Logs unificados de SQL e integraciones, con filtros.
Informes generados por el worker local (ver seccion dedicada) + listado de usuarios problematicos detectados, con boton Generar ahora.
Series temporales con selector de:
group_by): hour / day / weekLos milestones se pintan como lineas verticales sobre estas graficas.
src/
index.ts -- Entry point: Express + MCP
config.ts -- Variables de entorno
db/
connection.ts -- Pool PostgreSQL interna
migrate.ts -- Schema + seed admin
models/ -- user, connection, integration, permission, group,
*-connection/integration, *-log, analysis-job,
worker-token, milestone, oauth-client, oauth-code, oauth-token
auth/
middleware.ts -- JWT (web) + token (MCP/proxy) + workerAuth
password.ts, token.ts
analysis/
collector.ts -- recolecta datos para el analisis IA
generator.ts -- prepara el prompt y procesa el resultado del worker
mcp/
server.ts -- MCP server Streamable HTTP
tools/ -- list-databases, describe-database, get-database-guide,
search-schema, query, query-history, whoami,
odoo-*, odoo-create, odoo-write, odoo-unlink,
shopify-list-integrations, shopify-list-resources,
shopify-describe-resource, shopify-query
api/
auth.routes.ts
proxy.routes.ts -- REST proxy (SQL + integraciones, lectura y escritura)
worker.routes.ts -- endpoints del worker (poll + result)
admin/ -- users, connections, integrations, permissions, groups,
logs, stats, analyses, worker-tokens, milestones,
metrics, errors
user/ -- profile, history
connectors/
factory.ts -- SQL
mysql.ts, postgresql.ts
odoo.ts -- JSON-RPC 2.0
shopify.ts -- GraphQL client
schema-cache.ts -- cache del esquema (usado por search_schema)
utils/
encryption.ts -- AES-256-GCM
query-validator.ts -- Valida SELECT-only
error-logger.ts -- captura console.log/warn/error a fichero
self-monitor.ts -- heartbeat + DNS sentinel + captura de signals
frontend/ -- HTML/CSS/JS paneles web
| Variable | Descripcion |
|---|---|
DATABASE_URL |
Connection string PostgreSQL interna |
JWT_SECRET |
Secret para JWT |
ENCRYPTION_KEY |
Clave AES-256 (min 32 chars) |
ADMIN_PASSWORD |
Password del admin inicial |
PORT |
Default 8000 |
NODE_ENV |
production o development |
ANTHROPIC_API_KEYya no es necesaria desde que el analisis IA se genera con el worker local (suscripcion Claude Code) en lugar de la API de Anthropic. El config todavia la lee si esta presente, pero puede quitarse de Coolify sin ningun efecto.
SIGTERM/SIGINT con shutdown limpio: cierra el servidor HTTP con un timeout de seguridad de 5s.restart_policy=always.SIGTERM externo (por ejemplo, el deploy de otra app en el mismo host, o el kernel) hace que el contenedor salga limpio y Coolify lo reinicie automaticamente en menos de 10s.SIGTERM misterioso e intermitente tras deploys de otras apps. Mitigacion aplicada el 2026-06-29 (graceful shutdown + restart always); la causa raiz sigue pendiente de auditoria.startSelfMonitor() ejecuta un heartbeat periodico, un DNS sentinel (comprueba resolucion de api.anthropic.com y bitbucket.org) y captura signals del proceso.console.log/warn/error se captura a fichero via utils/error-logger.ts, consultable en /api/admin/errors/* (ver tabla de endpoints admin).Como el analisis IA ya no depende de la API de Anthropic, el DNS sentinel sobre
api.anthropic.compodria eliminarse en el futuro — no es urgente.
El middleware express.json tiene limit: '25mb' (el default de Express es 100KB). Aplica a /api/proxy/* y /mcp. Este limite permite subir imagenes en base64 directamente a Odoo (por ejemplo product.template.image_1920, res.partner.image_1920).
git push origin main
curl -s -X POST -H 'Authorization: Bearer <COOLIFY_TOKEN>' \
https://coolify.pampl.ing/api/v1/deploy?uuid=q179bkab6m4lsz7ogg31uzn5
La URL con la IP interna (
http://192.168.1.10:8000/...) sigue funcionando, pero el dominiocoolify.pampl.ing(TLS publico) es mas portable.
/user/)"type": "http" (no streamableHttp, sse ni stdio)execution que anade el SDK 1.29+ (fix aplicado)Solo SELECT, SHOW, DESCRIBE, EXPLAIN. No se permiten INSERT/UPDATE/DELETE ni multiples sentencias separadas por ;.
coolifyENCRYPTION_KEY, los passwords cifrados antiguos fallaranEl proxy usa el mismo token que el MCP. Verificar Authorization: Bearer TOKEN.
La app consumidora no esta en la misma red Docker coolify, o el contenedor pampling-global-database se recreo justo antes. Verificar con docker inspect que el alias esta activo, y que la app consumidora use la misma red.
pending indefinidamente)Verificar que el worker local esta corriendo en la PC del admin (proceso globaldb-worker.mjs activo) y que su token (GLOBALDB_WORKER_TOKEN) no ha sido revocado desde /admin/users.html → pestana Workers.
Ultima actualizacion: 2026-07-09.