Chrome拡張機能を自作してローカルLLMと繋げる
公開日: 2026/03/13 / 更新日: 2026/03/28
PC外部への通信を一切行わずに、現在開いている Web ページの本文を抽出してローカル LLM に要約させる Chrome 拡張機能を作ってみる。
具体的な機能
- 拡張機能を起動するとポップアップが出現する
- ポップアップの「要約する」を押すと
- 要約結果がポップアップに表示される
要約は、現在のタブの本文を抽出する→抽出した本文をローカルLLMに送る→要約結果を返す、という流れで行う。
Chrome拡張機能の実装に関するルール
デベロッパーモードで個人的に使用するだけなら、細かいことは気にしなくても問題なく動作するらしいが、今回はChromeウェブストアに公開する際の審査基準を可能な限り満たせるような実装を目指している(なお実際に通過するかは不明)。
- Manifest V3(現行のChrome 拡張機能はこれが標準)を使う
- 権限は activeTab と scripting のみ
<all_urls>は使わない- API 通信は background 側でのみ行う
innerHTMLやeval()のような危険な処理は使わない- 通信先は http://localhost:1234/ に限定する
つまり、拡張機能として必要なことだけを許可して、それ以外の拡張性?は極力持たせない方針。
技術スタック
- Vite + React + TypeScript
- CRXJS(viteのプラグインでChrome拡張機能作成が便利になるやつ。)
Viteを土台にしてReact + TypeScriptで本体を作り、CRXJSでChrome拡張機能としてビルド。
実装の手順
1. LM Studio でローカル LLM サーバーを起動する
この拡張は LM Studio の OpenAI 互換 API を叩くようにするため、先に LM Studio 側で任意のモデルをロードしてサーバーを起動しておく。(LM Studioの基本的なセットアップは割愛。)
適当なCLI から以下のコマンドを実行していく。
lms lslms load ロードしたいモデル名lms server start("--port 1234"のようにポート番号を指定してもいいが、何もつけなくても1234になった。)
すると以下のようになる。
lms ls
You have 3 models, taking up 12.19 GB of disk space.
LLM PARAMS ARCH SIZE DEVICE
dolphin3.0-llama3.1-8b 8B Llama 4.69 GB Local
wizard-vicuna-13b-uncensored 13B Llama 7.41 GB Local
EMBEDDING PARAMS ARCH SIZE DEVICE
text-embedding-nomic-embed-text-v1.5 Nomic BERT 84.11 MB Local
lms load dolphin3.0-llama3.1-8b
Model loaded successfully in 1.64s.
(4.37 GiB)
To use the model in the API/SDK, use the identifier "dolphin3.0-llama3.1-8b".
lms server start
Success! Server is now running on port 1234今回は、dolphin3.0-llama3.1-8bという無修正モデルをロードした。このモデルは80億パラメーターの4ビット量子化でサイズは4GBちょっと、一般的には8GB以上のメモリがあればローアルで快適に動作すると言われている。(今回はM1 MacBook Pro 16GB)
これでLLMサーバーが localhost:1234 で待ち構えてくれるようになった。
2. 拡張機能を実装する
構成
.
├─ index.html
├─ manifest.json
├─ package.json
├─ package-lock.json
├─ tsconfig.json
├─ vite.config.ts
└─ src
├─ App.tsx
├─ background.ts
├─ content.ts
├─ main.tsx
├─ styles.css
└─ vite-env.d.ts処理の流れ
→ 拡張アイコンが押されると index.html が開き main.tsx が読み込まれて App.tsx が表示される
→ App.tsx で要約実行ボタンが押される
→ App.tsx が background に指示を出す
→ background.ts が処理を開始して今のタブを特定する
→ background.ts が content.ts をそのページ上で実行する
→ content.ts がページの本文を工夫しながら抜き出す
→ background.ts が抽出結果を受け取りプロンプトを組み立ててLLMサーバーに送る
→ background.ts が要約結果を App.tsx に返す
→ App.tsx が結果を表示する
App.tsx はポップアップUIの描画と状態管理に専念、background.ts は処理の進行役で content.ts は、service workerである background.ts が読めないページの中身を見にいく係。私は現代っ子なので、それぞれのコードの細かい中身については、優秀なコーディングエージェントに丁重にお任せした。
manifest.jsonについて(Chromeはこれをみて拡張機能の動作範囲を制御する)
{
"manifest_version": 3,
"name": "web analyzer",
"version": "1.0.0",
"description": "Analyze the current page with a local LLM running in LM Studio.",
"permissions": ["activeTab", "scripting"],
"host_permissions": ["http://localhost:1234/*"],
"action": {
"default_title": "web analyzer",
"default_popup": "index.html"
},
"background": {
"service_worker": "src/background.ts",
"type": "module"
},
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; connect-src http://localhost:1234"
}
}ポイント1:権限は必要最小限。
- 関数は、"activeTab"(今ユーザーが開いていて拡張を使用したタブ内で)と"scripting"(そのタブでのスクリプト実行)のみを許可
- 通信は、http://localhost:1234/(ローカルLLMサーバー)のみ許可
ポイント2:backgroundのservice workerとしてbackground.tsを登録する
- Mnifest V3ではイベント駆動型のservice workerとして処理を実行する仕様。
3. ビルドする
package.jsonとvite.config.tsにCRXJSに関する設定を書いていれば以下のコマンドでChrome拡張機能ようにビルドされる。
npm install
npm run build4. Chrome から動作確認する
chrome://extensionsを開く- ”デベロッパー モード”をオンにする
- ”パッケージ化されていない拡張機能を読み込む”を押す
dist/を選ぶ- 普通の Web ページを開いて(Chrome ウェブストア上では動かなかった)拡張機能から"web analyzer"を選ぶ
- ”要約する”を押して結果を確認する

総括
この度は、LLMを使ってブラウザで開いた内容の解析をChromeの拡張機能からPC外部への通信なしにして実行できることが確認できた。今回LLMに頼んだのは簡単な要約だった(今回のモデルでは1000字程度を要約するのに10秒ほどかかっている)が、より高度なLLMと接続することで可能性が広がる。例えば、内容を構造化して別のデータと比較させたり、対話型のUIを実装することで自分だけのChrome専用引きこもりコパイロットへ進化させることもできそうだ。
ライター
HARLYA
無能アリ
趣味でフルスタック開発をしている。巨人の肩に乗りまくりで四捨五入すればエンドユーザー。