Skip to main content
Glama

safe-dev-mcp

A minimal MCP server that gives an editor LLM a fixed set of abilities: lint, test, git status, git diff, git log, git show.

Requirements

Python 3.11 or newer (the config parser is the standard library tomllib).

On macOS the built-in python3 is 3.9, which is too old for both this server and the mcp package. If pip install mcp reports "No matching distribution found", that is the cause. Install a current Python with Homebrew:

brew install python@3.12

Related MCP server: MCP ToolHub

One-time setup

Create a virtualenv inside this clone from the newer interpreter and install the MCP SDK:

cd /path/to/safe-dev-mcp
/opt/homebrew/bin/python3.12 -m venv .venv
.venv/bin/pip install mcp

Register the server once at Claude Code user scope. The Claude agent in Zed is Claude Code running over the Agent Client Protocol, so it reads this config:

claude mcp add --scope user safe-dev-tools -- \
  /path/to/safe-dev-mcp/.venv/bin/python \
  /path/to/safe-dev-mcp/server.py

If you prefer to configure it in Zed itself, add the same command to ~/.config/zed/settings.json. Zed forwards these to external agents:

{
  "context_servers": {
    "safe-dev-tools": {
      "command": "/path/to/safe-dev-mcp/.venv/bin/python",
      "args": ["/path/to/safe-dev-mcp/server.py"]
    }
  }
}

Then, in the agent's permissions, allow this server's tools and leave general terminal access denied.

Per-repo configuration

In each repo where you want lint or test tools:

cp /path/to/safe-dev-mcp/safe-dev.example.toml .safe-dev.toml
echo .safe-dev.toml >> .git/info/exclude

Edit .safe-dev.toml for that repo. Commands are grouped into targets. A target is a part of the repo with its own lint and test commands. Every key is optional, and a tool is only offered when at least one target defines it.

Single-surface repos

Top-level lint and test define one target that owns every path:

lint = ["ruff", "check", "."]
test = ["pytest", "-q"]

Monorepos

Use one [[targets]] table per surface instead:

[[targets]]
name = "frontend"
path_prefix = "apps/frontend"
cwd = "apps/frontend"
lint = ["yarn", "lint"]
test = ["yarn", "test"]

[[targets]]
name = "backend"
path_prefix = "apps/backend"
cwd = "apps/backend"
test = ["yarn", "test"]

[[targets]]
name = "e2e"
path_prefix = "e2e"
test = ["yarn", "test:e2e"]
timeout_seconds = 600

Key

Where

Effect

name

target

Label used in results. Required

path_prefix

target

Repo-relative directory this target owns. Omit to own every path

cwd

target

Repo-relative directory the commands run from. Default is the repo root

lint

top level or target

Command for run_lint

test

top level or target

Command for run_tests. A scoped path is appended to it

timeout_seconds

top level or target

Per-command timeout. Default 60. Target value overrides top level

max_output_chars

top level or target

Output truncation limit. Default 20000. Target value overrides top level

Top-level keys must appear before the first [[targets]] table, which is a TOML rule.

How the tools behave with targets:

  • run_tests(path) runs only the target whose path_prefix owns that path. When several match, the longest prefix wins. If none match, the tool returns an error naming the configured targets rather than guessing.

  • The scoped path is rewritten relative to the target's cwd before it is appended, and dropped entirely when it is the cwd itself. This matters for workspace scripts: yarn workspace backend test runs Vitest inside apps/backend, so a repo-relative path would be doubled. Set cwd to the workspace and call the script directly, as in the example above.

  • run_tests() with no path runs every target that has a test command and returns a summary line followed by a section per target.

  • run_lint() runs every target that has a lint command, skips the rest, and reports per target the same way.

  • The tool descriptions shown to the model list the target names and prefixes, so it knows which paths route where.

Repos that only need the read-only git tools need no config file at all.

Git worktrees

Because .safe-dev.toml is excluded from git, a linked worktree created with git worktree add does not get a copy. When the server starts in a worktree that has no config of its own, it follows the worktree's .git pointer back to the main worktree and reads the config there. path_prefix and cwd still resolve against the worktree you are in, so commands run on that worktree's files. Put a .safe-dev.toml inside a worktree to override the shared one for that worktree only.

The tool list is fixed when the server starts, so restart the agent thread after editing the config.

How the server finds the repo

First match wins:

  1. SAFE_DEV_REPO_ROOT environment variable, for manual testing.

  2. CLAUDE_PROJECT_DIR, which Claude Code sets for every MCP server it launches. User-scope servers run with ~/.claude as their working directory, so this is what makes a global registration work per repo.

  3. Walk up from the current working directory until a directory containing .git or .safe-dev.toml is found.

  4. The current working directory.

Testing it standalone

The MCP Inspector lets you see the tool list and call tools by hand. Launch it from inside the repo you want to test so the server resolves that repo as its root:

cd /path/to/some/repo
npx @modelcontextprotocol/inspector \
  /path/to/safe-dev-mcp/.venv/bin/python \
  /path/to/safe-dev-mcp/server.py

The SDK's mcp dev shortcut is not used here because it always launches the server through uv, which fails if uv is not installed.

Troubleshooting

On startup the server writes two lines to stderr: the repo root it resolved and the tools it registered. Run it directly from inside a repo to see them, then press Ctrl-C:

cd /path/to/some/repo
/path/to/safe-dev-mcp/.venv/bin/python /path/to/safe-dev-mcp/server.py

A malformed lint or test value (for example a plain string instead of a list) is reported on stderr and that tool is left out. The server still starts.

Extending it

To add another safe capability (git blame, a formatter check, a type checker), add a plain function following the same pattern and register it in _register_tools, conditionally if it depends on config. Fixed command list, validated and bounded parameters, no shell string concatenation. Resist the temptation to add a generic "run this git subcommand" tool. Enumerate the ones you actually want.

Available Tools

4 tools
git_diffA

Show unstaged changes (read-only), optionally scoped to one file.

Inputs: path. Returns: result

git_logA

Show recent commit history (read-only). max_count is capped at 50.

Inputs: max_count. Returns: result

git_showA

Show a specific commit's diff by hash/ref (read-only).

Inputs: ref (required). Returns: result

git_statusA

Show the working tree status (read-only).

Inputs: no named parameters. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool targets a distinct git inspection operation: working tree status (git_status), unstaged content changes (git_diff), commit history (git_log), and a single commit's changes (git_show). While git_diff and git_show both display diffs, their inputs and purposes are clearly different. No ambiguity exists within this small set.

    Naming Consistency5/5

    All tool names follow the exact pattern of git_<command>, mirroring native git subcommands (status, diff, log, show). The naming is uniform and predictable, making it trivial for an agent to infer usage.

    Tool Count5/5

    Four tools is a focused, well-scoped set for the stated purpose of safe, read-only git inspection. Each tool earns its place without redundancy or bloat, and the count falls comfortably within the ideal range.

    Completeness4/5

    The set covers the core read-only git workflows: checking status, reviewing working-tree changes, browsing history, and inspecting specific commits. Minor gaps include no direct way to view staged changes (git diff --cached) or list branches, but these can be worked around and do not cripple the domain.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      A
      quality
      B
      maintenance
      Enables coding agents to perform workspace-confined file operations, read-only Git inspection, and structured shell commands, while requiring out-of-band human approval for mutations and external executions and maintaining an audit trail.
      14
      3
      -
    • A
      license
      A
      quality
      B
      maintenance
      Enables coding agents to safely inspect and modify project files within explicitly allowed workspace roots, with controls for file operations, git status/diff, and optionally allowlisted command execution.
      11
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables MCP-compatible agents to execute bash and subprocess commands through a zero-dependency sandbox guard that blocks destructive system mutations, path traversals, reverse shells, and other unsafe execution patterns.
      7
      MIT