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
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:
| Herramienta | Disponible |
|---|---|
query, list_schemas, list_tables, describe_table | Siempre |
execute | Siempre (rechaza escrituras a menos que PG_ALLOW_WRITE=true) |
connect_db | Solo 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
| Variable | Predeterminado | Descripció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_PORT | 5432 | Puerto 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_WRITE | false | Cuando 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_CONNECT | false | Registra la herramienta connect_db (cambio de credenciales en tiempo de ejecución) |
PG_MAX_RESULT_BYTES | 32768 | Presupuesto 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_TIMEOUT | 30000 | Tiempo de espera de declaración en milisegundos, aplicado a cada sesión |
PG_CONNECT_TIMEOUT | 10000 | Tiempo 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
pgnunca se filtra más allá - Soporte
DATABASE_URLcon 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
truncateden 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:
-
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
SELECTexplícitamente en lugar depg_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. -
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 ONLYrevertida, 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"
}
}
}
}
| Variable | Predeterminado | Descripció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_PORT | 22 | Puerto 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_INTERVAL | 15000 | Intervalo 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_dbse 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_FINGERPRINTfijada: 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_hostsexistente, si ya accede al host a través dessh: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).
- en el propio bastión, o de su administrador:
- 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, yrejectUnauthorizedestá fijado para que unNODE_TLS_REJECT_UNAUTHORIZED=0heredado no pueda desactivarlo. ssh2es una dependencia opcional, cargada solo cuandoPG_SSH_HOSTestá configurado, por lo que una conexión directa nunca la inicializa. npm instala dependencias opcionales por defecto; ejecutenpm install --omit=optionalpara 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ódigo | significado | primera cosa a verificar |
|---|---|---|
28P01 | autenticación fallida | PG_USER / PG_PASSWORD |
3D000 | la base de datos no existe | PG_DATABASE |
42P01 | relación no encontrada | llame a list_tables |
42703 | columna no encontrada | llame a describe_table |
57014 | la 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 |
25006 | la transacción es de solo lectura | la 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 / ENOTFOUND | no se puede alcanzar o resolver el host de la base de datos | PG_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ódigo | significado | primera cosa a verificar |
|---|---|---|
SSH_CONFIG_INVALID | configuración SSH inválida, incl. un PG_SSH_FINGERPRINT malformado | los valores de PG_SSH_* |
SSH_KEY_INVALID | clave 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_FAILED | el bastión es inalcanzable, o la configuración SSH falló por una razón no clasificada | PG_SSH_HOST, PG_SSH_PORT, alcanzabilidad |
SSH_TIMEOUT | el bastión no respondió a tiempo | red/cortafuegos, PG_CONNECT_TIMEOUT |
SSH_AUTH_FAILED | el bastión rechazó la autenticación | PG_SSH_USER y la clave/agente/contraseña en uso |
SSH_HOST_KEY_MISMATCH | la clave del host no coincide con PG_SSH_FINGERPRINT (valor obsoleto o MITM) | vuelva a obtener la huella digital |
SSH_FORWARD_FAILED | el túnel está activo, pero el bastión no pudo alcanzar la base de datos | el host y el puerto de la BD vistos desde el bastión |
SSH_CONNECTION_LOST | un túnel establecido se cayó a mitad de sesión | transitorio; 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:
- Solo lectura por defecto. La herramienta
executesiempre es visible pero rechaza escrituras (con un error que nombra la bandera) a menos quePG_ALLOW_WRITE=true, y cada lectura se ejecuta dentro de una transacciónREAD ONLYimpuesta 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. connect_dbestá deshabilitado por defecto. El cambio de conexión en tiempo de ejecución (pasar credenciales a través de argumentos de herramientas) requierePG_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