mcp-postgres
MCP Server com acesso a PostgreSQL — Clean Architecture, Repository Pattern, asyncpg.
Compatível com qualquer agente MCP: Claude Code, Claude Desktop, LangChain, LlamaIndex e outros via HTTP.
Índice
- Visão Geral
- Arquitetura
- Estrutura de Ficheiros
- Instalação
- Configuração
- Execução
- Capacidades MCP
- Segurança
- Testes
- Integração com Agentes
- Decisões de Arquitetura
Visão Geral
Este servidor implementa o Model Context Protocol (MCP) para expor um banco de dados PostgreSQL a modelos de linguagem e agentes de IA.
O agente pode:
- Executar queries SQL de leitura
- Inspecionar schemas, tabelas, índices e foreign keys
- Obter estatísticas de tabelas
- Executar operações de escrita (quando explicitamente habilitado)
O servidor é read-only por padrão e funciona com qualquer banco PostgreSQL — basta mudar o .env ou a variável DATABASE_URL.
Arquitetura
┌─────────────────────────────────────────────────────────┐
│ Agente (Claude / LangChain / …) │
└────────────────────────┬────────────────────────────────┘
│ MCP Protocol
stdio │ ou HTTP (SSE / streamable-http)
┌────────────────────────▼────────────────────────────────┐
│ MCP Server (FastMCP) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Tools │ │ Resources │ │ Prompts │ │
│ └────┬─────┘ └─────┬─────┘ └──────────────────┘ │
│ │ │ │
│ ┌────▼───────────────▼────────────────────────────┐ │
│ │ Repositories │ │
│ │ BaseRepository → QueryRepository │ │
│ │ → SchemaRepository │ │
│ └────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼────────────────────────────┐ │
│ │ Database (asyncpg Pool) │ │
│ └────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼────────────────────────────┐ │
│ │ Config (Pydantic Settings + .env) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ TCP
┌────────────────────────▼────────────────────────────────┐
│ PostgreSQL │
└─────────────────────────────────────────────────────────┘
Regra de dependência: cada camada conhece apenas a camada imediatamente abaixo. Tools não conhecem o banco diretamente; Config não conhece ninguém.
Estrutura de Ficheiros
mcp-postgres/
│
├── .env # Configuração activa (não commitado)
├── .env.example # Template documentado de todas as variáveis
├── pyproject.toml # Dependências, scripts, ruff, mypy, pytest
├── .gitignore
├── claude_mcp_config.json # Config pronta para Claude Desktop / Claude Code
│
├── src/
│ └── mcp_postgres/
│ ├── __init__.py
│ ├── server.py # Entry point: cria FastMCP e selecciona transporte
│ │
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py # Pydantic Settings — DATABASE_URL ou POSTGRES_*
│ │
│ ├── database/
│ │ ├── __init__.py
│ │ └── connection.py # Singleton pool asyncpg + context managers
│ │
│ ├── repositories/
│ │ ├── __init__.py
│ │ ├── base.py # fetch_all, fetch_one, execute, transaction
│ │ ├── query_repository.py # SQL arbitrário com guard de writes
│ │ └── schema_repository.py # Introspection: tabelas, colunas, índices, FK, stats
│ │
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── query_tools.py # execute_query, execute_query_one, execute_statement
│ │ └── schema_tools.py # list_schemas/tables/indexes/fks, get_table_stats
│ │
│ ├── resources/
│ │ ├── __init__.py
│ │ └── schema_resources.py # URIs: postgres://schema/…
│ │
│ └── prompts/
│ ├── __init__.py
│ └── sql_prompts.py # explore_database, analyse_table, write_query, optimise_query
│
└── tests/
├── __init__.py
├── conftest.py
└── tools/
├── test_query_repository.py
└── test_schema_repository.py
Instalação
Pré-requisitos: Python 3.11+, uv, PostgreSQL acessível.
cd mcp-postgres
# Instalar dependências
uv sync
# Com dependências de desenvolvimento
uv sync --extra dev
Configuração
Toda a configuração é feita no ficheiro .env na raiz do projeto.
cp .env.example .env
# editar .env com os dados do teu ambiente
Conexão com o Banco
Existem duas formas de configurar a conexão — usa a que for mais conveniente:
Opção A — DATABASE_URL (tem prioridade)
Uma única variável com o DSN completo:
DATABASE_URL=postgresql://user:password@host:5432/dbname
Opção B — variáveis individuais
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=mydb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secret
Se
DATABASE_URLestiver definida, os valores dePOSTGRES_HOST,POSTGRES_PORT, etc., são ignorados. Caso contrário, as vars individuais são usadas. Se nenhuma for fornecida, o servidor tenta ligar alocalhost:5432/postgres.
Transporte MCP
A variável MCP_TRANSPORT define como o servidor comunica com o agente:
| Valor | Protocolo | Endpoint | Indicado para |
|---|---|---|---|
stdio (padrão) | stdin/stdout | — | Claude Code, Claude Desktop, agentes locais |
sse | HTTP Server-Sent Events | http://host:port/sse | LangChain, LlamaIndex, agentes HTTP legados |
streamable-http | HTTP streaming | http://host:port/mcp | Agentes MCP modernos via HTTP |
Para HTTP, define também o host e a porta:
MCP_TRANSPORT=sse
MCP_HOST=0.0.0.0
MCP_PORT=8080
Referência completa de variáveis
| Variável | Padrão | Descrição |
|---|---|---|
DATABASE_URL | — | DSN completo (prioridade sobre vars individuais) |
POSTGRES_HOST | localhost | Host do PostgreSQL |
POSTGRES_PORT | 5432 | Porta |
POSTGRES_DB | postgres | Nome do banco de dados |
POSTGRES_USER | postgres | Utilizador |
POSTGRES_PASSWORD | (vazio) | Password |
POSTGRES_MIN_POOL_SIZE | 2 | Conexões mínimas no pool |
POSTGRES_MAX_POOL_SIZE | 10 | Conexões máximas no pool |
POSTGRES_COMMAND_TIMEOUT | 60 | Timeout de comando em segundos |
POSTGRES_ALLOWED_SCHEMAS | public | Schemas expostos (vírgula; vazio = todos) |
POSTGRES_ALLOW_WRITES | false | Habilita INSERT/UPDATE/DELETE/DDL |
MCP_SERVER_NAME | postgres-mcp | Nome do servidor MCP |
MCP_LOG_LEVEL | INFO | Nível de log (DEBUG, INFO, WARNING, ERROR) |
MCP_TRANSPORT | stdio | Transporte: stdio, sse, streamable-http |
MCP_HOST | 0.0.0.0 | Host do servidor HTTP (só para SSE/streamable-http) |
MCP_PORT | 8080 | Porta do servidor HTTP (só para SSE/streamable-http) |
Execução
# Modo stdio (padrão)
uv run mcp-postgres
# Modo SSE — servidor HTTP na porta 8080
MCP_TRANSPORT=sse uv run mcp-postgres
# Modo desenvolvimento com MCP Inspector
uv run mcp dev src/mcp_postgres/server.py
Capacidades MCP
Tools
Tools são funções que o agente chama activamente para executar operações.
execute_query
Executa uma query SELECT e retorna os resultados como JSON.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
sql | str | sim | Query SQL a executar |
params | list | não | Parâmetros posicionais ($1, $2, …) |
-- Exemplo com parâmetro
SELECT id, name FROM users WHERE active = $1
params: [true]
execute_query_one
Executa uma query e retorna apenas o primeiro registo.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
sql | str | sim | Query SQL |
params | list | não | Parâmetros posicionais |
execute_statement
Executa um statement de escrita. Requer POSTGRES_ALLOW_WRITES=true.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
sql | str | sim | INSERT / UPDATE / DELETE / DDL |
params | list | não | Parâmetros posicionais |
Retorna o command tag do PostgreSQL (ex: UPDATE 3).
list_schemas
Lista todos os schemas não-sistema do banco.
list_tables
Lista tabelas e views de um schema.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
schema | str | public | Nome do schema |
describe_table
Retorna as definições de colunas de uma tabela.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
table | str | — | Nome da tabela |
schema | str | public | Nome do schema |
list_indexes
Lista os índices de uma tabela com unicidade e colunas cobertas.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
table | str | — | Nome da tabela |
schema | str | public | Nome do schema |
list_foreign_keys
Lista as foreign keys de uma tabela.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
table | str | — | Nome da tabela |
schema | str | public | Nome do schema |
get_table_stats
Retorna estatísticas operacionais via pg_stat_user_tables.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
table | str | — | Nome da tabela |
schema | str | public | Nome do schema |
Inclui: live_rows, dead_rows, last_vacuum, last_analyze, total_size.
Resources
Resources expõem dados como URIs navegáveis — o agente lê-os para obter contexto antes de agir.
| URI | Descrição |
|---|---|
postgres://schemas | Lista todos os schemas do banco |
postgres://schema/{schema}/tables | Lista tabelas de um schema |
postgres://schema/{schema}/table/{table} | Colunas + índices + FK + stats de uma tabela |
Prompts
Prompts são templates reutilizáveis que guiam o agente numa tarefa complexa.
| Prompt | Parâmetros | Descrição |
|---|---|---|
explore_database | — | Roteiro para explorar um banco desconhecido |
analyse_table | table, schema | Análise detalhada de uma tabela |
write_query | question | Gera SQL a partir de linguagem natural |
optimise_query | sql | Analisa e optimiza uma query existente |
Segurança
| Mecanismo | Detalhe |
|---|---|
| Read-only por padrão | Writes bloqueados via regex antes de qualquer acesso ao banco |
| Schemas permitidos | POSTGRES_ALLOWED_SCHEMAS limita a exposição |
| Queries parametrizadas | Todos os inputs são $1, $2 — sem interpolação de strings |
| Logs sanitizados | Password nunca aparece em logs (safe_dsn) |
| Erros sanitizados | Exceções retornam mensagem simples ao agente, sem stack trace |
| Pool limitado | max_pool_size=10 por padrão — evita saturar o banco |
Para habilitar escritas:
POSTGRES_ALLOW_WRITES=true
Testes
Os testes são de integração e requerem uma instância PostgreSQL acessível. Configura o .env antes de correr.
# Todos os testes
uv run pytest
# Com coverage
uv run pytest --cov=src/mcp_postgres --cov-report=html
# Verbose
uv run pytest -v
Integração com Agentes
Claude Code / Claude Desktop
Adicionar ao ~/.claude.json (user-level, disponível em todos os projetos):
{
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "uv",
"args": [
"--directory", "/caminho/para/mcp-postgres",
"run", "mcp-postgres"
],
"env": {
"DATABASE_URL": "postgresql://user:pass@host:5432/db"
}
}
}
}
Ou usar o ficheiro claude_mcp_config.json incluído no projeto como referência.
LangChain / LlamaIndex
Iniciar o servidor em modo SSE:
MCP_TRANSPORT=sse MCP_PORT=8080 uv run mcp-postgres
Conectar a partir do agente:
# LangChain + MCP
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"postgres": {
"url": "http://localhost:8080/sse",
"transport": "sse",
}
})
tools = await client.get_tools()
Agentes HTTP genéricos
Iniciar em modo streamable-http:
MCP_TRANSPORT=streamable-http MCP_PORT=8080 uv run mcp-postgres
Endpoint disponível em http://localhost:8080/mcp.
Docker
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install uv && uv sync
EXPOSE 8080
CMD ["uv", "run", "mcp-postgres"]
docker run -p 8080:8080 \
-e DATABASE_URL=postgresql://user:pass@host:5432/db \
-e MCP_TRANSPORT=sse \
mcp-postgres
Decisões de Arquitetura
Conexão dinâmica — DATABASE_URL vs POSTGRES_*
DATABASE_URL é o padrão de facto em ambientes cloud (Heroku, Railway, Render, etc.). As vars individuais são mais legíveis para desenvolvimento local. O model_validator do Pydantic extrai os campos do DSN se fornecido, garantindo que o dsn interno está sempre correcto independentemente de qual forma foi usada.
Transporte configurável
O protocolo MCP suporta vários transportes. stdio é o padrão para agentes locais (Claude Code lança o processo e comunica por stdin/stdout). Para agentes remotos ou multi-tenant, sse e streamable-http expõem o servidor como um serviço HTTP sem qualquer alteração de código — só muda a variável de ambiente.
src/ layout
Previne que o Python encontre o módulo via path local sem instalação — o que mascararia erros de packaging e tornaria os testes menos fiáveis.
Repository Pattern
Isola o SQL das tools MCP. As tools expressam intenção (list_tables), os repositórios expressam implementação (query contra information_schema). Trocar o banco ou reescrever uma query não exige tocar nas tools.
asyncpg em vez de psycopg2/3
Construído especificamente para asyncio — não é um wrapper sobre uma biblioteca síncrona. Em workloads com múltiplas queries concorrentes, a diferença de performance é significativa. O pool reutiliza conexões TCP e prepara statements automaticamente.
Lifespan para o pool
Garante que o pool abre antes do servidor aceitar requests e fecha sempre ao terminar — mesmo com Ctrl+C ou sinal do OS. Não há risco de conexões a vazar.