Skip to main content
Glama
PainInTheNic

ubuntu-mcp-server

by PainInTheNic

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.js as 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.log would 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 ed25519

Then 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:

  • defaults applies to every server; each entry can override username, port, privateKeyPath, or fingerprint.

  • servers.json is 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 the UBUNTU_MCP_KEY_PASSPHRASE environment 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 with ssh-keyscan your-server | ssh-keygen -lf -. See §7.

c. Build and register with Claude Code

npm install
npm run build

Register 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_services with state=failed

  • "Show me nginx errors from the last hour on web-01"ubuntu_tail_log

  • "Any security updates pending across my servers?"ubuntu_check_updates per server

  • "Restart nginx on web-01"ubuntu_manage_service (Claude Code will ask your permission first)

3. The tools

Tool

What it does

Mutates?

ubuntu_list_servers

Lists the inventory from servers.json (no SSH)

no

ubuntu_system_overview

Hostname, OS, kernel, uptime, load, memory, disk, reboot-required, failed units — one SSH round trip

no

ubuntu_list_services

systemd services, filterable by running/failed, paginated

no

ubuntu_service_status

Full systemctl status + enabled state for one service

no

ubuntu_manage_service

start/stop/restart/reload/enable/disable via sudo -n

yes

ubuntu_check_updates

Pending apt updates, security flags, reboot-required

no*

ubuntu_tail_log

journalctl or file tail, with since/grep filters

no

ubuntu_run_command

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)

  1. src/index.ts — the whole MCP lifecycle in ~40 lines: create an McpServer, register tools, connect a stdio transport. Everything else is plumbing for the tools.

  2. src/config.ts — loads servers.json and validates it with Zod. Zod is the pattern to internalize: you declare the shape once and get runtime validation and TypeScript types from it.

  3. src/format.ts — small but load-bearing: response helpers (ok/fail), the 25k-character truncation cap (protects Claude's context from a 10MB log), and shellQuote (the injection defense).

  4. 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.

  5. 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_overview runs 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_command is 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 returns structuredContent declares a matching outputSchema so clients can validate it.

5. Adding a new tool (10-minute recipe)

Say you want ubuntu_disk_hogs — biggest directories under a path:

  1. Pick the file (src/tools/system.ts) or create a new one.

  2. 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),
    };
  3. Register it: build the command with shellQuote(path), run execOnServer, format with ok()/fail().

    const result = await execOnServer(target, `du -xh --max-depth=2 ${shellQuote(path)} 2>/dev/null | sort -rh | head -n ${top}`, { timeoutMs: 60_000 });
  4. If you created a new file, add its register... call in src/index.ts.

  5. npm run build, then restart the Claude Code session (it launches the new build). Add a check to test/smoke.mjs if 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 -n commands the tools run. Policy is set by UBUNTU_MCP_HOST_KEY_CHECKING:

    • tofu (default) — trust-on-first-use: the key is remembered in a .host-keys.json store next to servers.json, and a changed key afterwards is refused.

    • strict — refuse any host that isn't already pinned (via fingerprint in servers.json) or remembered.

    • off — accept any host key (the old, unauthenticated behaviour). A per-server fingerprint pin always wins over the store and is never auto-learned. The store path can be overridden with UBUNTU_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 -n never prompts — it fails with instructions instead of hanging.

  • Composed commands run under bash -c with LC_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

/mcp shows the server as failed

Run node dist\index.js manually — startup errors print to stderr. Usually a missing npm run build.

"No server inventory found"

Copy servers.example.jsonservers.json (next to package.json).

"SSH authentication failed"

Does ssh user@host work in PowerShell? If your key has a passphrase, load it into ssh-agent or set UBUNTU_MCP_KEY_PASSPHRASE.

"sudo: a password is required"

Grant passwordless sudo on the server: echo 'user ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/user — or skip sudo.

"REMOTE HOST KEY CHANGED"

The server's SSH key differs from the one remembered in .host-keys.json. If you rebuilt/reinstalled the host, delete its entry there (or update fingerprint in servers.json) and reconnect. If you didn't, investigate — it can indicate a man-in-the-middle.

Tool changes not showing up

Rebuild (npm run build) and restart the Claude Code session — the old process keeps running until then.

Connection timed out

Host/port right? VPN up? Firewall allows 22?

Available Tools

8 tools
ubuntu_check_updatesCheck Package UpdatesA
Idempotent

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum packages to list
serverYesServer name from the inventory (see ubuntu_list_servers)
refresh_cacheNoRun '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_formatNo'markdown' for human-readable output, 'json' for machine-readablemarkdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
totalYes
serverYes
has_moreYes
packagesYes
list_truncatedNo
security_countYes
reboot_requiredYes
refresh_warningNo

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 ServersA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
response_formatNo'markdown' for human-readable output, 'json' for machine-readablemarkdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
serversYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 ServicesA
Read-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?"

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum services to return
stateNoFilter services by stateall
offsetNoPagination offset
serverYesServer name from the inventory (see ubuntu_list_servers)
response_formatNo'markdown' for human-readable output, 'json' for machine-readablemarkdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
totalYes
offsetYes
serverYes
has_moreYes
servicesYes
next_offsetNo
capture_truncatedNo

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 ServiceA
Destructive

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform on the service
serverYesServer name from the inventory (see ubuntu_list_servers)
serviceYessystemd unit name, e.g. 'nginx' or 'nginx.service'

Output Schema

ParametersJSON Schema
NameRequiredDescription
actionYes
serverYes
serviceYes
state_afterYes

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 CommandA
Destructive

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
sudoNoRun as root via 'sudo -n' (requires passwordless sudo on the server)
serverYesServer name from the inventory (see ubuntu_list_servers)
commandYesShell command line to execute; pipes, &&, and redirects are allowed
timeout_secondsNoAbort if the command runs longer than this (default 30)

Output Schema

ParametersJSON Schema
NameRequiredDescription
serverYes
signalNo
stderrYes
stdoutYes
exit_codeYes
capture_truncatedYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 StatusA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverYesServer name from the inventory (see ubuntu_list_servers)
serviceYessystemd unit name, e.g. 'nginx' or 'nginx.service'

Output Schema

ParametersJSON Schema
NameRequiredDescription
serverYes
statusYes
serviceYes
active_stateYes
enabled_stateYes

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 OverviewA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverYesServer name from the inventory (see ubuntu_list_servers)
response_formatNo'markdown' for human-readable output, 'json' for machine-readablemarkdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
osYes
diskYes
kernelYes
memoryYes
serverYes
uptimeYes
hostnameYes
failed_unitsYes
load_averageYes
reboot_requiredYes

TDQS

A4.3/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 LogsA
Read-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

ParametersJSON Schema
NameRequiredDescriptionDefault
grepNoOnly return lines containing this text (fixed string, case-insensitive)
pathNofile only: absolute path of the log file, e.g. '/var/log/nginx/error.log'
unitNojournal only: limit to one systemd unit, e.g. 'nginx' (omit for the full journal)
linesNoNumber of lines to return
sinceNojournal only: e.g. '1 hour ago', 'today', '2026-08-08 10:00'
serverYesServer name from the inventory (see ubuntu_list_servers)
sourceNo'journal' reads systemd's journal (journalctl); 'file' tails a log filejournal
use_sudoNoRead via sudo -n, for logs your user cannot read (requires passwordless sudo)

Output Schema

ParametersJSON Schema
NameRequiredDescription
serverYes
sourceYes
filteredYes
truncatedNo
line_countYes

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 8 tool updatesv1.0.0
    • First observedubuntu_check_updates
    • First observedubuntu_list_servers
    • First observedubuntu_list_services
    • First observedubuntu_manage_service
    • First observedubuntu_run_command
    • First observedubuntu_service_status
    • First observedubuntu_system_overview
    • First observedubuntu_tail_log

TDQS

A4.5/5.0
Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness5/5

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

ActivityMaintained
ResponsivenessNo issues

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

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    An 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.
    10
    38
    6
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for managing VPS servers via SSH, enabling command execution, file transfer, Docker management, and server documentation from within Claude.
    13
    ISC
  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP server for Claude Code to execute commands on any remote server over SSH. Provides tools for remote execution, file operations, and connection info.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    An 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

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