Spaces:
Sleeping
Sleeping
Create initial Linux MCP server
Browse files
app.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import asyncio
|
| 3 |
+
import uvicorn
|
| 4 |
+
from fastapi import FastAPI, Request
|
| 5 |
+
from fastapi.responses import StreamingResponse
|
| 6 |
+
import json
|
| 7 |
+
from mcp.server import Server
|
| 8 |
+
from mcp.types import Tool, TextContent
|
| 9 |
+
|
| 10 |
+
# Create FastAPI app
|
| 11 |
+
app = FastAPI(title="Linux MCP Server")
|
| 12 |
+
|
| 13 |
+
# Create MCP server instance
|
| 14 |
+
mcp_app = Server("linux-mcp")
|
| 15 |
+
|
| 16 |
+
# ============================================================================
|
| 17 |
+
# LINUX MCP HANDLER FUNCTIONS
|
| 18 |
+
# ============================================================================
|
| 19 |
+
|
| 20 |
+
def handle_user_management(args):
|
| 21 |
+
"""Generate Linux user management commands"""
|
| 22 |
+
action = args.get("action")
|
| 23 |
+
username = args.get("username", "")
|
| 24 |
+
groupname = args.get("groupname", "")
|
| 25 |
+
use_sudo = args.get("sudo", True)
|
| 26 |
+
sudo_prefix = "sudo " if use_sudo else ""
|
| 27 |
+
|
| 28 |
+
commands = []
|
| 29 |
+
|
| 30 |
+
if action == "add_user_to_group":
|
| 31 |
+
if not username or not groupname:
|
| 32 |
+
return [TextContent(type="text", text="β Username and groupname required for add_user_to_group")]
|
| 33 |
+
|
| 34 |
+
commands = [
|
| 35 |
+
f"# Add user '{username}' to group '{groupname}'",
|
| 36 |
+
f"{sudo_prefix}usermod -a -G {groupname} {username}",
|
| 37 |
+
"",
|
| 38 |
+
"# Alternative methods:",
|
| 39 |
+
f"{sudo_prefix}gpasswd -a {username} {groupname}",
|
| 40 |
+
f"{sudo_prefix}adduser {username} {groupname} # Ubuntu/Debian",
|
| 41 |
+
"",
|
| 42 |
+
"# Verify group membership:",
|
| 43 |
+
f"groups {username}",
|
| 44 |
+
f"id {username}",
|
| 45 |
+
f"getent group {groupname}"
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
elif action == "create_user":
|
| 49 |
+
if not username:
|
| 50 |
+
return [TextContent(type="text", text="β Username required for create_user")]
|
| 51 |
+
|
| 52 |
+
commands = [
|
| 53 |
+
f"# Create new user '{username}'",
|
| 54 |
+
f"{sudo_prefix}useradd -m -s /bin/bash {username}",
|
| 55 |
+
f"{sudo_prefix}passwd {username}",
|
| 56 |
+
"",
|
| 57 |
+
"# Create user with specific group:",
|
| 58 |
+
f"{sudo_prefix}useradd -m -g {groupname if groupname else 'users'} -s /bin/bash {username}",
|
| 59 |
+
"",
|
| 60 |
+
"# Add to sudo group (Ubuntu/Debian):",
|
| 61 |
+
f"{sudo_prefix}usermod -a -G sudo {username}",
|
| 62 |
+
"",
|
| 63 |
+
"# Add to wheel group (RHEL/CentOS):",
|
| 64 |
+
f"{sudo_prefix}usermod -a -G wheel {username}",
|
| 65 |
+
"",
|
| 66 |
+
"# Verify user creation:",
|
| 67 |
+
f"id {username}",
|
| 68 |
+
f"getent passwd {username}"
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
elif action == "delete_user":
|
| 72 |
+
if not username:
|
| 73 |
+
return [TextContent(type="text", text="β Username required for delete_user")]
|
| 74 |
+
|
| 75 |
+
commands = [
|
| 76 |
+
f"# Delete user '{username}'",
|
| 77 |
+
f"{sudo_prefix}userdel {username}",
|
| 78 |
+
"",
|
| 79 |
+
"# Delete user and home directory:",
|
| 80 |
+
f"{sudo_prefix}userdel -r {username}",
|
| 81 |
+
"",
|
| 82 |
+
"# Remove user from all groups:",
|
| 83 |
+
f"{sudo_prefix}gpasswd -d {username} groupname",
|
| 84 |
+
"",
|
| 85 |
+
"# Verify user deletion:",
|
| 86 |
+
f"getent passwd {username}",
|
| 87 |
+
"# Should return no results if deleted successfully"
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
elif action == "list_groups":
|
| 91 |
+
commands = [
|
| 92 |
+
"# List all groups:",
|
| 93 |
+
"cat /etc/group",
|
| 94 |
+
"getent group",
|
| 95 |
+
"",
|
| 96 |
+
"# List groups for specific user:",
|
| 97 |
+
f"groups {username}" if username else "groups $USER",
|
| 98 |
+
f"id {username}" if username else "id $USER",
|
| 99 |
+
"",
|
| 100 |
+
"# List users in specific group:",
|
| 101 |
+
f"getent group {groupname}" if groupname else "getent group sudo",
|
| 102 |
+
"",
|
| 103 |
+
"# List all users:",
|
| 104 |
+
"cut -d: -f1 /etc/passwd",
|
| 105 |
+
"getent passwd"
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
result = "\n".join(commands)
|
| 109 |
+
return [TextContent(type="text", text=result)]
|
| 110 |
+
|
| 111 |
+
def handle_file_permissions(args):
|
| 112 |
+
"""Generate file permission and ownership commands"""
|
| 113 |
+
action = args.get("action")
|
| 114 |
+
path = args.get("path")
|
| 115 |
+
permissions = args.get("permissions", "755")
|
| 116 |
+
owner = args.get("owner", "")
|
| 117 |
+
recursive = args.get("recursive", False)
|
| 118 |
+
|
| 119 |
+
if not path:
|
| 120 |
+
return [TextContent(type="text", text="β Path is required")]
|
| 121 |
+
|
| 122 |
+
commands = []
|
| 123 |
+
recursive_flag = "-R " if recursive else ""
|
| 124 |
+
|
| 125 |
+
if action == "chmod":
|
| 126 |
+
commands = [
|
| 127 |
+
f"# Change permissions for {path}",
|
| 128 |
+
f"chmod {recursive_flag}{permissions} {path}",
|
| 129 |
+
"",
|
| 130 |
+
"# Common permission examples:",
|
| 131 |
+
"# 755 = rwxr-xr-x (executable for owner, readable for others)",
|
| 132 |
+
"# 644 = rw-r--r-- (readable/writable for owner, readable for others)",
|
| 133 |
+
"# 600 = rw------- (readable/writable for owner only)",
|
| 134 |
+
"# 777 = rwxrwxrwx (full permissions for all - use with caution!)",
|
| 135 |
+
"",
|
| 136 |
+
"# Symbolic notation examples:",
|
| 137 |
+
f"chmod {recursive_flag}u+x {path} # Add execute for owner",
|
| 138 |
+
f"chmod {recursive_flag}g-w {path} # Remove write for group",
|
| 139 |
+
f"chmod {recursive_flag}o-r {path} # Remove read for others",
|
| 140 |
+
"",
|
| 141 |
+
"# Verify permissions:",
|
| 142 |
+
f"ls -la {path}",
|
| 143 |
+
f"stat {path}"
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
elif action == "chown":
|
| 147 |
+
if not owner:
|
| 148 |
+
return [TextContent(type="text", text="β Owner is required for chown")]
|
| 149 |
+
|
| 150 |
+
commands = [
|
| 151 |
+
f"# Change ownership for {path}",
|
| 152 |
+
f"sudo chown {recursive_flag}{owner} {path}",
|
| 153 |
+
"",
|
| 154 |
+
"# Change owner and group:",
|
| 155 |
+
f"sudo chown {recursive_flag}{owner}:{owner} {path}",
|
| 156 |
+
f"sudo chown {recursive_flag}{owner}:users {path}",
|
| 157 |
+
"",
|
| 158 |
+
"# Change only group:",
|
| 159 |
+
f"sudo chgrp {recursive_flag}{owner} {path}",
|
| 160 |
+
"",
|
| 161 |
+
"# Verify ownership:",
|
| 162 |
+
f"ls -la {path}",
|
| 163 |
+
f"stat {path}"
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
elif action == "find_permissions":
|
| 167 |
+
commands = [
|
| 168 |
+
f"# Find files with specific permissions in {path}",
|
| 169 |
+
f"find {path} -type f -perm {permissions}",
|
| 170 |
+
f"find {path} -type f -perm -{permissions} # At least these permissions",
|
| 171 |
+
"",
|
| 172 |
+
"# Find files by owner:",
|
| 173 |
+
f"find {path} -user {owner}" if owner else f"find {path} -user $USER",
|
| 174 |
+
f"find {path} -group {owner}" if owner else f"find {path} -group users",
|
| 175 |
+
"",
|
| 176 |
+
"# Find files with dangerous permissions:",
|
| 177 |
+
f"find {path} -type f -perm -002 # World-writable files",
|
| 178 |
+
f"find {path} -type f -perm -004 # World-readable files",
|
| 179 |
+
f"find {path} -type f -perm -006 # World-readable and writable",
|
| 180 |
+
"",
|
| 181 |
+
"# Find SUID/SGID files:",
|
| 182 |
+
f"find {path} -type f -perm -4000 # SUID files",
|
| 183 |
+
f"find {path} -type f -perm -2000 # SGID files",
|
| 184 |
+
"",
|
| 185 |
+
"# Find directories with specific permissions:",
|
| 186 |
+
f"find {path} -type d -perm 777 # World-writable directories"
|
| 187 |
+
]
|
| 188 |
+
|
| 189 |
+
result = "\n".join(commands)
|
| 190 |
+
return [TextContent(type="text", text=result)]
|
| 191 |
+
|
| 192 |
+
def handle_system_commands(args):
|
| 193 |
+
"""Generate Linux system administration commands"""
|
| 194 |
+
category = args.get("category")
|
| 195 |
+
action = args.get("action", "")
|
| 196 |
+
target = args.get("target", "")
|
| 197 |
+
|
| 198 |
+
commands = []
|
| 199 |
+
|
| 200 |
+
if category == "process":
|
| 201 |
+
commands = [
|
| 202 |
+
"# Process Management Commands",
|
| 203 |
+
"ps aux # List all processes with details",
|
| 204 |
+
"ps -ef # Alternative process listing",
|
| 205 |
+
"pstree # Show process tree",
|
| 206 |
+
"",
|
| 207 |
+
"# Find and manage specific processes:",
|
| 208 |
+
f"ps aux | grep {target if target else 'nginx'} # Find specific process",
|
| 209 |
+
f"pgrep -f {target if target else 'nginx'} # Get PID by name",
|
| 210 |
+
f"pkill -f {target if target else 'nginx'} # Kill process by name",
|
| 211 |
+
f"killall {target if target else 'nginx'} # Kill all processes by name",
|
| 212 |
+
"",
|
| 213 |
+
"# Process control:",
|
| 214 |
+
"kill -15 PID # Graceful termination (SIGTERM)",
|
| 215 |
+
"kill -9 PID # Force kill (SIGKILL)",
|
| 216 |
+
"kill -HUP PID # Reload configuration (SIGHUP)",
|
| 217 |
+
"",
|
| 218 |
+
"# Background processes:",
|
| 219 |
+
"nohup command & # Run command in background",
|
| 220 |
+
"screen -S session_name # Start screen session",
|
| 221 |
+
"tmux new -s session_name # Start tmux session",
|
| 222 |
+
"",
|
| 223 |
+
"# Process monitoring:",
|
| 224 |
+
"top # Real-time process monitor",
|
| 225 |
+
"htop # Enhanced process monitor",
|
| 226 |
+
"jobs # List background jobs",
|
| 227 |
+
"bg # Resume job in background",
|
| 228 |
+
"fg # Bring job to foreground"
|
| 229 |
+
]
|
| 230 |
+
|
| 231 |
+
elif category == "disk":
|
| 232 |
+
commands = [
|
| 233 |
+
"# Disk Usage and Management Commands",
|
| 234 |
+
"df -h # Show disk usage (human readable)",
|
| 235 |
+
"df -i # Show inode usage",
|
| 236 |
+
"du -sh /* # Show directory sizes in root",
|
| 237 |
+
f"du -sh {target if target else '/var/log'}/* | sort -hr # Sort by size",
|
| 238 |
+
"",
|
| 239 |
+
"# Disk analysis:",
|
| 240 |
+
"lsblk # List block devices",
|
| 241 |
+
"lsblk -f # Show filesystems",
|
| 242 |
+
"blkid # Show UUIDs and labels",
|
| 243 |
+
"fdisk -l # List disk partitions",
|
| 244 |
+
"parted -l # Alternative partition listing",
|
| 245 |
+
"",
|
| 246 |
+
"# Mount management:",
|
| 247 |
+
"mount # Show mounted filesystems",
|
| 248 |
+
"findmnt # Tree view of mounts",
|
| 249 |
+
"findmnt -D # Show duplicate mounts",
|
| 250 |
+
f"mount /dev/sdb1 /mnt/{target if target else 'backup'} # Mount filesystem",
|
| 251 |
+
f"umount /mnt/{target if target else 'backup'} # Unmount filesystem",
|
| 252 |
+
"",
|
| 253 |
+
"# Cleanup commands:",
|
| 254 |
+
"apt autoremove # Remove unused packages (Debian/Ubuntu)",
|
| 255 |
+
"yum autoremove # Remove unused packages (RHEL/CentOS)",
|
| 256 |
+
"journalctl --disk-usage # Check journal disk usage",
|
| 257 |
+
"journalctl --vacuum-time=3d # Clean old journal entries"
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
elif category == "network":
|
| 261 |
+
commands = [
|
| 262 |
+
"# Network Configuration and Diagnostics",
|
| 263 |
+
"ip addr show # Show IP addresses",
|
| 264 |
+
"ip link show # Show network interfaces",
|
| 265 |
+
"ip route show # Show routing table",
|
| 266 |
+
"ip neigh show # Show ARP table",
|
| 267 |
+
"",
|
| 268 |
+
"# Port and connection analysis:",
|
| 269 |
+
"ss -tuln # Show listening ports",
|
| 270 |
+
"ss -tulpn # Show ports with process names",
|
| 271 |
+
"netstat -tuln # Alternative port listing",
|
| 272 |
+
"netstat -rn # Show routing table",
|
| 273 |
+
"",
|
| 274 |
+
"# Connectivity testing:",
|
| 275 |
+
f"ping -c 4 {target if target else 'google.com'} # Test connectivity",
|
| 276 |
+
f"traceroute {target if target else 'google.com'} # Trace network path",
|
| 277 |
+
f"mtr {target if target else 'google.com'} # Network diagnostic tool",
|
| 278 |
+
f"nslookup {target if target else 'google.com'} # DNS lookup",
|
| 279 |
+
f"dig {target if target else 'google.com'} # DNS information",
|
| 280 |
+
"",
|
| 281 |
+
"# Network configuration:",
|
| 282 |
+
"wget -O- ifconfig.me # Get public IP",
|
| 283 |
+
"curl -s ifconfig.me # Alternative public IP",
|
| 284 |
+
"curl -s ipinfo.io # Detailed IP information",
|
| 285 |
+
"",
|
| 286 |
+
"# Interface management:",
|
| 287 |
+
"sudo ip link set eth0 up # Bring interface up",
|
| 288 |
+
"sudo ip link set eth0 down # Bring interface down",
|
| 289 |
+
"sudo dhclient eth0 # Request DHCP lease"
|
| 290 |
+
]
|
| 291 |
+
|
| 292 |
+
elif category == "service":
|
| 293 |
+
commands = [
|
| 294 |
+
"# Service Management (systemd)",
|
| 295 |
+
f"systemctl status {target if target else 'nginx'} # Check service status",
|
| 296 |
+
f"systemctl start {target if target else 'nginx'} # Start service",
|
| 297 |
+
f"systemctl stop {target if target else 'nginx'} # Stop service",
|
| 298 |
+
f"systemctl restart {target if target else 'nginx'} # Restart service",
|
| 299 |
+
f"systemctl reload {target if target else 'nginx'} # Reload configuration",
|
| 300 |
+
"",
|
| 301 |
+
"# Service persistence:",
|
| 302 |
+
f"systemctl enable {target if target else 'nginx'} # Enable at boot",
|
| 303 |
+
f"systemctl disable {target if target else 'nginx'} # Disable at boot",
|
| 304 |
+
f"systemctl is-enabled {target if target else 'nginx'} # Check if enabled",
|
| 305 |
+
"",
|
| 306 |
+
"# Service discovery:",
|
| 307 |
+
"systemctl list-units # List all active services",
|
| 308 |
+
"systemctl list-units --failed # List failed services",
|
| 309 |
+
"systemctl list-unit-files # List all unit files",
|
| 310 |
+
"",
|
| 311 |
+
"# Logs and troubleshooting:",
|
| 312 |
+
f"journalctl -u {target if target else 'nginx'} # View service logs",
|
| 313 |
+
f"journalctl -u {target if target else 'nginx'} -f # Follow service logs",
|
| 314 |
+
f"journalctl -u {target if target else 'nginx'} --since today # Today's logs",
|
| 315 |
+
"",
|
| 316 |
+
"# Legacy service management (SysV):",
|
| 317 |
+
f"service {target if target else 'nginx'} status # Check status (legacy)",
|
| 318 |
+
f"service {target if target else 'nginx'} start # Start service (legacy)",
|
| 319 |
+
f"/etc/init.d/{target if target else 'nginx'} status # Direct init script"
|
| 320 |
+
]
|
| 321 |
+
|
| 322 |
+
elif category == "firewall":
|
| 323 |
+
commands = [
|
| 324 |
+
"# Firewall Management",
|
| 325 |
+
"sudo ufw status verbose # Check UFW status (detailed)",
|
| 326 |
+
"sudo ufw --version # Check UFW version",
|
| 327 |
+
"",
|
| 328 |
+
"# UFW basic operations:",
|
| 329 |
+
"sudo ufw enable # Enable UFW",
|
| 330 |
+
"sudo ufw disable # Disable UFW",
|
| 331 |
+
"sudo ufw reset # Reset UFW rules",
|
| 332 |
+
"",
|
| 333 |
+
"# UFW rule management:",
|
| 334 |
+
f"sudo ufw allow {target if target else '22'} # Allow port/service",
|
| 335 |
+
f"sudo ufw deny {target if target else '23'} # Block port/service",
|
| 336 |
+
"sudo ufw allow ssh # Allow SSH service",
|
| 337 |
+
"sudo ufw allow 80/tcp # Allow HTTP",
|
| 338 |
+
"sudo ufw allow 443/tcp # Allow HTTPS",
|
| 339 |
+
"sudo ufw allow from 192.168.1.0/24 # Allow from subnet",
|
| 340 |
+
"",
|
| 341 |
+
"# UFW advanced rules:",
|
| 342 |
+
"sudo ufw delete allow 80 # Remove rule",
|
| 343 |
+
"sudo ufw insert 1 allow 22 # Insert rule at position",
|
| 344 |
+
"sudo ufw logging on # Enable logging",
|
| 345 |
+
"",
|
| 346 |
+
"# iptables (advanced):",
|
| 347 |
+
"sudo iptables -L # List iptables rules",
|
| 348 |
+
"sudo iptables -L -n # List with numeric output",
|
| 349 |
+
"sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT # Allow HTTP",
|
| 350 |
+
"sudo iptables -A INPUT -p tcp --dport 22 -j DROP # Block SSH",
|
| 351 |
+
"",
|
| 352 |
+
"# Save iptables rules:",
|
| 353 |
+
"sudo iptables-save > /etc/iptables/rules.v4 # Save IPv4 rules",
|
| 354 |
+
"sudo ip6tables-save > /etc/iptables/rules.v6 # Save IPv6 rules"
|
| 355 |
+
]
|
| 356 |
+
|
| 357 |
+
result = "\n".join(commands)
|
| 358 |
+
return [TextContent(type="text", text=result)]
|
| 359 |
+
|
| 360 |
+
# ============================================================================
|
| 361 |
+
# MCP SERVER CONFIGURATION
|
| 362 |
+
# ============================================================================
|
| 363 |
+
|
| 364 |
+
@mcp_app.list_tools()
|
| 365 |
+
async def list_tools():
|
| 366 |
+
"""List all available Linux system administration tools"""
|
| 367 |
+
return [
|
| 368 |
+
Tool(
|
| 369 |
+
name="user_management",
|
| 370 |
+
description="Generate Linux user management commands for creating, deleting, and managing users and groups",
|
| 371 |
+
inputSchema={
|
| 372 |
+
"type": "object",
|
| 373 |
+
"properties": {
|
| 374 |
+
"action": {
|
| 375 |
+
"type": "string",
|
| 376 |
+
"enum": ["add_user_to_group", "create_user", "delete_user", "list_groups"],
|
| 377 |
+
"description": "User management action to perform"
|
| 378 |
+
},
|
| 379 |
+
"username": {
|
| 380 |
+
"type": "string",
|
| 381 |
+
"description": "Username for the operation"
|
| 382 |
+
},
|
| 383 |
+
"groupname": {
|
| 384 |
+
"type": "string",
|
| 385 |
+
"description": "Group name for the operation"
|
| 386 |
+
},
|
| 387 |
+
"sudo": {
|
| 388 |
+
"type": "boolean",
|
| 389 |
+
"default": True,
|
| 390 |
+
"description": "Whether to use sudo for privileged operations"
|
| 391 |
+
}
|
| 392 |
+
},
|
| 393 |
+
"required": ["action"]
|
| 394 |
+
}
|
| 395 |
+
),
|
| 396 |
+
Tool(
|
| 397 |
+
name="file_permissions",
|
| 398 |
+
description="Generate file permission and ownership commands for chmod, chown, and permission discovery",
|
| 399 |
+
inputSchema={
|
| 400 |
+
"type": "object",
|
| 401 |
+
"properties": {
|
| 402 |
+
"action": {
|
| 403 |
+
"type": "string",
|
| 404 |
+
"enum": ["chmod", "chown", "find_permissions"],
|
| 405 |
+
"description": "File permission action to perform"
|
| 406 |
+
},
|
| 407 |
+
"path": {
|
| 408 |
+
"type": "string",
|
| 409 |
+
"description": "File or directory path"
|
| 410 |
+
},
|
| 411 |
+
"permissions": {
|
| 412 |
+
"type": "string",
|
| 413 |
+
"description": "Permission mode (e.g., 755, 644, u+x)"
|
| 414 |
+
},
|
| 415 |
+
"owner": {
|
| 416 |
+
"type": "string",
|
| 417 |
+
"description": "Owner username for chown operations"
|
| 418 |
+
},
|
| 419 |
+
"recursive": {
|
| 420 |
+
"type": "boolean",
|
| 421 |
+
"default": False,
|
| 422 |
+
"description": "Apply operation recursively"
|
| 423 |
+
}
|
| 424 |
+
},
|
| 425 |
+
"required": ["action", "path"]
|
| 426 |
+
}
|
| 427 |
+
),
|
| 428 |
+
Tool(
|
| 429 |
+
name="system_commands",
|
| 430 |
+
description="Generate common Linux system administration commands for processes, disk, network, services, and firewall",
|
| 431 |
+
inputSchema={
|
| 432 |
+
"type": "object",
|
| 433 |
+
"properties": {
|
| 434 |
+
"category": {
|
| 435 |
+
"type": "string",
|
| 436 |
+
"enum": ["process", "disk", "network", "service", "firewall"],
|
| 437 |
+
"description": "Category of system commands"
|
| 438 |
+
},
|
| 439 |
+
"action": {
|
| 440 |
+
"type": "string",
|
| 441 |
+
"description": "Specific action within the category"
|
| 442 |
+
},
|
| 443 |
+
"target": {
|
| 444 |
+
"type": "string",
|
| 445 |
+
"description": "Target for the operation (service name, hostname, etc.)"
|
| 446 |
+
}
|
| 447 |
+
},
|
| 448 |
+
"required": ["category"]
|
| 449 |
+
}
|
| 450 |
+
)
|
| 451 |
+
]
|
| 452 |
+
|
| 453 |
+
@mcp_app.call_tool()
|
| 454 |
+
async def call_tool(name: str, arguments: dict):
|
| 455 |
+
"""Handle tool calls"""
|
| 456 |
+
try:
|
| 457 |
+
if name == "user_management":
|
| 458 |
+
return handle_user_management(arguments)
|
| 459 |
+
elif name == "file_permissions":
|
| 460 |
+
return handle_file_permissions(arguments)
|
| 461 |
+
elif name == "system_commands":
|
| 462 |
+
return handle_system_commands(arguments)
|
| 463 |
+
else:
|
| 464 |
+
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
| 465 |
+
except Exception as e:
|
| 466 |
+
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
| 467 |
+
|
| 468 |
+
# ============================================================================
|
| 469 |
+
# FASTAPI WEB ENDPOINTS
|
| 470 |
+
# ============================================================================
|
| 471 |
+
|
| 472 |
+
@app.get("/")
|
| 473 |
+
async def root():
|
| 474 |
+
"""Status endpoint"""
|
| 475 |
+
return {
|
| 476 |
+
"service": "linux-mcp-server",
|
| 477 |
+
"status": "running",
|
| 478 |
+
"mcp_endpoint": "/mcp/sse",
|
| 479 |
+
"tools": 3,
|
| 480 |
+
"categories": ["user_management", "file_permissions", "system_commands"]
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
@app.get("/health")
|
| 484 |
+
async def health():
|
| 485 |
+
"""Health check"""
|
| 486 |
+
return {"status": "healthy", "service": "linux-mcp-server"}
|
| 487 |
+
|
| 488 |
+
@app.get("/mcp/sse")
|
| 489 |
+
async def mcp_sse_endpoint(request: Request):
|
| 490 |
+
"""MCP SSE endpoint for agent connections"""
|
| 491 |
+
|
| 492 |
+
async def event_stream():
|
| 493 |
+
try:
|
| 494 |
+
while True:
|
| 495 |
+
if await request.is_disconnected():
|
| 496 |
+
break
|
| 497 |
+
|
| 498 |
+
yield f"data: {json.dumps({'type': 'keepalive'})}\n\n"
|
| 499 |
+
await asyncio.sleep(30)
|
| 500 |
+
|
| 501 |
+
except asyncio.CancelledError:
|
| 502 |
+
pass
|
| 503 |
+
|
| 504 |
+
return StreamingResponse(
|
| 505 |
+
event_stream(),
|
| 506 |
+
media_type="text/event-stream",
|
| 507 |
+
headers={
|
| 508 |
+
"Cache-Control": "no-cache",
|
| 509 |
+
"Connection": "keep-alive",
|
| 510 |
+
"Access-Control-Allow-Origin": "*",
|
| 511 |
+
}
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
def main():
|
| 515 |
+
"""Main entry point"""
|
| 516 |
+
port = int(os.getenv("PORT", 7860))
|
| 517 |
+
|
| 518 |
+
print(f"π Starting Linux MCP Server on port {port}")
|
| 519 |
+
print(f"π Status: http://localhost:{port}/")
|
| 520 |
+
print(f"π MCP SSE: http://localhost:{port}/mcp/sse")
|
| 521 |
+
print(f"π οΈ Tools: User Management, File Permissions, System Commands")
|
| 522 |
+
|
| 523 |
+
uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")
|
| 524 |
+
|
| 525 |
+
if __name__ == "__main__":
|
| 526 |
+
main()
|