Skip to main content
Glama

CodeServer MCP

A production-oriented Model Context Protocol server for code-server, built around stateful development sessions rather than stateless file operations.

Architecture

Core Modules

workspace/ — Sandboxed file operations scoped to WORKSPACE_ROOT

  • read.py — Read files with optional line-range slicing

  • write.py — Atomic file writes (create/overwrite)

  • patch.py — Targeted edits via unified diff or find/replace (never resend the whole file)

  • search.py — Ripgrep-based search with structured results

  • tree.py — Recursive directory listing (respects .gitignore-style ignore rules)

  • watch.py — Async file watching; detect changes made by the editor, git, build tools

terminal/ — Persistent shells & background processes

  • pty.py — Real pseudo-terminal wrapper (ptyprocess) for authentic terminal behavior (colors, pagers, history)

  • shell.py — Persistent shell sessions that survive crashes/restarts

  • process.py — Background process manager with log capture

util/

  • paths.py — Workspace sandboxing: every file operation goes through resolve_path() which proves the result still lives inside WORKSPACE_ROOT

  • diff.py — Unified diff helpers for generating and applying patches

Key Design Decisions

  1. Real PTYs, not subprocesses

    • Shells are real pseudo-terminals (ptyprocess), so programs that check isatty() behave naturally.

    • Output includes ANSI colors, spinner sequences, and pager control codes.

    • Shell history and readline state persist across calls.

  2. Stateful vs. Stateless

    • Unlike generic filesystem MCP servers, shells and processes are first-class, long-lived entities.

    • A shell can run npm run dev, and the dev server keeps running. Later calls can read its output, resize its terminal, or send it signals.

    • Session recovery on restart: PTY metadata is stored in SQLite so crashed dev servers can be reconnected.

  3. Sandboxing

    • Every workspace operation (read/write/patch/search/tree/watch) goes through util.paths.resolve_path().

    • All paths are canonicalized and checked to be within WORKSPACE_ROOT — symlinks cannot escape.

  4. Patching, not Overwriting

    • replace_text() and apply_patch() let Claude make surgical edits without resending entire files.

    • Diffs are generated and returned so clients (Claude) can see what changed.

  5. Async-first

    • All I/O is async (asyncio, aiofiles, aiosqlite).

    • Long-lived watches and process log tailing don't block.

Related MCP server: Workspace MCP

Installation

docker-compose up -d
# MCP server now listens on localhost:8080

Local (Python 3.12+)

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install -r requirements.txt
export WORKSPACE_ROOT=/path/to/code
export MCP_PORT=8080
python app.py

Environment Variables

  • MCP_HOST (default: 0.0.0.0) — Listen address

  • MCP_PORT (default: 8080) — Listen port

  • WORKSPACE_ROOT (default: /workspace) — Sandbox root; all file operations must stay within this

  • RG_BIN (default: rg) — Path to ripgrep binary if not on PATH

  • LOG_LEVEL (default: info) — Uvicorn log level

MCP Tools

Workspace

  • workspace_read_file(path, start_line, end_line) — Read a file (with optional line range)

  • workspace_write_file(path, content, mode, create_dirs) — Write or create a file

  • workspace_replace_text(path, old, new, expected_count) — Find and replace (safe, requires uniqueness)

  • workspace_apply_patch(path, diff_text) — Apply a unified diff

  • workspace_search(pattern, path, glob, case_sensitive, fixed_string, max_results) — Ripgrep-based search

  • workspace_tree(path, max_depth, max_entries) — Directory listing

  • workspace_watch_start(path) — Start watching a directory for changes

  • workspace_watch_poll(watch_id, timeout) — Poll a watch for events

  • workspace_watch_stop(watch_id) — Stop a watch

  • workspace_watch_list() — List active watches

Terminal

  • terminal_shell_create(cwd) — Create a new persistent PTY shell

  • terminal_shell_execute(shell_id, command, timeout) — Run a command in a shell

  • terminal_shell_read(shell_id) — Read pending output (non-blocking)

  • terminal_shell_resize(shell_id, rows, cols) — Resize the terminal

  • terminal_shell_list() — List active shells

  • terminal_shell_terminate(shell_id) — Kill a shell

  • terminal_process_start(command, cwd, proc_id) — Start a background process

  • terminal_process_logs(proc_id, lines) — Read process logs

  • terminal_process_stop(proc_id, timeout) — Stop a process

  • terminal_process_list() — List active processes

Usage Examples

Create a Persistent Dev Server Shell

# Create a shell
shell_resp = await terminal_shell_create(cwd=".")
shell_id = shell_resp["id"]  # "shell-abc123"

# Start a dev server (runs in background)
await terminal_shell_execute(shell_id, "npm run dev")

# Read output later
output = await terminal_shell_read(shell_id)
print(output["output"])  # "VITE ready in 314ms..."

# Even if the MCP server crashes, the shell survives.
# On restart, terminal_shell_list() will still see it.

Edit a File Without Resending It

# Read a file
file_resp = workspace_read_file("src/app.py")
before = file_resp["content"]

# Client (or Claude) modifies it locally
after = before.replace("const x = 1", "const x = 2")

# Send only the diff
diff = generate_unified_diff(before, after, "src/app.py")
patch_resp = await workspace_apply_patch("src/app.py", diff)
print(patch_resp["diff"])  # Shows what changed

Watch for Changes

# Start watching the src/ directory
watch_resp = await workspace_watch_start("src")
watch_id = watch_resp["id"]

# Do work (edit files, run git pull, etc.)
await asyncio.sleep(5)

# Poll for changes
poll_resp = await workspace_watch_poll(watch_id, timeout=1)
for event in poll_resp["events"]:
    print(event["change"], event["path"])  # "modified src/main.py"

Reconnection & Session Recovery

When the MCP server restarts:

  1. Shells: Their PTY metadata is loaded from the database and re-spawned. Background processes (like npm run dev) will still be running on the system; reconnecting to the shell picks up where you left off.

  2. Processes: Background processes are re-attached to if they're still alive (by PID lookup).

  3. Watches: Not persisted (ephemeral); will need to be recreated.

This design assumes you're running this in a long-lived container (Docker or systemd) and not losing the PID space.

Security

  • Workspace sandboxing: resolve_path() ensures all operations stay within WORKSPACE_ROOT. Symlinks are resolved and validated.

  • No command injection: Process commands are passed as strings to subprocess.Popen(..., shell=True), so be careful with user input. Consider restricting this tool in production.

  • No authentication: This server is designed for a trusted network (your local machine, or behind a VPN/Tailscale). Run it behind a reverse proxy with auth in production.

Performance

  • First-call startup: ~50ms (database init, shell spawn)

  • Shell execute: 5–100ms depending on command

  • File operations: <5ms (mostly I/O latency, not CPU)

  • Search: 50–500ms depending on repo size and pattern complexity

  • Watch poll: 0ms if no changes, else <50ms to report changes

Testing

Run the workspace module smoke test:

export WORKSPACE_ROOT=/tmp/fake_workspace
python test_workspace_manual.py

This tests sandboxing, file I/O, patching, searching, tree walking, and watching.

TODO / Future

  • LSP diagnostics integration (pull errors from code-server's language servers)

  • VS Code Tasks runner

  • Port detection (surface forwarded ports from code-server)

  • Editor state (which files are open, cursor position)

  • Git wrappers (optional; can be used via shells)

  • Docker wrappers (optional; can be used via shells)

  • More comprehensive logging and telemetry

  • Pytest suite (currently only manual smoke tests)

Contributing

This is a single-developer project. If you'd like to extend it:

  1. Add new tools in the appropriate module (workspace/, terminal/, or a new one).

  2. Register them in app.py with @mcp.tool().

  3. Update this README.


Built for Claude on code-server. Not affiliated with Anthropic or Coder.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers