remote-ssh-server
You can manage SSH hosts and run remote SSH operations through MCP tools.
List hosts, receiving only
sys_nameanduid(thehost_idfor other tools).Create, update, and delete host entries; passwords/private keys are encrypted at rest and never returned.
Run a single non-interactive remote command with stdout/stderr/exit code and a required 1–600s timeout.
Run interactive commands using ordered
expect/answerprompt pairs.Execute multi-step playbooks with structured
# @...directives or legacyexpect:/answer:syntax; stops on first failure.Get topic-filtered help for the companion
remote-ssh-cli.Monitor connection-pool status via
pool_statusper the README, though that tool is absent from the provided schema.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@remote-ssh-serverList my SSH hosts, then run df -h on prod-web"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-ssh-server
A standalone stdio MCP server that turns SSH host management and remote command execution into MCP tools. It also ships an interactive / batch CLI (
remote-ssh-cli) for managing SSH hosts on the local machine.简体中文文档见 README.zh-CN.md。
This is a Node.js re-implementation of the SSH-host half of a FastAPI project
(app/services/llm_tools.py + app/utils/ssh_helper.py + app/models/host.py) —
without the backend, without the database, without a network service. Any MCP
client (Claude Desktop, Continue, a custom agent, …) can start it with npx
and talk to it over JSON-RPC.
Highlights
Fully standalone — its own encrypted JSON store (
~/.mcp-ssh-server/hosts.json); no FastAPI backend, no database.No native dependencies — Node's built-in
cryptofor AES-256-GCM, pure-JSssh2for SSH; runs on Windows / macOS / Linux.Standards-compliant stdio MCP — speaks the Model Context Protocol stdio transport and can be launched with
npx.Credentials never leave the server — host tool responses never contain a password or a private key; the SSH tools decrypt them internally to open a connection and never echo them back.
Connection pooling — one live SSH session per host, reused across commands and playbook steps.
Pool monitoring —
pool_statusrenders the live pool as a desensitized table: uid, name, connection state, executed / failed commands and the last command (secrets and IP addresses masked).CLI companion —
remote-ssh-climanages the very same store (interactive TUI, batch commands, CSV bulk import) and can monitor the connection pool live (one frame per second,qquits).
Related MCP server: SSH MCP Server
MCP tools
Nine tools are published:
Tool | Description |
| Installation / usage / troubleshooting notes for |
| List the known hosts — only |
| Add a host (password / private key / passphrase are encrypted at rest) |
| Patch host fields (omitted credential fields keep their current value) |
| Delete a host |
| Run a single non-interactive command on a host |
| Run a command that answers prompts ( |
| Run a multi-step playbook (structured |
| Monitor the live connection pool — one desensitized row per host (uid, name, state, executed / failed commands, last command) |
The host identifier contract
host_listis the entry point: every host comes back as exactly two fields —sys_name(the name) anduid(the unique identifier).Every other tool takes that same value under the name
host_id. Pass it back unchanged; never assemble or guess one.When an id does not resolve, the tool answers with an actionable error:
Host '<id>' not found. host_id must be the exact 'uid' value returned by host_list — call host_list to obtain the valid identifiers.Callhost_listagain and retry with the value it returns.host_getis deliberately not published to MCP clients: the model only ever seessys_name+uid, never one host's address, user name or credential. The CLI keeps its owngetsubcommand for human operators.
Arguments at a glance
Tool | Arguments |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ip_address is unique across the store — creating a duplicate fails.
Command results come back as a plain text block:
[stdout]
hello
[stderr]
[exit_code=0 success=true duration_ms=42]Quick start
git clone https://github.com/xlbbb-cn/mcp_ssh_exec_cmd.git
cd mcp_ssh_exec_cmd
npm install
npm run buildThe build output lands in dist/ and provides two binaries:
Binary | Entry point | Purpose |
|
| the stdio MCP server (started by your MCP client) |
|
| interactive / batch host-management CLI |
Install
The package is published to two registries under the same name,
@xlbbb-cn/mcp_ssh_exec_cmd:
Registry | Install command | Auth |
npmjs.org (recommended) |
| none — the public registry needs no token |
GitHub Packages |
| a personal access token (classic) with |
GitHub Packages requires authentication for installs as well — even for public packages — so give npm a token first:
npm login --scope=@xlbbb-cn --auth-type=legacy --registry=https://npm.pkg.github.com
# Username: <your GitHub username>
# Password: <your PAT>The same thing can be written into ~/.npmrc:
//npm.pkg.github.com/:_authToken=<PAT>
@xlbbb-cn:registry=https://npm.pkg.github.comGlobal install (CLI)
From a local checkout:
npm run build
npm install -g --allow-scripts=ssh2,cpu-features .Or straight from a registry:
npm install -g @xlbbb-cn/mcp_ssh_exec_cmd # npmjs.org
npm install -g @xlbbb-cn/mcp_ssh_exec_cmd --registry=https://npm.pkg.github.com # GitHub PackagesAfterwards remote-ssh-cli is on your PATH:
remote-ssh-cli # interactive TUI
remote-ssh-cli list # JSON list of every host
remote-ssh-cli get <host_id>
remote-ssh-cli create <json-file>
remote-ssh-cli update <host_id> <json-file>
remote-ssh-cli delete <host_id>
remote-ssh-cli import <csv-file> [--dry-run]
remote-ssh-cli monitor # live connection-pool monitor (q quits)create / update take a JSON file, e.g.:
{
"ip_address": "192.168.1.100",
"system_name": "web-01",
"ssh_port": 22,
"ssh_username": "root",
"ssh_auth_method": "password",
"ssh_password": "your-password"
}CSV bulk import
remote-ssh-cli import hosts.csv turns every row of the file into one host.
The first line must be a header row containing at least an ip_address column:
ip_address,system_name,ssh_port,ssh_username,ssh_auth_method,ssh_password
192.168.1.100,web-01,22,root,password,secret
192.168.1.101,db-01,2222,deploy,password,secretColumn | Description | Default |
| required, host address, unique across the table | — |
| display name | empty |
| SSH port |
|
| SSH user name |
|
|
|
|
| plain-text password (encrypted before it hits the disk) | — |
| plain-text private key (encrypted before it hits the disk) | — |
| path to a key file (alternative to | — |
| passphrase protecting the key (encrypted before it hits the disk) | — |
|
|
|
Column names are case-insensitive and accept aliases (
ip,host,name,port,user,password, …).Quoted commas, embedded newlines and
""escapes are parsed per RFC 4180, so private key material is never mangled.A bad row never aborts the batch: every row ends up reported as created / skipped (duplicate IP) / failed (invalid data). The command prints a summary and exits with code
1when at least one row failed.--dry-runonly validates and reports — nothing is written.The CSV template (header + example row) is printed in the
remote-ssh-clibanner, andremote-ssh-cli importwithout a file argument prints the same thing.
Windows note: if you hit
JScript runtime error (800A03EA), make sure npm is >= 10.8.0 or run the command from cmd.exe instead of PowerShell — the.ps1wrapper npm generates may not invokenodeexplicitly on older npm versions. Both entry points ship a#!/usr/bin/env nodeshebang.
Monitor the connection pool
remote-ssh-cli monitor opens a live view of the SSH connection pool. The
screen is redrawn once per second and the view is left with the q
key (Ctrl+C works as well):
SSH connection pool monitor · 15:05:31 · tick 42 · refresh 1000ms · last tick 41ms
pool 2/32 connections · idle timeout 1m00s · probe timeout 5s · press q to quit
HOST ADDRESS USER STATUS LATENCY LAST OK POOL
------------ ------------ ----- -------- ------- ------- ----
web-01 192.168.1.100 root up 12ms 15:05:30 1m05s
db-01 10.0.0.5:2222 deploy down 8ms - -
problems (1):
db-01 (10.0.0.5:2222): connect ECONNREFUSED 10.0.0.5:2222Column | Meaning |
|
|
| how long the last probe took, end to end |
| clock time of the last successful probe |
| age of the pooled connection — how long the session has been reused without being re-established |
The monitor keeps the pool warm: every host is probed through the very same
ConnectionPoolthe SSH tools use, so the connections it shows are real, reusable sessions.The CLI runs its own pool — the MCP server is a separate process, so its connections cannot be inspected from the terminal.
The default probe command is
echo(it exists in a POSIX shell and incmd.exe); every probe runs once per interval, so keep--intervalreasonable on large host lists.Without a terminal (a pipe, a CI job) the monitor prints a single frame instead of looping — there would be no key to stop it.
Option | Description | Default |
| redraw interval |
|
| per-host probe timeout (1-600 s) |
|
| command used to probe a host |
|
| monitor only this host (repeatable) | all hosts |
| also list disabled hosts (never probed) | off |
| print a single frame and exit | off |
The same view is available from the interactive TUI (7) Monitor connection pool).
Register it with an MCP client
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or
%APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "-p", "@xlbbb-cn/mcp_ssh_exec_cmd", "remote-ssh-server"]
}
}
}On GitHub Packages, add the registry to the same snippet:
"args": [
"--registry=https://npm.pkg.github.com",
"-y",
"-p",
"@xlbbb-cn/mcp_ssh_exec_cmd",
"remote-ssh-server"
]The file: form points npx straight at your local checkout — handy while
developing, and it needs no registry at all:
"args": ["-y", "-p", "file:./mcp_ssh_exec_cmd", "remote-ssh-server"]Configuration
Everything is configured through environment variables:
Variable | Description | Default |
| 32-byte key as a hex string (64 hex characters). Back this up: changing it — or moving to another machine — makes every credential in | generated on first start and stored in |
| override the configuration directory |
|
| how long an idle pooled connection is kept (ms). |
|
| how many hosts the pool keeps alive (the least recently used idle connection is evicted beyond this) |
|
SSH connection pool
Every host keeps one SSH connection; consecutive commands and the steps of a playbook reuse it instead of reconnecting.
The pool is keyed on the host id plus a fingerprint of the credentials. Changing the address, port, user name, auth method, password or key invalidates the cached connection right away;
host_updateandhost_deletedrop it explicitly.Idle connections are closed after
MCP_SSH_POOL_IDLE_MS. A connection the peer dropped in the meantime is transparently re-established on the next use (one retry).All pooled connections are closed when the process exits (i.e. when the MCP client disconnects).
remote-ssh-cli monitorshows the pool live (see Monitor the connection pool); it honours the same two environment variables.
Monitoring the pool from MCP
pool_status renders the state of the MCP server's own pool as a table —
one row per host that has a pooled connection or command traffic:
SSH connection pool · 1/32 connections · idle timeout 1m00s · 2 commands executed · 1 failed · updated 15:24:16
Commands and error messages are desensitized (secrets masked, IP addresses replaced).
UID SYS_NAME STATE EXECUTED FAILED LAST CMD LAST RESULT LAST ERROR LAST RUN POOL AGE
------------------------------------ -------- ------ -------- ------ ----------------------- ----------- ----------------------------- -------- --------
9bcb7682-537b-4837-8b9e-6e37b4c61cdc web-01 pooled 3 1 systemctl restart nginx exit=1 connect to *** port 22 failed 12s ago 1m05sColumn | Meaning |
| the host id — the same value |
| the host name |
|
|
| how many commands ran on the host, and how many of them failed |
| the last command and its outcome ( |
| the redacted error message of the last failure |
| how long ago the last command ran |
| how long the pooled connection has been reused |
The table is desensitized: credentials are masked (
mysql -pS3cret→mysql -p***), IP addresses are replaced (10.0.0.5→***), and no address, port, user name or credential of a host is ever shown.format: "json"returns the same information as a JSON document,include_history: trueadds the most recent commands per host,include_idle_hosts: truealso lists hosts without traffic, andhost_idnarrows the view to a single host.The counters live in the server process: they describe the commands this MCP server ran and are reset when it restarts. The CLI monitor is a separate process and shows its own pool.
Timeout semantics
timeout_seconds is a hard cap: when it expires the command is terminated
and the result reports success=false, exit_code=null plus a note in
stderr. For ssh_run_playbook the value is the budget for the whole
playbook — every step only gets the time that is left.
Playbook format
ssh_run_playbook accepts a multi-line body. Two formats are supported.
Structured — directives start with # @:
# @section "Restart nginx"
# @desc "Reload the reverse proxy after the config change"
systemctl stop nginx
# @on "systemctl start nginx"
expect: [sudo] password for deploy:
answer: secret
# @note "the site is back up"
# @danger "this drops active connections"Directive | Meaning |
| group the following steps under a section |
| description of the current section |
| note attached to the current step |
| danger warning attached to the current step |
| run this command interactively (answers the |
Legacy — one command per line, optionally followed by expect: / answer: pairs:
sudo systemctl restart nginx
expect: [sudo] password for deploy:
answer: secretOrdinary # comments are ignored. A playbook stops at the first non-zero exit
code and returns the transcript of every step that ran.
Storage & security
hosts.json— all host records as plain JSON. The metadata fields are stored in clear text;ssh_password,ssh_private_keyandssh_private_key_passphrasehold base64 ciphertext.master.key— the 32-byte master key (mode0o600on POSIX).Writes go through a
.tmpfile plus a rename, so a crash never leaves a half-written file behind.
Secrets are encrypted with AES-256-GCM using a key derived from the master
key with PBKDF2-SHA-256 (200k iterations, 16-byte random salt). The layout
is [16-byte salt][12-byte IV][16-byte tag][ciphertext]; salt and IV are
fresh for every encryption, so the same plaintext never produces the same
ciphertext twice.
Keep the whole directory out of version control:
# mcp-ssh-server state
.mcp-ssh-server/Project layout
src/
index.ts MCP entry point — stdio transport, tool registration, dispatch
cli.ts remote-ssh-cli — interactive TUI + batch subcommands
monitor.ts live connection-pool monitor (one frame per second, q quits)
pool.ts ConnectionPool — one live SSH connection per host + the command counters
ssh.ts ssh2 execution primitives + playbook parser/runner
storage.ts HostStore — hosts.json load/save with an atomic full-file rewrite
csv.ts RFC 4180 CSV reader + host-row mapping
importer.ts bulk-import orchestration
crypto.ts AES-256-GCM encryption of secrets at rest
master_key.ts master-key resolution (env var or key file)
paths.ts configuration-directory paths
redact.ts masks secrets and IP addresses in commands and error messages
tools/
_types.ts shared MCP tool shape + the host-identifier wording
hosts.ts host_list / host_create / host_update / host_delete
ssh_exec.ts ssh_run_command / ssh_run_interactive / ssh_run_playbook
pool_status.ts pool_status — the desensitized pool table
help.ts cli_help
test/
smoke.mjs encryption round-trip, HostStore CRUD, playbook parsing
features_smoke.mjs CSV parsing/import, connection pool behaviour
monitor_smoke.mjs monitor frames, the refresh loop, the 'q' shortcut
mcp_smoke.mjs end-to-end JSON-RPC handshake against a spawned serverDevelopment
npm run dev # tsc --watch
npm run build # tsc (the only typecheck)
npm test # all four smoke testsnpm run build is the verification step — there is no separate lint or format
configuration. The tests execute dist/, so build before testing.
Smoke tests
npm test runs four plain Node scripts; none of them needs a real SSH server:
test/smoke.mjs— encrypt/decrypt round-trip, the full HostStore CRUD path (encrypt on write, read back, list, update, delete) and playbook parsing for both formats.test/features_smoke.mjs— CSV parsing and host-row mapping, bulk import (including--dry-runand duplicate-IP skipping), connection-pool reuse / credential-change invalidation / idle eviction, playbook parsing edge cases, the redaction of commands and errors, and thepool_statustable.test/monitor_smoke.mjs— the connection-pool monitor: frame rendering, the one-frame-per-interval loop, theqshortcut, the SSH probe (against a fake client) and the pool snapshot.test/mcp_smoke.mjs— spawns the MCP server as a child process, performs the JSON-RPC handshake and asserts that all 9 tools are registered, thathost_listexposes onlysys_name+uid, thathost_getis not advertised, thatpool_statusreturns a desensitized table, and that no password ever appears in a response.
Run a single file with node test/<file>.mjs. Every test isolates its state in
a temporary MCP_SSH_CONFIG_DIR, so your real hosts.json is never touched.
Releasing
Releases are published to npmjs.org, GitHub Packages and the
official MCP Registry (registry.modelcontextprotocol.io) by a GitHub
Actions workflow (.github/workflows/publish.yml). The two package jobs
build, run the smoke tests and publish with npm provenance attached to the
tarball; the third job then publishes server.json to the MCP Registry,
which is what directories mirroring the registry (Glama, PulseMCP, …)
list the server from.
The MCP Registry job runs after the npmjs.org job because the registry
verifies npm ownership by reading the mcpName marker out of the published
package: mcpName in package.json must always equal server.json's
name (asserted by test/features_smoke.mjs), and CI re-syncs the version
in server.json from package.json before publishing.
# 1. bump the version in package.json, commit
git commit -am "chore: release v0.1.1" && git push github master
# 2. tag it — the tag triggers the publish workflow
git tag v0.1.1
git push github v0.1.1Registry | Credential | Where it comes from |
GitHub Packages |
| built into the workflow, nothing to configure |
npmjs.org | trusted publishing (OIDC) | configured once on npmjs.org: Packages → |
npmjs.org (fallback) |
| repository secret (Settings → Secrets and variables → Actions). Must be a granular npm token with Read and write on the |
MCP Registry | GitHub OIDC | built into the workflow, no secret: the |
Without either the NPM_TOKEN secret or a configured trusted publisher the
npmjs.org job fails with an actionable error message.
The fallback token can be created from the CLI (it needs your 2FA code once):
npm token create --scopes=xlbbb-cn --packages-and-scopes-permission=read-write \
--bypass-2fa --otp=<your-2fa-code>--bypass-2fa is what keeps npm from asking for a one-time password on every
publish; without it the registry answers with EOTP.
Once trusted publishing works, the NPM_TOKEN secret can be deleted and the
package's Publishing access can be set to "Require two-factor
authentication and disallow tokens".
The workflow can also be started by hand from the Actions tab
(workflow_dispatch); tick dry_run to verify the tarball without publishing.
The published packages show up under https://github.com/xlbbb-cn/mcp_ssh_exec_cmd/packages and https://www.npmjs.com/package/@xlbbb-cn/mcp_ssh_exec_cmd.
Publishing to npmjs.org requires an npm account that owns the
@xlbbb-cnscope (an npm user or organization namedxlbbb-cn).
Relation to the FastAPI backend
Aspect | Python backend | This MCP server |
Storage | PostgreSQL / SQLite ( | local JSON file ( |
Encryption | Fernet (AES-128-CBC + HMAC) | AES-256-GCM + PBKDF2 |
SSH client | paramiko | ssh2 |
Protocol | HTTP REST ( | MCP (JSON-RPC over stdio) |
AuthN/AuthZ | RBAC ( | single-user local process, protected by file-system permissions |
Sharing one storage backend is deliberately avoided: as soon as a database or an API is involved, this server can no longer run standalone — and pointing both the MCP server and the backend at the same JSON file would create consistency problems.
To migrate hosts from the FastAPI backend, write a one-off script: list
/api/v1/hosts(requireshost:secure:read) and feed the same field shape into this server'shost_create.
Known limitations
The host data lives in a single JSON file that is rewritten in full on every change — fine for hundreds of hosts, not for thousands.
There is no concurrent-write protection: do not run two instances against the same
MCP_SSH_CONFIG_DIRat the same time.There is no audit log or operation trail. If you need one, capture the SSH output in the calling agent's transcript.
The MCP surface intentionally exposes no per-host metadata, so a model cannot look up a host's address or user name — it has to go through
host_list/host_create.
Available Tools
8 toolscli_helpA
Get help on using the remote-ssh-cli command-line tool to manage SSH hosts outside the MCP protocol. Returns installation, usage, and troubleshooting instructions for the standalone CLI.
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Optional topic to filter help: 'install', 'list', 'get', 'create', 'update', 'delete', 'import', 'tui', or 'windows' for troubleshooting. Omit to get the full help text. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It discloses that the tool returns installation, usage, and troubleshooting instructions for the standalone CLI, which is useful context, but it does not mention side effects, permissions, or operational constraints. For a simple help tool this is minimally adequate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no wasted words. The core purpose and return scope are front-loaded, and the outside-MCP context is stated clearly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity, complete schema coverage for its single optional parameter, and the absence of an output schema, the description is nearly complete. It explains what the tool returns and its standalone-CLI scope. A minor gap is the lack of explicit confirmation that the operation is read-only and side-effect-free.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema already documents the optional 'topic' parameter with accepted values and the default behavior when omitted. The description adds no parameter-specific meaning beyond the schema, so the baseline score of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Get help') and resource ('remote-ssh-cli command-line tool') and clarifies the intended context: managing SSH hosts outside the MCP protocol. It distinguishes itself from the host_* and ssh_* siblings by emphasizing the standalone CLI, though it does not explicitly name alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use the tool by mentioning help for the standalone CLI outside MCP, but it does not explicitly state when to prefer this tool over the MCP-based sibling tools or any exclusions. Usage is inferable rather than clearly guided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
host_createA
Create a new SSH host entry. The supplied password or private key is encrypted at rest and never returned by any tool. The response contains the new host's public metadata, including the uid that identifies it in host_list.
| Name | Required | Description | Default |
|---|---|---|---|
| ssh_port | No | SSH port, default 22. | |
| is_enable | No | Whether SSH tools can target this host. Default true. | |
| ip_address | Yes | Unique IP address or hostname of the host. | |
| system_name | No | Human-readable name (e.g. 'web-01'). | |
| ssh_password | No | Plaintext password (encrypted at rest). Required when auth_method=password. | |
| ssh_username | No | SSH username, default 'root'. | |
| ssh_auth_method | No | 'password' (default) or 'pubkey'. | |
| ssh_private_key | No | Plaintext private key (encrypted at rest). Required when auth_method=pubkey. | |
| ssh_private_key_passphrase | No | Passphrase protecting the private key (encrypted at rest). Only used with auth_method=pubkey. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden well by disclosing that passwords/private keys are encrypted at rest and never returned by any tool. It also states that the response contains public metadata including the uid, though it does not cover permissions, duplicate-IP behavior, or idempotency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the core action, followed by security behavior and response identity. Every sentence adds useful information without repetition or waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a create mutation with no annotations and no output schema, the description covers the action, sensitive-data handling, and the key returned identifier. It could say more about prerequisites or duplicate handling, but the detailed schema compensates for most field-level gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the input schema already documents all nine parameters in detail. The description reinforces secret-handling semantics for password/private key fields but does not add parameter-level guidance beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: "Create a new SSH host entry." It also references `host_list` for the uid, helping distinguish the created host from listing behavior.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by "Create a new SSH host entry," but the description does not explicitly say when to use this versus `host_update`, `host_delete`, or other sibling tools. The `host_list` reference explains the returned uid but not tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
host_deleteA
Permanently delete a host entry. Call host_list first — it returns the uid of every host, and that value is what host_id expects.
| Name | Required | Description | Default |
|---|---|---|---|
| host_id | Yes | Identifier of an existing host: the exact `uid` value returned by `host_list`. Call `host_list` first and pass that value unchanged. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations present, the description must carry the behavioral burden; it does disclose that the deletion is permanent, which is the single most important trait. However, it says nothing about required permissions, whether dependent resources (credentials, SSH configs) are removed, or whether confirmation/undo exists — meaningful gaps for a destructive mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, both load-bearing: the first establishes the destructive action, the second the required acquisition path for the identifier. Nothing is padded or repeated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter destructive tool with no annotations and no output schema, the description covers the workflow and irreversibility but leaves the agent uninformed about permissions and side effects on related resources. Adequate but with clear gaps on exactly the dimensions annotations would normally supply.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter's schema description already states it is the exact uid returned by host_list. The description's cross-reference merely restates that, adding no new syntax, format, or validation detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Permanently delete a host entry") and the adverb "permanently" conveys irreversibility up front. It is trivially separable from host_create, host_update, and host_list in the sibling set.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit prerequisite: call host_list first to obtain the uid that host_id expects. It does not name any alternative or when-not case (e.g., use host_update to modify instead of delete), so it stops short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
host_listA
List the SSH hosts known to the server. Each entry contains exactly two fields: 'sys_name' (the host name) and 'uid' (its unique identifier). Start here: pass a 'uid' value to the other tools as their host_id argument. No address, port, user name or credential is returned.
| Name | Required | Description | Default |
|---|---|---|---|
| enabled_only | No | Only return enabled hosts. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does substantive work: it commits to exactly two returned fields and preempts the common misconception by stating no address, port, user, or credential comes back. It does not cover auth requirements, ordering, or empty-list behavior, which keeps it below a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, each doing distinct work: purpose, return shape, and cross-tool routing. The most important routing cue is front-loaded with 'Start here'.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must describe returns, and it does so precisely down to field names and deliberate exclusions. For a one-optional-parameter listing tool, an agent has everything needed to call it and consume the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single 'enabled_only' parameter is documented in the schema itself. The description says nothing about filtering, so it adds no meaning beyond the schema; baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the SSH hosts known to the server') and goes further to define the exact output shape ('sys_name' and 'uid'). It is unmistakably the enumeration tool among siblings that otherwise create, update, delete, or connect to hosts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'Start here' directive plus the explicit instruction to feed the returned 'uid' into other tools' host_id makes the workflow position of this tool clear. There is no explicit when-not-to-use statement, but there is little meaningful alternative to steer away from.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
host_updateA
Update fields on an existing host. Call host_list first — it returns the uid of every host, and that value is what host_id expects. Omit a credential field to keep the current value. Supply an empty string to clear it (only valid if you also flip the auth method).
| Name | Required | Description | Default |
|---|---|---|---|
| host_id | Yes | Identifier of an existing host: the exact `uid` value returned by `host_list`. Call `host_list` first and pass that value unchanged. | |
| ssh_port | No | New SSH port (1-65535). Omit to keep the current value. | |
| is_enable | No | Enable or disable the host. Omit to keep the current value. | |
| ip_address | No | New IP address or hostname. Omit to keep the current value. | |
| system_name | No | New human-readable name. Omit to keep the current value. | |
| ssh_password | No | New plaintext password. Omit to keep current value. | |
| ssh_username | No | New SSH username. Omit to keep the current value. | |
| ssh_auth_method | No | New auth method ('password' or 'pubkey'). Omit to keep the current value. | |
| ssh_private_key | No | New plaintext private key. Omit to keep current value. | |
| ssh_private_key_passphrase | No | New passphrase for the private key. Omit to keep current value. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the behavioral burden. It usefully explains partial update semantics for credentials and the special rule that clearing a value requires flipping the auth method. It does not mention permissions, error behavior, or whether updates are reversible.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the core action, followed by the prerequisite and credential semantics. No wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter mutation tool with complete schema descriptions but no output schema or annotations, the description covers the essential invocation semantics. It does not describe response shape, permission requirements, or side effects beyond credential clearing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents parameters thoroughly. The description adds meaningful semantics beyond the schema by explaining host_id should be the uid from host_list and by clarifying empty-string clearing only when auth method changes.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: updating fields on an existing host. It clearly distinguishes this from creation/deletion siblings by emphasizing existing host and field-level updates, and host_id is explicitly tied to host_list's uid.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a clear prerequisite to call host_list first and explains credential omission/clearing behavior. It does not explicitly contrast with host_create or state when not to use this tool, but the update context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ssh_run_commandA
Run a single non-interactive shell command on a remote host. Returns stdout, stderr, exit code, and duration. Call host_list first — it returns the uid of every host, and that value is what host_id expects. The model must use host_id only; connection credentials are resolved server-side and never returned. The model MUST supply timeout_seconds (1-600) appropriate for the command; the command is terminated when the timeout expires.
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | Shell command to execute on the remote host. | |
| host_id | Yes | Identifier of an existing host: the exact `uid` value returned by `host_list`. Call `host_list` first and pass that value unchanged. | |
| timeout_seconds | Yes | Hard timeout in seconds. REQUIRED. The model must estimate an appropriate timeout based on the command's complexity (1-600 seconds). The command is terminated when the timeout expires. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the return payload (stdout, stderr, exit code, duration), that credentials are resolved server-side and never exposed, and that the command is terminated on timeout. It does not warn that arbitrary remote command execution is potentially destructive or state whether large output is truncated, which are meaningful omissions for a mutation-capable tool with no annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose in the first sentence, followed by return values and then the two hard prerequisites. All sentences are relevant, though the host_list and timeout sentences largely duplicate schema text, which is slight redundancy rather than waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description compensates by enumerating the return fields and the timeout termination behavior. It is nearly complete for a three-parameter execution tool; the remaining gap is how non-zero exit codes and error conditions are surfaced.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so all three parameters are already documented in the schema, including the host_list prerequisite and the 1-600 timeout range. The description restates that same information rather than adding new syntax or format detail, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Run a single non-interactive shell command on a remote host') and the 'single non-interactive' qualifier implicitly distinguishes it from ssh_run_interactive and ssh_run_playbook. An agent can identify the tool's scope without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit prerequisite workflow ('Call host_list first — it returns the uid'), which is genuine when-to-use guidance. It stops short of naming alternatives such as ssh_run_interactive or ssh_run_playbook as the choice for multi-step or interactive work, so the routing to siblings is left implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ssh_run_interactiveA
Run a command on a remote host that needs to consume expect/answer prompts (e.g. sudo). Each entry in 'expects' is an object with 'expect' (the prompt text to wait for) and 'answer' (the response to send back). Call host_list first — it returns the uid of every host, and that value is what host_id expects. The model must use host_id only; credentials are resolved server-side. The model MUST supply timeout_seconds (1-600) appropriate for the command.
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | Command to execute on the remote host. | |
| expects | No | List of {expect, answer} pairs in order. | |
| host_id | Yes | Identifier of an existing host: the exact `uid` value returned by `host_list`. Call `host_list` first and pass that value unchanged. | |
| timeout_seconds | Yes | Hard timeout in seconds. REQUIRED. The model must estimate an appropriate timeout based on the command's complexity (1-600 seconds). The command is terminated when the timeout expires. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. It usefully discloses that credentials are resolved server-side and that timeout_seconds causes command termination, but it does not describe failure behavior, output format, side effects, or the destructive potential of running arbitrary remote commands.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with purpose and then covers the expects mechanism, required host lookup, credential handling, and timeout requirement in four compact sentences. There is little wasted text for a tool with this many calling constraints.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a command-execution tool with no output schema and no annotations, the description gives enough information to call it correctly: host_id provenance, expect/answer structure, credential handling, and timeout. It could still say more about return values or error behavior, but the essential invocation context is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaningful context: host_id must be the exact uid returned by host_list, expects entries are ordered expect/answer pairs, and timeout_seconds should be estimated for the command. These additions go beyond restating the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: running a command on a remote host, scoped to interactive commands that consume expect/answer prompts. The phrase 'needs to consume expect/answer prompts (e.g. sudo)' makes the tool's niche clear enough to distinguish it from a plain non-interactive command runner such as ssh_run_command.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear operational guidance: call host_list first, use host_id only, and always supply timeout_seconds within 1-600. It does not explicitly say when to prefer this over ssh_run_command or ssh_run_playbook, but the interactive-prompt condition strongly implies the selection boundary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ssh_run_playbookA
Execute a multi-step playbook on a remote host. The 'content' field is a multi-line string. Supports structured format with directives: '# @section "Title"' to group steps into sections, '# @desc "description"' for section description, '# @note "text"' for step notes, '# @danger "text"' for danger warnings, and '# @on "command"' for interactive commands (replaces legacy 'expect:'/'answer:' syntax). Legacy format (one command per line with optional 'expect:'/'answer:' pairs) is also supported. Stops on the first non-zero exit code and returns the transcript of every step that ran. Call host_list first — it returns the uid of every host, and that value is what host_id expects. The model must use host_id only; credentials are resolved server-side. The model MUST supply timeout_seconds (1-600) — it is the budget for the WHOLE playbook, each step only gets the time that is left.
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes | Multi-line playbook body. Supports structured directives (# @section, # @desc, # @note, # @danger, # @on) or legacy expect:/answer: pairs. | |
| host_id | Yes | Identifier of an existing host: the exact `uid` value returned by `host_list`. Call `host_list` first and pass that value unchanged. | |
| timeout_seconds | Yes | Hard timeout in seconds. REQUIRED. The model must estimate an appropriate timeout based on the command's complexity (1-600 seconds). The command is terminated when the timeout expires. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses fail-fast semantics (stops on first non-zero exit code), that a transcript of every executed step is returned, and that credentials are handled server-side. It omits any warning that these are arbitrary remote commands with side effects, which is the main residual risk an agent should know about.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, then constraints, then format details; almost every sentence adds operational value. The run of directive definitions is list-like and could be tightened, but it is not padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema, but the description states what comes back (transcript of every step that ran), which covers the key unknown. For a 3-param tool with full schema coverage, remaining gaps (transcript shape, partial-failure return structure) are minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, yet the description still adds meaning the schema lacks: timeout_seconds is a budget for the WHOLE playbook with each step consuming the remainder, and host_id must be the exact uid from host_list rather than a hostname. It also expands the content directives (including that @on replaces legacy expect:/answer:) well beyond the schema's one-line summary.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb and resource ("Execute a multi-step playbook on a remote host"), which implicitly separates it from ssh_run_command (single command) and ssh_run_interactive. It never names those siblings explicitly, so the differentiation is inferred rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Strong prerequisites and constraints: call host_list first, use only host_id, credentials resolved server-side, timeout_seconds is mandatory (1-600). It lacks an explicit "use ssh_run_command instead when you only need one command" exclusion, so the when-not side of the routing decision is left implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.0- First observed
cli_help - First observed
host_create - First observed
host_delete - First observed
host_list - First observed
host_update - First observed
ssh_run_command - First observed
ssh_run_interactive - First observed
ssh_run_playbook
TDQS
Scored across 8 tools
Each tool has a clearly distinct purpose: host CRUD tools operate on host records, while ssh_run_command, ssh_run_interactive, and ssh_run_playbook are separated by execution style (single command, expect-prompt interaction, multi-step playbook). cli_help is distinct as standalone CLI documentation. There is little risk of an agent confusing one tool for another.
All tool names use consistent snake_case with a resource/action structure: host_* for host management, ssh_run_* for remote execution, and cli_help for CLI help. The prefixes cleanly group related operations. No mixed camelCase or inconsistent verb conventions are present.
Eight tools are well-scoped for an SSH host management and command execution server. The host CRUD set is compact, and the three execution modes cover common remote operation needs without excessive surface area. Every tool appears to earn its place.
Host lifecycle coverage is strong with list, create, update, and delete, and execution covers non-interactive commands, interactive prompts, and playbooks. Minor gaps exist, such as no dedicated get-host details tool and no file transfer operations, but core workflows are covered.
Maintenance
Related MCP Connectors
Scoped, audited SSH exec, sessions, and SFTP on your saved servers without exposing credentials
Manage remote servers, SSH terminals, files, databases and proxies with scoped access and approvals.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Hosted MCP for Celesto cloud computers: create, exec, ports, start/stop/delete.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to list hosts, run commands, and orchestrate fleet automation over SSH, with per-host opt-in and approval-gated actions while keeping credentials secure.3MIT
- AlicenseAqualityBmaintenanceEnables AI clients to securely execute remote commands and transfer files over SSH through standardized MCP tools, with multi-host management, flexible authentication, and an admin console.516 npm3ISC
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to manage an entire fleet of servers over SSH through MCP, fanning a single intent out across many hosts with target expressions while enforcing a policy engine, approval gates, and tamper-evident audit logs. Supports rolling execution with circuit breakers, cross-host diffing, bulk file transfer, tmux sessions, and YAML workflows.2 npm17AGPL 3.0
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to list SSH hosts, test reachability, retrieve Linux resource metrics, index and search project files, and run remote SSH commands when allowed, with local-first and safety-focused operations.MIT