Skip to main content
Glama
get_console_logs.ts9.74 kB
import { BrowserToolBase } from '../base.js'; import { ToolContext, ToolResponse, ToolMetadata, SessionConfig, createSuccessResponse, createErrorResponse } from '../../common/types.js'; import { makeConfirmPreview } from '../../common/confirm_output.js'; interface ConsoleLogEntry { timestamp: number; message: string; } /** * Tool for retrieving and filtering console logs from the browser */ export class GetConsoleLogsTool extends BrowserToolBase { // Stored logs and timestamps private consoleLogs: ConsoleLogEntry[] = []; private lastCallTimestamp: number = 0; private lastNavigationTimestamp: number = 0; private lastInteractionTimestamp: number = 0; // Track latest instance for sibling tool access (module-level singleton pattern) static latestInstance: GetConsoleLogsTool | null = null; constructor(server: any) { super(server); GetConsoleLogsTool.latestInstance = this; } static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "get_console_logs", description: "Retrieve console logs with filtering and token‑efficient output. Defaults: since='last-interaction', limit=20, format='grouped'. Grouped output deduplicates identical lines and shows counts. Use format='raw' for chronological, ungrouped lines. Large outputs return a preview and a one-time token to fetch the full payload.", inputSchema: { type: "object", properties: { type: { type: "string", description: "Type filter (all, error, warning, log, info, debug, exception). Note: 'error' also includes 'exception' entries for convenience.", enum: ["all", "error", "warning", "log", "info", "debug", "exception"] }, search: { type: "string", description: "Text to search for in logs (handles text with square brackets)" }, limit: { type: "number", description: "Maximum entries to return (groups when grouped, lines when raw). Default: 20" }, since: { type: "string", description: "Filter logs since a specific event: 'last-call' (since last get_console_logs call), 'last-navigation' (since last page navigation), or 'last-interaction' (since last user interaction like click, fill, etc.). Default: 'last-interaction'", enum: ["last-call", "last-navigation", "last-interaction"] }, format: { type: "string", description: "Output format: 'grouped' (default, deduped with counts) or 'raw' (chronological, ungrouped)", enum: ["grouped", "raw"] }, }, required: [], }, }; } /** * Register a console message */ registerConsoleMessage(type: string, text: string): void { const logEntry: ConsoleLogEntry = { timestamp: Date.now(), message: `[${type}] ${text}` }; this.consoleLogs.push(logEntry); } /** * Update the last navigation timestamp */ updateLastNavigationTimestamp(): void { this.lastNavigationTimestamp = Date.now(); } /** * Update the last interaction timestamp */ updateLastInteractionTimestamp(): void { this.lastInteractionTimestamp = Date.now(); } async execute(args: any, context: ToolContext): Promise<ToolResponse> { // Defaults const format: 'grouped' | 'raw' = args.format === 'raw' ? 'raw' : 'grouped'; const limit: number = typeof args.limit === 'number' && args.limit > 0 ? Math.floor(args.limit) : 20; const sinceArg: string | undefined = args.since || 'last-interaction'; const PREVIEW_THRESHOLD = 2000; // chars let logs = [...this.consoleLogs]; // Filter by timestamp if 'since' parameter is specified if (sinceArg) { let sinceTimestamp: number; switch (sinceArg) { case 'last-call': sinceTimestamp = this.lastCallTimestamp; break; case 'last-navigation': sinceTimestamp = this.lastNavigationTimestamp; break; case 'last-interaction': sinceTimestamp = this.lastInteractionTimestamp; break; default: return createSuccessResponse(`Invalid 'since' value: ${sinceArg}. Must be one of: last-call, last-navigation, last-interaction`); } logs = logs.filter(log => log.timestamp > sinceTimestamp); } // Update last call timestamp this.lastCallTimestamp = Date.now(); // Filter by type if specified if (args.type && args.type !== 'all') { const wanted = args.type as string; // Treat 'error' as including both console errors and exceptions captured via pageerror/unhandledrejection const prefixes = wanted === 'error' ? ['[error]', '[exception]'] : [`[${wanted}]`]; logs = logs.filter(log => prefixes.some(p => log.message.startsWith(p))); } // Filter by search text if specified if (args.search) { logs = logs.filter(log => log.message.includes(args.search)); } // Build output according to format if (format === 'raw') { // Chronological lines, limit applied to last N entries const limited = limit > 0 ? logs.slice(-limit) : logs; const messages = limited.map(l => l.message); if (messages.length === 0) { return createSuccessResponse("No console logs matching the criteria"); } // Guard large outputs by character size const header = `Retrieved ${messages.length} console log(s):`; const textPayload = [header, ...messages].join('\n'); if (textPayload.length >= PREVIEW_THRESHOLD) { const previewLines = [ `Matched ${logs.length} log(s). Showing ${Math.min(messages.length, 10)} line(s) preview.`, ...messages.slice(0, Math.min(messages.length, 10)), ]; const preview = makeConfirmPreview(() => textPayload, { counts: { totalLength: textPayload.length, shownLength: previewLines.join('\n').length, totalMatched: logs.length, shownCount: Math.min(messages.length, 10), truncated: true }, previewLines, extraTips: ['Tip: refine with search/type/since/limit or prefer grouped format.'], }); return createSuccessResponse(preview.lines.join('\n')); } return createSuccessResponse([header, ...messages]); } // Grouped format (default) const groups = new Map<string, { count: number; firstTs: number; lastTs: number; example: string }>(); for (const l of logs) { const key = l.message; // includes [type] prefix per registerConsoleMessage const g = groups.get(key); if (g) { g.count += 1; g.lastTs = l.timestamp; } else { groups.set(key, { count: 1, firstTs: l.timestamp, lastTs: l.timestamp, example: l.message }); } } if (groups.size === 0) { return createSuccessResponse("No console logs matching the criteria"); } // Order groups by first occurrence time const ordered = Array.from(groups.entries()).sort((a, b) => a[1].firstTs - b[1].firstTs); const limitedGroups = limit > 0 ? ordered.slice(0, limit) : ordered; const lines: string[] = []; lines.push(`Retrieved ${limitedGroups.length} console log(s):`); for (const [msg, info] of limitedGroups) { const line = `${msg} (× ${info.count})`; lines.push(line); } // Guard large grouped outputs const textPayload = lines.join('\n'); if (textPayload.length >= PREVIEW_THRESHOLD) { const previewLines = [ `Matched ${groups.size} group(s). Showing ${limitedGroups.length}.`, ...lines.slice(0, Math.min(lines.length, 12)), ]; const preview = makeConfirmPreview(() => textPayload, { counts: { totalLength: textPayload.length, shownLength: previewLines.join('\n').length, totalMatched: groups.size, shownCount: limitedGroups.length, truncated: true }, previewLines, extraTips: ['Tip: refine with search/type/since/limit.'], }); return createSuccessResponse(preview.lines.join('\n')); } return createSuccessResponse(lines); } /** * Get all console logs */ getConsoleLogs(): string[] { return this.consoleLogs.map(log => log.message); } /** * Clear all console logs */ clearConsoleLogs(): void { this.consoleLogs = []; } /** * Return messages for logs captured after the last recorded navigation */ getLogsSinceLastNavigation(): string[] { const since = this.lastNavigationTimestamp; return this.consoleLogs .filter(log => log.timestamp > since) .map(log => log.message); } /** * Return messages for logs captured after the last recorded interaction */ getLogsSinceLastInteraction(): string[] { const since = this.lastInteractionTimestamp; return this.consoleLogs .filter(log => log.timestamp > since) .map(log => log.message); } } /** * Tool for clearing console logs (atomic operation) */ export class ClearConsoleLogsTool extends BrowserToolBase { static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "clear_console_logs", description: "Clears captured console logs and returns the number of entries cleared.", inputSchema: { type: "object", properties: {}, required: [], }, }; } async execute(args: any, context: ToolContext): Promise<ToolResponse> { const inst = GetConsoleLogsTool.latestInstance; if (!inst) { return createSuccessResponse('Cleared 0 console log(s)'); } const count = inst.getConsoleLogs().length; inst.clearConsoleLogs(); return createSuccessResponse(`Cleared ${count} console log(s)`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/antonzherdev/mcp-web-inspector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server