Se você trabalha com IA generativa em 2026, provavelmente já esbarrou no Model Context Protocol. O MCP se tornou o padrão da indústria para conectar modelos de linguagem a ferramentas e dados externos, com mais de 97 milhões de downloads mensais dos SDKs oficiais e adoção por empresas como Block, Bloomberg e Amazon.
Mas o que exatamente é o MCP? E mais importante: como você cria seu próprio servidor para expor ferramentas que um agente de IA pode usar? Neste guia vamos partir da arquitetura até um MCP server funcional em TypeScript.
Por que o MCP existe
Antes do MCP, cada ferramenta de IA tinha sua própria forma de se conectar ao mundo externo. O Copilot usava uma API, o Claude Desktop outra, o Cursor uma terceira. Se você queria expor uma funcionalidade para múltiplos agentes, precisava implementar integrações separadas para cada um.
O MCP resolve esse problema da mesma forma que o Language Server Protocol (LSP) resolveu para editores de código. O LSP criou um protocolo único para que qualquer editor pudesse se comunicar com qualquer language server. Antes do LSP, cada editor reimplementava autocomplete, go-to-definition e diagnósticos para cada linguagem. Depois do LSP, bastava um language server por linguagem e um cliente por editor.
O MCP faz o mesmo para agentes de IA. Em vez de N integrações para N ferramentas em M clientes (resultando em N x M implementações), você cria um MCP server por ferramenta e qualquer cliente compatível consegue usá-lo. A Anthropic abriu o protocolo em novembro de 2024, e em pouco mais de um ano empresas como OpenAI, Google DeepMind e Microsoft adotaram o padrão. Em dezembro de 2025, o protocolo foi doado à Agentic AI Foundation sob a Linux Foundation, garantindo governança neutra e aberta.
Arquitetura client-host-server
O MCP segue uma arquitetura com três participantes bem definidos que se comunicam através de duas camadas.
Os três participantes
O MCP Host é a aplicação de IA que orquestra tudo. Pode ser o Claude Desktop, o VS Code com extensão de IA, ou qualquer aplicação que você construa. O host cria e gerencia um ou mais MCP clients.
O MCP Client é um componente dentro do host que mantém uma conexão dedicada com um MCP server específico. Se o host se conecta a três servidores diferentes, ele cria três clients independentes.
O MCP Server é o programa que expõe funcionalidades. Pode rodar localmente (como um servidor de filesystem) ou remotamente (como o MCP server do Sentry). Um server local tipicamente atende um único client, enquanto um server remoto pode atender vários.
As duas camadas
A camada de dados implementa o protocolo JSON-RPC 2.0 que define a estrutura das mensagens. Ela cuida do ciclo de vida da conexão (inicialização, negociação de capacidades, encerramento) e dos primitivos que veremos na próxima seção.
A camada de transporte define como as mensagens trafegam entre client e server. O MCP suporta dois mecanismos:
- stdio para servidores locais, onde a comunicação acontece via stdin/stdout do processo, sem overhead de rede
- Streamable HTTP para servidores remotos, usando HTTP POST para mensagens do client e Server-Sent Events para streaming do server
O handshake de inicialização
Quando um client se conecta a um server, a primeira coisa que acontece é uma negociação de capacidades. O client envia um initialize com a versão do protocolo e suas capabilities. O server responde declarando quais primitivos suporta.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": { "elicitation": {} },
"clientInfo": {
"name": "meu-app",
"version": "1.0.0"
}
}
}
O server responde com seus próprios capabilities, indicando por exemplo que suporta tools e resources. Só depois dessa negociação o client pode começar a descobrir e usar as ferramentas disponíveis.
Os três primitivos: tools, resources e prompts
O MCP define três tipos de primitivos que um server pode expor. Entender cada um é fundamental para projetar bem o seu servidor.
Tools
Tools são funções executáveis que o modelo de IA pode invocar. São o primitivo mais usado. Cada tool tem um nome, uma descrição e um schema JSON que define seus parâmetros de entrada.
server.tool(
"buscar_clima",
"Retorna a previsão do tempo para uma cidade",
{
cidade: z.string().describe("Nome da cidade"),
unidade: z.enum(["celsius", "fahrenheit"]).default("celsius")
},
async ({ cidade, unidade }) => {
const dados = await apiClima.consultar(cidade, unidade);
return {
content: [{ type: "text", text: JSON.stringify(dados) }]
};
}
);
Quando o modelo de IA decide que precisa de uma informação, ele chama tools/call com o nome da tool e os argumentos. O server executa a lógica e retorna o resultado.
Resources
Resources são fontes de dados que fornecem contexto ao modelo. Diferente de tools, resources são passivas. O client faz resources/list para descobrir o que está disponível e resources/read para obter o conteúdo.
Um exemplo clássico é expor o schema de um banco de dados como resource. O modelo pode ler o schema para entender a estrutura antes de gerar queries SQL.
Prompts
Prompts são templates reutilizáveis que ajudam a estruturar interações com o modelo. Um server pode oferecer prompts pré-configurados que o usuário seleciona, como um template para code review ou para análise de segurança.
Na prática, a maioria dos MCP servers foca em tools. Resources e prompts são úteis para cenários mais específicos.
Criando um MCP server em TypeScript
Vamos construir um MCP server que expõe informações sobre repositórios do GitHub. O server terá duas tools: uma para buscar dados de um repositório e outra para listar issues abertas.
Configuração do projeto
Comece criando o projeto e instalando as dependências:
mkdir mcp-github-server
cd mcp-github-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
Configure o tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
Implementando o server
Crie o arquivo src/index.ts com o servidor completo:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "github-info",
version: "1.0.0"
});
const GITHUB_API = "https://api.github.com";
async function githubFetch(path: string) {
const headers: Record<string, string> = {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "mcp-github-server"
};
const token = process.env.GITHUB_TOKEN;
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch(`${GITHUB_API}${path}`, { headers });
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
return response.json();
}
server.tool(
"repo_info",
"Retorna informações sobre um repositório do GitHub",
{
owner: z.string().describe("Dono do repositório"),
repo: z.string().describe("Nome do repositório")
},
async ({ owner, repo }) => {
const data = await githubFetch(`/repos/${owner}/${repo}`);
const info = [
`Repositório: ${data.full_name}`,
`Descrição: ${data.description || "Sem descrição"}`,
`Stars: ${data.stargazers_count}`,
`Forks: ${data.forks_count}`,
`Issues abertas: ${data.open_issues_count}`,
`Linguagem principal: ${data.language || "N/A"}`,
`Criado em: ${data.created_at}`,
`Último push: ${data.pushed_at}`
].join("\n");
return {
content: [{ type: "text", text: info }]
};
}
);
server.tool(
"list_issues",
"Lista issues abertas de um repositório do GitHub",
{
owner: z.string().describe("Dono do repositório"),
repo: z.string().describe("Nome do repositório"),
limit: z.number().min(1).max(30).default(10)
.describe("Número máximo de issues")
},
async ({ owner, repo, limit }) => {
const issues = await githubFetch(
`/repos/${owner}/${repo}/issues?state=open&per_page=${limit}`
);
if (!Array.isArray(issues) || issues.length === 0) {
return {
content: [{ type: "text", text: "Nenhuma issue aberta encontrada." }]
};
}
const formatted = issues.map((issue: any) =>
`#${issue.number} - ${issue.title} (${issue.labels.map((l: any) => l.name).join(", ") || "sem labels"})`
).join("\n");
return {
content: [{ type: "text", text: formatted }]
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
O código acima define um MCP server com duas tools. A classe McpServer do SDK oficial cuida de todo o protocolo JSON-RPC, negociação de capabilities e ciclo de vida. Você só precisa declarar suas tools com nome, descrição, schema de parâmetros (via Zod) e a função de execução.
Repare que o transporte usado é StdioServerTransport. Isso significa que o server vai se comunicar via stdin/stdout, ideal para execução local.
Compilando e testando
Adicione os scripts ao package.json:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Compile e teste:
npm run build
Para testar o servidor sem um client de IA, você pode usar o MCP Inspector, uma ferramenta oficial que permite chamar tools interativamente:
npx @modelcontextprotocol/inspector node dist/index.js
O Inspector abre uma interface no navegador onde você pode ver as tools disponíveis, preencher parâmetros e executar chamadas. É a melhor forma de validar que seu server funciona antes de conectá-lo a um agente.
Conectando ao Claude Desktop
Com o server funcionando, o próximo passo é conectá-lo a um MCP host. Vamos usar o Claude Desktop como exemplo.
Edite o arquivo de configuração do Claude Desktop. No macOS fica em ~/Library/Application Support/Claude/claude_desktop_config.json. No Linux, em ~/.config/Claude/claude_desktop_config.json:
{
"mcpServers": {
"github-info": {
"command": "node",
"args": ["/caminho/completo/para/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_seu_token_aqui"
}
}
}
}
Reinicie o Claude Desktop. Ao abrir uma nova conversa, você verá as tools repo_info e list_issues disponíveis. Agora você pode perguntar coisas como "quantas stars tem o repositório vercel/next.js?" e o Claude vai chamar automaticamente a tool repo_info para buscar a resposta.
O mesmo server funciona com qualquer host compatível com MCP. No Claude Code, por exemplo, a configuração fica no arquivo .claude/settings.json do projeto. No VS Code com extensão Copilot, a configuração fica no settings.json da workspace.
Boas práticas e segurança
Criar um MCP server funcional é apenas o começo. Para produção, existem práticas que fazem a diferença entre um server confiável e um que causa problemas.
Validação rigorosa de entrada
O SDK oficial usa Zod para validação de schemas, mas não confie apenas nisso. Sanitize strings que serão usadas em queries, paths ou URLs. Nunca interpole input do usuário diretamente em comandos do sistema.
server.tool(
"buscar_arquivo",
"Lê o conteúdo de um arquivo no projeto",
{
caminho: z.string()
.refine(p => !p.includes(".."), "Path traversal não permitido")
.refine(p => p.startsWith("/src/"), "Apenas arquivos em /src/")
},
async ({ caminho }) => {
const conteudo = await fs.readFile(caminho, "utf-8");
return {
content: [{ type: "text", text: conteudo }]
};
}
);
Tratamento de erros
Retorne mensagens de erro claras e sem expor detalhes internos. O SDK permite retornar isError: true na resposta de uma tool para indicar falha sem derrubar o server.
try {
const data = await githubFetch(`/repos/${owner}/${repo}`);
return { content: [{ type: "text", text: formatRepo(data) }] };
} catch (error) {
return {
content: [{ type: "text", text: `Erro ao acessar repositório: ${error instanceof Error ? error.message : "erro desconhecido"}` }],
isError: true
};
}
Princípio do menor privilégio
Cada MCP server deve ter acesso apenas ao que precisa. Se o server lê dados do GitHub, ele não precisa de permissão para escrever. Use tokens com scopes mínimos e documente exatamente quais permissões são necessárias.
Timeout e rate limiting
Chamadas externas podem travar. Sempre defina timeouts e, se possível, implemente rate limiting local para evitar exceder limites da API que você consome.
Logging
O MCP suporta logging nativo. Use o primitivo de logging do protocolo para enviar mensagens de debug ao client sem poluir o stdout (que é usado pelo transporte stdio).
Conclusão
O Model Context Protocol transformou a forma como conectamos modelos de IA ao mundo real. Em vez de integrações ad-hoc para cada ferramenta e cada agente, temos um protocolo aberto com governança neutra e adoção massiva.
Neste guia você aprendeu a arquitetura client-host-server, os três primitivos (tools, resources e prompts) e construiu um MCP server funcional em TypeScript. O server que criamos é simples, mas a estrutura é exatamente a mesma usada em produção por empresas que expõem seus serviços via MCP.
O ecossistema está amadurecendo rápido. Já existem mais de 10.000 MCP servers disponíveis, e a tendência é que em breve expor uma API via MCP seja tão natural quanto disponibilizar um endpoint REST. Se você está construindo ferramentas para desenvolvedores ou serviços que interagem com IA, agora é o momento de começar.