Skip to main content
Glama
README.md
# DevVault — MCP Docs Server

Servidor MCP para centralizar documentações técnicas internas da empresa. Integra com Claude Code, VS Code, JetBrains e qualquer cliente compatível com o protocolo MCP.

A busca usa **matching semântico pela IA da IDE** — a IA lê o contexto extraído de cada documento e identifica os relevantes usando sua própria compreensão de linguagem natural, sem depender de APIs externas de embeddings.

---

## Stack

| Camada | Tecnologia |
|---|---|
| Servidor | Node.js 20 + Express |
| Protocolo | MCP SDK (`@modelcontextprotocol/sdk`) via HTTP + SSE |
| ORM | Prisma 6 |
| Banco | PostgreSQL 16 |
| Storage de arquivos | MinIO (compatível S3) |
| Rate limiting / cache | Redis 7 |
| Validação de schema | Zod |

---

## Pré-requisitos

- Node.js 20+
- Docker e Docker Compose

---

## Setup inicial

```bash
# 1. Instalar dependências
cd docs-mcp-server
npm install

# 2. Copiar e editar variáveis de ambiente
cp .env.example .env

# 3. Subir infraestrutura (PostgreSQL + Redis + MinIO)
npm run infra:up

# 4. Criar tabelas
npm run db:migrate

# 5. Gerar Prisma Client
npm run db:generate

# 6. Criar usuários e API keys iniciais
npm run db:seed

# 7. Fazer upload dos documentos de exemplo no MinIO
npx tsx scripts/upload-example-docs.ts

# 8. Iniciar servidor (porta 3339)
npm run dev
```

> **Guarde as API keys exibidas pelo seed** — elas são geradas com hash SHA-256 e não podem ser recuperadas depois.

---

## Variáveis de ambiente

```env
# Banco de dados
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mcp_docs

# Redis (rate limiting)
REDIS_URL=redis://localhost:6379

# MinIO (storage de arquivos Markdown)
S3_ENDPOINT=http://localhost:9002
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_BUCKET=mcp-docs-internal
S3_FORCE_PATH_STYLE=true

# Servidor
PORT=3339
NODE_ENV=development

# Segurança
API_KEY_SECRET_LENGTH=32
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_WINDOW_SECONDS=60
```

---

## Configurar no Claude Code

Crie `.mcp.json` na raiz do projeto:

```json
{
  "mcpServers": {
    "DevVault": {
      "type": "http",
      "url": "http://localhost:3339/mcp",
      "headers": {
        "Authorization": "Bearer docsk_dev_xxxx_<sua-api-key>"
      }
    }
  }
}
```

> **Atenção:** a chave correta é `"mcpServers"` (não `"servers"`). Editar `~/.claude/settings.json` não ativa o MCP — o arquivo `.mcp.json` na raiz do projeto é o que o Claude Code reconhece.

## Configurar no VS Code (extensão Claude Code)

Crie `.mcp.json` na raiz do workspace:

```json
{
  "mcpServers": {
    "DevVault": {
      "type": "http",
      "url": "http://localhost:3339/mcp",
      "headers": {
        "Authorization": "Bearer docsk_dev_xxxx_<sua-api-key>"
      }
    }
  }
}
```

> **Atenção:** a chave correta é `"mcpServers"` (não `"servers"`). Após criar ou editar o arquivo, recarregue a janela do VS Code (`Ctrl+Shift+P` → "Developer: Reload Window").

---

## Tools MCP disponíveis

| Tool | Escopo | Descrição |
|---|---|---|
| `list_docs_for_matching` | `docs:search` | Retorna metadados + contexto para matching semântico pela IA |
| `get_doc` | `docs:read` | Retorna conteúdo Markdown completo pelo ID |
| `download_doc` | `docs:download` | Gera URL pré-assinada de download (5 min) |
| `upload_markdown_doc` | `docs:upload` | Faz upload, extrai contexto e indexa automaticamente |
| `validate_markdown_doc` | `docs:upload` | Valida estrutura antes do upload |
| `list_recent_docs` | `docs:search` | Lista documentações recentes por projeto/módulo |
| `suggest_doc_template` | `docs:search` | Gera template Markdown no padrão da empresa |
| `create_user` | `admin:keys` | Cria usuário e gera API key — retorna a chave raw (exibida uma única vez) |

### Escopos por perfil

| Perfil | Escopos |
|---|---|
| Admin | todos (`docs:*` + `admin:keys` + `admin:audit`) |
| Colaborador | `docs:search`, `docs:read`, `docs:download`, `docs:upload` |
| Somente leitura | `docs:search`, `docs:read`, `docs:download` |

### Parâmetros da tool `create_user`

| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `name` | string | sim | Nome completo do usuário |
| `email` | string | sim | E-mail (deve ser único na base) |
| `role` | `admin` \| `collaborator` \| `readonly` | sim | Perfil — define os escopos da API key gerada automaticamente |
| `allowed_projects` | string[] | não | Projetos permitidos. Omita ou passe `[]` para acesso a todos |
| `expires_at` | string (ISO 8601) | não | Data de expiração da chave. Omita para chave sem expiração |

> **Atenção:** a API key retornada em `api_key` é exibida **uma única vez**. O servidor armazena apenas o hash SHA-256 — não é possível recuperá-la depois.

---

## Como a busca semântica funciona

### Fluxo de busca

```
Usuário: "como resolver consumer-timeout no RabbitMQ?"

1. IA chama list_docs_for_matching(query: "consumer-timeout rabbitmq")
   → banco pré-filtra por texto em título, tags e context (ILIKE)
   → retorna até 50 docs com { doc_id, title, project, category, tags, context[0:500] }

2. IA lê os contextos e identifica semanticamente os relevantes
   (sem API externa — usa a própria compreensão de linguagem)

3. IA chama get_doc(doc_id) para obter o conteúdo completo
   ou download_doc(doc_id) para gerar link de download
```

### Escalabilidade

| Tamanho da base | Estratégia recomendada |
|---|---|
| Até ~200 docs | `list_docs_for_matching` sem query — IA lê todos os contextos |
| 200–1000 docs | `list_docs_for_matching(query: "termos do problema")` — pré-filtro textual reduz o conjunto |
| 1000+ docs | Pré-filtro por `project` + `category` + `query` para manter o conjunto pequeno |

O parâmetro `query` realiza `ILIKE` em título, tags, filename e no campo `context` do banco — retornando apenas candidatos texualmente relacionados para a IA analisar semanticamente.

### Melhoria possível: Full-Text Search com PostgreSQL

A busca atual usa `ILIKE %termo%`, que funciona bem para bases pequenas e médias mas tem duas limitações relevantes em português:

- **Não entende variações morfológicas**: `"download"` não bate em `"downloads"`, `"erro"` não bate em `"erros"`.
- **Performance**: `ILIKE` com `%` no início da string não usa índices B-tree — em bases grandes, vira full table scan.

O PostgreSQL tem suporte nativo a **Full-Text Search** via `to_tsvector` / `plainto_tsquery`, com dicionário `portuguese` que faz stemming (reduz as palavras à raiz), normaliza acentos e ignora stopwords. Com isso:

- `"erros de download"` bate em documentos com `"erro"`, `"baixar"`, `"downloads"` etc.
- A busca usa índice GIN — muito mais rápida em bases com milhares de documentos.
- É possível ordenar resultados por relevância via `ts_rank`.

**Como implementar:**

1. Criar índice GIN na migration:
   ```sql
   CREATE INDEX idx_documents_fts ON documents
   USING gin(to_tsvector('portuguese', title || ' ' || COALESCE(context, '') || ' ' || array_to_string(tags, ' ')));
   ```

2. Substituir os filtros `ILIKE` por `to_tsvector` + `plainto_tsquery` em `listDocsForMatching`, via `prisma.$queryRaw` para o trecho da query, mantendo os filtros de `allowedProjects`, `project`, `module` e `category` como condições `AND` normais do Prisma.

3. Usar `ts_rank` para ordenar por relevância em vez de apenas `updatedAt`.

O fallback automático (quando a busca textual retorna zero) continua sendo válido com FTS — apenas o critério de "não encontrou nada" muda de "nenhum ILIKE bateu" para "nenhum documento passou no plainto_tsquery".

> Veja [docs/postgres-vs-dynamodb.md](docs/postgres-vs-dynamodb.md) para a análise de por que o DynamoDB não é adequado como substituto do PostgreSQL para este projeto.

### Upload de documento

```
Markdown recebido
  → extractTitle() + extractMetadata()   — título, categoria, projeto, módulo, tags
  → extractContext()                      — corpo sem seção de metadados nem blocos de código
  → validateMarkdown()                    — seções obrigatórias, secrets, HTML perigoso
  → MinIO/S3                             — arquivo .md completo armazenado
  → PostgreSQL documents                 — metadados + context salvos
  → PostgreSQL document_chunks           — seções indexadas por heading
```

O campo `context` extraído é o que a IA usa para matching semântico — quanto mais rico o conteúdo das seções do documento, mais precisa a identificação.

---

## Infraestrutura local

| Serviço | URL / Acesso |
|---|---|
| MCP Server | http://localhost:3339 |
| Health check | http://localhost:3339/health |
| MinIO Console | http://localhost:9003 — `minioadmin` / `minioadmin` |
| PostgreSQL | `localhost:5432` — `postgres` / `postgres` / db: `mcp_docs` |
| Redis | `localhost:6379` |

---

## Scripts

```bash
npm run dev                               # Servidor com hot-reload
npm run build                             # Compila TypeScript
npm test                                  # Roda testes (Vitest)
npm run test:mcp                          # Teste e2e com MCP SDK
npm run db:migrate                        # Aplica migrations pendentes
npm run db:generate                       # Regenera Prisma Client
npm run db:seed                           # Cria usuários e API keys de dev
npm run db:studio                         # Prisma Studio (visualizar banco)
npm run infra:up                          # Sobe PostgreSQL + Redis + MinIO
npm run infra:down                        # Para os containers
npx tsx scripts/upload-example-docs.ts   # Upload dos docs de exemplo no MinIO
```

---

## Exemplos de uso na IDE

```
Consulte o DevVault: existe documentação sobre erro de consumer-timeout no RabbitMQ?

Busque nas docs se já resolvemos problema com URL assinada expirando no módulo Admin do Reg+.

Faça upload deste arquivo como documentação do projeto Reg+, categoria Bug, módulo Mensageria.

Gere um template de documentação para um Bug no projeto SafeDocs, módulo Admin.

Liste as documentações mais recentes do projeto Reg+.

Crie um usuário chamado João Silva, e-mail joao@empresa.com, role collaborator, com acesso apenas ao projeto Reg+.
```

---

## Arquitetura e fluxo de comunicação

Veja [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) para os diagramas completos.

Veja [docs/search-flow.md](docs/search-flow.md) para o detalhamento do fluxo de busca (ILIKE atual vs Full-Text Search).