# coding-agent-mcp An MCP server for coding agents. It provides workspace-subpath-scoped file editing and typed build/test tasks, including Python tests. ## Security model This is intentionally a single combined server: `coding-agent-mcp`. The old split servers and the generic `run_command` API were removed. - HTTP requires an API key. Set `CODING_AGENT_MCP_API_KEYS` to `SUBJECT=TOKEN` entries separated by commas. - An SSE connection creates a server-generated session bound to that authenticated subject. MCP requests must use that live session; callers cannot choose a session ID. - The session's optional `workspaceSubpath` is bound when SSE opens and cannot change afterward. It is the only filesystem scope beneath the configured workspace root; the server-generated session ID is not part of the directory path. - File and task paths are relative only. The client cannot select a root or use absolute paths. - File paths are checked for traversal and symlink escapes. - HTTP sends no permissive CORS header. Set `CODING_AGENT_MCP_CORS_ORIGINS` to an explicit comma-separated origin allowlist only when browser access is required. - Remote HTTP command execution defaults to **disabled**. Set `CODING_AGENT_MCP_EXECUTION_BACKEND=local` only in the hardened runner deployment below. `run_task` deliberately accepts no shell command, flags, environment variables, npm script name, or local executable. It maps a typed request to a fixed invocation. Project tests and build scripts still execute project code, so this validation is not a sandbox. ## Tools Workspace tools: `list_files`, `read_file`, `read_files`, `read_binary_file`, `search_text`, `write_file`, `write_binary_file`, `apply_patch`, `make_directory`, `delete_path`, `move_path`, `rename_path`, and `file_info`. Execution tool: `run_task`. Example Python request: ```json { "runner": "pytest", "task": "test", "paths": ["tests/unit"], "timeoutSeconds": 300 } ``` Supported runners are `pytest`, `npm`, `pnpm`, `yarn`, `maven`, `gradle`, `go`, and `cargo`. Supported tasks depend on the runner; the server rejects unsupported combinations. ## Local trusted use Stdio is intended for a local, trusted client and enables typed task execution by default: ```bash npm run start:coding-agent -- --root /absolute/path/to/workspace ``` The runtime needs the selected tool installed. The supplied Docker image contains Python 3 and pytest. ## Remote hardened deployment 1. Copy `.env.example` to `.env` and set a long random API token. 2. Create a dedicated workspace directory; do not mount a home directory, source checkout, Docker socket, or secrets. 3. Make that directory writable by `CODING_AGENT_UID:CODING_AGENT_GID`. 4. Deploy with `docker compose up --build -d`. The Compose deployment runs as a non-root UID, makes the container filesystem read-only, provides an ephemeral `/tmp`, removes Linux capabilities, applies process/memory/CPU limits, and places the MCP service on an internal-only Docker network. The workspace mount is the only intended writable persistent path. This is a hardened local runner, not a VM-grade security boundary. For untrusted agents or untrusted repositories, connect `run_task` to an external ephemeral VM/container runner with a copy-on-write workspace, no host bind mounts, no Docker socket, no inherited secrets, resource quotas, and explicit network policy. Keep execution disabled until such a runner is configured. ## HTTP protocol All HTTP endpoints require `Authorization: Bearer TOKEN` (or `x-api-key`). ### Streamable HTTP (recommended) - Send an MCP `initialize` JSON-RPC request to `POST /mcp`. The response includes `Mcp-Session-Id`. - Send subsequent MCP requests to `POST /mcp` with that `Mcp-Session-Id` header. - Use `DELETE /mcp` with `Mcp-Session-Id` to terminate the session. - `GET /mcp` with `Mcp-Session-Id` opens an optional server-to-client SSE stream. ### Legacy SSE - `GET /sse` opens the former MCP SSE transport. It emits an `endpoint` event for posting messages. - For automatic legacy-client fallback, `GET /mcp` without `Mcp-Session-Id` provides the same SSE handshake. - The pre-existing custom transport is retained: `GET /events` returns `x-session-id`; send it on `POST /mcp` as `x-session-id`. - Bind `x-workspace-subpath` only when opening or initializing a session; it cannot change afterward. - `GET /health` reports server health. ## Development ```bash npm test ```