Initial commit: Agentic OS troubleshooting platform

Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
"""Generic SSH MCP server.
Read-only by default. Mutating commands are blocked unless SSH_ALLOW_WRITES=true.
Targets can be set with ssh_connect and reused, or passed per-call.
Conventions mirror the other device MCPs in this stack (saved active target,
*_ALLOW_WRITES gate for writes).
"""
from __future__ import annotations
import os
import re
import shlex
import paramiko
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("ssh-generic")
ALLOW_WRITES = os.environ.get("SSH_ALLOW_WRITES", "false").lower() == "true"
# Heuristic denylist for mutating operations when writes are not allowed.
_WRITE_TOKENS = (
"rm ", "mv ", "dd ", "mkfs", "systemctl start", "systemctl stop", "systemctl restart",
"systemctl disable", "systemctl enable", "iptables", "nft ", "ip route add",
"ip route del", "ip addr add", "ip link set", "apt", "yum", "dnf", "tee ",
"chmod", "chown", "passwd", "useradd", "userdel", "mount", "umount",
)
# Whole-word matches only (avoid false positives e.g. "halt" inside "head").
_WRITE_WORDS = re.compile(r"\b(reboot|shutdown|poweroff|halt|kill|pkill)\b")
# Shell output redirect to a file (allow stderr redirects like 2>/dev/null).
_WRITE_REDIRECT = re.compile(r"(?<![0-9])>(?!>)|>>")
# Active target: {"host","port","username","password"}
_active: dict | None = None
def _resolve(host, port, username, password) -> dict:
if host:
return {
"host": host,
"port": port or 22,
"username": username or "root",
"password": password,
}
if _active:
return _active
raise RuntimeError("No SSH target. Call ssh_connect first or pass host/username.")
def _client(target: dict) -> paramiko.SSHClient:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
hostname=target["host"],
port=int(target.get("port", 22)),
username=target.get("username"),
password=target.get("password"),
timeout=15,
allow_agent=False,
look_for_keys=bool(not target.get("password")),
)
return client
def _is_write(command: str) -> bool:
low = command.lower()
if _WRITE_WORDS.search(low):
return True
if _WRITE_REDIRECT.search(command):
return True
return any(tok in low for tok in _WRITE_TOKENS)
@mcp.tool()
def ssh_connect(host: str, username: str = "root", password: str | None = None, port: int = 22) -> str:
"""Set the active SSH target and verify connectivity."""
global _active
target = {"host": host, "port": port, "username": username, "password": password}
try:
c = _client(target)
c.close()
except Exception as exc: # noqa: BLE001
return f"connect failed: {exc}"
_active = target
return f"connected to {username}@{host}:{port}"
@mcp.tool()
def ssh_test_connection(host: str | None = None, username: str | None = None,
password: str | None = None, port: int = 22) -> str:
"""Test SSH connectivity to a host."""
target = _resolve(host, port, username, password)
try:
c = _client(target)
c.close()
return "ok"
except Exception as exc: # noqa: BLE001
return f"failed: {exc}"
@mcp.tool()
def ssh_run(command: str, host: str | None = None, username: str | None = None,
password: str | None = None, port: int = 22, timeout: int = 30) -> str:
"""Run a shell command over SSH. Mutating commands require SSH_ALLOW_WRITES=true."""
if _is_write(command) and not ALLOW_WRITES:
return (
"blocked: this looks like a mutating command and SSH_ALLOW_WRITES is false. "
"Use the approval flow / enable writes to proceed."
)
target = _resolve(host, port, username, password)
client = _client(target)
try:
_, stdout, stderr = client.exec_command(command, timeout=timeout)
out = stdout.read().decode(errors="replace")
err = stderr.read().decode(errors="replace")
code = stdout.channel.recv_exit_status()
result = out
if err:
result += f"\n[stderr]\n{err}"
return f"[exit {code}]\n{result}".strip()
finally:
client.close()
@mcp.tool()
def ssh_service_status(service: str, host: str | None = None, username: str | None = None,
password: str | None = None, port: int = 22) -> str:
"""Read-only systemd service status."""
return ssh_run(f"systemctl status {shlex.quote(service)} --no-pager",
host=host, username=username, password=password, port=port)
if __name__ == "__main__":
mcp.run()