
maxh8086/tokenfrugal
Developer toolsOffload routine coding to a local LLM (Ollama/OpenAI-compatible) via an MCP gateway with Docker tools, 280+ personas and a Neo4j task plan. Save Claude/Codex tokens.
Overview
Keywords: MCP server, Model Context Protocol, local LLM, Ollama, token saving, reduce Claude Code usage, Codex, LM Studio, vLLM, AI agents, Docker MCP router, offline coding assistant. Coding is evolving from a primary execution skill to an orchestration skill. To test this shift, I built this latest project entirely using AI coding agents. The takeaway? When AI handles the syntax, your ability to think deeply, design robust architectures, and drive creative solutions becomes your true competitive advantage. Check out the results and let me know your thoughts: linkedin.com/in/vpavtekar You talk to one big cloud model (Claude Code, Codex, ...). It hands small jobs to a free local model and gets back a short summary. - Each task runs on local model. The two models never run together and never talk to each other. - A model writes code and edits files. A model reads, analyses and browses.
README
TokenFrugal
Stop burning your Claude Code / Codex token quota on boilerplate. TokenFrugal hands routine coding, tests, docs, reviews and research to a free local LLM (Ollama or any OpenAI-compatible API) through one small MCP server, and keeps your paid cloud model for architecture and hard decisions.
Keywords: MCP server, Model Context Protocol, local LLM, Ollama, token saving, reduce Claude Code usage, Codex, LM Studio, vLLM, AI agents, Docker MCP router, offline coding assistant.
A note on how this was built
Coding is evolving from a primary execution skill to an orchestration skill. To test this shift, I built this latest project entirely using AI coding agents. The takeaway? When AI handles the syntax, your ability to think deeply, design robust architectures, and drive creative solutions becomes your true competitive advantage. Check out the results and let me know your thoughts: linkedin.com/in/vpavtekar
How it works (for beginners)
You talk to one big cloud model (Claude Code, Codex, …). It hands small jobs to a free local model and gets back a short summary.
You --> Cloud agent (plans, decides)
| dispatch_task(agent, task)
v
TokenFrugal router --> ONE local model (builder or thinker)
| |
|<-- short summary --+ (150-300 tokens)
|<-- task_id ------- if it failed: resume_task / get_detail
- Each task runs on one local model. The two models never run together and never talk to each other.
- A builder model writes code and edits files. A thinker model reads, analyses and browses.
- The only things that pass back to the cloud agent are the short summary, a
task_id, and the answer toask_followup. - Shared state lives in local stores (SQLite task log, Neo4j plan), not in the models’ memory.
- If something fails, nothing silently goes to the cloud. You get a short error and a
task_idto resume or re-scope.
| Role | Model | Used for |
|---|---|---|
| Builder | qwen2.5-coder-yarn:3b |
Writing and editing code, tests, docstrings, file edits |
| Thinker | llama3.2:3b-16k |
Reading, debugging, reviews, summaries, research, browsing |
The problem it solves
Corporate or entry-level AI subscriptions hit their limits fast. Most of those tokens go to routine work: boilerplate, tests, docstrings, lint fixes, summaries, log reading. A laptop with a GPU (or an Apple Silicon Mac) can do that work for free, but wiring a local model, tools and agents together is tedious.
TokenFrugal does the wiring:
- Your cloud agent calls one tool,
dispatch_task(agent, task), and gets back a 150-300 token summary instead of pages of output. - A local model does the work with a small allowlisted toolset (4-6 tools per role) so small models stay on track.
- 282 agency-agents personas are mapped to 11 roles (builder, analyzer, reviewer, security, docs, designer, browser, research, data, debugger, tracker).
- Failures return a short error and a
task_id(resume_task). No silent cloud fallback, so no surprise spend.
For companies this means a cheaper setup: standard-tier cloud subscriptions are enough because the cloud model only plans and reviews, while the bulk of the work runs on a local LLM hosted on a modest, inexpensive GPU (a 3B-class model fits in a few GB of VRAM) that is sized to deliver exactly what routine tasks need and no more.
Good fit: side projects, college projects, corporate seats with tight quotas, laptops with a GPU or Apple Silicon.
Requirements
| Need | Notes |
|---|---|
| Python 3.11+ and git | |
Docker (Desktop, Engine, Colima, Rancher Desktop or Podman with a docker shim) |
Runs the tool backends. Docker Desktop is free only for personal/education/small-business use; check its licence for your company. |
| Ollama or any OpenAI-compatible LLM API | Host and port set in .env (TOKENFRUGAL_LLM_URL). Default http://localhost:11434/v1. |
| Two local models | Pull the bases, then create the tuned tags the router expects (qwen2.5-coder-yarn:3b, llama3.2:3b-16k; see router/agents.yaml):ollama pull qwen2.5-coder:3b / ollama pull llama3.2:3b``ollama create qwen2.5-coder-yarn:3b -f models/builder.Modelfile``ollama create llama3.2:3b-16k -f models/thinker.Modelfile |
| An MCP-capable agent | Claude Code, Codex, Cursor or any MCP client. |
Check everything at once: python scripts/doctor.py (installs nothing; prints exact fix commands).
Install (Windows, macOS, Linux)
git clone https://github.com/maxh8086/tokenfrugal.git
cd tokenfrugal
python -m venv .venv
# Windows: .venv\Scripts\activate macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # set TOKENFRUGAL_LLM_URL (host:port) for your LLM
python scripts/doctor.py
Then, once:
- Start backends (creates secrets in
~/.docker/mcp/mcp.env, outside the repo):scripts/mcp_up.sh(macOS/Linux) orscripts/mcp_up.ps1(Windows). Helper images that wrap upstream tools are built from the Dockerfiles indocker/on first use and reused afterwards. - Install the tool profiles (build, build-data, analyze, security, debug, docs, design, browser) into Docker’s MCP Toolkit:
Setpython scripts/install_profiles.py # mounts CC_WORKSPACE from .env, else this repo; --workspace PATH to overrideCC_WORKSPACEin.envto the same absolute path. Re-run it any time to refresh the profiles. The committed.mcp.jsonpins no workspace, so the router mounts the git root of the folder the client launches it from. To mount a different folder (for example the parent that holds several projects), setCC_WORKSPACEas a real environment variable (user-level on Windows:setx CC_WORKSPACE C:/path/to/Projects, then restart the client). A real environment variable beats.env. Template for a per-user MCP override:"env": { "CC_WORKSPACE": "/absolute/path" }inside thetokenfrugalentry of a local-scope config. - Security role only (SonarQube): open http://localhost:9100, log in as
admin/adminand set a new password, then My Account -> Security -> Generate Token (type: User). Put the token in~/.docker/mcp/mcp.envasSONARQUBE_TOKEN=...(SONARQUBE_ORGis only needed for SonarCloud; leave it empty for the local server). If your secrets file lives elsewhere, setMCP_ENV_FILE=in.env(or the environment); the router anddocker-compose.ymlboth read that path instead. Then check it withpython scripts/smoke.py security. - Smoke tests:
python scripts/smoke.pyruns one tiny task per role (needs Ollama and the steps above);python -m scripts.smoke_planexercises the Neo4j plan store (prune, checkpoint, dependency guard) with throwaway data.
Register with your agent (stdio):
{ "mcpServers": { "tokenfrugal": { "type": "stdio", "command": "python", "args": ["-m", "router.server"], "cwd": "/tokenfrugal" } } }
For Codex use the equivalent [mcp_servers.tokenfrugal] table in ~/.codex/config.toml.
Use
Ask your agent: “use tokenfrugal to write unit tests for utils.py”. Tools: dispatch_task, ask_followup, get_detail, resume_task, list_roles, plus plan tools (plan_add, plan_overview, …) backed by Neo4j with automatic retention (PLAN_RETENTION_DAYS). server.py is a simpler two-tool server if you only want query_local_llm.
Choosing a model per agent
agent_models in router/agents.yaml maps an agent slug to the local model it runs on. Models for each agent can be compared in the benchmark report. A value is builder, thinker or any Ollama tag; agents not listed use their role’s model.
Edit the mapping in a browser instead of the yaml:
python -m scripts.agent_ui # http://127.0.0.1:7878
Your changes are saved to router/agent_models.json (git-ignored, or set TOKENFRUGAL_AGENT_MODELS_FILE) and override the yaml defaults. Restart the MCP client to apply. A blank choice means the role’s model. TOKENFRUGAL_AGENT_MODELS=0 or any TOKENFRUGAL_MODEL_* variable turns the mapping off and pins every agent to the role models.
Model results
Results change as models and code change, so they are not listed here. See the live benchmark report for rankings and the live explorer to filter and sort runs.
Adding MCP servers
Recommended: the Docker MCP Gateway with servers from the Docker MCP Toolkit catalogue. Most MCP servers you will want (filesystem, git, fetch, browser, search, databases, …) already exist in the catalogue as maintained, sandboxed containers. TokenFrugal runs each role through docker mcp gateway run --profile , so adding one is a profile edit, not new code:
- Add the catalogue server to a profile in
profiles/*.yaml(or pick it withdocker mcp/ Docker Desktop’s MCP Toolkit). - Allowlist 4-6 of its tools for the role in
router/agents.yaml; small local models do better with few tools. - Run
python scripts/install_profiles.pyto import the profiles, then restart the router.
Servers that are not in the catalogue are shipped with TokenFrugal rather than left to you. Two routes, both already used in this repo:
- Containerised helper: a Dockerfile in
docker/wraps the upstream tool (for exampletokenfrugal/chrome-devtools-mcp:1,tokenfrugal/penpot-mcp:1), anddocker-compose.yml(projectts-mcp) starts it on demand for the roles that list it undercompose:. - Native stdio server: a role can list a local stdio MCP server under
native:inrouter/agents.yamlwhen it cannot run in Docker.
When you need a new MCP server, ask Claude Code or Codex to add it. Point it at this README and CLAUDE.md: it should check the Docker MCP catalogue first, fall back to a helper image or native server only if the catalogue has nothing, update the profile and agents.yaml, and re-run scripts/install_profiles.py. The dashboard then shows the new server on the agent card and logs each call as it happens.
Building helper images from a pinned upstream commit
If you prefer to build from source at an exact commit instead of the npm package, docker/pins.yaml lists each upstream (repo, full 40-char sha, image tag, dockerfile). The helper fetches only that commit, checks that HEAD equals the pin, builds the image, and deletes the clone:
python scripts/pinned_build.py # all pins
python scripts/pinned_build.py chrome-devtools-mcp # one pin; --keep leaves the clone for inspection
To bump a pin, change sha (resolve it with git ls-remote --tags ), rebuild, and re-run the smoke tests.
A weekly GitHub Actions job (.github/workflows/bump-pins.yml, also runnable by hand) runs scripts/check_pins.py, which looks for newer release tags and opens a pull request that moves the pin. Nothing changes until you merge it; run python scripts/pinned_build.py first to confirm the image still builds.
Architecture
- High-level design: cloud agent, router, local LLMs, Docker MCP profiles, Neo4j, dashboard
- Low-level design: modules
- Low-level design: dispatch_task sequence
- Interactive routing flowchart: step through a routine task and a fallback task
- Full write-up
FAQ
Does it send my code to the cloud? Local models run on your machine; only the short summaries return to your cloud agent. Can the small models really code? For scoped, well-specified tasks yes; that is why tool allowlists and step limits exist. Keep architecture in the cloud model. Windows/macOS/Linux? All three; only git, Docker and Python are called.
Credits and licence
Apache-2.0 (see LICENSE, NOTICE). Inspired by and built on the work of many authors; see CREDITS.md. Third-party backends run as their own unmodified containers under their own licences.
Developed by Vaibhav Pavtekar with AI assistance (Claude Code).
Live dashboard
The dashboard starts with the router and opens in your browser at http://127.0.0.1:7777 (configurable, see below). At the top is a two-row status table: Status (idle, running, disconnected or unavailable, plus which client is connected: Claude, Codex or both), Ollama, Model and Agent on the first row; Docker (one third) and the MCP servers of the selected role (two thirds) on the second. Below it are the running tasks, a thinking box and a log, with a character that works while a task runs and sleeps when idle. If another router already serves the port, it is reused.
- A Tokens saved counter sits below the animated character: the running total, per task (failed ones too, since the local work still ran), of the tokens the local model handled (prompts, tool output, its own text; estimated at 4 characters per token) minus the short summary returned to your cloud agent. The label is
tokens_savedinrouter/ui.yaml. TOKENFRUGAL_UI=0disables it,TOKENFRUGAL_UI_OPEN=0skips the browser pop-up, the port isport:inrouter/ui.yaml(default 7777); theUI_PORTenvironment variable overrides it.- The dashboard is the router’s built-in MCP channel: every MCP call a local agent makes (tool name, arguments, step) is recorded as an event the moment it happens, and each agent card shows the MCP servers of its role with live status, the current tool, and a log of every call.
- The fourth top box shows synaptree-mcp (the code graph UI, http://127.0.0.1:8787/ui/):
runningbecomes a link that opens its UI in a new tab, otherwiseunavailable. The ports probed aresynaptree_portsinrouter/ui.yaml(default 8787, probed via/healthz). - All dashboard text comes from
router/ui.yaml; roles, models and tools come fromrouter/roles.json. - Needs Node 18+ (no npm packages); without Node the router runs as before.
node ui/server.jsstarts it by hand. - The router writes
router/events.jsonlandrouter/roles.json; override withGATEWAY_EVENTS. Restart the router after updating so it emits events.
They say we’re just “vibe coding” now, but curating immaculate vibes for the AI takes a shocking amount of caffeine. If you like what the agents built, help keep my vibes (and local GPUs) highly caffeinated: buymeacoffee.com/maxh8086
Install
This server does not publish a one-line install command.
Open the repository installation guideConfiguration
{ "mcpServers": { "tokenfrugal": { "type": "stdio", "command": "python", "args": ["-m", "router.server"], "cwd": "<path-to>/tokenfrugal" } } }