postgrad-study-mcp
# study-notes-mcp
[](https://github.com/wasinskivini/study-notes-mcp/actions/workflows/test.yml)
Servidor [MCP (Model Context Protocol)](https://modelcontextprotocol.io) para apoiar os estudos (pós-graduação, faculdade, cursos): busca nas suas anotações, no código e nos PDFs das aulas, registra dúvidas, acompanha o progresso e gera quizzes e planos de revisão — disponível em **qualquer projeto** aberto no VS Code (ou em outro cliente MCP).
> **O servidor não traz conteúdo nenhum.** Ele roda no seu computador e busca na **sua** pasta de estudos. Cada pessoa aponta para a própria pasta; ninguém acessa os arquivos de ninguém.
> 🎓 **Projeto educacional.** Criado durante a pós-graduação em Engenharia de Software com IA Aplicada para praticar a construção de servidores MCP e apoiar os estudos. Não é um produto oficial do curso nem da instituição, não tem garantia de funcionamento (veja a [licença](LICENSE)) e não substitui o material e as orientações dos professores.
---
## Instalação (passo a passo)
### 1. Pré-requisitos
- **Node.js 22.18 ou mais novo** — confira com `node -v`
- **VS Code** com **GitHub Copilot** (o plano Free funciona) ou outro cliente MCP
### 2. Não precisa baixar nada
O servidor está no [npm](https://www.npmjs.com/package/study-notes-mcp): o VS Code baixa e roda sozinho com `npx` quando você configurar o passo 4. Para mexer no código, veja [A partir do código-fonte](#a-partir-do-código-fonte).
### 3. Monte sua pasta de estudos
Escolha **uma pasta** com o que você quer consultar — ela vira o `NOTES_DIR`. Cada subpasta de primeiro nível é tratada como um módulo. Por exemplo:
```
Pós IA/
modulo01-fundamentos/ ← suas anotações (.md, .txt)
modulo03-mcp/ ← PDFs das aulas que você baixou
repo-do-curso/ ← clone do repositório do curso (código .ts/.js/.py)
```
Não precisa reorganizar nada: aponte para a pasta onde você já guarda o material. Arquivos novos aparecem na próxima busca, sem reiniciar.
> ⚠️ **Material do curso é seu, não do repositório.** PDFs, slides e gravações costumam ser protegidos por direitos autorais: mantenha-os só na sua pasta local e **nunca** faça commit deles aqui ou em outro repositório público.
### 4. Configure o VS Code
Abra `Cmd+Shift+P` (Mac) / `Ctrl+Shift+P` (Windows/Linux) → **MCP: Open User Configuration** e adicione o servidor dentro de `"servers"`. Configurando no nível de usuário, ele fica disponível em **todos** os projetos.
```json
{
"servers": {
"study-notes-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "study-notes-mcp@latest"],
"env": {
"NOTES_DIR": "/Users/seu-usuario/Documents/Pós IA",
"INCLUDE_PDF": "true"
}
}
}
}
```
No Windows, use `/` ou `\\` no caminho: `"NOTES_DIR": "C:/Users/seu-usuario/Documents/Pos IA"`. O caminho precisa ser **absoluto**: o servidor roda fora de qualquer projeto.
O `@latest` faz o `npx` conferir se há versão nova a cada início. Na primeira vez ele baixa o pacote e as dependências (alguns MB), por isso o servidor pode demorar alguns segundos para subir.
### Todos os projetos ou só um?
| Onde configurar | Arquivo | Quando usar |
|---|---|---|
| Usuário (recomendado) | **MCP: Open User Configuration** | Você estuda enquanto trabalha em vários projetos e quer o material sempre à mão |
| Só um projeto | `.vscode/mcp.json` dentro do projeto | Você abre uma pasta só para estudar, ou quer o servidor apenas ali |
Para um projeto só, crie `.vscode/mcp.json` com o mesmo conteúdo do passo 4. Se as notas ficam dentro do próprio projeto, use `${workspaceFolder}` no lugar do caminho absoluto:
```json
{
"servers": {
"study-notes-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "study-notes-mcp@latest"],
"env": { "NOTES_DIR": "${workspaceFolder}/notas" }
}
}
}
```
Dicas:
- **Escolha um dos dois.** Configurado nos dois lugares, o servidor aparece duplicado e o modelo vê as tools em dobro.
- **Não faça commit de caminhos pessoais.** Se o `.vscode/mcp.json` tiver um caminho da sua máquina, coloque-o no `.gitignore` ou use `${workspaceFolder}`.
- **Configurado para o usuário, mas sem uso em um projeto?** No Copilot Chat, clique no ícone de ferramentas (**Configure Tools**) e desmarque o `study-notes-mcp`. O modelo deixa de ver as 7 tools ali, sem afetar os outros projetos.
### 5. Inicie e teste
1. `Cmd/Ctrl+Shift+P` → **MCP: List Servers** → `study-notes-mcp` → **Start Server**
2. Em **Show Output**, confira se aparece `Discovered 7 tools`
3. Abra o Copilot Chat no modo **Agent** e pergunte algo sobre o seu material
Na primeira chamada de cada tool o VS Code pede permissão — clique em **Allow**. Clicando no bloco "Ran buscar_material" da resposta, dá para ver exatamente o que o servidor devolveu.
### Problemas comuns
| Sintoma | Causa e solução |
|---|---|
| `spawn npx ENOENT` ou "The command npx was not found" | O VS Code não achou o Node no PATH — comum com **nvm**, que só configura o PATH no terminal. Use o caminho absoluto em `command` (veja com `which npx` / `where npx`) ou abra o VS Code pelo terminal com `code .` |
| Erro de sintaxe ou `ERR_UNKNOWN_FILE_EXTENSION ".ts"` | Node antigo. Atualize para 22.18+ |
| Servidor aparece mas não acha nada | Confira o `NOTES_DIR` (caminho absoluto, pasta existe). Sem a [busca por significado](#busca-por-significado-opcional) ligada, a busca é por **texto literal**: tente outra palavra |
| "Language model unavailable" no chat | Problema do Copilot, não do servidor: atualize o VS Code e confira o login/plano do Copilot |
| O modelo responde sem usar o servidor | Seja explícito: "consulte minhas anotações sobre…" ou cite a tool com `#buscar_material` |
| Digitando `/` não aparecem `gerar_quiz` nem `plano_de_revisao` | A conversa está numa sessão **Copilot CLI**, que não mostra prompts de MCP. Abra um chat na sessão **Local**, modo **Agent**, e digite `/mcp.study-notes` |
| O chat leu um arquivo que o servidor bloqueia (`.zshrc`, `package.json`...) | Quem leu foi o **próprio Copilot**, com a ferramenta embutida dele (aparece "Read arquivo", não "Ran ler_material"). As proteções deste servidor valem só para o que passa por ele. Para testar o servidor, cite a tool: `#ler_material` |
| Parei o servidor e ele voltou sozinho | O VS Code reinicia servidores a cada conversa nova. Para desligar de verdade, remova a entrada do `mcp.json` |
---
## Exemplos de uso
Com o servidor rodando, no Copilot Chat (modo **Agent**):
| Você pede | O servidor faz |
|---|---|
| `O que eu anotei sobre embeddings?` | `buscar_material` em anotações, código e PDFs |
| `Busque "chunking" só nos PDFs e leia a página onde aparece` | `buscar_material` com `tipos: ["pdf"]` → `ler_material` com `paginas` |
| `Como foi implementado o registerTool nas aulas? Procure só no código` | `buscar_material` com `tipos: ["codigo"]` → `ler_material` |
| `Registre uma dúvida no modulo03: quando usar resource em vez de tool?` | `registrar_duvida` |
| `Marque a aula 05 do modulo03 como concluída` | `marcar_aula_concluida` |
| `Quais dúvidas eu ainda tenho pendentes?` | `ver_estudos` |
| `Já entendi a dúvida 2, pode marcar como resolvida` | `resolver_duvida` |
| `Onde as aulas explicam como transformar texto em números?` | `buscar_material` (com a busca por significado ligada, acha mesmo sem as mesmas palavras) |
Os prompts aparecem digitando `/` no chat: `/mcp.study-notes-mcp.gerar_quiz` e `/mcp.study-notes-mcp.plano_de_revisao`.
### Como o quiz e o plano de revisão funcionam
O servidor **não tem LLM**: um prompt MCP é só uma mensagem pronta. Ao escolher `gerar_quiz`, o servidor valida o módulo e devolve um texto que pede ao modelo do seu chat para buscar suas anotações (com as tools deste servidor) e montar as perguntas. Quem escreve o quiz é o modelo.
Por isso os prompts pedem que **cada resposta venha com a fonte** — `arquivo:linha` ou `arquivo, p. N` — e que o modelo diga "não encontrei" em vez de completar com conhecimento geral. No quiz, as fontes aparecem só junto das respostas, no final, para não entregar o gabarito. Isso não impede o modelo de errar, mas torna qualquer resposta suspeita conferível em segundos.
No `plano_de_revisao`, o próprio servidor lê as suas dúvidas e o seu progresso e já os coloca no texto do prompt. O modelo só precisa buscar no material onde revisar cada assunto.
---
## O que o servidor oferece
| Tipo | Nome | Descrição |
|---|---|---|
| 🔧 Tool | `listar_modulos` | Lista as pastas de módulos dentro de `NOTES_DIR` |
| 🔧 Tool | `buscar_material` | Busca nas anotações, no código e nos PDFs das aulas (até 20 resultados com arquivo, linha, página e trecho), com filtro opcional por `tipos`. Aceita um nome exato ou uma pergunta; com `SEMANTIC_SEARCH=true`, também acha o assunto por significado |
| 🔧 Tool | `ler_material` | Lê uma anotação ou arquivo de código completo, ou um PDF por páginas (até 5 por vez) |
| 🔧 Tool | `registrar_duvida` | Salva uma dúvida para revisar depois |
| 🔧 Tool | `marcar_aula_concluida` | Marca uma aula como concluída |
| 🔧 Tool | `resolver_duvida` | Marca uma dúvida como resolvida pelo número (`#2`); ela sai do plano de revisão mas continua no histórico |
| 🔧 Tool | `ver_estudos` | Lista as dúvidas não resolvidas e as aulas concluídas (o modelo não consegue abrir resources sozinho, então os mesmos dados também existem como tool) |
| 📄 Resource | `pos://duvidas` | Dúvidas registradas (JSON) |
| 📄 Resource | `pos://progresso` | Aulas concluídas por módulo (JSON) |
| 💬 Prompt | `gerar_quiz` | Quiz de múltipla escolha sobre um módulo, baseado nas suas anotações, com a fonte de cada resposta |
| 💬 Prompt | `plano_de_revisao` | Plano de revisão da semana a partir das dúvidas e do progresso, com a fonte de cada indicação |
---
## Configuração
| Variável | Padrão | Uso |
|---|---|---|
| `NOTES_DIR` | `~/Documents` | Pasta raiz das anotações (cada subpasta é um módulo) |
| `DATA_DIR` | `~/.study-notes-mcp` | Onde ficam `duvidas.json` e `progresso.json` |
| `INCLUDE_PDF` | `true` | `false` desliga totalmente a busca e a leitura de PDFs |
| `SEMANTIC_SEARCH` | `false` | `true` liga a [busca por significado](#busca-por-significado-opcional) |
| `MODELS_DIR` | `DATA_DIR/modelos` | Onde o modelo de embeddings fica guardado depois do download |
Arquivos lidos, por tipo:
| Tipo (`tipos`) | Extensões | Limite |
|---|---|---|
| `anotacoes` | `.md`, `.txt` | 200 KB |
| `codigo` | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py` | 200 KB |
| `pdf` | `.pdf` (somente PDFs com texto; escaneados são recusados com aviso) | 30 MB, leitura de até 5 páginas por vez |
### Filtrando por tipo
Não é preciso passar parâmetros: basta pedir no chat e o modelo preenche `tipos` sozinho.
```
Busque "embeddings" só nos PDFs das aulas
Procure "registerTool" só no código
```
Sem filtro, a busca olha todos os tipos, nesta ordem: **anotações, PDFs e por último código**. Ela devolve no máximo 20 resultados, até 3 por arquivo, para um arquivo só não ocupar a lista inteira. Se o limite for atingido, a resposta avisa: aí vale pedir "só nos PDFs" ou "só no módulo X".
### PDFs e tokens
O texto dos PDFs é extraído na sua máquina e fica em cache enquanto o arquivo não muda. Só vai para o modelo o que a tool devolve: os trechos da busca (até 20 × 200 caracteres) ou as páginas pedidas em `ler_material` (até 5, ~2 mil tokens). O modelo deve buscar primeiro e depois ler só as páginas indicadas.
### Busca por significado (opcional)
A busca normal é **literal**: procurar "vetor" não acha um trecho que só fala em "embedding". Com `SEMANTIC_SEARCH=true`, a tool `buscar_material` passa a achar também pelo **assunto**, mesmo com palavras diferentes ou em outro idioma. Não existe uma segunda tool: o servidor roda as duas buscas e junta os resultados, então o modelo não precisa decidir qual usar.
Ela usa **embeddings**, o mesmo "R" do RAG: cada trecho das suas anotações e PDFs vira um vetor de números, e a busca devolve os trechos com o vetor mais parecido com o da pergunta. Quem gera os vetores é um **modelo de embeddings** pequeno ([multilingual-e5-small](https://huggingface.co/intfloat/multilingual-e5-small), licença MIT), que roda **dentro do servidor, no seu computador**. Ele não escreve texto nem responde nada, e não precisa de Ollama nem de chave de API.
Para ligar, a biblioteca de embeddings entra como um segundo pacote no `npx` e a variável `SEMANTIC_SEARCH` no `env`:
```json
"study-notes-mcp": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"--package=study-notes-mcp@latest",
"--package=@huggingface/transformers@4.3.1",
"study-notes-mcp"
],
"env": {
"NOTES_DIR": "/Users/seu-usuario/Documents/Pós IA",
"INCLUDE_PDF": "true",
"SEMANTIC_SEARCH": "true"
}
}
```
A partir do código-fonte, basta o `"SEMANTIC_SEARCH": "true"`: o `npm install` já instala a biblioteca.
O que acontece:
1. **Primeira vez:** o modelo é baixado do Hugging Face (~145 MB, uma vez só) e guardado em `MODELS_DIR`. É o **único acesso à internet** do servidor. Seus arquivos nunca são enviados: os vetores são calculados localmente.
2. **Indexação:** o servidor divide anotações e PDFs em trechos de ~800 caracteres e gera o vetor de cada um, em segundo plano. Na primeira vez leva alguns minutos (~8 minutos para ~1.900 arquivos num Mac com Apple Silicon); enquanto isso a busca avisa que o índice está incompleto e a busca literal continua funcionando.
3. **Depois:** o índice fica salvo em `DATA_DIR/indice-semantico.json`. Nas próximas vezes só arquivos novos ou alterados são processados (segundos).
Código **não** entra no índice: nomes como `registerTool` já são termos exatos, e a busca literal resolve melhor.
**Por que juntar as duas buscas.** Cada uma erra onde a outra acerta. A literal acha `registerTool` e `OPENAI_API_KEY`, mas não entende sinônimos; a semântica acha o assunto com outras palavras, mas muitas vezes deixa de fora o arquivo que tem o nome exato. A junção usa *Reciprocal Rank Fusion*: cada busca dá a cada resultado a nota `1 / (60 + posição)`, e as notas se somam. Arquivos que as duas buscas acharam sobem na lista. Se a busca por significado falhar (biblioteca ausente, sem internet para baixar o modelo), a resposta traz só a literal, com um aviso.
**Quanto isso melhora?** Medido em dois lugares:
- No material do autor (~1.900 arquivos), para 10 nomes exatos: de 22 arquivos que a busca literal encontra, a semântica sozinha traz 8 e a híbrida traz 15. Esse material é privado e não está no repositório.
- No corpus de teste do repositório (12 arquivos, 20 perguntas, rodando `npm run test:semantic`): a literal acerta o arquivo em 1º lugar em 6 de 20 perguntas, e a semântica e a híbrida em 18 de 20. Com tão poucos arquivos, a híbrida só empata com a semântica; o corpus serve para detectar regressões, não para provar ganho.
Custos e limites: a biblioteca ([transformers.js](https://huggingface.co/docs/transformers.js)) ocupa ~470 MB, por isso **não** vem junto com o pacote: só baixa quem acrescentar o segundo `--package`. Sem ela, o resto do servidor funciona normalmente e `buscar_por_significado` explica o que falta. Cada busca devolve até 8 trechos de 500 caracteres.
---
## Custos
**Rodar o servidor não custa nada.** Ele não usa nenhuma API de IA nem chave, não acessa a internet e não tem LLM: só lê arquivos na sua máquina e devolve texto. A única exceção é opcional: com `SEMANTIC_SEARCH=true`, ele baixa uma vez o modelo de embeddings, que depois roda localmente e de graça. Por isso também não há `.env` com credenciais — o [`.env.example`](.env.example) lista as três variáveis, nenhuma secreta.
Quem "pensa" é o modelo do **seu** cliente MCP, cobrado pelo plano dele:
| Onde roda | Custo |
|---|---|
| `study-notes-mcp` (busca, PDFs, dúvidas, busca semântica) | Grátis — local |
| Modelo do Copilot Chat | Conforme o seu plano do Copilot. No **Free** há um limite mensal de uso e nada é cobrado; estudantes podem pedir o **Pro** grátis pelo [GitHub Education](https://education.github.com) |
| Outros clientes (Claude, Cursor…) | Conforme o plano/assinatura de cada um |
O servidor limita o que manda ao modelo — até 20 trechos de 200 caracteres por busca, arquivos de texto até 200 KB e PDFs de até 5 páginas por leitura — para não encher o contexto. Se você notar consumo alto, use o filtro `tipos` ou `INCLUDE_PDF=false`.
---
## O que sai do seu computador
| Parte | Onde roda | Envia seu material para fora? |
|---|---|---|
| Este servidor (busca literal, leitura, dúvidas, progresso) | No seu computador | Não |
| Busca por significado (modelo `multilingual-e5-small`) | No seu computador | Não. Só baixa o modelo uma vez do Hugging Face |
| O modelo do seu cliente MCP (Copilot, no caso do VS Code) | Na nuvem | **Sim**, apenas os trechos que as tools devolvem |
O modelo de busca por significado não é um LLM de conversa: ele só transforma texto em números para comparar significados, e isso acontece localmente. O que sai do computador é o resultado que o Copilot lê para responder, como acontece com qualquer arquivo que ele abre.
O servidor reduz esse envio: devolve trechos em vez de arquivos inteiros e troca segredos por `[REDACTED]` antes de responder. O que acontece com os dados depois disso depende da política do seu cliente (por exemplo, se o Copilot da sua conta ou organização permite usar os dados para treino). Veja a seção Segurança abaixo.
## Segurança
Tudo que as tools devolvem é enviado ao modelo do seu cliente MCP (na nuvem, no caso do Copilot). Por isso o servidor aplica várias camadas de proteção:
**Acesso a arquivos**
- Lista fechada de extensões: `.env`, `.json`, chaves (`.pem`, `.key`) e qualquer outra extensão nunca são lidos
- Arquivos e pastas ocultos (`.env`, `.git`, `.oculta/`) e `node_modules`, `dist`, `build`, `coverage`, `venv` são ignorados
- Caminhos com `../` e symlinks que apontam para fora de `NOTES_DIR` são bloqueados
- Segredos que aparecem no código (chaves `sk-…`, `ghp_…`, AWS, Google, Slack, chaves privadas, `apiKey = "…"`, `senha: "…"`, credenciais em URLs) são trocados por `[REDACTED]`
**Prompt injection** — não existe proteção 100%, então são várias camadas:
- O conteúdo dos arquivos volta delimitado por `<dados_nao_confiaveis id="…">` com um id aleatório a cada chamada; o arquivo não consegue "fechar" o bloco e escrever fora dele
- Caracteres invisíveis (zero-width, controle de direção, tag characters Unicode) são removidos, já que servem para esconder instruções do leitor humano
- Padrões típicos de injeção ("ignore as instruções anteriores", "you are now", `<system>`, "não conte ao usuário"…) geram um **ALERTA** no resultado e o campo `alertas` no `structuredContent`
- As `instructions` do servidor e os prompts dizem ao modelo para tratar o conteúdo como dados e só falar de prompt injection quando houver uma linha `⚠️ ALERTA`
- `registrar_duvida` recusa textos com padrões de injeção, para nada ser "plantado" no histórico que depois volta via `ver_estudos` e `plano_de_revisao`
- Busca semântica (opcional): o modelo é baixado de um commit fixo do Hugging Face (uma versão nova publicada lá não é baixada sem querer), e o índice salvo em `DATA_DIR` guarda o texto já com segredos redigidos e com permissão `600`
- Argumentos das tools têm tamanho máximo e não aceitam caracteres invisíveis; `gerar_quiz` só aceita módulos que existem
- Tools anotadas com `readOnlyHint`, para o cliente distinguir leitura de escrita; as de escrita só alteram os JSONs em `DATA_DIR` (criados com permissão `600`)
> Essas proteções valem para o que passa por este servidor. O Copilot tem ferramentas próprias (ler arquivos, terminal) que não passam por aqui; para controlar o que elas podem fazer, use as configurações de aprovação de tools do VS Code.
> Não coloque em `NOTES_DIR` material sigiloso ou com dados pessoais: mesmo com essas camadas, o que for lido vai para o modelo.
---
## Entendendo o código
O código é comentado como um **roteiro de leitura em passos**: cada arquivo é um passo, e cada ponto importante dentro dele tem um número (2.4, 3.6...). Os comentários citam esses números, então dá para seguir uma chamada de um arquivo para o outro.
### Estrutura
```
src/
index.ts # PASSO 1 — ponto de entrada: liga o servidor ao transporte stdio
mcp.ts # PASSO 2 — o que o servidor oferece: tools, resources e prompts
service.ts # PASSO 3 — as regras: tipos de arquivo, busca, leitura, PDFs, dados salvos
safety.ts # PASSO 4 — as proteções: caracteres invisíveis, segredos, prompt injection
semantic.ts # PASSO 6 — (opcional) busca por significado com embeddings
tests/
helpers.ts # PASSO 5.1 — cliente MCP de teste (sobe o servidor de verdade)
pdf.ts # PASSO 5.2 — gera PDFs mínimos para os testes
mcp.test.ts # PASSO 5.3 — testes de ponta a ponta
semantic.test.ts # PASSO 5.4 — busca semântica sem o modelo (trechos, similaridade)
semantic.model.ts # PASSO 5.5 — busca semântica com o modelo de verdade
avaliacao.model.ts # PASSO 5.7 — mede o acerto das buscas (literal, semântica e híbrida)
pacote.smoke.ts # PASSO 5.6 — instala o pacote do npm numa pasta vazia e sobe o servidor
fixtures/ # anotações de exemplo
```
### Ordem de leitura sugerida
| Passo | Arquivo | O que você aprende |
|---|---|---|
| 1 | [src/index.ts](src/index.ts) | Como um servidor MCP sobe e por que o stdout é sagrado (1.1 a 1.5) |
| 2 | [src/mcp.ts](src/mcp.ts) | Como declarar tools (2.3 a 2.8), resources (2.9) e prompts (2.10); instructions (2.1) e validação (2.2) |
| 3 | [src/service.ts](src/service.ts) | Tipos, limites e filtros (3.1 a 3.3), PDFs (3.4, 3.8), busca (3.6), leitura (3.7), segurança de caminhos (3.9), dados (3.10) |
| 4 | [src/safety.ts](src/safety.ts) | As camadas contra vazamento e prompt injection (4.1 a 4.5) |
| 5 | [tests/](tests/) | Como testar um servidor MCP pelo próprio protocolo |
| 6 | [src/semantic.ts](src/semantic.ts) | (Opcional) Embeddings na prática: dividir em trechos, indexar, buscar por similaridade (6.1 a 6.5) e juntar com a busca literal (6.6) |
### O caminho de uma busca, passo a passo
Quando você pergunta *"o que eu anotei sobre RAG nos PDFs?"*:
```
Copilot ── tools/call buscar_material {consulta:"RAG", tipos:["pdf"]} ──► PASSO 1.1 chega pelo stdin
PASSO 2.2 Zod valida os argumentos
PASSO 2.4 handler da tool
PASSO 6.6 buscaHibrida (literal + por significado)
PASSO 3.6 └ searchNotes (a busca literal)
PASSO 3.9 └ módulo dentro da pasta?
PASSO 3.5 └ lista os arquivos permitidos (3.3)
PASSO 3.4 └ extrai o texto dos PDFs (com cache)
PASSO 4.4 └ cada trecho: invisíveis, alertas, segredos
PASSO 4.5 delimita como <dados_nao_confiaveis>
Copilot ◄──────────────────── resultado (texto + structuredContent) ── PASSO 1.1 sai pelo stdout
```
Depois disso o modelo decide se lê as páginas (`ler_material` → PASSO 2.5 → 3.7 → 3.9 → 3.8 → 4.4 → 4.5) e escreve a resposta.
---
## Atualizando
Com `study-notes-mcp@latest` no `mcp.json`, basta reiniciar o servidor em **MCP: List Servers**: o `npx` baixa a versão nova sozinho.
---
## A partir do código-fonte
Para estudar o código, contribuir ou testar mudanças antes de publicar:
```bash
git clone https://github.com/wasinskivini/study-notes-mcp.git
cd study-notes-mcp
npm install
npm test
```
No `mcp.json`, troque `command` e `args` pelo Node rodando o TypeScript direto (sem build):
```json
"command": "node",
"args": ["/Users/seu-usuario/study-notes-mcp/src/index.ts"]
```
Para atualizar: `git pull && npm install` e reinicie o servidor.
---
## Desenvolvimento
| Script | Descrição |
|---|---|
| `npm start` | Inicia o servidor direto do TypeScript |
| `npm run build` | Compila para JavaScript em `dist/` (o que vai para o npm; roda sozinho no `npm pack`/`npm publish`) |
| `npm run dev` | Inicia com file-watch e inspector do Node.js |
| `npm test` | Roda os testes de integração |
| `npm run typecheck` | Verifica os tipos com `tsc` (o Node só remove os tipos, não os checa) |
| `npm run test:dev` | Testes em modo watch |
| `npm run test:semantic` | Testes da busca semântica e a avaliação das buscas, com o modelo de verdade (baixa ~145 MB na primeira vez) |
| `npm run test:pack` | Gera o pacote do npm, instala numa pasta vazia e sobe o servidor por ele |
| `npm run mcp:inspect` | Abre o MCP Inspector usando as anotações de exemplo de `tests/fixtures` |
Os testes sobem o servidor via stdio com uma cópia temporária de `tests/fixtures/notas` e um `DATA_DIR` temporário, então não tocam nos seus dados reais. Os casos de ataque (`.env`, symlink para fora, pasta oculta, segredo no código, arquivo com injeção e caracteres invisíveis) e os PDFs de teste (com várias páginas, escaneado e com injeção) são criados na hora pelos testes.
A cada push na `main` e em todo pull request, o GitHub Actions roda `typecheck` e `test` no Ubuntu, no macOS e no Windows, com Node 22 e 24. Dois jobs separados testam o pacote do npm (todos os testes contra o `dist/` compilado, com `TEST_DIST=1`, mais o `test:pack`) e a busca semântica com o modelo de verdade.
### Contribuindo
Contribuições são bem-vindas! O passo a passo completo — fork, branch, convenções do código, testes e pull request — está no **[CONTRIBUTING.md](CONTRIBUTING.md)**. Em resumo:
1. Faça um fork e crie uma branch (`feat/…`, `fix/…`, `docs/…`)
2. Siga as convenções (separação mcp/service/safety, comentários de passo, testes para tudo)
3. Rode `npm run typecheck && npm test`
4. Abra um pull request — o template traz um checklist
Para bugs e ideias, use as [issues](https://github.com/wasinskivini/study-notes-mcp/issues/new/choose). Falhas de segurança **não** vão em issue pública: veja o [SECURITY.md](SECURITY.md).
---
## Licença
[ISC](LICENSE)
TDQS
Scored across 5 tools
Each tool targets a distinct action: record a doubt, mark a lesson complete, list modules, search notes, and read a file. The search vs. read pair is the only potential overlap, but the descriptions explicitly describe a workflow (search first, then read the indicated pages), which removes ambiguity.
All five tools follow a consistent Portuguese verb_noun pattern in snake_case: registrar_duvida, marcar_aula_concluida, listar_modulos, buscar_anotacao, ler_anotacao. No mixed casing or inconsistent verb styles.
Five tools is well within the appropriate range and each one maps to a distinct study-assistant capability (search, read, list, track progress, record doubts). Nothing appears redundant or bloated.
The read-side surface is solid (list modules, search, read), but the write-side tools are dead ends: there is no way to list or resolve registered doubts, no way to view study progress or unmark a lesson, and no way to list lessons within a module. Agents can partially work around this by reading notes and module folders, but core lifecycle operations are missing.