From b7ec4faf60bc5eb9ac9bcb50b82a66a1422020f6 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 9 Sep 2025 06:25:38 -0600 Subject: [PATCH] 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." --- src/context.ts | 74 +++++ src/tools.ts | 2 + src/tools/codeInjection.ts | 547 +++++++++++++++++++++++++++++++++ test-code-injection-simple.cjs | 147 +++++++++ 4 files changed, 770 insertions(+) create mode 100644 src/tools/codeInjection.ts create mode 100755 test-code-injection-simple.cjs diff --git a/src/context.ts b/src/context.ts index db6ea62..83eedc0 100644 --- a/src/context.ts +++ b/src/context.ts @@ -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 { + 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); + } + } } diff --git a/src/tools.ts b/src/tools.ts index a6ddc64..f947a96 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -16,6 +16,7 @@ import artifacts from './tools/artifacts.js'; import common from './tools/common.js'; +import codeInjection from './tools/codeInjection.js'; import configure from './tools/configure.js'; import console from './tools/console.js'; import dialogs from './tools/dialogs.js'; @@ -39,6 +40,7 @@ import type { FullConfig } from './config.js'; export const allTools: Tool[] = [ ...artifacts, + ...codeInjection, ...common, ...configure, ...console, diff --git a/src/tools/codeInjection.ts b/src/tools/codeInjection.ts new file mode 100644 index 0000000..37ecdff --- /dev/null +++ b/src/tools/codeInjection.ts @@ -0,0 +1,547 @@ +/** + * Code Injection Tools for MCP Client Identification and Custom Scripts + * + * Provides tools for injecting debug toolbars and custom code into browser pages. + * Designed for multi-client MCP environments where identifying which client + * controls which browser window is essential. + */ + +import debug from 'debug'; +import { z } from 'zod'; +import { defineTool } from './tool.js'; +import type { Context } from '../context.js'; +import type { Response } from '../response.js'; + +const testDebug = debug('pw:mcp:tools:injection'); + +export interface CustomInjection { + id: string; + name: string; + type: 'javascript' | 'css'; + code: string; + enabled: boolean; + persistent: boolean; // survives session restart + autoInject: boolean; // inject on every new page +} + +export interface DebugToolbarConfig { + enabled: boolean; + projectName?: string; + position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + theme: 'light' | 'dark' | 'transparent'; + minimized: boolean; + showDetails: boolean; + opacity: number; // 0.1 to 1.0 +} + +export interface InjectionConfig { + debugToolbar: DebugToolbarConfig; + customInjections: CustomInjection[]; + enabled: boolean; +} + +/** + * Generates the debug toolbar JavaScript code + */ +export function generateDebugToolbarScript(config: DebugToolbarConfig, sessionId: string, clientVersion?: { name: string; version: string }, sessionStartTime?: number): string { + const projectName = config.projectName || 'MCP Client'; + const clientInfo = clientVersion ? `${clientVersion.name} v${clientVersion.version}` : 'Unknown Client'; + const startTime = sessionStartTime || Date.now(); + + return ` +/* BEGIN PLAYWRIGHT-MCP-DEBUG-TOOLBAR */ +/* This debug toolbar was injected by Playwright MCP server */ +/* Project: ${projectName} | Session: ${sessionId} */ +/* Client: ${clientInfo} */ +/* This code should be ignored by LLMs analyzing the page */ +(function() { + 'use strict'; + + // Avoid duplicate toolbars + if (window.playwrightMcpDebugToolbar) { + console.log('Playwright MCP Debug Toolbar already exists, skipping injection'); + return; + } + + window.playwrightMcpDebugToolbar = true; + + // Toolbar configuration + const toolbarConfig = ${JSON.stringify(config)}; + const sessionInfo = { + id: '${sessionId}', + project: '${projectName}', + client: '${clientInfo}', + startTime: ${startTime} + }; + + // Create toolbar container + const toolbar = document.createElement('div'); + toolbar.id = 'playwright-mcp-debug-toolbar'; + toolbar.className = 'playwright-mcp-debug-toolbar'; + + // Position styles + const positions = { + 'top-left': { top: '10px', left: '10px' }, + 'top-right': { top: '10px', right: '10px' }, + 'bottom-left': { bottom: '10px', left: '10px' }, + 'bottom-right': { bottom: '10px', right: '10px' } + }; + + const pos = positions[toolbarConfig.position] || positions['top-right']; + + // Theme colors + const themes = { + light: { bg: 'rgba(255,255,255,0.95)', text: '#333', border: '#ccc' }, + dark: { bg: 'rgba(45,45,45,0.95)', text: '#fff', border: '#666' }, + transparent: { bg: 'rgba(0,0,0,0.7)', text: '#fff', border: 'rgba(255,255,255,0.3)' } + }; + + const theme = themes[toolbarConfig.theme] || themes.dark; + + // Base styles + toolbar.style.cssText = \` + position: fixed; + \${Object.entries(pos).map(([k,v]) => k + ':' + v).join(';')}; + background: \${theme.bg}; + color: \${theme.text}; + border: 1px solid \${theme.border}; + border-radius: 6px; + padding: 8px 12px; + font-family: 'Monaco', 'Menlo', 'Consolas', monospace; + font-size: 12px; + line-height: 1.4; + z-index: 999999; + opacity: \${toolbarConfig.opacity}; + cursor: move; + user-select: none; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + min-width: 150px; + max-width: 300px; + \`; + + // Create content + function updateToolbarContent() { + const uptime = Math.floor((Date.now() - sessionInfo.startTime) / 1000); + const hours = Math.floor(uptime / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = uptime % 60; + const uptimeStr = hours > 0 ? + \`\${hours}h \${minutes}m \${seconds}s\` : + minutes > 0 ? \`\${minutes}m \${seconds}s\` : \`\${seconds}s\`; + + if (toolbarConfig.minimized) { + toolbar.innerHTML = \` +
+ โ— + + \${sessionInfo.project} + + โŠž +
+ \`; + } else { + toolbar.innerHTML = \` +
+
+ โ— + \${sessionInfo.project} +
+ โŠŸ +
+ \${toolbarConfig.showDetails ? \` +
+
Session: \${sessionInfo.id.substring(0, 12)}...
+
Client: \${sessionInfo.client}
+
Uptime: \${uptimeStr}
+
URL: \${window.location.hostname}
+
+ \` : ''} + \`; + } + } + + // Toggle function + toolbar.playwrightToggle = function() { + toolbarConfig.minimized = !toolbarConfig.minimized; + updateToolbarContent(); + }; + + // Dragging functionality + let isDragging = false; + let dragOffset = { x: 0, y: 0 }; + + toolbar.addEventListener('mousedown', function(e) { + isDragging = true; + dragOffset.x = e.clientX - toolbar.offsetLeft; + dragOffset.y = e.clientY - toolbar.offsetTop; + toolbar.style.cursor = 'grabbing'; + e.preventDefault(); + }); + + document.addEventListener('mousemove', function(e) { + if (isDragging) { + toolbar.style.left = (e.clientX - dragOffset.x) + 'px'; + toolbar.style.top = (e.clientY - dragOffset.y) + 'px'; + // Remove position properties when dragging + toolbar.style.right = 'auto'; + toolbar.style.bottom = 'auto'; + } + }); + + document.addEventListener('mouseup', function() { + if (isDragging) { + isDragging = false; + toolbar.style.cursor = 'move'; + } + }); + + // Update content initially and every second + updateToolbarContent(); + setInterval(updateToolbarContent, 1000); + + // Add to page + document.body.appendChild(toolbar); + + console.log(\`[Playwright MCP] Debug toolbar injected - Project: \${sessionInfo.project}, Session: \${sessionInfo.id}\`); +})(); +/* END PLAYWRIGHT-MCP-DEBUG-TOOLBAR */ +`; +} + +/** + * Wraps custom code with LLM-safe markers + */ +export function wrapInjectedCode(injection: CustomInjection, sessionId: string, projectName?: string): string { + const projectInfo = projectName ? ` | Project: ${projectName}` : ''; + const header = ` + +`; + const footer = ``; + + if (injection.type === 'javascript') { + return `${header} + +${footer}`; + } else if (injection.type === 'css') { + return `${header} + +${footer}`; + } + + return `${header} +${injection.code} +${footer}`; +} + +/** + * Generates JavaScript to inject code into the page + */ +export function generateInjectionScript(wrappedCode: string): string { + return ` +(function() { + try { + const injectionContainer = document.createElement('div'); + injectionContainer.innerHTML = \`${wrappedCode.replace(/`/g, '\\`')}\`; + + // Extract and execute scripts + const scripts = injectionContainer.querySelectorAll('script'); + scripts.forEach(script => { + const newScript = document.createElement('script'); + if (script.src) { + newScript.src = script.src; + } else { + newScript.textContent = script.textContent; + } + document.head.appendChild(newScript); + }); + + // Extract and add styles + const styles = injectionContainer.querySelectorAll('style'); + styles.forEach(style => { + document.head.appendChild(style.cloneNode(true)); + }); + + // Add any remaining content to body + const remaining = injectionContainer.children; + for (let i = 0; i < remaining.length; i++) { + if (remaining[i].tagName !== 'SCRIPT' && remaining[i].tagName !== 'STYLE') { + document.body.appendChild(remaining[i].cloneNode(true)); + } + } + } catch (error) { + console.error('[Playwright MCP] Injection error:', error); + } +})(); +`; +} + +// Tool schemas +const enableDebugToolbarSchema = z.object({ + projectName: z.string().optional().describe('Name of your project/client to display in the toolbar'), + position: z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']).optional().describe('Position of the toolbar on screen'), + theme: z.enum(['light', 'dark', 'transparent']).optional().describe('Visual theme for the toolbar'), + minimized: z.boolean().optional().describe('Start toolbar in minimized state'), + showDetails: z.boolean().optional().describe('Show session details in expanded view'), + opacity: z.number().min(0.1).max(1.0).optional().describe('Toolbar opacity') +}); + +const injectCustomCodeSchema = z.object({ + name: z.string().describe('Unique name for this injection'), + type: z.enum(['javascript', 'css']).describe('Type of code to inject'), + code: z.string().describe('The JavaScript or CSS code to inject'), + persistent: z.boolean().optional().describe('Keep injection active across session restarts'), + autoInject: z.boolean().optional().describe('Automatically inject on every new page') +}); + +const clearInjectionsSchema = z.object({ + includeToolbar: z.boolean().optional().describe('Also disable debug toolbar') +}); + +// Tool definitions +const enableDebugToolbar = defineTool({ + capability: 'core', + schema: { + name: 'browser_enable_debug_toolbar', + title: 'Enable Debug Toolbar', + description: 'Enable the debug toolbar to identify which MCP client is controlling the browser', + inputSchema: enableDebugToolbarSchema, + type: 'destructive', + }, + handle: async (context: Context, params: z.output, response: Response) => { + testDebug('Enabling debug toolbar with params:', params); + + const config: DebugToolbarConfig = { + enabled: true, + projectName: params.projectName || 'MCP Client', + position: params.position || 'top-right', + theme: params.theme || 'dark', + minimized: params.minimized || false, + showDetails: params.showDetails !== false, + opacity: params.opacity || 0.9 + }; + + // Store config in context + if (!context.injectionConfig) { + context.injectionConfig = { + debugToolbar: config, + customInjections: [], + enabled: true + }; + } else { + context.injectionConfig.debugToolbar = config; + context.injectionConfig.enabled = true; + } + + // Generate toolbar script + const toolbarScript = generateDebugToolbarScript(config, context.sessionId, context.clientVersion, (context as any)._sessionStartTime); + + // Inject into current page if available + const currentTab = context.currentTab(); + if (currentTab) { + try { + await currentTab.page.addInitScript(toolbarScript); + await currentTab.page.evaluate(toolbarScript); + testDebug('Debug toolbar injected into current page'); + } catch (error) { + testDebug('Error injecting toolbar into current page:', error); + } + } + + const resultMessage = `Debug toolbar enabled for project "${config.projectName}"`; + response.addResult(resultMessage); + response.addResult(`Session ID: ${context.sessionId}`); + response.addResult(`Auto-injection enabled for new pages`); + } +}); + +const injectCustomCode = defineTool({ + capability: 'core', + schema: { + name: 'browser_inject_custom_code', + title: 'Inject Custom Code', + description: 'Inject custom JavaScript or CSS code into all pages in the current session', + inputSchema: injectCustomCodeSchema, + type: 'destructive', + }, + handle: async (context: Context, params: z.output, response: Response) => { + testDebug('Injecting custom code:', { name: params.name, type: params.type }); + + if (!context.injectionConfig) { + context.injectionConfig = { + debugToolbar: { enabled: false, minimized: false, showDetails: true, position: 'top-right', theme: 'dark', opacity: 0.9 }, + customInjections: [], + enabled: true + }; + } + + // Create injection object + const injection: CustomInjection = { + id: `${params.name}_${Date.now()}`, + name: params.name, + type: params.type, + code: params.code, + enabled: true, + persistent: params.persistent !== false, + autoInject: params.autoInject !== false + }; + + // Remove any existing injection with the same name + context.injectionConfig.customInjections = context.injectionConfig.customInjections.filter( + inj => inj.name !== params.name + ); + + // Add new injection + context.injectionConfig.customInjections.push(injection); + + // Wrap code with LLM-safe markers + const wrappedCode = wrapInjectedCode(injection, context.sessionId, context.injectionConfig.debugToolbar.projectName); + const injectionScript = generateInjectionScript(wrappedCode); + + // Inject into current page if available + const currentTab = context.currentTab(); + if (currentTab && injection.autoInject) { + try { + await currentTab.page.addInitScript(injectionScript); + await currentTab.page.evaluate(injectionScript); + testDebug('Custom code injected into current page'); + } catch (error) { + testDebug('Error injecting custom code into current page:', error); + } + } + + response.addResult(`Custom ${params.type} injection "${params.name}" added successfully`); + response.addResult(`Total injections: ${context.injectionConfig.customInjections.length}`); + response.addResult(`Auto-inject enabled: ${injection.autoInject}`); + } +}); + +const listInjections = defineTool({ + capability: 'core', + schema: { + name: 'browser_list_injections', + title: 'List Injections', + description: 'List all active code injections for the current session', + inputSchema: z.object({}), + type: 'readOnly', + }, + handle: async (context: Context, params: any, response: Response) => { + const config = context.injectionConfig; + + if (!config) { + response.addResult('No injection configuration found'); + return; + } + + response.addResult(`Session ID: ${context.sessionId}`); + response.addResult(`\nDebug Toolbar:`); + response.addResult(`- Enabled: ${config.debugToolbar.enabled}`); + if (config.debugToolbar.enabled) { + response.addResult(`- Project: ${config.debugToolbar.projectName}`); + response.addResult(`- Position: ${config.debugToolbar.position}`); + response.addResult(`- Theme: ${config.debugToolbar.theme}`); + response.addResult(`- Minimized: ${config.debugToolbar.minimized}`); + } + + response.addResult(`\nCustom Injections (${config.customInjections.length}):`); + if (config.customInjections.length === 0) { + response.addResult('- None'); + } else { + config.customInjections.forEach(inj => { + response.addResult(`- ${inj.name} (${inj.type}): ${inj.enabled ? 'Enabled' : 'Disabled'}`); + response.addResult(` Auto-inject: ${inj.autoInject}, Persistent: ${inj.persistent}`); + response.addResult(` Code length: ${inj.code.length} characters`); + }); + } + } +}); + +const disableDebugToolbar = defineTool({ + capability: 'core', + schema: { + name: 'browser_disable_debug_toolbar', + title: 'Disable Debug Toolbar', + description: 'Disable the debug toolbar for the current session', + inputSchema: z.object({}), + type: 'destructive', + }, + handle: async (context: Context, params: any, response: Response) => { + if (context.injectionConfig) { + context.injectionConfig.debugToolbar.enabled = false; + } + + // Remove from current page if available + const currentTab = context.currentTab(); + if (currentTab) { + try { + await currentTab.page.evaluate(() => { + const toolbar = document.getElementById('playwright-mcp-debug-toolbar'); + if (toolbar) { + toolbar.remove(); + } + (window as any).playwrightMcpDebugToolbar = false; + }); + testDebug('Debug toolbar removed from current page'); + } catch (error) { + testDebug('Error removing toolbar from current page:', error); + } + } + + response.addResult('Debug toolbar disabled'); + } +}); + +const clearInjections = defineTool({ + capability: 'core', + schema: { + name: 'browser_clear_injections', + title: 'Clear Injections', + description: 'Remove all custom code injections (keeps debug toolbar)', + inputSchema: clearInjectionsSchema, + type: 'destructive', + }, + handle: async (context: Context, params: z.output, response: Response) => { + if (!context.injectionConfig) { + response.addResult('No injections to clear'); + return; + } + + const clearedCount = context.injectionConfig.customInjections.length; + context.injectionConfig.customInjections = []; + + if (params.includeToolbar) { + context.injectionConfig.debugToolbar.enabled = false; + + // Remove toolbar from current page + const currentTab = context.currentTab(); + if (currentTab) { + try { + await currentTab.page.evaluate(() => { + const toolbar = document.getElementById('playwright-mcp-debug-toolbar'); + if (toolbar) { + toolbar.remove(); + } + (window as any).playwrightMcpDebugToolbar = false; + }); + } catch (error) { + testDebug('Error removing toolbar from current page:', error); + } + } + } + + response.addResult(`Cleared ${clearedCount} custom injections${params.includeToolbar ? ' and disabled debug toolbar' : ''}`); + } +}); + +export default [ + enableDebugToolbar, + injectCustomCode, + listInjections, + disableDebugToolbar, + clearInjections, +]; \ No newline at end of file diff --git a/test-code-injection-simple.cjs b/test-code-injection-simple.cjs new file mode 100755 index 0000000..96809bd --- /dev/null +++ b/test-code-injection-simple.cjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node + +/** + * Simple test to verify code injection tools are available + */ + +const { spawn } = require('child_process'); + +async function runMCPCommand(toolName, params = {}, timeoutMs = 15000) { + return new Promise((resolve, reject) => { + const mcp = spawn('node', ['cli.js'], { + stdio: ['pipe', 'pipe', 'pipe'], + cwd: __dirname + }); + + let stdout = ''; + let stderr = ''; + + const timeout = setTimeout(() => { + mcp.kill(); + reject(new Error(`Timeout after ${timeoutMs}ms`)); + }, timeoutMs); + + mcp.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + mcp.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + mcp.on('close', (code) => { + clearTimeout(timeout); + resolve({ code, stdout, stderr }); + }); + + const request = { + jsonrpc: '2.0', + id: Date.now(), + method: 'tools/call', + params: { + name: toolName, + arguments: params + } + }; + + mcp.stdin.write(JSON.stringify(request) + '\n'); + mcp.stdin.end(); + }); +} + +async function testCodeInjectionTools() { + console.log('๐Ÿงช Testing Code Injection Tools...\n'); + + try { + // Test 1: List tools to verify code injection tools are available + console.log('๐Ÿ“‹ 1. Checking available tools...'); + const listResult = await runMCPCommand('tools/list', {}); + + if (listResult.stderr) { + console.log('stderr:', listResult.stderr); + } + + const response = JSON.parse(listResult.stdout.split('\n')[0]); + const tools = response.result?.tools || []; + + const injectionTools = tools.filter(tool => + tool.name.includes('debug_toolbar') || tool.name.includes('inject') + ); + + console.log(`โœ… Found ${injectionTools.length} code injection tools:`); + injectionTools.forEach(tool => console.log(` - ${tool.name}: ${tool.description}`)); + + // Test 2: Enable debug toolbar + console.log('\n๐Ÿท๏ธ 2. Testing debug toolbar activation...'); + const toolbarResult = await runMCPCommand('browser_enable_debug_toolbar', { + projectName: 'Test Project', + position: 'top-right', + theme: 'dark', + minimized: false, + showDetails: true, + opacity: 0.9 + }); + + if (toolbarResult.stderr) { + console.log('stderr:', toolbarResult.stderr); + } + + if (toolbarResult.stdout) { + const toolbarResponse = JSON.parse(toolbarResult.stdout.split('\n')[0]); + if (toolbarResponse.result) { + console.log('โœ… Debug toolbar enabled successfully'); + toolbarResponse.result.content?.forEach(item => + console.log(` ${item.text}`) + ); + } + } + + // Test 3: List injections + console.log('\n๐Ÿ“Š 3. Testing injection listing...'); + const listInjectionsResult = await runMCPCommand('browser_list_injections', {}); + + if (listInjectionsResult.stdout) { + const listResponse = JSON.parse(listInjectionsResult.stdout.split('\n')[0]); + if (listResponse.result) { + console.log('โœ… Injection listing works:'); + listResponse.result.content?.forEach(item => + console.log(` ${item.text}`) + ); + } + } + + // Test 4: Add custom injection + console.log('\n๐Ÿ’‰ 4. Testing custom code injection...'); + const injectionResult = await runMCPCommand('browser_inject_custom_code', { + name: 'test-console-log', + type: 'javascript', + code: 'console.log("Test injection from MCP client identification system!");', + persistent: true, + autoInject: true + }); + + if (injectionResult.stdout) { + const injectionResponse = JSON.parse(injectionResult.stdout.split('\n')[0]); + if (injectionResponse.result) { + console.log('โœ… Custom code injection works:'); + injectionResponse.result.content?.forEach(item => + console.log(` ${item.text}`) + ); + } + } + + console.log('\n๐ŸŽ‰ All code injection tools are working correctly!'); + console.log('\n๐Ÿ’ก The system provides:'); + console.log(' โœ… Debug toolbar for client identification'); + console.log(' โœ… Custom code injection capabilities'); + console.log(' โœ… Session persistence'); + console.log(' โœ… Auto-injection on new pages'); + console.log(' โœ… LLM-safe code wrapping'); + + } catch (error) { + console.error('โŒ Test failed:', error.message); + process.exit(1); + } +} + +testCodeInjectionTools().catch(console.error); \ No newline at end of file