e-300/Tailscale-MCP-Remote-Ops-Agent
If you are the rightful owner of Tailscale-MCP-Remote-Ops-Agent and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
This document provides a structured summary of a Model Context Protocol (MCP) server, detailing its overview, features, tools, resources, usage, and FAQs.
Tailscale-MCP-Remote-Ops-Agent
Secure AI-powered infrastructure management over zero-trust networks
This is a proof-of-concept agent that lets you chat with Claude to execute commands on a remote server via SSH and Tailscale, using MCP. It isn’t production-ready more of a personal POC I hacked together over a few days to explore the AgentOps space.
Note: This is an independent proof-of-concept and not an official Tailscale product.
The Problem
Managing servers today forces a bad tradeoff: either give AI full shell access which is insane, copy-paste commands manually which is tedious, or build fragile custom integrations. This POC demonstrates a middle path where an AI agent can execute only whitelisted commands over Tailscale’s zero-trust network, letting you manage infrastructure through natural conversation without sacrificing safety.
This POC combines:
- AI agent (Claude with MCP tools)
- Zero-trust overlay (Tailscale)
- Controlled SSH command execution (whitelist + sanitization)
🧠 How It Works (Claude + MCP + SSH + Tailscale)
- You talk to Claude via a local Gradio chat UI.
- Claude calls MCP tools (e.g.,
server_status,list_commands,execute_command) exposed by a local MCP server. - The MCP server uses an async SSH client to run whitelisted commands on a Tailscale-reachable host.
- Results are returned through MCP back to Claude, which explains them in natural language.
Claude never has raw shell access. It can only invoke predefined tools that map to whitelisted commands.
Example conversation:
Demo Video:
https://github.com/user-attachments/assets/91cb238b-8e1a-4157-8f60-814d6143c8f0
🗺️ Architecture
Local Deployment
┌─────────────────────────────────────────────────────────────────────────────┐
│ Tailnet │
│ │
│ ┌──────────────┐ ┌─────────────────────┐ ┌──────────────┐ │
│ │ Local │ ◄──────►│ Agent │ ◄──────►│Remote Server │ │
│ │ Browser │ HTTP │ (self-hosted) │ SSH │ │ │
│ └──────────────┘ │ │ │ Executes: │ │
│ │ - Gradio UI │ │ df -h │ │
│ │ - MCP tools │ │ free -m │ │
│ │ - SSH client │ │ systemctl │ │
│ └──────────┬──────────┘ └──────────────┘ │
│ │ │
└──────────────────────────────────────┼──────────────────────────────────────┘
│
│ HTTPS (API call)
▼
┌──────────────────┐
│ Anthropic API │
└──────────────────┘
Docker Deployment
┌────────────────────────────────────────────────────────────────────────────────┐
│ Tailnet │
│ │
│ ┌──────────────┐ ┌───────────────────────────────────────────────┐ │
│ │ Browser │ ◄───────►│ Docker Host │ │
│ └──────────────┘port:7860 │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ tailscale │ │ mcp-agent │ │ │
│ │ │ (sidecar) │◄───► │ │ │ │
│ │ │ │shared│ - Gradio UI │ │ │
│ │ │ - VPN │ net │ - Claude API │ │ │
│ │ │ - Routes │ │ - SSH client │ │ │
│ │ └─────────────┘ └────────┬────────┘ │ │
│ └────────────────────────────────┼──────────────┘ │
│ │ │
│ │ SSH │
│ ▼ │
│ ┌──────────────┐ │
│ │Remote Server │ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
The Docker setup uses two containers:
- tailscale: Sidecar container providing VPN connectivity to your Tailnet.
- mcp-agent: Main application container sharing the Tailscale network namespace.
🚀 Deployment Options
There are two ways to run this project:
- Option A: Docker Deployment (Recommended)
- Option B: Local Python Environment
Option A: Docker deployment (recommended)
1. Prerequisites
- Docker Engine 20.10+
- Docker Compose v2.0+
- A Tailscale account
- An Anthropic API key
- SSH access to your remote server (over Tailscale, e.g.,
100.x.x.x)
2. Clone the repo
git clone git@github.com:e-300/Tailscale-MCP-Remote-Ops-Agent.git
cd Tailscale-MCP-Remote-Ops-Agent
3. Configure environment (.env)
Use the Docker-focused env template:
mv .env.docker .env
nano .env
Set at minimum:
ANTHROPIC_API_KEYREMOTE_HOST(e.g.,100.x.x.x)REMOTE_USERSSH_KEY_PATH/SSH_KEY_FILENAME(for Docker SSH key mounting)- Either
TS_AUTHKEYor be ready to do manual auth
The .env file explains which values are required and which are optional.
4. Authenticate with Tailscale
You have two options: automated (auth key) or manual interactive.
4.1 Automated Tailscale authentication (recommended)
-
Generate an auth key at: https://login.tailscale.com/admin/settings/keys
-
Add it to
.env:TS_AUTHKEY=tskey-auth-xxxxx
When you run docker compose up, the Tailscale container will auto-auth and join your Tailnet.
4.2 Manual Tailscale authentication
# Start only the Tailscale container
docker compose up -d tailscale
# Trigger interactive auth (one-time)
docker compose exec tailscale tailscale up
# → Follow the URL printed in the terminal, sign in, and approve the node.
# Verify status
docker compose exec tailscale tailscale status
Tailscale state is persisted in the container volume until you remove volumes.
5. Start the agent
# Start all services (tailscale + mcp-agent)
docker compose up -d
# View agent logs
docker compose logs -f mcp-agent
Once started, open: http://localhost:7860 in your browser.
6. SSH key setup options (Docker)
Option 1: Mount existing keys
Mount your ~/.ssh directory (default-style setup):
volumes:
- ${SSH_KEY_PATH:-~/.ssh}:/app/ssh-keys:ro
In .env:
SSH_KEY_PATH=~/.ssh
SSH_KEY_FILENAME=id_ed25519
Option 2: Use a specific key file
# Create a dedicated keys directory
mkdir -p ./secrets/ssh
# Copy your key (keep it secure!)
cp ~/.ssh/id_ed25519 ./secrets/ssh/
# Update .env
SSH_KEY_PATH=./secrets/ssh
SSH_KEY_FILENAME=id_ed25519
Option 3: Docker secrets (Swarm mode)
For production with Docker Swarm:
secrets:
ssh_key:
file: ./secrets/id_ed25519
services:
mcp-agent:
secrets:
- ssh_key
environment:
- REMOTE_SSH_KEY_PATH=/run/secrets/ssh_key
7. Common Docker commands
# Start all containers
docker compose up -d
# Stop all containers
docker compose down
# View logs
docker compose logs -f
docker compose logs -f mcp-agent
# Rebuild after code changes
docker compose build --no-cache mcp-agent
docker compose up -d mcp-agent
# Check Tailscale status
docker compose exec tailscale tailscale status
# Access agent container shell
docker compose exec mcp-agent bash
# Test SSH connection from inside container
docker compose exec mcp-agent python -c "
import asyncio
from src.config import SSHConfig
from src.ssh_client import test_ssh_connection
config = SSHConfig.from_env()
print(asyncio.run(test_ssh_connection(config)))
"
8. Docker-specific troubleshooting
Tailscale won’t connect
# Check Tailscale logs
docker compose logs tailscale
# Verify TUN device exists
docker compose exec tailscale ls -la /dev/net/tun
# Re-authenticate
docker compose exec tailscale tailscale up --reset
SSH connection fails
# Verify key is mounted
docker compose exec mcp-agent ls -la /app/ssh-keys/
# Check permissions
docker compose exec mcp-agent stat /app/ssh-keys/id_ed25519
# Test SSH manually (if openssh-client is installed)
docker compose exec mcp-agent ssh -i /app/ssh-keys/id_ed25519 user@100.x.x.x
Can’t reach remote server
# Verify Tailscale is connected
docker compose exec tailscale tailscale status
# Ping remote server
docker compose exec tailscale ping 100.x.x.x
# Check if agent can reach Tailnet
docker compose exec mcp-agent ping 100.x.x.x
Port 7860 not accessible
# Check if port is exposed
docker compose ps
# Verify Gradio is running
docker compose logs mcp-agent | grep -i gradio
# Check port binding (container name may differ)
docker port tailscale-mcp 7860
Option B: Local Python environment
1. Prerequisites
-
Python 3.11+
-
pipandvenv -
Tailscale running on:
- Your local machine
- The remote server
-
Anthropic API key
-
SSH access to the remote over Tailscale (e.g.,
ssh user@100.x.x.xworks)
2. Clone and set up environment
git clone git@github.com:e-300/Tailscale-MCP-Remote-Ops-Agent.git
cd Tailscale-MCP-Remote-Ops-Agent
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
3. Configure environment (.env)
cp .env.example .env
nano .env
Example values:
ANTHROPIC_API_KEY=sk-ant-your-key
REMOTE_HOST=100.x.x.x
REMOTE_USER=server-username
REMOTE_SSH_KEY_PATH=~/.ssh/id_ed25519
REMOTE_SSH_KEY_PASSPHRASE=optional
REMOTE_SSH_PORT=22
Ensure:
- You can SSH into the remote using the key/path you configured.
- Tailscale is connected on both ends (
tailscale status).
4. Run the agent
python3 run.py
Then open: http://localhost:7860 in your browser.
5. Local Python troubleshooting
“SSH not configured”
Ensure these are set in .env:
REMOTE_HOSTREMOTE_USERREMOTE_SSH_KEY_PATH
“SSH key not found”
ls -la ~/.ssh/id_ed25519
“Connection refused”
- Verify Tailscale is running:
tailscale status - Check you can ping the remote:
ping 100.x.x.x - Verify SSH is running on remote:
ssh user@100.x.x.x
“Command not in whitelist”
- Add the command to
config/commands.yaml - Or use the MCP tool
list_commandsin the UI to see available options.
📖 Usage
Example prompts
Once the agent is running (Docker or local), try:
- “Check the server status” – Verifies SSH connectivity
- “How much disk space is available?” – Runs
df -h - “Show me the memory usage” – Runs
free -h - “Give me a system overview” – Runs multiple commands
- “What commands can you run?” – Lists whitelisted commands
- “Is the docker service running?” – Checks systemd service
- “Check the size of /var/log” – Runs
du -sh /var/log
Available MCP tools
| Tool | Description |
|---|---|
ping | Test MCP server connectivity |
server_status | Check SSH connection status |
list_commands | Show all available commands |
execute_command | Run a whitelisted command |
check_disk | Quick disk space check |
check_memory | Quick memory check |
check_service | Check a systemd service |
check_path_size | Check directory size |
system_overview | Get comprehensive system information |
Whitelisted commands
Commands are organized by category:
- System:
hostname,uptime,whoami,uname,date - Disk:
disk_usage,disk_usage_path,largest_files - Memory:
memory_usage,memory_detailed - Process:
top_processes,top_memory_processes,process_count - Network:
network_interfaces,listening_ports,routing_table - Services:
service_status,failed_services,service_logs - Docker:
docker_ps,docker_stats,docker_logs,docker_images
See config/commands.yaml for the full list and to add custom commands.
🔒 Security
Design principles
- Command whitelisting – Only pre-approved commands can run.
- Parameter sanitization – All inputs are sanitized to prevent injection.
- Tailscale encryption – All traffic between machines is encrypted at the network layer.
- No shell access – Commands run in isolation, not interactive shells.
Adding custom commands
Edit config/commands.yaml:
- name: my_custom_command
description: What this command does (be descriptive for Claude)
command_template: echo "Hello {name}"
category: custom
parameters:
name: The name to greet
Security audit checklist
Before deploying:
- Review all commands in
config/commands.yaml - Remove any commands you don’t need
- Verify SSH key has minimal permissions on the remote server
- Consider running the agent in a container
- Enable Tailscale ACLs to restrict access
- Run
check_setup.pyto verify dependencies are installed correctly
🏗️ Project Structure
Tailscale-MCP-Remote-Ops-Agent/
├── src/
│ ├── __init__.py # Package init
│ ├── chat_ui.py # Gradio chat interface + agentic loop
│ ├── mcp_server.py # MCP server with tool definitions
│ ├── mcp_client.py # MCP client for agent
│ ├── ssh_client.py # SSH command execution
│ └── config.py # Configuration models
├── config/
│ └── commands.yaml # Whitelisted commands
├── Dockerfile # Container build
├── docker-compose.yaml # Multi-container orchestration
├── .env.example # Environment template (local)
├── .env.docker # Environment template (Docker)
├── requirements.txt
├── check_setup.py # Environment / dependency checks
├── run.py # Startup script
└── README.md
🛠️ Development Status
- Phase 1: Foundation – Basic Gradio chat with Claude
- Phase 2: MCP server – Define tools with MCP
- Phase 3: SSH connection – Remote command execution
- Phase 4: Tool definitions – Whitelisted commands
- Phase 5: MCP client – Agent talks MCP
- Phase 6: Full integration – End-to-end flow
- Phase 7: Configuration – User-friendly setup
- Phase 8: Security – Hardening and audit
- Phase 9: Docker containerization
- Phase 10: Polish – Documentation and UX
🧹 Cleanup
# Stop and remove containers
docker compose down
# Also remove volumes (WARNING: loses Tailscale auth state)
docker compose down -v
# Remove built images
docker compose down --rmi local
📄 License
MIT
🙏 Acknowledgments
- Anthropic for Claude
- Tailscale for secure networking
- Model Context Protocol for the tool framework
- Gradio for the UI