注意
Chromeの履歴データのタイトル・url・日付情報がLLMに渡されます。
気になる方はフィルタリング処理などを適宜入れてください
特に、サービスへの認証処理などで一時的なトークンなどの非公開情報がurlに含まれている場合があります。クエリパラメータは消すようにしていますが、それでも何かしら残っている可能性がありますのでご注意ください。
前提
以下のチュートリアルが完了していること
※ これを改変して作っているので、チュートリアルを読んでいただくと理解が深まると思います
参考
- https://qiita.com/ham0215/items/cf9a8a0a8aec33158925
- https://www.rasukarusan.com/entry/2019/04/27/000000
コード
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"
]
}
}
}