argsbarg 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: MCP v1.1 polish
3
+ overview: "Ship four agent-focused MCP improvements: richer tool descriptions with CLI paths, per-leaf `mcpEnabled` opt-out, stderr surfaced on successful tool calls, and `structuredContent` when handler stdout is valid JSON."
4
+ todos:
5
+ - id: types-mcpEnabled
6
+ content: "Add mcpEnabled?: boolean to CliCommandBase; validate leaf-only in validate.ts"
7
+ status: completed
8
+ - id: tool-descriptions
9
+ content: Add mcpToolDescription(path, rootKey, description); enrich descriptions; filter mcpEnabled === false
10
+ status: completed
11
+ - id: result-builder
12
+ content: Create src/mcp/result.ts with buildToolCallSuccess (stderr blocks + JSON structuredContent)
13
+ status: completed
14
+ - id: server-wire
15
+ content: Wire buildToolCallSuccess into tools/call success path in server.ts
16
+ status: completed
17
+ - id: tests
18
+ content: Unit tests for description, mcpEnabled, result builder; subprocess tests for JSON structuredContent and stderr-on-success
19
+ status: completed
20
+ - id: docs-changelog
21
+ content: Update docs/mcp.md and CHANGELOG.md; run just typegen and just test
22
+ status: completed
23
+ isProject: false
24
+ ---
25
+
26
+ # MCP follow-up improvements plan
27
+
28
+ Four targeted enhancements to the existing opt-in MCP server. No new dependencies, no protocol transport changes, no public export of internals beyond the new `mcpEnabled` schema field.
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ subgraph toolsList [tools/list]
33
+ Walk[collectMcpTools]
34
+ Desc["path + description"]
35
+ Filter[mcpEnabled filter]
36
+ end
37
+ subgraph toolCall [tools/call]
38
+ Invoke[cliInvoke]
39
+ Build[buildToolCallResult]
40
+ Resp["content + structuredContent?"]
41
+ end
42
+ Walk --> Filter --> Desc
43
+ Invoke --> Build --> Resp
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 1. CLI path in tool descriptions
49
+
50
+ **Goal:** Agents see the human invocation path without reading the schema resource first.
51
+
52
+ **Format:**
53
+
54
+ | Path | Leaf description | MCP `description` |
55
+ | --- | --- | --- |
56
+ | `["stat","owner","lookup"]` | `Resolve owner info.` | `stat owner lookup — Resolve owner info.` |
57
+ | `["read"]` | `Print the first line…` | `read — Print the first line…` |
58
+ | `[]` (root leaf app) | `Tiny demo.` | `{root.key} — Tiny demo.` |
59
+
60
+ Use em dash (`—`) between path and description. Path segments are raw `key` values (not sanitized tool names). Every tool description uses the `prefix — description` form — there is no bare-description fallback.
61
+
62
+ **Implementation:**
63
+
64
+ - Add helper in [`src/mcp/tools.ts`](src/mcp/tools.ts). The helper **must** receive `rootKey` so root-leaf apps (empty path) can use the program name as the prefix:
65
+
66
+ ```typescript
67
+ /** Builds MCP tool description: "{cli path} — {description}". */
68
+ function mcpToolDescription(path: string[], rootKey: string, description: string): string {
69
+ const prefix = path.length > 0 ? path.join(" ") : rootKey;
70
+ return `${prefix} — ${description}`;
71
+ }
72
+ ```
73
+
74
+ - Call from `collectMcpTools` when building each `McpToolDef.description`:
75
+
76
+ ```typescript
77
+ description: mcpToolDescription(path, root.key, cmd.description),
78
+ ```
79
+
80
+ - `tools/list` already maps `t.description` — no server change needed.
81
+
82
+ **Tests:** Update [`src/index.test.ts`](src/index.test.ts):
83
+
84
+ - `stat_owner_lookup` description is `stat owner lookup — Resolve owner info.`
85
+ - If testing a root-leaf fixture, description is `{root.key} — {description}`.
86
+
87
+ ---
88
+
89
+ ## 2. Per-leaf opt-out via `mcpEnabled`
90
+
91
+ **Goal:** Authors can keep CLI commands for humans while hiding them from MCP tool discovery.
92
+
93
+ **Schema change** in [`src/types.ts`](src/types.ts):
94
+
95
+ ```typescript
96
+ export interface CliCommandBase {
97
+ // ...existing fields...
98
+ /** Leaf-only. When `false`, omit this command from MCP tools (default: exposed). */
99
+ mcpEnabled?: boolean;
100
+ }
101
+ ```
102
+
103
+ **Semantics:**
104
+
105
+ - Omitted or `true` → leaf is exposed (current behavior).
106
+ - `false` → skipped in `collectMcpTools` walk.
107
+ - Root `mcp?: CliMcpConfig` unchanged — it enables the server; `mcpEnabled` is unrelated.
108
+
109
+ **Validation** in [`src/validate.ts`](src/validate.ts) `walkCommand`:
110
+
111
+ - If `isRoot && cmd.mcpEnabled !== undefined` → throw `mcpEnabled is only supported on leaf commands`.
112
+ - If routing node (has `commands`, no `handler`) and `mcpEnabled !== undefined` → same error.
113
+ - No validation needed for `false` vs `true` beyond that.
114
+
115
+ **Tool collection** in [`src/mcp/tools.ts`](src/mcp/tools.ts) `walk`:
116
+
117
+ ```typescript
118
+ if ("handler" in cmd && cmd.handler) {
119
+ if (cmd.key === "completion" || cmd.key === "mcp") return;
120
+ if (cmd.mcpEnabled === false) return;
121
+ // push tool...
122
+ }
123
+ ```
124
+
125
+ **Schema export:** Leave [`src/schema.ts`](src/schema.ts) unchanged for v1 — `--schema` still lists all commands (CLI discovery). Only MCP `tools/list` respects `mcpEnabled`. Document this distinction in [`docs/mcp.md`](docs/mcp.md).
126
+
127
+ **Example (optional):** Add a hidden leaf in `nestedMcpFixture` only (not `examples/nested.ts`) with `mcpEnabled: false` to prove filtering without cluttering the demo app.
128
+
129
+ ---
130
+
131
+ ## 3. stderr on successful tool calls
132
+
133
+ **Goal:** Warnings and diagnostic output on stderr are not silently dropped when `exitCode === 0`.
134
+
135
+ **Current behavior** ([`src/mcp/server.ts`](src/mcp/server.ts) ~L133–141): success returns only `invokeResult.stdout`.
136
+
137
+ **New behavior:** Extract a small builder (new file [`src/mcp/result.ts`](src/mcp/result.ts) recommended to keep server readable):
138
+
139
+ ```typescript
140
+ export interface McpToolCallSuccess {
141
+ content: { type: "text"; text: string }[];
142
+ structuredContent?: unknown;
143
+ isError: false;
144
+ }
145
+
146
+ export function buildToolCallSuccess(stdout: string, stderr: string): McpToolCallSuccess
147
+ ```
148
+
149
+ **stderr formatting rules:**
150
+
151
+ - If `stderr.trim()` is empty → single `content` item with stdout (or empty string if stdout empty).
152
+ - If stderr non-empty → **two** `content` items:
153
+ 1. `{ type: "text", text: stdout }` (may be empty string)
154
+ 2. `{ type: "text", text: stderr.trim() }` — **raw stderr text, no `stderr:` prefix**
155
+
156
+ Use multiple content blocks rather than concatenating into one string so hosts can distinguish streams; the second block’s position in the array is the signal that it came from stderr. Some MCP hosts render content blocks with their own labeling — a literal `stderr:` prefix would leak into user-visible tool output. Existing failure path (~L144–151) already prefers stderr — leave as-is.
157
+
158
+ **Tests:**
159
+
160
+ - Unit test `buildToolCallSuccess` with stdout-only, stderr-only, both.
161
+ - Subprocess test: add a fixture leaf (in test fixture or temporary nested command) that `console.warn`s on stderr but exits 0; assert two content blocks and `isError: false`.
162
+
163
+ ---
164
+
165
+ ## 4. `structuredContent` when stdout is valid JSON
166
+
167
+ **Goal:** When handlers emit JSON (e.g. `nested.ts` with `--json`), MCP clients get machine-readable output per [MCP tools spec](https://modelcontextprotocol.io/specification/draft/server/tools).
168
+
169
+ **Parsing rules** (in `buildToolCallSuccess`):
170
+
171
+ 1. Let `trimmed = stdout.trim()`.
172
+ 2. If `trimmed.length === 0` → no `structuredContent`.
173
+ 3. Try `JSON.parse(trimmed)`.
174
+ 4. On success → set `structuredContent` to the parsed value (object, array, or primitive — all valid per 2025 spec).
175
+ 5. On `SyntaxError` → no `structuredContent` (plain text handlers unchanged).
176
+ 6. **Always** keep `content[0].text` as the raw stdout string when stdout is non-empty (spec: structured tools SHOULD also return serialized JSON in `content` — we already have the raw stdout which satisfies this for JSON handlers).
177
+
178
+ **Primitive footgun (document, do not guard):** `JSON.parse("true")` yields `structuredContent: true`. A handler that intentionally prints the string `true` as human-readable output would get machine-typed output. This is spec-correct and rare in practice. Document in [`docs/mcp.md`](docs/mcp.md) rather than limiting to `typeof parsed === "object"` — that would reject valid JSON arrays and primitives that the 2025 spec explicitly allows.
179
+
180
+ **Do not** auto-generate `outputSchema` in this release — that requires schema author input or inference and is out of scope.
181
+
182
+ **Server wiring** in [`src/mcp/server.ts`](src/mcp/server.ts):
183
+
184
+ ```typescript
185
+ const result = buildToolCallSuccess(invokeResult.stdout, invokeResult.stderr);
186
+ writeResponse({ jsonrpc: "2.0", id, result });
187
+ ```
188
+
189
+ Spread `structuredContent` onto result only when defined.
190
+
191
+ **Tests:**
192
+
193
+ - Unit: `buildToolCallSuccess('{"a":1}\n', '')` → `structuredContent: { a: 1 }`, content text preserved.
194
+ - Unit: `buildToolCallSuccess('lookup user=x\n', '')` → no `structuredContent`.
195
+ - Unit: `buildToolCallSuccess('true\n', '')` → `structuredContent: true` (documents primitive behavior).
196
+ - Subprocess: extend existing `stat_owner_lookup` test or add parallel test with `json: true`:
197
+
198
+ ```typescript
199
+ params: {
200
+ name: "stat_owner_lookup",
201
+ arguments: { path: readme, "user-name": "test", json: true },
202
+ }
203
+ ```
204
+
205
+ Assert `result.structuredContent` deep-equals `{ user: "test", path: readme }` and `content[0].text` is valid JSON string.
206
+
207
+ ---
208
+
209
+ ## Documentation and changelog
210
+
211
+ Update [`docs/mcp.md`](docs/mcp.md):
212
+
213
+ - **Tool names** section: document new description format with example (including root-leaf `{root.key} — …` case).
214
+ - **Per-leaf visibility**: `mcpEnabled: false` on leaves; note `--schema` still includes the command.
215
+ - **Tool results**: stderr on success as a second raw-text content block (no prefix); `structuredContent` when stdout is valid JSON (including primitives — document footgun); link to MCP spec.
216
+ - **Short flags**: one-line note that tool args use long option names only (unchanged, but good to document while editing).
217
+
218
+ Update [`CHANGELOG.md`](CHANGELOG.md) under `[Unreleased]`:
219
+
220
+ - Richer MCP tool descriptions with CLI paths.
221
+ - `mcpEnabled` leaf opt-out.
222
+ - MCP tool success responses include stderr when present.
223
+ - MCP tool success responses include `structuredContent` for JSON stdout.
224
+
225
+ Trim [`README.md`](README.md) MCP blurb only if needed — one line pointing to docs is enough.
226
+
227
+ ---
228
+
229
+ ## Type declarations and release hygiene
230
+
231
+ - Run `just typegen` after [`src/types.ts`](src/types.ts) change so [`index.d.ts`](index.d.ts) exports `mcpEnabled`.
232
+ - Run `just test` (typecheck + lint + `bun test`).
233
+
234
+ **Public API surface:** Only `mcpEnabled?: boolean` on `CliCommandBase` is new. `cliInvoke`, `buildToolCallSuccess`, and MCP runtime remain internal.
235
+
236
+ ---
237
+
238
+ ## File change summary
239
+
240
+ | File | Changes |
241
+ | --- | --- |
242
+ | [`src/types.ts`](src/types.ts) | Add `mcpEnabled?: boolean` with JSDoc |
243
+ | [`src/validate.ts`](src/validate.ts) | Reject `mcpEnabled` on root and routing nodes |
244
+ | [`src/mcp/tools.ts`](src/mcp/tools.ts) | `mcpToolDescription`, filter `mcpEnabled === false` |
245
+ | [`src/mcp/result.ts`](src/mcp/result.ts) | **New** — `buildToolCallSuccess` |
246
+ | [`src/mcp/server.ts`](src/mcp/server.ts) | Use builder for success path |
247
+ | [`src/index.test.ts`](src/index.test.ts) | Unit + subprocess tests for all four features |
248
+ | [`docs/mcp.md`](docs/mcp.md) | Document behavior |
249
+ | [`CHANGELOG.md`](CHANGELOG.md) | Unreleased entries |
250
+ | [`index.d.ts`](index.d.ts) | Regenerated via typegen |
251
+
252
+ ---
253
+
254
+ ## Out of scope (explicitly deferred)
255
+
256
+ - Tool list caching
257
+ - `outputSchema` on tools
258
+ - Hiding `mcpEnabled: false` commands from `--schema`
259
+ - Group-level `mcpEnabled` inheritance
260
+ - `mcp.json` / Cursor config changes
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2026-06-19
11
+
12
+ ### Added
13
+
14
+ - **Opt-in MCP** — set `mcpServer: {}` on the program root to enable `myapp mcp`, a stdio MCP server (tools + `argsbarg://schema` resource). Hand-rolled JSON-RPC; zero new dependencies.
15
+ - **MCP tool descriptions** — `tools/list` descriptions include the CLI path (e.g. `stat owner lookup — Resolve owner info.`).
16
+ - **`mcpTool` leaf opt-out** — set `mcpTool: { enabled: false }` on a leaf to omit it from MCP tools while keeping it in the CLI and `--schema`.
17
+ - **MCP stderr on success** — successful tool calls return a second content block when the handler wrote to stderr.
18
+ - **MCP `structuredContent`** — when handler stdout is valid JSON, tool results include parsed `structuredContent` alongside text content.
19
+
20
+ ### Fixed
21
+
22
+ - **Parent-scoped options before positionals** — nested commands accept flags from ancestor nodes when options appear before positional arguments (required for MCP tool argv layout).
23
+
10
24
  ## [1.3.1] - 2026-06-19
11
25
 
12
26
  ### Fixed
@@ -84,7 +98,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
84
98
  - Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
85
99
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
86
100
 
87
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v1.3.1...HEAD
101
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v1.4.0...HEAD
102
+ [1.4.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.4.0
88
103
  [1.3.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.3.1
89
104
  [1.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.3.0
90
105
  [1.2.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v1.2.1
package/README.md CHANGED
@@ -10,9 +10,15 @@ Build beautiful, well-behaved CLI apps with Bun — **no third-party runtime dep
10
10
 
11
11
  Why another CLI parser?
12
12
 
13
- *Schema-first* -- define your entire CLI’s structure, commands, options, and help in a single, explicit data model, making the command-line interface centralized, clear, and self-describing upfront.
13
+ *Schema-first* define your entire CLI’s structure, commands, options, and help in a single, explicit data model, making the command-line interface centralized, clear, and self-describing upfront.
14
14
 
15
- *Bun-optimized* -- built from the ground up for Bun and TypeScript, leveraging Bun’s performance and modern JavaScript features without any extra dependencies.
15
+ *Beautiful `-h` screens* scoped help at any routing depth, rendered in rounded UTF-8 boxes with tables, terminal-width wrapping, and color when stdout is a TTY. Errors print in red with contextual help on stderr.
16
+
17
+ *Shell completions* — `completion bash` and `completion zsh` built-ins generate installable scripts from your schema so users get tab completion for commands, flags, and positionals without extra tooling.
18
+
19
+ *Optional MCP server* — set `mcpServer: {}` on the program root to expose leaf commands as MCP tools and the full CLI tree as a schema resource (`myapp mcp` over stdio). See [docs/mcp.md](docs/mcp.md).
20
+
21
+ *Bun-optimized* — built from the ground up for Bun and TypeScript, leveraging Bun’s performance and modern JavaScript features without any extra dependencies.
16
22
 
17
23
  Also checkout ArgsBarg for [cpp](https://github.com/bdombro/cpp-argsbarg), [nim](https://github.com/bdombro/nim-argsbarg), and [swift](https://github.com/bdombro/swift-argsbarg)!
18
24
 
@@ -90,11 +96,20 @@ Every app gets:
90
96
  - `-h` / `--help` at any routing depth (scoped help).
91
97
  - **`--schema`** at the program root — print the full command tree as JSON (for tooling and agents).
92
98
  - **`completion bash` / `completion zsh`** — print shell completion scripts to stdout (injected by `cliRun`).
99
+ - **`mcp`** — when `mcpServer: {}` is set on the program root, run an MCP server over stdio (`myapp mcp`).
93
100
 
94
101
  Do not declare a top-level command named **`completion`** — it is reserved for this built-in.
102
+ Do not declare a top-level command named **`mcp`** — it is reserved when MCP is enabled.
95
103
  Do not declare an option named **`schema`** — it is reserved for `--schema`.
96
104
 
97
105
 
106
+ ### MCP (AI agents)
107
+
108
+ Opt in on the program root with `mcpServer: {}` (or `{ name, version, … }`), then run `myapp mcp` for a stdio MCP server. Each leaf command becomes a tool; the CLI tree is available as resource `argsbarg://schema`.
109
+
110
+ See **[docs/mcp.md](docs/mcp.md)** for configuration, Cursor setup, tool naming, argument mapping, and protocol details.
111
+
112
+
98
113
  ### Shell completions
99
114
 
100
115
  ```bash
package/docs/mcp.md ADDED
@@ -0,0 +1,211 @@
1
+ # MCP server
2
+
3
+ ArgsBarg can expose your CLI to AI agents through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Each **leaf command** becomes an MCP tool; the full command tree is available as a schema resource. The server speaks JSON-RPC over stdio — one JSON object per line on stdin and stdout.
4
+
5
+ MCP is **opt-in**. Apps that do not set `mcpServer` on the program root behave exactly as before.
6
+
7
+ ## Quick start
8
+
9
+ 1. Add `mcpServer` to your program root:
10
+
11
+ ```typescript
12
+ const cli: CliCommand = {
13
+ key: "myapp",
14
+ description: "My app.",
15
+ mcpServer: { name: "myapp", version: "1.0.0" },
16
+ commands: [/* ... */],
17
+ };
18
+ ```
19
+
20
+ `mcpServer: {}` is enough to enable the server. Optional fields override defaults (see [Configuration](#configuration)).
21
+
22
+ 2. Run the MCP server:
23
+
24
+ ```bash
25
+ myapp mcp
26
+ ```
27
+
28
+ The process reads NDJSON requests from stdin and writes NDJSON responses to stdout. It stays alive until stdin closes.
29
+
30
+ 3. Point your MCP client at that command. See [Client setup](#client-setup).
31
+
32
+ The `examples/nested.ts` demo enables MCP — try:
33
+
34
+ ```bash
35
+ bun run examples/nested.ts mcp
36
+ ```
37
+
38
+ ## Client setup
39
+
40
+ ### Cursor
41
+
42
+ Add a server entry under `mcpServers` in your Cursor MCP config:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "myapp": {
48
+ "command": "bun",
49
+ "args": ["run", "myapp.ts", "mcp"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Use your real binary or script path. For a compiled CLI, `command` can be the installed binary and `args` can be `["mcp"]` only.
56
+
57
+ ### Other MCP hosts
58
+
59
+ Any host that spawns a subprocess and wires stdin/stdout works the same way: the **command** is your app, and **`mcp`** is the subcommand that starts the server.
60
+
61
+ ## Configuration
62
+
63
+ Set `mcpServer` on the **program root only** (the `CliCommand` passed to `cliRun`). Validation rejects `mcpServer` on nested nodes.
64
+
65
+ | Field | Default | Purpose |
66
+ | --- | --- | --- |
67
+ | `name` | root `key` | `serverInfo.name` in the `initialize` response |
68
+ | `version` | `package.json` `version` in cwd, else `"0.0.0"` | `serverInfo.version` |
69
+ | `schemaResourceUri` | `"argsbarg://schema"` | URI for the schema resource |
70
+
71
+ Example with all fields:
72
+
73
+ ```typescript
74
+ mcpServer: {
75
+ name: "nested-demo",
76
+ version: "1.0.0",
77
+ schemaResourceUri: "argsbarg://schema",
78
+ }
79
+ ```
80
+
81
+ ## Tools
82
+
83
+ Every **user-defined leaf command** in your schema becomes one MCP tool. Built-ins (`completion`, `mcp`) are not exposed as tools.
84
+
85
+ ### Tool names
86
+
87
+ Tool names are derived from the command path, with each segment sanitized (non-alphanumeric characters become `_`) and joined with `_`.
88
+
89
+ | CLI invocation | Tool name |
90
+ | --- | --- |
91
+ | `myapp deploy` | `deploy` |
92
+ | `myapp stat owner lookup` | `stat_owner_lookup` |
93
+ | `nested.ts read` | `read` |
94
+
95
+ ### Tool descriptions
96
+
97
+ Each tool’s `description` includes the human CLI path and the leaf’s help text, separated by an em dash:
98
+
99
+ | CLI path | MCP `description` |
100
+ | --- | --- |
101
+ | `stat owner lookup` | `stat owner lookup — Resolve owner info.` |
102
+ | `read` | `read — Print the first line of each file.` |
103
+ | (root leaf app) | `{root.key} — Tiny demo.` |
104
+
105
+ ### Per-leaf visibility
106
+
107
+ Set `mcpTool: { enabled: false }` on a **leaf command** to hide it from `tools/list` while keeping it in the CLI and in `--schema` output:
108
+
109
+ ```typescript
110
+ {
111
+ key: "debug",
112
+ description: "Internal diagnostics.",
113
+ mcpTool: { enabled: false },
114
+ handler: () => { /* ... */ },
115
+ }
116
+ ```
117
+
118
+ Omitted or `enabled: true` exposes the command (default). `mcpTool` is only valid on leaves — not on the program root or routing groups.
119
+
120
+ ### Tool arguments
121
+
122
+ Each tool’s `inputSchema` is a JSON Schema object built from your CLI definition:
123
+
124
+ - **Options** — parent-scoped flags are included (e.g. `stat`’s `--json` appears on `stat_owner_lookup`). Presence options are `boolean`; string and number options match their `CliOptionKind`. Required options are listed in `required`.
125
+ - **Positionals** — one property per `CliPositional` on the leaf. Single-slot positionals are `string`; varargs tails (`argMax: 0`) are `string[]`. Required positionals are listed in `required`.
126
+
127
+ Arguments are a **flat JSON object** keyed by option and positional names (same names as in your schema, including hyphenated option names like `"user-name"`).
128
+
129
+ Example for `nested.ts stat owner lookup`:
130
+
131
+ ```json
132
+ {
133
+ "path": "/path/to/file",
134
+ "user-name": "alice",
135
+ "json": true
136
+ }
137
+ ```
138
+
139
+ This maps to argv: `stat owner lookup --json --user-name alice /path/to/file`.
140
+
141
+ Tool arguments use **long option names** only (`user-name`, not `-u`). Short aliases from your schema are not accepted in MCP tool calls.
142
+
143
+ ### Tool results
144
+
145
+ On success (`isError: false`):
146
+
147
+ - **stdout** — first `content` text block with the handler’s captured stdout (raw, unchanged).
148
+ - **stderr** — when non-empty, a second `content` text block with trimmed stderr (no prefix). The block’s position signals stderr; hosts may label it themselves.
149
+ - **structuredContent** — when trimmed stdout is valid JSON, the parsed value is also returned per the [MCP tools spec](https://modelcontextprotocol.io/specification/draft/server/tools). Objects and arrays from flags like `--json` are the common case. JSON **primitives** (`true`, `42`, `"hello"`) are parsed too — a handler that prints the literal string `true` as human text would get `structuredContent: true`. Prefer objects for machine-readable output.
150
+
151
+ On failure (parse error, validation error, non-zero exit, thrown error), the message is returned as text content with `isError: true`. Handler stderr is included when present.
152
+
153
+ Help and `--schema` are not available through tool calls; use the schema resource or run the CLI directly for those.
154
+
155
+ ## Schema resource
156
+
157
+ The server advertises one MCP resource: your full CLI tree as JSON — the same output as `myapp --schema`.
158
+
159
+ | Property | Value |
160
+ | --- | --- |
161
+ | Default URI | `argsbarg://schema` |
162
+ | MIME type | `application/json` |
163
+ | Contents | `cliSchemaJson(root)` — handlers omitted, built-ins excluded |
164
+
165
+ Agents can read this resource to discover commands, options, and positionals without guessing tool shapes.
166
+
167
+ ## Protocol
168
+
169
+ - **Transport:** stdio, newline-delimited JSON (NDJSON).
170
+ - **JSON-RPC:** version `2.0`.
171
+ - **MCP protocol version:** `2024-11-05` (reported in `initialize`).
172
+
173
+ ### Supported methods
174
+
175
+ | Method | Description |
176
+ | --- | --- |
177
+ | `initialize` | Returns capabilities (`tools`, `resources`) and `serverInfo`. |
178
+ | `notifications/initialized` | Acknowledged; no response (notification). |
179
+ | `ping` | Returns `{}`. |
180
+ | `tools/list` | Lists all tools with `name`, `description`, `inputSchema`. |
181
+ | `tools/call` | Runs a leaf handler; params: `name`, `arguments` (object). |
182
+ | `resources/list` | Lists the schema resource. |
183
+ | `resources/read` | Returns schema JSON; params: `uri`. |
184
+
185
+ Requests without an `id` are treated as notifications and do not receive a response (except `notifications/initialized`, which is ignored after parsing).
186
+
187
+ ### Manual smoke test
188
+
189
+ ```bash
190
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | bun run examples/nested.ts mcp
191
+ ```
192
+
193
+ You should get one JSON line on stdout with `result.capabilities` and `result.serverInfo`.
194
+
195
+ ## Reserved names
196
+
197
+ When MCP is enabled:
198
+
199
+ - Do not declare a top-level command named **`mcp`** — it is reserved for the built-in subcommand.
200
+ - Do not declare a top-level command named **`completion`** — reserved for shell completions.
201
+ - Do not declare an option named **`schema`** — reserved for `--schema`.
202
+
203
+ Running `myapp mcp` without `mcpServer` on the root fails with an error (exit 1).
204
+
205
+ ## Design notes
206
+
207
+ - **Zero extra dependencies** — hand-rolled NDJSON JSON-RPC on top of ArgsBarg’s existing parser and schema.
208
+ - **Same handlers** — tool calls run your real leaf handlers via an internal invoke path that captures stdout/stderr and does not exit the process, so the MCP server can handle many requests in one process.
209
+ - **User schema only** — tool dispatch uses your program root, not merged presentation builtins.
210
+
211
+ For the `--schema` export used by the resource, see the main README built-ins section.
@@ -12,6 +12,7 @@ import { cliRun, CliCommand, CliOptionKind, CliFallbackMode } from "../src/index
12
12
  const cli: CliCommand = {
13
13
  key: "nested.ts",
14
14
  description: "Nested groups demo.",
15
+ mcpServer: { name: "nested-demo", version: "1.0.0" },
15
16
  commands: [
16
17
  {
17
18
  key: "stat",
package/index.d.ts CHANGED
@@ -66,6 +66,24 @@ export interface CliPositional {
66
66
  */
67
67
  argMax?: number;
68
68
  }
69
+ /**
70
+ * Root-only. Enables `myapp mcp` and MCP stdio server metadata.
71
+ */
72
+ export interface CliMcpServerConfig {
73
+ /** `initialize` serverInfo.name (default: root `key`). */
74
+ name?: string;
75
+ /** `initialize` serverInfo.version (default: see resolveMcpVersion). */
76
+ version?: string;
77
+ /** Resource URI for schema export (default: `"argsbarg://schema"`). */
78
+ schemaResourceUri?: string;
79
+ }
80
+ /**
81
+ * Leaf-only. Controls how this command appears as an MCP tool.
82
+ */
83
+ export interface CliMcpToolConfig {
84
+ /** When `false`, omit from `tools/list` (default: exposed). */
85
+ enabled?: boolean;
86
+ }
69
87
  /**
70
88
  * Base properties shared by all command nodes.
71
89
  */
@@ -78,6 +96,10 @@ export interface CliCommandBase {
78
96
  notes?: string;
79
97
  /** Global or command-level flags/options. */
80
98
  options?: CliOption[];
99
+ /** Root-only. When set, enables the `mcp` built-in subcommand. */
100
+ mcpServer?: CliMcpServerConfig;
101
+ /** Leaf-only. Per-tool MCP exposure and metadata. */
102
+ mcpTool?: CliMcpToolConfig;
81
103
  }
82
104
  /**
83
105
  * A command node: either a routing group (has commands) or a leaf (has handler).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "//just": "echo this app uses justfile for development tasks"
package/src/completion.ts CHANGED
@@ -522,7 +522,7 @@ export function completionZshScript(schema: CliCommand): string {
522
522
  }
523
523
 
524
524
  /**
525
- * Returns a schema suitable for help display, including the reserved `completion` subtree.
525
+ * Returns a schema suitable for help display, including reserved built-in subtrees.
526
526
  * Routing roots get `completion` merged; leaf roots are wrapped as a tiny router.
527
527
  */
528
528
  export function cliPresentationRoot(root: CliCommand): CliCommand {
@@ -534,15 +534,33 @@ export function cliPresentationRoot(root: CliCommand): CliCommand {
534
534
  key: root.key,
535
535
  description: root.description,
536
536
  options: root.options,
537
- commands: [cliBuiltinCompletionGroup(root.key)],
537
+ commands: presentationBuiltins(root),
538
538
  } as CliCommand;
539
539
  }
540
540
  return {
541
541
  ...root,
542
- commands: [...(root.commands ?? []), cliBuiltinCompletionGroup(root.key)],
542
+ commands: [...(root.commands ?? []), ...presentationBuiltins(root)],
543
543
  } as CliCommand;
544
544
  }
545
545
 
546
+ /** Built-in commands shown in help and merged for routing CLIs. */
547
+ function presentationBuiltins(root: CliCommand): CliCommand[] {
548
+ const cmds: CliCommand[] = [cliBuiltinCompletionGroup(root.key)];
549
+ if (root.mcpServer !== undefined) {
550
+ cmds.push(cliBuiltinMcpCommand());
551
+ }
552
+ return cmds;
553
+ }
554
+
555
+ /** Builds the static `mcp` leaf command (merged when `root.mcpServer` is set). */
556
+ export function cliBuiltinMcpCommand(): CliCommand {
557
+ return {
558
+ key: "mcp",
559
+ description: "Run as an MCP server over stdio (for AI agents).",
560
+ handler: () => {},
561
+ };
562
+ }
563
+
546
564
  /**
547
565
  * Builds the static `completion` / `bash` / `zsh` command subtree (merged into the program root at runtime).
548
566
  */