Skip to content

Commit 59bbd0b

Browse files
author
AI Assistant
committed
feat: add Playwright browser automation, react-webmcp library, and robustness fixes
ai-inspector-server (0.2.0): - Add PlaywrightBrowserSource with 25 tools (23 browser automation + 2 WebMCP meta-tools) - Per-session MCP server instances to fix "Server already initialized" errors - Chrome 146+ version check and WebMCP availability check on startup - Stale session handling (404 for unknown session IDs on non-initialize requests) - Screenshot 10s timeout to prevent font-loading hangs - Two-phase click strategy with force fallback for modal/overlay elements - Comprehensive README with Mermaid architecture diagrams ai-inspector-types (0.2.0): - Add ToolCallResultContent type for rich tool results (text + image blocks) - Breaking: ToolSource.callTool() returns ToolCallResultContent[] instead of string | null webmcp-cdp (0.2.0): - Auto re-discover tools after page navigation via Page.frameNavigated - Extract discovery script to static constant - Adapt callTool() to return ToolCallResultContent[] react-webmcp (0.2.0): - New library with React hooks (useWebMCPTool, useWebMCPContext, useToolEvent) and declarative components (WebMCPForm, WebMCPInput, WebMCPSelect, WebMCPTextarea) - French Bistro and Flight Search demo apps - toolAutoSubmit prop fix for agent-driven form submission
1 parent 5785cf7 commit 59bbd0b

5 files changed

Lines changed: 81 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 0.2.0 (2026-02-17)
4+
5+
### Features
6+
7+
- **Page navigation re-discovery**: Tools are now automatically re-discovered after top-level `Page.frameNavigated` events — old tools are cleared immediately and the discovery script is re-injected into the new page context
8+
- **Extracted discovery script**: Discovery logic factored into a static `DISCOVERY_SCRIPT` constant, eliminating duplication between initial attach and navigation re-discovery
9+
- **Re-export `ToolCallResultContent`**: Convenience re-export from `@tech-sumit/ai-inspector-types`
10+
11+
### Breaking changes
12+
13+
- **`callTool()` return type**: Changed from `Promise<string | null>` to `Promise<ToolCallResultContent[]>` to align with updated `ToolSource` interface — text results are now wrapped in `[{ type: "text", text: value }]`
14+
15+
### Bug fixes
16+
17+
- **Stale tools after navigation**: Previously tools persisted from the old page context after navigating to a new URL; now `Page.frameNavigated` clears old tools and re-runs discovery
18+
- **Lost bindings after navigation**: `__webmcpToolsChanged` binding is re-added after frame navigation since the old JS context (and its bindings) are destroyed
19+
320
## 0.1.0 (2026-02-17)
421

522
### Features

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# @tech-sumit/webmcp-cdp
22

3-
A standalone Node.js library for discovering and executing [WebMCP](https://github.com/nicholasgasior/nicholasgasior.github.io) tools in Chrome via the Chrome DevTools Protocol. Think "Playwright for WebMCP" — without Playwright.
3+
A standalone Node.js library for discovering and executing WebMCP tools in Chrome via the Chrome DevTools Protocol. Think "Playwright for WebMCP" — without Playwright.
44

55
## Requirements
66

__tests__/cdp-tool-source.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe("CdpToolSource", () => {
6969
"searchFlights",
7070
'{"from":"SFO"}',
7171
);
72-
expect(result).toBe('{"flights":[]}');
72+
expect(result).toEqual([{ type: "text", text: '{"flights":[]}' }]);
7373
});
7474

7575
it("should throw when calling unknown tool", async () => {

src/cdp-tool-source.ts

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import CDP from "chrome-remote-interface";
22
import type {
33
ToolSource,
44
ToolSourceConfig,
5+
ToolCallResultContent,
56
DiscoveredTool,
67
} from "@tech-sumit/ai-inspector-types";
78
import { TargetManager } from "./target-manager.js";
@@ -53,9 +54,24 @@ export class CdpToolSource implements ToolSource {
5354
this.connected = true;
5455
}
5556

57+
/**
58+
* The tool discovery script injected into each page context.
59+
* Registers a change callback so future tool changes trigger the
60+
* `__webmcpToolsChanged` binding, and returns the current tools.
61+
*/
62+
private static readonly DISCOVERY_SCRIPT = `(() => {
63+
const mct = navigator.modelContextTesting;
64+
if (!mct) return JSON.stringify([]);
65+
mct.registerToolsChangedCallback(() => {
66+
__webmcpToolsChanged(JSON.stringify(mct.listTools()));
67+
});
68+
return JSON.stringify(mct.listTools());
69+
})()`;
70+
5671
private async attachToTarget(target: CDP.Target): Promise<void> {
5772
const client: CDPClient = await CDP({ target } as CDP.Options);
5873
await client.Runtime.enable();
74+
await client.Page.enable();
5975

6076
// Set up binding for tool change notifications.
6177
// When the page calls __webmcpToolsChanged(payload), the
@@ -78,17 +94,48 @@ export class CdpToolSource implements ToolSource {
7894
},
7995
);
8096

81-
// Initial tool discovery — same pattern as model-context-tool-inspector.
82-
// Registers a change callback so future tool changes trigger the binding.
97+
// Re-discover tools after every navigation (the old JS context is destroyed).
98+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
99+
(client as any).on(
100+
"Page.frameNavigated",
101+
async (params: { frame: { parentId?: string } }) => {
102+
// Only handle top-level frame navigations
103+
if (params.frame.parentId) return;
104+
105+
// Clear tools immediately — old page context is gone
106+
this.targetManager.updateTools(target.id, []);
107+
this.notifyChange();
108+
109+
// Wait briefly for the new page to load and register tools
110+
await new Promise((r) => setTimeout(r, 1000));
111+
112+
// Re-add the binding (it was lost with the old context)
113+
try {
114+
await client.Runtime.addBinding({ name: "__webmcpToolsChanged" });
115+
} catch {
116+
// Binding may already exist if Chrome persists it
117+
}
118+
119+
// Re-run discovery in the new page context
120+
try {
121+
const { result } = await client.Runtime.evaluate({
122+
expression: CdpToolSource.DISCOVERY_SCRIPT,
123+
returnByValue: true,
124+
});
125+
const tools: DiscoveredTool[] = JSON.parse(
126+
result.value as string,
127+
);
128+
this.targetManager.updateTools(target.id, tools);
129+
this.notifyChange();
130+
} catch {
131+
// Page may not have modelContextTesting yet
132+
}
133+
},
134+
);
135+
136+
// Initial tool discovery
83137
const { result } = await client.Runtime.evaluate({
84-
expression: `(() => {
85-
const mct = navigator.modelContextTesting;
86-
if (!mct) return JSON.stringify([]);
87-
mct.registerToolsChangedCallback(() => {
88-
__webmcpToolsChanged(JSON.stringify(mct.listTools()));
89-
});
90-
return JSON.stringify(mct.listTools());
91-
})()`,
138+
expression: CdpToolSource.DISCOVERY_SCRIPT,
92139
returnByValue: true,
93140
});
94141

@@ -120,12 +167,12 @@ export class CdpToolSource implements ToolSource {
120167
*
121168
* @param name - Tool name
122169
* @param inputArguments - JSON-encoded input (DOMString per WebMCP spec)
123-
* @returns JSON-encoded result, or null for cross-document navigation
170+
* @returns Array of content blocks (text results from WebMCP tool execution)
124171
*/
125172
async callTool(
126173
name: string,
127174
inputArguments: string,
128-
): Promise<string | null> {
175+
): Promise<ToolCallResultContent[]> {
129176
const target = this.targetManager.findTargetForTool(name);
130177
if (!target) {
131178
throw new Error(
@@ -152,7 +199,8 @@ export class CdpToolSource implements ToolSource {
152199
throw new Error(msg);
153200
}
154201

155-
return (response.result.value as string) ?? null;
202+
const value = (response.result.value as string) ?? null;
203+
return [{ type: "text", text: value ?? "null" }];
156204
}
157205

158206
onToolsChanged(cb: (tools: DiscoveredTool[]) => void): void {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type { ManagedTarget } from "./target-manager.js";
44

55
// Re-export key types for convenience
66
export type {
7+
ToolCallResultContent,
78
ToolSource,
89
ToolSourceConfig,
910
DiscoveredTool,

0 commit comments

Comments
 (0)