From 34dea65a078442adf29844252b967e6ecc8a0b46 Mon Sep 17 00:00:00 2001 From: tanmay11k Date: Wed, 18 Feb 2026 23:27:00 -0500 Subject: [PATCH] feat: MCP server management and skills commands from chat, future-changes doc with UI/skills/MCP plans - mcp list/add/remove commands to manage MCP servers from any channel - skill list/show/create/remove/reload commands for skill management - skill create generates a SKILL.md template, or ask the AI naturally - Updated help text, commands.md with new commands - future-changes.md: WebChat UI overhaul plan (OpenClaw-inspired), MCP management UI, skill creator system, ClawHub/GitHub skill import plans --- docs/commands.md | 29 ++++++ docs/future-changes.md | 96 ++++++++++++++++++ main.py | 215 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 339 insertions(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index 3aa1872..b8457ce 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -79,6 +79,35 @@ After `config set`, run `reload` to apply changes that don't auto-apply (adapter | `cron list` | List all scheduled jobs | | `cron remove ` | Remove a scheduled job by ID | +### MCP Servers + +| Command | Description | +|---|---| +| `mcp list` | List configured MCP servers | +| `mcp add [args]` | Add a new MCP server | +| `mcp remove ` | Remove an MCP server | + +Examples: + +``` +mcp add brave-search uvx brave-search-mcp@latest +mcp add filesystem npx @anthropic-ai/mcp-filesystem /home/user/projects +mcp add github uvx github-mcp-server +mcp remove brave-search +``` + +### Skills + +| Command | Description | +|---|---| +| `skill list` | List all loaded skills | +| `skill show ` | View a skill's content | +| `skill create ` | Create a new skill template | +| `skill remove ` | Remove a skill | +| `skill reload` | Reload all skills | + +You can also create skills via natural language — just ask: "Create a skill for checking stock prices" and the AI will write the SKILL.md for you. + ### AI Chat Any message that isn't a command is sent to the AI. The AI can also trigger actions by including tags in its response: diff --git a/docs/future-changes.md b/docs/future-changes.md index 5d4480b..5c2a544 100644 --- a/docs/future-changes.md +++ b/docs/future-changes.md @@ -188,6 +188,102 @@ Key files to study in `inspirations/nanoclaw/`: ## Other Planned Changes +### WebChat UI Overhaul + +The current chat.html is a minimal single-file UI. Upgrade to match OpenClaw's design language: + +**Design elements from OpenClaw to adopt:** +- Color palette: `--bg: #12141a`, `--card: #181b22`, `--accent: #ff5c5c` (or Aetheel's own brand color) +- Typography: Space Grotesk for body, JetBrains Mono for code +- Chat bubbles: user messages right-aligned with accent background, AI messages left-aligned with card background +- Streaming indicator: pulsing border on AI bubble while generating +- Tool cards: collapsible cards showing tool usage (web search, file ops, etc.) +- Compose area: multi-line textarea with send button, sticky at bottom +- Status bar: connection status, current engine/model display + +**New panels (sidebar or tabs):** +- Settings panel: engine/model/provider switching, timeout config +- MCP Servers panel: add/remove/enable MCP servers +- Skills panel: view loaded skills, create new ones +- Usage panel: cost tracking, request history, rate limit status +- Sessions panel: active sessions, session history + +### MCP Server Management + +Add the ability to manage MCP servers from chat, CLI, and the WebChat UI. + +**Chat commands:** +``` +mcp list # List configured MCP servers +mcp add [args...] # Add a new MCP server +mcp remove # Remove an MCP server +mcp enable # Enable a disabled server +mcp disable # Disable without removing +``` + +**Examples:** +``` +mcp add brave-search uvx brave-search-mcp@latest +mcp add filesystem npx @anthropic-ai/mcp-filesystem /home/user/projects +mcp add github uvx github-mcp-server +mcp remove brave-search +``` + +**Implementation:** +- Chat commands write to `config.json` → `mcp.servers` section +- `write_mcp_config()` already generates `.mcp.json` (Claude) or `opencode.json` (OpenCode) +- After adding/removing, auto-call `write_mcp_config()` and notify user to reload +- WebChat UI: form with name, command, args fields + env vars +- CLI: `aetheel mcp add/remove/list` subcommands + +**Popular MCP servers to suggest during setup:** +- `brave-search` — Web search +- `filesystem` — File system access +- `github` — GitHub API +- `postgres` / `sqlite` — Database access +- `puppeteer` — Browser automation +- `memory` — Persistent memory server + +### Skills System Enhancement + +#### Skill Creator (via chat) + +The AI should be able to create new skills when asked. This works by having the AI write a `SKILL.md` file to the workspace. + +**How it works today:** +- The system prompt already tells the AI: "You can create new skills by writing SKILL.md files to ~/.aetheel/workspace/skills//SKILL.md" +- The AI has file write access via its runtime tools (Bash, Write, Edit) +- So skill creation via natural language already works — just ask: "Create a skill for checking weather" + +**What to add:** +- A `skill` chat command for explicit management: + ``` + skill list # List all loaded skills + skill create # Interactive skill creation wizard + skill remove # Remove a skill + skill reload # Reload all skills (same as /reload) + skill show # Show a skill's SKILL.md content + ``` +- Skill templates: pre-built SKILL.md templates for common use cases +- Skill import from URL: `skill import https://github.com/user/repo/SKILL.md` + +#### ClawhHub / Community Skills + +Integration with external skill sources: + +- **ClawHub.ai**: If they provide an API or registry, add `skill search ` and `skill install ` commands +- **GitHub Anthropic skills**: Import from `github.com/anthropics/claude-code/tree/main/skills/` +- **User-defined skill repos**: `skill import ` clones a repo's skills into the workspace + +**Implementation:** +``` +skill search weather # Search ClawHub/GitHub for skills +skill install clawhub/weather # Install from ClawHub +skill import https://github.com/user/skills-repo # Import from git +``` + +Each imported skill gets its own folder under `~/.aetheel/workspace/skills//` with a `SKILL.md` and optionally a `handler.py`. + ### Security Fixes (from security-audit.md) - Path containment check in `memory/manager.py` `read_file()` diff --git a/main.py b/main.py index 9b45e4c..283098f 100644 --- a/main.py +++ b/main.py @@ -242,6 +242,12 @@ def ai_handler(msg: IncomingMessage) -> str: if cmd.startswith("usage"): return _handle_usage_command() + if cmd.startswith("mcp"): + return _handle_mcp_command(msg.text.strip().lstrip("/")) + + if cmd.startswith("skill"): + return _handle_skill_command(msg.text.strip().lstrip("/")) + # Build context from memory + skills context = _build_context(msg) @@ -894,10 +900,206 @@ def _handle_usage_command() -> str: # --------------------------------------------------------------------------- -# Scheduler Callback +# MCP Server Management Commands # --------------------------------------------------------------------------- +def _handle_mcp_command(text: str) -> str: + """ + Handle mcp commands — manage MCP servers from chat. + + mcp list List configured servers + mcp add [args...] Add a server + mcp remove Remove a server + mcp enable Enable a disabled server + mcp disable Disable a server + """ + import json as _json + + parts = text.strip().split(maxsplit=3) + subcommand = parts[1] if len(parts) > 1 else "list" + + cfg = load_config() + + if subcommand == "list": + if not cfg.mcp.servers: + return "📦 No MCP servers configured.\n\nAdd one: `mcp add [args]`" + lines = ["📦 *MCP Servers:*\n"] + for name, srv in cfg.mcp.servers.items(): + args_str = " ".join(srv.args) if srv.args else "" + lines.append(f"• `{name}` — `{srv.command} {args_str}`") + lines.append(f"\nManage: `mcp add`, `mcp remove `") + return "\n".join(lines) + + elif subcommand == "add" and len(parts) >= 3: + rest = text.strip().split(maxsplit=2)[2] # everything after "mcp add" + add_parts = rest.split() + name = add_parts[0] + command = add_parts[1] if len(add_parts) > 1 else "uvx" + args = add_parts[2:] if len(add_parts) > 2 else [] + + # Update config + server_data = {"command": command, "args": args, "env": {}} + _update_config_file({"mcp": {"servers": {name: server_data}}}) + + # Rewrite MCP config for the runtime + new_cfg = load_config() + write_mcp_config(new_cfg.mcp, new_cfg.memory.workspace, _use_claude) + + return ( + f"✅ MCP server `{name}` added\n" + f"• Command: `{command} {' '.join(args)}`\n" + f"Run `reload` to apply." + ) + + elif subcommand == "remove" and len(parts) >= 3: + name = parts[2].strip() + try: + with open(CONFIG_PATH, "r", encoding="utf-8") as f: + data = _json.load(f) + servers = data.get("mcp", {}).get("servers", {}) + if name not in servers: + return f"⚠️ MCP server `{name}` not found." + del servers[name] + with open(CONFIG_PATH, "w", encoding="utf-8") as f: + _json.dump(data, f, indent=2) + + new_cfg = load_config() + write_mcp_config(new_cfg.mcp, new_cfg.memory.workspace, _use_claude) + return f"✅ MCP server `{name}` removed." + except Exception as e: + return f"⚠️ Failed to remove: {e}" + + else: + return ( + "Usage:\n" + "• `mcp list` — List servers\n" + "• `mcp add [args]` — Add a server\n" + "• `mcp remove ` — Remove a server\n" + "\nExamples:\n" + "• `mcp add brave-search uvx brave-search-mcp@latest`\n" + "• `mcp add filesystem npx @anthropic-ai/mcp-filesystem /home/user`\n" + "• `mcp add github uvx github-mcp-server`" + ) + + +# --------------------------------------------------------------------------- +# Skills Management Commands +# --------------------------------------------------------------------------- + + +def _handle_skill_command(text: str) -> str: + """ + Handle skill commands — manage skills from chat. + + skill list List loaded skills + skill show Show a skill's content + skill create Create a new skill interactively + skill remove Remove a skill + skill reload Reload all skills + """ + global _skills + + parts = text.strip().split(maxsplit=2) + subcommand = parts[1] if len(parts) > 1 else "list" + + if subcommand == "list": + if not _skills or not _skills.skills: + return ( + "🎯 No skills loaded.\n\n" + "Create one: `skill create `\n" + "Or ask me naturally: \"Create a skill for checking weather\"" + ) + lines = ["🎯 *Skills:*\n"] + for s in _skills.skills: + triggers = ", ".join(s.triggers[:4]) + lines.append(f"• `{s.name}` — {s.description}\n Triggers: {triggers}") + lines.append(f"\nManage: `skill show `, `skill create `, `skill remove `") + return "\n".join(lines) + + elif subcommand == "show" and len(parts) >= 3: + name = parts[2].strip() + if not _skills: + return "⚠️ Skills system not initialized." + for s in _skills.skills: + if s.name.lower() == name.lower(): + content = s.body[:2000] + return ( + f"🎯 *{s.name}*\n" + f"_{s.description}_\n" + f"Triggers: {', '.join(s.triggers)}\n\n" + f"```\n{content}\n```" + ) + return f"⚠️ Skill `{name}` not found. Run `skill list` to see available skills." + + elif subcommand == "create" and len(parts) >= 3: + name = parts[2].strip().lower().replace(" ", "-") + cfg = load_config() + workspace = os.path.expanduser(cfg.memory.workspace) + skill_dir = os.path.join(workspace, "skills", name) + skill_path = os.path.join(skill_dir, "SKILL.md") + + if os.path.exists(skill_path): + return f"⚠️ Skill `{name}` already exists at `{skill_path}`" + + os.makedirs(skill_dir, exist_ok=True) + template = ( + f"---\n" + f"name: {name}\n" + f"description: TODO — describe what this skill does\n" + f"triggers: [{name}]\n" + f"---\n\n" + f"# {name.title()} Skill\n\n" + f"\n\n" + f"When the user asks about {name}:\n" + f"1. ...\n" + f"2. ...\n" + f"3. ...\n" + ) + with open(skill_path, "w", encoding="utf-8") as f: + f.write(template) + + return ( + f"✅ Skill `{name}` created at:\n" + f"`{skill_path}`\n\n" + f"Edit the SKILL.md to add your instructions, then run `skill reload`.\n" + f"Or just tell me what the skill should do and I'll write it for you." + ) + + elif subcommand == "remove" and len(parts) >= 3: + name = parts[2].strip() + cfg = load_config() + workspace = os.path.expanduser(cfg.memory.workspace) + skill_dir = os.path.join(workspace, "skills", name) + + if not os.path.isdir(skill_dir): + return f"⚠️ Skill `{name}` not found." + + import shutil + shutil.rmtree(skill_dir) + if _skills: + _skills.reload() + return f"✅ Skill `{name}` removed." + + elif subcommand == "reload": + if _skills: + loaded = _skills.reload() + return f"🔄 Reloaded {len(loaded)} skill(s)." + return "⚠️ Skills system not initialized." + + else: + return ( + "Usage:\n" + "• `skill list` — List loaded skills\n" + "• `skill show ` — Show skill content\n" + "• `skill create ` — Create a new skill template\n" + "• `skill remove ` — Remove a skill\n" + "• `skill reload` — Reload all skills\n" + "\nYou can also create skills naturally:\n" + "\"Create a skill for checking stock prices\"" + ) + + def _on_scheduled_job(job: ScheduledJob) -> None: """ Called by the scheduler when a job fires. @@ -1089,6 +1291,17 @@ def _format_help() -> str: "• `cron remove ` — Remove a scheduled job\n" "• `subagents` — List active background tasks\n" "\n" + "*MCP Servers:*\n" + "• `mcp list` — List configured MCP servers\n" + "• `mcp add [args]` — Add a server\n" + "• `mcp remove ` — Remove a server\n" + "\n" + "*Skills:*\n" + "• `skill list` — List loaded skills\n" + "• `skill create ` — Create a new skill\n" + "• `skill show ` — View a skill\n" + "• `skill remove ` — Remove a skill\n" + "\n" "*AI Chat:*\n" "• Send any other message and the AI will respond\n" "• Each thread maintains its own conversation\n"