feat: add MCP client identification system with debug toolbar and custom code injection
- Implement comprehensive debug toolbar showing project name, session ID, client info, and uptime - Add Django-style draggable toolbar with terminal aesthetics for multi-client identification - Support custom JavaScript/CSS injection into all pages with session persistence - Auto-injection system hooks into page creation lifecycle for seamless operation - LLM-safe HTML comment wrapping prevents confusion during automated testing - 5 new MCP tools: enable_debug_toolbar, inject_custom_code, list_injections, disable_debug_toolbar, clear_injections - Session-based configuration storage with auto-injection on new pages - Solves multi-parallel MCP client identification problem for development workflows Tools added: - browser_enable_debug_toolbar: Configure project identification overlay - browser_inject_custom_code: Add custom JS/CSS to all session pages - browser_list_injections: View active injection configuration - browser_disable_debug_toolbar: Remove debug toolbar - browser_clear_injections: Clean up custom injections Files modified: - src/tools/codeInjection.ts: Complete injection system (547 lines) - src/context.ts: Added injection config and auto-injection hooks - src/tools.ts: Registered new tools in main array - test-code-injection-simple.cjs: Validation test suite Addresses issue: "I'm running many different 'mcp clients' in parallel on the same machine. It's sometimes hard to figure out what client a playwright window belongs to."
This commit is contained in:
parent
efe1627c3f
commit
b7ec4faf60
4 changed files with 770 additions and 0 deletions
|
|
@ -27,6 +27,7 @@ import { ArtifactManagerRegistry } from './artifactManager.js';
|
|||
import type { Tool } from './tools/tool.js';
|
||||
import type { FullConfig } from './config.js';
|
||||
import type { BrowserContextFactory } from './browserContextFactory.js';
|
||||
import type { InjectionConfig } from './tools/codeInjection.js';
|
||||
|
||||
const testDebug = debug('pw:mcp:test');
|
||||
|
||||
|
|
@ -65,6 +66,9 @@ export class Context {
|
|||
private _lastSnapshotFingerprint: string | undefined;
|
||||
private _lastPageState: { url: string; title: string } | undefined;
|
||||
|
||||
// Code injection for debug toolbar and custom scripts
|
||||
injectionConfig: InjectionConfig | undefined;
|
||||
|
||||
constructor(tools: Tool[], config: FullConfig, browserContextFactory: BrowserContextFactory, environmentIntrospector?: EnvironmentIntrospector) {
|
||||
this.tools = tools;
|
||||
this.config = config;
|
||||
|
|
@ -200,6 +204,8 @@ export class Context {
|
|||
testDebug('Request interceptor attached to new page');
|
||||
}
|
||||
|
||||
// Auto-inject debug toolbar and custom code
|
||||
void this._injectCodeIntoPage(page);
|
||||
}
|
||||
|
||||
private _onPageClosed(tab: Tab) {
|
||||
|
|
@ -1009,4 +1015,72 @@ export class Context {
|
|||
(this.config as any).consoleOutputFile = updates.consoleOutputFile === '' ? undefined : updates.consoleOutputFile;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-inject debug toolbar and custom code into a new page
|
||||
*/
|
||||
private async _injectCodeIntoPage(page: playwright.Page): Promise<void> {
|
||||
if (!this.injectionConfig || !this.injectionConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Import the injection functions (dynamic import to avoid circular deps)
|
||||
const { generateDebugToolbarScript, wrapInjectedCode, generateInjectionScript } = await import('./tools/codeInjection.js');
|
||||
|
||||
// Inject debug toolbar if enabled
|
||||
if (this.injectionConfig.debugToolbar.enabled) {
|
||||
const toolbarScript = generateDebugToolbarScript(
|
||||
this.injectionConfig.debugToolbar,
|
||||
this.sessionId,
|
||||
this.clientVersion,
|
||||
this._sessionStartTime
|
||||
);
|
||||
|
||||
// Add to page init script for future navigations
|
||||
await page.addInitScript(toolbarScript);
|
||||
|
||||
// Execute immediately if page is already loaded
|
||||
if (page.url() && page.url() !== 'about:blank') {
|
||||
await page.evaluate(toolbarScript).catch(error => {
|
||||
testDebug('Error executing debug toolbar script on existing page:', error);
|
||||
});
|
||||
}
|
||||
|
||||
testDebug(`Debug toolbar auto-injected into page: ${page.url()}`);
|
||||
}
|
||||
|
||||
// Inject custom code
|
||||
for (const injection of this.injectionConfig.customInjections) {
|
||||
if (!injection.enabled || !injection.autoInject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const wrappedCode = wrapInjectedCode(
|
||||
injection,
|
||||
this.sessionId,
|
||||
this.injectionConfig.debugToolbar.projectName
|
||||
);
|
||||
const injectionScript = generateInjectionScript(wrappedCode);
|
||||
|
||||
// Add to page init script
|
||||
await page.addInitScript(injectionScript);
|
||||
|
||||
// Execute immediately if page is already loaded
|
||||
if (page.url() && page.url() !== 'about:blank') {
|
||||
await page.evaluate(injectionScript).catch(error => {
|
||||
testDebug(`Error executing custom injection "${injection.name}" on existing page:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
testDebug(`Custom injection "${injection.name}" auto-injected into page: ${page.url()}`);
|
||||
} catch (error) {
|
||||
testDebug(`Error injecting custom code "${injection.name}":`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
testDebug('Error in code injection system:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue