Skip to main content
Glama

search_history

Search browser history across multiple browsers to find previously visited pages by URL or title. Retrieve formatted results with visit times for personalized data access.

Instructions

Search browser history for URLs and titles containing the search term.

Args: search_term: The text to search for in URLs and page titles limit: Maximum number of results to return (default: 50, max: 500) browser: Which browser to search ("brave", "safari", "chrome", "firefox", "edge", "arc", "opera", or "duckduckgo")

Returns: Formatted list of matching history entries with titles, URLs, and visit times

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
search_termYes
limitNo
browserNobrave

Implementation Reference

  • The core handler function for the 'search_history' tool. It supports searching history across 8 browsers, handles platform differences, database encryption (DuckDuckGo macOS), custom SQL schemas per browser, timestamp conversions, and returns a formatted list of results.
    @mcp.tool def search_history(search_term: str, limit: int = 50, browser: Literal["brave", "safari", "chrome", "firefox", "edge", "arc", "opera", "duckduckgo"] = "brave") -> str: """ Search browser history for URLs and titles containing the search term. Args: search_term: The text to search for in URLs and page titles limit: Maximum number of results to return (default: 50, max: 500) browser: Which browser to search ("brave", "safari", "chrome", "firefox", "edge", "arc", "opera", or "duckduckgo") Returns: Formatted list of matching history entries with titles, URLs, and visit times """ if limit > 500: limit = 500 if browser == "duckduckgo": if is_duckduckgo_encrypted(): # DuckDuckGo macOS - encrypted database schema query = """ SELECT ZURLENCRYPTED, ZTITLEENCRYPTED, ZNUMBEROFTOTALVISITS, ZNUMBEROFTRACKERSBLOCKED, ZLASTVISIT FROM ZHISTORYENTRYMANAGEDOBJECT WHERE ZURLENCRYPTED IS NOT NULL ORDER BY ZLASTVISIT DESC LIMIT ? """ # DuckDuckGo needs special handling - decrypt first, then filter all_results = query_duckduckgo_db(query, (limit * 2,)) # Get more results to account for filtering # Filter results by search term after decryption results = [ entry for entry in all_results if search_term.lower() in entry['url'].lower() or search_term.lower() in entry['title'].lower() ][:limit] else: # DuckDuckGo Windows - Chromium-based, no encryption query = """ SELECT url, title, visit_count, last_visit_time FROM urls WHERE (url LIKE ? OR title LIKE ?) AND url NOT LIKE 'https://static.ddg.local/%' ORDER BY last_visit_time DESC LIMIT ? """ search_pattern = f"%{search_term}%" results = query_history_db(query, (search_pattern, search_pattern, limit), browser) elif browser == "safari": # Safari database schema query = """ SELECT history_items.url as url, history_visits.title as title, COUNT(history_visits.id) as visit_count, MAX(history_visits.visit_time) as last_visit_time FROM history_items JOIN history_visits ON history_items.id = history_visits.history_item WHERE history_items.url LIKE ? OR history_visits.title LIKE ? GROUP BY history_items.url ORDER BY last_visit_time DESC LIMIT ? """ elif browser == "firefox": # Firefox database schema (places.sqlite) query = """ SELECT moz_places.url as url, moz_places.title as title, moz_places.visit_count as visit_count, moz_places.last_visit_date as last_visit_time FROM moz_places WHERE (moz_places.url LIKE ? OR moz_places.title LIKE ?) AND moz_places.hidden = 0 ORDER BY last_visit_time DESC LIMIT ? """ else: # Chromium-based browsers (Brave/Chrome/Edge/Arc/Opera) database schema query = """ SELECT url, title, visit_count, last_visit_time FROM urls WHERE url LIKE ? OR title LIKE ? ORDER BY last_visit_time DESC LIMIT ? """ # Query databases (DuckDuckGo already queried above) if browser != "duckduckgo": search_pattern = f"%{search_term}%" results = query_history_db(query, (search_pattern, search_pattern, limit), browser) if not results: return f"No history entries found matching '{search_term}' in {browser.capitalize()}" output = [f"Found {len(results)} {browser.capitalize()} history entries matching '{search_term}':\n"] for i, entry in enumerate(results, 1): title = entry['title'] or "No title" url = entry['url'] visit_count = entry['visit_count'] if browser == "duckduckgo": if is_duckduckgo_encrypted(): # macOS encrypted version has trackers blocked last_visit = cocoa_timestamp_to_datetime(entry['last_visit_time']) trackers_blocked = entry.get('trackers_blocked', 0) output.append(f"{i}. {title}") output.append(f" URL: {url}") output.append(f" Visits: {visit_count} | Trackers blocked: {trackers_blocked} | Last visited: {last_visit}") output.append("") else: # Windows Chromium version last_visit = chrome_timestamp_to_datetime(entry['last_visit_time']) output.append(f"{i}. {title}") output.append(f" URL: {url}") output.append(f" Visits: {visit_count} | Last visited: {last_visit}") output.append("") elif browser == "safari": last_visit = safari_timestamp_to_datetime(entry['last_visit_time']) output.append(f"{i}. {title}") output.append(f" URL: {url}") output.append(f" Visits: {visit_count} | Last visited: {last_visit}") output.append("") elif browser == "firefox": last_visit = firefox_timestamp_to_datetime(entry['last_visit_time']) output.append(f"{i}. {title}") output.append(f" URL: {url}") output.append(f" Visits: {visit_count} | Last visited: {last_visit}") output.append("") else: last_visit = chrome_timestamp_to_datetime(entry['last_visit_time']) output.append(f"{i}. {title}") output.append(f" URL: {url}") output.append(f" Visits: {visit_count} | Last visited: {last_visit}") output.append("") return "\n".join(output)
  • Type annotations and documentation string defining the input schema (parameters with types and descriptions) and output format for the tool.
    def search_history(search_term: str, limit: int = 50, browser: Literal["brave", "safari", "chrome", "firefox", "edge", "arc", "opera", "duckduckgo"] = "brave") -> str: """ Search browser history for URLs and titles containing the search term. Args: search_term: The text to search for in URLs and page titles limit: Maximum number of results to return (default: 50, max: 500) browser: Which browser to search ("brave", "safari", "chrome", "firefox", "edge", "arc", "opera", or "duckduckgo") Returns: Formatted list of matching history entries with titles, URLs, and visit times
  • mcp_server.py:465-465 (registration)
    FastMCP decorator that registers the search_history function as a tool.
    @mcp.tool
  • Critical helper function to resolve the correct history database file path for any supported browser and operating system.
    def get_history_db_path(browser: Literal["brave", "safari", "chrome", "firefox", "edge", "arc", "opera", "duckduckgo"] = "brave") -> Path: """ Get the path to the browser history database. Handles cross-platform paths (Windows, macOS, Linux) for various browsers. Safari only supported on macOS. DuckDuckGo and Arc available on macOS and Windows. Args: browser: Which browser to get history from ("brave", "safari", "chrome", "firefox", "edge", "arc", "opera", or "duckduckgo") """ if browser == "duckduckgo": # DuckDuckGo browser paths if os.name == 'nt': # Windows # Try Windows Store version first store_path = find_windows_store_app_path("DuckDuckGo") if store_path: history_path = store_path else: # Fall back to standard installation (Chromium-based, no encryption) history_path = Path.home() / "AppData" / "Local" / "DuckDuckGo" / "User Data" / "Default" / "History" elif platform.system() == 'Darwin': # macOS # DuckDuckGo for macOS (uses encryption) history_path = Path.home() / "Library/Containers/com.duckduckgo.mobile.ios/Data/Library/Application Support/Database.sqlite" else: # Linux raise ValueError("DuckDuckGo is not available on Linux") return history_path if browser == "safari": # Safari only available on macOS if platform.system() != 'Darwin': raise ValueError("Safari is only available on macOS") return Path.home() / "Library" / "Safari" / "History.db" # Chromium-based browsers (Brave, Chrome, Edge) if browser == "chrome": if os.name == 'nt': # Windows history_path = Path.home() / "AppData" / "Local" / "Google" / "Chrome" / "User Data" / "Default" / "History" elif platform.system() == 'Darwin': # macOS history_path = Path.home() / "Library" / "Application Support" / "Google" / "Chrome" / "Default" / "History" else: # Linux history_path = Path.home() / ".config" / "google-chrome" / "Default" / "History" elif browser == "edge": # Microsoft Edge (Chromium-based) if os.name == 'nt': # Windows history_path = Path.home() / "AppData" / "Local" / "Microsoft" / "Edge" / "User Data" / "Default" / "History" elif platform.system() == 'Darwin': # macOS history_path = Path.home() / "Library" / "Application Support" / "Microsoft Edge" / "Default" / "History" else: # Linux history_path = Path.home() / ".config" / "microsoft-edge" / "Default" / "History" elif browser == "arc": # Arc Browser (Chromium-based) if os.name == 'nt': # Windows # Try Windows Store version first store_path = find_windows_store_app_path("Arc") if store_path: history_path = store_path else: # Fall back to standard installation history_path = Path.home() / "AppData" / "Local" / "Arc" / "User Data" / "Default" / "History" elif platform.system() == 'Darwin': # macOS history_path = Path.home() / "Library" / "Application Support" / "Arc" / "User Data" / "Default" / "History" else: # Linux raise ValueError("Arc browser is not available on Linux") elif browser == "opera": # Opera (Chromium-based) if os.name == 'nt': # Windows history_path = Path.home() / "AppData" / "Roaming" / "Opera Software" / "Opera Stable" / "Default" / "History" elif platform.system() == 'Darwin': # macOS history_path = Path.home() / "Library" / "Application Support" / "com.operasoftware.Opera" / "Default" / "History" else: # Linux history_path = Path.home() / ".config" / "opera" / "Default" / "History" elif browser == "firefox": # Firefox uses a different structure with profile folders if os.name == 'nt': # Windows profiles_path = Path.home() / "AppData" / "Roaming" / "Mozilla" / "Firefox" / "Profiles" elif platform.system() == 'Darwin': # macOS profiles_path = Path.home() / "Library" / "Application Support" / "Firefox" / "Profiles" else: # Linux profiles_path = Path.home() / ".mozilla" / "firefox" # Firefox stores history in places.sqlite in the profile directory # Look for the default profile (prioritize .default-release over .default) if profiles_path.exists(): # Try .default-release first (newer Firefox versions) profile_folders = list(profiles_path.glob("*.default-release")) if not profile_folders: # Fall back to .default profile_folders = list(profiles_path.glob("*.default")) if profile_folders: # Check if places.sqlite actually exists in the profile for profile in profile_folders: potential_path = profile / "places.sqlite" if potential_path.exists(): history_path = potential_path break else: raise FileNotFoundError(f"places.sqlite not found in Firefox profile folders at {profiles_path}") else: raise FileNotFoundError(f"Firefox profile folder not found in {profiles_path}") else: raise FileNotFoundError(f"Firefox profiles directory not found at {profiles_path}") else: # brave if os.name == 'nt': # Windows history_path = Path.home() / "AppData" / "Local" / "BraveSoftware" / "Brave-Browser" / "User Data" / "Default" / "History" elif platform.system() == 'Darwin': # macOS history_path = Path.home() / "Library" / "Application Support" / "BraveSoftware" / "Brave-Browser" / "Default" / "History" else: # Linux history_path = Path.home() / ".config" / "BraveSoftware" / "Brave-Browser" / "Default" / "History" return history_path
  • Helper to safely execute SQL queries on browser history databases by using temporary copies to prevent file locking issues.
    def query_history_db(query: str, params: tuple = (), browser: Literal["brave", "safari", "chrome", "firefox", "edge", "arc", "opera"] = "brave") -> list: """ Query the browser history database safely by creating a temporary copy. The database may be locked if browser is running, so we copy it first. Args: query: SQL query to execute params: Query parameters browser: Which browser to query ("brave", "safari", "chrome", "firefox", "edge", "arc", or "opera") """ history_path = get_history_db_path(browser) if not history_path.exists(): raise FileNotFoundError(f"{browser.capitalize()} history database not found at {history_path}") # Create a temporary copy of the database to avoid locking issues with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp_file: tmp_path = tmp_file.name try: shutil.copy2(history_path, tmp_path) except PermissionError as e: # Clean up temp file if copy failed if os.path.exists(tmp_path): os.unlink(tmp_path) if browser == "safari": raise PermissionError( f"Permission denied to access Safari history.\n\n" f"To fix this, grant Full Disk Access:\n" f"1. Open System Settings > Privacy & Security > Full Disk Access\n" f"2. Click the '+' button\n" f"3. Add your terminal app (Terminal.app or iTerm.app) or IDE (VS Code, etc.)\n" f"4. Restart the application\n\n" f"Alternatively, you can access Brave history which doesn't require special permissions." ) from e else: raise try: # Query the temporary database conn = sqlite3.connect(tmp_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute(query, params) results = cursor.fetchall() conn.close() return [dict(row) for row in results] finally: # Clean up temporary file if os.path.exists(tmp_path): os.unlink(tmp_path)

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/ronantakizawa/search-history-mcp'

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