Ubuntu MCP Server
Click on "Deploy 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 Server check the system's CPU and memory usage"
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 & Web Console
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:
🖥️ Interactive Linux Terminal: Full ANSI color, real-time bash prompt, autocomplete, vim/nano support, connected to the active Ubuntu/WSL session!
⚡ MCP Playground & Tool Explorer: Browse all 38 tools, fill schema-driven argument forms, and execute tools live with formatted JSON outputs and latency metrics.
📊 Live System Monitor: Real-time dials for CPU, RAM, Disk utilization, and a live top processes table.
🔌 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 hostThere 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)systemctloptional (onlyget_service_statusneeds 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.shThis 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 .envEnvironment 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 |
|
| Sandbox root. Every filesystem/git/code tool is confined here. |
|
| Max file size for read/write/append. |
|
| Max bytes |
|
| Default |
|
| Set |
|
|
|
|
| Bind address for |
Never commit your real .env.
Running locally (stdio — the normal case)
source .venv/bin/activate
python -m ubuntu_mcpIn 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 -q61 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.mdand summarize it.""How much CPU and memory is this machine using right now?"
"Show me
git logfor theapiproject, last 5 commits.""Is
nginxrunning?""Fetch
https://example.com/status.jsonand tell me what's in it.""Find all Python files under
src/that mentionTODO.""Convert
data.csvto JSON."
Remote deployment
Local (default, recommended):
AI Client → spawns this process directly → talks over stdioNo 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 hostRunning 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
Filesystem sandbox (
security.safe_path): every path is resolved relative toMCP_WORKSPACE...traversal and absolute paths that would land outside the workspace are rejected; an absolute-looking path like/etc/passwdis safely re-rooted inside the workspace rather than touching the real one.Network SSRF guard (
security.assert_public_host):fetch_urlandcheck_connectivityresolve the target hostname and reject loopback, link-local, and RFC1918 private ranges unless you explicitly setMCP_ALLOW_PRIVATE_NETWORK=true.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.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 withpip install -e ..get_service_statusreturnsavailable: false—systemctlisn't installed/available on this host (e.g. inside some containers); this is expected, not a bug.fetch_url/check_connectivityraise a security error — the target resolved to a private/loopback address; that's the SSRF guard working as intended. SetMCP_ALLOW_PRIVATE_NETWORK=trueonly if you trust the target and understand the tradeoff.git tools say "not a git repository" — the path must contain a
.gitdirectory; rungit initthere first.
How to add a new tool
Implement the function in the relevant
tools/*.pymodule (or a new module), following the existing pattern:async def my_tool(...) -> dict, raising one of the exceptions inexceptions.pyon failure.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)Add tests in
tests/test_<module>.pycovering the happy path and at least one failure path.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 mainDocker Deployment
Build and run in seconds with Docker:
docker compose up -d --buildThen 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 toolsappend_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
Scored across 38 tools
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.
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.
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.
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
Related MCP Connectors
Persistent cloud workspaces for AI agents: run commands, edit files, use git and a browser.
Persistent Linux computers for AI agents: desktop, signed-in browser, vault, apps and shell.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Real Linux labs your AI agent deploys, routes and runs, with domains, TLS, DBs and an audit log.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables 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
- FlicenseNot gradedqualityBmaintenanceEnables 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.-
- AlicenseNot gradedqualityAmaintenanceEnables 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
- AlicenseAqualityBmaintenanceEnables 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.323,836 npmAGPL 3.0