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 list` | List all scheduled jobs |
| `cron remove <id>` | Remove a scheduled job by ID | | `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 ### 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: 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 ## 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) ### Security Fixes (from security-audit.md)
- Path containment check in `memory/manager.py` `read_file()` - 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"): if cmd.startswith("usage"):
return _handle_usage_command() 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 # Build context from memory + skills
context = _build_context(msg) 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: def _on_scheduled_job(job: ScheduledJob) -> None:
""" """
Called by the scheduler when a job fires. Called by the scheduler when a job fires.
@@ -1089,6 +1291,17 @@ def _format_help() -> str:
"• `cron remove <id>` — Remove a scheduled job\n" "• `cron remove <id>` — Remove a scheduled job\n"
"• `subagents` — List active background tasks\n" "• `subagents` — List active background tasks\n"
"\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" "*AI Chat:*\n"
"• Send any other message and the AI will respond\n" "• Send any other message and the AI will respond\n"
"• Each thread maintains its own conversation\n" "• Each thread maintains its own conversation\n"