They were three folders on one laptop: a coding agent with a git history of its own, and two servers - a development server and a browser - with none at all. What makes them a stack is what sits between them, and that lived nowhere: the compose file, the gateway, the egress proxy and the networks that keep the worker off the internet. So the whole thing is one repository, and the coding agent's separate history is folded into it rather than kept alongside. What is deliberately absent: .env, node_modules, and the live service definitions. The .example files next to them say what belongs there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| deploy | ||
| examples | ||
| src | ||
| test | ||
| .env.example | ||
| Dockerfile | ||
| README.md | ||
| docker-compose.yml | ||
| package.json | ||
README.md
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_KEYStoSUBJECT=TOKENentries 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
workspaceSubpathis 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_ORIGINSto 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=localonly 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:
{
"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:
npm run start:coding-agent -- --root /absolute/path/to/workspaceThe runtime needs the selected tool installed. The supplied Docker image contains Python 3 and pytest.
Remote hardened deployment
- Copy
.env.exampleto.envand set a long random API token. - Create a dedicated workspace directory; do not mount a home directory, source checkout, Docker socket, or secrets.
- Make that directory writable by
CODING_AGENT_UID:CODING_AGENT_GID. - 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
initializeJSON-RPC request toPOST /mcp. The response includesMcp-Session-Id. - Send subsequent MCP requests to
POST /mcpwith thatMcp-Session-Idheader. - Use
DELETE /mcpwithMcp-Session-Idto terminate the session. GET /mcpwithMcp-Session-Idopens an optional server-to-client SSE stream.
Legacy SSE
GET /sseopens the former MCP SSE transport. It emits anendpointevent for posting messages.- For automatic legacy-client fallback,
GET /mcpwithoutMcp-Session-Idprovides the same SSE handshake. - The pre-existing custom transport is retained:
GET /eventsreturnsx-session-id; send it onPOST /mcpasx-session-id. - Bind
x-workspace-subpathonly when opening or initializing a session; it cannot change afterward. GET /healthreports server health.
Development
npm test