mcpField
S
Scry

Scry

リモート
docs.scrymore.com開発ツール02 週間前に更新
リポジトリ公式サイト

Scry について

Scryは、チームが既存のStorybookコンポーネントを検索して再利用するためのリモートMCPサーバーです。テキストと画像で検索でき、デザイン課題やスクリーンショット向けの段階提供ツールもあります。

主な機能

  1. 01セマンティック検索とキーワード検索
  2. 02画像入力によるビジュアル検索
  3. 03コンポーネントのスクリーンショット取得
  4. 04オプションのデザイン課題ツール
  5. 05オプションのスクリーンショット取得ツール

活用シーン

  1. 01テキストの説明から再利用可能なUIコンポーネントを探す
  2. 02スクリーンショットから類似コンポーネントを探す
  3. 03エージェント経由で公開済みデザイン課題を確認する
  4. 04最新のScry Snipキャプチャを読む

MCP 対応

MCPクライアントにコンポーネント検索、スクリーンショット取得、ユーザー情報のツールを提供します。Streamable HTTPとOAuth 2.1を使用し、Firebase認証は上流で処理します。追加ツール群は機能フラグで制御されます。

設定

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

エンドポイントhttps://mcp.scrymore.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 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

詳細

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_componentsUI コンポーネントを対象とした、テキストベースのセマンティック検索とキーワード検索を組み合わせたハイブリッド検索です。scope: "project"(デフォルト)では検索範囲を広げません。scope: "org" では、オプトイン済みで読み取り可能な、同じ組織内の別プロジェクトの行も crossProject としてマークして返します。versions: "latest"(デフォルト)では画面ごとに 1 件(インデックス作成済みの最新のコピーを返し、versionCount と Versions: N indexed の行を含む)を返します。versions: "all" ではインデックス作成済みのすべてのバージョンを一覧表示します
search_by_imagebase64 形式の画像入力を使用した視覚的類似検索です。scope と versions の動作は同じです
get_component_screenshotコンポーネントのスクリーンショットを取得します(画像ブロックと署名付き URL を返します)
generate_imageGemini による画像生成(高速/高品質)です。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_issue1 件の課題について、両方の切り抜き画像、Figma ファイルキーとノード ID、ストーリーとソースファイル、期待値、トラック、最終再確認日時、修正方法を返します
claim_design_issueコード側またはデザイン側の課題を取得(15 分間のリース)または解放します
mark_design_issue_fixedPR 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_capture1 つのプロジェクトにおける呼び出し元自身の最新キャプチャです。ユーザーが撮影したばかりだと言った場合に 1 回だけ使います。ループ内では使用しないでください。maxAgeMinutes(デフォルトは 15)で対象期間を広げられます
get_captureID を指定して 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)
posthogPostHog の 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

前提条件

ローカル開発

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 に昇格させます。

ブランチ環境ワーカー URLWrangler のターゲット
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 のセットアップ

  1. console.firebase.google.com で Firebase プロジェクトを作成します
  2. Authentication > Sign-in method > Google および/または Email/Password を有効にします
  3. Worker のドメインを承認済みドメインに追加します:Authentication > Settings > Authorized domains > Add scry-mcp.<your-account>.workers.dev
  4. 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 typecheckTypeScript の型チェック
npm run lintESLint
npm test単体テストと統合テストを実行します
npm run test:e2eE2E テストを実行します(開発サーバーが必要です)
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> を実行します。

コメント

MCP は初めてですか?

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

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