Problem
Adding a skill or an MCP server today means the user does the work, by hand:
- Skills —
/skill new <name> (src/cli/ui/slash/handlers/skill.ts:9) writes a stub SKILL.md. Filling in the frontmatter (name, description, allowed-tools, runAs, model) + body is on the user. They have to know the schema.
- MCP servers — first-run wizard or
/mcp slash / reasonix mcp CLI. The user picks from the catalog or types command + args + env themselves.
So when a user types into chat "add a skill that runs typecheck before I commit" or "hook up a postgres MCP server pointed at my local db", the agent's only options are:
- Tell the user to run a slash command and write the file themselves.
write_file a SKILL.md by guessing the path + schema, then hope the loader picks it up.
Both are bad. (1) defeats the point of having an agent. (2) bypasses validation — invalid frontmatter, conflict with existing skill name, wrong scope (project vs global), etc. all fail silently or at next launch.
What I want
Give the model first-class tools so "add a skill that does X" / "add an MCP server for Y" works as a normal chat request. Two new tools, both gated by the same permission flow as everything else:
create_skill
Wraps SkillStore.create() (src/skills.ts). Args:
name (required) — validated against VALID_SKILL_NAME (skills.ts:13).
scope — \"project\" | \"global\". Default project when there is one, else global.
description, allowed_tools[], run_as (inline | subagent), model — pre-fills frontmatter so the model doesn't have to write raw YAML and risk indentation bugs.
body — markdown body of the skill prompt.
Returns the resolved path + scope. Reuses the existing name-collision / validation paths so we don't reinvent error handling.
add_mcp_server
Wraps the same persistence path the wizard / /mcp CLI uses (src/cli/commands/mcp.ts + config.ts). Args:
name (required, unique per scope).
scope — \"project\" | \"global\".
command, args[], env{} for stdio servers; url + transport: \"sse\" | \"streamable-http\" for remote.
from_catalog — optional shortcut: name out of MCP_CATALOG (src/mcp/catalog.ts) → fill the rest from the bundled entry.
Runs the same preflight (src/mcp/preflight.ts) the wizard runs, so the model gets immediate feedback if the command fails to spawn / handshake. Persists via writeConfig only on preflight pass.
Why tools, not a slash-command shim
Asking the model to "emit a /skill new foo command for the user to run" is the wrong shape — the user typed in chat because they wanted the agent to do it. We already give the model write_file, run_command, etc.; skill + MCP scaffolding is no more privileged than those.
Both tools should be in the default allow-list of skill runAs: subagent too — init-style skills that bootstrap a project ought to be able to drop in their own helpers.
Out of scope
- Editing / removing existing skills + MCP servers from chat. Add separately if needed; create-only covers the common ask.
- A natural-language "do you mean…" disambiguator for catalog matches. The model can negotiate that itself before calling the tool.
- Auto-detecting which skills the project "should" have. The user (or
init) drives that.
Problem
Adding a skill or an MCP server today means the user does the work, by hand:
/skill new <name>(src/cli/ui/slash/handlers/skill.ts:9) writes a stubSKILL.md. Filling in the frontmatter (name,description,allowed-tools,runAs,model) + body is on the user. They have to know the schema./mcpslash /reasonix mcpCLI. The user picks from the catalog or types command + args + env themselves.So when a user types into chat "add a skill that runs typecheck before I commit" or "hook up a postgres MCP server pointed at my local db", the agent's only options are:
write_filea SKILL.md by guessing the path + schema, then hope the loader picks it up.Both are bad. (1) defeats the point of having an agent. (2) bypasses validation — invalid frontmatter, conflict with existing skill name, wrong scope (project vs global), etc. all fail silently or at next launch.
What I want
Give the model first-class tools so "add a skill that does X" / "add an MCP server for Y" works as a normal chat request. Two new tools, both gated by the same permission flow as everything else:
create_skillWraps
SkillStore.create()(src/skills.ts). Args:name(required) — validated againstVALID_SKILL_NAME(skills.ts:13).scope—\"project\"|\"global\". Default project when there is one, else global.description,allowed_tools[],run_as(inline|subagent),model— pre-fills frontmatter so the model doesn't have to write raw YAML and risk indentation bugs.body— markdown body of the skill prompt.Returns the resolved path + scope. Reuses the existing name-collision / validation paths so we don't reinvent error handling.
add_mcp_serverWraps the same persistence path the wizard /
/mcpCLI uses (src/cli/commands/mcp.ts+config.ts). Args:name(required, unique per scope).scope—\"project\"|\"global\".command,args[],env{}for stdio servers;url+transport: \"sse\" | \"streamable-http\"for remote.from_catalog— optional shortcut: name out ofMCP_CATALOG(src/mcp/catalog.ts) → fill the rest from the bundled entry.Runs the same preflight (
src/mcp/preflight.ts) the wizard runs, so the model gets immediate feedback if the command fails to spawn / handshake. Persists viawriteConfigonly on preflight pass.Why tools, not a slash-command shim
Asking the model to "emit a
/skill new foocommand for the user to run" is the wrong shape — the user typed in chat because they wanted the agent to do it. We already give the modelwrite_file,run_command, etc.; skill + MCP scaffolding is no more privileged than those.Both tools should be in the default allow-list of skill
runAs: subagenttoo —init-style skills that bootstrap a project ought to be able to drop in their own helpers.Out of scope
init) drives that.