diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 35078be..cf99c0e 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,8 +5,17 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -31,10 +40,10 @@
"accountId": "bd799ac0-7624-42b2-999a-b96e6e11f98a"
}
}
-
-
+ {}
+ {
+ "isMigrated": true
+}
@@ -82,6 +91,7 @@
1781996088468
+
diff --git a/Documentacao/INVENTARIO_AGENT_GATEWAY_MCP_GATEWAY.md b/Documentacao/INVENTARIO_AGENT_GATEWAY_MCP_GATEWAY.md
new file mode 100644
index 0000000..4fb79bb
--- /dev/null
+++ b/Documentacao/INVENTARIO_AGENT_GATEWAY_MCP_GATEWAY.md
@@ -0,0 +1,133 @@
+# Inventário — Agent Gateway + MCP Gateway Overlay
+
+Este inventário lista os arquivos incluídos no overlay `agent_platform_agent_gateway_mcp_gateway_overlay.zip`, indicando a área, o tipo de alteração e a finalidade de cada arquivo.
+
+## Resumo
+
+| Área | Quantidade |
+|---|---:|
+| Documentação | 1 |
+| Agent Gateway | 10 |
+| MCP Gateway | 5 |
+| Agent Framework | 4 |
+| Template Backend | 2 |
+| MCP Server Mock | 2 |
+| Deploy | 2 |
+
+## Arquivos por área
+
+### Documentação
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `README_AGENT_GATEWAY_AND_MCP_GATEWAY_EVOLUTION.md` | Novo / overlay | Documento principal do overlay. Explica a nova arquitetura sem AI Gateway separado, com Agent Gateway governando políticas/modelos e MCP Gateway separado para tools. |
+
+### Agent Gateway
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `apps/agent_gateway/app/config/governance_loader.py` | Novo / overlay | Carrega o arquivo YAML de governança do Agent Gateway a partir de AGENT_GATEWAY_GOVERNANCE_CONFIG. |
+| `apps/agent_gateway/app/governance/__init__.py` | Novo / overlay | Inicializa o pacote Python de governança do Agent Gateway. |
+| `apps/agent_gateway/app/governance/audit.py` | Novo / overlay | Centraliza logging/auditoria das decisões de governança do Agent Gateway, com proteção simples para não logar mensagem completa. |
+| `apps/agent_gateway/app/governance/evaluation_hooks.py` | Novo / overlay | Hooks antes e depois da chamada ao backend/runtime. Serve para amostragem, evaluator, scoring ou integração futura com Langfuse. |
+| `apps/agent_gateway/app/governance/model_policies.py` | Novo / overlay | Resolve políticas de modelo/profile no Agent Gateway. Define qual provider/model/profile deve ser usado por operação, tenant e agente. |
+| `apps/agent_gateway/app/governance/rate_limit.py` | Novo / overlay | Implementa rate limit em memória por tenant, agente e canal antes de encaminhar a requisição ao backend/runtime. |
+| `apps/agent_gateway/app/governance/usage.py` | Novo / overlay | Hook para registrar uso de gateway, políticas aplicadas e respostas do backend. Pronto para plugar métricas, banco, Langfuse ou OTEL. |
+| `apps/agent_gateway/app/governance_middleware.py` | Novo / overlay | Componente principal de governança do Agent Gateway. Aplica rate limit, resolve model_policy, gera headers/metadados e executa hooks antes/depois do backend. |
+| `apps/agent_gateway/app/routes/governed_proxy_example.py` | Novo / overlay | Exemplo de rota governada para demonstrar como aplicar governança antes de encaminhar para o Agent Backend/Runtime. |
+| `apps/agent_gateway/config/gateway_governance.yaml` | Novo / overlay | Configuração de governança do Agent Gateway: profiles, operation_profiles, providers permitidos, rate limits, headers propagados e evaluation hooks. |
+
+### MCP Gateway
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `apps/mcp_gateway/Dockerfile` | Novo / overlay | Imagem Docker do MCP Gateway. |
+| `apps/mcp_gateway/app/__init__.py` | Novo / overlay | Inicializa o pacote Python da aplicação MCP Gateway. |
+| `apps/mcp_gateway/app/main.py` | Novo / overlay | Aplicação FastAPI do MCP Gateway. Expõe health, ready, catálogo de tools e endpoint de invoke com auth, autorização, mapping, cache, timeout e retry. |
+| `apps/mcp_gateway/config/mcp_gateway.yaml` | Novo / overlay | Configuração central do MCP Gateway: MCP servers, tools, versões, cache, timeout, retry, autorização por agente/canal e mapping BusinessContext → parâmetros. |
+| `apps/mcp_gateway/requirements.txt` | Novo / overlay | Dependências Python do MCP Gateway. |
+
+### Agent Framework
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `libs/agent_framework/src/agent_framework/gateway_policy_context.py` | Novo / overlay | Helper no framework para o Runtime ler a política de modelo enviada pelo Agent Gateway em state['metadata']['model_policy']. |
+| `libs/agent_framework/src/agent_framework/gateways/__init__.py` | Novo / overlay | Inicializa o pacote de clients de gateways no framework, exportando MCPGatewayClient. |
+| `libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py` | Novo / overlay | Client assíncrono do framework para chamar o MCP Gateway: listar tools e executar tools. |
+| `libs/agent_framework/src/agent_framework/runtime_mcp_gateway_adapter.py` | Novo / overlay | Mixin opcional para agentes/runtime chamarem tools via MCP Gateway e anexarem resultados em state['mcp_results']. |
+
+### Template Backend
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `templates/agent_template_backend/app/mcp_gateway_client_factory.py` | Novo / overlay | Factory no template backend para construir MCPGatewayClient a partir de variáveis de ambiente. |
+
+### MCP Server Mock
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `mcp/servers/mock_telecom_mcp/app.py` | Novo / overlay | Mock MCP Server com tools consultar_fatura e consultar_pagamentos para validar o MCP Gateway localmente. |
+| `mcp/servers/mock_telecom_mcp/requirements.txt` | Novo / overlay | Dependências do mock MCP Server de telecom usado para testes locais. |
+
+### Deploy
+
+| Arquivo | Tipo | Finalidade |
+|---|---|---|
+| `deploy/docker/docker-compose.mcp-gateway.yml` | Novo / overlay | Docker Compose para subir MCP Gateway e mock_telecom_mcp localmente. |
+| `deploy/k8s/mcp-gateway.yaml` | Novo / overlay | Manifest Kubernetes de Deployment e Service do MCP Gateway. |
+
+## Observações de integração
+
+### Agent Gateway
+
+Os arquivos em `apps/agent_gateway` não criam um novo serviço. Eles evoluem o Agent Gateway existente para atuar como gateway dedicado da plataforma, centralizando:
+
+- políticas de modelo/profile;
+- rate limit;
+- auditoria;
+- hooks de avaliação;
+- propagação de metadados de governança para o Runtime.
+
+A rota `governed_proxy_example.py` é um exemplo de integração. O handler real do `POST /gateway/message` deve aplicar:
+
+```python
+governed_body, headers = governance.prepare_backend_request(body)
+```
+
+antes de chamar o backend/runtime, e:
+
+```python
+return governance.process_backend_response(data)
+```
+
+após receber a resposta.
+
+### MCP Gateway
+
+O MCP Gateway é um serviço separado. Ele centraliza:
+
+- catálogo de tools;
+- autorização por agente/canal;
+- versionamento de tools;
+- mapping de BusinessContext para parâmetros;
+- cache;
+- timeout;
+- retry;
+- auditoria simples.
+
+### Runtime / Backend
+
+O Runtime continua responsável por:
+
+- LangGraph;
+- estado;
+- memória;
+- checkpoints;
+- fluxo;
+- providers LLM existentes.
+
+O Runtime passa a chamar tools via MCP Gateway usando `MCPGatewayClient` e/ou `MCPGatewayRuntimeMixin`.
+
+### AI Gateway
+
+Este overlay não cria `apps/ai_gateway`. A governança de modelo fica no Agent Gateway, e a execução LLM continua no Runtime/backend usando os providers já existentes.
diff --git a/Documentacao/MANUAL_EXECUCAO_AGENT_GATEWAY_MCP_GATEWAY_FRONTEND.md b/Documentacao/MANUAL_EXECUCAO_AGENT_GATEWAY_MCP_GATEWAY_FRONTEND.md
new file mode 100644
index 0000000..4e1c1bc
--- /dev/null
+++ b/Documentacao/MANUAL_EXECUCAO_AGENT_GATEWAY_MCP_GATEWAY_FRONTEND.md
@@ -0,0 +1,627 @@
+# Manual de Execução Local
+## Agent Gateway + MCP Gateway + Agent Template Backend + Frontend
+
+## 1. Arquitetura de execução
+
+A arquitetura local fica assim:
+
+```text
+Frontend
+ porta 5173
+ │
+ ▼
+Agent Gateway
+ porta 9000
+ │
+ ▼
+Agent Template Backend / Agent Runtime
+ porta 8000
+ │
+ ▼
+MCP Gateway
+ porta 8300
+ │
+ ▼
+MCP Server / Mock Telecom MCP
+ porta 8001
+```
+
+A governança de modelo, rate limit, auditoria e políticas ficam no **Agent Gateway**.
+
+O **Agent Runtime / Agent Template Backend** continua responsável por:
+
+- LangGraph;
+- estado;
+- memória;
+- checkpoints;
+- supervisor/router;
+- guardrails;
+- judges;
+- chamada LLM via providers existentes;
+- chamada de tools via MCP Gateway.
+
+---
+
+## 2. Portas
+
+| Componente | Porta | URL |
+|---|---:|---|
+| Frontend | 5173 | `http://localhost:5173` |
+| Agent Gateway | 9000 | `http://localhost:9000` |
+| Agent Template Backend | 8000 | `http://localhost:8000` |
+| MCP Gateway | 8300 | `http://localhost:8300` |
+| MCP Server / Mock Telecom MCP | 8001 | `http://localhost:8001` |
+
+---
+
+## 3. Ordem recomendada para subir
+
+Subir nesta ordem:
+
+1. MCP Server / Mock Telecom MCP
+2. MCP Gateway
+3. Agent Template Backend
+4. Agent Gateway
+5. Frontend
+
+---
+
+# 4. Terminal 1 — MCP Server / Mock Telecom MCP
+
+Se estiver usando o mock incluído no overlay:
+
+```bash
+cd agent_platform_oci/mcp/servers/mock_telecom_mcp
+
+python -m venv .venv
+source .venv/bin/activate
+
+pip install -r requirements.txt
+
+uvicorn app:app --host 0.0.0.0 --port 8001 --reload
+```
+
+Validar:
+
+```bash
+curl http://localhost:8001/health
+```
+
+Resultado esperado:
+
+```json
+{
+ "status": "ok",
+ "service": "mock_telecom_mcp"
+}
+```
+
+---
+
+# 5. Terminal 2 — MCP Gateway
+
+```bash
+cd agent_platform_oci/apps/mcp_gateway
+
+python -m venv .venv
+source .venv/bin/activate
+
+pip install -r requirements.txt
+
+export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
+
+uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
+```
+
+Validar health:
+
+```bash
+curl http://localhost:8300/health
+```
+
+Validar readiness:
+
+```bash
+curl http://localhost:8300/ready
+```
+
+Listar tools:
+
+```bash
+curl -s http://localhost:8300/v1/tools | jq
+```
+
+Executar tool:
+
+```bash
+curl -s -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
+ -H "Content-Type: application/json" \
+ -d '{
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "channel": "web",
+ "tool_name": "consultar_fatura",
+ "business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+ }
+ }' | jq
+```
+
+Resultado esperado:
+
+```json
+{
+ "tool_name": "consultar_fatura",
+ "version": "1.0.0",
+ "ok": true,
+ "data": {
+ "invoice_id": "INV-001",
+ "msisdn": "11999999999",
+ "valor_total": 249.9,
+ "vencimento": "2026-06-10",
+ "status": "ABERTA"
+ }
+}
+```
+
+---
+
+# 6. Terminal 3 — Agent Template Backend / Agent Runtime
+
+```bash
+cd agent_platform_oci/templates/agent_template_backend
+```
+
+ou, se o seu backend estiver em outra pasta:
+
+```bash
+cd agent_platform_oci/templates/agent_template_backend
+```
+
+Ativar ambiente:
+
+```bash
+source .venv/bin/activate
+```
+
+Se ainda não existir `.venv`:
+
+```bash
+python -m venv .venv
+source .venv/bin/activate
+pip install -r requirements.txt
+```
+
+Configurar variáveis:
+
+```bash
+export MCP_GATEWAY_ENABLED=true
+export MCP_GATEWAY_URL=http://localhost:8300
+export MCP_GATEWAY_TIMEOUT_SECONDS=60
+
+export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
+```
+
+Se estiver usando OCI/OpenAI-compatible, manter também as variáveis já existentes do backend:
+
+```bash
+export LLM_PROVIDER=oci_openai
+export OCI_GENAI_API_KEY=
+```
+
+ou, para mock:
+
+```bash
+export LLM_PROVIDER=mock
+```
+
+Subir backend:
+
+```bash
+python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
+```
+
+Validar:
+
+```bash
+curl http://localhost:8000/health
+```
+
+Validar agentes:
+
+```bash
+curl http://localhost:8000/agents | jq
+```
+
+Testar backend direto:
+
+```bash
+curl -s -X POST http://localhost:8000/gateway/message \
+ -H "Content-Type: application/json" \
+ -d '{
+ "channel": "web",
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "payload": {
+ "message": "Quero consultar minha fatura",
+ "session_id": "session-001",
+ "user_id": "user-001",
+ "message_id": "msg-001",
+ "business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+ }
+ }
+ }' | jq
+```
+
+---
+
+# 7. Terminal 4 — Agent Gateway
+
+```bash
+cd agent_platform_oci/apps/agent_gateway
+```
+
+Ativar ambiente:
+
+```bash
+python -m venv .venv
+source .venv/bin/activate
+pip install -r requirements.txt
+```
+
+Configurar variáveis:
+
+```bash
+export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
+export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
+```
+
+Subir Agent Gateway:
+
+```bash
+python -m uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
+```
+
+Validar:
+
+```bash
+curl http://localhost:9000/health
+```
+
+Se a rota governada de exemplo estiver registrada no `app.main`, testar:
+
+```bash
+curl -s -X POST http://localhost:9000/gateway/message/governed \
+ -H "Content-Type: application/json" \
+ -d '{
+ "channel": "web",
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "payload": {
+ "message": "Quero consultar minha fatura",
+ "session_id": "session-001",
+ "user_id": "user-001",
+ "message_id": "msg-001",
+ "metadata": {
+ "operation": "agent.final_answer"
+ },
+ "business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+ }
+ }
+ }' | jq
+```
+
+Se a rota real for `/gateway/message`, testar:
+
+```bash
+curl -s -X POST http://localhost:9000/gateway/message \
+ -H "Content-Type: application/json" \
+ -d '{
+ "channel": "web",
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "payload": {
+ "message": "Quero consultar minha fatura",
+ "session_id": "session-001",
+ "user_id": "user-001",
+ "message_id": "msg-001",
+ "metadata": {
+ "operation": "agent.final_answer"
+ },
+ "business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+ }
+ }
+ }' | jq
+```
+
+---
+
+# 8. Terminal 5 — Frontend
+
+```bash
+cd agent_platform_oci/agent_frontend
+```
+
+ou a pasta onde estiver o frontend.
+
+Instalar dependências:
+
+```bash
+npm install
+```
+
+Subir:
+
+```bash
+npm run dev -- --host 0.0.0.0 --port 5173
+```
+
+Abrir:
+
+```text
+http://localhost:5173
+```
+
+Configurar no frontend:
+
+```text
+Backend URL: http://localhost:9000
+Agent: telecom_contas
+Session ID: session-001
+Customer Key: 11999999999
+Contract Key: INV-001
+```
+
+O frontend deve chamar o **Agent Gateway** na porta 9000, não o MCP Gateway.
+
+---
+
+# 9. Fluxo final esperado
+
+```text
+Frontend 5173
+ ↓
+Agent Gateway 9000
+ ↓
+Agent Template Backend 8000
+ ↓
+MCP Gateway 8300
+ ↓
+Mock Telecom MCP 8001
+```
+
+---
+
+# 10. Docker Compose para MCP Gateway + Mock MCP
+
+Também é possível subir MCP Gateway + Mock MCP com Docker Compose:
+
+```bash
+cd agent_platform_oci
+
+docker compose -f deploy/docker/docker-compose.mcp-gateway.yml up --build
+```
+
+Isso sobe:
+
+```text
+MCP Gateway http://localhost:8300
+Mock Telecom MCP http://localhost:8001
+```
+
+Depois subir manualmente:
+
+- Agent Template Backend na porta 8000;
+- Agent Gateway na porta 9000;
+- Frontend na porta 5173.
+
+---
+
+# 11. Checklist de validação
+
+## MCP Server
+
+```bash
+curl http://localhost:8001/health
+```
+
+## MCP Gateway
+
+```bash
+curl http://localhost:8300/health
+curl http://localhost:8300/v1/tools
+```
+
+## Backend Runtime
+
+```bash
+curl http://localhost:8000/health
+curl http://localhost:8000/agents
+```
+
+## Agent Gateway
+
+```bash
+curl http://localhost:9000/health
+```
+
+## Frontend
+
+```text
+http://localhost:5173
+```
+
+---
+
+# 12. Erros comuns
+
+## 12.1. Frontend chamando porta errada
+
+Errado:
+
+```text
+Frontend → http://localhost:8000
+```
+
+Correto:
+
+```text
+Frontend → http://localhost:9000
+```
+
+Se você quiser testar sem Agent Gateway, pode apontar temporariamente para 8000. Mas no modelo final, o frontend deve usar o Agent Gateway.
+
+---
+
+## 12.2. MCP Gateway sem MCP Server
+
+Sintoma:
+
+```text
+MCP server unavailable
+```
+
+Correção:
+
+```bash
+curl http://localhost:8001/health
+```
+
+Se falhar, subir o mock MCP server.
+
+---
+
+## 12.3. Tool sem BusinessContext
+
+Sintoma:
+
+```json
+{
+ "missing_business_keys": ["customer_key", "contract_key"]
+}
+```
+
+Correção:
+
+enviar:
+
+```json
+"business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+}
+```
+
+---
+
+## 12.4. Agent Gateway não encontra backend
+
+Sintoma:
+
+```text
+Connection refused http://localhost:8000
+```
+
+Correção:
+
+validar:
+
+```bash
+curl http://localhost:8000/health
+```
+
+e configurar:
+
+```bash
+export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
+```
+
+---
+
+## 12.5. Rota governada não registrada
+
+Se `/gateway/message/governed` retornar 404, significa que o arquivo de exemplo ainda não foi incluído no `app.main`.
+
+Nesse caso, use a rota real `/gateway/message` ou registre no `main.py`:
+
+```python
+from app.routes.governed_proxy_example import router as governed_router
+
+app.include_router(governed_router)
+```
+
+---
+
+# 13. Variáveis consolidadas
+
+## Agent Gateway
+
+```env
+DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
+AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
+```
+
+## Agent Template Backend
+
+```env
+MCP_GATEWAY_ENABLED=true
+MCP_GATEWAY_URL=http://localhost:8300
+MCP_GATEWAY_TIMEOUT_SECONDS=60
+LLM_PROVIDER=mock
+```
+
+## MCP Gateway
+
+```env
+MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
+```
+
+---
+
+# 14. Resumo rápido
+
+Em cinco terminais:
+
+```bash
+# Terminal 1
+cd mcp/servers/mock_telecom_mcp
+source .venv/bin/activate
+uvicorn app:app --host 0.0.0.0 --port 8001 --reload
+
+# Terminal 2
+cd apps/mcp_gateway
+source .venv/bin/activate
+export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
+uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
+
+# Terminal 3
+cd templates/agent_template_backend
+source .venv/bin/activate
+export MCP_GATEWAY_ENABLED=true
+export MCP_GATEWAY_URL=http://localhost:8300
+uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
+
+# Terminal 4
+cd apps/agent_gateway
+source .venv/bin/activate
+export DEFAULT_AGENT_BACKEND_URL=http://localhost:8000
+export AGENT_GATEWAY_GOVERNANCE_CONFIG=config/gateway_governance.yaml
+uvicorn app.main:app --host 0.0.0.0 --port 9000 --reload
+
+# Terminal 5
+cd agent_frontend
+npm install
+npm run dev -- --host 0.0.0.0 --port 5173
+```
diff --git a/Documentacao/MCP_GATEWAY_RUNBOOK.md b/Documentacao/MCP_GATEWAY_RUNBOOK.md
new file mode 100644
index 0000000..2b5bfe9
--- /dev/null
+++ b/Documentacao/MCP_GATEWAY_RUNBOOK.md
@@ -0,0 +1,93 @@
+# MCP Gateway Runbook
+
+## Arquitetura corrigida
+
+O backend/agente não deve chamar diretamente os MCP servers finais. O fluxo correto é:
+
+```text
+agent_template_backend / agent_framework
+ -> MCP Gateway Client
+ -> apps/mcp_gateway
+ -> mcp/servers/telecom_mcp_server ou mcp/servers/retail_mcp_server
+```
+
+## Subir localmente
+
+A partir da raiz do projeto:
+
+### Terminal 1 - Telecom MCP Server
+
+```bash
+cd mcp/servers/telecom_mcp_server
+python -m uvicorn main:app --host 0.0.0.0 --port 8100 --reload
+```
+
+### Terminal 2 - Retail MCP Server
+
+```bash
+cd mcp/servers/retail_mcp_server
+python -m uvicorn main:app --host 0.0.0.0 --port 8200 --reload
+```
+
+### Terminal 3 - MCP Gateway
+
+```bash
+cd apps/mcp_gateway
+export MCP_GATEWAY_CONFIG_PATH=config/mcp_gateway.yaml
+python -m uvicorn app.main:app --host 0.0.0.0 --port 8300 --reload
+```
+
+### Terminal 4 - Backend/agente
+
+No `.env` do backend/agente ou do runtime que usa o `agent_framework`, habilite:
+
+```env
+ENABLE_MCP_TOOLS=true
+MCP_GATEWAY_ENABLED=true
+MCP_GATEWAY_URL=http://localhost:8300
+MCP_GATEWAY_AGENT_ID=telecom_contas
+MCP_GATEWAY_TENANT_ID=default
+```
+
+## Testes rápidos
+
+### Health do gateway
+
+```bash
+curl http://localhost:8300/health
+```
+
+### Lista de tools expostas pelo gateway
+
+```bash
+curl http://localhost:8300/v1/tools
+```
+
+### Chamada de tool via gateway
+
+```bash
+curl -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "channel": "web",
+ "tool_name": "consultar_fatura",
+ "arguments": {
+ "msisdn": "11999999999",
+ "invoice_id": "INV-123"
+ },
+ "business_context": {},
+ "metadata": {"session_id": "local-test"}
+ }'
+```
+
+Resposta esperada: `ok: true`, `data.invoice_id`, `data.msisdn`, `metadata.server: telecom`.
+
+## O que foi corrigido
+
+- `apps/mcp_gateway/config/mcp_gateway.yaml` agora aponta para os MCP servers reais nas portas `8100` e `8200`.
+- O MCP Gateway agora suporta o contrato legado dos MCP servers: `POST /mcp/tools/call` com `{tool_name, arguments}`.
+- O `agent_framework` ganhou flags `MCP_GATEWAY_ENABLED`, `MCP_GATEWAY_URL`, `MCP_GATEWAY_TOKEN`, `MCP_GATEWAY_AGENT_ID` e `MCP_GATEWAY_TENANT_ID`.
+- O `MCPToolRouter` passa a chamar o MCP Gateway quando `MCP_GATEWAY_ENABLED=true`.
+- `libs/agent_framework/config/mcp_servers.yaml` foi mantido como registry lógico/fallback, não como caminho principal quando o gateway está ativo.
diff --git a/Documentacao/README_AGENT_GATEWAY_AND_MCP_GATEWAY_EVOLUTION.md b/Documentacao/README_AGENT_GATEWAY_AND_MCP_GATEWAY_EVOLUTION.md
new file mode 100644
index 0000000..7ad83e6
--- /dev/null
+++ b/Documentacao/README_AGENT_GATEWAY_AND_MCP_GATEWAY_EVOLUTION.md
@@ -0,0 +1,129 @@
+# Agent Platform OCI — Agent Gateway + MCP Gateway Evolution
+
+Este overlay remove o conceito de `AI Gateway` separado.
+
+## Arquitetura
+
+```text
+Frontend
+ ↓
+Agent Gateway
+ ├── governance
+ ├── model policies
+ ├── rate limit
+ ├── audit
+ └── evaluation hooks
+ ↓
+Agent Backend / Runtime
+ ├── LangGraph
+ ├── state
+ ├── memory
+ ├── checkpoints
+ └── LLM providers via profiles existentes
+ ↓
+ MCP Gateway
+ ↓
+ MCP Servers
+```
+
+## O que entra no Agent Gateway
+
+```text
+apps/agent_gateway/app/governance/
+apps/agent_gateway/app/governance_middleware.py
+apps/agent_gateway/app/routes/governed_proxy_example.py
+apps/agent_gateway/config/gateway_governance.yaml
+```
+
+## O que entra no MCP Gateway
+
+```text
+apps/mcp_gateway/
+libs/agent_framework/src/agent_framework/gateways/mcp_gateway_client.py
+libs/agent_framework/src/agent_framework/runtime_mcp_gateway_adapter.py
+```
+
+## Aplicar overlay
+
+```bash
+unzip agent_platform_agent_gateway_mcp_gateway_overlay.zip -d /tmp/overlay
+rsync -av /tmp/overlay/ ./
+```
+
+## Subir MCP Gateway local
+
+```bash
+docker compose -f deploy/docker/docker-compose.mcp-gateway.yml up --build
+```
+
+Serviços:
+
+```text
+MCP Gateway http://localhost:8300
+Mock Telecom MCP http://localhost:8001
+```
+
+## Testar MCP Gateway
+
+```bash
+curl http://localhost:8300/health
+curl http://localhost:8300/v1/tools
+```
+
+Executar tool:
+
+```bash
+curl -s -X POST http://localhost:8300/v1/tools/consultar_fatura/invoke \
+ -H "Content-Type: application/json" \
+ -d '{
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "channel": "web",
+ "tool_name": "consultar_fatura",
+ "business_context": {
+ "customer_key": "11999999999",
+ "contract_key": "INV-001",
+ "session_key": "session-001"
+ }
+ }' | jq
+```
+
+## Como plugar no Agent Gateway
+
+No handler real do `POST /gateway/message`, antes de encaminhar ao backend/runtime:
+
+```python
+governed_body, headers = governance.prepare_backend_request(body)
+```
+
+Ao receber resposta do backend:
+
+```python
+return governance.process_backend_response(data)
+```
+
+O arquivo abaixo mostra um exemplo completo:
+
+```text
+apps/agent_gateway/app/routes/governed_proxy_example.py
+```
+
+## Variáveis do Runtime
+
+```env
+MCP_GATEWAY_ENABLED=true
+MCP_GATEWAY_URL=http://localhost:8300
+MCP_GATEWAY_TIMEOUT_SECONDS=60
+```
+
+## Importante
+
+Não existe `apps/ai_gateway`.
+
+A governança de modelo fica no Agent Gateway como policy/metadados.
+
+O Runtime continua usando os LLM providers existentes, podendo ler a política enviada pelo Gateway em:
+
+```python
+state["metadata"]["model_policy"]
+```
diff --git a/apps/mcp_gateway/app/main.py b/apps/mcp_gateway/app/main.py
index c03056e..c4ace57 100644
--- a/apps/mcp_gateway/app/main.py
+++ b/apps/mcp_gateway/app/main.py
@@ -45,14 +45,25 @@ class ToolResult(BaseModel):
metadata: dict[str, Any] = Field(default_factory=dict)
+class DiscoveryResult(BaseModel):
+ ok: bool
+ servers_scanned: int = 0
+ tools_discovered: int = 0
+ errors: list[dict[str, Any]] = Field(default_factory=list)
+ tools: list[str] = Field(default_factory=list)
+
+
def load_config() -> dict[str, Any]:
path = Path(os.getenv("MCP_GATEWAY_CONFIG_PATH", "config/mcp_gateway.yaml"))
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
config = load_config()
+static_tools: dict[str, dict[str, Any]] = dict(config.get("tools") or {})
+discovered_tools: dict[str, dict[str, Any]] = {}
+discovery_state: dict[str, Any] = {"last_sync": None, "errors": [], "tools": []}
cache: dict[str, tuple[float, Any]] = {}
-app = FastAPI(title="Agent Platform OCI - MCP Gateway", version="1.0.0")
+app = FastAPI(title="Agent Platform OCI - MCP Gateway", version="1.1.0")
def audit(name: str, payload: dict[str, Any]) -> None:
@@ -70,6 +81,13 @@ def auth_check(authorization: str | None) -> None:
raise HTTPException(status_code=403, detail="Invalid MCP Gateway token")
+def all_tools() -> dict[str, dict[str, Any]]:
+ merged = dict(discovered_tools)
+ # Static config wins over discovery so operators can override metadata safely.
+ merged.update(static_tools)
+ return merged
+
+
def map_arguments(tool_name: str, args: dict[str, Any], bc: dict[str, Any]) -> dict[str, Any]:
result = dict(args or {})
for source, target in ((config.get("parameter_mapping") or {}).get(tool_name) or {}).items():
@@ -100,16 +118,22 @@ async def post_with_retry(url: str, payload: dict[str, Any], timeout: int, retry
raise RuntimeError(str(last_exc))
-def build_server_payload(tool_name: str, args: dict[str, Any], server: dict[str, Any], tool: dict[str, Any]) -> dict[str, Any]:
- """Builds the payload expected by the downstream MCP server.
+async def get_json(url: str, timeout: int) -> Any:
+ async with httpx.AsyncClient(timeout=timeout) as client:
+ resp = await client.get(url)
+ resp.raise_for_status()
+ return resp.json()
- The example servers under mcp/servers expose the framework legacy contract:
- POST /mcp/tools/call {tool_name, arguments}. Some mocks expose direct
- tool endpoints that accept only the argument object. The gateway supports
- both shapes through the server/tool config.
+
+def build_server_payload(tool_name: str, args: dict[str, Any], server: dict[str, Any], tool: dict[str, Any]) -> dict[str, Any]:
+ """Build the payload expected by the downstream MCP server.
+
+ Supported protocols:
+ - legacy_http/framework_http: POST /mcp/tools/call with {tool_name, arguments}
+ - direct_http: POST to the configured endpoint with only the argument object
"""
protocol = str(tool.get("protocol") or server.get("protocol") or "legacy_http")
- if protocol in {"legacy_http", "framework_http"}:
+ if protocol in {"legacy_http", "framework_http", "fastmcp_http"}:
return {"tool_name": tool_name, "arguments": args or {}}
return args or {}
@@ -121,36 +145,202 @@ def normalize_server_response(data: Any) -> tuple[bool, Any, str | None, dict[st
return True, data, None, {}
+def _tool_name(raw: dict[str, Any]) -> str | None:
+ return raw.get("name") or raw.get("tool_name") or raw.get("id")
+
+
+def _tool_schema(raw: dict[str, Any]) -> dict[str, Any]:
+ schema = raw.get("input_schema") or raw.get("inputSchema") or raw.get("schema") or {}
+ if isinstance(schema, dict):
+ return schema
+ return {}
+
+
+def _extract_tools_from_catalog(catalog: Any) -> list[dict[str, Any]]:
+ """Normalize common MCP/FastMCP/custom catalog shapes.
+
+ Accepted examples:
+ - {"tools": [{"name": "x", "description": "...", "input_schema": {...}}]}
+ - [{"name": "x", "inputSchema": {...}}]
+ - {"server_id": "s", "capabilities": {"tools": [...]}}
+ - {"data": {"tools": [...]}}
+ """
+ if isinstance(catalog, list):
+ return [x for x in catalog if isinstance(x, dict)]
+ if not isinstance(catalog, dict):
+ return []
+ if isinstance(catalog.get("tools"), list):
+ return [x for x in catalog["tools"] if isinstance(x, dict)]
+ data = catalog.get("data")
+ if isinstance(data, dict) and isinstance(data.get("tools"), list):
+ return [x for x in data["tools"] if isinstance(x, dict)]
+ caps = catalog.get("capabilities")
+ if isinstance(caps, dict) and isinstance(caps.get("tools"), list):
+ return [x for x in caps["tools"] if isinstance(x, dict)]
+ return []
+
+
+def _catalog_urls(server_id: str, server: dict[str, Any]) -> list[str]:
+ if server.get("manifest_url"):
+ return [str(server["manifest_url"])]
+ base = str(server.get("url", "")).rstrip("/")
+ if server.get("catalog_endpoint"):
+ return [f"{base}{server['catalog_endpoint']}"]
+ discovery = config.get("discovery") or {}
+ endpoints = server.get("discovery_endpoints") or discovery.get("default_catalog_endpoints") or [
+ "/.well-known/mcp-server.json",
+ "/manifest",
+ "/mcp/tools",
+ "/tools",
+ "/v1/tools",
+ ]
+ return [f"{base}{endpoint}" for endpoint in endpoints]
+
+
+def _endpoint_for_tool(server: dict[str, Any], raw_tool: dict[str, Any]) -> str:
+ if raw_tool.get("endpoint"):
+ return str(raw_tool["endpoint"])
+ protocol = str(raw_tool.get("protocol") or server.get("protocol") or "legacy_http")
+ if protocol in {"legacy_http", "framework_http", "fastmcp_http"}:
+ return str(server.get("invoke_endpoint") or "/tools/call")
+ name = _tool_name(raw_tool) or ""
+ return str(server.get("invoke_endpoint") or f"/tools/{name}")
+
+
+def normalize_discovered_tool(server_id: str, server: dict[str, Any], raw_tool: dict[str, Any]) -> tuple[str, dict[str, Any]] | None:
+ name = _tool_name(raw_tool)
+ if not name:
+ return None
+ discovery = config.get("discovery") or {}
+ defaults = discovery.get("tool_defaults") or {}
+ tool_cfg: dict[str, Any] = {
+ "version": str(raw_tool.get("version") or defaults.get("version") or "1.0.0"),
+ "server": server_id,
+ "endpoint": _endpoint_for_tool(server, raw_tool),
+ "protocol": raw_tool.get("protocol") or server.get("protocol") or defaults.get("protocol") or "legacy_http",
+ "enabled": bool(raw_tool.get("enabled", defaults.get("enabled", True))),
+ "idempotent": bool(raw_tool.get("idempotent", defaults.get("idempotent", True))),
+ "cache_ttl_seconds": int(raw_tool.get("cache_ttl_seconds", defaults.get("cache_ttl_seconds", 0)) or 0),
+ "timeout_seconds": int(raw_tool.get("timeout_seconds", server.get("timeout_seconds", defaults.get("timeout_seconds", 30))) or 30),
+ "retry": raw_tool.get("retry") or defaults.get("retry") or {"enabled": False},
+ "allowed_agents": raw_tool.get("allowed_agents") or defaults.get("allowed_agents") or [],
+ "allowed_channels": raw_tool.get("allowed_channels") or defaults.get("allowed_channels") or [],
+ "required_business_keys": raw_tool.get("required_business_keys") or defaults.get("required_business_keys") or [],
+ "description": raw_tool.get("description") or raw_tool.get("doc") or "",
+ "input_schema": _tool_schema(raw_tool),
+ "source": "discovery",
+ }
+ return name, tool_cfg
+
+
+async def discover_server(server_id: str, server: dict[str, Any]) -> tuple[list[tuple[str, dict[str, Any]]], list[dict[str, Any]]]:
+ errors: list[dict[str, Any]] = []
+ if not server.get("enabled", True):
+ return [], []
+ if not server.get("discover", False):
+ return [], []
+ timeout = int((config.get("discovery") or {}).get("timeout_seconds", server.get("timeout_seconds", 10)) or 10)
+ for url in _catalog_urls(server_id, server):
+ try:
+ catalog = await get_json(url, timeout=timeout)
+ raw_tools = _extract_tools_from_catalog(catalog)
+ normalized = []
+ for raw in raw_tools:
+ item = normalize_discovered_tool(server_id, server, raw)
+ if item:
+ normalized.append(item)
+ if normalized:
+ return normalized, errors
+ errors.append({"server": server_id, "url": url, "error": "catalog returned no tools"})
+ except Exception as exc:
+ errors.append({"server": server_id, "url": url, "error": str(exc)})
+ return [], errors
+
+
+async def sync_discovery() -> DiscoveryResult:
+ discovered_tools.clear()
+ errors: list[dict[str, Any]] = []
+ tools_count = 0
+ scanned = 0
+ for server_id, server in (config.get("servers") or {}).items():
+ if not isinstance(server, dict) or not server.get("discover", False):
+ continue
+ scanned += 1
+ normalized, server_errors = await discover_server(server_id, server)
+ errors.extend(server_errors)
+ for name, tool_cfg in normalized:
+ discovered_tools[name] = tool_cfg
+ tools_count += 1
+ discovery_state.update({
+ "last_sync": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
+ "errors": errors,
+ "tools": sorted(discovered_tools.keys()),
+ })
+ audit("mcp.discovery.completed", {"servers_scanned": scanned, "tools_discovered": tools_count, "errors": len(errors)})
+ return DiscoveryResult(ok=not errors, servers_scanned=scanned, tools_discovered=tools_count, errors=errors, tools=sorted(discovered_tools.keys()))
+
+
+@app.on_event("startup")
+async def startup_discovery() -> None:
+ discovery = config.get("discovery") or {}
+ if discovery.get("enabled", False) and discovery.get("sync_on_startup", True):
+ try:
+ await sync_discovery()
+ except Exception as exc:
+ audit("mcp.discovery.failed", {"error": str(exc)})
+
+
@app.get("/health")
async def health():
- return {"status": "ok", "service": "mcp_gateway"}
+ return {"status": "ok", "service": "mcp_gateway", "version": "1.1.0"}
@app.get("/ready")
async def ready():
- enabled_tools = [k for k, v in (config.get("tools") or {}).items() if v.get("enabled", True)]
- return {"status": "ready", "tools": enabled_tools}
+ enabled_tools = [k for k, v in all_tools().items() if v.get("enabled", True)]
+ return {"status": "ready", "tools": enabled_tools, "discovery": discovery_state}
@app.get("/v1/tools")
async def tools():
- return {"tools": [{"name": name, **cfg} for name, cfg in (config.get("tools") or {}).items()]}
+ return {"tools": [{"name": name, **cfg} for name, cfg in all_tools().items()]}
@app.get("/v1/tools/{tool_name}")
async def tool_detail(tool_name: str):
- tool = (config.get("tools") or {}).get(tool_name)
+ tool = all_tools().get(tool_name)
if not tool:
raise HTTPException(status_code=404, detail=f"Tool not found: {tool_name}")
return {"name": tool_name, **tool}
+@app.get("/v1/discovery/servers")
+async def discovery_servers():
+ servers = []
+ for server_id, server in (config.get("servers") or {}).items():
+ servers.append({
+ "id": server_id,
+ "enabled": server.get("enabled", True),
+ "discover": server.get("discover", False),
+ "url": server.get("url"),
+ "manifest_url": server.get("manifest_url"),
+ "catalog_endpoint": server.get("catalog_endpoint"),
+ })
+ return {"servers": servers, "state": discovery_state}
+
+
+@app.post("/v1/discovery/sync", response_model=DiscoveryResult)
+async def discovery_sync(authorization: str | None = Header(default=None)):
+ auth_check(authorization)
+ return await sync_discovery()
+
+
@app.post("/v1/tools/{tool_name}/invoke", response_model=ToolResult)
async def invoke(tool_name: str, invocation: ToolInvocation, authorization: str | None = Header(default=None)):
started = time.perf_counter()
auth_check(authorization)
- tool = (config.get("tools") or {}).get(tool_name)
+ tool = all_tools().get(tool_name)
if not tool or not tool.get("enabled", True):
raise HTTPException(status_code=404, detail=f"Tool not found or disabled: {tool_name}")
@@ -217,7 +407,7 @@ async def invoke(tool_name: str, invocation: ToolInvocation, authorization: str
error=error,
cache={"hit": False, "key": ck, "ttl_seconds": ttl},
latency_ms=latency_ms,
- metadata={"server": tool.get("server"), **server_metadata},
+ metadata={"server": tool.get("server"), "source": tool.get("source", "static"), **server_metadata},
)
except Exception as exc:
latency_ms = int((time.perf_counter() - started) * 1000)
@@ -228,5 +418,5 @@ async def invoke(tool_name: str, invocation: ToolInvocation, authorization: str
ok=False,
error=str(exc),
latency_ms=latency_ms,
- metadata={"server": tool.get("server")},
+ metadata={"server": tool.get("server"), "source": tool.get("source", "static")},
)
diff --git a/apps/mcp_gateway/config/mcp_gateway.yaml b/apps/mcp_gateway/config/mcp_gateway.yaml
index 978aba5..e181cf1 100644
--- a/apps/mcp_gateway/config/mcp_gateway.yaml
+++ b/apps/mcp_gateway/config/mcp_gateway.yaml
@@ -1,9 +1,36 @@
# Dedicated MCP Gateway configuration.
# The agent backend/framework calls this gateway; this gateway calls the final MCP servers.
+
+# Discovery allows the gateway to sync tool catalogs from registered MCP servers.
+# Static tools below remain supported and override discovered tools with the same name.
+discovery:
+ enabled: true
+ sync_on_startup: true
+ timeout_seconds: 10
+ default_catalog_endpoints:
+ - /.well-known/mcp-server.json
+ - /manifest
+ - /mcp/tools
+ - /tools/list
+ - /tools
+ - /v1/tools
+ tool_defaults:
+ version: 1.0.0
+ protocol: legacy_http
+ enabled: true
+ idempotent: true
+ cache_ttl_seconds: 300
+ timeout_seconds: 30
+ retry: {enabled: true, max_attempts: 2, backoff_ms: 250}
+ allowed_agents: []
+ allowed_channels: []
+ required_business_keys: []
+
servers:
telecom:
enabled: true
+ discover: false
protocol: legacy_http
transport: http
# Local run: uvicorn mcp.servers.telecom_mcp_server.main:app --port 8100
@@ -12,6 +39,7 @@ servers:
retail:
enabled: true
+ discover: false
protocol: legacy_http
transport: http
# Local run: uvicorn mcp.servers.retail_mcp_server.main:app --port 8200
@@ -170,3 +198,19 @@ auth:
static_tokens:
runtime-local-token:
agents: []
+
+
+# Example of a dynamically discovered MCP server.
+# Uncomment and adjust the URL to plug a new server that exposes a catalog/manifest.
+# servers:
+# nf_items:
+# enabled: true
+# discover: true
+# protocol: legacy_http
+# transport: http
+# url: http://localhost:8400/mcp
+# # Optional. If omitted, the gateway tries the discovery.default_catalog_endpoints.
+# # manifest_url: http://localhost:8400/.well-known/mcp-server.json
+# # catalog_endpoint: /tools
+# # invoke_endpoint: /tools/call
+# timeout_seconds: 30
diff --git a/docs/MCP_GATEWAY_DISCOVERY.md b/docs/MCP_GATEWAY_DISCOVERY.md
new file mode 100644
index 0000000..89fabb0
--- /dev/null
+++ b/docs/MCP_GATEWAY_DISCOVERY.md
@@ -0,0 +1,156 @@
+# MCP Gateway — Server Discovery and Catalog Sync
+
+## Goal
+
+This evolution allows the MCP Gateway to discover tools from registered MCP Servers by reading a manifest or catalog endpoint.
+
+The framework still points to a single MCP Gateway:
+
+```env
+MCP_GATEWAY_ENABLED=true
+MCP_GATEWAY_URL=http://localhost:8300
+MCP_GATEWAY_TIMEOUT_SECONDS=60
+```
+
+The MCP Gateway can point to many MCP Servers:
+
+```text
+Agent Framework
+ -> MCP Gateway
+ -> telecom_mcp_server
+ -> retail_mcp_server
+ -> nf_items_mcp_server
+ -> any other MCP Server
+```
+
+## What is automatic
+
+After a server is registered in `apps/mcp_gateway/config/mcp_gateway.yaml` with `discover: true`, the gateway can:
+
+- call its manifest/catalog endpoint;
+- normalize the returned tool list;
+- publish the tools in `GET /v1/tools`;
+- execute the discovered tool through `POST /v1/tools/{tool_name}/invoke`.
+
+## What is still explicit
+
+The gateway does not scan the network or GitHub by itself. You still register the MCP Server endpoint in YAML.
+
+Example:
+
+```yaml
+servers:
+ nf_items:
+ enabled: true
+ discover: true
+ protocol: legacy_http
+ transport: http
+ url: http://localhost:8400/mcp
+ catalog_endpoint: /tools
+ invoke_endpoint: /tools/call
+ timeout_seconds: 30
+```
+
+If `catalog_endpoint` is omitted, the gateway tries:
+
+```text
+/.well-known/mcp-server.json
+/manifest
+/mcp/tools
+/tools/list
+/tools
+/v1/tools
+```
+
+## Expected manifest/catalog formats
+
+The gateway accepts common shapes:
+
+```json
+{
+ "server_id": "nf_items",
+ "tools": [
+ {
+ "name": "buscar_notas_por_criterios",
+ "description": "Search invoice items by criteria.",
+ "input_schema": {
+ "cliente": "string",
+ "estado": "string",
+ "preco": "number",
+ "ean": "string",
+ "margem": "number"
+ }
+ }
+ ]
+}
+```
+
+It also accepts:
+
+```json
+{"tools": [...]}
+```
+
+```json
+{"data": {"tools": [...]}}
+```
+
+```json
+{"capabilities": {"tools": [...]}}
+```
+
+## New endpoints
+
+### List discovery servers
+
+```bash
+curl http://localhost:8300/v1/discovery/servers | jq
+```
+
+### Force catalog sync
+
+```bash
+curl -X POST http://localhost:8300/v1/discovery/sync | jq
+```
+
+### List merged static + discovered tools
+
+```bash
+curl http://localhost:8300/v1/tools | jq
+```
+
+## Precedence rule
+
+Static tools configured under `tools:` override discovered tools with the same name. This allows operations teams to override timeout, cache, allowed agents, required business keys, and endpoint behavior safely.
+
+## Plugging a new MCP Server
+
+1. Start the MCP Server.
+2. Confirm that it exposes a catalog or manifest endpoint.
+3. Add it under `servers:` in `mcp_gateway.yaml` with `discover: true`.
+4. Restart the MCP Gateway or call `POST /v1/discovery/sync`.
+5. Confirm the tool appears in `GET /v1/tools`.
+6. Invoke the tool through the gateway.
+
+## Example invocation
+
+```bash
+curl -s -X POST http://localhost:8300/v1/tools/buscar_notas_por_criterios/invoke \
+ -H "Content-Type: application/json" \
+ -d '{
+ "tenant_id": "default",
+ "agent_id": "telecom_contas",
+ "channel": "web",
+ "tool_name": "buscar_notas_por_criterios",
+ "arguments": {
+ "cliente": "CLIENTE-001",
+ "estado": "SP",
+ "preco": 100.0,
+ "ean": "7890000000000",
+ "margem": 0.05
+ },
+ "business_context": {
+ "session_key": "session-001"
+ }
+ }' | jq
+```
diff --git a/mcp/servers/retail_mcp_server/main.py b/mcp/servers/retail_mcp_server/main.py
index 97a8b73..e0c75a1 100644
--- a/mcp/servers/retail_mcp_server/main.py
+++ b/mcp/servers/retail_mcp_server/main.py
@@ -31,6 +31,19 @@ TOOLS = {
async def health():
return {"status": "ok", "server": "retail_mcp_server"}
+
+@app.get("/.well-known/mcp-server.json")
+async def well_known_manifest():
+ return {"server_id": "retail", "protocol": "legacy_http", "invoke_endpoint": "/tools/call", "tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}
+
+@app.get("/manifest")
+async def manifest():
+ return await well_known_manifest()
+
+@app.get("/mcp/tools")
+async def tools_catalog():
+ return {"tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}
+
@app.get("/mcp/tools/list")
async def list_tools():
return {"tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}
diff --git a/mcp/servers/telecom_mcp_server/main.py b/mcp/servers/telecom_mcp_server/main.py
index 2626726..b16cb53 100644
--- a/mcp/servers/telecom_mcp_server/main.py
+++ b/mcp/servers/telecom_mcp_server/main.py
@@ -31,6 +31,19 @@ TOOLS = {
async def health():
return {"status": "ok", "server": "telecom_mcp_server"}
+
+@app.get("/.well-known/mcp-server.json")
+async def well_known_manifest():
+ return {"server_id": "telecom", "protocol": "legacy_http", "invoke_endpoint": "/tools/call", "tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}
+
+@app.get("/manifest")
+async def manifest():
+ return await well_known_manifest()
+
+@app.get("/mcp/tools")
+async def tools_catalog():
+ return {"tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}
+
@app.get("/mcp/tools/list")
async def list_tools():
return {"tools": [{"name": name, **cfg} for name, cfg in TOOLS.items()]}