Skip to main content
Glama

Ubuntu MCP Server & Web Console

License: MIT Python 3.10+ MCP Protocol Tests: 61 passed Docker Support

A sandboxed Model Context Protocol (MCP) server exposing 38 tools for filesystem, directory, Ubuntu system, network, git, and code/text operations — packaged with a futuristic Web GUI & Interactive Linux Terminal (xterm.js + WebSockets).

Connect directly with MCP-compatible AI clients (Antigravity IDE, Claude Desktop, Cursor, Claude Code) or interact directly through your browser!


⚡ Quick Start — Launch Web GUI & Terminal

# Clone the repository
git clone https://github.com/<your-username>/ubuntu-mcp-server.git
cd ubuntu-mcp-server

# Run the Web Console (opens automatically at http://localhost:8000)
python run_gui.py
# Or on Windows: .\run_gui.bat
# Or on Linux:   ./run_gui.sh

✨ Web Console Features:

  1. 🖥️ Interactive Linux Terminal: Full ANSI color, real-time bash prompt, autocomplete, vim/nano support, connected to the active Ubuntu/WSL session!

  2. ⚡ MCP Playground & Tool Explorer: Browse all 38 tools, fill schema-driven argument forms, and execute tools live with formatted JSON outputs and latency metrics.

  3. 📊 Live System Monitor: Real-time dials for CPU, RAM, Disk utilization, and a live top processes table.

  4. 🔌 1-Click Integration Hub: Instant copy-paste configurations for Antigravity, Claude Desktop, Cursor, and Docker.


Related MCP server: Connector MCP

Architecture

        AI Client (Claude Code / Claude Desktop / Cursor / ...)
                          │
                          │  MCP protocol (stdio, or streamable-http if remote)
                          ▼
                 ┌─────────────────────┐
                 │  Ubuntu MCP Server  │
                 │   (MCPServer/       │
                 │    FastMCP)         │
                 └──────────┬──────────┘
                             │
      ┌───────────┬─────────┼─────────┬───────────┬──────────┐
      ▼           ▼         ▼         ▼           ▼          ▼
 Filesystem   Directories  System   Network      Git      Code/Text
  8 tools      6 tools     6 tools  6 tools     5 tools    7 tools
      │           │         │         │           │          │
      └───────────┴─────────┼─────────┴───────────┴──────────┘
                             ▼
                     Security boundary
                (security.py: workspace sandbox
                 + SSRF-blocking network guard)
                             │
                             ▼
                        Ubuntu host

There is no arbitrary shell-execution tool. The only subprocess calls in the project are fixed, whitelisted invocations: git <fixed subcommand> and systemctl status <validated-name> --no-pager. Nothing here accepts a free-form command string from the AI client.

Features / tool list

All tool names below are exactly what the MCP client sees.

Filesystem (src/ubuntu_mcp/tools/filesystem.py) read_file, write_file, append_file, delete_file, copy_file, move_file, file_exists, get_file_info

Directories (directories.py) list_directory, create_directory, delete_directory, directory_exists, find_files, get_directory_size

Ubuntu / system (system.py) — all read-only get_system_info, get_cpu_info, get_memory_info, get_disk_info, list_processes, get_service_status

Network (network.py) — SSRF-guarded fetch_url, check_connectivity, resolve_dns, validate_url, parse_url, build_url

Git (git_tools.py) — read-only, fixed subcommands only git_status, git_log, git_diff, git_branches, git_current_branch

Code / text / data (code_tools.py) detect_project_type, search_text_in_files, count_words, format_json, validate_json, csv_to_json, csv_get_columns

Every tool returns the same predictable shape:

{"success": true,  "data": {...}, "error": null}
{"success": false, "data": null,  "error": {"code": "...", "message": "..."}}

Requirements

  • Ubuntu (or any modern Linux/macOS) with Python 3.10+

  • git (for the git tools; everything else works without it)

  • systemctl optional (only get_service_status needs it; it degrades gracefully to {"available": false} if missing)

Installation

Quick path

git clone <your-fork-or-copy-of-this-repo> ubuntu-mcp-server
cd ubuntu-mcp-server
./deploy/install.sh

This installs OS packages, creates .venv/, installs the project, copies .env.example → .env, and runs the test suite as a smoke check.

Manual path (Ubuntu/Linux/macOS)

sudo apt update && sudo apt install -y python3 python3-venv python3-pip git   # Ubuntu only

python3 -m venv .venv
source .venv/bin/activate           # macOS/Linux
# .venv\Scripts\activate            # Windows (PowerShell)

pip install --upgrade pip
pip install -e ".[dev]"

cp .env.example .env

Environment configuration

All configuration is read from environment variables (optionally via .env, loaded with python-dotenv). See .env.example for the full, commented list. Key ones:

Variable

Default

Purpose

MCP_WORKSPACE

./workspace

Sandbox root. Every filesystem/git/code tool is confined here.

MCP_MAX_FILE_SIZE

10485760 (10 MB)

Max file size for read/write/append.

MCP_MAX_RESPONSE_SIZE

5242880 (5 MB)

Max bytes fetch_url will buffer.

MCP_HTTP_TIMEOUT

10

Default fetch_url timeout, seconds.

MCP_ALLOW_PRIVATE_NETWORK

false

Set true to let fetch_url/check_connectivity reach private/loopback hosts.

MCP_TRANSPORT

stdio

stdio (local, client-launched) or streamable-http (remote).

MCP_HTTP_HOST / MCP_HTTP_PORT

127.0.0.1 / 8765

Bind address for streamable-http.

Never commit your real .env.

Running locally (stdio — the normal case)

source .venv/bin/activate
python -m ubuntu_mcp

In stdio mode the process expects to be launched by an MCP client, which owns its stdin/stdout. Running it by hand like this will just sit there — that's expected; an MCP client is what talks to it. To see it actually respond, configure a client (next section) instead of running it standalone.

Testing

source .venv/bin/activate
pytest -q

61 tests cover every tool module, including: happy paths, invalid input, path-traversal / workspace-escape attempts, SSRF blocking (loopback and private IPs rejected), a real throwaway git repo, and a full round trip through the actual MCP call_tool() protocol layer (not just the underlying functions). Network tests mock httpx — no live network access is required to run the suite.

MCP client configuration

Exact configuration syntax depends on your specific client (Claude Code, Claude Desktop, Cursor, etc.) — check that client's docs for where this config file lives. The common shape (stdio, local) looks like:

{
  "mcpServers": {
    "ubuntu-mcp": {
      "command": "/absolute/path/to/ubuntu-mcp-server/.venv/bin/python",
      "args": ["-m", "ubuntu_mcp"],
      "env": {
        "MCP_WORKSPACE": "/absolute/path/to/a/workspace/directory"
      }
    }
  }
}

Use absolute paths — the client launches this as its own subprocess, so relative paths resolve against wherever the client happens to run from.

Example prompts once connected

  • "List everything in my workspace."

  • "Read notes.md and summarize it."

  • "How much CPU and memory is this machine using right now?"

  • "Show me git log for the api project, last 5 commits."

  • "Is nginx running?"

  • "Fetch https://example.com/status.json and tell me what's in it."

  • "Find all Python files under src/ that mention TODO."

  • "Convert data.csv to JSON."

Remote deployment

Local (default, recommended):

AI Client  →  spawns this process directly  →  talks over stdio

No network port is opened. This is the safest mode and needs no authentication of its own, because the client process owns the pipe.

Remote (opt-in, MCP_TRANSPORT=streamable-http):

AI Client  →  HTTPS  →  reverse proxy (TLS + auth)  →  Ubuntu MCP Server (streamable-http)  →  Ubuntu host

Running with MCP_TRANSPORT=streamable-http binds a plain HTTP port with no built-in authentication. Do not expose that port to the internet directly. Put it behind a reverse proxy (nginx, Caddy, etc.) that terminates TLS and enforces auth, or keep it reachable only over a private network / VPN / SSH tunnel. deploy/ubuntu-mcp.service is a systemd template for running this mode as a background service:

sudo cp deploy/ubuntu-mcp.service /etc/systemd/system/ubuntu-mcp@.service
sudo systemctl daemon-reload
sudo systemctl enable --now ubuntu-mcp@yourusername
journalctl -u ubuntu-mcp@yourusername -f

(Only streamable-http mode makes sense as a systemd service — stdio mode has no persistent process to supervise, since it's designed to be spawned per-client-session.)

Security model

  1. Filesystem sandbox (security.safe_path): every path is resolved relative to MCP_WORKSPACE. .. traversal and absolute paths that would land outside the workspace are rejected; an absolute-looking path like /etc/passwd is safely re-rooted inside the workspace rather than touching the real one.

  2. Network SSRF guard (security.assert_public_host): fetch_url and check_connectivity resolve the target hostname and reject loopback, link-local, and RFC1918 private ranges unless you explicitly set MCP_ALLOW_PRIVATE_NETWORK=true.

  3. No arbitrary command execution: git and systemd tools only ever invoke a fixed subcommand list via asyncio.create_subprocess_exec (never a shell string), with strictly validated arguments.

  4. Structured, leak-free errors: every tool is wrapped by @safe_tool, which catches exceptions and returns {"success": false, "error": {...}} — never a raw traceback, environment variable, or secret.

Troubleshooting

  • "No module named ubuntu_mcp" — activate the venv (source .venv/bin/activate) or reinstall with pip install -e ..

  • get_service_status returns available: false — systemctl isn't installed/available on this host (e.g. inside some containers); this is expected, not a bug.

  • fetch_url / check_connectivity raise a security error — the target resolved to a private/loopback address; that's the SSRF guard working as intended. Set MCP_ALLOW_PRIVATE_NETWORK=true only if you trust the target and understand the tradeoff.

  • git tools say "not a git repository" — the path must contain a .git directory; run git init there first.

How to add a new tool

  1. Implement the function in the relevant tools/*.py module (or a new module), following the existing pattern: async def my_tool(...) -> dict, raising one of the exceptions in exceptions.py on failure.

  2. In server.py, add:

    @mcp.tool()
    @safe_tool
    async def my_tool(arg: str) -> dict:
        """One-line summary.
    
        Args:
            arg: what it's for.
        """
        return await my_module.my_tool(arg)
  3. Add tests in tests/test_<module>.py covering the happy path and at least one failure path.

  4. Run pytest -q.

Publish to Your Own GitHub Account

You can publish this entire project to your personal GitHub repository:

# 1. Initialize git in the project root (if not already done)
git init
git add .
git commit -m "feat: Ubuntu MCP Server with Web Console, Terminal & 38 Tools"

# 2. Add your GitHub remote repository
git remote add origin https://github.com/<your-username>/ubuntu-mcp-server.git
git branch -M main

# 3. Push to GitHub!
git push -u origin main

Docker Deployment

Build and run in seconds with Docker:

docker compose up -d --build

Then visit http://localhost:8000 for the Web Console & Terminal, and http://localhost:8765/mcp for the MCP endpoint.

License

MIT — see LICENSE.

Available Tools

38 tools
append_fileA

Append text to an existing file inside the workspace.

Args: path: Path to an existing file, relative to the workspace root. content: Text to append.

Returns: The path and number of bytes appended. Fails if the file does not exist or if appending would exceed the max file size.

Inputs: path (required), content (required).

build_urlB

Construct a URL from a base URL, an appended path, and optional query parameters.

Args: base_url: Base http/https URL. path: Path segment to append. query: Optional dict of query parameters to encode and attach.

Inputs: base_url (required), path, query.

check_connectivityA

Check whether a TCP port on a public host is reachable.

Args: host: Hostname or IP to test (private/loopback hosts are blocked). port: TCP port number (1-65535). timeout: Connection timeout in seconds.

Inputs: host (required), port (required), timeout.

copy_fileA

Copy a file within the workspace, preserving metadata.

Args: source: Existing file path, relative to the workspace root. destination: Destination path; parent directories are created automatically.

Inputs: source (required), destination (required).

count_wordsB

Count words, characters, and lines in a block of text.

Args: text: The text to analyze.

Inputs: text (required).

create_directoryB

Create a directory (and any missing parent directories) in the workspace.

Args: path: Directory path to create, relative to the workspace root.

Inputs: path (required).

csv_get_columnsA

Read just the header row of a CSV file in the workspace.

Args: path: Path to the CSV file, relative to the workspace root.

Inputs: path (required).

csv_to_jsonB

Read a CSV file from the workspace and convert it to a list of row objects keyed by header column.

Args: path: Path to the CSV file, relative to the workspace root.

Inputs: path (required).

delete_directoryA

Delete a directory inside the workspace.

Args: path: Directory to delete, relative to the workspace root. recursive: Required to be True to delete a non-empty directory and everything inside it. The workspace root itself can never be deleted.

Inputs: path (required), recursive.

delete_fileA

Delete a single file inside the workspace.

Args: path: Path to the file to delete, relative to the workspace root.

Returns: The deleted path. Fails if the path does not exist or is a directory (use delete_directory for directories).

Inputs: path (required).

detect_project_typeA

Guess the project type(s) present in a directory by looking for common marker files (pyproject.toml, package.json, Cargo.toml, etc.).

Args: path: Directory to inspect, relative to the workspace root.

Inputs: path.

directory_existsA

Check whether a given path exists and is a directory.

Args: path: Path to check, relative to the workspace root.

Inputs: path (required).

fetch_urlA

Fetch a public HTTP/HTTPS URL and return its status, headers, and body.

Blocks requests to localhost, loopback, link-local, and private IP ranges to prevent SSRF (unless MCP_ALLOW_PRIVATE_NETWORK=true). Response bodies are capped at MCP_MAX_RESPONSE_SIZE bytes.

Args: url: The http:// or https:// URL to fetch. timeout: Optional per-request timeout in seconds (defaults to MCP_HTTP_TIMEOUT).

Inputs: url (required), timeout.

file_existsA

Check whether a given path exists and is a regular file.

Args: path: Path to check, relative to the workspace root.

Inputs: path (required).

find_filesA

Recursively find files under a directory matching a glob pattern.

Args: path: Directory to search under, relative to the workspace root. pattern: Filename glob, e.g. ".py" or ".md" (matched against the filename only, not the full path).

Returns: Up to 500 matching relative file paths.

Inputs: path, pattern.

format_jsonB

Pretty-print (reformat) a JSON string with the given indentation.

Args: json_text: Raw JSON text to parse and reformat. indent: Number of spaces per indent level (0-8).

Inputs: json_text (required), indent.

get_cpu_infoA

Get CPU information: core counts, current utilization (overall and per-core), clock frequency, and 1/5/15-minute load averages.

Inputs: no named parameters.

get_directory_sizeA

Recursively calculate the total size of a directory's contents.

Args: path: Directory to measure, relative to the workspace root.

Returns: Total size in bytes and megabytes, plus the number of files counted.

Inputs: path.

get_disk_infoA

Get disk usage (total/used/free, in GB) for the filesystem containing the given workspace-relative path.

Args: path: Path (inside the workspace) whose filesystem to inspect.

Inputs: path.

get_file_infoA

Get metadata about a file: size, modified/created time, extension.

Args: path: Path to the file, relative to the workspace root.

Inputs: path (required).

get_memory_infoA

Get RAM and swap usage in gigabytes and as a percentage.

Inputs: no named parameters.

get_service_statusA

Get the systemctl status output for one systemd service (read-only; cannot start, stop, restart, enable, or disable services).

Args: service_name: Service unit name, e.g. "nginx" or "ssh.service". Only letters, digits, '@', '.', '_', and '-' are allowed.

Inputs: service_name (required).

get_system_infoA

Get basic host information: OS, kernel release, architecture, hostname, Python version, and boot time.

Inputs: no named parameters.

git_branchesB

List local branches for a repository inside the workspace, marking which one is currently checked out.

Args: path: Path to the git repository, relative to the workspace root.

Inputs: path.

git_current_branchB

Get the name of the currently checked-out branch.

Args: path: Path to the git repository, relative to the workspace root.

Inputs: path.

git_diffA

Get the current diff for a repository inside the workspace.

Args: path: Path to the git repository, relative to the workspace root. staged: If True, show staged (index) changes instead of the working-tree diff. file_path: Optional single file to limit the diff to.

Inputs: path, staged, file_path.

git_logB

Get recent commit history for a repository inside the workspace.

Args: path: Path to the git repository, relative to the workspace root. limit: Number of commits to return (1-200).

Inputs: path, limit.

git_statusA

Get git status (branch info + changed files) for a repository inside the workspace.

Args: path: Path to the git repository, relative to the workspace root.

Inputs: path.

list_directoryA

List the contents of a directory inside the workspace.

Args: path: Directory to list, relative to the workspace root (default: the workspace root itself). recursive: If True, walk subdirectories too (capped by MCP_MAX_DIRECTORY_DEPTH).

Returns: Each entry's name, relative path, whether it's a file or directory, and file size when applicable.

Inputs: path, recursive.

list_processesA

List running processes on the host (read-only; cannot start, stop, or signal anything).

Args: limit: Max number of processes to return (1-200). sort_by: "cpu" or "memory" -- which usage metric to sort by, descending.

Returns: Each process's pid, name, username, cpu%, memory%, and status.

Inputs: limit, sort_by.

move_fileB

Move or rename a file within the workspace.

Args: source: Existing file path, relative to the workspace root. destination: New path; parent directories are created automatically.

Inputs: source (required), destination (required).

parse_urlB

Break a URL down into scheme, hostname, port, path, query, and fragment.

Args: url: The URL to parse.

Inputs: url (required).

read_fileA

Read a UTF-8 (or other specified encoding) text file from the workspace.

Args: path: Path to the file, relative to the workspace root. encoding: Text encoding to decode with (default "utf-8").

Returns: The file's path, size in bytes, encoding, and full text content. Fails if the file does not exist, isn't a file, exceeds the configured max file size, or isn't valid text in that encoding.

Inputs: path (required), encoding.

resolve_dnsB

Resolve a hostname to its IP address(es) and flag whether any are private.

Args: hostname: The hostname to resolve.

Inputs: hostname (required).

search_text_in_filesA

Recursively regex-search text files under a directory (like a scoped grep), confined to the workspace.

Args: path: Directory to search under, relative to the workspace root. pattern: Python regular expression to search each line for. file_glob: Glob to filter which files are searched, e.g. "*.py". max_results: Maximum number of matching lines to return (1-500).

Inputs: path, pattern, file_glob, max_results.

validate_jsonB

Check whether a string is valid JSON without modifying it.

Args: json_text: The text to validate.

Inputs: json_text (required).

validate_urlA

Validate that a string is a well-formed http/https URL (syntax check only; does not make a network request).

Args: url: The URL string to validate.

Inputs: url (required).

write_fileA

Create or overwrite a text file inside the workspace.

Args: path: Destination path, relative to the workspace root. Parent directories are created automatically. content: Text content to write (UTF-8). overwrite: If False and the file already exists, fails instead of overwriting it.

Returns: The path and number of bytes written.

Inputs: path (required), content (required), overwrite.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 38 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes, with minimal overlap. Minor confusion possible between file_exists and get_file_info, or between git_branches and git_current_branch, but descriptions clarify their differences.

    Naming Consistency4/5

    Tools follow a consistent snake_case pattern overall, but there is a mix of verb_noun (get_memory_info) and noun_verb (file_exists) conventions. Domain-prefixed names like git_* and csv_* are consistent within their groups.

    Tool Count2/5

    With 38 tools, the server is heavily over the typical 3-15 range. Many tools are very granular and could be combined (e.g., file_exists and get_file_info, or the URL manipulation tools), making the set feel bloated for the purpose.

    Completeness4/5

    The surface covers file management, system inspection, network utilities, git read operations, and text processing well. Notable gaps include command execution, package management, and process control, but these may be intentionally excluded for safety.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to work in a persistent, isolated Linux workspace with file management, Bash execution, SSH/SFTP access, and durable storage while keeping workloads contained from the host and private networks.
      MIT
    • F
      license
      Not graded
      quality
      B
      maintenance
      Enables AI coding agents to run shell commands, spawn persistent background tasks, and read/write files inside isolated remote container sandboxes on a VPS. It also provides project workspace listing, task attach/kill controls, and a searchable knowledge graph for memory continuity across sessions.
      -
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables web-based AI assistants and local IDEs to securely operate a local machine through an OAuth-gated suite of 38 tools covering files, shell, search, Git, databases, browser automation, DevTools, background tasks, clipboard, localhost services, and Postman. Access is constrained by a command whitelist, folder allowlists, and rate limits so clients never receive unrestricted shell access.
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables coding agents to browse, read, search, and write files inside a locked workspace, run asynchronous shell commands, and batch read operations, all with validated caching, health diagnostics, and a SQLite audit trail.
      32
      3,836 npm
      AGPL 3.0