Skip to main content
Glama
luis-easyjur

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`.