cd /news/developer-tools/mcp-na-pratica-tools-resources-e-qua… · home topics developer-tools article
[ARTICLE · art-31558] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

MCP na prática: Tools, Resources e quando usar cada um

A developer building a Model Context Protocol (MCP) server for a course catalog learned to distinguish between Tools, Resources, and Prompts. The team refactored their V2 implementation, which incorrectly exposed read operations as Tools, into a V3 design where read-only data access uses Resources and write operations use Tools. This change eliminated redundancy and clarified the model's decision criteria.

read7 min views1 publishedJun 17, 2026

Aprendizados de construir um servidor MCP de catálogo de cursos — da POC à produção.

O Model Context Protocol (MCP) é um padrão aberto que permite que aplicações de IA (como o Cursor, Claude Desktop ou outros hosts) se conectem a fontes de dados e ferramentas externas de forma padronizada.

Pense no MCP como uma tomada universal: em vez de cada editor inventar sua própria integração com bancos, APIs e scripts, todos falam o mesmo protocolo — JSON-RPC 2.0 — sobre um transporte (stdio ou HTTP).

┌─────────────┐     JSON-RPC      ┌─────────────┐     HTTP/SQL    ┌─────────────┐
│   Cursor    │ ◄──────────────► │ MCP Server  │ ◄─────────────► │  Backend    │
│  (cliente)  │   tools/call     │  (adaptador)│                 │  (dados)    │
│             │   resources/read │             │                 │             │
└─────────────┘                   └─────────────┘                 └─────────────┘

O MCP não substitui sua API de negócio. Ele é a camada de adaptação entre o modelo de linguagem e o mundo real.

O protocolo expõe três tipos de capacidade. Entender a diferença entre elas é o ponto central deste artigo.

Primitiva Metáfora Quem controla Protocolo
Tool
Verbo — fazer algo
Modelo (com supervisão humana) tools/call
Resource
Substantivo — ler algo
Aplicação / usuário resources/read
Prompt
Template — como fazer
Usuário prompts/get

Tools são funções que o modelo pode chamar. Cada tool tem nome, descrição, schema de entrada (JSON Schema via Zod) e retorna um resultado.

{
  "name": "criar_curso",
  "description": "Cria um novo curso no catálogo.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "titulo": { "type": "string" },
      "cargaHoraria": { "type": "number" }
    },
    "required": ["titulo", "cargaHoraria"]
  }
}

Quando o modelo usa: o usuário pede uma ação — "crie um curso de NestJS com 12 horas" — e o modelo decide invocar criar_curso

.

Características:

isError: true

para o modelo se corrigirResources são dados identificados por URI que o host ou o modelo podem ler para obter contexto.

cursos://catalogo          → catálogo completo (JSON)
cursos://f47ac10b-58cc-... → detalhes de um curso
file:///docs/guia.md       → documento estático

Quando o modelo usa: o usuário quer consultar informação — "quais cursos existem?" — ou o host anexa o resource automaticamente ao contexto.

Características:

cursos://catalogo

) ou cursos://{uuid}

)Prompts são modelos de instrução parametrizados que guiam o modelo por um fluxo conhecido.

/plan-vacation destination=Barcelona duration=7 budget=3000

Quando usar: fluxos repetíveis onde você quer consistência — onboarding, checklists, workflows de revisão.

Pergunta Se a resposta for sim →
A operação altera algo no sistema?
Tool
A operação só lê dados existentes?
Resource
O modelo precisa decidir agir com parâmetros?
Tool
O dado serve como contexto de referência?
Resource
Há validação complexa ou efeito colateral?
Tool
O host pode anexar automaticamente ao chat?
Resource

Na V2 do nosso projeto, tínhamos três tools:

listar_cursos   → leitura (mas exposta como Tool)
buscar_curso    → leitura (mas exposta como Tool)
criar_curso     → escrita ✓

Na V3, separamos corretamente:

Resources:
  cursos://catalogo     → leitura do catálogo
  cursos://{uuid}       → leitura de um curso

Tools:
  criar_curso           → escrita
  atualizar_curso       → escrita
  arquivar_curso        → escrita

Por que mudamos? Porque listar_cursos

e buscar_curso

não tinham efeito colateral — eram consultas disfarçadas de ações. Isso gerava redundância: o modelo podia escolher entre duas formas de fazer a mesma coisa, sem critério claro.

Cenário Exemplo
Criar dados
criar_curso({ titulo, cargaHoraria })
Atualizar dados
atualizar_curso({ id, cargaHoraria: 10 })
Ações de domínio
arquivar_curso({ id }) — não é DELETE, é ação de negócio
Operações com validação
Rejeitar curso arquivado, validar campos obrigatórios
Integrações externas
Enviar email, chamar webhook, processar pagamento
Cálculos ou transformações
Gerar relatório, converter formato
Cenário Use em vez disso
Listar dados sem efeito colateral
Resource (cursos://catalogo )
Consultar um registro por ID
Resource (cursos://{uuid} )
Expor documentação estática
Resource (docs://api-reference )
Anexar contexto ao chat
Resource (application-driven)
Cenário Exemplo
Catálogo ou lista de referência
cursos://catalogo
Detalhe de entidade por URI
cursos://{uuid}
Documentação, schemas, configs
file:///README.md , schema://database
Dados que mudam pouco e servem de contexto
Políticas, glossários, FAQs
O host precisa descobrir o que existe
resources/list + resources/templates/list
Cenário Use em vez disso
Operação que altera estado
Tool
Busca com filtros complexos
Tool (ex.: buscar_cursos_por_categoria )
Ação que exige confirmação humana
Tool
Processamento ou cálculo
Tool
Tipo URI Quando usar
Fixo
cursos://catalogo
Dado único, endereço conhecido
Template
cursos://{uuid}
Parametrizado, N instâncias

O MCP define como cliente e server se comunicam. Isso é independente de Tools/Resources.

Transporte Como funciona Quando usar
stdio
Cursor spawna processo; JSON-RPC via stdin/stdout Dev local, integração com editores
Streamable HTTP
Server HTTP remoto; POST/GET com JSON-RPC Deploy remoto, múltiplos clientes, auth HTTP
{
  "mcpServers": {
    "mcp-cursos": {
      "command": "node",
      "args": ["apps/mcp/dist/mcp.js"],
      "env": {
        "BACKEND_URL": "http://localhost:3000/mcp/v1",
        "API_KEY": "sua-chave"
      }
    }
  }
}

Vantagens: simples, zero config de rede, padrão do ecossistema.

Limitação: processo local — o Cursor precisa spawnar o binário.

Server expõe URL (https://api.exemplo.com/mcp

); auth via header Authorization: Bearer

. Indicado para produção compartilhada entre times.

Um erro comum é colocar toda a lógica dentro do MCP server. A arquitetura que adotamos separa responsabilidades:

┌──────────────────────────────────────────────────────────┐
│  apps/mcp          Adaptador MCP (stdio)                 │
│  - Registra tools e resources                            │
│  - Traduz MCP → HTTP                                     │
│  - Não conhece banco de dados                            │
└────────────────────────┬─────────────────────────────────┘
                         │ HTTP + API Key
                         ▼
┌──────────────────────────────────────────────────────────┐
│  apps/backend      API de negócio (NestJS)               │
│  - REST /mcp/v1/cursos                                   │
│  - Auth, validação, regras de domínio                    │
│  - TypeORM + SQLite                                      │
└──────────────────────────────────────────────────────────┘

Por quê?

curl

, Postman)A spec MCP não prescreve como o server fala com sua API. REST, gRPC ou chamada in-process — escolha de implementação.

Versão Leitura Escrita Persistência Auth
V1
Tools (listar , buscar )
Tool (criar )
Mock in-memory
V2
Tools (listar , buscar )
Tool (criar )
SQLite + backend HTTP API Key
V3
Resources (cursos://… )
Tools (criar , atualizar , arquivar )
SQLite + soft delete API Key

A V3 aplicou a regra: Resource = ler, Tool = agir.

Em vez de deletar cursos, arquivamosarquivado: true

. O curso some do catálogo ativo, mas continua consultável por URI.

Onde Comportamento
cursos://catalogo
Lista só cursos ativos
cursos://{uuid} arquivado
Retorna curso com arquivado: true
atualizar_curso em arquivado
Bloqueado — exige reativar antes (futuro)
arquivar_curso
POST /cursos/:id/arquivar — ação explícita

atualizar_curso

aceita titulo

e/ou cargaHoraria

— pelo menos um obrigatório. Alinha com PATCH REST e evita reenviar dados desnecessários.

A spec MCP permite que o server notifique o cliente quando resources mudam (resources/subscribe

, resources/listChanged

).

Com subscribe: após criar_curso

, o Cursor poderia atualizar cursos://catalogo

automaticamente no contexto.

Sem subscribe (nossa V3): o modelo relê o resource quando o usuário pede — "mostra o catálogo atualizado".

Para a maioria dos casos, relê quando necessário é suficiente. Subscribe faz sentido quando o catálogo muda constantemente e o host precisa de refresh automático.

Antes de implementar, pergunte:

MCP não é magia — é protocolo. Tools são verbos, Resources são substantivos, Prompts são roteiros. Separar leitura de escrita torna seu server previsível para o modelo e mais fácil de manter para você.

Comece simples: stdio, poucas tools, mock ou API mínima. Evolua para Resources quando a leitura passiva fizer sentido, e para HTTP remoto quando precisar compartilhar o backend entre clientes.

O catálogo de cursos que construímos passou por três versões até chegar nesse modelo. Cada iteração ensinou algo — e documentar essas decisões (ADRs, glossário, este post) evita que o próximo dev reinvente a roda.

Escrito com base na implementação do projeto mcp-cursos — grill sessions, ADRs e código real.

── more in #developer-tools 4 stories · sorted by recency
── more on @model context protocol 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-na-pratica-tools…] indexed:0 read:7min 2026-06-17 ·