Skip to main content
Glama
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)