
Local Workspace Mcp
io.github.peter4leadsonv0.1.0Updated Oct 10, 2026
Bounded, read-oriented filesystem, git and named-task tools over authorized workspace roots.
Overview
Gives an assistant bounded, read-only access to authorized local workspace roots: file listing, reading, searching, and read-only git inspection.
- What it does
- A local stdio MCP server exposing 14 tools: workspace_roots, bounded filesystem reads and searches (fs_list, fs_stat, fs_read, fs_read_many, fs_search_files, fs_search_content), read-only git tools (git_status, git_diff, git_log, git_show, git_branches), and task_list plus task_run for operator-declared commands. Paths are confined to configured workspace roots and checked against a default-deny list for secrets, .env files, private keys, and .git internals. There are no write, delete, or arbitrary-shell tools.
- When to use it
- Use it when an assistant needs to read and search a local repository or project directory, or inspect uncommitted git state, without granting general filesystem or shell access. It suits teams that want the access boundary defined in an operator config rather than in the client's permission prompts.
- Requirements
- Node.js 20 or newer; git for the git_* tools and ripgrep (rg) for content search. Runs locally over stdio, spawned by the MCP host. An operator config file at ~/.config/local-workspace-mcp/config.json must define workspace roots and any allowlisted tasks. No accounts, API keys, or network access are declared.
Installation
In SourceWeft
- Open Local Workspace Mcp in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
local-workspace-mcp
Your AI assistant needs context. It does not need unrestricted access.
A read-oriented MCP server for local workspaces. Write, delete, and
arbitrary-shell tools do not exist in it. Not gated or disabled: absent. Every
filesystem path is checked against operator-authorized roots and a
default-deny list for secrets, .env files, private keys, and .git
internals. Git read tools and operator-declared named tasks provide
bounded capability without giving the model a shell.
- 14 tools over stdio JSON-RPC: bounded filesystem reads/searches,
read-only git (status/diff/log/show/branches), and
task_runfor explicitly allowlisted commands. - One transport: each MCP host spawns
workspace-mcp serve --stdio. No inbound listener, no daemon; the server itself makes no network calls (allowlisted tasks run as your user and are not network-restricted). - Honest scope:
task_runexecutes real commands as your user. The allowlist bounds what can be invoked, not what invoked code can do. It is not a sandbox.
Evaluating this for team use? Start at docs/THREAT-MODEL.md and SECURITY.md.
The problem
Hosts' built-in file tools are convenient but live in the client's
permission loop, the same loop users bypass out of fatigue
(--dangerously-skip-permissions, "yes to everything"). The official
@modelcontextprotocol/server-filesystem ships read and write tools
with no default secrets denylist, and has already overwritten a user's
.env (upstream issue #1869). Generic filesystem MCPs give a model file
access; they do not give it bounded access.
This server moves the boundary server-side: roots, deny rules, task allowlists, and audit live in an operator config that repository content cannot widen. A compromised or bypassed prompt loop cannot invoke a tool that does not exist.
Quick start
Requires Node ≥ 20, plus git for git_* tools and ripgrep (rg) for
content search. Building from a clone needs pnpm (corepack enable).
The generated config ships a placeholder workspace named example —
replace it with a real root or doctor will report root.example FAIL.
A healthy run ends:
Minimal working config (strict JSON — no comments or trailing commas):
Then connect a host (below), confirm it registered (claude mcp list,
/mcp, or your host's equivalent), and ask: "use workspace_roots, then
fs_list on myproj". First useful call sequence: fs_list → git_status
→ git_diff on uncommitted work, where a chat client otherwise
has no eyes.
Usage examples
Denied behavior is explicit, never silent:
Denial codes are the policy working as intended, not errors to report;
widening access happens only in the operator config. Task/search timeouts
and output limits return timedOut:true/truncated:true in a normal
result rather than an error. INTERNAL_ERROR covers spawn/tool failures and should not
appear in healthy use; git_* on a non-git root returns {repo:false}
gracefully.
Tool surface
All 14 tools, annotated readOnlyHint where true (hosts can auto-approve
pure reads). task_run is the only tool with side effects.
Supported hosts
GUI hosts (Claude Desktop especially) spawn servers with a minimal PATH,
not your shell's. If a host reports ENOENT or stays disconnected while
doctor is green, the binary is not on the host's PATH. Use the absolute
path (which workspace-mcp) as the command and check the host's MCP log.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json
on macOS):
Codex CLI (~/.codex/config.toml, or codex mcp add):
For ChatGPT, tunnel-client polls an outbound HTTPS path and spawns the
stdio command locally; no inbound network listener opens. (tunnel-client
can optionally bind a loopback-only health endpoint — see OPERATIONS.)
Operator runbook: docs/OPERATIONS.md.
Permission model
- Roots are explicit. Nothing outside
workspaces[].pathis reachable;~, control characters, Windows drive paths, and..traversal are rejected before any syscall, and again afterrealpath. Point roots at the projects you actually work on; never authorize~/or a parent directory that contains things the assistant should not read. - Denied by default, including:
.env*/*.envspellings (except.env[.*].{example,sample,template}),.envrc, private-key filenames,.pem/.key/.p12/.pfx/.jks/.kdbx,.ssh,.aws,.azure,.gnupg,.kube/kubeconfig,.docker,.npmrc,.netrc,.pgpass,.pypirc,.git-credentials/.gitconfig, shell histories,*.tfvars,secrets.{json,yaml,yml,toml}/.secrets/secrets.d,credentials/service-account*.json/gha-creds-*.json, and all.gitinternals. Representative list — the authoritative rules areDEFAULT_DENYinsrc/policy.ts(repository); denies are fail-closed. - Name is not the only boundary: a file containing
BEGIN … PRIVATE KEYarmor under a benign name is refused in reads,git_showblobs, and search previews. The scan is heuristic defense-in-depth (encoding and size-budget limits documented in the threat model); name/policy denies are the primary boundary. - Git is read-only and hardened: argv-only spawns,
--end-of-options, strict ref validation, literal pathspecs,GIT_TERMINAL_PROMPT=0. Inside allowlisted tasks the headline repo-config exec keys are pinned viaGIT_CONFIG_*overrides (core.fsmonitor,core.sshCommand,diff.external,core.hooksPath; per-driverdiff.<name>/filter.<name>hooks are a documented residual). Rename diffs and merge-conflictdiff --ccblocks are policy-checked on every path they name. - Tasks are declared, not typed: argv arrays in config,
shell:false, sanitized environment, per-task timeout and output caps. Operator env cannot injectGIT_*, loader, or interpreter hooks. - Errors are bounded: every failure is
CODE: message; absolute host paths are scrubbed from tool errors, except that a task-spawn failure reports the operator-configured argv itself.task_runreturns subprocess stdout/stderr verbatim — that output belongs to commands the operator allowlisted.
Errors: ACCESS_DENIED, OUTSIDE_ROOT, NOT_FOUND, BINARY_FILE,
RESOURCE_LIMIT, TASK_DENIED, UNKNOWN_WORKSPACE,
WORKSPACE_UNAVAILABLE, INVALID_ARGUMENT, INTERNAL_ERROR, plus
CONFIG_ERROR at config load/startup. task_run and search timeouts
return timedOut:true in the result. Git-command failures surface as
INVALID_ARGUMENT (git_diff/git_log/git_show map non-zero exits,
including timeout kills) or INTERNAL_ERROR (git_status); a timeout on
repo detection reads as repo:false, and git_branches tolerates a
non-zero exit with an empty list.
What it will not do
No writes, edits, deletes, renames, or file creation. No shell. No git
mutations (commit, push, clean …). No network listener. No
repo-controlled configuration. Tasks execute only what the operator
pre-declared, and even those run as your user with real side effects,
so keep the allowlist tight and prefer read-only commands.
Security architecture
Ports-and-adapters, one implementation, one transport:
The full boundary analysis, the 30-finding adversarial ledger, the 33-case regression suite, and documented residual risks live in docs/THREAT-MODEL.md. Disclosure policy in SECURITY.md.
Configuration reference
~/.config/local-workspace-mcp/config.json; refused at load if the file or
its directory is group/world-writable, or the path is a symlink.
Common limits keys (key: default): maxReadFileBytes: 5 MB file-size
ceiling for reads, defaultReadBytes: 64 KiB per read,
maxSearchResults: 200, searchDeadlineMs: 20 s, gitTimeoutMs: 15 s,
maxTaskOutputBytes: 64 KiB, walkEntryCap/walkDepthCap: 50k entries /
20 deep. Full schema with bounds: src/config.ts in the repository. Callers can pass
timeoutMs to task_run to shorten a task's timeout; it can never
exceed the configured value or the 300 s hard cap.
Overrides: WORKSPACE_MCP_CONFIG (config file), WORKSPACE_MCP_CONFIG_DIR
(config dir + audit log location), workspace-mcp serve --config <path>
(CLI flag). init-config refuses to overwrite an existing config; delete
the file first if you intend a reset.
Troubleshooting
workspace-mcp doctor --json gives machine-readable output for CI or
wrapper scripts. Full reset: delete the config and
~/.config/local-workspace-mcp/audit.jsonl, re-run init-config. To
uninstall entirely, also remove the host entry (claude mcp remove, the
mcpServers/mcp_servers block, or the tunnel profile) and npm uninstall -g local-workspace-mcp (or npm unlink if linked from a clone).
Compatibility
- Node ≥ 20 (uses
node:utilparseArgs; tested on Node 22). - macOS/Linux. Windows is untested; path validation is POSIX-shaped.
- MCP SDK
@modelcontextprotocol/sdkv1.x line (spec ≤ 2025-11-25); v2 API surface is deliberately not adopted yet (see docs/RESEARCH.md). - ripgrep required for
fs_search_content; git required forgit_*.
Verifying the build
Contributing, security, license
- CONTRIBUTING.md: RED-first tests, fail-closed rules, no new capability surface without design review.
- SECURITY.md: vulnerability reporting and scope.
- CHANGELOG.md: release notes by finding/version.
- License: MIT.
Release status
v0.1.0 is published on npm as local-workspace-mcp and listed on the
official MCP Registry as io.github.peter4leadson/local-workspace-mcp.
The evaluated release surface (this README,
docs/THREAT-MODEL.md, the test suite, and the packaged tarball) is
described in docs/RELEASE-PACKET.md.
Maintained by Peter C. Bennett.
Source: README.md at commit 8ac23da
Tools
0Version history
1- v0.1.0LatestOct 10, 2026


