Add MTP --describe support to any Commander.js CLI
npm install @modeltoolsprotocol/sdkAdd MTP --mtp-describe support to any Commander.js CLI. One function call, zero boilerplate.
``bash`
npm install @modeltoolsprotocol/sdk commander
`typescript
import { Command } from "commander";
import { withDescribe } from "@modeltoolsprotocol/sdk";
const program = new Command()
.name("filetool")
.version("1.0.0")
.description("Convert and validate files");
program
.command("convert")
.argument("", "Input file path")
.option("--format
.option("--pretty", "Pretty-print output")
.action((input, opts) => { / ... / });
withDescribe(program, {
commands: {
convert: {
stdin: { contentType: "text/plain", description: "Raw input" },
stdout: { contentType: "application/json" },
examples: [
{ description: "Convert CSV", command: "filetool convert data.csv --format json" },
],
},
},
});
program.parse();
`
`bash`
$ filetool --mtp-describe # MTP JSON schema
$ filetool convert data.csv # normal operation
Adds --mtp-describe to an existing Commander program. When invoked, outputs MTP-compliant JSON and exits.
- program — a Commander Command instance (your root program)
- options.commands — per-command annotations keyed by command name (stdin, stdout, examples, argTypes)
- options.auth — authentication config to include in the schema
- Returns the program for chaining
Pure function. Returns the ToolSchema object without side effects. Useful for testing or programmatic access.
The SDK introspects Commander's own data structures — cmd.options, cmd.registeredArguments, cmd.commands — so you never duplicate information. Supplemental metadata (stdin/stdout/examples) that Commander doesn't model is provided via the options map.
Arg types describe flags and positional arguments, which are always scalar on the command line:
| Commander signal | MTP type |
|---|---|
| option.isBoolean() | "boolean" |option.argChoices
| | "enum" + values |option.variadic
| / arg.variadic | "array" |argTypes
| explicit override | whatever you say |"string"
| everything else | |
For structured data flowing through stdin/stdout, use the schema field in IO descriptors. This supports full JSON Schema (draft 2020-12): nested objects, arrays, unions, pattern validation, conditional fields.
When a command accepts or produces JSON, describe the shape with a JSON Schema:
`typescript`
withDescribe(program, {
commands: {
process: {
stdin: {
contentType: "application/json",
description: "Configuration to process",
schema: {
type: "object",
properties: {
name: { type: "string" },
settings: {
type: "object",
properties: {
retries: { type: "integer" },
endpoints: { type: "array", items: { type: "string", format: "uri" } },
},
},
},
required: ["name"],
},
},
stdout: {
contentType: "application/json",
schema: {
type: "object",
properties: {
status: { type: "string", enum: ["ok", "error"] },
results: { type: "array", items: { type: "object" } },
},
},
},
},
},
});
- Programs with subcommands: each leaf command gets a space-separated path (e.g., "auth login")"_root"
- Programs with no subcommands: single command named
- Hidden commands and options are excluded
These are automatically excluded from schema output: --help, --version, --mtp-describe, and any hidden options.
Tools with no subcommands work the same way:
`typescript
import { Command } from "commander";
import { withDescribe } from "@modeltoolsprotocol/sdk";
const program = new Command()
.name("greet")
.version("1.0.0")
.description("Greet someone")
.argument("
.option("--loud", "Shout the greeting")
.action((name, opts) => {
const msg = Hello, ${name}!;
console.log(opts.loud ? msg.toUpperCase() : msg);
});
withDescribe(program);
program.parse();
`
This produces a schema with a single _root` command.