Skills create-mcp-server
git clone https://github.com/openclaw/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/arterialist/create-mcp-server" ~/.claude/skills/clawdbot-skills-create-mcp-server && rm -rf "$T"
skills/arterialist/create-mcp-server/SKILL.mdCreate MCP Servers with MCPHero
MCPHero lets agents build their own tools. Instead of burning tokens on API schemas, SQL queries, and output parsing every run, the agent creates a persistent MCP server once and calls it forever. A 50,000-token integration becomes a 50-token tool call.
This skill covers the mcpheroctl CLI workflow for building servers end-to-end.
Production API base URL:
https://api.mcphero.app/api
Prerequisites
Before using this skill, the user must have
mcpheroctl installed and authenticated.
Install mcpheroctl
# via Homebrew (macOS/Linux) brew install arterialist/mcpheroctl/mcpheroctl # via uv (cross-platform) uv tool install mcpheroctl
Authenticate
- Log in to the MCPHero Dashboard.
- Go to Settings → Organization → Developers.
- Click Create API key and copy the token.
- Run:
mcpheroctl auth login --token <YOUR_ORG_TOKEN>
Verify
mcpheroctl auth status
The Wizard Pipeline
The CLI follows this linear flow. After any async step, poll
wizard state and check processing_status == "idle" before proceeding.
1. create-session → Returns server_id (save it, needed everywhere) 2. conversation (loop) → Gather requirements; stop when is_ready: true 3. start → Transition to tool suggestion (async → poll) 4. list-tools → Review AI-suggested tools 5. refine-tools (optional) → Iterate on tools until satisfied (async → poll) 6. submit-tools → Confirm selection (deletes unselected tools) 7. (auto env var suggest) → Triggered automatically after submit-tools (async → poll) 8. list-env-vars → Review suggested env vars 9. refine-env-vars (opt.) → Iterate on env vars (async → poll) 10. submit-env-vars → Provide actual values (call even if list is empty — backend needs it to transition) 11. set-auth → Generate bearer token for the server 12. generate-code → Trigger code generation (async → poll) 13. deploy → Deploy to MCPHero runtime → returns server_url + bearer_token
Always call
, even when submit-env-vars
list-env-vars returns []. The backend requires this step to transition to the next state. With no env vars, just call it with no --var flags.
State Machine
The
setup_status field in wizard state tells you where you are:
gathering_requirements → User is chatting about requirements tools_generating → LLM is generating tool suggestions (async, poll) tools_selection → Tools ready for review/selection env_vars_generating → LLM is generating env var suggestions (async, poll) env_vars_setup → Env vars ready for review/submission auth_selection → Ready for auth setup code_generating → LLM is generating code (async, poll) code_gen → Code ready for review deployment_selection → Ready to deploy ready → Server deployed and live
States ending in
_generating are transient — poll until they transition. The processing_status field is the reliable check: "idle" means done, "processing" means wait, "error" means check processing_error.
Full Wizard Example
Always use
--json for scriptable output. Without it, human-friendly messages go to stderr and can confuse parsing.
# 1. Create session mcpheroctl wizard create-session --json # → {"server_id": "abc-123-..."} SERVER_ID="abc-123-..." # 2. Describe requirements (iterate until is_ready: true) mcpheroctl wizard conversation $SERVER_ID --json \ -m "I have a PostgreSQL database with customers and orders tables. I need tools to find customers by name, fetch orders for a customer, and get last hour's orders." # Repeat with follow-up messages until output shows: "is_ready": true # 3. Start tool suggestion (async) mcpheroctl wizard start $SERVER_ID --json # → {"status": "processing"} # Poll until processing is done (check processing_status, not setup_status) until mcpheroctl wizard state $SERVER_ID --json 2>/dev/null | \ python3 -c "import sys,json; exit(0 if json.load(sys.stdin).get('processing_status')=='idle' else 1)"; do sleep 3 done # 4. Review tools (state should be "tools_selection") mcpheroctl wizard list-tools $SERVER_ID --json # 5. Refine if needed (async → poll again) mcpheroctl wizard refine-tools $SERVER_ID --json \ -f "Add error handling for missing customers. Rename get_customers_orders to get_orders_by_customer." # 6. Submit selected tool IDs mcpheroctl wizard submit-tools $SERVER_ID --json \ --tool-id <tool-uuid-1> \ --tool-id <tool-uuid-2> \ --tool-id <tool-uuid-3> # 7. Wait for env var suggestion (auto-triggered, state becomes "env_vars_setup") until mcpheroctl wizard state $SERVER_ID --json 2>/dev/null | \ python3 -c "import sys,json; exit(0 if json.load(sys.stdin).get('processing_status')=='idle' else 1)"; do sleep 3 done # 8. Review env vars mcpheroctl wizard list-env-vars $SERVER_ID --json # 9. Submit env var values (format: VAR_UUID=VALUE) # ALWAYS call this even if list-env-vars returned [] — backend needs it to transition. # With env vars: mcpheroctl wizard submit-env-vars $SERVER_ID --json \ --var "<env-var-uuid-1>=localhost" \ --var "<env-var-uuid-2>=5432" # Without env vars (empty list): mcpheroctl wizard submit-env-vars $SERVER_ID --json # 10. Set authentication mcpheroctl wizard set-auth $SERVER_ID --json # → {"bearer_token": "..."} ← SAVE THIS # 11. Generate code (async → poll) mcpheroctl wizard generate-code $SERVER_ID --json until mcpheroctl wizard state $SERVER_ID --json 2>/dev/null | \ python3 -c "import sys,json; exit(0 if json.load(sys.stdin).get('processing_status')=='idle' else 1)"; do sleep 3 done # 12. Deploy mcpheroctl wizard deploy $SERVER_ID --json # → {"server_url": "/mcp/<server-id>/mcp", "bearer_token": "...", "step": "complete"}
IMPORTANT:
returns a relative deploy
like server_url
/mcp/<id>/mcp. Prepend the base domain to get the full URL:
https://api.mcphero.app/mcp/<server-id>/mcp
Server Management
mcpheroctl server list --json [CUSTOMER_ID] # List all servers mcpheroctl server get SERVER_ID --json # Get server details + status mcpheroctl server update SERVER_ID # Update name/description mcpheroctl server delete SERVER_ID --yes # Delete (irreversible) mcpheroctl server api-key SERVER_ID --json # Retrieve bearer token
Polling Pattern
The reliable way to poll is to check
processing_status, not setup_status:
until mcpheroctl wizard state $SERVER_ID --json 2>/dev/null | \ python3 -c "import sys,json; exit(0 if json.load(sys.stdin).get('processing_status')=='idle' else 1)"; do sleep 3 done
Connecting a Deployed Server to MCP Clients
After
deploy, construct the full server URL:
https://api.mcphero.app{server_url}
Claude Desktop config
{ "mcpServers": { "my-server": { "url": "https://api.mcphero.app/mcp/<server-id>/mcp", "headers": { "Authorization": "Bearer <bearer_token>" } } } }
Config file locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/claude/claude_desktop_config.json
Key Tips
Free tier: Max 5 tools per server.
wizard_submit_tools will error if more are selected.
server_id is everything: Save the UUID returned from
create_session. Every subsequent call needs it.
Env var format in CLI:
--var "UUID=VALUE" — the UUID is the env var's id from list-env-vars, not its name.
Empty env vars: Even if
list-env-vars returns [], you must still call submit-env-vars (with no vars) so the backend transitions to the next state.
Regenerate without redeploy: After using
wizard_regenerate_tool_code, the code change takes effect immediately for already-deployed servers (the server auto-remounts).
Always use
: In CLI commands, --json
--json sends structured data to stdout. Without it, human-friendly messages go to stderr and can break piping/parsing.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General failure |
| 2 | Usage/argument error |
| 3 | Resource not found |
| 4 | Not authenticated |
| 5 | Conflict |