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? |
Available Tools
8 toolsubuntu_check_updatesCheck Package UpdatesAIdempotent
List pending apt package updates on an Ubuntu server, flag security updates, and report whether a reboot is required. Does NOT install anything.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
refresh_cache (boolean): run 'apt-get update' first via sudo -n for current results (default false)
limit (number): max packages to list, 1-200 (default 50)
response_format ('markdown' | 'json'): output format (default 'markdown')
Returns: total pending updates, security update count, reboot-required flag, and per-package old → new versions.
To actually install updates, use ubuntu_run_command with sudo, e.g. command='apt-get upgrade -y' sudo=true — after confirming with the user.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum packages to list | |
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| refresh_cache | No | Run 'sudo -n apt-get update' first so results are current (requires passwordless sudo). When false, results come from the last time the package cache was refreshed. | |
| response_format | No | 'markdown' for human-readable output, 'json' for machine-readable | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| total | Yes | |
| server | Yes | |
| has_more | Yes | |
| packages | Yes | |
| list_truncated | No | |
| security_count | Yes | |
| reboot_required | Yes | |
| refresh_warning | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (readOnlyHint=false, openWorldHint=true, idempotentHint=true, destructiveHint=false), the description discloses that refresh_cache runs 'apt-get update' via sudo -n and requires passwordless sudo, and explicitly states the tool does not install packages. This adds context about side effects and prerequisites, with no contradiction to 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?
Description is well-structured with a purpose statement, Args section, Returns, and usage note. Every sentence contributes value, and it is neither too terse nor verbose.
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?
With complete schema descriptions, an output schema, and a clear return-value description, the tool is fully documented. The alternative install path is also mentioned, making the context 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?
Input schema already provides 100% coverage with descriptive parameter docs. The description adds a concise inline summary for each param (e.g., 'server name from the inventory'), but does not add new semantics beyond the schema. Baseline 3 is appropriate.
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 'List[s] pending apt package updates on an Ubuntu server, flag[s] security updates, and report[s] whether a reboot is required.' It explicitly distinguishes itself by saying 'Does NOT install anything' and pointing to ubuntu_run_command for installs, differentiating it from sibling 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?
Provides explicit guidance: 'Does NOT install anything' and 'To actually install updates, use ubuntu_run_command with sudo... after confirming with the user.' Also explains conditions like refresh_cache requiring passwordless sudo, so the agent knows when to use this vs alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_list_serversList Ubuntu ServersARead-onlyIdempotent
List all Ubuntu servers configured in the inventory (servers.json), with their connection details.
Call this first to discover valid values for the 'server' parameter used by every other ubuntu_* tool.
Args:
response_format ('markdown' | 'json'): output format (default 'markdown')
Returns: name, host, port, username and description for each configured server. Does not contact the servers, so a listed server is not necessarily reachable right now.
| Name | Required | Description | Default |
|---|---|---|---|
| response_format | No | 'markdown' for human-readable output, 'json' for machine-readable | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| servers | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint and idempotentHint, but the description adds valuable context with 'Does not contact the servers, so a listed server is not necessarily reachable right now.' It also discloses the return fields (name, host, port, username, description), going beyond the structured 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?
The description is well-structured: purpose statement, usage guidance, Args, and Returns. Every sentence serves a purpose with no redundant filler. It is front-loaded with the core purpose and efficiently organized.
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?
This is a simple read-only listing tool with a single optional parameter. The description covers the return format, the non-contact behavior, and its role as a discovery tool. Combined with annotations and schema, nothing essential is missing.
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 only parameter, response_format, is fully covered by the schema (enum, default, description). The description repeats the parameter info without adding extra meaning. Since schema coverage is 100%, the baseline is 3; no additional value is provided.
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: 'List all Ubuntu servers configured in the inventory (servers.json), with their connection details.' This specifies a precise verb and resource. It also distinguishes itself from sibling tools by noting it is the discovery mechanism for the 'server' parameter used by all other ubuntu_* 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 instructs: 'Call this first to discover valid values for the server parameter used by every other ubuntu_* tool.' This provides clear when-to-use guidance and explains the tool's role relative to alternatives. It also adds a caveat that servers are not contacted, so a listed server may not be reachable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_list_servicesList systemd ServicesARead-onlyIdempotent
List systemd services on an Ubuntu server, optionally filtered by state.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
state ('all' | 'running' | 'failed'): filter (default 'all')
limit (number): max services to return, 1-200 (default 50)
offset (number): skip this many for pagination (default 0)
response_format ('markdown' | 'json'): output format (default 'markdown')
Returns: unit name, active/sub state, and description per service, with pagination metadata (total, has_more, next_offset).
Example: state='failed' answers "is anything broken on web-01?"
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum services to return | |
| state | No | Filter services by state | all |
| offset | No | Pagination offset | |
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| response_format | No | 'markdown' for human-readable output, 'json' for machine-readable | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| total | Yes | |
| offset | Yes | |
| server | Yes | |
| has_more | Yes | |
| services | Yes | |
| next_offset | No | |
| capture_truncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds meaningful context beyond annotations: pagination metadata (total, has_more, next_offset), the return fields (unit name, active/sub state, description), and the state filter behavior. This provides a clearer picture of the tool's operational characteristics.
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 concise opening sentence, a clear Args list, a Returns line, and an example. While it duplicates schema details, the structure makes it easy to scan and the example is valuable. It is appropriately sized for the tool's complexity.
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, the description need not explain return values in depth, but it still provides a useful summary and pagination metadata. It also includes a practical example and references to related tools. The description is complete for a read-only list tool, covering parameters, output, and usage context.
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 schema already documents all parameters and defaults. The description repeats the parameter list but adds marginal value by cross-referencing ubuntu_list_servers for the server parameter and providing a usage example. It does not materially enrich parameter meaning 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 clearly states the tool's function: 'List systemd services on an Ubuntu server, optionally filtered by state.' The verb 'List' and resource 'systemd services' are specific, and the optional filtering distinguishes it from sibling tools like ubuntu_service_status, which likely targets a single service.
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 a clear example ('state='failed' answers "is anything broken on web-01?"') and references the prerequisite source for the server parameter (ubuntu_list_servers). However, it does not explicitly mention when not to use this tool or alternatives like ubuntu_service_status for individual service details, so it lacks full exclusion guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_manage_serviceManage ServiceADestructive
Start, stop, restart, reload, enable, or disable a systemd service. Runs via 'sudo -n', so the server must allow passwordless sudo for the configured user.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
service (string): unit name, e.g. 'nginx'
action ('start' | 'stop' | 'restart' | 'reload' | 'enable' | 'disable'): what to do
Returns: confirmation plus the service's state after the action.
Error handling: if sudo requires a password the error explains how to configure passwordless sudo. 'reload' fails for services that don't support it — use 'restart' instead.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform on the service | |
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| service | Yes | systemd unit name, e.g. 'nginx' or 'nginx.service' |
Output Schema
| Name | Required | Description |
|---|---|---|
| action | Yes | |
| server | Yes | |
| service | Yes | |
| state_after | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (destructiveHint=true, readOnlyHint=false), the description discloses the sudo -n requirement, the return format (confirmation plus service state), and error handling specifics (password sudo errors, reload failure). This provides substantial behavioral context that helps the agent anticipate side effects and troubleshoot failures.
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: a clear introductory line, an Args list, a Returns note, and an Error handling section. Each sentence adds necessary information without redundancy or fluff.
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?
The description covers prerequisites (passwordless sudo), all supported actions, return behavior, and common failure scenarios. Given the presence of an output schema and complete schema documentation, the description is fully sufficient for the agent to select and 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?
Schema coverage is 100%, so the parameters are already well-documented. The description repeats the parameter meanings and adds minimal examples (e.g., 'nginx'), but does not introduce new semantic information beyond the schema. Baseline 3 is appropriate.
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 opens with 'Start, stop, restart, reload, enable, or disable a systemd service' – a specific verb+resource pairing that clearly states the tool's purpose. It distinguishes itself from sibling read-only tools like ubuntu_service_status and ubuntu_list_services by focusing on state-changing operations.
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 context on how to invoke the tool (requires passwordless sudo), and includes explicit guidance on 'reload' failing for unsupported services with the recommendation to use 'restart' instead. It does not explicitly name alternative tools for when not to use it, but the context is sufficiently clear to infer appropriate usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_run_commandRun Shell CommandADestructive
Run an arbitrary shell command on a configured Ubuntu server over SSH and return stdout, stderr, and the exit code.
Prefer the specialized tools when they fit (ubuntu_system_overview, ubuntu_list_services, ubuntu_service_status, ubuntu_check_updates, ubuntu_tail_log) — they produce cleaner output. Use this tool for everything they don't cover.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
command (string): shell command line; pipes and redirects work
sudo (boolean): run as root via 'sudo -n' — requires passwordless sudo on the server (default false)
timeout_seconds (number): 1-300, default 30
Returns: exit code plus stdout/stderr text, also available as structured content.
Error handling:
Unknown server names return the list of valid names.
A non-zero exit code is NOT a tool error — inspect stderr to understand what the command reported.
If sudo fails with "a password is required", the server lacks passwordless sudo; the output includes the fix.
| Name | Required | Description | Default |
|---|---|---|---|
| sudo | No | Run as root via 'sudo -n' (requires passwordless sudo on the server) | |
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| command | Yes | Shell command line to execute; pipes, &&, and redirects are allowed | |
| timeout_seconds | No | Abort if the command runs longer than this (default 30) |
Output Schema
| Name | Required | Description |
|---|---|---|
| server | Yes | |
| signal | No | |
| stderr | Yes | |
| stdout | Yes | |
| exit_code | Yes | |
| capture_truncated | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds significant behavioral details beyond the annotations: pipes/redirects work, sudo requires passwordless sudo, non-zero exit codes are not tool errors, unknown servers return valid names, and sudo failure output includes the fix. It complements the destructiveHint annotation without contradiction.
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 clear sections (purpose, alternatives, args, returns, error handling). Despite its length, every sentence provides useful operational context, and it is front-loaded with the primary 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 tool's complexity (arbitrary shell execution, destructive potential) and the presence of an output schema, the description is complete. It covers error handling, prerequisites, and alternatives, and doesn't need to detail return structure since an output schema exists.
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 baseline is 3. The description's Args section largely restates what is already in the schema (e.g., 'pipes and redirects work', 'requires passwordless sudo'), adding no new meaning beyond the structured field 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 runs an arbitrary shell command on a configured Ubuntu server via SSH and returns stdout, stderr, and exit code. It uses a specific verb ('Run') and resource, and differentiates itself from sibling tools by explicitly listing specialized 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?
It provides explicit guidance: 'Prefer the specialized tools when they fit' and lists them, then says to use this tool for everything they don't cover. Also mentions checking ubuntu_list_servers for valid server names, offering clear context and alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_service_statusService StatusARead-onlyIdempotent
Show detailed status of one systemd service: the full 'systemctl status' output (state, recent log lines, PID, memory) plus whether it is enabled at boot.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
service (string): unit name, e.g. 'nginx' or 'ssh'
Returns: raw status text plus parsed active_state ('active'/'inactive'/'failed') and enabled state ('enabled'/'disabled'/'static').
Error handling: reports if the unit does not exist and suggests ubuntu_list_services to find the right name.
| Name | Required | Description | Default |
|---|---|---|---|
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| service | Yes | systemd unit name, e.g. 'nginx' or 'nginx.service' |
Output Schema
| Name | Required | Description |
|---|---|---|
| server | Yes | |
| status | Yes | |
| service | Yes | |
| active_state | Yes | |
| enabled_state | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, and non-destructive behavior. The description adds substantial context beyond that: it specifies the full systemctl status output components, parsed active_state and enabled state, and error handling behavior (reports missing unit and suggests a fallback tool). This fully discloses what the tool does and returns, exceeding the annotation baseline.
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 clear sections: purpose, args, returns, and error handling. Every sentence adds value, and the main purpose is front-loaded. No redundant or vague phrasing; it's appropriately sized for the tool's simplicity.
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 tool with 2 parameters and a simple read-only operation, the description covers all necessary context: what it does, what inputs are expected, what returns look like, and how errors are handled. The output schema existence means return value details are optional, but the description includes them anyway, making it self-contained and 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?
Schema coverage is 100%, with parameter descriptions already present. The description additionally clarifies the 'server' parameter's source (inventory from ubuntu_list_servers) and gives examples for 'service' (e.g., 'nginx' or 'ssh'). This adds practical guidance beyond the schema, justifying an above-baseline score.
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: 'Show detailed status of one systemd service' with specific details about the output (state, log lines, PID, memory) and boot-enabled status. It distinguishes from siblings by focusing on a single service's status, contrasting with listing (ubuntu_list_services) or managing (ubuntu_manage_service).
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 context for when to use: when you need detailed status of a single service. It also references ubuntu_list_servers to obtain server names and suggests ubuntu_list_services in error handling for finding correct service names. It doesn't explicitly state 'when not to use', but the alternatives are clearly implied, which is sufficient guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_system_overviewSystem OverviewARead-onlyIdempotent
Get a one-shot health overview of an Ubuntu server: hostname, OS release, kernel, uptime, load average, memory usage, disk usage, whether a reboot is required, and any failed systemd services.
This is the best first call when asked "how is server X doing?" — it gathers everything in a single SSH round trip.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
response_format ('markdown' | 'json'): output format (default 'markdown')
Returns: the sections listed above; memory and disk are the raw 'free -h' / 'df -h' tables.
| Name | Required | Description | Default |
|---|---|---|---|
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| response_format | No | 'markdown' for human-readable output, 'json' for machine-readable | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| os | Yes | |
| disk | Yes | |
| kernel | Yes | |
| memory | Yes | |
| server | Yes | |
| uptime | Yes | |
| hostname | Yes | |
| failed_units | Yes | |
| load_average | Yes | |
| reboot_required | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish the tool as read-only, idempotent, and non-destructive. The description adds behavioral context by noting it gathers everything in one SSH round trip and clarifying that memory and disk are returned as raw 'free -h' / 'df -h' tables, which helps set expectations about output verbosity.
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 concise and well-organized: a purpose sentence, a usage recommendation, an Args section, and a Returns note. Every sentence contributes information without redundancy or padding, and the most important information 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?
For a read-only overview tool with only two parameters and an output schema, the description covers all necessary context: what it returns, the exact sections, and the caveat about raw tables. Combined with the schema and annotations, an agent has everything it needs to select and invoke 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 input schema already provides full descriptions for both parameters (server from inventory, response_format enum with default). The Args section in the description largely duplicates the schema, adding no new meaning beyond what the structured data already provides, so it meets the baseline for high schema coverage.
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 opens with a specific verb and resource ('Get a one-shot health overview of an Ubuntu server') and enumerates the exact metrics collected (hostname, OS release, kernel, uptime, load, memory, disk, reboot required, failed services). This clearly distinguishes it from siblings like ubuntu_list_services or ubuntu_check_updates, which focus on narrower concerns.
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 explicitly recommends this as 'the best first call when asked "how is server X doing?"' and explains the benefit (single SSH round trip). While it doesn't list when not to use it or name alternative tools, the stated context is clear enough for an agent to choose it for health checks.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ubuntu_tail_logTail LogsARead-onlyIdempotent
Read recent log lines from an Ubuntu server — either from the systemd journal (journalctl) or from a log file — optionally filtered to lines containing a search string.
Args:
server (string): server name from the inventory (see ubuntu_list_servers)
source ('journal' | 'file'): where to read from (default 'journal')
unit (string, journal only): systemd unit, e.g. 'nginx' — omit for the whole journal
path (string, file only, required): absolute file path, e.g. '/var/log/syslog'
lines (number): how many recent lines, 1-1000 (default 100)
since (string, journal only): time filter like '1 hour ago' or 'today'
grep (string): only lines containing this text (case-insensitive fixed string)
use_sudo (boolean): read as root for protected logs (default false)
Returns: the matching log lines as plain text.
Error handling: permission errors suggest use_sudo=true or adding the user to the 'adm'/'systemd-journal' groups.
Examples:
"errors in nginx logs in the last hour" -> source='journal', unit='nginx', since='1 hour ago', grep='error'
"last 50 lines of /var/log/auth.log" -> source='file', path='/var/log/auth.log', lines=50
| Name | Required | Description | Default |
|---|---|---|---|
| grep | No | Only return lines containing this text (fixed string, case-insensitive) | |
| path | No | file only: absolute path of the log file, e.g. '/var/log/nginx/error.log' | |
| unit | No | journal only: limit to one systemd unit, e.g. 'nginx' (omit for the full journal) | |
| lines | No | Number of lines to return | |
| since | No | journal only: e.g. '1 hour ago', 'today', '2026-08-08 10:00' | |
| server | Yes | Server name from the inventory (see ubuntu_list_servers) | |
| source | No | 'journal' reads systemd's journal (journalctl); 'file' tails a log file | journal |
| use_sudo | No | Read via sudo -n, for logs your user cannot read (requires passwordless sudo) |
Output Schema
| Name | Required | Description |
|---|---|---|
| server | Yes | |
| source | Yes | |
| filtered | Yes | |
| truncated | No | |
| line_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the tool read-only and idempotent. The description adds valuable behavioral context beyond that: permission errors suggest use_sudo=true or group membership, and it explicitly states that the tool returns plain text. This goes beyond simple safety signaling and covers real edge cases.
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 clear sections: summary, Args, Returns, Error handling, and Examples. Every section earns its place; there is no filler. It front-loads the core purpose and avoids redundancy with the schema.
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 tool with 8 parameters, two source modes, and conditional requirements, this description is highly complete. It covers both modes, defaults, error handling, sudo usage, and return format, and includes worked examples. No critical use-case information is missing.
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 baseline is 3. The description adds value by grouping parameters by source mode ('journal only', 'file only'), showing defaults, and providing concrete examples that combine multiple parameters (e.g., source='journal', unit='nginx', since='1 hour ago', grep='error'). This is more helpful than the raw 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 opens with a specific verb and resource: 'Read recent log lines from an Ubuntu server' and clearly distinguishes the two source modes (journal vs file). It is immediately distinct from sibling tools like ubuntu_service_status and ubuntu_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?
The description provides clear context for when to use this tool (need recent log lines) and gives detailed examples (e.g., 'errors in nginx logs in the last hour') that map natural language to parameter combinations. However, it does not explicitly mention when NOT to use it or contrast it with alternatives like ubuntu_service_status, so top marks are withheld.
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.
8 tool updates
v1.0.0- First observed
ubuntu_check_updates - First observed
ubuntu_list_servers - First observed
ubuntu_list_services - First observed
ubuntu_manage_service - First observed
ubuntu_run_command - First observed
ubuntu_service_status - First observed
ubuntu_system_overview - First observed
ubuntu_tail_log
TDQS
Each tool has a clearly distinct purpose: inventory listing, health overview, service listing/status/management, update checking, log reading, and a fallback shell command. The descriptions explicitly note when to prefer specialized tools over the generic runner, eliminating ambiguity.
All tools share the consistent 'ubuntu_' prefix and use lowercase with underscores. Most follow a verb_noun pattern (list_servers, manage_service, tail_log), but 'system_overview' and 'service_status' deviate to noun-first style, creating a minor inconsistency in the otherwise predictable naming convention.
The 8 tools are well-scoped for an Ubuntu server management MCP. The count is neither too small to cover common tasks nor too large to add confusion, and each tool earns its place in the set.
The surface covers the core server administration lifecycle: listing inventory, health checks, service management, update checks, log inspection, and a generic command runner for everything else. The fallback ensures no dead ends and the specialized tools cover the most frequent operations.
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
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
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
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
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.10386MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for managing VPS servers via SSH, enabling command execution, file transfer, Docker management, and server documentation from within Claude.13ISC
- FlicenseNot gradedqualityDmaintenanceMCP server for Claude Code to execute commands on any remote server over SSH. Provides tools for remote execution, file operations, and connection info.-
- FlicenseNot gradedqualityDmaintenanceAn MCP server for managing Ubuntu/Linux systems, enabling AI assistants to execute commands, manage services, files, logs, and packages via local or SSH connection.-
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