matheus@devops:~$ cat sobre.txtServidor HTTP pequeno que expΓ΅e N backends de database/REST atrΓ‘s de um ΓΊnico endpoint protegido por auth, com cache LRU, allow-list de SQL, health check por target, rate limit, e audit log JSONL.
Pensado pro caso onde um LLM agent (ou qualquer client thin) precisa de acesso de leitura em vΓ‘rias fontes de dados heterogΓͺneas e vocΓͺ nΓ£o quer rodar um servidor separado por fonte.
matheus@devops:~$ ls stack/matheus@devops:~$ cat por-que-existe.txtO conselho padrΓ£o pra "deixa meu AI agent consultar banco" Γ© expor um MCP server por banco. Funciona atΓ© vocΓͺ ter trΓͺs.
TrΓͺs vira problema porque:
- TrΓͺs deployments. TrΓͺs healthchecks. TrΓͺs conjuntos de credencial pra rotacionar.
- TrΓͺs lugares pra adicionar caching, rate-limit, audit log β e eles divergem.
- TrΓͺs "shapes" diferentes de SQL safety (ou, mais provΓ‘vel, sΓ³ um deles faz certo).
- O agent precisa saber qual servidor perguntar pra quΓͺ. LΓ³gica de roteamento sai da data layer e vai pros prompts.
Isso aqui Γ© um servidor HTTP com um token de auth. Adiciona target editando config/targets.json β sem processo novo, sem imagem nova, sem regra de ingress nova. O servidor descobre targets no startup, expΓ΅e cada um em POST /query/<target>, e reporta health por-target em GET /health.
matheus@devops:~$ cat o-que-faz.txt- Auth. Bearer token ΓΊnico (env var). Rejeita qualquer outra coisa.
- SQL allow-list. SΓ³
SELECT/SHOW/DESCRIBE/EXPLAIN/WITH. ProΓbeINSERT/UPDATE/DELETE/DROP/TRUNCATE/..., statement stacking (;), e comentΓ‘rio SQL. Defesa em profundidade β tambΓ©m usa um user de DB com sΓ³ SELECT. - LRU cache. Keyed por
(target, sql-hash). TTL configurΓ‘vel por request (nocache: trueno body bypassa). - Auto-summarize. Query que retorna mais que 200 linhas vira agregados (sum/avg/min/max/top-N/by-month) + preview de 50 linhas, mantendo a resposta dentro do orΓ§amento de contexto tΓpico de LLM.
nocache: truese vocΓͺ de fato precisa das linhas inteiras. - Rate limit. Por-source-IP, janela deslizante de 60s, cap configurΓ‘vel.
- Health per-target.
GET /healthpercorre todo adapter e reportaup | down. Sem auth (pra watchdog poder bater). - Audit log. JSONL append-only de toda request: timestamp, IP, target, status, row count, elapsed ms, primeiros 500 chars do SQL.
AUDIT_LOG_FILE=vazio desabilita.
matheus@devops:~$ cat arquitetura.txt ββββββββββββββββββββββββββββββββββββββββ
β mcp-multi-target-http (1 processo) β
β β
client ββBearerββ> β ββββββββββββββββββββββββββββββββ β
β β auth + rate limit + SQL β β
β β allow-list + LRU cache β β
β ββββββββββββ¬ββββββββββββββββββββ β
β β β
β ββββββββ΄βββββββ β
β β dispatcher β β
β ββββ¬ββββ¬ββββ¬βββ β
β β β β β
β ββββββββΌβ ββΌβββ ββΌβββββββ adapters β
β β pg β βmysβ β rest β β
β βββββ¬ββββ βββ¬ββ βββββ¬ββββ β
βββββββββΌββββββββΌββββββββΌβββββββββββββββ
β β β
ββββββΌβ ββββββΌβββ ββββΌββββββ
β PG β β MySQL β β REST β
βpool β β pool β β host β
βββββββ βββββββββ ββββββββββ
Cada backend Γ© embrulhado num adapter pequeno com o mesmo contrato β { healthcheck(), query(sql, opts), close() }. Adicionar tipo novo de backend Γ© escrever um arquivo em lib/adapters/ e registrar em lib/loader.mjs.
Dois adapters no repo: postgres.mjs (usando pg) e mysql.mjs (usando mysql2/promise). ~30 linhas cada.
matheus@devops:~$ ./quick-start.shgit clone https://github.com/MatheusHenriquePrates/mcp-multi-target-http
cd mcp-multi-target-http
npm install
cp .env.example .env
cp config/targets.example.json config/targets.json
# Gera bearer token
echo "AUTH_TOKEN=$(openssl rand -hex 32)" >> .env
# Credenciais dos bancos (os targets de exemplo leem essas env vars):
echo "PG_PRIMARY_PASSWORD=..." >> .env
echo "MYSQL_REPORTING_PASSWORD=..." >> .env
node server.mjsEm outro terminal:
# Health (sem auth)
curl -s http://127.0.0.1:4910/health | jq
# Lista targets configurados
curl -s -H "Authorization: Bearer $AUTH_TOKEN" http://127.0.0.1:4910/targets | jq
# Query (auth + SQL allow-listed)
curl -s -X POST http://127.0.0.1:4910/query/primary-pg \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT 1 AS one"}' | jqmatheus@devops:~$ cat targets.jsonconfig/targets.json declara cada backend. Use placeholders ${ENV_VAR} pra qualquer coisa sensΓvel β resolvidos do environment no startup, entΓ£o secret fica fora do arquivo:
{
"targets": {
"primary-pg": {
"kind": "postgres",
"config": {
"host": "127.0.0.1",
"port": 5432,
"user": "appuser",
"password": "${PG_PRIMARY_PASSWORD}",
"database": "primary",
"connectionTimeoutMillis": 8000,
"max": 5
}
},
"reporting-mysql": {
"kind": "mysql",
"config": {
"host": "127.0.0.1",
"port": 3306,
"user": "ro_reports",
"password": "${MYSQL_REPORTING_PASSWORD}",
"database": "reporting",
"connectionLimit": 5
}
}
}
}O kind de cada target mapeia pra um adapter; o config Γ© passado direto pro driver (pg.Pool, mysql.createPool). Qualquer coisa que o driver aceita funciona β pool, SSL, statement timeout etc.
matheus@devops:~$ ls endpoints/| Endpoint | MΓ©todo | Auth | Body | O que faz |
|---|---|---|---|---|
/ |
GET | nΓ£o | β | Banner + lista de targets configurados |
/health |
GET | nΓ£o | β | Status per-target up/down, stats de cache |
/targets |
GET | sim | β | Lista de target names configurados |
/query/<target> |
POST | sim | {sql, nocache?} |
Roda SELECT validado contra <target> |
Shape da response (resultado pequeno):
{
"ok": true,
"target": "primary-pg",
"rowCount": 3,
"truncated": false,
"elapsedMs": 12,
"cache": "MISS",
"rows": [ ]
}Shape da response (resultado grande, > 200 linhas): rows Γ© substituΓdo por summary + preview + hint. Pra forΓ§ar linhas inteiras, passa { "sql": "...", "nocache": true }.
matheus@devops:~$ cat watchdog.txtwatchdog.sh Γ© um shell script auto-contido que bate em /health a cada 60s, rastreia falhas consecutivas por target, e dispara alerta no Telegram depois de N ciclos. A lista de targets pra vigiar Γ© derivada da prΓ³pria resposta do /health, entΓ£o o watchdog nΓ£o precisa reconfigurar quando target Γ© adicionado ou removido.
* * * * * TELEGRAM_TOKEN=... TELEGRAM_CHAT_ID=... /usr/local/bin/watchdog.shDeixa as env vars do Telegram vazias pra desabilitar alerta (continua logando falha localmente).
matheus@devops:~$ ./deploy.sh[Unit]
Description=mcp-multi-target-http
After=network.target
[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-multi-target-http
EnvironmentFile=/etc/mcp-multi-target-http.env
ExecStart=/usr/bin/node server.mjs
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetdocker build -t mcp-multi-target-http .
docker run -d --name mcp \
-p 127.0.0.1:4910:4910 \
-v "$PWD/config:/app/config:ro" \
-v "$PWD/logs:/app/logs" \
--env-file .env \
mcp-multi-target-httpExpΓ΅e sΓ³ em 127.0.0.1 e bota seu reverse proxy existente na frente (nginx, Caddy, Traefik). O container nΓ£o faz terminaΓ§Γ£o TLS.
matheus@devops:~$ cat notas.txt- Bind interface. Default
0.0.0.0. Pra maioria dos deploys vocΓͺ quer127.0.0.1+ reverse proxy. O server nΓ£o faz TLS. - Grants do user de DB. A allow-list pega modificaΓ§Γ£o Γ³bvia, mas nΓ£o Γ© sandbox. Sempre usa user de banco com sΓ³
SELECTnos schemas que esse server vΓͺ. Se o user pode escrever, o validator Γ© sua ΓΊltima linha β nΓ£o seja a ΓΊnica. - InvalidaΓ§Γ£o de cache. NΓ£o tem. Entradas expiram pelo TTL. Pra dado fresco numa call, passa
nocache: true. Global, abaixaCACHE_DEFAULT_TTL_SEC. - Rate limit. SΓ³ por-IP. AtrΓ‘s de reverse proxy, set
proxy_set_header X-Forwarded-For ...e ajusta se quer limite por IP real. - Audit em containers. Pra log de auditoria sobreviver restart, monta
/app/logs.
matheus@devops:~$ cat o-que-NAO-e.txt- NΓ£o Γ© um MCP server "de verdade" no sentido estrito do Model Context Protocol. Γ HTTP server que preenche o mesmo nicho (um endpoint LLM-facing expondo tools) mas com shape
POST /query/<target>muito mais simples. Se precisa MCP de fato, embrulha esse server num shim MCP. - NΓ£o Γ© proxy de escrita. Rejeita tudo exceto
SELECT/SHOW/DESCRIBE/EXPLAIN/WITHsem escape hatch. - NΓ£o Γ© query planner / federated query engine. Cada call bate em exatamente um target. Pra join cross-target, vocΓͺ faz client-side (ou no LLM).
- NΓ£o Γ© substituto pra API de dados de verdade. Se o acesso primΓ‘rio do time Γ© "queries parametrizadas, auditadas, schema-aware, com row-level access control", constrΓ³i GraphQL/REST de verdade. Isso aqui Γ© pra camada ad hoc.
matheus@devops:~$ cat LICENSEMIT. Veja LICENSE.
matheus@devops:~$ contactmatheus@devops:~$ _matheus@devops:~$ cat about.txtA small HTTP server that exposes N database/REST backends behind one auth-protected endpoint, with LRU cache, SQL allow-list, per-target health checks, rate limiting, and JSONL audit log.
Designed for the case where an LLM agent (or any thin client) needs read access to several heterogeneous data sources and you don't want to run a separate server per source.
matheus@devops:~$ ls stack/matheus@devops:~$ cat what-it-does.txt- Auth. Single Bearer token (env var).
- SQL allow-list. Only
SELECT/SHOW/DESCRIBE/EXPLAIN/WITH. Forbids modifications, statement stacking, and SQL comments. - LRU cache. Keyed by
(target, sql-hash).nocache: truebypasses. - Auto-summarize. > 200 rows β aggregates + 50-row preview.
- Rate limit. Per-source-IP, sliding 60s window.
- Per-target health.
GET /healthreportsup | downper adapter. - Audit log. JSONL append-only of every request.
matheus@devops:~$ ./quick-start.shgit clone https://github.com/MatheusHenriquePrates/mcp-multi-target-http
cd mcp-multi-target-http
npm install
cp .env.example .env
cp config/targets.example.json config/targets.json
echo "AUTH_TOKEN=$(openssl rand -hex 32)" >> .env
node server.mjsmatheus@devops:~$ ls endpoints/| Endpoint | Method | Auth | Body | What |
|---|---|---|---|---|
/ |
GET | no | β | Service banner + targets |
/health |
GET | no | β | Per-target up/down, cache stats |
/targets |
GET | yes | β | Configured target names |
/query/<target> |
POST | yes | {sql, nocache?} |
Validated SELECT against <target> |
matheus@devops:~$ cat what-this-is-NOT.txt- Not a real MCP server in the strict Model Context Protocol sense. Simpler
POST /query/<target>shape. - Not a write proxy. Only
SELECT/SHOW/DESCRIBE/EXPLAIN/WITH. - Not a federated query engine. Each call hits exactly one target.
- Not a replacement for a proper data API. This is the ad hoc layer.
matheus@devops:~$ cat LICENSEMIT. See LICENSE.
matheus@devops:~$ contactmatheus@devops:~$ _