MCP PostgreSQL Server
Um servidor que permite que modelos de IA interajam com bancos de dados PostgreSQL por meio de uma interface padronizada.
Documentação
MCP PostgreSQL Server
Um servidor Model Context Protocol (MCP) para PostgreSQL: bancos de dados locais, Docker, RDS, Neon e Supabase.
O servidor é pequeno e auditável, com quatro dependências de runtime: o SDK MCP,
pg, pg-connection-string e zod (além de ssh2, uma dependência opcional usada
apenas para tunelamento SSH).
Requer Node.js 20 ou mais recente.
Início rápido
A forma preferida de configurar o servidor é um ú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"
}
}
}
}
Com PG_ALLOW_WRITE definido como "false", o servidor tem acesso somente leitura ao
banco de dados. Este é o padrão; defina-o como "true" apenas se o modelo precisar escrever.
O mesmo JSON funciona em qualquer cliente MCP que fale stdio: VS Code, Cursor, Claude Code, Codex, Windsurf.
Alternativamente, defina as variáveis PG_* individuais; elas são usadas quando
DATABASE_URL não está definido:
{
"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"
}
}
}
}
Instalação Manual
npm install mcp-postgres-server
Ou execute diretamente com:
npx mcp-postgres-server
Conecte-se ao seu banco de dados
Postgres local:
DATABASE_URL=postgres://mcp_readonly:secret@localhost:5432/mydb
Postgres no Docker: se o banco de dados estiver em um contêiner com uma porta
publicada, conecte-se a localhost:<published-port> normalmente. Se o próprio servidor MCP
estiver em um contêiner e o banco de dados estiver na sua máquina host,
use host.docker.internal em vez 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
Ferramentas
A disponibilidade das ferramentas depende da configuração:
| Ferramenta | Disponível |
|---|---|
query, list_schemas, list_tables, describe_table | Sempre |
execute | Sempre (recusa gravações a menos que PG_ALLOW_WRITE=true) |
connect_db | Somente quando PG_ENABLE_RUNTIME_CONNECT=true |
1. query
Executa uma instrução SQL somente leitura. Aceita SELECT, WITH ... SELECT,
EXPLAIN e SHOW. Uma instrução por chamada — entrada com múltiplas instruções é rejeitada
pelo protocolo de consulta estendida. No modo somente leitura (o padrão), a instrução é executada
como BEGIN READ ONLY, a consulta e ROLLBACK — três comandos, aproximadamente duas
viagens de ida e volta na rede com pipelining — então o próprio banco de dados recusa qualquer gravação.
Com PG_ALLOW_WRITE=true, a instrução é enviada diretamente, sem esse wrapper, então uma
gravação executada via query seria executada — use execute para gravações.
Suporta parâmetros de instrução preparada no estilo PostgreSQL $1, $2; os valores são vinculados
pelo driver e nunca interpolados no texto SQL.
use_mcp_tool({
server_name: "postgres",
tool_name: "query",
arguments: {
sql: "SELECT * FROM users WHERE id = $1",
params: [1]
}
});
Retorna JSON compacto: {"rows": [...], "rowCount": n, "returnedRows": n, "truncated": false}.
Quando as linhas serializadas excedem PG_MAX_RESULT_BYTES, apenas as linhas que cabem são retornadas
(returnedRows < rowCount), truncated é true e uma dica sugere adicionar LIMIT/WHERE
ou selecionar menos colunas.
2. list_schemas
Lista todos os esquemas no banco de dados conectado.
use_mcp_tool({
server_name: "postgres",
tool_name: "list_schemas",
arguments: {}
});
3. list_tables
Lista tabelas no banco de dados conectado. Aceita um parâmetro opcional de esquema (padrão: '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
Obtém a estrutura de uma tabela específica (colunas, tipos, nulabilidade, padrões, chaves primárias). Aceita um parâmetro opcional de esquema (padrão: 'public').
use_mcp_tool({
server_name: "postgres",
tool_name: "describe_table",
arguments: {
table: "users",
schema: "my_schema" // optional
}
});
5. execute — requer PG_ALLOW_WRITE=true
Executa uma instrução INSERT, UPDATE, DELETE ou DDL. Sempre registrada, mas
no modo somente leitura (o padrão) ela recusa com um erro nomeando PG_ALLOW_WRITE
e não altera nada — a instrução nunca chega ao banco de dados. Com
PG_ALLOW_WRITE=true, ela executa: o mesmo tratamento de parâmetros $1, $2 que query, uma
instrução completa por chamada, e o papel conectado determina o que ela pode fazer.
Retorna {"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 — requer PG_ENABLE_RUNTIME_CONNECT=true
Conecta-se a um banco de dados PostgreSQL diferente em tempo de execução usando as
credenciais fornecidas. Não registrada por padrão — prefira configurar credenciais
através do ambiente para que nunca passem por argumentos visíveis ao modelo.
Os limites de sessão (statement_timeout, idle_in_transaction_session_timeout) são
reaplicados após cada reconexão; leituras somente leitura aplicam somente leitura em sua própria
transação 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"
}
});
Referência de configuração
| Variável | Padrão | Descrição |
|---|---|---|
DATABASE_URL | - | String de conexão completa (preferida). Suporta ?sslmode= na URL. |
PG_HOST | - | Host do banco de dados (fallback quando DATABASE_URL não está definido) |
PG_PORT | 5432 | Porta do banco de dados |
PG_USER | - | Usuário do banco de dados |
PG_PASSWORD | - | Senha do banco de dados |
PG_DATABASE | - | Nome do banco de dados |
PG_ALLOW_WRITE | false | Quando true, execute executa gravações e leituras são enviadas diretamente. Desligado (padrão) é somente leitura: execute recusa gravações e cada leitura é executada em uma transação READ ONLY |
PG_SSLMODE | - | disable | allow | prefer | require | verify-ca | verify-full. require/allow/prefer criptografam sem verificar o certificado; verify-ca/verify-full verificam (forneça uma CA via PG_SSL_CA). Valores não reconhecidos falham na inicialização. Limitação: ao contrário do libpq, allow/prefer não fazem fallback para texto simples (node-postgres não tem SSL oportunista), então um servidor sem TLS precisa de disable. |
PG_SSL_CA | - | Caminho para um arquivo de certificado CA. Definir isso sozinho implica verify-full |
PG_ENABLE_RUNTIME_CONNECT | false | Registra a ferramenta connect_db (troca de credenciais em tempo de execução) |
PG_MAX_RESULT_BYTES | 32768 | Orçamento de bytes para um resultado query enviado ao modelo. Linhas inteiras são mantidas enquanto couberem; acima do orçamento returnedRows < rowCount e truncated: true (se nem a primeira linha couber, returnedRows é 0 com uma dica). ~32 KiB ≈ 8 mil tokens; reduza para clientes estritos, aumente se seu cliente permitir mais. |
PG_STATEMENT_TIMEOUT | 30000 | Tempo limite de instrução em milissegundos, aplicado a cada sessão |
PG_CONNECT_TIMEOUT | 10000 | Tempo limite em milissegundos para uma única tentativa de conexão (aumente para links lentos ou túneis SSH) |
Para alcançar um banco de dados acessível apenas através de um bastion, veja Tunelamento SSH (adiciona variáveis PG_SSH_*).
Recursos
- Somente leitura por padrão; gravações são um opt-in explícito (
PG_ALLOW_WRITE=true) - Somente leitura aplicado pelo mecanismo (
BEGIN READ ONLY), nunca por análise SQL no lado do cliente - Acesso a dados atrás de uma pequena interface tipada; o driver
pgnunca vaza além dela - Suporte a
DATABASE_URLcom SSL (sslmode=disable|allow|prefer|require|verify-ca|verify-full, CA personalizada) - Parâmetros de instrução preparada: placeholders no estilo
$1, vinculados pelo driver - Limite de tamanho do resultado (orçamento de bytes) com um sinalizador explícito
truncatedem vez de inundar o contexto do modelo - Tempo limite de instrução da sessão mais um prazo do cliente; poolers de transação podem não preservar configurações de sessão
- Erros retornados como resultados de ferramenta legíveis com dicas baseadas em SQLSTATE, para que o modelo possa se autocorrigir
- Sobrevive a conexões perdidas — reconecta de forma preguiçosa em vez de travar
- Tunelamento SSH opcional (
PG_SSH_*) com verificação obrigatória de chave de host, carregado apenas quando configurado - Anotações de ferramenta MCP (dicas somente leitura / destrutivas) conforme especificação 2025-11-25
- Suporte a múltiplos esquemas para operações de banco de dados
Segurança
Detalhes completos, incluindo o modelo de ameaças e o processo de divulgação, estão em SECURITY.md. A versão resumida:
-
Um papel de banco de dados com privilégios mínimos é a fronteira real. O MCP trabalha com credenciais existentes; criar ou alterar papéis não é necessário. Um papel dedicado é o que realmente garante que gravações sejam impossíveis. No PostgreSQL 14+, o seguinte é um ponto 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(No PostgreSQL 13 ou mais antigo, conceda
SELECTexplicitamente em vez depg_read_all_data— veja SECURITY.md.) O servidor avisa no stderr se você conectar como superusuário. Concessões de leitura não revogam privilégios existentes, e padrões permanecem mutáveis; funções disponíveis, propriedade e privilégios herdados também importam. -
O mecanismo aplica somente leitura. Não há análise SQL no lado do cliente. No modo somente leitura, cada leitura é executada em uma transação
BEGIN READ ONLYrevertida, então o próprio PostgreSQL — que sozinho sabe o que uma função, visão ou regra faz — recusa qualquer gravação com SQLSTATE 25006 e reverte qualquer mudança de sessão que a instrução tenha feito. O protocolo estendido rejeita strings com múltiplos comandos.
Enquadramento honesto: a transação somente leitura é defesa em profundidade sobre o papel, não um substituto para ele. O modo somente leitura impede que um modelo confuso ou injetado por prompt escreva no seu banco de dados; ele não impede injeção de prompt transportada nos dados das linhas que uma consulta retorna. Não aponte este servidor para produção — use uma réplica, um snapshot ou um papel com escopo restrito. Veja SECURITY.md.
Tunelamento SSH
Defina PG_SSH_HOST (além de autenticação e verificação de chave de host) para alcançar um banco de dados acessível apenas
através de um bastion (um host de salto SSH). A string de conexão / campos PG_* descrevem então o
banco de dados como visto a partir do bastion:
{
"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"
}
}
}
}
| Variável | Padrão | Descrição |
|---|---|---|
PG_SSH_HOST | - | Host bastion SSH. Definir isso ativa o tunelamento: o servidor alcança o banco de dados apenas através de um túnel SSH para este host (veja abaixo). Recurso opcional; precisa da dependência opcional ssh2. |
PG_SSH_PORT | 22 | Porta do bastion SSH |
PG_SSH_USER | - | Nome de usuário SSH |
PG_SSH_PRIVATE_KEY | - | Caminho para um arquivo de chave privada. Se não definido, a autenticação faz fallback como ssh: um agente em execução (SSH_AUTH_SOCK), depois uma chave padrão (~/.ssh/id_ed25519, id_rsa, id_ecdsa) |
PG_SSH_PASSPHRASE | - | Frase secreta para a chave privada, se criptografada |
PG_SSH_AGENT | - | true para usar o agente ambiente (SSH_AUTH_SOCK), ou um caminho de socket explícito / pipe nomeado do Windows (\\.\pipe\openssh-ssh-agent) |
PG_SSH_PASSWORD | - | Senha de login SSH. Opt-in; uma chave ou agente tem precedência. Prefira chaves — um bastion frequentemente desativa autenticação por senha. |
PG_SSH_FINGERPRINT | - | Impressão digital de chave de host fixada (SHA256:...). A verificação de chave de host é obrigatória e definida apenas desta forma: sem ela, o túnel recusa a conexão (falha fechada). Obtenha com ssh-keygen -lF host (lê seu known_hosts) ou ssh-keyscan host | ssh-keygen -lf - (veja a nota de confiança abaixo) |
PG_SSH_KEEPALIVE_INTERVAL | 15000 | Intervalo de keepalive SSH em ms; o túnel cai após 3 keepalives sem resposta, e a próxima chamada reconecta |
- SSH altera apenas o transporte. A aplicação de somente leitura, o limite de tamanho do resultado, os timeouts e
connect_dbse comportam exatamente como em uma conexão direta, e nenhum SQL extra é enviado por consulta. - A verificação da chave do host é obrigatória por meio de um
PG_SSH_FINGERPRINTfixado - o túnel não conectará sem ele, portanto, um bastião intermediário (man-in-the-middle) é recusado. Obtenha a impressão digital por um canal em que você confia, do mais confiável primeiro:- no próprio bastião, ou de seu administrador:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub(sem envolvimento de rede); - do seu
~/.ssh/known_hostsexistente, se você já alcança o host viassh:ssh-keygen -lF bastion.example.com; - obtido do host:
ssh-keyscan bastion.example.com | ssh-keygen -lf -(confie nisso apenas quando executado de uma posição de rede em que você confia - ele aceita o que o host retornar).
- no próprio bastião, ou de seu administrador:
- TLS valida o nome de host real do banco de dados. Com
verify-full, o certificado é verificado contra o nome de host do próprio banco de dados (ex.:db.internal), não o loopback ao qual o túnel se liga localmente, erejectUnauthorizedé fixado para que umNODE_TLS_REJECT_UNAUTHORIZED=0herdado não possa desativá-lo. ssh2é uma dependência opcional, carregada apenas quandoPG_SSH_HOSTestá definido, então uma conexão direta nunca a inicializa. O npm instala dependências opcionais por padrão; executenpm install --omit=optionalpara ignorá-la completamente (uma conexão direta não precisa dela).
Uma conexão tunelada que falha relata um código SSH_* estável - veja Tratamento de Erros.
Tratamento de Erros
Falhas de SQL e de conexão são retornadas como resultados de ferramenta (isError: true)
com uma mensagem, o código SQLSTATE e uma dica. A dica do próprio servidor PostgreSQL é
usada quando presente; caso contrário, estes fallbacks se aplicam:
| código | significado | primeira coisa a verificar |
|---|---|---|
28P01 | autenticação falhou | PG_USER / PG_PASSWORD |
3D000 | o banco de dados não existe | PG_DATABASE |
42P01 | relação não encontrada | chame list_tables |
42703 | coluna não encontrada | chame describe_table |
57014 | a consulta foi cancelada (um timeout ou uma solicitação de cancelamento) | se estiver expirando, adicione um LIMIT / simplifique-a, ou aumente PG_STATEMENT_TIMEOUT |
25006 | a transação é somente leitura | a origem pode ser um papel somente leitura, uma réplica, um padrão do servidor, ou (para query) o wrapper somente leitura; execute gravações precisam de PG_ALLOW_WRITE=true |
ECONNREFUSED / ENOTFOUND | não é possível alcançar ou resolver o host do banco de dados | PG_HOST / PG_PORT / DATABASE_URL |
Através de um túnel SSH, uma falha carrega um code estável (e, onde
a causa é determinada, uma dica nomeando a configuração a corrigir), então a fase com falha é inequívoca:
| código | significado | primeira coisa a verificar |
|---|---|---|
SSH_CONFIG_INVALID | configuração SSH inválida, incl. um PG_SSH_FINGERPRINT malformado | os valores de PG_SSH_* |
SSH_KEY_INVALID | chave ilegível, não analisável, uma chave pública, ou criptografada sem a frase secreta correta (uma chave criptografada com o PG_SSH_PASSPHRASE correto funciona) | PG_SSH_PRIVATE_KEY, PG_SSH_PASSPHRASE |
SSH_CONNECT_FAILED | o bastião está inacessível, ou a configuração SSH falhou por um motivo não classificado | PG_SSH_HOST, PG_SSH_PORT, acessibilidade |
SSH_TIMEOUT | o bastião não respondeu a tempo | rede/firewall, PG_CONNECT_TIMEOUT |
SSH_AUTH_FAILED | o bastião rejeitou a autenticação | PG_SSH_USER e a chave/agente/senha em uso |
SSH_HOST_KEY_MISMATCH | a chave do host não corresponde a PG_SSH_FINGERPRINT (valor desatualizado ou MITM) | re-obtenha a impressão digital |
SSH_FORWARD_FAILED | o túnel está ativo, mas o bastião não conseguiu alcançar o banco de dados | o host e a porta do DB vistos do bastião |
SSH_CONNECTION_LOST | um túnel estabelecido caiu no meio da sessão | transitório; a próxima chamada reconecta |
Um erro genuíno do PostgreSQL através de um túnel saudável mantém seu próprio código (ex.: 28P01 para credenciais
de banco de dados erradas), não um código SSH.
Migrando de 0.1.x
Não é necessário para novas instalações. Duas mudanças de comportamento desde 0.1.x:
- Somente leitura por padrão. A ferramenta
executeestá sempre visível, mas recusa gravações (com um erro nomeando a flag) a menos quePG_ALLOW_WRITE=true, e cada leitura é executada dentro de uma transaçãoREAD ONLYimposta pelo mecanismo. Se seu fluxo de trabalho grava no banco de dados, defina"PG_ALLOW_WRITE": "true"para restaurar o comportamento de 0.1.x. connect_dbestá desabilitado por padrão. A troca de conexão em tempo de execução (passando credenciais por argumentos de ferramenta) requerPG_ENABLE_RUNTIME_CONNECT=true; caso contrário, os detalhes da conexão vêm apenas do ambiente.
Nomes de ferramentas, nomes de parâmetros e variáveis PG_* não mudaram. Os payloads de resultado
agora são JSON compacto estruturado para cada ferramenta (ex.: query retorna
{rows, rowCount, returnedRows, truncated} em vez de um array de linhas simples) - veja
CHANGELOG.md para as formas exatas antes de atualizar qualquer coisa que
analise a saída da ferramenta.
Licença
MIT