argsbarg 1.3.1 → 1.4.1

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.
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
 
@@ -72,7 +78,7 @@ await cliRun(cli);
72
78
  Everything you need for a first-class CLI:
73
79
 
74
80
  - **Nested subcommands** (`CliCommand` with `commands` for groups, `handler` for leaves)
75
- - **POSIX-style options** (`-x`, `--long`, `--long=value`)
81
+ - **POSIX-style options** (`-x`, `--long`, `--long=value`) — kinds: presence, string, number, **enum** (`choices` array)
76
82
  - **Bundled presence flags** (`-abc`)
77
83
  - **Positional arguments and varargs tails** (`CliPositional` objects on `positionals`)
78
84
  - **Scoped help** at any routing depth (`-h` / `--help`)
@@ -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`. Handlers can read `ctx.invocation` and use `cliInvoke` for headless testing.
109
+
110
+ See **[docs/mcp.md](docs/mcp.md)** for configuration, env bootstrapping, custom resources, Cursor setup, and protocol details.
111
+
112
+
98
113
  ### Shell completions
99
114
 
100
115
  ```bash
@@ -182,10 +197,11 @@ The package root (`argsbarg` / `src/index.ts`) exports the types and runtime you
182
197
  | Symbol | Role |
183
198
  | --- | --- |
184
199
  | `CliCommand`, `CliOption`, `CliPositional`, `CliHandler` | Schema and handler types. |
185
- | `CliOptionKind`, `CliFallbackMode` | Option kinds and root fallback behavior. |
200
+ | `CliOptionKind`, `CliFallbackMode` | Option kinds (`Presence`, `String`, `Number`, `Enum`) and root fallback behavior. |
186
201
  | `CliSchemaValidationError` | Thrown when the static command tree violates schema rules. |
187
- | `CliContext` | Handler context (`ctx.flag`, `ctx.stringOpt`, `ctx.args`, …). |
202
+ | `CliContext` | Handler context (`ctx.flag`, `ctx.stringOpt`, `ctx.args`, `ctx.invocation`, …). |
188
203
  | `cliRun(root, [argv])` | Validate, parse argv, dispatch, exit. |
204
+ | `cliInvoke(root, argv)` | Parse and dispatch without exiting; returns captured stdout/stderr. |
189
205
  | `cliErrWithHelp(ctx, msg)` | Print error + scoped help on stderr, exit 1. |
190
206
 
191
207
  Reserved identifier (validated at startup): root command **`completion`**.
package/docs/mcp.md ADDED
@@ -0,0 +1,300 @@
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
+ | `shellEnv` | off | Capture login-shell `env` at startup (`true` uses `$SHELL`, or pass a shell path) |
71
+ | `envFile` | off | Load a `.env` file after `shellEnv` (`~` supported); warns on stderr if missing |
72
+ | `resources` | `[]` | Custom `CliMcpResource` entries for `resources/list` and `resources/read` |
73
+
74
+ Example with all fields:
75
+
76
+ ```typescript
77
+ mcpServer: {
78
+ name: "nested-demo",
79
+ version: "1.0.0",
80
+ schemaResourceUri: "argsbarg://schema",
81
+ }
82
+ ```
83
+
84
+ ## Tools
85
+
86
+ Every **user-defined leaf command** in your schema becomes one MCP tool. Built-ins (`completion`, `mcp`) are not exposed as tools.
87
+
88
+ ### Tool names
89
+
90
+ Tool names are derived from the command path, with each segment sanitized (non-alphanumeric characters become `_`) and joined with `_`.
91
+
92
+ | CLI invocation | Tool name |
93
+ | --- | --- |
94
+ | `myapp deploy` | `deploy` |
95
+ | `myapp stat owner lookup` | `stat_owner_lookup` |
96
+ | `nested.ts read` | `read` |
97
+
98
+ ### Tool descriptions
99
+
100
+ Each tool’s `description` includes the human CLI path and the leaf’s help text, separated by an em dash:
101
+
102
+ | CLI path | MCP `description` |
103
+ | --- | --- |
104
+ | `stat owner lookup` | `stat owner lookup — Resolve owner info.` |
105
+ | `read` | `read — Print the first line of each file.` |
106
+ | (root leaf app) | `{root.key} — Tiny demo.` |
107
+
108
+ ### Per-leaf visibility
109
+
110
+ Set `mcpTool: { enabled: false }` on a **leaf command** to hide it from `tools/list` while keeping it in the CLI and in `--schema` output:
111
+
112
+ ```typescript
113
+ {
114
+ key: "debug",
115
+ description: "Internal diagnostics.",
116
+ mcpTool: { enabled: false },
117
+ handler: () => { /* ... */ },
118
+ }
119
+ ```
120
+
121
+ Omitted or `enabled: true` exposes the command (default). `mcpTool` is only valid on leaves — not on the program root or routing groups.
122
+
123
+ ### Per-leaf tool metadata
124
+
125
+ ```typescript
126
+ mcpTool: {
127
+ enabled: true,
128
+ description: "Custom tools/list text (overrides auto-generated path + help).",
129
+ requiresEnv: ["API_TOKEN", "DATABASE_URL"],
130
+ }
131
+ ```
132
+
133
+ - **`description`** — when set, replaces the auto-generated `path — help` description entirely (no automatic `requiresEnv` suffix; mention vars in your text if needed).
134
+ - **`requiresEnv`** — on auto-generated descriptions, appended as `[requires env: …]`. Enforced at `tools/call` time before the handler runs. Empty or unset env values count as missing.
135
+
136
+ ### Tool arguments
137
+
138
+ Each tool’s `inputSchema` is a JSON Schema object built from your CLI definition:
139
+
140
+ - **Options** — parent-scoped flags are included (e.g. `stat`’s `--json` appears on `stat_owner_lookup`). Presence options are `boolean`; string, number, and **enum** options match their `CliOptionKind` (`Enum` uses JSON Schema `enum`). Required options are listed in `required`.
141
+ - **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`.
142
+
143
+ 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"`).
144
+
145
+ Example for `nested.ts stat owner lookup`:
146
+
147
+ ```json
148
+ {
149
+ "path": "/path/to/file",
150
+ "user-name": "alice",
151
+ "json": true
152
+ }
153
+ ```
154
+
155
+ This maps to argv: `stat owner lookup --json --user-name alice /path/to/file`.
156
+
157
+ Tool arguments use **long option names** only (`user-name`, not `-u`). Short aliases from your schema are not accepted in MCP tool calls.
158
+
159
+ ### Tool results
160
+
161
+ On success (`isError: false`):
162
+
163
+ - **stdout** — first `content` text block with the handler’s captured stdout (raw, unchanged).
164
+ - **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.
165
+ - **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.
166
+
167
+ 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.
168
+
169
+ Help and `--schema` are not available through tool calls; use the schema resource or run the CLI directly for those.
170
+
171
+ ## Schema and custom resources
172
+
173
+ The built-in resource `argsbarg://schema` (or `schemaResourceUri`) exposes your full CLI tree as JSON — the same output as `myapp --schema`.
174
+
175
+ | Property | Value |
176
+ | --- | --- |
177
+ | Default URI | `argsbarg://schema` |
178
+ | MIME type | `application/json` |
179
+ | Contents | `cliSchemaJson(root)` — handlers omitted, built-ins excluded |
180
+
181
+ Add custom resources on the program root:
182
+
183
+ ```typescript
184
+ mcpServer: {
185
+ resources: [
186
+ {
187
+ uri: "myapp://config",
188
+ name: "config",
189
+ description: "Resolved app configuration.",
190
+ mimeType: "application/json",
191
+ load: () => JSON.stringify({ /* … */ }),
192
+ },
193
+ ],
194
+ },
195
+ ```
196
+
197
+ URIs must be unique and must not equal `schemaResourceUri`. `load()` runs synchronously at `resources/read` time.
198
+
199
+ ## Invocation context
200
+
201
+ Handlers receive `ctx.invocation`: `"cli"` for normal `cliRun` dispatch, `"mcp"` for MCP `tools/call`.
202
+
203
+ Use this to branch subprocess behavior — MCP stdout is the JSON-RPC wire, so child processes must not inherit it:
204
+
205
+ ```typescript
206
+ handler: async (ctx) => {
207
+ const proc = Bun.spawn(["my-tool", ...ctx.args], {
208
+ stdout: ctx.invocation === "mcp" ? "pipe" : "inherit",
209
+ stderr: "inherit",
210
+ });
211
+ // capture proc.stdout when piping…
212
+ };
213
+ ```
214
+
215
+ `Bun.spawn({ stdout: "inherit" })` under MCP corrupts the wire. Prefer `"pipe"` and let argsbarg return captured handler stdout in the tool result.
216
+
217
+ ### `cliInvoke` (public API)
218
+
219
+ `cliInvoke(root, argv)` runs a leaf handler without exiting the process — useful for tests and headless integrations. Returns `{ kind, exitCode, stdout, stderr }`. MCP tool dispatch uses this internally.
220
+
221
+ **Note:** Tool output is buffered until the handler completes. Live streaming (e.g. `tail -f`) is not supported yet; see [Design notes](#design-notes).
222
+
223
+ ## Environment bootstrapping
224
+
225
+ MCP hosts (e.g. Cursor) often spawn your server with a minimal environment — missing `PATH` entries for Homebrew, nvm, rbenv, etc.
226
+
227
+ At server start (`cliMcpServeStdio`), before the NDJSON loop:
228
+
229
+ | Order | Source | Behavior |
230
+ | --- | --- | --- |
231
+ | 1 | `shellEnv` | Spawns `$SHELL -l -c env`; merges into `process.env` |
232
+ | 2 | `envFile` | Loads `.env`; **overwrites** keys from step 1 |
233
+
234
+ **`shellEnv` merge rules:**
235
+
236
+ - **`PATH`** — shell-only segments are **prepended** to the host `PATH` (always merged).
237
+ - **Other vars** — set only when absent from the host environment (host wins).
238
+ - On failure — one-line warning on **stderr**; server continues.
239
+
240
+ **`envFile`:**
241
+
242
+ - Supports `~` expansion.
243
+ - Missing file — warning on stderr, server continues.
244
+ - Keys from the file **always overwrite** `process.env`.
245
+
246
+ Example:
247
+
248
+ ```typescript
249
+ mcpServer: {
250
+ shellEnv: true,
251
+ envFile: "~/.config/myapp/mcp.env",
252
+ },
253
+ ```
254
+
255
+ ## Protocol
256
+
257
+ - **Transport:** stdio, newline-delimited JSON (NDJSON).
258
+ - **JSON-RPC:** version `2.0`.
259
+ - **MCP protocol version:** `2024-11-05` (reported in `initialize`).
260
+
261
+ ### Supported methods
262
+
263
+ | Method | Description |
264
+ | --- | --- |
265
+ | `initialize` | Returns capabilities (`tools`, `resources`) and `serverInfo`. |
266
+ | `notifications/initialized` | Acknowledged; no response (notification). |
267
+ | `ping` | Returns `{}`. |
268
+ | `tools/list` | Lists all tools with `name`, `description`, `inputSchema`. |
269
+ | `tools/call` | Runs a leaf handler; params: `name`, `arguments` (object). |
270
+ | `resources/list` | Lists schema + custom resources. |
271
+ | `resources/read` | Returns resource body; params: `uri`. |
272
+
273
+ Requests without an `id` are treated as notifications and do not receive a response (except `notifications/initialized`, which is ignored after parsing).
274
+
275
+ ### Manual smoke test
276
+
277
+ ```bash
278
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | bun run examples/nested.ts mcp
279
+ ```
280
+
281
+ You should get one JSON line on stdout with `result.capabilities` and `result.serverInfo`.
282
+
283
+ ## Reserved names
284
+
285
+ When MCP is enabled:
286
+
287
+ - Do not declare a top-level command named **`mcp`** — it is reserved for the built-in subcommand.
288
+ - Do not declare a top-level command named **`completion`** — reserved for shell completions.
289
+ - Do not declare an option named **`schema`** — reserved for `--schema`.
290
+
291
+ Running `myapp mcp` without `mcpServer` on the root fails with an error (exit 1).
292
+
293
+ ## Design notes
294
+
295
+ - **Zero extra dependencies** — hand-rolled NDJSON JSON-RPC on top of ArgsBarg’s existing parser and schema.
296
+ - **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.
297
+ - **User schema only** — tool dispatch uses your program root, not merged presentation builtins.
298
+ - **Buffered output** — MCP tool results are sent after the handler finishes. Incremental stdout (log tail, progress) is not streamed; a future release may add MCP progress notifications.
299
+
300
+ For the `--schema` export used by the resource, see the main README built-ins section.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bun
2
+ /*
3
+ MCP test fixture for subprocess integration tests only.
4
+ */
5
+
6
+ import { cliRun, CliCommand, CliOptionKind } from "../src/index.ts";
7
+
8
+ const envFilePath = process.env.ARGS_TEST_ENV_FILE;
9
+
10
+ const cli: CliCommand = {
11
+ key: "mcp-test",
12
+ description: "MCP integration test fixture.",
13
+ mcpServer: {
14
+ name: "mcp-test",
15
+ version: "0.0.0-test",
16
+ ...(envFilePath ? { envFile: envFilePath } : {}),
17
+ resources: [
18
+ {
19
+ uri: "test://hello",
20
+ name: "hello",
21
+ description: "Test resource.",
22
+ mimeType: "text/plain",
23
+ load: () => "hello resource",
24
+ },
25
+ ],
26
+ },
27
+ commands: [
28
+ {
29
+ key: "echo-env",
30
+ description: "Echo an env var.",
31
+ mcpTool: {
32
+ requiresEnv: ["ARGS_TEST_SECRET"],
33
+ },
34
+ options: [
35
+ {
36
+ name: "name",
37
+ description: "Env var name to read.",
38
+ kind: CliOptionKind.String,
39
+ required: true,
40
+ },
41
+ ],
42
+ handler: (ctx) => {
43
+ const name = ctx.stringOpt("name") ?? "";
44
+ console.log(process.env[name] ?? "");
45
+ },
46
+ },
47
+ {
48
+ key: "set-mode",
49
+ description: "Set mode enum.",
50
+ options: [
51
+ {
52
+ name: "mode",
53
+ description: "Operating mode.",
54
+ kind: CliOptionKind.Enum,
55
+ choices: ["dev", "prod"],
56
+ required: true,
57
+ },
58
+ ],
59
+ handler: (ctx) => {
60
+ console.log(`mode=${ctx.stringOpt("mode")}`);
61
+ },
62
+ },
63
+ ],
64
+ };
65
+
66
+ await cliRun(cli);
@@ -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
@@ -1,7 +1,35 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  /**
4
- * Option kinds: presence (boolean flag), string (free-form text), or number (strict double).
4
+ * Values passed to a leaf command handler after parsing: app name, routed path, args, and merged options.
5
+ */
6
+ export declare class CliContext {
7
+ readonly appName: string;
8
+ readonly commandPath: string[];
9
+ readonly args: string[];
10
+ readonly schema: CliCommand;
11
+ readonly opts: Record<string, string>;
12
+ readonly invocation: CliInvocation;
13
+ /** Captures the merged program root, routed path, positional words, and option map for a leaf handler. */
14
+ constructor(appName: string, commandPath: string[], args: string[], opts: Record<string, string>, schema: CliCommand, invocation?: CliInvocation);
15
+ /** Returns whether a presence flag was set (including implicit "1" for boolean options). */
16
+ hasFlag(name: string): boolean;
17
+ /** Returns the string value for a string-valued option, if present. */
18
+ stringOpt(name: string): string | undefined;
19
+ /** Parses a stored string as a number; returns null if missing or not a strict double string. */
20
+ numberOpt(name: string): number | null;
21
+ /**
22
+ * Generic typed accessor: parses a stored string using the provided parse function.
23
+ * This is the TypeScript-native advantage over the Swift version.
24
+ */
25
+ typedOpt<T>(name: string, parse: (s: string) => T): T | null;
26
+ }
27
+ /**
28
+ * How a leaf handler was dispatched.
29
+ */
30
+ export type CliInvocation = "cli" | "mcp";
31
+ /**
32
+ * Option kinds: presence (boolean flag), string (free-form text), number (strict double), or enum (fixed choices).
5
33
  */
6
34
  export declare enum CliOptionKind {
7
35
  /** Boolean flag: no value token (may be implicit `"1"` when set). */
@@ -9,7 +37,9 @@ export declare enum CliOptionKind {
9
37
  /** Free-form string value. */
10
38
  String = "string",
11
39
  /** Strict floating-point value (parsed at validation time). */
12
- Number = "number"
40
+ Number = "number",
41
+ /** Fixed set of allowed string values. Requires non-empty `choices` on the option. */
42
+ Enum = "enum"
13
43
  }
14
44
  /**
15
45
  * When fallbackCommand is used for missing or unknown top-level tokens.
@@ -44,6 +74,11 @@ export interface CliOption {
44
74
  shortName?: string;
45
75
  /** Whether this option must be provided. Cannot be used with Presence kind. */
46
76
  required?: boolean;
77
+ /**
78
+ * Allowed values. Required when kind === Enum; ignored otherwise.
79
+ * Must be a non-empty array of distinct non-empty strings.
80
+ */
81
+ choices?: string[];
47
82
  }
48
83
  /**
49
84
  * An ordered positional argument slot, listed on `CliCommand.positionals`.
@@ -66,6 +101,67 @@ export interface CliPositional {
66
101
  */
67
102
  argMax?: number;
68
103
  }
104
+ /**
105
+ * Root-only. Enables `myapp mcp` and MCP stdio server metadata.
106
+ */
107
+ export interface CliMcpServerConfig {
108
+ /** `initialize` serverInfo.name (default: root `key`). */
109
+ name?: string;
110
+ /** `initialize` serverInfo.version (default: see resolveMcpVersion). */
111
+ version?: string;
112
+ /** Resource URI for schema export (default: `"argsbarg://schema"`). */
113
+ schemaResourceUri?: string;
114
+ /**
115
+ * Capture the user's login shell environment at MCP server start and merge it
116
+ * into process.env. Solves missing PATH, nvm/rbenv shims, Homebrew binaries,
117
+ * and shell exports that MCP hosts (e.g. Cursor) don't inherit.
118
+ */
119
+ shellEnv?: boolean | string;
120
+ /**
121
+ * Path to a .env file loaded into process.env at MCP server start, after shellEnv.
122
+ * Supports `~` expansion. Warns on stderr if the file does not exist.
123
+ * Always overwrites — envFile is authoritative for its keys.
124
+ */
125
+ envFile?: string;
126
+ /**
127
+ * Custom MCP resources exposed alongside the built-in argsbarg://schema resource.
128
+ * URIs must be unique and must not equal schemaResourceUri.
129
+ */
130
+ resources?: CliMcpResource[];
131
+ }
132
+ /**
133
+ * A custom MCP resource exposed under resources/list and resources/read.
134
+ */
135
+ export interface CliMcpResource {
136
+ /** Resource URI (must be unique; must not equal schemaResourceUri). */
137
+ uri: string;
138
+ /** Short display name for resources/list. */
139
+ name: string;
140
+ /** Optional human description for resources/list. */
141
+ description?: string;
142
+ /** MIME type (default: "text/plain"). */
143
+ mimeType?: string;
144
+ /** Called at resources/read time; must return the resource body. */
145
+ load: () => string;
146
+ }
147
+ /**
148
+ * Leaf-only. Controls how this command appears as an MCP tool.
149
+ */
150
+ export interface CliMcpToolConfig {
151
+ /** When `false`, omit from `tools/list` (default: exposed). */
152
+ enabled?: boolean;
153
+ /**
154
+ * Override the generated MCP tool description.
155
+ * Default: auto-generated from command path and description.
156
+ */
157
+ description?: string;
158
+ /**
159
+ * Environment variable names required at runtime.
160
+ * Appended to auto-generated MCP tool descriptions; enforced at tools/call time.
161
+ * Empty string counts as absent.
162
+ */
163
+ requiresEnv?: string[];
164
+ }
69
165
  /**
70
166
  * Base properties shared by all command nodes.
71
167
  */
@@ -78,6 +174,10 @@ export interface CliCommandBase {
78
174
  notes?: string;
79
175
  /** Global or command-level flags/options. */
80
176
  options?: CliOption[];
177
+ /** Root-only. When set, enables the `mcp` built-in subcommand. */
178
+ mcpServer?: CliMcpServerConfig;
179
+ /** Leaf-only. Per-tool MCP exposure and metadata. */
180
+ mcpTool?: CliMcpToolConfig;
81
181
  }
82
182
  /**
83
183
  * A command node: either a routing group (has commands) or a leaf (has handler).
@@ -120,29 +220,26 @@ export declare class CliSchemaValidationError extends Error {
120
220
  /** Creates a schema validation error with a human-readable rule violation. */
121
221
  constructor(message: string);
122
222
  }
223
+ /** Outcome of a non-exiting CLI invocation. */
224
+ export type CliInvokeKind = "ok" | "help" | "schema" | "error";
225
+ /** Result of cliInvoke: captured output and exit metadata without process.exit. */
226
+ export interface CliInvokeResult {
227
+ /** Invocation outcome. */
228
+ kind: CliInvokeKind;
229
+ /** Simulated exit code. */
230
+ exitCode: number;
231
+ /** Captured stdout during handler execution. */
232
+ stdout: string;
233
+ /** Captured stderr during handler execution. */
234
+ stderr: string;
235
+ /** Set when kind === "error" (parse/validation message). */
236
+ errorMsg?: string;
237
+ }
123
238
  /**
124
- * Values passed to a leaf command handler after parsing: app name, routed path, args, and merged options.
239
+ * Parses argv against the user root, runs the leaf handler, and returns captured output.
240
+ * Never calls process.exit.
125
241
  */
126
- export declare class CliContext {
127
- readonly appName: string;
128
- readonly commandPath: string[];
129
- readonly args: string[];
130
- readonly schema: CliCommand;
131
- readonly opts: Record<string, string>;
132
- /** Captures the merged program root, routed path, positional words, and option map for a leaf handler. */
133
- constructor(appName: string, commandPath: string[], args: string[], opts: Record<string, string>, schema: CliCommand);
134
- /** Returns whether a presence flag was set (including implicit "1" for boolean options). */
135
- hasFlag(name: string): boolean;
136
- /** Returns the string value for a string-valued option, if present. */
137
- stringOpt(name: string): string | undefined;
138
- /** Parses a stored string as a number; returns null if missing or not a strict double string. */
139
- numberOpt(name: string): number | null;
140
- /**
141
- * Generic typed accessor: parses a stored string using the provided parse function.
142
- * This is the TypeScript-native advantage over the Swift version.
143
- */
144
- typedOpt<T>(name: string, parse: (s: string) => T): T | null;
145
- }
242
+ export declare function cliInvoke(root: CliCommand, argv: string[]): Promise<CliInvokeResult>;
146
243
  /**
147
244
  * Validates the schema, parses argv, prints help or errors, runs completion or the leaf handler, then exits.
148
245
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "//just": "echo this app uses justfile for development tasks"