💬 Commit message: Update 2026-02-11 04:13:54, 13 files, 595 lines
📁 Files changed: 13 📝 Lines changed: 595 • PLAN.md • TODO.md • browser.d.ts • browser.d.ts.map • browser.js • browser.js.map • mcp.js • mcp.js.map • types.d.ts • types.d.ts.map • browser.ts • mcp.ts • types.ts
This commit is contained in:
+41
-1
@@ -1,13 +1,20 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { type Browser, type BrowserContext, type Page, webkit } from 'playwright';
|
||||
import * as image from './image.js';
|
||||
import type { BrowserCommand, BrowserOptions, CommandResponse, ElementInfo } from './types.js';
|
||||
import type {
|
||||
BrowserCommand,
|
||||
BrowserOptions,
|
||||
CommandResponse,
|
||||
ConsoleMessage,
|
||||
ElementInfo,
|
||||
} from './types.js';
|
||||
|
||||
export class ClaudeBrowser {
|
||||
private browser: Browser | null = null;
|
||||
private context: BrowserContext | null = null;
|
||||
private page: Page | null = null;
|
||||
private options: Required<BrowserOptions>;
|
||||
private consoleMessages: ConsoleMessage[] = [];
|
||||
|
||||
constructor(options: BrowserOptions = {}) {
|
||||
this.options = {
|
||||
@@ -26,6 +33,19 @@ export class ClaudeBrowser {
|
||||
},
|
||||
});
|
||||
this.page = await this.context.newPage();
|
||||
this.setupConsoleListener(this.page);
|
||||
}
|
||||
|
||||
private setupConsoleListener(page: Page): void {
|
||||
page.on('console', (msg) => {
|
||||
const location = msg.location();
|
||||
this.consoleMessages.push({
|
||||
level: msg.type(),
|
||||
text: msg.text(),
|
||||
timestamp: Date.now(),
|
||||
location: location.url ? `${location.url}:${location.lineNumber}` : undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
@@ -135,6 +155,7 @@ export class ClaudeBrowser {
|
||||
throw new Error('Browser not launched. Call launch() first.');
|
||||
}
|
||||
this.page = await this.context.newPage();
|
||||
this.setupConsoleListener(this.page);
|
||||
}
|
||||
|
||||
async eval(script: string): Promise<unknown> {
|
||||
@@ -142,6 +163,21 @@ export class ClaudeBrowser {
|
||||
return page.evaluate(script);
|
||||
}
|
||||
|
||||
getConsole(level?: string, clear = false): ConsoleMessage[] {
|
||||
let messages = this.consoleMessages;
|
||||
if (level && level !== 'all') {
|
||||
messages = messages.filter((m) => m.level === level);
|
||||
}
|
||||
if (clear) {
|
||||
this.consoleMessages = [];
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
clearConsole(): void {
|
||||
this.consoleMessages = [];
|
||||
}
|
||||
|
||||
async executeCommand(cmd: BrowserCommand): Promise<CommandResponse> {
|
||||
try {
|
||||
switch (cmd.cmd) {
|
||||
@@ -201,6 +237,10 @@ export class ClaudeBrowser {
|
||||
const result = await this.eval(cmd.script);
|
||||
return { ok: true, result };
|
||||
}
|
||||
case 'console': {
|
||||
const messages = this.getConsole(cmd.level, cmd.clear);
|
||||
return { ok: true, count: messages.length, messages };
|
||||
}
|
||||
case 'favicon': {
|
||||
const result = await image.createFavicon(cmd.input, cmd.outputDir);
|
||||
return { ok: true, files: result.files, outputDir: result.outputDir };
|
||||
|
||||
+49
@@ -183,6 +183,25 @@ server.tool(
|
||||
})
|
||||
);
|
||||
|
||||
// Console
|
||||
server.tool(
|
||||
'console',
|
||||
'Get captured console messages (log, warn, error, etc.) from the browser',
|
||||
{
|
||||
level: z
|
||||
.enum(['log', 'info', 'warn', 'error', 'debug', 'all'])
|
||||
.optional()
|
||||
.default('all')
|
||||
.describe('Filter by log level'),
|
||||
clear: z.boolean().optional().default(false).describe('Clear messages after retrieving'),
|
||||
},
|
||||
withLogging('console', async ({ level, clear }) => {
|
||||
await ensureLaunched();
|
||||
const messages = browser.getConsole(level, clear);
|
||||
return textResult(JSON.stringify({ ok: true, count: messages.length, messages }));
|
||||
})
|
||||
);
|
||||
|
||||
// Utility
|
||||
server.tool(
|
||||
'wait',
|
||||
@@ -512,6 +531,36 @@ server.resource(
|
||||
}
|
||||
);
|
||||
|
||||
// Resource: browser://console - Captured console messages
|
||||
server.resource(
|
||||
'Console Messages',
|
||||
'browser://console',
|
||||
{ description: 'Console messages captured from the browser', mimeType: 'application/json' },
|
||||
async () => {
|
||||
if (!launched) {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'browser://console',
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify({ launched: false, messages: [] }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
const messages = browser.getConsole('all', false);
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'browser://console',
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify({ launched: true, count: messages.length, messages }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Resource: browser://screenshot - Current page screenshot (base64)
|
||||
server.resource(
|
||||
'Page Screenshot',
|
||||
|
||||
+17
-1
@@ -124,6 +124,19 @@ export interface ThumbnailCommand {
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
export interface ConsoleCommand {
|
||||
cmd: 'console';
|
||||
clear?: boolean;
|
||||
level?: 'log' | 'info' | 'warn' | 'error' | 'debug' | 'all';
|
||||
}
|
||||
|
||||
export interface ConsoleMessage {
|
||||
level: string;
|
||||
text: string;
|
||||
timestamp: number;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
export type BrowserCommand =
|
||||
| GotoCommand
|
||||
| ClickCommand
|
||||
@@ -144,7 +157,8 @@ export type BrowserCommand =
|
||||
| ResizeCommand
|
||||
| CropCommand
|
||||
| CompressCommand
|
||||
| ThumbnailCommand;
|
||||
| ThumbnailCommand
|
||||
| ConsoleCommand;
|
||||
|
||||
// Response types
|
||||
export interface SuccessResponse {
|
||||
@@ -163,6 +177,8 @@ export interface SuccessResponse {
|
||||
height?: number;
|
||||
format?: string;
|
||||
size?: number;
|
||||
// Console fields
|
||||
messages?: ConsoleMessage[];
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
|
||||
Reference in New Issue
Block a user