mcpField
Q
quorum

quorum

リモート
github.com/Godzilla-labビジネス・マーケティング06 週間前に更新
リポジトリ

quorum について

Quorumは公開アーカイブから市場や顧客の証拠を調べるツールで、製品・ブランド調査やデューデリジェンス向けです。証拠を保持するコーパスと独立記録による裏付け、取得可能なレシートIDにより、主張を個別に検証できます。

主な機能

  1. 01公開アーカイブからの調査
  2. 02保存済み証拠の取得可能なID
  3. 03独立記録による発見の裏付け
  4. 04継続的な照会に使えるコーパス
  5. 05オフライン調査モード
  6. 06CLI、SDK、HTTP API、MCPサーバー

活用シーン

  1. 01顧客の不満の調査
  2. 02個別検索による競合比較
  3. 03広告掲載期間に基づく広告形式の評価
  4. 04エージェントの調査結果の監査

MCP 対応

MCPサーバーはstdio経由で5つのツールを公開し、そのうち4つは `/mcp` からリモート利用できます。同じ調査パイプラインを使い、保存された証拠レコードで主張を検証できます。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://quorum-api-j15n.onrender.com/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

5
パラメーター型必須説明
termsarrayいいえ調べる内容:"sizing"、"durability" のような単一の概念。省略可能です。
subjectstringはい製品名、トピック、または製品 URL。

詳細

Quorum

根拠が明確な市場の声。

調べたいテーマを入力すると、市場で実際に何が語られているかが返ってきます。すべての主張にはレシート ID が付き、各レシート ID は取得可能な、実際に保存された記録に解決されます。捏造された引用は構造上不可能です。それを README の主張で済ませず、テストで証明しています。

CI License Node npm Tests Runtime dependencies OpenAPI

ヒント

興味深いのは、根拠を見つけられることではありません。根拠を確認できることです。回答に含まれるすべての ID は GET /v1/evidence/{id} で解決でき、POST /v1/verify を使えば、自分たちのコーパスでも、ほかの人のコーパスでも、一連の主張を再照合できます。解決できない ID は、実在しなかった主張です。

{
  "finding": "Sizing runs small, and buyers only discover it after delivery",
  "corroboration": { "records": 31, "channels": 5 },
  "receipts": ["rc_8f2a1", "rc_44b0e", "rc_91c37"],
  "confidence": "finding"
}

GET /v1/evidence/rc_8f2a1 を呼び出すと、実際のコメント、そのスコア、投稿先、投稿日時、パーマリンクが返されます。あなたのお客様のお客様も、主張をクリックして、それを述べた本人の発言を読むことができます。

ホスト版 API は https://quorum-api-j15n.onrender.com で利用できます。無料プランのインスタンスは、アイドル状態が続くとスリープするため、しばらく利用されていなかった後の最初のリクエストでは、起動まで数秒かかります。

ホスト版インスタンスでは、API の初期段階のため認証キーが必要です。 API ルートではキーの申請方法を確認でき、接続先のインスタンスがどのモードで動作しているかも常に表示されます。ヘルスエンドポイントにはキーが不要なので、現在稼働中か確認できます。

curl https://quorum-api-j15n.onrender.com/v1/healthz

セルフホストでは、オープンモードまたはキーごとの認証、クォータ、テナンシー、Webhook シークレットをサポートしており、すべて GET /v1/usage からセルフサービスで利用できます。

メモ

Windows では、Git Bash に同梱されている curl が Schannel TLS スタックを使用します。証明書失効サーバーに接続できない場合(企業プロキシや VPN の背後でよく発生します)、HTTPS 接続が拒否されます(CRYPT_E_REVOCATION_OFFLINE)。これはネットワーク側の問題であり、この API の問題ではありません。curl コマンドに --ssl-revoke-best-effort を追加するか、代わりに WSL または PowerShell から呼び出してください。

目次

機能

公開アーカイブから顧客の声を収集し、実際の掲載期間の長さで競合広告を順位付けし、動画と静止画のどちらが優れているかを意見ではなく算術で判定します。読み込んだデータはすべて保持するため、同じ市場について2回目に質問するときのコストはほぼゼロになり、過去についてアーカイブに質問できます。

主な機能

  • リンクではなく、証拠を提示。 保持されたコーパス内の安定した ID で、元の情報源が削除された後も含め、いつでも同じレコードを参照できます。
  • 算術による裏付け。 主張を調査結果として出力するには、少なくとも3件の独立したレコードが必要で、その件数も併せて示されます。
  • 1つのインターフェースの背後に10の情報源。 ボランティア運営の Reddit アーカイブや GitHub の課題検索から、4つの政府安全規制機関までを利用でき、未設定でも失敗せず、機能を縮小して動作します。
  • 蓄積されていくコーパス。 コールド検索では596秒、スロットリングされたリクエストは約500件でした。同じカテゴリなら、ウォーム状態では0.5秒で回答できます。
  • オフラインモードではネットワークに接続せず、費用もかかりません。
  • 15の HTTP 操作、型付き SDK、MCP サーバー、CLIが、すべて同じパイプラインを利用します。
出力例

2026-08-24 に実行したコールド状態の npx quorum "espresso machine" の実際の出力から抜粋:227秒で401件のレコードを取得、費用は $0.00 です。

EVIDENCE  a claim needs 3 independent receipts to be stated as a finding
          the percentage is share of all 401 records held for this category

  quality     88 receipts /  64 channels  21.9%  A0 B3 C85 D0   [finding]
      "I often hear the priority list being 1. Quality of beans 2. Quality
       of the grinder 3. Quality of the espresso machine..."
         hackernews Why a spritz of water before grinding coffee yields
         better results   rc_4d6d444821b0044f

ATTESTED  7 records from 6 named parties   [finding]
  A named party stated this to a regulator, on the record, with consequences
  for lying. Two of these are a finding on their own.

      "CPSC And Krups Announce Action On Espresso Makers..."
         cpsc Krups   rc_a4883bfb1b04d1c1

RECEIPTS  195 cited, 195 resolved back to real records

COST      $0.0000 in 227.3s

この出力にあるすべての ID は、その実行で書き込まれたコーパスから取得できます。ID を解決できない場合、実行は非ゼロの終了コードで終了し、該当する ID を示します。

ユースケース

  • 製品・ブランド調査。 購入者が実際に何に不満を感じているかを、信頼するしかない要約ではなく、すべての主張の根拠となるコメントとともに把握できます。
  • 競合分析。 競合ごとに完全な検索を1回実行し、件数ではなくシェアを比較するため、同時出現をシグナルと誤認しません。
  • 広告クリエイティブの判断。 プラットフォーム独自のラベルではなく、日付付きの観測データをもとに、実際のキャンペーン掲載期間の長さで動画と静止画を判定します。
  • 監査可能なエージェント向けツール。 呼び出し元のモデルがすべての主張を独立して検証できる MCP サーバーを提供します。これは、リサーチツールが偽装できない唯一の要素です。
  • デューデリジェンスとモニタリング。 指名された当事者が規制機関に提出した証明付きレコードと、同じ対象に関する前回のレポートとの差分を確認できます。

ステータス

エンジン動作しています。オフラインかつキー不要のテストが1,330件
CLI動作しています。すべてのフラグに対応
MCP サーバー動作しています。stdio 経由の5つのツールのうち、4つは /mcp でもリモート利用できます
JavaScript SDK動作しています。11のメソッド
Python SDK公開済み:pip install quorum-api。標準ライブラリのみで14のメソッドを提供
Webhook動作しています。Standard Webhooks 形式の署名に対応し、永続化され、約75時間にわたって再試行されます
ホステッド APIPostgreSQL 上で デプロイ済み・稼働中:https://quorum-api-j15n.onrender.com。稼働中のインスタンスで検証済み
npm公開済み:npx quorum-api "running shoes"。5つのパッケージで、外部依存関係はありません
コーパスまだ初期段階です。2026-08-25 時点で、消費者向けおよび開発者向けの22カテゴリをホステッドインスタンスでウォームアップ済み。それ以外も、コールド実行すれば利用できます

メモ

現時点では API の仕様に依存しないでください。 実装に利用できる程度には安定していますが、まだ確定していません。

後から驚かないよう、あらかじめ明記します。リクエストクォータはメモリ内に保持されるため、1分あたりの検索カウンターは再起動時にリセットされ、2つのインスタンス間で共有されるカウンターもありません。ただし、1時間あたりのレポートカウンターだけは例外です。起動時に永続化済みのレポートから再設定されるため、再起動しても枠は補充されません。また、evals/ にあるのは最初の層だけで、CI でスコアを付けるラベル付き関連性セットです。プロジェクトのドキュメントに記載されている、有料のスコア付きレポート評価はまだ存在しません。

要件

Node 22.18 以降。エンジンのランタイム依存関係は、ホステッドサーバーでのみ使用する pg の1つだけです。フレームワークも、ORM も、HTTP クライアントも、テストランナーもありません。CLI にもコーパスにも、依存関係は一切ありません。これは自慢ではなく、意図的な制約です。公開インターネットから信頼できないテキストを取得するツールでは、依存関係が1つ増えるたびに、こちらに代わってネットワークにアクセスできるものが1つ増えるからです。pg を採用したのは、データベースの再起動後も機能するコネクションプールだからです。手作業で実装する価値のあるものではありません。

どの機能にもキーは不要です。公開アーカイブ経由の Reddit、Hacker News、App Store、CFPB の消費者苦情データベース、4つの政府安全アーカイブは、すべて無料でキーも不要です。キーがあればソースが追加されるだけです(無料の API キーによる YouTube コメント、Amazon レビュー、Apify 経由の Meta 広告ライブラリ)。キーがない場合は実行が失敗するのではなく、取得できる情報が減ります。

環境変数。すべて任意です。リポジトリのルートにある Git の追跡対象外の .env に設定してください。
QUORUM_YOUTUBE_API_KEY無料の YouTube Data API v3 キーです。レビュー動画のコメントが声のティアに加わります。未設定の場合、ソースは未設定であることを報告し、それなしで実行を続けます。費用はかかりません。1日あたりの無料枠で数十回実行できます。
QUORUM_CONTACT_EMAILキーではありません。SEC は、呼び出し元を示す User-Agent を必須としており、未設定の場合は 403 を返します。そのため、これを設定するまで sec-edgar は未設定として報告されます。担当者が変わっても使い続けられるよう、役割用のメールアドレスを設定してください。
OPENROUTER_API_KEY主題の拡張、--synthesise、--read-images に使用します。3つともデフォルトでは無効で、件数にモデルを使うことはないため、このキーがなくても決定論的なレポートは同一です。
APIFY_TOKEN従量課金の2つの処理、つまり Meta 広告ライブラリと Amazon レビューに使用します。どちらも同じ Apify アカウント経由です。呼び出しごとに利用料金が発生し、レポートの請求に加算されます。未設定の場合は、広告に対する --no-ads と同様に、両方の処理がスキップされます。
QUORUM_CORPUSSQLite コーパスのパスです。サーバーと MCP サーバーが読み込みます。デフォルトは ./quorum.db です。CLI はこれを読み込みません。代わりに --corpus を指定してください。
QUORUM_PG_URLホステッドコーパス用の Postgres 接続 URL です。プロバイダーの URI 全体を貼り付けてください。分割せずに解析するため、sslmode が適用され、@ を含むパスワードもそのまま使えます。
QUORUM_PG_CAプロバイダー CA のパスです。設定することをおすすめします。sslmode=require は暗号化を意味しますが、検証は行いません。
QUORUM_PG_CA_PEM同じ CA をインライン PEM で指定します。シークレットをファイルではなく値として提供するホスト向けです。
QUORUM_API_KEYSサーバー用の、カンマ区切りのベアラーキーです。未設定の場合、インスタンスは誰でもアクセス可能になり、サーバーの起動時にその旨が明示されます。
QUORUM_WEBHOOK_SECRETWebhook 配信用の署名シークレットです。24文字以上にしてください。未設定の場合、Webhook は受け付けられますが配信されません。このこともサーバーの起動時に明示されます。
QUORUM_CONCURRENCY同時に実行するレポートの数です。デフォルトは 2 です。スループットを調整する値ではありません。同時実行するレポートごとに、同じボランティア運営のアーカイブへ同時に負荷がかかります。
PORTデフォルトは 8787 です。通常はホスト側で設定します。
QUORUM_REPORTS_PER_MINUTE1つのキーが1分間に開始できるレポート数です。デフォルトは 20 です。
QUORUM_LOOKUPS_PER_MINUTEキーごとに1分間に実行できる、その他すべての処理数です。デフォルトは 600、つまり1秒あたり10回です。
QUORUM_MAX_CAP_USD1つのレポートに対する上限額です。呼び出し元が指定する capUsd は、この上限を引き下げることしかできません。デフォルトは 0 です。
QUORUM_SPEND_PER_KEY_USD1つのキーが従量課金ソースに1日あたり使用できる金額です。デフォルトは 0 です。
QUORUM_SPEND_TOTAL_USDすべてのキーを合わせて1日あたり使用できる金額です。デフォルトは 0 です。
## クイックスタート

必要なのは Node 22.18 以降だけです。

npx quorum-api "running shoes"

インストールはこれだけです。npm から約 240 KB のパッケージが 5 つダウンロードされます。外部依存関係はゼロで、最初のレポートがすぐに開始されます。

Quorum 自体を開発する場合は、クローンする方法があります。

git clone https://github.com/Godzilla-lab/Quorum-API && cd Quorum-API
npm install
npm run build
npm test          # 1,330 tests, offline, no keys

それでは、何かを調査してみましょう。入力するのは URL ではなく対象です。プレーンテキストでも、製品の URL でも使えます。ストアに拒否される製品 URL でも使えます。これは重要な点です。実測時には、実際のストアページ 4 件すべてでサーバー側のフェッチがブロックされました。

npx quorum "running shoes" --offline    # corpus only, no network, no cost
npx quorum "running shoes" --offline --corpus ./mine.db   # somewhere else

メモ

上記の操作にキーは不要で、--offline はネットワークに一切接続しません。 まずはキーなしで始め、レポートでアクセスできなかった対象が示されたときに追加してください。

使用方法

CLI

所有するコーパスを使って、マシン上でエンジン全体を実行します。

# A subject in plain words
npx quorum "running shoes" --communities running,runningshoegeeks

# A product url, including one a store refuses to serve to a server
npx quorum "https://allbirds.com/products/mens-wool-runners"

# Corpus only. No network, no cost, and it says so
npx quorum "running shoes" --offline

# Machine readable. Progress goes to stderr, so this pipes cleanly
npx quorum "running shoes" --json | jq '.findings[].term'

# Versus what: one full retrieval per rival
npx quorum "wool runner" --compare "brooks ghost"

# Skip the one metered source explicitly
npx quorum "running shoes" --no-ads

--compare は競合相手ごとに完全な検索を実行します。これは必須です。あるコーパス内で競合相手に言及しているレコード数を数えても、測定できるのは併起だけです。「Brooks より小さく動く」というコメントは競合相手と不満の両方に言及していますが、その不満がどちらにも帰属するとは限りません。そのため、比較対象ごとにそれぞれ独自のコーパスを用い、件数ではなく割合を比較します。また、そのコーパスの規模におけるサンプリング誤差の範囲内の差は、結果としてではなく、差なしと報告されます。

出力される主張はすべて、少なくとも 3 件の独立した証拠に裏付けられた所見か、追跡調査できるように表示されるものの、市場の傾向として述べられることはない弱いシグナルのどちらかです。何かを出力する前に、引用された証拠をすべてコーパスから再取得し、ひとつでも解決できない場合は、実行を非ゼロで終了します。

フラグの一覧は quorum --help で確認できます。上記の各コマンドは、何もクローンせずに npx quorum-api 経由で実行することも、チェックアウト内の quorum バイナリ経由で実行することもできます。どちらも同じバイナリです。

HTTP API

レポートを開始し、ポーリングして、レポートが引用する ID を解決します。

# Start one. Returns 202 immediately with an id
curl -sX POST http://localhost:8787/v1/reports \
  -H "authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{"subject":"wool runner","terms":["sizing","durability"]}'
{ "id": "rep_3568995af829290a", "status": "running", "coalesced": false,
  "category": "wool runner", "queuePosition": 1, "estimatedSeconds": 60 }
# Poll it. Honours Retry-After while running, and ETags so polling costs nothing
curl -s http://localhost:8787/v1/reports/rep_3568995af829290a \
  -H "authorization: Bearer $KEY" -H 'if-none-match: "rep_3568995af829290a-7"'

# Watch it instead of polling
curl -N http://localhost:8787/v1/reports/rep_3568995af829290a/stream \
  -H "authorization: Bearer $KEY"

# Resolve one receipt. This is the endpoint everything else rests on
curl -s http://localhost:8787/v1/evidence/rc_a8697befab91e873 \
  -H "authorization: Bearer $KEY"

# Resolve up to 200 at once, because a report cites more than one
curl -sX POST http://localhost:8787/v1/evidence/batch \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"receiptIds":["rc_8f2a1","rc_44b0e","rc_91c37"]}'

# Is this category warm? Free, and worth asking before paying for a cold report
curl -s http://localhost:8787/v1/categories/running%20shoes \
  -H "authorization: Bearer $KEY"

# Check somebody's claims, ours or a competitor's
curl -sX POST http://localhost:8787/v1/verify \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"claims":[{"text":"sizing runs small","receipts":["rc_8f2a1"]}]}'
JavaScript SDK

型付きで、依存関係ゼロです。spec/openapi.yaml に基づいて実装されています。各メソッドは、そのファイル内の 1 つの operationId に対応します。

import { createClient } from '@quorum/sdk-js';

const quorum = createClient({ baseUrl: 'https://quorum-api-j15n.onrender.com', apiKey: process.env.QUORUM_KEY });

const found = await quorum.searchEvidence({ query: 'sizing', category: 'running shoes' });
if (!found.ok) throw new Error(found.error.message);

for (const record of found.data.records) console.log(record.receiptId, record.text);

エラーはスローされず、値として返されます。ベンダーがダウンしている可能性がある場合、エンジンが従うのと同じルールです。呼び出し元には、サーバーの type、requestId、および存在する場合は retryAfterSeconds を含む { ok: false, error } が返されます。429、503、あるいはまだ完了していないレポートは、いずれも通常の状態であり、例外的な事態ではないためです。

サーバーのリクエスト間隔を尊重します。 waitForReport は、サーバーから Retry-After が送られた場合はそれに従ってポーリングし、503 を失敗ではなく負荷を抑制する仕組みとして扱います。最初の拒否であきらめるクライアントは、混雑しているサービスを故障していると報告してしまいます。まさにそれを防ぐために、負荷抑制の仕組みがあります。

const started = await quorum.createReport({ subject: 'wool runner', offline: true });
if (started.ok) {
  const report = await quorum.waitForReport(started.data.id, {
    onPoll: (r) => console.log(r.status),
  });
}

streamReport は同じ実行結果をサーバー送信イベントの非同期イテラブルとして返します。Node には Authorization ヘッダーを受け付ける EventSource がないため、イベントは手動で解析します。

Python SDK

同じ仕様に基づいて同じ 2 つの方針で実装された Python 用の同一クライアントが、packages/sdk-py にあります。エラーはスローせず値として返し、wait_for_report は Retry-After を尊重するため、混雑しているサービスを故障していると報告しません。標準ライブラリのみを使用し、依存関係はありません。また、トランスポートを差し替えられるため、ネットワーク接続なしでテストを実行できます。

pip install quorum-api
from quorum_api import QuorumClient

quorum = QuorumClient("https://quorum-api-j15n.onrender.com", api_key="qk_...")
started = quorum.create_report("wool runner", offline=True)
if started.ok:
    report = quorum.wait_for_report(started.data["id"])
MCP サーバー

依存関係なしで、2 種類のトランスポートを介して JSON-RPC として呼び出せる 5 つのツールです。

リモート:URL を必要とするコネクタフォーム向けです。ホストされたインスタンスは、次の Streamable HTTP で応答します。

https://quorum-api-j15n.onrender.com/mcp

キーなしで利用できます。読み取りツールは費用がかからず、公開データを提供します。また、匿名の呼び出し元は共通のレート上限を使用するため、キーを持つ顧客の利用を圧迫しません。調査対象についてまだデータがない場合、research_product は収集を開始してすぐに戻ります。データは到着次第検索できるため、実行中も search_evidence と category_warmth が応答し、その旨を伝えます。公開窓口では、一度に実行できる収集は 1 件で、1 時間あたり最大 3 件です。キーがあれば通常のレポート利用枠が適用されます。広告と統合分析はここからは実行されません。

ローカル:コマンドを起動するクライアント向けに、stdio を介して利用できます。

QUORUM_CORPUS=./quorum.db node packages/mcp/src/bin.ts
{
  "mcpServers": {
    "quorum": {
      "command": "node",
      "args": ["packages/mcp/src/bin.ts"],
      "env": { "QUORUM_CORPUS": "./quorum.db" }
    }
  }
}
ツール回答する内容
search_evidence独立した記録がいくつあるか、いくつのチャネルにまたがるか、しきい値を超えているか、特に目立つ引用をいくつか
get_receiptID を実際の記録に解決します。エージェントが私たちを検証する方法です
category_warmth質問に即座かつ無料で答えられるのか、数分かかり費用もかかるのか
compare_formats実際の広告の掲載期間に基づく、動画と静止画の比較
research_productリモート:収集を開始してすぐに戻ります。ローカル:完全なレポートを返します。デフォルトでは無効です(QUORUM_MCP_RESEARCH=1 の場合を除く)

後からツールのスキーマを変更するのは大変なため、知っておくべき設計上の判断が 3 つあります。

エンドポイントごとではなく、ツールは 5 つ。 ツール定義はターンごとに 100~500 トークンを消費するため、10 個のルートをそのまま反映するサーバーでは、モデルが何かを行う前にコンテキストウィンドウを使い切ってしまいます。

行をそのまま並べず、集計して返します。 search_evidence は件数と少数の引用を返します。コーパスをコンテキストウィンドウに流し込む調査ツールでは、節約するはずだった予算を使い切ってしまいます。

JSON ではなく Markdown。同じ内容でもトークン数はおよそ 60%です。

research_product はデフォルトで無効です。レポートの作成には、ボランティアのアーカイブからレート制限付きでデータを取得するため数分かかり、エージェントが誤って開始できるべきではないからです。他の 4 つはすでに保持されているデータから回答し、ネットワークには接続しません。

この設計によって、他のどの調査サーバーにも提供できない価値が生まれます。呼び出し元のエージェントが、すべての主張を独立して検証できます。 search_evidence は ID を引用し、get_receipt はその ID を解決します。解決できない ID は、実在しない主張です。その場合、応答の中でも特に目立つ行として示されます。

Webhook

レポートの生成には数分かかるため、ポーリングする代わりに POST /v1/reports からコールバックを受け取れます。署名シークレットを設定すると、配信が有効になります。

QUORUM_WEBHOOK_SECRET=$(openssl rand -base64 32) npm start
curl -sX POST http://localhost:8787/v1/reports \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"subject":"wool runner","webhookUrl":"https://your-app.example/hooks/quorum"}'

レポートが終端状態に達すると、GET /v1/reports/{id} とバイト単位で同一の本文に加え、 Standard Webhooks の仕様に従った3つのヘッダーを含む POST を受け取ります。独自に考案したコードではなく、既製のライブラリを使って検証できます。

ヘッダー値
webhook-idレポート ID です。再試行しても変わらないため、この値で重複を排除してください
webhook-timestamp今回の試行時点の Unix 秒です
webhook-signaturev1,{base64 hmac-sha256} を空白区切りで並べたリストです

署名対象の内容は {webhook-id}.{webhook-timestamp}.{body} で、キーはシークレットの Base64 エンコードされた本文であり、whsec_ のラベルではありません。シークレットは GET /v1/usage の webhookSecret フィールドです。キーごとに割り当てられ、一定で、保存ではなく導出されるため、運用者がインスタンスのシークレットをローテーションしたときにのみローテーションされ、予告なく変更されることはありません。

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, headers, body, toleranceSeconds = 300) {
  const id = headers['webhook-id'];
  const timestamp = Number(headers['webhook-timestamp']);
  /* A signature alone is replayable forever, so bound the age first. */
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();

  /* The header is a list so a secret can be rotated. Any one match is enough. */
  return String(headers['webhook-signature']).split(' ').some((part) => {
    const [version, value] = part.split(',');
    if (version !== 'v1') return false;
    const offered = Buffer.from(value ?? '', 'base64');
    return offered.length === expected.length && timingSafeEqual(offered, expected);
  });
}

配信は少なくとも1回行われ、約75時間にわたって再試行されます。 2xx は成功です。 5xx、429、タイムアウト、またはネットワーク障害の場合は、Standard Webhooks のスケジュール(即時、5秒後、5分後、30分後、2時間後、5時間後、10時間後、14時間後、20時間後、24時間後。ジッターあり)に従って再試行されます。それ以外の4xxは再試行されません。本文を拒否する受信側は、再度送信しても拒否するためです。配信はデータベースに保持されるため、再起動しても失われません。

重要

URL は検証されます。検証するタイミングによって、返される結果が異なります。 https である必要があり、認証情報を含めることはできません。IP アドレスを直接指定した場合は送信時に検証されるため、https://169.254.169.254/ は即座に 400 になります。 ホスト名は配信時に検証され、その時点で解決されたアドレスに固定されます。リクエストからこちらの接続までの間に、名前の解決先が変わる可能性があるためです。リダイレクトの各段階でも再検証されます。

見つけるまで分からないことがないよう、2つの制限を明記します。10回目の試行後、配信は中止され、ログに記録されます。この API にはエンドポイントのレジストリがないため、無効化されるものはなく、メールも送信されません。また、QUORUM_API_KEYS を設定せずに稼働しているインスタンスでは、すべての呼び出し元が同じキーラベルを共有し、署名シークレットも共通になります。そのため、署名によって証明されるのは、その配信が当該インスタンスから来たことまでであり、どの呼び出し元が要求したかではありません。

API を自分で実行する

ホスト型 API は、1つのプロセスとコーパスで構成されています。CLI が実行するものと同じエンジンです。

QUORUM_CORPUS=./quorum.db \
QUORUM_API_KEYS=$(openssl rand -hex 32) \
QUORUM_WEBHOOK_SECRET=$(openssl rand -base64 32) \
PORT=8787 npm start

QUORUM_API_KEYS を設定してください。設定しない場合、インスタンスは公開状態になり、起動時にそのことが通知されます。呼び出し元はベアラートークンで認証します。

curl -H "Authorization: Bearer $KEY" localhost:8787/v1/categories/running%20shoes

1つのインスタンスですべてのユーザーにサービスを提供します。これは意図的な設計です。あるカテゴリで呼び出し元が回答をウォームアップすると、次の呼び出し元が即座に回答を得られるため、コーパスはグローバルです。それこそが、1つにまとめておく理由です。例外はレポートと webhook 配信です。これらはテナントごとに所有され、行レベルセキュリティによって保護されます。実際の PostgreSQL サーバーで検証されています。

Postgres

QUORUM_PG_URL を設定すると、サーバーは SQLite の代わりに Postgres を使用します。それ以外に変更はありません。

npm run migrate -w packages/corpus     # applies the schema, refuses to re-run one

QUORUM_PG_URL='postgres://user:pass@host:5432/db?sslmode=require' \
QUORUM_PG_CA=./provider-ca.pem \
QUORUM_API_KEYS=$(openssl rand -hex 32) npm start

警告

ノート PC 以外で使う場合は、必ず設定してください。 node:sqlite は同期処理のため、コーパスを読み込むたびにイベントループがブロックされます。計測では、エビデンス検索は毎秒 124 リクエストが上限でしたが、インデックス付きの読み込みでは毎秒 7,477 件を処理できました。また、1 件目のリクエストの実行中は 2 件目を受け取れないため、同一のレポートが統合されることはありません。永続ディスクのないホストでは、再起動のたびにファイルとコーパスが消えるため、そもそも選択肢になりません。

QUORUM_PG_CA を設定してください。 sslmode=require は暗号化を行いますが、検証は行いません。マネージドプロバイダーは独自の CA で署名するため、パブリックなルート証明書ストアでは拒否されます。プロバイダーの CA をダウンロードすれば、単に暗号化されるだけでなく、接続が検証されます。起動時に、サーバーがどちらの状態になったかを表示します。

このリポジトリで実行時依存関係となるのは pg だけで、packages/server のみに含まれています。コーパスパッケージには依然として依存関係がないため、ドライバーは注入されたクライアントを受け取ります。これにより、SQLite を使う CLI の利用者は Postgres ドライバーをインストールする必要がありません。詳しくは docs/postgres.md を参照してください。

API の機能

エンドポイントは 10 個です。スキーマを含む完全な仕様は spec/openapi.yaml を参照してください。

レポート、時間のかかる処理

カテゴリが未収集の場合、レポートの取得にはレート制限下で数分かかります。データが揃っていれば約 0.5 秒です。そのため、これはリクエストではなくジョブとして扱います。

POST /v1/reportsレポートを開始します。すぐに 202 と ID を返します。同一の対象について進行中の処理があれば、1 回の実行に統合され、自分のリクエストが統合されたかどうかがレスポンスに示されます。
GET /v1/reports/:id状況を確認します。findings、weakSignals、rejected を含み、実行中は Retry-After に従います。
GET /v1/reports/:id/stream同じ処理をサーバー送信イベントとして返すため、スピナーではなく各ステージを確認できます。
エビデンス、高速処理

レポートに表示される数値はすべて、ここから確認できます。これにより主張を検証可能にし、データが揃ったコーパスに対しては 1 桁ミリ秒で応答します。

GET /v1/evidence/:receiptId1 件のレコードを返します。誰かが書いたテキスト、スコア、チャネル、パーマリンク、タイムスタンプが含まれます。
POST /v1/evidence/batchレポートでは複数のエビデンスを引用するため、一度に最大 200 件を取得できます。
POST /v1/evidence/search保持されているコーパス全体を全文検索します。カテゴリで絞り込めます。
GET /v1/evidence/ads/:adId1 件の広告について、すべての観測結果を返します。これにより、掲載期間を推定ではなく事実として示せます。
GET /v1/categories/:slugカテゴリのデータ充足度を確認します。保持レコード数、チャネル、経過時間を返します。無料で利用でき、データがないカテゴリのレポートに料金を払う前に確認する価値があります。
検証
POST /v1/verifyレシート ID 付きの主張を渡すと、コーパスに照らして各項目を再解決します。自分たちの出力も、他者の出力も検証できます。
GET /v1/usageこのキーの利用状況、許可されている内容、Webhook 署名シークレットを返します。
GET /v1/healthz稼働状態を確認します。データベースにはアクセスせず、レート制限も適用されません。
制限

キーごとに1つではなく、2つのクォータを設けます。レポートは上流からの取得が制限された状態で数分間にわたる一方、エビデンスの検索はインデックス化されたデータの読み取りが1回だからです。単一の共通制限では、検索が締め出されるか、レポートが保護されなくなります。

デフォルト
開始できるレポート数1分あたり20件
その他すべて1分あたり600件、つまり1秒あたり10件
同時に実行中のレポート数3件

計測対象となるすべての応答には、成功か拒否かを問わず、X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset が含まれます。そのため、呼び出し元は拒否された後ではなく、拒否される前に送信ペースを調整できます。GET /v1/healthz は制限の対象外なので、これらのヘッダーは送信しません。インスタンスは負荷が高いときでも稼働中であることを示せる必要があるためです。拒否された場合は Retry-After を伴う 429 が返されます。また、拒否されたリクエストはウィンドウのカウント対象になりません。そうでなければ、再試行を繰り返すクライアントはいつまでも復帰できなくなります。

認証が無効でも制限は有効です。 キーがないインスタンスでは、呼び出し元は定義上1つだけなので、許容量はなくなるのではなく共有されます。制限が耐えなければならないのは、設定し忘れが起きることです。

計測対象のソースと料金

競合他社の広告は有料ベンダーから取得するため、includeAds はすべての呼び出し元が利用でき、レート制限ではなく予算によって上限が設けられます。レート制限で制限できるのは頻度であり、費用ではありません。 広告を有効にしたレポートを1分あたり20件実行すると、計測対象のベンダー実行も1分あたり20回になります。レート制限ではこれを問題なく許可します。

停止する対象
QUORUM_MAX_CAP_USD1件の暴走したレポートによる予算の使い切り
QUORUM_SPEND_PER_KEY_USD1人の呼び出し元による予算の使い切り
QUORUM_SPEND_TOTAL_USDすべての呼び出し元による合計での予算の使い切り

広告をすべての人に開放する場合、重要なのは3つ目です。これがなければ、実際の上限はキーごとの金額にキーの数を掛けた額になり、上限とは言えません。

3つの値はすべてデフォルトで0になっており、広告は無効です。 予算を設定していないオペレーターは請求に同意していないため、設定し忘れても請求書ではなく、広告なしのレポートが生成されます。サーバーは起動時に現在の状態を通知します。

予算を使い切っても、エラーにはなりません。レポートの実行と回答は続けられますが、計測対象の処理部分を除いて回答し、劣化の一覧にはその理由が明記されます。任意の追加機能を理由にレポート全体を失敗させれば、無料の取得処理に費やした数分を無駄にすることになります。

まとめて実行された処理の料金は、その処理を開始した人が負担し、後から参加した呼び出し元は無料で利用できます。これが処理をまとめる目的です。また、異なるタイミングで参加した呼び出し元の間で料金を分割するのは、誰も確認できない恣意的な方法になってしまいます。費用は見積もりではなく、コストメーターに実際に記録された値から請求され、GET /v1/usage で確認できます。

これが異なる理由

いずれも意見の問題ではない、3つの特性があります。

証跡は劣化しません。 ほかの調査 API はすべて、クエリ時点のライブインデックスを参照する URL を返すため、ページが消えるとその URL も機能しなくなります。証跡は、保持されたコーパス内の安定した ID です。元の情報源が削除された後も含め、いつまでも同じ内容を取得できます。

裏付けは算術で示せます。 主張は、独立した記録が少なくとも3件なければ調査結果として表示されず、その件数も主張とともに示されます。「5つのチャネルにまたがる31件の記録」は、どの検索 API にも生成できない一文です。件数を数えるためのコーパスを保持している API がないからです。これはプロンプトインジェクションへの防御策でもあります。仕込まれたコメントが、自分自身の裏付けになることはありません。

アーカイブには過去について質問できます。 記録を取得するのではなく保持するため、ライブインデックスでは構造上答えられない質問にもコーパスが答えられます。

  • トレンドは、生の件数ではなく、時間の経過に伴う会話の占有率として示します。月ごとの記録数を数えると、何もかも増加しているように見えます。市場ではなく、こちらの収集量を測っているからです。
  • 特定時点の情報として、各記録を見つけた時期ではなく、書かれた時期で絞り込み、3月に市場が何を語っていたかを回答します。アーカイブは、私たちが3月に把握していた以上のことを、3月について把握していてもかまいません。
  • 差分では、同じ対象について前回のレポートから何が変わったかを、新しい証跡を示して説明します。件数を数えるだけではありません。
  • 比較では、競合ごとに完全な検索を1回ずつ実行します。1つのコーパス内で競合に言及する記録の数を数えても、共起を測るだけで、何が起きたかを特定の競合に結び付けることはできません。
  • 証明済みの証拠とは、規制当局に記名で提出された記録です。フォーラムのコメントより優先されますが、どのリスニングツールも保持していません。

そして、見落としやすい点があります。Meta は、配信が終了した商用広告をアーカイブしません。 キャンペーンが終了すると、94日間掲載されていたという記録は消え、どれだけお金をかけても取り戻せません。Shopify では、商品が取り下げられた当日に、販売終了商品が/products.jsonから削除されます。こうした情報源では、コーパスはキャッシュではなく、今後存在する唯一のコピーです。そして、それが存在するのは、その日に記録していた場合に限られます。

ドキュメント

spec/openapi.yamlAPI コントラクトです。SDK はこれに基づいて作成されています
docs/citation-integrity.md他所で測定された捏造率と、ここでは捏造を不可能にする構造について
docs/gummysearch-alternative.mdGummySearch ユーザー向け:共通点、違い、そしてこれは何ではないか
docs/postgres.mdPostgres での実行方法と、検証で判明したこと
docs/rate-limits.mdすべての上流サービス、負荷をかけたときの挙動、およびその対処方法
bench/README.md負荷テストと不正利用テスト、およびそこで見つかった不具合
CONTRIBUTING.mdソースの追加方法。これが主な貢献方法です
SECURITY.md脆弱性の報告

貢献

ソースの追加が主な貢献方法です。 変更するのは独自のディレクトリだけです。Sourceインターフェースに対応するアダプターを1つ、フィクスチャを1つ、適合性テストケースを1つ追加します。詳しくはCONTRIBUTING.mdをご覧ください。

npm run build          # tsc, declarations only, node runs the source directly
npm test               # node:test, offline, no keys required
npm run verify         # build, test, copy style, security, drift, spec

見落とすとビルドに失敗する、プロジェクト内のルールが2つあります。npm run lint:copyで禁止を徹底しているため、em ダッシュや en ダッシュは一切使わないこと、そして名前付きエクスポートのみを使うことです。

脆弱性を発見した場合は、イシューではなくSECURITY.mdをご覧ください。

開発

npm run build          # tsc across workspaces
npm test               # node:test, all packages, offline, no keys
npm run lint:copy      # house style
npm run check:security # secrets, SSRF guard, no-auth rule, audit
npm run check:spec     # the spec against the code it describes
npm run migrate -w packages/corpus     # Postgres schema
npm run test:postgres  # the driver against a real server, needs QUORUM_PG_URL

CI は、ホスト外への経路がないネットワーク名前空間内でテストスイートを実行します。そのため、気付かれないままネットワークに接続するアダプターは、後で不安定になるのではなく、すぐに失敗します。1,330件のテストのうち3件は実際の PostgreSQL サーバーが必要で、QUORUM_PG_URLが設定されていない場合はスキップされます。

ライセンス

エンジン全体に Apache 2.0 を適用しています。すべてのアダプター、すべてのゲート、両方のコーパスドライバー、CLI、SDK が対象です。機能制限はありません。

ホステッドサービスが提供する、ソースだけでは得られないものは、温まったコーパスの1つだけです。セルフホストすれば、エンジン全体を利用できますが、コールドスタートになります。それも実際に価値のある選択肢です。ご自身に合っているなら、ぜひご利用ください。

謝辞

このサービスは、他の人々が維持しているアーカイブを利用しています。その大半は無料です。

  • 公開 Reddit アーカイブの Arctic Shift。ボランティアプロジェクトであり、そのように扱っています。
  • Hacker News の検索
  • ドキュメントに記載のとおり、キーなしで使用する GitHub issue search API。これにより、報告済みの不具合をリアクション数とともにティア B に掲載できます。
  • Apple App Store の公開レビュー配信
  • リコールデータによって証明済みのエビデンスを可能にする、4つの規制機関である CPSC、openFDA、NHTSA、EU Safety Gate
  • 利用者に身元の提示を求める SEC EDGAR。これが QUORUM_CONTACT_EMAIL が存在する理由です。
  • CFPB Consumer Complaint Database。CC0 ライセンスの1,700万件の苦情には、何が起きたのかについての消費者自身の説明が含まれています。公開検索 API を通じて利用でき、フォーラムの情報が乏しい金融分野の声を補うティアです。
  • ドキュメントに記載のとおり、無料のキーで使用する YouTube Data API。レビュー動画のコメントは購入者の声です。無料枠は1日あたり10,000ユニットで、コメント100件につき1ユニットを消費します。
  • 唯一の有料ベンダーである Apify。従量課金の2つの機能、Meta Ad Library スクレイパーと Amazon 商品レビューを提供しています。レビュー機能が取得するのは、Amazon の公開商品ページでログアウト状態の訪問者に表示される情報のみです。調査対象が Amazon の商品でない限り、処理を計画しません。また、すべての呼び出しで、実際の請求額に照らして確認済みの料金に基づき、コストメーターに課金します。
  • Webhook 署名を独自設計ではなく、十分にレビューされた他者の設計に基づいて行うための Standard Webhooks

警告

許容される利用方法。 取得する情報はすべて公開されており、ログインせずに取得しています。このプロジェクトはスクレイピング元に対して認証を行いませんし、今後も行いません。法的な根拠はそれに基づいているためです。セッション Cookie を使えばその根拠が失われます。そのため、このルールは単なる好みではなく、アーキテクチャ上の要件です。Certificate Transparency のデータはブランドと商品の発見にのみ使用し、インフラの列挙には使用しません。上記のアーカイブを尊重してください。その大半はボランティアによって維持されており、このリポジトリのスロットル曲線も、その人々への配慮から設けています。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。