Repository navigation
feat(mcp): serve a usage spec to an agent over stdio - #746
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
📝 WalkthroughWalkthroughAdds a new ChangesMCP usage server
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant UsageMcp
participant SpecServer
participant UsageSpec
MCPClient->>UsageMcp: Send JSON-RPC over stdin
UsageMcp->>SpecServer: Start MCP stdio transport
SpecServer->>UsageSpec: Resolve commands and flags
SpecServer-->>MCPClient: Return tool response over stdout
Possibly related PRs
Poem
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryAdds a local stdio MCP server for querying usage specifications.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (5): Last reviewed commit: "fix(mcp): make the two tools agree on ho..." | Re-trigger Greptile |
|
Switched the implementation to My original reason for hand-rolling the protocol — avoiding an async runtime in a synchronous CLI — was asserted rather than measured. Measured, the cost is binary size and nothing else:
The tokio runtime is built inside Two bugs surfaced while doing it:
Verified end to end against a real client handshake — This comment was generated by an AI coding assistant. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
|
Addressed the review: Stdin spec exhausts MCP stream (cursor, greptile) — fixed in 4d831d9. Inherited global flags are omitted (greptile) — correct and fixed in e9bc59f. usage resolves ancestors' Describe exposes hidden commands (cursor) — deliberate, now commented and tested rather than incidental. Verified end to end against a client handshake; This comment was generated by an AI coding assistant. |
My previous commit walked the ancestor chain itself and let a nearer declaration shadow a farther one. That is backwards. `merge_subcommand_flags` treats a non-global re-declaration sharing a global's long name as the *same* logical flag: the global's declaration survives and only the re-declaration's extra aliases are unioned in. So a subcommand re-declaring `-y --yes` without an effect was reported as `effect: null` when the flag actually carries the global's `write` — the exact failure this server exists to prevent, and worse than the omission it replaced. Rather than restate those rules a second time, `usage::available_flags(chain)` exposes the parser's own resolution and `describe_command` calls it. A test asserts it agrees with `parse_partial` for every command in a spec, so the two cannot drift. Two things fell out: - The merge can leave one logical flag under two `Arc`s — the merged declaration on the long key, the pre-merge one on the short — so `available_flags` collapses by name after the pointer dedup. Harmless for parsing, which looks up by key, visible to anything listing flags. - The `"inherited"` field is gone. Under these rules there is no clean local-vs-inherited split, and asserting one would be another small lie. `"global"` already says the flag is accepted everywhere. Reported by cursor and greptile on #746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Rebased onto main (which now has #747) and fixed the flag resolution. cursor and greptile were both right, and my previous fix was worse than the bug it replaced. I walked the ancestor chain by hand and let a nearer declaration shadow a farther one. The fix isn't to restate those rules more carefully. Two things fell out:
Verified end to end: a spec with a long-only global This comment was generated by an AI coding assistant. |
`usage mcp -f mycli.usage.kdl` speaks the Model Context Protocol, so an
agent can ask what a command does before running it:
pitchfork logs effect=read
--clear effect=destructive Delete logs
pitchfork daemons remove effect=destructive
That is the payoff for `effect=`. Five CLIs now declare it across roughly
385 commands, and until now nothing could read any of it — the data existed
and no consumer did.
Local rather than hosted, on purpose. An agent doing real work is in a
project, in front of a CLI that is installed, so "what does this do" is a
local question. It costs nothing to run, has no abuse surface, and works for
private and internal CLIs a public service could never see. usage.sh stays
the right place for the other case: a CLI you do not have and want to ask
about.
Two tools. `list_commands` gives the tree with each command's effect;
`describe_command` gives one command's help, flags and arguments with the
effect of each. Effect is reported as an attribute alongside help and
aliases, not as a separate concept, and an unset one stays null rather than
defaulting to something reassuring. The server instructions spell out what
the three values mean and that a missing one means ask, since the client
sees them before it sees any command.
Hidden commands are excluded with their subtrees, since a visible child of a
hidden parent is not a documented path, and `include_hidden` opts back in.
The protocol is hand-written: JSON-RPC 2.0 over newline-delimited stdio, and
a read-only server needs `initialize`, `tools/list` and `tools/call`. An SDK
would have pulled an async runtime into a CLI that has no tokio and does not
need one. serde_json was already here.
13 tests cover the protocol edges as well as the data — notifications
getting no reply, unparseable input answering with a null id, an unknown
method being a protocol error while an unknown command is a tool error the
agent can recover from, and structured results also carrying their JSON as
text, which the spec asks for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first pass implemented the protocol by hand to avoid pulling an async
runtime into a synchronous CLI. That justification was asserted, not
measured, and it diverges from `mise mcp` and `fnox mcp`, which both use
rmcp — three servers in one author's tools should not each reimplement a
different subset of MCP.
Measured, the cost is size only:
binary 8,417,552 -> 11,365,072 bytes (+2.8 MiB)
complete-word (mise's 208 KB spec, 120 runs)
117.8 ms -> 118.1 ms median
The runtime is built inside `mcp run`, so nothing else pays for it. In
exchange the server gets version negotiation, pagination, cancellation,
`tools/list` schemas generated from the params structs, and correct
protocol-vs-tool error framing, and the module drops ~170 lines.
Two things the rewrite fixed on the way:
- `get_info` sets `server_info` explicitly. rmcp's default reads the
`CARGO_*` vars of its own crate, so a server that omits it introduces
itself to clients as "rmcp 2.2.0".
- `--file -` is now rejected. It reads stdin to EOF, which is the
transport the server then wants to serve on, so it could only ever
produce a session that ended before it began.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
usage resolves `global=#true` flags from ancestors while parsing an invocation rather than copying them onto each subcommand, so `describe_command` reporting only `cmd.flags` left them out entirely. `pitchfork daemons remove` accepts `--yes`, and that flag declares `effect="write"` — precisely the thing this server exists to surface. Inherited flags are appended after the command's own and marked `"inherited": true`. A nearer definition shadows a farther one, matching how the parser resolves the collision, so a subcommand that redefines a global without an effect is not reported as carrying the global's. Describing a hidden command stays allowed, now with a comment and a test saying so deliberately. `list_commands` omits them, but an agent that names one already knows it exists; refusing would only mean it runs the command without learning the effect. The response carries `"hidden": true` either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
My previous commit walked the ancestor chain itself and let a nearer declaration shadow a farther one. That is backwards. `merge_subcommand_flags` treats a non-global re-declaration sharing a global's long name as the *same* logical flag: the global's declaration survives and only the re-declaration's extra aliases are unioned in. So a subcommand re-declaring `-y --yes` without an effect was reported as `effect: null` when the flag actually carries the global's `write` — the exact failure this server exists to prevent, and worse than the omission it replaced. Rather than restate those rules a second time, `usage::available_flags(chain)` exposes the parser's own resolution and `describe_command` calls it. A test asserts it agrees with `parse_partial` for every command in a spec, so the two cannot drift. Two things fell out: - The merge can leave one logical flag under two `Arc`s — the merged declaration on the long key, the pre-merge one on the short — so `available_flags` collapses by name after the pointer dedup. Harmless for parsing, which looks up by key, visible to anything listing flags. - The `"inherited"` field is gone. Under these rules there is no clean local-vs-inherited split, and asserting one would be another small lie. `"global"` already says the flag is accepted everywhere. Reported by cursor and greptile on #746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`list_commands` emitted `"daemons remove"` while `describe_command` answered with `"pitchfork daemons remove"`. An agent doing the obvious thing — read the list, describe an entry, describe something the first response mentioned — got a tool error on a command that exists, because `find_chain` read the binary name as a subcommand. `describe_command` now reports the path alone and carries `bin` beside it, the way `list_commands` already does. A test walks every row of `list_commands` through `describe_command` and asserts the name comes back unchanged, so the two ends cannot drift apart again. A leading binary name is also accepted now, since an agent that has seen the CLI in a shell writes the whole line. Only skipped when the root has no subcommand by that name, so a CLI with a `usage usage` keeps resolving its own command rather than losing it to the prefix. Rebased onto main, which brought #751. Its `nothing_is_unclassified_by_accident` test failed immediately on `mcp`, which is what it is for; classified `read`, since every tool this serves only reads the spec it was handed. Unlike `mise mcp`, which stays unclassified because it serves a tool that runs tasks, nothing here can act on the CLI it describes. Reported by cursor on #746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Rebased onto main (which brought #751) and fixed the path inconsistency. Command path format inconsistent (cursor) — correct.
A leading binary name is accepted too, since an agent that has seen the CLI in a shell will write the whole line. It's only skipped when the root has no subcommand by that name — a CLI with a One thing worth reporting: the rebase brought #751's Verified against This comment was generated by an AI coding assistant. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 259021a. Configure here.
| let mut available = gather_flags(root); | ||
| for cmd in rest { | ||
| merge_subcommand_flags(&mut available, gather_flags(cmd), false); | ||
| } |
There was a problem hiding this comment.
Mount crossing ignored in available_flags
Medium Severity
The new available_flags helper always calls merge_subcommand_flags with crossing_mount set to false. During real parsing, that flag is true when entering a subcommand whose mounted field is set while the parent is not, which changes how inherited globals collide with the mounted program’s flags. Flag lists from available_flags can then disagree with parse_partial for the same command chain, including what the MCP server reports via describe_command when the chain includes mounted commands.
Reviewed by Cursor Bugbot for commit 259021a. Configure here.
Instruction counts
2 benchmark(s) above the 1% gate: Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.
|
|
The perf gate is red: Method: build
In absolute terms it's +56 µs. For the path that actually matters, Your call, and I'd rather you made it than have me pick:
Happy to do either. What I won't do is quietly widen the gate to make my own check green. This comment was generated by an AI coding assistant. |
⚠️ **CAUTION: this is a major update, indicating a breaking change!**⚠️ This MR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [usage](https://github.com/jdx/usage) | tools | major | `3.5.6` → `5.1.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/usage (usage)</summary> ### [`v5.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#510---2026-08-09) [Compare Source](jdx/usage@v5.0.0...v5.1.0) ##### 🚀 Features - **(spec)** parse usage comments from strings by [@​jdx](https://github.com/jdx) in [#​782](jdx/usage#782) ##### 🐛 Bug Fixes - **(spec)** avoid inferred metadata from included specs by [@​jdx](https://github.com/jdx) in [#​786](jdx/usage#786) ##### 🧪 Testing - **(windows)** make the suite runnable on Windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​771](jdx/usage#771) ##### 📦️ Dependency Updates - update rust crate rmcp to v3 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​780](jdx/usage#780) ### [`v5.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#500---2026-08-02) [Compare Source](jdx/usage@v4.1.0...v5.0.0) ##### 🚀 Features - **(cli)** allow overriding the shell program with USAGE\_SHELL\_<SHELL> by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​767](jdx/usage#767) ##### 🐛 Bug Fixes - **(cli)** forward parsed args to WSL bash via WSLENV on windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​764](jdx/usage#764) - **(cli)** let generate markdown write to stdout by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​766](jdx/usage#766) - **(complete)** use `type -P` so the CLI-presence guard ignores shell functions by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​760](jdx/usage#760) - **(parse)** enforce double\_dash="required" for positional args by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​762](jdx/usage#762) - **(windows)** run `run=` scripts with sh when available by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​765](jdx/usage#765) ##### 🎨 Styling - fix clippy and deprecation warnings in test and bench targets by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​763](jdx/usage#763) ### [`v4.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#410---2026-07-30) [Compare Source](jdx/usage@v4.0.0...v4.1.0) ##### 🚀 Features - **(cli)** declare what each usage command does to the world by [@​jdx](https://github.com/jdx) in [#​751](jdx/usage#751) - **(mcp)** serve a usage spec to an agent over stdio by [@​jdx](https://github.com/jdx) in [#​746](jdx/usage#746) - **(spec)** add a top-level `repository` field by [@​jdx](https://github.com/jdx) in [#​747](jdx/usage#747) ##### 🐛 Bug Fixes - **(parse)** keep a re-declared global's aliases on one flag by [@​jdx](https://github.com/jdx) in [#​752](jdx/usage#752) - complete repeated variadic args by [@​Jai-JAP](https://github.com/Jai-JAP) in [#​753](jdx/usage#753) ##### New Contributors - [@​Jai-JAP](https://github.com/Jai-JAP) made their first contribution in [#​753](jdx/usage#753) ### [`v4.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#400---2026-07-25) [Compare Source](jdx/usage@v3.6.0...v4.0.0) ##### 🚀 Features - **(spec)** allow effect= on flags and args by [@​jdx](https://github.com/jdx) in [#​742](jdx/usage#742) ### [`v3.6.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#360---2026-07-25) [Compare Source](jdx/usage@v3.5.7...v3.6.0) ##### 🚀 Features - **(spec)** add effect= to declare what a command does to the world by [@​jdx](https://github.com/jdx) in [#​739](jdx/usage#739) ##### 🚜 Refactor - **(spec)** make missed SpecCommand fields a compile error, and fix the four that were already missed by [@​jdx](https://github.com/jdx) in [#​740](jdx/usage#740) ### [`v3.5.7`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#357---2026-07-25) [Compare Source](jdx/usage@v3.5.6...v3.5.7) ##### 🐛 Bug Fixes - **(parse)** don't leak the mounting CLI's flags into mounted commands; scan past non-global flags by [@​jdx](https://github.com/jdx) in [#​738](jdx/usage#738) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODguMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWFqb3IiXX0=-->
usage 4.1.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>Puts the `effect=` work from 4.0 to use: a new `usage mcp` server lets agents read command effects locally over stdio, the CLI declares effects for its own commands, and specs gain a `repository` field so consumers away from the checkout can find the source. ## Added - **`usage mcp` — serve a spec to an agent over stdio** ([#746](jdx/usage#746) by @jdx). A local Model Context Protocol server that speaks JSON-RPC 2.0 over newline-delimited stdio, so an agent can ask what a command does before running it. Load a spec with `-f` or `-s` (stdin is the transport, so `--file -` is rejected): ``` usage mcp -f mycli.usage.kdl ``` Two tools: - `list_commands` — the command tree, each entry tagged with its effect - `describe_command` — one command's help, flags, arguments, and the effect of each Effect is reported as an attribute alongside `help` and `aliases`; an unset effect stays `null` rather than defaulting to something reassuring, and the server `instructions` tell the client that a missing effect means *ask*. Hidden commands are excluded from listings by default, with `include_hidden` to opt back in. This is the first consumer of the `effect=` metadata now declared across roughly 385 commands in mise, hk, pitchfork, aube, and communique. - **`repository` field on the spec** ([#747](jdx/usage#747) by @jdx). A plain top-level URL, mirroring what `Cargo.toml`, `package.json`, or `pyproject.toml` carry — declared as a `repository "..."` node at the top of a spec. Distinct from `source_code_link_template` (which is a per-command deep link with a `{{path}}` placeholder). Anything reading a spec away from its checkout — usage.sh, a registry, an agent handed a `.usage.kdl` — previously had no way back to the project it described. Round-trips through parse, merge, serialize, and template rendering. - **`usage` CLI declares its own effects** ([#751](jdx/usage#751) by @jdx). Every command is now classified: most `generate` subcommands are `read`, with `--out-file` / `--out-dir` raising them to `write`; `generate sdk` is `write` at the command level (its output flag is required). Script runners (`bash`, `fish`, `zsh`, `powershell`, `exec`) are deliberately unclassified — their effect is whatever the user's script does — and a test asserts every unclassified command has an entry in `UNCLASSIFIED` with a reason. Also emits `min_usage_version "4.0"` so an older `usage` doesn't silently drop the annotation. - **`clap_usage::spec()`** ([#743](jdx/usage#743) by @jdx). Returns the `Spec` derived from a `clap::Command` (with `bin` already set), so callers can annotate it before rendering: ```rust let mut spec = clap_usage::spec(&mut cmd, "mycli"); spec.cmd.subcommands.get_mut("rm").unwrap().effect = Some(SpecCommandEffect::Destructive); println!("{spec}"); ``` `generate()` now delegates to `spec()` plus two `writeln!`s; a test asserts its output is byte-identical. - **`usage::available_flags` is public** ([#746](jdx/usage#746) by @jdx), so consumers can enumerate flags the same way the parser does — respecting global merge and re-declaration rules. ## Fixed - **Re-declared globals keep their aliases on one flag** ([#752](jdx/usage#752) by @jdx). A global like `flag "-y --yes" global=#true` re-declared non-globally by a subcommand (optionally adding a third alias, e.g. `-y --yes --assume-yes`) could leave `-y` and `--yes` pointing at two different `Arc<SpecFlag>` objects, with the `-y` view missing the new alias. The collision guard now compares flag *origins* rather than `Arc` identity, and rebinds every existing alias for the global to the merged flag on first merge. - **Completions for repeated variadic args** ([#753](jdx/usage#753) by @Jai-JAP). `complete-word` counted parsed positional entries to pick which arg to complete next, but variadic values all collapse into one entry — so after the first value, completion fell through to files. It now checks the parsed value per positional and keeps using the variadic arg's completer until `var_max` is reached. ## Changed - **`clap_usage` republished as 4.0.0** ([#743](jdx/usage#743) by @jdx). crates.io still shipped `clap_usage` 2.0.3, pinned to `usage-lib ^2.0.3`, because its source hadn't changed since — blocking downstream crates from adopting `effect=`. It now tracks `usage-lib`'s major, and the `spec()` addition means dependents no longer have to inline `generate()` and depend on `usage-lib` directly. ## New Contributors * @Jai-JAP made their first contribution in [#753](jdx/usage#753) **Full Changelog**: jdx/usage@v4.0.0...v4.1.0 ## 💚 Sponsor usage usage is maintained by [@jdx](https://github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools including [mise](https://mise.jdx.dev/), [aube](https://aube.jdx.dev/), hk, and more. Work on usage is funded by sponsorships. If `usage` powers CLI specs, docs, or completions for a tool you maintain or use, please consider [sponsoring at jdx.dev](https://jdx.dev/sponsors.html). Every sponsorship helps the project stay independent and moving. </pre> <p>View the full release notes at <a href="https://github.com/jdx/usage/releases/tag/v4.1.0">https://github.com/jdx/usage/releases/tag/v4.1.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!15526


usage mcp -f mycli.usage.kdlspeaks the Model Context Protocol, so an agent can ask what a command does before running it. Against pitchfork's real spec:This is the payoff for
effect=. Five CLIs now declare it across roughly 385 commands — and until now nothing could read any of it. The data existed and no consumer did.Why local rather than usage.sh
An agent doing real work is in a project, in front of a CLI that is installed. "What does this do" is a local question. Answering it locally costs nothing to run, has no abuse surface, and works for private and internal CLIs a public service could never see.
usage.sh stays the right home for the other case — a CLI you don't have and want to ask about — which is also where the vendor skills live (jdx/usage-sh#8).
The tools
list_commandsdescribe_commandEffect is reported as an attribute beside
helpandaliases, not as a separate concept — per your call on #742. An unset effect staysnullrather than defaulting to something reassuring, and the serverinstructionsspell out what the three values mean and that a missing one means ask, since the client sees those before it sees any command.Hidden commands are excluded with their subtrees — a visible child of a hidden parent isn't a documented path, the same bug found in jdx/usage-sh#8 — with
include_hiddento opt back in.Hand-written protocol
JSON-RPC 2.0 over newline-delimited stdio. A read-only server needs
initialize,tools/listandtools/call; an SDK would have pulled an async runtime into a CLI that has no tokio and doesn't need one.serde_jsonwas already a dependency, so this adds none.Tests
13, covering the protocol edges as much as the data:
notifications/initialized; answering it is a violation)list_commandsservehandles a full session: two requests plus a notification produce exactly two responsesVerified end to end by piping a real session into the built binary against
pitchfork.usage.kdl, not only through unit tests.cargo test -p usage-cli,clippy --all-targets,fmt --checkclean.mise run renderregenerated the spec, man page, fig completions and CLI docs.Worth noting
usage's own spec declares no effects yet —
usage mcpcan describe every CLI except the one it ships in. Dogfooding that is a natural follow-up and I left it out to keep this reviewable.This PR was generated by an AI coding assistant.
Note
Medium Risk
New async MCP surface and dependency stack (tokio/rmcp); incorrect effect or flag metadata could mislead agents, though the server only reads the loaded spec.
Overview
Adds
usage mcp(aliasmcp-server), a local Model Context Protocol server on stdio viarmcpand a minimaltokioruntime. Clients load a spec with-f/-s;--file -is rejected because stdin is the transport.The server exposes
list_commandsanddescribe_command, returning help pluseffecton commands, flags, and args (unset staysnull). Server instructions explainread/write/destructive. Hidden commands are omitted from listings unless requested, but can still be described.usage-libgains publicavailable_flagsso flag reporting matches parser merge rules (globals, re-declarations).command_effectsclassifiesmcpas read-only. Generated CLI docs, man page, and Fig completions are updated.Reviewed by Cursor Bugbot for commit 259021a. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
usage mcpcommand (also available asmcp-server) to serve usage specifications through the Model Context Protocol.Documentation