Skip to main content
Glama
README.md
# LocalForge MCP

Local execution and repository runtime for MCP coding agents on Windows.

LocalForge MCP gives an MCP client structured access to a real workspace, files, code search, Git, foreground commands, and long-running development processes. It runs on the host machine under the current Windows user account. It does not create a fake filesystem or hide projects inside a virtual sandbox.

> Beta software with powerful host access. Read [Security model](#security-model) before use.

**Disclaimer:** This is a small, quick project that was partially vibe-coded. While I used AI agents to assist with development, I personally reviewed, tested, and validated the code rather than relying on generated output without verification. This project is primarily for personal use, especially to make `Amazon Quick` more useful in my company and bring it closer to the experience of a real coding agent.

## Features

- Workspace root, current directory, and Git-root discovery.
- Real filesystem list, stat, bounded read, atomic write, guarded text replacement, move, mkdir, and delete operations.
- Ripgrep-backed code and filename search with Python fallback.
- Batched `multi_search`: up to 16 parallel searches in one round-trip.
- Batched `batch`: run any mix of tool calls in parallel in one round-trip.
- `multi_edit`: several exact replacements per file in one call, validated end-to-end and written atomically.
- Tolerant argument aliases (`old`→`old_text`, `replacement`→`new_text`, `text`→`content`, `file`→`path`, `pattern`→`query`, `cmd`→`command`, and more), so mis-remembered parameter names do not cost a failed round-trip.
- Persistent `todo` tool: TodoWrite-style task list backed by the state file; survives restarts.
- MCP 2025-06-18 protocol with `structuredContent` tool results and the `logging` capability.
- Long-session guards: Git-root caching, whole-file-edit size limits, and a fixed-size striped path-lock table.
- Structured Git operations plus generic Git argument arrays.
- Foreground execution with separate stdout/stderr, timeout, truncation, duration, and exit metadata.
- Long-running process IDs, incremental split-stream logs, stdin, status, restart, stop, and process-tree cleanup.
- Canonical path checks with separate read/write roots and denied paths.
- Explicit environment inheritance, secret-name filtering, and basic output redaction.
- Concurrent tool execution: pipelined tool calls run in parallel instead of queueing behind the slowest call; edits to the same file serialize while distinct files edit in parallel.
- Nine stable MCP tools rather than many command wrappers.
- No runtime approval tool; approval remains the responsibility of the MCP host such as Amazon Quick.

## Requirements

- Windows 10 or Windows 11.
- Python 3.11 or newer.
- Git for Git tools.
- Ripgrep is optional but recommended for fast search.
- Developer tools required by your projects, such as Node.js, .NET SDK, Python, Java, or Docker.

## Repository layout

```text
localforge-mcp/
├── agent_runtime/
│   ├── capabilities.py
│   ├── config.py
│   ├── errors.py
│   ├── patch.py
│   ├── processes.py
│   ├── security.py
│   ├── server.py
│   ├── state.py
│   └── todos.py
├── tests/
├── localforge.example.json
├── QUICK.md
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── SECURITY.md
└── LICENSE
```

## Install on Windows

```powershell
cd D:\Repositories\localforge-mcp

py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -e .

Copy-Item localforge.example.json localforge.json
notepad localforge.json
```

Set `workspace_root`, `allowed_read_roots`, and `allowed_write_roots` before starting.

For one repository:

```json
{
  "workspace_root": "D:\\Repositories\\MyApp",
  "allowed_read_roots": ["D:\\Repositories\\MyApp"],
  "allowed_write_roots": ["D:\\Repositories\\MyApp"]
}
```

For multiple repositories under one parent:

```json
{
  "workspace_root": "D:\\Repositories",
  "allowed_read_roots": ["D:\\Repositories"],
  "allowed_write_roots": ["D:\\Repositories"]
}
```

Keep the other properties from `localforge.example.json`.

## Amazon Quick setup

Create a **Local** MCP connection with these values.

**ID**

```text
localforge-mcp
```

**Name**

```text
LocalForge MCP
```

**Command**

```text
D:\Repositories\localforge-mcp\.venv\Scripts\python.exe
```

**Arguments**

```text
-m agent_runtime.server
```

**Description**

```text
Local Windows coding runtime with workspace files, code search, Git, command execution, and long-running development processes.
```

Add environment variable:

```text
LOCALFORGE_CONFIG=D:\Repositories\localforge-mcp\localforge.json
```

Set startup timeout to `60` seconds, save, then toggle the MCP connection off and on after configuration changes.

**Agent prompt:** paste the rules in [`QUICK.md`](QUICK.md) into the connection's agent/system prompt. They steer the model to batch-first dispatch, filesystem-first edits, and `process` for long waits — the behaviors that make Quick feel like a CLI coding agent.

The older environment variable `WIN_AGENT_RUNTIME_CONFIG` remains accepted for compatibility.

## Manual smoke test

Start server:

```powershell
$env:LOCALFORGE_CONFIG = "D:\Repositories\localforge-mcp\localforge.json"
.\.venv\Scripts\python.exe -m agent_runtime.server
```

A blank terminal means the server is waiting for newline-delimited JSON-RPC input. Press `Ctrl+C` to stop it.

## MCP tools

### `workspace`

- `get`: return workspace root, current directory, and detected Git root.
- `set_cwd`: change current directory inside read policy.

### `filesystem`

- `list`, `stat`, `read`
- `write`, `replace_text`, `apply_patch`, `mkdir`, `move`, `delete`, `copy`

`read` supports byte offset and bounded output. `replace_text` defaults to exactly one expected occurrence, preventing accidental broad replacements. `multi_edit` applies several exact replacements to one file in order — the whole set is validated before anything is written, so a failing edit leaves the file untouched; prefer it for multi-part changes to a single file. `apply_patch` takes a unified diff for multi-hunk or multi-file edits: exact context, validated end-to-end before anything is written, all-or-nothing. Prefer it over writing plus executing a script to edit code. `copy` duplicates a file (or a directory with `recursive`) under policy checks — prefer it over shell copy commands, which mis-handle bracketed filenames like `[KB1] - doc.pdf` without `-LiteralPath`.

Argument names are tolerant: the server accepts common model guesses as aliases — `old`/`find` for `old_text`, `new`/`replacement` for `new_text`, `text` for `content`, `file`/`target` for `path`, `start`/`end` for `line_start`/`line_end`, `dest` for `destination`, `pattern` for `query` (search), `cmd` for `command` (execute/process), `id` for `process_id`, and `dir` for `path` (workspace). A canonical name always wins when both are sent.

### `search`

Search text or filenames recursively with include/exclude globs, case control, fixed-string or regex matching, and result limits.

### `multi_search`

Run up to 16 independent searches in one call. Each entry takes the same arguments as `search` (plus an optional `label` echoed back with its results), and all entries execute in parallel. Use it to fan out several queries in a single round-trip instead of one `search` per call — especially useful with clients that issue one tool call per turn. A failing entry is reported per-entry (`error`) without failing the whole batch.

```json
{"searches": [
  {"query": "UserService", "glob": ["*.py"], "label": "users"},
  {"query": "TODO", "files_only": true, "label": "todos"}
]}
```

Responses are matched to requests by id, so when a client sends several tool calls in one turn they run concurrently and may complete in any order. Tune the worker count with `max_concurrency` (default 8).

### `batch`

Run up to 16 tool calls of any type in one call, in parallel — the closest match to how a CLI agent dispatches several tools in a single turn. Each entry names a tool and its arguments exactly as a `tools/call` request, with an optional `label` echoed back. Distinct paths run concurrently; edits to the same file serialize on that path (a second edit sees a stale base and fails with `content_mismatch` rather than overwriting). A failing call is reported per-call (`error`) without failing the batch. Nested `batch` calls are rejected.

```json
{"calls": [
  {"tool": "filesystem", "arguments": {"action": "read", "path": "src/app.py"}, "label": "app"},
  {"tool": "search", "arguments": {"query": "UserService", "glob": ["*.py"]}, "label": "users"},
  {"tool": "git", "arguments": {"action": "status"}, "label": "git"}
]}
```

Prefer `filesystem write`, `replace_text`, `multi_edit`, and `apply_patch` for file edits over shelling out to python or shell commands: they are atomic, policy-checked, and give structured errors. `execute` and `process` are for running programs, builds, and tests — those legitimately write files as the current user.

### `todo`

Persistent task list (TodoWrite-style) with `get`, `add`, `update`, `delete`, and `clear`.

```json
{"action":"add","title":"Fix login bug"}
{"action":"update","id":1,"status":"in_progress"}
{"action":"get"}
```

Keep it current across turns: add before starting work, mark `in_progress` while working and `completed` when done, delete stale entries. State is stored next to the config (`.localforge-state.json`) and survives server restarts; without a state file it is in-memory only.

### `git`

Presets: `status`, `diff`, `log`, `show`, `branch`, and `root`.
Broad `git add -A` / `--all` / `.` is blocked (stage explicit files instead), as is staging private keys (`*.pfx`, `*.p12`, `*.pem`, `*.key`).

Use `run` with an argument array for other operations:

```json
{"action":"run","args":["add","src/app.py"]}
```

### GitHub via `gh` CLI

There is no GitHub API tool; use `gh` through `execute` (one-time `gh auth login` on the box — tokens cannot ride `env` overrides by design, and stored credentials work since processes run as your user):

```json
{"command":["gh","pr","create","--title","x","--body","y"]}
{"command":["gh","issue","list","--limit","20"]}
```

Prefer `gh ... --json <fields>` for machine-readable output. Note `gh` counts as network activity when `network` is `disabled`.

### `execute`

Prefer argument arrays:

```json
{"command":["npm","test"],"cwd":".","timeout":600}
```

String commands automatically use the configured shell:

```json
{"command":"npm test | Select-String failed"}
```

Pass `shell` explicitly only to force array commands through the shell. Argument arrays bypass shell parsing and stay preferred.

For SSH, prefer key auth with fail-fast flags so prompts never hang the call:

```json
{"command":["ssh","-i","C:\\Keys\\deploy.pem","-o","BatchMode=yes","-o","StrictHostKeyChecking=accept-new","-o","ConnectTimeout=15","ubuntu@host","docker ps"]}
```

Two Windows gotchas: keys under Downloads usually have permissive ACLs, which ssh rejects (`Permissions ... are too open`) — fix with `icacls key.pem /inheritance:r /grant:r "$env:USERNAME:F"`. And ssh requires `%ProgramData%` in the environment (inherited by default since 1.4.0); without it ssh dies instantly with exit 255 and no output.

Complex quoting (multi-line PowerShell with `$vars`, nested `python -c`, bracketed paths) breaks when passed as a command string. Write the script with `filesystem write` to a temp file, then run it by path (`powershell -ExecutionPolicy Bypass -File script.ps1`, `python script.py`). Prefer `filesystem copy` over shell copy commands.

### `process`

Actions: `start`, `read`, `write`, `status`, `list`, `restart`, and `stop`.

Use `after` with the previous `next_after` value for incremental log reads. `cursor_lost=true` means older output was evicted from the bounded buffer.

### Quick examples

```json
{"action":"get"}
{"action":"read","path":"src/app.py","line_start":1,"line_end":80}
{"query":"UserService","glob":["*.py"],"context_lines":2}
{"action":"status"}
{"command":["npm","test"],"cwd":".","timeout":600}
{"action":"start","command":["npm","run","dev"]}
```

## Configuration

### Modes

- `READ_ONLY`: filesystem inspection only.
- `WORKSPACE`: workspace filesystem changes, but no process execution.
- `DEVELOPMENT`: workspace access and process execution.
- `FULL_ACCESS`: dedicated filesystem tools may access paths outside configured roots, while denied paths still apply.

### Network

- `unrestricted`: runtime does not block recognized network commands.
- `disabled`: blocks recognized package, Git, and URL-oriented commands.

This is command classification, not packet filtering. Use Windows Firewall, a controlled proxy, AppContainer, container, or VM for enforceable network restrictions.

### Shells

Supported `default_shell` values:

- `powershell`
- `pwsh`
- `cmd`
- `sh`

`execute.timeout` is seconds. `process.wait_ms` is milliseconds (max 60000). `limit_bytes`, `max_bytes`, and `max_file_read_bytes` are bytes. `filesystem offset` is a byte offset in legacy read mode; use `line_start`/`line_end` for line mode. `max_concurrency` bounds how many tool calls run in parallel (default 8). `log_to_client` (default false) emits an MCP logging notification per tool call (tool, duration, outcome) visible in the client; `LOCALFORGE_LOG=1` writes the same line to stderr. `replace_text` and `multi_edit` refuse files larger than `max_file_read_bytes` with `file_too_large`. `kill_children_on_exit` (default true) puts every spawned child into a Windows Job Object that the kernel terminates when the server exits, however it exits; set it to false to let children outlive the server.

## Security model

LocalForge MCP provides application-level mediation, not an OS security boundary.

Dedicated filesystem tools canonicalize paths and resolve existing links before checking roots. However, once command execution is allowed, the child process has the current Windows user's rights. Build scripts, package lifecycle hooks, Git hooks, interpreters, native binaries, and child processes can access anything that user can access.

For untrusted agents or repositories, run LocalForge MCP under a dedicated low-privilege Windows account and add OS-level isolation. See `SECURITY.md` for remaining reparse-point race and network limitations.

## Development

```powershell
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\python.exe -m pytest -q
.\.venv\Scripts\python.exe -m compileall -q agent_runtime
```

## Status and limitations

- Windows-native process-tree behavior requires Windows testing; cross-platform tests cover portable logic.
- Process state is in-memory and does not survive runtime restart.
- Children die with the server however it exits on Windows (Job Object); on POSIX they die only on explicit stop/restart or clean exit.
- Shell command network detection is intentionally conservative and cannot inspect arbitrary scripts.
- File writes are atomic at replacement time but cannot preserve every filesystem-specific metadata attribute.
- MCP transport is stdio only.

## License

MIT. See `LICENSE`.