Skip to content

fix(cli): the generated CLI says unknown command, prints an MCP error prefix once, and offers load_skill only the run lines it has - #1140

Merged
alexmercerpo merged 6 commits into
fix/skilled-confirm-edge-auditfrom
fix/cli-skilled-messages
Nov 5, 2026
Merged

alexmercerpo merged 6 commits into
fix/skilled-confirm-edge-auditfrom
fix/cli-skilled-messages

Conversation

@frontegg-david

Copy link
Copy Markdown
Contributor

Stacks on #1139.

What and why

Three messages of a generated CLI (frontmcp build --target cli) were wrong.

A failed read printed the MCP error prefix twice. <bin> resource read app://missing printed Error: MCP error -32002: MCP error -32002: Resource not found: app://missing. Before protocol 2026-07-28, the server sends its McpError's message as it is, and that message already starts with MCP error <code>: . The client then adds the prefix again. A tool's error has no prefix (Error: Cannot divide by zero), so a read's error now reads the same way:

  • _exitWithError, the error path every generated command shares, removes the leading MCP error <code>: prefixes before it prints. It covers resources, templates, prompts and a tool call that throws.
  • The Skilled OpenAPI commands print their own errors, so they remove the prefix too. That fixes skills read <missing>.
  • The pattern is defined once, as MCP_ERROR_PREFIX beside the error snippet. The build copies the snippet's compiled module into the CLI bundle, next to skilled-commands, which imports it.
  • The exit code doesn't change (1).

An unknown command said too many arguments. The root program had an action that printed the help. With a root action, commander takes an unknown first word as an argument of the root, and commander 13 refuses the extra argument. The footer's command:* listener (Unknown command: X, exit 1) never ran: commander only calls it when the root has no action. Both are removed, so:

  • commander answers error: unknown command '<name>', with its (Did you mean <command>?) suggestion when a command is close, and exits 2 (commander.unknownCommand in the existing exitOverride);
  • a CLI started with no command still prints its help on standard output and exits 0, because the footer parses it as --help;
  • commands that take variadic arguments (search_skill [query...], skills load <skills...>) are matched by name before this point, so they work as before. allowExcessArguments is untouched.

load_skill printed execute lines when the CLI had no execute command. With cli.excludeTools: ['execute_action'], the CLI has no execute command, but load_skill still printed <bin> execute <actionId> --input '<json>' for each action. The plugin has no option of its own to leave out one of its tools. A server that doesn't list all four of them gets no Skilled OpenAPI commands at all, so cli.excludeTools is the case that reaches this code. The line now follows the commands the CLI has:

  • execute, when the CLI has it, as before;
  • else a workflow of one call, when it has run_workflow: <bin> run_workflow 'return await callTool("getInvoice", {"id":"<id>"});'. install already wrote this form in a bundle skill's SKILL.md. It is now one function, workflowCommandLine, which both use;
  • else no line. The command's help then drops "with a ready command for each".

The plugin's load_skill is unchanged. It returns the actions as data, and the CLI formats them.

Scenarios covered: a missing resource (in-process), a missing skill through skills read, an unknown command, a mistyped command (with its suggestion), a top-level skill command on a server with tools of its own, a bare invocation (help on standard output), and load_skill without execute_action (with run_workflow, and without either).

User-facing change

  • What changed:
    • A generated CLI prints a failed call's error without the JSON-RPC MCP error <code>: prefix.
    • An unknown command says unknown command '<name>', with commander's suggestion, and exits 2.
    • Without execute_action (cli.excludeTools), load_skill prints each action as a one-call run_workflow script, or prints no command line when run_workflow is left out too.
  • Before → after:
    • <bin> resource read app://missing: Error: MCP error -32002: MCP error -32002: Resource not found: app://missing → Error: Resource not found: app://missing (exit 1 both).
    • <bin> skills read refunds (Skilled OpenAPI): Error: MCP error -32002: MCP error -32002: Resource not found: skill://refunds/SKILL.md → Error: Resource not found: skill://refunds/SKILL.md.
    • <bin> nonexistent-command: error: too many arguments. Expected 0 arguments but got 1. → error: unknown command 'nonexistent-command' (exit 2 both).
    • <bin> greeet: error: too many arguments. ... → error: unknown command 'greeet' and (Did you mean greet?).
    • <bin> load_skill invoices with execute_action excluded: <bin> execute getInvoice --input '{"id":"<id>"}' → <bin> run_workflow 'return await callTool("getInvoice", {"id":"<id>"});'. With run_workflow excluded too: no command line.
    • <bin> --output json with no command (options only): the help went to standard output → it goes to standard error (exit 0 both). A bare <bin> still prints it on standard output.
  • frontmcp.dev pages affected (if known): the CLI build target page (the generated CLI's errors and unknown commands, and the Skilled OpenAPI commands with cli.excludeTools).
  • Skills catalog entries updated:
    • frontmcp-deployment/references/build-for-cli.md: a new "Usage errors and failed calls" section, load_skill without execute_action, and a troubleshooting row for error: unknown command.

The CLI's README is updated too.

Tests

Added:

  • extract-public-message-parity.spec.ts (2 new tests): _exitWithError prints a doubled-prefix error as Error: Resource not found: app://missing, drops a single prefix (the daemon's), and leaves a message without a prefix as it is.
  • skilled-commands.spec.ts (2 new tests, 1 changed):
    • with run_workflow but not execute_action, load_skill prints each action as a one-call workflow, and no execute line;
    • with neither, it prints the actions with no command line, and skills load's help says so;
    • the test client's missing resource now throws as the in-process client does (with the prefix twice), and skills read prints it without the prefix.
  • generated-cli-smoke.spec.ts (2 new tests): the generated CLI runs in the test process with stubbed runtime modules. An unknown command prints error: unknown command 'gret' and (Did you mean greet?) and exits 2. No command prints the help on standard output and exits 0. The first test fails with the old root action.
  • generate-cli-entry.spec.ts and generated-cli-smoke.spec.ts: the source checks for the old root action and parseAsync(process.argv) now check the new footer.
  • demo-e2e-cli-exec:
    • cli-errors.e2e.spec.ts: an unknown command says error: unknown command 'nonexistent-command' exactly (it accepted too many arguments before), and a mistyped greeet suggests greet;
    • cli-resources.e2e.spec.ts: resource read app://missing exits 1 with Error: Resource not found: app://missing on stderr;
    • cli-skilled-openapi.e2e.spec.ts: skills read refunds prints the error without MCP error; on a server with tools of its own, search_skill says unknown command 'search_skill'; a new project whose CLI excludes execute_action prints run_workflow lines from load_skill, no execute line, and has no execute command.

Run (after merging #1139, which has #1138's latest):

  • yarn install --immutable, then npx nx run-many -t build -p sdk,adapters,cli,plugin-skilled-openapi,testing,plugin-cache,plugin-remember: passed.
  • npx nx test cli: 133 suites, 2235 tests, passed.
  • npx nx test plugin-skilled-openapi: 53 suites, 844 tests, passed.
  • npx nx test skills: 3 suites, 249 tests, passed.
  • npx nx test demo-e2e-cli-exec: 44 suites, 270 tests. 262 passed and 6 skipped. 2 failed because they need Docker, which this machine doesn't have: cli-build-tasks-redis and cli-build-decorated-stores start Redis with testcontainers. The three touched specs (cli-skilled-openapi, cli-errors, cli-resources) passed.
  • npx nx run cli:typecheck, npx tsc --noEmit -p libs/cli/tsconfig.lib.json and npx nx run demo-e2e-cli-exec:typecheck-e2e: no errors.
  • Prettier, ESLint and the unused-import cleanup on the changed files: clean.
  • No SDK change, so the bundle-size e2e wasn't run.

…ed, and load_skill offers only commands the CLI has

A generated CLI printed a failed resource read as "MCP error -32002: MCP error
-32002: Resource not found: ...": the server's McpError message already carries
the prefix, and the client adds it again. _exitWithError and the Skilled
OpenAPI commands now drop the leading prefixes, so the error reads as a tool's
error does.

An unknown command said "too many arguments": the root program's help action
took the word as its own argument. Without that action, commander answers
"unknown command '<name>'" with its suggestion, and exits 2. A bare invocation
still prints the help, parsed as --help. The command:* listener, which never
ran, is removed.

With execute_action in cli.excludeTools, load_skill still printed an execute
line for each action. It now prints a one-call run_workflow script instead, or
no line when run_workflow is left out too.
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 28 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 17dcc39 and f9aa965.

Files ignored due to path filters: 9

  • libs/cli/src/commands/build/exec/__tests__/generate-cli-entry.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/__tests__/generated-cli-smoke.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/__tests__/extract-public-message-parity.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/__tests__/skilled-commands.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/bundle-skills.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/extract-public-message.snippet.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/generate-cli-entry.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/skilled-commands.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/index.ts is excluded by !**/build/**
Files selected for processing: 5

  • apps/e2e/demo-e2e-cli-exec/e2e/cli-errors.e2e.spec.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/cli-resources.e2e.spec.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/cli-skilled-openapi.e2e.spec.ts
  • libs/cli/README.md
  • libs/skills/catalog/frontmcp-deployment/references/build-for-cli.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-05T17:44:59.444Z
Commit: 709d6e09

@alexmercerpo
alexmercerpo merged commit 4cfe9a2 into release/1.11.x Nov 5, 2026
46 checks passed
@alexmercerpo
alexmercerpo deleted the fix/cli-skilled-messages branch November 5, 2026 17:54
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Cherry-picked with its stack

This PR is part of stack #1121. Its cherry-pick to main is made as one PR for the whole stack when the stack's top PR, #1142, merges.

If #1142 does not merge, run the Cherry-pick to main workflow by hand with stack: 1121.

frontegg-david added a commit that referenced this pull request Nov 5, 2026
* Cherry-pick: fix(adapters,plugins,edge): an older saas bundle, pulled or pushed, doesn't roll the server back

Cherry-picked from #1119 (merged to release/1.11.x)
Original commit: d88f422

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,plugins): an @app's Skilled OpenAPI credential resolver reaches execute_action and this.callTool() of an operation

Cherry-picked from #1120 (merged to release/1.11.x)
Original commit: 00a3b00

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(adapters,plugins,edge): a push token replayed to another replica or Worker isolate is refused

Cherry-picked from #1122 (merged to release/1.11.x)
Original commit: 1341026

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(plugins): a skill a skills:filter hook hides can't be run by naming its actions

Cherry-picked from #1123 (merged to release/1.11.x)
Original commit: a081fbf

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk): a plugin listed twice in one plugins list is refused at startup instead of keeping only its last entry

Cherry-picked from #1127 (merged to release/1.11.x)
Original commit: 3dbadd3

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(plugins,edge): a Cron run that doesn't apply its bundle fails, and /readyz says the bundle served is stale

Cherry-picked from #1128 (merged to release/1.11.x)
Original commit: 4938d21

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk): connect() with an authToken connects an anonymous caller again on a server without auth

Cherry-picked from #1129 (merged to release/1.11.x)
Original commit: 3d238da

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(auth,adapters,plugins,edge): operators can tell why a pull or push token was refused, while callers still get the generic answer

Cherry-picked from #1130 (merged to release/1.11.x)
Original commit: 913f4f5

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,auth,plugins): this.authProviders works: the SDK wires the AuthProviders vault from a new authProvidersVault option

Cherry-picked from #1131 (merged to release/1.11.x)
Original commit: e14c30c

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,auth): /oauth/connect connects a verified caller who has no vault yet, and asks an anonymous one to sign in

Cherry-picked from #1132 (merged to release/1.11.x)
Original commit: f46a87a

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,plugins): the SDK skill surfaces say a plugin's skills haven't loaded yet, retryable, and carry bundleVersion

Cherry-picked from #1133 (merged to release/1.11.x)
Original commit: 0d1dc94

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,auth,cli,plugins): the credentials-required error keeps its connect link out of the message, and asks through URL elicitation where the client supports it

Cherry-picked from #1134 (merged to release/1.11.x)
Original commit: 6d8ea24

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk): a health check registered after startup runs in /readyz, and registerHealthProbe returns a remover

Cherry-picked from #1135 (merged to release/1.11.x)
Original commit: 7f2f48f

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,plugins): a bundle swap sends one list_changed per list it changed, not one per entry

Cherry-picked from #1136 (merged to release/1.11.x)
Original commit: 0296291

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(adapters,plugins,edge): a restarted server serves its cached bundle in place of an older or refused pull, and doesn't write it back

Cherry-picked from #1137 (merged to release/1.11.x)
Original commit: 358d114

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,adapters,plugins,edge): a push token with a body_sha256 claim can't carry another bundle

Cherry-picked from #1138 (merged to release/1.11.x)
Original commit: b870325

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(plugins,edge): managed mode takes confirm, and the audit log says whether the user confirmed a call

Cherry-picked from #1139 (merged to release/1.11.x)
Original commit: 588289f

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(cli): the generated CLI says unknown command, prints an MCP error prefix once, and offers load_skill only the run lines it has

Cherry-picked from #1140 (merged to release/1.11.x)
Original commit: 4cfe9a2

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(plugins): load_skill's outputSchema describes a loaded skill once

Cherry-picked from #1141 (merged to release/1.11.x)
Original commit: 470010e

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

* Cherry-pick: fix(sdk,cli): skills/load's default answer sends formattedContent again, as 1.11.0 did; format: 'markdown' is the opt-in for the smaller one

Cherry-picked from #1142 (merged to release/1.11.x)
Original commit: a680fe7

Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com>

---------

Co-authored-by: agentfront[bot] <agentfront[bot]@users.noreply.github.com>
Co-authored-by: frontegg-david <69419539+frontegg-david@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants