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
This commit is contained in:
2026-02-18 23:27:00 -05:00
parent b8b3f44d52
commit 34dea65a07
3 changed files with 339 additions and 1 deletions

View File

@@ -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 <id>` | Remove a scheduled job by ID |
### MCP Servers
| Command | Description |
|---|---|
| `mcp list` | List configured MCP servers |
| `mcp add <name> <command> [args]` | Add a new MCP server |
| `mcp remove <name>` | 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 <name>` | View a skill's content |
| `skill create <name>` | Create a new skill template |
| `skill remove <name>` | 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:

View File

@@ -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 <name> <command> [args...] # Add a new MCP server
mcp remove <name> # Remove an MCP server
mcp enable <name> # Enable a disabled server
mcp disable <name> # 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/<name>/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 <name> # Interactive skill creation wizard
skill remove <name> # Remove a skill
skill reload # Reload all skills (same as /reload)
skill show <name> # 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 <query>` and `skill install <name>` commands
- **GitHub Anthropic skills**: Import from `github.com/anthropics/claude-code/tree/main/skills/`
- **User-defined skill repos**: `skill import <git-url>` 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/<name>/` with a `SKILL.md` and optionally a `handler.py`.
### Security Fixes (from security-audit.md)
- Path containment check in `memory/manager.py` `read_file()`

215
main.py
View File

@@ -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 <name> <command> [args...] Add a server
mcp remove <name> Remove a server
mcp enable <name> Enable a disabled server
mcp disable <name> 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 <name> <command> [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 <name>`")
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 <name> <command> [args]` — Add a server\n"
"• `mcp remove <name>` — 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 <name> Show a skill's content
skill create <name> Create a new skill interactively
skill remove <name> 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 <name>`\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 <name>`, `skill create <name>`, `skill remove <name>`")
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"<!-- Instructions for the AI when this skill is triggered -->\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 <name>` — Show skill content\n"
"• `skill create <name>` — Create a new skill template\n"
"• `skill remove <name>` — 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 <id>` — Remove a scheduled job\n"
"• `subagents` — List active background tasks\n"
"\n"
"*MCP Servers:*\n"
"• `mcp list` — List configured MCP servers\n"
"• `mcp add <name> <cmd> [args]` — Add a server\n"
"• `mcp remove <name>` — Remove a server\n"
"\n"
"*Skills:*\n"
"• `skill list` — List loaded skills\n"
"• `skill create <name>` — Create a new skill\n"
"• `skill show <name>` — View a skill\n"
"• `skill remove <name>` — 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"