Skip to main content
Glama
mixophrygian

Browser History Analysis MCP

by mixophrygian

get_browser_history

Retrieve raw browser history data from Firefox, Chrome, or Safari for analysis. Specify time period and browser type to extract browsing records.

Instructions

Step 2: Get raw browser history data without analysis. This is the fastest way to retrieve browser history and should be used before any analysis.

Args:
    time_period_in_days: Number of days of history to retrieve (default: 7)
    browser_type: Browser type ('firefox', 'chrome', 'safari', or None for auto-detect)
    all_browsers: If True, get history from all available browsers (default: True)

Returns:
    Either a list of history entries or a dictionary with partial results and browser status

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
time_period_in_daysNo
browser_typeNo
all_browsersNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • server/main.py:40-52 (registration)
    Registers the 'get_browser_history' tool using @mcp.tool() decorator. Defines input schema via type hints and comprehensive docstring describing parameters and return types.
    @mcp.tool()
    async def get_browser_history(time_period_in_days: int = 7, browser_type: Optional[str] = None, all_browsers: bool = True) -> Union[List[HistoryEntryDict], Dict[str, Any]]:
        """Step 2: Get raw browser history data without analysis. This is the fastest way to retrieve browser history and should be used before any analysis.
        
        Args:
            time_period_in_days: Number of days of history to retrieve (default: 7)
            browser_type: Browser type ('firefox', 'chrome', 'safari', or None for auto-detect)
            all_browsers: If True, get history from all available browsers (default: True)
        
        Returns:
            Either a list of history entries or a dictionary with partial results and browser status
        """
        return await tool_get_browser_history(time_period_in_days, CACHED_HISTORY, browser_type, all_browsers)
  • Core implementation of browser history retrieval. Detects available browsers (Firefox, Chrome, Safari), queries their SQLite databases for recent history, handles locked databases and errors, supports multi-browser aggregation and single-browser modes, returns structured results or errors with user guidance.
    async def tool_get_browser_history(time_period_in_days: int, CACHED_HISTORY: CachedHistory, browser_type: Optional[str] = None, all_browsers: bool = True) -> Union[List[HistoryEntryDict], BrowserHistoryResult]:
    
        start_time = time.time()
        print(f"🚀 Starting browser history retrieval for {time_period_in_days} days...")
    
        if time_period_in_days <= 0:
            raise ValueError("time_period_in_days must be a positive integer")
        
        # Map browser types to their handler functions
        browser_handlers = {
            "firefox": get_firefox_history,
            "chrome": get_chrome_history,
            "safari": get_safari_history
        }
        
        if all_browsers:
            # Step 1: Detect available browsers
            step_start = time.time()
            print("📊 Step 1: Detecting available browsers...")
            browser_status = tool_detect_available_browsers()
            detect_time = time.time() - step_start
            print(f"📊 Browser detection completed in {detect_time:.3f}s")
            
            if browser_status.get("status") == "error":
                print(f"❌ {browser_status['error_message']}")
                raise RuntimeError(browser_status['error_message'])
            elif browser_status.get("status") == "browser_locked":
                print(f"❌ {browser_status['error_message']}")
                raise RuntimeError(browser_status['error_message'])
            
            available_browsers = browser_status.get('available_browsers', [])
            if not available_browsers:
                print("❌ No available browsers found")
                raise RuntimeError("No browser history databases found. Please ensure Firefox, Chrome, or Safari is installed and try again.")
            
            print(f"📊 Available browsers: {available_browsers}")
            
            # Step 2: Get history from each browser
            all_entries = []
            successful_browsers = []
            failed_browsers = []
            failure_reasons = {}
            
            for browser in available_browsers:
                browser_start = time.time()
                print(f"📊 Step 2.{len(successful_browsers) + len(failed_browsers) + 1}: Getting {browser} history...")
                
                try:
                    entries = browser_handlers[browser](time_period_in_days)
                    browser_time = time.time() - browser_start
                    print(f"📊 {browser} history retrieved in {browser_time:.3f}s: {len(entries)} entries")
                    
                    logger.warning(f"Retrieved {len(entries)} {browser} history entries from last {time_period_in_days} days")
                    all_entries.extend([entry.to_dict() for entry in entries])
                    successful_browsers.append(browser)
                except Exception as e:
                    browser_time = time.time() - browser_start
                    error_msg = str(e)
                    print(f"❌ {browser} history failed in {browser_time:.3f}s: {error_msg}")
                    
                    logger.warning(f"Failed to get {browser} history: {error_msg}. If the database is locked, please try closing the browser and running the tool again.")
                    failed_browsers.append(browser)
                    failure_reasons[browser] = error_msg
                    continue
            
            total_time = time.time() - start_time
            print(f"📊 Total browser history retrieval time: {total_time:.3f}s")
            
            # If we have any successful browsers, return partial results
            if successful_browsers:
                recommendation = ""
                if failed_browsers:
                    locked_browsers = [browser for browser in failed_browsers if "database is locked" in failure_reasons.get(browser, "").lower()]
                    if locked_browsers:
                        recommendation = f"🔒 BROWSER LOCKED: {', '.join([b.title() for b in locked_browsers])} {'is' if len(locked_browsers) == 1 else 'are'} currently running. Please close {'this browser' if len(locked_browsers) == 1 else 'these browsers'} completely to get complete history analysis. You can restore tabs with Ctrl+Shift+T (Cmd+Shift+T on Mac). "
                    recommendation += f"Successfully retrieved {len(all_entries)} entries from {', '.join([b.title() for b in successful_browsers])}."
                else:
                    recommendation = f"✅ Successfully retrieved {len(all_entries)} entries from all browsers: {', '.join([b.title() for b in successful_browsers])}."
                
                logger.warning(f"Retrieved total of {len(all_entries)} history entries from {len(successful_browsers)} browsers")
                
                return {
                    "history_entries": all_entries,
                    "successful_browsers": successful_browsers,
                    "failed_browsers": failed_browsers,
                    "failure_reasons": failure_reasons,
                    "total_entries": len(all_entries),
                    "status": "partial_success" if failed_browsers else "success",
                    "user_action_required": bool(failed_browsers),
                    "recommendation": recommendation
                }
            
            # If no browsers succeeded, raise error with detailed information
            locked_browsers = [browser for browser in failed_browsers if "database is locked" in failure_reasons.get(browser, "").lower()]
            if locked_browsers:
                error_message = f"🔒 BROWSER LOCKED: All browsers ({', '.join([b.title() for b in locked_browsers])}) are currently running and their databases are locked. Please close ALL browsers completely to analyze history. You can restore tabs with Ctrl+Shift+T (Cmd+Shift+T on Mac)."
            else:
                error_details = "; ".join([f"{browser}: {reason}" for browser, reason in failure_reasons.items()])
                error_message = f"❌ ERROR: Failed to retrieve history from any browser: {error_details}"
            
            raise RuntimeError(error_message)
        
        else:
            # Single browser mode (original behavior)
            if browser_type is None:
                browser_status = tool_detect_available_browsers()
                if browser_status.get("status") == "error":
                    raise RuntimeError(browser_status['error_message'])
                elif browser_status.get("status") == "browser_locked":
                    raise RuntimeError(browser_status['error_message'])
                
                # Get the first available browser from the available_browsers list
                available_browsers = browser_status.get('available_browsers', [])
                browser_type = available_browsers[0] if available_browsers else None
                    
                if browser_type is None:
                    raise RuntimeError("No browser history databases found. Please ensure Firefox, Chrome, or Safari is installed and try again.")
                    
                logger.warning(f"Auto-detected active browser: {browser_type}")
            
            if browser_type not in browser_handlers:
                raise ValueError(f"Unsupported browser type: {browser_type}. Supported types: {list(browser_handlers.keys())}")
            
            try:
                entries = browser_handlers[browser_type](time_period_in_days)
                logger.warning(f"Retrieved {len(entries)} {browser_type} history entries from last {time_period_in_days} days")
    
                # Ensure we are always working with dictionaries
                entries_dict = [ensure_history_entry_dict(e) for e in entries]
    
                # Cache the history for later use
                CACHED_HISTORY.add_history(entries_dict, time_period_in_days, browser_type)
    
                return entries_dict
            except sqlite3.Error as e:
                logger.error(f"Error querying {browser_type} history: {e}")
                if "database is locked" in str(e).lower():
                    raise RuntimeError(f"🔒 BROWSER LOCKED: {browser_type.title()} is currently running and its database is locked. Please close {browser_type.title()} completely to analyze its history. You can restore tabs with Ctrl+Shift+T (Cmd+Shift+T on Mac).")
                else:
                    raise RuntimeError(f"❌ ERROR: Failed to query {browser_type.title()} history: {e}")
            except Exception as e:
                logger.error(f"Unexpected error querying {browser_type} history: {e}")
                raise RuntimeError(f"❌ ERROR: Failed to query {browser_type.title()} history: {e}")
  • Helper utility to detect available browsers and their history database accessibility. Critical for the main handler to determine which browsers to query and detect locked/running browsers.
    def tool_detect_available_browsers() -> Dict[str, Any]:
        browsers_to_check = []
        
        # Check Firefox
        if PATH_TO_FIREFOX_HISTORY:
            browsers_to_check.append(('firefox', PATH_TO_FIREFOX_HISTORY))
        
        # Check Chrome
        if PATH_TO_CHROME_HISTORY:
            browsers_to_check.append(('chrome', PATH_TO_CHROME_HISTORY))
        
        # Check Safari
        if PATH_TO_SAFARI_HISTORY:
            browsers_to_check.append(('safari', PATH_TO_SAFARI_HISTORY))
        
        if not browsers_to_check:
            logger.warning("No browser history databases found")
            return {
                "available_browsers": [],
                "active_browsers": [],
                "status": "error",
                "error_message": "No browser history databases found. Please ensure Firefox, Chrome, or Safari is installed.",
                "user_action_required": False,
                "recommended_action": "Install Firefox, Chrome, or Safari to use this tool"
            }
        
        browsers_in_use = []
        for browser_name, db_path in browsers_to_check:
            logger.warning(f"Checking {browser_name} database at {db_path}")
            try:
                # Try to connect with read-only mode
                conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
                
                # Test if we can actually query the database
                cursor = conn.cursor()
                cursor.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table';")
                table_count = cursor.fetchone()[0]
                conn.close()
                
                logger.warning(f"Successfully connected to {browser_name} database and queried {table_count} tables. Browser may still be running, encourage the user to close this browser to make history available.")
            except sqlite3.OperationalError as e:
                if "database is locked" in str(e).lower():
                    logger.warning(f"Database locked for {browser_name} - browser is likely open and active - prompt the user to close it to get complete history.")
                    # Short-circuit: return immediately if any browser is locked
                    return {
                        "available_browsers": [browser[0] for browser in browsers_to_check],
                        "active_browsers": [browser_name],
                        "status": "browser_locked",
                        "error_message": f"🔒 BROWSER LOCKED: {browser_name.title()} is currently running and its database is locked.",
                        "user_action_required": True,
                        "recommended_action": f"❗ IMPORTANT: Please close all browsers, especially {browser_name.title()} completely to analyze its history. You can restore your tabs later with Ctrl+Shift+T (Cmd+Shift+T on Mac).",
                        "technical_details": f"Database error: {str(e)}"
                    }
                else:
                    logger.warning(f"Error connecting to {browser_name} database: {e} - please inform the user that this browser is not available for analysis.")
            except Exception as e:
                logger.warning(f"Unexpected error connecting to {browser_name} database: {e}")
                # Short-circuit: return immediately if any browser has an error
                return {
                    "available_browsers": [browser[0] for browser in browsers_to_check],
                    "active_browsers": [browser_name],
                    "status": "error",
                    "error_message": f"❌ ERROR: Cannot access {browser_name.title()} database.",
                    "user_action_required": True,
                    "recommended_action": f"Please close all browsers, especially {browser_name.title()} completely and try again. You can restore your tabs later with Ctrl+Shift+T (Cmd+Shift+T on Mac).",
                    "technical_details": f"Technical error: {str(e)}"
                }
        
        # If we get here, no browsers are locked
        available_browsers = [browser[0] for browser in browsers_to_check]
        logger.warning(f"No active browser detected, available browsers: {available_browsers}")
        return {
            "available_browsers": available_browsers,
            "active_browsers": [],
            "status": "ready",
            "error_message": None,
            "user_action_required": False,
            "recommended_action": f"✅ All browsers are available for analysis. Found: {', '.join(available_browsers)}"
        }
  • Firefox-specific history extractor: connects to places.sqlite, queries moz_places table, converts timestamps, filters irrelevant entries.
    def get_firefox_history(days: int) -> List[HistoryEntry]:
        """Get Firefox history from the last N days"""
        firefox_start = time.time()
        print(f"📊 Firefox: Starting history retrieval for {days} days...")
        
        # Check if database exists
        if not os.path.exists(PATH_TO_FIREFOX_HISTORY):
            raise RuntimeError(f"Firefox history not found at {PATH_TO_FIREFOX_HISTORY}")
        
        # Connect to the database
        print(f"📊 Firefox: Connecting to database...")
        conn = sqlite3.connect(f"file:{PATH_TO_FIREFOX_HISTORY}?mode=ro", uri=True)
        try:
            cursor = conn.cursor()
            
            # Firefox stores timestamps as microseconds since Unix epoch
            cutoff_time = (datetime.now() - timedelta(days=days)).timestamp() * 1_000_000
            
            query = """
            SELECT DISTINCT h.url, h.title, h.visit_count, h.last_visit_date
            FROM moz_places h
            WHERE h.last_visit_date > ? 
            AND h.hidden = 0
            AND h.url NOT LIKE 'moz-extension://%'
            ORDER BY h.last_visit_date DESC
            """
            
            cursor.execute(query, (cutoff_time,))
            results = cursor.fetchall()
            
            entries = []
            for url, title, visit_count, last_visit_date in results:
                # Convert Firefox timestamp (microseconds) to datetime
                visit_time = datetime.fromtimestamp(last_visit_date / 1_000_000)
                
                entries.append(HistoryEntry(
                    url=url or "",
                    title=title,
                    visit_count=visit_count or 0,
                    last_visit_time=visit_time
                ))
            
            firefox_time = time.time() - firefox_start
            print(f"📊 Firefox: History retrieval completed in {firefox_time:.3f}s: {len(entries)} entries")
            return entries
        except Exception as e:
            firefox_time = time.time() - firefox_start
            print(f"❌ Firefox: History retrieval failed in {firefox_time:.3f}s: {e}")
            logger.error(f"Error querying Firefox history: {e}")
            raise RuntimeError(f"Failed to query Firefox history: {e}")
        finally:
            if conn:
                conn.close()
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions speed ('fastest way') and that it returns 'raw' data 'without analysis', which adds useful context. However, it doesn't disclose important behavioral aspects like whether this requires special permissions, what format the history entries have, or how errors are handled for a tool that interacts with browser data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and appropriately sized. It begins with the core purpose, provides usage guidance, then documents parameters and return values in clear sections. Every sentence earns its place, with no redundant information. The 'Step 2' prefix is slightly odd but doesn't detract significantly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema (though not shown), the description doesn't need to fully explain return values, though it helpfully mentions the two possible return structures. With no annotations and 3 parameters, the description does a good job explaining parameters and basic behavior. The main gap is lack of information about permissions, error handling, or data format details that would be important for browser history access.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description provides parameter documentation in the 'Args' section that explains all three parameters with meaningful context beyond the schema. With 0% schema description coverage, this fully compensates by explaining what each parameter does, including defaults and browser options. The only minor gap is not explaining what 'auto-detect' means for browser_type when None.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Get raw browser history data without analysis.' It specifies the verb ('get') and resource ('raw browser history data'), distinguishing it from analysis-focused siblings like 'analyze_browser_history'. However, it doesn't explicitly differentiate from other retrieval siblings like 'search_browser_history' beyond speed emphasis.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool: 'This is the fastest way to retrieve browser history and should be used before any analysis.' This implies it's for initial data retrieval rather than analysis or searching. It doesn't explicitly state when NOT to use it or name alternatives, but the 'before any analysis' guidance is helpful.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/mixophrygian/browser_history_mcp'

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