{
  "markdown": "<!-- mcp-name: io.github.ZahiriNatZuke/local-delegate -->\r\n\r\n# local-delegate\r\n\r\n**Delega tareas mecánicas texto→texto a un LLM local para conservar la cuota de tu suscripción de Claude.**\r\nUn servidor MCP (`stdio` o daemon HTTP compartido) que es cliente **genérico** de cualquier\r\nendpoint OpenAI-compatible — llama-swap, Ollama, LM Studio, vLLM.\r\n\r\n[![PyPI](https://img.shields.io/pypi/v/local-delegate-mcp.svg)](https://pypi.org/project/local-delegate-mcp/)\r\n[![CI](https://github.com/ZahiriNatZuke/local-delegate/actions/workflows/ci.yml/badge.svg)](https://github.com/ZahiriNatZuke/local-delegate/actions/workflows/ci.yml)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)\r\n\r\n**[zahirinatzuke.github.io/local-delegate](https://zahirinatzuke.github.io/local-delegate/)** — qué\r\nhace y por qué, en una página (es/en). Su fuente está en [`site/`](./site).\r\n\r\n## Demo\r\n\r\n<!-- URL absoluta a raw.githubusercontent.com para que la imagen también se renderice en\r\n     PyPI (los links relativos solo se resuelven dentro de GitHub). -->\r\n![Dashboard de ahorro de local-delegate](https://raw.githubusercontent.com/ZahiriNatZuke/local-delegate/main/docs/assets/dashboard.png)\r\n\r\n*Dashboard embebido (datos de ejemplo): estado del backend local (modelos montados, delegación en curso con su progreso por trozos, tools MCP), RAM/VRAM del sistema con consumo por proceso, tokens de contexto conservados, ahorro por herramienta y modelo, dónde corrió el cómputo —esta máquina o un backend remoto— y actividad reciente paginada en tu hora local. Se sirve en `http://127.0.0.1:9393`.*\r\n\r\n## ¿Por qué?\r\n\r\nCuando Claude tiene que resumir un log enorme, clasificar, extraer campos o generar boilerplate,\r\ngasta cuota de tu suscripción en trabajo **mecánico**. `local-delegate` expone esas tareas como\r\ntools MCP que corren en un LLM **local**: pasas `path` en vez de `text` y el archivo se lee\r\n**del lado del servidor**, así el contenido grande **nunca entra al contexto de Claude**. Solo\r\nvuelve el resultado corto — cuota que no gastaste.\r\n\r\n## Instalación rápida\r\n\r\nCon [`uv`](https://docs.astral.sh/uv/) no hay nada que instalar: `uvx` baja y ejecuta el paquete aislado.\r\n\r\nAñádelo a tu config de MCP (Claude Desktop / Claude Code) en modo compatible `stdio`:\r\n\r\n```json\r\n{\r\n  \"mcpServers\": {\r\n    \"local-delegate\": {\r\n      \"command\": \"uvx\",\r\n      \"args\": [\"local-delegate-mcp\"]\r\n    }\r\n  }\r\n}\r\n```\r\n\r\nVer plantillas completas en [`examples/`](./examples).\r\n\r\nO deja que el paquete lo configure todo por ti —entrada MCP, hooks, skill y la regla de\r\ndelegación en tu `CLAUDE.md`/`AGENTS.md` global— con un solo comando:\r\n\r\n```bash\r\nuv tool install local-delegate-mcp          # deja `local-delegate` en el PATH\r\nlocal-delegate install --dry-run            # muestra exactamente qué tocaría\r\nlocal-delegate install                      # aplica\r\n```\r\n\r\nTambién sirve `uvx local-delegate-mcp install` para probarlo sin instalar nada, pero ten en cuenta\r\nque **`uvx` no deja el comando disponible**: monta un entorno efímero y lo borra al terminar, así\r\nque después `local-delegate doctor` responderá «command not found». El propio `install` te lo avisa\r\nsi detecta ese caso.\r\n\r\nEs idempotente, deja `.bak` de lo que edita, no toca configuración ajena y se revierte con\r\n`local-delegate uninstall`. Detalle y opciones en [Instalación de la integración](./docs/wiki/Integration-install.md).\r\n\r\nSi usas **varias sesiones o varios clientes** en la misma máquina, se recomienda un solo daemon:\r\n\r\n```powershell\r\nuvx local-delegate-mcp serve\r\n```\r\n\r\nEl daemon sirve MCP en `http://127.0.0.1:9393/mcp` y el dashboard en\r\n`http://127.0.0.1:9393/`. Codex, Claude Code, opencode y cualquier cliente compatible con Streamable HTTP\r\npueden compartir esa URL sin levantar procesos MCP duplicados. Guía completa:\r\n[Daemon compartido](./docs/wiki/Daemon.md).\r\n\r\nPara usar la GPU de otra máquina manteniendo los paths locales del cliente, usa un MCP local que\r\napunte al backend remoto: [guía Mac → PC](./docs/wiki/Remote-backend.md) y\r\n[recipe técnica completa](./docs/recipes/remote-backend.md).\r\n\r\n> **No fijes una versión vieja «por estabilidad».** Un pin (`==X.Y.Z`) congela también los rangos de\r\n> dependencias que declaraba aquel wheel, y eso envejece mal: las versiones anteriores a la 0.12.2\r\n> pedían `mcp` **sin techo**, así que hoy resuelven al SDK 2.x y **mueren en el import**. Si necesitas\r\n> fijar, fija la **actual**, y súbela cuando salga una nueva.\r\n\r\nEn Windows, si lo registras como tarea al iniciar sesión, ejecuta el `pythonw.exe` del entorno\r\ndonde instalaste el paquete con `-m local_delegate serve --log-level warning`. `pythonw` no crea\r\nconsola ni botón en la barra de tareas. La tarea pertenece al usuario de **Windows**, no a Codex\r\nni a Claude: cualquier cliente local comparte el mismo daemon. El dashboard identifica ese único\r\nproceso con la insignia `DAEMON MCP`; las sesiones conectadas son clientes HTTP, no procesos MCP\r\nadicionales.\r\n\r\n## Requisitos\r\n\r\n**Python 3.11+** — con `uvx` no tienes que instalarlo tú, lo resuelve él; solo importa si instalas\r\ncon `pip` en un entorno propio.\r\n\r\nY un **endpoint OpenAI-compatible** ya corriendo, accesible en `LOCAL_DELEGATE_BASE_URL`\r\n(default `http://127.0.0.1:9292/v1`). Cualquiera sirve:\r\n\r\n- **llama-swap** — ver [recipe con GPU Blackwell](./docs/recipes/llama-swap-blackwell.md).\r\n- **Ollama** — `http://127.0.0.1:11434/v1`.\r\n- **LM Studio**, **vLLM**, o cualquier servidor que hable la API de OpenAI.\r\n\r\nEl paquete **no arranca** ningún backend por defecto (`LOCAL_DELEGATE_AUTOSTART=0`). El\r\nauto-arranque de llama-swap es opt-in (ver tabla de configuración).\r\n\r\n¿Qué versiones de `llama-server`/`llama-swap` usar y cómo disponer el workspace? Ver\r\n[Versiones del backend y workspace de referencia](./docs/wiki/Backend-versions.md) (sugerencia\r\nprobada, no requisito). `local-delegate doctor` compara tu instalación contra esas versiones y, de\r\npaso, comprueba el resto del andamiaje —hooks, skill, memoria, entradas MCP y el daemon— sin\r\nescribir nada ([qué mira cada check](./docs/wiki/Integration-install.md#comprobar-la-instalación-local-delegate-doctor)).\r\n\r\n## Tools\r\n\r\nPasar `path` (en vez de `text`) hace que el MCP lea el archivo server-side → ahorro real de cuota.\r\n\r\n| Tool | Qué hace | Rol de modelo (default) |\r\n|---|---|---|\r\n| `local_summarize` | Resume texto o archivo | mecánico / largo (auto) |\r\n| `local_classify` | Devuelve UNA etiqueta de una lista | mecánico |\r\n| `local_extract` | Extrae campos → **objeto validado**, no una cadena que haya que parsear | mecánico / largo (auto) |\r\n| `local_boilerplate` | Genera código desde una spec | código |\r\n| `local_delegate` | Escape genérico texto→texto | mecánico (o el que pases) |\r\n| `local_lint_summary` | Resume logs de lint/tests/CI | mecánico / largo (auto) |\r\n| `local_commit_msg` | Mensaje de commit desde un diff | código |\r\n| `local_translate` | Traduce texto o archivo | mecánico / largo (auto) |\r\n| `local_explain_code` | Explica código en prosa | código |\r\n| `local_describe_image` | Describe una imagen o responde una pregunta sobre ella (imagen→texto) | visión |\r\n| `local_status` | Diagnóstico de solo lectura: backend, catálogo, log, VRAM, RAM de sistema | — (no llama al backend de chat) |\r\n\r\nLos modelos locales **no** usan tool-calling: el server arma el prompt + guardrails, hace POST al\r\nendpoint y devuelve **solo texto**.\r\n\r\n**Documentos largos.** `local_translate` (y `local_delegate` con entradas largas) parten el texto\r\npor límites naturales —headers Markdown, párrafos, líneas— y procesan **un trozo por llamada**\r\nrespetando el techo de `max_tokens`, concatenando las salidas en orden y conservando el formato en\r\nlas costuras. Un documento de 20 000+ caracteres vuelve completo en vez de cortado a mitad. El log\r\nregistra `chunks: N` y el dashboard muestra el progreso (`trozo 3/7`) mientras corre.\r\n\r\n**Resúmenes de documentos enormes.** `local_summarize` y `local_lint_summary` hacen **map-reduce**\r\ncuando la entrada no cabe en el modelo: resumen cada parte y luego resumen los resúmenes, por\r\nniveles si hace falta. Antes truncaban —de un log de CI enorme se resumía el principio y el resto\r\nse descartaba en silencio, que es justo donde suelen estar los errores— y ahora se lee entero.\r\n`local_extract` sigue truncando a propósito: fusionar el JSON de varios trozos no tiene una\r\nrespuesta única y adivinarla sería peor que avisar.\r\n\r\n## Configuración\r\n\r\nTodo por variables de entorno; nada hardcodeado. Los ids de modelo default son solo eso —\r\ncámbialos por los de tu backend.\r\n\r\n| Variable | Default | Descripción |\r\n|---|---|---|\r\n| `LOCAL_DELEGATE_BASE_URL` | `http://127.0.0.1:9292/v1` | Endpoint OpenAI-compatible |\r\n| `LOCAL_DELEGATE_API_KEY` | *(vacío)* | Bearer token, si tu endpoint lo exige |\r\n| `LOCAL_DELEGATE_BACKEND_ORIGIN` | `auto` | `local`/`remote` fuerzan el origen del cómputo; `auto` lo deduce del host. Ponlo si llegas al backend por un **túnel** (`ssh -L`, port-forward): en loopback se vería como local |\r\n| `LOCAL_DELEGATE_TIMEOUT` | `180` | Timeout HTTP (segundos) |\r\n| `LOCAL_DELEGATE_MAX_CONCURRENT_REQUESTS` | `2` | Backpressure máximo por proceso; compartido por todos los clientes del daemon |\r\n| `LOCAL_DELEGATE_ASK` | `1` | Preguntar al usuario (vía `elicitation`) en vez de fallar seco: backend caído, modelo fuera del catálogo, `output_format` vacío. `0` lo desactiva |\r\n| `LOCAL_DELEGATE_ASK_TIMEOUT` | `30` | Segundos de espera por una respuesta; agotados, la tool sigue como si no hubiera preguntado |\r\n| `LOCAL_DELEGATE_LOG_DIR` | *(dir de datos de usuario)* | Directorio de los `usage-YYYYMM.jsonl` rotados por mes y del `clients.jsonl` |\r\n| `LOCAL_DELEGATE_LOG` | *(vacío = rotación activa)* | Si se fija, ruta de un `usage.jsonl` explícito sin rotar (compatibilidad) |\r\n| `LOCAL_DELEGATE_MODEL_MECHANICAL` | `gemma3-4b` | Modelo para clasificar/extraer/resumen corto |\r\n| `LOCAL_DELEGATE_MODEL_LONG` | `llama31-8b` | Modelo para documentos largos |\r\n| `LOCAL_DELEGATE_MODEL_CODE` | `qwen25-coder-14b` | Modelo para código |\r\n| `LOCAL_DELEGATE_MODEL_FAST` | `qwen35-2b` | Modelo ultrarrápido / trivial |\r\n| `LOCAL_DELEGATE_MODEL_VISION` | `qwen3-vl-8b` | Modelo de visión para `local_describe_image` |\r\n| `LOCAL_DELEGATE_MAX_IMAGE_MB` | `8` | Tope de tamaño de imagen para `local_describe_image` |\r\n| `LOCAL_DELEGATE_LONG_INPUT_CHARS` | `6000` | Umbral mecánico↔largo |\r\n| `LOCAL_DELEGATE_CHUNK_CHARS` | `3500` | Tamaño de trozo al partir documentos largos (`local_translate`, `local_delegate`) |\r\n| `LOCAL_DELEGATE_CHUNK_MAX_TOKENS` | `2048` | Techo de `max_tokens` por trozo |\r\n| `LOCAL_DELEGATE_CHUNK_MIN_CHARS` | `400` | Trozo mínimo: por debajo ya no se vuelve a partir |\r\n| `LOCAL_DELEGATE_JSON_SCHEMA` | `auto` | `response_format` con schema en `local_extract`: `auto`/`on`/`off` |\r\n| `LOCAL_DELEGATE_FEEDBACK` | `1` | Línea de ahorro anexada al resultado cuando `source=path` (`0` la apaga). En `local_extract` no se anexa al texto —rompería el JSON—: va dentro de `_local_delegate` |\r\n| `LOCAL_DELEGATE_ALLOWED_DIRS` | *(vacío = sin restricción)* | Raíces permitidas para `path`, separadas por `;` |\r\n| `LOCAL_DELEGATE_WEB` | `1` | Web embebida del modo `stdio` (`0` para desactivarla) |\r\n| `LOCAL_DELEGATE_WEB_HOST` / `_PORT` | `127.0.0.1` / `9393` | Host/puerto de la web o del daemon |\r\n| `LOCAL_DELEGATE_WEB_FONTS` | `1` | Tipografía de marca desde Google Fonts (`0` = cero peticiones a terceros) |\r\n| `LOCAL_DELEGATE_AUTOSTART` | `0` | Auto-arranque de llama-swap (opt-in) |\r\n| `LLAMASWAP_EXE` / `LLAMASWAP_CONFIG` / `LLAMASWAP_LISTEN` | — | Solo si `AUTOSTART=1` |\r\n| `LLAMASWAP_WATCH_CONFIG` | `0` | `1` añade `-watch-config` al backend autoarrancado |\r\n\r\n## La métrica de ahorro\r\n\r\nEl MCP registra cada llamada en un log rotado por mes y sirve un **dashboard** en\r\n`http://127.0.0.1:9393`, con selector de rango y visibilidad de delegaciones en curso.\r\nEl *ahorro de contexto* = la entrada leída server-side (llamadas con `source=path`) ≈ tokens que\r\nnunca entraron al contexto de Claude, contados **una vez por delegación** aunque el MCP la trocee.\r\nEnfrente, el *coste local* = los tokens que consumió de verdad tu GPU **sumando todas** las\r\nllamadas: una delegación troceada repite el prompt de sistema en cada trozo, y esa diferencia es\r\nlo que costó trocear. Se usa siempre el token real que reporta el backend; `chars ÷ 4` es solo el\r\nrespaldo cuando no lo da. Detalle en la [wiki](./docs/wiki/Home.md).\r\n\r\nLos rangos, los días del gráfico y las horas de la tabla usan **tu zona horaria** (el log se\r\nescribe en UTC, que es un instante sin ambigüedad; la conversión es de presentación). El\r\ndashboard también separa **dónde corrió el cómputo**: `local` si el backend escucha en loopback,\r\n`remote` si la inferencia se fue a otra máquina —por ejemplo esta Mac usando la GPU de la PC—.\r\nLos eventos anteriores a la v0.11.0 no traen el campo y aparecen como `n/d`.\r\n\r\n## Alcance / no-objetivos\r\n\r\n`local-delegate` es deliberadamente **texto/imagen→texto**: arma el prompt (o el payload\r\nmultimodal), hace POST a `/chat/completions` y devuelve solo texto. Cosas que **no** hace\r\na propósito:\r\n\r\n- **Tool-calling local.** Los modelos locales no invocan herramientas ni ejecutan código;\r\n  eso lo sigue haciendo Claude. Añadirlo convertiría este paquete en un orquestador\r\n  paralelo, que no es el objetivo.\r\n- **Generación o edición de imágenes.** `local_describe_image` es solo imagen→texto\r\n  (describir, leer texto visible, responder una pregunta puntual); nada de generar ni\r\n  editar imágenes.\r\n- **Audio.** Para transcripción usa el companion\r\n  [`whisper-transcribe-mcp`](https://github.com/ZahiriNatZuke/whisper-transcribe-mcp) en\r\n  vez de intentar meter audio aquí.\r\n- **Sustituir la suscripción.** El objetivo es conservar cuota delegando pasos mecánicos\r\n  acotados, no enrutar todo el trabajo a modelos locales.\r\n\r\n## Integración con el cliente: hooks, skill y memoria\r\n\r\n`local-delegate install` deja lista la integración completa en tu HOME:\r\n\r\n| Componente | Dónde | Qué hace |\r\n|---|---|---|\r\n| Entrada MCP | config de Claude Code / `~/.codex/config.toml` / `~/.config/opencode/opencode.json[c]` | registra el servidor (stdio con `uvx` o HTTP contra el daemon) |\r\n| Hooks | `~/.claude/hooks/local-delegate/` + `settings.json` | sugieren delegar sin bloquear nunca la tool original |\r\n| Skill | `~/.claude/skills/delegacion-local/` y `~/.config/opencode/skill/delegacion-local/` | regla de oro y catálogo de tools |\r\n| Memoria | bloque gestionado en `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` y `~/.config/opencode/AGENTS.md` | la regla en una nota corta siempre cargada |\r\n\r\nPor defecto se configuran **solo los clientes que tengas instalados**; se elige a mano con\r\n`--clients claude|codex|opencode`. Los **hooks** son solo de Claude Code: opencode extiende con\r\nplugins en TypeScript, que es otra superficie. Cada pieza se puede excluir (`--no-hooks`, `--no-skill`,\r\n`--no-memory`, `--no-mcp`). Los hooks recomendados tras el piloto A/B son\r\n`UserPromptSubmit` (intenciones mecánicas) y `PreToolUse`/`Bash` (salidas largas de lint/tests);\r\nel experimento `PreToolUse`/`Read` queda apagado salvo `--enable-read-hook`, que lo registra y lo\r\nenciende (`uninstall` lo apaga).\r\nVer [Instalación de la integración](./docs/wiki/Integration-install.md) y\r\n[`docs/recipes/claude-code-hooks.md`](./docs/recipes/claude-code-hooks.md).\r\n\r\n## Groups de llama-swap (opcional)\r\n\r\nCon `pip install \"local-delegate-mcp[llamaswap]\"` quedan disponibles dos CLIs para gestionar\r\n**groups** de llama-swap (un modelo residente siempre cargado + un pool que se turna) con\r\nguardrail de VRAM **y RAM de sistema** incorporado (`--ram-gb` es opcional: `llama-server`\r\nmapea el GGUF también en RAM aunque el cómputo sea 100% GPU, así que un catálogo que cabe en\r\nVRAM puede igual agotar la RAM en máquinas con menos de 32 GB):\r\n\r\n```bash\r\nlocal-delegate check-llamaswap --config config.yaml --vram-gb 16 --ram-gb 32\r\nlocal-delegate init-llamaswap --config config.yaml --resident gemma3-4b --swap llama31-8b,qwen25-coder-14b --vram-gb 16 --ram-gb 32\r\n```\r\n\r\nEl paquete **nunca** toca tu `config.yaml` por su cuenta — estos comandos solo corren si vos\r\nlos invocás. `init-llamaswap` corre el/los guardrail(es) antes de escribir (no escribe nada si\r\nno cabe en VRAM o, si pasaste `--ram-gb`, en RAM) y nunca sobreescribe sin `--force` (dejando\r\n`.bak`). Detalle completo, semántica de `groups` verificada contra el código de llama-swap, y\r\nritual de aplicación en [`docs/recipes/llama-swap-groups.md`](./docs/recipes/llama-swap-groups.md).\r\n\r\n## Enlaces\r\n\r\n- [Wiki](./docs/wiki/Home.md) · [Recipes](./docs/recipes)\r\n- [CONTRIBUTING](./CONTRIBUTING.md) · [CODE OF CONDUCT](./CODE_OF_CONDUCT.md) · [CHANGELOG](./CHANGELOG.md)\r\n- [Licencia MIT](./LICENSE)\r\n",
  "bytes": 16802,
  "sha": "a49172fb1da8ebdcd17c523fd64e6128e3a046c655657899dc7285b284bb90f9",
  "repo_slug": "zahirinatzuke/local-delegate",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_zahirinatzuke_local_delegate_ea7ae783/readme"
}