@neoline/hostbridge 1.4.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -137
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/src/server_control_mcp/__init__.py +5 -5
- package/src/server_control_mcp/cli.py +229 -1231
- package/src/server_control_mcp/client.py +478 -0
- package/src/server_control_mcp/config.py +158 -0
- package/src/server_control_mcp/daemon.py +387 -306
- package/src/server_control_mcp/doctor.py +108 -0
- package/src/server_control_mcp/hosts.py +52 -7
- package/src/server_control_mcp/mock_ssh.py +279 -99
- package/src/server_control_mcp/policy.py +2 -0
- package/src/server_control_mcp/protocol.py +362 -0
- package/src/server_control_mcp/runtime.py +40 -0
- package/src/server_control_mcp/secrets.py +8 -0
- package/src/server_control_mcp/server.py +132 -188
- package/src/server_control_mcp/services.py +508 -0
- package/src/server_control_mcp/transports/__init__.py +17 -0
- package/src/server_control_mcp/transports/base.py +78 -0
- package/src/server_control_mcp/transports/pty.py +427 -0
- package/src/server_control_mcp/transports/ssh.py +195 -0
- package/src/server_control_mcp/manager.py +0 -925
package/README.md
CHANGED
|
@@ -25,57 +25,27 @@ This repo is portable: it ships with no personal server paths or built-in hosts.
|
|
|
25
25
|
|
|
26
26
|
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`).
|
|
27
27
|
|
|
28
|
-
## Install
|
|
28
|
+
## Install and configure v1
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
The shared distribution path is npm. Users can run the MCP server or setup wizard with `npx`:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npx -y @neoline/hostbridge install
|
|
36
|
-
npx -y @neoline/hostbridge setup
|
|
37
|
-
npx -y @neoline/hostbridge list
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
For a persistent global install:
|
|
30
|
+
Install from npm or Python, then create a schema-v1 configuration and start the daemon:
|
|
41
31
|
|
|
42
32
|
```bash
|
|
43
33
|
npm install -g @neoline/hostbridge
|
|
44
|
-
hostbridge install
|
|
45
34
|
hostbridge setup
|
|
35
|
+
hostbridge daemon start
|
|
36
|
+
hostbridge doctor
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
- Codex CLI: `~/.codex/config.toml`
|
|
51
|
-
- Claude Code: `~/.claude.json`
|
|
52
|
-
- Cursor: `~/.cursor/mcp.json`
|
|
53
|
-
- Gemini CLI: `~/.gemini/settings.json`
|
|
54
|
-
- opencode: `~/.config/opencode/opencode.json`
|
|
55
|
-
- Hermes Agent: `~/.hermes/mcp.json`
|
|
56
|
-
- Antigravity IDE: `~/.antigravity/mcp.json`
|
|
57
|
-
- Kiro: `~/.kiro/settings/mcp.json`
|
|
39
|
+
The 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.
|
|
58
40
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
This npm package is a Node launcher around the Python MCP core, so the machine still needs Python 3.11+ and the package's Python dependencies available to that interpreter. To point at a non-default interpreter:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
HOSTBRIDGE_PYTHON=/path/to/python3 npx -y @neoline/hostbridge setup
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
For local development from this repository:
|
|
41
|
+
Add a host as one validated JSON object:
|
|
68
42
|
|
|
69
43
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
python3 -m server_control_mcp.server install
|
|
74
|
-
npm run test:node
|
|
44
|
+
hostbridge hosts add '{"id":"gpu","label":"GPU","ssh":{"host":"alice@gpu.example.com"},"transport":"auto"}'
|
|
45
|
+
hostbridge hosts list
|
|
46
|
+
hostbridge hosts check gpu
|
|
75
47
|
```
|
|
76
48
|
|
|
77
|
-
|
|
78
|
-
|
|
79
49
|
## File transfer model
|
|
80
50
|
|
|
81
51
|
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:
|
|
@@ -92,7 +62,7 @@ Give file tools local and remote paths; do not paste base64 into tool arguments.
|
|
|
92
62
|
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.
|
|
93
63
|
|
|
94
64
|
```bash
|
|
95
|
-
hostbridge mock-ssh a800_95
|
|
65
|
+
hostbridge mock-ssh start a800_95
|
|
96
66
|
```
|
|
97
67
|
|
|
98
68
|
By 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:
|
|
@@ -108,7 +78,7 @@ identity file: /Users/alice/.hostbridge/mock-ssh/a800_95/client_ed25519_key
|
|
|
108
78
|
Point the compute provider at the printed SSH alias, for example `ssh:hostbridge-a800_95`. To remove the managed SSH config block later:
|
|
109
79
|
|
|
110
80
|
```bash
|
|
111
|
-
hostbridge mock-ssh a800_95
|
|
81
|
+
hostbridge mock-ssh remove a800_95
|
|
112
82
|
```
|
|
113
83
|
|
|
114
84
|
Advanced options are still available for fixed deployments: `--listen-host`, `--port`, `--key-dir`, `--ssh-host-alias`, `--ssh-config`, and `--no-ssh-config`.
|
|
@@ -159,81 +129,11 @@ For MCP clients, prefer setting the environment variable instead of relying on s
|
|
|
159
129
|
|
|
160
130
|
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).
|
|
161
131
|
|
|
162
|
-
##
|
|
163
|
-
|
|
164
|
-
Run the local setup wizard instead of hand-writing `hosts.json`:
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
hostbridge setup
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
You can also pass the host id up front to reduce prompts:
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
hostbridge setup gpu_1
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
The wizard now records the real login path instead of asking a fixed questionnaire:
|
|
177
|
-
|
|
178
|
-
1. Collect only a stable host id, display label, and the command you already run locally, such as `ssh bastion`, `ssh -J bastion alice@gpu.internal`, `bash ~/.ssh/connect_gpu.sh`, or `docker exec -it training-box bash`.
|
|
179
|
-
2. Start that command in a terminal-style PTY proxy: remote output is printed directly, and user input is forwarded to the setup session.
|
|
180
|
-
3. Record the prompt visible when each line is typed. Secret-looking prompts (`password`, `passphrase`, `token`, `OTP`, `密码`, `验证码`) do not echo locally and are encrypted before saving.
|
|
181
|
-
4. Convert the recorded prompt/input pairs into ordered `login_steps`; type `/ready` once the final remote shell prompt is available.
|
|
182
|
-
5. Send a readiness probe through the same session, then replay the recorded `login_steps` in a fresh connection before saving `~/.hostbridge/hosts.json`.
|
|
132
|
+
## Configuration lifecycle
|
|
183
133
|
|
|
184
|
-
|
|
134
|
+
`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.
|
|
185
135
|
|
|
186
|
-
|
|
187
|
-
hostbridge install
|
|
188
|
-
hostbridge list
|
|
189
|
-
hostbridge check gpu_1
|
|
190
|
-
hostbridge remove gpu_1
|
|
191
|
-
hostbridge setup --config ./hosts.json
|
|
192
|
-
hostbridge reload
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Agent-friendly setup
|
|
196
|
-
|
|
197
|
-
Agents should prefer the CLI setup flow over searching shell history or hand-editing JSON. A common JumpServer pattern is to clone a known bastion entry and change only the menu-selected host:
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
hostbridge setup gpu_lab_1 \
|
|
201
|
-
--label "Lab GPU server" \
|
|
202
|
-
--from-host bastion \
|
|
203
|
-
--menu-selection "gpu-lab-01" \
|
|
204
|
-
--final-prompt '.*[$#] '
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
This reuses the source host's connection command and encrypted secrets, replaces the menu selection, verifies the resulting runtime path, then saves the new host. After setup, running MCP servers that include runtime reload support will pick up the new entry on the next `hosts_list` or `session_open`; no client restart is required. If an already-running MCP process predates this feature, restart it once.
|
|
208
|
-
|
|
209
|
-
Recommended agent flow:
|
|
210
|
-
|
|
211
|
-
1. Call `hosts_list`.
|
|
212
|
-
2. If the target host exists, call `session_open`.
|
|
213
|
-
3. If `session_open` times out or fails, run `hostbridge check <host_id>` to verify the recorded login path, then retry or repair setup.
|
|
214
|
-
4. If it does not exist and the user identifies a similar bastion-backed host, run `hostbridge setup <new_id> --from-host <source_id> --menu-selection <ip-or-name> --final-prompt <regex>`.
|
|
215
|
-
5. Call `hosts_list` again, then `session_open(<new_id>)`.
|
|
216
|
-
6. Do not inspect `~/.ssh`, old expect scripts, or shell snapshots unless the user explicitly asks or the setup command lacks required information.
|
|
217
|
-
|
|
218
|
-
### Encrypted login automation
|
|
219
|
-
|
|
220
|
-
Some environments require a password prompt, a bastion menu, or a JumpServer host selector before the remote shell is available. The setup wizard can save these as ordered `login_steps`. Secrets are encrypted with a local Fernet key at `~/.hostbridge/key` or `HOSTBRIDGE_KEY_FILE`; the decrypted value is used only in memory while opening a session.
|
|
221
|
-
|
|
222
|
-
Equivalent non-interactive commands are available:
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
hostbridge secret set bastion password
|
|
226
|
-
hostbridge step add bastion --expect '(?i)password:' --send-secret password
|
|
227
|
-
hostbridge step add bastion --expect 'Opt>' --send 'gpu-lab-01'
|
|
228
|
-
hostbridge step add bastion --expect '.*[$#] ' --send ''
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Security model:
|
|
232
|
-
|
|
233
|
-
- `hosts.json` never stores plaintext passwords.
|
|
234
|
-
- `hostbridge list` and `hosts_list` do not expose secret material.
|
|
235
|
-
- `~/.hostbridge/hosts.json` and `~/.hostbridge/key` should be readable only by the local user running the MCP server.
|
|
236
|
-
- Local encryption protects config-at-rest, not a fully compromised same-user account. Prefer SSH keys or system keychains when available.
|
|
136
|
+
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.
|
|
237
137
|
|
|
238
138
|
## Configure hosts
|
|
239
139
|
|
|
@@ -243,6 +143,7 @@ Create `~/.hostbridge/hosts.json`:
|
|
|
243
143
|
mkdir -p ~/.hostbridge
|
|
244
144
|
cat > ~/.hostbridge/hosts.json <<'JSON'
|
|
245
145
|
{
|
|
146
|
+
"schema_version": 1,
|
|
246
147
|
"hosts": [
|
|
247
148
|
{
|
|
248
149
|
"id": "gpu_direct",
|
|
@@ -256,7 +157,7 @@ cat > ~/.hostbridge/hosts.json <<'JSON'
|
|
|
256
157
|
JSON
|
|
257
158
|
```
|
|
258
159
|
|
|
259
|
-
|
|
160
|
+
The daemon validates this file at startup. Restart the daemon after editing it.
|
|
260
161
|
|
|
261
162
|
### Direct SSH, no jump host
|
|
262
163
|
|
|
@@ -339,26 +240,14 @@ Use this for non-SSH interactive shells, containers, or custom connection tools.
|
|
|
339
240
|
|
|
340
241
|
## MCP client config
|
|
341
242
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
hostbridge install
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Single-client install is also available:
|
|
349
|
-
|
|
350
|
-
```bash
|
|
351
|
-
hostbridge install --target codex
|
|
352
|
-
hostbridge install --target cursor
|
|
353
|
-
hostbridge install --target opencode
|
|
354
|
-
```
|
|
243
|
+
Register the stdio entrypoint in each client. HostBridge v1 does not mutate client configuration files.
|
|
355
244
|
|
|
356
|
-
Codex
|
|
245
|
+
Codex example in `~/.codex/config.toml`:
|
|
357
246
|
|
|
358
247
|
```toml
|
|
359
248
|
[mcp_servers.hostbridge]
|
|
360
249
|
command = "npx"
|
|
361
|
-
args = ["-y", "hostbridge"]
|
|
250
|
+
args = ["-y", "@neoline/hostbridge"]
|
|
362
251
|
[mcp_servers.hostbridge.env]
|
|
363
252
|
HOSTBRIDGE_AGENT_ID = "codex"
|
|
364
253
|
```
|
|
@@ -418,14 +307,12 @@ See `SECURITY.md` and `docs/THREAT_MODEL.md` for the trust model and audited eve
|
|
|
418
307
|
## Typical agent workflow
|
|
419
308
|
|
|
420
309
|
1. Call `hosts_list` and choose a configured host id.
|
|
421
|
-
2.
|
|
310
|
+
2. Add missing hosts with `hostbridge hosts add '<json>'`, then restart the daemon.
|
|
422
311
|
3. Call `session_open` once for the chosen host.
|
|
423
|
-
4. Only
|
|
424
|
-
5. Use `command_run` for quick
|
|
425
|
-
6. Use
|
|
426
|
-
7.
|
|
427
|
-
8. Call `task_stop` early if a job should be aborted.
|
|
428
|
-
9. Call `session_close` when finished, or `sessions_close_all` to tear down every session.
|
|
312
|
+
4. Only after a connection failure, run `hostbridge hosts check <host_id>`.
|
|
313
|
+
5. Use `command_run` for quick work and `task_start` / `task_status` for long jobs.
|
|
314
|
+
6. Use text tools for UTF-8 and upload/download tools for streamed binary transfers.
|
|
315
|
+
7. Close the session when finished.
|
|
429
316
|
|
|
430
317
|
## Security notes
|
|
431
318
|
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hostbridge"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.1"
|
|
8
8
|
description = "HostBridge MCP server for persistent allowlisted SSH and shell sessions."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -45,6 +45,7 @@ dependencies = [
|
|
|
45
45
|
[project.optional-dependencies]
|
|
46
46
|
dev = [
|
|
47
47
|
"pytest>=8.0.0",
|
|
48
|
+
"pytest-asyncio>=0.24.0",
|
|
48
49
|
"pytest-cov>=4.1.0",
|
|
49
50
|
"ruff>=0.6.0",
|
|
50
51
|
"mypy>=1.10.0",
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import sys
|
|
6
5
|
import os
|
|
7
6
|
import subprocess
|
|
7
|
+
import sys
|
|
8
8
|
from typing import NoReturn
|
|
9
9
|
|
|
10
10
|
__all__ = ["__version__", "ensure_runtime"]
|
|
11
|
-
__version__ = "
|
|
11
|
+
__version__ = "2.0.1"
|
|
12
12
|
|
|
13
13
|
# (import_name, pip_name) pairs for runtime dependency probing.
|
|
14
14
|
_REQUIRED_DEPENDENCIES = (
|
|
15
15
|
("mcp", "mcp"),
|
|
16
16
|
("pexpect", "pexpect"),
|
|
17
17
|
("cryptography", "cryptography"),
|
|
18
|
+
("asyncssh", "asyncssh"),
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
@@ -40,9 +41,8 @@ def ensure_runtime() -> None:
|
|
|
40
41
|
missing = _missing_dependencies()
|
|
41
42
|
if not missing:
|
|
42
43
|
return
|
|
43
|
-
if _auto_install_enabled() and _install_missing_dependencies(missing):
|
|
44
|
-
|
|
45
|
-
return
|
|
44
|
+
if _auto_install_enabled() and _install_missing_dependencies(missing) and not _missing_dependencies():
|
|
45
|
+
return
|
|
46
46
|
|
|
47
47
|
install_command = "pip install " + " ".join(missing)
|
|
48
48
|
message = (
|