Skip to main content
Glama

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-write

    Then ask ChatGPT to call run_command with e.g. python gen_sprite.py --seed 42 --out sprites/hero.png, and read_file / list_directory to inspect the results. (--allow-write is 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_image returns 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:

  1. 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).

  2. One command, no shell. Commands are parsed with shlex and executed with shell=False. Pipes (|), chaining (&&, ;), redirects (>), background (&) and command substitution ($(...), backticks) are all rejected — so git status && rm -rf / never runs.

  3. Path containment. Every file path is fully resolved (symlinks and .. included) and must land inside the configured root directory.

  4. Writes are opt-in. write_file is disabled unless you start with --allow-write. The default is read-only.

  5. Fail closed on exposure. The server refuses to start in HTTP mode without an auth token of at least 16 characters.

  6. 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/repo

That 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.json

  • Windows: %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/repo

Then /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 path auth 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 /mcp return 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 8000

ngrok 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:404
cloudflared tunnel run lucky-tunnel

Your 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

--root

LTMCP_ROOT

cwd

Directory the server is confined to

--transport

LTMCP_TRANSPORT

stdio

stdio or http

--host

LTMCP_HOST

127.0.0.1

HTTP bind host

--port

LTMCP_PORT

8000

HTTP bind port

--auth

LTMCP_AUTH_MODE

bearer

HTTP auth mode: bearer (header) or path (secret in URL)

--auth-token

LTMCP_AUTH_TOKEN

—

Bearer token (required for bearer mode)

--mcp-path

LTMCP_MCP_PATH

/mcp

Path the MCP endpoint is served at; for path auth, end it with a long random segment

--allowed-hosts

LTMCP_ALLOWED_HOSTS

any

Comma-separated Host header allowlist (e.g. your tunnel hostname)

--allow-commands

LTMCP_ALLOW_COMMANDS

read-only set

Comma-separated allowlist patterns (e.g. git *,rg *)

--no-contain-path-args

LTMCP_CONTAIN_PATH_ARGS=false

on

Stop confining command path-args to the root

--approval

LTMCP_APPROVAL_MODE

none

Ask before running non-allowlisted commands: none/tty/file

--approvals-dir

LTMCP_APPROVALS_DIR

—

Directory to coordinate approvals in file mode

--approval-timeout

LTMCP_APPROVAL_TIMEOUT

60

Seconds to wait for a decision

--allowlist-file

LTMCP_ALLOWLIST_FILE

—

JSON file that "always allow" appends to

--image-gen-cmd

LTMCP_IMAGE_GEN_CMD

—

Local image generator template ({prompt}/{output}); enables generate_image

--image-timeout

LTMCP_IMAGE_TIMEOUT

300

Image generation timeout (seconds)

--inbox

LTMCP_INBOX

—

Staging folder (ideally a repo subdir like incoming) to import files FROM; enables the import tools

--inbox-ttl-days

LTMCP_INBOX_TTL_DAYS

0

Auto-delete inbox files older than N days (0 = never); use only with a dedicated staging folder

--allow-write

LTMCP_ALLOW_WRITE

false

Enable apply_patch, write_file, write_file_base64, move_file, copy_file

--max-output-bytes

LTMCP_MAX_OUTPUT_BYTES

100000

Output truncation limit

--timeout

LTMCP_TIMEOUT

120

Per-command timeout (seconds)

Tools exposed

Tool

Available when

Description

run_command

always

Run one allowlisted command (no shell). cd persists per session; takes an optional session.

read_file

always

Read a text file inside the root. Pass offset (1-based line) and limit (line count) to page through a long file instead of reading it whole.

read_image

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.

list_directory

always

List a directory inside the root.

list_images

always

List image files (and subfolders) in a directory, image-filtered, with sizes — for finding images to view or share.

image_url

http + --allowed-hosts

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.

open_terminal

always

Open a session with its own persistent working directory.

list_terminals

always

List open sessions and their directories.

close_terminal

always

Close a session.

apply_patch

--allow-write

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_file

--allow-write

Write a UTF-8 text file inside the root.

write_file_base64

--allow-write

Write a binary file (e.g. a PNG) from base64 — for saving generated images and other assets.

move_file

--allow-write

Move/rename a file or directory within the root.

copy_file

--allow-write

Copy a file or directory within the root.

generate_image

--image-gen-cmd

Generate an image from a prompt using a local generator, saved into the root.

list_inbox

--inbox

List recent files (images) in a trusted inbox folder (e.g. Downloads).

import_recent_images

--inbox + --allow-write

Import the most recent image(s) from the inbox into the repo.

import_file

--inbox + --allow-write

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

…/<secret>/gallery[?path=<dir>]

A minimal browsable gallery (open it on your phone).

…/<secret>/images[?path=<dir>]

JSON listing of a folder's subfolders + images.

…/<secret>/download?path=<rel>[&dl=1]

Fetch one image inline, or &dl=1 to save it.

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 Patch

Rename 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:

  • git or git * — git with any arguments.

  • git log * — only git 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 7

Then, 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_image returns 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.json

file 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 python3 grants 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 .      # lint

The 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 tools
list_directoryB

List the entries of a directory inside the project root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNo.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
cwdNo
commandYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

  1. 3 tool updatesv0.1.0
    • First observedlist_directory
    • First observedread_file
    • First observedrun_command

TDQS

A3.6/5.0

Scored across 3 tools

Disambiguation4/5

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.

Naming Consistency5/5

All three names follow a clean verb_noun snake_case pattern (run_command, read_file, list_directory) with no deviations or mixed conventions.

Tool Count4/5

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.

Completeness3/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Zero-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.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    This 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 npm
    MIT