Skip to content

Commit 60ee2ce

Browse files
Add simple SSH connection wizard
1 parent 234dacc commit 60ee2ce

10 files changed

Lines changed: 170 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.5.0
4+
5+
- Added `remote_connection_wizard`, a simple user-facing Add SSH Connection tool with only Name, SSH Host, SSH Port, and Identity File fields.
6+
- Added `codex-remote-ssh add` CLI fallback for users who need a terminal-based setup flow.
7+
- Added `codex-remote-ssh list` for inspecting saved connection profiles without secrets.
8+
39
## 0.4.0
410

511
- Added remote workspace bootstrap checks for OS, shell, user, workspace path, and common development tools.

plugins/remote-ssh/.codex-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "remote-ssh",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"description": "Enterprise-grade Remote SSH tools for Codex with host policies, audit logging, and safe file operations.",
55
"author": {
66
"name": "Zain Technologies LTD",
@@ -22,7 +22,7 @@
2222
"interface": {
2323
"displayName": "Remote SSH",
2424
"shortDescription": "Enterprise Remote SSH operations for Codex",
25-
"longDescription": "Remote SSH by Zain Technologies LTD connects Codex to trusted servers, devboxes, and private infrastructure through a local MCP bridge. It supports conversational connection setup, saved host aliases, remote workspace bootstrap checks, tree/search/git tools, path allowlists, write opt-in controls, blocked command policies, timeouts, output limits, and JSONL audit logs for professional remote development and operations workflows.",
25+
"longDescription": "Remote SSH by Zain Technologies LTD connects Codex to trusted servers, devboxes, and private infrastructure through a local MCP bridge. It supports a simple Add SSH Connection wizard, conversational connection setup, saved host aliases, remote workspace bootstrap checks, tree/search/git tools, path allowlists, write opt-in controls, blocked command policies, timeouts, output limits, and JSONL audit logs for professional remote development and operations workflows.",
2626
"developerName": "Zain Technologies LTD",
2727
"category": "Development",
2828
"capabilities": [

plugins/remote-ssh/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.5.0
4+
5+
- Added `remote_connection_wizard`, a simple user-facing Add SSH Connection tool with only Name, SSH Host, SSH Port, and Identity File fields.
6+
- Added `codex-remote-ssh add` CLI fallback for users who need a terminal-based setup flow.
7+
- Added `codex-remote-ssh list` for inspecting saved connection profiles without secrets.
8+
39
## 0.4.0
410

511
- Added remote workspace bootstrap checks for OS, shell, user, workspace path, and common development tools.

plugins/remote-ssh/CONFIGURATION.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Advanced users can override this with `REMOTE_SSH_CONFIG_FILE` or provide epheme
1616

1717
## Automatic Connection Setup
1818

19-
`remote_add_host` accepts the same fields users expect from a modern Remote SSH form:
19+
`remote_connection_wizard` accepts the same fields users expect from a modern Remote SSH form:
2020

2121
| Form Field | Tool Field | Notes |
2222
| --- | --- | --- |
@@ -28,6 +28,14 @@ Advanced users can override this with `REMOTE_SSH_CONFIG_FILE` or provide epheme
2828
| Allowed Paths | `allowedPaths` | Optional safety policy for file tools. |
2929
| Allow Writes | `allowWrites` | Defaults to `false`. |
3030

31+
Advanced users and admins can use `remote_add_host` for policy fields such as `workspaceRoot`, `allowedPaths`, and `allowWrites`.
32+
33+
Terminal fallback:
34+
35+
```bash
36+
codex-remote-ssh add
37+
```
38+
3139
## `REMOTE_SSH_HOSTS`
3240

3341
Set this environment variable to a JSON object keyed by host alias.

plugins/remote-ssh/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ Most users should add connections conversationally:
7777
Add an SSH connection named hms for hmsadmin@192.168.128.7 using identity file ~/.ssh/id_ed25519_hms.
7878
```
7979

80+
If Codex renders the tool as a form, the normal setup only needs:
81+
82+
```text
83+
Name
84+
SSH Host
85+
SSH Port
86+
Identity File (Private Key)
87+
```
88+
89+
If the plugin UI is unavailable, use the terminal fallback:
90+
91+
```bash
92+
codex-remote-ssh add
93+
```
94+
8095
The plugin saves profiles to:
8196

8297
```text
@@ -127,6 +142,7 @@ Use Remote SSH to tail the last 100 lines of /var/log/nginx/error.log on hms.
127142

128143
| Tool | Purpose |
129144
| --- | --- |
145+
| `remote_connection_wizard` | Adds a connection with the simple Name, SSH Host, SSH Port, Identity File form. |
130146
| `remote_add_host` | Saves or updates an SSH connection profile. |
131147
| `remote_remove_host` | Removes a saved SSH connection profile. |
132148
| `remote_test_connection` | Validates a saved SSH connection. |

plugins/remote-ssh/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zaintechnologiesltd/codex-remote-ssh",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"description": "Enterprise-grade Remote SSH MCP tools for OpenAI Codex.",
55
"license": "MIT",
66
"author": {
@@ -27,6 +27,7 @@
2727
"developer-tools"
2828
],
2929
"bin": {
30+
"codex-remote-ssh": "scripts/remote-ssh-cli.js",
3031
"codex-remote-ssh-mcp": "scripts/remote-ssh-mcp.js"
3132
},
3233
"files": [
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env node
2+
"use strict";
3+
4+
const readline = require("node:readline/promises");
5+
const { stdin: input, stdout: output } = require("node:process");
6+
const {
7+
cleanHostProfile,
8+
readWritableConfig,
9+
redactConfig,
10+
writeWritableConfig,
11+
} = require("./remote-ssh-mcp.js");
12+
13+
function usage() {
14+
console.log(`Codex Remote SSH
15+
16+
Usage:
17+
codex-remote-ssh add
18+
codex-remote-ssh list
19+
20+
Commands:
21+
add Prompt for Name, SSH Host, SSH Port, and Identity File.
22+
list List saved SSH connection profiles without secrets.
23+
`);
24+
}
25+
26+
async function addConnection() {
27+
const rl = readline.createInterface({ input, output });
28+
try {
29+
const name = (await rl.question("Name: ")).trim();
30+
const sshHost = (await rl.question("SSH Host (user@hostname or ~/.ssh/config host): ")).trim();
31+
const portAnswer = (await rl.question("SSH Port [22]: ")).trim();
32+
const identityFile = (await rl.question("Identity File (Private Key) [default SSH key]: ")).trim();
33+
34+
if (!name || !sshHost) {
35+
throw new Error("Name and SSH Host are required.");
36+
}
37+
38+
const { file, config } = readWritableConfig();
39+
config.hosts[name] = cleanHostProfile({
40+
name,
41+
sshHost,
42+
port: portAnswer ? Number(portAnswer) : 22,
43+
identityFile: identityFile || undefined,
44+
overwrite: true,
45+
});
46+
writeWritableConfig(file, config);
47+
48+
console.log(`Saved ${name} to ${file}`);
49+
} finally {
50+
rl.close();
51+
}
52+
}
53+
54+
function listConnections() {
55+
const { file, config } = readWritableConfig();
56+
console.log(`Config: ${file}`);
57+
console.log(JSON.stringify(
58+
Object.fromEntries(
59+
Object.entries(config.hosts).map(([name, profile]) => [name, redactConfig({ alias: name, ...profile })]),
60+
),
61+
null,
62+
2,
63+
));
64+
}
65+
66+
async function main() {
67+
const command = process.argv[2];
68+
if (command === "add") {
69+
await addConnection();
70+
return;
71+
}
72+
if (command === "list") {
73+
listConnections();
74+
return;
75+
}
76+
usage();
77+
process.exitCode = command ? 1 : 0;
78+
}
79+
80+
main().catch((error) => {
81+
console.error(error.message);
82+
process.exitCode = 1;
83+
});
84+

plugins/remote-ssh/scripts/remote-ssh-mcp.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const os = require("node:os");
77
const path = require("node:path");
88
const readline = require("node:readline");
99

10-
const SERVER_VERSION = "0.4.0";
10+
const SERVER_VERSION = "0.5.0";
1111
const PROTOCOL_VERSION = "2024-11-05";
1212
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
1313
const DEFAULT_CONNECT_TIMEOUT_SECONDS = 15;
@@ -354,6 +354,42 @@ function textResult(payload) {
354354
}
355355

356356
const tools = [
357+
{
358+
name: "remote_connection_wizard",
359+
description: "Add SSH connection using the simple user-facing form: Name, SSH Host, SSH Port, and Identity File.",
360+
inputSchema: {
361+
type: "object",
362+
title: "Add SSH connection",
363+
description: "Connect to a remote machine for Codex remote work.",
364+
properties: {
365+
name: {
366+
type: "string",
367+
title: "Name",
368+
description: "A friendly name for this SSH connection, such as My Server.",
369+
},
370+
sshHost: {
371+
type: "string",
372+
title: "SSH Host",
373+
description: "user@myserver.com or a host from ~/.ssh/config.",
374+
},
375+
port: {
376+
type: "integer",
377+
title: "SSH Port",
378+
description: "Leave empty to use default 22 or SSH config.",
379+
minimum: 1,
380+
maximum: 65535,
381+
default: 22,
382+
},
383+
identityFile: {
384+
type: "string",
385+
title: "Identity File (Private Key)",
386+
description: "Leave empty to use default SSH key or SSH config. Example: ~/.ssh/id_rsa.",
387+
},
388+
},
389+
required: ["name", "sshHost"],
390+
additionalProperties: false,
391+
},
392+
},
357393
{
358394
name: "remote_add_host",
359395
description: "Add or update a saved SSH connection profile in the Remote SSH config file.",
@@ -575,7 +611,7 @@ const tools = [
575611
];
576612

577613
async function callTool(name, args) {
578-
if (name === "remote_add_host") {
614+
if (name === "remote_connection_wizard" || name === "remote_add_host") {
579615
const alias = String(args.name || "").trim();
580616
if (!alias || !/^[A-Za-z0-9_.-]+$/.test(alias)) {
581617
throw new Error("Connection name must contain only letters, numbers, dots, underscores, or hyphens.");
@@ -788,8 +824,11 @@ module.exports = {
788824
handle,
789825
loadHostConfig,
790826
normalizeRemotePath,
827+
readWritableConfig,
828+
redactConfig,
791829
resolveRemoteWorkspacePath,
792830
shellQuote,
793831
startServer,
794832
tools,
833+
writeWritableConfig,
795834
};

plugins/remote-ssh/skills/remote-ssh/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Use this skill when the user asks Codex to inspect or operate a configured remot
1717
- optional identity file
1818
- optional workspace root
1919
- optional allowed remote paths
20-
Then use `remote_add_host` and test with `remote_test_connection`.
20+
Then use `remote_connection_wizard` and test with `remote_test_connection`.
21+
Use `remote_add_host` only when the user asks for advanced policy fields such as workspace root, allowed paths, or write access.
2122
3. When the user wants to work in a remote project, prefer workspace tools:
2223
- use `remote_workspace_bootstrap` to check environment readiness
2324
- use `remote_tree` to inspect project structure

plugins/remote-ssh/test/remote-ssh-mcp.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ assert.throws(() => assertCommandAllowed(config, "cat /etc/passwd"), /allowlist/
3434
assert.throws(() => assertCommandAllowed({ ...config, allowedCommands: [] }, "rm -rf /srv/app"), /policy/);
3535

3636
assert.ok(tools.some((tool) => tool.name === "remote_hosts"));
37+
assert.ok(tools.some((tool) => tool.name === "remote_connection_wizard"));
3738
assert.ok(tools.some((tool) => tool.name === "remote_add_host"));
3839
assert.ok(tools.some((tool) => tool.name === "remote_remove_host"));
3940
assert.ok(tools.some((tool) => tool.name === "remote_test_connection"));
@@ -87,6 +88,6 @@ assert.deepEqual(
8788
assert.equal(base64Text("hello"), "aGVsbG8=");
8889

8990
handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }).then((response) => {
90-
assert.ok(response.tools.length >= 15);
91+
assert.ok(response.tools.length >= 16);
9192
console.log("remote-ssh-mcp tests passed");
9293
});

0 commit comments

Comments
 (0)