@neoline/hostbridge
v2.0.1
Published
HostBridge MCP server for persistent allowlisted SSH and shell sessions.
Maintainers
Readme
hostbridge
A local MCP server for AI agents to operate allowlisted interactive shells, usually SSH sessions. It keeps PTYs alive in memory, so an agent can open a session once, run quick commands, start long-running background jobs, and poll logs until completion.
This repo is portable: it ships with no personal server paths or built-in hosts. Each user declares their own connection targets in ~/.hostbridge/hosts.json or a path set by HOSTBRIDGE_HOSTS_FILE.
Tools
hosts_list— reload and show configured connection targets and local script availability when applicable.hosts_reload— explicitly reload configured connection targets without restarting the MCP server.session_open(host_id, connect_timeout=30)— open a persistent interactive shell for one configured host.sessions_list()— list live sessions in this MCP process.sessions_close_all()— close every live session in one call; useful for shutdown and cleanup.command_run(session_id, command, timeout=60)— run a foreground command and wait for completion.task_start(session_id, command)— start a remote background job under$HOME/.hostbridge/jobs/<task_id>.task_status(session_id, task_id, tail_lines=80)— poll job state, exit code, and log tail.task_stop(session_id, task_id)— terminate a running background job and reap its process.session_write(session_id, text)— send a raw line for interactive edge cases.session_read(session_id, timeout=1.0, max_chars=20000)— read available raw output after interactive writes.file_write_text(session_id, remote_path, content)— write a UTF-8 text file through the active shell, up to 100MiB.file_read_text(session_id, remote_path)— read a UTF-8 text file through the active shell, up to 100MiB.file_upload(session_id, local_path, remote_path)— upload a local file with chunked shell/base64 transfer, up to 100MiB.file_download(session_id, remote_path, local_path)— download a remote file with chunked shell/base64 transfer, up to 100MiB.session_close(session_id)— close a persistent session.
Every tool returns a structured payload with a stable error_code on failure (HOST_NOT_FOUND, SESSION_NOT_FOUND, POLICY_DENIED, TASK_NOT_FOUND, TIMEOUT, INTERNAL).
Install and configure v1
Install from npm or Python, then create a schema-v1 configuration and start the daemon:
npm install -g @neoline/hostbridge
hostbridge setup
hostbridge daemon start
hostbridge doctorThe public CLI is intentionally small: setup, migrate, doctor, hosts, daemon, mock-ssh, and version. Configure MCP clients manually to run npx -y @neoline/hostbridge; when no CLI command is supplied the same entrypoint serves MCP over stdio.
Add a host as one validated JSON object:
hostbridge hosts add '{"id":"gpu","label":"GPU","ssh":{"host":"[email protected]"},"transport":"auto"}'
hostbridge hosts list
hostbridge hosts check gpuFile transfer model
HostBridge file transfer is shell-based because recorded login paths may be JumpServer menus, OTP flows, wrapper scripts, or docker exec sessions rather than standard SSH/SFTP connections. The first-class transfer tools stream chunked base64 through the already-open shell session, verify hashes, and work for text or binary artifacts up to 100MiB:
file_upload(session_id, "/absolute/local/script.py", "/remote/work/script.py")
file_download(session_id, "/remote/work/result.log", "/absolute/local/result.log")Give file tools local and remote paths; do not paste base64 into tool arguments. Files larger than 100MiB are deliberately rejected with guidance to use remote pull instead, for example curl, wget, git clone, modelscope download, or another remote-native downloader. The MCP file tools intentionally keep this shell/base64 backend for non-standard login flows. The optional mock-ssh bridge below exposes SFTP/SCP protocol compatibility on top of the same backend for clients which require SSH file-transfer channels.
Mock SSH bridge
Some clients, including compute providers, know how to connect to SSH targets but cannot run HostBridge's MCP connector inside their local sandbox. hostbridge mock-ssh exposes one configured HostBridge host as a local SSH server while keeping the actual remote access path behind HostBridge. This is useful when the client-side SSH implementation runs outside a restricted sandbox.
hostbridge mock-ssh start a800_95By default HostBridge selects a free local port from 2222-2299, creates host/client keys under ~/.hostbridge/mock-ssh/<host_id>/, and writes a managed Host hostbridge-<host_id> block into ~/.ssh/config. The command prints the chosen alias, port, config path, and identity file:
hostbridge mock-ssh forwarding host 'a800_95'
listening on ssh://127.0.0.1:2222
ssh host alias: hostbridge-a800_95
ssh config: /Users/alice/.ssh/config
identity file: /Users/alice/.hostbridge/mock-ssh/a800_95/client_ed25519_keyPoint the compute provider at the printed SSH alias, for example ssh:hostbridge-a800_95. To remove the managed SSH config block later:
hostbridge mock-ssh remove a800_95Advanced options are still available for fixed deployments: --listen-host, --port, --key-dir, --ssh-host-alias, --ssh-config, and --no-ssh-config.
The bridge supports:
- SSH exec requests, suitable for compute
call_commandAPIs. - Interactive shell channels.
- SFTP upload/download/list/stat/mkdir/remove/rename operations.
- OpenSSH
scpin its default SFTP-backed mode. - Legacy OpenSSH
scp -Omode through AsyncSSH's SCP server.
Each SSH channel opens its own HostBridge backend session and closes it when the channel ends, so an interactive shell exit does not kill later exec or file-transfer requests. The SFTP/SCP implementation is a compatibility layer backed by HostBridge's shell/base64 transfer primitives, so it keeps working across JumpServer/menu/wrapper login flows where direct remote SFTP is unavailable. It inherits the same transfer size limit (100MiB by default); for larger files, start a remote-native pull from the target host.
Daemon lifecycle
HostBridge always keeps remote sessions in the local daemon. The MCP server process is only a lightweight stdio adapter for agent clients and talks to the daemon over a Unix domain socket. If the daemon is not already running, the MCP adapter starts it lazily on the first tool call.
hostbridge daemon start
hostbridge daemon status
hostbridge daemon stopThe daemon uses a Unix domain socket with a small JSON-line RPC protocol by default, not HTTP and not a TCP port:
~/.hostbridge/daemon.sockWhen launched through the npm wrapper on macOS, HostBridge sets HOSTBRIDGE_DAEMON_SOCKET to /private/tmp/hostbridge-$USER/daemon.sock so sandboxed MCP launchers do not accidentally isolate the daemon under a rewritten HOME. Python-only launches keep the ~/.hostbridge/daemon.sock default unless you set HOSTBRIDGE_DAEMON_SOCKET or HOSTBRIDGE_DAEMON_DIR.
This avoids fixed port conflicts, removes the local HTTP surface, and keeps the control API local to the current machine. The daemon directory should be private to the local user. To point daemon management commands at a custom socket path:
hostbridge daemon start --socket /tmp/hostbridge.sockFor MCP clients, prefer setting the environment variable instead of relying on symlinks:
{
"env": {
"HOSTBRIDGE_DAEMON_SOCKET": "/private/tmp/hostbridge-shengzhoukong/daemon.sock"
}
}Sessions can outlive a single agent client process. HostBridge records an optional HOSTBRIDGE_AGENT_ID owner on sessions and refuses cross-owner operations unless a force-close path is used. Set HOSTBRIDGE_MAX_SESSIONS_PER_HOST to cap concurrent live sessions per configured host (default 0, unlimited).
Configuration lifecycle
hostbridge setup creates an empty schema-v1 file with mode 0600. It does not run an interactive login recorder. Use hostbridge hosts add for new entries and hostbridge migrate OLD NEW for legacy files. The daemon loads one validated config at startup; restart it after configuration changes.
Use transport: "auto" for normal operation, pty for JumpServer/menu/wrapper flows, or ssh for native AsyncSSH/SFTP-capable targets. Commands receive stdin explicitly through the protocol; HostBridge no longer guesses whether a command reads stdin.
Configure hosts
Create ~/.hostbridge/hosts.json:
mkdir -p ~/.hostbridge
cat > ~/.hostbridge/hosts.json <<'JSON'
{
"schema_version": 1,
"hosts": [
{
"id": "gpu_direct",
"label": "GPU direct SSH",
"ssh": {
"host": "[email protected]"
}
}
]
}
JSONThe daemon validates this file at startup. Restart the daemon after editing it.
Direct SSH, no jump host
{
"hosts": [
{
"id": "gpu",
"label": "GPU server",
"ssh": {
"host": "[email protected]"
}
}
]
}SSH with port, key, and jump host
{
"hosts": [
{
"id": "gpu_jump",
"label": "GPU via bastion",
"ssh": {
"host": "[email protected]",
"port": 2222,
"identity_file": "~/.ssh/gpu_key",
"proxy_jump": "bastion.example.com",
"extra_args": ["-o", "ServerAliveInterval=30"]
}
}
]
}Existing shell script wrapper
Use this if you already have a .sh or expect wrapper that opens an interactive remote shell.
{
"hosts": [
{
"id": "legacy_gpu",
"label": "Legacy GPU wrapper",
"script": "~/.ssh/connect_gpu.sh"
}
]
}The wrapper does not need executable permission; the MCP runs it with /bin/sh.
Advanced command mode
Use this for non-SSH interactive shells, containers, or custom connection tools. The MCP calls the executable directly without shell=True.
{
"hosts": [
{
"id": "container",
"label": "Local container shell",
"command": "docker",
"args": ["exec", "-it", "training-box", "bash"]
}
]
}Host config rules
idmay contain only letters, numbers,_,-, and..- Each host must define exactly one practical connection style:
ssh,script, orcommand. ssh.hostcan be any target accepted byssh, such as[email protected]or an alias from~/.ssh/config.ssh.identity_fileandscriptexpand~for the local user running the MCP server.- For a different config path, set
HOSTBRIDGE_HOSTS_FILE=/path/to/hosts.jsonin the MCP server environment.
MCP client config
Register the stdio entrypoint in each client. HostBridge v1 does not mutate client configuration files.
Codex example in ~/.codex/config.toml:
[mcp_servers.hostbridge]
command = "npx"
args = ["-y", "@neoline/hostbridge"]
[mcp_servers.hostbridge.env]
HOSTBRIDGE_AGENT_ID = "codex"Claude Desktop style config:
{
"mcpServers": {
"hostbridge": {
"command": "npx",
"args": ["-y", "hostbridge"],
"env": {"HOSTBRIDGE_AGENT_ID": "claude-code"}
}
}
}To use a project-specific host file:
[mcp_servers.hostbridge.env]
HOSTBRIDGE_HOSTS_FILE = "/absolute/path/to/hosts.json"See examples/ for copyable config templates.
Policy, audit, and idle reaping
hostbridge ships a lightweight policy layer that runs inside the daemon process. It is on by default and can be tuned or disabled via environment variables or a JSON config file.
Environment variables:
| Variable | Effect |
| --- | --- |
| HOSTBRIDGE_DISABLE_POLICY=1 | Disable policy checks and audit logging entirely. |
| HOSTBRIDGE_POLICY_FILE=/path/to/policy.json | Override policy config path (default ~/.hostbridge/policy.json). |
| HOSTBRIDGE_DENYLIST=pat1:pat2 | Append extra deny regex patterns (colon-separated). |
| HOSTBRIDGE_IDLE_TIMEOUT=900 | Idle timeout in seconds before an inactive session is reaped (default 1800). |
| HOSTBRIDGE_AUDIT_LOG=/path/to/audit.jsonl | Override audit log path (default ~/.hostbridge/audit.jsonl). |
Default denylist blocks obviously destructive commands (rm -rf /, mkfs, dd of=/dev/sd*, fork bombs, shutdown, reboot). A denied command returns error_code=POLICY_DENIED with the matched pattern, and is recorded in the audit log alongside session/task lifecycle events.
Policy config file (optional):
{
"idle_timeout_seconds": 1800,
"use_default_denylist": true,
"command_denylist": ["\\bmytool\\b"],
"audit_log_path": "~/.hostbridge/audit.jsonl"
}See SECURITY.md and docs/THREAT_MODEL.md for the trust model and audited events.
Typical agent workflow
- Call
hosts_listand choose a configured host id. - Add missing hosts with
hostbridge hosts add '<json>', then restart the daemon. - Call
session_openonce for the chosen host. - Only after a connection failure, run
hostbridge hosts check <host_id>. - Use
command_runfor quick work andtask_start/task_statusfor long jobs. - Use text tools for UTF-8 and upload/download tools for streamed binary transfers.
- Close the session when finished.
Security notes
- Host access is allowlisted by config; MCP callers cannot provide arbitrary local connection commands at runtime.
- The server never reads or logs passwords from underlying SSH/script prompts.
- Commands still run with your remote shell privileges. Use only with trusted local MCP clients.
- Background task logs and exit codes are stored on the remote host in
$HOME/.hostbridge/jobs/. - The policy layer blocks a built-in destructive denylist and writes structured audit events. It is defense-in-depth, not a sandbox.
Contributing
See CONTRIBUTING.md. Run tests with python -m pytest -q. The full suite should pass before pushing.
License
MIT. See LICENSE.
