chronicle-mcp 1.4.0
MCP server for secure local browser history access
License: MIT
Requires Python: >=3.10
Keywords: mcp, browser, history, anthropic, cursor

# ChronicleMCP

<div align="center">

**Secure, local-first Model Context Protocol (MCP) server for browser history**

[![PyPI Version](https://img.shields.io/pypi/v/chronicle-mcp)](https://pypi.org/project/chronicle-mcp/)
[![License](https://img.shields.io/pypi/l/chronicle-mcp)](https://opensource.org/licenses/MIT/)
[![Tests](https://img.shields.io/github/actions/workflow/status/nikolasil/chronicle-mcp/ci.yml?label=tests)](https://github.com/nikolasil/chronicle-mcp/actions)
[![Coverage](https://img.shields.io/codecov/c/github/nikolasil/chronicle-mcp)](https://codecov.io/gh/nikolasil/chronicle-mcp)

</div>

---

## Table of Contents

1. [Quick Start](#quick-start)
2. [Features](#features)
3. [Installation](#installation)
4. [Usage](#usage)
   - [CLI Commands](#cli-commands)
   - [MCP Tools](#mcp-tools)
   - [HTTP API](#http-api)
5. [Configuration](#configuration)
6. [Security & Privacy](#security--privacy)
7. [Supported Browsers](#supported-browsers)
8. [Troubleshooting](#troubleshooting)
9. [Development](#development)
10. [Contributing](#contributing)
11. [License](#license)

---

## Quick Start

```bash
# Install
pip install chronicle-mcp

# Run MCP server (stdio mode for AI agents like Claude, Cursor)
chronicle-mcp mcp

# Or start an HTTP server
chronicle-mcp http --port 8080

# Check available browsers
chronicle-mcp list-browsers
```

---

## Features

| Feature | Description |
|---------|-------------|
| 🔒 **Privacy-First** | All data stays on your machine. No cloud sync, no data collection. |
| 🌐 **Multi-Browser** | Chrome, Firefox, Edge, Brave, Safari, Vivaldi, Opera support |
| 🔍 **Multiple Search Tools** | Search by query, date range, domain, or recent history |
| 📊 **Output Formats** | Markdown (default) or JSON |
| ⚡ **Fast Performance** | Built with Python and SQLite |
| 🔐 **Secure** | URL sanitization removes sensitive query parameters |
| 🐳 **Docker Support** | Run as a container |
| 🔧 **CLI Interface** | Full command-line control |
| 🌐 **HTTP API** | RESTful API for integrations |
| 🔖 **Bookmarks** | Read bookmarks from all supported browsers |
| ⬇️ **Downloads** | Track download history from all supported browsers |

---

## Installation

### pip (Recommended)

```bash
pip install chronicle-mcp
```

### pipx (Isolated Installation)

```bash
pipx install chronicle-mcp
```

### Docker

```bash
# Pull the latest image
docker pull ghcr.io/nikolasil/chronicle-mcp:latest

# Run the server
docker run -p 8080:8080 ghcr.io/nikolasil/chronicle-mcp
```

### From Source

```bash
git clone https://github.com/nikolasil/chronicle-mcp.git
cd chronicle-mcp
pip install -e .
```

### Homebrew (macOS)

```bash
brew tap nikolasil/chronicle-mcp
brew install chronicle-mcp
```

---

## Usage

### CLI Commands

#### `chronicle-mcp mcp`

Run the MCP server for AI agents.

```bash
# Stdio mode (default for AI assistants)
chronicle-mcp mcp

# SSE mode for HTTP clients
chronicle-mcp mcp --sse --port 8080
```

| Option | Description |
|--------|-------------|
| `--sse` | Use SSE transport instead of stdio |
| `--host` | Host to bind (SSE mode only) |
| `--port` | Port to listen on (SSE mode only) |

#### `chronicle-mcp http`

Start a long-running HTTP REST API server.

```bash
# Default (foreground, port 8080)
chronicle-mcp http

# Custom port
chronicle-mcp http --port 9000

# Daemon mode
chronicle-mcp http --port 8080 --daemon

# Different browser
chronicle-mcp http --browser firefox
```

| Option | Description |
|--------|-------------|
| `--host` | Host to bind (default: `127.0.0.1`) |
| `--port` | Port to listen on (default: `8080`) |
| `--browser` | Default browser (default: `chrome`) |
| `--foreground` | Run in foreground (default: true) |
| `--daemon` | Run as daemon |

#### `chronicle-mcp status`

Check if the server is running.

```bash
chronicle-mcp status --port 8080
```

#### `chronicle-mcp logs`

View server logs.

```bash
chronicle-mcp logs --port 8080 --lines 100
```

#### `chronicle-mcp version`

Show version information.

```bash
chronicle-mcp version
```

#### `chronicle-mcp list-browsers`

List available browsers on the system.

```bash
chronicle-mcp list-browsers
# Output: Available browsers: chrome, edge
```

#### `chronicle-mcp completion`

Generate shell completion scripts.

```bash
# Bash
chronicle-mcp completion bash >> ~/.bashrc

# Zsh
chronicle-mcp completion zsh >> ~/.zshrc

# Fish
chronicle-mcp completion fish > ~/.config/fish/completions/chronicle-mcp.fish
```

---

### MCP Tools

#### `search_history`

Search browser history for keywords in titles or URLs.

```python
def search_history(
    query: str,
    limit: int = 5,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
# Markdown output (default)
search_history("python tutorial", limit=10, browser="chrome")

# JSON output
search_history("github", limit=5, format_type="json")
```

#### `get_recent_history`

Get recent browsing history from the last N hours.

```python
def get_recent_history(
    hours: int = 24,
    limit: int = 20,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
# Last 24 hours
get_recent_history(hours=48, limit=20)
```

#### `count_visits`

Count total visits to a specific domain.

```python
def count_visits(
    domain: str,
    browser: str = "chrome"
) -> str
```

**Example:**
```python
count_visits("github.com", browser="chrome")
# Output: Visits to 'github.com' in chrome: 42
```

#### `list_top_domains`

Get the most visited domains.

```python
def list_top_domains(
    limit: int = 10,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
list_top_domains(limit=20)
```

#### `search_history_by_date`

Search history within a date range.

```python
def search_history_by_date(
    query: str,
    start_date: str,  # ISO format: YYYY-MM-DD
    end_date: str,    # ISO format: YYYY-MM-DD
    limit: int = 10,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
search_history_by_date(
    "python",
    start_date="2024-01-01",
    end_date="2024-12-31",
    limit=20
)
```

#### `list_available_browsers`

Returns a list of browsers with detected history databases.

```python
def list_available_browsers() -> str
```

**Example:**
```python
list_available_browsers()
# Output: Available browsers: chrome, edge, firefox, brave
```

#### `delete_history`

Delete history entries matching a query.

```python
def delete_history(
    query: str,
    limit: int = 100,
    browser: str = "chrome",
    confirm: bool = False
) -> str
```

**Example:**
```python
# Preview what would be deleted (default)
delete_history("spam.com", browser="chrome")
# Actually delete
delete_history("spam.com", browser="chrome", confirm=True)
```

#### `search_by_domain`

Search history within specific domain(s).

```python
def search_by_domain(
    domain: str,
    query: str = None,
    limit: int = 20,
    browser: str = "chrome",
    format_type: str = "markdown",
    exclude_domains: list[str] = None
) -> str
```

**Example:**
```python
search_by_domain("github.com", browser="chrome")
```

#### `get_browser_stats`

Get browsing statistics for the browser database.

```python
def get_browser_stats(browser: str = "chrome") -> str
```

**Example:**
```python
get_browser_stats(browser="firefox")
# Returns JSON with total visits, domains, date range, etc.
```

#### `get_most_visited_pages`

Get the most visited individual pages.

```python
def get_most_visited_pages(
    limit: int = 20,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
get_most_visited_pages(limit=10, browser="chrome")
```

#### `export_history`

Export history to CSV or JSON format.

```python
def export_history(
    format_type: str = "csv",
    limit: int = 1000,
    query: str = None,
    browser: str = "chrome"
) -> str
```

**Example:**
```python
# Export to CSV
export_history(format_type="csv", limit=1000, browser="chrome")

# Export to JSON
export_history(format_type="json", limit=500, browser="firefox")
```

#### `search_history_advanced`

Advanced search with multiple options including regex and fuzzy matching.

```python
def search_history_advanced(
    query: str,
    limit: int = 20,
    browser: str = "chrome",
    format_type: str = "markdown",
    exclude_domains: list[str] = None,
    sort_by: str = "date",
    use_regex: bool = False,
    use_fuzzy: bool = False,
    fuzzy_threshold: float = 0.6
) -> str
```

**Example:**
```python
# Fuzzy search
search_history_advanced("pythn tutorial", use_fuzzy=True, fuzzy_threshold=0.7)

# Regex search
search_history_advanced(r"github\.com/.*/issues/\d+", use_regex=True)
```

#### `sync_history`

Sync history between browsers.

```python
def sync_history(
    source_browser: str,
    target_browser: str,
    merge_strategy: str = "latest",
    dry_run: bool = True
) -> str
```

**Example:**
```python
# Preview sync
sync_history("chrome", "firefox", dry_run=True)

# Actually sync
sync_history("chrome", "firefox", dry_run=False)
```

#### `list_available_bookmarks`

Returns a list of browsers with detected bookmarks.

```python
def list_available_bookmarks() -> str
```

**Example:**
```python
list_available_bookmarks()
# Output: Available browsers with bookmarks: chrome, edge, firefox
```

#### `list_available_downloads`

Returns a list of browsers with detected downloads history.

```python
def list_available_downloads() -> str
```

**Example:**
```python
list_available_downloads()
# Output: Available browsers with downloads: chrome, edge, firefox
```

#### `get_bookmarks`

Get bookmarks from a browser.

```python
def get_bookmarks(
    query: str = None,
    limit: int = 50,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
# Get all bookmarks
get_bookmarks(browser="chrome")

# Search bookmarks
get_bookmarks(query="python", browser="firefox")
```

#### `get_downloads`

Get downloads history from a browser.

```python
def get_downloads(
    query: str = None,
    limit: int = 50,
    browser: str = "chrome",
    format_type: str = "markdown"
) -> str
```

**Example:**
```python
# Get all downloads
get_downloads(browser="chrome")

# Search downloads
get_downloads(query="pdf", browser="firefox")
```

---

### HTTP API

#### Endpoints

| Method | Endpoint | Description |
|--------|---------|-------------|
| GET | `/health` | Health check |
| GET | `/ready` | Readiness check |
| GET | `/metrics` | Basic metrics |
| GET | `/metrics/prometheus` | Prometheus metrics |
| GET | `/api/browsers` | List available browsers |
| POST | `/api/search` | Search history |
| POST | `/api/recent` | Recent history |
| POST | `/api/count` | Count domain visits |
| POST | `/api/top-domains` | Top domains |
| POST | `/api/search-date` | Search by date |
| POST | `/api/delete` | Delete history entries |
| POST | `/api/domain-search` | Search by domain |
| POST | `/api/stats` | Browser statistics |
| POST | `/api/most-visited` | Most visited pages |
| POST | `/api/export` | Export history |
| POST | `/api/advanced-search` | Advanced search |
| POST | `/api/sync` | Sync between browsers |
| GET | `/api/bookmarks` | List available bookmarks |
| POST | `/api/bookmarks/query` | Query bookmarks |
| GET | `/api/downloads` | List available downloads |
| POST | `/api/downloads/query` | Query downloads |

#### Request/Response Examples

**POST /api/search**

```bash
curl -X POST http://localhost:8080/api/search \
  -H "Content-Type: application/json" \
  -d '{"query": "python tutorial", "limit": 10, "browser": "chrome", "format": "markdown"}'
```

**Response:**
```json
{
  "results": [
    {
      "title": "Python Tutorial",
      "url": "https://docs.python.org/3/tutorial/",
      "timestamp": "2024-01-15T10:30:00+00:00"
    }
  ],
  "count": 1
}
```

**GET /health**

```bash
curl http://localhost:8080/health
```

**Response:**
```json
{
  "status": "healthy",
  "service": "chronicle-mcp",
  "version": "1.1.0",
  "timestamp": "2024-01-15T10:30:00+00:00"
}
```

---

## Configuration

ChronicleMCP can be configured using environment variables or a config file.

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `CHRONICLE_CONFIG` | Path to config file | `~/.config/chronicle-mcp/config.toml` |
| `CHRONICLE_PORT` | Default port | `8080` |
| `CHRONICLE_HOST` | Default host | `127.0.0.1` |
| `CHRONICLE_BROWSER` | Default browser | `chrome` |

### Config File

Create `~/.config/chronicle-mcp/config.toml`:

```toml
[default]
browser = "chrome"
limit = 10
format = "markdown"
log_level = "INFO"
```

---

## Security & Privacy

- **Local-Only:** All data stays on your machine
- **URL Sanitization:** Sensitive query parameters are automatically removed:
  - `token`, `session`, `key`, `password`, `auth`, `sid`, `access_token`
- **Temp Files:** History is copied to temporary files that are cleaned up after each query
- **No Data Collection:** Your browsing data is never sent to any server
- **Error Messages:** No sensitive file paths are exposed

---

## Supported Browsers

ChronicleMCP supports reading history from the following browsers:

| Browser | Version | Windows | macOS | Linux |
|---------|---------|---------|-------|-------|
| Chrome | 120+ | `%LocalAppData%\Google\Chrome\User Data\Default\History` | `~/Library/Application Support/Google/Chrome/Default/History` | `~/.config/google-chrome/Default/History` |
| Edge | 120+ | `%LocalAppData%\Microsoft\Edge\User Data\Default\History` | `~/Library/Application Support/Microsoft Edge/Default/History` | `~/.config/microsoft-edge/Default/History` |
| Firefox | 121+ | `%AppData%\Mozilla\Firefox\Profiles\*.default\places.sqlite` | `~/Library/Mozilla/Firefox/Profiles/*.default/places.sqlite` | `~/.mozilla/firefox/*.default/places.sqlite` |
| Brave | 1.30+ | `%LocalAppData%\BraveSoftware\Brave-Default\History` | `~/Library/Application Support/BraveSoftware/Brave-Default/History` | `~/.config/BraveSoftware/Brave-Default/History` |
| Safari | 16+ | N/A | `~/Library/Safari/History.db` | N/A |
| Vivaldi | 6.0+ | `%LocalAppData%\Vivaldi\Default\History` | `~/Library/Application Support/Vivaldi/Default/History` | `~/.config/vivaldi/Default/History` |
| Opera | 105+ | `%AppData%\Opera Software\Opera Stable\History` | `~/Library/Application Support/com.operasoftware.Opera/History` | `~/.config/opera/History` |

---

## Troubleshooting

### "Browser history not found"

- Ensure the browser is installed
- Check that Chrome/Edge isn't currently open (locks the database)
- Run `chronicle-mcp list-browsers` to see detected browsers

### "Permission denied"

- Check file permissions on the browser's history database
- On Windows, ensure the browser is closed before querying

### Empty results

- Try a more specific search term
- Check the date range for `search_history_by_date`
- Verify the browser has history data

### Performance issues

- Large history databases may take longer to query
- Consider reducing the `limit` parameter

---

## Development

### Running Tests

```bash
# Run all tests
pytest

# Verbose output
pytest -v

# Specific test file
pytest tests/test_database.py

# Single test
pytest -k test_name

# With coverage
pytest --cov=chronicle_mcp
```

### Development Server

```bash
# Run MCP server (stdio mode)
python -m chronicle_mcp mcp

# Run HTTP server
python -m chronicle_mcp http --port 8080
```

### Code Quality

```bash
# Linting
ruff check .

# Formatting
ruff format .

# Type checking
mypy chronicle_mcp/
```

---

## Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.

---

## License

MIT License - See [LICENSE](LICENSE) file for details.

---

<div align="center">

**Built with ❤️ for AI agents and developers**

</div>