UNPKG

chromium-codesearch-mcp

Version:

MCP server for searching Chromium source code via source.chromium.org with advanced Code Search syntax support

988 lines (987 loc) • 209 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import fetch from "node-fetch"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { chromium } from 'playwright'; // Custom error types for better error handling class ChromiumSearchError extends Error { cause; constructor(message, cause) { super(message); this.cause = cause; this.name = 'ChromiumSearchError'; } } class GerritAPIError extends Error { statusCode; cause; constructor(message, statusCode, cause) { super(message); this.statusCode = statusCode; this.cause = cause; this.name = 'GerritAPIError'; } } // Dynamic version loading const __filename = fileURLToPath(import.meta.url); const packageRoot = path.resolve(path.dirname(__filename), '..'); const packageJsonPath = path.join(packageRoot, 'package.json'); let packageInfo; try { packageInfo = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); } catch (error) { console.error("Failed to load package.json:", error); packageInfo = { version: "0.0.0-error", name: "chromium-codesearch-mcp" }; } class ChromiumCodeSearchServer { server; cache = new Map(); constructor() { this.server = new Server({ name: "chromium-codesearch", version: packageInfo.version, }, { capabilities: { tools: {}, }, }); this.setupToolHandlers(); } log(level, message, data) { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level: level.toUpperCase(), message, ...data }; // Output structured logs to stderr to avoid interfering with MCP protocol console.error(JSON.stringify(logEntry)); } handleError(error, toolName, args) { // Determine error type and create appropriate response if (error instanceof ChromiumSearchError) { return `Chromium search failed: ${error.message}`; } if (error instanceof GerritAPIError) { return `Gerrit API error: ${error.message}${error.statusCode ? ` (HTTP ${error.statusCode})` : ''}`; } // Handle fetch/network errors if (error.name === 'FetchError' || error.code === 'ENOTFOUND') { return `Network error: Unable to reach search API. Please check your internet connection.`; } // Handle timeout errors if (error.name === 'TimeoutError' || error.message?.includes('timeout')) { return `Request timeout: The search took too long to complete. Try a more specific query.`; } // Generic error handling const errorMessage = error.message || 'Unknown error occurred'; return `Tool execution failed: ${errorMessage}`; } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_chromium_code", description: "Search for code in the Chromium source repository using Google's official Code Search syntax", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query using Code Search syntax. Examples: 'LOG(INFO)', 'class:Browser', 'function:CreateWindow', 'lang:cpp memory', 'file:*.cc content:\"base::\"', 'comment:\"TODO: fix\"'", }, case_sensitive: { type: "boolean", description: "Make search case sensitive (adds 'case:yes' to query)", default: false, }, language: { type: "string", description: "Filter by programming language (e.g., 'cpp', 'javascript', 'python')", }, file_pattern: { type: "string", description: "File pattern filter (e.g., '*.cc', '*.h', 'chrome/browser/*')", }, search_type: { type: "string", enum: ["content", "function", "class", "symbol", "comment"], description: "Specific search type: 'content' (file contents), 'function' (function names), 'class' (class names), 'symbol' (symbols), 'comment' (comments only)", }, exclude_comments: { type: "boolean", description: "Exclude comments and string literals from search (uses 'usage:' filter)", default: false, }, limit: { type: "number", description: "Maximum number of results to return (default: 20)", default: 20, }, }, required: ["query"], }, }, { name: "find_chromium_symbol", description: "Find symbol definition, references, and usage in Chromium source", inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Symbol to find (function, class, method, etc.)", }, file_path: { type: "string", description: "Optional file path context for better symbol resolution", }, }, required: ["symbol"], }, }, { name: "get_chromium_file", description: "Get contents of a specific file from Chromium source", inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Path to the file in Chromium source (e.g., 'base/logging.cc')", }, line_start: { type: "number", description: "Optional starting line number", }, line_end: { type: "number", description: "Optional ending line number", }, }, required: ["file_path"], }, }, { name: "get_gerrit_cl_status", description: "Get status and test results for a Chromium Gerrit CL", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '6624568' or 'https://chromium-review.googlesource.com/c/chromium/src/+/6624568')", }, }, required: ["cl_number"], }, }, { name: "get_gerrit_cl_comments", description: "Get review comments for a Chromium Gerrit CL patchset", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '6624568' or 'https://chromium-review.googlesource.com/c/chromium/src/+/6624568')", }, patchset: { type: "number", description: "Optional specific patchset number to get comments for (if not specified, gets comments for current patchset)", }, include_resolved: { type: "boolean", description: "Include resolved comments (default: true)", default: true, }, }, required: ["cl_number"], }, }, { name: "get_gerrit_cl_diff", description: "Get the diff/changes for a Chromium Gerrit CL patchset to understand what code was modified", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '6624568' or 'https://chromium-review.googlesource.com/c/chromium/src/+/6624568')", }, patchset: { type: "number", description: "Optional specific patchset number to get diff for (if not specified, gets diff for current patchset)", }, file_path: { type: "string", description: "Optional specific file path to get diff for (if not specified, gets diff for all files)", }, }, required: ["cl_number"], }, }, { name: "get_gerrit_patchset_file", description: "Get the content of a specific file from a Gerrit patchset for making code changes", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '6624568' or 'https://chromium-review.googlesource.com/c/chromium/src/+/6624568')", }, file_path: { type: "string", description: "Path to the file to get content for (e.g., 'chrome/browser/ui/browser.cc')", }, patchset: { type: "number", description: "Optional specific patchset number (if not specified, gets file from current patchset)", }, }, required: ["cl_number", "file_path"], }, }, { name: "get_gerrit_cl_trybot_status", description: "Get detailed try-bot status for a Chromium Gerrit CL, including individual bot results and pass/fail counts", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '6624568' or 'https://chromium-review.googlesource.com/c/chromium/src/+/6624568')", }, patchset: { type: "number", description: "Optional specific patchset number to get bot status for (if not specified, gets status for latest patchset)", }, failed_only: { type: "boolean", description: "Only return failed bots (default: false)", default: false, }, }, required: ["cl_number"], }, }, // PDFium Gerrit tools { name: "get_pdfium_gerrit_cl_status", description: "Get status and test results for a PDFium Gerrit CL", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '12345' or 'https://pdfium-review.googlesource.com/c/pdfium/+/12345')", }, }, required: ["cl_number"], }, }, { name: "get_pdfium_gerrit_cl_comments", description: "Get review comments for a PDFium Gerrit CL patchset", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '12345' or 'https://pdfium-review.googlesource.com/c/pdfium/+/12345')", }, patchset: { type: "number", description: "Optional specific patchset number to get comments for (if not specified, gets comments for current patchset)", }, include_resolved: { type: "boolean", description: "Include resolved comments (default: true)", default: true, }, }, required: ["cl_number"], }, }, { name: "get_pdfium_gerrit_cl_diff", description: "Get the diff/changes for a PDFium Gerrit CL patchset to understand what code was modified", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '12345' or 'https://pdfium-review.googlesource.com/c/pdfium/+/12345')", }, patchset: { type: "number", description: "Optional specific patchset number to get diff for (if not specified, gets diff for current patchset)", }, file_path: { type: "string", description: "Optional specific file path to get diff for (if not specified, gets diff for all files)", }, }, required: ["cl_number"], }, }, { name: "get_pdfium_gerrit_patchset_file", description: "Get the content of a specific file from a PDFium Gerrit patchset for making code changes", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '12345' or 'https://pdfium-review.googlesource.com/c/pdfium/+/12345')", }, file_path: { type: "string", description: "Path to the file to get content for (e.g., 'core/fpdfapi/parser/cpdf_parser.cpp')", }, patchset: { type: "number", description: "Optional specific patchset number (if not specified, gets file from current patchset)", }, }, required: ["cl_number", "file_path"], }, }, { name: "get_pdfium_gerrit_cl_trybot_status", description: "Get detailed try-bot status for a PDFium Gerrit CL, including individual bot results and pass/fail counts", inputSchema: { type: "object", properties: { cl_number: { type: "string", description: "CL number or full Gerrit URL (e.g., '12345' or 'https://pdfium-review.googlesource.com/c/pdfium/+/12345')", }, patchset: { type: "number", description: "Optional specific patchset number to get bot status for (if not specified, gets status for latest patchset)", }, failed_only: { type: "boolean", description: "Only return failed bots (default: false)", default: false, }, }, required: ["cl_number"], }, }, { name: "list_pdfium_gerrit_cls", description: "List PDFium Gerrit CLs from PDFium dashboard (requires authentication cookie)", inputSchema: { type: "object", properties: { query: { type: "string", description: "Gerrit search query for PDFium (e.g., 'owner:me', 'status:open', 'change:12345 OR change:67890')", }, auth_cookie: { type: "string", description: "Authentication cookie - just need ONE of: __Secure-1PSID=... OR __Secure-3PSID=...", }, limit: { type: "number", description: "Maximum number of CLs to return (default: 25, max: 100)", default: 25, }, }, required: ["auth_cookie"], }, }, { name: "find_chromium_owners_file", description: "Find OWNERS files for a given file path in Chromium source code by searching up the directory tree", inputSchema: { type: "object", properties: { file_path: { type: "string", description: "Path to the file to find OWNERS for (e.g., 'chrome/browser/ui/browser.cc')", }, }, required: ["file_path"], }, }, { name: "search_chromium_commits", description: "Search commit messages and metadata in the Chromium repository using Gitiles API", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query for commit messages, file paths, or metadata", }, author: { type: "string", description: "Filter by author name or email (optional)", }, since: { type: "string", description: "Only commits after this date (YYYY-MM-DD format, optional)", }, until: { type: "string", description: "Only commits before this date (YYYY-MM-DD format, optional)", }, limit: { type: "number", description: "Maximum number of commits to return (default: 20, max: 100)", }, }, required: ["query"], }, }, { name: "get_chromium_issue", description: "Get details for a specific Chromium issue/bug from issues.chromium.org", inputSchema: { type: "object", properties: { issue_id: { type: "string", description: "Issue ID or full URL (e.g., '422768753' or 'https://issues.chromium.org/issues/422768753')", }, }, required: ["issue_id"], }, }, { name: "search_chromium_issues", description: "Search for issues in the Chromium issue tracker with full-text search across titles, descriptions, and metadata", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query for issue titles, descriptions, or metadata (e.g., 'memory leak', 'pkasting', 'security')", }, limit: { type: "number", description: "Maximum number of results to return (default: 50, max: 100)", default: 50, }, start_index: { type: "number", description: "Starting index for pagination (default: 0)", default: 0, }, }, required: ["query"], }, }, { name: "list_chromium_folder", description: "List files and folders in a specific directory of the Chromium source tree", inputSchema: { type: "object", properties: { folder_path: { type: "string", description: "Path to the folder in Chromium source (e.g., 'third_party/blink/renderer/core/style')", }, }, required: ["folder_path"], }, }, { name: "list_gerrit_cls", description: "List Gerrit CLs from Chromium dashboard (requires authentication cookie)", inputSchema: { type: "object", properties: { query: { type: "string", description: "Gerrit search query (e.g., 'owner:me', 'status:open', 'change:1234 OR change:5678')", }, auth_cookie: { type: "string", description: "Authentication cookie - just need ONE of: __Secure-1PSID=... OR __Secure-3PSID=...", }, limit: { type: "number", description: "Maximum number of CLs to return (default: 25, max: 100)", default: 25, }, }, required: ["auth_cookie"], }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const startTime = Date.now(); try { this.log('info', `Executing tool: ${name}`, { args }); let result; switch (name) { case "search_chromium_code": result = await this.searchChromiumCode(args); break; case "find_chromium_symbol": result = await this.findChromiumSymbol(args); break; case "get_chromium_file": result = await this.getChromiumFile(args); break; case "get_gerrit_cl_status": result = await this.getGerritCLStatus(args); break; case "get_gerrit_cl_comments": result = await this.getGerritCLComments(args); break; case "get_gerrit_cl_diff": result = await this.getGerritCLDiff(args); break; case "get_gerrit_patchset_file": result = await this.getGerritPatchsetFile(args); break; case "get_gerrit_cl_trybot_status": result = await this.getGerritCLTrybotStatus(args); break; // PDFium Gerrit handlers case "get_pdfium_gerrit_cl_status": result = await this.getPDFiumGerritCLStatus(args); break; case "get_pdfium_gerrit_cl_comments": result = await this.getPDFiumGerritCLComments(args); break; case "get_pdfium_gerrit_cl_diff": result = await this.getPDFiumGerritCLDiff(args); break; case "get_pdfium_gerrit_patchset_file": result = await this.getPDFiumGerritPatchsetFile(args); break; case "get_pdfium_gerrit_cl_trybot_status": result = await this.getPDFiumGerritCLTrybotStatus(args); break; case "list_pdfium_gerrit_cls": result = await this.listPDFiumGerritCLs(args); break; case "find_chromium_owners_file": result = await this.findChromiumOwnersFile(args); break; case "search_chromium_commits": result = await this.searchChromiumCommits(args); break; case "get_chromium_issue": result = await this.getChromiumIssue(args); break; case "search_chromium_issues": result = await this.searchChromiumIssues(args); break; case "list_chromium_folder": result = await this.listChromiumFolder(args); break; case "list_gerrit_cls": result = await this.listGerritCLs(args); break; default: throw new Error(`Unknown tool: ${name}`); } const executionTime = Date.now() - startTime; this.log('info', `Tool executed successfully: ${name}`, { executionTime }); return result; } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = this.handleError(error, name, args); this.log('error', `Tool execution failed: ${name}`, { error: error.message, executionTime, errorType: error.constructor.name }); return { content: [ { type: "text", text: errorMessage, }, ], }; } }); } async searchChromiumCode(args) { const { query, case_sensitive = false, language, file_pattern, search_type, exclude_comments = false, limit = 20 } = args; // Build the enhanced search query using Code Search syntax let searchQuery = query; // Add case sensitivity if requested if (case_sensitive) { searchQuery = `case:yes ${searchQuery}`; } // Add language filter if specified if (language) { searchQuery = `lang:${language} ${searchQuery}`; } // Add file pattern filter if specified if (file_pattern) { searchQuery = `file:${file_pattern} ${searchQuery}`; } // Add search type filter if specified if (search_type) { switch (search_type) { case 'content': searchQuery = `content:${query}`; break; case 'function': searchQuery = `function:${query}`; break; case 'class': searchQuery = `class:${query}`; break; case 'symbol': searchQuery = `symbol:${query}`; break; case 'comment': searchQuery = `comment:${query}`; break; } // Apply other filters to the type-specific query if (case_sensitive) searchQuery = `case:yes ${searchQuery}`; if (language) searchQuery = `lang:${language} ${searchQuery}`; if (file_pattern) searchQuery = `file:${file_pattern} ${searchQuery}`; } // Add usage filter to exclude comments if requested if (exclude_comments && !search_type) { searchQuery = `usage:${query}`; if (case_sensitive) searchQuery = `case:yes ${searchQuery}`; if (language) searchQuery = `lang:${language} ${searchQuery}`; if (file_pattern) searchQuery = `file:${file_pattern} ${searchQuery}`; } try { const response = await this.callChromiumSearchAPI(searchQuery, limit); const results = this.parseChromiumAPIResponse(response); if (results.length === 0) { const fallbackUrl = `https://source.chromium.org/search?q=${encodeURIComponent(query)}&ss=chromium%2Fchromium%2Fsrc`; return { content: [ { type: "text", text: `No results found for query: "${query}"\n\nšŸ” **Direct search URL:**\n${fallbackUrl}`, }, ], }; } // Format results with enhanced information let resultText = `## Search Results for "${query}"\n\n`; if (response.estimatedResultCount) { resultText += `šŸ“Š **Total estimated matches:** ${response.estimatedResultCount}\n`; resultText += `šŸ“„ **Showing:** ${results.length} results\n\n`; } resultText += results .map((result, index) => { const lineInfo = result.line > 0 ? `:${result.line}` : ''; return `### ${index + 1}. ${result.file}${lineInfo}\n` + `\`\`\`\n${result.content.trim()}\n\`\`\`\n` + `šŸ”— [View in source.chromium.org](${result.url})\n`; }) .join("\n"); // Add pagination info if available if (response.nextPageToken) { resultText += `\nšŸ’” **More results available** - Use the web interface to see additional matches.\n`; } resultText += `\nšŸ” **Direct search:** https://source.chromium.org/search?q=${encodeURIComponent(query)}&ss=chromium%2Fchromium%2Fsrc`; return { content: [ { type: "text", text: resultText, }, ], }; } catch (error) { const fallbackUrl = `https://source.chromium.org/search?q=${encodeURIComponent(query)}&ss=chromium%2Fchromium%2Fsrc`; return { content: [ { type: "text", text: `Search failed: ${error.message}\n\nTry searching manually at: ${fallbackUrl}`, }, ], }; } } async findChromiumSymbol(args) { const { symbol, file_path } = args; try { // Search for symbol definitions using Code Search syntax const symbolResults = await this.callChromiumSearchAPI(`symbol:${symbol}`, 10); const symbolParsed = this.parseChromiumAPIResponse(symbolResults); // Search for class definitions const classResults = await this.callChromiumSearchAPI(`class:${symbol}`, 5); const classParsed = this.parseChromiumAPIResponse(classResults); // Search for function definitions const functionResults = await this.callChromiumSearchAPI(`function:${symbol}`, 5); const functionParsed = this.parseChromiumAPIResponse(functionResults); // Search for general usage in content (excluding comments) const usageResults = await this.callChromiumSearchAPI(`usage:${symbol}`, 10); const usageParsed = this.parseChromiumAPIResponse(usageResults); let resultText = `## Symbol: ${symbol}\n\n`; if (file_path) { resultText += `**Context file:** ${file_path}\n\n`; } // Symbol-specific results if (symbolParsed.length > 0) { resultText += `### šŸŽÆ Symbol Definitions:\n`; symbolParsed.forEach((result, index) => { resultText += `#### ${index + 1}. ${result.file}:${result.line}\n`; resultText += `\`\`\`cpp\n${result.content.trim()}\n\`\`\`\n`; resultText += `šŸ”— [View source](${result.url})\n\n`; }); } // Class definitions if (classParsed.length > 0) { resultText += `### šŸ—ļø Class Definitions:\n`; classParsed.forEach((result, index) => { resultText += `#### ${index + 1}. ${result.file}:${result.line}\n`; resultText += `\`\`\`cpp\n${result.content.trim()}\n\`\`\`\n`; resultText += `šŸ”— [View source](${result.url})\n\n`; }); } // Function definitions if (functionParsed.length > 0) { resultText += `### āš™ļø Function Definitions:\n`; functionParsed.forEach((result, index) => { resultText += `#### ${index + 1}. ${result.file}:${result.line}\n`; resultText += `\`\`\`cpp\n${result.content.trim()}\n\`\`\`\n`; resultText += `šŸ”— [View source](${result.url})\n\n`; }); } // Usage examples if (usageParsed.length > 0) { resultText += `### šŸ“š Usage Examples (excluding comments):\n`; if (usageResults.estimatedResultCount) { resultText += `*Found ${usageResults.estimatedResultCount} total usage matches across the codebase*\n\n`; } usageParsed.slice(0, 8).forEach((result, index) => { resultText += `#### ${index + 1}. ${result.file}:${result.line}\n`; resultText += `\`\`\`cpp\n${result.content.trim()}\n\`\`\`\n`; resultText += `šŸ”— [View source](${result.url})\n\n`; }); if (usageParsed.length >= 8 && usageResults.estimatedResultCount > 8) { resultText += `šŸ’” *Showing first 8 examples. Total usage matches: ${usageResults.estimatedResultCount}*\n\n`; } } // Show if no results found if (symbolParsed.length === 0 && classParsed.length === 0 && functionParsed.length === 0 && usageParsed.length === 0) { resultText += `### āŒ No results found\n\nThis could mean:\n- The symbol doesn't exist in the current codebase\n- It might be spelled differently\n- Try searching with different capitalization or as a partial string\n\n`; } resultText += `### šŸ” Search URLs:\n`; resultText += `- **Symbol:** https://source.chromium.org/search?q=${encodeURIComponent(`symbol:${symbol}`)}&ss=chromium%2Fchromium%2Fsrc\n`; resultText += `- **Class:** https://source.chromium.org/search?q=${encodeURIComponent(`class:${symbol}`)}&ss=chromium%2Fchromium%2Fsrc\n`; resultText += `- **Function:** https://source.chromium.org/search?q=${encodeURIComponent(`function:${symbol}`)}&ss=chromium%2Fchromium%2Fsrc\n`; resultText += `- **Usage:** https://source.chromium.org/search?q=${encodeURIComponent(`usage:${symbol}`)}&ss=chromium%2Fchromium%2Fsrc`; return { content: [ { type: "text", text: resultText, }, ], }; } catch (error) { const fallbackUrl = `https://source.chromium.org/search?q=${encodeURIComponent(symbol)}&ss=chromium%2Fchromium%2Fsrc`; return { content: [ { type: "text", text: `Symbol lookup failed: ${error.message}\n\nTry searching manually at: ${fallbackUrl}`, }, ], }; } } async getChromiumFile(args) { const { file_path, line_start, line_end } = args; try { // Check if this is a submodule file const knownSubmodules = ['v8/', 'third_party/webrtc/']; const isSubmodulePath = knownSubmodules.some(submodule => file_path.startsWith(submodule)); if (isSubmodulePath) { // Handle submodule files if (file_path.startsWith('v8/')) { return await this.getV8FileViaGitHub(file_path, line_start, line_end); } if (file_path.startsWith('third_party/webrtc/')) { return await this.getWebRTCFileViaGitiles(file_path, line_start, line_end); } } // Fetch from Gitiles API const gitileUrl = `https://chromium.googlesource.com/chromium/src/+/main/${file_path}?format=TEXT`; this.log('debug', 'Fetching file from Gitiles', { file_path, gitileUrl }); const response = await fetch(gitileUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, }); if (!response.ok) { // If Gitiles fails, provide the browser URL as fallback const browserUrl = `https://source.chromium.org/chromium/chromium/src/+/main:${file_path}`; return { content: [ { type: "text", text: `āŒ **Failed to fetch file content** (HTTP ${response.status})\n\n**File:** ${file_path}\n**Browser URL:** ${browserUrl}\n\nUse the URL above to view the file in your browser.`, }, ], }; } // The response is base64 encoded const base64Content = await response.text(); const fileContent = Buffer.from(base64Content, 'base64').toString('utf-8'); // Split into lines for line number processing const lines = fileContent.split('\n'); let displayLines = lines; let startLine = 1; // Apply line range if specified if (line_start) { const start = Math.max(1, parseInt(line_start)) - 1; // Convert to 0-based const end = line_end ? Math.min(lines.length, parseInt(line_end)) : lines.length; displayLines = lines.slice(start, end); startLine = start + 1; } // Format content with line numbers const numberedLines = displayLines.map((line, index) => { const lineNum = (startLine + index).toString().padStart(4, ' '); return `${lineNum} ${line}`; }).join('\n'); // Create browser URL for reference let browserUrl = `https://source.chromium.org/chromium/chromium/src/+/main:${file_path}`; if (line_start) { browserUrl += `;l=${line_start}`; if (line_end) { browserUrl += `-${line_end}`; } } const totalLines = lines.length; const displayedLines = displayLines.length; const lineRangeText = line_start ? ` (lines ${line_start}${line_end ? `-${line_end}` : '+'})` : ''; return { content: [ { type: "text", text: `## File: ${file_path}${lineRangeText}\n\nšŸ“Š **Total lines:** ${totalLines} | **Displayed:** ${displayedLines}\nšŸ”— **Browser URL:** ${browserUrl}\n\n\`\`\`${this.getFileExtension(file_path)}\n${numberedLines}\n\`\`\``, }, ], }; } catch (error) { this.log('error', 'Failed to fetch file content', { file_path, error: error.message }); // Fallback to browser URL const browserUrl = `https://source.chromium.org/chromium/chromium/src/+/main:${file_path}`; return { content: [ { type: "text", text: `āŒ **Error fetching file:** ${error.message}\n\n**File:** ${file_path}\n**Browser URL:** ${browserUrl}\n\nUse the URL above to view the file in your browser.`, }, ], }; } } async fetchWithCache(url) { const cacheHit = this.cache.has(url); this.log('debug', 'Fetching URL', { url: url.substring(0, 100) + '...', cacheHit }); if (cacheHit) { return this.cache.get(url); } try { const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', }, }); if (!response.ok) { this.log('error', 'HTTP request failed', { url: url.substring(0, 100) + '...', status: response.status, statusText: response.statusText }); throw new GerritAPIError(`HTTP ${response.status}: ${response.statusText}`, response.status); } const result = await response.json(); this.cache.set(url, result); // Simple cache cleanup - remove old entries if (this.cache.size > 100) { const firstKey = this.cache.keys().next().value; if (firstKey) { this.cache.delete(firstKey); } } this.log('debug', 'HTTP request successful', { url: url.substring(0, 100) + '...', cacheSize: this.cache.size }); return result; } catch (error) { if (error instanceof GerritAPIError) { throw error; } this.log('error', 'Network error during fetch', { url: url.substring(0, 100) + '...', error: error.message }); throw new GerritAPIError(`Network error: ${error.message}`, undefined, error); } } parseSearchResponse(response) { const results = []; if (!response.search_response || !response.search_response[0]) { return results; } const searchResult = response.search_response[0]; if (!searchResult.search_result) { return results; } for (const fileResult of searchResult.search_result) { let filename = fileResult.file.name; if (filename.startsWith("src/")) { filename = filename.substr(4); } for (const match of fileResult.match || []) { const lineNumber = parseInt(match.line_number) || 0; const content = match.line_text || ''; const url = `https://source.chromium.org/chromium/chromium/src/+/main:${filename};l=${lineNumber}`; results.push({ file: filename, line: lineNumber, content: content, url: url, }); } } return results; } async getGerritCLStatus(args) { const { cl_number } = args; // Extract CL number from URL if provided const clMatch = cl_number.match(/(\d+)$/); const clId = clMatch ? clMatch[1] : cl_number; try { // Fetch CL details const clDetailsUrl = `https://chromium-review.googlesource.com/changes/?q=change:${clId}&o=DETAILED_ACCOUNTS&o=CURRENT_REVISION&o=SUBMIT_REQUIREMENTS&o=MESSAGES`;