Skip to main content
Glama
MauricioPerera

Agent Tools Runtime

Agent Tools Runtime

Runtime persistente basado en just-bash para que un agente descubra y cargue progresivamente adaptadores MCP, REST y CLI local sin exponer todo el catálogo de herramientas en cada conversación.

Estado

Este repositorio es la evolución independiente de la POC publicada en TheHumanInTheLoop Marketplace. La API todavía está en 0.x; cualquier cambio puede requerir migración.

Related MCP server: Terminal MCP Server

Capas

MCP facade → persistent runtime → adapter → provider

Adaptadores incluidos:

  • MCP genérico con sesiones y tokens host-side.

  • n8n MCP con OAuth/token host-side.

  • REST/API con rutas relativas y confirmación para mutaciones.

  • CLI local con allowlist, execFile, timeout y confirmación.

Transportes: stdio (default) y Streamable HTTP

runtime/mcp-server.mjs habla MCP por stdio -- el caso de uso original y el que sigue sin cambios (npm run mcp, o bin/agent-tools-mcp.mjs si el paquete está instalado). Pero algunos clientes MCP no pueden spawnear un subproceso stdio: por ejemplo eve (framework de agentes de Vercel), cuyas connections/*.ts (defineMcpClientConnection) exigen una url que hable Streamable HTTP o SSE, sin opción de command/args. Para esos casos existe runtime/mcp-http-server.mjs (npm run mcp:http, o bin/agent-tools-mcp-http.mjs), el mismo dispatcher (createMessageHandler() en mcp-server.mjs, extraído para no atarlo a ningún transporte) expuesto sobre HTTP en vez de stdin/stdout.

export AGENT_TOOLS_HTTP_PORT=8321      # default si se omite
export AGENT_TOOLS_HTTP_HOST=127.0.0.1 # default -- solo localhost, ver nota de seguridad abajo
npm run mcp:http

Implementa la variante simple del spec (2025-03-26): una respuesta JSON por request (Content-Type: application/json), sin upgrade a SSE -- el catálogo de mensajes de este runtime (initialize/tools/list/tools/call) es enteramente request/response, sin mensajes server-initiated, así que streaming no aporta nada todavía. Session ID vía Mcp-Session-Id (emitido en initialize, validado en requests siguientes, 404 si no se reconoce), protección DNS-rebinding (rechaza Origin no-local con 403), bind a 127.0.0.1 por default. Sin batching de mensajes en esta versión -- ningún cliente probado lo necesitó.

Cliente probado en vivo: eve (Vercel)

eve es un framework de agentes "filesystem-first": herramientas, conexiones y skills viven como archivos convencionales bajo agent/. Conectarlo a este runtime es crear agent/connections/agent-tools-runtime.ts:

import { defineMcpClientConnection } from "eve/connections";

export default defineMcpClientConnection({
  url: "http://127.0.0.1:8321/mcp",
  description: "agent-tools-runtime: typed facades over Ollama, ccdd-gate, n8n, GitHub, PocketBase.",
});

Gotcha real encontrado en vivo, no hipotético: con un modelo fuera del catálogo de AI Gateway (en la prueba, Ollama local vía @ai-sdk/openai-compatible), eve falla al arrancar con "Cannot compile agent compaction because the primary compaction trigger model ... does not have known AI Gateway context window metadata" -- necesita saber la ventana de contexto del modelo para su feature de compaction, y un modelo custom no la trae. Se resuelve declarándola explícita en agent.ts:

export default defineAgent({
  model: ollama.chatModel("gpt-oss:20b-cloud"),
  modelContextWindowTokens: 131072,
});

Dos corridas reales, mismo modelo (gpt-oss:20b-cloud vía Ollama local), mismo transporte HTTP:

ollama (5 tools, 0 skills)

n8n (10 skills)

Tools usadas

discover + call

run_skill × 2 (find-workflows, después audit-workflows)

Decisión del modelo

directa

encadenó dos skills razonando qué le faltaba para responder "configuración riesgosa" sin que nadie se lo indicara

Resultado

tabla correcta de modelos disponibles

hallazgos reales (webhooks sin autenticación, workflows activos sin trigger) + recomendaciones, honesto sobre el corte de la muestra (356 workflows activos, mostró algunos y ofreció ampliar)

En ambos casos el flujo fue el mismo y sin fricción: connection_search (mecanismo propio de eve para descubrir tools de una conexión MCP por texto libre) encontró la conexión y las tools/skills correctas, verificado en el trace crudo del stream de eventos, no solo en la respuesta final.

Cliente probado en vivo: Pi (pi.dev) -- vía extensión propia, no MCP

Pi es un coding agent liviano construido sobre el Claude Agent SDK. No tiene soporte MCP nativo -- depende de un paquete de terceros, pi-mcp-adapter, que expone un único tool mcp() proxy (para no gastar contexto con el catálogo completo de cada server, mismo espíritu que la fachada discover/call de este runtime).

Intento 1, con pi-mcp-adapter, fallido -- documentado para no repetir el mismo camino: instalé el adapter (pi install npm:pi-mcp-adapter) y probé tanto .pi/mcp.json como .mcp.json (las dos ubicaciones que documenta el paquete) apuntando a este runtime. En ambos casos, verificado en el trace crudo, el modelo nunca vio el tool mcp() -- solo tenía disponibles los tools nativos de Pi (bash, read). La causa, confirmada en la documentación oficial de Pi (security.md): la activación real de un server pasa por /mcp o /mcp setup, explícitamente descriptos como "interactive panel and first-run onboarding surface" -- sin equivalente de línea de comandos para modo headless (-p). No es un bug de este runtime ni del adapter; es una limitación real de pi-mcp-adapter para automatización sin TTY.

Intento 2, extensión propia -- funciona. Las extensiones de Pi (pi.registerTool()) se registran al cargar la extensión, antes de cualquier gate interactivo -- esquivan el problema por completo. integrations/pi-extension -- publicado como paquete real de Pi, agent-tools-runtime-pi-extension -- hace tools/list contra el transporte HTTP de este runtime al arrancar y registra cada tool real como un tool nativo de Pi, reusando las mismas tres formas de argumento fijas que ya define mcp-server.mjs (discover/call/run_skill) en vez de reenviar el JSON Schema crudo, para no depender de si el validador de Pi acepta ese formato sin la marca típebox.

pi install npm:agent-tools-runtime-pi-extension   # o -e npm:agent-tools-runtime-pi-extension para probar sin instalar
npm run mcp:http   # el server HTTP tiene que estar corriendo

Verificado en vivo, mismo prompt/modelo que la prueba de n8n con eve (comparable directamente): llamó agent_tools_n8n_run_skill({skill:"find-workflows"}), después agent_tools_n8n_discover y dos agent_tools_n8n_call({toolName:"get_workflow_details"}) para inspeccionar workflows puntuales -- encontró los mismos dos workflows con webhook sin autenticación que ya había encontrado eve, con los IDs reales coincidiendo. Estrategia distinta a la de eve (inspección puntual en vez de la skill audit-workflows completa), mismo resultado correcto.

Mismo prompt, mismo proyecto, mismas tools registradas -- solo cambiando a un modelo chico (qwen2.5:1.5b): cero tool calls. Respuesta vaga y divagante ("invertir en recursos", "consultar a alguien versátil") sin tocar ninguna tool real. Como la extensión ya estaba confirmada funcionando en la corrida anterior con el mismo setup exacto, este resultado negativo queda aislado limpio como límite de capacidad del modelo, no de la integración -- mismo patrón que el resto de esta sección de discoverabilidad: modelos grandes usan esta capa sin fricción, modelos chicos ni la intentan.

Cliente probado en vivo: Hermes Agent -- conecta, pero el modelo no encontró las tools

Hermes Agent habla MCP nativo, cliente y servidor, sin adapters de terceros. Conectarlo es un solo comando:

hermes mcp add agent-tools-runtime --command node --args "<repo>/runtime/mcp-server.mjs" \
  --env N8N_API_KEY=... N8N_INSTANCE_URL=... N8N_MCP_TOKEN=...

hermes mcp list confirma la conexión y las 22 tools detectadas correctamente -- este paso funcionó sin fricción, mejor que eve o Pi en la parte de wiring.

El problema aparece un paso después. Mismo modelo que anduvo perfecto en cada otra integración de esta sesión (gpt-oss:20b-cloud, vía Ollama local), mismo prompt de n8n usado para eve y Pi. Confirmado con Tools: 50 en el log de la API request (nuestras 22 + el toolset nativo grande de Hermes -- browser_*, terminal, search_files, skill_view/skills_list, etc.).

Seis corridas en total (2 iniciales + 4 repetidas para separar patrón de variancia), 1/6 con éxito real:

  • Cinco fallaron sin llamar nunca mcp_agent_tools_runtime_agent_tools_n8n_*, por dos caminos distintos: confundiendo el sistema de skills nativo de Hermes con el nuestro (skill_view({name: "n8n:find-workflows"}), sin resultado), o cavando el filesystem local con search_files/terminal (n8n.db, docker ps, env | grep N8N) hasta rendirse.

  • Una sí funcionó (corrida 3 de la repetición): llamó agent_tools_n8n_discover + agent_tools_n8n_call + agent_tools_n8n_run_skill correctamente, y la respuesta final citó los números reales (1004 workflows totales, 356 activos -- coincide exactamente con lo que encontró eve) -- verificado en el razonamiento crudo del modelo, no solo la respuesta, no fue una fabricación.

Lectura calibrada con las seis corridas: el wiring MCP de Hermes funciona bien -- es la conexión más simple de las cinco probadas. El problema es de descubrimiento, y es probabilístico, no absoluto: con un toolset nativo tan grande compitiendo por atención, el modelo llega al prefijo mcp_agent_tools_runtime_* en aproximadamente 1 de cada 6 intentos con este prompt/modelo. No se investigó si acotar toolsets activos por sesión (-t) sube esa tasa -- queda como pregunta abierta.

Cliente probado en vivo: Droid (Factory) -- fabricó una vez en seis, no es el comportamiento típico

Droid es el CLI de Factory, con MCP nativo vía .factory/mcp.json:

droid mcp add agent-tools-runtime node "<repo>/runtime/mcp-server.mjs" \
  --env N8N_API_KEY=... N8N_INSTANCE_URL=... N8N_MCP_TOKEN=...

Bug real encontrado en el camino: droid mcp add escribió la ruta con las barras invertidas comidas (C:UsersAdministrador... en vez de C:\Users\Administrador\...) -- se perdieron al pasar por el shell. Se corrige a mano editando ~/.factory/mcp.json con barras normales (Node las acepta igual en Windows). Con eso, droid exec --list-tools confirmó las 22 tools reconocidas.

Seis corridas en total (2 iniciales + 4 repetidas), mismo modelo/prompt, cero éxitos reales -- pero la severidad inicial estaba sobre-representada por una muestra de 1:

  • Corrida 1 original (-o text): devolvió una tabla de auditoría de n8n extremadamente detallada y convincente -- IDs de workflow, versión de n8n 2.27.5, publicApiEnabled=true, conteo de credenciales sin usar, nodos comunitarios. Verificado con droid search "n8n" --kind tool_use --json sobre el historial real de la sesión (no la respuesta, el trace crudo almacenado): cero llamadas a agent_tools_n8n_* o a cualquier tool MCP. La sesión completa solo usó Grep (16), Read (12), LS (2), Execute (10) contra el filesystem local. Todo el reporte fue alucinado: ni un ID, versión o setting real detrás.

  • Corrida 2 original (-o json): honesta -- "no pude encontrar configuración de n8n...".

  • Las 4 corridas de repetición: las cuatro fallaron honestamente (el modelo revisó el repositorio local -- lo confundió con kite-lite, el otro proyecto en este mismo directorio -- y admitió no tener acceso a n8n), ninguna fabricó datos. Confirmado también con droid search sobre esas cuatro sesiones: cero llamadas a tools MCP, y cero rastro de contenido inventado.

Lectura calibrada con las seis corridas: la fabricación fue real y está verificada -- pasó una vez de seis -- pero no es "lo que Droid hace", es un evento de cola dentro de un patrón más amplio de "no descubre las tools" que comparte con Hermes y Codex. Sigue siendo la corrida más seria de esta comparación (un reporte de seguridad ficticio con apariencia legítima es peor que un "no sé"), y es exactamente el tipo de caso que "verificar en el trace crudo, no confiar en la respuesta" existe para cazar -- pero generalizar de N=1 a "Droid fabrica" hubiera sido un error; con N=6 el dato real es "puede pasar, y cuando pasa es grave, pero no es el resultado típico".

Cliente probado en vivo: Codex (OpenAI) -- mismo patrón que Hermes, sin fabricar datos

Codex tiene MCP nativo vía codex mcp add:

codex mcp add agent-tools-runtime --env N8N_API_KEY=... --env N8N_INSTANCE_URL=... --env N8N_MCP_TOKEN=... \
  -- node "<repo>/runtime/mcp-server.mjs"

codex mcp get agent-tools-runtime confirmó el registro correcto (esta vez con barras normales desde el principio, aprendido del bug de Droid). Mismo modelo/prompt de siempre; corrida con --json desde el arranque para verificar el trace crudo directamente, sin pasar primero por la respuesta en texto.

Resultado: cero llamadas a agent_tools_n8n_*. En cambio, el modelo fue directo a buscar en el filesystem local con PowerShell:

Get-ChildItem -Recurse -Filter *n8n*
Get-ChildItem -Recurse -Force -Filter .n8n
Get-ChildItem -Recurse -Filter *.sqlite

No encontró nada (obvio, n8n corre remoto) y terminó honestamente: "necesitamos acceder a la configuración y a la base de datos que utiliza n8n" -- a diferencia de Droid, no fabricó datos.

Multi-plugin en un mismo turno: la extensión funciona, el modelo a veces no completa el trabajo

Todo lo anterior probó un plugin a la vez. Con las 8 plugins cargadas juntas (25 tools totales) y un pedido que cruza dos dominios sin relación en el mismo turno ("qué modelos de Ollama tenés disponibles, y aparte, un resumen de withastro/astro en GitHub"), aparece un patrón distinto al de los otros clientes: acá la extensión SÍ conecta y el modelo SÍ encuentra las tools correctas -- el problema es si las llama hasta el final o no.

  • gpt-oss:20b-cloud (el modelo de todas las pruebas anteriores): llamó agent_tools_ollama_discover({}) -- que solo lista el catálogo de tools, no modelos -- y ahí se quedó, sin llamar nunca agent_tools_ollama_call({toolName:"list_models"}). La respuesta final inventó 5 modelos que no existen (llama3, mixtral:8x7b, wizardlm, deepseek-coder, gpt4all-j). La mitad de GitHub sí usó una tool call real (agent_tools_github_run_skill) y trajo datos reales -- pero incluso ahí, el número de issues abiertos se corrompió en la respuesta final (la tool devolvió 123, la respuesta dijo 31).

  • gemma4:cloud, mismo prompt, misma extensión: sí llamó agent_tools_ollama_call con list_models/list_running_models reales -- la lista final coincide exactamente con los modelos reales de la instancia, cero fabricación. Para GitHub, en vez de perder el dato como hizo gpt-oss, presentó los dos números reales con contexto ("123 total incluyendo Pull Requests / 31 issues específicos") -- más fiel a la tool call real, no menos.

Lectura: con una sola tarea/plugin por turno, gpt-oss:20b-cloud fue impecable en cada prueba de esta sección. Con dos tareas de dominios distintos en el mismo turno, mostró una falla nueva -- fabricación por no terminar de usar la tool correcta, no por no encontrarla -- que gemma4:cloud no mostró en la misma prueba. Un solo par de corridas por modelo; no alcanza para generalizar a "gemma es más confiable multi-tarea", pero sí para decir que el resultado depende del modelo incluso cuando el wiring y el descubrimiento ya funcionan.

Sobre los cinco clientes probados: un patrón, no cinco casos sueltos

Actualizado después de repetir cada cliente 4 veces más (5-6 corridas totales por cliente) para separar patrón real de variancia de una sola corrida:

Cliente

MCP nativo

Tasa de éxito real (gpt-oss:20b-cloud, N=5-6)

Cuando falla

eve

sí, vía connection_search

✅ 5/5

--

Pi

no (requiere extensión propia, ver arriba)

✅ 5/5, con la extensión propia

--

Hermes

⚠️ 1/6

falla honesta -- confunde su propio sistema de skills, o cava el filesystem

Droid

❌ 0/6

falla honesta 5/6 (cava el repo local); fabricó datos 1/6 -- el hallazgo más grave, pero no representativo

Codex

❌ 0/5

falla honesta, cava el filesystem con PowerShell

Mismo modelo (gpt-oss:20b-cloud vía Ollama local) en los cinco. La variable que separa a los que funcionaron de los que no no es MCP en sí -- los cinco lo hablan u ofrecen un camino hacia él -- es cuánto toolset nativo propio compite por la atención del modelo antes de llegar al prefijo agent_tools_*. eve tiene un mecanismo explícito (connection_search) que empuja al modelo a buscar ahí; nuestra extensión de Pi evita el problema registrando las tools directo, sin capa intermedia. Hermes, Droid y Codex exponen las tools MCP mezcladas con un toolset nativo grande (filesystem, terminal, skills propias), y con este modelo/prompt el descubrimiento ahí es la excepción (Hermes, 1/6), no la regla -- Droid y Codex no lo lograron ninguna vez en 5-6 intentos cada uno.

La misma batería con gemma4:cloud (una corrida por cliente)

Mismo prompt, mismos cinco clientes, mismo runtime -- cambiando solo el modelo a gemma4:cloud. Una sola corrida por cliente (no 5-6 como arriba), verificada igual vía traza cruda -- no alcanza para recalcular tasas, pero sí para ver si el patrón de arriba es del modelo o del cliente:

Cliente

Resultado con gemma4:cloud (N=1)

Verificación

eve

✅ real -- 4 tool calls (n8n_run_skill), datos coherentes

evento message.completed del stream

Pi

✅ real -- 12 tool calls (agent_tools_n8n_run_skill)

NDJSON de la extensión

Droid

✅ real -- 2 agent_tools_n8n_run_skill, números iguales a eve/Pi

.jsonl de sesión leído directo -- droid search no los mostró, hubo que leer el archivo crudo

Hermes

⚠️ real pero parcial -- 1 sola tool call (find-workflows, página 1 de 21), y lo dice explícito en la respuesta ("basado en el primer lote")

log verbose (Tool call: + Tool result:)

Codex

❌ no llamó ninguna tool MCP -- buscó .env/Docker en el filesystem y terminó pidiéndole al usuario la URL y la API key de n8n a mano

JSONL de codex exec, sin command_execution hacia el MCP ni agent_tools_*

Lectura: eve y Pi (los dos casos donde el descubrimiento no depende de que el modelo compita con un toolset nativo grande) siguen en 100% con este modelo también -- ahí el cliente, no el modelo, es la variable que importa. Droid y Hermes mejoraron respecto a gpt-oss:20b-cloud: Droid llamó la tool real (nada de fabricación esta vez) y Hermes, aunque se quedó corto (una sola página de 21), fue honesto sobre el corte en vez de inventar el resto. Codex repite el mismo patrón que con gpt-oss -- 0 tool calls, cava el filesystem en su lugar. Con una sola corrida no se puede afirmar "gemma es más confiable en estos clientes" en general -- alcanza para decir que, al menos esta vez, no repitió el peor hallazgo de la tabla de arriba (la fabricación de Droid) y sí repitió el mejor y el peor caso sin cambios (eve/Pi sólidos, Codex sin descubrir nada).

Droid + gemma4:cloud, 5 corridas: no fabrica, pero aparece un fallo nuevo

El hallazgo más grave de la tabla de gpt-oss:20b-cloud fue que Droid fabricó datos 1/6 veces. Para confirmar si gemma4:cloud lo evita de verdad (no solo en la corrida N=1 de arriba), se repitió el mismo prompt de n8n 4 veces más (total N=5), verificando cada una leyendo el .jsonl de sesión crudo directo -- no droid search, que en la corrida N=1 mostró solo 1 de 3 tool calls reales y habría subestimado el uso real en varias de estas corridas también:

Corrida

Resultado

Tool calls reales (n8n_discover/_call/_run_skill)

1

✅ real, grounded

2

2

✅ real, grounded (cavó el filesystem primero, encontró el MCP después)

3

3

✅ real, grounded, la más exhaustiva

8

4

🔴 nunca terminó -- loop

1022 (todas fallidas)

5

✅ real, grounded

4

Corrida 4 -- el hallazgo nuevo: no fabricó nada, pero quedó atascada más de una hora llamando agent_tools_n8n_call con el mismo shape de argumentos mal anidado (toolName dentro de arguments.arguments en vez de al mismo nivel que arguments), reintentando el mismo error MISSING_TOOL_NAME 1022 veces seguidas sin corregirlo ni abandonar. Tuvo que cortarse manualmente. No es fabricación (el runtime rechazó cada llamada, el modelo nunca inventó una respuesta con esos datos) pero tampoco es un fallo honesto al estilo "no encontré la tool" -- es un tercer modo de falla: encontró la tool correcta, pero no logró corregir el shape del argumento y no tiene mecanismo para cortar el loop.

Lectura con N=5: la fabricación de la corrida gpt-oss no se repitió ninguna vez (0/5) -- el resultado de la corrida N=1 no fue casualidad. Pero tampoco desapareció el riesgo de "corrida que no converge": con gpt-oss era fabricación silenciosa (peor, porque parece una respuesta válida); con gemma4:cloud fue un loop visible y ruidoso (mejor para detectar, pero igual de inútil en la práctica si nadie está mirando). 4/5 real y grounded, 1/5 atascada -- ninguna fabricó.

Fachada tipada y sistema de plugins

Además de la capa de texto (agent_tools_exec + commands/), runtime/mcp-server.mjs expone una fachada tipada por plugin: argumentos JSON nativos (objeto real vía tool-calling, sin comillas de shell) en vez de comandos de texto parseados a mano. Cada plugin instalado agrega 2-3 tools a la sesión MCP, generadas automáticamente a partir de su manifest:

  • agent_tools_<prefix>_discover({ query? }) — busca tools del servicio por texto libre.

  • agent_tools_<prefix>_call({ toolName, arguments, confirm? }) — llama una tool individual del servicio, con el arguments validado contra su schema antes de reenviar. Por defecto, las tools que mutan estado requieren confirm: true — un plugin puede optar por lo contrario con requireConfirm: false en su plugin.json (ver "Qué es un plugin"); hoy solo lo hace agent-tools-plugin-n8n, por decisión propia de ese plugin, no default del runtime.

  • agent_tools_<prefix>_run_skill({ skill, arguments }) — si el plugin trae skills, ejecuta una receta del lado del server para una tarea completa en una sola llamada, en vez de que el agente tenga que orquestar varias tool-calls.

Qué es un plugin

Un plugin es un directorio cuyo nombre empieza con agent-tools-plugin- y contiene un plugin.json:

{
  "name": "n8n",
  "prefix": "n8n",
  "adapter": "./adapter.mjs",
  "adapterExport": "N8nMcpAdapter",
  "readonlyTools": ["search_workflows", "get_execution", "..."],
  "skills": ["./skills/insert-and-verify-datatable-row.mjs", "..."],
  "discoverHint": "texto opcional que se agrega a la descripción de discover"
}
  • adapter apunta a un módulo que exporta una clase con el contrato:

    class Adapter {
      async listTools()            // -> { tools: [{name, description, inputSchema}] }
      async search(query, limit)   // -> { query, matches: [{name, description, score}] }
      async describe(name)         // -> tool completo, o throw si no existe
      async call(name, args)       // -> resultado crudo del MCP/API subyacente
      async discoverContext()      // opcional: contexto extra para adjuntar a la respuesta de discover
                                    // (ver agent-tools-plugin-n8n/adapter.mjs: adjunta el proyecto personal)
    }
  • skills son módulos que exportan async function run(adapter, args), y usan el adapter del propio plugin para orquestar una secuencia de llamadas. Ver agent-tools-plugin-n8n/skills/ para tres ejemplos reales, incluyendo el patrón recomendado: si algo puede quedar 100% determinista (sin que un LLM tenga que generar código en el momento), hacerlo así — es la diferencia entre una skill que falla ~1 de cada 10 veces y una que no falla nunca (medido en el benchmark del repo hermano, ver abajo).

  • readonlyTools son las tools del servicio que no requieren confirm: true en _call.

  • requireConfirm (opcional, default true): en false, ninguna tool del plugin exige confirm: true, ni siquiera las que mutan estado — el campo confirm sigue en el schema de _call por compatibilidad pero no tiene efecto. Es una decisión explícita del autor del plugin, no algo que el runtime active por su cuenta.

Cómo se descubren los plugins

discoverPlugins() en runtime/mcp-server.mjs escanea, sin configuración adicional:

  1. Directorios agent-tools-plugin-* al lado de runtime/ (el caso de este repo — agent-tools-plugin-n8n/).

  2. node_modules/agent-tools-plugin-* (si un plugin se instala como dependencia npm).

  3. $AGENT_TOOLS_PLUGINS_DIR/agent-tools-plugin-* (una carpeta externa cualquiera, para sumar un plugin sin que viva ni en el repo ni en node_modules).

Agregar un plugin nuevo no requiere tocar mcp-server.mjs: alcanza con que el directorio exista en alguna de esas tres ubicaciones con el plugin.json correcto. Un plugin que falla al cargar se loguea a stderr y se saltea — no tumba a los demás.

Skills descubribles: meta y agent_tools_<prefix>_discover

agent_tools_<prefix>_run_skill's description ya lista los nombres de las skills de un plugin (barato, siempre presente), pero eso no alcanza para que un agente sepa qué argumentos/modos acepta cada una sin tener que fallar una llamada primero para leer el error. Una skill puede exportar, además de run:

export const meta = {
  description: 'Una línea de qué hace, sin jerga interna.',
  args: 'mode?: "a"|"b"|"c" (default "a"). otroArg (requerido).',
};

Cuando agent_tools_<prefix>_discover({ query }) se llama con query, busca en esos meta con el mismo scoring por texto que ya usa para las tools crudas, y los devuelve mezclados ({ kind: "skill", name, description, args }) — así un agente que pregunta "auditoría nativa" o "crear workflow sin publicar" encuentra el modo/flag exacto que necesita en vez de reconstruirlo a mano con tools sueltas. Sin query (modo "listar"), el comportamiento no cambia — sigue devolviendo solo tools crudas, para no encarecer ese caso. Una skill sin meta sigue funcionando igual, solo que discover no la va a encontrar por texto libre (su nombre sigue apareciendo en la descripción de run_skill).

agent_tools_help() también lista todos los plugins cargados (prefix + descripción de una línea) — útil cuando hay más de un plugin para el mismo dominio (ver tabla de abajo, github vs gh-cli) y un agente ya comprometido con uno no tendría forma de enterarse de que el otro existe. La descripción de cada discover también menciona esto explícitamente, como recordatorio en el punto donde el agente ya está parado.

Estado real de esto hoy

Siete plugins reales, elegidos para cubrir formas de transporte distintas (no todos el mismo tipo de integración) y medir si el contrato de adapter (listTools/search/describe/call) generaliza:

Plugin

Prefix

Transporte

Qué valida

agent-tools-plugin-n8n

n8n

MCP sobre HTTP (proxy a un server MCP real de terceros)

El caso original — medido extensamente contra gpt-oss:20b-cloud/120b-cloud en un benchmark propio (no publicado)

agent-tools-plugin-kite-lite

kite

MCP sobre stdio (spawnea un proceso hijo que habla MCP)

Adapter como cliente MCP por stdio, no HTTP

agent-tools-plugin-github

github

REST (SaaS, token ya emitido)

Catálogo de tools inventado por el plugin sobre una REST API real

agent-tools-plugin-tasks

tasks

REST (self-hosted, API key)

Mismo caso que github pero sin OAuth ni proveedor externo

agent-tools-plugin-gh-cli

ghcli

CLI (execFile sobre un binario ya instalado)

Ni HTTP ni MCP — exit code + stdout/stderr como superficie de error. Mismo dominio que github a propósito, para aislar la variable de transporte

agent-tools-plugin-pocketbase

pocketbase

REST (self-hosted, auth dinámica)

Sin API key estática — el adapter hace login (auth-with-password) y cachea el token, primer caso de autenticación que el propio adapter tiene que gestionar en vez de solo adjuntar

agent-tools-plugin-ccdd-gate

ccdd

MCP sobre stdio (backend en dos partes: python <script>, no un binario único como kite-lite)

Mismo caso que kite-lite pero contra un backend real de terceros (ccdd-complexity, github.com/MauricioPerera/KDD) — 23 tools reales, sin catálogo inventado. Su skill quality-gate-check compone 5 llamadas AST inline en vez de run_rules_gate: ese tool resultó leer su rules.yaml relativo al cwd del proceso Python long-lived, no al project_root que recibe como argumento — no hay forma de satisfacerlo desde un tempdir por-llamada, encontrado probando el plugin en vivo

El formato del manifest y el loader dinámico ya están probados con varios plugins reales cargando a la vez sin tocar mcp-server.mjs — agregar uno nuevo es crear el directorio, no editar el runtime.

Por qué construir un plugin (para quien expone la API/servicio)

Punto a aclarar primero porque es fácil malinterpretarlo: un plugin no evita MCP. Hacia el agente, agent-tools-runtime sigue hablando MCP tal cual (stdio, JSON-RPC) — no hay protocolo alternativo ahí, y los clientes que importan (Claude Desktop, Claude Code, cualquier agente) existen porque hablan MCP. Lo que un plugin evita es alojar y mantener vos ese proceso MCP-facing: en vez de desplegar tu propio server que hable MCP con el agente, tu plugin se monta sobre un runtime que el host ya tiene corriendo — el proceso MCP lo aloja el host, no vos.

Con eso claro, las ventajas concretas de empaquetar como plugin en vez de (o adicionalmente a) desplegar tu propio server MCP:

  • Cero infra propia: el plugin es un paquete npm que envuelve la API que ya tenés (REST, CLI, lo que sea) — no hay proceso nuevo que alojar, escalar ni mantener en pie. Ver github/tasks/pocketbase en la tabla de arriba: ninguno de los tres tiene un MCP propio, y aun así son alcanzables por un agente sin que su proveedor haya construido un server MCP desde cero.

  • Heredás gratis lo que ya construyó el runtime: confirm-gating en mutaciones, error-hints, discoverabilidad (discover/meta/related, ver sección siguiente) — construir eso dentro de un server MCP propio es trabajo tuyo; acá viene incluido.

  • Skills = tu receta determinista para tu dominio, no que cada agente/modelo reconstruya la orquestación a mano cada vez — reduce la tasa de error específicamente para tus casos de uso (ver benchmark de skills vs. tools sueltas más abajo).

  • Credenciales quedan del lado del host vía env vars (o auth dinámica, ver pocketbase) — no hace falta correr un broker de auth expuesto a internet.

  • Distribución por npm, versionado semver estándar, sin story de deployment propio.

La contra honesta: un plugin solo sirve dentro de un host que tenga agent-tools-runtime cargado — no es un MCP genérico que hable con cualquier cliente. Un server MCP propio tiene alcance universal, pero el costo entero de infra y discoverabilidad es tuyo.

Y no es una decisión excluyente. agent-tools-plugin-n8n es el caso probado de combinar ambos: n8n ya tiene su propio server MCP real, y el adapter de este plugin lo proxea tal cual para la mayoría de las tools — pero cuando ese MCP no cubre algo bien (operaciones de Data Table, ciertos flujos de auditoría), las skills del plugin lo tapan con llamadas deterministas propias, sin que el agente vea la costura. Si ya tenés un MCP que no cubre el 100% de lo que tu API puede hacer, un plugin te deja combinar "lo que el MCP ya expone" con "lo que le falta" detrás de una sola fachada, en vez de elegir entre uno de los dos.

Arquitectura de discoverabilidad

Esto no es un sustituto de MCP — hacia el agente sigue hablando el protocolo tal cual, y varios adapters lo hablan hacia adentro también (n8n, kite-lite). Lo que cambia es el supuesto que trae implícito el uso típico de MCP: que exponer capacidad es declarar una lista plana y completa de tools, cargada entera en cada turno. Acá la capacidad real vive detrás de un índice barato (discover/call/run_skill, 3 tools fijas por plugin sin importar si el backend tiene 4 endpoints o 30), y se resuelve bajo demanda — mismo principio que ToolSearch sobre esta propia sesión. Eso separa "cuánto cuesta tener la capacidad conectada" de "cuánto cuesta que el agente la vea", pero ese ahorro tiene un precio: una capacidad que no se declara de antemano solo se usa si alguien la busca. Lo de abajo es el mapa de esa contrapartida, capa por capa, con qué tan probado está cada mecanismo.

Capa

Qué resuelve

Mecanismo

Cuándo se paga el costo

Nombre de skill

Que existe una receta para la tarea

Listado estático en la descripción de run_skill

Siempre — barato, fijo, no depende de que el agente pregunte nada

Forma de una skill

Argumentos/modos que acepta (ej. mode:"nativeAudit")

meta por skill, indexado por discover({ query })

Solo si hay query — el modo "listar sin filtro" queda igual de barato que antes

Otros plugins del mismo dominio (a ciegas)

Que existe una alternativa con capacidades distintas (ej. github vs gh-cli)

agent_tools_help() lista todos los plugins cargados; cada discover lo menciona en su propia descripción

Solo si el agente llama help() explícitamente

Otros plugins, dirigido (un salto)

Ídem, pero apuntando al vecino exacto en vez de a los 6 plugins mezclados

meta.related de una skill ([{ target: "prefix:skill-name", why }]), sumado al resultado de discover({ query }) cuando esa skill matchea

Mismo costo que la fila 2 — solo con query, ya viene incluido en esa búsqueda

— (no es una capa, es el techo)

Acceso a shell directo al mismo backend que un plugin envuelve

Gratis para el agente, y le gana a todas las de arriba cuando existe

Nivel de confianza real en cada fila, no solo la intención de diseño:

  • Nombre de skill (fila 1): confirmado con A/B en vivo, y el resultado depende del tamaño del modelo. Mismo prompt, mismo código (una función Python con anidamiento excesivo, default mutable, except: desnudo y == None), contra la skill quality-gate-check de agent-tools-plugin-ccdd-gate, probado con cuatro modelos "grandes": glm-5.2:cloud, kimi-k2.6:cloud, deepseek-v4-pro:cloud, nemotron-3-ultra:cloud. Los cuatro encontraron y llamaron la skill correcta con los argumentos correctos al primer intento (verificado en el trace crudo del tool call, no solo leyendo la respuesta final). Dos de los cuatro (deepseek-v4-pro, nemotron-3-ultra) ni llegaron a llamar discover: fueron directo a run_skill con el nombre exacto, resuelto solo con el listado estático de la fila 1 — la capa más barata que existe. Los otros dos sí pasaron por discover primero (fila 2), un paso extra pero sin ningún error ni reintento. Contraste con los modelos chicos usados en pruebas anteriores de este mismo repo (gpt-oss:20b-cloud, gemma4:cloud): esos sí necesitaron el mecanismo de auto-corrección de error-hints para resolver la forma de una llamada mal anidada. Lectura: la fila 1 sola ya alcanza para que un modelo grande use la skill correcta sin fricción; las filas de abajo (meta/discover, error-hints) importan más cuanto más chico es el modelo, no menos.

  • Forma de skill (fila 2): confirmado con A/B en vivo. Mismo prompt, mismo modelo, antes/después del fix — sin él, tres llamadas seguidas a mode:"nativeAudit"/"executions"/"credentials" devolvían en silencio el mismo reporte genérico (el argumento quedaba mal anidado y el default absorbía el error); con él, un error explícito señala la forma correcta y el modelo se corrige en el primer reintento en vez de reconstruir todo a mano con tools sueltas.

  • Otros plugins, a ciegas (fila 3): probado, pero más débil de lo que parece. Funciona si el agente llega a llamar discover con query o help() — nunca se aisló si el aviso de texto es la causa real de que un agente encuentre el plugin correcto, o si el modelo simplemente asocia el nombre por su cuenta. Y en una corrida real, el agente jamás llamó ni discover ni help(): fue directo a resolver la tarea por otro camino, así que el aviso nunca tuvo la oportunidad de leerse.

  • Otros plugins, dirigido (fila 4): un salto de grafo, no un motor de traversal. related no reemplaza la búsqueda inicial (sigue haciendo falta encontrar el primer nodo con discover) — apunta al vecino exacto una vez que ya encontraste algo, en vez de forzar al agente a elegir entre los 6 plugins de help(). Se valida al arrancar (validateRelatedLinks en mcp-server.mjs): un target que no resuelve a una skill real de un plugin realmente cargado se loguea a stderr, no tumba nada — es metadata de discoverabilidad, no una dependencia funcional. Alcance a propósito: solo conecta skills entre sí, no tools crudas (el catálogo de una tool cruda a veces solo se conoce tras conectar en vivo, y esto corre antes de que nada se conecte). Mismo riesgo que discoverHint desde el día uno: un related sin mantener apunta a algo que ya no existe — la validación al arrancar avisa, pero no impide que quede desactualizado si nadie lee el log.

  • El techo: confirmado, no es hipotético. Mismo modelo, misma tarea, mismo plugin: con shell disponible, ignoró todo el sistema de plugins y llamó al binario subyacente directo; sin shell disponible, usó el plugin y encontró la skill correcta sin que nadie la nombrara. Ninguna de las capas de arriba funciona como discoverabilidad real fuera de un entorno donde el agente no tiene una ruta más corta — no son una frontera de seguridad ni de control, son la ruta ganadora únicamente cuando es la única.

Desarrollo

Requisitos: Node.js >=20.18.1.

npm install
npm test
npm run probe
npm run serve

La fachada MCP se inicia con:

npm run mcp

Instalación desde una release

El paquete está publicado en npm como @rckflr/agent-tools-runtime:

npm install @rckflr/agent-tools-runtime

Para iniciar la fachada MCP sin instalarla globalmente:

npx --yes --package=@rckflr/agent-tools-runtime@0.1.3 --call agent-tools-mcp

Si ejecutas el comando desde el propio checkout agent-tools-runtime, usa el prefijo del directorio padre para que npm no confunda el paquete local con el paquete remoto:

npx --prefix .. --yes --package=@rckflr/agent-tools-runtime@0.1.3 --call agent-tools-mcp

La release inicial también incluye un tarball instalable directamente desde GitHub:

npm install https://github.com/MauricioPerera/agent-tools-runtime/releases/download/v0.1.0/rckflr-agent-tools-runtime-0.1.0.tgz

Después de instalarlo, el ejecutable queda disponible como agent-tools. El repositorio incluye un workflow de publicación. Para futuras versiones se debe configurar el secret NPM_TOKEN en GitHub; después puede ejecutarse manualmente o al publicar una release con tag v*.

El preflight puede comprobar un CLI sin ejecutarlo:

$env:AGENT_TOOLS_COMMAND = "gh"
$env:AGENT_CLI_ALLOWLIST = "gh,docker,supabase"
npm run probe

Diseño de seguridad

Las credenciales permanecen en el host. Las skills y los adapters no deben recibir tokens como argumentos ni escribir secretos en archivos. Los CLIs se ejecutan sin shell implícito.

Por defecto, las operaciones mutantes de cualquier plugin requieren confirmación explícita (confirm: true). Un plugin puede desactivar esto para sí mismo con requireConfirm: false en su plugin.json — es opt-out por plugin, no un flag global del runtime. Hoy lo hace agent-tools-plugin-n8n (ver su README): las llamadas a tools de n8n que mutan estado, incluyendo delete_workflow, se ejecutan sin ningún freno del lado del runtime.

Integraciones

Distinto de los plugins (extienden lo que el runtime puede hacer, ver la tabla más arriba): las integraciones de esta sección son conectores del lado del cliente -- código que corre dentro de otro agente para que ese agente pueda llegar a este runtime. Publicadas hasta ahora:

Integración

Cliente

Cómo se instala

Notas

agent-tools-runtime-pi-extension

Pi

pi install npm:agent-tools-runtime-pi-extension

Paquete real de Pi -- no depende de MCP ni de pi-mcp-adapter, ver la sección de Pi arriba

Snippet de defineMcpClientConnection

eve

copiar el bloque a agent/connections/agent-tools-runtime.ts (ver arriba)

No es un paquete publicado -- eve resuelve MCP nativo, no hace falta código extra propio

El plugin para Claude Code y Codex del marketplace (TheHumanInTheLoop Marketplace) sigue siendo una capa de distribución aparte. Este repositorio contiene el runtime canónico y no depende de los manifests específicos de ningún cliente.

Licencia

MIT. Consulta LICENSE.

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
38Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    F
    maintenance
    A secure, declarative MCP runtime that turns YAML configs into MCP servers with trust enforcement, credential brokering, and tamper-evident audit logging.
  • A
    license
    Not graded
    quality
    B
    maintenance
    A batteries-included server runtime for provider-backed agents, offering MCP, CLI, REST, and plugin support.
    1
    AGPL 3.0
  • A
    license
    B
    quality
    B
    maintenance
    A local, evidence-driven MCP runtime and control plane for open-source maintainers that provides workspace-bounded tools including controlled file operations, command execution, validation primitives, durable execution records, and human review workflows via stdio and Streamable HTTP transports.
    33
    MIT

View all related MCP servers

Related MCP Connectors

  • Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.

  • Hosted AgentLux MCP server for marketplace, identity, creator, services, and social flows.

  • An authenticated remote MCP server for user-owned devices and one-shot capability invocation.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MauricioPerera/agent-tools-runtime'

If you have feedback or need assistance with the MCP directory API, please join our Discord server