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/.cursor/plans/mcp_v1.1_polish_e9656029.plan.md +260 -0
- package/.cursor/plans/mcp_v1.2_invocation_and_extensions_a4f82c1e.plan.md +647 -0
- package/CHANGELOG.md +30 -1
- package/README.md +21 -5
- package/docs/mcp.md +300 -0
- package/examples/mcp-test.ts +66 -0
- package/examples/nested.ts +1 -0
- package/index.d.ts +120 -23
- package/package.json +1 -1
- package/src/completion.ts +76 -4
- package/src/context.ts +4 -1
- package/src/help.ts +12 -2
- package/src/index.test.ts +692 -1
- package/src/index.ts +12 -1
- package/src/invoke.ts +192 -0
- package/src/mcp/env.ts +99 -0
- package/src/mcp/result.ts +57 -0
- package/src/mcp/server.ts +238 -0
- package/src/mcp/tools.ts +254 -0
- package/src/mcp.ts +28 -0
- package/src/parse.ts +17 -2
- package/src/runtime.ts +26 -2
- package/src/types.ts +81 -1
- package/src/validate.ts +54 -1
|
@@ -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
|