From 002cb8cbc84f545930de0ffb5cbfe35f319bab5b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 7 Nov 2025 14:51:47 +0000 Subject: [PATCH] Allow positional inline commands for generate-cli --- CHANGELOG.md | 2 +- README.md | 2 ++ docs/cli-generator.md | 1 + docs/cli-reference.md | 2 +- docs/spec.md | 4 ++-- src/cli/generate-cli-runner.ts | 36 ++++++++++++++++++++++++++++++- tests/cli-generate-runner.test.ts | 10 +++++++++ 7 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8055ca4..179633b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - Generated CLIs now embed their metadata (generator version, resolved server definition, invocation flags) behind a hidden `__mcporter_inspect` command. `mcporter inspect-cli` / `mcporter generate-cli --from ` read directly from the artifact, while legacy `.metadata.json` sidecars remain as a fallback for older binaries. - Shared the TypeScript signature formatter between `mcporter list` and `mcporter generate-cli`, ensuring command summaries, CLI hints, and generator help stay pixel-perfect and are backed by new snapshot/unit tests. - Introduced `mcporter emit-ts`, a codegen command that emits `.d.ts` tool interfaces or ready-to-run client wrappers (`--mode types|client`, `--include-optional`) using the same doc/comment data that powers the CLI, so agents/tests can consume MCP servers with strong TypeScript types. -- `mcporter generate-cli` now accepts inline stdio commands via `--command "npx -y package@latest"`, automatically splits the command/args, infers a friendly name from scripts or package scopes, and documents the chrome-devtools one-liner in the README; additional unit tests cover HTTP, stdio, and scoped package shorthands. +- `mcporter generate-cli` now accepts inline stdio commands via `--command "npx -y package@latest"` or by quoting the command as the first positional argument, automatically splits the command/args, infers a friendly name from scripts or package scopes, and documents the chrome-devtools one-liner in the README; additional unit tests cover HTTP, stdio, scoped package, and positional shorthand flows. ### Documentation & references - Added `docs/tool-calling.md`, `docs/call-syntax.md`, and `docs/call-heuristic.md` to capture every invocation style (flags, function expressions, inferred verbs) plus the typo-correction rules. diff --git a/README.md b/README.md index 2c74814..d72bb87 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,8 @@ npx mcporter generate-cli \ > > `npx mcporter generate-cli --command "npx -y chrome-devtools-mcp@latest"` +Tip: you can drop `--command` when the inline command is the first positional argument (e.g., `npx mcporter generate-cli "npx -y chrome-devtools-mcp@latest"`). + - `--name` overrides the inferred CLI name. - Add `--description "..."` if you want a custom summary in the generated help output. - Add `--bundle [path]` to emit an esbuild bundle alongside the template. diff --git a/docs/cli-generator.md b/docs/cli-generator.md index 035cc4b..da9caf2 100644 --- a/docs/cli-generator.md +++ b/docs/cli-generator.md @@ -79,6 +79,7 @@ npx mcporter generate-cli --command "npx -y chrome-devtools-mcp@latest" - Omit `--name` to let mcporter infer it from the command URL (for example, `https://mcp.context7.com/mcp` becomes `context7`). - When targeting an existing config entry, you can skip `--server` and pass the name as a positional argument: `npx mcporter generate-cli linear --bundle dist/linear.js`. +- When the MCP server is a stdio command, you can also skip `--command` by quoting the inline command as the first positional argument (e.g., `npx mcporter generate-cli "npx -y chrome-devtools-mcp@latest"`). ``` diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 58d419d..d6e682b 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -27,7 +27,7 @@ A quick reference for the primary `mcporter` subcommands. Each command inherits compiling with Bun). - Key flags: - `--server ` (or inline JSON) – choose the server definition. - - `--command ` – point at an ad-hoc HTTP endpoint or a stdio command (e.g., `"npx -y chrome-devtools-mcp@latest"`); mcporter infers the name when omitted. + - `--command ` – point at an ad-hoc HTTP endpoint or a stdio command (e.g., `"npx -y chrome-devtools-mcp@latest"`); mcporter infers the name when omitted. Quoting the command as the first positional argument works too, so `npx mcporter generate-cli "npx -y chrome-devtools-mcp@latest"` is equivalent. - `--output ` – where to write the TypeScript template. - `--bundle ` – emit a bundle (Node/Bun) ready for `bun x`. - `--compile ` – compile with Bun (implies `--runtime bun`). diff --git a/docs/spec.md b/docs/spec.md index 5951ad3..1e367f8 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -18,7 +18,7 @@ summary: 'Plan for the mcporter package replacing the Sweetistics pnpm MCP helpe - Typed utilities for env/header resolution and stdio command execution. - CLI entry point (`npx mcporter list|call`) built on the same runtime with configurable log levels (`--log-level` flag, `MCPORTER_LOG_LEVEL` env) defaulting to `warn`. Single-server listings must render as TypeScript-style headers: dimmed `/** ... */` doc blocks followed by `function name(...)` signatures, inferred return annotations when schemas expose a `title`, inline enum/format hints, and an optional-parameter summary (`// optional (N): a, b, ...`). Optional fields are hidden by default (unless there are ≤2 of them and <4 required parameters) and the CLI should tell users to run `--all-parameters` whenever anything is suppressed. The CLI should also infer the verb when users omit it: bare server names run `list ` (with the same typo-friendly heuristic used by `mcporter list`), while dotted tokens such as `linear.list_issues` dispatch to `call` automatically. Anonymous HTTP MCP servers (e.g., shadcn) must be auto-detected: if an ad-hoc URL returns MCP-shaped JSON even with a non-200 status, mcporter treats it as authenticated instead of launching the OAuth flow. Likewise, `call` must recognise HTTP selectors that inline the tool name (e.g., `https://www.shadcn.io/api/mcp.getComponent(...)`), strip the `.tool` suffix to derive the base server, parse any function-call arguments, and reuse an existing definition when the base URL matches a configured server. Selectors may omit the protocol entirely—we assume HTTPS when no scheme is present and treat hosts that only differ by a leading `www.` as identical for reuse purposes. See [docs/cli-reference.md](./cli-reference.md) for day-to-day usage/flag details. - CLI generator (`npx mcporter generate-cli`) that emits standalone CLIs (plain TypeScript or bundled JS) with embedded schemas and Commander-based subcommands, targeting Node or Bun. -- CLI generator (`npx mcporter generate-cli`) that emits standalone CLIs (plain TypeScript or bundled JS) with embedded schemas and Commander-based subcommands, targeting Node or Bun. It must accept server references by name (positional), JSON, HTTP URL (with optional `.tool` suffix / missing scheme), or inline stdio commands (split into `command` + `args` so invocations like `--command "npx -y chrome-devtools-mcp@latest"` work without a config entry) just like the main CLI. +- CLI generator (`npx mcporter generate-cli`) that emits standalone CLIs (plain TypeScript or bundled JS) with embedded schemas and Commander-based subcommands, targeting Node or Bun. It must accept server references by name (positional), JSON, HTTP URL (with optional `.tool` suffix / missing scheme), or inline stdio commands (split into `command` + `args` so invocations like `--command "npx -y chrome-devtools-mcp@latest"` or positional equivalents work without a config entry) just like the main CLI. - Test harness using the Sweetistics MCP fixtures to validate every configured server definition. - Documentation: README, usage examples, migration guide for replacing `pnpm mcp:*`. @@ -47,7 +47,7 @@ summary: 'Plan for the mcporter package replacing the Sweetistics pnpm MCP helpe - Back the proxy with targeted unit tests that cover primitive-only calls, positional tuples + option bags, and error fallbacks when schemas are missing. ## Standalone CLI Generation -- `generate-cli` should accept inline JSON, file paths, inline stdio commands, or existing config names and produce a ready-to-run CLI that maps tools to Commander subcommands. +- `generate-cli` should accept inline JSON, file paths, inline stdio commands (either via `--command` or as the first positional argument), or existing config names and produce a ready-to-run CLI that maps tools to Commander subcommands. - Embed schemas (via `listTools { includeSchema: true }`) directly in the generated source so repeat executions avoid additional metadata calls. - Support optional bundling through esbuild, producing Node-friendly `.cjs` files or Bun-ready `.js` binaries with executable shebangs. - Surface flags for output path, runtime target (`node` or `bun`), bundle destination, and per-call timeout (default 30s). diff --git a/src/cli/generate-cli-runner.ts b/src/cli/generate-cli-runner.ts index f0f05f6..637744a 100644 --- a/src/cli/generate-cli-runner.ts +++ b/src/cli/generate-cli-runner.ts @@ -40,7 +40,9 @@ export async function handleGenerateCli(args: string[], globalFlags: FlagMap): P if (position !== -1) { args.splice(position, 1); } - if (looksLikeHttpUrl(positional) || positional.includes('://')) { + if (looksLikeInlineCommand(positional)) { + parsed.command = normalizeCommandInput(positional); + } else if (looksLikeHttpUrl(positional) || positional.includes('://')) { parsed.command = positional; } else { parsed.server = positional; @@ -295,6 +297,23 @@ function parseGenerateFlags(args: string[]): GenerateFlags { index += 1; } + if (!server && !command && !from) { + const positional = args.find((token) => token && !token.startsWith('--')); + if (positional) { + const position = args.indexOf(positional); + if (position !== -1) { + args.splice(position, 1); + } + if (looksLikeInlineCommand(positional)) { + command = normalizeCommandInput(positional); + } else if (looksLikeHttpUrl(positional) || positional.includes('://')) { + command = positional; + } else { + server = positional; + } + } + } + return { server, name, @@ -395,6 +414,21 @@ function stripExtension(value: string): string { return value.slice(0, index); } +function looksLikeInlineCommand(value: string): boolean { + if (!value) { + return false; + } + if (!/\s/.test(value)) { + return false; + } + try { + const parts = splitCommandLine(value.trim()); + return parts.length > 0; + } catch { + return false; + } +} + function deriveNameFromUrl(url: URL): string | undefined { const genericHosts = new Set(['www', 'api', 'mcp', 'service', 'services', 'app', 'localhost']); const knownTlds = new Set(['com', 'net', 'org', 'io', 'ai', 'app', 'dev', 'co', 'cloud']); diff --git a/tests/cli-generate-runner.test.ts b/tests/cli-generate-runner.test.ts index 7b22e5a..7eac749 100644 --- a/tests/cli-generate-runner.test.ts +++ b/tests/cli-generate-runner.test.ts @@ -69,6 +69,16 @@ describe('generate-cli runner internals', () => { expect(inferred).toBe('shadcn'); }); + it('treats positional inline commands as generate-cli targets', () => { + const args = ['npx -y chrome-devtools-mcp@latest']; + const parsed = generateInternals.parseGenerateFlags([...args]); + expect(parsed.command).toBeDefined(); + expect(parsed.server).toBeUndefined(); + const spec = parsed.command as { command: string; args?: string[] }; + expect(spec.command).toBe('npx'); + expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp@latest']); + }); + it('builds regenerate commands honoring global flags and invocation overrides', () => { const definition: SerializedServerDefinition = { name: 'demo',