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
JavaScript
#!/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`;