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.
@@ -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