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 MCPO que fazEquivalente em infra
MCP ServerExpõe dados/ferramentas via protocolo padrãoAPI server, microserviço
MCP ClientConsome dados/ferramentas de MCP serversAPI client, SDK
HostAplicação que roda o client (VS Code, Claude, etc.)Browser, app que consome APIs
ToolFunção executável via MCPEndpoint REST (POST /action)
ResourceDado acessível via MCPEndpoint REST (GET /data)
PromptTemplate de interaçãoSlash command, prompt salvo, formulário parametrizado
TransportComo client e server se comunicamstdio, 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.

Sem padrão (N×M problem):AI Apps:Data Sources:ClaudeGPTCopilotCustomGitHubJiraAzure MonitorPostgreSQL4 apps × 4 sources = 16 integrações custom

Com MCP:

Com padrão (N+M):AI Apps:MCP Servers:MCPprotocolo padrãoClaudeGPTCopilotCustomGitHub MCP ServerJira MCP ServerAzure Monitor MCP ServerPostgreSQL MCP Server4 + 4 = 8 componentes (cada um implementa MCP uma vez)

É o mesmo valor que USB-C trouxe: um dispositivo implementa USB-C uma vez, funciona em qualquer computador.

Arquitetura do protocolo

HOST(VS Code, Claude Desktop, custom app)MCP CLIENT- Descobre capabilities do server- Invoca tools- Acessa resourcesTransport (stdio, Streamable HTTP)MCP SERVERCapabilitiesTools(ações)Resources(dados)Prompts(templates)Backend: GitHub API, Database, File System, etc.

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.

Host processMCP ClientMCP Server processspawnsJSON-RPC via stdin/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.

Host (browser, app)MCP ClientRemote MCP endpointStreamable HTTP + JSON-RPCPOST /mcprequest JSON-RPCGET /mcp (opcional)notifications via SSE

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 ServerO que expõeMaintained by
GitHubRepos, issues, PRs, code searchGitHub/Community
PostgreSQLQuery, schema infoCommunity
FilesystemRead/write filesAnthropic
Azure DevOpsWork items, pipelinesCommunity
KubernetesPods, logs, eventsCommunity
SlackMessages, channelsCommunity

A lista cresce rápido. Confira em modelcontextprotocol.io o registry atualizado.

MCP vs Function Calling: qual a diferença?

AspectoFunction CallingMCP
Definido porCada provider (OpenAI, Anthropic)Protocolo aberto
EscopoUma chamada de APIProtocolo completo (discovery, transports, auth HTTP, streaming)
PortabilidadeLock-in no providerQualquer client, qualquer server
DiscoveryPrecisa definir tools no requestServer expõe capabilities dinamicamente
StateNormalmente statelessPode manter sessão, ou operar stateless no HTTP
TransportHTTP requeststdio, 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