
Sshboard
io.github.meta-taroPublished by GitHub account meta-tarov0.1.39Updated Nov 6, 2026
An AI agent reads your server over the one SSH session you are already watching.
Overview
Desktop SSH app with a built-in MCP server that lets an AI agent read your servers over the same SSH session you are watching.
- What you can do
- List connections, browse directories, stat and read files, search, download, and check disk usage
- Inspect processes, service status, runtime versions, logs, and listening network ports
- Run read-only commands only by id from a person-written allowlist, with refusals logged
- Run or propose health checks built from fixed criteria, with results shown on screen
- Run state-changing operations only from a person-written list, each needing on-screen approval and an hourly ceiling
- When to use it
- Useful when you maintain a VPS or shared host and want an agent to look at configs, logs and services instead of you pasting state back and forth. Suited to incident triage and read-only investigation on live servers, with writes kept behind explicit human-written allowlists.
- Requirements
- A desktop app for macOS or Windows (Linux is not a distribution target); the MCP server runs inside the app, not as a separate package. Building from source needs Rust 1.98.0, Node 20 or newer and pnpm; Docker only for the local test server. The agent connects over stdio via the app's proxy or over HTTP on port 22022 with an Authorization Bearer token. SSH keys are best kept in ssh-agent.
Installation
In SourceWeft
- Open Sshboard in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
sshboard
English / 日本語
Your AI agent and you, looking at the same server, on the same screen, over one SSH connection.
You keep your own SFTP client and your own terminal. The agent works beside you — inside a fence you drew — and every single thing it does scrolls past in your window while it happens.
[The connection list, with a connection being edited]
⚠️ This is alpha. It is in use on production servers (investigating a failed certificate renewal, restarting pm2, and so on). Installers are published for Windows (x64 / ARM64) and macOS. Passing tests does not mean it works as a tool — we shipped a build whose terminal displayed not one byte for three days (Issue #10).
⬇ Download · Direction · Every decision, with its reasoning
When you would reach for this
"Renew the certificates on three hosts, today"
You have a terminal open per host and you are copying the same command into each, watching for the one that fails. sshboard opens one console per connection — or two on the same host, side by side — and the agent can read all of them while you work. Not "the agent runs it for you": the agent watches what you are doing and tells you which pane went wrong.
[One shared console; the tabs along the top are the open connections]
"Mail stopped arriving and I don't know where to look"
You ask the agent. It reads /etc/postfix/main.cf, checks the service, tails the log —
over the same SSH session you are already on, and you see each step as it happens.
It cannot run an arbitrary command to do it: there is no run_command(cmd) tool.
"I want help, but not an agent loose on my server"
The agent may write only inside directories you listed, one connection at a time. The list starts empty, so it cannot write a single byte until you say where. Delete, rename, move, chmod, restart, package installs, sudo — none of them exist for the agent. You are not restricted anywhere.
[The two-pane file browser; the agent's writable directory is shown at the top]
Try it in five minutes
Install the app, then point your agent at it. There is nothing else to run — the MCP server lives inside the app.
Register 127.0.0.1:2222, user probe, connect, then press Copy the MCP command in
the window's title bar and paste it into your agent:
Prebuilt installers are on the releases page (Windows x64 / ARM64, macOS Apple Silicon). They are not code-signed, so SmartScreen and Gatekeeper will both stop you the first time.
What you see while the agent works
Every operation, yours and the agent's, lands in one band. You do not have to ask what it did.
[The activity band: one line per operation, with [AI] marking the agent's]
When something fails, the same log the agent reads over MCP is on your screen — which stage it reached, why it stopped, and what to do next. It carries no hostnames, no usernames and no secrets.
Why this exists
There are services still running on ordinary shared hosting and VPSes. Maintain one of those with an AI agent and this happens every single time:
This tool exists to delete that round trip. The agent does not need to operate your server. Letting it see is enough.
How you actually use it
This is not a tool you drive by clicking buttons. You talk to an AI agent in your terminal, and the agent calls sshboard's built-in MCP server.
You are not restricted. The two-pane file browser and the terminal tab work like any other SFTP client and terminal. Only the agent's side is fenced.
What the agent may call (Phase 1 — reading)
list_connections / list_directory / stat / read_file / search / download /
disk_usage / process_list / service_status / runtime_versions / read_log /
network_listen / run_readonly (allowlist) / list_checks / run_check / propose_check (health checks)
There is no run_command(cmd). One such tool does everything, and once you have it
the only thing standing between an agent and a destructive command is a promise not to
use it. A promise is a runbook, not a gate.
With run_readonly the agent may pass only an id that a person wrote into
readonly.toml. There is no argument slot. The list ships empty — until you write
it, nothing runs at all. Refusals show up in the window's activity band and are
recorded in readonly-refused.log. Adding entries is a person's job.
The one success criterion
The agent stops saying "please tell me the state of the server."
Not downloads, not stars. Whether the round trip disappeared during real maintenance.
It will not modernise your setup
- This is not a tool for migrating your server somewhere else
- Working with the setup you already have is the point
- These services will still be here in ten years. Nothing exists today that brings AI development to them
Shape
We do not keep two SFTP implementations, and we never open an SSH session you cannot see. Being invisible is the single biggest danger here.
Whoever touched what flows into the same band. Different surfaces, one record.
Phase 1 is read-only
No writes at all. Every intended use — incident triage, mail-config investigation, writing up a proposal — is reading. That makes the risk close to zero, which means you can point it at a live production server on day one.
There is no run_command(cmd)
Put one arbitrary-command tool in place and it can do everything. That is exactly why it does not exist.
- Hand it over and the only remaining defence against a destructive command is a promise
- A promise is a runbook, not a gate
- With an allowlist, dangerous commands are not expressible
What readonly.toml looks like (a person writes it)
The product ships with zero entries. Nobody has the list of what real maintenance
needs, and filling it in by guesswork gets it wrong. Refused calls pile up in
readonly-refused.log — read that, and add only what was genuinely needed.
What this cannot verify: whether the run you wrote is truly read-only. The
product cannot know. No machine can tell uptime from rm -rf / by inspection. What
this prevents is exactly one thing: the agent composing a command string.
The human side (the GUI) is not restricted. Use it as an ordinary SFTP client and terminal. Only the agent's side is fenced.
Health checks (checks.toml)
Group the things you look at in the same order every time — disk, memory, a service, a
listening port — into one check. Write it once; a person runs it from the menu (Checks →
Run) and an agent runs the same check with run_check.
- A step only names an id from
readonly.toml. There is no field for a command line - Criteria come in fixed shapes only: text present (
contains) / absent (absent), a numberbelow/abovea limit - Each step comes back good, bad or unknown. Something that could not be measured is never counted as good
- The run does not stop at the first bad step, and the previous value sits beside the new one (until the app quits)
- Every step shows on the band before it runs. Checks the agent runs appear in the Checks dialog too
- The list ships empty. A person writes it; the agent cannot
- The agent can propose a check with
propose_check. Every step and criterion appears on screen, and only the person can save it (saving runs nothing)
A template lives in docs/checks.example.toml.
State-changing operations (operations.toml)
The list ships empty. Until you write this file, the agent can run nothing.
This is a separate file from the read allowlist (readonly.toml), so that write
operations never live in a file called readonly. Telling them apart by name is the
point. Put it next to connections.toml.
Gates it passes through before running
- It is in the list a person wrote. Unknown ids are refused, and the refusal is recorded
- It is under the hourly ceiling
- A person approves it on screen. The agent's first call is always refused, and the window shows what would actually run
- An approval is spent once. One approval, one run
- An approval expires after five minutes. People press allow and the agent never
calls back (the conversation moves on). We do not leave approvals lying around —
with
become = "ask", the password is dropped here too
A person at the screen does not pass 3–5. They are looking at it.
The one exception is the become = "ask" password — that is not approval, it is a
credential, and the product cannot invent it.
Refused even if you write it down
No setting enables these. They are rejected even when present in operations.toml.
- The cage itself —
sudoers/visudo/authorized_keys - Keys —
.ssh/id_rsa/id_ed25519/id_ecdsa - Where passwords live —
/etc/shadow//etc/gshadow - sshboard's own config —
connections.toml/readonly.toml/operations.toml/checks.toml - Users and roles —
useradd/usermod/userdel/groupadd - Destruction —
mkfs/fdisk/dd/rm -rf
The agent cannot widen its own cage. That is what this list is for.
Operations that need root (become)
operations.toml decides which commands may run. How privilege is obtained is
written per connection (connections.toml).
Only a run that starts with sudo is touched. Everything else is left byte for
byte. Choosing ask does not make non-sudo operations ask for a password.
sudoers (recommended)
This is a gate the OS enforces, not a promise the app makes. The scope is written in
sudoers, so a person can audit it afterwards, and sshboard gains no secret.
Check that it works:
If the second one succeeds, your scope is too wide.
ask (when you cannot touch sudoers)
Not being able to touch the server is a real situation — rented, no rights, too many
machines. On a server where sudo -n answers "a password is required", the sudoers
route was never open. That is what ask is for.
A password field appears on the approval dialog and a person types it there.
- Never stored. Not on disk, not in the OS credential store
- Never on the command line. It goes in over stdin, so it does not show up in
ps - Never on screen and never in the log. What appears is
$ sudo -S -p '' …and no more - One approval, one run. Dropped the instant it is used
- Refuse, and it is dropped there and then
Reading a log only root can read works the same way:
This does not apply to the read allowlist (readonly.toml). If it did, every read
would ask for a password. Reads that need root belong in operations.toml.
Becoming root first with su is not recommended — the scope disappears. On servers
that only have su -, use the console: the person types su -, presses [Hand to the AI],
the agent carries on in the same shell, and hands it back with console_release without
closing it. The person can always take it back.
What the product cannot guarantee
Whether an operation really does only what it says, the product cannot verify. What
runs is a string a person wrote. As with readonly.toml, that is the part a person
has to read and confirm. Which is why sudoers makes the OS enforce it.
There is no way to deliver an approval to someone who is away from the screen yet
(.claude/decisions.md D47 — undecided). Today only a person at the screen can
answer. With nobody there, the operation does not run.
What this will not do
Running it
This is still alpha. Before pointing it at a production server, run it once against the local test server.
Requirements
Keeping your key in ssh-agent is recommended. Then sshboard never receives a passphrase at all.
Start
Tests
Local test server
It does not touch your servers at all. It creates a throwaway key and brings up one machine in Docker.
Connecting an agent (MCP)
Recommended — the token is never written anywhere
--scope user registers it for every folder. Without it, Claude Code keeps it only for the folder
you typed the command in — open Claude Code anywhere else and sshboard does not even appear in /mcp.
This way the token is left nowhere (Issue #2). Started with --mcp-stdio-proxy,
sshboard runs as a relay with no window. It reads the token itself and passes it to
the running app.
- The token appears neither in
~/.claude.jsonnor in your shell history - Move the port (
SSHBOARD_MCP_PORT) and you do not have to re-register - If the app is not running you get: "sshboard is not running. Start the app and try again."
Only the app opens SSH connections. The relay holds no engine and no band. It adds not one invisible SSH session (prohibition 3).
Straight over HTTP
The "Copy the MCP registration command" button at the top right of the window copies a single line, token included.
--transport http is required. The path is /mcp and the token goes in
Authorization: Bearer.
The port is fixed at 22022 (D33), and the token is reused, so you register once.
Restarting keeps the same URL.
⚠️
claude mcp addleaves the token in~/.claude.jsonin plain text. It is a loopback handle rather than a key, but whoever holds it can read the servers you are connected to. Rememberclaude mcp remove sshboardwhen you are done.
.mcp.json works too (also plain text on disk):
If the port is taken, it does not quietly move somewhere else. The window says "failed to start (port 22022)". Set
SSHBOARD_MCP_PORTto move it.
Wake the agent when the person answers (Claude Code channels)
When the person answers a passphrase, host key, console permission or operation approval on screen, sshboard pushes that fact to the agent's session. Even if the agent ended its turn instead of waiting, it wakes up and carries on — nobody has to tell it "I pressed it".
This uses Claude Code's channels (a research preview). Start Claude Code with:
Started without it, Claude Code silently drops the notice (await_answer still works as before).
The notice says only which kind of question was answered — never where the connection points.
In Team / Enterprise organisations an admin has to enable channels first.
Updates
When a new version exists, the window tells you at startup (D34).
It never replaces itself quietly. Downloading, installing and restarting are all things a person presses. This tool handles SSH keys, so it does not rewrite itself without being asked.
The update itself is signed with minisign, and Tauri always verifies it. That is separate from code signing (D12 — whether the OS shows a warning); what is guaranteed here is that the update came from us.
Things to know during alpha
- It is not code-signed. Whether Windows stops you depends on how you fetched it
(Issue #3, measured):
- Downloaded in a browser, double-clicked → SmartScreen stops you
gh run download/ CI / a script → it does not. No Mark of the Web, so SmartScreen's reputation check has nothing to trigger on- macOS Gatekeeper is untested
- The agent may write only under directories a person listed for that connection. The list is empty by default — not one byte
- The
run_readonlyallowlist is empty by default too. Nothing runs until a person writesreadonly.toml. The purpose-built tools (disk_usageand friends) work without it - The terminal is shared between you and the agent. While the agent holds it your input is locked out, and [Stop] always works
- Installers are attached to each Release (D32).
Windows gets
.msiand an NSIS-setup.exe, macOS gets a.dmg. They are unsigned, so Windows shows SmartScreen and macOS shows Gatekeeper. If a warning stopped you and you did not know what to do, please open an issue — that is what decides whether we buy a certificate (D12)
Stack
Licence
MIT
Development rules
This repository follows a baseline set of rules for AI-agent development. See
.claude/rules/product-baseline.md and
CLAUDE.md.
- The AI commits, a human pushes (no pushing without human review)
- Tests are never deferred and never deleted
- This is a public repository. No server hostnames, IPs, usernames, personal names or personal email addresses in code, docs, commit history or screenshots
Source: README.md at commit d235d8b
Tools
0Version history
12- v0.1.39LatestNov 6, 2026
- v0.1.37Nov 5, 2026
- v0.1.36Nov 5, 2026
- v0.1.35Nov 3, 2026
- v0.1.34Oct 29, 2026
- v0.1.33Oct 26, 2026
- v0.1.32Oct 26, 2026
- v0.1.30Oct 26, 2026
- v0.1.29Oct 25, 2026
- v0.1.27Oct 24, 2026
- v0.1.25Oct 23, 2026
- v0.1.24Oct 13, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts