Skip to content

feat: carry tool provenance, watch for capabilities, adopt today's spec changes - #12

Merged
jmagar merged 1 commit into
mainfrom
claude/tool-provenance
Aug 14, 2026
Merged

feat: carry tool provenance, watch for capabilities, adopt today's spec changes#12
jmagar merged 1 commit into
mainfrom
claude/tool-provenance

Conversation

@jmagar

@jmagar jmagar commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Closes the three gaps listed at the end of #11. One of them resolved itself upstream while this branch was being written, and the tracking is what caught it.

executeTool() was specified today

The watch added in this PR fired on its first real run. Upstream landed three commits on 2026-08-14:

  • #226Spec the executeTool() API
  • #241RegisteredTool.inputSchema: DOMStringobject
  • #240 — validate exposedTo before registering the AbortSignal algorithm

The specified signature is:

Promise<DOMString> executeTool(RegisteredTool tool, DOMString inputArguments,
                               optional ModelContextExecuteToolOptions options = {});

That is exactly the call invokeWebMcp already made, {signal} included. And #241 is a breaking type change that Webby absorbed for free, because the probe already accepted both a string and an object schema.

Both are reviewed and adopted here. The comments asserting executeTool was unspecified are corrected — left alone they would now actively mislead.

Two things still gate the feature: no browser implements it, and webmcp-types (0.1.3) predates the change, so probe.js still reaches it through a narrow cast until the definitions catch up.

I was wrong about origin

I previously wrote that title/origin carry no safety signal. The spec disagrees:

"This member is only meaningful when the tool is cross-origin, and the consumer of a tool cannot otherwise get the tool's origin from its window."

A frame can expose tools into a page via exposedTo. Dropping origin meant a third party's tool reaching an MCP client attributed to the page that merely embedded it. It is now carried, sanitized (only a well-formed http/https origin with no path, query, or fragment is stored), and the dashboard badges any tool whose origin differs from its page. title is carried alongside.

getTools() is still called without fromOrigins, deliberately — restricting to same-origin would silently drop tools a page intended to expose. Carrying the origin makes the situation visible rather than hidden or silently trusted.

Capability watches

github_file_symbol inverts the usual question: has this arrived rather than has this changed. chrome_status tracks Chrome's shipping status.

On the origin-trial gap specifically — Chrome Status cannot answer it. Its WebMCP entry reports status: Proposed, origintrial: false, and no milestones, contradicting the spec repo's implementation-status.md, which cites a live trial in Chrome 149. So the contract tracks the shipping-status transition, and the docs say plainly that expiry dates aren't published there rather than implying coverage.

Two bugs found building this

  • Req decodes on content-type, so chromestatus's )]}' anti-hijacking prefix failed before my handler ran. The body is now fetched raw.
  • raw_headers rebuilt the header list positionally, dropping the authorization header along with the accept it meant to replace. Invisible without a token — and therefore broken on every CI run, where GITHUB_TOKEN is always set. Caught by testing with a real token rather than assuming.

Verification

  • Extension: type check clean, 41 tests pass, including cross-origin and titled catalogs in the injection equivalence suite.
  • Elixir: 104 tests pass (13 new), credo --strict clean.
  • mix webby.contracts.check clean against live upstreams with auth.

test/support.js centralizes the normalized tool shape — two field additions in a row have meant hand-rewriting every assertion, which is how a suite ends up pinning a shape nobody deliberately chose.

…ec changes

Three gaps, and an upstream change the tracking caught while closing them.

Tool provenance. RegisteredTool.origin is not cosmetic, and an earlier
note here calling it a non-signal was wrong: the specification says it
"is only meaningful when the tool is cross-origin", which is exactly the
case Webby cannot otherwise see. A frame can expose tools into a page
via exposedTo, so dropping it meant a third party's tool reaching an MCP
client attributed to the page that merely embedded it. It is now carried
and sanitized -- only a well-formed http/https origin with no path,
query, or fragment is stored -- and the dashboard badges any tool whose
origin differs from its page. title is carried alongside it.

getTools() is still called without fromOrigins, deliberately: restricting
to same-origin would silently drop tools a page meant to expose. Carrying
the origin makes that visible instead of hidden.

Capability watches. github_file_symbol inverts the usual question, asking
whether a symbol has arrived rather than whether a file changed, so an
API Webby depends on but upstream had not defined is watched directly.
chrome_status tracks Chrome's shipping status.

The executeTool watch fired on its first run. Upstream specified it the
same day (webmachinelearning/webmcp#226), with the signature Webby
already called, {signal} included. #241 also changed
RegisteredTool.inputSchema from DOMString to object; Webby accepted both
already. Both are reviewed and adopted here, and the comments claiming
executeTool was unspecified are corrected -- they would now mislead.

Chrome Status cannot answer the origin-trial question that was asked of
it: its WebMCP entry reports status Proposed with origintrial false and
no milestones, contradicting the spec repo's implementation-status.md,
which cites a live trial in Chrome 149. It is tracked for the transition
to a shipped status, and the docs say plainly that expiry is not
available there.

Two bugs found while building this. Req decodes on content-type, so
chromestatus's )]}' guard prefix failed before the handler ran; the body
is now fetched raw. And raw_headers rebuilt the header list
positionally, dropping the authorization header along with the accept it
meant to replace -- invisible until a token was present, which is every
CI run.

test/support.js centralizes the normalized tool shape. Two field
additions in a row have meant rewriting every assertion by hand, which
is how a suite ends up pinning a shape nobody chose.
@jmagar
jmagar merged commit 6abd13d into main Aug 14, 2026
2 checks passed
@jmagar
jmagar deleted the claude/tool-provenance branch August 14, 2026 20:30
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