OPEN-SOURCE RELEASE · NPM + OFFICIAL MCP REGISTRY
Useful AI access, governed by explicit boundaries.
Local Workspace MCP gives an AI assistant read-oriented tools over the workspaces an operator declares (files, git, and bounded inspection of images and documents), and nothing else by default. The whole boundary, including what the one execution path is, is written down in the repository.
- CONTEXT
- Independent open-source developer tool, released October 2026. A Model Context Protocol server for bounded local workspace access.
- ROLE
- Sole designer and implementer: threat model, tool surface, containment checks, test suite, release.
- THE DECISION
- Default-deny. The assistant sees only roots an operator declared, through tools that read rather than act; the single execution path is a named task from an operator-written allowlist.
- THE RESULT
- 23 read-oriented tools behind two layers of root containment, a published threat model, 276 tests, four rounds of independent adversarial security review, and an independent architecture review before release.
- THE SOURCE
- Public repository, the npm package local-workspace-mcp@0.2.0, and the official MCP Registry listing io.github.peter4leadson/local-workspace-mcp.

THE PROBLEM
An assistant that reads everything, or one that reads nothing.
AI assistants are only useful with real context, and the Model Context Protocol made it easy to give them tools for local files. The options on offer tended to one of two shapes: broad filesystem access that a careful operator should not hand over, or configurations so restrictive the assistant can do nothing useful. For anyone who thinks carefully about what a model can reach, "just give it the home directory" was never an acceptable answer.
The question I wanted to answer: can access be useful while staying inside declarations the operator wrote down, so the boundary belongs to the operator and is not a promise the tool makes?
THE CONSEQUENTIAL DECISION
Default-deny, then a deliberately small read surface.
The boundary is default-deny: file and media access reaches only operator-declared workspace roots, and the server enforces that declaration two independent ways: lexical containment first, then canonical-realpath containment so a symlink that points outside the declared root is refused. Inside the boundary sit twenty-three tools, all read-oriented: bounded file reads, directory listing, lexical search, read-only git inspection, and bounded inspection of images, archives, PDFs, and Office documents. Each advertises readOnlyHint, marking it as a tool meant to mutate nothing.
There is no write tool, no delete tool, no arbitrary shell. The declared configuration is TOFU-pinned (trust on first use), so a quiet change to the allowlist later is a detected event, not a silent grant. The nine media tools run through the same containment and deny gates, plus their own hard bounds: byte caps, pixel caps, archive-member and PDF-page limits, and streamed decompression under a byte counter. One of them, fs_image, returns images as native MCP image blocks; that path was verified end to end over a live ChatGPT MCP connection, though image-block support stays host-dependent.
THE HARD PART
One declared actuator, labeled for what it is.
Reads can be offered broadly inside a boundary; execution cannot. I kept exactly one execution path: task_run, which invokes a named task from a short operator-written allowlist: a build, a test command, whatever the operator chose to declare. Those commands run as the user, with the user's permissions, on the user's machine.
It is not a sandbox, and the documentation says so. An allowlisted command is a real process with real side effects; the boundary is that the assistant can invoke only names the operator wrote down, never arbitrary commands.
ADVERSARIAL REVIEW
The boundary was attacked before it was published.
Before release, the design went through four rounds of independent adversarial security review and an independent architecture review. Findings were remediated and re-reviewed until the review converged on ship. The threat model is published in the repository: the actors considered, the trust boundaries drawn, what the design protects against, and what it does not claim to.
A 276-test suite covers the cases that matter for a containment claim: path escapes, symlink tricks, undeclared roots, task names that were never allowlisted, and media-specific bounds like archive bombs and oversized renders. Review and tests show the boundary held under the attacks they covered. They are not a guarantee that no vulnerability exists.
RESULTS AND LIMITS
A released tool, at its actual size.
What this demonstrates: threat-model-first design for AI tooling; a deliberately narrow tool surface, extended to visual and document artifacts under the same gates; containment enforced two independent ways; the dangerous path labeled plainly; and a release carried through adversarial review into the official registry, published with OIDC trusted publishing and recorded provenance.
What it does not demonstrate: adoption. An npm package and a registry listing mean the tool is available, not that anyone uses it. It also has real limits, stated in the repository: host built-in file tools overlap part of its surface, tool schemas cost context tokens, whether the model actually sees returned image content depends on the host client, an allowlisted task is bounded in name not in what the command itself can do, and Windows is unsupported.
WHERE TO FIND IT
Where the release lives.
The repository is public, including the threat model that states the system's own limits. The release is local-workspace-mcp@0.2.0 on npm, and the official MCP Registry lists it as io.github.peter4leadson/local-workspace-mcp.
Read the repository and threat model (opens in a new tab)View the npm package (opens in a new tab)View the MCP Registry listing (opens in a new tab)
If bounded AI access is a problem on your desk, write to peter@petercbennett.com.