mcp-mysql-remote
by luis-easyjur
README.md
# mcp-mysql-remote
Servidor **MCP remoto, somente-leitura**, que expõe consultas SQL de diagnóstico ao MySQL
do EasyJur para o **Claude.ai (web)** via HTTPS, protegido por **OAuth 2.1** restrito ao
Claude. É o porte do MCP **stdio** local (`easyjur/fenix/mcp/mysql`) para um serviço
publicável no **Railway**.
> ⚠️ **Segurança primeiro.** Este serviço fica exposto na internet sobre dados jurídicos
> multi-tenant (LGPD). Ele **não** deve apontar para produção direta: use **réplica de
> leitura ou homologação**, usuário MySQL com **`GRANT SELECT` apenas**, segredos só em
> Railway Variables, OAuth com **PKCE S256** e `redirect_uri` restrito ao Claude.
---
## Onde está o roadmap (fonte única)
Todo o planejamento vive como uma change OpenSpec (spec-driven). **Não duplique detalhes
aqui** — edite os artefatos:
```
openspec/changes/bootstrap-remote-mysql-mcp/
├── proposal.md # POR QUÊ + o que muda + capabilities + impacto
├── design.md # COMO: arquitetura, design patterns, diagramas, decisões, riscos
├── specs/ # O QUÊ (requisitos testáveis, WHEN/THEN):
│ ├── read-only-query/spec.md
│ ├── remote-mcp-transport/spec.md
│ └── oauth-authorization/spec.md
└── tasks.md # passo a passo de implementação (setup → deploy → Claude.ai)
```
Comandos úteis:
```bash
openspec show bootstrap-remote-mysql-mcp # visão geral da change
openspec status --change bootstrap-remote-mysql-mcp
openspec validate bootstrap-remote-mysql-mcp --strict
```
---
## Arquitetura (resumo)
```
Claude.ai ──HTTPS+Bearer──► Railway: mcp-mysql-remote
│ [Middleware] auth OAuth (JWT/PKCE)
│ [Adapter] Streamable HTTP ⇄ MCP Server (SDK)
│ [Strategy] QueryValidator (somente leitura)
│ [Gateway] DbClient (pool mysql2)
▼
MySQL (réplica, SELECT-only)
```
Detalhes, patterns e fluxo OAuth completo em `design.md`.
Estrutura implementada:
```
src/
├── index.js # bootstrap HTTP (Express) + montagem de rotas
├── config.js # env → config, validação fail-fast no boot
├── health.js # GET /health (sem auth, sem DB)
├── mcp/
│ ├── server.js # MCP Server + tool query_mysql
│ └── httpTransport.js # Streamable HTTP (stateless) em POST /mcp
├── db/
│ ├── queryValidator.js # validação read-only por token (fix substring)
│ └── dbClient.js # pool mysql2; SHOW/DESC/EXPLAIN via query(), resto execute()
└── auth/
├── clients.js # cliente único (Claude) + allowlist de redirect_uri
├── discovery.js # RFC 8414 + RFC 9728
├── oauthServer.js # GET /authorize + POST /token (PKCE S256, JWT HS256)
└── middleware.js # Bearer obrigatório em /mcp (401 + WWW-Authenticate)
```
---
## Setup local
Requisitos: Node.js 20+.
```bash
npm install
cp .env.example .env # preencha os valores (nunca commite o .env)
npm start # sobe em http://localhost:3000
npm test # testes unitários do validador + E2E OAuth/MCP
```
Segredos podem ser gerados com:
```bash
openssl rand -hex 32 # OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET
openssl rand -hex 64 # JWT_SECRET
```
O boot **falha cedo** se faltar env obrigatória (`DB_PASSWORD`, banco, `OAUTH_CLIENT_ID`,
`OAUTH_CLIENT_SECRET`, `JWT_SECRET`).
---
## Deploy no Railway
1. **Banco:** provisione réplica de leitura ou homologação (nunca produção direta) e crie
um usuário com `GRANT SELECT` apenas nos bancos necessários.
2. **Serviço:** crie um projeto no Railway a partir deste repositório (o `Dockerfile` é
detectado automaticamente) ou use `railway up`.
3. **Variables:** configure em Settings → Variables (nada no repo):
`DB_HOST`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD`, `DB_CONNECTION` (ou `DB_NAME`),
`OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `JWT_SECRET`, `PUBLIC_URL`,
`OAUTH_REDIRECT_ALLOWLIST` (opcional; padrão já é o callback do Claude).
4. **Domínio + healthcheck:** ative o domínio HTTPS do Railway, aponte `PUBLIC_URL` para
ele (ex.: `https://mcp-mysql-remote.up.railway.app`) e configure o healthcheck do
serviço para `GET /health`.
5. **Firewall do MySQL:** restrinja o acesso ao IP de saída do Railway (security group).
6. Verifique: `https://<dominio>/health` → 200 e
`https://<dominio>/.well-known/oauth-authorization-server` → JSON com URLs `https://`.
---
## Conexão no Claude.ai
1. Em **Settings → Connectors → Add custom connector**, informe a URL do MCP:
`https://<dominio-railway>/mcp`.
2. Insira o **OAuth Client ID** e o **Client Secret** (os mesmos das Railway Variables).
3. Complete o fluxo OAuth (o callback é `https://claude.ai/api/mcp/auth_callback`).
O connector deve ficar **Connected**.
4. No chat, a tool `query_mysql` fica disponível — rode um `SELECT` de diagnóstico.
---
## Checklist de segurança
- [ ] Banco alvo é réplica/homologação (host distinto de produção)
- [ ] Usuário MySQL com `GRANT SELECT` apenas (escrita negada pelo banco)
- [ ] Segredos só em Railway Variables; `.env` ignorado pelo git
- [ ] `redirect_uri` restrito a `https://claude.ai/api/mcp/auth_callback`
- [ ] PKCE S256 obrigatório; tokens JWT com TTL curto
- [ ] Acesso ao MySQL restrito ao IP de saída do Railway
- [ ] `npm test` verde (regressão read-only: escrita/DDL/stacked queries recusadas)
---
## Status
Fase atual: **apply concluído localmente** — `src/` implementado com testes passando.
Pendências para produção (ver `tasks.md §5–§7`): provisionar banco réplica + usuário
SELECT-only, deploy no Railway e registro do connector no Claude.ai. Após validar de
ponta a ponta: `openspec archive bootstrap-remote-mysql-mcp`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing