@@ -73,17 +73,24 @@ stdio client is unaffected. See
7373 [ ` .claude/skills/live-agent-loop/SKILL.md ` ] ( .claude/skills/live-agent-loop/SKILL.md ) ,
7474 reference in [ docs/live-agent-loop.md] ( docs/live-agent-loop.md ) .
7575- ` npm run live-coverage ` reports which of the 160 registered tools are driven
76- against the real Google API by checked-in code (28) and which have unit tests
77- only (132), and exits non-zero if a scenario calls a tool that is no longer
78- registered. Tools that provably cannot reach Google are reported separately
76+ against the real Google API by checked-in code (29) and which have unit tests
77+ only (131), and exits non-zero if a scenario calls a tool that is no longer
78+ registered, or if it finds no covered tools at all (a silent zero means the
79+ scan broke, not the coverage). Calls through the ` ctx.createDoc() ` and
80+ ` ctx.createFolder() ` helpers are credited to ` createDocument ` and
81+ ` createFolder ` , any quote style around a tool name counts, and
82+ ` live/missions/archive/ ` is skipped: it holds the frozen iteration-1 and
83+ iteration-2 transcripts, which record what the agent hit at the time and are
84+ not expected to pass on fixed code (loop-2 sends the nested ` formatCells `
85+ shape that 3.0 rejects by design). Tools that provably cannot reach Google are reported separately
7986 rather than counted as covered, because a scenario names them only to assert
8087 the refusal holds: ` forwardMessage ` , which the runner blocks before
8188 ` execute() ` , and ` createPresentation ` , which ` guard.mjs ` denies outright
8289 because the Slides API creates in Drive root whatever parent it is given. It
8390 runs offline, registering every category against a recording
8491 stub. The point is that "all suites pass" and "this tool works" are different
8592 claims, and the gap between them should be a number rather than a feeling.
86- - Jest ` testTimeout ` raised to 30s. The four suites that call ` registerAllTools `
93+ - Jest ` testTimeout ` raised to 30s. The seven suites that call ` registerAllTools `
8794 dynamically import all 12 tool categories (~ 180 modules): about 750ms warm, but
8895 measured past Jest's 5s default against a cold filesystem cache. That is
8996 exactly the CI shape, since both workflows run ` npm ci ` and then the suite, and
@@ -101,6 +108,37 @@ stdio client is unaffected. See
101108 counted what the runner had noticed. One shared extractor now handles every
102109 shape including the prose form, and a creating call whose id cannot be found
103110 is reported as ` UNTRACKED ` and fails the run rather than passing quietly.
111+ The cleanup loop itself is now one shared implementation
112+ (` scripts/live-smoke/cleanup.mjs ` ) instead of two copies, because both copies
113+ had the same holes: a file the run had already deleted was reported as left
114+ behind (the containment check ran before the existence check, and an
115+ unreadable file is "not proven inside"); an ` invalid_grant ` rebuild inside
116+ the run's last call left the raw Drive handle null so every trash failed with
117+ a ` TypeError ` ; a guard refusal was counted as neither pass nor fail; and a
118+ mission that ` track() ` ed an id the runner had already registered made
119+ cleanup say ` 2/2 ` for one file. After cleanup both runners now list the test
120+ folder and any leftover drafts and fail on anything this run left there, and
121+ ` --keep ` prints each kept id with the ` npm run live-call -- --cleanup <ids> `
122+ command that trashes them later.
123+ - The set of tools ` live-coverage ` reports as "denied by ` guard.mjs ` " is derived
124+ from the guard's own deny table and read-only deciders (` MUTATING_VERB ` is now
125+ exported for that) rather than a hand-maintained list beside it, so the two
126+ cannot drift. The created-resource pin test executes all eight creating tools
127+ against fake clients and feeds their real return values to the shared id
128+ extractor, replacing a test that only compared two hand-written literals.
129+ - ` verify-preserve-heading ` probe 3 deletes a heading on purpose; its verdict
130+ logic expected the heading to survive and reported false friction on every
131+ run. The probe now states which outcome it expects.
132+ - The post-cleanup audit fails closed. It listed only the top level of the test
133+ folder, so a file inside a folder the run created was hidden behind its
134+ parent; a listing that threw came back as an empty array; a listing the tool
135+ cut short was read as complete; and any ` getDraft ` error (auth, quota,
136+ network) was taken as "the draft is gone". The scan is now recursive
137+ (` depth: 'all' ` , up to 5000 items), and a listing that could not be made, was
138+ truncated, or skipped a folder it could not read is reported as ` UNVERIFIED `
139+ and fails the run. Only an error carrying a real 404 status counts as a
140+ deleted draft; anything else is reported per draft and fails the run too.
141+ Covered by ` tests/liveHarnessCleanupAudit.test.js ` .
104142
105143### Security
106144
@@ -238,12 +276,63 @@ stdio client is unaffected. See
238276 token fails every in-flight call at once, and each failure nulled the shared
239277 clients out from under the others mid-rebuild. Both now hold the in-flight
240278 Promise, and both release it when it settles, so a declined consent screen is
241- never replayed to the next caller.
279+ never replayed to the next caller. Each flow releases only the latch it owns:
280+ ` logout ` drops the latch mid-flow and the next request starts a fresh one,
281+ and when the abandoned flow later fails it must not clear that newer latch,
282+ or a third request would open a third browser window behind the one the
283+ user is already looking at. Nor may the abandoned flow * succeed* : ` logout `
284+ bumps an authorization generation, and a flow that started under the old one
285+ throws ` Logged out while authorization was in progress ` instead of installing
286+ a client the user has just asked to discard, whichever flow finishes last.
287+ A cold request that arrives while a re-authorization is running joins it
288+ rather than opening a second consent screen beside it.
242289- ** ` GET /healthz ` reported a closed runtime as healthy.** The health branch sat
243290 above the ` closed ` check, so a drained handler answered ` 200 {"status":"ok"} `
244291 while ` /mcp ` already answered ` 503 ` — the one probe meant to notice a dead
245292 runtime was the only route that never did. It now answers
246293 ` 503 {"status":"closed"} ` , still behind the same auth gate.
294+ - ** ` setup ` and ` doctor ` could not finish on any machine that had Codex or
295+ Claude Code installed.** Both clients report a missing registration as a
296+ failed command (` codex mcp get google --json ` exits 1 with "No MCP server
297+ named 'google' found."), and every rejection was mapped to ` unknown ` without
298+ reading it, so setup stopped at Step 5 with "was left unconfigured (unknown).
299+ Setup is incomplete." and doctor said "unrecognized client entry". The Claude
300+ Code probe was worse: ` claude mcp get -s user google --json ` is answered by
301+ every version with ` error: unknown option '-s' ` , so inspection was ` unknown `
302+ on every machine. A rejection whose text says "missing" now means missing,
303+ and Claude Code's user-scope entry is read from the file `claude mcp add -s
304+ user` writes ( ` ~ /.claude.json` , or ` $CLAUDE_CONFIG_DIR/.claude.json`), with
305+ its ` type: 'stdio' ` discriminator and empty ` env ` stripped so a correct entry
306+ compares equal to the desired one instead of being re-added on every run.
307+ - ** ` doctor ` reported every README-documented registration as a problem.** Its
308+ "recommended" stdio entry is the absolute path of the copy running doctor,
309+ while setup writes ` node <global npm root>/google-tools-mcp/dist/index.js `
310+ and the README's own instructions register the bare ` google-tools-mcp ` bin
311+ or ` npx -y google-tools-mcp ` . All three were "entry differs from recommended
312+ configuration", exit 1, including ` npx -y google-tools-mcp doctor ` run right
313+ after a successful setup. An entry that launches this package (its bin, a
314+ Windows shim, ` npx google-tools-mcp[@version] ` , a ` dist/index.js ` next to a
315+ ` google-tools-mcp ` package.json) is now ` configured ` with a note saying it is
316+ not the entry setup would write, and the npx form additionally says why setup
317+ prefers a direct launch. A Codex entry without ` CODEX_MCP_PROTOCOL_VERSION `
318+ in its env stays a problem, and now names the missing variable and value. An
319+ entry that launches something else, a ` @latest ` target, or an HTTP URL that
320+ differs is still a problem. So is an entry that launches this package with a
321+ subcommand or flag after it (` google-tools-mcp doctor ` , `npx google-tools-mcp
322+ setup` , ` dist/index.js auth` ), because ` dist/index.js` dispatches on
323+ ` argv[2] ` and such an entry never starts the server, and one whose env sets
324+ ` GOOGLE_MCP_TRANSPORT ` to an HTTP transport.
325+ - ** ` doctor --json ` printed ` "args": "[Circular]" ` for the second client.** The
326+ diagnostic redactor treated every object it had already seen as a cycle, so
327+ the ` args ` array the two recommended entries share was replaced on its second
328+ appearance. It now tracks ancestors only: a true cycle still prints
329+ ` [Circular] ` , a shared reference prints its value.
330+ - ** The update check hit the npm registry on every launch.** ` checkForUpdate `
331+ read and wrote its cache through injectable ` readFile ` /` writeFile ` /` mkdir `
332+ parameters whose defaults were never set, so outside the unit tests every
333+ cache read threw, was swallowed, and the registry was asked again. The
334+ defaults are the real ` node:fs/promises ` functions, and a test now proves a
335+ second launch within the TTL makes no network request.
247336- ** ` readDocument(format='markdown') ` wrapped every run in a colour span nobody
248337 asked for, which made read-back verification impossible.** #14 requires every
249338 run this server writes to carry an explicit ` foregroundColor ` , and Google's
@@ -292,7 +381,14 @@ stdio client is unaffected. See
292381 agent into skipping discovery and guessing argument names. ` help ` now accepts
293382 ` tool ` (returns that tool's description and JSON Schema, ~ 3,000 characters)
294383 and ` listTools ` (just the registered names). Calling it with no arguments
295- still returns the full manual, so nothing that worked before changed.
384+ still returns the full manual, so nothing that worked before changed. The
385+ schema it returns is rendered the same way the SDK renders ` tools/list `
386+ (` io: 'input' ` , draft 2020-12): the first cut used Zod's bare
387+ ` toJSONSchema() ` , whose default is the * output* schema, so every
388+ ` .optional().default(x) ` field came back as ` required ` . For 50 of the 160
389+ tools that contradicted ` tools/list ` ; ` readDocument ` alone claimed all seven
390+ of its optional fields were mandatory. A test now pins ` help ` 's schema to the
391+ SDK's conversion for every registered tool.
296392- ** ` formatCells ` now names its accepted arguments when none are supplied.**
297393 The tool takes flat options with hex-string colors, not the nested Google
298394 Sheets API ` CellFormat ` shape, which is what a caller who knows the underlying
0 commit comments