Skip to content

feat(sdk,cli): a generated CLI's skills commands are the Skilled OpenAPI tools, top-level on a skilled-only server, with terminal confirmation - #1102

Merged
frontegg-david merged 7 commits into
feat/skilled-browserfrom
feat/cli-skilled-commands
Nov 4, 2026
Merged

frontegg-david merged 7 commits into
feat/skilled-browserfrom
feat/cli-skilled-commands

Conversation

@frontegg-david

Copy link
Copy Markdown
Contributor

What and why

Stacks on #1101.

The CLI that frontmcp build --target cli makes (mymcp below) treated a Skilled OpenAPI server like any other:

  • the plugin's tools were plain tool commands: search-skill --query, load-skill --skill-id (one id), run-workflow --script (the script only as a flag value), and execute-action --action-id;
  • skills search and skills load called the SDK's skill methods, which return no actions for bundle skills;
  • a destructive action that the plugin's confirm option names was always refused (this client can't ask for it), because the CLI couldn't answer an MCP elicitation.

A Skilled OpenAPI server's skills are tools, not standard skills, so this PR gives the CLI commands for them.

Commands

The build detects the plugin from its four tools (search_skill, load_skill, execute_action, run_workflow). It then generates these commands in place of the standard skills commands:

Command Calls Prints
skills search <query...> [--limit n] [--tags a,b] [--not-query x] search_skill each skill's name, id, score and description
skills load <skill...> load_skill (skillId for one, skillIds for several) the instructions, then each action: its summary, a ready mymcp execute <actionId> --input '<json>' line built from the input schema's required fields, and its optional fields
skills execute <actionId> [--input '<json>' | --input - | --input-file f.json] execute_action the action's data
skills run [script] [--file f], or - for stdin run_workflow the value the script returns
skills read <skill> skill://<name>/SKILL.md the skill's SKILL.md
skills list skill://index.json the skills the caller can use

On a server whose only tools are the plugin's, the same commands are also top-level:

mymcp search_skill "refund an invoice"
mymcp load_skill invoices
mymcp execute getInvoice --input '{"id":"INV-1001"}'
mymcp run_workflow --file refund.js
  • They answer to search-skill, load-skill, execute_action / execute-action and run-workflow too.
  • A server with tools of its own has them only under skills, so scripts that use skills ... keep working if the server later gains tools.
  • cli.excludeTools naming one of the plugin's tools leaves its command out.

The plugin's tools as tool commands

They are hidden: the plugin's four tools are no longer generated as tool commands. To keep 1.11.0 scripts working, the top-level commands also take the old flags, hidden from --help: search-skill --query, load-skill --skill-id and run-workflow --script. On a server with tools of its own there are no top-level commands, so a 1.11.0 script there moves to skills search, skills load or skills run.

Output and exit codes

  • --output json prints the tool's structured content, or the whole result when there is none.
  • Every command exits 0 on success; 1 when the tool fails (isError: a failed action or workflow, a skill not found, an authority denial, a declined confirmation); 2 for a usage error (no query or script, two script sources, --input that isn't a JSON object, --input with --input-file, an unreadable file). The error text goes to stderr.

Confirming a destructive action

The CLI now answers a server's elicitation:

  • The generated CLI passes an elicitation handler to connect(), and hands the same handler to its daemon client.
  • A yes/no question, such as the plugin's confirmation, is printed on stderr and read as y/N when stdin is a terminal.
  • The global --yes answers yes without asking. Commander reads a program option anywhere, so it works before or after the command.
  • With no terminal and no --yes, the CLI declines and prints the question and Declined: there is no terminal to ask on. Run the command again with --yes to confirm. The action isn't sent, and the command exits 1 with the user declined: voidInvoice.
  • A question with other fields asks for each one in the terminal. With no terminal, the CLI declines it.

The daemon's transport carries server-to-client requests, so nothing falls back to in-process:

  • The daemon client declares the elicitation capability when it has a handler.
  • It reads each call's SSE stream as the stream arrives. When an elicitation/create arrives, it answers with a POST to the session. Any other server request gets Method not found.
  • It tells the call's response from a server request with the same id by the request's method.

Two related changes:

  • SDK fix. A CLI-mode scope (connect(..., { mode: 'cli' })) opened no elicitation store, so in-process this.elicit() failed with ElicitationStore not initialized once the client could answer. In CLI mode, the store now stays in memory: the process asks its own user and ends with the command. Persistent questions and the sendElicitationResult fallback tool stay off in CLI mode, as before.
  • No fallback command. The CLI has no command for the sendElicitationResult fallback tool anymore. The server lists that tool only to clients that can't answer, and the CLI now can. The tool also no longer counts as a tool of the server's own when the build decides whether a server is Skilled OpenAPI only.

Lockfile

#1101's yarn.lock lacked the plugin's optional @enclave-vm/browser peer. This branch fixed the same three lines (117cba2) before #1101's own fix (2a57dc5) landed, and the merge leaves no difference. yarn install --immutable passes.

Bundle size

The SDK change grows the browser bundles of create() + one tool by 83 bytes (1,288,965) and of create() + WebMcpPlugin + one tool by 81 bytes (1,296,155). The budgets are set to these sizes. The Vite initial chunk is unchanged.

User-facing change

  • What changed:
    • A CLI built for a Skilled OpenAPI server has skills search, skills load, skills execute, skills run, skills read and skills list, which call the plugin's tools.
    • On a server whose only tools are the plugin's, it also has search_skill, load_skill, execute and run_workflow at the top level.
    • The CLI answers elicitations: y/N on a terminal, a new global --yes, and a decline with no terminal.
    • connect() in mode: 'cli' can elicit.
  • Before → after:
    • mymcp search-skill --query x → mymcp search_skill x (the old form still works on a skilled-only server). --limit, --tags a,b and --not-query are new.
    • mymcp load-skill --skill-id x printed JSON → mymcp load_skill x [y ...] prints the instructions and a ready execute line per action.
    • mymcp execute-action --action-id x --input j → mymcp execute x --input j, --input - or --input-file f.json.
    • mymcp run-workflow --script s → mymcp run_workflow s, --file f or -.
    • --output json on these commands printed the whole CallToolResult → it prints the tool's structured content.
    • On a server with tools of its own: the plugin's tools were plain tool commands → they're only under skills.
    • skills load on a Skilled OpenAPI server printed Loaded 1 of 1 skill(s). with no actions → it prints the actions.
    • A confirmed action failed with this client can't ask for it → the CLI asks, --yes confirms, and with no terminal it declines (the user declined: <actionId>, exit 1).
    • The send-elicitation-result command, generated for servers with elicitation.enabled, is gone.
    • In-process connect(config, { mode: 'cli', onElicitation }) failed with ElicitationStore not initialized → it asks the handler.
  • frontmcp.dev pages affected (if known): the CLI target reference (generated CLI commands, global options, exit codes), the Skilled OpenAPI plugin page (CLI usage, confirmation), elicitation configuration.
  • Skills catalog entries updated:
    • frontmcp-deployment/references/build-for-cli.md: new "Skilled OpenAPI commands" and "Questions from the server" sections, and the troubleshooting rows;
    • frontmcp-config/references/configure-elicitation.md: the generated CLI asks in the terminal.

The CLI README and the plugin README are updated too.

Tests

Added:

  • libs/cli generation, in generate-cli-entry.spec.ts (7 tests):
    • a skilled-only server gets the top-level commands and the group, and no tool commands;
    • a server with tools of its own gets only the group, and keeps its tool commands;
    • a server without the plugin keeps the standard skills commands;
    • excludeTools drops a command;
    • detection needs all four tools;
    • the elicitation handler, --yes and the daemon handler are wired;
    • sendElicitationResult gets no command.
  • A syntax smoke test of a skilled entry (generated-cli-smoke.spec.ts).
  • The commands, in skilled-commands.spec.ts (39 tests), run on a real commander program with a fake client:
    • the arguments and options of each command;
    • the kebab-case names and the 1.11.0 flags;
    • --input -, --input-file, --file and - for stdin;
    • text and json output;
    • exit 0, 1 and 2;
    • --yes after the command;
    • skills read (an encoded name) and skills list;
    • top-level commands absent on a mixed server.
  • The terminal handler, in terminal-elicitation.spec.ts (9 tests): --yes, no terminal, y / n / empty / other answers, and per-field questions.
  • The daemon client, in daemon-client.spec.ts (3 tests): it answers an elicitation/create during a call and then reads the result, declines when its handler fails, and declares no elicitation without a handler.
  • The SDK, in connect-client.spec.ts (1 test): connect() in mode: 'cli' asks the handler. This test fails without the fix.
  • E2E in demo-e2e-cli-exec, in cli-skilled-openapi.e2e.spec.ts, rewritten (28 tests), on a built CLI with a Skilled OpenAPI bundle and a loopback API:
    • help;
    • search, in text, json and the 1.11.0 form;
    • load, with its execute lines, json, not found, skills read and skills list;
    • execute: data, --input-file, --input -, a 404 failure, an unknown action, bad JSON;
    • run_workflow: argument, --file, stdin, the 1.11.0 --script, a failure;
    • confirmation in-process: declined with no terminal, --yes before and after, a workflow asked once;
    • confirmation through the daemon, declined and confirmed, with no fall back to in-process;
    • the daemon serving the commands after the bundle file is gone;
    • a second fixture with a tool of its own: the commands only under skills.

Run:

  • npx nx test cli: 131 suites, 2180 tests passed.
  • libs/sdk, the whole jest suite: 691 suites, 9640 tests passed.
  • demo-e2e-cli-exec, the whole app: 42 suites, 252 tests passed; 1 suite (4 tests) skipped. The skipped one is the login-identity suite, which skips on macOS so it never writes to the Keychain, so it runs only on Linux CI. It lists skills with skills list, which now reads skill://index.json. The plugin's authorization tests check that this resource is filtered per caller.
  • demo-e2e-bundle-size: 12 suites, 63 tests passed, after the budget update.
  • npx nx run cli:typecheck, sdk:typecheck, demo-e2e-cli-exec:typecheck: pass. npx tsc --noEmit -p libs/cli/tsconfig.lib.json and -p libs/sdk/tsconfig.lib.json: pass.
  • prettier and eslint on the changed files: clean (eslint: 0 errors; one existing warning in index.ts).
  • yarn install --immutable: passes.

…-vm/browser peer

The plugin's package.json gained the peer, but the lockfile's workspace entry
did not, so yarn install --frozen-lockfile refused to run.
…'s questions

A CLI-mode scope opened no elicitation store, so once a client could answer,
this.elicit() failed with "ElicitationStore not initialized". In CLI mode the
store now stays in memory: the process asks its own user and ends with the
command. Persistent questions and the sendElicitationResult fallback tool stay
off there, as before.

The browser bundles of create() grow by 83 and 81 bytes; their budgets take it.
…l confirmation

When the server has the Skilled OpenAPI plugin's four tools, the build
generates commands for them in place of the standard skills commands, which
showed no actions for bundle skills:

- skills search, load, execute, run, read and list, always;
- search_skill, load_skill, execute and run_workflow at the top level too,
  on a server whose only tools are the plugin's. They answer to their
  kebab-case names and keep the 1.11.0 --query, --skill-id and --script flags.

load prints each action with a ready execute command. execute takes --input,
--input - or --input-file; run_workflow takes the script, --file or -. The
plugin's tools are no longer plain tool commands. --output json prints the
tool's structured content; a tool error exits 1, a usage error 2.

The CLI now answers a server's elicitation, in-process and through the
daemon, which reads the SSE stream as it arrives: a yes/no question, such as
the confirmation of a destructive action, reads y/N on a terminal, the global
--yes answers yes, and with no terminal the CLI declines. The
sendElicitationResult fallback tool is no longer a command.
…ion, end to end

A CLI built for a server with only the plugin's tools: help, search, load
with its execute lines, execute (data, --input-file, --input -, failure,
unknown action), run_workflow (argument, --file, -, failure), --output json,
the 1.11.0 flags, and a destructive action declined with no terminal and run
with --yes, in-process and through the daemon. A second fixture with a tool
of its own has the commands only under skills.
@frontegg-david
frontegg-david marked this pull request as ready for review November 4, 2026 21:42
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 4, 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 26 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 2bd6c3d and 63ad376.

Files ignored due to path filters: 10
  • libs/cli/src/commands/build/exec/__tests__/daemon-client.spec.ts is excluded by !**/build/**
  • 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__/skilled-commands.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/__tests__/terminal-elicitation.spec.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/cli-runtime/daemon-client.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/cli-runtime/terminal-elicitation.ts is excluded by !**/build/**
  • libs/cli/src/commands/build/exec/index.ts is excluded by !**/build/**
Files selected for processing: 10
  • apps/e2e/demo-e2e-bundle-size/e2e/browser-bundle-size.e2e.spec.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/cli-skilled-openapi.e2e.spec.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/helpers/exec-cli.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/helpers/skilled-cli-project.ts
  • libs/cli/README.md
  • libs/sdk/src/direct/__tests__/connect-client.spec.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/skills/catalog/frontmcp-config/references/configure-elicitation.md
  • libs/skills/catalog/frontmcp-deployment/references/build-for-cli.md
  • plugins/plugin-skilled-openapi/README.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 4, 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-04T22:24:39.889Z
Commit: e1e3c152

@frontegg-david
frontegg-david merged commit 4e29f92 into release/1.11.x Nov 4, 2026
46 checks passed
@frontegg-david
frontegg-david deleted the feat/cli-skilled-commands branch November 4, 2026 23:23
frontegg-david added a commit that referenced this pull request Nov 4, 2026
… Skilled OpenAPI tools, top-level on a skilled-only server, with terminal confirmation (#1102)

Cherry-picked from #1102 (merged to release/1.11.x)
Original commit: 4e29f92
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