ubuntu-mcp-server
Provides tools for managing Ubuntu servers over SSH, including system overview, service management, log tailing, update checks, and arbitrary command execution.
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., "@ubuntu-mcp-serverHow is web-01 doing?"
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.
ubuntu-mcp-server
An MCP server that lets Claude manage your Ubuntu machines over SSH — check health, inspect services, tail logs, review pending updates, and run commands, all from a Claude conversation.
This README doubles as a tutorial: it explains where an MCP server runs, how this one is put together, and how to extend it — so the next one you build takes an afternoon, not a weekend.
1. What is an MCP server, actually?
MCP (Model Context Protocol) is a standard way to give an AI client (Claude Code, Claude Desktop, etc.) extra abilities, called tools. The mental model:
┌──────────────────────── Your Windows PC ───────────────────────┐
│ │
│ Claude Code ── JSON-RPC over stdin/stdout ──► this server │
│ (MCP client) (Node process) │
│ │ │
└──────────────────────────────────────────────────────┼─────────┘
│ SSH (port 22)
┌─────────────────┼─────────────────┐
▼ ▼ ▼
web-01 db-01 backup-01
(your Ubuntu servers — nothing installed on them)Key facts that answer "where does this run?":
The MCP server runs on this PC. Claude Code starts
node dist/index.jsas a child process automatically whenever you start a session, and stops it when you're done. You never launch it by hand.They talk over stdin/stdout ("stdio transport") using JSON-RPC messages. That's why the code only ever logs to stderr — a stray
console.logwould corrupt the protocol stream.Your Ubuntu servers need nothing new. The server reaches them with plain SSH key authentication, same as your terminal does.
The conversation flow: you ask Claude something → Claude picks a tool and arguments → Claude Code asks you for permission (for non-read-only tools) → the tool runs over SSH → the result goes back into Claude's context → Claude answers you.
Related MCP server: vps-mcp
2. Quick start
a. One-time SSH key setup (skip if ssh you@server already works without a password)
ssh-keygen -t ed25519Then install the public key on each Ubuntu server (from PowerShell):
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh youruser@your-server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"b. Describe your servers
Copy servers.example.json to servers.json and fill in your machines:
{
"defaults": { "username": "youruser", "port": 22, "privateKeyPath": "~/.ssh/id_ed25519" },
"servers": [
{ "name": "web-01", "host": "192.168.1.10", "description": "Main web server" },
{ "name": "db-01", "host": "192.168.1.11", "username": "ubuntu", "description": "Database" }
]
}Notes:
defaultsapplies to every server; each entry can overrideusername,port,privateKeyPath, orfingerprint.servers.jsonis git-ignored — your inventory stays on your machine.The file is re-read on every tool call, so you can add servers without restarting anything.
Passwords are deliberately unsupported: key auth only. Keys with a passphrase work if the key is loaded in
ssh-agent, or set theUBUNTU_MCP_KEY_PASSPHRASEenvironment variable.Host-key verification authenticates the server (not just you). By default the server remembers each host's key on first connection and refuses to connect if that key later changes (the tell-tale sign of a man-in-the-middle). To pin a key up front, add
"fingerprint": "SHA256:…"to an entry — get the value withssh-keyscan your-server | ssh-keygen -lf -. See §7.
c. Build and register with Claude Code
npm installnpm run buildRegister it (the --scope user flag makes it available in every project, not just this folder):
claude mcp add --scope user ubuntu -- node "C:\path\to\MCP-Ubuntu\dist\index.js"Verify with /mcp inside a Claude Code session — you should see ubuntu connected with 8 tools.
d. Use it
Just talk to Claude:
"How is web-01 doing?" →
ubuntu_system_overview"Is anything failing on db-01?" →
ubuntu_list_serviceswithstate=failed"Show me nginx errors from the last hour on web-01" →
ubuntu_tail_log"Any security updates pending across my servers?" →
ubuntu_check_updatesper server"Restart nginx on web-01" →
ubuntu_manage_service(Claude Code will ask your permission first)
3. The tools
Tool | What it does | Mutates? |
| Lists the inventory from servers.json (no SSH) | no |
| Hostname, OS, kernel, uptime, load, memory, disk, reboot-required, failed units — one SSH round trip | no |
| systemd services, filterable by | no |
| Full | no |
| start/stop/restart/reload/enable/disable via | yes |
| Pending apt updates, security flags, reboot-required | no* |
| journalctl or file tail, with | no |
| Arbitrary shell command — the escape hatch | can |
* refresh_cache=true runs apt-get update first (metadata only, needs passwordless sudo). Because of that optional refresh the tool is annotated readOnlyHint: false, so a client may prompt for it even in the default refresh_cache=false case, which really is read-only.
Anything that uses sudo runs it as sudo -n (never prompt): if the server doesn't allow passwordless sudo, the tool fails fast with an explanation instead of hanging forever waiting for a password nobody can type.
4. Reading the code (suggested order)
src/index.ts— the whole MCP lifecycle in ~40 lines: create anMcpServer, register tools, connect a stdio transport. Everything else is plumbing for the tools.src/config.ts— loadsservers.jsonand validates it with Zod. Zod is the pattern to internalize: you declare the shape once and get runtime validation and TypeScript types from it.src/format.ts— small but load-bearing: response helpers (ok/fail), the 25k-character truncation cap (protects Claude's context from a 10MB log), andshellQuote(the injection defense).src/ssh.ts— one cached SSH connection per server, lazy connect, a single retry on stale connections, hard timeouts, and error messages rewritten to say what to fix ("is sshd running?", "check authorized_keys") rather than raw socket errors.src/tools/*.ts— one file per domain. Each follows the same recipe, which is 90% of what "writing an MCP server" means day-to-day.
Anatomy of one tool (the recipe)
server.registerTool(
"ubuntu_service_status", // 1. name: {service}_{action}_{resource}, snake_case
{
title: "Service Status", // 2. human-facing label
description: `...`, //3. THE MOST IMPORTANT PART — this is Claude's
// only manual for the tool: args, returns,
// examples, error behavior
inputSchema: { // 4. Zod shape — validated before your code runs
server: z.string().min(1).describe("..."),
service: UnitName, // invalid input never reaches the handler
},
outputSchema: { // 5. shape of `structuredContent` you return —
server: z.string(), // lets clients validate/type the machine-
active_state: z.string(), // readable output. REQUIRED if you return
enabled_state: z.string(), // structuredContent, and the SDK validates
status: z.string(), // every result against it at runtime.
},
annotations: { // 6. behavior hints for the client:
readOnlyHint: true, // read-only tools can be auto-approved;
destructiveHint: false, // destructive ones always prompt
idempotentHint: true,
openWorldHint: true,
},
},
async ({ server, service }) => { // 7. handler: typed, validated args in →
try { // CallToolResult out
...
return ok(markdownText, structuredData); // structuredData MUST match outputSchema
} catch (error) {
return fail(errMessage(error)); // 8. errors are RESULTS (isError: true), not
} // crashes — Claude reads them and adapts
},
);Design choices worth copying into future servers:
Batch round trips.
ubuntu_system_overviewruns nine commands in one SSH exec with===SECTION:x===markers and splits the output, instead of nine tool calls.Errors teach. "Unknown server 'web1'. Configured servers: web-01, db-01" lets Claude fix its own mistake without asking you.
Validate + quote everything. Unit names and paths pass a strict regex and get single-quote shell escaping.
run_commandis intentionally open — that's what the destructive annotation and permission prompt are for.Two output shapes. Human-readable text plus
structuredContent(machine-readable JSON) in the same response — and every tool that returnsstructuredContentdeclares a matchingoutputSchemaso clients can validate it.
5. Adding a new tool (10-minute recipe)
Say you want ubuntu_disk_hogs — biggest directories under a path:
Pick the file (
src/tools/system.ts) or create a new one.Define the input shape:
const InputShape = { server: z.string().min(1).describe("Server name from the inventory"), path: z.string().regex(/^\/[^\n\r\0]*$/).default("/").describe("Directory to analyze"), top: z.number().int().min(1).max(50).default(10), };Register it: build the command with
shellQuote(path), runexecOnServer, format withok()/fail().const result = await execOnServer(target, `du -xh --max-depth=2 ${shellQuote(path)} 2>/dev/null | sort -rh | head -n ${top}`, { timeoutMs: 60_000 });If you created a new file, add its
register...call insrc/index.ts.npm run build, then restart the Claude Code session (it launches the new build). Add a check totest/smoke.mjsif the tool has SSH-free paths.
6. Testing
npm run smoke— starts the built server exactly like Claude Code does (subprocess + stdio), performs the MCP handshake, and checks all tools, error paths, and schema validation. No real Ubuntu server needed.MCP Inspector — a browser UI to poke tools by hand, great for learning:
npx @modelcontextprotocol/inspector node dist/index.js
7. Security model
Runs locally with your permissions; nothing listens on any network port.
SSH key auth only — the code has no concept of a password and stores no secrets. Inventory (
servers.json) is git-ignored.The server's host key is verified on every connection, so a spoofed host (IP/DNS redirection) can't impersonate one of your machines and harvest the privileged
sudo -ncommands the tools run. Policy is set byUBUNTU_MCP_HOST_KEY_CHECKING:tofu(default) — trust-on-first-use: the key is remembered in a.host-keys.jsonstore next toservers.json, and a changed key afterwards is refused.strict— refuse any host that isn't already pinned (viafingerprintinservers.json) or remembered.off— accept any host key (the old, unauthenticated behaviour). A per-serverfingerprintpin always wins over the store and is never auto-learned. The store path can be overridden withUBUNTU_MCP_HOST_KEYS.
Every model-supplied value is Zod-validated and shell-quoted before touching a command line; names/paths also can't start with
-(option-injection).sudo -nnever prompts — it fails with instructions instead of hanging.Composed commands run under
bash -cwithLC_ALL=C(immune to the remote user's shell and locale), and exit-code markers embedded in remote output carry a per-call random nonce so log content can't forge them.Retries after connection failures happen only when the command provably never started — a mid-command drop is reported as a connection loss, never as a "successful" partial result, and never silently re-run.
Mutating tools are annotated so Claude Code shows you a permission prompt before they run; output is capped at 25k characters so a runaway command can't flood the model.
8. Troubleshooting
Symptom | Fix |
| Run |
"No server inventory found" | Copy |
"SSH authentication failed" | Does |
"sudo: a password is required" | Grant passwordless sudo on the server: |
"REMOTE HOST KEY CHANGED" | The server's SSH key differs from the one remembered in |
Tool changes not showing up | Rebuild ( |
Connection timed out | Host/port right? VPN up? Firewall allows 22? |
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 Servers
- AlicenseAqualityCmaintenanceAn SSH MCP server that enables users to connect to and manage remote servers directly from Claude Code. It provides tools to execute commands, monitor connection status, and dynamically manage server configurations through natural language.Last updated10356MIT
- Alicense-qualityDmaintenanceMCP server for managing VPS servers via SSH, enabling command execution, file transfer, Docker management, and server documentation from within Claude.Last updated7ISC
- Flicense-qualityDmaintenanceMCP server for Claude Code to execute commands on any remote server over SSH. Provides tools for remote execution, file operations, and connection info.Last updated
- Flicense-qualityDmaintenanceAn MCP server for managing Ubuntu/Linux systems, enabling AI assistants to execute commands, manage services, files, logs, and packages via local or SSH connection.Last updated
Related MCP Connectors
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Operate your Linux servers from your LLM. Every action runs through an auditable allowlist.
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/PainInTheNic/MCP-Ubuntu'
If you have feedback or need assistance with the MCP directory API, please join our Discord server