En octubre de 2025, un único commit en GitHub introdujo 47 "skills" generadas por LLM en un repositorio popular de agentes. Una de ellas enseñaba a Claude a ejecutar npx react-codeshift. El paquete no existía. Nunca existió. Claude lo había alucinado durante el entrenamiento, el autor no lo había comprobado, y en cuestión de semanas el archivo se había propagado a 237 forks. Desarrolladores reales ejecutaron el comando en terminales reales. npm intentó alegremente resolver un paquete fantasma miles de veces.

Esta es la parte de la historia de los agentes de IA que no aparece en el keynote. Los agentes derivan. Dados un shell y una tarea vaga, alcanzarán el comando que suene más plausible, incluso cuando no exista — y cuanto más general sea la herramienta que les das, más derivan.

La respuesta estándar en 2026 es: escribe un servidor MCP personalizado. Define las acciones que quieres que el agente realice, dales esquemas estrictos, distribúyelos como un binario separado, regístralos en la config del agente. Restringe al modelo dándole herramientas específicas en vez de bash.

La respuesta estándar es correcta en principio y equivocada en la práctica. Escribir un servidor MCP real — framing JSON-RPC, handshake stdio, definiciones de esquema, árbol de dependencias separado — para un solo comando de shell es teatro de ingeniería. La mayoría de equipos se rinden y conviven con el drift.

Octomind 0.29.0 cierra esa brecha. Un MCP personalizado no debería ser un proyecto. Debería ser un archivo en tu repo.


El problema del drift, respaldado por números

Si has visto correr a un agente durante un rato, has visto drift:

  • Ejecuta npm test en un monorepo de pnpm.
  • Hace curl a https://staging.example.com/api en vez de tu URL real con auth https://api-staging.acme.internal/v3.
  • Intenta instalar un paquete con un nombre casi pero no exactamente correcto.
  • Usa git tag crudo en vez del script de release de tu equipo.

El paper de mayo de 2025 RAG-MCP: Mitigating Prompt Bloat in LLM Tool Selection (Gan & Sun) corrió un stress test de MCP y encontró que la precisión base de selección de herramientas colapsó a 13.62% a escala — el modelo ya no podía elegir confiablemente la herramienta correcta porque cada descripción de herramienta que tenía que considerar saturaba el prompt y diluía la atención. Su solución basada en retrieval triplica eso a 43.13% mientras corta los tokens del prompt en más del 50%. Aún así una precisión terrible, pero la dirección de la curva es el punto: cuantas más herramientas metes, peor se vuelve el modelo para elegir entre ellas.

Anthropic, quien creó el protocolo MCP, admitió el mismo punto el 4 de noviembre de 2025:

"In cases where agents are connected to thousands of tools, they'll need to process hundreds of thousands of tokens before reading a request." (Cuando los agentes están conectados a miles de herramientas, necesitarán procesar cientos de miles de tokens antes de leer una petición.)

Sus propios benchmarks mostraron un flujo Drive → Salesforce costando 150,000 tokens bajo carga MCP ingenua. Con ejecución de código y descubrimiento de herramientas bajo demanda, el mismo flujo corrió en 2,000 tokens — una reducción del 98.7%. Su seguimiento Tool Search Tool (versión API 20251119) difiere las definiciones de herramientas hasta que se necesitan; la documentación afirma que esto típicamente corta la sobrecarga de contexto en más del 85% y señala que "Claude's ability to correctly pick the right tool degrades significantly once you exceed 30–50 available tools" (la capacidad de Claude para elegir correctamente la herramienta adecuada se degrada significativamente una vez que superas las 30–50 herramientas disponibles).

La lección a la que todos están convergiendo: menos herramientas, más estrechas y relevantes al proyecto le ganan siempre a un catálogo gigante. El modelo rinde mejor. La factura de tokens cae. El drift cae con ella.

Así que la pregunta correcta no es "¿cómo escribo más MCPs?". Es "¿cómo hago que los que realmente necesito no cuesten nada escribir y distribuir?".


Por qué "simplemente escribe un servidor MCP" no escala

Un análisis de febrero de 2026 de Bloomberry sobre 1,412 servidores MCP encontró que el 38.7% de ellos se distribuyen sin autenticación alguna, el servidor mediano expone solo cinco herramientas, y aproximadamente la mitad de las compañías que los publican ni siquiera tienen una API pública. El framing de Bloomberry: "MCP might not be a wrapper on an existing API. It might be the first machine-readable interface they've ever shipped." (Puede que MCP no sea un wrapper sobre una API existente. Puede que sea la primera interfaz legible por máquina que jamás hayan distribuido.) Gran parte de la explosión es distribución por gente averiguándolo en producción.

El problema no es que el protocolo esté roto. Es que la unidad de trabajo es la equivocada para acciones específicas del proyecto. Escribir un servidor MCP en Python o Node con framing stdio, registro de esquema, y un pipeline de deployment separado es la forma correcta para "GitHub API" o "base de datos Postgres". Es la forma equivocada para "ejecutar nuestro script de deploy a staging" — una pieza de código que son cuarenta líneas de bash, ya en bin/deploy, que te gustaría que el agente llamara por nombre en vez de adivinar.

Los profesionales están llegando a la misma conclusión. Del hilo de Hacker News "MCP explained without hype or fluff" (item 44063141), el comentarista 0x457:

"Many of MCPs shouldn't have existed, IMO. […] I wanted amazon q to interact with github, so I added to its context that it can use gh-cli and it it worked pretty well. […] To make its and mine life easier, common things were saved as scripts in bin/ and Justfile (also generated by it). […] Using github's official MCP bricks chat session every time for me." (Muchos MCPs no deberían haber existido. […] Quería que amazon q interactuara con github, así que añadí a su contexto que puede usar gh-cli y funcionó bastante bien. […] Para facilitarme la vida, las cosas comunes se guardaron como scripts en bin/ y Justfile. […] Usar el MCP oficial de github me bloquea la sesión de chat cada vez.)

Y dvt:

"MCP is bloated AI hype that basically solves nothing […]. It's APIs talking to APIs that talk to other APIs." (MCP es hype de IA inflado que básicamente no resuelve nada. Son APIs hablando con APIs que hablan con otras APIs.)

Ambos comentarios son correctos sobre los casos que describen. Las CLIs funcionan. Los scripts locales en bin/ funcionan. Envolver un comando de shell de una línea en un servidor MCP de 200 líneas porque ese es el único patrón bendecido es lo que está roto.


Qué es realmente una herramienta MCP

Quita el protocolo y una herramienta MCP son tres cosas:

  1. Un nombre y una descripción para que el modelo sepa cuándo recurrir a ella.
  2. Un esquema de parámetros para que el modelo sepa qué pasar.
  3. Un ejecutable que hace el trabajo.

Todo lo demás — framing JSON-RPC, handshake stdio, negociación de capacidades — es preocupación del runtime, no del autor. El paper de 2023 Tool Documentation Enables Zero-Shot Tool-Usage with Large Language Models (Hsieh et al.) mostró empíricamente que para herramientas no familiares, una documentación clara le gana a las demostraciones few-shot — y que "zero-shot prompts with only tool documentation" pueden igualar prompts few-shot en tareas fuera de distribución. La implicación: una descripción corta y precisa en la herramienta es más valiosa que un esquema complejo o una pila de ejemplos.

Si la herramienta es corta y los docs son cortos, toda la interfaz debería ser expresable como unas pocas líneas de comentarios de cabecera en el script que hace el trabajo.

Eso es lo que hace Octomind 0.29.0.


El patrón: .agents/tools/<name>

Aquí está el contrato entero:

mkdir -p .agents/tools
cat > .agents/tools/deploy <<'EOF'
#!/usr/bin/env bash
# @description Deploy the API service to a target environment using our team's pipeline.
# @param *env string Target environment (staging|production)
# @param dry_run boolean If true, validate without applying

set -euo pipefail
cd "$OCTOMIND_WORKDIR"

env="$OCTOMIND_PARAM_ENV"
dry="${OCTOMIND_PARAM_DRY_RUN:-false}"

if [[ "$dry" == "true" ]]; then
  ./bin/deploy --env "$env" --dry-run
else
  ./bin/deploy --env "$env"
fi
EOF
chmod +x .agents/tools/deploy

Acaban de pasar tres cosas:

  1. Octomind descubrió el script la próxima vez que se ejecutó la sesión.
  2. Parseó las líneas @description y @param en una definición de herramienta JSON-Schema.
  3. El modelo ahora ve una herramienta deploy — con alcance a este repo — con env (obligatorio) y dry_run (opcional). Elegirá deploy(env="staging") por encima de bash("./bin/deploy --env staging") porque la herramienta nombrada tiene una relación señal-ruido más clara.

Esa preferencia es exactamente el guardarraíl que quieres. El modelo ya no está asociando libremente desde "deploy" a "kubectl apply" a "helm install". Tiene un único punto de entrada, con un parámetro tipado, que ejecuta el deploy real del equipo.

Para verificar el cableado, ejecuta la one-liner de validación:

echo "/mcp" | octomind run

Verás un servidor MCP local en la salida con deploy listado debajo. Si el script no aparece, el runtime te dice exactamente por qué — permisos incorrectos, @description faltante, punto en el nombre del archivo.


Anatomía de la cabecera

El bloque de comentario líder es la interfaz entera. Octomind lo re-lee en cada turno (barato — un read_dir) y reconstruye el esquema. Edita y guarda, la siguiente llamada a la herramienta usa la nueva versión. Sin daemon que reiniciar.

#!/usr/bin/env bash
# @description Short summary the model sees. Continuation lines without an
# @-tag append to the previous one, so multi-line descriptions Just Work.
# @param *target string Path to operate on (* prefix = required)
# @param force boolean Overwrite if the destination exists
# @param count integer Number of iterations
# @param tags array JSON list, e.g. ["foo","bar"]

El prefijo de comentario puede ser # (bash, python, ruby, lua, perl, awk), // (node, deno), o -- (lua, wrappers SQL). Al runtime no le importa qué lenguaje ejecuta el trabajo.

Cuando el modelo llama a la herramienta, octomind te entrega los parámetros de dos maneras:

Canal Forma
stdin Un objeto JSON y luego EOF: {"target":"src/x","force":true}
env OCTOMIND_PARAM_TARGET=src/x, OCTOMIND_PARAM_FORCE=true, más OCTOMIND_WORKDIR y OCTOMIND_TOOL_NAME

Los scripts de bash normalmente leen variables de entorno. Los scripts de Python normalmente parsean JSON de stdin. Ambos llegan en cada llamada. Stdout se convierte en el resultado que el modelo ve. Stderr se anexa con un marcador [stderr]. Exit distinto de cero → error de herramienta.

Esa es la superficie entera.


Cuatro drifts reales, cuatro herramientas reales

Vimos fallar a un agente en cada uno de estos en nuestros propios repos antes de escribir el wrapper. El wrapper fue lo que detuvo el fallo.

1. El drift: "Ejecuta npm test en un monorepo de pnpm"

El agente ve un package.json en la raíz, escribe npm test, recibe un muro de errores "no script found", y reporta el proyecto como roto.

El wrapper — .agents/tools/test:

#!/usr/bin/env bash
# @description Run the project test suite. Defaults to fast unit tests.
# @param scope string One of: unit, integration, all (default: unit)
# @param package string Optional package filter, e.g. @acme/api

set -euo pipefail
cd "$OCTOMIND_WORKDIR"

scope="${OCTOMIND_PARAM_SCOPE:-unit}"
pkg="${OCTOMIND_PARAM_PACKAGE:-}"

case "$scope" in
  unit)        cmd=("pnpm" "-r" "test:unit") ;;
  integration) cmd=("pnpm" "-r" "test:integration" "--" "--runInBand") ;;
  all)         cmd=("pnpm" "-r" "test") ;;
  *) echo "Unknown scope: $scope" >&2; exit 2 ;;
esac

[[ -n "$pkg" ]] && cmd+=("--filter" "$pkg")
exec "${cmd[@]}"

Ahora el agente ve test(scope, package) en su lista de herramientas. La descripción le dice los valores por defecto. Llama a test() y obtiene un resultado real al primer intento. Nadie en el equipo vuelve a teclear "usa pnpm -r test:unit" en una ventana de chat.

2. El drift: "Alucinó la URL de staging"

El agente necesita leer un registro de tu API real de staging en https://api-staging.acme.internal/v3/users/42, que requiere un header X-Acme-Token. En su lugar hace curl a https://staging.example.com/api/users/42 — una URL que inventó a partir de datos de entrenamiento — no recibe nada de vuelta, y reporta el servicio como caído.

El wrapper — .agents/tools/api:

#!/usr/bin/env bash
# @description Call our internal staging API. Returns raw JSON.
# @param *path string Path under /v3, e.g. /users/42
# @param method string HTTP method (default: GET)
# @param body string Optional JSON body for POST/PUT/PATCH

set -euo pipefail
: "${ACME_STAGING_TOKEN:?ACME_STAGING_TOKEN not set in environment}"

path="$OCTOMIND_PARAM_PATH"
method="${OCTOMIND_PARAM_METHOD:-GET}"
body="${OCTOMIND_PARAM_BODY:-}"
url="https://api-staging.acme.internal/v3${path}"

args=(-sS -X "$method" -H "X-Acme-Token: $ACME_STAGING_TOKEN")
[[ -n "$body" ]] && args+=(-H "Content-Type: application/json" --data "$body")

curl "${args[@]}" "$url"

La URL ya no deriva porque la URL ya no es trabajo del modelo. El agente llama a api(path="/users/42") y la URL real vive en el script. El token viene del entorno del desarrollador (.envrc, direnv, tu shell rc) — la herramienta está commiteada, el secreto no.

3. El drift: "Re-explicó el servicio de feature flags por cuarta vez"

El agente depura un problema de UI. Le recuerdas (otra vez) que existe el servicio de feature flags, que las flags difieren por entorno, y que staging es el relevante. Asiente, olvida, vuelve a preguntar en la siguiente sesión.

El wrapper — .agents/tools/flags:

#!/usr/bin/env python3
# @description Return the currently enabled feature flags for an environment.
# @param env string Environment to inspect (default: staging)

import json, os, sys, urllib.request

params = json.load(sys.stdin) if not sys.stdin.isatty() else {}
env = params.get("env") or os.environ.get("OCTOMIND_PARAM_ENV") or "staging"

url = f"https://flags.acme.internal/api/snapshot?env={env}"
req = urllib.request.Request(url, headers={"X-Acme-Token": os.environ["ACME_TOKEN"]})

with urllib.request.urlopen(req, timeout=10) as r:
    flags = json.load(r)

enabled = sorted(k for k, v in flags.items() if v.get("enabled"))
print("\n".join(enabled) if enabled else "(no flags enabled)")

El modelo ahora abre una sesión, ve flags en su lista de herramientas, y la llama él solo cuando lo necesita. Dejas de ser la caché humana de "qué está activo en staging".

4. El drift: "Usó git tag en vez de nuestro script de release"

Tu equipo construyó acme-cli para que los bumps de versión, los changelogs, y el tag-luego-push ocurran en el orden correcto. El agente se lo salta, ejecuta git tag v1.2.3 crudo, rompe la convención, y ahora el siguiente run de CI no encuentra el changelog.

El wrapper — .agents/tools/release:

#!/usr/bin/env bash
# @description Cut a release using the team's acme-cli. Bumps version, updates
# CHANGELOG, tags the commit, and pushes. Use bump=patch unless minor or major
# is explicitly requested.
# @param *bump string One of: patch, minor, major
# @param dry_run boolean If true, show what would happen without pushing

set -euo pipefail
cd "$OCTOMIND_WORKDIR"

bump="$OCTOMIND_PARAM_BUMP"
dry="${OCTOMIND_PARAM_DRY_RUN:-false}"

args=(release --bump "$bump")
[[ "$dry" == "true" ]] && args+=(--dry-run)

acme-cli "${args[@]}"

Esta es la que conquista a los compañeros de equipo. El agente ahora sigue la convención de release de tu equipo por su cuenta — versión, changelog, tag, push — porque la herramienta existe en el repo y el modelo la elige por encima de git crudo.


El efecto acumulativo: vive en el repo

El post de Anthropic sobre ejecución de código con MCP argumenta que el futuro es el descubrimiento dinámico: cargar herramientas cuando se necesiten, descartarlas en caso contrario. El patrón shebang es la versión local-first de esa idea. Se apilan tres propiedades:

1. Superficie estrecha, drift bajo. El modelo ve una acción nombrada y parametrizada con una descripción de una línea en vez de un shell genérico. Hsieh et al. encontraron que la descripción misma hace la mayor parte del trabajo para la selección de herramientas — los ejemplos son nice-to-have. La cabecera del shebang es exactamente la forma correcta para eso: nombre, descripción, parámetros tipados, todo en un bloque de comentarios que el autor de la herramienta ya mantiene junto al código.

2. Todo el equipo lo obtiene gratis. Un servidor MCP tradicional vive en el portátil de un solo desarrollador. Cuando un compañero clona el repo, el servidor no está ahí, el agente no ve la herramienta, y el drift vuelve. Un script en .agents/tools/ está commiteado en git. Clona, octomind run, las mismas herramientas que todos los demás.

3. Las herramientas evolucionan con el código. ¿Renombraste un servicio? Actualiza el wrapper en el mismo PR. ¿Añadiste una feature flag? Actualiza el wrapper en el mismo PR. Tu tooling de IA está sujeto a code review como cualquier otra cosa. El drift entre lo que el agente piensa que parece el repo y lo que el repo realmente parece — el asesino silencioso de la fiabilidad del agente — se cierra automáticamente.

Después de una semana de esto, el agente en la terminal de cada desarrollador converge al mismo comportamiento porque todos ejecutan las mismas herramientas del mismo repo. El conocimiento institucional deja de ser "lo que le dije al agente en el chat" y empieza a ser un artefacto versionado y revisable.


Cuándo recurrir a un servidor MCP en su lugar

Las herramientas locales cubren el 90% específico del proyecto. No lo cubren todo. Usa esta matriz:

Quieres… Usa
Acción específica del proyecto (deploy, test, consultar flags, llegar a un endpoint interno) shebang en .agents/tools/
Inyectar instrucciones de dominio en el contexto Skills
Herramienta reutilizable entre proyectos con esquema rico y lógica multi-paso Escribir un tap con un servidor MCP
Proceso de vida larga (pool de conexiones, sesión de navegador, websocket) Servidor MCP externo stdio/http

El modelo mental: si el cuerpo de tu servidor MCP es "parsear params, lanzar este binario, devolver stdout", no necesitas un servidor — necesitas un script de shell de 15 líneas.


El bucle de validación

Ejecuta esto después de escribir o cambiar cualquier herramienta:

echo "/mcp" | octomind run

Arranca una sesión en el directorio actual, lista cada servidor MCP en alcance, y sale. Verás:

local: ✅ Running
  Type: LocalTools
  Configured tools: deploy, test, api, flags, release

Si una herramienta falta:

Síntoma Causa Solución
No aparece en la salida de /mcp No es ejecutable chmod +x .agents/tools/<name>
No aparece en la salida de /mcp @description faltante Añade la línea
No aparece en la salida de /mcp El nombre del archivo tiene . Quita la extensión (deploy, no deploy.sh)
La herramienta da error al llamarla Desajuste en la lectura de params Confirma que lees OCTOMIND_PARAM_* o stdin, no $1

Para depuración más profunda: OCTOMIND_LOG=debug echo "/mcp" | octomind run muestra por qué cada archivo candidato fue aceptado o saltado.

El runtime trata .agents/tools/ como hot-reloaded — edita, guarda, la siguiente llamada a la herramienta usa la nueva versión. Sin octomind restart, sin daemon. Escribe, guarda, llama.


Empezar

Si no tienes octomind:

# macOS
brew install muvon/tap/octomind

# Any platform
cargo install octomind

Luego en cualquier repo:

mkdir -p .agents/tools
cat > .agents/tools/branch <<'EOF'
#!/usr/bin/env bash
# @description Print the current branch name.
git -C "$OCTOMIND_WORKDIR" rev-parse --abbrev-ref HEAD
EOF
chmod +x .agents/tools/branch

echo "/mcp" | octomind run

Deberías ver local listado con branch. Hazle commit:

git add .agents/tools/branch
git commit -m "agent: add branch-name tool for AI sessions"

Ese es el patrón completo. Cada dev que clona el repo tiene la herramienta. Cada run de agente en el directorio la ve. Sin más configuración en ningún sitio en la máquina de nadie.


FAQ

¿Por qué una herramienta envuelta reduce el drift si el comando subyacente es el mismo?

Porque el modelo ya no está eligiendo entre miles de invocaciones de shell igualmente plausibles. Está eligiendo entre un puñado de herramientas nombradas y tipadas con descripciones escritas para él. RAG-MCP midió el efecto: la precisión de selección de herramientas colapsa a medida que el catálogo crece. Un conjunto pequeño y nombrado con descripciones claras es la forma empíricamente correcta para LLMs que usan herramientas.

¿Es esto específico de Octomind?

El descubrimiento de .agents/tools/ es una feature del runtime de Octomind. Dentro de Octomind, cada modelo — Claude, Kimi, GLM, GPT, modelos locales — las ve como herramientas MCP estándar. Si también usas Claude Desktop o Cursor, puedes envolver .agents/tools/ con un shim MCP fino, pero el mayor aprovechamiento está dentro de Octomind donde el descubrimiento es automático y con alcance al proyecto.

¿Y los secretos?

La herramienta está en el repo. El secreto no. Los scripts leen ACME_TOKEN (o similar) del entorno del desarrollador — direnv, .envrc, shell rc. Commit la herramienta, no la credencial. Si la variable de entorno falta, sal con exit distinto de cero con un mensaje claro para que el modelo aflore el error real en vez de fabricar uno.

¿Puede el agente ejecutar cualquiera de estas herramientas sin que yo apruebe cada llamada?

Sí — mismo flujo de permisos que cualquier herramienta MCP. Auto-aprueba las de solo lectura (branch, flags). Pregunta siempre para las destructivas (deploy, release). Octomind trata las herramientas locales como de primera clase para el sistema de permisos.

¿Y si quiero la misma herramienta en múltiples repos?

Dos opciones. Ligera: enlace simbólico de .agents/tools/foo a una ubicación compartida. Adecuada: escribir un tap — un registro estilo Homebrew. Las herramientas locales son para este repo específicamente. Los taps son para reutilización entre proyectos.

¿Funciona esto en CI / runs no interactivos?

Sí. octomind run --format=plain y --format=jsonl ambos respetan .agents/tools/. Corremos code review dirigido por agente en nuestro propio CI de esta manera — las herramientas lint, test y coverage del agente de review viven todas en .agents/tools/ y envuelven los comandos exactos del equipo.

El nombre de una herramienta colisiona con uno integrado. ¿Qué pasa?

Gana el integrado. No puedes sobrescribir shell o view nombrando un script shell — octomind registra la colisión y mantiene el original. Guardarraíl deliberado.


El cambio

Hay una historia que se está contando ahora mismo de que el futuro de los agentes son más servidores MCP — catálogos más grandes, ecosistemas más ricos, más integraciones. Los datos de RAG-MCP, la auditoría de Bloomberry, el propio post de ejecución de código de Anthropic, y los desarrolladores que silenciosamente vuelven a las CLIs apuntan todos a una dirección distinta: los agentes que rinden mejor en repos reales ven menos herramientas, más estrechas, y herramientas escritas para el proyecto en el que están.

Un script en .agents/tools/ es la unidad más pequeña posible de ese patrón. Sesenta segundos de "sigo viendo al agente derivar en esto" a "el agente tiene una herramienta para ello, el equipo la tiene, está en git, seguimos adelante".

Pruébalo en la próxima cosa que te encuentres explicando dos veces.

— Don


Octomind es open source bajo Apache-2.0. La feature de herramientas locales se distribuyó en 0.29.0. Si algo aquí te desbloquea un flujo, abre un issue — las features pedidas en mayo tienden a distribuirse en junio.