agent-tools-plugin-n8n
v0.3.3
Published
n8n plugin for agent-tools-runtime: typed facade tools plus deterministic skills for data tables, workflow orchestration and read-only security/robustness auditing (insert-and-verify-datatable-row, data-table-crud, create-and-verify-workflow, audit-workfl
Maintainers
Readme
agent-tools-plugin-n8n
Plugin de agent-tools-runtime para
n8n. El primero y más medido de los tres plugins del proyecto — extensamente
benchmarkeado contra gpt-oss:20b-cloud y gpt-oss:120b-cloud.
Instalación
npm install agent-tools-plugin-n8nConfiguración
Una sola variable de URL para todo el plugin, más las credenciales:
export N8N_INSTANCE_URL="https://tu-instancia.n8n" # una sola vez, cubre MCP y REST
export N8N_MCP_TOKEN="<tu token MCP>"
export N8N_API_KEY="<tu api key REST, Settings → n8n API>" # solo si usás audit-workflows/delete-workflow(-bulk)
# O un token store persistente en disco (ver n8n-oauth.mjs), si preferís
# no pasar N8N_MCP_TOKEN por variable de entorno cada vez.N8N_INSTANCE_URL es la única variable de URL que hace falta configurar: tanto el adapter MCP
(agent_tools_n8n_discover/_call) como las tres skills que hablan la REST API directo
(audit-workflows, delete-workflow, delete-workflows-bulk — ver la sección de Skills) la
derivan de ahí, completando cada una el path que necesita (/mcp-server/http para el MCP, la raíz
para la REST API). Ninguna llamada necesita que le pases url a mano.
N8N_MCP_URL (formato completo, con /mcp-server/http) sigue existiendo como override — solo
hace falta si tu MCP y tu REST API viven en hosts distintos, o para configs de antes de que
existiera N8N_INSTANCE_URL. Si no tenés ese caso puntual, no la definas: alcanza con
N8N_INSTANCE_URL.
Tools expuestas
Con prefix: "n8n", el runtime genera:
agent_tools_n8n_discover({ query? })agent_tools_n8n_call({ toolName, arguments, confirm? })agent_tools_n8n_run_skill({ skill, arguments })
_call da acceso a las tools del MCP de n8n (creación/lectura de workflows, data tables, ejecuciones,
etc.) — el catálogo lo define n8n, este plugin solo lo reenvía tipado.
Este plugin corre con requireConfirm: false (plugin.json, campo leído por el runtime desde
>=0.2.2): a diferencia de los demás plugins de agent-tools-runtime, agent_tools_n8n_call ejecuta
tools que mutan estado (create_workflow_from_code, update_workflow, archive_workflow,
publish_workflow, etc.) sin exigir confirm: true — el argumento sigue existiendo en el schema por
compatibilidad, pero no tiene efecto acá. No hay freno del lado del runtime contra una mutación
accidental; queda en quien llama a la tool.
Nota sobre borrado real de workflows: el catálogo MCP de n8n no tiene un delete_workflow — lo más
parecido es archive_workflow, que archiva, no borra. Un borrado permanente solo existe en la REST API
de n8n (DELETE /api/v1/workflows/{id}), fuera del MCP; por eso es una skill aparte
(delete-workflow, ver abajo), no una tool de _call.
url es opcional en audit-workflows, delete-workflow y delete-workflows-bulk: estas tres
hablan la REST API directo (no el MCP), así que no heredan N8N_MCP_URL del adapter automáticamente.
Orden de resolución si no se pasa url en la llamada: N8N_INSTANCE_URL (si está seteada, gana) →
si no, se deriva de N8N_MCP_URL (o su default) sacándole el sufijo /mcp-server/http. No hace
falta pasar url en ninguna llamada salvo un caso puntual: REST y MCP en hosts distintos sin haber
seteado N8N_INSTANCE_URL.
Skills
Las primeras tres, medidas en un benchmark real (ver detalle):
insert-and-verify-datatable-row({ column, value, dataTableId?, tableName?, confirm })— determinista, no genera código: crea la data table si hace falta, crea y publica un workflow con una plantilla ya probada, lo ejecuta y confirma el valor leído. La más medida y confiable de las tres.data-table-crud({ operation: "create"|"insert"|"read", ... })— determinista.createeinsertson llamadas directas a n8n (sin workflow);readarma un workflow mínimo porque n8n no expone una tool directa de lectura de filas.create-and-verify-workflow({ code, name, publish?, execute? })— la única genuinamente genérica: vos generás el código del@n8n/workflow-sdk, la skill valida/crea/publica/ejecuta en una sola llamada por intento, con errores estructurados por etapa.audit-workflows({ url?, mode?, all?, workflowId?, exportDir?, auditCategories?, daysAbandoned?, status?, maxExecutions?, page?, pageSize? })— auditoría de seguridad/robustez de solo lectura, vía la REST API de n8n (no el MCP). Envuelveaudit_n8n_workflows.py(vendorizado desden8n-workflow-auditor, mismo autor, MIT) a través del adapterlocal-clidel runtime — no reescribe la lógica en JS.mode:audit(default, 7 reglas por workflow: webhooks sin auth, credenciales hardcodeadas, nodos de alto riesgo, error workflow, reintentos, nodos huérfanos, trigger alcanzable),summary(inventario),export(backup aexportDir),nativeAudit(envuelvePOST /api/v1/auditde n8n),executions(tasa de error real por workflow),credentials(inventario de metadata, nunca valores).allpor default estrue(catálogo completo, activos + inactivos) — el script solo trae workflows activos si no se le pasa--all, así que uninactive: 0sin este default reflejaba que nunca miró los inactivos, no que no existieran. Pasáall: falseexplícito solo si de verdad querés la vista recortada a activos.El script trae siempre el resultado completo (en una instancia con cientos de workflows eso es decenas de KB en un solo array); la skill lo pagina después, sobre el JSON ya completo, sin tocar el script. Aplica a los modos que devuelven una lista grande —
audit/summary(workflows),executions(by_workflow),credentials(credentials) — no aexport/nativeAudit.page(default1) ypageSize(default50, tope200) son opcionales; la respuesta agrega un campopagination: { page, pageSize, totalItems, totalPages }. Pedir una página fuera de rango la ajusta a la última disponible en vez de devolver vacío por error.Requiere en el entorno del proceso del runtime (no se puede pasar por argumento de la skill):
export N8N_API_KEY="<api key REST de n8n, Settings → n8n API — distinta del token MCP>" # opcional: export N8N_AUDIT_PYTHON_BIN="/ruta/a/python3" # default: python3 en PATHdelete-workflow({ url?, workflowId })odelete-workflow({ url?, namePattern })— borrado real y permanente de un workflow, víaDELETE /api/v1/workflows/{id}de la REST API de n8n (no el MCP — ver la nota de arriba sobre por qué no es una tool de_call).namePattern(mismo argumento quefind-workflows/delete-workflows-bulk) resuelve a unworkflowIdvia substring match case-insensitive: error si matchea 0 workflows, o si matchea más de 1 (devuelve la lista de matches en vez de adivinar cuál — para eso estádelete-workflows-bulk). No aceptaworkflowIdynamePatterna la vez. RequiereN8N_API_KEYen el entorno del proceso del runtime, igual queaudit-workflows. Sin confirmación propia — coherente conrequireConfirm: falsedel resto del plugin: si se llama, borra. Devuelve{ isError: false, workflowId, deleted: <workflow borrado> }en éxito, o{ isError: true, status, error }si n8n rechaza el pedido (ej. id inexistente → 404).delete-workflows-bulk({ url?, active?, namePattern? })— borra en lote los workflows que matchean el filtro.active(boolean) filtra por estado activo/inactivo;namePattern(string) hace substring match case-insensitive contra el nombre; se puede pasar uno, el otro, o ambos (AND). Exige al menos uno de los dos — sin filtro, error, no borra nada (no hay "borrar todo" implícito). Pagina la lista completa antes de filtrar (GET /api/v1/workflowsde n8n pagina de a 100 — undelete-workflowuno-por-uno sobre solo la primera página se queda corto en cualquier instancia con más de 100 workflows). Sin API de bulk-delete en n8n: por dentro sigue siendo unDELETEpor workflow. Devuelve{ isError, totalWorkflows, matchedCount, deletedCount, failedCount, deleted: [...], failed: [...] }—isErrorsolo estruesi hubo matches y ninguno se pudo borrar; fallas parciales quedan enfailedsin marcar la llamada entera como error. RequiereN8N_API_KEY, sin confirmación propia, misma política quedelete-workflow.find-workflows({ url?, active?, namePattern?, page?, pageSize? })— de solo lectura, equivalente adelete-workflows-bulkpero sin el paso de borrado: mismo filtro (active/namePattern, ambos opcionales acá — sin ninguno devuelve el catálogo completo), misma paginación REST completa por dentro (compartenfetchAllWorkflowsen_shared.mjs). Devuelve{ isError: false, totalWorkflows, matchedCount, workflows: [{id, name, active, createdAt, updatedAt}, ...], pagination: {page, pageSize, totalItems, totalPages} }. Existe porque la tool MCPsearch_workflowsde n8n no es confiable para esto: su schema real es sololimit/projectId/query/sortBy/tags(topelimit=200), sin cursor ni filtro poractive— pedirle distintas páginas con parámetros que no existen (offset,skip) devuelve siempre el mismo lote sin avisar del error. Usáfind-workflowspara cualquier "buscar/contar/listar workflows [in]activos", ysearch_workflowssolo para lo que sí soporta (buscar por nombre/tag dentro de los primeros 200).find-node-types({ queries: string[] })ofind-node-types({ nodeIds: [{nodeId, resource?, operation?, mode?, version?}, ...] })— juntasearch_nodes/get_node_types(las dos tools MCP para identificar y tipar nodos antes de escribir código concreate-and-verify-workflow) bajo una skill con su propia validación, en vez del error genérico de_callcuando falta un argumento o el shape no es el esperado.queriesbusca nodos por nombre/servicio (equivalente asearch_nodes, ya devuelve la guía de qué llamar después);nodeIdstrae la definición TypeScript exacta de nodos ya identificados (equivalente aget_node_types, valida que cada entrada tenganodeIdantes de llamar). No acepta ambos a la vez — es un paso, no encadena automáticamente dequeriesanodeIds: la salida desearch_nodeses texto libre pensado para que lo lea un LLM (discriminadores anidados en prosa, no JSON estructurado), parsearlo a ciegas para "elegir el mejor match" arriesgaría construir unnodeIdequivocado en silencio.create-credential({ type, name?, data? })— crea una credencial en n8n víaPOST /api/v1/credentials(no existe en el catálogo MCP —list_credentialses de solo lectura). Sinname/data, solo trae el schema real del tipo (GET /credentials/schema/{type}) para saber qué campos pide, sin crear nada. Con los tres, valida contra ese mismo schema quedatatenga los campos requeridos antes de llamar (error específico — "falta accessToken" — en vez del genérico de n8n) y crea la credencial. No obtiene secretos por su cuenta: para tipos con token estático (ej.slackApi, que solo pideaccessToken) alcanza con que el humano haya generado ese token una vez en la app de origen; para tipos OAuth2 reales (ej.slackOAuth2Api), conseguir el token todavía exige el consentimiento por navegador — eso lo sigue haciendo un humano, esta skill solo registra los datos ya obtenidos. Devuelve{ isError: false, credentialId, name, type }en éxito, o{ isError: false, mode: "schema", type, schema }en el modo de solo consulta. RequiereN8N_API_KEY, misma política que las demás skills REST de este plugin.delete-credential({ url?, credentialId })— borrado real de una credencial, víaDELETE /api/v1/credentials/{id}de la REST API (mismo motivo quedelete-workflow: no existe en el catálogo MCP).credentialIdsale delist_credentials. Devuelve{ isError: false, credentialId, deleted }en éxito. RequiereN8N_API_KEY, sin confirmación propia — misma políticarequireConfirm: falseque el resto del plugin.
Licencia
MIT. scripts/audit_n8n_workflows.py vendorizado desde
thehumanintheloop-marketplace-codex,
también MIT, mismo autor.
