{"slug": "mcp-na-pratica-tools-resources-e-quando-usar-cada-um", "title": "MCP na prática: Tools, Resources e quando usar cada um", "summary": "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.", "body_md": "*Aprendizados de construir um servidor MCP de catálogo de cursos — da POC à produção.*\n\nO **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.\n\nPense 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).\n\n```\n┌─────────────┐     JSON-RPC      ┌─────────────┐     HTTP/SQL    ┌─────────────┐\n│   Cursor    │ ◄──────────────► │ MCP Server  │ ◄─────────────► │  Backend    │\n│  (cliente)  │   tools/call     │  (adaptador)│                 │  (dados)    │\n│             │   resources/read │             │                 │             │\n└─────────────┘                   └─────────────┘                 └─────────────┘\n```\n\nO MCP **não substitui** sua API de negócio. Ele é a **camada de adaptação** entre o modelo de linguagem e o mundo real.\n\nO protocolo expõe três tipos de capacidade. Entender a diferença entre elas é o ponto central deste artigo.\n\n| Primitiva | Metáfora | Quem controla | Protocolo |\n|---|---|---|---|\nTool |\nVerbo — fazer algo\n|\nModelo (com supervisão humana) | `tools/call` |\nResource |\nSubstantivo — ler algo\n|\nAplicação / usuário | `resources/read` |\nPrompt |\nTemplate — como fazer\n|\nUsuário | `prompts/get` |\n\nTools 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.\n\n```\n{\n  \"name\": \"criar_curso\",\n  \"description\": \"Cria um novo curso no catálogo.\",\n  \"inputSchema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"titulo\": { \"type\": \"string\" },\n      \"cargaHoraria\": { \"type\": \"number\" }\n    },\n    \"required\": [\"titulo\", \"cargaHoraria\"]\n  }\n}\n```\n\n**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`\n\n.\n\n**Características:**\n\n`isError: true`\n\npara o modelo se corrigirResources são **dados identificados por URI** que o host ou o modelo podem **ler** para obter contexto.\n\n```\ncursos://catalogo          → catálogo completo (JSON)\ncursos://f47ac10b-58cc-... → detalhes de um curso\nfile:///docs/guia.md       → documento estático\n```\n\n**Quando o modelo usa:** o usuário quer **consultar** informação — *\"quais cursos existem?\"* — ou o host anexa o resource automaticamente ao contexto.\n\n**Características:**\n\n`cursos://catalogo`\n\n) ou `cursos://{uuid}`\n\n)Prompts são **modelos de instrução** parametrizados que guiam o modelo por um fluxo conhecido.\n\n```\n/plan-vacation destination=Barcelona duration=7 budget=3000\n```\n\n**Quando usar:** fluxos repetíveis onde você quer **consistência** — onboarding, checklists, workflows de revisão.\n\n| Pergunta | Se a resposta for sim → |\n|---|---|\nA operação altera algo no sistema? |\nTool |\nA operação só lê dados existentes? |\nResource |\nO modelo precisa decidir agir com parâmetros? |\nTool |\nO dado serve como contexto de referência? |\nResource |\nHá validação complexa ou efeito colateral? |\nTool |\nO host pode anexar automaticamente ao chat? |\nResource |\n\nNa **V2** do nosso projeto, tínhamos três tools:\n\n```\nlistar_cursos   → leitura (mas exposta como Tool)\nbuscar_curso    → leitura (mas exposta como Tool)\ncriar_curso     → escrita ✓\n```\n\nNa **V3**, separamos corretamente:\n\n```\nResources:\n  cursos://catalogo     → leitura do catálogo\n  cursos://{uuid}       → leitura de um curso\n\nTools:\n  criar_curso           → escrita\n  atualizar_curso       → escrita\n  arquivar_curso        → escrita\n```\n\n**Por que mudamos?** Porque `listar_cursos`\n\ne `buscar_curso`\n\nnã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.\n\n| Cenário | Exemplo |\n|---|---|\nCriar dados |\n`criar_curso({ titulo, cargaHoraria })` |\nAtualizar dados |\n`atualizar_curso({ id, cargaHoraria: 10 })` |\nAções de domínio |\n`arquivar_curso({ id })` — não é DELETE, é ação de negócio |\nOperações com validação |\nRejeitar curso arquivado, validar campos obrigatórios |\nIntegrações externas |\nEnviar email, chamar webhook, processar pagamento |\nCálculos ou transformações |\nGerar relatório, converter formato |\n\n| Cenário | Use em vez disso |\n|---|---|\nListar dados sem efeito colateral |\nResource (`cursos://catalogo` ) |\nConsultar um registro por ID |\nResource (`cursos://{uuid}` ) |\nExpor documentação estática |\nResource (`docs://api-reference` ) |\nAnexar contexto ao chat |\nResource (application-driven) |\n\n| Cenário | Exemplo |\n|---|---|\nCatálogo ou lista de referência |\n`cursos://catalogo` |\nDetalhe de entidade por URI |\n`cursos://{uuid}` |\nDocumentação, schemas, configs |\n`file:///README.md` , `schema://database`\n|\nDados que mudam pouco e servem de contexto |\nPolíticas, glossários, FAQs |\nO host precisa descobrir o que existe |\n`resources/list` + `resources/templates/list`\n|\n\n| Cenário | Use em vez disso |\n|---|---|\nOperação que altera estado |\nTool |\nBusca com filtros complexos |\nTool (ex.: `buscar_cursos_por_categoria` ) |\nAção que exige confirmação humana |\nTool |\nProcessamento ou cálculo |\nTool |\n\n| Tipo | URI | Quando usar |\n|---|---|---|\nFixo |\n`cursos://catalogo` |\nDado único, endereço conhecido |\nTemplate |\n`cursos://{uuid}` |\nParametrizado, N instâncias |\n\nO MCP define **como** cliente e server se comunicam. Isso é independente de Tools/Resources.\n\n| Transporte | Como funciona | Quando usar |\n|---|---|---|\nstdio |\nCursor spawna processo; JSON-RPC via stdin/stdout | Dev local, integração com editores |\nStreamable HTTP |\nServer HTTP remoto; POST/GET com JSON-RPC | Deploy remoto, múltiplos clientes, auth HTTP |\n\n```\n{\n  \"mcpServers\": {\n    \"mcp-cursos\": {\n      \"command\": \"node\",\n      \"args\": [\"apps/mcp/dist/mcp.js\"],\n      \"env\": {\n        \"BACKEND_URL\": \"http://localhost:3000/mcp/v1\",\n        \"API_KEY\": \"sua-chave\"\n      }\n    }\n  }\n}\n```\n\n**Vantagens:** simples, zero config de rede, padrão do ecossistema.\n\n**Limitação:** processo local — o Cursor precisa spawnar o binário.\n\nServer expõe URL (`https://api.exemplo.com/mcp`\n\n); auth via header `Authorization: Bearer`\n\n. Indicado para **produção compartilhada** entre times.\n\nUm erro comum é colocar toda a lógica dentro do MCP server. A arquitetura que adotamos separa responsabilidades:\n\n```\n┌──────────────────────────────────────────────────────────┐\n│  apps/mcp          Adaptador MCP (stdio)                 │\n│  - Registra tools e resources                            │\n│  - Traduz MCP → HTTP                                     │\n│  - Não conhece banco de dados                            │\n└────────────────────────┬─────────────────────────────────┘\n                         │ HTTP + API Key\n                         ▼\n┌──────────────────────────────────────────────────────────┐\n│  apps/backend      API de negócio (NestJS)               │\n│  - REST /mcp/v1/cursos                                   │\n│  - Auth, validação, regras de domínio                    │\n│  - TypeORM + SQLite                                      │\n└──────────────────────────────────────────────────────────┘\n```\n\n**Por quê?**\n\n`curl`\n\n, Postman)A **spec MCP não prescreve** como o server fala com sua API. REST, gRPC ou chamada in-process — escolha de implementação.\n\n| Versão | Leitura | Escrita | Persistência | Auth |\n|---|---|---|---|---|\nV1 |\nTools (`listar` , `buscar` ) |\nTool (`criar` ) |\nMock in-memory | — |\nV2 |\nTools (`listar` , `buscar` ) |\nTool (`criar` ) |\nSQLite + backend HTTP | API Key |\nV3 |\nResources (`cursos://…` ) |\nTools (`criar` , `atualizar` , `arquivar` ) |\nSQLite + soft delete | API Key |\n\nA V3 aplicou a regra: **Resource = ler, Tool = agir**.\n\nEm vez de deletar cursos, **arquivamos** — `arquivado: true`\n\n. O curso some do catálogo ativo, mas continua consultável por URI.\n\n| Onde | Comportamento |\n|---|---|\n`cursos://catalogo` |\nLista só cursos ativos\n|\n`cursos://{uuid}` arquivado |\nRetorna curso com `arquivado: true`\n|\n`atualizar_curso` em arquivado |\nBloqueado — exige reativar antes (futuro) |\n`arquivar_curso` |\n`POST /cursos/:id/arquivar` — ação explícita |\n\n`atualizar_curso`\n\naceita `titulo`\n\ne/ou `cargaHoraria`\n\n— pelo menos um obrigatório. Alinha com PATCH REST e evita reenviar dados desnecessários.\n\nA spec MCP permite que o server **notifique** o cliente quando resources mudam (`resources/subscribe`\n\n, `resources/listChanged`\n\n).\n\n**Com subscribe:** após `criar_curso`\n\n, o Cursor poderia atualizar `cursos://catalogo`\n\nautomaticamente no contexto.\n\n**Sem subscribe (nossa V3):** o modelo relê o resource quando o usuário pede — *\"mostra o catálogo atualizado\"*.\n\nPara 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.\n\nAntes de implementar, pergunte:\n\nMCP 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ê.\n\nComece 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.\n\nO 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.\n\n*Escrito com base na implementação do projeto mcp-cursos — grill sessions, ADRs e código real.*", "url": "https://wpnews.pro/news/mcp-na-pratica-tools-resources-e-quando-usar-cada-um", "canonical_source": "https://dev.to/higorae/mcp-na-pratica-tools-resources-e-quando-usar-cada-um-13aa", "published_at": "2026-06-17 18:03:32+00:00", "updated_at": "2026-06-17 18:21:38.847227+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-agents", "ai-infrastructure"], "entities": ["Model Context Protocol", "Cursor", "Claude Desktop", "JSON-RPC 2.0", "Zod"], "alternates": {"html": "https://wpnews.pro/news/mcp-na-pratica-tools-resources-e-quando-usar-cada-um", "markdown": "https://wpnews.pro/news/mcp-na-pratica-tools-resources-e-quando-usar-cada-um.md", "text": "https://wpnews.pro/news/mcp-na-pratica-tools-resources-e-quando-usar-cada-um.txt", "jsonld": "https://wpnews.pro/news/mcp-na-pratica-tools-resources-e-quando-usar-cada-um.jsonld"}}