Skip to content

[feat]: add WebMCP tools added/removed event hooks - #2916

Open
seanmcguire12 wants to merge 10 commits into
mainfrom
add-ontoolsadded-hook
Open

[feat]: add WebMCP tools added/removed event hooks#2916
seanmcguire12 wants to merge 10 commits into
mainfrom
add-ontoolsadded-hook

Conversation

@seanmcguire12

@seanmcguire12 seanmcguire12 commented Sep 9, 2026

Copy link
Copy Markdown
Member

why

users need a way to subscribe to events that fire when webmcp tools were added or removed from a page

note:

this PR ended up a lot larger than just adding two hooks because WebMCP.enable causes Chrome to emit toolsAdded events for tools that already exist (docs here).

ie, the WebMCP.toolsAdded event would fire every time someone called page.tools(). therefore, we needed to manage some state, and essentially keep a registry of tools that have been added/removed.

with the changes from this PR, page.tools() reads that registry instead of enabling WebMCP again. a lot of the code here is related to state synchronization, because the registry now needs to be kept in sync across navigation and iframe detachment etc..

what changed

  • added onToolsAdded() / onToolsRemoved() in TypeScript, on_tools_added() / on_tools_removed() in Python, and OnToolsAdded() / OnToolsRemoved() in Go
  • reused the existing subscription handles for unsubscribe() / Close(ctx)
  • added typed page.event notifications alongside the existing raw page.cdp_event path, with shared protocol schemas and generated SDK models
  • made subscription setup asynchronous, with cleanup when registration fails or the subscriber disconnects during setup
  • moved WebMCP discovery onto shared page-owned tool state, so page.tools() and the hooks use the same state without repeatedly enabling WebMCP or replaying discovery results to listeners
  • tracked tools across the main frame and child frames, including out-of-process iframes, and emitted removals when their document navigates or their frame detaches
  • kept tool tracking alive independently of individual subscriptions, while cleaning up listeners when the page or client is disposed
  • also updated the WebMCP examples in all three SDKs

test plan

  • Protocol tests cover typed notification payloads and the unchanged console event contract.
  • Extension tests cover shared discovery state, future-only delivery, frame navigation and detachment, independent subscribers, registration races, & cleanup.
  • TypeScript, Python, and Go SDK tests cover typed callback values, subscription routing, callable added tools, & unsubscribe behavior.
  • Real-browser WebMCP tests cover discovery and invocation, callback invocation without deadlocking, same-origin and cross-origin iframe changes, no replay, & independent unsubscribe.
  • Cross-SDK parity checks and example typechecks/compilation pass.

Summary by cubic

Adds WebMCP tool add/remove event hooks so users can subscribe when tools appear or disappear on a page. Previously only console events were available; the new onToolsAdded() / onToolsRemoved() (TypeScript), on_tools_added() / on_tools_removed() (Python), and OnToolsAdded() / OnToolsRemoved() (Go) hooks deliver typed payloads over a new page.event notification alongside the existing raw page.cdp_event path.

New Features

  • Hooks report future changes only, not tools already present on the page.
  • Added hooks deliver callable WebMCPTool objects; removed hooks deliver tool identities.
  • WebMCP discovery moved onto shared page-owned tool state so page.tools() and the hooks stay consistent without repeatedly enabling WebMCP or replaying results.
  • Tools are tracked across the main frame, child frames, and out-of-process iframes, with removals on document navigation or frame detach.
  • Tool tracking persists independently of subscriptions and is cleaned up on page or client disposal; subscription setup is now asynchronous and cleans up on registration failure or mid-setup disconnect.

Written for commit ed74093. Summary will update on new commits.

Review in cubic

@mintlify

mintlify Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
stagehand 🟢 Ready View Preview Sep 9, 2026, 2:58 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@changeset-bot

changeset-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ed74093

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@browserbasehq/stagehand-python Minor
@browserbasehq/stagehand-extension Minor
@browserbasehq/stagehand-protocol Minor
@browserbasehq/stagehand-go Minor
@browserbasehq/stagehand Minor
@browserbasehq/stagehand-integrations Patch
@browserbasehq/stagehand-integrations-example-eve-facade Patch
@browserbasehq/stagehand-integrations-example-pi-facade Patch
@browserbasehq/stagehand-integrations-claude-agent-sdk Patch
@browserbasehq/stagehand-integrations-example-claude-code-facade Patch
@browserbasehq/stagehand-integrations-codex-sdk Patch
@browserbasehq/stagehand-integrations-example-codex-facade Patch
@browserbasehq/stagehand-integrations-cursor-sdk Patch
@browserbasehq/stagehand-integrations-deepagents-sdk Patch
@browserbasehq/stagehand-integrations-eve-sdk Patch
@browserbasehq/stagehand-integrations-fx-sdk Patch
@browserbasehq/stagehand-integrations-mastra-sdk Patch
@browserbasehq/stagehand-integrations-example-mastra-facade Patch
@browserbasehq/stagehand-integrations-pi-sdk Patch
@browserbasehq/stagehand-integrations-example-vercel-ai-facade Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@seanmcguire12
seanmcguire12 marked this pull request as ready for review September 9, 2026 15:23
@seanmcguire12
seanmcguire12 requested a review from a team as a code owner September 9, 2026 15:23
@seanmcguire12
seanmcguire12 removed the request for review from a team September 9, 2026 15:24

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 42 files

Architecture diagram
sequenceDiagram
    participant App as TypeScript / Python / Go Client
    participant SDK as Page SDK Wrapper
    participant RPC as JSON-RPC Client
    participant SW as Extension Service Worker
    participant Runtime as Stagehand Runtime
    participant Page as Page State Manager
    participant Main as Main CDP Session
    participant OOPIF as Child / OOPIF CDP Sessions
    participant Browser as Chrome WebMCP Domain

    Note over App,Browser: WebMCP tool discovery, shared page state, and typed page event delivery

    App->>SDK: page.onToolsAdded or page.onToolsRemoved
    SDK->>RPC: Register page.on with event toolsadded or toolsremoved
    RPC->>SW: JSON-RPC request page.on
    SW->>Runtime: Forward page.on with pageId, subscriptionId, and event

    Runtime->>Page: Changed, asynchronously subscribe to shared WebMCP tool state
    Page->>Page: Ensure tracking is initialized for all owned sessions
    Page->>Main: Enable WebMCP once and attach toolsAdded and toolsRemoved listeners
    Page->>OOPIF: Enable WebMCP for current and future child sessions
    Main-->>Page: Initial toolsAdded discovery events
    OOPIF-->>Page: Initial child-frame discovery events
    Page->>Page: Store tools by frame and session in page-owned registry
    Page-->>Runtime: Registration ready, future-only listener active
    Runtime-->>SW: page.on registration result
    SW-->>RPC: JSON-RPC response
    RPC-->>SDK: Subscription handle
    SDK-->>App: Subscription for unsubscribe or Close

    alt Tool is registered in the page or a frame
        Browser-->>Main: WebMCP toolsAdded
        Main-->>Page: Tool descriptor with frame identity
        Browser-->>OOPIF: WebMCP toolsAdded
        OOPIF-->>Page: Child-frame tool descriptor
        Page->>Page: Update registry and notify matching subscribers
        Page->>Runtime: Typed toolsadded payload with sessionId and targetId
        Runtime->>SW: Emit page.event notification
        SW->>RPC: JSON-RPC page.event
        RPC->>SDK: Decode typed PageToolsAddedNotification
        SDK->>App: Callback with callable WebMCPTool wrappers
        App->>RPC: Optional page.webmcp_invoke_tool
        RPC->>SW: Forward tool invocation
        SW->>Runtime: Invoke against owning frame and session
        Runtime->>Main: WebMCP.invokeTool or route to OOPIF
    else Tool is removed, document navigates, or frame detaches
        Browser-->>Main: WebMCP toolsRemoved
        Main-->>Page: Tool identity
        Page->>Page: Remove tool from registry
        Page->>Page: Invalidate tools for navigated or detached frame
        Page->>Runtime: Typed toolsremoved payload with tool identities
        Runtime->>SW: Emit page.event notification
        SW->>RPC: JSON-RPC page.event
        RPC->>SDK: Decode typed PageToolsRemovedNotification
        SDK->>App: Callback with name and frame identity values
    end

    App->>SDK: page.tools
    SDK->>RPC: Request current WebMCP tools
    RPC->>SW: JSON-RPC page.tools
    SW->>Runtime: Query page
    Runtime->>Page: Read shared registry
    Page-->>Runtime: Current tools without re-enabling WebMCP
    Runtime-->>SW: Tool snapshot
    SW-->>RPC: JSON-RPC response
    RPC-->>SDK: Tool descriptors
    SDK-->>App: Callable tool objects

    opt Unsubscribe or disposal
        App->>SDK: unsubscribe or Close
        SDK->>RPC: page.off with subscriptionId
        RPC->>SW: JSON-RPC request page.off
        SW->>Runtime: Abort pending setup and detach subscriber
        Runtime->>Page: Remove subscriber only
        Page->>Page: Keep registry tracking alive
        Page->>Main: Cleanup listeners when page is disposed
        Page->>OOPIF: Cleanup listeners on frame detach or page disposal
    end

    alt Subscription setup fails or disconnects during setup
        Page-->>Runtime: Registration error or cancellation
        Runtime->>Runtime: Abort controller and release subscription reservation
        Runtime->>Page: Dispose partially created listener
        Runtime-->>SW: JSON-RPC error
        SW-->>RPC: Registration failure
        RPC-->>SDK: Reject subscription
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/sdk-python/src/stagehand/_generated/models.py
Comment thread packages/sdk-go/page_event_notification.go
Comment thread packages/sdk-ts/tests/integration/webmcpDiscovery.test.ts
Comment thread packages/sdk-python/src/stagehand/page.py
Comment thread packages/sdk-go/examples/webmcp.go Outdated
@antonvishal

Copy link
Copy Markdown
Contributor

#2917

Hey @seanmcguire12, could you take a look at this PR when you have a chance?

I have a follow up change that adds support for WebMCP's consequentialHint annotation across the protocol, extension, SDKs, and docs. Since this PR is adding hooks for WebMCP, it may be cleaner to bring those changes into this PR once that is approved.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 8 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/sdk-go/examples/webmcp.go
@seanmcguire12

Copy link
Copy Markdown
Member Author

@antonvishal if you don't mind rebasing #2917 after this one lands that would be ideal! cheers

@antonvishal

Copy link
Copy Markdown
Contributor

@antonvishal if you don't mind rebasing #2917 after this one lands that would be ideal! cheers

Sounds good! I’ll rebase #2917 once this one lands. Cheers!

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.

2 participants