Skip to content

Introduce the Tool interface - #348

Open
domfarolino wants to merge 11 commits into
mainfrom
tool-interface
Open

domfarolino wants to merge 11 commits into
mainfrom
tool-interface

Conversation

@domfarolino

@domfarolino domfarolino commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

This PR introduces the Tool interface which encapsulates a tool's new UUID. Tools are now keyed and identified by this ID. This replaces the RegisteredTool dictionary with the new Tool interface, fixes a number of possible race conditions (such as https://webmachinelearning.github.io/webmcp/#unregistration-execution-race), and also unblocks us from being able to execute registered by opaque origin documents (see #349).

Note that there are some compatibility changes with this:

  • RegisteredTool#inputSchema and RegisteredTool#annotations could be undefined before this PR, where as Tool#inputSchema and Tool#annotations are not object? which makes more sense for an interface object. This means these two members can be null when their respective values are not provided. They are never purely undefined now.
  • JSON.stringify(tool) returns {}, and the spread operator copies nothing.
  • structuredClone(tool) throws, since this type is not yet [Serializable].

This makes some progress towards the discussion in #236 (/cc @jakearchibald), and helps us out with future worker integration.

/cc @nmccormack-oai from who was interested in this fix from an agent's perspective. Note that the spec still doesn't define a native-agent-initiated tool execution path (equivalent to CDP-initiated tool execution in Chromium, or Gemini-in-Chrome/"actor stack"-initiated tool execution in branded Chrome), but that's coming soon, and the changes here are required to support the correctness / race fixes in those areas too.


Preview | Diff

Comment thread index.bs
@domfarolino
domfarolino marked this pull request as ready for review October 16, 2026 13:35

@markafoltz markafoltz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good!

Comment thread index.bs
Comment thread index.bs
Comment thread index.bs Outdated
Comment thread index.bs
Comment thread index.bs Outdated
Comment thread index.bs
Comment thread index.bs
Comment thread index.bs
Comment thread index.bs
@kenchris

Copy link
Copy Markdown

Binding a Tool to one registration is useful for agents. If an agent approves a tool and calls it later, a Tool for an unregistered tool fails to execute even when the same name is registered again. Before, a page could swap the tool under the same name and the old approval would still apply.

A question from the extension side. We discover tools from a content script and have to pass them on to a service worker and an extension page before the model calls one, so a tool has to cross a few contexts. Since structuredClone(tool) throws and the id isn't exposed, I think an extension can only copy the fields across and then has no way to point back at that specific registration, and would be back to matching on name and origin. I expect the WebExtensions API work that @dotproto proposed in the TPAC breakout (w3c/tpac2026-breakouts#84) will take care of some of this, because an extension API could hand out its own handles. But some of it will probably still be needed here, so is [Serializable] meant to keep the binding when it's cloned (so the clone still refers to the same registration)? If so that would solve it for us. Otherwise some opaque id that can be passed around and given back to executeTool() would work too. I'm only asking whether it's meant to be possible, I don't need a specific shape.

A smaller one: as far as I can tell there is no way to change a registered tool, so a changed description is a new Tool with a new id. That seems fine, but a host that wants to ask the user again when an approved tool changes (#262) would need to match the new one to the old one by name, so it may be worth mentioning how the two relate.

Comment thread index.bs
@domfarolino

Copy link
Copy Markdown
Collaborator Author

Thanks for the reviews! I'll hold off on landing this for a little bit to give @jakearchibald more time to take a look.


A question from the extension side. We discover tools from a content script and have to pass them on to a service worker and an extension page before the model calls one, so a tool has to cross a few contexts. Since structuredClone(tool) throws and the id isn't exposed, I think an extension can only copy the fields across and then has no way to point back at that specific registration, and would be back to matching on name and origin.

This was broken before this PR too, right @kenchris? (Since window is not serializable).

I expect the WebExtensions API work that @dotproto proposed in the TPAC breakout (w3c/tpac2026-breakouts#84) will take care of some of this, because an extension API could hand out its own handles. But some of it will probably still be needed here

Extensions-specific work or something of the sort will take care of this, right. I don't think it'll propagate to any spec changes here, although we may independently make Tool a [Serializable] object anyways (see the discussion in #236).

@kenchris

Copy link
Copy Markdown

Yes, same before, window wasn't cloneable either. It's not a regression, I was only asking whether it's meant to be possible. Good to hear Tool may become [Serializable] with #236. If it does, I'd just want the clone to still point at the same registration, so a host can tell which one it is. Nothing needed in this PR from my side.

This branch has not been deployed

No deployments
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.

3 participants