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ê.
As Três Contas AWS
- LangGraph Supervisor — orquestração e decisão
- Redis (ElastiCache) — checkpoints, working memory, sessão
- ContextEnricher — lê Redis, monta payloads
- VPC Endpoint de saída para Conta Agentes
- MCP Agentes — roteamento stateless por agent
- ECS Agent Pesquisa — RAG + lógica de pesquisa
- ECS Agent Análise — RAG + análise de dados
- ECS Agent Relatório — RAG + geração de docs
- OpenSearch — KBs por agente
- VPC Endpoint de saída para Conta BFFs
- MCP BFFs — orquestra todas as actions
- Action CRM — busca/atualiza clientes
- Action ERP — oportunidades e pedidos
- Action Notificação — email, SMS, push
- Action DB — consultas internas
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.
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.
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.
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.
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)
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
working:{session_id}, filtra contexto relevante para o agente alvoworking:{session_id} para uso nos próximos agentesO ponto crítico da nova arquitetura
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.