dev-mcps/coding-agent-mcp/README.md

83 lines
4.3 KiB
Markdown

# 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
```