Skip to main content
Glama
inspect-logs.ts8.73 kB
/** * inspect_logs Tool Handler * MCP tool for inspecting device logs (logcat, OSLog) */ import { isPlatform } from '../../models/constants.js'; import { Errors } from '../../models/errors.js'; import { LogEntry, LogFilter, LogLevel, LogInspectionResult, generateLogSummary, } from '../../models/log-entry.js'; import { captureLogcat, getLogsByTag as getAndroidLogsByTag } from '../../platforms/android/logcat.js'; import { captureOSLog, getAppLogs as getIOSAppLogs } from '../../platforms/ios/oslog.js'; import { getToolRegistry, createInputSchema } from '../register.js'; /** * Input arguments for inspect_logs tool */ export interface InspectLogsArgs { /** Target platform */ platform: string; /** App package/bundle ID (optional) */ appId?: string; /** Device ID */ deviceId?: string; /** Minimum log level to include */ minLevel?: string; /** Tags to include (Android) */ tags?: string[]; /** Tags to exclude */ excludeTags?: string[]; /** Search pattern (regex) */ pattern?: string; /** Case insensitive search */ ignoreCase?: boolean; /** Subsystem filter (iOS) */ subsystem?: string; /** Category filter (iOS) */ category?: string; /** Maximum entries to return */ maxEntries?: number; /** Time range - last N seconds */ lastSeconds?: number; /** Clear log buffer before capture (Android) */ clear?: boolean; /** Include crash/fault logs */ includeCrashes?: boolean; /** Timeout in milliseconds */ timeoutMs?: number; } /** * Inspect logs tool handler */ export async function inspectLogs(args: InspectLogsArgs): Promise<LogInspectionResult> { const { platform, appId, deviceId, minLevel, tags, excludeTags, pattern, ignoreCase = true, subsystem, category, maxEntries = 200, lastSeconds = 300, clear = false, includeCrashes = true, timeoutMs = 30000, } = args; const startTime = Date.now(); // Validate platform if (!isPlatform(platform)) { throw Errors.invalidArguments(`Invalid platform: ${platform}. Must be 'android' or 'ios'`); } // Validate log level if provided if (minLevel && !isValidLogLevel(minLevel)) { throw Errors.invalidArguments( `Invalid log level: ${minLevel}. Must be one of: verbose, debug, info, warning, error, fatal` ); } try { let entries: LogEntry[]; // Build filter const filter: LogFilter = {}; if (minLevel) { filter.minLevel = minLevel as LogLevel; } if (tags && tags.length > 0) { filter.tags = tags; } if (excludeTags && excludeTags.length > 0) { filter.excludeTags = excludeTags; } if (pattern) { filter.pattern = pattern; filter.ignoreCase = ignoreCase; } if (maxEntries > 0) { filter.limit = maxEntries; } // Capture logs based on platform if (platform === 'android') { entries = await captureAndroidLogs({ deviceId, appId, tags, maxEntries, clear, includeCrashes, timeoutMs, filter: Object.keys(filter).length > 0 ? filter : undefined, }); } else { entries = await captureIOSLogs({ deviceId, appId, subsystem, category, maxEntries, lastSeconds, timeoutMs, filter: Object.keys(filter).length > 0 ? filter : undefined, }); } return { success: true, platform, appId, deviceId, entries, totalEntries: entries.length, appliedFilters: Object.keys(filter).length > 0 ? filter : undefined, durationMs: Date.now() - startTime, }; } catch (error) { return { success: false, platform, appId, deviceId, entries: [], error: String(error), durationMs: Date.now() - startTime, }; } } /** * Capture Android logs */ async function captureAndroidLogs(options: { deviceId?: string; appId?: string; tags?: string[]; maxEntries: number; clear: boolean; includeCrashes: boolean; timeoutMs: number; filter?: LogFilter; }): Promise<LogEntry[]> { const { deviceId, appId, tags, maxEntries, clear, includeCrashes, timeoutMs, filter } = options; // If specific tags requested, use tag-based capture if (tags && tags.length > 0) { return getAndroidLogsByTag(tags, { deviceId, maxLines: maxEntries, timeoutMs, }); } // Otherwise use general capture return captureLogcat({ deviceId, packageName: appId, maxLines: maxEntries, filter, includeCrashes, timeoutMs, clear, }); } /** * Capture iOS logs */ async function captureIOSLogs(options: { deviceId?: string; appId?: string; subsystem?: string; category?: string; maxEntries: number; lastSeconds: number; timeoutMs: number; filter?: LogFilter; }): Promise<LogEntry[]> { const { deviceId, appId, subsystem, category, maxEntries, lastSeconds, timeoutMs, filter } = options; // If app ID provided, use app-specific capture if (appId) { return getIOSAppLogs(appId, { deviceId, maxEntries, lastSeconds, subsystem, category, filter, timeoutMs, }); } // Otherwise use general capture return captureOSLog({ deviceId, maxEntries, lastSeconds, subsystem, category, filter, timeoutMs, }); } /** * Check if log level is valid */ function isValidLogLevel(level: string): boolean { const validLevels = ['verbose', 'debug', 'info', 'warning', 'error', 'fatal', 'silent']; return validLevels.includes(level.toLowerCase()); } /** * Format log inspection result for AI */ export function formatLogResult(result: LogInspectionResult): string { if (!result.success) { const lines = [ `## Log Inspection: Failed`, ``, `**Error**: ${result.error}`, ]; return lines.join('\n'); } return generateLogSummary(result); } /** * Register the inspect_logs tool */ export function registerInspectLogsTool(): void { getToolRegistry().register( 'inspect_logs', { description: 'Inspect device logs (Android logcat or iOS unified logs). ' + 'Can filter by app, log level, tags, patterns, and time range.', inputSchema: createInputSchema( { platform: { type: 'string', enum: ['android', 'ios'], description: 'Target platform', }, appId: { type: 'string', description: 'App package name (Android) or bundle ID (iOS) to filter logs', }, deviceId: { type: 'string', description: 'Device ID (optional, uses first available)', }, minLevel: { type: 'string', enum: ['verbose', 'debug', 'info', 'warning', 'error', 'fatal'], description: 'Minimum log level to include', }, tags: { type: 'array', items: { type: 'string' }, description: 'Tags to include (Android logcat)', }, excludeTags: { type: 'array', items: { type: 'string' }, description: 'Tags to exclude from results', }, pattern: { type: 'string', description: 'Search pattern (regex) to filter messages', }, ignoreCase: { type: 'boolean', description: 'Case insensitive pattern matching (default: true)', }, subsystem: { type: 'string', description: 'Subsystem filter (iOS only)', }, category: { type: 'string', description: 'Category filter (iOS only)', }, maxEntries: { type: 'number', description: 'Maximum log entries to return (default: 200)', }, lastSeconds: { type: 'number', description: 'Time range - logs from last N seconds (iOS, default: 300)', }, clear: { type: 'boolean', description: 'Clear log buffer before capture (Android only)', }, includeCrashes: { type: 'boolean', description: 'Include crash/fault logs (default: true)', }, timeoutMs: { type: 'number', description: 'Timeout in milliseconds (default: 30000)', }, }, ['platform'] ), }, async (args) => { const result = await inspectLogs(args as unknown as InspectLogsArgs); return { ...result, formattedOutput: formatLogResult(result), }; } ); }

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/abd3lraouf/specter-mcp'

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