Skip to main content
Glama
README.md
# local-worker-mcp

> **Status (2026-09-10):** kept as our own custom delegation tool, but
> **not the default anymore** -- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
> (`dsh --profile worker "<task>"`) is, after a head-to-head benchmark on
> the same task/model showed its real `bash`/`fs` tools verify their own
> work more reliably out of the box. See `~/local-llm-bench` for the
> comparison and `tools/claude-hooks/README.md` for the delegation
> guardrail that now points at `dsh` first. This server is still useful
> for cases dsh doesn't fit, and got a `run_script` tool added as a direct
> result of that benchmark (closing the verification gap it found).

An MCP server that exposes a local Ollama model (e.g. Qwen3.6) as an
**agentic worker** -- with its own tool-calling loop (read/list/write
files inside an allow-listed folder, search the web, get the real current
date/time) -- for use from **any MCP client**: Claude Code, Claude
Desktop, Continue, Cursor, whatever.

"Foreman/worker" pattern: the MCP client (you, or a larger LLM like
Claude) keeps the full context and decides what's worth delegating; this
server only ever executes the one scoped, well-specified task it's handed,
with no memory of anything else.

## Why this exists

Grew out of using Continue.dev with local models and hitting real
instability in that extension (it hangs). Instead of continuing to patch
a VS Code extension, this project separates the concern: a plain MCP
server, no GUI in the loop, talking straight to Ollama.

## Architecture

```
MCP client (Claude Code, Claude Desktop, ...)
        │  tools/call "delegate_task"
        ▼
local-worker-mcp (this server, streamable HTTP)
        │  /api/chat with tools attached
        ▼
Ollama (localhost:11434, or remote over Tailscale)
        │  tool_calls: read_file / write_file / search_web / ...
        ▼
local-worker-mcp executes each tool call, scoped to workspace_root
```

## Security (read this before using it)

- **`allowedRoots`** in the config is a real allow-list: the server
  refuses any `workspace_root` or path that isn't literally inside one of
  those folders -- empty by default, by design. You have to explicitly
  add folders via the dashboard or the config file before the worker can
  touch anything.
- **`trustedParents`** is the ergonomic escape hatch for the above: any
  subdirectory under one of these is auto-allowed without registering it
  individually -- defaults to `~/code`, so any project you keep there
  (and whatever `workspace_root` your MCP client passes for it, e.g. the
  current Claude Code project directory) just works with zero manual
  setup. Use `allowedRoots` instead when you want something scoped
  tighter than "everything under this folder".
- Every write (`write_file`) is logged to `~/.local-worker-mcp/activity.log`
  with the full path and size -- so you can audit what the model touched
  afterward.
- Explicitly tested that a directory-escape attempt (`../../etc/...`) is
  rejected by the server itself, not just "trusted" to the model behaving.
- The local model can get things wrong (see `~/local-llm-bench/RESULTS.md`
  for real data on when and how much) -- always review `steps` in the
  response before trusting a file change.

## Install / run

```bash
git clone https://github.com/deatherick/local-worker-mcp.git
cd local-worker-mcp
npm install
npm run build
node dist/server.js
```

Open `http://localhost:8787` for the config dashboard (model, context
length, allowed folders, etc. -- saved to `~/.local-worker-mcp/config.json`).

To run it centralized (one Mac with Ollama, reachable from other machines
over Tailscale/LAN) and connect from an MCP client on another machine,
point that client's MCP config at `http://<host>:8787/mcp`
(`streamable-http` transport), e.g. in Continue:

```yaml
mcpServers:
  - name: Local Worker
    type: streamable-http
    url: http://your-host.your-tailnet.ts.net:8787/mcp
```

## Exposed tools

- `delegate_task(task, workspace_root, model?, think?, max_steps?)` --
  runs the full loop. Returns `finalText` plus every `steps` entry (which
  tool it called, with what arguments, and the result) so you can audit
  before trusting the outcome.
- `list_worker_config()` -- current config, so a client can confirm which
  model/folders it's talking to before delegating.

## Development

```bash
npm run dev   # build + start
```

## Roadmap / not included yet

- Proxying to other existing MCP servers (Git, Context7) from inside the
  worker's own loop -- today it only has its own `search_web` plus the
  native file tools.
- A shell-command tool -- deliberately NOT included in v0.1 given the
  risk; if added, it needs its own allow-list of permitted commands, not
  free execution.
- Auth on the HTTP endpoint -- currently assumes it runs behind a trusted
  network (Tailscale/LAN), not exposed to the open internet.

## License

Apache 2.0.