ssh-for-agents
Enables local Ollama models to execute SSH commands and manage remote files through a function-calling bridge.
Click on "Install 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., "@ssh-for-agentsrun 'uptime' on myserver"
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.
ssh-for-agents
An MCP (Model Context Protocol) server that lets AI agents talk to SSH servers — run commands, read files, and list directories on remote hosts — behind a configurable command-safety policy.
Because it speaks MCP, any MCP-capable agent (Claude Desktop, Claude Code, or your
own client) can use it with no glue code. Connections are async (built on
asyncssh) and pooled per host.
AI agent ──MCP(stdio)──▶ ssh-for-agents ──asyncssh──▶ remote host(s)
│
guardrails policy
(readonly / guarded / unrestricted)Quickstart
git clone https://github.com/WilliamSmithEdward/pySSHForAgents.git
cd pySSHForAgents
python -m venv .venv
.venv\Scripts\Activate.ps1 # macOS/Linux: source .venv/bin/activate
pip install -e .Store your SSH key's passphrase in an environment variable (it's never written to a
config file) — Windows setx NAME "value" then open a new terminal, macOS/Linux
export NAME=value — then register a host and test it end to end:
ssh-for-agents-config add myserver --hostname 203.0.113.10 --user deploy \
--passphrase-env MYSERVER_SSH_PASSPHRASE
ssh-for-agents-run myserver "uptime"That's it. Now point your agent at it — see Connect to Claude below, or Codex / a local Ollama model. The full walkthrough (keys, host-key verification, every agent) is in SETUP.md.
Related MCP server: MCP SSH Server
Tools exposed to the agent
Tool | What it does |
| List configured hosts and the active safety policy. |
| Dry-run the policy for a command (allow / needs_confirmation / block). |
| Run a shell command on a host. Destructive commands need |
| Read a remote file over SFTP (size-capped). |
| List a remote directory over SFTP. |
Requirements
Python 3.10+
An SSH key (password auth is not wired up by design — use keys).
Install
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -e .asyncssh needs bcrypt to decrypt passphrase-protected OpenSSH keys; it's a
declared dependency, so the install above pulls it in.
Configure
Copy the example and edit it:
cp hosts.example.json hosts.json{
"hosts": {
"myserver": {
"hostname": "203.0.113.10",
"username": "deploy",
"port": 22,
"private_key": "~/.ssh/id_ed25519",
"passphrase_env": "MYSERVER_SSH_PASSPHRASE",
"known_hosts": "~/.ssh/known_hosts",
"verify_host_key": true
}
},
"policy": {
"mode": "guarded",
"extra_denylist": [],
"extra_readonly_allow": [],
"max_output_bytes": 100000,
"default_timeout": 60
}
}Host fields:
private_key— path to the private key (~is expanded).passphrase_env— name of an environment variable holding the key's passphrase. The passphrase itself is never stored in the config. Omit if the key has no passphrase.known_hosts— path to a known_hosts file used to verify the server's host key. Omit to use the default (~/.ssh/known_hosts+ system files).verify_host_key— setfalseto skip host-key verification (insecure; only for throwaway hosts).
The server looks for hosts.json in the working directory, or wherever
SSH_AGENT_CONFIG points.
hosts.jsonis git-ignored since it describes your infrastructure. Commithosts.example.jsoninstead.
Adding & managing hosts
The config holds any number of hosts — add as many as you like under hosts.
Manage them with the bundled CLI instead of hand-editing JSON:
# add or update a host
ssh-for-agents-config add prod \
--hostname 203.0.113.10 --user deploy \
--key ~/.ssh/id_ed25519 --passphrase-env PROD_SSH_PASSPHRASE
ssh-for-agents-config list # show configured hosts + policy
ssh-for-agents-config remove prod # drop a host
ssh-for-agents-config import-ssh-config # pull hosts from ~/.ssh/config--config <path> (or $SSH_AGENT_CONFIG) targets a specific hosts.json.
Hot-reload: a running server watches hosts.json and reloads it when it
changes, so a newly added host is usable on the next tool call — no restart needed.
Live connections to unchanged hosts are kept; a half-written or invalid edit is
ignored (the last good config stays active).
Shell CLI — for agents that can't call MCP tools
Some agents run shell commands but can't reliably call MCP tools — notably a local
(Ollama) model in Codex, which emits tool-call names the host rejects. For those,
the same hosts and safety policy are exposed as a plain command, ssh-for-agents-run:
ssh-for-agents-run hosts # list aliases + policy
ssh-for-agents-run linode "df -h" # run a command (default verb)
ssh-for-agents-run linode "rm /tmp/x" --confirm
ssh-for-agents-run check linode "rm -rf /" # dry-run the policy
ssh-for-agents-run read linode /etc/os-release
ssh-for-agents-run ls linode /var/logIt reuses hosts.json and the guardrails, and on Windows resolves the key passphrase
from the persisted user environment even when the launching shell filters env vars.
See SETUP.md for wiring it into Codex (AGENTS.md + giving the sandbox read
access to ~/.ssh).
Run
ssh-for-agents # console script (serves over stdio)
python -m ssh_agent_mcp # equivalentThe server communicates over stdio — it's normally launched by an MCP client rather than run by hand.
Connect to Claude Code
claude mcp add ssh-for-agents -- /abs/path/to/.venv/Scripts/python.exe -m ssh_agent_mcpSet the working directory (or SSH_AGENT_CONFIG) so it finds hosts.json.
Connect to Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"ssh-for-agents": {
"command": "C:\\path\\to\\.venv\\Scripts\\python.exe",
"args": ["-m", "ssh_agent_mcp"],
"env": {
"SSH_AGENT_CONFIG": "C:\\path\\to\\hosts.json",
"MYSERVER_SSH_PASSPHRASE": "..."
}
}
}
}Use it from a local Ollama model
Ollama runs models and does function calling, but it is not an MCP client — so a
small host/bridge sits between them: it launches this server, hands the model the
tools, runs the tool-calling loop, and routes tool calls back. A working one is
included at examples/ollama_bridge.py:
pip install -e ".[examples]" # adds the `ollama` client library
ollama pull qwen3-coder:30b # any tool-calling model works
python examples/ollama_bridge.py "what is the uptime and disk usage on linode?"The model discovers hosts via list_hosts and runs commands via run_command. When
the policy returns needs_confirmation, the bridge prompts you on the terminal
rather than letting the model authorize its own destructive command. Pick a model
with OLLAMA_MODEL=...; it must support tool calling (qwen3-coder, llama3.1/3.2,
mistral-nemo, …).
Ollama model ◀──function calling──▶ bridge ◀──MCP(stdio)──▶ ssh-for-agents ──▶ hostSafety model
The policy runs before any command reaches the server. Pick a mode:
Mode | Behavior |
| Only commands on a read-only allowlist run; everything else is blocked. |
| (default) Most commands run freely. Destructive/state-changing commands return |
| Anything runs. Only sensible for disposable/sandboxed hosts. |
In guarded mode:
Needs confirmation:
rm,dd,mkfs,shutdown/reboot,kill,mount, firewall edits,crontab, package installs/removals (apt install, …), service changes (systemctl stop, …),git push/reset,curl … | sh, recursivechmod/chown, redirects into system paths, and more.Hard-blocked (cannot be confirmed away): fork bombs, recursive deletes of root paths (
rm -rf /,/home, …), and writing/formatting raw disk devices (dd of=/dev/sda,mkfs … /dev/nvme0n1).Commands are parsed into segments across
;,&&,||, and pipes, andsudo/envwrappers are seen through, socd /x && sudo rm yis still flagged.
Extend it without editing code via extra_denylist (more binaries that need
confirmation) and extra_readonly_allow (more binaries allowed in readonly mode).
Important — this is a safety net, not a sandbox. String-based inspection of shell commands can always be evaded by a determined caller (
base64 … | sh, exotic quoting, writing then running a script, etc.). It exists to prevent accidents and obvious mistakes. For a real trust boundary, connect as a dedicated unprivileged user and constrain it with OS permissions and/or sshdForceCommand. Treat the mode as defense-in-depth, not as containment of an adversarial agent.
Develop & test
pip install -e ".[dev]"
pytest # guardrail policy tests (no SSH needed)Project layout
ssh_agent_mcp/
guardrails.py # command-safety policy engine (pure, well-tested)
config.py # hosts.json loading + validation
ssh_client.py # asyncssh connection pool (run / read_file / list_dir)
server.py # FastMCP server + tools (with hosts.json hot-reload)
manage.py # `ssh-for-agents-config` CLI: add / list / remove / import hosts
run_cli.py # `ssh-for-agents-run` CLI: run SSH from the shell (Codex/local models)
tests/
test_guardrails.py
test_manage.py
test_run_cli.py
examples/
ollama_bridge.py # drive a local Ollama model with these tools
hosts.example.json
SETUP.md # step-by-step reproduction guideAvailable Tools
5 toolscheck_commandA
Dry-run the safety policy for a command without executing it.
Returns the verdict (allow / needs_confirmation / block), a
human-readable reason, and which rules matched. Use this to understand why
a command would be refused before calling run_command.
| Name | Required | Description | Default |
|---|---|---|---|
| host | Yes | ||
| command | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden. It discloses that the tool does not execute the command (non-destructive), returns a verdict, reason, and matched rules. However, it does not mention any side effects or whether it requires specific permissions, but for a dry-run tool this is sufficient.
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, front-loaded with the key action and outcome. Every sentence adds value with no 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?
Given the presence of an output schema (implied from context signals), the description provides adequate high-level information about return values. It could mention pagination or error cases, but for a simple check tool this is complete enough.
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 0%, and the description does not explain the parameters 'host' and 'command'. While the names are somewhat self-explanatory, the description should add context like format or examples to compensate for the lack of schema descriptions.
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 clearly states the tool's purpose: dry-run safety policy without executing. It specifies the verb 'check' and resource 'command safety policy', and distinguishes from sibling run_command by contrasting execution vs dry-run.
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 explicitly says 'Use this to understand why a command would be refused before calling run_command.' This provides clear guidance on when to use this tool and implies not to use it when you intend to execute the command.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_dirB
List the entries of a remote directory over SFTP.
| Name | Required | Description | Default |
|---|---|---|---|
| host | Yes | ||
| path | No | . |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden. It indicates a read-only listing operation, which is inherently non-destructive. However, it does not mention permissions, error handling, or any side effects, leaving some ambiguity.
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 a single, clear sentence with no unnecessary words. It is front-loaded and efficiently conveys the tool's purpose.
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 simplicity of the tool and the presence of an output schema (for return format), the description is minimally complete. However, it lacks details about hidden files, sorting, error conditions, or path handling.
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 0%, and the description does not explain the parameters (host, path) beyond their names. While the names are somewhat self-explanatory, the description adds no additional semantic context about expected values or formats.
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 clearly states it lists directory entries over SFTP, with a specific verb and resource. It implicitly differentiates from sibling tools like read_file (file content) and run_command (command execution), though it doesn't explicitly compare.
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?
No guidance is given on when to use this tool versus alternatives, nor are there prerequisites or exclusions provided. The description lacks any when-to-use or when-not-to-use context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_hostsA
List the SSH hosts this server can reach and the active safety policy.
Call this first: use the returned alias values as the host argument for
the other tools. Never guess a hostname or IP — only configured aliases work.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but the description explains the output usage (aliases for other tools) and implies a read-only operation. It does not detail side effects or idempotency, but the context is sufficient for a simple list tool.
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 concise sentences, front-loaded with purpose and immediate usage guidance. No wasted words.
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 no parameters, output schema exists, and sibling tools are provided, the description fully covers the tool's role and integration with other tools. It tells the agent exactly when and how to use it.
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?
No parameters exist, so baseline is 4. The description adds value by explaining the purpose of the output, which goes beyond the empty 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?
Clearly states it lists SSH hosts and active safety policy, with a specific verb and resource. Differentiates from siblings by indicating it's the first call to provide aliases for other tools.
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?
Explicitly says 'Call this first' and instructs to use returned alias values as host argument for other tools. Also warns against guessing hostnames, providing clear when-to-use and what not to do.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_fileA
Read a remote file's contents over SFTP (size-capped).
Args: host: Alias of a configured host. path: Absolute or relative remote path. max_bytes: Max bytes to read (defaults to policy max_output_bytes).
| Name | Required | Description | Default |
|---|---|---|---|
| host | Yes | ||
| path | Yes | ||
| max_bytes | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden. It discloses key behaviors: reading over SFTP, size cap via max_bytes, and default policy. However, it omits error handling, authentication needs, and handling of missing files or binary content.
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 paragraphs: a one-sentence summary followed by a concise argument list. Every sentence adds value, no fluff, and the purpose is front-loaded.
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 presence of an output schema (which explains return values), the description covers purpose, parameters, and behavior adequately. It lacks guidance on error conditions or security, but for a simple read tool it is nearly complete.
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?
Since schema property descriptions are missing (0% coverage), the description adds meaning by explaining each parameter: host as alias, path as absolute/relative, max_bytes with default behavior. This compensates well, though some details (e.g., host alias format) are absent.
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 clearly states the verb (Read), resource (remote file), method (over SFTP), and constraint (size-capped). It distinguishes this tool from siblings like list_dir and run_command by specifying its exact function.
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 the tool is for reading files, but does not explicitly state when to use alternatives (e.g., list_dir for directory listings) or when not to use it. No prerequisites mentioned, but the purpose is clear enough for basic selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_commandA
Run one shell command on an SSH host, subject to the safety policy.
host must be an alias from list_hosts (not a raw hostname/IP). Read
exit_status, stdout, and stderr from the result. The result status:
"ok": executed.
"needs_confirmation": destructive command — explain it to the user and only re-call with confirm=true after they approve; do not retry blindly.
"blocked": catastrophic; cannot run — use a safer approach.
"error": see
reason(unknown host, timeout, connection failure).
Args: host: Alias of a configured host (call list_hosts to discover them). command: A single shell command to execute. confirm: Authorize a command previously flagged needs_confirmation. timeout: Per-command timeout in seconds (defaults to policy setting).
| Name | Required | Description | Default |
|---|---|---|---|
| host | Yes | ||
| command | Yes | ||
| confirm | No | ||
| timeout | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
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. It details the behavior: subject to safety policy, possible statuses (ok, needs_confirmation, blocked, error), and how to handle destructive commands. It could mention more about the safety policy, but it's adequately transparent.
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 well-structured with a main sentence, a bullet list for result status, and a numbered list for parameters. It is concise yet comprehensive, with no wasted words.
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 complexity (4 parameters, output schema exists), the description covers all essential aspects: host source, command execution, status handling, and parameter details. It explains the output structure indirectly by mentioning exit_status, stdout, stderr, which is sufficient for an agent to use the tool correctly.
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?
The description provides rich semantics for each parameter beyond the schema: host must be an alias (not raw), command is a single shell command, confirm authorizes previously flagged commands, and timeout has a default and per-command context. With 0% schema coverage, this fully compensates.
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 clearly states 'Run one shell command on an SSH host', which is a specific verb-resource pair. It distinguishes from sibling tools like check_command and list_hosts by specifying that the host parameter must be an alias from list_hosts, and outlines the output structure.
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 provides clear guidance on when to use the tool, including that host must be an alias from list_hosts, and explains the result statuses and how to handle the 'needs_confirmation' status with a confirmation flow. It lacks explicit exclusion of other cases but is sufficient.
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. Dates show when Glama detected each change.
5 tool updates
v0.1.0- First observed
check_command - First observed
list_dir - First observed
list_hosts - First observed
read_file - First observed
run_command
TDQS
Each tool has a clearly distinct purpose: check_command is for policy dry-run, list_dir for directory listing, list_hosts for host discovery, read_file for file reading, and run_command for command execution. There is no overlap.
All tool names follow a consistent verb_noun pattern (e.g., check_command, list_dir) using snake_case. No mixing of conventions.
With 5 tools, the server is well-scoped for its purpose of secure SSH command execution with file reading and directory listing. Each tool serves a necessary role without redundancy.
The tools cover the core workflow of host discovery, command safety checking, command execution, and file reading. However, there is a notable gap: no tool for writing or uploading files, which may limit some use cases.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
- emisarOAuthdev.emisar
Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.
Scoped, audited SSH exec, sessions, and SFTP on your saved servers without exposing credentials
Provides capabilities that let LLM agents perform a range of infrastructure management tasks.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI assistants to securely execute remote SSH commands, perform file transfers, and monitor system status through a standardized interface. It features robust security controls including command whitelisting, blacklisting, and credential isolation to prevent unauthorized operations.1029MIT
- AlicenseNot gradedqualityFmaintenanceEnables AI assistants to execute commands and transfer files on remote servers over SSH connections.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to securely execute commands, transfer files, and manage port forwarding on remote servers via SSH.16836Apache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables AI to execute commands on remote hosts via SSH, supporting password and key authentication.-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/WilliamSmithEdward/pySSHForAgents'
If you have feedback or need assistance with the MCP directory API, please join our Discord server