local-terminal-mcp
Run and read data from a local machine as an AI assistant: run allowlisted commands, read files, and inspect images/directories, with optional write and image-generation capabilities.
Run a single allowlisted shell command (e.g.
git,rg,cat,find) inside the project root and get its output; pipes, chaining, redirects and command substitution are rejected.Read files (UTF-8 text) and read images (png/jpg/gif/webp/bmp) — images are returned as actual images the model can see, auto-downscaled if large.
List directories inside the project root.
Open, list, and close terminals with persistent working directories (sessions).
Write files — UTF-8 text (
write_file) and binary/base64 (write_file_base64) — only if started with--allow-write.Move and copy files/directories inside the root (with
--allow-write).Generate images locally from a prompt using a configured local generator (
--image-gen-cmd).Import files/images from a staging inbox (
--inbox) into the repo, with optional auto-cleanup.Approve non-allowlisted commands dynamically (deny/once/always) via
--approvaltty or file mode.Connect over stdio (local, no network) or HTTP with auth (bearer or secret URL path) for ChatGPT, Claude Desktop, and Claude Code.
Enables ChatGPT to connect over public HTTPS and interact with a local project directory by listing directories, reading files, and running allowlisted commands (e.g., git, rg, cat) for code analysis and local generation tasks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@local-terminal-mcpsummarize the recent git changes in this project"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
local-terminal-mcp
A small, security-first MCP server that lets an AI assistant — ChatGPT, Claude Desktop, Claude Code, or any other MCP client — run a set of allowlisted commands and read files from a local directory on your machine.
The motivating use case: point ChatGPT (or Claude) at a local codebase so it can
run git, rg, cat, etc. and analyse the repo — without copying files around
by hand, and, for ChatGPT, using your flat-rate subscription instead of metered
API/agent tokens.
⚠️ This runs commands on your computer. It is built to be safe by default (read-only, allowlisted, no shell, path-contained, auth-required over the network), but you are responsible for how you configure and expose it. Read Security model before exposing it to the internet.
🚀 Just want to get going? Follow the install & setup checklist.
Table of contents
Related MCP server: ChampCity GPT MCP Launcher
Why this exists
Running a coding agent against a large repo through a metered API burns tokens fast. If you already pay for a ChatGPT or Claude subscription, you can instead give the assistant a tool to read files and run read-only commands on your machine, and let it analyse the code conversationally. That is what this server provides, with a security model strong enough that you can leave write access turned off and expose only read access.
What you can do with it
Work on your codebase from ChatGPT without spending Codex/API tokens. Let ChatGPT run
git,rg,cat,find, etc. to read, search and reason about a local repo — billed to your flat ChatGPT subscription instead of metered agent/API usage.Run local generators from ChatGPT — images, sprites, assets — unmetered. The server runs any program you allowlist. Allowlist your own generation CLI or script and ChatGPT can trigger it locally, as many times as you like, without using ChatGPT's built-in image quota. For example, to let ChatGPT drive a local sprite/image script:
local-terminal-mcp --transport http --port 3003 \ --root /path/to/assets-project \ --auth path --mcp-path "/mcp/$SECRET" \ --allow-commands "python,node,convert,aseprite" \ --allow-writeThen ask ChatGPT to call
run_commandwith e.g.python gen_sprite.py --seed 42 --out sprites/hero.png, andread_file/list_directoryto inspect the results. (--allow-writeis only needed if the generator writes into the root; keep it off for read-only analysis.)Save images ChatGPT makes with its own image skill into your repo. A small browser extension auto-saves every image ChatGPT generates straight into your repo (full quality, hands-free) — the one thing a plain connector can't do. See Saving ChatGPT's own generated images.
Let ChatGPT see images in your repo.
read_imagereturns a picture as an image (auto-downscaled if large), so ChatGPT can critique art, read text from a screenshot/resume, or compare assets.
You decide exactly which programs are reachable. The default allowlist is read-only; everything beyond it is opt-in.
How it works
MCP client (ChatGPT / Claude Desktop / Claude Code)
│
│ stdio ── local, no network ────────────┐
│ │
│ HTTP (Streamable) + bearer token │
▼ ▼
cloudflared / reverse proxy ──────────► local-terminal-mcp
(only needed for ChatGPT) │
▼
policy engine → git / rg / cat / ...
(allowlist, no shell, path containment)Local clients (Claude Desktop, Claude Code) talk to the server over stdio — the server is a child process, nothing is exposed to the network. This is the most secure mode and needs no tunnel.
ChatGPT can only reach servers over public HTTPS, so for ChatGPT you run the server in HTTP mode behind a tunnel (e.g.
cloudflared) and protect it with a bearer token (ideally plus Cloudflare Access).
Security model
The server never trusts the model's judgement. A deterministic policy layer
(src/local_terminal_mcp/policy.py) gates every request:
Allowlist, deny by default. Only commands whose program is on the allowlist run. Anything else is rejected. The default allowlist is read-only (
git,rg,grep,ls,cat,head,tail,wc,find,tree,stat,file,pwd,echo,diff).One command, no shell. Commands are parsed with
shlexand executed withshell=False. Pipes (|), chaining (&&,;), redirects (>), background (&) and command substitution ($(...), backticks) are all rejected — sogit status && rm -rf /never runs.Path containment. Every file path is fully resolved (symlinks and
..included) and must land inside the configured root directory.Writes are opt-in.
write_fileis disabled unless you start with--allow-write. The default is read-only.Fail closed on exposure. The server refuses to start in HTTP mode without an auth token of at least 16 characters.
Bounded output and time. Output is truncated to a byte limit and every command has a timeout.
Layers you add around it:
Put Cloudflare Access (or equivalent) in front of the tunnel. A bearer token is the app-level check; Access is the network-level wall. Obscure tunnel URLs are not security.
Run the server as a low-privilege user, ideally inside a container or VM, not as your main account.
Keep write access off unless you truly need it, and when you do, keep the root scoped to a single project directory.
Install
Requires Python 3.10+.
git clone https://github.com/luckysolanki/local-terminal-mcp.git
cd local-terminal-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e .Quick start (local / stdio)
Run it read-only against a project directory:
local-terminal-mcp --root /path/to/your/repoThat starts the server on stdio, waiting for an MCP client. See the sections below to connect a specific client.
Connect to Claude Desktop
Add the server to Claude Desktop's config file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"local-terminal": {
"command": "/path/to/local-terminal-mcp/.venv/bin/local-terminal-mcp",
"args": ["--root", "/path/to/your/repo"]
}
}
}Restart Claude Desktop. The tools appear under the connectors / tools menu.
Connect to Claude Code
claude mcp add local-terminal -- \
/path/to/local-terminal-mcp/.venv/bin/local-terminal-mcp --root /path/to/your/repoThen /mcp inside Claude Code will list the server.
Connect to ChatGPT (over the internet)
📄 For the exact, step-by-step tested procedure, see docs/CHATGPT_SETUP.md. The summary below covers the same flow.
ChatGPT can only reach servers over public HTTPS, so run in HTTP mode behind a tunnel. The flow is: (1) start the server, (2) expose it with a tunnel (ngrok or cloudflared), (3) add the connector in ChatGPT.
Why
pathauth for ChatGPT? ChatGPT's Create custom MCP server dialog only offers OAuth or No authentication — there is no field for a static bearer token. So instead of a header, we put an unguessable secret in the URL path (a capability URL) and select No authentication in ChatGPT. The MCP route exists only at that secret path; probes of the bare/mcpreturn 404. This is appropriate for personal use; note the secret appears in the URL (and thus in edge/proxy logs). For a shared or higher-value deployment, implement OAuth instead.
The server also validates the incoming Host header for DNS-rebinding
protection. Because tunnels forward an arbitrary public hostname, the server
accepts any host by default; pin it to your tunnel hostname with
--allowed-hosts for defense in depth.
Step 1 — start the server in HTTP mode (path auth)
SECRET="$(openssl rand -hex 16)"
echo "MCP URL path: /mcp/$SECRET"
local-terminal-mcp --transport http --host 127.0.0.1 --port 8000 \
--root /path/to/your/repo \
--auth path --mcp-path "/mcp/$SECRET"Step 2 — expose it with a tunnel
Install ngrok and add your authtoken (one-time, from the ngrok dashboard):
brew install ngrok # or: https://ngrok.com/download
ngrok config add-authtoken <YOUR_NGROK_AUTHTOKEN>Start the tunnel pointing at the local port:
ngrok http 8000ngrok prints a forwarding URL like https://a1b2-34-56.ngrok-free.app. Your
MCP endpoint is that URL + your secret path, e.g.
https://a1b2-34-56.ngrok-free.app/mcp/<SECRET>.
Notes for the free tier:
The URL is random and changes every restart — you'll re-paste it into ChatGPT each session. (A paid plan gives a stable
--domain.)ngrok shows a browser interstitial on the free tier for browser traffic; ChatGPT's MCP client sends API requests, so it is not affected.
Optionally lock the Host header to the tunnel domain:
local-terminal-mcp --transport http --port 8000 --root /path/to/repo \ --auth path --mcp-path "/mcp/$SECRET" \ --allowed-hosts a1b2-34-56.ngrok-free.app
With a named tunnel on your own domain (see
examples/cloudflared-config.yml):
tunnel: lucky-tunnel
credentials-file: /Users/you/.cloudflared/<tunnel-id>.json
ingress:
- hostname: mcp.yourdomain.com
service: http://localhost:8000
- service: http_status:404cloudflared tunnel run lucky-tunnelYour MCP endpoint is https://mcp.yourdomain.com/mcp/<SECRET>. (If you also
front it with Cloudflare Access, note ChatGPT cannot complete an Access login,
so use a reserved port/hostname routed directly to the server, as here, rather
than an Access-gated one.)
Step 3 — add the connector in ChatGPT
Requires a plan that has the custom MCP feature (Plus/Pro/Team/Enterprise/Edu):
Plugins → Add ▾ → Create custom MCP server.
Name: e.g.
Local Terminal.Server URL: your tunnel URL + secret path (e.g.
https://a1b2-34-56.ngrok-free.app/mcp/<SECRET>).Authentication: No authentication (the secret path is the credential).
Tick I understand and want to continue, then Create as a plugin and Connect.
ChatGPT discovers the tools automatically. A GET /healthz endpoint (no auth)
is available for tunnel/uptime checks. To use the tools in a chat, ask ChatGPT
to use the connector by name.
Configuration reference
Every option has a CLI flag and an LTMCP_-prefixed environment variable. CLI
flags win over environment variables.
CLI flag | Env var | Default | Meaning |
|
| cwd | Directory the server is confined to |
|
|
|
|
|
|
| HTTP bind host |
|
|
| HTTP bind port |
|
|
| HTTP auth mode: |
|
| — | Bearer token (required for |
|
|
| Path the MCP endpoint is served at; for |
|
| any | Comma-separated |
|
| read-only set | Comma-separated allowlist patterns (e.g. |
|
| on | Stop confining command path-args to the root |
|
|
| Ask before running non-allowlisted commands: |
|
| — | Directory to coordinate approvals in |
|
|
| Seconds to wait for a decision |
|
| — | JSON file that "always allow" appends to |
|
| — | Local image generator template ( |
|
|
| Image generation timeout (seconds) |
|
| — | Staging folder (ideally a repo subdir like |
|
|
| Auto-delete inbox files older than N days ( |
|
|
| Enable |
|
|
| Output truncation limit |
|
|
| Per-command timeout (seconds) |
Tools exposed
Tool | Available when | Description |
| always | Run one allowlisted command (no shell). |
| always | Read a text file inside the root. Pass |
| always | Read an image (png/jpg/gif/webp/bmp) and return it as an image the model can see, plus a text line (path, size, dimensions) so the turn stays useful if the client drops the image. Large images are auto-downscaled to fit. |
| always | List a directory inside the root. |
| always | List image files (and subfolders) in a directory, image-filtered, with sizes — for finding images to view or share. |
| http + | Return a shareable https link to a repo image that opens on any device over the tunnel — so the model can hand you a link with no gallery navigation. |
| always | Open a session with its own persistent working directory. |
| always | List open sessions and their directories. |
| always | Close a session. |
|
| Apply a V4A patch (create/update/rename/delete across multiple files) atomically, anchored by context rather than line numbers — the right tool for edits to existing files instead of rewriting them. |
|
| Write a UTF-8 text file inside the root. |
|
| Write a binary file (e.g. a PNG) from base64 — for saving generated images and other assets. |
|
| Move/rename a file or directory within the root. |
|
| Copy a file or directory within the root. |
|
| Generate an image from a prompt using a local generator, saved into the root. |
|
| List recent files (images) in a trusted inbox folder (e.g. Downloads). |
|
| Import the most recent image(s) from the inbox into the repo. |
|
| Import a named file from the inbox into the repo. |
Images
write_file only carries UTF-8 text, so binary assets (PNGs) use
write_file_base64 (plain base64 or a data: URL). read_image returns the
file as an MCP image block, so the assistant actually sees it rather than
getting base64 text. To generate images locally (free, unlimited, on your
machine), point --image-gen-cmd at a local generator with {prompt} and
{output} placeholders — e.g. sd -p {prompt} -o {output} --steps 8 — and the
generate_image tool runs it server-side (no shell), writing straight into the
repo. A ready-to-use example generator is included at
examples/pixel_gen.py (procedural pixel-art sprites,
no model download — --image-gen-cmd "python examples/pixel_gen.py {prompt} {output}");
swap in mflux / stable-diffusion.cpp / the OpenAI image API for photoreal output.
Note: ChatGPT caps each tool call at ~45s, so a slow generator can time out there
(local MCP clients have no such limit).
Note on ChatGPT's own image skill. The above generates images via a local tool. ChatGPT's built-in "Create image" produces pictures that live only in the browser and can't be handed to a connector — to save those, see Saving ChatGPT's own generated images.
Browse & download repo images from any device (HTTP mode)
When you run over a tunnel, three secret-gated GET routes under your MCP path
turn the server into a tiny cross-device image store — the same Mac, tunnel,
and path containment, no second service:
Route | Does |
| A minimal browsable gallery (open it on your phone). |
| JSON listing of a folder's subfolders + images. |
| Fetch one image inline, or |
They're gated by the same unguessable path segment as /upload, and every
path is confined to the root (a .. escape returns 403); only image files
are listed or served. Pair them with the existing POST …/<secret>/upload
(raw bytes — from the browser extension, curl, or an iOS Shortcut) to push
and pull images across devices. Read-only, so they work with or without
--allow-write. (As with everything here, it's reachable only while the Mac is
awake and the server + tunnel are up.)
Editing files (apply_patch)
For changes to existing files, prefer apply_patch over rewriting the whole
file with write_file: the model reads a file, then sends a V4A patch that
locates each change by surrounding context (not line numbers, which models
miscount), so edits land reliably even in long, mature files. One call can
create, update, rename, and delete several files, and it's atomic — if any
hunk doesn't match, nothing is written and the result says which file and why,
so the model can re-read and retry. Paths are confined to the root and binaries
are refused (those stay with write_file_base64), exactly like every other
write tool.
*** Begin Patch
*** Update File: src/app.py
@@ def handler():
request = parse(raw)
- return request
+ validate(request)
+ return request
*** Add File: src/validate.py
+def validate(req):
+ ...
*** Delete File: src/old.py
*** End PatchRename by putting *** Move to: <new/path> directly under an *** Update File:
header. The V4A matcher is vendored from the
OpenAI Agents SDK (MIT; see
THIRD_PARTY_NOTICES.md) — the same format GPT-class
models are trained to emit — while all path resolution and write-gating stay in
this project's policy layer.
Sessions (persistent working directory)
You don't need to prefix every call with cd …. Run cd <dir> once and it
persists for later calls — there's a shared default session, and you can
open_terminal for additional named sessions (pass the returned id as
session). This is not a real shell: commands still run one-at-a-time with
shell=False and the allowlist, so cd src && rm -rf . is still rejected —
you just cd src once, then run commands.
Allowlist patterns
--allow-commands entries are glob patterns over the command's argv:
gitorgit *—gitwith any arguments.git log *— onlygit log ….rg *,cat *, etc.
A program-position wildcard (*) and absolute-path programs (/bin/rm) are
rejected. With path containment on (default), arguments that are absolute,
~, or use .. are refused — so cat /etc/passwd stays blocked even under
cat *. Disable with --no-contain-path-args.
Saving ChatGPT's own generated images
ChatGPT's native image skill renders images only in the browser — the model and the MCP server never receive the bytes (no URL either), so they can't be saved through a normal connector tool. There are two working routes:
Route 1 — browser extension (recommended; full quality, hands-free). The
browser extension reads the rendered image from the page
and POSTs it to the server's /upload endpoint (secret-gated, write-mode,
path-contained, CORS-restricted to ChatGPT), writing it straight into your repo
(e.g. a staging folder like incoming/). Loading it is a one-time step;
after that, turn on auto-save and every new image ChatGPT generates is
uploaded automatically as it appears — no clicks, and no MCP→browser channel is
needed (the extension watches the page itself; the server just receives the
uploads). Setup: browser-extension/README.md.
Why an extension at all? The model can't pass image bytes to a connector tool (it won't, even for a tiny payload), the code-interpreter sandbox has no network, and the page's CSP blocks direct posts to localhost — so the browser is the only place that can both see the generated image and reach your local server. An extension is the clean form of that bridge.
Route 2 — download + import (no install). Use ChatGPT's own download button (saves the full-res image to your Downloads folder), then import it with the inbox tools. Run with a staging inbox and a TTL so it self-cleans:
local-terminal-mcp --transport http --port 3003 --root /path/to/repo \
--auth path --mcp-path "/mcp/$SECRET" --allow-write \
--inbox /path/to/repo/incoming --inbox-ttl-days 7Then, after downloading images into that folder (or configuring the extension to upload there):
"Local Terminal: import the 3 most recent images from my inbox into assets/."
list_inbox lists the staging folder, import_recent_images / import_file
copy or move them into a permanent folder, and files left in the inbox longer
than --inbox-ttl-days are auto-deleted. The inbox is a source only — writes
still land only inside the root.
Reading images back.
read_imagereturns an image file as an MCP image block, so the assistant can actually see it — describe art, read text from a screenshot/resume, etc. Large images are auto-downscaled to fit the transport limit (Pillow required —pip install -e ".[images]"); the file on disk is unchanged.
Approvals & the dynamic allowlist (Claude Code–style)
By default, a command whose program isn't on the static allowlist is simply
refused. Enable --approval to be asked instead — the same model as Claude
Code:
deny → the command is refused.
once → it runs this time only; nothing is saved.
always → it runs and the program is appended to a JSON allowlist (
--allowlist-file), so it's permitted without asking next time.
The decision is always made locally, on the machine the server runs on — a remote caller (ChatGPT) can only request a command.
tty mode (server running in a terminal): you're prompted right there.
local-terminal-mcp --root /path/to/repo \
--approval tty --allowlist-file ~/.ltmcp-allow.jsonfile mode (server backgrounded, e.g. behind a tunnel): the request is
queued and you decide with the approve/deny CLI.
# server
local-terminal-mcp --transport http --port 3003 --root /path/to/repo \
--auth path --mcp-path "/mcp/$SECRET" \
--approval file --approvals-dir ~/.ltmcp/approvals \
--allowlist-file ~/.ltmcp/allow.json
# in another terminal, when ChatGPT tries something new:
local-terminal-mcp approvals --approvals-dir ~/.ltmcp/approvals # list pending
local-terminal-mcp approve <id> --always --approvals-dir ~/.ltmcp/approvals
local-terminal-mcp deny <id> --approvals-dir ~/.ltmcp/approvals⚠️ Approving a program like
python3grants it broad power (it's an interpreter). Approve deliberately — "always allow" is persistent.
Development
pip install -e ".[dev]"
pytest # run the test suite
ruff check . # lintThe security-critical logic lives in policy.py and is covered by
tests/test_policy.py. If you change the policy, add a test for it.
FAQ
Can it run any command? No — only programs on the allowlist, one at a time,
with no shell. Expand the static allowlist with --allow-commands, or turn on
--approval to be asked (once / always) when a new program is requested, Claude
Code–style — see Approvals.
How is the HTTP endpoint protected? With a credential, never obscurity of
the tunnel hostname alone. Use bearer auth (header token) for clients that
support custom headers, or path auth (a long random secret in the URL, a
capability URL) for ChatGPT, whose UI only offers OAuth or no-auth. For a
shared or higher-value deployment, implement OAuth and/or front it with
Cloudflare Access.
Why no pipes or &&? Because allowing shell composition is the easiest way
to smuggle a dangerous command past an allowlist. Run multiple tool calls
instead.
License
MIT — see LICENSE.
Available Tools
3 toolslist_directoryB
List the entries of a directory inside the project root.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | . |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden, and it does disclose one meaningful constraint: paths are confined to the project root, implying a sandbox. However, it says nothing about recursion, symlink handling, error behavior for missing directories, or whether the listing is shallow, leaving notable gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with no filler. Every word earns its place and the scoping constraint appears immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the tool is simple with one optional parameter. Still, the description omits when-to-use guidance and any meaning for the 'path' argument, which are the remaining pieces an agent needs for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the single parameter 'path' (with default '.') is never mentioned in the description. The description does not clarify whether the path is relative to the project root, whether an absolute path is accepted, or how the default is applied, so it fails to compensate for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the entries of a directory') with a scope qualifier ('inside the project root'). It is clear what the tool does, but it does not distinguish itself from siblings like read_file or run_command, so it falls short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus read_file or run_command, nor any prerequisites or exclusions. Usage can only be inferred from the name and the phrase 'inside the project root'; the description offers no routing help.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_fileA
Read a UTF-8 text file located inside the project root.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses two real constraints (UTF-8 only, project-root sandbox), but says nothing about error behavior for missing/binary files, file size or truncation limits, or whether the entire file is returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence, front-loaded with the action and immediately qualified by the two constraints. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool with an output schema (so return values need no explanation) and no annotations, the description is nearly sufficient. Only minor gaps remain around failure modes and size limits.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single 'path' parameter has 0% schema description coverage, so the description must compensate. It partially does by stating the path refers to a UTF-8 text file inside the project root, but gives no format details (relative vs absolute, extension handling).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource ('Read a UTF-8 text file') with two meaningful scope qualifiers: encoding (UTF-8) and location (inside the project root). This clearly distinguishes it from siblings like run_command, though it never names or contrasts them explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit when-to-use or when-not-to-use guidance, but the 'inside the project root' and 'UTF-8' constraints implicitly bound applicability (no binary files, no paths outside the project). Usage is implied rather than stated, so this lands at the minimum-viable level.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_commandA
Run a single allowlisted command inside the project root and return its output. Exactly one program per call: pipes, chaining (&&, ;), redirects (>) and command substitution are not allowed. Allowed programs: cat, diff, echo, file, find, git, grep, head, ls, pwd, rg, stat, tail, tree, wc. Optionally set 'cwd' to a subdirectory of the root.
| Name | Required | Description | Default |
|---|---|---|---|
| cwd | No | ||
| command | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does disclose important constraints: exactly one program per call, no pipes/chaining/redirects/substitution, confinement to the project root, and an explicit allowlist. It does not state whether any allowed commands can mutate files or repository state, nor does it mention timeouts, output limits, or authentication requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and then immediately lists the hard restrictions and allowlist. The longer allowlist is necessary and earns its place; there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a command-execution tool with two parameters, no annotations, and an output schema, the description covers the essential invocation constraints well. The main remaining gap is the absence of a clear safety profile, such as whether commands are read-only or can modify project files.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for both parameters, and it does: it explains that 'command' must be a single allowlisted program with no shell metacharacters, and that 'cwd' optionally points to a subdirectory of the project root. It would be stronger with explicit mention of the default root behavior or example command syntax.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Run') and resource ('a single allowlisted command inside the project root'), and clearly states that it returns output. It is plainly distinct from the read-only file and directory siblings, even without naming them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear constraints for what kinds of commands are acceptable, including the allowlist and prohibition on shell chaining, but it never states when an agent should choose run_command over read_file or list_directory. Usage is therefore implied rather than explicitly guided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
list_directory - First observed
read_file - First observed
run_command
TDQS
Scored across 3 tools
The three tools have mostly clear roles, but run_command's allowlist includes cat, ls, find, and grep, which functionally overlaps with read_file and list_directory. An agent could read a file or list a directory via either path, though the dedicated tools are clearly documented as the simpler option.
All three names follow a clean verb_noun snake_case pattern (run_command, read_file, list_directory) with no deviations or mixed conventions.
Three tools is on the lean side, but the server is deliberately a narrow, sandboxed read-only surface, so each tool earns its place and nothing is redundant padding.
The read surface is coherent, but there is no write, create, edit, or delete capability anywhere in the set, and redirects/chaining are blocked in run_command, so agents hit a hard dead end for any mutation task. This may be intentional sandboxing but leaves the lifecycle incomplete.
Maintenance
Related MCP Connectors
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceLocal MCP server bridging ChatGPT Web to local tools for file, shell, git, test, and process management with secure policy controls.MIT
- FlicenseBqualityCmaintenanceChatGPT-compatible MCP server and Electron launcher for controlled local project-file access, providing read-only and approval-gated write tools over STDIO or HTTP with optional OAuth and HTTPS tunneling.19-
- FlicenseNot gradedqualityDmaintenanceZero-dependency MCP server that provides AI models with secure read/write/exec access to local files and directories over HTTP and SSE, designed to be tunneled via ngrok for integration with Claude Web.-
- AlicenseNot gradedqualityAmaintenanceThis self-hosted MCP server connects an MCP client such as ChatGPT to approved local project folders, enabling file reading and editing, code search, shell command execution, and Git worktree workflows without uploading code to a third party. It also supports mounting external stdio or Streamable HTTP MCP servers as dynamic capabilities.310 npmMIT