perf(mcp): cut tool catalog context by 24.5%
What
Cuts the glab mcp serve tool catalog from 150,434 to 113,633 bytes (-24.5%), fixes three correctness bugs found on the way, and makes response truncation visible.
Measured by driving initialize + tools/list over stdio against the built binary, before and after. No remaining tool is renamed and no call semantics change; five CLI-housekeeping tools are removed (see commit 3).
Why
Every registered command ships its full input schema at connect time, so the catalog is fixed context cost in every session that loads this server. Roughly 29% of the payload was the same wrapper repeated 200 times.
Commits
1. fix(mcp): trim schema filler and surface truncated output
- Drops
"description": "Command flags"from the flags object on all 200 tools. The property is namedflagsand holds flags. - Drops the bare
"Positional arguments"on the 77 tools whosecmd.Usenames no arguments. The 123 tools with a real hint (Positional arguments: <issue-id>) keep it. - Read-only tools were advertised as destructive. They carried no annotations at all, and the spec defaults
destructiveHinttotruewhen absent, soissue_list,mr_view,ci_statusand 70 others read as mutating to any client that gates confirmation on the hint. Now explicitlyreadOnlyHint: true. - Truncation was silent, and the server instructions named a field that did not exist. They told agents to "check
total_sizein response metadata"; no MCP response ever contained it (total_sizeis a container-registry payload field). Responses now carrytotal_size, plusnext_offsetwhen cut, and a truncated response says so in its text content.
2. perf(mcp): drop limit and offset from tool input schemas
Removes the two paging parameters from every schema. convertParamsToArgs still reads both off the params map, so the capability is unchanged and a caller can pass either; only the per-tool advertisement is gone, and a test pins that contract.
This commit is deliberately separable. If reviewers would rather keep the parameters declared, reverting it alone costs ~10 KB and leaves the rest intact.
3. fix(mcp): stop advertising CLI housekeeping commands as tools
check-update, completion, version, whatsnew and mcp serve manage the local CLI rather than act on GitLab. glab_mcp_serve was the clearest case: an MCP server offering a tool that starts an MCP server. They carried mcp:safe, which registers them; they now carry the existing mcp:exclude, so the next reader sees a decision rather than a missing annotation. 200 tools to 195.
The guard test lives next to NewCmdRoot rather than in the MCP package, which cannot build the real command tree without an import cycle.
On the truncation cap
Worth knowing, because it is worse than it looks and this MR only makes it visible rather than fixing it: a plain glab_mr_list against this project returns ~165,000 characters against a 50,000 cap, and issue_list ~154,000. Those tools discard ~70% of their response on every call. Because the server force-enables --output json, the surviving window is an incomplete JSON fragment that will not parse, so structuredContent.data is silently absent and byte paging cannot reassemble it.
The truncation notice is therefore output-aware: JSON responses are steered towards per_page, page and jq, and only plain-text output (ci trace, mr diff) is pointed at offset.
Shrinking those responses at the source is the follow-up, and is worth more than any schema trim.
Verification
lefthook run pre-pushclean: build,check-generated,check-args,check-embed,comment-ratio,go-test,go-lint.make gen-docsproduces no drift.comment-overlapfindings ininternal/commands/mcp/servewent from 11 onmainto 9.- Verified end-to-end against a live
glab_mr_listthat the notice fires with correct counts, thatstructuredContentcarriestotal_size/next_offset, and that an undeclaredlimit: 1000000still returns the full 164,653-character response as valid JSON.
Not included
The remaining ~7 KB of annotation boilerplate (readOnlyHint: false, idempotentHint: false on destructive tools) is unreachable: the SDK declares both as bare bool without omitempty, so they always marshal. The only lever is the global MCPGODEBUG=hintomitempty=1 godebug, which is not appropriate to set from library code.