Arquitectura
arcane-mcp sigue el diseño basado en catálogo de gitlab-mcp-server, adaptado a una API que publica un contrato legible por máquina.
Un catálogo, tres superficies
Sección titulada «Un catálogo, tres superficies»Cada capacidad de Arcane se declara una sola vez, como un toolutil.ActionSpec con un ID canónico dominio.acción (container.restart, swarm_service.scale). Las superficies son proyecciones de ese catálogo, nunca registros paralelos, así que no pueden divergir:
| Superficie | Paquete | Herramientas | Forma de llamada |
|---|---|---|---|
| dynamic (predeterminada) | internal/tools/dynamic | arcane_find_action, arcane_execute_action | {"action":"container.restart","params":{...},"confirm":false} |
| meta | internal/tools/meta | arcane_<dominio> | {"action":"restart","params":{...},"confirm":false} |
| individual | internal/tools/individual | arcane_<dominio>_<acción> | {...parámetros, "confirm":false} |
Las tres llaman a toolutil.Dispatch, que aplica las mismas reglas en el mismo orden: valida los argumentos contra el esquema de entrada de la acción; ante una acción destructiva sin confirm: true, pregunta al usuario mediante elicitación MCP si el cliente lo admite y el modo seguro está desactivado y, si no, la rechaza; responde con una vista previa en modo seguro; ejecuta dentro del plazo por acción (ARCANE_MCP_ACTION_TIMEOUT) y presenta el resultado; escribe un registro de auditoría. Los errores se devuelven como resultados de herramienta con isError y un siguiente paso, nunca como errores de protocolo.
internal/wiring construye el servidor para la superficie configurada; internal/tools/manifest añade los recursos arcane://tools y arcane://tools/{id}, internal/resources los recursos con el estado de Docker y las guías de trabajo (Recursos), internal/prompts los prompts de flujo de trabajo (Prompts), e internal/completions las sugerencias para sus argumentos y para las variables de las plantillas de recurso, sacadas de las acciones de listado del catálogo con el mismo cliente y guardadas en caché unos segundos (Autocompletado). El modo de solo lectura y las exclusiones se aplican una sola vez, cuando actioncatalog.Build filtra el catálogo, de modo que ninguna superficie tiene que acordarse de ellos.
api/arcane.openapi.json ─┐api/extra-routes.yaml ───┼─► cmd/gen_actions ─► internal/tools/<dominio>/zz_generated_actions.goapi/actions.yaml ────────┘ + capa escrita a mano (actions.go) │ internal/tools.BuildCatalog │ ┌───────────────────────────────┼───────────────────────────────┐ dynamic meta individual find / execute (2 herramientas) una herramienta por dominio una herramienta por acciónDe dónde sale el catálogo
Sección titulada «De dónde sale el catálogo»api/arcane.openapi.json el documento OpenAPI del propio Arcane (generado por Huma), incorporado al repositorioapi/actions.yaml operationId → dominio.acción, más correcciones de seguridadapi/extra-routes.yaml rutas que Arcane sirve fuera de OpenAPI (sacadas de su código)api/coverage-allowlist.yaml rutas que no se exponen a propósito, con el motivo │ ▼ go run ./cmd/gen_actionsinternal/tools/<paquete>/zz_generated_actions.go constantes de ID + declaracionesinternal/tools/<paquete>/actions.go capa propia, escrita a manointernal/tools/zz_generated_domains.go la lista de dominios │ ▼ internal/tools.BuildCatalogactioncatalog.Catalog (validado, filtrado por solo lectura / exclusiones)- La API REST de Arcane se declara con Huma, así que el documento OpenAPI que sirve se genera a partir del mismo código que enruta las peticiones. Está incorporado en
api/, y de él se deriva el esquema de entrada de cada acción. - Las declaraciones generadas contienen el ID, el operationId, la ruta, el resumen y la descripción de Arcane, y la clasificación como modificadora, destructiva o idempotente.
api/actions.yamlda nombre a cada operación y corrige la clasificación cuando un verbo engaña. - Los esquemas de entrada no se escriben a mano:
internal/openapideriva el de cada acción a partir de la operación al construir el catálogo. Los parámetros de ruta, consulta, cabecera y cookie y las propiedades del cuerpo JSON se convierten en un único objeto plano; los componentes referenciados se copian bajo$defs. El parámetro de ruta{id}de/environments/{id}/...pasa a ser unenvironmentIdopcional que toma por defectoARCANE_ENVIRONMENT_ID. - Las rutas fuera del documento OpenAPI —WebSockets, pprof, el disparador de webhooks, el intercambio de tokens federados— se enumeraron a partir del código de Arcane y se declaran en
api/extra-routes.yaml. Las pocas que no se exponen (el protocolo de los agentes, el relé de telemetría del navegador, ayudas que solo existen para pruebas) figuran con su motivo enapi/coverage-allowlist.yaml. - Las capas escritas a mano (
actions.goen cada paquete de dominio) añaden lo que una especificación no puede saber: notas de uso que distinguen acciones vecinas (project.downfrente aproject.destroy), alias (docker ps), acciones relacionadas y manejadores para las acciones con comportamiento propio. Aplicar una capa falla ante un ID que ya no existe, así que un cambio de nombre en Arcane no puede dejar atrás un ajuste obsoleto.
Ejecución
Sección titulada «Ejecución»La mayoría de las acciones pasan por el ejecutor genérico toolutil.ExecuteREST: los argumentos se vuelven a asociar a la plantilla de ruta, la cadena de consulta, las cabeceras, las cookies y el cuerpo (JSON, multipart con objetos de fichero o un octet-stream en bruto) mediante las asociaciones con las que se construyó el esquema. Después, toolutil.ReadResponse da forma a la respuesta:
| Arcane responde | La acción devuelve |
|---|---|
JSON {success, data, pagination} | data como contenido estructurado (las listas bajo items), con pagination al lado |
progreso application/x-json-stream (pull, build, up, redeploy) | se consume hasta el final; estado, ID de actividad, errores y últimas líneas de progreso |
| una descarga de texto (logs, YAML, PEM) | el texto, limitado por ARCANE_MCP_MAX_DOWNLOAD_BYTES |
| una descarga binaria (tarballs, copias de seguridad, perfiles) | metadatos más un recurso blob incrustado |
Los manejadores escritos a mano (internal/tools/livestream y las capas de cada dominio) cubren las rutas que quedan fuera de OpenAPI: logs y estadísticas por WebSocket, el terminal de contenedores tras container.exec, el canal SSE, los flujos de diagnóstico, pprof, el disparador anónimo de webhooks y el intercambio de tokens federados RFC 8693. Flujos y ficheros describe qué devuelve cada uno.
Autenticación
Sección titulada «Autenticación»internal/arcane.Client envía X-API-Key cuando hay una clave de API configurada. Con ARCANE_USERNAME/ARCANE_PASSWORD inicia sesión, guarda los tokens de acceso y de renovación y, ante un 401, renueva (o vuelve a iniciar sesión) y repite la petición una vez. Las operaciones cuya seguridad OpenAPI solo admite una sesión Bearer (passkeys, MFA) usan la sesión aunque haya una clave de API configurada.
En modo HTTP el servidor mantiene un servidor MCP por cada clave de Arcane distinta (la X-Arcane-API-Key del cliente o la del propio servidor), todos compartiendo un único catálogo construido.
Comprobaciones de paridad
Sección titulada «Comprobaciones de paridad»| Comprobación | Qué demuestra |
|---|---|
go run ./cmd/gen_actions -check | Las declaraciones generadas coinciden con la especificación incorporada y con los ficheros de inventario |
go run ./cmd/audit_1to1 | Cada operación OpenAPI y cada ruta adicional corresponde a una acción del catálogo (o a la lista de excepciones), y cada acción cubre una ruta |
tests de internal/wiring | Cada acción está registrada en las superficies meta e individual y la resuelve el execute dinámico; la misma llamada da el mismo resultado en las tres |
test/e2e | Los escenarios se ejecutan contra instancias reales de Arcane, cada uno como un subtest por superficie |
go run ./cmd/gen_tool_reference -check | La referencia de herramientas de este sitio coincide con el catálogo |
Actualizar a una nueva versión de Arcane
Sección titulada «Actualizar a una nueva versión de Arcane»ARCANE_URL=http://<nuevo arcane> make update-spec # descarga el nuevo documento OpenAPImake generate # se niega hasta que cada operación nueva tenga nombre en api/actions.yamlmake audit-1to1 # todas las rutas cubiertasmake gen-tool-reference # actualiza la referencia de este sitioDespués, ejecuta la batería de extremo a extremo contra un entorno de la nueva versión.