Arquitectura y evolución técnica de neges-mcp
Documento de referencia para entender cómo se construyó el MCP de Neges, cómo funcionan sus tools, OAuth, permisos y contratos, y cómo evolucionarlo sin romper seguridad ni compatibilidad.
Esta es la referencia técnica principal de neges-mcp. Parte desde los conceptos básicos, explica el código y las decisiones de seguridad vigentes, registra las deudas conocidas y termina con un procedimiento para construir o modificar cualquier MCP. La fuente de verdad final sigue siendo el código desplegado, el contrato MCP-CONTRACT.md de neges-strapi y las pruebas automatizadas.
Objetivo y límites del componente
neges-mcp es un servicio NestJS que expone operaciones seguras de Neges mediante Model Context Protocol. Su trabajo es traducir una intención conversacional a una acción tipada, validar la autorización MCP, llamar al backend con la identidad real del usuario y devolver un resultado pequeño y predecible.
No es un segundo backend de negocio. Strapi sigue siendo la fuente de verdad para usuarios, empresas, membresías, roles, features, productos, pedidos y reglas de integridad. Si una regla debe cumplirse también desde backoffice, API o procesos internos, esa regla pertenece a Strapi y no sólo al MCP.
| Responsabilidad del MCP | Responsabilidad de Strapi |
|---|---|
| Publicar el protocolo MCP por HTTPS. | Conservar los datos de negocio. |
| Describir tools para que el modelo sepa cuándo usarlas. | Resolver tenant, membresía, rol y feature. |
| Validar schemas de entrada y salida. | Aplicar reglas de dominio y relaciones. |
| Validar access token, audience y scope. | Autorizar definitivamente cada endpoint. |
| Adaptar respuestas de Strapi a contratos estables. | Ejecutar cambios transaccionales o integraciones sensibles. |
| Minimizar PII y errores expuestos al modelo. | Registrar y proteger el sistema de registro. |
Glosario esencial
| Concepto | Explicación simple |
|---|---|
| MCP | Protocolo abierto que permite a un cliente de IA descubrir y llamar herramientas o recursos de un servidor externo. |
| Host | La aplicación donde trabaja el usuario. En este caso, ChatGPT. |
| Cliente MCP | La parte de ChatGPT que negocia el protocolo, lista tools y envía llamadas al servidor. |
| Servidor MCP | El servicio remoto que anuncia capacidades y ejecuta las llamadas. En Neges es mcp.neges.cl. |
| Tool | Una acción con nombre, descripción, entrada, salida, seguridad y comportamiento definidos; por ejemplo list_products. |
| Resource MCP | Contenido que un servidor puede exponer al cliente. Puede servir documentos o una interfaz HTML. Neges V1 no necesita widgets. |
| OAuth resource o audience | El destinatario exacto para el que fue emitido un token. No es lo mismo que un resource MCP. En producción es https://mcp.neges.cl/mcp. |
| Prompt | La solicitud del usuario. No es una llamada de tool: el modelo decide qué tool y argumentos usar a partir del prompt. |
| Schema | Contrato machine-readable de campos y tipos permitidos. Neges usa Zod y el SDK lo expone como JSON Schema. |
| Scope | Permiso OAuth grueso, como products:read o products:write, que limita qué grupo de tools puede ejecutarse. |
| securitySchemes | Metadata por tool que informa al cliente si requiere OAuth y qué scopes necesita. |
| Annotation | Pista sobre efectos de una tool: lectura, destructiva, idempotente o con impacto en el mundo abierto. |
| structuredContent | JSON conciso que el modelo puede interpretar y usar en respuestas o llamadas posteriores. |
| Transport | Mecanismo que lleva mensajes MCP. Neges usa Streamable HTTP sobre HTTPS. |
| Tenant | Empresa aislada dentro de Neges. Las operaciones tenant-aware usan X-Tenant-Id. |
| documentId | Identificador público y estable de un documento Strapi v5. Los IDs numéricos quedan para compatibilidad interna. |
| DCR | Dynamic Client Registration. Permite que ChatGPT registre un client_id OAuth en el servidor. |
| PKCE | Protección del Authorization Code Flow que impide canjear un código robado sin el verifier secreto temporal. |
Arquitectura de extremo a extremo
ChatGPT es el host, el modelo decide qué acción conviene y su cliente MCP llama por HTTPS. Railway ejecuta neges-mcp como una capa stateless por request. El estado que debe sobrevivir despliegues —clientes OAuth, sesiones, códigos y tokens— se guarda en un PostgreSQL propio del MCP.
El MCP recupera o renueva el access token de Strapi asociado a la sesión y llama la Content API con Authorization: Bearer y X-Tenant-Id. Strapi resuelve la empresa y aplica sus políticas. El storefront público vive en Vercel, pero las operaciones de mantenimiento pasan por Strapi; el MCP no llama Vercel directamente.
Stack y superficies públicas
| Elemento | Implementación vigente |
|---|---|
| Runtime | Node.js 22. |
| Framework | NestJS 11 con TypeScript estricto. |
| Protocolo | @modelcontextprotocol/sdk, Streamable HTTP. |
| Validación | Zod para inputs, outputs y variables de entorno. |
| Contexto por request | nestjs-cls sobre AsyncLocalStorage. |
| Persistencia OAuth | PostgreSQL con migraciones al iniciar. |
| Backend de negocio | neges-strapi, Strapi v5 y PostgreSQL. |
| Hosting MCP | Railway bajo mcp.neges.cl. |
| Storefront | neges-store en Vercel. |
| URL o ruta | Función |
|---|---|
| https://mcp.neges.cl/mcp | Endpoint MCP protegido; audiencia OAuth canónica. |
| https://mcp.neges.cl/health | Salud básica del servicio. |
| /.well-known/oauth-protected-resource/mcp | Metadata del recurso protegido. |
| /.well-known/oauth-authorization-server | Metadata del authorization server. |
| /authorize | Inicio del Authorization Code Flow. |
| /token | Canje de código y rotación de refresh token. |
| /register | Registro dinámico de clientes OAuth. |
| /revoke | Revocación de access o refresh token. |
| /oauth/login | Formulario de login Neges durante OAuth. |
| /.well-known/openai-apps-challenge | Verificación de dominio para publicación. |
Estructura del repositorio
neges-mcp/
├── src/main.ts # inicio, CORS y router OAuth
├── src/app.module.ts # composición NestJS y contexto por request
├── src/mcp/ # endpoint /mcp, servidor y resultados
├── src/auth/ # bearer, AuthContext y scopes por tool
├── src/oauth/ # OAuth 2.1, login, sesiones y stores
├── src/database/ # Postgres y migraciones propias
├── src/neges-api/ # cliente Strapi, queries y errores
├── src/features/
│ ├── tenants/ # empresas y configuración de venta
│ ├── products/ # catálogo, categorías y precios
│ ├── product-images/ # imágenes y protecciones SSRF
│ ├── orders/ # lectura y transiciones de pedidos
│ ├── redirects/ # redirecciones de URL
│ └── storefront/ # caché vía storefront-studio de Strapi
├── src/audit/ # eventos seguros de mutaciones
├── test/ # pruebas unitarias y de contrato
├── scripts/check-strapi-contract.cjs # guard contra drift de schemas
└── docs/ # alcance V1, scopes y paquete de publicaciónCada feature mantiene cerca sus tools, schemas, types, adapters, queries y servicio. Esa proximidad evita un registro monolítico y permite probar reglas de un dominio sin conocer todos los demás módulos.
McpServerFactory crea un McpServer nuevo por solicitud, registra las tools de todos los features y lo conecta a un StreamableHTTPServerTransport. Al terminar la respuesta, cierra transporte y servidor. El estado durable no vive dentro de esa instancia.
Qué ocurre en una conexión MCP
Inicialización
Cliente y servidor negocian versión del protocolo y capacidades.
Descubrimiento de tools
ChatGPT solicita la lista. Recibe nombres, descripciones, schemas, seguridad y annotations.
Selección por el modelo
El modelo compara el prompt con la metadata. El código de Neges no decide qué tool elegir; sí valida todo lo que recibe.
tools/call
El cliente envía el nombre de la tool y un objeto de argumentos compatible con inputSchema.
Ejecución autorizada
El servidor verifica token y scope, llama al servicio del feature y luego a Strapi.
Resultado
La tool devuelve content y structuredContent compatible con outputSchema. El modelo redacta la respuesta final.
OAuth 2.1, PKCE y resource/audience
neges-mcp funciona como authorization server OAuth frente al login existente de Strapi. ChatGPT se registra mediante DCR y ejecuta Authorization Code + PKCE S256. El MCP emite sus propios access y refresh tokens opacos; no entrega el JWT de Strapi al cliente.
resource es el destinatario solicitado según RFC 8707. Neges lo fija exactamente en https://mcp.neges.cl/mcp, lo conserva en autorización pendiente, authorization code, access token y refresh token, y vuelve a validarlo al intercambiar, renovar y usar el token. Un token emitido para otro recurso se rechaza aunque sea válido en los demás aspectos.
| Protección | Problema que evita |
|---|---|
| Redirect URI registrada | Enviar el código a un destino no autorizado. |
| state | Confundir una respuesta OAuth con otra solicitud. |
| PKCE S256 | Canjear un authorization code interceptado. |
| Código de un solo uso y corta duración | Repetir un canje o guardar un código antiguo. |
| resource/audience | Usar el token contra otro servidor. |
| Scope | Usar una tool que no fue autorizada. |
| Refresh token rotativo | Reutilizar indefinidamente un token de renovación. |
Cómo se conserva y protege el estado OAuth
| Tabla lógica | Contenido | Protección |
|---|---|---|
| oauth_clients | Clientes registrados y redirect URIs. | JSON durable en Postgres. |
| oauth_user_sessions | Usuario, scopes y sesión Strapi. | Access token y refresh cookie cifrados con AES-256-GCM. |
| oauth_pending_authorizations | Solicitud aparcada mientras el usuario inicia sesión. | Expiración corta y consumo al leer. |
| oauth_auth_codes | Código, PKCE, scopes, resource y sesión. | Código guardado como SHA-256 y eliminado al canjear. |
| oauth_access_tokens | Cliente, sesión, scopes, resource y expiración. | Token opaco guardado sólo como hash. |
| oauth_refresh_tokens | Cliente, sesión, scopes, resource y expiración. | Token opaco guardado sólo como hash y rotado al usar. |
Las migraciones son forward-only y se ejecutan al arrancar. El storage durable permite que una conexión continúe después de un redeploy o entre réplicas. Sin NEGES_DATABASE_URL el código ofrece stores de memoria o archivo sólo para desarrollo y pruebas.
Anatomía de una tool
server.registerTool(
'list_products',
{
title: 'List Neges products',
description: 'Lists or searches products for a Neges tenant...',
inputSchema: listProductsInputSchema,
outputSchema: listProductsOutputSchema,
_meta: oauthToolMeta('products:read'),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async (input) =>
toolAuthorization.requireScope('products:read', async () =>
jsonToolResult(await productsService.listProducts(input)),
),
);| Parte | Para qué sirve |
|---|---|
| name | Identificador estable y machine-readable. Renombrarlo rompe clientes y contratos. |
| title | Nombre legible para interfaces y revisión. |
| description | Indica cuándo usar la acción, precondiciones, límites y relación con otras tools. |
| inputSchema | Valida argumentos antes de ejecutar; evita inputs arbitrarios. |
| outputSchema | Describe la forma de structuredContent y detecta drift. |
| securitySchemes | Anuncia OAuth y el scope exacto. En Neges lo agrega oauthToolMeta. |
| annotations | Describe efectos para selección, confirmaciones y revisión. |
| handler | Orquesta autorización, servicio de feature y construcción del resultado. |
La regla de diseño es una tool por intención clara. list_products busca o lista; get_product abre un objeto; update_product cambia campos. Una tool genérica execute_strapi_request sería difícil de describir, imposible de revisar con seguridad y permitiría saltarse el diseño del dominio.
Inventario vigente de 30 tools
| # | Tool | Acción | Scope |
|---|---|---|---|
| 1 | list_tenants | Lista las empresas del usuario. | tenants:read |
| 2 | get_tenant_profile | Consulta perfil, SEO y estado de empresa. | tenants:read |
| 3 | update_tenant_profile | Actualiza campos editables del perfil. | tenants:write |
| 4 | get_sale_settings | Consulta métodos y tipos de venta. | tenants:read |
| 5 | update_sale_settings | Reemplaza configuración de venta. | tenants:write |
| 6 | list_products | Lista o busca productos. | products:read |
| 7 | get_product | Obtiene el detalle de un producto. | products:read |
| 8 | create_product | Crea un producto. | products:write |
| 9 | update_product | Actualiza campos de un producto. | products:write |
| 10 | archive_product | Archiva con confirmación. | products:write |
| 11 | list_product_categories | Lista o busca categorías. | products:read |
| 12 | create_product_category | Crea una categoría. | products:write |
| 13 | update_product_category | Actualiza una categoría. | products:write |
| 14 | delete_product_category | Previsualiza y elimina con confirmación. | products:write |
| 15 | set_product_categories | Reemplaza categorías asignadas. | products:write |
| 16 | update_product_price | Actualiza un precio existente. | products:write |
| 17 | list_price_lists | Lista listas de precios. | products:read |
| 18 | list_tax_categories | Lista categorías tributarias. | products:read |
| 19 | create_product_price | Crea una definición de precio. | products:write |
| 20 | add_product_image | Sube y asocia una imagen. | products:write |
| 21 | list_product_images | Lista la galería de un producto. | products:read |
| 22 | remove_product_image | Quita una imagen con confirmación. | products:write |
| 23 | reorder_product_images | Reordena galería e imagen primaria. | products:write |
| 24 | list_orders | Lista pedidos con PII enmascarada. | orders:read |
| 25 | get_order | Abre el detalle de un pedido. | orders:read |
| 26 | update_order_status | Ejecuta una transición confirmada. | orders:write |
| 27 | refresh_storefront_cache | Refresca caché autorizado del tenant. | storefront:maintenance |
| 28 | list_url_redirects | Lista o busca redirecciones. | redirects:read |
| 29 | create_url_redirect | Crea una redirección. | redirects:write |
| 30 | delete_url_redirect | Elimina con confirmación. | redirects:write |
securitySchemes y requireScope
securitySchemes es la declaración: permite que ChatGPT sepa que una tool necesita OAuth y qué scope solicitar. requireScope es la aplicación real: detiene el handler si el token no contiene el permiso. Ambas partes deben coincidir y Strapi debe volver a autorizar.
| Scope | Tools protegidas |
|---|---|
| tenants:read | list_tenants, get_tenant_profile, get_sale_settings |
| tenants:write | update_tenant_profile, update_sale_settings |
| products:read | list_products, get_product, list_product_categories, list_price_lists, list_tax_categories, list_product_images |
| products:write | create/update/archive product, categorías, precios y mutaciones de imágenes |
| orders:read | list_orders, get_order |
| orders:write | update_order_status |
| redirects:read | list_url_redirects |
| redirects:write | create_url_redirect, delete_url_redirect |
| storefront:maintenance | refresh_storefront_cache |
Si falta un scope, ToolAuthorizationService devuelve un resultado MCP con isError y _meta["mcp/www_authenticate"]. Ese challenge indica insufficient_scope, metadata del recurso y scope requerido, para que ChatGPT pueda iniciar una reautorización en vez de mostrar sólo un error genérico.
Annotations, idempotencia y confirmaciones
| Annotation | Significado | Ejemplo Neges |
|---|---|---|
| readOnlyHint | true sólo si no cambia estado. | list_products = true; update_product = false. |
| destructiveHint | true si puede borrar, sobrescribir o causar un efecto difícil de revertir. | archive_product y delete_url_redirect = true. |
| idempotentHint | true si repetir el mismo input deja el mismo resultado final. | update_product = true; create_product = false. |
| openWorldHint | true si una escritura puede cambiar estado público en internet. | Cambiar producto, imagen, redirect o caché = true. |
Las annotations ayudan a ChatGPT a decidir confirmaciones, pero no son controles de seguridad. Neges valida confirm=true dentro del input y vuelve a comprobarlo en el servicio para archivar productos, quitar imágenes, eliminar redirecciones y cambiar estados de pedidos. delete_product_category agrega una fase de preview con affectedProductCount.
Seguridad multi-tenant en capas
| Capa | Comprobación |
|---|---|
| OAuth bearer middleware | Token válido, no expirado y emitido para /mcp. |
| ToolAuthorizationService | Scope exacto de la tool. |
| Servicio MCP | tenantDocumentId requerido y allowlist opcional de ambiente. |
| Strapi auth | JWT de una sesión Neges real. |
| resolve-tenant | Empresa existente y activa a partir de X-Tenant-Id. |
| require-membership | Membresía activa del usuario en esa empresa. |
| require-feature / RBAC | Permiso de la acción y módulo habilitado. |
| Controllers y services | Integridad de relaciones, estados y reglas de dominio. |
Las relaciones también deben permanecer dentro del tenant. Por ejemplo, asignar una categoría o un precio no puede conectar documentos de otra empresa. Esta defensa pertenece al backend, porque cualquier cliente —no sólo ChatGPT— podría intentar la misma operación.
Contrato entre neges-mcp y neges-strapi
El MCP consume rutas REST, nombres de campos, relaciones pobladas, enums y formas de respuesta específicas. Los adapters convierten ese formato a objetos de dominio estables. Una modificación de Strapi puede compilar correctamente y aun romper una tool en silencio si el adapter espera otro campo.
| Artefacto | Responsabilidad |
|---|---|
| neges-strapi/MCP-CONTRACT.md | Inventario canónico de endpoints, campos e invariantes consumidos. |
| features/*/*.queries.ts | Construye paths, filtros y payloads Strapi. |
| features/*/*.adapter.ts | Parsea respuestas desconocidas y reduce el payload. |
| features/*/*.types.ts | Define el modelo interno de cada feature. |
| scripts/check-strapi-contract.cjs | Compara campos documentados con schemas reales. |
| Tests de adapters/services | Protegen formas, casos límite y comportamiento. |
- Usar documentId como identificador público estable.
- Enviar Authorization: Bearer del usuario y X-Tenant-Id en rutas tenant-aware.
- No exponer query syntax de Strapi como input público de una tool.
- No devolver payloads Strapi crudos; adaptar y minimizar.
- Actualizar ambos repos y MCP-CONTRACT.md en el mismo trabajo cuando cambia el contrato.
- Ejecutar npm run check:contract y los tests de ambos repos.
Inputs, outputs y errores seguros
Los inputs se validan con Zod antes de llegar al servicio. Deben representar conceptos del negocio —tenantDocumentId, SKU, acción de pedido— y no detalles de transporte. Los campos opcionales deben diferenciar “no cambiar” de “limpiar” cuando corresponda.
{
"content": [
{
"type": "text",
"text": "{ ...resultado serializado... }"
}
],
"structuredContent": {
"items": [],
"pagination": {
"page": 1,
"pageSize": 20,
"pageCount": 0,
"total": 0
}
}
}Todas las tools públicas declaran outputSchema y devuelven structuredContent. jsonToolResult agrega también una representación textual JSON como fallback. El output debe incluir identificadores útiles para la siguiente llamada, pero no tokens, cookies, stack traces, URLs internas ni objetos completos de infraestructura.
| Error | Respuesta esperada |
|---|---|
| Input inválido | Mensaje accionable sin ejecutar Strapi. |
| Scope insuficiente | Error MCP con challenge de reautorización. |
| Sin membresía o feature | Error seguro 403 mapeado desde Strapi. |
| Documento inexistente | 404 con identificador seguro, sin consulta interna. |
| Conflicto de negocio | Código estable como SKU duplicado o transición inválida. |
| Error inesperado | Mensaje genérico; detalle sólo en logs controlados. |
Casos sensibles implementados
| Caso | Diseño de seguridad |
|---|---|
| Pedidos | El listado enmascara nombre y correo; get_order entrega contacto sólo para un pedido explícito; las mutaciones son acciones de dominio confirmadas. |
| Imágenes remotas | Sólo HTTP/HTTPS, MIME image/*, tamaño máximo, timeout, sin redirects y bloqueo de hosts privados/loopback para reducir SSRF. |
| Categorías por nombre | Resolución sin acentos/mayúsculas; si falta o hay varias coincidencias, no asigna y devuelve candidatos. |
| Creación de precio bruto | Consulta categoría tributaria y convierte a neto; rechaza duplicado en la misma lista y tramo. |
| Eliminación de categoría | Preview del impacto; soft delete predeterminado; hard delete sólo si se pide expresamente. |
| Caché del storefront | Scope propio, autorización Strapi y tag derivado por backend; el input no acepta provider, URL, tag ni deploy hook. |
Caso de estudio: autorización de storefront
La implementación inicial podía ejecutar mantenimiento de infraestructura desde el MCP usando una allowlist global. Eso no demostraba que el usuario autenticado perteneciera al tenant y colocaba secretos operativos demasiado cerca de la capa conversacional.
La corrección reutilizó el módulo storefront-studio de Strapi. La única tool pública vigente es refresh_storefront_cache. Strapi exige el feature storefront-maintenance.refresh, deriva internamente el tag tenant:<slug> y mantiene los secretos de Vercel. Las tools antiguas de status, sitemap, revalidación por path, purge CDN y redeploy fueron retiradas.
Auditoría y observabilidad actuales
Las mutaciones registran eventos JSON con acción, actorUserId, tenantDocumentId, targetDocumentId, metadata segura y timestamp. /health entrega servicio, versión y estado, sin exponer URL de Strapi, configuración ni tokens.
- Registrar tool, actor, tenant, target, resultado y código de error.
- No registrar prompts completos por defecto.
- No registrar passwords, access tokens, refresh tokens, cookies ni payloads con PII innecesaria.
- Agregar requestId o correlationId end-to-end.
- Alertar por tasas anormales de 401, 403, 429, 5xx y latencia.
- Separar health de readiness cuando existan dependencias críticas.
Estrategia de pruebas y verificación
| Nivel | Qué protege |
|---|---|
| Schemas | Inputs válidos, defaults, enums, confirmaciones y rechazo de payloads maliciosos. |
| Adapters | Formas reales de Strapi, campos opcionales, PII y errores por drift. |
| Services | Queries, tenant headers, reglas y auditoría. |
| Auth | Scopes, challenges, expiración, audience y contexto por request. |
| OAuth | PKCE, DCR, login, código, refresh, revocación y stores Postgres. |
| MCP server | Inventario exhaustivo, schemas, annotations, securitySchemes y outputs. |
| Contract guard | Campos consumidos versus schema.json de Strapi. |
| Smoke | Inicialización MCP y llamada real sobre HTTP. |
| Developer Mode | Selección por el modelo, OAuth y experiencia end-to-end en ChatGPT. |
cd /ruta/a/neges-mcp
npm ci
npm run verify
# verify ejecuta, en orden:
# check:contract → lint → typecheck → test → buildLa suite debe fallar si una tool pública no tiene scope, outputSchema o annotations coherentes. Los tests de seguridad mínimos incluyen token para otra audiencia, scope faltante, acceso cruzado de tenant, confirm=false, refresh rotado y sesión Strapi revocada.
Developer Mode, Scan Tools y publicación
Desplegar un endpoint HTTPS estable
La V1 usa https://mcp.neges.cl/mcp. El hostname debe fijarse antes de publicar.
Probar en Developer Mode
Crear una app manual, conectar OAuth, revisar las 30 tools y ejecutar casos positivos y negativos con una cuenta de prueba.
Verificar dominio
El portal entrega un token; Railway lo configura como OPENAI_APPS_CHALLENGE y el endpoint well-known devuelve sólo ese valor.
Ejecutar Scan Tools
OpenAI importa nombres, descripciones, schemas, securitySchemes, annotations, _meta e instrucciones.
Completar el paquete de revisión
Identidad verificada, textos, íconos, política, términos, soporte, prompts, cinco pruebas positivas, tres negativas y credenciales demo.
Enviar, aprobar y publicar
Enviar inicia la revisión; no publica automáticamente. Después de aprobación, el responsable elige Publish.
La app de Neges se distribuye como un plugin con una app respaldada por MCP. Una UI embebida es opcional; la V1 funciona sólo con tools y respuestas estructuradas. Un widget futuro puede agregarse para tablas, previews o formularios sin mover la autoridad de negocio fuera de Strapi.
Versionado y compatibilidad después de publicar
| Tipo de cambio | Tratamiento |
|---|---|
| Bug interno, optimización o validación compatible | Desplegar backend; el usuario no reinstala. |
| Agregar campo opcional compatible al resultado | Evaluar outputSchema y snapshot; normalmente requiere metadata actualizada si cambia el contrato escaneado. |
| Nueva tool o cambio de nombre | Nueva versión, Scan Tools, revisión y publicación. |
| Cambiar input/output schema, descripción, annotation o securitySchemes | Nueva versión revisada del plugin. |
| Hacer obligatorio un campo antes opcional o eliminar un enum | Breaking change: publicar alternativa compatible y migrar antes de retirar. |
| Cambiar hostname del MCP | Evitarlo; el origen forma parte de la identidad pública y puede exigir una nueva app. |
En Developer Mode, después de cambiar la lista o descripción de tools se usa Refresh en la configuración de la app. Un plugin publicado usa un snapshot revisado: desplegar código no reemplaza automáticamente la metadata aprobada.
Procedimiento para agregar o modificar una tool
Escribir la intención de usuario
Define una frase concreta, el resultado esperado, quién puede ejecutarla y qué efectos causa. Evita empezar por un endpoint existente.
Decidir la frontera
Si hay integridad, transacción, permisos o secretos, implementa un comando seguro en Strapi. El MCP sólo adapta y orquesta.
Definir el contrato
Elige nombre estable, descripción, inputSchema, outputSchema, scope y annotations. Usa documentId y conceptos de dominio.
Diseñar confirmación e idempotencia
Para efectos sensibles agrega preview/confirm. Para reintentos de creación diseña una Idempotency-Key durable en el backend antes de marcar idempotente.
Implementar por feature
Agrega schema, types, queries, adapter, service y tool en el módulo correcto. Mantén el handler delgado.
Minimizar el output
Devuelve resumen, identificadores estables y campos útiles. Quita PII, secretos y detalles internos.
Actualizar el contrato compartido
Si consume algo nuevo de Strapi, modifica MCP-CONTRACT.md y agrega el guard automático cuando sea posible.
Probar fallos y seguridad
Incluye input inválido, scope ausente, tenant ajeno, permiso Strapi ausente, retry, timeout, conflicto y output schema.
Verificar y probar con el modelo
Ejecuta npm run verify, despliega y prueba prompts que deberían y no deberían elegir la tool.
Versionar la app
Si cambió metadata pública, Refresh en Developer Mode y Scan Tools para una nueva versión del plugin.
Método general para construir cualquier MCP
| Etapa | Preguntas que debes responder |
|---|---|
| 1. Casos de uso | ¿Qué tareas completas necesita el usuario? ¿Qué queda explícitamente fuera? |
| 2. Autoridad | ¿Cuál es el sistema de registro? ¿Dónde viven permisos y reglas de negocio? |
| 3. Riesgo | ¿Qué lee, escribe, publica, elimina, envía dinero o expone PII? |
| 4. Tools | ¿Existe una acción clara por intención? ¿Nombres y descripciones guían bien al modelo? |
| 5. Contratos | ¿Inputs y outputs son explícitos, pequeños, versionables y validados? |
| 6. Autenticación | ¿Cómo se valida token, issuer, audience, expiry y scope en cada llamada? |
| 7. Autorización | ¿El backend vuelve a comprobar usuario, organización, rol y recurso? |
| 8. Efectos | ¿Hay confirmación, preview, idempotencia, compensación y auditoría? |
| 9. Operación | ¿Existen timeout, métricas, errores seguros, health, backups y rollback? |
| 10. Distribución | ¿HTTPS, Developer Mode, scan, pruebas, privacidad y revisión están listos? |
Deuda técnica conocida y evolución recomendada
| Prioridad | Deuda | Evolución recomendada |
|---|---|---|
| Alta | La contraseña atraviesa /oauth/login del MCP. | Mover login y consentimiento a auth.neges.cl; dejar el MCP como resource server. |
| Alta | StrapiClientService no define timeout y retry general. | Agregar timeout; retry con backoff sólo para lecturas y estados transitorios seguros. |
| Alta | No existe idempotencia durable general para escrituras. | Implementar Idempotency-Key en comandos Strapi antes de reintentar creaciones. |
| Alta | AuditLogService depende del logger. | Crear audit sink durable con requestId, retención y redacción. |
| Media | La conversión precio bruto→neto está en el MCP. | Mover la regla tributaria a Strapi para una sola implementación. |
| Media | Carga de imagen es una saga de tres pasos desde el MCP. | Encapsular comando durable de upload/asociación/compensación en backend. |
| Media | El consentimiento no muestra scopes con granularidad al usuario. | Añadir pantalla de consentimiento clara y escalamiento de permisos. |
| Media | El guard de contrato cubre campos, no toda semántica. | Exportar contrato machine-readable y ejecutar tests integrados entre repos. |
| Baja | La V1 no tiene widget embebido. | Agregar UI sólo donde tablas, previews o formularios mejoren materialmente el flujo. |
Runbook de diagnóstico
| Síntoma | Revisión en orden |
|---|---|
| ChatGPT no conecta | Health → HTTPS/DNS → metadata OAuth → CORS → logs del registro de cliente. |
| Login dice solicitud expirada | Iniciar authorize de nuevo; revisar TTL, reloj y persistencia de pending authorization. |
| OAuth vuelve a ChatGPT con error | Redirect URI, CSP form-action, PKCE, resource y client_id durable. |
| Tool pide reconectar | Scope declarado, scope del token, challenge mcp/www_authenticate y audience. |
| 403 desde Strapi | Tenant activo, membresía, rol, feature y X-Tenant-Id; no ampliar scopes a ciegas. |
| Campos null o relaciones vacías | MCP-CONTRACT.md, query populate, adapter y cambio reciente de schema/controller Strapi. |
| Escritura con timeout | Consultar el estado real antes de repetir; no reintentar si la operación no es idempotente. |
| Tool nueva no aparece | Registro en McpServerFactory, despliegue correcto, list tools y Refresh/Scan Tools. |
| El modelo elige una tool incorrecta | Descripción, solapamiento de tools, ejemplos, campos requeridos y prompt de prueba. |
Definition of Done para un cambio MCP
- La intención y los límites están documentados.
- La regla de negocio vive en Strapi cuando corresponde.
- La tool tiene nombre estable, descripción precisa e input/output schema.
- securitySchemes y requireScope coinciden.
- Annotations reflejan efectos reales.
- Tenant, membresía, RBAC y feature se validan en backend.
- Las acciones sensibles tienen preview o confirmación en código.
- El output es mínimo, estructurado y no expone secretos ni PII innecesaria.
- MCP-CONTRACT.md y adapters están sincronizados.
- Tests positivos, negativos, auth, cross-tenant y output schema pasan.
- npm run verify pasa en neges-mcp y las pruebas afectadas pasan en neges-strapi.
- El despliegue fue probado en Developer Mode.
- Si cambió metadata, se ejecutó Refresh o Scan Tools y se preparó una nueva versión.
- La documentación del centro de ayuda fue actualizada.
- Existe un plan de rollback y no se incluyeron secretos en código o logs.
Fuentes y documentos de referencia
¿Te fue útil este contenido?
Tu feedback nos ayuda a mejorar la documentación de Neges.
¿No encuentras lo que buscas?
Conversa con nuestro equipo de soporte en español. Respondemos por WhatsApp, correo o ticket en menos de 24 horas hábiles.