Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Explicar o problema que o MCP resolve e por que surgiu em novembro de 2024
Descrever a arquitetura de 3 camadas host/client/server e o papel de cada componente
Diferenciar tools, resources e prompts como as 3 primitivas do protocolo
Identificar quando usar MCP versus tool use direto em código
Avaliar o ecossistema atual de clients e servers disponíveis e oportunidades de integração
Por que isso importa
Antes do MCP, conectar um LLM a uma ferramenta externa era um trabalho artesanal. Você escrevia function definitions no formato da OpenAI, ou tool schemas no formato da Anthropic, ou function declarations no formato do Google — cada um com suas idiossincrasias. Migrar de modelo significava reescrever todas as integrações. E se você queria que a mesma ferramenta funcionasse com Claude Desktop E com Cursor AND com seu código customizado, tinha que implementar três vezes.
O MCP (Model Context Protocol) resolve isso com um protocolo padronizado, análogo ao que o Language Server Protocol (LSP) fez para IDEs em 2016. O LSP permitiu que você escrevesse um servidor de linguagem uma vez (ex: TypeScript) e ele funcionasse em VS Code, Neovim, Emacs, e qualquer outro editor compatível. O MCP faz o mesmo para LLMs e ferramentas.
Em novembro de 2024, a Anthropic lançou o MCP como padrão aberto. Em seis meses, já havia centenas de servers MCP publicados: para Slack, GitHub, Notion, PostgreSQL, Figma, Google Drive, e dezenas de ferramentas corporativas. Claude Desktop e Cursor foram os primeiros grandes clientes a adotá-lo. Em 2025, qualquer aplicação de IA que precisa de ferramentas externas tem o MCP como primeira escolha de arquitetura.
A importância para você como desenvolvedor: dominar MCP significa que você escreve uma integração que funciona com todos os clientes MCP, hoje e no futuro.
Conceitos Fundamentais
O Problema que o MCP Resolve
Antes do MCP, uma integração típica LLM + ferramenta externa parecia assim:
Aplicação A (Claude) ←→ Integração A (código específico para Claude)
Aplicação B (GPT-4) ←→ Integração B (código específico para GPT-4)
Aplicação C (Cursor) ←→ Integração C (código específico para Cursor)Cada Integração N era duplicação de código para o mesmo serviço (ex: buscar dados do Slack). Com MCP:
Aplicação A (Claude) ──┐
Aplicação B (Cursor) ├──→ MCP Server (Slack) ←→ Slack API
Aplicação C (Windsurf) ┘Um server, N clients. Escreve uma vez, funciona em todos.
A Arquitetura de 3 Camadas
┌─────────────────────────────────────────────────┐
│ HOST APPLICATION │
│ (Claude Desktop, Cursor, seu app customizado) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │MCP Client│ │MCP Client│ │MCP Client│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└────────│─────────────│─────────────│────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│MCP Server│ │MCP Server│ │MCP Server│
│ Slack │ │ GitHub │ │ Postgres │
└──────────┘ └──────────┘ └──────────┘Host: a aplicação que contém o LLM e gerencia os clients MCP. Claude Desktop é um host. Cursor é um host. Seu próprio backend Express pode ser um host.
Client: componente dentro do host que mantém uma conexão 1-para-1 com um server. Cada server tem seu próprio client. O client é responsável por descobrir capabilities do server, fazer chamadas, e retornar resultados.
Server: processo que expõe tools, resources e/ou prompts para o client consumir. Pode ser um processo separado (comunicação via stdio ou HTTP/SSE) ou in-process (InMemoryTransport para testes).
As 3 Primitivas do MCP
Tools (Ferramentas): ações que o LLM pode executar.
Características: - Têm um nome, descrição, e schema de input (JSON Schema) - São chamadas pelo LLM quando ele decide que são relevantes para a tarefa - Produzem um resultado (texto, dados estruturados, erro) - Podem ter efeitos colaterais (escrever no banco, enviar email, etc.)
Exemplos: buscar_produto(id), criar_ticket(titulo, prioridade), enviar_email(para, assunto, corpo)
Resources (Recursos): dados que o host pode ler do server.
Características: - São identificados por URI (ex: file:///docs/manual.pdf, postgres://db/schema) - São lidos sob demanda pelo host/usuário, não chamados pelo LLM diretamente - Podem ser texto, JSON, binário - Análogo a endpoints GET de uma API REST
Exemplos: config://empresa.json, file:///logs/2025-01.log, db://clientes/schema
Prompts (Templates de Prompt): templates pré-definidos que o host pode usar.
Características: - Encapsulam padrões de prompt reutilizáveis - Podem aceitar argumentos para customização - Selecionados pelo usuário ou pelo host, não pelo LLM
Exemplos: template para análise de código, template para sumarização de documento, template para revisão de PR
Transport Layers: Como Client e Server Se Comunicam
O protocolo MCP é independente do transport. Dois transports são definidos na spec:
stdio: server roda como processo filho, comunicação via stdin/stdout. Mais simples para desenvolvimento e desktop applications.
Host → [spawn processo] → Server
Client ←→ stdin/stdout ←→ ServerHTTP + SSE: server é um processo HTTP separado. Client faz requests HTTP, server envia eventos via SSE. Melhor para produção e ambientes distribuídos.
Client → POST /messages → Server
Client ← SSE events ← ServerInMemoryTransport: para testes e prototipagem, client e server compartilham memória diretamente. Zero latência de rede. Muito útil para labs e testes unitários.
O Protocolo Subjacente: JSON-RPC 2.0
O MCP usa JSON-RPC 2.0 como protocolo de mensagens. Cada mensagem é um JSON com:
// Request (client → server)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "buscar_produto",
"arguments": { "id": "P001" }
}
}
// Response (server → client)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "{\"id\":\"P001\",\"nome\":\"Notebook Pro\"}" }
]
}
}Como desenvolvedor, você raramente escreve JSON-RPC diretamente — o SDK TypeScript abstrai isso completamente. Mas entender o protocolo subjacente é útil para debugging.
Aprofundamento Técnico
Lifecycle de uma Conexão MCP
1. INITIALIZE
Client → Server: {"method": "initialize", "params": {"protocolVersion": "0.1.0", "clientInfo": {...}}}
Server → Client: {"result": {"protocolVersion": "0.1.0", "serverInfo": {...}, "capabilities": {...}}}
2. DISCOVERY
Client → Server: {"method": "tools/list"}
Server → Client: {"result": {"tools": [...]}}
3. OPERATION (repetido)
Client → Server: {"method": "tools/call", "params": {"name": "...", "arguments": {...}}}
Server → Client: {"result": {"content": [...]}}
4. SHUTDOWN
Client → Server: fecha conexãoNa prática com o SDK TypeScript:
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
// Conecta client ao server
await client.connect(clientTransport)
// Discovery
const { tools } = await client.listTools()
console.log('Tools disponíveis:', tools.map(t => t.name))
// Operação
const resultado = await client.callTool({ name: 'minha_tool', arguments: { param: 'valor' } })
console.log('Resultado:', resultado.content[0].text)Capabilities: O Sistema de Negociação
No handshake de inicialização, client e server negociam capabilities — o que cada um suporta:
// Server declara o que suporta
const server = new Server(
{ name: 'meu-server', version: '1.0.0' },
{
capabilities: {
tools: {}, // suporta tools
resources: {}, // suporta resources
prompts: {}, // suporta prompts
// logging: {} // se quiser suporte a logs estruturados
}
}
)Client só tentará usar capabilities que o server declarou suportar. Isso permite evolução do protocolo de forma backward-compatible.
MCP vs Tool Use Direto: Quando Usar Cada Um
Situação | Use Tool Use Direto | Use MCP |
Prototipagem rápida | ✓ | |
Uma ferramenta, um LLM, uma app | ✓ | |
Ferramenta usada por múltiplos clientes LLM | ✓ | |
Integração deve persistir entre sessões de agente | ✓ | |
Equipe diferente mantém a ferramenta | ✓ | |
Deploy independente da aplicação principal | ✓ | |
Ferramentas precisam ser descobertas dinamicamente | ✓ |
Exemplos Anotados
Exemplo 1: Comparação de Código — Antes e Depois do MCP
Antes do MCP (Tool Use Direto — específico para Anthropic):
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic()
// Tool definida no formato Anthropic
const tools = [{
name: 'buscar_clima',
description: 'Retorna o clima de uma cidade',
input_schema: {
type: 'object' as const,
properties: { cidade: { type: 'string' } },
required: ['cidade'],
},
}]
// Execução manual no código
function executarTool(nome: string, args: Record<string, unknown>) {
if (nome === 'buscar_clima') {
const { cidade } = args as { cidade: string }
return `Clima em ${cidade}: 25°C, ensolarado` // mock
}
}
// Agent loop manual
const mensagens = [{ role: 'user' as const, content: 'Qual o clima em SP?' }]
let response = await client.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 300, tools, messages: mensagens })
while (response.stop_reason === 'tool_use') {
const toolUse = response.content.find(b => b.type === 'tool_use')
if (toolUse?.type === 'tool_use') {
const resultado = executarTool(toolUse.name, toolUse.input as Record<string, unknown>)
mensagens.push({ role: 'assistant', content: response.content })
mensagens.push({
role: 'user',
content: [{ type: 'tool_result', tool_use_id: toolUse.id, content: resultado }]
})
response = await client.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 300, tools, messages: mensagens })
}
}Com MCP (portável, reutilizável):
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
// Server: definido uma vez, funciona com qualquer cliente MCP
const server = new Server({ name: 'clima', version: '1.0.0' }, { capabilities: { tools: {} } })
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'buscar_clima',
description: 'Retorna clima de uma cidade',
inputSchema: { type: 'object', properties: { cidade: { type: 'string' } }, required: ['cidade'] },
}]
}))
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { cidade } = req.params.arguments as { cidade: string }
return { content: [{ type: 'text', text: `Clima em ${cidade}: 25°C, ensolarado` }] }
})
// Client: pode ser qualquer cliente MCP (Claude Desktop, Cursor, código custom)
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
const mcpClient = new Client({ name: 'meu-client', version: '1.0.0' }, { capabilities: {} })
await server.connect(serverTransport)
await mcpClient.connect(clientTransport)
// Agora qualquer cliente MCP pode usar este server
const { tools } = await mcpClient.listTools()
const resultado = await mcpClient.callTool({ name: 'buscar_clima', arguments: { cidade: 'São Paulo' } })
console.log(resultado.content[0].text) // "Clima em São Paulo: 25°C, ensolarado"Exemplo 2: Ecossistema de Servers MCP Públicos
// Exemplos de servers MCP disponíveis no ecossistema (2025)
const serversPopulares = {
// Desenvolvimento
'filesystem': {
url: 'npm install @modelcontextprotocol/server-filesystem',
tools: ['read_file', 'write_file', 'list_directory', 'search_files'],
uso: 'Acesso ao sistema de arquivos local'
},
'github': {
url: 'npm install @modelcontextprotocol/server-github',
tools: ['create_pull_request', 'list_issues', 'search_code', 'get_file_contents'],
uso: 'Integração com GitHub API'
},
'postgres': {
url: 'npm install @modelcontextprotocol/server-postgres',
tools: ['execute_query', 'list_tables', 'describe_table'],
uso: 'Queries SQL em PostgreSQL'
},
// Produtividade
'slack': {
tools: ['send_message', 'list_channels', 'search_messages'],
uso: 'Integração com Slack'
},
'notion': {
tools: ['create_page', 'search_pages', 'update_block'],
uso: 'Integração com Notion'
},
'google-drive': {
tools: ['list_files', 'read_file', 'create_file'],
uso: 'Integração com Google Drive'
},
}
// Configuração no Claude Desktop (claude_desktop_config.json):
const exemploClaude = {
mcpServers: {
filesystem: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/Users/me/projects'],
},
postgres: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres', 'postgresql://localhost/mydb'],
},
}
}
// Depois disso, Claude Desktop tem acesso automático às tools dos dois serversPadrões e Armadilhas
Padrões
Padrão 1: Tools para ações, Resources para leitura de dados estáticos Tools são para “fazer algo” (criar ticket, buscar ação, calcular). Resources são para “ler algo que não muda frequentemente” (documentação, schema de banco, configuração). Não use tool para retornar documentação estática — use resource.
Padrão 2: Descriptions ricas de tools para o LLM decidir melhor
// POBRE: o LLM não sabe quando usar
{ name: 'get_data', description: 'Obtém dados' }
// RICO: o LLM entende o contexto e como usar
{
name: 'buscar_cliente',
description: 'Busca informações de um cliente por ID (formato: C001) ou nome parcial. Use quando o usuário perguntar sobre um cliente específico, histórico de pedidos, ou conta ativa.',
}Padrão 3: InMemoryTransport para desenvolvimento, stdio/HTTP para produção InMemoryTransport é perfeito para testes e labs — zero configuração, zero latência de rede. Para produção, use stdio (processos desktop) ou HTTP+SSE (servidores web).
Armadilhas
⚠️ Armadilha 1: Server sem cleanup de recursos
// PROBLEMA: server com conexão de banco nunca fechada
const server = new Server(...)
// esqueceu de fechar quando o processo termina
// CORRETO: cleanup explícito
process.on('SIGINT', async () => {
await db.close()
process.exit(0)
})⚠️ Armadilha 2: Tool com side effects não documentados
// PERIGOSO: nome sugere leitura, mas deleta
{ name: 'processar_pedido', description: 'Processa um pedido' }
// O LLM não sabe que isso é destrutivo
// MELHOR: description deixa claro o side effect
{ name: 'processar_pedido', description: 'IRREVERSÍVEL: marca o pedido como processado e envia email de confirmação' }⚠️ Armadilha 3: Tool schema sem validação no handler
// PROBLEMA: confia no input sem validar
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { chave } = req.params.arguments // pode ser undefined ou tipo errado
// CORRETO: valida com Zod antes de usar
const schema = z.object({ chave: z.string().min(1) })
const { chave } = schema.parse(req.params.arguments) // lança se inválidoSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
Esta unidade é conceitual — não tem código de lab. A atividade é de análise e configuração:
Leia o diagrama de arquitetura desta aula e mapeie cada componente para o código que você verá nas próximas unidades
Instale o MCP Inspector para visualizar servers em tempo real:
npx @modelcontextprotocol/inspectorExplore o hub de servers oficiais no GitHub (modelcontextprotocol/servers) — identifique 3 servers que seriam úteis em projetos que você conhece
Mapeie como você poderia expor um sistema que você conhece (API, banco de dados, serviço web) como MCP server — liste as tools que faria sentido expor
Nas próximas unidades você vai implementar cada parte desta arquitetura em código real.
Agora você está pronto para o lab.