Skip to content

fix: self-contained injected functions, aligned catalog normalization, clean drift reports - #9

Merged
jmagar merged 1 commit into
mainfrom
claude/followups
Aug 14, 2026
Merged

fix: self-contained injected functions, aligned catalog normalization, clean drift reports#9
jmagar merged 1 commit into
mainfrom
claude/followups

Conversation

@jmagar

@jmagar jmagar commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Followups from #6 and #7. Two of these are real runtime bugs that the test suites could not see.

The injected functions were not self-contained

chrome.scripting.executeScript({func}) serializes the function, losing its execution context. A module-scope helper is therefore not defined in the page.

#7 extracted readInputSchema out of probeWebMcp. Injected, that threw ReferenceError — and probeWebMcp's own try/catch swallowed it into {supported: false, tools: []}. Discovery would have silently reported no WebMCP support on every page, with a fully green test suite, because importing a function in a test keeps module scope alive.

Reproduced before fixing:

$ node -e 'const injected = new Function("return (" + probeWebMcp.toString() + ")")(); ...'
result: {"supported":false,"tools":[]}

All three injected functions are now self-contained. The duplication between probeWebMcp and invokeWebMcp is forced by that boundary and is documented as such.

The two normalizations disagreed

invokeWebMcp compares its catalog against expectedCatalog, which the service worker builds as normalizeTools(probeWebMcp output). normalizeTools sorts by name, bounds names to 128 characters, and truncates descriptions to 1000. invokeWebMcp did none of those.

Any page whose tools were not already in alphabetical order would have failed every invocation with stale_catalog. This predates the merge — it was in the original invocation path in #6.

A test that can actually see both

test/injection.test.js rebuilds each function from source the way Chrome does, then asserts the two normalization paths agree across unsorted, over-long-name, over-long-description, over-cap, object-schema, and snake_case catalogs.

Verified by reintroducing each bug and confirming the suite goes red — removing the sort fails out of order; reintroducing a module-scope helper fails 3 tests.

Smaller fixes

  • Drift reports no longer contain build noise. The scheduled watcher teed the checker's stdout, which Mix shares with dependency-compilation progress — so issue #8, generated by a deliberate test of that path, had ~25 lines of Compiling ... spliced in ahead of the report. The checker now takes --output and writes the report directly.
  • stableStringify moved from service_worker.js to scanning.js, so the comparison basis is importable by tests rather than private to a module that cannot be imported outside a browser.
  • executeTool is documented as unspecified where it is used. It is not in the WebMCP IDL or webmcp-types; the upstream README still reads "TODO: Spec and describe the modelContext.getTools() and modelContext.executeTool() APIs", and webmachinelearning/webmcp#51 has been open since 2025-11-03. getTools() was specced in #223; its sibling was not. The existing feature-detect-and-report-unavailable behavior is unchanged and is what §21 of the design spec requires.
  • src/globals.d.ts declares Webby's own main-world global, so webmcp-types stays the only externally-owned contract asserted.

Verification

  • Extension: type check clean, 33 tests pass (11 new). Rename detection re-confirmed after inlining — renaming getTools or inputSchema in the definitions still fails the check, now in both functions.
  • Elixir: 88 tests pass, credo --strict clean, format clean.

…lization

Two runtime bugs in the extension's main-world functions, neither of
which the unit tests could see.

chrome.scripting.executeScript({func}) serializes the function and
loses its execution context, so a module-scope helper is simply not
defined in the page. PR #7 extracted readInputSchema out of
probeWebMcp, which made every injection throw ReferenceError -- and
probeWebMcp's own try/catch turned that into {supported: false} on
every page. Silent, total failure of discovery, with a green test
suite, because importing a function in a test keeps module scope alive.
probeWebMcp, invokeWebMcp, and cancelWebMcp are now self-contained; the
duplication between them is forced by that boundary, and is documented
as such rather than looking like an oversight.

invokeWebMcp's normalization also did not match the one that produces
the value it compares against. The service worker builds
expectedCatalog as normalizeTools(probeWebMcp output) -- which sorts by
name, bounds names to 128 characters, and truncates descriptions to
1000 -- while invokeWebMcp did none of those. Any page whose tools were
not already in alphabetical order would have failed every invocation
with stale_catalog. This predates the merge; it was in the original
invocation path.

test/injection.test.js pins both: it rebuilds each function from source
the way Chrome does, and asserts the two normalization paths agree
across unsorted, over-long, over-cap, and snake_case catalogs. Verified
by reintroducing each bug and confirming the suite goes red.

stableStringify moves from service_worker.js to scanning.js so the
comparison basis is importable by tests instead of private to a module
that cannot be imported outside a browser.

Also fixes the drift watcher capturing Mix dependency-compile output
into the issue body: the checker now writes the report with --output
rather than having CI tee it off stdout.
@jmagar
jmagar merged commit 1c18020 into main Aug 14, 2026
2 checks passed
@jmagar
jmagar deleted the claude/followups branch August 14, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant