
O que é MCP e para que serve
Introdução
O Model Context Protocol (MCP) é um protocolo aberto desenvolvido pela Anthropic que padroniza a forma como aplicações de IA se conectam com fontes de dados externas. Pense nele como um USB-C para integrações de IA: ao invés de criar um adaptador específico para cada fonte de dados, você tem um padrão universal que funciona em qualquer lugar.
Lançado em novembro de 2024, o MCP resolve um problema crítico do desenvolvimento com LLMs: a fragmentação das integrações. Antes dele, cada ferramenta precisava implementar suas próprias conexões customizadas, resultando em código duplicado e manutenção complexa.
Arquitetura básica
Perfeito, vou usar uma analogia que deixa tudo mais claro:
Arquitetura básica
O MCP funciona como um sistema de telefonia corporativa:
Hosts são as empresas que precisam fazer ligações externas. No nosso caso, ferramentas como VS Code com Cline, Claude Desktop ou Zed. Cada empresa tem uma central telefônica interna que gerencia as ligações.
MCP Client é a central telefônica da empresa. Você não vê ela funcionando, mas quando alguém na empresa precisa ligar para fora, a central pega o número, disca, estabelece a conexão e gerencia a chamada. Se a empresa precisa falar com 5 fornecedores diferentes, a central gerencia as 5 linhas simultaneamente.
Servers são os fornecedores externos que você liga. Cada um oferece serviços específicos: um fornece dados do PostgreSQL, outro acessa o Google Drive, outro conecta com o GitHub. Cada fornecedor atende ligações de múltiplas empresas ao mesmo tempo.
Quando você configura um server MCP, você está basicamente adicionando um número na agenda da central telefônica. A central (client) cuida de discar, manter a linha aberta e traduzir as conversas para um formato que ambos os lados entendem.
A comunicação acontece via JSON-RPC 2.0, tanto por stdio (linha direta) quanto SSE (linha compartilhada), permitindo flexibilidade na implementação.
Principais primitivas
O protocolo define três primitivas fundamentais:
Resources são dados que podem ser lidos pelos LLMs. Podem ser arquivos, registros de banco, páginas web ou qualquer conteúdo contextual. O server expõe URIs que o client pode solicitar.
Tools são funções que o LLM pode executar. Diferente de resources que são passivos, tools permitem ações: criar registros, executar queries, fazer requisições HTTP. O LLM decide quando e como usar cada tool baseado no contexto.
Prompts são templates reutilizáveis que estruturam interações. Eles encapsulam workflows comuns e podem incluir resources e tools predefinidos.
Casos de uso práticos
O MCP brilha em cenários onde você precisa conectar LLMs a dados privados ou ferramentas específicas:
Integração com bancos de dados permite que o LLM consulte e manipule dados empresariais sem expor credenciais ou lógica de negócio no prompt. Um server SQLite, por exemplo, pode executar queries com segurança enquanto o LLM apenas decide qual query fazer.
Acesso a sistemas de arquivos locais ou remotos facilita análise de documentos, processamento de logs e manipulação de configurações sem precisar fazer upload manual de cada arquivo.
Conexão com APIs externas padroniza a forma como o LLM interage com serviços third-party. Um server pode encapsular autenticação, rate limiting e transformação de dados, deixando o LLM focar apenas na lógica de alto nível.
Automação de workflows permite orquestrar múltiplas ferramentas de forma coordenada. O LLM pode buscar dados de um CRM via MCP, processar no Google Sheets via outro server, e enviar notificações via Slack, tudo através do mesmo protocolo.
Por que usar MCP
A principal vantagem é a reutilização. Um server MCP funciona com qualquer host compatível. Você escreve a integração uma vez e ela funciona no Claude Desktop, na sua CLI customizada ou em qualquer outra ferramenta que implemente o protocolo.
A separação de responsabilidades também é clara: o server cuida de autenticação, segurança e acesso aos dados. O LLM apenas decide o que fazer com as capacidades disponíveis. Isso mantém prompts limpos e focados na lógica de negócio.
O ecossistema está crescendo rápido. A Anthropic mantém servers oficiais para casos comuns, e a comunidade já criou dezenas de implementações para diversos serviços e plataformas.
Implementando um MCP Server
Anatomia de um server
Vamos construir um server MCP funcional para gerenciar uma lista de tarefas. A implementação usa o SDK oficial da Anthropic em TypeScript, mas o conceito se aplica a qualquer linguagem.
Todo server MCP precisa declarar suas capacidades durante a inicialização. O host pergunta o que o server oferece, e o server responde com a lista de resources, tools e prompts disponíveis.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{
name: "todo-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
Definindo tools
Tools são as ações que o LLM pode executar. Cada tool precisa de um schema JSON que descreve seus parâmetros e um handler que implementa a lógica.
// Storage simples em memória
let todos: Array<{ id: number; task: string; done: boolean }> = [];
let nextId = 1;
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "add_todo",
description: "Adiciona uma nova tarefa na lista",
inputSchema: {
type: "object",
properties: {
task: {
type: "string",
description: "Descrição da tarefa",
},
},
required: ["task"],
},
},
{
name: "list_todos",
description: "Lista todas as tarefas",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "complete_todo",
description: "Marca uma tarefa como concluída",
inputSchema: {
type: "object",
properties: {
id: {
type: "number",
description: "ID da tarefa",
},
},
required: ["id"],
},
},
],
}));
O handler de execução processa as chamadas dos tools:
server.setRequestHandler(CallToolRequestSchema, async request => {
const { name, arguments: args } = request.params;
switch (name) {
case "add_todo": {
const todo = {
id: nextId++,
task: args.task as string,
done: false,
};
todos.push(todo);
return {
content: [
{
type: "text",
text: `Tarefa adicionada com ID ${todo.id}`,
},
],
};
}
case "list_todos": {
const list = todos
.map(t => `[${t.done ? "x" : " "}] ${t.id}. ${t.task}`)
.join("\n");
return {
content: [
{
type: "text",
text: list || "Nenhuma tarefa cadastrada",
},
],
};
}
case "complete_todo": {
const todo = todos.find(t => t.id === args.id);
if (!todo) {
throw new Error(`Tarefa ${args.id} não encontrada`);
}
todo.done = true;
return {
content: [
{
type: "text",
text: `Tarefa ${todo.id} marcada como concluída`,
},
],
};
}
default:
throw new Error(`Tool desconhecida: ${name}`);
}
});
Inicializando o transport
O server precisa de um transport para se comunicar com o host. Stdio é o mais comum para ferramentas locais:
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Todo MCP Server rodando em stdio");
}
main().catch(error => {
console.error("Erro fatal:", error);
process.exit(1);
});
Configurando no VS Code
Para conectar o server ao VS Code com a extensão Cline (ou qualquer outra ferramenta compatível com MCP como Continue, Claude Desktop, Zed), adicione a configuração no arquivo de settings da extensão.
No caso do Cline, edite o arquivo cline_mcp_settings.json:
{
"mcpServers": {
"todo": {
"command": "node",
"args": ["/caminho/absoluto/para/todo-server/build/index.js"]
}
}
}
Cada host MCP tem sua própria forma de configuração. O Claude Desktop usa claude_desktop_config.json, o Continue tem configuração no settings.json do VS Code, e assim por diante. O importante é que todos seguem o mesmo padrão de declarar o comando e os argumentos para iniciar o server.
Após reiniciar a extensão ou o editor, o ícone de ferramentas mostra os tools disponíveis para uso.
Fluxo completo de uma interação
Vamos ver o que acontece quando você pede ao assistente para gerenciar suas tarefas:
sequenceDiagram
participant User
participant Host as Host MCP (VS Code/Cline)
participant Server as Todo Server
User->>Host: "Adiciona uma tarefa: comprar leite"
Host->>Server: list_tools()
Server-->>Host: [add_todo, list_todos, complete_todo]
Host->>Server: call_tool(add_todo, {task: "comprar leite"})
Server-->>Host: "Tarefa adicionada com ID 1"
Host-->>User: "Adicionei a tarefa 'comprar leite' na sua lista"
User->>Host: "Quais tarefas eu tenho?"
Host->>Server: call_tool(list_todos)
Server-->>Host: "[ ] 1. comprar leite"
Host-->>User: "Você tem 1 tarefa pendente: comprar leite"
User->>Host: "Marca a primeira como concluída"
Host->>Server: call_tool(complete_todo, {id: 1})
Server-->>Host: "Tarefa 1 marcada como concluída"
Host-->>User: "Tarefa concluída! Sua lista está vazia agora"
Input do usuário
Adiciona três tarefas: estudar MCP, escrever artigo e revisar código
Processamento interno
O LLM analisa a request e identifica que precisa usar o tool add_todo três vezes. Ele faz as chamadas sequencialmente:
Request 1
{
"method": "tools/call",
"params": {
"name": "add_todo",
"arguments": {
"task": "estudar MCP"
}
}
}
Response 1
{
"content": [
{
"type": "text",
"text": "Tarefa adicionada com ID 1"
}
]
}
Isso se repete para as outras duas tarefas, gerando IDs 2 e 3.
Output para o usuário
Adicionei as três tarefas na sua lista:
1. estudar MCP
2. escrever artigo
3. revisar código
Quer que eu marque alguma como concluída ou adicione mais alguma tarefa?
Diagrama da arquitetura completa
graph TB
subgraph "Host MCP (VS Code, Claude Desktop, Zed, etc)"
UI[Interface do Usuário]
Client[MCP Client]
end
subgraph "Todo Server (MCP Server)"
Handlers[Request Handlers]
Storage[(In-Memory Storage)]
end
UI -->|"User input"| Client
Client <-->|"JSON-RPC via stdio"| Handlers
Handlers <-->|"Read/Write"| Storage
Client -->|"LLM response"| UI
style UI fill:#e1f5ff
style Client fill:#fff4e1
style Handlers fill:#ffe1f5
style Storage fill:#e1ffe1
Expandindo com resources
Além de tools, podemos expor a lista de tarefas como um resource que o LLM pode ler a qualquer momento:
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "todo://list",
name: "Lista de Tarefas",
description: "Todas as tarefas cadastradas",
mimeType: "text/plain",
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async request => {
const { uri } = request.params;
if (uri === "todo://list") {
const content = todos
.map(t => `${t.id}. [${t.done ? "DONE" : "PENDING"}] ${t.task}`)
.join("\n");
return {
contents: [
{
uri,
mimeType: "text/plain",
text: content || "Nenhuma tarefa",
},
],
};
}
throw new Error(`Resource desconhecido: ${uri}`);
});
Agora o LLM pode ler o resource automaticamente quando precisar de contexto sobre as tarefas, sem precisar chamar explicitamente o tool list_todos.
Boas práticas e considerações de segurança
Quando você expõe dados e funcionalidades através de MCP, algumas precauções são essenciais para manter a integridade do sistema.
Validação de inputs
Nunca confie cegamente nos argumentos que chegam nos tools. O LLM pode interpretar mal uma instrução do usuário ou gerar valores inesperados:
case "complete_todo": {
const id = args.id as number;
if (typeof id !== "number" || id < 1) {
throw new Error("ID inválido");
}
const todo = todos.find((t) => t.id === id);
if (!todo) {
throw new Error(`Tarefa ${id} não encontrada`);
}
todo.done = true;
return {
content: [{ type: "text", text: `Tarefa ${id} concluída` }]
};
}
Limites e rate limiting
Para servers que fazem chamadas externas ou operações custosas, implemente controles de uso:
let requestCount = 0;
const MAX_REQUESTS_PER_MINUTE = 60;
setInterval(() => {
requestCount = 0;
}, 60000);
server.setRequestHandler(CallToolRequestSchema, async request => {
if (++requestCount > MAX_REQUESTS_PER_MINUTE) {
throw new Error("Rate limit excedido, tente novamente em alguns segundos");
}
// processamento normal
});
Persistência de dados
O exemplo usa memória volátil, mas servers reais precisam persistir dados. SQLite é uma opção simples para começar:
import Database from "better-sqlite3";
const db = new Database("todos.db");
db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task TEXT NOT NULL,
done INTEGER DEFAULT 0
)
`);
const addTodo = db.prepare("INSERT INTO todos (task) VALUES (?)");
const listTodos = db.prepare("SELECT * FROM todos");
const completeTodo = db.prepare("UPDATE todos SET done = 1 WHERE id = ?");
Gestão de erros
Sempre retorne mensagens de erro claras para o LLM. Isso ajuda ele a entender o que deu errado e como corrigir:
try {
// operação perigosa
} catch (error) {
return {
content: [
{
type: "text",
text: `Erro ao processar: ${error.message}. Verifique os parâmetros e tente novamente.`,
},
],
isError: true,
};
}
Segredos e credenciais
Nunca hardcode chaves de API ou senhas no código. Use variáveis de ambiente:
import dotenv from "dotenv";
dotenv.config();
const API_KEY = process.env.EXTERNAL_API_KEY;
if (!API_KEY) {
throw new Error("EXTERNAL_API_KEY não configurada");
}
No arquivo de configuração do host, você pode passar environment variables:
{
"mcpServers": {
"external-service": {
"command": "node",
"args": ["build/index.js"],
"env": {
"EXTERNAL_API_KEY": "sua-chave-aqui"
}
}
}
}
Ecossistema e próximos passos
O MCP está em crescimento acelerado. A Anthropic mantém uma lista de servers oficiais no repositório modelcontextprotocol/servers que incluem integrações com:
- Banco de dados: PostgreSQL, SQLite, MySQL
- Cloud storage: Google Drive, AWS S3
- Ferramentas dev: GitHub, GitLab, Linear
- Produtividade: Slack, Gmail, Google Calendar
- Busca e análise: Brave Search, Exa, Puppeteer
A comunidade também está criando servers para casos específicos. Você pode encontrar implementações em Python, Go, Rust e outras linguagens além do TypeScript.
Se você quer explorar mais:
- SDK oficial: github.com/modelcontextprotocol/typescript-sdk
- Especificação completa: spec.modelcontextprotocol.io
- Exemplos práticos: modelcontextprotocol.io/quickstart
Conclusão
O Model Context Protocol resolve um problema fundamental no desenvolvimento com LLMs: a fragmentação das integrações. Ao invés de cada ferramenta implementar suas próprias conexões customizadas, temos agora um padrão universal que funciona em qualquer host compatível.
A arquitetura baseada em hosts, clients e servers cria uma separação clara de responsabilidades. O LLM decide o que fazer, o server cuida de como fazer, e o protocolo garante que todos falem a mesma língua.
As três primitivas - resources, tools e prompts - cobrem os casos de uso mais comuns: ler dados contextuais, executar ações e estruturar workflows. E a implementação é surpreendentemente direta quando você usa os SDKs oficiais.
O mais interessante é que estamos vendo apenas o começo. Com o crescimento do ecossistema, a tendência é que servers MCP se tornem tão comuns quanto bibliotecas npm ou gems Ruby. Você vai encontrar integrações prontas para praticamente qualquer serviço ou fonte de dados que precise conectar.
Vale a pena investir tempo aprendendo MCP agora. O protocolo ainda está em fase inicial, mas a adoção está crescendo rápido. Empresas já estão construindo ferramentas internas baseadas nele, e a comunidade está criando servers para os mais diversos casos de uso.
Se você trabalha com LLMs em produção, MCP vai eventualmente fazer parte do seu stack. Melhor entender os fundamentos agora do que ter que aprender correndo quando for inevitável.