@aaarc/handfree-ssh-mcp 1.0.2 → 1.0.3
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
CHANGED
|
@@ -268,7 +268,7 @@ Rules (enforced at config load — bad configs fail fast):
|
|
|
268
268
|
- **Independent policy.** The target authenticates with its OWN `username` / `password` / `privateKey`, and its own `whitelist` / `blacklist` / `safeDirectory` / `allowed*Directories` apply. The jump host is purely transport.
|
|
269
269
|
- **Jump host is still a normal server.** You can run tools against `bastion` directly; its connection is separate from the tunneling one.
|
|
270
270
|
|
|
271
|
-
**Hot-reload note:** `jumpHost` is connection-level,
|
|
271
|
+
**Hot-reload note:** `jumpHost` is a connection-level field, so it hot-reloads. Editing a server's `jumpHost` (or any hop's connection fields) in `servers.yaml` resets the affected connection on the fly — the change takes effect on the next tool call, no restart needed. This is chain-aware: if an intermediate hop's host / port / credentials change, every target that tunnels through it is reset too.
|
|
272
272
|
|
|
273
273
|
### Connection lifecycle (connect / reconnect)
|
|
274
274
|
|
package/build/config/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const SERVER_CONFIG = {
|
|
2
2
|
name: "ssh-mcp-server",
|
|
3
|
-
version: "1.0.
|
|
3
|
+
version: "1.0.3",
|
|
4
4
|
};
|
|
5
5
|
export const SERVER_INSTRUCTIONS = `This server provides SSH access to servers loaded from OpenSSH config (~/.ssh/config by default) and optional YAML config/policy overlays.
|
|
6
6
|
|
|
@@ -116,18 +116,52 @@ export class SSHConnectionManager {
|
|
|
116
116
|
*/
|
|
117
117
|
replaceConfig(configs, enabledServers) {
|
|
118
118
|
const previous = this.configs;
|
|
119
|
-
|
|
119
|
+
// Pass 1: servers whose own connection fields changed (or that vanished).
|
|
120
|
+
const directlyChanged = new Set();
|
|
120
121
|
for (const [name, oldConfig] of Object.entries(previous)) {
|
|
121
122
|
const nextConfig = configs[name];
|
|
122
123
|
if (!nextConfig || this.connectionFieldsChanged(oldConfig, nextConfig)) {
|
|
123
|
-
|
|
124
|
-
this.statusCache.delete(name);
|
|
125
|
-
changedConnections.push(name);
|
|
124
|
+
directlyChanged.add(name);
|
|
126
125
|
}
|
|
127
126
|
}
|
|
127
|
+
// Pass 2: a target must ALSO reset if any hop in its jump chain directly
|
|
128
|
+
// changed — otherwise it keeps tunneling through a stale intermediate hop.
|
|
129
|
+
// Walk both the old and new chains so topology shifts (added/removed hops)
|
|
130
|
+
// are covered too.
|
|
131
|
+
const toReset = new Set(directlyChanged);
|
|
132
|
+
for (const name of Object.keys(previous)) {
|
|
133
|
+
if (toReset.has(name))
|
|
134
|
+
continue;
|
|
135
|
+
if (this.jumpChainTouchesChanged(name, previous, directlyChanged) ||
|
|
136
|
+
this.jumpChainTouchesChanged(name, configs, directlyChanged)) {
|
|
137
|
+
toReset.add(name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const name of toReset) {
|
|
141
|
+
this.closeClient(name, true);
|
|
142
|
+
this.statusCache.delete(name);
|
|
143
|
+
}
|
|
128
144
|
this.setConfig(configs, enabledServers);
|
|
129
145
|
Logger.log(`Hot-reloaded SSH config: ${Object.keys(configs).length} server(s), ` +
|
|
130
|
-
`${
|
|
146
|
+
`${toReset.size} connection(s) reset`, "info");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Walk `name`'s jump chain in `configMap` and report whether any hop is in the
|
|
150
|
+
* `changed` set. Guards against cycles defensively (config load rejects them).
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
jumpChainTouchesChanged(name, configMap, changed) {
|
|
154
|
+
const seen = new Set([name]);
|
|
155
|
+
let hop = configMap[name]?.jumpHost;
|
|
156
|
+
while (hop !== undefined) {
|
|
157
|
+
if (changed.has(hop))
|
|
158
|
+
return true;
|
|
159
|
+
if (seen.has(hop))
|
|
160
|
+
break;
|
|
161
|
+
seen.add(hop);
|
|
162
|
+
hop = configMap[hop]?.jumpHost;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
131
165
|
}
|
|
132
166
|
/**
|
|
133
167
|
* Set the root directory under which execute-command full-output logs are
|