MCP PostgreSQL Server

Un servidor que permite a los modelos de IA interactuar con bases de datos PostgreSQL a través de una interfaz estandarizada.

Documentación

Servidor MCP PostgreSQL

npm version CI

Un servidor de Protocolo de Contexto de Modelo (MCP) para PostgreSQL: bases de datos locales, Docker, RDS, Neon y Supabase.

El servidor es pequeño y auditable, con cuatro dependencias de ejecución: el SDK de MCP, pg, pg-connection-string y zod (además de ssh2, una dependencia opcional utilizada solo para túneles SSH).

Requiere Node.js 20 o superior.

Inicio rápido

La forma preferida de configurar el servidor es un único DATABASE_URL:

{
  "mcpServers": {
    "postgres": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "mcp-postgres-server"],
      "env": {
        "DATABASE_URL": "postgres://user:password@localhost:5432/mydb",
        "PG_ALLOW_WRITE": "false"
      }
    }
  }
}

Con PG_ALLOW_WRITE establecido en "false", el servidor tiene acceso de solo lectura a la base de datos. Este es el valor predeterminado; establézcalo en "true" solo si el modelo debe escribir.

El mismo JSON funciona en cualquier cliente MCP que hable stdio: VS Code, Cursor, Claude Code, Codex, Windsurf.

Alternativamente, establezca las variables PG_* individuales; se utilizan cuando DATABASE_URL no está establecido:

{
  "mcpServers": {
    "postgres": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "mcp-postgres-server"],
      "env": {
        "PG_HOST": "your_host",
        "PG_PORT": "5432",
        "PG_USER": "your_user",
        "PG_PASSWORD": "your_password",
        "PG_DATABASE": "your_database",
        "PG_ALLOW_WRITE": "false"
      }
    }
  }
}

Instalación manual

npm install mcp-postgres-server

O ejecute directamente con:

npx mcp-postgres-server

Conéctese a su base de datos

Postgres local:

DATABASE_URL=postgres://mcp_readonly:secret@localhost:5432/mydb

Postgres en Docker: si la base de datos se ejecuta en un contenedor con un puerto publicado, conéctese a localhost:<published-port> como de costumbre. Si el servidor MCP en sí se ejecuta dentro de un contenedor y la base de datos se ejecuta en su máquina host, use host.docker.internal en lugar de localhost:

DATABASE_URL=postgres://mcp_readonly:secret@host.docker.internal:5432/mydb

Amazon RDS:

DATABASE_URL=postgres://mcp_readonly:secret@mydb.xxxxxx.us-east-1.rds.amazonaws.com:5432/mydb?sslmode=require

Neon:

DATABASE_URL=postgres://mcp_readonly:secret@ep-xxx-xxx.us-east-2.aws.neon.tech/mydb?sslmode=require

Supabase:

DATABASE_URL=postgres://postgres.xxxxxxxx:secret@aws-0-us-east-1.pooler.supabase.com:5432/postgres?sslmode=require

Herramientas

La disponibilidad de herramientas depende de la configuración:

HerramientaDisponible
query, list_schemas, list_tables, describe_tableSiempre
executeSiempre (rechaza escrituras a menos que PG_ALLOW_WRITE=true)
connect_dbSolo cuando PG_ENABLE_RUNTIME_CONNECT=true

1. query

Ejecuta una declaración SQL de solo lectura. Acepta SELECT, WITH ... SELECT, EXPLAIN y SHOW. Una declaración por llamada: la entrada de múltiples declaraciones es rechazada por el protocolo de consulta extendida. En modo de solo lectura (el predeterminado), la declaración se ejecuta como BEGIN READ ONLY, la consulta y ROLLBACK: tres comandos, aproximadamente dos viajes de ida y vuelta de red con canalización, por lo que la base de datos misma rechaza cualquier escritura. Con PG_ALLOW_WRITE=true, la declaración se envía directamente, sin ese envoltorio, por lo que una escritura ejecutada a través de query se ejecutaría; use execute para escrituras. Admite parámetros de declaraciones preparadas estilo PostgreSQL $1, $2; los valores se vinculan mediante el controlador y nunca se insertan en el texto SQL.

use_mcp_tool({
  server_name: "postgres",
  tool_name: "query",
  arguments: {
    sql: "SELECT * FROM users WHERE id = $1",
    params: [1]
  }
});

Devuelve JSON compacto: {"rows": [...], "rowCount": n, "returnedRows": n, "truncated": false}. Cuando las filas serializadas exceden PG_MAX_RESULT_BYTES, solo se devuelven las filas que caben (returnedRows < rowCount), truncated es true, y una sugerencia indica agregar LIMIT/WHERE o seleccionar menos columnas.

2. list_schemas

Lista todos los esquemas en la base de datos conectada.

use_mcp_tool({
  server_name: "postgres",
  tool_name: "list_schemas",
  arguments: {}
});

3. list_tables

Lista las tablas en la base de datos conectada. Acepta un parámetro de esquema opcional (el valor predeterminado es 'public').

// List tables in the 'public' schema (default)
use_mcp_tool({
  server_name: "postgres",
  tool_name: "list_tables",
  arguments: {}
});

// List tables in a specific schema
use_mcp_tool({
  server_name: "postgres",
  tool_name: "list_tables",
  arguments: {
    schema: "my_schema"
  }
});

4. describe_table

Obtiene la estructura de una tabla específica (columnas, tipos, nulabilidad, valores predeterminados, claves primarias). Acepta un parámetro de esquema opcional (el valor predeterminado es 'public').

use_mcp_tool({
  server_name: "postgres",
  tool_name: "describe_table",
  arguments: {
    table: "users",
    schema: "my_schema"  // optional
  }
});

5. execute - requiere PG_ALLOW_WRITE=true

Ejecuta una declaración INSERT, UPDATE, DELETE o DDL. Siempre está registrada, pero en modo de solo lectura (el predeterminado) se niega con un error que nombra PG_ALLOW_WRITE y no cambia nada: la declaración nunca llega a la base de datos. Con PG_ALLOW_WRITE=true se ejecuta: el mismo manejo de parámetros $1, $2 que query, una declaración completa por llamada, y el rol conector gobierna lo que puede hacer. Devuelve {"rowCount": n, "command": "INSERT"}.

use_mcp_tool({
  server_name: "postgres",
  tool_name: "execute",
  arguments: {
    sql: "INSERT INTO users (name, email) VALUES ($1, $2)",
    params: ["John Doe", "john@example.com"]
  }
});

6. connect_db - requiere PG_ENABLE_RUNTIME_CONNECT=true

Conecta a una base de datos PostgreSQL diferente en tiempo de ejecución usando las credenciales proporcionadas. No está registrada por defecto: prefiera configurar las credenciales a través del entorno para que nunca pasen por argumentos visibles al modelo. Los límites de sesión (statement_timeout, idle_in_transaction_session_timeout) se vuelven a aplicar después de cada reconexión; las lecturas de solo lectura aplican el modo de solo lectura en su propia transacción BEGIN READ ONLY.

use_mcp_tool({
  server_name: "postgres",
  tool_name: "connect_db",
  arguments: {
    host: "localhost",
    port: 5432,
    user: "your_user",
    password: "your_password",
    database: "your_database"
  }
});

Referencia de configuración

VariablePredeterminadoDescripción
DATABASE_URL-Cadena de conexión completa (preferida). Admite ?sslmode= en la URL.
PG_HOST-Host de la base de datos (respaldo cuando DATABASE_URL no está establecido)
PG_PORT5432Puerto de la base de datos
PG_USER-Usuario de la base de datos
PG_PASSWORD-Contraseña de la base de datos
PG_DATABASE-Nombre de la base de datos
PG_ALLOW_WRITEfalseCuando true, execute realiza escrituras y las lecturas se envían directamente. Desactivado (predeterminado) es de solo lectura: execute rechaza escrituras y cada lectura se ejecuta en una transacción READ ONLY
PG_SSLMODE-disable | allow | prefer | require | verify-ca | verify-full. require/allow/prefer cifran sin verificar el certificado; verify-ca/verify-full verifican (proporcione una CA a través de PG_SSL_CA). Los valores no reconocidos fallan al inicio. Limitación: a diferencia de libpq, allow/prefer no recurren a texto plano (node-postgres no tiene SSL oportunista), por lo que un servidor sin TLS necesita disable.
PG_SSL_CA-Ruta a un archivo de certificado de CA. Establecerlo por sí solo implica verify-full
PG_ENABLE_RUNTIME_CONNECTfalseRegistra la herramienta connect_db (cambio de credenciales en tiempo de ejecución)
PG_MAX_RESULT_BYTES32768Presupuesto de bytes para un resultado query enviado al modelo. Se conservan filas completas mientras quepan; sobre el presupuesto, returnedRows < rowCount y truncated: true (si ni siquiera la primera fila cabe, returnedRows es 0 con una sugerencia). ~32 KiB ≈ 8k tokens; redúzcalo para clientes estrictos, auméntelo si su cliente permite más.
PG_STATEMENT_TIMEOUT30000Tiempo de espera de declaración en milisegundos, aplicado a cada sesión
PG_CONNECT_TIMEOUT10000Tiempo de espera en milisegundos para un solo intento de conexión (auméntelo para enlaces lentos o túneles SSH)

Para alcanzar una base de datos solo accesible a través de un bastión, consulte Túneles SSH (agrega variables PG_SSH_*).

Características

  • Solo lectura por defecto; las escrituras son una opción explícita (PG_ALLOW_WRITE=true)
  • Solo lectura aplicado por el motor (BEGIN READ ONLY), nunca por análisis SQL del lado del cliente
  • Acceso a datos detrás de una pequeña interfaz tipada; el controlador pg nunca se filtra más allá
  • Soporte DATABASE_URL con SSL (sslmode=disable|allow|prefer|require|verify-ca|verify-full, CA personalizada)
  • Parámetros de declaraciones preparadas: marcadores de posición estilo $1, vinculados por el controlador
  • Límite de tamaño de resultado (presupuesto de bytes) con una bandera explícita truncated en lugar de inundar el contexto del modelo
  • Tiempo de espera de declaración de sesión más un plazo del cliente; los agrupadores de transacciones pueden no preservar la configuración de sesión
  • Errores devueltos como resultados de herramientas legibles con sugerencias basadas en SQLSTATE, para que el modelo pueda autocorregirse
  • Sobrevive a conexiones caídas: se reconecta de forma diferida en lugar de fallar
  • Túneles SSH opcionales (PG_SSH_*) con verificación obligatoria de clave de host, cargados solo cuando se configuran
  • Anotaciones de herramientas MCP (sugerencias de solo lectura / destructivas) según la especificación 2025-11-25
  • Soporte multi-esquema para operaciones de base de datos

Seguridad

Los detalles completos, incluido el modelo de amenazas y el proceso de divulgación, están en SECURITY.md. La versión corta:

  1. Un rol de base de datos con privilegios mínimos es el límite real. El MCP funciona con credenciales existentes; no se requiere crear o cambiar roles. Un rol dedicado es lo que realmente garantiza que las escrituras sean imposibles. En PostgreSQL 14+, el siguiente es un punto de partida:

    CREATE ROLE mcp_readonly LOGIN PASSWORD 'change-me';
    GRANT CONNECT ON DATABASE your_database TO mcp_readonly;
    GRANT pg_read_all_data TO mcp_readonly;                          -- adds read privileges
    ALTER ROLE mcp_readonly SET default_transaction_read_only = on;  -- read-only by default
    

    (En PostgreSQL 13 o anterior, otorgue SELECT explícitamente en lugar de pg_read_all_data: consulte SECURITY.md). El servidor advierte en stderr si se conecta como superusuario. Las concesiones de lectura no revocan privilegios existentes, y los valores predeterminados permanecen mutables; las funciones disponibles, la propiedad y los privilegios heredados también importan.

  2. El motor aplica el modo de solo lectura. No hay análisis SQL del lado del cliente. En modo de solo lectura, cada lectura se ejecuta en una transacción BEGIN READ ONLY revertida, por lo que PostgreSQL mismo (que solo él sabe qué hace una función, vista o regla) rechaza cualquier escritura con SQLSTATE 25006 y revierte cualquier cambio de sesión que la declaración haya realizado. El protocolo extendido rechaza cadenas de múltiples comandos.

Marco honesto: la transacción de solo lectura es defensa en profundidad sobre el rol, no un reemplazo. El modo de solo lectura detiene a un modelo confundido o inyectado por indicaciones de escribir en su base de datos; no detiene la inyección de indicaciones transportada en los datos de fila que devuelve una consulta. No apunte este servidor a producción: use una réplica, una instantánea o un rol con alcance estricto. Consulte SECURITY.md.

Túneles SSH

Establezca PG_SSH_HOST (además de autenticación y verificación de clave de host) para alcanzar una base de datos que solo es accesible a través de un bastión (un host de salto SSH). La cadena de conexión / los campos PG_* describen entonces la base de datos vista desde el bastión:

{
  "mcpServers": {
    "postgres": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "mcp-postgres-server"],
      "env": {
        "DATABASE_URL": "postgres://mcp_readonly:secret@db.internal:5432/mydb?sslmode=verify-full",
        "PG_SSH_HOST": "bastion.example.com",
        "PG_SSH_USER": "jump",
        "PG_SSH_PRIVATE_KEY": "/home/me/.ssh/id_ed25519",
        "PG_SSH_FINGERPRINT": "SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}
VariablePredeterminadoDescripción
PG_SSH_HOST-Host bastión SSH. Establecerlo habilita el túnel: el servidor alcanza la base de datos solo a través de un túnel SSH a este host (ver más abajo). Característica opcional; necesita la dependencia opcional ssh2.
PG_SSH_PORT22Puerto del bastión SSH
PG_SSH_USER-Nombre de usuario SSH
PG_SSH_PRIVATE_KEY-Ruta a un archivo de clave privada. Si no se establece, la autenticación recurre como ssh: un agente en ejecución (SSH_AUTH_SOCK), luego una clave predeterminada (~/.ssh/id_ed25519, id_rsa, id_ecdsa)
PG_SSH_PASSPHRASE-Frase de contraseña para la clave privada, si está cifrada
PG_SSH_AGENT-true para usar el agente ambiental (SSH_AUTH_SOCK), o una ruta de socket explícita / tubería con nombre de Windows (\\.\pipe\openssh-ssh-agent)
PG_SSH_PASSWORD-Contraseña de inicio de sesión SSH. Opt-in; una clave o agente tiene prioridad. Prefiera claves: un bastión a menudo deshabilita la autenticación por contraseña.
PG_SSH_FINGERPRINT-Huella digital de clave de host fijada (SHA256:...). La verificación de clave de host es obligatoria y se establece solo de esta manera: sin ella, el túnel se niega a conectarse (falla cerrada). Obténgala con ssh-keygen -lF host (lee su known_hosts) o ssh-keyscan host | ssh-keygen -lf - (consulte la nota de confianza a continuación)
PG_SSH_KEEPALIVE_INTERVAL15000Intervalo de keepalive SSH en ms; el túnel se cae después de 3 keepalives sin respuesta, y la siguiente llamada se reconecta
  • SSH solo cambia el transporte. La aplicación de solo lectura, el límite de tamaño de resultados, los tiempos de espera y connect_db se comportan exactamente igual que en una conexión directa, y no se envía SQL adicional por consulta.
  • La verificación de la clave del host es obligatoria mediante una PG_SSH_FINGERPRINT fijada: el túnel no se conectará sin ella, por lo que se rechaza un bastión intermediario. Obtenga la huella digital a través de un canal de confianza, primero el más confiable:
    • en el propio bastión, o de su administrador: ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub (sin red involucrada);
    • desde su ~/.ssh/known_hosts existente, si ya accede al host a través de ssh: ssh-keygen -lF bastion.example.com;
    • obtenida del host: ssh-keyscan bastion.example.com | ssh-keygen -lf - (confíe en esto solo cuando se ejecute desde una posición de red que usted confíe: acepta lo que el host devuelva).
  • TLS valida el nombre de host real de la base de datos. Con verify-full, el certificado se verifica contra el nombre de host propio de la base de datos (por ejemplo, db.internal), no el bucle local al que el túnel se enlaza, y rejectUnauthorized está fijado para que un NODE_TLS_REJECT_UNAUTHORIZED=0 heredado no pueda desactivarlo.
  • ssh2 es una dependencia opcional, cargada solo cuando PG_SSH_HOST está configurado, por lo que una conexión directa nunca la inicializa. npm instala dependencias opcionales por defecto; ejecute npm install --omit=optional para omitirla por completo (una conexión directa no la necesita).

Una conexión tunelizada que falla reporta un código SSH_* estable: consulte Manejo de errores.

Manejo de errores

Los fallos de SQL y de conexión se devuelven como resultados de herramientas (isError: true) con un mensaje, el código SQLSTATE y una pista. Se usa la pista del servidor de PostgreSQL cuando está presente; de lo contrario, se aplican estos respaldos:

códigosignificadoprimera cosa a verificar
28P01autenticación fallidaPG_USER / PG_PASSWORD
3D000la base de datos no existePG_DATABASE
42P01relación no encontradallame a list_tables
42703columna no encontradallame a describe_table
57014la consulta fue cancelada (un tiempo de espera o una solicitud de cancelación)si hay tiempo de espera, agregue un LIMIT / simplifíquela, o aumente PG_STATEMENT_TIMEOUT
25006la transacción es de solo lecturala fuente puede ser un rol de solo lectura, una réplica, un valor predeterminado del servidor o (para query) el envoltorio de solo lectura; las escrituras de execute necesitan PG_ALLOW_WRITE=true
ECONNREFUSED / ENOTFOUNDno se puede alcanzar o resolver el host de la base de datosPG_HOST / PG_PORT / DATABASE_URL

A través de un túnel SSH, un fallo lleva un code estable (y, donde la causa es determinada, una pista que nombra el ajuste a corregir), por lo que la fase fallida es inequívoca:

códigosignificadoprimera cosa a verificar
SSH_CONFIG_INVALIDconfiguración SSH inválida, incl. un PG_SSH_FINGERPRINT malformadolos valores de PG_SSH_*
SSH_KEY_INVALIDclave ilegible, no analizable, una clave pública, o cifrada sin la frase de contraseña correcta (una clave cifrada con el PG_SSH_PASSPHRASE correcto funciona)PG_SSH_PRIVATE_KEY, PG_SSH_PASSPHRASE
SSH_CONNECT_FAILEDel bastión es inalcanzable, o la configuración SSH falló por una razón no clasificadaPG_SSH_HOST, PG_SSH_PORT, alcanzabilidad
SSH_TIMEOUTel bastión no respondió a tiempored/cortafuegos, PG_CONNECT_TIMEOUT
SSH_AUTH_FAILEDel bastión rechazó la autenticaciónPG_SSH_USER y la clave/agente/contraseña en uso
SSH_HOST_KEY_MISMATCHla clave del host no coincide con PG_SSH_FINGERPRINT (valor obsoleto o MITM)vuelva a obtener la huella digital
SSH_FORWARD_FAILEDel túnel está activo, pero el bastión no pudo alcanzar la base de datosel host y el puerto de la BD vistos desde el bastión
SSH_CONNECTION_LOSTun túnel establecido se cayó a mitad de sesióntransitorio; la siguiente llamada se reconecta

Un error genuino de PostgreSQL a través de un túnel saludable mantiene su propio código (por ejemplo, 28P01 para credenciales de base de datos incorrectas), no un código SSH.

Migración desde 0.1.x

No es necesario para instalaciones nuevas. Dos cambios de comportamiento desde 0.1.x:

  1. Solo lectura por defecto. La herramienta execute siempre es visible pero rechaza escrituras (con un error que nombra la bandera) a menos que PG_ALLOW_WRITE=true, y cada lectura se ejecuta dentro de una transacción READ ONLY impuesta por el motor. Si su flujo de trabajo escribe en la base de datos, configure "PG_ALLOW_WRITE": "true" para restaurar el comportamiento de 0.1.x.
  2. connect_db está deshabilitado por defecto. El cambio de conexión en tiempo de ejecución (pasar credenciales a través de argumentos de herramientas) requiere PG_ENABLE_RUNTIME_CONNECT=true; de lo contrario, los detalles de conexión provienen solo del entorno.

Los nombres de herramientas, nombres de parámetros y variables PG_* no cambian. Los payloads de resultados ahora son JSON compacto estructurado para cada herramienta (por ejemplo, query devuelve {rows, rowCount, returnedRows, truncated} en lugar de una matriz de filas simple): consulte CHANGELOG.md para las formas exactas antes de actualizar cualquier cosa que analice la salida de herramientas.

Licencia

MIT