MCP y WebSocket¶
Expone capacidades de tu plugin a agentes de IA (MCP) y a la UI en tiempo real (WebSocket) sin duplicar la lógica REST.
Cuándo leer esta página¶
- Quieres que un agente invoque operaciones de tu dominio (
helloworld_list_items,template_create, …). - Necesitas push bidireccional desde el panel (chat, notificaciones, progreso).
- Ya tienes servicios o handlers REST y buscas una fachada adicional.
Beta
MCP HTTP y WebSocket están implementados en el monorepo. La autenticación OIDC en MCP sigue en diseño (ADR 005); en desarrollo local se usa el header Authorization como en REST.
Regla de oro¶
Los handlers MCP y los handlers WebSocket reutilizan la misma lógica que tus endpoints REST. No copies reglas de negocio en un archivo aparte.
| Fachada | Ruta | Hook de bootstrap |
|---|---|---|
| MCP (Streamable HTTP) | POST /mcp | register_mcp_tools |
| WebSocket | WS /api/v1/ws | register_ws_namespaces |
El servidor MCP implementa el ciclo de vida mínimo (initialize, notifications/initialized, tools/list, tools/call, ping) compatible con clientes como Cursor IDE. URL local en mcp.json: http://localhost:8000/mcp (API, no Vite).
Leyenda: Handshake MCP Streamable HTTP y convención de nombres snake_case. Actores: cliente MCP, servidor FastAPI. Estado: Implementado (beta).
sequenceDiagram
participant Client as Cliente MCP
participant API as POST /mcp
participant Reg as McpToolRegistry
Client->>API: initialize
API-->>Client: capabilities
Client->>API: notifications/initialized
Client->>API: tools/list
API->>Reg: listar tools activas
Reg-->>API: helloworld_list_items core_hello
API-->>Client: tools
Client->>API: tools/call helloworld_list_items
API->>Reg: invocar handler
Reg-->>API: resultado
API-->>Client: JSON-RPC result Leyenda: Hub WebSocket con namespaces registrados por plugins. Actores: panel React, WsHub, ws_handler. Estado: Implementado (beta).
flowchart TB
subgraph panel [Panel React]
Client[WebSocket client]
end
subgraph api [FastAPI]
Hub["WS /api/v1/ws"]
Registry[WsNamespaceRegistry]
end
subgraph plugins [Plugins]
AI[ai-agents ws_handler]
Demo[demo namespace]
end
Client -->|envelope namespace type payload| Hub
Hub --> Registry
Registry --> AI
Registry --> Demo
AI -->|chat.reply| Hub
Hub --> Client Broadcasting de dominio (Diseño)
WebSocket es el transporte. El broadcasting de hechos de dominio hacia la UI es una fachada prevista sobre io.ws (Eventos de dominio, ADR 021). No sustituye events entre plugins ni MCP.
MCP: registrar tools¶
- Crea
mcp_tools.pyen tu plugin. - Exporta
register_mcp_toolsen__init__.py. - Usa
PluginMcpRegistrarpara el prefijo{namespace}_{action}(guiones del plugin se normalizan a_).
from cortex_framework.io.mcp.helpers import PluginMcpRegistrar
from cortex_framework.io.mcp.registry import McpToolRegistry
async def _list_items(_arguments: dict) -> dict:
return {"items": [...], "count": 0}
def register_mcp_tools(registry: McpToolRegistry) -> None:
mcp = PluginMcpRegistrar("helloworld", registry)
mcp.register("list_items", "Lista ítems de demostración.", _list_items)
# Nombre final: helloworld_list_items
Referencia completa: plugins/helloworld/cortex_plugin_helloworld/mcp_tools.py (si existe) o plugins/booking/... en el producto de referencia.
Convención de nombres¶
| Tool | Equivalente REST típico |
|---|---|
helloworld_list_items | GET /api/v1/samples/todo |
helloworld_create_item | POST /api/v1/samples/todo |
CRUD: verbos cortos (list, get, create, update, delete). Acciones de dominio: verbo_sujeto (p. ej. check_availability, add_payment). El framework registra core_hello como tool de prueba de plataforma.
Protocolo HTTP¶
Petición JSON-RPC 2.0:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": "helloworld_list_items", "arguments": {}}
}
Header obligatorio en tests y dev: Authorization.
WebSocket: registrar namespaces¶
- Implementa un handler async
(message, context) -> None. - Regístralo con
register_ws_namespaces. - El cliente envía envelopes
{namespace, type, payload}.
from cortex_framework.io.ws.registry import WsNamespaceRegistry
async def handle_demo_ws(message: dict, context) -> None:
msg_type = str(message.get("type", "")).strip()
if msg_type == "ping":
await context.send("pong", {}, namespace="demo")
def register_ws_namespaces(registry: WsNamespaceRegistry) -> None:
registry.register("demo", handle_demo_ws)
Referencia: plugins/ai-agents/cortex_plugin_ai_agents/ws_handler.py (namespace ai-agents, tipos chat y tools.list).
Envelope¶
| Campo | Descripción |
|---|---|
namespace | Id registrado en bootstrap (único por handler) |
type | Tipo de mensaje acordado entre cliente y plugin |
payload | Objeto JSON con datos del mensaje |
Al conectar, el servidor envía {"type": "connected"}. Si el namespace no existe, responde con type: error y payload.code: UNKNOWN_NAMESPACE.
Errores frecuentes¶
| Síntoma | Causa probable |
|---|---|
Tool no aparece en tools/list | Plugin no está activo en /control/plugins o falta exportar register_mcp_tools |
Unknown tool | Nombre sin prefijo {namespace}_ o typo en tools/call |
UNKNOWN_NAMESPACE en WS | Namespace no registrado o plugin ai-agents desactivado |
| Lógica duplicada REST/MCP | Handler MCP debería llamar al mismo servicio que el router FastAPI |
Siguiente paso¶
- Ciclo de vida — orden de hooks en bootstrap.
- API REST — fuente de verdad del dominio.
- ADR 006 — decisiones de arquitectura MCP.