Skip to main content
Glama
README.md
# Tech Exam MCP

Aplicação MCP independente para criar e realizar provas do ENEM dentro de clientes com suporte a Apps, como ChatGPT e Claude. A interface React oferece uma experiência visual, mas a fonte de verdade é o servidor: questões, ordem, respostas, pontuação e status são persistidos no MongoDB.

## Stack e arquitetura

- Skybridge 1.3, TypeScript strict, React 19 e CSS Modules.
- Zod valida todas as entradas MCP.
- MongoDB (driver oficial `mongodb`, sem ORM) — banco único, dev e produção.
- Vitest cobre domínio, concorrência, segurança e fluxo completo, contra um MongoDB real (`mongodb-memory-server`, replica set de 1 nó).
- `src/server` concentra tools, serviços, repositórios, schemas e erros.
- `src/shared` contém DTOs públicos e constantes compartilhadas.
- `src/views/exam-app.tsx` é a view Skybridge; `src/components` divide a UI por responsabilidade.

O `examId` é a chave da tentativa. `useViewState` ajuda a view a reencontrá-lo, mas todo carregamento consulta novamente `get_exam_progress` e `get_current_question`. `submit_answer` usa uma transação Mongo (sessão de client, requer replica set), trava por prova no processo e a restrição única `(examId, questionId)` para impedir avanço ou pontuação duplicados.

## Requisitos

- Node.js 24.18 ou superior
- npm 11 ou superior
- Um MongoDB (Atlas ou self-hosted, precisa ser replica set — Atlas já é por padrão, inclusive no tier grátis)
- Docker opcional

## Instalação e dados

```bash
cp .env.example .env   # preencha MONGODB_URI (e MONGODB_DB, se quiser outro nome)
npm install
npm run seed:mongo     # popula enem_questions/enem_disciplines/enem_exams a partir de data/enem/
npm run dev
```

`seed:mongo` só precisa rodar uma vez por banco (ou de novo se `data/enem/` mudar — é idempotente, usa upsert). As imagens das questões apontam pro Cloudinary por padrão; para reenviá-las a uma conta própria, rode `CLOUDINARY_URL=cloudinary://... npm run upload:cloudinary` e ajuste `CLOUDINARY_IMAGE_BASE_URL` antes do seed.

`npm run dev` escolhe uma porta livre a partir da 3000, inicia Skybridge e ngrok em conjunto e imprime a URL HTTPS pública já terminada em `/mcp`. Isso permite executar o Tech Exam mesmo quando outro MCP já está usando a porta 3000. O DevTools usa a porta local exibida e o inspetor do ngrok fica em `http://localhost:4040`. Pressione `Ctrl+C` para encerrar os dois processos. Para solicitar outra porta inicial, use `DEV_PORT=3100 npm run dev`.

O comando exige o ngrok instalado e autenticado (`ngrok config add-authtoken ...`). Para desenvolver sem endereço público, use:

```bash
npm run dev:local
```

`npm run dev:alpic` mantém o túnel nativo do Skybridge como alternativa.

Comandos de qualidade e produção:

```bash
npm run lint
npm run typecheck
npm test
npm run build
npm start
```

## Tools MCP

| Tool | Efeito |
| --- | --- |
| `create_exam` | Sorteia `numberOfQuestions` questões oficiais do ENEM da `disciplina` informada (ou de todas, com `"todas"`), entre todos os anos disponíveis, persiste a tentativa e abre a view. |
| `get_current_question` | Retorna a questão ainda não respondida sem avançar. |
| `submit_answer` | Valida e persiste uma resposta de forma transacional e idempotente, revela o feedback e avança uma vez. |
| `pause_exam` | Pausa sem remover progresso. |
| `resume_exam` | Retoma e devolve a questão atual. |
| `get_exam_progress` | Retorna status, posição, total, respondidas, acertos e percentual. |
| `finish_exam` | Finaliza de forma idempotente e calcula erros, explicações, desempenho e estudos recomendados. |

Todas retornam uma estrutura consistente com `exam`, `progress`, `question?` e `result?`. Antes de uma resposta, `PublicQuestion` não contém gabarito, explicação ou metadado equivalente. O gabarito aparece somente no `result` da questão já respondida ou no relatório final.

## Provas do ENEM

`create_exam` aceita `disciplina` (`Linguagens`, `Ciências Humanas`, `Ciências da Natureza`, `Matemática`, ou `"todas"` para misturar todas) e `numberOfQuestions`. O servidor sorteia questões dessa disciplina entre todos os anos já semeados no MongoDB (`enem_questions`/`enem_exams` — ver "Instalação e dados", amostragem aleatória via `$sample`, sem repetir questão na mesma prova), grava uma cópia normalizada em `questions` (`_id` no formato `enem-{year}-{index}`) e monta a tentativa com essa seleção — sem replicar a ordem de nenhuma prova específica. As alternativas usam as letras originais (A–E) e podem ser texto ou imagem (algumas questões, sobretudo química/física de provas antigas, têm alternativas só com figura, hospedada no Cloudinary). Como a fonte não traz explicação pedagógica, o campo `explanation` só informa o gabarito oficial. Formato inesperado no Mongo retorna `ENEM_API_ERROR`; pedir mais questões do que o pool da disciplina tem disponível retorna `INSUFFICIENT_QUESTIONS`.

## ChatGPT

1. Rode `npm run dev` e copie a URL exibida em `MCP público`.
2. No ChatGPT, habilite o modo de desenvolvedor para Apps/Connectors.
3. Crie um conector MCP apontando para `<URL-DO-TUNNEL>/mcp`.
4. Peça, por exemplo: “Crie uma prova do ENEM 2022 com 5 questões”.

Em uma implantação pública, use a URL HTTPS do serviço no lugar do túnel.

## Claude

Clientes Claude com transporte HTTP podem usar o mesmo endpoint MCP. No Claude Code:

```bash
claude mcp add --transport http tech-exam http://localhost:3000/mcp
```

Para um cliente remoto, substitua o endereço local pela URL HTTPS. A disponibilidade de views MCP depende da versão/capacidade do host; quando a view não for suportada, as tools ainda devolvem conteúdo estruturado e textual.

## Docker

```bash
docker build -t tech-exam-mcp .
docker run --rm -p 3000:3000 -e MONGODB_URI="mongodb+srv://..." -e MONGODB_DB="questions" tech-exam-mcp
```

Sem volume — o estado vive inteiramente no MongoDB apontado por `MONGODB_URI`, externo ao container.

## Persistência, usuários e limitações

- `ExamAttempt.userId` é opcional; nunca usa e-mail como chave. Sem identificador, o servidor cria `sessionId` aleatório.
- MongoDB é a única base suportada; precisa ser um replica set (Atlas já é, mesmo no tier grátis) porque `submit_answer` usa transação de sessão.
- A view se adapta ao tema, telas estreitas e fullscreen solicitado pelo usuário. Hosts MCP controlam altura, composer, modais e podem recusar mudanças de modo.
- O botão “Iniciar outra prova” envia uma mensagem ao assistente, que coleta os novos parâmetros de modo conversacional.
- A primeira versão não inclui autenticação OAuth.
- `create_exam` lê de `enem_questions`/`enem_exams`, já semeados no Mongo — nenhuma chamada de rede pra terceiros na hora de criar a prova.

## Estrutura principal

```text
data/enem/                  cópia própria dos JSONs de prova/questão do ENEM, fonte do seed:mongo
scripts/
  seed-mongo.ts              popula enem_questions/enem_disciplines/enem_exams a partir de data/enem/
  upload-cloudinary.ts       envia as imagens de public/ pro Cloudinary (pasta enem-files)
src/
  components/
    AlternativeList/
    AnswerFeedback/
    ExamApp/
    ExamHeader/
    ExamProgress/
    ExamResult/
    QuestionCard/
  server/
    db.ts                    MongoClient/Db singleton
    errors/
    repositories/
    schemas/
    services/
    tools/
  shared/
    constants/
    types/
  views/exam-app.tsx
  server.ts
tests/exam.service.test.ts
```

As decisões de produto e API completas estão em [SPEC.md](./SPEC.md).