🚀 Search Stack
AIエージェント専用のWeb検索とクローリングの中間層です。
OpenClaw、Claude Code、DifyなどのAIエージェントに統一されたWebアクセスAPIを提供します。多エンジン検索の自動フォールバック、ヘッドレスChromeによる反クローリングレンダリング、Cookie注入によるログイン状態維持、本文の正確な抽出などの機能があります。一度デプロイすれば、すべてのエージェントが共有できます。
🚀 クイックスタート
なぜSearch Stackが必要なのか
| 問題点 |
Search Stackの解決策 |
| Brave/Google検索に無料利用制限がある |
多エンジンフォールバック(Tavily → Serper → SearXNG)、SearXNGは完全に無料で無制限 |
| AIによるWebページのクローリングがCloudflare/反クローリングによってブロックされる |
組み込みのBrowserlessヘッドレスChromeを使用し、Stealthモードを有効にして検出を回避 |
| ログインが必要なサイト(知乎、小红书など)の本文が取得できない |
Cookie管理API + 自動注入レンダリング + ログイン検出ガイド |
| エージェントが誘導されてイントラネットにアクセスする(SSRF) |
組み込みのプライベートIPブラックリスト + DNS検証 |
| 検索結果が要約のみで全文がない |
enrich=trueで検索後、各結果の全文を自動的に取得 |
Brave Searchとの比較
以下のデータは実際のテスト(2026-02-07)に基づいており、検索キーワードは「claude opus 4.6 評価」です。
| 項目 |
Search Stack |
Brave Search(OpenClaw内蔵) |
| 検索速度 |
0.8 - 1.5秒(Tavily/Serper) |
~1 - 2秒 |
| キャッシュヒット |
13ms(Redisキャッシュ15分) |
キャッシュなし、毎回再リクエスト |
| 中国語検索 |
結果が豊富(掘金、知乎、什么值得买など) |
中国語の結果が少なく、英語のソースに偏る |
| 英語検索 |
優れている |
優れている |
| 高可用性 |
3つのエンジンが自動的にフォールバック |
単一ノード、障害時に利用不可 |
| 全文取得 |
enrich=trueで検索と本文取得を一度に |
要約のみ返し、追加で本文を取得する必要あり |
| 反クローリングサイト |
Browserless Chromeによるレンダリング |
取得できない |
| ログインが必要なサイト |
Cookie注入 + 自動検出ガイド |
サポートしない |
| 無料利用制限 |
SearXNGで無制限に対応 |
無料キーには厳しい制限がある |
結論:検索速度は同等で、中国語の検索品質が良く、機能はBraveを大幅に上回ります。
アーキテクチャ
+-----------+
AI Agent ──────────>| search- |──> Tavily API
(OpenClaw / Claude) | proxy |──> Serper API (Google)
POST /search | (FastAPI) |──> SearXNG (self-hosted)
POST /fetch +-----+-----+
|
+-------------+-------------+
| |
+-----+-----+ +--------+--------+
| Redis | | Browserless |
| (cache + | | (headless Chrome |
| rate-limit)| | anti-bot render)|
+------------+ +-----------------+
4つのコンテナを一度に起動できます:
| サービス |
役割 |
| search-proxy |
FastAPIのコアプロキシ、統一された検索/クローリングインターフェース |
| Redis |
結果のキャッシュ(15分TTL)+ APIのレート制限カウント |
| SearXNG |
自前でホストするメタ検索エンジン(Google、DuckDuckGo、Braveなどを統合、無料で無制限) |
| Browserless |
ヘッドレスChrome、JSページのレンダリング、Stealthモードで反クローリングを回避 |
✨ 主な機能
- 多エンジンフォールバック — Tavily → Serper → SearXNGの順に自動的に切り替わり、単一のエンジンが障害してもサービスに影響しません。
- 検索 + クローリング一体 —
/searchで検索、/fetchで本文を取得、enrich=trueで検索後に自動的に全文を取得します。
- 反クローリング対策 — BrowserlessのStealthモードを使用し、Cloudflare / JS Challengeを回避します。
- 本文抽出 — trafilatura + BeautifulSoup + readabilityの3つのエンジンを使用し、正確に本文を抽出します。
- Cookie管理 — APIを使用してCookieを動的に追加、削除し、Chromeのレンダリングに自動的に注入します。ブラウザのCookieを直接貼り付けることもできます。
- ログイン/反クローリング検出 — 多面的なヒューリスティック検出:HTTPステータスコード(401/403)、テキストキーワード(中英日)、ページタイトル、HTML構造(パスワード入力欄、CAPTCHA埋め込み、meta refreshリダイレクト)、SPAのログイン画面などを検出し、
needs_loginフラグを返してCookieの更新を促します。
- Cookie Catcher — ブラウザ内でのリモートログイン:WebSocket + CDP Screencastを使用してWeb UIでリモートChromeを操作し、ログインを完了し、Cookieを一括保存します。
- SSRF防止 — プライベートIP(127/10/172.16/192.168/169.254)へのアクセスを拒否します。
- URL重複排除 — 自動的にトラッキングパラメータ(utm_*、fbclidなど)を削除し、同じドメインの結果数を制限します。
- Redisキャッシュ — 15分のTTLで、重複したクエリは即座に返されます。
- API Key認証 + レート制限 — スライディングウィンドウ方式でレート制限を行います。
- MCP Server — stdioモードのMCP Server(
mcp-server.ts)を提供し、mcporterを介してOpenClawなどのエージェントで使用できます。
- TikHubソーシャルメディアAPI — オプションで統合可能で、TikHubの803のソーシャルプラットフォームツール(抖音、TikTok、微博など)を代理し、自動フォールバック機能を内蔵しています。
- HTTPプロキシ — HTTP / SOCKS5プロキシをサポートし、反クローリングのための固定IPや、YouTubeなどのブロックされたサイトへのアクセスに使用できます。
- 全非同期 — async Redis + 共有httpx接続プールを使用し、高い並行性と低い遅延を実現します。
📦 インストール
前提条件
- Docker + Docker Compose
- (オプション)Tavily API Key — 月1000回の無料利用可能
- (オプション)Serper API Key — 2500回の無料利用可能
Tavily / SerperのAPI Keyを設定しなくても、自動的にSearXNGにフォールバックして使用できます(完全無料)。
1. プロジェクトをクローンする
git clone https://github.com/pinkpills/search-stack.git
cd search-stack
2. 環境変数を設定する
cp .env.example .env
.envを編集します:
TAVILY_API_KEY=your_tavily_key
SERPER_API_KEY=your_serper_key
SEARXNG_SECRET=
PROXY_API_KEY=
BROWSERLESS_TOKEN=
REDIS_PASSWORD=
ランダムなシークレットキーを一括生成するには:
python3 -c "
import secrets
for name in ['SEARXNG_SECRET', 'PROXY_API_KEY', 'BROWSERLESS_TOKEN', 'REDIS_PASSWORD']:
print(f'{name}={secrets.token_hex(16)}')
" >> .env
3. SearXNGを設定する
必須! この手順を行わないと、SearXNGのJSON APIが403を返します。
cp searxng/settings.yml.example searxng/settings.yml
searxng/settings.ymlを編集し、以下の内容が含まれていることを確認します:
search:
formats:
- html
- json
以前にSearXNGを起動したことがあり、自動的にsettings.ymlが生成されている場合は、手動でformats設定を追加してコンテナを再起動してください。
4. サービスを起動する
docker compose -f search-stack.yml up -d
すべてのコンテナが正常に起動するのを待ちます(約30秒):
docker compose -f search-stack.yml ps
すべてのコンテナがhealthyと表示されれば完了です。
5. 検証する
curl -s -H "X-API-Key: YOUR_PROXY_API_KEY" http://127.0.0.1:17080/health | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "hello world", "count": 3}' | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "hello world", "count": 3, "provider": "searxng"}' | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/fetch \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "render": false}' | python3 -m json.tool
ヒント: SearXNGが502または空の結果を返す場合は、formats: [html, json]設定が不足している可能性が高いです。手順3を参照してください。
💻 使用例
基本的な使用法
curl -s -H "X-API-Key: YOUR_PROXY_API_KEY" http://127.0.0.1:17080/health | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "hello world", "count": 3}' | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "hello world", "count": 3, "provider": "searxng"}' | python3 -m json.tool
curl -s -X POST http://127.0.0.1:17080/fetch \
-H "X-API-Key: YOUR_PROXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "render": false}' | python3 -m json.tool
高度な使用法
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: KEY" -H "Content-Type: application/json" \
-d '{"query": "Python asyncio", "count": 3, "enrich": true}'
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: KEY" -H "Content-Type: application/json" \
-d '{"query": "AI news", "count": 5, "provider": "searxng"}'
📚 ドキュメント
OpenClawとの統合
Search Stackは、OpenClawのデフォルトの検索/クローリングツールとして使用でき、内蔵のBrave検索を置き換えることができます。2つの統合方法があります。
方法1:ネイティブプラグイン(推奨)
ネイティブプラグインは、ツールを直接AIのツールリストに登録し、OpenClawプロセス内で実行されます。MCP+mcporter方式と比較して、以下の利点があります。
- タイムアウト制御可能:AbortSignalを使用してタイムアウト時にエラーを返し、AIは常にエラー情報を確認できます(SIGKILLによる零出力は発生しません)。
- 低遅延:サブプロセスを起動する必要がないため、遅延が少なくなります。
- 高信頼性:mcporterデーモンに依存しないため、より信頼性が高いです。
手順1:プラグインをインストールする
openclaw plugins install --link /opt/search-stack/plugin/
手順2:設定を行う
~/.openclaw/openclaw.jsonを編集し、plugins.entriesに以下の内容を追加します:
{
"plugins": {
"entries": {
"search-stack": {
"enabled": true,
"config": {
"apiUrl": "http://127.0.0.1:17080",
"apiKey": "your_proxy_api_key",
"tikhubApiKey": "your_tikhub_key"
}
}
}
}
}
apiKeyの値は、.envのPROXY_API_KEYと同じです。
tikhubApiKeyはオプションで、TikHubのAPI Keyを入力すると、ソーシャルメディアAPIを有効にできます。
注意: 設定はconfigオブジェクト内に配置する必要があり、search-stackの直下に配置しないでください。
手順3:内蔵のBrave検索を無効にする
~/.openclaw/openclaw.jsonを編集します:
{
"tools": {
"web": {
"search": {
"enabled": false
}
}
}
}
手順4:Skillファイルを作成する
mkdir -p ~/.openclaw/workspace/skills/web-search/
cp /opt/search-stack/skill-template/SKILL.md ~/.openclaw/workspace/skills/web-search/SKILL.md
Skillファイルは、AIがいつ、どのように検索ツールを使用するかを指示します(二段階法則、Cookieワークフロー、TikHubの優先順位など)。テンプレートファイルはskill-template/SKILL.mdにあり、必要に応じて編集できます。
手順5:再起動して検証する
sudo systemctl restart openclaw
openclaw plugins list
重要: AIがまだ古い方法を使用している場合は、古いセッションをアーカイブする必要があります。OpenClawのセッションコンテキストは、以前のツールモードをキャッシュするため、設定を更新しても、古いセッションは古い動作を引き続き使用します。詳細は「よくある質問 → AIがsearch-stackを使用しない」を参照してください。
方法2:MCP + mcporter(代替案)
ネイティブプラグインをインストールしたくない場合、またはOpenClaw以外の環境で使用する場合に適しています。
手順1:MCP Serverの依存関係をインストールする
MCP Serverは、Bun + @modelcontextprotocol/sdkを使用して実行されます:
curl -fsSL https://bun.sh/install | bash
bun add -g @modelcontextprotocol/sdk zod
手順2:mcporterに登録する
~/.mcporter/mcporter.jsonを編集し、search-stackを追加します:
{
"mcpServers": {
"search-stack": {
"command": "/home/your_user/.bun/bin/bun",
"args": ["run", "/opt/search-stack/proxy/mcp-server.ts"],
"keepAlive": true,
"env": {
"SEARCH_STACK_URL": "http://127.0.0.1:17080",
"SEARCH_STACK_API_KEY": "your_proxy_api_key",
"TIKHUB_API_KEY": "your_tikhub_key"
}
}
}
}
登録を検証します:
mcporter daemon restart
mcporter list
手順3:Skillを作成して再起動する
~/.openclaw/workspace/skills/web-search/SKILL.mdを作成し(mcporter execを使用する形式)、Brave検索を無効にし、OpenClawを再起動します。
注意: MCP+mcporter方式では、AIはexecツールを使用してmcporter call search-stack.*コマンドを実行します。タイムアウト時にSIGKILLが発生すると零出力になり、AIは「検索エンジンがダウンした」と判断する可能性があります。この問題を回避するためには、ネイティブプラグイン方式を推奨します。
異なるマシンでのデプロイ(OpenClawとSearch Stackが異なるサーバーで実行される場合)
Search StackがサーバーA(公開ドメインを持つ)で実行され、OpenClawがサーバーBで実行され、Search StackのAPIをリモートで呼び出す必要がある場合に適用されます。
サーバーA (search-stack) サーバーB (openclaw)
┌──────────────────────┐ ┌──────────────────────┐
│ Docker四件套 │ HTTPS │ OpenClaw │
│ search-proxy :17080 │◄────────────│ search-stackプラグイン │
│ Redis / SearXNG │ │ │
│ Browserless │ │ plugin/ディレクトリのみ必要 │
└──────────────────────┘ └──────────────────────┘
手順1:サーバーA — リバースプロキシを設定する(HTTPS)
Search Stackはデフォルトで127.0.0.1:17080のみを監視しているため、異なるマシンからアクセスする場合は、Nginxを使用してHTTPSポートを公開する必要があります。
Nginxの設定例(ドメイン名がsearch.example.comの場合):
location /search-stack/ {
proxy_pass http://127.0.0.1:17080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
セキュリティ警告: API Keyはリクエストヘッダーに平文で送信されるため、必ずHTTPSを使用してください。Certbotを使用してLet's Encryptの証明書を自動的に取得することをお勧めします。
手順2:サーバーB — プラグインコードを取得する
plugin/とskill-template/の2つのディレクトリのみが必要で、Dockerや完全なプロジェクトをインストールする必要はありません:
git clone https://github.com/pinkpills/search-stack.git /opt/search-stack
cd /opt/search-stack/plugin && npm install
mkdir -p /opt/search-stack && cd /opt/search-stack
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/pinkpills/search-stack.git .
git sparse-checkout set plugin skill-template
cd plugin && npm install
手順3:サーバーB — OpenClawにプラグインをインストールする
openclaw plugins install --link /opt/search-stack/plugin/
手順4:サーバーB — リモートAPIアドレスを設定する
~/.openclaw/openclaw.jsonを編集します:
{
"plugins": {
"entries": {
"search-stack": {
"enabled": true,
"config": {
"apiUrl": "https://search.example.com/search-stack",
"apiKey": "your_proxy_api_key",
"publicUrl": "https://search.example.com/search-stack",
"tikhubApiKey": "your_tikhub_key"
}
}
}
},
"tools": {
"web": {
"search": {
"enabled": false
}
}
}
}
設定の説明:
| フィールド |
説明 |
apiUrl |
サーバーAのSearch Stack APIアドレス(NginxでプロキシされたHTTPS URL) |
apiKey |
サーバーAの.envのPROXY_API_KEY |
publicUrl |
Cookie Catcherのリンクで使用する公開URL(ユーザーのブラウザからアクセス可能)、通常はapiUrlと同じ |
tikhubApiKey |
(オプション)TikHub API Key |
手順5:サーバーB — Skillを作成して再起動する
mkdir -p ~/.openclaw/workspace/skills/web-search/
cp /opt/search-stack/skill-template/SKILL.md ~/.openclaw/workspace/skills/web-search/SKILL.md
sudo systemctl restart openclaw
検証
OpenClawで対話テストを行います:
ユーザー: "Claude Opus 4.6の評価を検索して"
AI: web_searchを呼び出し → 結果を返す(リモートのSearch Stackから)
ユーザー: "最初のリンクを開いて全文を見る"
AI: page_fetchを呼び出し → 全文を返す(リモートのBrowserlessでレンダリング)
ツールの呼び出しが失敗した場合は、以下を確認してください:
- サーバーBが
apiUrlにアクセスできるか:curl -H "X-API-Key: KEY" https://search.example.com/search-stack/health
- プラグインが読み込まれているか:
openclaw plugins list
- 古いセッションのキャッシュ:古いセッションをアーカイブして再起動する(詳細は「よくある質問 → AIがsearch-stackを使用しない」を参照)
複数マシンでの並列実行とリソース制御
複数のマシンでSearch Stackを共有する場合、Browserless Chromeが主なボトルネックになります。各並列レンダリングセッションは約400 - 500MBのメモリを消費します。デフォルトの設定は3台のクライアントに最適化されています:
MAX_CONCURRENT_SESSIONS=10
MAX_QUEUE_LENGTH=30
CONNECTION_TIMEOUT=120000
deploy:
resources:
limits:
memory: 4g
クライアントの数に応じた調整提案:
| クライアント数 |
MAX_CONCURRENT_SESSIONS |
memory |
サーバーメモリの推奨 |
| 1 - 2台 |
5 |
2g |
4GB以上 |
| 3 - 5台 |
10 |
4g |
8GB以上 |
| 5 - 10台 |
20 |
8g |
16GB以上 |
並列数の上限を超えると、Browserlessはリクエストをキューに入れて待機させます。キューもいっぱいになると429を返し、page_fetchはエラーになりますが、検索機能(web_search)には影響しません(web_searchはChromeに依存しません)。Cookie Catcherには2セッションのハード制限があり、複数のユーザーが同時にログインする場合はキューに入る必要があります。
ツールリスト
ネイティブプラグインまたはMCP方式のいずれを使用する場合でも、提供されるツールは同じです:
| ツール |
説明 |
web_search |
多エンジン検索、enrichオプションで全文取得をサポート |
page_fetch |
ウェブページの本文を取得、Cookie注入 + Chromeレンダリング + ログイン検出をサポート |
cookies_list |
設定されているCookieのドメインを一覧表示 |
cookies_update |
ドメインのCookieを追加/更新(raw文字列の貼り付けをサポート) |
cookies_delete |
ドメインのCookieを削除 |
cookie_catcher_link |
リモートブラウザのログインリンクを生成(Cookie Catcher) |
tikhub_call |
TikHubのソーシャルメディアAPIを呼び出す(Keyの設定が必要、必要に応じて使用) |
注意: クローリングツールの名前はpage_fetchであり、web_fetchではありません。これは、OpenClawに内蔵されているweb_fetchツールとの名前の衝突を避けるためです。内蔵のweb_fetchはCookie注入とChromeレンダリングをサポートしていないため、同じ名前を使用するとAIが誤ったツールを呼び出す可能性があります。
Cookieワークフローの実践
Cookieを取得する方法は2つあります:
方法1:手動でコピーして貼り付ける(デスクトップ端末用)
ユーザー: "このウェブページ https://zhuanlan.zhihu.com/p/xxxx を見てくれ"
AI: page_fetchを呼び出し → 本文が不完全(タイトル/要約のみ)
AI: "このサイトの反クローリングが厳しく、本文が完全に取得できませんでした。
完全な内容が必要な場合は、このサイトのCookieを提供してください:
1. ブラウザでこのURLを開いてログインします
2. F12を押してNetworkタブを開き、ページを更新します
3. リクエストヘッダーのCookie: 行を見つけます
4. 全体の値をコピーして私に送ってください"
ユーザー: "z_c0=xxx; _xsrf=yyy; d_c0=zzz ..."
AI: 自動的にドメインzhihu.comを抽出 → cookies_update → 保存成功
AI: bypass_cache:trueで再取得 → 完全な記事内容を取得
方法2:Cookie Catcherを使用したリモートログイン(複雑なログインフロー用)
1. ブラウザで開く:http://YOUR_HOST:17080/cookie-catcher?key=API_KEY&url=https://threads.net
2. リモートChromeの画面でログインを完了します(マウス/キーボード/タッチ操作をサポート)
3. "Save Cookies"をクリック → cookies.jsonに自動的に保存
4. 以降の/fetchリクエストで自動的にCookieが注入されます
OAuthのリダイレクト、QRコードスキャン、携帯電話の認証コードなどの複雑なログインシナリオに適しています。
よくある質問とトラブルシューティング
デプロイ関連
Q: SearXNGの検索が403 / 502 / 空の結果を返す
これは最も一般的な問題です。SearXNGはデフォルトでJSON形式の検索APIを無効にしています。search-stackが?format=jsonで呼び出すと、403 Forbiddenが返されます。
解決策: searxng/settings.ymlに以下の内容が含まれていることを確認してください:
search:
formats:
- html
- json
変更後、コンテナを再起動します:
docker compose -f search-stack.yml restart searxng
なぜ見つけにくいのか? search-stackのプロキシは、SearXNGの403を「利用不可」と見なし、静かにスキップしてTavily/Serperにフォールバックします。あなたはSearXNGが正常に動作していると思っているかもしれませんが、実際には一度も使用されていない可能性があります。provider: "searxng"を指定して一度テストすると、問題がわかります。
Q: SearXNGの初回起動時に権限エラーが発生する
SearXNGのコンテナはUID 977で実行されます。マウントディレクトリの権限が正しくないと、起動に失敗します:
sudo chown -R 977:977 /opt/search-stack/searxng/
docker compose -f search-stack.yml restart searxng
Q: Browserlessがタイムアウトまたはクラッシュする
Browserlessはデフォルトで最大5つの並列Chromeセッションをサポートしています。頻繁にタイムアウトする場合は、メモリ(少なくとも2GB)を確認してください:
docker stats browserless
search-stack.ymlのMAX_CONCURRENT_SESSIONSを調整することができます。
Q: Redisの接続に失敗する
REDIS_PASSWORDが.envに設定されており、空でないことを確認してください:
docker exec search-redis redis-cli -a YOUR_REDIS_PASSWORD ping
Q: search-proxyが起動時にredis.exceptions.ConnectionErrorエラーを返す
search-proxyはRedisとSearXNGが先に起動する必要があります。docker composeのdepends_on + healthcheckで通常は問題なく動作しますが、Redisの起動が遅い場合は:
docker compose -f search-stack.yml restart search-proxy
MCP Server関連
Q: mcporter listでsearch-stackが不健康と表示される
以下の手順で問題を特定してください:
- Dockerコンテナが実行中であることを確認:
docker compose -f search-stack.yml ps
- APIにアクセスできることを確認:
curl -H "X-API-Key: KEY" http://127.0.0.1:17080/health
- Bunのパスが正しいことを確認:
which bun
- 直接実行してエラーを確認:
SEARCH_STACK_URL=http://127.0.0.1:17080 SEARCH_STACK_API_KEY=your_key bun run /opt/search-stack/proxy/mcp-server.ts
Q: z.record() / schema._zodエラーが発生する
MCP SDK v1.26.0 + Zod v4の既知の問題です。z.record()をtools/listでシリアライズすると、Cannot read properties of undefined (reading '_zod')エラーが発生します。
解決策(このプロジェクトでは対応済み):
- パラメータを
z.string()で置き換え、ハンドラーでJSON.parse()を使用します。
- オブジェクトと文字列の両方を受け取る必要があるパラメータには
z.any()を使用します。
Q: mcporterでJSONパラメータを渡すと"expected string, received object"エラーが発生する
mcporterはJSON文字列を自動的にオブジェクトに解析してMCPツールに渡します。スキーマがz.string()に定義されている場合、検証に失敗します。
解決策(このプロジェクトでは対応済み): z.any()を使用し、ハンドラーで両方のタイプを処理します:
const rawArgs = params.arguments as unknown;
if (typeof rawArgs === "object" && rawArgs !== null) {
args = rawArgs as Record<string, unknown>;
} else {
args = JSON.parse((rawArgs as string) || "{}");
}
OpenClaw統合関連
Q: AIがsearch-stackを使用せず、内蔵のBrave検索を引き続き使用する
以下の3点を確認してください:
- Braveが無効になっていることを確認:
~/.openclaw/openclaw.jsonの"search": { "enabled": false }
- SKILL.mdが存在することを確認:
ls ~/.openclaw/workspace/skills/web-search/SKILL.md
- (最も重要)古いセッションをアーカイブする: OpenClawのセッションコンテキスト(数十万トークンになることもあります)は、以前のツール呼び出しモードをキャッシュします。SKILL.mdを更新しても、古いセッションは古い動作を引き続き使用します。必ずアーカイブしてください:
ls -lt ~/.openclaw/agents/main/sessions/*.jsonl | head -3
mv ~/.openclaw/agents/main/sessions/SESSION_ID.jsonl \
~/.openclaw/agents/main/sessions/SESSION_ID.jsonl.archived
sudo systemctl restart openclaw
新しいセッションを開始すると、AIはSKILL.mdを再読み込みし、mcporter callコマンドを使用します。
Q: AIが一部の内容を取得したが、ユーザーにCookieを提供するように誘導しない
SKILL.mdには、すべてのCookie誘導のトリガー条件を明確に記載する必要があります:
** LOGIN REQUIRED **が返される
- 本文内容が不完全(タイトル/要約のみ、本文が切り捨てられているまたは空)
- 反クローリングのメッセージが表示される(「ログインしてください」、「認証が必要です」など)
- 内容が期待と大きく異なる(記事ページでサイドバーのみが取得される)
同時に、AIに**「しないこと」**を明確に伝える必要があります。記事内容の説明でクローリングの失敗を代用しないでください。誘導をスキップしないでください。LOGIN REQUIREDのみを条件として記載すると、AIは一部の内容を取得したときに誘導をトリガーしません。
Q: Threads/InstagramなどのSPAサイトのクローリングが失敗し、"JS SPA requiring Chrome"と表示される
これは通常、Chromeが不足している問題ではありません(Browserlessはデフォルトで実行されています)。実際の原因はCookieが期限切れになっていることです:
- BrowserlessがChromeでレンダリングし、Cookieを注入しましたが、セッションが無効になっています。
- React SPAが実際の内容をレンダリングせず、ログインページを返します。
- ログインページのテキストが短いため、AIはJSレンダリングが失敗したと誤って判断します。
解決策: ブラウザからCookieを再エクスポートし(ログインしていることを確認)、cookies_updateで更新した後、bypass_cache: trueで再試行してください。
detect_needs_loginは多面的な検出をサポートしています:HTTPステータスコード(401/403)、テキストキーワード(中英日)、ページタイトル、HTMLのパスワード入力欄/CAPTCHA/meta refresh、SPAのログイン画面(Threads/Instagram/Facebook)などを検出し、明確なneeds_login: trueのヒントを返します。
また、Cookie Catcherを使用してリモートログインすることもできます。/cookie-catcher?key=API_KEY&url=TARGET_URLを開き、リモートChromeでログインを完了した後、Cookieを一括保存してください。
Q: AIがexec + curlでBraveを呼び出し、mcporter callを使用しない
OpenClawのAIはexecツールを使用してシェルコマンドを実行し、MCPを呼び出します。SKILL.mdには具体的なコマンド形式を使用する必要があります:
mcporter call search-stack.web_search query="キーワード" --output json
抽象的なsearch-stack.web_search(query="キーワード")と書くことはできません。AIは自動的にシェルコマンドに変換しません。
Q: SKILL.mdを更新してもAIの動作が変わらない
これは最もよく見られる問題の1つです。原因と解決策は以下の通りです:
背景となるメカニズム: OpenClawにはSkills Watcher(デフォルトで有効、skills.load.watch: true)があり、SKILL.mdファイルの変更を監視し、バージョン番号を更新します。ただし、これはskillのリスト(どのskillが利用可能か)のみを更新し、AIがSKILL.mdの内容を強制的に再読み込みするわけではありません。
SKILL.mdを変更しても効果がない理由:
- AIは毎回SKILL.mdを読み取るわけではなく、セッションの最初のラウンドまたは必要と判断したときにのみ読み取ります。
- 読み取った内容はセッションコンテキストにキャッシュされます(数十万トークンになることもあります)。
- 最も重要なのは、AIはほとんどの場合、ツールのdescriptionフィールド(登録時に固定された短いテキスト)のみを見ており、SKILL.mdを見ていません。
正しい解決方法(推奨順):
- ツールのdescriptionを変更する(最も効果的)— ツールのdescriptionは常にAIに表示されるため、
plugin/index.tsのdescriptionフィールドを変更し、OpenClawを再起動すると、新しいセッションですぐに反映されます。重要な動作制約はdescriptionに記載する必要があり、SKILL.mdだけに記載するのでは不十分です。
- 古いセッションをアーカイブする — 新しいセッションですべてのコンテキストを再読み込みさせます:
for f in ~/.openclaw/agents/main/sessions/*.jsonl; do
mv "$f" "$f.archived"
done
echo '{"sessions":[]}' > ~/.openclaw/agents/main/sessions/sessions.json
sudo systemctl restart openclaw
- Skills Watcherが有効になるのを待つ — SKILL.mdの補足説明を変更した場合(核心的な動作に関係ない場合)、AIが次の対話でSKILL.mdを再読み込みするのを待つことができます。
ベストプラクティス: 核心的な動作制約(例:「まずAツールを使用してからBツールを使用する」)はツールのdescriptionに記載し、詳細な手順とサンプルはSKILL.mdに記載します。これにより、AIがSKILL.mdを読まなくても、ツールのdescriptionがバックアップとして機能します。
Q: プラグインコード(index.ts)を更新した後に何をする必要があるか
プラグインはOpenClawのプロセス内で実行されるため、コードを変更した後は以下の操作が必要です:
sudo systemctl restart openclaw
同時にSKILL.mdを変更した場合は、上記の手順で古いセッションをアーカイブすることをお勧めします。
🔧 技術詳細
APIドキュメント
すべてのリクエストにはX-API-Keyヘッダーを付ける必要があります。
GET /health
ヘルスチェックです。
{
"ok": true,
"redis": true,
"order": ["tavily", "serper", "searxng"],
"browserless_configured": true,
"dedupe": { "enabled": true, "max_per_host": 2 }
}
POST /search
Web検索です。
| パラメータ |
タイプ |
デフォルト値 |
説明 |
query |
文字列 |
必須 |
検索キーワード |
count |
整数 |
5 |
返される結果数(1 - 10) |
provider |
文字列 |
自動 |
強制的に指定するエンジン:tavily / serper / searxng |
enrich |
ブール値 |
false |
各結果のウェブページの全文を取得する |
max_chars |
整数 |
8000 |
enrich時の各ページの最大文字数 |
render |
ブール値 |
true |
ヘッドレスブラウザでレンダリングする |
concurrency |
整数 |
3 |
enrich時の並列取得数 |
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: KEY" -H "Content-Type: application/json" \
-d '{"query": "Docker best practices", "count": 5}'
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: KEY" -H "Content-Type: application/json" \
-d '{"query": "Python asyncio", "count": 3, "enrich": true}'
curl -s -X POST http://127.0.0.1:17080/search \
-H "X-API-Key: KEY" -H "Content-Type: application/json" \
-d '{"query": "AI news", "count": 5, "provider": "searxng"}'
返却例:
{
"query": "Docker best practices",
"count": 5,
"cached": false,
"provider": "tavily",
"results": [
{
"title": "Docker Best Practices",
"url": "https://example.com/docker",
"snippet": "Top 10 Docker best practices for production...",
"source": "tavily"
}
]
}
POST /fetch
ウェブページの本文を取得します。
| パラメータ | タイプ | デフォルト値 | 説明 |