Generate cli from mcp tool list
Skill kjuhwa/skills-hub/skills/cli/generate-cli-from-mcp-tool-list
Auto-generate a yargs-based CLI from an MCP server's tool list and JSON schemas so every tool becomes a subcommand with typed positional args for required fields and flags for optional ones.From its SKILL.md
npx -y skills add kjuhwa/skills-hub --skill generate-cli-from-mcp-tool-listAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.8 KB, 789 tokens by cl100k_base, as published. Nobody here has run it
Generate CLI from MCP Tool List
When to use
- You already have an MCP server with N tools and want a shell CLI that mirrors them without hand-writing N yargs commands.
- You want the CLI to stay in sync with new/renamed tools automatically when you rebuild.
- You want required parameters to be positional (
mycli click <uid>) and optional parameters to be flags (--fullPage).
How it works
- In a
scripts/generate-cli.tsbuild step, spin up the real MCP server viaStdioClientTransport, callclient.listTools(), and walk each tool'sinputSchema(standard JSON Schema). - Convert each property to a
{name, type, description, required, default, enum}record. For each tool, emit an entry{description, category, args}into acommandsmap. - Write that map to a generated TS file (
cliDefinitions.ts) as aconstexport. Ship it as source — generated, but checked in — so the CLI binary can run without a codegen step. - In the CLI entry (
bin/mycli.ts), readcommands, and for each entry register a yargs command: required args becomey.positional(name, {type, choices, default}), optional args becomey.option(name, ...). Fallthrough handler callssendCommand({method:'invoke_tool', tool:name, args}). - Filter inappropriate tools at generation time (e.g. skip tools whose schema is an array or nested object — shells don't pass JSON well).
Example
// scripts/generate-cli.ts
const tools = (await client.listTools()).tools.filter(t =>
t.name !== 'fill_form' && t.name !== 'wait_for' // skip complex schemas
);
const commands = Object.fromEntries(tools.map(t => [t.name, {
description: t.description,
category: toolNameToCategory.get(t.name),
args: schemaToCLIOptions(t.inputSchema), // {name,type,required,default,enum}
}]));
fs.writeFileSync(OUTPUT_PATH, `export const commands = ${JSON.stringify(commands, null, 2)} as const;`);
// bin/mycli.ts - at runtime
for (const [name, def] of Object.entries(commands)) {
const required = Object.entries(def.args).filter(([,a]) => a.required);
let cmdStr = name + required.map(([n]) => ` <${n}>`).join('');
y.command(cmdStr, def.description, y => {
for (const [argName, opt] of Object.entries(def.args)) {
opt.required ? y.positional(argName, {...}) : y.option(argName, {...});
}
}, async argv => sendCommand({method:'invoke_tool', tool:name, args: argv}));
}
Gotchas
- JSON-Schema
typecan be a union (["string","null"]); treat that as the non-null variant or skip the tool. Yargs doesn't model nullable well. - Map schema types to yargs types explicitly:
integer|number → number,array → array, everything else →string. Don't assumetype: 'object'tools make sense in a shell. - Pass
CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=true(or your equivalent) when booting the server for codegen — you don't want to spam telemetry from a build script. - Keep an escape hatch in the CLI binary to disable tools that make no sense in a shell (e.g.
fill_formrequiring nested JSON). Filter at generation, not at runtime. - Pre-generate at build time, don't call
listTools()on every CLI invocation — that would defeat a fast daemon-backed CLI.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.