Tópico 03

Arquitetura AWS
Multi-Conta com MCP por Camada

Conta Supervisora com Redis centralizado. Conta de Agentes com ECS + MCP próprio. Conta de BFFs com actions orquestradas por MCP independente. O Supervisor como único dono do estado.

O que Muda na Nova Arquitetura

A constraint fundamental: Redis fica exclusivamente na conta supervisora. O MCP sai da conta do supervisor e passa para a conta dos agentes. Isso muda quem é responsável por quê.

⟳ Mudanças estruturais
MCP Central na conta do Supervisor
enriquecia contexto via Redis
→
MCP na conta dos Agentes
só roteia — não vê o Redis
MCP Central donod o estado cross-agent
→
Supervisor é o único dono do Redis e do contexto
Actions sem camada própria
→
Conta de BFFs com MCP próprio para actions
Consequência imediata: o Supervisor precisa serializar o contexto do Redis e injetar no payload de cada chamada MCP. O MCP de Agentes recebe o payload já enriquecido e só roteia para o ECS correto.

As Três Contas AWS

Responsabilidades por função

Responsabilidade Quem executa
Orquestração, decisão de qual agente invocar Supervisor (LangGraph)
Estado de sessão e working memory Redis (Conta Supervisora)
Enriquecimento de contexto antes de invocar Supervisor (lê Redis, serializa)
Roteamento para ECS Agent correto MCP Agentes
Execução especializada + RAG ECS Agents
Execução de actions externas MCP BFFs
Persistência de resultados no Redis Supervisor (após receber resultado)

Topologia da Arquitetura

┌──────────────────────────────────────────────────────────────────────┐
│                      CONTA SUPERVISORA                              │
│                                                                      │
│   ┌──────────────────┐       ┌───────────────────────────────────┐   │
│   │  LangGraph App   │ ←───→ │         Redis (ElastiCache)       │   │
│   │                  │       │                                   │   │
│   │  AgentState:     │       │  checkpoint:{thread_id}:{id}      │   │
│   │  - messages      │       │  working:{session_id}             │   │
│   │  - session_id    │       │  session:user:{user_id}:active    │   │
│   │  - intent        │       └───────────────────────────────────┘   │
│   │                  │                                               │
│   │  ContextEnricher │  lê Redis → monta payload → injeta contexto  │
│   └────────┬─────────┘                                               │
│            │ VPC Endpoint (saída)                                    │
└────────────┼────────────────────────────────────────────────────────┘
             │  payload enriquecido: {session_id, instrucoes, contexto}
             ↓
┌────────────────────────────────────────────────────────────────────────┐
│                       CONTA AGENTES                                    │
│                                                                        │
│   ┌──────────────────────────────────────────────────────────────┐     │
│   │                     MCP Agentes                              │     │
│   │  recebe payload → roteia para ECS correto → devolve output  │     │
│   └─────────────────────┬──────────────────────────────────────┘     │
│              ┌───────────┼────────────────┐                           │
│              ↓           ↓                ↓                           │
│        ┌──────────┐ ┌──────────┐  ┌──────────────┐                   │
│        │ECS Agent │ │ECS Agent │  │ECS Agent     │                   │
│        │Pesquisa  │ │Análise   │  │Relatório     │                   │
│        └────┬─────┘ └────┬─────┘  └──────┬───────┘                   │
│             │            │               │                            │
│        OpenSearch   OpenSearch      OpenSearch                        │
│        kb-pesquisa  kb-analise      kb-relatorio                     │
│              │           │               │                            │
│              └───────────┴───────────────┘                           │
│                          │ VPC Endpoint (saída)                       │
└──────────────────────────┼────────────────────────────────────────────┘
                           │  chamadas de actions
                           ↓
┌──────────────────────────────────────────────────────────────────────┐
│                        CONTA BFFs                                    │
│                                                                      │
│   ┌────────────────────────────────────────────────────────────┐     │
│   │                       MCP BFFs                             │     │
│   │  expõe todas as actions como tools — qualquer ECS invoca  │     │
│   └────────┬────────────────────────────────────────────────┘       │
│            │                                                          │
│   ┌────────┴───────────────────────────────────────┐                 │
│   ↓          ↓           ↓              ↓           │                 │
│ Action     Action      Action         Action        │                 │
│ CRM        ERP         Notificação    DB            │                 │
└──────────────────────────────────────────────────────────────────────┘
      

Conta Supervisora — Implementação

O Supervisor acumula a responsabilidade de enriquecimento de contexto que antes era do MCP Central. Antes de invocar qualquer agente, ele lê o Redis e injeta o contexto filtrado no payload.

python — conta_supervisora/supervisor.py
class AgentState(TypedDict):
    messages:            Annotated[List[BaseMessage], add_messages]
    session_id:          str
    user_id:             str
    intent:              str
    # Não carrega dados volumosos — só referências ao que existe no Redis
    working_memory_keys: List[str]


class ContextEnricher:
    def __init__(self, redis_client):
        self.redis = redis_client

    async def build_payload(self, session_id: str, agent_name: str, instrucoes: str) -> dict:
        raw = await self.redis.get(f"working:{session_id}")
        working_mem = json.loads(raw) if raw else {}

        # Filtra apenas o contexto relevante para cada agente
        context_map = {
            "agent_pesquisa":  ["queries_anteriores", "intent"],
            "agent_analise":   ["pesquisa_resultado", "intent", "dados_coletados"],
            "agent_relatorio": ["pesquisa_resultado", "analise_resultado", "intent"],
        }
        keys = context_map.get(agent_name, [])
        contexto = {k: working_mem[k] for k in keys if k in working_mem}

        return {"session_id": session_id, "instrucoes": instrucoes, "contexto": contexto}

    async def persist_result(self, session_id: str, agent_name: str, resultado: str):
        # Supervisor é o único escritor do Redis
        raw = await self.redis.get(f"working:{session_id}")
        mem = json.loads(raw) if raw else {}
        mem[agent_name.replace("agent_", "") + "_resultado"] = resultado
        await self.redis.setex(f"working:{session_id}", 3600, json.dumps(mem))


# Tool usada pelo supervisor para invocar agente via MCP Agentes
@tool
async def invocar_agent_analise(session_id: str, instrucoes: str) -> str:
    """Invoca o agente de análise. Contexto da sessão é injetado automaticamente."""
    payload = await enricher.build_payload(session_id, "agent_analise", instrucoes)

    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://mcp-agentes.conta-agentes.internal/tools/agent_analise",
            json=payload, timeout=60.0
        )
    resultado = resp.json()["output"]

    # Persiste resultado no Redis para uso dos próximos agentes
    await enricher.persist_result(session_id, "agent_analise", resultado)
    return resultado

Conta Agentes — MCP Server

O MCP de Agentes é completamente stateless. Ele não conhece Redis, não conhece LangGraph. Recebe o payload já enriquecido pelo Supervisor e roteia para o ECS correto via HTTP interno.

python — conta_agentes/mcp_server.py
from mcp.server import FastMCP
import httpx

mcp = FastMCP("mcp-agentes")

ECS_AGENTS = {
    "agent_pesquisa":  "http://ecs-pesquisa.interno:8080/invoke",
    "agent_analise":   "http://ecs-analise.interno:8081/invoke",
    "agent_relatorio": "http://ecs-relatorio.interno:8082/invoke",
}

@mcp.tool()
async def agent_pesquisa(session_id: str, instrucoes: str, contexto: dict) -> str:
    """Agente especialista em pesquisa. Contexto fornecido pelo Supervisor."""
    return await _invocar_ecs("agent_pesquisa", session_id, instrucoes, contexto)

@mcp.tool()
async def agent_analise(session_id: str, instrucoes: str, contexto: dict) -> str:
    """Agente especialista em análise. Recebe pesquisa prévia no contexto."""
    return await _invocar_ecs("agent_analise", session_id, instrucoes, contexto)

@mcp.tool()
async def agent_relatorio(session_id: str, instrucoes: str, contexto: dict) -> str:
    """Agente especialista em geração de relatórios."""
    return await _invocar_ecs("agent_relatorio", session_id, instrucoes, contexto)


async def _invocar_ecs(agent: str, session_id: str, instrucoes: str, contexto: dict) -> str:
    payload = {"session_id": session_id, "instrucoes": instrucoes, "contexto": contexto}
    async with httpx.AsyncClient() as client:
        resp = await client.post(ECS_AGENTS[agent], json=payload, timeout=90.0)
        resp.raise_for_status()
        return resp.json()["output"]

Conta Agentes — ECS Agent com MCP BFFs

Cada ECS Agent é um serviço FastAPI independente. Ele recebe o payload enriquecido, faz RAG no seu OpenSearch local e pode invocar o MCP BFFs para executar actions — tudo sem saber nada sobre Redis ou LangGraph.

python — conta_agentes/ecs/agent_analise/agent.py
from fastapi import FastAPI
from langchain_mcp_adapters.client import MultiServerMCPClient

app = FastAPI()

# Conecta ao MCP BFFs (Conta BFFs via VPC Endpoint)
bff_client = MultiServerMCPClient({
    "bffs": {
        "url":       "https://mcp-bffs.conta-bffs.internal/mcp",
        "transport": "streamable_http"
    }
})

# Retriever no OpenSearch local da Conta Agentes
retriever = criar_retriever_opensearch(
    url="https://opensearch.interno.conta-agentes",
    index="kb-analise"
)

@app.post("/invoke")
async def invocar(payload: dict):
    instrucoes = payload["instrucoes"]
    contexto   = payload.get("contexto", {})
    session_id = payload["session_id"]

    # 1. RAG no KB local
    docs = retriever.get_relevant_documents(instrucoes)

    # 2. Carrega tools do MCP BFFs disponíveis para este agente
    bff_tools = await bff_client.get_tools()

    # 3. Executa ReAct agent com tools de BFF + contexto injetado
    agent = criar_react_agent(
        llm=llm,
        tools=bff_tools,
        prompt=montar_prompt(instrucoes, docs, contexto)
    )
    resultado = await agent.ainvoke({"messages": [HumanMessage(instrucoes)]})

    return {"output": resultado["messages"][-1].content}

Conta BFFs — MCP de Actions

O MCP BFFs é totalmente independente do fluxo de sessão. Expõe actions como tools MCP. Qualquer ECS Agent de qualquer conta pode invocá-lo. Não conhece Redis, não conhece session_id — só executa actions e retorna resultados.

python — conta_bffs/mcp_server.py
from mcp.server import FastMCP

mcp = FastMCP("mcp-bffs")

@mcp.tool()
async def buscar_cliente_crm(cliente_id: str) -> dict:
    """Busca dados completos de um cliente no CRM."""
    return await crm_client.get_cliente(cliente_id)

@mcp.tool()
async def atualizar_oportunidade_erp(oportunidade_id: str, dados: dict) -> dict:
    """Atualiza uma oportunidade no ERP."""
    return await erp_client.update_oportunidade(oportunidade_id, dados)

@mcp.tool()
async def enviar_notificacao(destinatario: str, canal: str, mensagem: str) -> dict:
    """Envia notificação pelo canal especificado (email | sms | push)."""
    return await notification_service.send(destinatario, canal, mensagem)

@mcp.tool()
async def consultar_base_dados(query_sql: str, database: str) -> list:
    """Executa consulta somente-leitura em base de dados interna."""
    return await db_client.query(database, query_sql)
Design limpo: os ECS Agents carregam as tools do MCP BFFs via get_tools() e as usam como qualquer tool LangChain. O agente não sabe se está chamando uma API REST, um banco ou um serviço externo — o MCP BFFs abstrai tudo isso.

Fluxo Ponta a Ponta

1
Usuário envia mensagem
LangGraph recebe, roda nó detect_intent, classifica intenção
2
Supervisor lê Redis e monta payload
ContextEnricher busca working:{session_id}, filtra contexto relevante para o agente alvo
3
Supervisor emite tool_call → MCP Agentes (Conta Agentes)
payload: {session_id, instrucoes, contexto filtrado}
4
MCP Agentes roteia → ECS Agent correto
HTTP interno para o container ECS. Repassa payload integralmente.
5
ECS Agent: RAG + lógica + BFF actions
Faz retrieval no OpenSearch local. Se precisar de action → invoca MCP BFFs (Conta BFFs via VPC Endpoint)
6
MCP BFFs executa action
CRM, ERP, notificação, DB — retorna resultado para o ECS Agent
7
ECS Agent retorna resultado
Output sobe: ECS → MCP Agentes → Supervisor (como ToolMessage no state)
8
Supervisor persiste no Redis
ContextEnricher salva resultado em working:{session_id} para uso nos próximos agentes
9
Supervisor decide próximo passo
Loop até não haver mais tool_calls → END. Checkpointer salva estado final no Redis.

O ponto crítico da nova arquitetura

Antes: Supervisor → MCP Central (enriquece + persiste) → Agentes
Agora: Supervisor (enriquece + persiste) → MCP Agentes (só roteia) → Agentes

Isso é saudável — o Supervisor tem o Redis, faz sentido ele ser o único dono do contexto. O MCP de Agentes fica limpo fazendo só roteamento. O MCP de BFFs fica independente, podendo ser chamado por qualquer agente sem acoplar ao fluxo de sessão.
LangGraph Supervisor Redis ElastiCache ContextEnricher MCP Agentes ECS Fargate OpenSearch MCP BFFs Actions VPC Endpoint