8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chromeの履歴を引っ張ってこれるMCPサーバのコード

Posted at

注意

Chromeの履歴データのタイトル・url・日付情報がLLMに渡されます。
気になる方はフィルタリング処理などを適宜入れてください

特に、サービスへの認証処理などで一時的なトークンなどの非公開情報がurlに含まれている場合があります。クエリパラメータは消すようにしていますが、それでも何かしら残っている可能性がありますのでご注意ください。

前提

以下のチュートリアルが完了していること
※ これを改変して作っているので、チュートリアルを読んでいただくと理解が深まると思います

参考

コード

history.py
import shutil
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
from tempfile import TemporaryDirectory

from mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("history")

# sqlite3ファイル
HISTORY_FILE = Path(
    "~/Library/Application Support/Google/Chrome/Default/History"
).expanduser()


def ut_to_last_visit_time(ut: int) -> int:
    """Convert UNIX timestamp to Chrome's last visit time format."""
    # 1601-01-01 00:00:00 = 1 かつ ミリ秒単位で入っているらしい
    # 参考: https://www.rasukarusan.com/entry/2019/04/27/000000
    # 1601-01-01 00:00:00をunixtime換算すると -11644473600 なので逆に戻してmsにする
    return (ut + 11644473600) * 1000000


def query_chrome_history(history_file: Path, from_unix: int, to_unix: int) -> str:
    # chromeの履歴データはsqlite3で管理されているので、クエリが投げられる
    conn = sqlite3.connect(history_file.as_posix())
    cursor = conn.cursor()

    # SQL query to fetch history
    cursor.execute(
        """
            SELECT url, title, (last_visit_time / 1000000 -11644473600) + 3600 * 9 as visit_unixtime_jst
            FROM urls
            WHERE last_visit_time BETWEEN ? AND ?
            ORDER BY last_visit_time DESC
        """,
        (ut_to_last_visit_time(from_unix), ut_to_last_visit_time(to_unix)),
    )

    # Fetch all results
    results = cursor.fetchall()

    # Format results
    history = []
    for url, title, visit_time in results:
        # urlからクエリパラメータ以外を抽出
        # googleへのログイン時など、クエリパラメータにあまりLLMに渡さない方が良さそうな情報が入っていたため
        parsed_url = urllib.parse.urlparse(url)
        url_without_query = urllib.parse.urlunparse(
            (parsed_url.scheme, parsed_url.netloc, parsed_url.path, "", "", "")
        )

        history.append(
            f"Title: {title}, URL: {url_without_query}, Visit Time: {datetime.fromtimestamp(visit_time).strftime('%Y-%m-%d %H:%M:%S')}"
        )

    return "\n".join(history) if history else "No browsing history found."


def get_chrome_history(from_unix: int, to_unix: int) -> str:
    """Get browsing history from Chrome."""
    try:
        return query_chrome_history(HISTORY_FILE, from_unix, to_unix)
    except sqlite3.OperationalError as e:
        # Chromeを起動している場合、SQLiteのロックエラーが発生する
        # なので別ファイルにコピーして、そこにselectする
        with TemporaryDirectory() as tmp_dir:
            shutil.copy(HISTORY_FILE, tmp_dir)
            tmp_history_file = Path(f"{tmp_dir}/History")
            return query_chrome_history(tmp_history_file, from_unix, to_unix)
    except Exception as e:
        return f"An error occurred: {e}"


@mcp.tool()
async def get_history(from_time: str, to_time: str | None) -> str:
    """Get browsing history from Chrome.

    Args:
        from_time: Start time in YYYY-MM-DD HH:MM:SS format
        to_time: End time in YYYY-MM-DD HH:MM:SS format. If None, use current time.
    """

    # from_timeをJST Aware datetimeに変換
    from_dt = datetime.strptime(from_time, "%Y-%m-%d %H:%M:%S")
    from_dt = from_dt.replace(tzinfo=timezone(timedelta(hours=9)))

    if to_time is None:
        to_dt = datetime.now(timezone(timedelta(hours=9)))
    else:
        to_dt = datetime.strptime(to_time, "%Y-%m-%d %H:%M:%S")
        to_dt = to_dt.replace(tzinfo=timezone(timedelta(hours=9)))

    from_timestamp = int(from_dt.timestamp())
    to_timestamp = int(to_dt.timestamp())

    # Get history from Chrome
    history = get_chrome_history(from_timestamp, to_timestamp)
    return history


if __name__ == "__main__":
    # Initialize and run the server
    mcp.run(transport="stdio")
claude_desktop_config.json
{
	"mcpServers": {
		"history": {
			"command": "uv",
			"args": [
				"--directory",
				"/path/to/your/directory",
				"run",
				"history.py"
			]
		}
	}
}

動いている様子

スクリーンショット 2025-04-03 23.36.18.png

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?