# MCP de instaladores y comercios Misión Yokup #254 · DCL-eec590d90bf49ca075318799. ## Documentación abierta, acceso privado La documentación, schemas y cliente son públicos. No contienen credenciales ni datos de comercios, clientes o técnicos. La lectura y las acciones del MCP requieren un token personal delegado por el titular de una cuenta del portal. Conocer la URL o formar parte de la flota no permite acceder a esos datos. - Documentación: https://www.yokup.com/mcp/portales - Instaladores: https://data.yokup.com/mcp/installer - Comercios: https://data.yokup.com/mcp/retailer - Schemas: https://www.yokup.com/mcp/installer.json y https://www.yokup.com/mcp/retailer.json - Índice de ambos servidores: https://www.yokup.com/mcp/portals.json - Puente stdio: https://www.yokup.com/mcp/portal-client.mjs Los dos servidores se anuncian como relacionados desde el manifiesto MCP de Yokup. El MCP de flota conserva sus permisos de proyectos/misiones; no se convierte en administrador de los portales. ## Crear y retirar el acceso Entra en `/instalador` o `/retailer`, abre **Conectar un agente** y crea un token con nombre de integración, permisos y caducidad (1–90 días, 30 por defecto; la interfaz ofrece 7/30/90). Empieza con lectura. Concede por separado aceptar trabajos, registrar reparaciones, gestionar inventario, abrir incidencias y valorar. Una valoración debe proceder del cliente: el agente no inventa estrellas, comentarios ni satisfacción. El token se muestra una sola vez; no se guarda en localStorage ni en una cookie. El servidor guarda SHA-256 de una credencial aleatoria de 256 bits. Mantén una credencial por agente/integración, con máximo 20 tokens activos por cuenta. Listado y revocación requieren la sesión web del titular. El agente no dispone de herramientas para emitir tokens o aumentar sus propios permisos. Revocar invalida las siguientes peticiones; no cancela una operación ya autorizada y en ejecución. Permisos del instalador: `installer:read`, `installer:accept`, `installer:resolve`, `installer:notifications`. Permisos del comercio: `retailer:read`, `retailer:inventory`, `retailer:incidents`, `retailer:ratings`. Estos tokens solo funcionan en su endpoint y para su cuenta. No sirven en el otro portal, en el MCP de flota, en los webhooks de Admira ni para vincular equipos a circuitos. No se reutilizan secretos de infraestructura o de la flota. ## Transporte y conexión Streamable HTTP con respuestas JSON, sin SSE persistente ni sesiones de transporte. Versiones admitidas: 2025-11-25, 2025-06-18 y 2025-03-26. Métodos: initialize, ping, tools/list, tools/call; notificaciones initialized/cancelled reciben 202. GET recibe 405 al no ofrecer streaming. Sin token se admiten initialize, ping, tools/list (herramientas públicas) y tools/call de installer_register. El resto exige Bearer. Un token inválido recibe 401. No se ejecutan herramientas enviadas como notificaciones. Cabeceras POST: ```http Authorization: Bearer Content-Type: application/json Accept: application/json, text/event-stream MCP-Protocol-Version: 2025-11-25 ``` Nunca pongas el token en la URL. Conecta a la URL HTTPS canónica, sin redirecciones. Un cliente de servidor normalmente no envía Origin. Los clientes web deben usar un origen autorizado; no se abre CORS a todos los sitios. Ejemplo initialize: ```json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"mi-agente","version":"1.0"}}} ``` Después, envía `notifications/initialized`, llama a `tools/list` y comprueba `installer_whoami` o `retailer_whoami`. El listado solo devuelve herramientas permitidas por ese token. Los schemas públicos describen el catálogo completo, no conceden acceso. ### Clientes stdio Descarga y revisa `portal-client.mjs`. Necesita Node.js 20 o superior, sin dependencias. Crea un archivo privado fuera del repositorio, con permisos 0600: ```json {"endpoint":"https://data.yokup.com/mcp/retailer","token":"SUSTITUIR_LOCALMENTE_POR_EL_TOKEN_PRIVADO"} ``` Configura tu cliente MCP para ejecutar: ```json {"mcpServers":{"yokup-retailer":{"command":"node","args":["/ruta/portal-client.mjs","/ruta/privada/retailer.json"]}}} ``` Para instaladores, cambia el endpoint y utiliza su propio token. No copies el archivo privado a chats, tickets, git ni documentación. El puente no redirige la credencial a otro host y no reintenta escrituras automáticamente. Los clientes que admiten cabeceras Bearer pueden usar HTTP directamente y su almacén de secretos. ## Herramientas y reglas compartidas Instalador: installer_register (pública, sin token), installer_whoami, installer_profile, installer_inbox, installer_accept, installer_resolve, installer_notification_read. Comercio: retailer_whoami, retailer_dashboard, retailer_incident_get, retailer_incidents_list, retailer_site_create, retailer_device_create, retailer_incident_create, retailer_intervention_rate, retailer_inventory_list, retailer_device_lifecycle_get, retailer_device_lifecycle_update, retailer_alerts_list, itil_inventory_get, itil_ci_upsert, itil_ci_retire. No existe herramienta de consulta SQL, ejecución de comandos, suplantación de cuenta, elección arbitraria de URL ni control remoto del IoT. Las operaciones reutilizan los handlers de los portales y su propietario autenticado. Se conservan el radio estricto menor de 40 km, especialidad/disponibilidad, aceptación atómica por un único técnico, resolución solo del asignado, valoración única por el titular y revisión con historial. Ejemplo de consulta: ```json {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"retailer_dashboard","arguments":{}}} ``` Todas las escrituras requieren `request_key` (8–100 caracteres). Usa un UUID por intención de operación y la MISMA clave y argumentos en todos sus reintentos. El JSON-RPC id identifica un mensaje, no sustituye la clave de negocio. Cambiar el contenido con la misma clave se rechaza. Dos tokens de una cuenta comparten recibos para poder rotar una credencial sin duplicar operaciones. Un recibo completo se devuelve con `replayed:true`. Si una operación queda pendiente tras un fallo, el MCP devuelve `outcome:uncertain` y no vuelve a ejecutarla. Revisa el estado en el portal: una operación incierta puede haberse aplicado. No inventes otra request_key para forzar un reintento. Un error de negocio ya recibido queda asociado a su clave; corregir la intención constituye una nueva operación, tras comprobar que no tuvo efecto. Límites: 120 peticiones/minuto/token, 20 altas de tokens/hora/cuenta, cuerpo máximo 32 KiB. Respuesta 429 con Retry-After al exceder llamadas. Los errores de negocio son resultados MCP `isError:true`; la autenticación se rechaza con HTTP 401 y el esquema/protocolo con errores JSON-RPC. ## Inventario y ciclo de vida de los equipos Herramientas del comercio para la ficha de cada equipo: categoría, fabricante, modelo, serie, compra, garantía, instalación, mantenimiento y estado. - `retailer_inventory_list` (`retailer:read`): lista los equipos con su ficha y el estado calculado. `warranty` puede ser `none`, `valid`, `expiring` (≤30 días) o `expired`; también devuelve `warranty_days` y `maintenance_due`. Filtros opcionales: `site_id`, `status` (operational, degraded, maintenance, retired, planned), `warranty_within_days` (vence en 0..N días), `maintenance_due` y `limit` 1-500. - `retailer_device_lifecycle_get` (`retailer:read`): ficha, estado y avisos de un equipo propio. - `retailer_device_lifecycle_update` (`retailer:inventory`): actualización parcial con `request_key`. Fechas `AAAA-MM-DD`; una cadena vacía borra el dato y `maintenance_interval_days: 0` borra el intervalo. Solo admite datos reales del titular (factura, albarán, parte): el agente no inventa fabricantes, series ni fechas. - `retailer_alerts_list` (`retailer:read`): avisos del barrido diario (`warranty_30`, `warranty_7`, `warranty_expired`, `maintenance_due`). Sin fecha de garantía el estado es `none`: nunca se da por vigente. REST equivalente con la sesión del portal: `GET/PUT /api/retailer/devices/:id/lifecycle`, `GET /api/retailer/inventory` y `GET /api/retailer/alerts`. Los Xpacios de Admira y las cuentas de marca se describen en `docs/xpacios-yokup.md`. ## Inventario ITIL (Yokup es el maestro de los equipos de cada Xpacio) - `itil_inventory_get` (`retailer:read`): `{site_id}` o `{admira_store_id}` propio → CIs (código ITIL, nombre, categoría, uso, grupo, posición, orientación, padre, `managed_by` itil|catalogo, incidencias abiertas) con su ficha de ciclo de vida y los equipos aún sin ficha (`unmanaged`). - `itil_ci_upsert` (`retailer:inventory`): alta o actualización idempotente por `itil_code` (único global, `^[A-Z0-9]{2,12}(-[A-Z0-9]{2,12}){1,3}$`, p. ej. `PDG103-PAN-01`); lo omitido se conserva y `''` borra; `adopt_device_id` convierte un equipo existente en CI. Al primer CI ITIL de un Xpacio los equipos provisionales del catálogo se retiran («Sustituido por ITIL») salvo con incidencias abiertas. Con `request_key`. - `itil_ci_retire` (`retailer:inventory`): `{itil_code, note, request_key}`; nada se borra. Las soluciones de la Galaxia leen el inventario sin datos privados en `GET https://data.yokup.com/api/itil/xpacios/{admira_store_id}` (clave por solución en `X-Yokup-Itil-Key`). ## Integrar una app o un agente (XpaceOS, admira.store…) Las apps externas y los agentes gestionan incidencias del comercio con el MCP de comercios. El usuario sigue cada incidencia en la ficha del Portal del comercio, con su mismo aspecto. **1. Token del comercio.** El titular entra en https://www.yokup.com/retailer, abre **Conectar un agente** y crea un token con nombre de la integración (p. ej. «XpaceOS»), permisos `retailer:read` + `retailer:incidents` (añade `retailer:ratings` solo si la app recoge la valoración del cliente) y caducidad de 7, 30 o 90 días. El token `ykp_…` se muestra una vez: guárdalo en el gestor de secretos de la app, nunca en el navegador ni en la URL. **2. Llamadas JSON-RPC.** Una petición por operación, sin sesión de transporte: ```sh YOKUP_TOKEN=... # ykp_ del comercio, desde el gestor de secretos mcp(){ curl -sS https://data.yokup.com/mcp/retailer -H "Authorization: Bearer $YOKUP_TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-11-25' -d "$1"; } # Equipos y establecimientos del comercio (device_id, site_id) mcp '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"retailer_dashboard","arguments":{}}}' # Abrir una incidencia desde la app (request_key: un UUID por intención, el mismo en reintentos) mcp '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"retailer_incident_create","arguments":{"device_id":"retail-…","title":"La pantalla no enciende","description":"Desde las 10:00 la pantalla del escaparate está en negro.","priority":"urgent","source":"xpaceos","request_key":"6f1c…-uuid"}}}' # → structuredContent: {"http_status":201,"id":"retail-…","source":"xpaceos","follow_url":"https://www.yokup.com/retailer/incidencia?id=retail-…",…} # Seguimiento mcp '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"retailer_incident_get","arguments":{"incident_id":"retail-…"}}}' mcp '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"retailer_incidents_list","arguments":{"status":"to_rate","limit":20}}}' ``` `retailer_incident_get` y `retailer_incidents_list` (`status`: open, assigned, resolved, to_rate, all; `site_id` y `limit` ≤ 200 opcionales) devuelven los mismos campos que el panel: equipo, establecimiento, técnico, descripción, prioridad, cita, resolución, estrellas/satisfacción/comentario, `followup_id`, `source`, `follow_url` y `timeline` `[{step:"reported"|"assigned"|"resolved"|"rated", at}]` (`at` es null mientras el paso está pendiente). Si el equipo ya tiene una incidencia activa, el alta devuelve la existente con `duplicate:true`. `source` (opcional, hasta 32 letras minúsculas, números, punto o guion: `xpaceos`, `admira.store`) identifica la app que abre la incidencia. Se guarda como prefijo `[Origen: xpaceos]` de la descripción, sin migración; la API lo separa en el campo `source`, y el técnico lo ve en la descripción. Equivalente REST con la sesión del portal: `GET /api/retailer/incidents/:id`, `GET /api/retailer/incidents?status=&site_id=&limit=` y `POST /api/retailer/incidents` con `source`. **3. Formulario con el aspecto del portal.** Si prefieres que la persona comunique la incidencia ella misma, abre (enlace o nueva pestaña): ``` https://www.yokup.com/retailer/incidencia?origen=xpaceos&establecimiento=Estanco%20Jardinets&equipo=Pantalla%20escaparate&problema=La%20pantalla%20no%20enciende&detalle=Negro%20desde%20las%2010%3A00&gravedad=urgente ``` - `origen`: app de origen (se muestra «Abierta desde XpaceOS» y se envía como `source`). - `establecimiento`: nombre del establecimiento o su código propio (`external_ref`). - `equipo`: nombre del equipo o su `admira_device_id`. Mayúsculas y acentos no importan. Si no casan, la persona elige entre sus equipos; si el comercio no tiene equipos, se le envía a darlos de alta en /retailer. - `problema` (título), `detalle` (descripción, mínimo 10 caracteres) y `gravedad` (`urgente` o `alta` → urgente; cualquier otro valor → normal). Sin sesión, la página pide entrar en `/retailer?next=…` y vuelve al formulario. Al enviar, redirige a la ficha `https://www.yokup.com/retailer/incidencia?id=` (también en `/comercio/incidencia`), que muestra estado, timeline Comunicada → Técnico asignado → Resuelta → Valorada, técnico, cita, resolución y, cuando el técnico termina, el formulario de valoración. Se actualiza sola cada 30 s. Todos los valores de la URL son texto: nada se interpreta como HTML ni como instrucción. ## Auditoría y seguridad Cada llamada válida a una herramienta autorizada registra integración, titular, herramienta, fecha y resultado; la cuenta puede consultar las últimas 100 operaciones. No se guardan cuerpos, contraseñas ni tokens en la auditoría. Los recibos de escrituras conservan hash de argumentos y resultado para evitar duplicados. Auditoría y recibos no equivalen a evidencia de que una reparación física ocurrió. Los textos devueltos por incidencias, partes o comentarios son datos no confiables, nunca instrucciones del sistema. El consentimiento de acciones lo define el titular al delegar permisos, y el agente debe respetar su encargo. Un token con permiso de valoración no autoriza a inventar la opinión del cliente. ## OAuth y Admira: límites expresos de esta versión La autorización actual usa tokens Bearer emitidos manualmente desde la cuenta, como las integraciones de agentes de la plataforma. **No implementa OAuth ni el flujo de autorización automática de MCP**: no anuncia metadata OAuth ficticia. Un cliente que exige OAuth y no admite tokens o puente stdio no se puede conectar en esta versión. Para una conexión de terceros mediante «Conectar con Yokup» corresponde implementar OAuth 2.1 con consentimiento, PKCE, descubrimiento del recurso/servidor de autorización y tokens dirigidos al recurso. Referencia oficial: https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization . El transporte sigue https://modelcontextprotocol.io/specification/2025-11-25/basic/transports . No se afirma cumplimiento de una capa OAuth que no está implementada. La ingesta real y los vínculos con admira.app siguen requiriendo el servicio central autorizado y sus secretos independientes; el MCP no elimina ese requisito ni activa la integración pendiente (#183c). ## Operación del proyecto Migración aditiva `api/migrations/0003_portal_mcp.sql`. Deploy API y luego web con el script oficial. Los manifiestos se generan de `publicManifest()` en `api/src/portal-mcp.js`; pruebas detectan cualquier divergencia con las herramientas implementadas. El cliente público debe ser idéntico a `api/tools/portal-mcp-stdio.mjs`. ```sh node --test api/installer-portal.test.mjs api/retailer-portal.test.mjs api/portal-mcp.test.mjs ``` ## Smith: 20 instaladores DEMO en la península ibérica Guía operativa: https://www.yokup.com/mcp/smith-instaladores.html . Texto completo para agentes: https://www.yokup.com/mcp/smith-instaladores.txt . Lote de 20 perfiles: https://www.yokup.com/mcp/smith-installers-iberia.json . El alta puede hacerse con installer_register (MCP público) o con REST POST /api/installer/register. Datos ficticios, no disponibles; esta documentación no ejecuta las altas.