MCP Todo List
by vinirz
README.md
# 📋 TODO List API com MCP — Prova de Conceito
---
## Sumário
1. [O que é MCP?](#1-o-que-é-mcp)
2. [Cenário escolhido](#2-cenário-escolhido)
3. [Arquitetura da solução](#3-arquitetura-da-solução)
4. [Exemplo prático de funcionamento](#4-exemplo-prático-de-funcionamento)
5. [MCP vs. Integração tradicional](#5-mcp-vs-integração-tradicional)
6. [Benefícios, riscos e boas práticas](#6-benefícios-riscos-e-boas-práticas)
7. [Como executar](#7-como-executar)
---
## 1. O que é MCP?
O **Model Context Protocol (MCP)** é um protocolo aberto criado pela Anthropic em 2024 que padroniza a forma como modelos de linguagem (LLMs) interagem com sistemas externos — como APIs, bancos de dados, sistemas de arquivos e ferramentas diversas.
### Analogia
Antes do MCP, cada integração de IA com uma ferramenta externa precisava ser construída do zero, de forma proprietária. O MCP funciona como um **"USB-C para IA"**: um conector universal que permite que qualquer modelo compatível se conecte a qualquer ferramenta que implemente o protocolo.
### Como funciona
O MCP define três papéis:
| Papel | Responsabilidade |
|---|---|
| **Host** | Aplicação que hospeda o modelo de IA (ex: Claude Desktop, opencode) |
| **Client** | Componente dentro do Host que gerencia conexões MCP |
| **Server** | Processo externo que expõe ferramentas/recursos ao modelo |
A comunicação entre Client e Server ocorre via **JSON-RPC 2.0** sobre transporte **stdio** (ou HTTP/SSE para servidores remotos). O servidor declara suas capacidades ao iniciar, e o modelo pode chamá-las durante uma conversa.
### Primitivas do protocolo
```
MCP Server pode expor:
├── Tools → funções que o modelo pode chamar (ex: criar_todo)
├── Resources → dados que o modelo pode ler (ex: conteúdo de arquivos)
└── Prompts → templates de prompt reutilizáveis
```
Esta POC utiliza exclusivamente **Tools**, que é a primitiva mais comum e diretamente análoga a chamadas de função em APIs REST.
---
## 2. Cenário escolhido
### Contexto
O cenário implementado é uma **API de gerenciamento de tarefas (TODO List)** exposta ao modelo de linguagem através de um servidor MCP. Isso permite que um assistente de IA gerencie tarefas em linguagem natural, sem que o usuário precise conhecer endpoints, verbos HTTP ou formatos JSON.
### Motivação
Gerenciadores de tarefas são onipresentes em ambientes de desenvolvimento e produtividade. Integrar um assistente de IA capaz de criar, listar, marcar e remover tarefas através de linguagem natural representa uma aplicação real e de valor imediato — especialmente em ferramentas como o **opencode**, onde o desenvolvedor já está em contexto de trabalho.
### Componentes implementados
| Componente | Tecnologia | Descrição |
|---|---|---|
| **API REST** | Node.js + Express | Servidor HTTP com CRUD completo de tarefas |
| **Banco de dados** | SQLite (arquivo) | Persistência leve, sem infraestrutura adicional |
| **MCP Server** | Node.js + `@modelcontextprotocol/sdk` | Exposição das operações como ferramentas ao modelo |
| **Orquestração** | Docker + Docker Compose | Containerização da API |
| **Cliente MCP** | opencode | Assistente de IA que consome o servidor MCP |
### Ferramentas expostas pelo MCP
| Tool | Descrição |
|---|---|
| `list_todos` | Lista todas as tarefas cadastradas |
| `create_todo` | Cria uma nova tarefa com título |
| `toggle_todo` | Alterna o status de conclusão de uma tarefa |
| `delete_todo` | Remove uma tarefa pelo ID |
---
## 3. Arquitetura da solução
### Visão geral
```mermaid
graph TB
subgraph Usuario["👤 Usuário"]
U[Linguagem Natural]
end
subgraph opencode["🖥️ opencode (MCP Host)"]
LLM[Modelo de Linguagem]
MC[MCP Client]
end
subgraph MCP["⚙️ MCP Server (Node.js · stdio)"]
direction TB
S[server.js]
T1[tool: list_todos]
T2[tool: create_todo]
T3[tool: toggle_todo]
T4[tool: delete_todo]
end
subgraph Docker["🐳 Docker Container"]
API[API REST · Express]
DB[(SQLite\ntodos.db)]
end
U -->|pergunta/comando| LLM
LLM -->|decide usar tool| MC
MC -->|JSON-RPC sobre stdio| S
S --> T1 & T2 & T3 & T4
T1 & T2 & T3 & T4 -->|HTTP fetch| API
API <-->|SQL| DB
API -->|JSON response| S
S -->|resultado| MC
MC -->|contexto| LLM
LLM -->|resposta em linguagem natural| U
```
### Fluxo detalhado de uma requisição
```mermaid
sequenceDiagram
actor U as Usuário
participant LLM as Modelo de IA
participant MC as MCP Client
participant MS as MCP Server
participant API as API REST
participant DB as SQLite
U->>LLM: "Cria uma tarefa: Estudar MCP"
LLM->>MC: Tool call: create_todo("Estudar MCP")
MC->>MS: JSON-RPC: tools/call {name: "create_todo", arguments: {title: "Estudar MCP"}}
MS->>API: POST /todos {"title": "Estudar MCP"}
API->>DB: INSERT INTO todos...
DB-->>API: id: 1
API-->>MS: 201 {"id":1, "title":"Estudar MCP", "done":false}
MS-->>MC: {"content":[{"type":"text","text":"{...}"}]}
MC-->>LLM: Resultado da tool
LLM-->>U: "Tarefa criada com sucesso! ID: 1 — 'Estudar MCP'"
```
### Estrutura de arquivos
```
trabalho-5/
├── package.json # Workspace root (pnpm workspaces)
├── pnpm-workspace.yaml # Declara pacotes api/ e mcp/
├── docker-compose.yml # Orquestração da API
│
├── api/ # Pacote: todo-api
│ ├── package.json
│ ├── server.js # API Express + SQLite
│ └── Dockerfile
│
└── mcp/ # Pacote: todo-mcp
├── package.json
├── server.js # MCP Server (stdio transport)
└── claude_desktop_config.example.json
```
---
## 4. Exemplo prático de funcionamento
### Cenário demonstrado
O assistente de IA é utilizado dentro do **opencode** para gerenciar tarefas de um sprint de desenvolvimento, tudo em linguagem natural.
### Interações de exemplo
**Criando tarefas:**
```
Usuário: "Cria três tarefas: implementar autenticação, escrever testes e atualizar documentação"
Assistente: Criando as tarefas...
✅ #1 — "implementar autenticação"
✅ #2 — "escrever testes"
✅ #3 — "atualizar documentação"
Três tarefas adicionadas ao seu backlog!
```
**Listando e gerenciando:**
```
Usuário: "Quais tarefas estão pendentes?"
Assistente: Você tem 3 tarefas pendentes:
○ #1 — implementar autenticação
○ #2 — escrever testes
○ #3 — atualizar documentação
Usuário: "Marquei a autenticação como feita"
Assistente: Tarefa #1 marcada como concluída ✓
```
### Chamadas realizadas pelo modelo (bastidores)
Para a interação acima, o modelo executa automaticamente:
```jsonc
// 1. list_todos
{ "name": "list_todos", "arguments": {} }
// 2. toggle_todo
{ "name": "toggle_todo", "arguments": { "id": 1 } }
```
O modelo interpreta as respostas JSON e as traduz para linguagem natural — o usuário nunca vê os dados brutos.
### Endpoints da API (acesso direto via HTTP)
| Método | Rota | Descrição |
|---|---|---|
| `GET` | `/todos` | Lista todas as tarefas |
| `GET` | `/todos/:id` | Busca uma tarefa |
| `POST` | `/todos` | Cria tarefa (`{ "title": "..." }`) |
| `PUT` | `/todos/:id` | Atualiza tarefa |
| `PATCH` | `/todos/:id/toggle` | Alterna done |
| `DELETE` | `/todos/:id` | Remove tarefa |
---
## 5. MCP vs. Integração tradicional
### Integração tradicional (sem MCP)
Na abordagem convencional, o modelo de IA precisa ser instruído manualmente sobre como usar uma API:
```
Prompt do sistema:
"Você tem acesso a uma API REST de tarefas.
Para listar: GET http://localhost:3000/todos
Para criar: POST http://localhost:3000/todos com body {"title":"..."}
Para deletar: DELETE http://localhost:3000/todos/{id}
Use ferramentas HTTP genéricas para interagir com ela."
```
**Problemas desta abordagem:**
- Instruções no prompt consomem tokens de contexto
- O modelo precisa "adivinhar" detalhes (autenticação, formato, erros)
- Cada integração é proprietária e não reutilizável
- Sem padronização de erros ou tipagem de parâmetros
### Integração via MCP
Com MCP, o servidor declara suas ferramentas com schema preciso:
```json
{
"name": "create_todo",
"description": "Create a new todo item",
"inputSchema": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "Title of the todo item" }
},
"required": ["title"]
}
}
```
### Comparação direta
| Aspecto | Integração Tradicional | Via MCP |
|---|---|---|
| **Configuração** | Prompt manual por API | Declarativa, via schema JSON |
| **Portabilidade** | Específica por modelo/app | Qualquer cliente MCP compatível |
| **Tipagem** | Nenhuma (texto livre) | JSON Schema com validação |
| **Descoberta** | Estática (hardcoded no prompt) | Dinâmica (negociação na conexão) |
| **Tokens de contexto** | Alto consumo (instruções no prompt) | Baixo (tools ficam fora do contexto) |
| **Manutenção** | Alterar API → alterar prompt | Alterar API → alterar só o servidor MCP |
| **Segurança** | Controle limitado | Permissões granulares por tool |
| **Reutilização** | Zero — acoplado ao sistema | Total — qualquer host MCP usa o mesmo server |
### Diagrama comparativo
```mermaid
graph LR
subgraph Tradicional["❌ Integração Tradicional"]
direction TB
M1[Modelo A] -->|prompt customizado| API1[API REST]
M2[Modelo B] -->|prompt diferente| API1
M3[App C] -->|código proprietário| API1
end
subgraph MCP["✅ Via MCP"]
direction TB
MCP_S[MCP Server] -->|HTTP| API2[API REST]
MA[Modelo A] -->|protocolo padrão| MCP_S
MB[Modelo B] -->|protocolo padrão| MCP_S
MC2[App C] -->|protocolo padrão| MCP_S
end
```
---
## 6. Benefícios, riscos e boas práticas
### ✅ Benefícios
**Para o desenvolvedor:**
- **Produtividade**: comandos em linguagem natural substituem sequências de `curl` ou cliques em interfaces
- **Interoperabilidade**: um servidor MCP funciona com Claude Desktop, opencode, Cursor, Zed e qualquer futuro cliente compatível
- **Separação de responsabilidades**: a lógica de negócio fica na API; a "tradução" para o modelo fica no servidor MCP
- **Manutenibilidade**: mudanças na API exigem alterações apenas no servidor MCP, sem reescrever prompts
**Para o modelo de IA:**
- **Context window eficiente**: as definições de tools não ocupam o contexto da conversa
- **Erros estruturados**: o servidor MCP pode retornar erros tipados, que o modelo interpreta corretamente
- **Segurança por design**: o modelo só acessa o que o servidor MCP expõe explicitamente
### ⚠️ Riscos
| Risco | Descrição | Mitigação |
|---|---|---|
| **Prompt Injection** | Dados externos maliciosos podem manipular o modelo através das respostas do servidor MCP | Sanitizar dados antes de retorná-los; validar inputs |
| **Execução não intencional** | O modelo pode chamar tools destrutivas (ex: `delete_todo`) sem confirmação explícita | Configurar `permission: "ask"` no host para operações irreversíveis |
| **Superfície de ataque** | Servidores MCP locais executam código arbitrário — um servidor malicioso em um projeto clonado representa risco real | Auditar `opencode.json` de repositórios de terceiros antes de rodar |
| **Dependência de disponibilidade** | Se a API estiver offline, o servidor MCP falha e o modelo não tem fallback | Implementar timeout e mensagens de erro claras |
| **Vazamento de dados** | O modelo pode incluir dados sensíveis retornados pelas tools em sua resposta | Filtrar dados sensíveis no servidor MCP antes de retornar |
### 📐 Boas práticas
**No servidor MCP:**
- Escrever descrições claras em cada tool — o modelo usa o texto para decidir quando chamá-la
- Retornar erros informativos (não apenas stack traces)
- Usar tipos precisos nos schemas (`z.number()`, `z.string().min(1)`) para evitar chamadas inválidas
- Manter o servidor MCP **stateless** — o estado deve viver na API/banco, não no processo MCP
**Na configuração do host:**
- Usar path absoluto para o executável (`node`) para evitar dependências de PATH
- Definir `environment` com variáveis específicas ao invés de herdar todo o ambiente
- Desabilitar servidores MCP não utilizados com `"enabled": false`
**No design das tools:**
- Preferir **granularidade fina**: uma tool por operação (ao invés de uma tool genérica com `action` como parâmetro)
- Expor apenas o mínimo necessário — não criar tools para operações administrativas sensíveis
- Nomear as tools com verbos claros: `create_todo`, `delete_todo`, não `todo_operation`
---
## 7. Como executar
### Pré-requisitos
- Docker e Docker Compose
- Node.js 18+ com pnpm
- opencode instalado
### 1. Clonar e instalar dependências
```bash
git clone <repositório>
cd trabalho-5
# Instala dependências de todos os pacotes (pnpm workspace)
pnpm install
```
### 2. Subir a API
```bash
docker compose up --build -d
# API disponível em http://localhost:3000
```
### 3. Configurar o opencode
Adicione ao seu `~/.config/opencode/opencode.jsonc`:
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"mcpServers": {
"todo-app": {
"type": "local",
"command": [
"/home/<seu-usuario>/.nvm/versions/node/v22.23.1/bin/node",
"/caminho/para/trabalho-5/mcp/server.js"
],
"environment": {
"TODO_API_URL": "http://localhost:3000"
},
"enabled": true
}
}
}
```
### 4. Testar a API diretamente
```bash
# Criar tarefa
curl -X POST http://localhost:3000/todos \
-H "Content-Type: application/json" \
-d '{"title": "Testar o MCP"}'
# Listar
curl http://localhost:3000/todos
# Marcar como feito
curl -X PATCH http://localhost:3000/todos/1/toggle
# Deletar
curl -X DELETE http://localhost:3000/todos/1
```
### 5. Acessar o banco de dados (Beekeeper Studio)
O arquivo do banco é mapeado via volume Docker em `./data/todos.db`.
Conecte via **SQLite** apontando para esse arquivo.
---
## Referências
- [Model Context Protocol — Documentação oficial](https://modelcontextprotocol.io)
- [MCP SDK for TypeScript/JavaScript](https://github.com/modelcontextprotocol/typescript-sdk)
- [opencode — Configuração de MCP](https://opencode.ai/docs)
- [Anthropic — Introdução ao MCP](https://www.anthropic.com/news/model-context-protocol)
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues