@modeltoolsprotocol/sdk
v0.5.0
Published
Add MTP --describe support to any Commander.js CLI
Readme
@modeltoolsprotocol/sdk
Add MTP --mtp-describe support to any Commander.js CLI. One function call, zero boilerplate.
Install
npm install @modeltoolsprotocol/sdk commanderQuick Start
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>", "Input file path")
.option("--format <fmt>", "Output format", "json")
.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();$ filetool --mtp-describe # MTP JSON schema
$ filetool convert data.csv # normal operationAPI
withDescribe(program, options?)
Adds --mtp-describe to an existing Commander program. When invoked, outputs MTP-compliant JSON and exits.
- program — a Commander
Commandinstance (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
describe(program, options?)
Pure function. Returns the ToolSchema object without side effects. Useful for testing or programmatic access.
How It Works
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.
Type Inference
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" |
| explicit argTypes override | whatever you say |
| everything else | "string" |
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.
Structured IO
When a command accepts or produces JSON, describe the shape with a JSON Schema:
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" } },
},
},
},
},
},
});Command Naming
- Programs with subcommands: each leaf command gets a space-separated path (e.g.,
"auth login") - Programs with no subcommands: single command named
"_root" - Hidden commands and options are excluded
Filtered Options
These are automatically excluded from schema output: --help, --version, --mtp-describe, and any hidden options.
Single-Command Tools
Tools with no subcommands work the same way:
import { Command } from "commander";
import { withDescribe } from "@modeltoolsprotocol/sdk";
const program = new Command()
.name("greet")
.version("1.0.0")
.description("Greet someone")
.argument("<name>", "Name to greet")
.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.
