惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
M
MIT News - Artificial intelligence
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
IT之家
IT之家
F
Fortinet All Blogs
博客园 - 聂微东
U
Unit 42
Martin Fowler
Martin Fowler
腾讯CDC
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
MCP na prática: Tools, Resources e quando usar cada um
Higor Morais · 2026-06-18 · via DEV Community

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


O que é o Model Context Protocol (MCP)?

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.


As três primitivas do MCP

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 — ações invocáveis

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:

  • Pode ter efeito colateral (criar, atualizar, deletar, enviar email)
  • Retorna erro estruturado com isError: true para o modelo se corrigir
  • O humano deve estar no loop (aprovação, logs, confirmação)

Resources — dados passivos legíveis

Resources 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:

  • Somente leitura (por design)
  • Identificados por URI (esquemas customizados são permitidos)
  • Podem ser fixos (cursos://catalogo) ou templates (cursos://{uuid})
  • Application-driven: o host decide como exibir e quando incluir no contexto

Prompts — templates reutilizáveis

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.


A regra de ouro: Tool vs Resource

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
validação complexa ou efeito colateral? Tool
O host pode anexar automaticamente ao chat? Resource

Exemplo concreto: catálogo de cursos

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.


Quando usar Tools

✅ Use Tool quando...

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

❌ Não use Tool quando...

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)

Quando usar Resources

✅ Use Resource quando...

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

❌ Não use Resource quando...

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

Resource fixo vs template

Tipo URI Quando usar
Fixo cursos://catalogo Dado único, endereço conhecido
Template cursos://{uuid} Parametrizado, N instâncias

Quando usar Prompts

✅ Use Prompt quando...

  • Existe um fluxo repetível com parâmetros conhecidos
  • Você quer consistência na forma como o modelo aborda uma tarefa
  • O usuário invoca explicitamente (slash command, palette)

❌ Não use Prompt quando...

  • A tarefa é ad hoc e imprevisível → deixe o modelo usar tools/resources livremente
  • O fluxo depende de muitas variáveis dinâmicas → tools são mais flexíveis

Transporte: stdio vs HTTP

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

stdio — nosso caminho na POC

{
  "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.

Streamable HTTP — evolução futura

Server expõe URL (https://api.exemplo.com/mcp); auth via header Authorization: Bearer. Indicado para produção compartilhada entre times.


Arquitetura: MCP não é o backend

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ê?

  • Backend testável isoladamente (curl, Postman)
  • MCP fino — só adaptação de protocolo
  • Mesma API serve MCP, integrações externas e mobile
  • Deploy independente (MCP local, backend remoto)

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


Evolução do nosso projeto: V1 → V2 → V3

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.


Decisões de domínio que impactam o MCP

Arquivamento (soft delete)

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

Atualização parcial

atualizar_curso aceita titulo e/ou cargaHoraria — pelo menos um obrigatório. Alinha com PATCH REST e evita reenviar dados desnecessários.


Notificações: subscribe e listChanged

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.


Checklist rápido para seu próximo MCP

Antes de implementar, pergunte:

  1. O que é leitura? → Resource com URI clara
  2. O que é ação? → Tool com schema validado
  3. O MCP fala direto com o banco? → Prefira não; use API intermediária
  4. stdio ou HTTP? → stdio para local; HTTP para remoto
  5. Preciso de notificações? → Comece sem; adicione se o refresh manual incomodar
  6. Tools de leitura coexistem com Resources? → Evite redundância

Referências


Conclusão

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.