diff --git a/packages/playwright-core/src/tools/backend/context.ts b/packages/playwright-core/src/tools/backend/context.ts index 0de784b4ff306..132f7e790cadf 100644 --- a/packages/playwright-core/src/tools/backend/context.ts +++ b/packages/playwright-core/src/tools/backend/context.ts @@ -119,7 +119,6 @@ export class Context { private _recordedActions: string[] | undefined; private _disposables: Disposable[] = []; - private _webmcpTools: WebMCPToolDefinition[] = []; private _webmcpToolsSignature = ''; private _runningToolName: string | undefined; @@ -328,16 +327,15 @@ export class Context { } currentWebMCPTools(): WebMCPToolDefinition[] { - return this._webmcpTools; + // Handlers are bound to a tab and frame, always take the fresh ones. + return this._currentTab?.webmcpTools()?.tools.map(tool => tool.mcpTool) ?? []; } maybeNotifyWebMCPToolsChanged() { - const tools = this._currentTab?.webmcpTools()?.tools.map(tool => tool.mcpTool) ?? []; - const signature = JSON.stringify(tools.map(tool => tool.schema)); + const signature = JSON.stringify(this.currentWebMCPTools().map(tool => tool.schema)); if (signature === this._webmcpToolsSignature) return; this._webmcpToolsSignature = signature; - this._webmcpTools = tools; this.options.onWebMCPToolsChanged?.(); } diff --git a/tests/mcp/webmcp-dynamic.spec.ts b/tests/mcp/webmcp-dynamic.spec.ts index 3725780cc5727..8e55c9fe4ceaa 100644 --- a/tests/mcp/webmcp-dynamic.spec.ts +++ b/tests/mcp/webmcp-dynamic.spec.ts @@ -186,6 +186,29 @@ test('only the current tab contributes tools, switching tabs swaps them', async expect(await names()).toEqual(['webmcp_add']); }); +test('a tool registered in an iframe can be called after the page is reloaded', async ({ startClient, server, mcpBrowser }) => { + test.skip(mcpBrowser === 'firefox', 'Firefox does not list tools registered in iframes'); + server.setRoute('/frame', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(`frame${registerScript(kAdd)}`); + }); + server.setRoute('/', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(`top`); + }); + + const { client } = await startClient({ config: webmcpConfig(mcpBrowser) }); + await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } }); + expect(await client.callTool({ name: 'webmcp_add', arguments: { a: 1, b: 2 } })).toHaveResponse({ + result: expect.stringContaining('"text": "3"'), + }); + + await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } }); + expect(await client.callTool({ name: 'webmcp_add', arguments: { a: 2, b: 3 } })).toHaveResponse({ + result: expect.stringContaining('"text": "5"'), + }); +}); + test('--no-webmcp opts out of page tools entirely', async ({ startClient, server, mcpBrowser }) => { server.setRoute('/', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' });