diff --git a/README.md b/README.md index ecbe734..691acd6 100644 --- a/README.md +++ b/README.md @@ -237,9 +237,12 @@ If Google keeps showing CAPTCHAs: - Add 10-30 second delays between searches - The server automatically restarts after 3 consecutive CAPTCHAs -**Browser won't launch:** +**Browser won't launch or persistent issues:** -Clear the browser profile: +The easiest way to fix browser-level issues or log out of Google is to use the built-in tool: +- Tell your agent: "Clear my browser profile" or run the `clear_browser_profile` tool. + +Manual fallback: ```bash # Linux/macOS rm -rf ~/.local/share/google-ai-mode-mcp/chrome_profile @@ -250,7 +253,7 @@ rmdir /s "%LOCALAPPDATA%\google-ai-mode-mcp\chrome_profile" **Wrong language results:** -The server forces English results. If you still get wrong languages, clear the profile (see above). +The server forces English results. If you still get wrong languages, use the `clear_browser_profile` tool. **Missing citations:** diff --git a/src/index.ts b/src/index.ts index 37ce9e5..f23475e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ import { } from "@modelcontextprotocol/sdk/types.js"; import { ToolHandler } from "./tools/tool-handler.js"; -import { SEARCH_AI_TOOL } from "./tools/search-tool.js"; +import { SEARCH_AI_TOOL, CLEAR_PROFILE_TOOL } from "./tools/search-tool.js"; import { GOOGLE_AI_SEARCH_PROMPT, GOOGLE_AI_SEARCH_PROMPT_TEMPLATE, @@ -85,7 +85,7 @@ class GoogleAiSearchMCPServer { this.server.setRequestHandler(ListToolsRequestSchema, async () => { log.info("๐Ÿ“‹ [MCP] list_tools request received"); return { - tools: [SEARCH_AI_TOOL], + tools: [SEARCH_AI_TOOL, CLEAR_PROFILE_TOOL], }; }); @@ -130,6 +130,10 @@ class GoogleAiSearchMCPServer { ); break; + case "clear_browser_profile": + result = await this.toolHandler.handleClearProfile(sendProgress); + break; + default: log.error(`โŒ [MCP] Unknown tool: ${name}`); return { diff --git a/src/search/search-handler.ts b/src/search/search-handler.ts index ff1d82c..765d9c5 100644 --- a/src/search/search-handler.ts +++ b/src/search/search-handler.ts @@ -77,7 +77,7 @@ export class SearchHandler { // Navigate to Google AI Search log.info("๐ŸŒ Navigating to Google AI Search..."); await page.goto(searchUrl, { - waitUntil: "domcontentloaded", + waitUntil: "networkidle", // Wait for network to be idle to captures redirects/AJAX correctly timeout: CONFIG.browserTimeout, }); diff --git a/src/tools/search-tool.ts b/src/tools/search-tool.ts index 84cbc98..f479f5a 100644 --- a/src/tools/search-tool.ts +++ b/src/tools/search-tool.ts @@ -50,3 +50,13 @@ Note: If CAPTCHA is detected, you will be prompted to solve it in a visible brow required: ["query"], }, }; + +export const CLEAR_PROFILE_TOOL: Tool = { + name: "clear_browser_profile", + description: "Clears the browser profile (cookies, session, cache). Useful for resetting Google login or fixing persistent issues.", + inputSchema: { + type: "object", + properties: {}, + }, +}; + diff --git a/src/tools/tool-handler.ts b/src/tools/tool-handler.ts index 8b92a7e..39a7a84 100644 --- a/src/tools/tool-handler.ts +++ b/src/tools/tool-handler.ts @@ -123,10 +123,12 @@ export class ToolHandler { // Generate filename let filename: string; if (customFilename) { - // Use custom filename, ensure .md extension - filename = customFilename.endsWith(".md") - ? customFilename - : `${customFilename}.md`; + // Sanitize filename to prevent Path Traversal + const sanitized = path.basename(customFilename); + // Use sanitized filename, ensure .md extension + filename = sanitized.endsWith(".md") + ? sanitized + : `${sanitized}.md`; } else { // Auto-generate from query and timestamp const timestamp = new Date() @@ -149,6 +151,65 @@ export class ToolHandler { return filePath; } + /** + * Handle clear_browser_profile tool call + */ + async handleClearProfile(sendProgress: ProgressCallback): Promise { + try { + log.info("๐Ÿงน Tool call: clear_browser_profile"); + + // 1. Close search handler (which closes the current browser context) + await sendProgress("Terminating active browser sessions..."); + await this.searchHandler.cleanup(); + + // 2. Cooldown to allow OS to release file locks + await new Promise(resolve => setTimeout(resolve, 2000)); + + // 3. Wipe browser profile data + await sendProgress("Wiping browser profile data..."); + + const profileDir = CONFIG.browserProfileDir; + if (fs.existsSync(profileDir)) { + try { + // Recursive deletion of profile directory + // Using force:true to ignore non-existent files and handle read-only files + fs.rmSync(profileDir, { recursive: true, force: true }); + + // Triple-check: if still exists, it might be a permission/lock issue + if (fs.existsSync(profileDir)) { + log.warning(`โš ๏ธ Profile directory still exists at: ${profileDir} - attempting second pass`); + await new Promise(resolve => setTimeout(resolve, 1000)); + fs.rmSync(profileDir, { recursive: true, force: true }); + } + + log.success(`โœ… Profile cleared at: ${profileDir}`); + } catch (rmError) { + log.error(`Deletion failed: ${rmError}`); + // On Windows, if process is still locked, we might need to inform the user + if (process.platform === 'win32') { + throw new Error(`Could not delete profile directory. It may be locked by another process. Please close all Chrome/Chromium windows and try again.`); + } + throw rmError; + } + } else { + log.info("Profile directory already empty or not found."); + } + + await sendProgress("Profile cleared successfully!", 100, 100); + + return { + success: true, + message: "Browser profile cleared successfully. Local history, sessions, and cookies have been removed.", + }; + } catch (error) { + log.error(`Clear profile error: ${error}`); + return { + success: false, + error: error instanceof Error ? error.message : String(error), + }; + } + } + /** * Cleanup handler */