Scry について
Scryは、チームが既存のStorybookコンポーネントを検索して再利用するためのリモートMCPサーバーです。テキストと画像で検索でき、デザイン課題やスクリーンショット向けの段階提供ツールもあります。
主な機能
- 01セマンティック検索とキーワード検索
- 02画像入力によるビジュアル検索
- 03コンポーネントのスクリーンショット取得
- 04オプションのデザイン課題ツール
- 05オプションのスクリーンショット取得ツール
活用シーン
- 01テキストの説明から再利用可能なUIコンポーネントを探す
- 02スクリーンショットから類似コンポーネントを探す
- 03エージェント経由で公開済みデザイン課題を確認する
- 04最新のScry Snipキャプチャを読む
MCP 対応
MCPクライアントにコンポーネント検索、スクリーンショット取得、ユーザー情報のツールを提供します。Streamable HTTPとOAuth 2.1を使用し、Firebase認証は上流で処理します。追加ツール群は機能フラグで制御されます。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://mcp.scrymore.com/mcpclaude mcp add --transport http epinnock-scry https://mcp.scrymore.com/mcp{"mcpServers":{"epinnock-scry":{"type":"http","url":"https://mcp.scrymore.com/mcp"}}}Cursor で開く{"servers":{"epinnock-scry":{"type":"http","url":"https://mcp.scrymore.com/mcp"}}}VS Code で開く{"mcpServers":{"epinnock-scry":{"type":"http","url":"https://mcp.scrymore.com/mcp"}}}{"mcpServers":{"epinnock-scry":{"serverUrl":"https://mcp.scrymore.com/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
Scry MCP Server
Cloudflare Workers 上の仕様準拠リモート MCP サーバーで、Scry コンポーネントデータベースに対するマルチモーダルなベクトル検索を提供します。Firebase 経由でユーザーを認証し、MCP クライアント向けに独自の OAuth トークンを発行します。
Claude Desktop、ChatGPT、Cursor、および mcp-remote を介したあらゆる MCP クライアントと互換性があります。
アーキテクチャ
Claude Desktop / ChatGPT / Any MCP Client
│
│ (stdio via mcp-remote)
▼
mcp-remote (local proxy)
│
│ (Streamable HTTP + OAuth 2.1)
▼
Cloudflare Worker ──────────────► Firebase Auth (upstream IdP)
├─ OAuthProvider (token mgmt)
├─ FirebaseAuthHandler (login UI + token verify)
└─ ScryMCP (Durable Object)
│
▼
Scry Search API (Next.js)
├─ POST /api/search — vector search (text, image, hybrid)
├─ GET /api/image/... — image proxy (CDN auth)
└─ POST /api/image/presign — presigned URL generation
Worker は、MCP クライアントに対する OAuth サーバー(独自のトークンを発行)と、Firebase に対する OAuth クライアント(上流でユーザーを認証)の両方として機能します。MCP クライアントが Firebase トークンを参照することはありません。
ツール
| ツール | 説明 |
|---|---|
search_components | UI コンポーネントを対象とした、テキストベースのセマンティック検索とキーワード検索を組み合わせたハイブリッド検索です。scope: "project"(デフォルト)では検索範囲を広げません。scope: "org" では、オプトイン済みで読み取り可能な、同じ組織内の別プロジェクトの行も crossProject としてマークして返します。versions: "latest"(デフォルト)では画面ごとに 1 件(インデックス作成済みの最新のコピーを返し、versionCount と Versions: N indexed の行を含む)を返します。versions: "all" ではインデックス作成済みのすべてのバージョンを一覧表示します |
search_by_image | base64 形式の画像入力を使用した視覚的類似検索です。scope と versions の動作は同じです |
get_component_screenshot | コンポーネントのスクリーンショットを取得します(画像ブロックと署名付き URL を返します) |
generate_image | Gemini による画像生成(高速/高品質)です。AI クレジットが課金されます。LLM_GATEWAY_URL が設定されている場合は、Cloudflare AI Gateway 経由でルーティングされます |
whoami | 認証済みユーザーの情報を返します |
get_more_tools | 必要としていた機能を提供するツールがない場合に、Scry にその内容を伝えます(チーム向けに記録されます。実行される処理はありません)。ANALYTICS_AGENT_ARGS="on"(ステージングおよび本番環境)の場合のみ使用できます |
課題解決ツール(ステージ環境。ISSUE_TOOLS_ENABLED="1")
ダッシュボードで人が昇格させたデザイン差分の課題を、コードまたは Figma で解決できます。
6 つのツールはいずれも、署名付きの X-Scry-Caller(対象者 scry-dashboard-agent、クレーム sub = uid、agent_client = MCP クライアント名)を使用して、ダッシュボードの /api/agent/issues/* を呼び出します。ダッシュボードはメンバーシップとロールを確認し、actor_kind: "agent" を記録します。
SCRY_DASHBOARD_API_URL、SCRY_AGENT_ASSERTION_SECRET(ダッシュボードとのみ共有する秘密情報。検索用の秘密情報とは別です。未設定の場合は SERVER_MISCONFIGURED)、およびアクセス保護されたステージ環境のダッシュボードでは SCRY_DASHBOARD_BYPASS_TOKEN シークレットが必要です。書き込みは、ユーザーあたり毎分 60 件のリクエスト上限に加えて、毎分 30 件に制限されます。
| ツール | 説明 |
|---|---|
list_design_issues | プロジェクト内の昇格済み課題です。リンク、Figma ノード、ストーリー、修正対象側、ステータス、対象側のステータス、重大度、担当者、changed_since で絞り込めます |
get_design_issue | 1 件の課題について、両方の切り抜き画像、Figma ファイルキーとノード ID、ストーリーとソースファイル、期待値、トラック、最終再確認日時、修正方法を返します |
claim_design_issue | コード側またはデザイン側の課題を取得(15 分間のリース)または解放します |
mark_design_issue_fixed | PR URL、コミット、Figma バージョンを添えて修正を記録します |
request_verify | 今すぐ再確認します(無料、プロジェクトあたり毎時 20 件、1 日 200 件)。または rediff: true を指定します(10 クレジット) |
comment_design_issue | タイムラインにコメントを追加します。修正対象側を提案することもできます |
Snip キャプチャツール(ステージ;CAPTURE_TOOLS_ENABLED="1")
ユーザーが Scry Snip で撮影したスクリーンショットを、エージェントが読み取ります(「今撮ったスクリーンショットを修正して」)。
ツールは 4 つだけです。課題ツールと同じ署名付き X-Scry-Caller ホップおよび環境変数を使って、ダッシュボードの /api/agent/captures/* を呼び出します。スニップを読み取れるユーザー(所有者、または所有者が有効にした対象範囲)はダッシュボードが判断し、スニップに関する情報はログに一切記録しません。本番環境では、機能の Gate B まではフラグがありません。delete_capture はユーザーあたり 10 回/分に制限されています。
各結果はテキストブロック(キャプチャ ID、経過時間、撮影者、サイズ)から始まり、続いて画像(WebP、長辺は最大 1280 px、約 70 KB、75,000 バイト(約 100k 個の base64 文字)以下の場合のみインライン表示。両方の数値は src/captures/constants.ts の CAPTURE_AGENT_IMAGE にあります)、1 時間で有効期限が切れるオリジナルへの署名付きリンクが続きます。画像が上限を超える場合、または取得できない場合は、リンクのみになります。レンディションは Sync アプリが生成し、このサーバーでは画像処理を行いません。
| ツール | 説明 |
|---|---|
latest_capture | 1 つのプロジェクトにおける呼び出し元自身の最新キャプチャです。ユーザーが撮影したばかりだと言った場合に 1 回だけ使います。ループ内では使用しないでください。maxAgeMinutes(デフォルトは 15)で対象期間を広げられます |
get_capture | ID を指定して 1 件のキャプチャを取得します(自身のもの、または呼び出し元と共有されたもの) |
list_captures | 新しい順のテキスト一覧です。scope は mine(デフォルト)または shared、project_id、limit(最大 50)、before を指定します。共有一覧でプロジェクトを指定せず、プロジェクト数の上限 50 件に達した場合は、projectsTruncated とその注記を表示します |
delete_capture | 呼び出し元が撮影したキャプチャを完全に削除します(所有者のみ) |
エラーは isError を伴う JSON {error, message, retryable} です。エラーには CAPTURE_STALE(最新のキャプチャが対象期間より古い場合。ID と経過時間を含みます)、AMBIGUOUS_PROJECT(メッセージにプロジェクトの一覧が含まれます)、CAPTURE_NOT_READY(アップロード中)、CAPTURE_NOT_FOUND(存在しないキャプチャと、呼び出し元が閲覧できないキャプチャはバイト単位で同一です)、CAPTURE_NOT_OWNER(削除時)、および汎用エラーの RATE_LIMITED、SERVER_MISCONFIGURED、TIMEOUT、DASHBOARD_UNREACHABLE があります。ユーザーが入力した注記は、信頼できないデータとしてラベルを付け、引用符で囲んで返します。ログ行にはキャプチャ ID、注記、アプリ名、URL は記録されません。
ストック画像の検索(ステージング;STOCK_TOOLS_ENABLED="1")
ツールは 1 つで、search_stock(query, type?, provider?, limit?) は、scry-stock Worker
(STOCK_SERVICE_URL、シークレット STOCK_SERVICE_TOKEN)を通じて、無料のストックライブラリ(Pixabay、
Unsplash、Openverse;サービスで有効化されている場合は Pexels)をリアルタイム検索します。この呼び出しでは、サービスのベアラー、対象者が scry-stock の有効期間 60 秒の
X-Scry-Caller アサーション(ストック Worker が検証するシークレット
SCRY_CALLER_ASSERTION_SECRET で署名)と、呼び出しのリクエスト ID が送信され、3 秒後に
中断されます。各結果には provider、
title、creditLine、creditParts(順序付きの {text, href?} パーツとして表されるクレジット)、pageUrl、providerUrl、previewUrl、type、licenseLabel、licenseUrl に加え、プロバイダーごとのステータスが含まれます。テキスト出力には、リンク付きの Markdown 形式でクレジットが表示され(Unsplash の写真家リンクと Unsplash のリンクには utm_source=scry の組み合わせが付与されます)、サービスからライセンスの Deed URL が返される場合はライセンスがリンクとして表示され、Sources: 行と、利用規約で求められるプロバイダーの注記(Openverse:「made with Openverse, not endorsed or certified by Openverse」;Pexels:「Photos provided by
Pexels」)も表示されます。同じ注記は structuredContent.notices にも含まれます。ツールの説明では、エージェントにクレジットをリンクも含めてそのまま表示し、画像はプロバイダーのサイトで開くよう指示しています。何も保存されず、クエリがログに記録されることもありません。クエリは POST 本文でのみ送信され、ツールには注入された分析用の context / conversation_id 引数は渡されません(NO_AGENT_ARGS_TOOLS)。ステージングのみです。本番環境ではどちらの変数も設定されないため、この機能の Gate B まではツールは利用できません。ソース:src/stock/。
エラーは JSON {error, message, retryable} 形式で、ステータスごとに固定のテキストを返します(サービスのレスポンス本文は使用しません):
RATE_LIMITED、VALIDATION_ERROR、STOCK_TIMEOUT、STOCK_UNREACHABLE、STOCK_SERVICE_ERROR、
STOCK_PROVIDERS_UNAVAILABLE、SERVER_MISCONFIGURED。
使用状況分析
各ツールハンドラーの呼び出しにつき、Workers Analytics Engine のデータポイントが 1 件記録され、
ダッシュボードのスタッフ専用 KPI ページで使用されます。記録される値はツール名
(blob1)、環境(blob2)、Firebase uid(blob3 と index1)、および
カウント用の double1 = 1 のみです。環境または uid の値がない場合は、それぞれ unknown と
anonymous になります。クエリテキスト、プロンプト、画像データ、トークン、その他のツールデータは
Analytics Engine に送信されません。内部の診断ログによって使用状況のデータポイントが追加されることはありません。Analytics のバインディングがない場合や、エラーが発生した場合でも、ツールの応答には影響しません。
MCP_USAGE バインディングは、本番環境では scry_mcp_usage に、ステージング環境では
scry_mcp_usage_staging に書き込みます。Analytics Engine SQL
API を使って本番環境のデータをクエリします(ステージングの場合は、ステージング用データセット名に置き換えてください):
SELECT blob1 AS tool, count() AS n FROM scry_mcp_usage WHERE timestamp > NOW() - INTERVAL '30' DAY GROUP BY tool
アナリティクス
機能 mcp-analytics。各ツール呼び出しでは、許可リストに基づいてベンダー中立のイベント mcp_tool_call.v1(src/analytics/event.ts)が1件生成されます。イベントは ANALYTICS_SINKS(カンマ区切り)で指定されたシンクに送信されます。値にかかわらず、log シンクは常に有効です(none、off、未設定の場合はいずれも log のみとなります)。そのため、リストに残してください。不明な名前は無視され、警告が1行出力されます。
| シンク | 動作 | 保存先 |
|---|---|---|
log | スキーマ v1 形式の msg:"mcp_tool_call" 行を1行出力します(route = tool、status、ms、err_code、uid_hash、client = 名前/バージョン)。request_id で msg:"request" 行と関連付けられます。デフォルトです。 | R2 ログストア(scry-logs.py --request-id) |
posthog | PostHog の MCP アナリティクス SDK を介して $mcp_tool_call、$mcp_initialize、$mcp_tools_list、$mcp_missing_capability、$exception を送信します。POSTHOG_PROJECT_TOKEN シークレットがない場合は何も行いません。また、すべてのイベントが匿名になるため、SCRY_LOG_SALT シークレットがない場合は起動せず、警告を1行出力します。SDK の読み込みに失敗した場合は60秒後に再試行し、失敗ごとに警告を1行出力します。 | PostHog プロジェクト 625583(ステージング)。Gate B までは本番環境から何も送信されません。 |
収集される情報:ツール名、結果、レイテンシ、エラーコード、確認済みのプロジェクト ID、ユーザー ID のソルト付きハッシュ(uid_hash。uid やメールアドレスは収集しません)、MCP クライアントの名前/バージョン/プロトコル、クライアントが示す場合の呼び出し元モデル(Codex のみ)、引数名、レスポンスのサイズ、セッション ID(Durable Object ID)。PostHog には、エージェントの任意の context 文も送られます。メールアドレス、URL、キー形式の文字列を除去し、300文字以内に制限します。ログには記録されません。
収集されない情報:引数の値、レスポンス本文、画像データ、クエリ、未加工の uid、メールアドレス、トークン。
クライアントラベル(クライアント名/バージョン、プロトコルのバージョン、モデル)は、安全な短いトークンの場合にのみ送信されます(英数字と . _ - / ( ) +、およびスペースで構成され、64文字以内で、ログのスクラバーによって変更されないもの)。それ以外は、ログと PostHog の両方で other として記録されます。intent は1000文字で切り詰め、スクラブ処理(メールアドレス、URL、電話番号、IPv4/IPv6、キー、長い ID を除去)を行った後、300文字で切り詰めて再度スクラブします。
エージェントから見える変更は、ANALYTICS_AGENT_ARGS="on" の場合に限られます(2026-10-08 以降、本番環境とステージング環境で有効です。未設定またはその他の値の場合は無効となり、イベントは引き続き送信されますが、ツールに変更は加えられません)。各ツールで、任意の引数 context(エージェントがツールを呼び出す理由)と conversation_id(呼び出しをグループ化)を受け付けるようになります。ラッパーはこれらを各ツールのスキーマに追加し、ハンドラーの実行前に取り除くため、ツールのコードから参照されることはありません。文字列以外の値、または4096文字を超える値はアナリティクスから除外され、呼び出しは通常どおり続行されます。ツールが独自の context または conversation_id を宣言している場合は、そのまま保持されます(注入も除去もされず、intent として読み取られることもありません)。get_more_tools ツールを使うと、エージェントは Scry が提供していない機能を伝えられます。このツールは不足機能イベントを記録する以外、何も行いません。
シンクの追加または置き換え:AnalyticsSink を実装する src/analytics/sinks/<name>.ts を作成します(toolCall、initialize、toolsList を実装し、例外をスローしないこと)。sinks.ts の KNOWN_SINKS に名前を追加し、index.ts の createSinks に case を追加します。PostHog を削除するには、sinks/posthog.ts と index.ts の該当する case、および @posthog/mcp と posthog-node の依存関係を削除します。ESLint(no-restricted-imports)により、PostHog のインポートはその1ファイル内でのみ許可されています。シンクは時間制限付きのファイア・アンド・フォーゲット方式で、waitUntil 内で実行されるため、遅いシンクや失敗したシンクがツールの結果を変更したり遅延させたりすることはありません。
緊急停止:ANALYTICS_SINKS から posthog を削除するか、POSTHOG_PROJECT_TOKEN シークレットを削除してください。ログ記録は継続されます。運用手順書:scry-management/docs/runbooks/mcp-analytics.md。
リクエスト ID とエラー追跡
2 種類の ID は結合されません(機能 observability-request-id。契約は
scry-management/features/observability-request-id/briefs/_request-id-contract.md、手順書は
docs/runbooks/observability-request-id.md にあります):
- エッジ ID:HTTP リクエストごとに 1 つ(
src/lib/edge-request.ts、F32/G3):Worker は各リクエストに対して ULID を生成し、すべてのレスポンス(エラー、リダイレクト、OAuth、SSE)にx-scry-request-idとして設定します(ヘッダーのみ。本文とストリームには手を加えません)。また、ルートパターン(固定テーブルにあるもの、なければunmatched)、ステータス、所要時間(ms)、client(検証済みのx-scry-clientリクエストヘッダー)を含むmsg:"request"の行を 1 つ書き込みます。エッジはリクエスト本文を読み取ったり変更したりしません。 - ツール ID:ツール呼び出しごとに 1 つ(
src/lib/tool-request.ts):ツールラッパーはツール呼び出しごとに独自の ULID を生成し、それをツールのログ行に記録し(route= ツール名)、Scry へのすべてのホップに転送して、ツールの結果に含めて返します(以下のとおり、エラー本文には"request_id"が含まれます)。1 つの MCP HTTP リクエストには複数のツール呼び出し(バッチ、1 つのセッションでの並列呼び出し)を含められるため、MCP リクエストにはエッジ ID が 1 つあり、その中の各ツール呼び出しにはそれぞれ異なるツール ID があります。両者は結び付けられません。エッジ ID はツールの結果に含まれず、ツール ID はレスポンスヘッダーに含まれません。ユーザーに表示されたツール ID を追跡するには、その ID を検索します(scry-logs.py --request-id <tool id>):ツール行と、その ID を共有する下流の検索行およびクレジット行が見つかります。x-scry-request-idヘッダーのエッジ ID で見つかるのは、HTTP リクエスト行のみです。 - 受信した
x-scry-request-idは常に無視されます(両方の ID が対象)。MCP サーバーはエンドユーザーと API クライアントに対応するため、契約の信頼ルールに従い、呼び出し元が指定した ID を受け付けません。 - Langfuse のサンプリングには、ID ではなく、呼び出しごとのサーバー側のランダム抽選(
src/telemetry/producer.tsのserverDraw)が使用されます。 - すべての Scry ホップに転送されます:検索(
/api/search、/api/image/presign、/api/image/upload)、ダッシュボードの issue API、diff-service のクレジット台帳。Google には送信されません。 - ツールエラーには ID が含まれます:すべての JSON エラー本文に
"request_id": "<id>"が含まれます(ツールラッパーが追加するため、各ツールで記憶しておく必要はありません)。例:{"error":"SEARCH_API_500","message":"…","retryable":true,"request_id":"01M3…"}。 エラーをスローしたハンドラーは、ID 付きのINTERNAL_ERROR/UPSTREAM_TIMEOUTになります。 - ツール呼び出しごとにツール行を 1 つ共有の scry-log ロガー経由で呼び出しの最後に書き込みます(ログスキーマ v1、feature log-standardization。
src/lib/scry-log/にベンダー化され、信頼できるソースはscry-management/lib/scry-log/、標準はscry-management/skills/feature-workflow/references/logging-tracing-standard.md):{"v":1,"ts":"…","level":"info","service":"mcp","env":"staging","version":"<sha>","msg":"request","request_id":"01M…","route":"search_components","status":200,"ms":412,"project":"4vR5…","uid_hash":"<12 hex>"}。statusは成功時に 200、サーバー側のツールエラー時に 500、それ以外では 400 です。エラーには固定の小文字のerr_code(search_api_500)が含まれます。err_codeの区分は、INSUFFICIENT_CREDITSが 402、ACCESS_DENIEDが 403、RATE_LIMITEDが 429 です。uid_hashは sha256(uid +SCRY_LOG_SALT) の先頭 12 桁の 16 進数で、リクエストパス外で計算されます。SCRY_LOG_SALTはシークレットです(wrangler secret put SCRY_LOG_SALT [--env staging]。環境ごとにランダムな値を 1 つ設定し、git の変数として保存しません)。staging または production で未設定の場合、uid_hashは省略されます(公開フォールバックはありません)。clientはエッジ行にのみ表示されます(x-scry-clientヘッダーに由来し、ツールハンドラーからは見えません)。生の uid、メールアドレス、クエリテキスト、本文は記録されません。呼び出し中の診断情報(logDiagnostic)には、固定の語句とstatus、ms、err_code、uid_hashのみが含まれます。上流のエラーテキストは決して記録されません。 staging では、tail_consumersを介して各行がscry-logsに送信されます。呼び出しを見つけるにはscry-management/scripts/scry-logs.py --request-id <id>を実行します。 - generate_image はリクエスト ID を実行 ID として使用します。AI Gateway の
runメタデータ、Langfuse トレース(metadata.request_id。トレース ID は ULID の 128 ビットを 16 進数にしたもので、src/telemetry/ids.tsのtraceIdForが生成)に使用されます。リクエスト ID はトレース専用で、課金キーや冪等性キーとして使われることはありません。クレジットの保留に使うref_idは、呼び出しごとにサーバーが生成する UUID(mcp-image:<uuid>)のままです。そのため、受信した ID が再利用されても、保留を再実行したり 2 つの請求を統合したりすることはできません。 - Sentry(
src/lib/sentry-options.ts):environment = SCRY_ENV(staging|production)、sendDefaultPii: false、本文は含めず、イベントとパンくずにスクラバー(build-processing からコピーしたsrc/lib/sentry-scrub.ts)を適用します。ツールの Durable Object もインストルメントされているため、ツールがエラーをスローすると、request_idとtoolのタグ付きで Sentry に送信されます。SENTRY_DSNがない場合、SDK は何もしません。staging 用 DSN:wrangler secret put SENTRY_DSN --env staging(service=scry-mcpタグを付けた共有 Workers プロジェクト)。
プロジェクト構成
scry-mcp/
├── src/
│ ├── index.ts # Entry point — OAuthProvider wrapper
│ ├── firebase-handler.ts # Auth handler — login UI + Firebase verification
│ ├── mcp.ts # MCP server — 5 tools (search, screenshot, generate_image, whoami)
│ ├── credits.ts / wallet.ts # AI-credits hold/settle against the diff-service ledger
│ ├── captures/ # snip-capture tools (constants, format, tools)
│ ├── analytics/ # vendor-neutral tool-call events + sinks (log, posthog)
│ ├── llm-gateway.ts # Cloudflare AI Gateway routing for Gemini
│ ├── telemetry/ # Langfuse spans -> TELEMETRY_QUEUE producer
│ └── utils/
│ └── firebase-verify.ts # Firebase ID token verification (Workers-compatible)
├── test/
│ ├── firebase-verify.test.ts # Unit tests for token verification
│ ├── firebase-handler.test.ts# Integration tests for auth handler
│ ├── mcp.test.ts # Tests for MCP tools
│ ├── e2e.test.ts # E2E smoke tests (requires dev server)
│ └── fixtures/
│ └── tokens.ts # Test JWT fixtures
├── vitest.config.ts
├── wrangler.jsonc
├── package.json
├── tsconfig.json
├── eslint.config.mjs
└── worker-configuration.d.ts
前提条件
- Node.js v22+
- Cloudflare アカウント
- Authentication が有効になっている Firebase プロジェクト
- Scry Next.js 検索 API が稼働していること(
/api/searchと/api/image/presignを提供)
ローカル開発
1. 依存関係をインストールする
npm install
2. ローカル環境ファイルを作成する
cp .dev.vars.example .dev.vars
.dev.vars を編集し、値を設定します。
FIREBASE_API_KEY=your-firebase-api-key
FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
FIREBASE_PROJECT_ID=your-project-id
SCRY_SEARCH_API_URL=http://localhost:3000
SCRY_SEARCH_API_KEY=your-search-api-key
SCRY_CALLER_ASSERTION_SECRET=same-value-as-scry-nextjs-SCRY_CALLER_ASSERTION_SECRET
COOKIE_ENCRYPTION_KEY=any-random-string-for-dev
SCRY_SEARCH_API_KEY はトランスポート認証用です。このワーカーからリクエストが送信されたことを証明します。SCRY_CALLER_ASSERTION_SECRET は、有効期間の短い X-Scry-Caller JWT に署名し、検索 API にワーカーが「どのユーザー」の代わりに動作しているかを伝えます。uid を含む唯一の経路であり、検索 API は同じシークレットで検証してから uid を信頼します。このシークレットがない場合、検索ツールは匿名で検索せず、SERVER_MISCONFIGURED を返します。
3. KV の設定
本番環境とステージング環境の KV 名前空間はすでに存在し、wrangler.jsonc で設定されています。ローカル開発では Wrangler のローカル KV エミュレーターを使用するため、クラウド名前空間を作成する必要はありません。
4. 開発サーバーを起動する
npm run dev
サーバーは http://localhost:8787 で起動します。
5. MCP Inspector でテストする
npx @modelcontextprotocol/inspector
# Enter: http://localhost:8787/mcp
テストの実行
# Unit + integration tests
npm test
# With verbose output
npx vitest run --reporter=verbose
# Watch mode
npm run test:watch
# E2E tests (requires dev server running in another terminal)
npm run dev # terminal 1
npm run test:e2e # terminal 2
# Full verification (typecheck + lint + tests)
npm run verify
デプロイ
共有ワークフローは .github/workflows/deploy.yml です。Node 22 と Wrangler 4.72.0(ロックファイルのバージョン)を使用します。stage または main を対象とする PR では、デプロイせずに型チェック、Lint、ウィジェットのビルド、テストを実行します。機能追加の PR は stage を対象とし、検証済みのステージングコミットを main に昇格させます。
| ブランチ | 環境 | ワーカー URL | Wrangler のターゲット |
|---|---|---|---|
stage | ステージング | https://mcp-stage.scrymore.com | --env staging |
main | 本番環境 | https://mcp.scrymore.com | トップレベル(--env なし) |
コネクター URL は https://mcp.scrymore.com/mcp です(ステージング:
https://mcp-stage.scrymore.com/mcp)。*.epinnock.workers.dev の名前も同じワーカーを指しており、引き続き解決されます。廃止されることはありません。各 scrymore.com ホスト名は Workers カスタムドメインであると同時に、明示的な <host>/* ルートでもあります。これは、scry-cdn-service が *.scrymore.com/* ルートを所有しており、そのままではゾーン内のプロキシ経由のすべてのホスト名に応答するためです。テーブル全体とルーティングのルールについては、
scry-management/ENDPOINTS.md を参照してください。
これらのブランチへのプッシュでは、チェックが成功するとデプロイが実行されます。Markdown と docs/** の変更だけでは、プッシュ時の CI は実行されません。手動実行では、ワークフローの environment を選択し、デプロイするコミットを含むブランチを指定します。デプロイは ref ごとに直列化され、Phase 0 のコミット、ブランチ、ビルド時刻、実行 ID、実行者、Sentry リリースのスタンプが含まれます。スモークテストのゲートでは、コミットが実行 SHA と一致するまで /healthz を 10 秒間隔で 6 回再試行します。その後、認証なしの /mcp が 401 を返し、/.well-known/oauth-authorization-server が 200 を返すことを確認します。
承認済みの手動デプロイでは、npm run deploy:staging がステージングを選択します。npm run deploy:production(または npm run deploy)は、既存の本番ワーカーとスタンプ用フラグをそのまま使用します。本番環境には意図的に env.production ブロックがありません。--env production ではなく、トップレベルのデプロイまたはドライランを使用してください。
CI では、リポジトリに既存の CLOUDFLARE_API_TOKEN と CLOUDFLARE_ACCOUNT_ID を再利用します。GitHub のデプロイ環境は staging と production で、それぞれのワーカー URL が設定されています。既存の staging-OAUTH_KV 名前空間を再利用し、Wrangler が新しいワーカーの ScryMCP Durable Object にマイグレーション v1 を適用します。KV の作成も、DO の手動マイグレーションも必要ありません。
Phase 1 のヘルスチェックと OAuth スモークテストは、ワーカーのシークレットなしで動作します。ヘルスチェックはスタンプ用変数のみを読み込み、プロバイダーはリクエストのオリジンとエンドポイント設定からメタデータを構築します。また、ベアラー認証がない場合は KV やサービスにアクセスする前に 401 を返します。Firebase でのサインイン、検索、画像生成には Phase 2 の設定が必要です。
scry-dev-dashboard-stage用にステージングのFIREBASE_API_KEY、FIREBASE_AUTH_DOMAIN、FIREBASE_PROJECT_IDを追加します。SCRY_SEARCH_API_URLをhttps://search-stage.scrymore.com(本番環境ではhttps://search.scrymore.com)に設定し、SCRY_SEARCH_API_KEYをステージング専用の API キーに設定します。このキーは Vercel で、機密情報扱いではなく、通常の暗号化された Preview 変数として保存します。機密情報扱いの値は読み戻せないため、プロモーション時のスモークチェック(scry-management/smoke-search.py)ではvercel env pullを使って取得します。SCRY_SEARCH_API_BYPASS_TOKENを、scry-nextjs プロジェクトの Protection Bypass for Automation トークンに設定します。ステージングの検索ホストは Vercel Deployment Protection の背後にあり、ワーカーからの通常のリクエストにはログインページを返します(カスタムドメインでも変わりません。ブランチに割り当てられたドメインはプレビュー環境を配信し、ドメインごとの例外設定には有料プランが必要です)。このシークレットが存在する場合のみ、ワーカーはトークンをx-vercel-protection-bypassとして送信します。本番環境にはこのシークレットがなく、ヘッダーも送信されません。SCRY_CALLER_ASSERTION_SECRETを、scry-nextjs の Preview デプロイメントに設定されたSCRY_CALLER_ASSERTION_SECRETと同じ値に設定します(wrangler secret put SCRY_CALLER_ASSERTION_SECRET --env staging。本番環境ではトップレベルに設定し、wrangler secret put SCRY_CALLER_ASSERTION_SECRETを実行します)。環境ごとに異なる値をopenssl rand -base64 48で生成します。まず scry-nextjs に設定し、移行フラグALLOW_LEGACY_USER_HEADER=trueを有効にした状態で、次にワーカーをデプロイし、その後フラグを無効にします。SCRY_AGENT_ASSERTION_SECRET(issue ツール専用)を、その環境のダッシュボードに設定されたSCRY_AGENT_ASSERTION_SECRETと同じ値に設定します(wrangler secret put SCRY_AGENT_ASSERTION_SECRET --env staging。本番環境ではトップレベルに設定します)。SCRY_CALLER_ASSERTION_SECRETとは異なる値にし、ステージングと本番環境の間でも異なる値にする必要があります。- ステージングの
COOKIE_ENCRYPTION_KEY、画像生成用のGEMINI_API_KEY、および任意でエラー報告用のSENTRY_DSNを追加します。フェーズ 2 では、wrangler secret put <NAME> --env stagingを使用します。本番環境のシークレットは引き続きトップレベルに設定します。 - AI クレジット(
generate_imageは scry-diff-service の台帳で、呼び出し元のウォレットから fast で 40 クレジット、quality で 150 クレジットを確保します):CREDITS_API_TOKENを、その環境の diff-service のSERVICE_AUTH_TOKENに設定します(wrangler secret put CREDITS_API_TOKEN --env staging)。CREDITS_MODE(vars)はoff|shadow|enforceです。shadow は台帳に書き込みますが、拒否はしません。enforce はINSUFFICIENT_CREDITSを返し(Gemini は呼び出しません)、台帳に接続できない場合はフェイルクローズしてCREDITS_UNAVAILABLEを返します。ステージングはenforce、本番環境は 2026-09-24 以降enforceです(shadow 期間は省略されました)。CREDITS_API_URL(diff-service の台帳)とCREDITS_PAGE_URL(ダッシュボードの/credits。拒否メッセージからリンクされます)は、wrangler.jsoncの vars です。ウォレットは呼び出し元の組織です(memberIdsに含まれている場合はorg:<users/{uid}.activeOrgId>、それ以外はorg:personal_<uid>)。Firebase Admin サービスアカウントを使って Firestore から読み取ります。その環境のサービスアカウント JSON からFIREBASE_CLIENT_EMAILとFIREBASE_PRIVATE_KEY(シークレット)を設定します。 - LLM テレメトリ:
LLM_GATEWAY_URL(vars)は Gemini を認証済み AI Gateway(scry-stage/scry-prod)経由でルーティングし、CF_AIG_TOKENシークレットが必要です。この変数を削除すると、Google に直接リクエストします(キルスイッチ)。LANGFUSE_ENABLED/LANGFUSE_SAMPLE_RATEは Langfuse スパンを制御します。スパンはTELEMETRY_QUEUEプロデューサー(scry-telemetry-staging/-production)にキューイングされます。コンシューマーは scry-diff-service で、R2 にアーカイブし、Langfuse に配信します。 サンプリングは適応型です。テレメトリが有効な場合、ワーカーは diff-service のユニット予算ジョブから、応答のttl_s(既定値は 300 秒)の間、分離環境ごとにキャッシュされるサンプリングレート(rates.mcp)をGET $CREDITS_API_URL/api/telemetry/samplingから読み取ります(Bearer 認証にはクレジットと同じサービス認証情報CREDITS_API_TOKENを使用します。追加のシークレットは不要です)。タイムアウト(1.5 秒)、エラー、非 200 応答、またはrates: nullの場合はLANGFUSE_SAMPLE_RATEにフォールバックします(失敗は 60 秒間キャッシュされます)。環境変数のみを使うには、LANGFUSE_DYNAMIC_SAMPLING=0を設定します。サンプリングの判定は、実行 ID ごとに常に同じです。 - ステージングの Firebase コンソールで
scry-mcp-staging.epinnock.workers.devを承認し、意図したサインインプロバイダー(GitHub に加えて Google とメールアドレス/パスワード)を有効にします。 デプロイ済みの Worker ではDEV_BYPASS_AUTHを有効にしないでください。これらのシークレットと Firebase の設定が揃った後、フェーズ 2 で完全な認証済みステージング検証を行います。
Firebase のセットアップ
- console.firebase.google.com で Firebase プロジェクトを作成します
- Authentication > Sign-in method > Google および/または Email/Password を有効にします
- Worker のドメインを承認済みドメインに追加します:Authentication > Settings > Authorized domains > Add
scry-mcp.<your-account>.workers.dev - Project Settings > General > Web app config から設定値
apiKey、authDomain、projectIdをコピーします
クライアントのセットアップ(Claude Desktop)
Claude Desktop の設定(macOS では ~/Library/Application Support/Claude/claude_desktop_config.json)に追加します:
{
"mcpServers": {
"scry": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp.scrymore.com/mcp"
]
}
}
}
ローカル開発の場合:
{
"mcpServers": {
"scry": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8787/mcp"]
}
}
}
Claude Desktop を再起動します。初回起動時に、Firebase サインイン用のブラウザーウィンドウが開きます。認証後、Claude のツールピッカーに 5 つのツールが表示されます。
認証フロー
Claude Desktop mcp-remote Worker Firebase
│ │ │ │
│── start ──────────►│ │ │
│ │── GET /mcp ─────►│ │
│ │◄── 401 ──────────│ │
│ │ │ │
│ (browser opens) │── GET /authorize►│ │
│ │ │── render login ───►│
│ │ │◄── ID token ───────│
│ │ │ verify + issue │
│ │◄── MCP token ────│ │
│ │ │ │
│── "find buttons" ─►│── tool call ────►│── POST /api/search►
│◄── components ─────│◄── results ──────│◄──────────────────
セキュリティ
- トークンの分離:Firebase トークンが MCP クライアントに公開されることはありません。Worker はトークンを KV で暗号化し、個別の MCP スコープ付きトークンを発行します。
- CSRF 保護:
HttpOnly、Secure、SameSite=Laxを指定した__Host-プレフィックス付き Cookie。 - レート制限:ユーザーごとに毎分 60 リクエスト(Durable Object 内のスライディングウィンドウ)。
- リクエストタイムアウト:すべての上流呼び出しに 30 秒の
AbortControllerタイムアウトを設定します。 - 入力検証:Zod スキーマにより、クエリの長さ(500 文字)、画像サイズ(10MB)、プロジェクト ID の長さ(128 文字)を検証します。
- 署名付き URL:有効期間は 1 時間で、サーバー側で生成されます。R2 の認証情報が Next.js サービスの外部に出ることはありません。署名リクエストには呼び出し元のアサーションが含まれるため、Next.js サービスはユーザーが読み取り可能なキーに限って署名します。
- 呼び出し元の ID:Worker は
SCRY_CALLER_ASSERTION_SECRETを使った HS256 JWT({sub: uid, aud: "scry-search", iat, exp ≤ 60s})であるX-Scry-Callerを送信するため、共有のSCRY_SEARCH_API_KEYを使ってユーザーになりすますことはできません。このアサーションが唯一の ID チャネルです。検索 API の移行フラグは廃止され、署名なしのX-User-Idヘッダーは検索 API で読み取られなくなり、Worker も送信しなくなりました。 - 検索範囲:両方の検索ツールで明示的に
scopeを指定します。デフォルトはprojectで、検索範囲が広がることはありません。orgは、別のプロジェクトがオプトイン済み(discoverableByOrg)で、かつユーザーがそのプロジェクトを読み取れる場合にのみ、そのプロジェクトの行を返します。
MCP レジストリ
server.json は、サーバーを MCP レジストリに
io.github.epinnock/scry-mcp として公開します(リモート:ストリーミング可能な HTTP、接続先:
https://mcp.scrymore.com/mcp)。
利用可能なスクリプト
| スクリプト | 説明 |
|---|---|
npm run dev | ローカル開発サーバーを起動します |
npm run deploy / npm run deploy:production | スタンプ付きの本番環境デプロイ |
npm run deploy:staging | スタンプ付きのステージング環境デプロイ |
npm run typecheck | TypeScript の型チェック |
npm run lint | ESLint |
npm test | 単体テストと統合テストを実行します |
npm run test:e2e | E2E テストを実行します(開発サーバーが必要です) |
npm run test:coverage | カバレッジレポート付きでテストを実行します |
npm run verify | 型チェック、lint、テストをまとめて実行します |
ログスキーマの差分チェック
scry-log-drift ワークフロー(scripts/scry-log-drift.sh)は、ベンダー化された src/lib/scry-log がステージログ Worker より先行している PR を失敗させます(「まず logs-service をデプロイしてください」)。再度ベンダー化するには、scry-management/lib/scry-log/sync.sh <this repo> を実行します。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。