remote-dev-mcp is a config-driven MCP server for remote C/C++ workflows where the
agent must be able to build, test, and run things on another machine without being
given unrestricted shell access.
This project is aimed at the common setup:
- development on a local laptop
- commits pushed from the laptop
- heavy build / runtime execution on a remote Linux box
- Copilot or another MCP client driving only approved actions
The server keeps the MCP surface intentionally small and pushes almost all behavior
into a local TOML config.
Typical remote dev flows usually fall into one of two bad extremes:
- too little automation, where every build/test/run is a manual SSH +
tmuxdance - too much power, where the AI gets a raw shell and can do anything the remote user can do
remote-dev-mcp sits in the middle:
- the client can only call configured actions
- each action has fixed program / args / cwd / env
- parameters are validated before templating
- long-running work is persisted in
tmux - output is polled from logs, not from a fragile interactive terminal session
- The MCP server runs locally on your machine.
- Actions execute on a named host over
ssh, or locally for tests. tmuxon the target host owns the actual process lifecycle.- Local metadata lives in SQLite.
- Remote output and final status live in files under
remote_state_dir.
The exposed MCP tools are intentionally minimal:
list_actionsrun_actionget_sessionlist_sessionsread_session_outputstop_session
There is no generic shell tool.
- Config-driven build / test / runtime actions
tmux-backed sessions that survive client disconnects- bounded parameter schemas with
enum,string,int,float,bool, and guardedpath - enforced
timeout_sec - stable remote log polling
ready_regexfor long-running actionsconcurrency_policyfor duplicate-run protection- CLI diagnostics for configured hosts
- local cleanup of old sessions and optional remote state cleanup
- Define a small set of approved actions in
config.toml. - Run
remote-dev-mcp --check-configto validate the config. - Run
remote-dev-mcp --diagnose-host <host>to verify the remote environment. - Start the server over
stdiofor Copilot / VS Code. - Let the client call:
list_actionsrun_actionget_sessionread_session_outputstop_session
For runtime actions, the client can keep polling until:
status == "running"andready == true, or- the session reaches a terminal state
Create a virtual environment and install the package:
python3 -m venv .venv
. .venv/bin/activate
pip install -e .Start from the provided examples:
Validate a config:
remote-dev-mcp --config /path/to/config.toml --check-configDiagnose a host before first use:
remote-dev-mcp --config /path/to/config.toml --diagnose-host devboxRun over stdio:
remote-dev-mcp --config /path/to/config.tomlRun over Streamable HTTP:
remote-dev-mcp --config /path/to/config.toml --transport streamable-httpClean old session records and optionally delete remote state:
remote-dev-mcp \
--config /path/to/config.toml \
--cleanup-sessions \
--max-age-hours 168 \
--remove-remote-stateAll real behavior lives in TOML.
High-level structure:
[server]
name = "remote-dev-mcp"
default_host = "devbox"
state_dir = "~/.remote-dev-mcp"
session_prefix = "rdmcp"
[hosts.devbox]
transport = "ssh"
destination = "buildbox"
remote_state_dir = "/tmp/remote-dev-mcp"
allowed_cwd_roots = [
"/work/project",
"/work/project/build-debug",
"/work/project/build-release",
]
[hosts.devbox.variables]
repo_root = "/work/project"
runtime_bin = "/work/project/out/debug/bin/app"
[actions.build_target]
description = "Build a configured CMake target"
host = "devbox"
program = "cmake"
args = ["--build", "{build_dir}", "--target", "{target}", "-j", "{jobs}"]
cwd = "{repo_root}"
timeout_sec = 1800
[actions.build_target.params.build_dir]
type = "enum"
values = ["/work/project/build-debug", "/work/project/build-release"]
[actions.build_target.params.target]
type = "enum"
values = ["core", "tests", "app"]
[actions.start_instance]
description = "Start a long-running runtime instance"
host = "devbox"
program = "{runtime_bin}"
args = ["--config", "{instance_config}", "--log-file", "{log_file}"]
cwd = "{repo_root}"
timeout_sec = 86400
ready_regex = "INSTANCE_STARTED"
concurrency_policy = "replace"stringintfloatboolenumpath
Useful constraints:
defaultrequiredpatternminmaxvaluesallow_emptymust_be_relativeroots
For path parameters, you must set one of:
must_be_relative = trueroots = ["/allowed/root", ...]
Unbounded absolute path params are rejected at config load time.
ready_regex
- optional
- uses remote
grep -Eagainst the session log - once matched, the session is marked ready and
ready_atis stored - useful for runtime processes that start quickly but become usable only after a known log line
concurrency_policy
allowdeny_if_runningreplace
Concurrency is evaluated per:
- host
- action name
- exact normalized parameter set
That means two runs of the same action with different params are treated as different sessions.
remote-dev-mcp --check-config
- validates the config without starting the MCP server
remote-dev-mcp --diagnose-host <host>
- checks connectivity
- checks
shell_path - checks
tmux - checks the required shell utilities used by the wrapper
- checks renderable action programs for that host
remote-dev-mcp --cleanup-sessions
- prunes old local session metadata
- can also delete remote log / status files
Returns discoverable actions with:
- parameter schema
- host
- tags
- timeout
ready_regexconcurrency_policy
- validates params
- renders templates
- applies concurrency policy
- starts a
tmuxsession - records local metadata
- returns session metadata
If concurrency_policy = "replace", the response also includes replaced_session_ids.
Returns normalized lifecycle state:
runningcompletedfailedstoppedlost
For actions with ready_regex, it also returns:
ready_checkreadyready_at
Lists known sessions from local SQLite state.
Reads remote output either:
- from a byte
offset - or by
tail_lines
Stops the backing tmux session and records a stable stopped state when possible.
Local stdio server:
{
"servers": {
"remote-dev": {
"type": "local",
"command": "/absolute/path/to/.venv/bin/remote-dev-mcp",
"args": ["--config", "/absolute/path/to/config.toml"],
"tools": [
"list_actions",
"run_action",
"get_session",
"list_sessions",
"read_session_output",
"stop_session"
]
}
}
}Run the main suite:
PYTHONPATH=src python3 -m unittest discover -s tests -vRun the optional real SSH integration with Docker:
REMOTE_DEV_MCP_RUN_DOCKER_TESTS=1 \
PYTHONPATH=src \
python3 -m unittest tests.test_ssh_integration -vBuild and validate distributable artifacts locally:
python3 -m pip install --upgrade build twine
python3 -m build
python3 -m twine check dist/*This repository is set up for publishing through GitHub Actions to PyPI using Trusted Publishing.
Files involved:
Before the first release, you still need to do one-time setup outside the repository:
- Create the
remote-dev-mcpproject on PyPI, or create a pending publisher for it. - Configure a Trusted Publisher on PyPI for:
- GitHub owner:
RadioCpp - repository:
remote-dev-mcp - workflow:
publish.yml - environment:
pypi
- GitHub owner:
- Create a GitHub environment named
pypi.
After that, the release flow is:
- Bump the version in pyproject.toml.
- Push the version change.
- Create a GitHub Release, for example
v0.1.0. - The
Publishworkflow builds and uploads the package to PyPI.
This server reduces risk, but it is not a sandbox.
- The local user running the server still has the power of that local user.
- The remote account should be restricted and should not have
sudo. config.tomlmust be reviewed like code.- Keep
allowed_cwd_rootsnarrow. - Prefer
enumparams for build dirs, targets, tests, and instance names. - Only use
pathparams when you actually need them. - Do not reintroduce a generic shell tool.
The common setup of:
- macOS laptop
- local MCP server on the laptop
- remote Linux build/runtime box over
ssh
is supported well.
Notes:
tmuxis required on every host that actually runs actionssetsidimproves timeout/stop behavior but is not strictly required- the wrapper assumes POSIX shell behavior
ready_regexdepends on remotegrep -E
If you run actions locally on macOS instead of on a Linux box:
- make sure
tmuxis installed - expect process-tree termination to be weaker if
setsidis missing
This repository is already in usable internal-beta shape:
- config validation exists
- fake
tmuxend-to-end tests exist - real Docker-based
ssh -> tmux -> cmake -> ctest -> runtimeintegration exists
The remaining roadmap is tracked in TODO.md.
This project is licensed under Apache License 2.0.
See LICENSE.