Cada vez que um agent precisa acessar um novo data source, alguém escreve uma integração custom. GitHub? Integração custom. Jira? Integração custom. Azure Monitor? Integração custom. Banco de dados? Mais uma.
Se isso parece com o problema que REST/HTTP resolveu pra APIs web, você está pensando certo. MCP (Model Context Protocol) é a tentativa de padronizar como AI models e agents se conectam a data sources e ferramentas.
O mapa pro profissional de infra
| Conceito MCP | O que faz | Equivalente em infra |
|---|---|---|
| MCP Server | Expõe dados/ferramentas via protocolo padrão | API server, microserviço |
| MCP Client | Consome dados/ferramentas de MCP servers | API client, SDK |
| Host | Aplicação que roda o client (VS Code, Claude, etc.) | Browser, app que consome APIs |
| Tool | Função executável via MCP | Endpoint REST (POST /action) |
| Resource | Dado acessível via MCP | Endpoint REST (GET /data) |
| Prompt | Template de interação | Slash command, prompt salvo, formulário parametrizado |
| Transport | Como client e server se comunicam | stdio, Streamable HTTP (SSE legado só por compatibilidade) |
O problema N×M
Antes de MCP, cada combinação de (AI application × data source) precisava de código custom.
Com MCP:
É o mesmo valor que USB-C trouxe: um dispositivo implementa USB-C uma vez, funciona em qualquer computador.
Arquitetura do protocolo
Os três primitivos do MCP
1. Tools (ações que o model pode executar)
Tools são funções com input/output definidos. O model decide quando chamá-las.
{
"name": "restart_service",
"description": "Reinicia um serviço systemd no servidor especificado",
"inputSchema": {
"type": "object",
"properties": {
"hostname": {
"type": "string",
"description": "Hostname do servidor"
},
"service_name": {
"type": "string",
"description": "Nome do serviço systemd"
}
},
"required": ["hostname", "service_name"]
}
}
Similar a: um endpoint REST com OpenAPI spec.
2. Resources (dados que o model pode ler)
Resources são URIs que retornam dados. O model (ou host) decide quando acessá-los.
{
"uri": "server://web-prod-01/metrics",
"name": "Métricas de web-prod-01",
"description": "CPU, memória, disco e rede do servidor web-prod-01",
"mimeType": "application/json"
}
Similar a: um endpoint GET com resposta structured.
3. Prompts (templates de interação)
Prompts são templates pré-definidos que o host pode oferecer ao usuário.
{
"name": "diagnose_alert",
"description": "Template pra diagnosticar um alerta de monitoramento",
"arguments": [
{"name": "alert_name", "description": "Nome do alerta", "required": true},
{"name": "resource", "description": "Recurso afetado", "required": true}
]
}
Similar a: um form template que guia o input do usuário.
Implementando um MCP Server
Um MCP server que expõe métricas de Azure Monitor. Aqui eu usei a API atual de alto nível da SDK Python, que registra tools, resources e prompts direto com decorators.
Com Python (SDK oficial)
import os
import subprocess
from typing import Annotated
from mcp.server import MCPServer
from pydantic import Field
SUBSCRIPTION_ID = os.environ["AZURE_SUBSCRIPTION_ID"]
mcp = MCPServer("azure-monitor")
@mcp.tool()
def get_vm_metrics(
resource_group: str,
vm_name: str,
offset: Annotated[str, Field(description="Ex: 1h ou 24h")] = "1h",
) -> str:
"""Retorna métricas de CPU de uma VM Azure."""
resource_id = (
f"/subscriptions/{SUBSCRIPTION_ID}"
f"/resourceGroups/{resource_group}"
f"/providers/Microsoft.Compute/virtualMachines/{vm_name}"
)
result = subprocess.run(
[
"az", "monitor", "metrics", "list",
"--resource", resource_id,
"--metric", "Percentage CPU",
"--interval", "PT5M",
"--offset", offset,
"--output", "json",
],
capture_output=True,
text=True,
check=True,
)
return result.stdout
@mcp.tool()
def list_metric_alert_rules(resource_group: str) -> str:
"""Lista regras de alerta baseadas em métricas no resource group."""
result = subprocess.run(
[
"az", "monitor", "metrics", "alert", "list",
"--resource-group", resource_group,
"--output", "json",
],
capture_output=True,
text=True,
check=True,
)
return result.stdout
@mcp.resource("azure://monitor/metric-alert-rules")
def metric_alert_rules() -> str:
"""Regras de alertas de métricas disponíveis na subscription."""
result = subprocess.run(
[
"az", "monitor", "metrics", "alert", "list",
"--output", "json",
],
capture_output=True,
text=True,
check=True,
)
return result.stdout
@mcp.prompt()
def diagnose_alert(alert_name: str, resource: str) -> str:
"""Monta um prompt inicial pra diagnosticar um alerta."""
return (
f"Diagnostique o alerta {alert_name} no recurso {resource}. "
"Liste hipóteses, sinais pra confirmar cada uma e a ordem de checagem."
)
if __name__ == "__main__":
mcp.run()
Configurando no client (VS Code / Claude Desktop)
{
"mcpServers": {
"azure-monitor": {
"command": "python",
"args": ["./mcp-servers/azure-monitor/server.py"],
"env": {
"AZURE_SUBSCRIPTION_ID": "seu-sub-id"
}
}
}
}
Transport: como client e server se comunicam
Na prática, você vai lidar com dois transports padrão: stdio localmente e Streamable HTTP no modo remoto. SSE continua existindo por compatibilidade com clients antigos, mas não é o caminho novo.
stdio (local)
O client sobe o server como processo filho. A conversa vai por stdin e stdout.
Quando usar: tools locais (file system, CLI), integrações que rodam na mesma máquina.
Streamable HTTP (remoto)
No modo remoto, cada mensagem JSON-RPC vai por HTTP POST no endpoint MCP. O server pode responder com JSON puro ou abrir um stream SSE na mesma rota. Se precisar mandar notificações fora de uma request em andamento, o client pode abrir um GET no mesmo endpoint.
Quando usar: server compartilhado, server em cloud, vários clients apontando pro mesmo endpoint.
from server import mcp
if __name__ == "__main__":
mcp.run(
transport="streamable-http",
host="127.0.0.1",
port=8000,
streamable_http_path="/mcp",
)
Se você ainda precisa atender client antigo, a SDK Python também consegue subir SSE legado. Eu só não começaria projeto novo assim.
Security considerations
MCP servers expõem ações reais. Um MCP server mal configurado é um vetor de ataque.
Autenticação
No stdio, o comum é herdar credenciais do ambiente local. No HTTP, a especificação atual já descreve um fluxo de autorização baseado em OAuth 2.1, metadata do resource server e WWW-Authenticate. Mesmo assim, autenticar o client é só metade do trabalho. Você ainda precisa decidir o que cada identidade pode fazer.
# Adicionar auth no server HTTP
from starlette.authentication import requires
from starlette.responses import Response
@requires("authenticated")
async def handle_mcp_http_request(request):
payload = await request.json()
if payload.get("method") == "tools/call":
token = request.auth.credentials
allowed_tools = get_allowed_tools(token)
tool_name = payload["params"]["name"]
if tool_name not in allowed_tools:
return Response(status_code=403)
# Encaminhar a requisição pro handler MCP real
...
Princípio de least privilege
# Não exponha tools destrutivas por padrão
SAFE_TOOLS = ["get_metrics", "list_alerts", "get_logs"]
DANGEROUS_TOOLS = ["restart_service", "scale_resource", "delete_resource"]
def exposed_tools_for(client_level: str) -> list[str]:
tool_names = SAFE_TOOLS.copy()
if client_level == "admin":
tool_names += DANGEROUS_TOOLS
return tool_names
Input validation
def validate_get_vm_metrics(arguments: dict) -> dict:
if not validate_resource_group(arguments["resource_group"]):
raise ValueError("resource group inválido ou sem permissão")
return {
"resource_group": arguments["resource_group"],
"vm_name": sanitize_resource_name(arguments["vm_name"]),
"offset": arguments.get("offset", "1h"),
}
Ecossistema atual
| MCP Server | O que expõe | Maintained by |
|---|---|---|
| GitHub | Repos, issues, PRs, code search | GitHub/Community |
| PostgreSQL | Query, schema info | Community |
| Filesystem | Read/write files | Anthropic |
| Azure DevOps | Work items, pipelines | Community |
| Kubernetes | Pods, logs, events | Community |
| Slack | Messages, channels | Community |
A lista cresce rápido. Confira em modelcontextprotocol.io o registry atualizado.
MCP vs Function Calling: qual a diferença?
| Aspecto | Function Calling | MCP |
|---|---|---|
| Definido por | Cada provider (OpenAI, Anthropic) | Protocolo aberto |
| Escopo | Uma chamada de API | Protocolo completo (discovery, transports, auth HTTP, streaming) |
| Portabilidade | Lock-in no provider | Qualquer client, qualquer server |
| Discovery | Precisa definir tools no request | Server expõe capabilities dinamicamente |
| State | Normalmente stateless | Pode manter sessão, ou operar stateless no HTTP |
| Transport | HTTP request | stdio, Streamable HTTP |
MCP não substitui tool calling do provider no lado do modelo. Ele substitui a integração ad-hoc entre host e sistemas externos. Muitos clients traduzem tools MCP pro formato de function calling do provider, mas isso é detalhe de implementação do client.
O que levar pra segunda-feira
- MCP é o “USB-C dos AI agents”. Padroniza a conversa entre hosts, clients e sistemas externos.
- Três primitivos aparecem o tempo todo: Tools (ações), Resources (dados) e Prompts (templates).
- Segurança continua sendo trabalho de engenharia. A spec ajuda no fluxo HTTP, mas autorização fina, validação e observabilidade continuam na sua mão.
- Comece expondo tools read-only. get_metrics, list_resources, get_logs. Ações de escrita entram depois.
- stdio pra local, Streamable HTTP pra remoto. SSE legado existe, mas hoje é mais compatibilidade do que recomendação.
No próximo post: projetando um assistente AI pessoal de ponta a ponta.
Leitura complementar
- How MCP Works (Neo Kim, System Design Newsletter)
- Model Context Protocol specification
- MCP Servers repository