Skip to main content

El MCP como fuente única de capacidades — estándar y catálogo

Actualización 2026-09-11 (PR #698): la familia de Empresas pasó a persona-primero. contact_discover, contact_select, contacts_acquired, contact_quote y contact_unlock se retiraron; su lugar lo ocupan people_search, people_quote, people_acquired, people_reveal, people_file y people_status. Las filas de contact_* de este doc describen el estado previo a esa retirada. El detalle vigente vive en luna-chat/05-catalogo-core-y-chat.md y proveedores-una-puerta.md.
Decisión. El MCP es la fuente de verdad de toda capacidad de Brein. Luna consume las mismas herramientas en proceso. Lo que no se usa se borra. El MCP se estandariza y se simplifica. Broker/integraciones quedan fuera de esta versión. Base auditada (solo lectura): apps/mcp/src/**, apps/api/src/{app.module.ts, market-data/market-data-mcp.adapter.ts, cognitive/, luna/}, docs/mcp/, skills/, .driftless/, apps/cli/src, scripts/harness/mcp-e2e.mjs, libs/analytics.

0. Seis hallazgos que corrigen el encargo

No existe evidencia de volumen: se decide con evidencia de referencia. Arreglar la telemetría es una línea, va en Fase A y no recupera historia. Los docs: overview.mdx lista bien las 18 retiradas, pero documenta como vivas context_comment y note_add y nombra tres inexistentes (driftless_agent_stats, driftless_project, driftless_notion_*); al revés, company_save, contact_select y contact_prepare no aparecen en ningún doc de usuario ni skill.

1. Inventario con evidencia de uso

CLI = fila en CAPABILITY_MATRIX (paridad de capacidad, no llamada). Skill = skills/driftless/. Brein = las 4 skills públicas brein-*. Har. = mcp-e2e.mjs.

Cerebro — 20

Mercado — 13 (las 13 referenciadas por las skills públicas)

CRM, Empresas y contactos, Cuenta — 20

“Broker fuera”, en concreto: (a) el default de DRIFTLESS_BROKER_ENABLED se invierte a false y se fija así en todos los entornos; (b) driftless_broker sale del array de registro — el handler puede quedarse en el archivo, pero deja de resolverse en tools/call; (c) connectorTools() deja de invocarse desde paginateFor (una línea) y tool-synthesis.ts queda inerte; (d) se borran references/broker.md (9 menciones en 2 archivos, espejadas en .driftless/) y su fila de la tabla de ruteo; (e) docs/mcp/integrations.mdx se marca no disponible. Conectar proveedores sigue siendo flujo humano. Cuentas. KEEP 20 · RENAME 6 · MERGE 8→2 · DELETE 25catálogo objetivo: 24 públicas.

2. El estándar de una herramienta

3. El catálogo objetivo — 24 herramientas

La regla de fusión, del banco 3 de Luna. market_analyze {op, filters} fracasó porque firstMissingRequiredArg (cognitive/registry-tools.ts:143) solo lee required del nivel raíz: un obligatorio dentro de filters era una frase, nunca una compuerta, y 5 de 8 fallos de intención fueron el modelo sin llamar la herramienta. Regla: fusiona solo herramientas sin obligatorios propios distintos. Las 5 búsquedas no tienen ninguno (solo kind); los 2 get comparten record_ref. Las 4 de análisis sí los tienen y se quedan separadas. Por qué cada conteo: Mercado son las cinco preguntas comerciales reales (¿quién hay?, ¿cuántos?, ¿cuánto?, ¿quién es este?, ¿es riesgoso?) más comparar y el contrato de capacidades. Cerebro es leer y escribir: la gobernanza es acto humano. CRM es leer y mover: configurar el pipeline es humano. Empresas/contactos son los siete pasos del ciclo gobernado, cada uno con su chequeo de derechos — fusionarlos borraría el punto de control. Cuenta son los dos hechos que un agente necesita antes de prometer algo.

4. Cómo Luna consume el MCP en proceso

La costura ya existe y nadie la usó. McpModule.register() exporta ToolRegistry, y AppModule ya lo importa con el adaptador in-process (app.module.ts:334). No hace falta HTTP ni JSON-RPC.
  1. Handle compartido. Extraer apps/api/src/mcp.dynamic.ts con export const MCP = McpModule.register({useExisting: MarketDataMcpAdapter, imports:[MarketDataModule]}) e importar esa misma referencia desde AppModule y LunaModule (Nest deduplica un DynamicModule por identidad de objeto). LunaToolsService inyecta ToolRegistry.
  2. Lista y llamada. registry.catalog(): DriftlessTool[] (Luna conserva su toRuntimeToolDef) y registry.call(name, args, ctx) con un ToolCallContext armado desde el Principal de la sesión — el mismo que arma el controller, sin authorization porque no hay salto de red.
  3. Subconjunto por etapa — recomendación: stages en el catálogo, no allowlist en Luna. Hoy LUNA_TOOL_STAGES es una tabla paralela por nombre que hay que tocar por cada herramienta nueva; el propio archivo lo admite. Añadir stages?: SessionStage[] a DriftlessTool y que toolsForStage filtre por el campo. MAX_VISIBLE_TOOLS = 12 se queda en Luna: es regla de la conversación. De las 24, Luna verá 5 en sin_criterio y 11 después; market_capabilities se marca sin etapas a propósito, porque la cobertura ya le llega compilada en el TurnInput.
Luna conserva: luna-manual.ts, luna-input.compiler.ts y luna-criterion.ts (los dos compiladores), luna-intent.ts (las compuertas parse/schema/refs/pregunta/lexico/cifra), el cosechador de refs y stripRefTokens, luna-stage.ts y el cobro. Luna borra: luna-tools.ts (551 líneas) salvo MEXICAN_STATES, que sube a contratos con su guardia; y de luna-tools.service.ts (862) las ~600 de despacho, mapeo acción→ruta, recorte por kind y proyección. anchorRow, capOutput y toLunaToolError se mudan al MCP como envelope, presupuesto y error estándar — no se duplican. next_action y presupuesto contra el validador. Luna hoy quita next_action a propósito (luna-tools.service.ts:7); con el envelope estándar lo verán los gates de cifras y refs. next_action es instrucción, no material citable: el cosechador lo ignora por nombre y el validador lo trata como no-citable, igual que a hint — una línea junto a REF_FIELDS. El tope del MCP (50 KiB) es ~11× el de Luna (4 500 caracteres) y recorta distinto; el outputBudget por herramienta los reconcilia con min(outputBudget, tope) y el recorte por filas, conservando la excepción de las páginas de mercado, que se rechazan en vez de trocearse. cognitive/tool-registry.ts se adopta, no se retira. Es el único tipo que ya declara efecto, política, costo, idempotencia, presupuesto y ejemplos, y ya trae un spec de contrato (mcp-mapping.contract.spec.ts) que lee el registro vivo del MCP y verifica la derivación. Mover tool-registry.ts + tool-policy.ts + tool-citations.ts a libs/agent-tools y que apps/mcp importe DriftlessTool: eso convierte “el MCP es la fuente de verdad” en algo que defiende el compilador.

5. Orden de migración y tamaño

Total −3 460 / +1 880 líneas, ~59 archivos. 12-14 días de un ingeniero con Claude (A 1,5 · B 3,5 · C 2 · D 1,5 · E 4, más un día de margen en E). A-D son independientes y se mezclan una por una; E necesita A-D dentro.

6. Riesgos