From cf0efd7b3eaa422496a890a5f194e4f25c0b767a Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:00:30 +0000 Subject: [PATCH 1/5] Surface tools registered through a navigator.modelContext polyfill Chromium moved the WebMCP registry from navigator.modelContext to document.modelContext and removed the old alias, so a page that installs its own polyfill on navigator.modelContext never reaches the CDP WebMCP domain and its tools are missing from GET /webmcp/tools. Read such polyfills from page context during discovery and merge their tools into the same list and invoke paths. Polyfill tools get stable refs, follow document and frame lifecycle like native tools, yield to a native registration of the same name, and are marked with source.polyfill in the API. Invocation runs the polyfill's own execute path and reports completed, error, or outcome_unknown with the existing shapes; a tool that navigates its document completes with empty output like the native domain. --- server/cmd/api/api/webmcp.go | 37 + server/cmd/api/api/webmcp_test.go | 39 + server/e2e/e2e_playwright_test.go | 3 + server/e2e/e2e_webmcp_polyfill_test.go | 137 ++ server/e2e/testdata/webmcp/README.md | 12 + server/e2e/testdata/webmcp/polyfill.html | 120 ++ server/lib/browsersurface/frames.go | 15 +- .../iframe_initialization_test.go | 2 +- server/lib/browsersurface/tracker.go | 32 + server/lib/browsersurface/tracker_test.go | 25 + server/lib/browsersurface/types.go | 22 +- server/lib/oapi/oapi.go | 1348 +++++++++-------- server/lib/webmcpclient/client.go | 51 +- server/lib/webmcpclient/client_test.go | 23 +- server/lib/webmcpclient/manager.go | 1 + server/lib/webmcpclient/polyfill.go | 412 +++++ server/lib/webmcpclient/polyfill_page.js | 240 +++ server/lib/webmcpclient/polyfill_test.go | 311 ++++ server/lib/webmcpclient/types.go | 21 +- server/openapi.yaml | 10 +- server/runtime/webmcp-polyfill.test.ts | 213 +++ server/runtime/webmcp.ts | 1 + 22 files changed, 2375 insertions(+), 700 deletions(-) create mode 100644 server/e2e/e2e_webmcp_polyfill_test.go create mode 100644 server/e2e/testdata/webmcp/polyfill.html create mode 100644 server/lib/webmcpclient/polyfill.go create mode 100644 server/lib/webmcpclient/polyfill_page.js create mode 100644 server/lib/webmcpclient/polyfill_test.go create mode 100644 server/runtime/webmcp-polyfill.test.ts diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index bed24802..ada9a7aa 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -62,6 +62,14 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Autosubmit: boolPointer(tool.Annotations.Autosubmit), } } + if tool.Polyfill { + metadata.Title = nonEmptyString(tool.Title) + if tool.OutputSchema != nil { + outputSchema := tool.OutputSchema + metadata.OutputSchema = &outputSchema + } + metadata.Annotations = polyfillAnnotations(tool.Hints) + } responseTool := oapi.WebMCPTool{ ToolRef: tool.Ref, Tool: metadata, @@ -78,6 +86,9 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Url: tool.Source.Frame.URL, } } + if tool.Polyfill { + responseTool.Source.Polyfill = boolPointer(true) + } if tool.CustomID != "" { definition, ok := customDefinitions[tool.CustomID] if !ok { @@ -104,6 +115,32 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT func boolPointer(value bool) *bool { return &value } +func polyfillAnnotations(hints map[string]bool) *oapi.WebMCPToolAnnotations { + if len(hints) == 0 { + return nil + } + annotations := &oapi.WebMCPToolAnnotations{} + for hint, value := range hints { + switch hint { + case "readOnlyHint": + annotations.ReadOnlyHint = boolPointer(value) + case "destructiveHint": + annotations.DestructiveHint = boolPointer(value) + case "idempotentHint": + annotations.IdempotentHint = boolPointer(value) + case "openWorldHint": + annotations.OpenWorldHint = boolPointer(value) + case "consequentialHint": + annotations.ConsequentialHint = boolPointer(value) + case "untrustedContentHint": + annotations.UntrustedContentHint = boolPointer(value) + case "autosubmit": + annotations.Autosubmit = boolPointer(value) + } + } + return annotations +} + func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWebMCPToolRequestObject) (oapi.InvokeWebMCPToolResponseObject, error) { if request.Body == nil { return oapi.InvokeWebMCPTool400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 22b062a4..240f64ee 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -85,6 +85,45 @@ func writeCustomToolsSnapshot(t *testing.T, manager *browserReplManager, tools [ require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), data, 0o600)) } +func TestGetWebMCPToolsMarksPolyfillTools(t *testing.T) { + client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ + Ref: "wmcp_poly", + Name: "search_items", + Title: "Search", + Description: "Search the catalog.", + InputSchema: map[string]any{"type": "object"}, + OutputSchema: map[string]any{"type": "object"}, + Hints: map[string]bool{"readOnlyHint": true, "destructiveHint": false}, + Polyfill: true, + Source: webmcpclient.ToolSource{ + WindowID: 1, + TabID: 1, + PageTitle: "Catalog", + PageURL: "https://shop.example/", + }, + }}} + service := &ApiService{webmcp: client} + + response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) + require.NoError(t, err) + body := response.(oapi.GetWebMCPTools200JSONResponse) + require.Len(t, body.Tools, 1) + tool := body.Tools[0] + require.NotNil(t, tool.Source.Polyfill) + require.True(t, *tool.Source.Polyfill) + require.Nil(t, tool.Source.Custom) + require.Nil(t, tool.Source.TargetId) + require.Equal(t, "Search", *tool.Tool.Title) + require.Equal(t, map[string]any{"type": "object"}, *tool.Tool.OutputSchema) + require.True(t, *tool.Tool.Annotations.ReadOnlyHint) + require.False(t, *tool.Tool.Annotations.DestructiveHint) + require.Nil(t, tool.Tool.Annotations.ConsequentialHint) + + encoded, err := json.Marshal(tool) + require.NoError(t, err) + require.Contains(t, string(encoded), `"polyfill":true`) +} + func TestGetWebMCPToolsRejectsUnreadableSnapshot(t *testing.T) { manager := newBrowserReplManager() writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{Id: "ct_abcdefghijklmnopqrstuvwx"}}) diff --git a/server/e2e/e2e_playwright_test.go b/server/e2e/e2e_playwright_test.go index a1fb61bd..cbe1f7f9 100644 --- a/server/e2e/e2e_playwright_test.go +++ b/server/e2e/e2e_playwright_test.go @@ -265,6 +265,9 @@ func TestPlaywrightExecuteAPI(t *testing.T) { t.Run("WebMCPCustomNavigation", func(t *testing.T) { testCustomWebMCPInvokesAcrossNavigation(t, ctx, client) }) + t.Run("WebMCPPolyfill", func(t *testing.T) { + testWebMCPPolyfill(t, ctx, client) + }) } func TestPlaywrightExecuteTimeoutReturnsPromptlyAndRecovers(t *testing.T) { diff --git a/server/e2e/e2e_webmcp_polyfill_test.go b/server/e2e/e2e_webmcp_polyfill_test.go new file mode 100644 index 00000000..543aba26 --- /dev/null +++ b/server/e2e/e2e_webmcp_polyfill_test.go @@ -0,0 +1,137 @@ +package e2e + +import ( + "bytes" + "context" + "net/http" + "os" + "testing" + "time" + + instanceoapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// testWebMCPPolyfill covers tools that a page registers through its own +// navigator.modelContext polyfill instead of the native document.modelContext +// registry: late installation, same-origin embedded frames, native precedence +// for a shared name, invocation results, unregistration, and navigation. +func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses) { + t.Helper() + + fixture, err := os.ReadFile("testdata/webmcp/polyfill.html") + require.NoError(t, err) + written, err := client.WriteFileWithBodyWithResponse(ctx, + &instanceoapi.WriteFileParams{Path: "/tmp/polyfill.html"}, "text/html", bytes.NewReader(fixture)) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, written.StatusCode(), "%s", written.Body) + + const pageURL = "file:///tmp/polyfill.html" + var readyState string + executeWebMCPPlaywright(t, ctx, client, ` + await page.goto('file:///tmp/polyfill.html', { waitUntil: 'load' }); + return page.evaluate(() => document.readyState); + `, &readyState) + require.Equal(t, "complete", readyState) + + toolsByName := func(collect *assert.CollectT) map[string]instanceoapi.WebMCPTool { + rsp, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, rsp.StatusCode(), "%s", rsp.Body) || rsp.JSON200 == nil { + return nil + } + tools := make(map[string]instanceoapi.WebMCPTool) + for _, tool := range rsp.JSON200.Tools { + if tool.Source.PageUrl == pageURL { + tools[tool.Tool.Name] = tool + } + } + return tools + } + + // The polyfill installs 1.5s after load; discovery picks it up on a later listing. + var tools map[string]instanceoapi.WebMCPTool + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools = toolsByName(collect) + for _, name := range []string{"search_items", "shared_name", "failing_tool", "navigate_away", "frame_tool"} { + assert.Contains(collect, tools, name) + } + }, 15*time.Second, 250*time.Millisecond) + t.Logf("GET /webmcp/tools: %d tools on %s", len(tools), pageURL) + + search := tools["search_items"] + require.NotNil(t, search.Source.Polyfill) + require.True(t, *search.Source.Polyfill) + require.Nil(t, search.Source.Frame) + require.Nil(t, search.Source.Custom) + require.Equal(t, "Search the catalog.", search.Tool.Description) + require.Equal(t, []any{"query"}, search.Tool.InputSchema["required"]) + + // The same name registered natively lists once, as the native tool. + shared := tools["shared_name"] + require.Nil(t, shared.Source.Polyfill) + require.Equal(t, "Native copy.", shared.Tool.Description) + + frameTool := tools["frame_tool"] + require.NotNil(t, frameTool.Source.Polyfill) + require.NotNil(t, frameTool.Source.Frame) + require.Equal(t, "about:srcdoc", frameTool.Source.Frame.Url) + + timeout := 10 + invoke := func(ref string, input map[string]any) *instanceoapi.InvokeWebMCPToolResponse { + rsp, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: ref, Input: input, TimeoutSec: &timeout, + }) + require.NoError(t, err) + t.Logf("POST /webmcp/invoke %s: %s", ref, rsp.Body) + return rsp + } + + searched := invoke(search.ToolRef, map[string]any{"query": "lamp"}) + require.Equal(t, http.StatusOK, searched.StatusCode(), "%s", searched.Body) + require.NotNil(t, searched.JSON200) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusCompleted, searched.JSON200.Status) + require.NotEmpty(t, searched.JSON200.InvocationId) + require.Equal(t, map[string]any{"results": []any{"match for lamp"}, "page": "/tmp/polyfill.html"}, searched.JSON200.Output) + + var logText string + executeWebMCPPlaywright(t, ctx, client, `return page.evaluate(() => document.querySelector('#log').textContent);`, &logText) + require.Equal(t, "searched lamp", logText) + + framed := invoke(frameTool.ToolRef, map[string]any{"item": 1}) + require.Equal(t, http.StatusOK, framed.StatusCode(), "%s", framed.Body) + require.Equal(t, map[string]any{"frame": "Embedded polyfill catalog", "input": map[string]any{"item": float64(1)}}, framed.JSON200.Output) + + native := invoke(shared.ToolRef, map[string]any{}) + require.Equal(t, http.StatusOK, native.StatusCode(), "%s", native.Body) + require.Equal(t, map[string]any{"source": "native"}, native.JSON200.Output) + + failed := invoke(tools["failing_tool"].ToolRef, map[string]any{}) + require.Equal(t, http.StatusOK, failed.StatusCode(), "%s", failed.Body) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, failed.JSON200.Status) + require.NotNil(t, failed.JSON200.ErrorText) + require.Equal(t, "nothing to do", *failed.JSON200.ErrorText) + + // Unregistration through the polyfill drops the tool on the next listing, + // and the surviving tool keeps its reference. + var clicked bool + executeWebMCPPlaywright(t, ctx, client, `await page.click('#unregister'); return true;`, &clicked) + require.True(t, clicked) + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools = toolsByName(collect) + assert.NotContains(collect, tools, "failing_tool") + assert.Equal(collect, search.ToolRef, tools["search_items"].ToolRef) + }, 10*time.Second, 250*time.Millisecond) + + // A tool that navigates its document completes like a native one and the + // old registrations disappear with the document. + navigated := invoke(tools["navigate_away"].ToolRef, map[string]any{}) + require.Equal(t, http.StatusOK, navigated.StatusCode(), "%s", navigated.Body) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusCompleted, navigated.JSON200.Status) + require.Equal(t, []any{}, navigated.JSON200.Output) + require.EventuallyWithT(t, func(collect *assert.CollectT) { + assert.Empty(collect, toolsByName(collect)) + }, 10*time.Second, 250*time.Millisecond) + stale := invoke(search.ToolRef, map[string]any{"query": "x"}) + require.Equal(t, http.StatusNotFound, stale.StatusCode(), "%s", stale.Body) +} diff --git a/server/e2e/testdata/webmcp/README.md b/server/e2e/testdata/webmcp/README.md index 1350b240..abf75398 100644 --- a/server/e2e/testdata/webmcp/README.md +++ b/server/e2e/testdata/webmcp/README.md @@ -14,3 +14,15 @@ E2E_CHROMIUM_HEADLESS_IMAGE=kernel-headless-test \ ``` Chromium 152.0.7977.42 with the image's default WebMCP flags exposes `reserve_table` with string name/date fields, a numeric party size with bounds, and a seating enum. It adds a date format hint to the field description. Both top-level and embedded invocations return `completed` and the submitted values; the tests also read the DOM through `/playwright/execute` to verify native agent submission occurred exactly once. Discovery and invocation responses are logged by the test. Missing declarative tools fail the test rather than silently skipping supported behavior. + +# Polyfill WebMCP fixture + +`polyfill.html` mirrors sites that ship their own tools through a `navigator.modelContext` +polyfill (a registry object plus `registerTool`, `unregisterTool`, `listTools`, and +`callTool`). The polyfill installs 1.5s after load, the page also registers `shared_name` +natively so native precedence is covered, and a `srcdoc` child frame carries a Map-based +registry of its own. Run with: + +```bash +GOFLAGS='-run=TestPlaywrightExecuteAPI/WebMCPPolyfill -count=1' make test-e2e +``` diff --git a/server/e2e/testdata/webmcp/polyfill.html b/server/e2e/testdata/webmcp/polyfill.html new file mode 100644 index 00000000..b300b5e8 --- /dev/null +++ b/server/e2e/testdata/webmcp/polyfill.html @@ -0,0 +1,120 @@ + + + + + Polyfill catalog + + +

Catalog

+

+ + + + + + + diff --git a/server/lib/browsersurface/frames.go b/server/lib/browsersurface/frames.go index e8b4a770..279260a5 100644 --- a/server/lib/browsersurface/frames.go +++ b/server/lib/browsersurface/frames.go @@ -165,7 +165,7 @@ func (t *Tracker) initializeSession(sessionID string) { result.FrameTree.Frame.ParentID = existing.parentID } } - t.addFrameTreeLocked(sess.tabID, result.FrameTree) + t.addFrameTreeLocked(sess.tabID, sessionID, result.FrameTree) if sess.target.Type == "page" { if trackedTab := t.tabs[sess.tabID]; trackedTab != nil { trackedTab.rootFrameID = result.FrameTree.Frame.ID @@ -189,14 +189,14 @@ func (t *Tracker) failSessionInitialization(sessionID string, err error) { } } -func (t *Tracker) addFrameTreeLocked(tabID int, tree frameTree) { - t.upsertFrameLocked(tabID, tree.Frame) +func (t *Tracker) addFrameTreeLocked(tabID int, sessionID string, tree frameTree) { + t.upsertFrameLocked(tabID, sessionID, tree.Frame) for _, child := range tree.ChildFrames { - t.addFrameTreeLocked(tabID, child) + t.addFrameTreeLocked(tabID, sessionID, child) } } -func (t *Tracker) upsertFrameLocked(tabID int, info frameInfo) { +func (t *Tracker) upsertFrameLocked(tabID int, sessionID string, info frameInfo) { tracked := t.frames[info.ID] if tracked == nil { publicID := 0 @@ -212,6 +212,7 @@ func (t *Tracker) upsertFrameLocked(tabID int, info frameInfo) { } tracked.parentID = info.ParentID tracked.tabID = tabID + tracked.sessionID = sessionID tracked.url = info.URL } @@ -255,7 +256,7 @@ func (t *Tracker) bindSessionsLocked() { func (t *Tracker) attachFrame(sessionID, frameID, parentFrameID string) { t.stateMu.Lock() if sess := t.sessions[sessionID]; sess != nil && sess.tabID != 0 { - t.upsertFrameLocked(sess.tabID, frameInfo{ID: frameID, ParentID: parentFrameID}) + t.upsertFrameLocked(sess.tabID, sessionID, frameInfo{ID: frameID, ParentID: parentFrameID}) t.bindSessionsLocked() } t.stateMu.Unlock() @@ -272,7 +273,7 @@ func (t *Tracker) navigateFrame(sessionID string, info frameInfo) { info.ParentID = existing.parentID } } - t.upsertFrameLocked(sess.tabID, info) + t.upsertFrameLocked(sess.tabID, sessionID, info) if trackedTab := t.tabs[sess.tabID]; trackedTab != nil && trackedTab.rootFrameID == info.ID { trackedTab.url = info.URL } diff --git a/server/lib/browsersurface/iframe_initialization_test.go b/server/lib/browsersurface/iframe_initialization_test.go index 6f2c2e90..bf2717cd 100644 --- a/server/lib/browsersurface/iframe_initialization_test.go +++ b/server/lib/browsersurface/iframe_initialization_test.go @@ -15,7 +15,7 @@ func TestExplicitIframeInitializationDoesNotWaitForParent(t *testing.T) { tracker.stateMu.Lock() // The page's frame is known, but its session initialization is still pending. tracker.sessions["session-a"] = &session{id: "session-a", target: page, tabID: tabID, initializing: true} - tracker.upsertFrameLocked(tabID, frameInfo{ID: "page-a"}) + tracker.upsertFrameLocked(tabID, "session-a", frameInfo{ID: "page-a"}) tracker.stateMu.Unlock() tracker.addSession("oopif-session", "", targetInfo{ TargetID: "oopif", Type: "iframe", ParentFrameID: "page-a", diff --git a/server/lib/browsersurface/tracker.go b/server/lib/browsersurface/tracker.go index 2e7e6502..7d29bdcb 100644 --- a/server/lib/browsersurface/tracker.go +++ b/server/lib/browsersurface/tracker.go @@ -296,6 +296,38 @@ func (t *Tracker) Snapshot() Snapshot { return snapshot } +// SessionFrames lists every frame of an initialized page or iframe session, +// roots first, so callers can evaluate in each document through the session +// that owns it. +func (t *Tracker) SessionFrames() []SessionFrame { + t.stateMu.RLock() + defer t.stateMu.RUnlock() + frames := make([]SessionFrame, 0, len(t.frames)) + for _, tracked := range t.frames { + sess := t.sessions[tracked.sessionID] + if sess == nil || !sess.initialized || sess.tabID == 0 || sess.tabID != tracked.tabID { + continue + } + parent := t.frames[tracked.parentID] + frames = append(frames, SessionFrame{ + SessionID: sess.id, + FrameID: tracked.rawID, + Root: parent == nil || parent.sessionID != tracked.sessionID, + URL: tracked.url, + }) + } + sort.Slice(frames, func(i, j int) bool { + if frames[i].SessionID != frames[j].SessionID { + return frames[i].SessionID < frames[j].SessionID + } + if frames[i].Root != frames[j].Root { + return frames[i].Root + } + return frames[i].FrameID < frames[j].FrameID + }) + return frames +} + func (t *Tracker) SessionExists(sessionID string) bool { t.stateMu.RLock() defer t.stateMu.RUnlock() diff --git a/server/lib/browsersurface/tracker_test.go b/server/lib/browsersurface/tracker_test.go index a1af6806..ca2cf582 100644 --- a/server/lib/browsersurface/tracker_test.go +++ b/server/lib/browsersurface/tracker_test.go @@ -341,6 +341,31 @@ func TestTrackerMapsBrowserSurfaceAndPublishesLifecycleEvents(t *testing.T) { require.Equal(t, 2, moved.WindowID) } +func TestSessionFramesReportOwnersAndRoots(t *testing.T) { + protocol := newFakeProtocol() + tracker := New(protocol) + require.NoError(t, tracker.Start(context.Background())) + protocol.emitTarget("Target.targetCreated", map[string]any{ + "targetInfo": map[string]any{ + "targetId": "oopif", "type": "iframe", "url": "https://cross-origin.example/", + "parentFrameId": "root-a", + }, + }) + + var frames []SessionFrame + require.Eventually(t, func() bool { + frames = tracker.SessionFrames() + return len(frames) == 5 + }, time.Second, 10*time.Millisecond) + require.Equal(t, []SessionFrame{ + {SessionID: "oopif-session", FrameID: "oopif", Root: true, URL: "https://cross-origin.example/"}, + {SessionID: "session-a", FrameID: "root-a", Root: true, URL: "https://store.example/"}, + {SessionID: "session-a", FrameID: "inner", URL: "https://bank.example/"}, + {SessionID: "session-a", FrameID: "outer", URL: "https://payments.example/"}, + {SessionID: "session-b", FrameID: "root-b", Root: true, URL: "https://travel.example/"}, + }, frames) +} + func TestTrackerPreservesFramesDuringProcessSwap(t *testing.T) { protocol := newFakeProtocol() tracker := New(protocol) diff --git a/server/lib/browsersurface/types.go b/server/lib/browsersurface/types.go index 1684632e..ccf36c45 100644 --- a/server/lib/browsersurface/types.go +++ b/server/lib/browsersurface/types.go @@ -76,6 +76,17 @@ type FrameLocation struct { URL string } +// SessionFrame is a frame whose document is reachable through the session +// that reported it. Root frames are the main frame of a page target or the +// frame of an out-of-process iframe target; other frames are same-process +// children of the session's root. +type SessionFrame struct { + SessionID string + FrameID string + Root bool + URL string +} + type Location struct { WindowID int TabID int @@ -132,9 +143,10 @@ type tab struct { } type frame struct { - id int - rawID string - parentID string - tabID int - url string + id int + rawID string + parentID string + tabID int + sessionID string + url string } diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index e71c32e9..734554f3 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -7006,6 +7006,10 @@ type WebMCPToolSource struct { // PageUrl Current URL of the top-level page with the fragment omitted. PageUrl string `json:"page_url"` + // Polyfill True when the page registered this tool through a JavaScript `navigator.modelContext` + // polyfill rather than the native `document.modelContext` registry; omitted otherwise. + Polyfill *bool `json:"polyfill,omitempty"` + // TabId Monotonically increasing identifier for the tab during the current browser process. TabId int `json:"tab_id"` @@ -25663,679 +25667,677 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "0+4mMfVi444J9T98+P8PAL9tCgY8tgMA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/webmcpclient/client.go b/server/lib/webmcpclient/client.go index 01425e88..8ff2f323 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -41,6 +41,7 @@ type connection struct { invocations map[invocationKey]invocationResponse waitingInvocations map[invocationKey]string abandonedInvocations map[invocationKey]time.Time + polyfillWatches map[*polyfillWatch]struct{} stateChangedCh chan struct{} logger *slog.Logger @@ -63,6 +64,7 @@ func newConnection(protocol *cdpclient.Client) *connection { invocations: make(map[invocationKey]invocationResponse), waitingInvocations: make(map[invocationKey]string), abandonedInvocations: make(map[invocationKey]time.Time), + polyfillWatches: make(map[*polyfillWatch]struct{}), stateChangedCh: make(chan struct{}, 1), logger: slog.Default(), eventsCancel: cancel, @@ -115,17 +117,22 @@ func (c *connection) eventLoop(events <-chan browsersurface.Event) { case browsersurface.EventDocumentChanged: c.stateMu.Lock() c.removeFrameToolsLocked(event.SessionID, event.FrameID) + c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { + return watch.sessionID == event.SessionID && watch.frameID == event.FrameID + }) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameInvalidated: c.stateMu.Lock() c.removeFrameToolsAcrossSessionsLocked(event.FrameID) + c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == event.FrameID }) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameRemoved: c.stateMu.Lock() c.abandonFrameInvocationsAcrossSessionsLocked(event.FrameID) c.removeFrameToolsAcrossSessionsLocked(event.FrameID) + c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == event.FrameID }) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventProtocol: @@ -278,8 +285,19 @@ func (c *connection) addTools(sessionID string, tools []toolEvent) { func (c *connection) toolsSnapshot() []Tool { c.stateMu.RLock() defer c.stateMu.RUnlock() + // A name registered both natively and through a polyfill lists once, as + // the native tool. + native := make(map[string]bool) + for _, tool := range c.tools { + if !tool.polyfill && tool.customID == "" { + native[tool.key()] = true + } + } result := make([]Tool, 0, len(c.tools)) for _, tool := range c.tools { + if tool.polyfill && native[toolKey(tool.sessionID, tool.frameID, tool.name)] { + continue + } location, ok := c.surface.Resolve(tool.sessionID, tool.frameID) if !ok { continue @@ -295,13 +313,17 @@ func (c *connection) toolsSnapshot() []Tool { source.Frame = &ToolFrame{FrameID: location.Frame.ID, URL: location.Frame.URL} } result = append(result, Tool{ - Ref: tool.ref, - Name: tool.name, - Description: tool.description, - InputSchema: tool.inputSchema, - Annotations: tool.annotations, - CustomID: tool.customID, - Source: source, + Ref: tool.ref, + Name: tool.name, + Description: tool.description, + InputSchema: tool.inputSchema, + Annotations: tool.annotations, + CustomID: tool.customID, + Polyfill: tool.polyfill, + Title: tool.title, + OutputSchema: tool.outputSchema, + Hints: tool.hints, + Source: source, }) } sort.Slice(result, func(i, j int) bool { @@ -361,6 +383,14 @@ func (c *connection) invoke(ctx context.Context, toolRef string, input map[strin c.stateMu.RUnlock() return InvocationResult{}, ErrToolNotFound } + if tool.polyfill { + polyfillTool := *tool + c.stateMu.RUnlock() + if !c.surface.SessionExists(polyfillTool.sessionID) { + return InvocationResult{}, ErrToolNotFound + } + return c.invokePolyfill(ctx, &polyfillTool, input) + } sessionID, frameID, name := tool.sessionID, tool.frameID, tool.registeredName awaitingSubmission := tool.declarative && (tool.annotations == nil || !tool.annotations.Autosubmit) c.stateMu.RUnlock() @@ -491,6 +521,7 @@ func (c *connection) removeSession(sessionID string) { defer c.stateMu.Unlock() delete(c.enabledSessions, sessionID) delete(c.toolLimitWarned, sessionID) + c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.sessionID == sessionID }) for key := range c.waitingInvocations { if key.sessionID == sessionID { c.abandonInvocationLocked(key) @@ -498,7 +529,7 @@ func (c *connection) removeSession(sessionID string) { } for ref, tool := range c.tools { if tool.sessionID == sessionID { - delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) + delete(c.toolRefs, tool.key()) delete(c.tools, ref) } } @@ -516,7 +547,7 @@ func (c *connection) abandonFrameInvocationsAcrossSessionsLocked(frameID string) func (c *connection) removeFrameToolsLocked(sessionID, frameID string) { for ref, tool := range c.tools { if tool.sessionID == sessionID && tool.frameID == frameID { - delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) + delete(c.toolRefs, tool.key()) delete(c.tools, ref) } } @@ -525,7 +556,7 @@ func (c *connection) removeFrameToolsLocked(sessionID, frameID string) { func (c *connection) removeFrameToolsAcrossSessionsLocked(frameID string) { for ref, tool := range c.tools { if tool.frameID == frameID { - delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) + delete(c.toolRefs, tool.key()) delete(c.tools, ref) } } diff --git a/server/lib/webmcpclient/client_test.go b/server/lib/webmcpclient/client_test.go index 70cca9a6..65bd205c 100644 --- a/server/lib/webmcpclient/client_test.go +++ b/server/lib/webmcpclient/client_test.go @@ -47,6 +47,7 @@ type wireRequest struct { } type fakeCDP struct { + t *testing.T server *httptest.Server url string @@ -67,12 +68,19 @@ type fakeCDP struct { popupOpen bool iframeOpen bool nestedFrameOpen bool + childFrameOpen bool + windowUnavailable bool + polyfillTools map[string][]map[string]any + polyfillInvoke func(windowID, name string, input map[string]any) map[string]any + polyfillInvokeError string + polyfillInvokeDelay time.Duration + polyfillInvocations []polyfillInvocation write func(any) } func newFakeCDP(t *testing.T, omitResponse bool) *fakeCDP { t.Helper() - fake := &fakeCDP{enabledSessions: make(map[string]int), omitResponse: omitResponse, toolCount: 1} + fake := &fakeCDP{t: t, enabledSessions: make(map[string]int), omitResponse: omitResponse, toolCount: 1} fake.server = httptest.NewServer(http.HandlerFunc(fake.serve)) fake.url = "ws" + strings.TrimPrefix(fake.server.URL, "http") t.Cleanup(fake.server.Close) @@ -211,7 +219,18 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) { case "Page.enable": respond(map[string]any{}) case "Page.getFrameTree": - respond(map[string]any{"frameTree": frameTreeForSession(request.SessionID)}) + tree := frameTreeForSession(request.SessionID) + f.mu.Lock() + childFrameOpen := f.childFrameOpen + f.mu.Unlock() + if childFrameOpen && request.SessionID == "page-session" { + tree["childFrames"] = []map[string]any{{"frame": map[string]any{ + "id": "page-child", "parentId": "page-frame", "loaderId": "child-loader", "url": "https://merchant.example/child", + }}} + } + respond(map[string]any{"frameTree": tree}) + case "Runtime.evaluate", "Runtime.callFunctionOn", "Runtime.releaseObjectGroup", "DOM.getFrameOwner", "DOM.resolveNode": + f.servePolyfill(request, respond, write) case "WebMCP.enable": f.mu.Lock() f.enabledSessions[request.SessionID]++ diff --git a/server/lib/webmcpclient/manager.go b/server/lib/webmcpclient/manager.go index 93d2495f..d0dc9459 100644 --- a/server/lib/webmcpclient/manager.go +++ b/server/lib/webmcpclient/manager.go @@ -51,6 +51,7 @@ func (m *Manager) Tools(ctx context.Context) ([]Tool, error) { if !conn.surface.HasTabs() { return nil, ErrNoPageTarget } + conn.syncPolyfillTools(ctx) return conn.toolsSnapshot(), nil } diff --git a/server/lib/webmcpclient/polyfill.go b/server/lib/webmcpclient/polyfill.go new file mode 100644 index 00000000..d2a4b420 --- /dev/null +++ b/server/lib/webmcpclient/polyfill.go @@ -0,0 +1,412 @@ +package webmcpclient + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/kernel/kernel-images/server/lib/browsersurface" + "github.com/nrednav/cuid2" +) + +// Sites that ship their own WebMCP tools before the browser exposes the +// native registry install a polyfill on navigator.modelContext. Chromium +// dropped that alias in favor of document.modelContext, so nothing such a +// polyfill registers reaches the CDP WebMCP domain. polyfill_page.js reads and +// invokes those tools from the page's main world without leaving anything +// behind on the page. +// +//go:embed polyfill_page.js +var polyfillPageSource string + +const ( + polyfillSyncTimeout = 2 * time.Second + polyfillSyncConcurrency = 8 + polyfillReleaseTimeout = time.Second +) + +type polyfillToolMetadata struct { + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + InputSchema map[string]any `json:"inputSchema"` + OutputSchema map[string]any `json:"outputSchema"` + Annotations map[string]bool `json:"annotations"` +} + +type polyfillInvokeResult struct { + OK bool `json:"ok"` + Output *string `json:"output"` + Error string `json:"error"` +} + +type polyfillFrameTools struct { + frame browsersurface.SessionFrame + tools []polyfillToolMetadata + read bool +} + +func polyfillToolKey(sessionID, frameID, name string) string { + return toolKey(sessionID, frameID, name) + "\x00polyfill" +} + +// polyfillFrameURL excludes browser-internal documents; every web document, +// including about:blank and srcdoc frames that inherit their parent's origin, +// can carry a polyfill. +func polyfillFrameURL(url string) bool { + return !strings.HasPrefix(url, "chrome") && !strings.HasPrefix(url, "devtools://") +} + +// syncPolyfillTools reads polyfill-registered tools from every tracked frame +// and reconciles them with the registry. A frame that cannot be read keeps its +// previous entries; surface events remove them when the document goes away. +func (c *connection) syncPolyfillTools(ctx context.Context) { + frames := c.surface.SessionFrames() + if len(frames) == 0 { + return + } + ctx, cancel := context.WithTimeout(ctx, polyfillSyncTimeout) + defer cancel() + + results := make([]polyfillFrameTools, len(frames)) + var wg sync.WaitGroup + semaphore := make(chan struct{}, polyfillSyncConcurrency) + for i, frame := range frames { + if !polyfillFrameURL(frame.URL) { + continue + } + wg.Add(1) + go func(i int, frame browsersurface.SessionFrame) { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + tools, err := c.readPolyfillTools(ctx, frame) + if err != nil { + return + } + results[i] = polyfillFrameTools{frame: frame, tools: tools, read: true} + }(i, frame) + } + wg.Wait() + + c.stateMu.Lock() + defer c.stateMu.Unlock() + for _, result := range results { + if result.read { + c.applyPolyfillToolsLocked(result.frame, result.tools) + } + } +} + +func (c *connection) readPolyfillTools(ctx context.Context, frame browsersurface.SessionFrame) ([]polyfillToolMetadata, error) { + raw, err := c.callPolyfill(ctx, frame, "list", nil, false) + if err != nil { + return nil, err + } + if raw == nil { + return nil, nil + } + var result struct { + Tools []polyfillToolMetadata `json:"tools"` + } + if err := json.Unmarshal(raw, &result); err != nil { + return nil, fmt.Errorf("WebMCP: decode polyfill tools: %w", err) + } + return result.Tools, nil +} + +func (c *connection) applyPolyfillToolsLocked(frame browsersurface.SessionFrame, tools []polyfillToolMetadata) { + if !c.surface.SessionExists(frame.SessionID) { + return + } + native := make(map[string]bool) + tracked := 0 + for _, existing := range c.tools { + if existing.sessionID != frame.SessionID { + continue + } + tracked++ + if !existing.polyfill && existing.customID == "" && existing.frameID == frame.FrameID { + native[existing.name] = true + } + } + desired := make(map[string]polyfillToolMetadata, len(tools)) + for _, tool := range tools { + if !native[tool.Name] { + desired[tool.Name] = tool + } + } + + for ref, existing := range c.tools { + if !existing.polyfill || existing.sessionID != frame.SessionID || existing.frameID != frame.FrameID { + continue + } + if _, keep := desired[existing.name]; keep { + continue + } + delete(c.toolRefs, existing.key()) + delete(c.tools, ref) + tracked-- + } + for _, tool := range tools { + if _, keep := desired[tool.Name]; !keep { + continue + } + key := polyfillToolKey(frame.SessionID, frame.FrameID, tool.Name) + ref := c.toolRefs[key] + if ref == "" { + if tracked >= maxToolsPerSession { + if !c.toolLimitWarned[frame.SessionID] { + c.toolLimitWarned[frame.SessionID] = true + c.logger.Warn("WebMCP tool limit reached", "session_id", frame.SessionID, "limit", maxToolsPerSession) + } + continue + } + ref = "wmcp_" + cuid2.Generate() + c.toolRefs[key] = ref + tracked++ + } + c.tools[ref] = ®isteredTool{ + ref: ref, + sessionID: frame.SessionID, + name: tool.Name, + registeredName: tool.Name, + description: tool.Description, + inputSchema: tool.InputSchema, + frameID: frame.FrameID, + polyfill: true, + rootFrame: frame.Root, + title: tool.Title, + outputSchema: tool.OutputSchema, + hints: tool.Annotations, + } + } +} + +// polyfillWatch is released when the document that is running a polyfill +// invocation goes away. +type polyfillWatch struct { + sessionID string + frameID string + released chan struct{} +} + +func (c *connection) watchPolyfillDocument(sessionID, frameID string) (*polyfillWatch, func()) { + watch := &polyfillWatch{sessionID: sessionID, frameID: frameID, released: make(chan struct{})} + c.stateMu.Lock() + c.polyfillWatches[watch] = struct{}{} + c.stateMu.Unlock() + return watch, func() { + c.stateMu.Lock() + delete(c.polyfillWatches, watch) + c.stateMu.Unlock() + } +} + +func (c *connection) releasePolyfillWatchesLocked(matches func(*polyfillWatch) bool) { + for watch := range c.polyfillWatches { + if matches(watch) { + close(watch.released) + delete(c.polyfillWatches, watch) + } + } +} + +func (c *connection) invokePolyfill(ctx context.Context, tool *registeredTool, input map[string]any) (InvocationResult, error) { + frame := browsersurface.SessionFrame{SessionID: tool.sessionID, FrameID: tool.frameID, Root: tool.rootFrame} + invocation := InvocationResult{InvocationID: cuid2.Generate()} + + // Chromium never answers a call whose document navigates before the + // returned promise settles. The native WebMCP domain reports such an + // invocation as completed with empty output, so watch the document and do + // the same. + watch, stopWatching := c.watchPolyfillDocument(tool.sessionID, tool.frameID) + defer stopWatching() + callCtx, cancelCall := context.WithCancel(ctx) + defer cancelCall() + type callResult struct { + raw json.RawMessage + err error + } + results := make(chan callResult, 1) + go func() { + raw, err := c.callPolyfill(callCtx, frame, "invoke", []any{tool.name, input}, true) + results <- callResult{raw: raw, err: err} + }() + var raw json.RawMessage + select { + case result := <-results: + raw = result.raw + if result.err != nil { + var notDispatched *polyfillNotDispatchedError + if errors.As(result.err, ¬Dispatched) && ctx.Err() == nil { + return InvocationResult{}, ErrToolNotFound + } + return invocation, ErrOutcomeUnknown + } + case <-watch.released: + cancelCall() + result := <-results + if result.err != nil { + invocation.Status = "completed" + invocation.Output = []any{} + return invocation, nil + } + raw = result.raw + } + var result polyfillInvokeResult + if err := json.Unmarshal(raw, &result); err != nil { + return invocation, fmt.Errorf("WebMCP: decode polyfill invocation: %w", err) + } + if !result.OK { + invocation.Status = "error" + invocation.ErrorText = result.Error + return invocation, nil + } + if result.Output != nil { + if err := json.Unmarshal([]byte(*result.Output), &invocation.Output); err != nil { + return invocation, fmt.Errorf("WebMCP: decode polyfill output: %w", err) + } + } + invocation.Status = "completed" + return invocation, nil +} + +// polyfillNotDispatchedError reports a failure before the page function ran, +// so the caller knows the tool did not execute. +type polyfillNotDispatchedError struct { + cause error +} + +func (e *polyfillNotDispatchedError) Error() string { return e.cause.Error() } +func (e *polyfillNotDispatchedError) Unwrap() error { return e.cause } + +// callPolyfill runs one entry point of polyfill_page.js in the frame's main +// world and returns the JSON value it produced, or nil for null. +func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.SessionFrame, method string, args []any, userGesture bool) (json.RawMessage, error) { + group := "kernel-webmcp-polyfill-" + cuid2.Generate() + defer func() { + releaseCtx, cancel := context.WithTimeout(context.Background(), polyfillReleaseTimeout) + defer cancel() + _, _ = c.surface.Send(releaseCtx, "Runtime.releaseObjectGroup", map[string]any{"objectGroup": group}, frame.SessionID) + }() + + windowID, err := c.frameWindow(ctx, frame, group) + if err != nil { + return nil, &polyfillNotDispatchedError{cause: err} + } + arguments := make([]map[string]any, 0, len(args)) + for _, arg := range args { + arguments = append(arguments, map[string]any{"value": arg}) + } + raw, err := c.surface.Send(ctx, "Runtime.callFunctionOn", map[string]any{ + "objectId": windowID, + "functionDeclaration": fmt.Sprintf("function(...args) { return (%s).%s(this, ...args); }", polyfillPageSource, method), + "arguments": arguments, + "awaitPromise": true, + "returnByValue": true, + "userGesture": userGesture, + "objectGroup": group, + }, frame.SessionID) + if err != nil { + return nil, err + } + var result struct { + Result struct { + Value json.RawMessage `json:"value"` + } `json:"result"` + ExceptionDetails *struct { + Text string `json:"text"` + Exception *struct { + Description string `json:"description"` + } `json:"exception"` + } `json:"exceptionDetails"` + } + if err := json.Unmarshal(raw, &result); err != nil { + return nil, fmt.Errorf("WebMCP: decode polyfill %s response: %w", method, err) + } + if details := result.ExceptionDetails; details != nil { + description := details.Text + if details.Exception != nil && details.Exception.Description != "" { + description = details.Exception.Description + } + return nil, fmt.Errorf("WebMCP: polyfill %s threw: %s", method, description) + } + if len(result.Result.Value) == 0 || string(result.Result.Value) == "null" { + return nil, nil + } + return result.Result.Value, nil +} + +// frameWindow returns a remote object handle for the frame's Window. Root +// frames evaluate directly in their session; same-process child frames are +// reached through their owner element's contentWindow, which only succeeds +// for same-origin documents. +func (c *connection) frameWindow(ctx context.Context, frame browsersurface.SessionFrame, group string) (string, error) { + if frame.Root { + raw, err := c.surface.Send(ctx, "Runtime.evaluate", map[string]any{ + "expression": "window", + "objectGroup": group, + }, frame.SessionID) + if err != nil { + return "", err + } + var result struct { + Result struct { + ObjectID string `json:"objectId"` + } `json:"result"` + } + if err := json.Unmarshal(raw, &result); err != nil || result.Result.ObjectID == "" { + return "", fmt.Errorf("WebMCP: frame window is unavailable") + } + return result.Result.ObjectID, nil + } + + raw, err := c.surface.Send(ctx, "DOM.getFrameOwner", map[string]any{"frameId": frame.FrameID}, frame.SessionID) + if err != nil { + return "", err + } + var owner struct { + BackendNodeID int `json:"backendNodeId"` + } + if err := json.Unmarshal(raw, &owner); err != nil || owner.BackendNodeID == 0 { + return "", fmt.Errorf("WebMCP: frame owner is unavailable") + } + raw, err = c.surface.Send(ctx, "DOM.resolveNode", map[string]any{ + "backendNodeId": owner.BackendNodeID, + "objectGroup": group, + }, frame.SessionID) + if err != nil { + return "", err + } + var node struct { + Object struct { + ObjectID string `json:"objectId"` + } `json:"object"` + } + if err := json.Unmarshal(raw, &node); err != nil || node.Object.ObjectID == "" { + return "", fmt.Errorf("WebMCP: frame owner is unavailable") + } + raw, err = c.surface.Send(ctx, "Runtime.callFunctionOn", map[string]any{ + "objectId": node.Object.ObjectID, + "functionDeclaration": "function() { return this.contentWindow; }", + "objectGroup": group, + }, frame.SessionID) + if err != nil { + return "", err + } + var window struct { + Result struct { + ObjectID string `json:"objectId"` + } `json:"result"` + } + if err := json.Unmarshal(raw, &window); err != nil || window.Result.ObjectID == "" { + return "", fmt.Errorf("WebMCP: frame window is unavailable") + } + return window.Result.ObjectID, nil +} diff --git a/server/lib/webmcpclient/polyfill_page.js b/server/lib/webmcpclient/polyfill_page.js new file mode 100644 index 00000000..c7ccccc8 --- /dev/null +++ b/server/lib/webmcpclient/polyfill_page.js @@ -0,0 +1,240 @@ +// Discovers and invokes tools that a page registers through a JavaScript +// navigator.modelContext polyfill instead of the native document.modelContext +// registry that the CDP WebMCP domain reports. Evaluated in the page's main +// world with the frame's Window passed in; it stores nothing on the page. +(() => { + const MAX_TOOLS = 256; + const MAX_TEXT = 64 * 1024; + const MAX_TOOL_BYTES = 256 * 1024; + const MAX_OUTPUT_BYTES = 1024 * 1024; + const HINTS = [ + 'readOnlyHint', + 'destructiveHint', + 'idempotentHint', + 'openWorldHint', + 'consequentialHint', + 'untrustedContentHint', + 'autosubmit', + ]; + + function isObject(value) { + return typeof value === 'object' && value !== null; + } + + function isFunction(value) { + return typeof value === 'function'; + } + + function message(error) { + try { + return isObject(error) && typeof error.message === 'string' ? error.message : String(error); + } catch { + return 'unknown error'; + } + } + + function text(value, fallback) { + if (typeof value !== 'string') return fallback; + return value.length > MAX_TEXT ? value.slice(0, MAX_TEXT) : value; + } + + // Returns the page-created object on navigator.modelContext, or null when the + // property is absent or is the native registry that CDP already reports. + function polyfill(window) { + let context; + try { + context = window.navigator.modelContext; + } catch { + return null; + } + if (!isObject(context) && !isFunction(context)) return null; + let native; + try { + native = window.document.modelContext; + } catch { + native = undefined; + } + if (context === native) return null; + try { + if (Object.prototype.toString.call(context) === '[object ModelContext]') return null; + } catch { + return null; + } + return context; + } + + function mapLike(value) { + return isObject(value) && isFunction(value.values) && isFunction(value.get); + } + + // Tool registries that polyfills keep next to their public methods. + function registryEntries(context, window) { + const candidates = []; + for (const key of ['_registeredTools', '_tools', 'tools']) { + try { + candidates.push(context[key]); + } catch { + // A throwing accessor is not a registry. + } + } + try { + candidates.push(window.__webmcp && window.__webmcp.tools); + } catch { + // Same as above. + } + for (const registry of candidates) { + try { + if (mapLike(registry)) return Array.from(registry.values()); + if (Array.isArray(registry)) return registry; + if (isObject(registry)) return Object.values(registry); + } catch { + continue; + } + } + return null; + } + + async function listedTools(context, window) { + for (const method of ['listTools', 'getTools']) { + let fn; + try { + fn = context[method]; + } catch { + continue; + } + if (!isFunction(fn)) continue; + try { + const tools = await fn.call(context); + if (Array.isArray(tools)) return tools; + } catch { + // Fall through to the next source. + } + } + return registryEntries(context, window); + } + + function schema(value) { + if (typeof value === 'string') { + try { + value = JSON.parse(value); + } catch { + return undefined; + } + } + return isObject(value) && !Array.isArray(value) ? value : undefined; + } + + function metadata(tool) { + try { + if (!isObject(tool)) return null; + const name = tool.name; + if (typeof name !== 'string' || name === '' || name.length > 256) return null; + const entry = {name, description: text(tool.description, '')}; + const title = text(tool.title, undefined); + if (title !== undefined) entry.title = title; + // Some polyfills accept the pre-standard `parameters` alias for inputSchema. + entry.inputSchema = schema(tool.inputSchema) ?? schema(tool.parameters) ?? {}; + const outputSchema = schema(tool.outputSchema); + if (outputSchema) entry.outputSchema = outputSchema; + if (isObject(tool.annotations)) { + const annotations = {}; + for (const hint of HINTS) { + if (typeof tool.annotations[hint] === 'boolean') annotations[hint] = tool.annotations[hint]; + } + if (Object.keys(annotations).length) entry.annotations = annotations; + } + const serialized = JSON.stringify(entry); + if (serialized.length > MAX_TOOL_BYTES) return null; + return JSON.parse(serialized); + } catch { + return null; + } + } + + async function list(window) { + const context = polyfill(window); + if (!context) return null; + const tools = await listedTools(context, window); + if (!Array.isArray(tools)) return null; + const seen = new Set(); + const result = []; + for (const tool of tools) { + if (result.length >= MAX_TOOLS) break; + const entry = metadata(tool); + if (!entry || seen.has(entry.name)) continue; + seen.add(entry.name); + result.push(entry); + } + return {tools: result}; + } + + function registryEntry(context, window, name) { + const entries = registryEntries(context, window); + if (!entries) return null; + for (const entry of entries) { + try { + if (isObject(entry) && entry.name === name && isFunction(entry.execute)) return entry; + } catch { + continue; + } + } + return null; + } + + async function execute(context, window, name, input) { + let callTool; + try { + callTool = context.callTool; + } catch { + callTool = undefined; + } + if (isFunction(callTool)) { + // Site polyfills take (name, input); MCP-style polyfills take ({name, arguments}). + if (callTool.length >= 2) return callTool.call(context, name, input); + return callTool.call(context, {name, arguments: input}); + } + const entry = registryEntry(context, window, name); + if (entry) return entry.execute(input); + let executeTool; + try { + executeTool = context.executeTool; + } catch { + executeTool = undefined; + } + if (isFunction(executeTool)) { + const output = await executeTool.call(context, name, JSON.stringify(input)); + if (typeof output !== 'string') return output; + try { + return JSON.parse(output); + } catch { + return output; + } + } + throw new Error('the modelContext polyfill does not expose a way to execute tools'); + } + + function serialize(output) { + if (output === undefined) return null; + let serialized; + try { + serialized = JSON.stringify(output); + } catch (error) { + throw new Error(`tool output is not JSON-serializable: ${message(error)}`); + } + if (serialized === undefined) return null; + if (serialized.length > MAX_OUTPUT_BYTES) throw new Error('tool output exceeds 1 MiB'); + return serialized; + } + + async function invoke(window, name, input) { + const context = polyfill(window); + if (!context) return {ok: false, error: 'the page no longer exposes a modelContext polyfill'}; + try { + return {ok: true, output: serialize(await execute(context, window, name, input))}; + } catch (error) { + return {ok: false, error: text(message(error), 'tool execution failed')}; + } + } + + return {list, invoke}; +})() diff --git a/server/lib/webmcpclient/polyfill_test.go b/server/lib/webmcpclient/polyfill_test.go new file mode 100644 index 00000000..b7b20706 --- /dev/null +++ b/server/lib/webmcpclient/polyfill_test.go @@ -0,0 +1,311 @@ +package webmcpclient + +import ( + "context" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type polyfillInvocation struct { + windowID string + name string + input map[string]any +} + +// servePolyfill answers the Runtime and DOM commands used to read a page's +// navigator.modelContext polyfill. Window handles are "window:" for +// roots and "window::page-child" for the same-process child frame. +func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write func(any)) { + fail := func(message string) { + write(map[string]any{"id": request.ID, "error": map[string]any{"code": -32000, "message": message}}) + } + var params struct { + ObjectID string `json:"objectId"` + FunctionDeclaration string `json:"functionDeclaration"` + FrameID string `json:"frameId"` + Arguments []struct { + Value json.RawMessage `json:"value"` + } `json:"arguments"` + } + _ = json.Unmarshal(request.Params, ¶ms) + f.mu.Lock() + windowUnavailable := f.windowUnavailable + tools := f.polyfillTools[params.ObjectID] + invoke := f.polyfillInvoke + invokeError := f.polyfillInvokeError + invokeDelay := f.polyfillInvokeDelay + f.mu.Unlock() + + switch request.Method { + case "Runtime.evaluate": + if windowUnavailable { + fail("Cannot find context with specified id") + return + } + respond(map[string]any{"result": map[string]any{"type": "object", "className": "Window", "objectId": "window:" + request.SessionID}}) + case "DOM.getFrameOwner": + if params.FrameID != "page-child" { + fail("Frame with the given id was not found.") + return + } + respond(map[string]any{"backendNodeId": 7}) + case "DOM.resolveNode": + respond(map[string]any{"object": map[string]any{"type": "object", "subtype": "node", "objectId": "iframe:" + request.SessionID}}) + case "Runtime.releaseObjectGroup": + respond(map[string]any{}) + case "Runtime.callFunctionOn": + switch { + case strings.Contains(params.FunctionDeclaration, "contentWindow"): + respond(map[string]any{"result": map[string]any{"type": "object", "className": "Window", "objectId": "window:" + request.SessionID + ":page-child"}}) + case strings.Contains(params.FunctionDeclaration, ").list("): + if tools == nil { + respond(map[string]any{"result": map[string]any{"type": "object", "subtype": "null", "value": nil}}) + return + } + respond(map[string]any{"result": map[string]any{"type": "object", "value": map[string]any{"tools": tools}}}) + case strings.Contains(params.FunctionDeclaration, ").invoke("): + if invokeDelay > 0 { + time.Sleep(invokeDelay) + } + if invokeError != "" { + fail(invokeError) + return + } + var name string + var input map[string]any + require.Len(f.t, params.Arguments, 2) + require.NoError(f.t, json.Unmarshal(params.Arguments[0].Value, &name)) + require.NoError(f.t, json.Unmarshal(params.Arguments[1].Value, &input)) + f.mu.Lock() + f.polyfillInvocations = append(f.polyfillInvocations, polyfillInvocation{windowID: params.ObjectID, name: name, input: input}) + f.mu.Unlock() + respond(map[string]any{"result": map[string]any{"type": "object", "value": invoke(params.ObjectID, name, input)}}) + default: + fail("unexpected function") + } + } +} + +func newPolyfillFakeCDP(t *testing.T) *fakeCDP { + t.Helper() + fake := newFakeCDP(t, false) + fake.childFrameOpen = true + fake.polyfillTools = map[string][]map[string]any{ + "window:page-session": { + {"name": "merchant_tool", "description": "polyfill copy", "inputSchema": map[string]any{"type": "object"}}, + { + "name": "poly_search", "title": "Search", "description": "Search the catalog.", + "inputSchema": map[string]any{"type": "object", "properties": map[string]any{"query": map[string]any{"type": "string"}}}, + "outputSchema": map[string]any{"type": "object"}, + "annotations": map[string]any{"readOnlyHint": true}, + }, + }, + "window:page-session:page-child": { + {"name": "child_poly", "description": "child", "inputSchema": map[string]any{"type": "object"}}, + }, + } + fake.polyfillInvoke = func(_ string, name string, input map[string]any) map[string]any { + if name != "poly_search" { + return map[string]any{"ok": false, "error": "Tool not found: " + name} + } + output, _ := json.Marshal(map[string]any{"results": []any{input["query"]}}) + return map[string]any{"ok": true, "output": string(output)} + } + return fake +} + +func toolsByName(tools []Tool) map[string]Tool { + byName := make(map[string]Tool, len(tools)) + for _, tool := range tools { + byName[tool.Name] = tool + } + return byName +} + +func TestPolyfillToolsAreDiscoveredNextToNativeTools(t *testing.T) { + fake := newPolyfillFakeCDP(t) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + require.Len(t, tools, 6) + byName := toolsByName(tools) + + // The native registration wins over the polyfill copy of the same name. + require.False(t, byName["merchant_tool"].Polyfill) + require.Equal(t, "merchant_tool description", byName["merchant_tool"].Description) + + search := byName["poly_search"] + require.True(t, search.Polyfill) + require.Empty(t, search.CustomID) + require.Equal(t, "Search", search.Title) + require.Equal(t, "Search the catalog.", search.Description) + require.Equal(t, map[string]any{"type": "object"}, search.OutputSchema) + require.Equal(t, map[string]bool{"readOnlyHint": true}, search.Hints) + require.Equal(t, 1, search.Source.TabID) + require.Equal(t, "https://merchant.example/", search.Source.PageURL) + require.Nil(t, search.Source.Frame) + + child := byName["child_poly"] + require.True(t, child.Polyfill) + require.Equal(t, 1, child.Source.TabID) + require.NotNil(t, child.Source.Frame) + require.Equal(t, "https://merchant.example/child", child.Source.Frame.URL) + + customID, targetID, err := manager.CustomTool(context.Background(), search.Ref) + require.NoError(t, err) + require.Empty(t, customID) + require.Empty(t, targetID) + + // References stay stable while the registration lives. + again, err := manager.Tools(context.Background()) + require.NoError(t, err) + require.Equal(t, search.Ref, toolsByName(again)["poly_search"].Ref) + require.Equal(t, child.Ref, toolsByName(again)["child_poly"].Ref) + + // A tool the polyfill unregisters disappears on the next listing. + fake.mu.Lock() + fake.polyfillTools["window:page-session"] = fake.polyfillTools["window:page-session"][:1] + fake.mu.Unlock() + after, err := manager.Tools(context.Background()) + require.NoError(t, err) + require.NotContains(t, toolsByName(after), "poly_search") + require.Equal(t, child.Ref, toolsByName(after)["child_poly"].Ref) + + // Navigation replaces the document, so surviving names get new references. + fake.mu.Lock() + fake.polyfillTools["window:page-session"] = append(fake.polyfillTools["window:page-session"], map[string]any{ + "name": "poly_search", "description": "Search the catalog.", "inputSchema": map[string]any{"type": "object"}, + }) + fake.mu.Unlock() + fake.emit(map[string]any{ + "method": "Page.frameNavigated", "sessionId": "page-session", + "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, + }) + var navigated map[string]Tool + require.Eventually(t, func() bool { + tools, err := manager.Tools(context.Background()) + if err != nil { + return false + } + navigated = toolsByName(tools) + _, ok := navigated["poly_search"] + return ok && navigated["poly_search"].Ref != search.Ref + }, 3*time.Second, 20*time.Millisecond) + require.NotContains(t, navigated, "child_poly") +} + +func TestPolyfillInvocationRunsInTheRegisteringFrame(t *testing.T) { + fake := newPolyfillFakeCDP(t) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + byName := toolsByName(tools) + + result, err := manager.Invoke(context.Background(), byName["poly_search"].Ref, map[string]any{"query": "lamp"}) + require.NoError(t, err) + require.NotEmpty(t, result.InvocationID) + require.Equal(t, "completed", result.Status) + require.Equal(t, map[string]any{"results": []any{"lamp"}}, result.Output) + require.Empty(t, result.ErrorText) + + failed, err := manager.Invoke(context.Background(), byName["child_poly"].Ref, map[string]any{}) + require.NoError(t, err) + require.Equal(t, "error", failed.Status) + require.Equal(t, "Tool not found: child_poly", failed.ErrorText) + require.Nil(t, failed.Output) + + fake.mu.Lock() + defer fake.mu.Unlock() + require.Equal(t, []polyfillInvocation{ + {windowID: "window:page-session", name: "poly_search", input: map[string]any{"query": "lamp"}}, + {windowID: "window:page-session:page-child", name: "child_poly", input: map[string]any{}}, + }, fake.polyfillInvocations) +} + +func TestPolyfillInvocationThatNavigatesCompletesLikeNativeTools(t *testing.T) { + fake := newPolyfillFakeCDP(t) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + ref := toolsByName(tools)["poly_search"].Ref + + // Chromium never answers the call once the document navigates away. + fake.mu.Lock() + fake.polyfillInvokeDelay = 5 * time.Second + fake.mu.Unlock() + go func() { + time.Sleep(100 * time.Millisecond) + fake.emit(map[string]any{ + "method": "Page.frameNavigated", "sessionId": "page-session", + "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, + }) + }() + started := time.Now() + result, err := manager.Invoke(context.Background(), ref, map[string]any{}) + require.NoError(t, err) + require.Less(t, time.Since(started), 3*time.Second) + require.Equal(t, "completed", result.Status) + require.Equal(t, []any{}, result.Output) + require.NotEmpty(t, result.InvocationID) +} + +func TestPolyfillInvocationProtocolFailureHasUnknownOutcome(t *testing.T) { + fake := newPolyfillFakeCDP(t) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + ref := toolsByName(tools)["poly_search"].Ref + + fake.mu.Lock() + fake.polyfillInvokeError = "Target closed." + fake.mu.Unlock() + result, err := manager.Invoke(context.Background(), ref, map[string]any{}) + require.ErrorIs(t, err, ErrOutcomeUnknown) + require.NotEmpty(t, result.InvocationID) +} + +func TestPolyfillInvocationTimeoutHasUnknownOutcome(t *testing.T) { + fake := newPolyfillFakeCDP(t) + fake.polyfillInvokeDelay = 300 * time.Millisecond + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + ref := toolsByName(tools)["poly_search"].Ref + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + result, err := manager.Invoke(ctx, ref, map[string]any{}) + require.ErrorIs(t, err, ErrOutcomeUnknown) + require.NotEmpty(t, result.InvocationID) +} + +func TestPolyfillInvocationReportsMissingDocument(t *testing.T) { + fake := newPolyfillFakeCDP(t) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + ref := toolsByName(tools)["poly_search"].Ref + + fake.mu.Lock() + fake.windowUnavailable = true + fake.mu.Unlock() + _, err = manager.Invoke(context.Background(), ref, map[string]any{}) + require.ErrorIs(t, err, ErrToolNotFound) + + // An unreadable frame keeps its previous registrations until the surface + // reports that the document changed. + again, err := manager.Tools(context.Background()) + require.NoError(t, err) + require.Equal(t, ref, toolsByName(again)["poly_search"].Ref) +} diff --git a/server/lib/webmcpclient/types.go b/server/lib/webmcpclient/types.go index 9f2bcebe..1e798c18 100644 --- a/server/lib/webmcpclient/types.go +++ b/server/lib/webmcpclient/types.go @@ -17,7 +17,14 @@ type Tool struct { InputSchema map[string]any Annotations *Annotations CustomID string - Source ToolSource + // Polyfill tools were read from a page-JavaScript navigator.modelContext + // polyfill rather than the native registry. Title, OutputSchema, and + // Hints are only populated for them. + Polyfill bool + Title string + OutputSchema map[string]any + Hints map[string]bool + Source ToolSource } type ToolSource struct { @@ -59,6 +66,18 @@ type registeredTool struct { customID string frameID string declarative bool + polyfill bool + rootFrame bool + title string + outputSchema map[string]any + hints map[string]bool +} + +func (t *registeredTool) key() string { + if t.polyfill { + return polyfillToolKey(t.sessionID, t.frameID, t.name) + } + return toolKey(t.sessionID, t.frameID, t.name) } type toolEvent struct { diff --git a/server/openapi.yaml b/server/openapi.yaml index 7cc3e8e7..dbfc9727 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1361,7 +1361,10 @@ paths: summary: Discover WebMCP tools across the browser description: | Returns a snapshot of WebMCP tools registered by every open tab and by embedded - content within those tabs. + content within those tabs. Tools that a page registers through a JavaScript + `navigator.modelContext` polyfill instead of the native `document.modelContext` + registry are included and marked with `source.polyfill`; when a page registers the + same name both ways, the native registration is listed. On the first request, Kernel starts monitoring the browser. Tabs and windows that are already open are included, and tabs, windows, and embedded content opened later are tracked automatically. @@ -6548,6 +6551,11 @@ components: description: Embedded frame that registered the tool, or null when the top-level page registered it. custom: $ref: "#/components/schemas/WebMCPCustomToolSource" + polyfill: + type: boolean + description: | + True when the page registered this tool through a JavaScript `navigator.modelContext` + polyfill rather than the native `document.modelContext` registry; omitted otherwise. WebMCPToolMetadata: type: object description: | diff --git a/server/runtime/webmcp-polyfill.test.ts b/server/runtime/webmcp-polyfill.test.ts new file mode 100644 index 00000000..e7efbf31 --- /dev/null +++ b/server/runtime/webmcp-polyfill.test.ts @@ -0,0 +1,213 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import test from 'node:test'; +import vm from 'node:vm'; + +// The page-side reader that lib/webmcpclient evaluates in a frame's main world. +const source = readFileSync(new URL('../lib/webmcpclient/polyfill_page.js', import.meta.url), 'utf8'); + +interface PageScript { + list(window: unknown): Promise<{tools: unknown[]} | null>; + invoke(window: unknown, name: string, input: Record): Promise<{ok: boolean; output?: string | null; error?: string}>; +} + +// Results come from the vm realm; strip prototypes before comparing. +function plain(value: T): T { + return JSON.parse(JSON.stringify(value)); +} + +function pageScript(): PageScript { + const script = vm.runInNewContext(source, {}) as PageScript; + return { + list: async (window) => plain(await script.list(window)), + invoke: async (window, name, input) => plain(await script.invoke(window, name, input)), + }; +} + +type Tool = {name: string; description?: string; inputSchema?: unknown; execute?: (input: unknown) => unknown} & Record; + +// Mirrors the polyfill shape that sites ship before Chromium exposed +// document.modelContext: a registry object plus listTools/callTool helpers. +function sitePolyfill(): Record { + const registry: Record = {}; + return { + _registeredTools: registry, + registerTool(tool: Tool, options?: {signal?: AbortSignal}) { + registry[tool.name] = { + name: tool.name, + title: tool.title, + description: tool.description, + inputSchema: tool.inputSchema ?? tool.parameters, + execute: tool.execute, + }; + options?.signal?.addEventListener('abort', () => delete registry[tool.name], {once: true}); + }, + unregisterTool(name: string) { + delete registry[name]; + }, + async callTool(name: string, input: unknown) { + const tool = registry[name]; + if (!tool) throw new Error(`Tool not found: ${name}`); + return tool.execute?.(input); + }, + listTools: () => Object.values(registry).map((tool) => ({name: tool.name, description: tool.description, inputSchema: tool.inputSchema})), + }; +} + +function fakeWindow(modelContext: unknown, documentModelContext?: unknown): Record { + return {navigator: {modelContext}, document: {modelContext: documentModelContext}}; +} + +test('lists and invokes tools from a site polyfill on navigator.modelContext', async () => { + const polyfill = sitePolyfill() as ReturnType & {registerTool: (tool: Tool) => void}; + polyfill.registerTool({ + name: 'search_items', + description: 'Search the catalog.', + inputSchema: {type: 'object', properties: {query: {type: 'string'}}, required: ['query']}, + execute: async (input) => ({results: [`match for ${(input as {query: string}).query}`]}), + }); + polyfill.registerTool({ + name: 'legacy_params', + description: 'Uses the pre-standard parameters alias.', + parameters: {type: 'object', properties: {}}, + execute: async () => undefined, + }); + polyfill.registerTool({name: 'broken', description: 'Throws.', inputSchema: {type: 'object'}, execute: async () => { + throw new Error('page rejected the call'); + }}); + const window = fakeWindow(polyfill); + const script = pageScript(); + + const listed = await script.list(window); + assert.deepEqual(listed, { + tools: [ + {name: 'search_items', description: 'Search the catalog.', inputSchema: {type: 'object', properties: {query: {type: 'string'}}, required: ['query']}}, + {name: 'legacy_params', description: 'Uses the pre-standard parameters alias.', inputSchema: {type: 'object', properties: {}}}, + {name: 'broken', description: 'Throws.', inputSchema: {type: 'object'}}, + ], + }); + + const invoked = await script.invoke(window, 'search_items', {query: 'lamp'}); + assert.deepEqual(invoked, {ok: true, output: JSON.stringify({results: ['match for lamp']})}); + assert.deepEqual(await script.invoke(window, 'legacy_params', {}), {ok: true, output: null}); + assert.deepEqual(await script.invoke(window, 'broken', {}), {ok: false, error: 'page rejected the call'}); + assert.deepEqual(await script.invoke(window, 'missing', {}), {ok: false, error: 'Tool not found: missing'}); + + (polyfill.unregisterTool as (name: string) => void)('search_items'); + assert.equal((await script.list(window))?.tools.length, 2); +}); + +test('supports MCP-style polyfills that take callTool({name, arguments})', async () => { + const tools = new Map(); + tools.set('add_to_cart', { + name: 'add_to_cart', + title: 'Add to cart', + description: 'Add an item.', + inputSchema: {type: 'object', properties: {sku: {type: 'string'}}}, + outputSchema: {type: 'object'}, + annotations: {readOnlyHint: false, destructiveHint: true, ignored: 'x'}, + execute: async () => ({content: [{type: 'text', text: 'added'}]}), + }); + const polyfill = { + _tools: tools, + listTools: () => [...tools.values()].map(({execute: _execute, ...metadata}) => metadata), + async callTool(params: {name: string; arguments: unknown}) { + const tool = tools.get(params.name); + if (!tool) throw new Error(`unknown tool ${params.name}`); + return tool.execute?.(params.arguments); + }, + }; + const script = pageScript(); + const window = fakeWindow(polyfill); + + const listed = await script.list(window); + assert.deepEqual(listed?.tools, [{ + name: 'add_to_cart', + title: 'Add to cart', + description: 'Add an item.', + inputSchema: {type: 'object', properties: {sku: {type: 'string'}}}, + outputSchema: {type: 'object'}, + annotations: {readOnlyHint: false, destructiveHint: true}, + }]); + assert.deepEqual(await script.invoke(window, 'add_to_cart', {sku: '1'}), { + ok: true, + output: JSON.stringify({content: [{type: 'text', text: 'added'}]}), + }); +}); + +test('falls back to the registry when the polyfill has no list or call helpers', async () => { + const registry = new Map(); + registry.set('get_context', {name: 'get_context', description: 'Context.', inputSchema: {type: 'object'}, execute: async () => 'ctx'}); + const polyfill = {registerTool() {}, unregisterTool() {}, provideContext() {}, clearContext() {}}; + const script = pageScript(); + const window = {...fakeWindow(polyfill), __webmcp: {tools: registry}}; + + assert.deepEqual(await script.list(window), {tools: [{name: 'get_context', description: 'Context.', inputSchema: {type: 'object'}}]}); + assert.deepEqual(await script.invoke(window, 'get_context', {}), {ok: true, output: '"ctx"'}); + assert.deepEqual(await script.invoke(fakeWindow(polyfill), 'get_context', {}), { + ok: false, + error: 'the modelContext polyfill does not expose a way to execute tools', + }); +}); + +test('ignores the native registry and pages without a polyfill', async () => { + const script = pageScript(); + class ModelContext { + get [Symbol.toStringTag]() { + return 'ModelContext'; + } + async getTools() { + return [{name: 'native_tool', description: 'Native.', inputSchema: {type: 'object'}}]; + } + } + const native = new ModelContext(); + + assert.equal(await script.list(fakeWindow(undefined)), null); + assert.equal(await script.list(fakeWindow(native, native)), null); + assert.equal(await script.list(fakeWindow(native)), null); + assert.equal(await script.list(fakeWindow('not an object')), null); + assert.equal(await script.list({navigator: {get modelContext() { + throw new Error('blocked'); + }}, document: {}}), null); + assert.deepEqual(await script.invoke(fakeWindow(undefined), 'x', {}), { + ok: false, + error: 'the page no longer exposes a modelContext polyfill', + }); +}); + +test('drops malformed entries and bounds output size', async () => { + const script = pageScript(); + const polyfill = { + listTools: () => [ + null, + {name: 42}, + {name: '', description: 'empty'}, + {name: 'dup', description: 'first', inputSchema: '{"type":"object"}'}, + {name: 'dup', description: 'second'}, + {name: 'bad_schema', inputSchema: 'not json', description: 7}, + {name: 'huge', description: 'x'.repeat(300 * 1024)}, + ], + async callTool(name: string, _input: unknown) { + if (name === 'large') return {payload: 'x'.repeat(1024 * 1024)}; + if (name === 'cyclic') { + const value: Record = {}; + value.self = value; + return value; + } + return {name}; + }, + }; + const window = fakeWindow(polyfill); + + const listed = await script.list(window); + assert.deepEqual(listed?.tools.slice(0, 2), [ + {name: 'dup', description: 'first', inputSchema: {type: 'object'}}, + {name: 'bad_schema', description: '', inputSchema: {}}, + ]); + assert.equal(listed?.tools.length, 3); + assert.equal((listed?.tools[2] as {description: string}).description.length, 64 * 1024); + assert.deepEqual(await script.invoke(window, 'large', {}), {ok: false, error: 'tool output exceeds 1 MiB'}); + const cyclic = await script.invoke(window, 'cyclic', {}); + assert.equal(cyclic.ok, false); + assert.match(cyclic.error ?? '', /not JSON-serializable/); +}); diff --git a/server/runtime/webmcp.ts b/server/runtime/webmcp.ts index 44bce957..02c26538 100644 --- a/server/runtime/webmcp.ts +++ b/server/runtime/webmcp.ts @@ -16,6 +16,7 @@ export interface WebMCPToolSource { page_url: string; frame: WebMCPToolFrame | null; custom?: WebMCPCustomToolSource; + polyfill?: boolean; } export interface WebMCPToolMetadata { From eccd1f672127696a4fbdab343847334dd536165d Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:15:50 +0000 Subject: [PATCH 2/5] Harden polyfill discovery and invocation Bound the serialized tool list a frame can return so a bloated page cannot exceed the CDP read limit, discard reads of a document that was replaced while it was being read, keep native-name precedence in one place so a shadowed polyfill tool keeps its reference, and let polyfill tools invoke even when the WebMCP domain could not be enabled for their session. Prefer a registry entry's own execute over callTool, accept listTools results wrapped in {tools}, and report unreadable page results as tool errors. Treat a failed call as a navigation when the document is released shortly after, drop the synthetic user gesture, and release remote object groups without blocking the caller. Resolve out-of-process iframe frame ownership from the live iframe session instead of the last reporting session, and clear ownership when a session detaches. --- server/e2e/e2e_webmcp_polyfill_test.go | 9 +- server/lib/browsersurface/frames.go | 5 + server/lib/browsersurface/tracker.go | 23 ++++- server/lib/webmcpclient/client.go | 15 ++- server/lib/webmcpclient/client_test.go | 9 +- server/lib/webmcpclient/manager.go | 8 +- server/lib/webmcpclient/polyfill.go | 118 ++++++++++++++--------- server/lib/webmcpclient/polyfill_page.js | 31 +++--- server/lib/webmcpclient/polyfill_test.go | 89 +++++++++++++++-- 9 files changed, 229 insertions(+), 78 deletions(-) diff --git a/server/e2e/e2e_webmcp_polyfill_test.go b/server/e2e/e2e_webmcp_polyfill_test.go index 543aba26..6a61814b 100644 --- a/server/e2e/e2e_webmcp_polyfill_test.go +++ b/server/e2e/e2e_webmcp_polyfill_test.go @@ -125,12 +125,19 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. // A tool that navigates its document completes like a native one and the // old registrations disappear with the document. + tabID := search.Source.TabId navigated := invoke(tools["navigate_away"].ToolRef, map[string]any{}) require.Equal(t, http.StatusOK, navigated.StatusCode(), "%s", navigated.Body) require.Equal(t, instanceoapi.WebMCPInvocationResultStatusCompleted, navigated.JSON200.Status) require.Equal(t, []any{}, navigated.JSON200.Output) require.EventuallyWithT(t, func(collect *assert.CollectT) { - assert.Empty(collect, toolsByName(collect)) + rsp, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, rsp.StatusCode(), "%s", rsp.Body) || rsp.JSON200 == nil { + return + } + for _, tool := range rsp.JSON200.Tools { + assert.NotEqual(collect, tabID, tool.Source.TabId, "%s survived navigation", tool.Tool.Name) + } }, 10*time.Second, 250*time.Millisecond) stale := invoke(search.ToolRef, map[string]any{"query": "x"}) require.Equal(t, http.StatusNotFound, stale.StatusCode(), "%s", stale.Body) diff --git a/server/lib/browsersurface/frames.go b/server/lib/browsersurface/frames.go index 279260a5..233fe931 100644 --- a/server/lib/browsersurface/frames.go +++ b/server/lib/browsersurface/frames.go @@ -349,6 +349,11 @@ func (t *Tracker) removeSessionLocked(sessionID string) []string { removed = append(removed, id) } } + for _, tracked := range t.frames { + if toRemove[tracked.sessionID] { + tracked.sessionID = "" + } + } sort.Strings(removed) return removed } diff --git a/server/lib/browsersurface/tracker.go b/server/lib/browsersurface/tracker.go index 7d29bdcb..f55d2a89 100644 --- a/server/lib/browsersurface/tracker.go +++ b/server/lib/browsersurface/tracker.go @@ -302,17 +302,34 @@ func (t *Tracker) Snapshot() Snapshot { func (t *Tracker) SessionFrames() []SessionFrame { t.stateMu.RLock() defer t.stateMu.RUnlock() + // An out-of-process iframe session owns its own frame regardless of which + // session reported the frame last. + iframeSessions := make(map[string]*session) + for _, sess := range t.sessions { + if sess.target.Type == "iframe" { + iframeSessions[sess.target.TargetID] = sess + } + } + owner := func(tracked *frame) *session { + if sess := iframeSessions[tracked.rawID]; sess != nil { + return sess + } + return t.sessions[tracked.sessionID] + } frames := make([]SessionFrame, 0, len(t.frames)) for _, tracked := range t.frames { - sess := t.sessions[tracked.sessionID] + sess := owner(tracked) if sess == nil || !sess.initialized || sess.tabID == 0 || sess.tabID != tracked.tabID { continue } - parent := t.frames[tracked.parentID] + root := true + if parent := t.frames[tracked.parentID]; parent != nil { + root = owner(parent) != sess + } frames = append(frames, SessionFrame{ SessionID: sess.id, FrameID: tracked.rawID, - Root: parent == nil || parent.sessionID != tracked.sessionID, + Root: root, URL: tracked.url, }) } diff --git a/server/lib/webmcpclient/client.go b/server/lib/webmcpclient/client.go index 8ff2f323..1647141b 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -42,6 +42,7 @@ type connection struct { waitingInvocations map[invocationKey]string abandonedInvocations map[invocationKey]time.Time polyfillWatches map[*polyfillWatch]struct{} + documentGeneration map[string]uint64 stateChangedCh chan struct{} logger *slog.Logger @@ -65,6 +66,7 @@ func newConnection(protocol *cdpclient.Client) *connection { waitingInvocations: make(map[invocationKey]string), abandonedInvocations: make(map[invocationKey]time.Time), polyfillWatches: make(map[*polyfillWatch]struct{}), + documentGeneration: make(map[string]uint64), stateChangedCh: make(chan struct{}, 1), logger: slog.Default(), eventsCancel: cancel, @@ -117,22 +119,20 @@ func (c *connection) eventLoop(events <-chan browsersurface.Event) { case browsersurface.EventDocumentChanged: c.stateMu.Lock() c.removeFrameToolsLocked(event.SessionID, event.FrameID) - c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { - return watch.sessionID == event.SessionID && watch.frameID == event.FrameID - }) + c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameInvalidated: c.stateMu.Lock() c.removeFrameToolsAcrossSessionsLocked(event.FrameID) - c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == event.FrameID }) + c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameRemoved: c.stateMu.Lock() c.abandonFrameInvocationsAcrossSessionsLocked(event.FrameID) c.removeFrameToolsAcrossSessionsLocked(event.FrameID) - c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == event.FrameID }) + c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventProtocol: @@ -522,6 +522,11 @@ func (c *connection) removeSession(sessionID string) { delete(c.enabledSessions, sessionID) delete(c.toolLimitWarned, sessionID) c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.sessionID == sessionID }) + for _, tool := range c.tools { + if tool.sessionID == sessionID { + c.documentGeneration[tool.frameID]++ + } + } for key := range c.waitingInvocations { if key.sessionID == sessionID { c.abandonInvocationLocked(key) diff --git a/server/lib/webmcpclient/client_test.go b/server/lib/webmcpclient/client_test.go index 65bd205c..d9918075 100644 --- a/server/lib/webmcpclient/client_test.go +++ b/server/lib/webmcpclient/client_test.go @@ -73,14 +73,16 @@ type fakeCDP struct { polyfillTools map[string][]map[string]any polyfillInvoke func(windowID, name string, input map[string]any) map[string]any polyfillInvokeError string - polyfillInvokeDelay time.Duration + polyfillInvokeHang bool + polyfillNavigateDuringList bool polyfillInvocations []polyfillInvocation + methods map[string]int write func(any) } func newFakeCDP(t *testing.T, omitResponse bool) *fakeCDP { t.Helper() - fake := &fakeCDP{t: t, enabledSessions: make(map[string]int), omitResponse: omitResponse, toolCount: 1} + fake := &fakeCDP{t: t, enabledSessions: make(map[string]int), methods: make(map[string]int), omitResponse: omitResponse, toolCount: 1} fake.server = httptest.NewServer(http.HandlerFunc(fake.serve)) fake.url = "ws" + strings.TrimPrefix(fake.server.URL, "http") t.Cleanup(fake.server.Close) @@ -143,6 +145,9 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) { respond := func(result any) { write(map[string]any{"id": request.ID, "result": result}) } + f.mu.Lock() + f.methods[request.Method]++ + f.mu.Unlock() switch request.Method { case "Target.setDiscoverTargets": respond(map[string]any{}) diff --git a/server/lib/webmcpclient/manager.go b/server/lib/webmcpclient/manager.go index d0dc9459..9f0184eb 100644 --- a/server/lib/webmcpclient/manager.go +++ b/server/lib/webmcpclient/manager.go @@ -63,7 +63,13 @@ func (m *Manager) CustomTool(ctx context.Context, toolRef string) (string, strin conn.stateMu.RLock() defer conn.stateMu.RUnlock() tool, ok := conn.tools[toolRef] - if !ok || !conn.enabledSessions[tool.sessionID] { + if !ok { + return "", "", ErrToolNotFound + } + if tool.polyfill { + return "", "", nil + } + if !conn.enabledSessions[tool.sessionID] { return "", "", ErrToolNotFound } if tool.customID == "" { diff --git a/server/lib/webmcpclient/polyfill.go b/server/lib/webmcpclient/polyfill.go index d2a4b420..8d7ffa8d 100644 --- a/server/lib/webmcpclient/polyfill.go +++ b/server/lib/webmcpclient/polyfill.go @@ -28,6 +28,7 @@ const ( polyfillSyncTimeout = 2 * time.Second polyfillSyncConcurrency = 8 polyfillReleaseTimeout = time.Second + polyfillNavigationGrace = 500 * time.Millisecond ) type polyfillToolMetadata struct { @@ -46,9 +47,10 @@ type polyfillInvokeResult struct { } type polyfillFrameTools struct { - frame browsersurface.SessionFrame - tools []polyfillToolMetadata - read bool + frame browsersurface.SessionFrame + generation uint64 + tools []polyfillToolMetadata + read bool } func polyfillToolKey(sessionID, frameID, name string) string { @@ -62,6 +64,14 @@ func polyfillFrameURL(url string) bool { return !strings.HasPrefix(url, "chrome") && !strings.HasPrefix(url, "devtools://") } +// documentGoneLocked records that a frame's document was replaced or removed +// so that in-flight reads of the old document are discarded and invocations +// running in it are released. +func (c *connection) documentGoneLocked(frameID string) { + c.documentGeneration[frameID]++ + c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == frameID }) +} + // syncPolyfillTools reads polyfill-registered tools from every tracked frame // and reconciles them with the registry. A frame that cannot be read keeps its // previous entries; surface events remove them when the document goes away. @@ -74,6 +84,12 @@ func (c *connection) syncPolyfillTools(ctx context.Context) { defer cancel() results := make([]polyfillFrameTools, len(frames)) + c.stateMu.RLock() + for i, frame := range frames { + results[i].generation = c.documentGeneration[frame.FrameID] + } + c.stateMu.RUnlock() + var wg sync.WaitGroup semaphore := make(chan struct{}, polyfillSyncConcurrency) for i, frame := range frames { @@ -89,7 +105,9 @@ func (c *connection) syncPolyfillTools(ctx context.Context) { if err != nil { return } - results[i] = polyfillFrameTools{frame: frame, tools: tools, read: true} + results[i].frame = frame + results[i].tools = tools + results[i].read = true }(i, frame) } wg.Wait() @@ -97,14 +115,15 @@ func (c *connection) syncPolyfillTools(ctx context.Context) { c.stateMu.Lock() defer c.stateMu.Unlock() for _, result := range results { - if result.read { + // A document replaced while it was being read is stale. + if result.read && c.documentGeneration[result.frame.FrameID] == result.generation { c.applyPolyfillToolsLocked(result.frame, result.tools) } } } func (c *connection) readPolyfillTools(ctx context.Context, frame browsersurface.SessionFrame) ([]polyfillToolMetadata, error) { - raw, err := c.callPolyfill(ctx, frame, "list", nil, false) + raw, err := c.callPolyfill(ctx, frame, "list", nil) if err != nil { return nil, err } @@ -120,33 +139,28 @@ func (c *connection) readPolyfillTools(ctx context.Context, frame browsersurface return result.Tools, nil } +// applyPolyfillToolsLocked replaces the frame's polyfill registrations with +// the tools just read. Native tools of the same name take precedence when the +// snapshot is built, so a shadowed polyfill tool keeps its reference. func (c *connection) applyPolyfillToolsLocked(frame browsersurface.SessionFrame, tools []polyfillToolMetadata) { if !c.surface.SessionExists(frame.SessionID) { return } - native := make(map[string]bool) tracked := 0 for _, existing := range c.tools { - if existing.sessionID != frame.SessionID { - continue - } - tracked++ - if !existing.polyfill && existing.customID == "" && existing.frameID == frame.FrameID { - native[existing.name] = true + if existing.sessionID == frame.SessionID { + tracked++ } } - desired := make(map[string]polyfillToolMetadata, len(tools)) + desired := make(map[string]bool, len(tools)) for _, tool := range tools { - if !native[tool.Name] { - desired[tool.Name] = tool - } + desired[tool.Name] = true } - for ref, existing := range c.tools { if !existing.polyfill || existing.sessionID != frame.SessionID || existing.frameID != frame.FrameID { continue } - if _, keep := desired[existing.name]; keep { + if desired[existing.name] { continue } delete(c.toolRefs, existing.key()) @@ -154,9 +168,6 @@ func (c *connection) applyPolyfillToolsLocked(frame browsersurface.SessionFrame, tracked-- } for _, tool := range tools { - if _, keep := desired[tool.Name]; !keep { - continue - } key := polyfillToolKey(frame.SessionID, frame.FrameID, tool.Name) ref := c.toolRefs[key] if ref == "" { @@ -221,10 +232,10 @@ func (c *connection) invokePolyfill(ctx context.Context, tool *registeredTool, i frame := browsersurface.SessionFrame{SessionID: tool.sessionID, FrameID: tool.frameID, Root: tool.rootFrame} invocation := InvocationResult{InvocationID: cuid2.Generate()} - // Chromium never answers a call whose document navigates before the - // returned promise settles. The native WebMCP domain reports such an - // invocation as completed with empty output, so watch the document and do - // the same. + // A document that navigates before the returned promise settles either + // never answers the call or fails it as the context goes away. The native + // WebMCP domain reports such an invocation as completed with empty output, + // so watch the document and do the same. watch, stopWatching := c.watchPolyfillDocument(tool.sessionID, tool.frameID) defer stopWatching() callCtx, cancelCall := context.WithCancel(ctx) @@ -235,33 +246,47 @@ func (c *connection) invokePolyfill(ctx context.Context, tool *registeredTool, i } results := make(chan callResult, 1) go func() { - raw, err := c.callPolyfill(callCtx, frame, "invoke", []any{tool.name, input}, true) + raw, err := c.callPolyfill(callCtx, frame, "invoke", []any{tool.name, input}) results <- callResult{raw: raw, err: err} }() + navigated := func() (InvocationResult, error) { + invocation.Status = "completed" + invocation.Output = []any{} + return invocation, nil + } var raw json.RawMessage select { - case result := <-results: + case <-watch.released: + cancelCall() + result := <-results + if result.err != nil { + return navigated() + } raw = result.raw + case result := <-results: if result.err != nil { var notDispatched *polyfillNotDispatchedError if errors.As(result.err, ¬Dispatched) && ctx.Err() == nil { return InvocationResult{}, ErrToolNotFound } + // The navigation that failed the call may still be settling. + grace := time.NewTimer(polyfillNavigationGrace) + defer grace.Stop() + select { + case <-watch.released: + return navigated() + case <-grace.C: + case <-ctx.Done(): + } return invocation, ErrOutcomeUnknown } - case <-watch.released: - cancelCall() - result := <-results - if result.err != nil { - invocation.Status = "completed" - invocation.Output = []any{} - return invocation, nil - } raw = result.raw } var result polyfillInvokeResult if err := json.Unmarshal(raw, &result); err != nil { - return invocation, fmt.Errorf("WebMCP: decode polyfill invocation: %w", err) + invocation.Status = "error" + invocation.ErrorText = "the page returned an unreadable result" + return invocation, nil } if !result.OK { invocation.Status = "error" @@ -270,7 +295,9 @@ func (c *connection) invokePolyfill(ctx context.Context, tool *registeredTool, i } if result.Output != nil { if err := json.Unmarshal([]byte(*result.Output), &invocation.Output); err != nil { - return invocation, fmt.Errorf("WebMCP: decode polyfill output: %w", err) + invocation.Status = "error" + invocation.ErrorText = "the page returned an unreadable result" + return invocation, nil } } invocation.Status = "completed" @@ -288,13 +315,9 @@ func (e *polyfillNotDispatchedError) Unwrap() error { return e.cause } // callPolyfill runs one entry point of polyfill_page.js in the frame's main // world and returns the JSON value it produced, or nil for null. -func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.SessionFrame, method string, args []any, userGesture bool) (json.RawMessage, error) { +func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.SessionFrame, method string, args []any) (json.RawMessage, error) { group := "kernel-webmcp-polyfill-" + cuid2.Generate() - defer func() { - releaseCtx, cancel := context.WithTimeout(context.Background(), polyfillReleaseTimeout) - defer cancel() - _, _ = c.surface.Send(releaseCtx, "Runtime.releaseObjectGroup", map[string]any{"objectGroup": group}, frame.SessionID) - }() + defer func() { go c.releaseObjectGroup(frame.SessionID, group) }() windowID, err := c.frameWindow(ctx, frame, group) if err != nil { @@ -310,7 +333,6 @@ func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.Sess "arguments": arguments, "awaitPromise": true, "returnByValue": true, - "userGesture": userGesture, "objectGroup": group, }, frame.SessionID) if err != nil { @@ -343,6 +365,12 @@ func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.Sess return result.Result.Value, nil } +func (c *connection) releaseObjectGroup(sessionID, group string) { + ctx, cancel := context.WithTimeout(context.Background(), polyfillReleaseTimeout) + defer cancel() + _, _ = c.surface.Send(ctx, "Runtime.releaseObjectGroup", map[string]any{"objectGroup": group}, sessionID) +} + // frameWindow returns a remote object handle for the frame's Window. Root // frames evaluate directly in their session; same-process child frames are // reached through their owner element's contentWindow, which only succeeds diff --git a/server/lib/webmcpclient/polyfill_page.js b/server/lib/webmcpclient/polyfill_page.js index c7ccccc8..36d75dfa 100644 --- a/server/lib/webmcpclient/polyfill_page.js +++ b/server/lib/webmcpclient/polyfill_page.js @@ -6,6 +6,7 @@ const MAX_TOOLS = 256; const MAX_TEXT = 64 * 1024; const MAX_TOOL_BYTES = 256 * 1024; + const MAX_LIST_BYTES = 1024 * 1024; const MAX_OUTPUT_BYTES = 1024 * 1024; const HINTS = [ 'readOnlyHint', @@ -80,7 +81,7 @@ try { candidates.push(window.__webmcp && window.__webmcp.tools); } catch { - // Same as above. + // Not a registry either. } for (const registry of candidates) { try { @@ -104,8 +105,9 @@ } if (!isFunction(fn)) continue; try { - const tools = await fn.call(context); - if (Array.isArray(tools)) return tools; + const result = await fn.call(context); + if (Array.isArray(result)) return result; + if (isObject(result) && Array.isArray(result.tools)) return result.tools; } catch { // Fall through to the next source. } @@ -144,13 +146,15 @@ if (Object.keys(annotations).length) entry.annotations = annotations; } const serialized = JSON.stringify(entry); - if (serialized.length > MAX_TOOL_BYTES) return null; - return JSON.parse(serialized); + if (typeof serialized !== 'string' || serialized.length > MAX_TOOL_BYTES) return null; + return {entry: JSON.parse(serialized), bytes: serialized.length}; } catch { return null; } } + // The listing travels back over the CDP connection, so the total stays well + // under the connection's read limit. async function list(window) { const context = polyfill(window); if (!context) return null; @@ -158,12 +162,15 @@ if (!Array.isArray(tools)) return null; const seen = new Set(); const result = []; + let bytes = 0; for (const tool of tools) { if (result.length >= MAX_TOOLS) break; - const entry = metadata(tool); - if (!entry || seen.has(entry.name)) continue; - seen.add(entry.name); - result.push(entry); + const item = metadata(tool); + if (!item || seen.has(item.entry.name)) continue; + if (bytes + item.bytes > MAX_LIST_BYTES) break; + bytes += item.bytes; + seen.add(item.entry.name); + result.push(item.entry); } return {tools: result}; } @@ -182,6 +189,9 @@ } async function execute(context, window, name, input) { + // The registry entry's own execute is unambiguous; callTool's shape is not. + const entry = registryEntry(context, window, name); + if (entry) return entry.execute(input); let callTool; try { callTool = context.callTool; @@ -193,8 +203,6 @@ if (callTool.length >= 2) return callTool.call(context, name, input); return callTool.call(context, {name, arguments: input}); } - const entry = registryEntry(context, window, name); - if (entry) return entry.execute(input); let executeTool; try { executeTool = context.executeTool; @@ -222,6 +230,7 @@ throw new Error(`tool output is not JSON-serializable: ${message(error)}`); } if (serialized === undefined) return null; + if (typeof serialized !== 'string') throw new Error('tool output is not JSON-serializable'); if (serialized.length > MAX_OUTPUT_BYTES) throw new Error('tool output exceeds 1 MiB'); return serialized; } diff --git a/server/lib/webmcpclient/polyfill_test.go b/server/lib/webmcpclient/polyfill_test.go index b7b20706..c486ca38 100644 --- a/server/lib/webmcpclient/polyfill_test.go +++ b/server/lib/webmcpclient/polyfill_test.go @@ -3,6 +3,7 @@ package webmcpclient import ( "context" "encoding/json" + "fmt" "strings" "testing" "time" @@ -37,7 +38,9 @@ func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write fu tools := f.polyfillTools[params.ObjectID] invoke := f.polyfillInvoke invokeError := f.polyfillInvokeError - invokeDelay := f.polyfillInvokeDelay + invokeHang := f.polyfillInvokeHang + navigateDuringList := f.polyfillNavigateDuringList && params.ObjectID == "window:page-session" + f.polyfillNavigateDuringList = f.polyfillNavigateDuringList && !navigateDuringList f.mu.Unlock() switch request.Method { @@ -62,14 +65,23 @@ func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write fu case strings.Contains(params.FunctionDeclaration, "contentWindow"): respond(map[string]any{"result": map[string]any{"type": "object", "className": "Window", "objectId": "window:" + request.SessionID + ":page-child"}}) case strings.Contains(params.FunctionDeclaration, ").list("): + if navigateDuringList { + // The document is replaced while its tools are being read. + write(map[string]any{ + "method": "Page.frameNavigated", "sessionId": request.SessionID, + "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, + }) + time.Sleep(200 * time.Millisecond) + } if tools == nil { respond(map[string]any{"result": map[string]any{"type": "object", "subtype": "null", "value": nil}}) return } respond(map[string]any{"result": map[string]any{"type": "object", "value": map[string]any{"tools": tools}}}) case strings.Contains(params.FunctionDeclaration, ").invoke("): - if invokeDelay > 0 { - time.Sleep(invokeDelay) + if invokeHang { + // Chromium never answers once the document navigates away. + return } if invokeError != "" { fail(invokeError) @@ -77,9 +89,12 @@ func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write fu } var name string var input map[string]any - require.Len(f.t, params.Arguments, 2) - require.NoError(f.t, json.Unmarshal(params.Arguments[0].Value, &name)) - require.NoError(f.t, json.Unmarshal(params.Arguments[1].Value, &input)) + if len(params.Arguments) != 2 || + json.Unmarshal(params.Arguments[0].Value, &name) != nil || + json.Unmarshal(params.Arguments[1].Value, &input) != nil { + fail("unexpected invoke arguments") + return + } f.mu.Lock() f.polyfillInvocations = append(f.polyfillInvocations, polyfillInvocation{windowID: params.ObjectID, name: name, input: input}) f.mu.Unlock() @@ -107,6 +122,9 @@ func newPolyfillFakeCDP(t *testing.T) *fakeCDP { "window:page-session:page-child": { {"name": "child_poly", "description": "child", "inputSchema": map[string]any{"type": "object"}}, }, + "window:iframe-session": { + {"name": "payment_poly", "description": "payment", "inputSchema": map[string]any{"type": "object"}}, + }, } fake.polyfillInvoke = func(_ string, name string, input map[string]any) map[string]any { if name != "poly_search" { @@ -133,7 +151,7 @@ func TestPolyfillToolsAreDiscoveredNextToNativeTools(t *testing.T) { tools, err := manager.Tools(context.Background()) require.NoError(t, err) - require.Len(t, tools, 6) + require.Len(t, tools, 7) byName := toolsByName(tools) // The native registration wins over the polyfill copy of the same name. @@ -157,6 +175,19 @@ func TestPolyfillToolsAreDiscoveredNextToNativeTools(t *testing.T) { require.NotNil(t, child.Source.Frame) require.Equal(t, "https://merchant.example/child", child.Source.Frame.URL) + // An out-of-process iframe is read through its own session. + payment := byName["payment_poly"] + require.True(t, payment.Polyfill) + require.NotNil(t, payment.Source.Frame) + require.Equal(t, "https://payments.example/element", payment.Source.Frame.URL) + + // Discovery never enables the Runtime or DOM domains, which pages can detect. + fake.mu.Lock() + require.Zero(t, fake.methods["Runtime.enable"]) + require.Zero(t, fake.methods["DOM.enable"]) + require.Positive(t, fake.methods["Runtime.callFunctionOn"]) + fake.mu.Unlock() + customID, targetID, err := manager.CustomTool(context.Background(), search.Ref) require.NoError(t, err) require.Empty(t, customID) @@ -237,9 +268,8 @@ func TestPolyfillInvocationThatNavigatesCompletesLikeNativeTools(t *testing.T) { require.NoError(t, err) ref := toolsByName(tools)["poly_search"].Ref - // Chromium never answers the call once the document navigates away. fake.mu.Lock() - fake.polyfillInvokeDelay = 5 * time.Second + fake.polyfillInvokeHang = true fake.mu.Unlock() go func() { time.Sleep(100 * time.Millisecond) @@ -275,7 +305,7 @@ func TestPolyfillInvocationProtocolFailureHasUnknownOutcome(t *testing.T) { func TestPolyfillInvocationTimeoutHasUnknownOutcome(t *testing.T) { fake := newPolyfillFakeCDP(t) - fake.polyfillInvokeDelay = 300 * time.Millisecond + fake.polyfillInvokeHang = true manager := NewManager(staticUpstream{url: fake.url}) t.Cleanup(func() { _ = manager.Close() }) tools, err := manager.Tools(context.Background()) @@ -309,3 +339,42 @@ func TestPolyfillInvocationReportsMissingDocument(t *testing.T) { require.NoError(t, err) require.Equal(t, ref, toolsByName(again)["poly_search"].Ref) } + +func TestPolyfillToolsShareTheSessionLimit(t *testing.T) { + fake := newPolyfillFakeCDP(t) + oversized := make([]map[string]any, 0, maxToolsPerSession+10) + for i := range cap(oversized) { + oversized = append(oversized, map[string]any{ + "name": fmt.Sprintf("poly_%d", i), "description": "polyfill", "inputSchema": map[string]any{"type": "object"}, + }) + } + fake.polyfillTools["window:page-session"] = oversized + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + pageTools := 0 + for _, tool := range tools { + if tool.Source.TabID == 1 && tool.Source.Frame == nil { + pageTools++ + } + } + require.Equal(t, maxToolsPerSession, pageTools) +} + +func TestPolyfillReadOfReplacedDocumentIsDiscarded(t *testing.T) { + fake := newPolyfillFakeCDP(t) + fake.polyfillNavigateDuringList = true + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + require.NotContains(t, toolsByName(tools), "poly_search") + + // The next listing reads the new document. + tools, err = manager.Tools(context.Background()) + require.NoError(t, err) + require.Contains(t, toolsByName(tools), "poly_search") +} From 33b8e101f24e3925a6df0c31363884491e1e3591 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:41:46 +0000 Subject: [PATCH 3/5] Bridge polyfill tools into the native registry Instead of reading and invoking a navigator.modelContext polyfill from the Go client, copy its tools into the frame's native document.modelContext registry. The CDP WebMCP domain then lists, invokes, and removes them like any other page tool, so no separate source marker, metadata, or invocation path is needed. The bridge runs in the page's main world and is reachable only through a remote object handle held by the client. Each listing syncs it: new polyfill tools are registered, removed or changed ones are unregistered, and a name the page already registered natively keeps the native tool. --- server/cmd/api/api/webmcp.go | 37 - server/cmd/api/api/webmcp_test.go | 39 - server/e2e/e2e_webmcp_polyfill_test.go | 6 - server/lib/oapi/oapi.go | 1348 ++++++++++---------- server/lib/webmcpclient/client.go | 59 +- server/lib/webmcpclient/client_test.go | 17 +- server/lib/webmcpclient/manager.go | 12 +- server/lib/webmcpclient/polyfill.go | 406 ++---- server/lib/webmcpclient/polyfill_bridge.js | 281 ++++ server/lib/webmcpclient/polyfill_page.js | 249 ---- server/lib/webmcpclient/polyfill_test.go | 370 ++---- server/lib/webmcpclient/types.go | 21 +- server/openapi.yaml | 10 +- server/runtime/webmcp-polyfill.test.ts | 212 +-- server/runtime/webmcp.ts | 1 - 15 files changed, 1294 insertions(+), 1774 deletions(-) create mode 100644 server/lib/webmcpclient/polyfill_bridge.js delete mode 100644 server/lib/webmcpclient/polyfill_page.js diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index ada9a7aa..bed24802 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -62,14 +62,6 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Autosubmit: boolPointer(tool.Annotations.Autosubmit), } } - if tool.Polyfill { - metadata.Title = nonEmptyString(tool.Title) - if tool.OutputSchema != nil { - outputSchema := tool.OutputSchema - metadata.OutputSchema = &outputSchema - } - metadata.Annotations = polyfillAnnotations(tool.Hints) - } responseTool := oapi.WebMCPTool{ ToolRef: tool.Ref, Tool: metadata, @@ -86,9 +78,6 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Url: tool.Source.Frame.URL, } } - if tool.Polyfill { - responseTool.Source.Polyfill = boolPointer(true) - } if tool.CustomID != "" { definition, ok := customDefinitions[tool.CustomID] if !ok { @@ -115,32 +104,6 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT func boolPointer(value bool) *bool { return &value } -func polyfillAnnotations(hints map[string]bool) *oapi.WebMCPToolAnnotations { - if len(hints) == 0 { - return nil - } - annotations := &oapi.WebMCPToolAnnotations{} - for hint, value := range hints { - switch hint { - case "readOnlyHint": - annotations.ReadOnlyHint = boolPointer(value) - case "destructiveHint": - annotations.DestructiveHint = boolPointer(value) - case "idempotentHint": - annotations.IdempotentHint = boolPointer(value) - case "openWorldHint": - annotations.OpenWorldHint = boolPointer(value) - case "consequentialHint": - annotations.ConsequentialHint = boolPointer(value) - case "untrustedContentHint": - annotations.UntrustedContentHint = boolPointer(value) - case "autosubmit": - annotations.Autosubmit = boolPointer(value) - } - } - return annotations -} - func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWebMCPToolRequestObject) (oapi.InvokeWebMCPToolResponseObject, error) { if request.Body == nil { return oapi.InvokeWebMCPTool400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 240f64ee..22b062a4 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -85,45 +85,6 @@ func writeCustomToolsSnapshot(t *testing.T, manager *browserReplManager, tools [ require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), data, 0o600)) } -func TestGetWebMCPToolsMarksPolyfillTools(t *testing.T) { - client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ - Ref: "wmcp_poly", - Name: "search_items", - Title: "Search", - Description: "Search the catalog.", - InputSchema: map[string]any{"type": "object"}, - OutputSchema: map[string]any{"type": "object"}, - Hints: map[string]bool{"readOnlyHint": true, "destructiveHint": false}, - Polyfill: true, - Source: webmcpclient.ToolSource{ - WindowID: 1, - TabID: 1, - PageTitle: "Catalog", - PageURL: "https://shop.example/", - }, - }}} - service := &ApiService{webmcp: client} - - response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) - require.NoError(t, err) - body := response.(oapi.GetWebMCPTools200JSONResponse) - require.Len(t, body.Tools, 1) - tool := body.Tools[0] - require.NotNil(t, tool.Source.Polyfill) - require.True(t, *tool.Source.Polyfill) - require.Nil(t, tool.Source.Custom) - require.Nil(t, tool.Source.TargetId) - require.Equal(t, "Search", *tool.Tool.Title) - require.Equal(t, map[string]any{"type": "object"}, *tool.Tool.OutputSchema) - require.True(t, *tool.Tool.Annotations.ReadOnlyHint) - require.False(t, *tool.Tool.Annotations.DestructiveHint) - require.Nil(t, tool.Tool.Annotations.ConsequentialHint) - - encoded, err := json.Marshal(tool) - require.NoError(t, err) - require.Contains(t, string(encoded), `"polyfill":true`) -} - func TestGetWebMCPToolsRejectsUnreadableSnapshot(t *testing.T) { manager := newBrowserReplManager() writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{Id: "ct_abcdefghijklmnopqrstuvwx"}}) diff --git a/server/e2e/e2e_webmcp_polyfill_test.go b/server/e2e/e2e_webmcp_polyfill_test.go index 6a61814b..3223ca5f 100644 --- a/server/e2e/e2e_webmcp_polyfill_test.go +++ b/server/e2e/e2e_webmcp_polyfill_test.go @@ -60,8 +60,6 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. t.Logf("GET /webmcp/tools: %d tools on %s", len(tools), pageURL) search := tools["search_items"] - require.NotNil(t, search.Source.Polyfill) - require.True(t, *search.Source.Polyfill) require.Nil(t, search.Source.Frame) require.Nil(t, search.Source.Custom) require.Equal(t, "Search the catalog.", search.Tool.Description) @@ -69,11 +67,9 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. // The same name registered natively lists once, as the native tool. shared := tools["shared_name"] - require.Nil(t, shared.Source.Polyfill) require.Equal(t, "Native copy.", shared.Tool.Description) frameTool := tools["frame_tool"] - require.NotNil(t, frameTool.Source.Polyfill) require.NotNil(t, frameTool.Source.Frame) require.Equal(t, "about:srcdoc", frameTool.Source.Frame.Url) @@ -109,8 +105,6 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. failed := invoke(tools["failing_tool"].ToolRef, map[string]any{}) require.Equal(t, http.StatusOK, failed.StatusCode(), "%s", failed.Body) require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, failed.JSON200.Status) - require.NotNil(t, failed.JSON200.ErrorText) - require.Equal(t, "nothing to do", *failed.JSON200.ErrorText) // Unregistration through the polyfill drops the tool on the next listing, // and the surviving tool keeps its reference. diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 734554f3..e71c32e9 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -7006,10 +7006,6 @@ type WebMCPToolSource struct { // PageUrl Current URL of the top-level page with the fragment omitted. PageUrl string `json:"page_url"` - // Polyfill True when the page registered this tool through a JavaScript `navigator.modelContext` - // polyfill rather than the native `document.modelContext` registry; omitted otherwise. - Polyfill *bool `json:"polyfill,omitempty"` - // TabId Monotonically increasing identifier for the tab during the current browser process. TabId int `json:"tab_id"` @@ -25667,677 +25663,679 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "0+4mMfVi444J9T98+P8PAL9tCgY8tgMA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/webmcpclient/client.go b/server/lib/webmcpclient/client.go index 1647141b..57e8b5b0 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -41,8 +41,8 @@ type connection struct { invocations map[invocationKey]invocationResponse waitingInvocations map[invocationKey]string abandonedInvocations map[invocationKey]time.Time - polyfillWatches map[*polyfillWatch]struct{} - documentGeneration map[string]uint64 + polyfillBridges map[string]*polyfillBridge + polyfillSyncMu sync.Mutex stateChangedCh chan struct{} logger *slog.Logger @@ -65,8 +65,7 @@ func newConnection(protocol *cdpclient.Client) *connection { invocations: make(map[invocationKey]invocationResponse), waitingInvocations: make(map[invocationKey]string), abandonedInvocations: make(map[invocationKey]time.Time), - polyfillWatches: make(map[*polyfillWatch]struct{}), - documentGeneration: make(map[string]uint64), + polyfillBridges: make(map[string]*polyfillBridge), stateChangedCh: make(chan struct{}, 1), logger: slog.Default(), eventsCancel: cancel, @@ -119,20 +118,17 @@ func (c *connection) eventLoop(events <-chan browsersurface.Event) { case browsersurface.EventDocumentChanged: c.stateMu.Lock() c.removeFrameToolsLocked(event.SessionID, event.FrameID) - c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameInvalidated: c.stateMu.Lock() c.removeFrameToolsAcrossSessionsLocked(event.FrameID) - c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventFrameRemoved: c.stateMu.Lock() c.abandonFrameInvocationsAcrossSessionsLocked(event.FrameID) c.removeFrameToolsAcrossSessionsLocked(event.FrameID) - c.documentGoneLocked(event.FrameID) c.stateMu.Unlock() c.signalStateChanged() case browsersurface.EventProtocol: @@ -285,19 +281,8 @@ func (c *connection) addTools(sessionID string, tools []toolEvent) { func (c *connection) toolsSnapshot() []Tool { c.stateMu.RLock() defer c.stateMu.RUnlock() - // A name registered both natively and through a polyfill lists once, as - // the native tool. - native := make(map[string]bool) - for _, tool := range c.tools { - if !tool.polyfill && tool.customID == "" { - native[tool.key()] = true - } - } result := make([]Tool, 0, len(c.tools)) for _, tool := range c.tools { - if tool.polyfill && native[toolKey(tool.sessionID, tool.frameID, tool.name)] { - continue - } location, ok := c.surface.Resolve(tool.sessionID, tool.frameID) if !ok { continue @@ -313,17 +298,13 @@ func (c *connection) toolsSnapshot() []Tool { source.Frame = &ToolFrame{FrameID: location.Frame.ID, URL: location.Frame.URL} } result = append(result, Tool{ - Ref: tool.ref, - Name: tool.name, - Description: tool.description, - InputSchema: tool.inputSchema, - Annotations: tool.annotations, - CustomID: tool.customID, - Polyfill: tool.polyfill, - Title: tool.title, - OutputSchema: tool.outputSchema, - Hints: tool.hints, - Source: source, + Ref: tool.ref, + Name: tool.name, + Description: tool.description, + InputSchema: tool.inputSchema, + Annotations: tool.annotations, + CustomID: tool.customID, + Source: source, }) } sort.Slice(result, func(i, j int) bool { @@ -383,14 +364,6 @@ func (c *connection) invoke(ctx context.Context, toolRef string, input map[strin c.stateMu.RUnlock() return InvocationResult{}, ErrToolNotFound } - if tool.polyfill { - polyfillTool := *tool - c.stateMu.RUnlock() - if !c.surface.SessionExists(polyfillTool.sessionID) { - return InvocationResult{}, ErrToolNotFound - } - return c.invokePolyfill(ctx, &polyfillTool, input) - } sessionID, frameID, name := tool.sessionID, tool.frameID, tool.registeredName awaitingSubmission := tool.declarative && (tool.annotations == nil || !tool.annotations.Autosubmit) c.stateMu.RUnlock() @@ -521,12 +494,6 @@ func (c *connection) removeSession(sessionID string) { defer c.stateMu.Unlock() delete(c.enabledSessions, sessionID) delete(c.toolLimitWarned, sessionID) - c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.sessionID == sessionID }) - for _, tool := range c.tools { - if tool.sessionID == sessionID { - c.documentGeneration[tool.frameID]++ - } - } for key := range c.waitingInvocations { if key.sessionID == sessionID { c.abandonInvocationLocked(key) @@ -534,7 +501,7 @@ func (c *connection) removeSession(sessionID string) { } for ref, tool := range c.tools { if tool.sessionID == sessionID { - delete(c.toolRefs, tool.key()) + delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) delete(c.tools, ref) } } @@ -552,7 +519,7 @@ func (c *connection) abandonFrameInvocationsAcrossSessionsLocked(frameID string) func (c *connection) removeFrameToolsLocked(sessionID, frameID string) { for ref, tool := range c.tools { if tool.sessionID == sessionID && tool.frameID == frameID { - delete(c.toolRefs, tool.key()) + delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) delete(c.tools, ref) } } @@ -561,7 +528,7 @@ func (c *connection) removeFrameToolsLocked(sessionID, frameID string) { func (c *connection) removeFrameToolsAcrossSessionsLocked(frameID string) { for ref, tool := range c.tools { if tool.frameID == frameID { - delete(c.toolRefs, tool.key()) + delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.name)) delete(c.tools, ref) } } diff --git a/server/lib/webmcpclient/client_test.go b/server/lib/webmcpclient/client_test.go index d9918075..5115d5d6 100644 --- a/server/lib/webmcpclient/client_test.go +++ b/server/lib/webmcpclient/client_test.go @@ -47,7 +47,6 @@ type wireRequest struct { } type fakeCDP struct { - t *testing.T server *httptest.Server url string @@ -69,20 +68,20 @@ type fakeCDP struct { iframeOpen bool nestedFrameOpen bool childFrameOpen bool - windowUnavailable bool - polyfillTools map[string][]map[string]any - polyfillInvoke func(windowID, name string, input map[string]any) map[string]any - polyfillInvokeError string - polyfillInvokeHang bool - polyfillNavigateDuringList bool - polyfillInvocations []polyfillInvocation + polyfillTools map[string][]string + bridgedTools map[string]map[string]bool + staleBridges map[string]bool + bridgesCreated int methods map[string]int write func(any) } func newFakeCDP(t *testing.T, omitResponse bool) *fakeCDP { t.Helper() - fake := &fakeCDP{t: t, enabledSessions: make(map[string]int), methods: make(map[string]int), omitResponse: omitResponse, toolCount: 1} + fake := &fakeCDP{ + enabledSessions: make(map[string]int), methods: make(map[string]int), omitResponse: omitResponse, toolCount: 1, + bridgedTools: make(map[string]map[string]bool), staleBridges: make(map[string]bool), + } fake.server = httptest.NewServer(http.HandlerFunc(fake.serve)) fake.url = "ws" + strings.TrimPrefix(fake.server.URL, "http") t.Cleanup(fake.server.Close) diff --git a/server/lib/webmcpclient/manager.go b/server/lib/webmcpclient/manager.go index 9f0184eb..4941a907 100644 --- a/server/lib/webmcpclient/manager.go +++ b/server/lib/webmcpclient/manager.go @@ -51,7 +51,9 @@ func (m *Manager) Tools(ctx context.Context) ([]Tool, error) { if !conn.surface.HasTabs() { return nil, ErrNoPageTarget } - conn.syncPolyfillTools(ctx) + if conn.syncPolyfillTools(ctx) { + conn.waitForSettled(ctx) + } return conn.toolsSnapshot(), nil } @@ -63,13 +65,7 @@ func (m *Manager) CustomTool(ctx context.Context, toolRef string) (string, strin conn.stateMu.RLock() defer conn.stateMu.RUnlock() tool, ok := conn.tools[toolRef] - if !ok { - return "", "", ErrToolNotFound - } - if tool.polyfill { - return "", "", nil - } - if !conn.enabledSessions[tool.sessionID] { + if !ok || !conn.enabledSessions[tool.sessionID] { return "", "", ErrToolNotFound } if tool.customID == "" { diff --git a/server/lib/webmcpclient/polyfill.go b/server/lib/webmcpclient/polyfill.go index 8d7ffa8d..317e35e7 100644 --- a/server/lib/webmcpclient/polyfill.go +++ b/server/lib/webmcpclient/polyfill.go @@ -11,50 +11,35 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/browsersurface" + "github.com/kernel/kernel-images/server/lib/cdpclient" "github.com/nrednav/cuid2" ) // Sites that ship their own WebMCP tools before the browser exposes the // native registry install a polyfill on navigator.modelContext. Chromium // dropped that alias in favor of document.modelContext, so nothing such a -// polyfill registers reaches the CDP WebMCP domain. polyfill_page.js reads and -// invokes those tools from the page's main world without leaving anything -// behind on the page. +// polyfill registers reaches the CDP WebMCP domain. polyfill_bridge.js copies +// those tools into the frame's native registry, after which they are listed, +// invoked, and removed like any other page tool. // -//go:embed polyfill_page.js -var polyfillPageSource string +//go:embed polyfill_bridge.js +var polyfillBridgeSource string const ( polyfillSyncTimeout = 2 * time.Second polyfillSyncConcurrency = 8 polyfillReleaseTimeout = time.Second - polyfillNavigationGrace = 500 * time.Millisecond ) -type polyfillToolMetadata struct { - Name string `json:"name"` - Title string `json:"title"` - Description string `json:"description"` - InputSchema map[string]any `json:"inputSchema"` - OutputSchema map[string]any `json:"outputSchema"` - Annotations map[string]bool `json:"annotations"` +// polyfillBridge is a remote object handle for one frame's bridge. The handle +// lives in the frame's execution context and becomes invalid when it goes away. +type polyfillBridge struct { + objectID string + group string } -type polyfillInvokeResult struct { - OK bool `json:"ok"` - Output *string `json:"output"` - Error string `json:"error"` -} - -type polyfillFrameTools struct { - frame browsersurface.SessionFrame - generation uint64 - tools []polyfillToolMetadata - read bool -} - -func polyfillToolKey(sessionID, frameID, name string) string { - return toolKey(sessionID, frameID, name) + "\x00polyfill" +func polyfillBridgeKey(sessionID, frameID string) string { + return sessionID + "\x00" + frameID } // polyfillFrameURL excludes browser-internal documents; every web document, @@ -64,305 +49,130 @@ func polyfillFrameURL(url string) bool { return !strings.HasPrefix(url, "chrome") && !strings.HasPrefix(url, "devtools://") } -// documentGoneLocked records that a frame's document was replaced or removed -// so that in-flight reads of the old document are discarded and invocations -// running in it are released. -func (c *connection) documentGoneLocked(frameID string) { - c.documentGeneration[frameID]++ - c.releasePolyfillWatchesLocked(func(watch *polyfillWatch) bool { return watch.frameID == frameID }) -} - -// syncPolyfillTools reads polyfill-registered tools from every tracked frame -// and reconciles them with the registry. A frame that cannot be read keeps its -// previous entries; surface events remove them when the document goes away. -func (c *connection) syncPolyfillTools(ctx context.Context) { +// syncPolyfillTools brings every tracked frame's bridged registrations up to +// date with its polyfill and reports whether any native registry changed. +func (c *connection) syncPolyfillTools(ctx context.Context) bool { + // One bridge per document: concurrent listings would otherwise create two + // and leave the first one's registrations unmanaged. + c.polyfillSyncMu.Lock() + defer c.polyfillSyncMu.Unlock() frames := c.surface.SessionFrames() - if len(frames) == 0 { - return + live := make(map[string]bool, len(frames)) + for _, frame := range frames { + live[polyfillBridgeKey(frame.SessionID, frame.FrameID)] = true } - ctx, cancel := context.WithTimeout(ctx, polyfillSyncTimeout) - defer cancel() - - results := make([]polyfillFrameTools, len(frames)) - c.stateMu.RLock() - for i, frame := range frames { - results[i].generation = c.documentGeneration[frame.FrameID] + c.stateMu.Lock() + for key := range c.polyfillBridges { + if !live[key] { + delete(c.polyfillBridges, key) + } } - c.stateMu.RUnlock() + c.stateMu.Unlock() - var wg sync.WaitGroup + ctx, cancel := context.WithTimeout(ctx, polyfillSyncTimeout) + defer cancel() + var ( + wg sync.WaitGroup + mu sync.Mutex + changed bool + ) semaphore := make(chan struct{}, polyfillSyncConcurrency) - for i, frame := range frames { + for _, frame := range frames { if !polyfillFrameURL(frame.URL) { continue } wg.Add(1) - go func(i int, frame browsersurface.SessionFrame) { + go func(frame browsersurface.SessionFrame) { defer wg.Done() semaphore <- struct{}{} defer func() { <-semaphore }() - tools, err := c.readPolyfillTools(ctx, frame) - if err != nil { - return + if c.syncPolyfillFrame(ctx, frame) { + mu.Lock() + changed = true + mu.Unlock() } - results[i].frame = frame - results[i].tools = tools - results[i].read = true - }(i, frame) + }(frame) } wg.Wait() - - c.stateMu.Lock() - defer c.stateMu.Unlock() - for _, result := range results { - // A document replaced while it was being read is stale. - if result.read && c.documentGeneration[result.frame.FrameID] == result.generation { - c.applyPolyfillToolsLocked(result.frame, result.tools) - } - } -} - -func (c *connection) readPolyfillTools(ctx context.Context, frame browsersurface.SessionFrame) ([]polyfillToolMetadata, error) { - raw, err := c.callPolyfill(ctx, frame, "list", nil) - if err != nil { - return nil, err - } - if raw == nil { - return nil, nil - } - var result struct { - Tools []polyfillToolMetadata `json:"tools"` - } - if err := json.Unmarshal(raw, &result); err != nil { - return nil, fmt.Errorf("WebMCP: decode polyfill tools: %w", err) - } - return result.Tools, nil + return changed } -// applyPolyfillToolsLocked replaces the frame's polyfill registrations with -// the tools just read. Native tools of the same name take precedence when the -// snapshot is built, so a shadowed polyfill tool keeps its reference. -func (c *connection) applyPolyfillToolsLocked(frame browsersurface.SessionFrame, tools []polyfillToolMetadata) { - if !c.surface.SessionExists(frame.SessionID) { - return - } - tracked := 0 - for _, existing := range c.tools { - if existing.sessionID == frame.SessionID { - tracked++ - } - } - desired := make(map[string]bool, len(tools)) - for _, tool := range tools { - desired[tool.Name] = true - } - for ref, existing := range c.tools { - if !existing.polyfill || existing.sessionID != frame.SessionID || existing.frameID != frame.FrameID { - continue - } - if desired[existing.name] { - continue - } - delete(c.toolRefs, existing.key()) - delete(c.tools, ref) - tracked-- - } - for _, tool := range tools { - key := polyfillToolKey(frame.SessionID, frame.FrameID, tool.Name) - ref := c.toolRefs[key] - if ref == "" { - if tracked >= maxToolsPerSession { - if !c.toolLimitWarned[frame.SessionID] { - c.toolLimitWarned[frame.SessionID] = true - c.logger.Warn("WebMCP tool limit reached", "session_id", frame.SessionID, "limit", maxToolsPerSession) - } - continue - } - ref = "wmcp_" + cuid2.Generate() - c.toolRefs[key] = ref - tracked++ - } - c.tools[ref] = ®isteredTool{ - ref: ref, - sessionID: frame.SessionID, - name: tool.Name, - registeredName: tool.Name, - description: tool.Description, - inputSchema: tool.InputSchema, - frameID: frame.FrameID, - polyfill: true, - rootFrame: frame.Root, - title: tool.Title, - outputSchema: tool.OutputSchema, - hints: tool.Annotations, +func (c *connection) syncPolyfillFrame(ctx context.Context, frame browsersurface.SessionFrame) bool { + key := polyfillBridgeKey(frame.SessionID, frame.FrameID) + c.stateMu.RLock() + bridge := c.polyfillBridges[key] + c.stateMu.RUnlock() + if bridge == nil { + var err error + if bridge, err = c.createPolyfillBridge(ctx, frame); err != nil { + return false } - } -} - -// polyfillWatch is released when the document that is running a polyfill -// invocation goes away. -type polyfillWatch struct { - sessionID string - frameID string - released chan struct{} -} - -func (c *connection) watchPolyfillDocument(sessionID, frameID string) (*polyfillWatch, func()) { - watch := &polyfillWatch{sessionID: sessionID, frameID: frameID, released: make(chan struct{})} - c.stateMu.Lock() - c.polyfillWatches[watch] = struct{}{} - c.stateMu.Unlock() - return watch, func() { c.stateMu.Lock() - delete(c.polyfillWatches, watch) + c.polyfillBridges[key] = bridge c.stateMu.Unlock() } -} -func (c *connection) releasePolyfillWatchesLocked(matches func(*polyfillWatch) bool) { - for watch := range c.polyfillWatches { - if matches(watch) { - close(watch.released) - delete(c.polyfillWatches, watch) - } - } -} - -func (c *connection) invokePolyfill(ctx context.Context, tool *registeredTool, input map[string]any) (InvocationResult, error) { - frame := browsersurface.SessionFrame{SessionID: tool.sessionID, FrameID: tool.frameID, Root: tool.rootFrame} - invocation := InvocationResult{InvocationID: cuid2.Generate()} - - // A document that navigates before the returned promise settles either - // never answers the call or fails it as the context goes away. The native - // WebMCP domain reports such an invocation as completed with empty output, - // so watch the document and do the same. - watch, stopWatching := c.watchPolyfillDocument(tool.sessionID, tool.frameID) - defer stopWatching() - callCtx, cancelCall := context.WithCancel(ctx) - defer cancelCall() - type callResult struct { - raw json.RawMessage - err error - } - results := make(chan callResult, 1) - go func() { - raw, err := c.callPolyfill(callCtx, frame, "invoke", []any{tool.name, input}) - results <- callResult{raw: raw, err: err} - }() - navigated := func() (InvocationResult, error) { - invocation.Status = "completed" - invocation.Output = []any{} - return invocation, nil - } - var raw json.RawMessage - select { - case <-watch.released: - cancelCall() - result := <-results - if result.err != nil { - return navigated() + raw, err := c.surface.Send(ctx, "Runtime.callFunctionOn", map[string]any{ + "objectId": bridge.objectID, + "functionDeclaration": "function() { return this.sync(); }", + "awaitPromise": true, + "returnByValue": true, + }, frame.SessionID) + if err == nil { + var result evaluationResult + if err = json.Unmarshal(raw, &result); err == nil && result.ExceptionDetails != nil { + err = errors.New("WebMCP: polyfill bridge threw") } - raw = result.raw - case result := <-results: - if result.err != nil { - var notDispatched *polyfillNotDispatchedError - if errors.As(result.err, ¬Dispatched) && ctx.Err() == nil { - return InvocationResult{}, ErrToolNotFound - } - // The navigation that failed the call may still be settling. - grace := time.NewTimer(polyfillNavigationGrace) - defer grace.Stop() - select { - case <-watch.released: - return navigated() - case <-grace.C: - case <-ctx.Done(): - } - return invocation, ErrOutcomeUnknown + if err == nil { + return string(result.Result.Value) == "true" } - raw = result.raw - } - var result polyfillInvokeResult - if err := json.Unmarshal(raw, &result); err != nil { - invocation.Status = "error" - invocation.ErrorText = "the page returned an unreadable result" - return invocation, nil - } - if !result.OK { - invocation.Status = "error" - invocation.ErrorText = result.Error - return invocation, nil } - if result.Output != nil { - if err := json.Unmarshal([]byte(*result.Output), &invocation.Output); err != nil { - invocation.Status = "error" - invocation.ErrorText = "the page returned an unreadable result" - return invocation, nil + // A protocol error means the handle's document is gone; a later sync + // bridges the frame's new document. Timeouts keep the handle, which may + // still own registrations. + var protocolErr *cdpclient.Error + if errors.As(err, &protocolErr) { + c.stateMu.Lock() + if c.polyfillBridges[key] == bridge { + delete(c.polyfillBridges, key) } + c.stateMu.Unlock() + go c.releaseObjectGroup(frame.SessionID, bridge.group) } - invocation.Status = "completed" - return invocation, nil + return false } -// polyfillNotDispatchedError reports a failure before the page function ran, -// so the caller knows the tool did not execute. -type polyfillNotDispatchedError struct { - cause error +type evaluationResult struct { + Result struct { + ObjectID string `json:"objectId"` + Value json.RawMessage `json:"value"` + } `json:"result"` + ExceptionDetails *json.RawMessage `json:"exceptionDetails"` } -func (e *polyfillNotDispatchedError) Error() string { return e.cause.Error() } -func (e *polyfillNotDispatchedError) Unwrap() error { return e.cause } - -// callPolyfill runs one entry point of polyfill_page.js in the frame's main -// world and returns the JSON value it produced, or nil for null. -func (c *connection) callPolyfill(ctx context.Context, frame browsersurface.SessionFrame, method string, args []any) (json.RawMessage, error) { +func (c *connection) createPolyfillBridge(ctx context.Context, frame browsersurface.SessionFrame) (*polyfillBridge, error) { group := "kernel-webmcp-polyfill-" + cuid2.Generate() - defer func() { go c.releaseObjectGroup(frame.SessionID, group) }() - windowID, err := c.frameWindow(ctx, frame, group) - if err != nil { - return nil, &polyfillNotDispatchedError{cause: err} - } - arguments := make([]map[string]any, 0, len(args)) - for _, arg := range args { - arguments = append(arguments, map[string]any{"value": arg}) - } - raw, err := c.surface.Send(ctx, "Runtime.callFunctionOn", map[string]any{ - "objectId": windowID, - "functionDeclaration": fmt.Sprintf("function(...args) { return (%s).%s(this, ...args); }", polyfillPageSource, method), - "arguments": arguments, - "awaitPromise": true, - "returnByValue": true, - "objectGroup": group, - }, frame.SessionID) - if err != nil { - return nil, err - } - var result struct { - Result struct { - Value json.RawMessage `json:"value"` - } `json:"result"` - ExceptionDetails *struct { - Text string `json:"text"` - Exception *struct { - Description string `json:"description"` - } `json:"exception"` - } `json:"exceptionDetails"` - } - if err := json.Unmarshal(raw, &result); err != nil { - return nil, fmt.Errorf("WebMCP: decode polyfill %s response: %w", method, err) - } - if details := result.ExceptionDetails; details != nil { - description := details.Text - if details.Exception != nil && details.Exception.Description != "" { - description = details.Exception.Description + if err == nil { + var raw json.RawMessage + raw, err = c.surface.Send(ctx, "Runtime.callFunctionOn", map[string]any{ + "objectId": windowID, + "functionDeclaration": polyfillBridgeSource, + "objectGroup": group, + }, frame.SessionID) + if err == nil { + var result evaluationResult + if err = json.Unmarshal(raw, &result); err == nil { + if result.ExceptionDetails != nil || result.Result.ObjectID == "" { + err = errors.New("WebMCP: polyfill bridge is unavailable") + } else { + return &polyfillBridge{objectID: result.Result.ObjectID, group: group}, nil + } + } } - return nil, fmt.Errorf("WebMCP: polyfill %s threw: %s", method, description) - } - if len(result.Result.Value) == 0 || string(result.Result.Value) == "null" { - return nil, nil } - return result.Result.Value, nil + go c.releaseObjectGroup(frame.SessionID, group) + return nil, err } func (c *connection) releaseObjectGroup(sessionID, group string) { @@ -384,11 +194,7 @@ func (c *connection) frameWindow(ctx context.Context, frame browsersurface.Sessi if err != nil { return "", err } - var result struct { - Result struct { - ObjectID string `json:"objectId"` - } `json:"result"` - } + var result evaluationResult if err := json.Unmarshal(raw, &result); err != nil || result.Result.ObjectID == "" { return "", fmt.Errorf("WebMCP: frame window is unavailable") } @@ -428,11 +234,7 @@ func (c *connection) frameWindow(ctx context.Context, frame browsersurface.Sessi if err != nil { return "", err } - var window struct { - Result struct { - ObjectID string `json:"objectId"` - } `json:"result"` - } + var window evaluationResult if err := json.Unmarshal(raw, &window); err != nil || window.Result.ObjectID == "" { return "", fmt.Errorf("WebMCP: frame window is unavailable") } diff --git a/server/lib/webmcpclient/polyfill_bridge.js b/server/lib/webmcpclient/polyfill_bridge.js new file mode 100644 index 00000000..1b11706e --- /dev/null +++ b/server/lib/webmcpclient/polyfill_bridge.js @@ -0,0 +1,281 @@ +// Copies tools that a page registers through a JavaScript +// navigator.modelContext polyfill into the frame's native document.modelContext +// registry, where the CDP WebMCP domain lists and invokes them like any other +// page tool. Called with a frame's Window as `this`; the returned bridge is +// reachable only through the caller's remote object handle, so nothing is +// stored on the page. +function () { + const MAX_TOOLS = 256; + const MAX_TEXT = 64 * 1024; + const MAX_TOOL_BYTES = 256 * 1024; + const MAX_LIST_BYTES = 1024 * 1024; + const MAX_OUTPUT_BYTES = 1024 * 1024; + const window = this; + + function isObject(value) { + return typeof value === 'object' && value !== null; + } + + function isFunction(value) { + return typeof value === 'function'; + } + + function isNative(context) { + try { + return Object.prototype.toString.call(context) === '[object ModelContext]'; + } catch { + return false; + } + } + + function nativeContext() { + try { + const context = window.document.modelContext; + return isNative(context) ? context : null; + } catch { + return null; + } + } + + // The page-created object on navigator.modelContext, or null when it is + // absent or is the native registry itself. + function polyfill() { + let context; + try { + context = window.navigator.modelContext; + } catch { + return null; + } + if (!isObject(context) && !isFunction(context)) return null; + if (isNative(context) || context === nativeContext()) return null; + return context; + } + + function currentDocument() { + try { + return window.document; + } catch { + return null; + } + } + + // Tool registries that polyfills keep next to their public methods. + function registryEntries(context) { + const candidates = []; + for (const key of ['_registeredTools', '_tools', 'tools']) { + try { + candidates.push(context[key]); + } catch { + // A throwing accessor is not a registry. + } + } + try { + candidates.push(window.__webmcp && window.__webmcp.tools); + } catch { + // Not a registry either. + } + for (const registry of candidates) { + try { + if (isObject(registry) && isFunction(registry.values) && isFunction(registry.get)) { + return Array.from(registry.values()); + } + if (Array.isArray(registry)) return registry; + if (isObject(registry)) return Object.values(registry); + } catch { + continue; + } + } + return null; + } + + async function listedTools(context) { + for (const method of ['listTools', 'getTools']) { + let fn; + try { + fn = context[method]; + } catch { + continue; + } + if (!isFunction(fn)) continue; + try { + const result = await fn.call(context); + if (Array.isArray(result)) return result; + if (isObject(result) && Array.isArray(result.tools)) return result.tools; + } catch { + // Fall through to the next source. + } + } + return registryEntries(context); + } + + function text(value) { + if (typeof value !== 'string') return undefined; + return value.length > MAX_TEXT ? value.slice(0, MAX_TEXT) : value; + } + + function schema(value) { + if (typeof value === 'string') { + try { + value = JSON.parse(value); + } catch { + return undefined; + } + } + return isObject(value) && !Array.isArray(value) ? value : undefined; + } + + // A JSON copy of the tool's metadata, so nothing page-owned reaches the + // native registry except the data itself. + function metadata(tool) { + try { + if (!isObject(tool)) return null; + const name = tool.name; + if (typeof name !== 'string' || name === '' || name.length > 256) return null; + const entry = { + name, + title: text(tool.title), + description: text(tool.description) ?? '', + // Some polyfills accept the pre-standard `parameters` alias for inputSchema. + inputSchema: schema(tool.inputSchema) ?? schema(tool.parameters) ?? {type: 'object'}, + outputSchema: schema(tool.outputSchema), + annotations: isObject(tool.annotations) ? tool.annotations : undefined, + }; + const serialized = JSON.stringify(entry); + if (typeof serialized !== 'string' || serialized.length > MAX_TOOL_BYTES) return null; + return {entry: JSON.parse(serialized), serialized}; + } catch { + return null; + } + } + + async function desiredTools(context) { + const tools = await listedTools(context); + const desired = new Map(); + if (!Array.isArray(tools)) return desired; + let bytes = 0; + for (const tool of tools) { + if (desired.size >= MAX_TOOLS) break; + const item = metadata(tool); + if (!item || desired.has(item.entry.name)) continue; + if (bytes + item.serialized.length > MAX_LIST_BYTES) break; + bytes += item.serialized.length; + desired.set(item.entry.name, item); + } + return desired; + } + + function registryEntry(context, name) { + const entries = registryEntries(context); + if (!entries) return null; + for (const entry of entries) { + try { + if (isObject(entry) && entry.name === name && isFunction(entry.execute)) return entry; + } catch { + continue; + } + } + return null; + } + + async function run(context, name, input) { + // The registry entry's own execute is unambiguous; callTool's shape is not. + const entry = registryEntry(context, name); + if (entry) return entry.execute(input); + let callTool; + try { + callTool = context.callTool; + } catch { + callTool = undefined; + } + if (isFunction(callTool)) { + // Site polyfills take (name, input); MCP-style polyfills take ({name, arguments}). + if (callTool.length >= 2) return callTool.call(context, name, input); + return callTool.call(context, {name, arguments: input}); + } + let executeTool; + try { + executeTool = context.executeTool; + } catch { + executeTool = undefined; + } + if (isFunction(executeTool)) { + const output = await executeTool.call(context, name, JSON.stringify(input)); + if (typeof output !== 'string') return output; + try { + return JSON.parse(output); + } catch { + return output; + } + } + throw new Error('the modelContext polyfill does not expose a way to execute tools'); + } + + function plain(output) { + if (output === undefined) return undefined; + let serialized; + try { + serialized = JSON.stringify(output); + } catch { + throw new Error('tool output is not JSON-serializable'); + } + if (serialized === undefined) return undefined; + if (typeof serialized !== 'string') throw new Error('tool output is not JSON-serializable'); + if (serialized.length > MAX_OUTPUT_BYTES) throw new Error('tool output exceeds 1 MiB'); + return JSON.parse(serialized); + } + + function execute(name) { + return async (input) => { + const context = polyfill(); + if (!context) throw new Error('the page no longer exposes a modelContext polyfill'); + return plain(await run(context, name, input)); + }; + } + + // name -> {controller, serialized} for tools registered in `bridgedDocument`. + const bridged = new Map(); + let bridgedDocument = null; + + function unregister(name) { + bridged.get(name).controller.abort(); + bridged.delete(name); + } + + return { + // Registers new polyfill tools, unregisters removed or changed ones, and + // reports whether the native registry changed. + async sync() { + const document = currentDocument(); + if (document !== bridgedDocument) { + // A same-origin child frame navigated; its registrations went with it. + bridged.clear(); + bridgedDocument = document; + } + const native = nativeContext(); + const context = polyfill(); + const desired = native && context ? await desiredTools(context) : new Map(); + let changed = false; + for (const [name, registration] of bridged) { + if (desired.get(name)?.serialized !== registration.serialized) { + unregister(name); + changed = true; + } + } + for (const [name, item] of desired) { + if (bridged.has(name)) continue; + const controller = new AbortController(); + try { + // Rejects when the page already registered the name natively, which + // keeps the native tool. + await native.registerTool({...item.entry, execute: execute(name)}, {signal: controller.signal}); + } catch { + controller.abort(); + continue; + } + bridged.set(name, {controller, serialized: item.serialized}); + changed = true; + } + return changed; + }, + }; +} diff --git a/server/lib/webmcpclient/polyfill_page.js b/server/lib/webmcpclient/polyfill_page.js deleted file mode 100644 index 36d75dfa..00000000 --- a/server/lib/webmcpclient/polyfill_page.js +++ /dev/null @@ -1,249 +0,0 @@ -// Discovers and invokes tools that a page registers through a JavaScript -// navigator.modelContext polyfill instead of the native document.modelContext -// registry that the CDP WebMCP domain reports. Evaluated in the page's main -// world with the frame's Window passed in; it stores nothing on the page. -(() => { - const MAX_TOOLS = 256; - const MAX_TEXT = 64 * 1024; - const MAX_TOOL_BYTES = 256 * 1024; - const MAX_LIST_BYTES = 1024 * 1024; - const MAX_OUTPUT_BYTES = 1024 * 1024; - const HINTS = [ - 'readOnlyHint', - 'destructiveHint', - 'idempotentHint', - 'openWorldHint', - 'consequentialHint', - 'untrustedContentHint', - 'autosubmit', - ]; - - function isObject(value) { - return typeof value === 'object' && value !== null; - } - - function isFunction(value) { - return typeof value === 'function'; - } - - function message(error) { - try { - return isObject(error) && typeof error.message === 'string' ? error.message : String(error); - } catch { - return 'unknown error'; - } - } - - function text(value, fallback) { - if (typeof value !== 'string') return fallback; - return value.length > MAX_TEXT ? value.slice(0, MAX_TEXT) : value; - } - - // Returns the page-created object on navigator.modelContext, or null when the - // property is absent or is the native registry that CDP already reports. - function polyfill(window) { - let context; - try { - context = window.navigator.modelContext; - } catch { - return null; - } - if (!isObject(context) && !isFunction(context)) return null; - let native; - try { - native = window.document.modelContext; - } catch { - native = undefined; - } - if (context === native) return null; - try { - if (Object.prototype.toString.call(context) === '[object ModelContext]') return null; - } catch { - return null; - } - return context; - } - - function mapLike(value) { - return isObject(value) && isFunction(value.values) && isFunction(value.get); - } - - // Tool registries that polyfills keep next to their public methods. - function registryEntries(context, window) { - const candidates = []; - for (const key of ['_registeredTools', '_tools', 'tools']) { - try { - candidates.push(context[key]); - } catch { - // A throwing accessor is not a registry. - } - } - try { - candidates.push(window.__webmcp && window.__webmcp.tools); - } catch { - // Not a registry either. - } - for (const registry of candidates) { - try { - if (mapLike(registry)) return Array.from(registry.values()); - if (Array.isArray(registry)) return registry; - if (isObject(registry)) return Object.values(registry); - } catch { - continue; - } - } - return null; - } - - async function listedTools(context, window) { - for (const method of ['listTools', 'getTools']) { - let fn; - try { - fn = context[method]; - } catch { - continue; - } - if (!isFunction(fn)) continue; - try { - const result = await fn.call(context); - if (Array.isArray(result)) return result; - if (isObject(result) && Array.isArray(result.tools)) return result.tools; - } catch { - // Fall through to the next source. - } - } - return registryEntries(context, window); - } - - function schema(value) { - if (typeof value === 'string') { - try { - value = JSON.parse(value); - } catch { - return undefined; - } - } - return isObject(value) && !Array.isArray(value) ? value : undefined; - } - - function metadata(tool) { - try { - if (!isObject(tool)) return null; - const name = tool.name; - if (typeof name !== 'string' || name === '' || name.length > 256) return null; - const entry = {name, description: text(tool.description, '')}; - const title = text(tool.title, undefined); - if (title !== undefined) entry.title = title; - // Some polyfills accept the pre-standard `parameters` alias for inputSchema. - entry.inputSchema = schema(tool.inputSchema) ?? schema(tool.parameters) ?? {}; - const outputSchema = schema(tool.outputSchema); - if (outputSchema) entry.outputSchema = outputSchema; - if (isObject(tool.annotations)) { - const annotations = {}; - for (const hint of HINTS) { - if (typeof tool.annotations[hint] === 'boolean') annotations[hint] = tool.annotations[hint]; - } - if (Object.keys(annotations).length) entry.annotations = annotations; - } - const serialized = JSON.stringify(entry); - if (typeof serialized !== 'string' || serialized.length > MAX_TOOL_BYTES) return null; - return {entry: JSON.parse(serialized), bytes: serialized.length}; - } catch { - return null; - } - } - - // The listing travels back over the CDP connection, so the total stays well - // under the connection's read limit. - async function list(window) { - const context = polyfill(window); - if (!context) return null; - const tools = await listedTools(context, window); - if (!Array.isArray(tools)) return null; - const seen = new Set(); - const result = []; - let bytes = 0; - for (const tool of tools) { - if (result.length >= MAX_TOOLS) break; - const item = metadata(tool); - if (!item || seen.has(item.entry.name)) continue; - if (bytes + item.bytes > MAX_LIST_BYTES) break; - bytes += item.bytes; - seen.add(item.entry.name); - result.push(item.entry); - } - return {tools: result}; - } - - function registryEntry(context, window, name) { - const entries = registryEntries(context, window); - if (!entries) return null; - for (const entry of entries) { - try { - if (isObject(entry) && entry.name === name && isFunction(entry.execute)) return entry; - } catch { - continue; - } - } - return null; - } - - async function execute(context, window, name, input) { - // The registry entry's own execute is unambiguous; callTool's shape is not. - const entry = registryEntry(context, window, name); - if (entry) return entry.execute(input); - let callTool; - try { - callTool = context.callTool; - } catch { - callTool = undefined; - } - if (isFunction(callTool)) { - // Site polyfills take (name, input); MCP-style polyfills take ({name, arguments}). - if (callTool.length >= 2) return callTool.call(context, name, input); - return callTool.call(context, {name, arguments: input}); - } - let executeTool; - try { - executeTool = context.executeTool; - } catch { - executeTool = undefined; - } - if (isFunction(executeTool)) { - const output = await executeTool.call(context, name, JSON.stringify(input)); - if (typeof output !== 'string') return output; - try { - return JSON.parse(output); - } catch { - return output; - } - } - throw new Error('the modelContext polyfill does not expose a way to execute tools'); - } - - function serialize(output) { - if (output === undefined) return null; - let serialized; - try { - serialized = JSON.stringify(output); - } catch (error) { - throw new Error(`tool output is not JSON-serializable: ${message(error)}`); - } - if (serialized === undefined) return null; - if (typeof serialized !== 'string') throw new Error('tool output is not JSON-serializable'); - if (serialized.length > MAX_OUTPUT_BYTES) throw new Error('tool output exceeds 1 MiB'); - return serialized; - } - - async function invoke(window, name, input) { - const context = polyfill(window); - if (!context) return {ok: false, error: 'the page no longer exposes a modelContext polyfill'}; - try { - return {ok: true, output: serialize(await execute(context, window, name, input))}; - } catch (error) { - return {ok: false, error: text(message(error), 'tool execution failed')}; - } - } - - return {list, invoke}; -})() diff --git a/server/lib/webmcpclient/polyfill_test.go b/server/lib/webmcpclient/polyfill_test.go index c486ca38..3d28d47c 100644 --- a/server/lib/webmcpclient/polyfill_test.go +++ b/server/lib/webmcpclient/polyfill_test.go @@ -3,7 +3,7 @@ package webmcpclient import ( "context" "encoding/json" - "fmt" + "sort" "strings" "testing" "time" @@ -11,15 +11,19 @@ import ( "github.com/stretchr/testify/require" ) -type polyfillInvocation struct { - windowID string - name string - input map[string]any +// Window handles are "window:" for roots and +// "window::page-child" for the same-process child frame; bridge +// handles are "bridge|". +var polyfillWindowFrames = map[string]string{ + "window:page-session": "page-frame", + "window:page-session:page-child": "page-child", + "window:iframe-session": "iframe-frame", } -// servePolyfill answers the Runtime and DOM commands used to read a page's -// navigator.modelContext polyfill. Window handles are "window:" for -// roots and "window::page-child" for the same-process child frame. +// servePolyfill answers the Runtime and DOM commands used to bridge a page's +// navigator.modelContext polyfill. A bridge sync emits WebMCP.toolsAdded and +// toolsRemoved for the difference, the way Chromium reports registrations in +// the native registry. func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write func(any)) { fail := func(message string) { write(map[string]any{"id": request.ID, "error": map[string]any{"code": -32000, "message": message}}) @@ -28,27 +32,11 @@ func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write fu ObjectID string `json:"objectId"` FunctionDeclaration string `json:"functionDeclaration"` FrameID string `json:"frameId"` - Arguments []struct { - Value json.RawMessage `json:"value"` - } `json:"arguments"` } _ = json.Unmarshal(request.Params, ¶ms) - f.mu.Lock() - windowUnavailable := f.windowUnavailable - tools := f.polyfillTools[params.ObjectID] - invoke := f.polyfillInvoke - invokeError := f.polyfillInvokeError - invokeHang := f.polyfillInvokeHang - navigateDuringList := f.polyfillNavigateDuringList && params.ObjectID == "window:page-session" - f.polyfillNavigateDuringList = f.polyfillNavigateDuringList && !navigateDuringList - f.mu.Unlock() switch request.Method { case "Runtime.evaluate": - if windowUnavailable { - fail("Cannot find context with specified id") - return - } respond(map[string]any{"result": map[string]any{"type": "object", "className": "Window", "objectId": "window:" + request.SessionID}}) case "DOM.getFrameOwner": if params.FrameID != "page-child" { @@ -64,41 +52,48 @@ func (f *fakeCDP) servePolyfill(request wireRequest, respond func(any), write fu switch { case strings.Contains(params.FunctionDeclaration, "contentWindow"): respond(map[string]any{"result": map[string]any{"type": "object", "className": "Window", "objectId": "window:" + request.SessionID + ":page-child"}}) - case strings.Contains(params.FunctionDeclaration, ").list("): - if navigateDuringList { - // The document is replaced while its tools are being read. - write(map[string]any{ - "method": "Page.frameNavigated", "sessionId": request.SessionID, - "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, - }) - time.Sleep(200 * time.Millisecond) - } - if tools == nil { - respond(map[string]any{"result": map[string]any{"type": "object", "subtype": "null", "value": nil}}) + case params.FunctionDeclaration == polyfillBridgeSource: + f.mu.Lock() + f.bridgesCreated++ + f.mu.Unlock() + respond(map[string]any{"result": map[string]any{"type": "object", "objectId": "bridge|" + params.ObjectID}}) + case strings.Contains(params.FunctionDeclaration, "this.sync()"): + windowID := strings.TrimPrefix(params.ObjectID, "bridge|") + f.mu.Lock() + if f.staleBridges[windowID] { + delete(f.staleBridges, windowID) + f.mu.Unlock() + fail("Could not find object with given id") return } - respond(map[string]any{"result": map[string]any{"type": "object", "value": map[string]any{"tools": tools}}}) - case strings.Contains(params.FunctionDeclaration, ").invoke("): - if invokeHang { - // Chromium never answers once the document navigates away. - return + desired := make(map[string]bool) + for _, name := range f.polyfillTools[windowID] { + desired[name] = true } - if invokeError != "" { - fail(invokeError) - return + bridged := f.bridgedTools[windowID] + var added, removed []map[string]any + for name := range bridged { + if !desired[name] { + removed = append(removed, map[string]any{"name": name, "frameId": polyfillWindowFrames[windowID]}) + } } - var name string - var input map[string]any - if len(params.Arguments) != 2 || - json.Unmarshal(params.Arguments[0].Value, &name) != nil || - json.Unmarshal(params.Arguments[1].Value, &input) != nil { - fail("unexpected invoke arguments") - return + for name := range desired { + if !bridged[name] { + added = append(added, map[string]any{ + "name": name, "description": name + " description", "frameId": polyfillWindowFrames[windowID], + "inputSchema": map[string]any{"type": "object"}, + }) + } } - f.mu.Lock() - f.polyfillInvocations = append(f.polyfillInvocations, polyfillInvocation{windowID: params.ObjectID, name: name, input: input}) + f.bridgedTools[windowID] = desired f.mu.Unlock() - respond(map[string]any{"result": map[string]any{"type": "object", "value": invoke(params.ObjectID, name, input)}}) + if len(removed) > 0 { + write(map[string]any{"method": "WebMCP.toolsRemoved", "sessionId": request.SessionID, "params": map[string]any{"tools": removed}}) + } + if len(added) > 0 { + write(map[string]any{"method": "WebMCP.toolsAdded", "sessionId": request.SessionID, "params": map[string]any{"tools": added}}) + } + respond(map[string]any{"result": map[string]any{"type": "boolean", "value": len(added)+len(removed) > 0}}) default: fail("unexpected function") } @@ -109,29 +104,10 @@ func newPolyfillFakeCDP(t *testing.T) *fakeCDP { t.Helper() fake := newFakeCDP(t, false) fake.childFrameOpen = true - fake.polyfillTools = map[string][]map[string]any{ - "window:page-session": { - {"name": "merchant_tool", "description": "polyfill copy", "inputSchema": map[string]any{"type": "object"}}, - { - "name": "poly_search", "title": "Search", "description": "Search the catalog.", - "inputSchema": map[string]any{"type": "object", "properties": map[string]any{"query": map[string]any{"type": "string"}}}, - "outputSchema": map[string]any{"type": "object"}, - "annotations": map[string]any{"readOnlyHint": true}, - }, - }, - "window:page-session:page-child": { - {"name": "child_poly", "description": "child", "inputSchema": map[string]any{"type": "object"}}, - }, - "window:iframe-session": { - {"name": "payment_poly", "description": "payment", "inputSchema": map[string]any{"type": "object"}}, - }, - } - fake.polyfillInvoke = func(_ string, name string, input map[string]any) map[string]any { - if name != "poly_search" { - return map[string]any{"ok": false, "error": "Tool not found: " + name} - } - output, _ := json.Marshal(map[string]any{"results": []any{input["query"]}}) - return map[string]any{"ok": true, "output": string(output)} + fake.polyfillTools = map[string][]string{ + "window:page-session": {"poly_search"}, + "window:page-session:page-child": {"child_poly"}, + "window:iframe-session": {"payment_poly"}, } return fake } @@ -144,237 +120,97 @@ func toolsByName(tools []Tool) map[string]Tool { return byName } -func TestPolyfillToolsAreDiscoveredNextToNativeTools(t *testing.T) { +func toolNames(tools []Tool) []string { + names := make([]string, 0, len(tools)) + for _, tool := range tools { + names = append(names, tool.Name) + } + sort.Strings(names) + return names +} + +func TestPolyfillToolsAreBridgedIntoTheNativeRegistry(t *testing.T) { fake := newPolyfillFakeCDP(t) manager := NewManager(staticUpstream{url: fake.url}) t.Cleanup(func() { _ = manager.Close() }) tools, err := manager.Tools(context.Background()) require.NoError(t, err) - require.Len(t, tools, 7) + require.Equal(t, []string{ + "bank_tool", "child_poly", "merchant_tool", "payment_poly", "payment_tool", "poly_search", "search_flights", + }, toolNames(tools)) byName := toolsByName(tools) + require.Nil(t, byName["poly_search"].Source.Frame) + require.Equal(t, "https://merchant.example/child", byName["child_poly"].Source.Frame.URL) + // An out-of-process iframe is bridged through its own session. + require.Equal(t, "https://payments.example/element", byName["payment_poly"].Source.Frame.URL) - // The native registration wins over the polyfill copy of the same name. - require.False(t, byName["merchant_tool"].Polyfill) - require.Equal(t, "merchant_tool description", byName["merchant_tool"].Description) - - search := byName["poly_search"] - require.True(t, search.Polyfill) - require.Empty(t, search.CustomID) - require.Equal(t, "Search", search.Title) - require.Equal(t, "Search the catalog.", search.Description) - require.Equal(t, map[string]any{"type": "object"}, search.OutputSchema) - require.Equal(t, map[string]bool{"readOnlyHint": true}, search.Hints) - require.Equal(t, 1, search.Source.TabID) - require.Equal(t, "https://merchant.example/", search.Source.PageURL) - require.Nil(t, search.Source.Frame) - - child := byName["child_poly"] - require.True(t, child.Polyfill) - require.Equal(t, 1, child.Source.TabID) - require.NotNil(t, child.Source.Frame) - require.Equal(t, "https://merchant.example/child", child.Source.Frame.URL) - - // An out-of-process iframe is read through its own session. - payment := byName["payment_poly"] - require.True(t, payment.Polyfill) - require.NotNil(t, payment.Source.Frame) - require.Equal(t, "https://payments.example/element", payment.Source.Frame.URL) - - // Discovery never enables the Runtime or DOM domains, which pages can detect. + // Bridging never enables the Runtime or DOM domains, which pages can detect. fake.mu.Lock() require.Zero(t, fake.methods["Runtime.enable"]) require.Zero(t, fake.methods["DOM.enable"]) - require.Positive(t, fake.methods["Runtime.callFunctionOn"]) + // Every web frame gets a bridge, including the two without a polyfill. + require.Equal(t, 5, fake.bridgesCreated) fake.mu.Unlock() - customID, targetID, err := manager.CustomTool(context.Background(), search.Ref) - require.NoError(t, err) - require.Empty(t, customID) - require.Empty(t, targetID) - - // References stay stable while the registration lives. + // Bridges and references are reused while the documents live. again, err := manager.Tools(context.Background()) require.NoError(t, err) - require.Equal(t, search.Ref, toolsByName(again)["poly_search"].Ref) - require.Equal(t, child.Ref, toolsByName(again)["child_poly"].Ref) - - // A tool the polyfill unregisters disappears on the next listing. + require.Equal(t, byName["poly_search"].Ref, toolsByName(again)["poly_search"].Ref) fake.mu.Lock() - fake.polyfillTools["window:page-session"] = fake.polyfillTools["window:page-session"][:1] + require.Equal(t, 5, fake.bridgesCreated) fake.mu.Unlock() - after, err := manager.Tools(context.Background()) - require.NoError(t, err) - require.NotContains(t, toolsByName(after), "poly_search") - require.Equal(t, child.Ref, toolsByName(after)["child_poly"].Ref) - - // Navigation replaces the document, so surviving names get new references. - fake.mu.Lock() - fake.polyfillTools["window:page-session"] = append(fake.polyfillTools["window:page-session"], map[string]any{ - "name": "poly_search", "description": "Search the catalog.", "inputSchema": map[string]any{"type": "object"}, - }) - fake.mu.Unlock() - fake.emit(map[string]any{ - "method": "Page.frameNavigated", "sessionId": "page-session", - "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, - }) - var navigated map[string]Tool - require.Eventually(t, func() bool { - tools, err := manager.Tools(context.Background()) - if err != nil { - return false - } - navigated = toolsByName(tools) - _, ok := navigated["poly_search"] - return ok && navigated["poly_search"].Ref != search.Ref - }, 3*time.Second, 20*time.Millisecond) - require.NotContains(t, navigated, "child_poly") -} - -func TestPolyfillInvocationRunsInTheRegisteringFrame(t *testing.T) { - fake := newPolyfillFakeCDP(t) - manager := NewManager(staticUpstream{url: fake.url}) - t.Cleanup(func() { _ = manager.Close() }) - tools, err := manager.Tools(context.Background()) - require.NoError(t, err) - byName := toolsByName(tools) + // Bridged tools invoke through the native WebMCP domain. result, err := manager.Invoke(context.Background(), byName["poly_search"].Ref, map[string]any{"query": "lamp"}) require.NoError(t, err) - require.NotEmpty(t, result.InvocationID) - require.Equal(t, "completed", result.Status) - require.Equal(t, map[string]any{"results": []any{"lamp"}}, result.Output) - require.Empty(t, result.ErrorText) - - failed, err := manager.Invoke(context.Background(), byName["child_poly"].Ref, map[string]any{}) - require.NoError(t, err) - require.Equal(t, "error", failed.Status) - require.Equal(t, "Tool not found: child_poly", failed.ErrorText) - require.Nil(t, failed.Output) - - fake.mu.Lock() - defer fake.mu.Unlock() - require.Equal(t, []polyfillInvocation{ - {windowID: "window:page-session", name: "poly_search", input: map[string]any{"query": "lamp"}}, - {windowID: "window:page-session:page-child", name: "child_poly", input: map[string]any{}}, - }, fake.polyfillInvocations) -} - -func TestPolyfillInvocationThatNavigatesCompletesLikeNativeTools(t *testing.T) { - fake := newPolyfillFakeCDP(t) - manager := NewManager(staticUpstream{url: fake.url}) - t.Cleanup(func() { _ = manager.Close() }) - tools, err := manager.Tools(context.Background()) - require.NoError(t, err) - ref := toolsByName(tools)["poly_search"].Ref - - fake.mu.Lock() - fake.polyfillInvokeHang = true - fake.mu.Unlock() - go func() { - time.Sleep(100 * time.Millisecond) - fake.emit(map[string]any{ - "method": "Page.frameNavigated", "sessionId": "page-session", - "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, - }) - }() - started := time.Now() - result, err := manager.Invoke(context.Background(), ref, map[string]any{}) - require.NoError(t, err) - require.Less(t, time.Since(started), 3*time.Second) - require.Equal(t, "completed", result.Status) - require.Equal(t, []any{}, result.Output) - require.NotEmpty(t, result.InvocationID) + require.Equal(t, "Completed", result.Status) } -func TestPolyfillInvocationProtocolFailureHasUnknownOutcome(t *testing.T) { +func TestPolyfillUnregistrationRemovesBridgedTools(t *testing.T) { fake := newPolyfillFakeCDP(t) manager := NewManager(staticUpstream{url: fake.url}) t.Cleanup(func() { _ = manager.Close() }) tools, err := manager.Tools(context.Background()) require.NoError(t, err) - ref := toolsByName(tools)["poly_search"].Ref + childRef := toolsByName(tools)["child_poly"].Ref fake.mu.Lock() - fake.polyfillInvokeError = "Target closed." + fake.polyfillTools["window:page-session"] = nil fake.mu.Unlock() - result, err := manager.Invoke(context.Background(), ref, map[string]any{}) - require.ErrorIs(t, err, ErrOutcomeUnknown) - require.NotEmpty(t, result.InvocationID) -} - -func TestPolyfillInvocationTimeoutHasUnknownOutcome(t *testing.T) { - fake := newPolyfillFakeCDP(t) - fake.polyfillInvokeHang = true - manager := NewManager(staticUpstream{url: fake.url}) - t.Cleanup(func() { _ = manager.Close() }) - tools, err := manager.Tools(context.Background()) + after, err := manager.Tools(context.Background()) require.NoError(t, err) - ref := toolsByName(tools)["poly_search"].Ref - - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel() - result, err := manager.Invoke(ctx, ref, map[string]any{}) - require.ErrorIs(t, err, ErrOutcomeUnknown) - require.NotEmpty(t, result.InvocationID) + require.NotContains(t, toolsByName(after), "poly_search") + require.Equal(t, childRef, toolsByName(after)["child_poly"].Ref) } -func TestPolyfillInvocationReportsMissingDocument(t *testing.T) { +func TestPolyfillBridgeIsRecreatedForANewDocument(t *testing.T) { fake := newPolyfillFakeCDP(t) manager := NewManager(staticUpstream{url: fake.url}) t.Cleanup(func() { _ = manager.Close() }) tools, err := manager.Tools(context.Background()) require.NoError(t, err) - ref := toolsByName(tools)["poly_search"].Ref + oldRef := toolsByName(tools)["poly_search"].Ref + // Navigation drops the document's registrations and invalidates the + // bridge handle; the next listing bridges the new document. fake.mu.Lock() - fake.windowUnavailable = true + fake.staleBridges["window:page-session"] = true + delete(fake.bridgedTools, "window:page-session") fake.mu.Unlock() - _, err = manager.Invoke(context.Background(), ref, map[string]any{}) - require.ErrorIs(t, err, ErrToolNotFound) - - // An unreadable frame keeps its previous registrations until the surface - // reports that the document changed. - again, err := manager.Tools(context.Background()) - require.NoError(t, err) - require.Equal(t, ref, toolsByName(again)["poly_search"].Ref) -} - -func TestPolyfillToolsShareTheSessionLimit(t *testing.T) { - fake := newPolyfillFakeCDP(t) - oversized := make([]map[string]any, 0, maxToolsPerSession+10) - for i := range cap(oversized) { - oversized = append(oversized, map[string]any{ - "name": fmt.Sprintf("poly_%d", i), "description": "polyfill", "inputSchema": map[string]any{"type": "object"}, - }) - } - fake.polyfillTools["window:page-session"] = oversized - manager := NewManager(staticUpstream{url: fake.url}) - t.Cleanup(func() { _ = manager.Close() }) - - tools, err := manager.Tools(context.Background()) - require.NoError(t, err) - pageTools := 0 - for _, tool := range tools { - if tool.Source.TabID == 1 && tool.Source.Frame == nil { - pageTools++ + fake.emit(map[string]any{ + "method": "Page.frameNavigated", "sessionId": "page-session", + "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, + }) + require.Eventually(t, func() bool { + tools, err := manager.Tools(context.Background()) + if err != nil { + return false } - } - require.Equal(t, maxToolsPerSession, pageTools) -} - -func TestPolyfillReadOfReplacedDocumentIsDiscarded(t *testing.T) { - fake := newPolyfillFakeCDP(t) - fake.polyfillNavigateDuringList = true - manager := NewManager(staticUpstream{url: fake.url}) - t.Cleanup(func() { _ = manager.Close() }) - - tools, err := manager.Tools(context.Background()) - require.NoError(t, err) - require.NotContains(t, toolsByName(tools), "poly_search") - - // The next listing reads the new document. - tools, err = manager.Tools(context.Background()) - require.NoError(t, err) - require.Contains(t, toolsByName(tools), "poly_search") + tool, ok := toolsByName(tools)["poly_search"] + return ok && tool.Ref != oldRef + }, 3*time.Second, 20*time.Millisecond) + fake.mu.Lock() + defer fake.mu.Unlock() + require.Equal(t, 6, fake.bridgesCreated) } diff --git a/server/lib/webmcpclient/types.go b/server/lib/webmcpclient/types.go index 1e798c18..9f2bcebe 100644 --- a/server/lib/webmcpclient/types.go +++ b/server/lib/webmcpclient/types.go @@ -17,14 +17,7 @@ type Tool struct { InputSchema map[string]any Annotations *Annotations CustomID string - // Polyfill tools were read from a page-JavaScript navigator.modelContext - // polyfill rather than the native registry. Title, OutputSchema, and - // Hints are only populated for them. - Polyfill bool - Title string - OutputSchema map[string]any - Hints map[string]bool - Source ToolSource + Source ToolSource } type ToolSource struct { @@ -66,18 +59,6 @@ type registeredTool struct { customID string frameID string declarative bool - polyfill bool - rootFrame bool - title string - outputSchema map[string]any - hints map[string]bool -} - -func (t *registeredTool) key() string { - if t.polyfill { - return polyfillToolKey(t.sessionID, t.frameID, t.name) - } - return toolKey(t.sessionID, t.frameID, t.name) } type toolEvent struct { diff --git a/server/openapi.yaml b/server/openapi.yaml index dbfc9727..7cc3e8e7 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1361,10 +1361,7 @@ paths: summary: Discover WebMCP tools across the browser description: | Returns a snapshot of WebMCP tools registered by every open tab and by embedded - content within those tabs. Tools that a page registers through a JavaScript - `navigator.modelContext` polyfill instead of the native `document.modelContext` - registry are included and marked with `source.polyfill`; when a page registers the - same name both ways, the native registration is listed. + content within those tabs. On the first request, Kernel starts monitoring the browser. Tabs and windows that are already open are included, and tabs, windows, and embedded content opened later are tracked automatically. @@ -6551,11 +6548,6 @@ components: description: Embedded frame that registered the tool, or null when the top-level page registered it. custom: $ref: "#/components/schemas/WebMCPCustomToolSource" - polyfill: - type: boolean - description: | - True when the page registered this tool through a JavaScript `navigator.modelContext` - polyfill rather than the native `document.modelContext` registry; omitted otherwise. WebMCPToolMetadata: type: object description: | diff --git a/server/runtime/webmcp-polyfill.test.ts b/server/runtime/webmcp-polyfill.test.ts index e7efbf31..23a73c3d 100644 --- a/server/runtime/webmcp-polyfill.test.ts +++ b/server/runtime/webmcp-polyfill.test.ts @@ -3,36 +3,52 @@ import {readFileSync} from 'node:fs'; import test from 'node:test'; import vm from 'node:vm'; -// The page-side reader that lib/webmcpclient evaluates in a frame's main world. -const source = readFileSync(new URL('../lib/webmcpclient/polyfill_page.js', import.meta.url), 'utf8'); +// The page-side bridge that lib/webmcpclient calls on a frame's Window. +const source = readFileSync(new URL('../lib/webmcpclient/polyfill_bridge.js', import.meta.url), 'utf8'); -interface PageScript { - list(window: unknown): Promise<{tools: unknown[]} | null>; - invoke(window: unknown, name: string, input: Record): Promise<{ok: boolean; output?: string | null; error?: string}>; +interface Bridge { + sync(): Promise; } -// Results come from the vm realm; strip prototypes before comparing. -function plain(value: T): T { - return JSON.parse(JSON.stringify(value)); +function bridge(window: unknown): Bridge { + const create = vm.runInNewContext(`(${source})`, {AbortController}) as (this: unknown) => Bridge; + return create.call(window); } -function pageScript(): PageScript { - const script = vm.runInNewContext(source, {}) as PageScript; - return { - list: async (window) => plain(await script.list(window)), - invoke: async (window, name, input) => plain(await script.invoke(window, name, input)), - }; -} +type Tool = {name: string; execute?: (input: unknown) => unknown} & Record; + +// Stands in for Chromium's document.modelContext: duplicate names reject and +// aborting the registration signal removes the tool. +class ModelContext { + readonly tools = new Map(); + + get [Symbol.toStringTag]() { + return 'ModelContext'; + } -type Tool = {name: string; description?: string; inputSchema?: unknown; execute?: (input: unknown) => unknown} & Record; + async registerTool(tool: Tool, options: {signal?: AbortSignal} = {}) { + if (this.tools.has(tool.name)) throw new Error(`Duplicate tool name: ${tool.name}`); + this.tools.set(tool.name, tool); + options.signal?.addEventListener('abort', () => this.tools.delete(tool.name), {once: true}); + } + + metadata(name: string) { + const {execute: _execute, ...metadata} = this.tools.get(name)!; + return JSON.parse(JSON.stringify(metadata)); + } + + async invoke(name: string, input: unknown) { + return JSON.parse(JSON.stringify(await this.tools.get(name)!.execute!(input) ?? null)); + } +} // Mirrors the polyfill shape that sites ship before Chromium exposed // document.modelContext: a registry object plus listTools/callTool helpers. -function sitePolyfill(): Record { +function sitePolyfill() { const registry: Record = {}; return { _registeredTools: registry, - registerTool(tool: Tool, options?: {signal?: AbortSignal}) { + registerTool(tool: Tool) { registry[tool.name] = { name: tool.name, title: tool.title, @@ -40,7 +56,6 @@ function sitePolyfill(): Record { inputSchema: tool.inputSchema ?? tool.parameters, execute: tool.execute, }; - options?.signal?.addEventListener('abort', () => delete registry[tool.name], {once: true}); }, unregisterTool(name: string) { delete registry[name]; @@ -54,12 +69,12 @@ function sitePolyfill(): Record { }; } -function fakeWindow(modelContext: unknown, documentModelContext?: unknown): Record { - return {navigator: {modelContext}, document: {modelContext: documentModelContext}}; +function fakeWindow(modelContext: unknown, native = new ModelContext()) { + return {navigator: {modelContext}, document: {modelContext: native}, native}; } -test('lists and invokes tools from a site polyfill on navigator.modelContext', async () => { - const polyfill = sitePolyfill() as ReturnType & {registerTool: (tool: Tool) => void}; +test('bridges a site polyfill into the native registry and follows its changes', async () => { + const polyfill = sitePolyfill(); polyfill.registerTool({ name: 'search_items', description: 'Search the catalog.', @@ -76,25 +91,29 @@ test('lists and invokes tools from a site polyfill on navigator.modelContext', a throw new Error('page rejected the call'); }}); const window = fakeWindow(polyfill); - const script = pageScript(); - - const listed = await script.list(window); - assert.deepEqual(listed, { - tools: [ - {name: 'search_items', description: 'Search the catalog.', inputSchema: {type: 'object', properties: {query: {type: 'string'}}, required: ['query']}}, - {name: 'legacy_params', description: 'Uses the pre-standard parameters alias.', inputSchema: {type: 'object', properties: {}}}, - {name: 'broken', description: 'Throws.', inputSchema: {type: 'object'}}, - ], - }); - - const invoked = await script.invoke(window, 'search_items', {query: 'lamp'}); - assert.deepEqual(invoked, {ok: true, output: JSON.stringify({results: ['match for lamp']})}); - assert.deepEqual(await script.invoke(window, 'legacy_params', {}), {ok: true, output: null}); - assert.deepEqual(await script.invoke(window, 'broken', {}), {ok: false, error: 'page rejected the call'}); - assert.deepEqual(await script.invoke(window, 'missing', {}), {ok: false, error: 'Tool not found: missing'}); + const {native} = window; + const b = bridge(window); - (polyfill.unregisterTool as (name: string) => void)('search_items'); - assert.equal((await script.list(window))?.tools.length, 2); + assert.equal(await b.sync(), true); + assert.deepEqual([...native.tools.keys()], ['search_items', 'legacy_params', 'broken']); + assert.deepEqual(native.metadata('search_items'), { + name: 'search_items', + description: 'Search the catalog.', + inputSchema: {type: 'object', properties: {query: {type: 'string'}}, required: ['query']}, + }); + assert.deepEqual(native.metadata('legacy_params').inputSchema, {type: 'object', properties: {}}); + assert.deepEqual(await native.invoke('search_items', {query: 'lamp'}), {results: ['match for lamp']}); + assert.equal(await native.invoke('legacy_params', {}), null); + await assert.rejects(native.invoke('broken', {}), /page rejected the call/); + + assert.equal(await b.sync(), false); + + polyfill.unregisterTool('search_items'); + polyfill.registerTool({name: 'broken', description: 'Fixed.', inputSchema: {type: 'object'}, execute: async () => 'ok'}); + assert.equal(await b.sync(), true); + assert.deepEqual([...native.tools.keys()].sort(), ['broken', 'legacy_params']); + assert.equal(native.metadata('broken').description, 'Fixed.'); + assert.equal(await native.invoke('broken', {}), 'ok'); }); test('supports MCP-style polyfills that take callTool({name, arguments})', async () => { @@ -105,78 +124,61 @@ test('supports MCP-style polyfills that take callTool({name, arguments})', async description: 'Add an item.', inputSchema: {type: 'object', properties: {sku: {type: 'string'}}}, outputSchema: {type: 'object'}, - annotations: {readOnlyHint: false, destructiveHint: true, ignored: 'x'}, + annotations: {readOnlyHint: false}, execute: async () => ({content: [{type: 'text', text: 'added'}]}), }); const polyfill = { - _tools: tools, - listTools: () => [...tools.values()].map(({execute: _execute, ...metadata}) => metadata), + listTools: () => ({tools: [...tools.values()].map(({execute: _execute, ...metadata}) => metadata)}), async callTool(params: {name: string; arguments: unknown}) { - const tool = tools.get(params.name); - if (!tool) throw new Error(`unknown tool ${params.name}`); - return tool.execute?.(params.arguments); + return tools.get(params.name)?.execute?.(params.arguments); }, }; - const script = pageScript(); const window = fakeWindow(polyfill); + await bridge(window).sync(); - const listed = await script.list(window); - assert.deepEqual(listed?.tools, [{ + assert.deepEqual(window.native.metadata('add_to_cart'), { name: 'add_to_cart', title: 'Add to cart', description: 'Add an item.', inputSchema: {type: 'object', properties: {sku: {type: 'string'}}}, outputSchema: {type: 'object'}, - annotations: {readOnlyHint: false, destructiveHint: true}, - }]); - assert.deepEqual(await script.invoke(window, 'add_to_cart', {sku: '1'}), { - ok: true, - output: JSON.stringify({content: [{type: 'text', text: 'added'}]}), + annotations: {readOnlyHint: false}, }); + assert.deepEqual(await window.native.invoke('add_to_cart', {sku: '1'}), {content: [{type: 'text', text: 'added'}]}); }); test('falls back to the registry when the polyfill has no list or call helpers', async () => { const registry = new Map(); registry.set('get_context', {name: 'get_context', description: 'Context.', inputSchema: {type: 'object'}, execute: async () => 'ctx'}); - const polyfill = {registerTool() {}, unregisterTool() {}, provideContext() {}, clearContext() {}}; - const script = pageScript(); + const polyfill = {registerTool() {}, provideContext() {}}; const window = {...fakeWindow(polyfill), __webmcp: {tools: registry}}; + await bridge(window).sync(); - assert.deepEqual(await script.list(window), {tools: [{name: 'get_context', description: 'Context.', inputSchema: {type: 'object'}}]}); - assert.deepEqual(await script.invoke(window, 'get_context', {}), {ok: true, output: '"ctx"'}); - assert.deepEqual(await script.invoke(fakeWindow(polyfill), 'get_context', {}), { - ok: false, - error: 'the modelContext polyfill does not expose a way to execute tools', - }); + assert.deepEqual([...window.native.tools.keys()], ['get_context']); + assert.equal(await window.native.invoke('get_context', {}), 'ctx'); }); -test('ignores the native registry and pages without a polyfill', async () => { - const script = pageScript(); - class ModelContext { - get [Symbol.toStringTag]() { - return 'ModelContext'; - } - async getTools() { - return [{name: 'native_tool', description: 'Native.', inputSchema: {type: 'object'}}]; - } - } +test('keeps native tools and ignores pages without a polyfill', async () => { const native = new ModelContext(); - - assert.equal(await script.list(fakeWindow(undefined)), null); - assert.equal(await script.list(fakeWindow(native, native)), null); - assert.equal(await script.list(fakeWindow(native)), null); - assert.equal(await script.list(fakeWindow('not an object')), null); - assert.equal(await script.list({navigator: {get modelContext() { + await native.registerTool({name: 'shared', description: 'Native copy.', execute: async () => 'native'}); + const polyfill = sitePolyfill(); + polyfill.registerTool({name: 'shared', description: 'Polyfill copy.', inputSchema: {type: 'object'}, execute: async () => 'polyfill'}); + const window = fakeWindow(polyfill, native); + assert.equal(await bridge(window).sync(), false); + assert.equal(await native.invoke('shared', {}), 'native'); + + // navigator.modelContext that is the native registry, or no polyfill at all. + const self = new ModelContext(); + assert.equal(await bridge(fakeWindow(self, self)).sync(), false); + assert.equal(await bridge(fakeWindow(undefined)).sync(), false); + assert.equal(await bridge({navigator: {get modelContext() { throw new Error('blocked'); - }}, document: {}}), null); - assert.deepEqual(await script.invoke(fakeWindow(undefined), 'x', {}), { - ok: false, - error: 'the page no longer exposes a modelContext polyfill', - }); + }}, document: {modelContext: new ModelContext()}}).sync(), false); + // A document whose modelContext is not the native registry. + assert.equal(await bridge({navigator: {modelContext: sitePolyfill()}, document: {modelContext: {}}}).sync(), false); }); -test('drops malformed entries and bounds output size', async () => { - const script = pageScript(); +test('drops malformed entries, bounds output, and resets for a new document', async () => { const polyfill = { listTools: () => [ null, @@ -185,29 +187,27 @@ test('drops malformed entries and bounds output size', async () => { {name: 'dup', description: 'first', inputSchema: '{"type":"object"}'}, {name: 'dup', description: 'second'}, {name: 'bad_schema', inputSchema: 'not json', description: 7}, - {name: 'huge', description: 'x'.repeat(300 * 1024)}, + {name: 'huge', description: 'x', inputSchema: {type: 'object', enum: ['x'.repeat(300 * 1024)]}}, ], async callTool(name: string, _input: unknown) { - if (name === 'large') return {payload: 'x'.repeat(1024 * 1024)}; - if (name === 'cyclic') { - const value: Record = {}; - value.self = value; - return value; - } - return {name}; + if (name === 'dup') return {payload: 'x'.repeat(1024 * 1024)}; + const value: Record = {}; + value.self = value; + return value; }, }; const window = fakeWindow(polyfill); - - const listed = await script.list(window); - assert.deepEqual(listed?.tools.slice(0, 2), [ - {name: 'dup', description: 'first', inputSchema: {type: 'object'}}, - {name: 'bad_schema', description: '', inputSchema: {}}, - ]); - assert.equal(listed?.tools.length, 3); - assert.equal((listed?.tools[2] as {description: string}).description.length, 64 * 1024); - assert.deepEqual(await script.invoke(window, 'large', {}), {ok: false, error: 'tool output exceeds 1 MiB'}); - const cyclic = await script.invoke(window, 'cyclic', {}); - assert.equal(cyclic.ok, false); - assert.match(cyclic.error ?? '', /not JSON-serializable/); + const b = bridge(window); + await b.sync(); + + assert.deepEqual([...window.native.tools.keys()], ['dup', 'bad_schema']); + assert.deepEqual(window.native.metadata('dup'), {name: 'dup', description: 'first', inputSchema: {type: 'object'}}); + assert.deepEqual(window.native.metadata('bad_schema'), {name: 'bad_schema', description: '', inputSchema: {type: 'object'}}); + await assert.rejects(window.native.invoke('dup', {}), /exceeds 1 MiB/); + await assert.rejects(window.native.invoke('bad_schema', {}), /not JSON-serializable/); + + // A same-origin child frame navigated: its registry is new and empty. + window.document = {modelContext: new ModelContext()}; + assert.equal(await b.sync(), true); + assert.deepEqual([...(window.document.modelContext as ModelContext).tools.keys()], ['dup', 'bad_schema']); }); diff --git a/server/runtime/webmcp.ts b/server/runtime/webmcp.ts index 02c26538..44bce957 100644 --- a/server/runtime/webmcp.ts +++ b/server/runtime/webmcp.ts @@ -16,7 +16,6 @@ export interface WebMCPToolSource { page_url: string; frame: WebMCPToolFrame | null; custom?: WebMCPCustomToolSource; - polyfill?: boolean; } export interface WebMCPToolMetadata { From a416c47b16c27594df9d2e42df9d7cb85205f48b Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:46:50 +0000 Subject: [PATCH 4/5] Rebridge a replaced document within the same listing --- server/lib/webmcpclient/polyfill.go | 40 ++++++++++++++++-------- server/lib/webmcpclient/polyfill_test.go | 18 +++++++---- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/server/lib/webmcpclient/polyfill.go b/server/lib/webmcpclient/polyfill.go index 317e35e7..69e5c435 100644 --- a/server/lib/webmcpclient/polyfill.go +++ b/server/lib/webmcpclient/polyfill.go @@ -98,6 +98,20 @@ func (c *connection) syncPolyfillTools(ctx context.Context) bool { } func (c *connection) syncPolyfillFrame(ctx context.Context, frame browsersurface.SessionFrame) bool { + // A handle from a replaced document fails once; the retry bridges the + // frame's current document in the same listing. + for attempt := 0; attempt < 2; attempt++ { + changed, stale := c.syncPolyfillBridge(ctx, frame) + if !stale { + return changed + } + } + return false +} + +// syncPolyfillBridge syncs the frame's bridge, creating it when needed, and +// reports whether the bridge's handle was stale. +func (c *connection) syncPolyfillBridge(ctx context.Context, frame browsersurface.SessionFrame) (changed, stale bool) { key := polyfillBridgeKey(frame.SessionID, frame.FrameID) c.stateMu.RLock() bridge := c.polyfillBridges[key] @@ -105,7 +119,7 @@ func (c *connection) syncPolyfillFrame(ctx context.Context, frame browsersurface if bridge == nil { var err error if bridge, err = c.createPolyfillBridge(ctx, frame); err != nil { - return false + return false, false } c.stateMu.Lock() c.polyfillBridges[key] = bridge @@ -124,22 +138,22 @@ func (c *connection) syncPolyfillFrame(ctx context.Context, frame browsersurface err = errors.New("WebMCP: polyfill bridge threw") } if err == nil { - return string(result.Result.Value) == "true" + return string(result.Result.Value) == "true", false } } - // A protocol error means the handle's document is gone; a later sync - // bridges the frame's new document. Timeouts keep the handle, which may - // still own registrations. + // A protocol error means the handle's document is gone. Timeouts keep the + // handle, which may still own registrations. var protocolErr *cdpclient.Error - if errors.As(err, &protocolErr) { - c.stateMu.Lock() - if c.polyfillBridges[key] == bridge { - delete(c.polyfillBridges, key) - } - c.stateMu.Unlock() - go c.releaseObjectGroup(frame.SessionID, bridge.group) + if !errors.As(err, &protocolErr) { + return false, false } - return false + c.stateMu.Lock() + if c.polyfillBridges[key] == bridge { + delete(c.polyfillBridges, key) + } + c.stateMu.Unlock() + go c.releaseObjectGroup(frame.SessionID, bridge.group) + return false, true } type evaluationResult struct { diff --git a/server/lib/webmcpclient/polyfill_test.go b/server/lib/webmcpclient/polyfill_test.go index 3d28d47c..2046b0fc 100644 --- a/server/lib/webmcpclient/polyfill_test.go +++ b/server/lib/webmcpclient/polyfill_test.go @@ -203,13 +203,19 @@ func TestPolyfillBridgeIsRecreatedForANewDocument(t *testing.T) { "params": map[string]any{"frame": map[string]any{"id": "page-frame", "loaderId": "next-loader", "url": "https://merchant.example/next"}}, }) require.Eventually(t, func() bool { - tools, err := manager.Tools(context.Background()) - if err != nil { - return false + for _, tab := range manager.connection.surface.Snapshot().Tabs { + if tab.PageURL == "https://merchant.example/next" { + return true + } } - tool, ok := toolsByName(tools)["poly_search"] - return ok && tool.Ref != oldRef - }, 3*time.Second, 20*time.Millisecond) + return false + }, 3*time.Second, 10*time.Millisecond) + + // The stale handle is replaced within the same listing. + tools, err = manager.Tools(context.Background()) + require.NoError(t, err) + require.Contains(t, toolsByName(tools), "poly_search") + require.NotEqual(t, oldRef, toolsByName(tools)["poly_search"].Ref) fake.mu.Lock() defer fake.mu.Unlock() require.Equal(t, 6, fake.bridgesCreated) From f9a863d9af2b1973a716eea42696281133e943ab Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:16:21 +0000 Subject: [PATCH 5/5] Keep bridged tools out of the page's own tool names Bridged polyfill tools now register under a reserved polyfill. prefix, so a page that later registers the same name natively no longer gets a duplicate-name rejection. Discovery strips the prefix and lists the page's native tool in place of a bridged copy, and the bridge withdraws its copy on the next sync. Also keep the registry entry's title when listTools() omits it, report what a page tool threw from the exception Chromium attaches to WebMCP.toolResponded, and release tool references by registered name so a replaced document never reuses a stale reference. --- server/e2e/e2e_webmcp_polyfill_test.go | 21 +++++++- server/e2e/testdata/webmcp/polyfill.html | 15 ++++++ server/lib/webmcpclient/client.go | 48 +++++++++++++++-- server/lib/webmcpclient/client_test.go | 6 +++ server/lib/webmcpclient/polyfill.go | 5 ++ server/lib/webmcpclient/polyfill_bridge.js | 60 +++++++++++++++++++--- server/lib/webmcpclient/polyfill_test.go | 45 ++++++++++++++-- server/lib/webmcpclient/types.go | 18 +++++-- server/runtime/webmcp-polyfill.test.ts | 51 +++++++++++++----- 9 files changed, 235 insertions(+), 34 deletions(-) diff --git a/server/e2e/e2e_webmcp_polyfill_test.go b/server/e2e/e2e_webmcp_polyfill_test.go index 3223ca5f..db7c6797 100644 --- a/server/e2e/e2e_webmcp_polyfill_test.go +++ b/server/e2e/e2e_webmcp_polyfill_test.go @@ -105,6 +105,8 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. failed := invoke(tools["failing_tool"].ToolRef, map[string]any{}) require.Equal(t, http.StatusOK, failed.StatusCode(), "%s", failed.Body) require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, failed.JSON200.Status) + require.NotNil(t, failed.JSON200.ErrorText) + require.Equal(t, "Error: nothing to do", *failed.JSON200.ErrorText) // Unregistration through the polyfill drops the tool on the next listing, // and the surviving tool keeps its reference. @@ -117,6 +119,23 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. assert.Equal(collect, search.ToolRef, tools["search_items"].ToolRef) }, 10*time.Second, 250*time.Millisecond) + // The page can still register a bridged name natively; the native tool is + // then listed and invoked in place of the bridged copy. + var nativeResult string + executeWebMCPPlaywright(t, ctx, client, ` + await page.click('#register-native'); + await page.locator('#native-result').filter({hasText: /./}).waitFor(); + return page.locator('#native-result').textContent(); + `, &nativeResult) + require.Equal(t, "registered", nativeResult) + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools = toolsByName(collect) + assert.Equal(collect, "Native search.", tools["search_items"].Tool.Description) + }, 10*time.Second, 250*time.Millisecond) + nativeSearch := invoke(tools["search_items"].ToolRef, map[string]any{}) + require.Equal(t, http.StatusOK, nativeSearch.StatusCode(), "%s", nativeSearch.Body) + require.Equal(t, map[string]any{"source": "native"}, nativeSearch.JSON200.Output) + // A tool that navigates its document completes like a native one and the // old registrations disappear with the document. tabID := search.Source.TabId @@ -133,6 +152,6 @@ func testWebMCPPolyfill(t *testing.T, ctx context.Context, client *instanceoapi. assert.NotEqual(collect, tabID, tool.Source.TabId, "%s survived navigation", tool.Tool.Name) } }, 10*time.Second, 250*time.Millisecond) - stale := invoke(search.ToolRef, map[string]any{"query": "x"}) + stale := invoke(tools["search_items"].ToolRef, map[string]any{"query": "x"}) require.Equal(t, http.StatusNotFound, stale.StatusCode(), "%s", stale.Body) } diff --git a/server/e2e/testdata/webmcp/polyfill.html b/server/e2e/testdata/webmcp/polyfill.html index b300b5e8..71cfe7a0 100644 --- a/server/e2e/testdata/webmcp/polyfill.html +++ b/server/e2e/testdata/webmcp/polyfill.html @@ -8,6 +8,8 @@

Catalog

+ +