关于 Scry
Scry 是面向团队的远程 MCP 服务,让 Agent 搜索并复用现有 Storybook 组件。它支持文本和图像检索,另有用于设计问题和截图的分阶段工具。
主要功能
- 01语义与关键词混合组件搜索
- 02通过图像输入进行视觉搜索
- 03获取组件截图
- 04可选的设计问题工具
- 05可选的截图捕获工具
适用场景
- 01按文本描述查找可复用 UI 组件
- 02根据截图查找视觉相似组件
- 03通过 Agent 查看已提升的设计问题
- 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 服务
部署在 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"(默认)每个屏幕返回一个结果(最新索引的副本,包含 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 中解决。
这六个工具都会调用仪表板的 /api/agent/issues/*,并携带已签名的 X-Scry-Caller
(受众 scry-dashboard-agent,声明 sub = uid,agent_client = MCP 客户端的
名称);仪表板会检查成员身份和角色,并记录 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 | 单个问题的详细信息,包含双方截图裁剪图、Figma 文件密钥 + 节点 ID、故事 + 源文件、预期值、跟踪记录、上次重新检查时间和修复方法 |
claim_design_issue | 认领(租期 15 分钟)或释放代码方或设计方的问题 |
mark_design_issue_fixed | 记录修复,并附上 PR URL / 提交 / Figma 版本 |
request_verify | 立即重新检查(免费,每个项目每小时 20 次,每天 200 次),或设置 rediff: true(消耗 10 个额度) |
comment_design_issue | 添加时间线评论,可选择提议修复方 |
Snip 捕获工具(阶段性;CAPTURE_TOOLS_ENABLED="1")
由 Agent 读取用户使用 Scry Snip 截取的屏幕截图(“修复我刚截取的屏幕截图”)。
仅有四个工具。它们通过相同的签名 X-Scry-Caller 跳转机制,以及与问题工具相同的环境变量,调用仪表板的 /api/agent/captures/*;仪表板会决定谁可以读取 snip(所有者,或所有者已启用的受众),且不会将相关信息写入日志。
在功能的 Gate B 之前,生产环境不会启用此功能标志。delete_capture 的速率上限为每位用户每分钟 10 次。
每个结果都以文本块开头(捕获 ID、距今时长、拍摄者、大小),然后是图片(WebP,长边至多 1280 px,约 70 KB,仅在不超过 75,000 字节时内嵌(约 100k 个 base64 字符);这两个数值均定义在 src/captures/constants.ts 中的 CAPTURE_AGENT_IMAGE),以及一个一小时后过期的原图签名链接。图片超出大小限制或无法获取时,结果会退化为仅提供链接。
Sync 应用会生成该版本图片;此服务端不做图像处理。
| 工具 | 描述 |
|---|---|
latest_capture | 调用者在一个项目中最新的自有捕获。用户表示刚刚截取了内容时使用一次;绝不要循环调用。maxAgeMinutes(默认值为 15)可扩大时间范围 |
get_capture | 按 ID 获取一条捕获(自有,或已与调用者共享) |
list_captures | 按时间从新到旧排列的文本列表。scope 为 mine(默认值)或 shared,以及 project_id、limit(最大值为 50)、before。当未限定范围的共享列表触及 50 个项目的上限时,会显示 projectsTruncated 及其说明 |
delete_capture | 永久删除调用者拍摄的捕获(仅限所有者) |
错误以 JSON {error, message, retryable} 格式返回,并带有 isError: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")
一个工具,search_stock(query, type?, provider?, limit?):通过 scry-stock Worker(STOCK_SERVICE_URL,密钥 STOCK_SERVICE_TOKEN)实时搜索免费图片素材库(Pixabay、Unsplash、Openverse;服务启用 Pexels 时也包括 Pexels)。调用会携带服务 bearer、有效期为 60 秒且受众为 scry-stock 的 X-Scry-Caller 断言(使用 SCRY_CALLER_ASSERTION_SECRET 签名,该密钥由 stock Worker 验证),以及调用的请求 ID;3 秒后放弃调用。每条结果都包含 provider、title、creditLine、creditParts(按顺序排列的 {text, href?} 署名部分)、pageUrl、providerUrl、previewUrl、type、licenseLabel 和 licenseUrl,并附有各提供方的状态。文本输出会将署名以带链接的 Markdown 格式呈现(Unsplash 摄影师和 Unsplash 链接会带有 utm_source=scry 参数),若服务提供许可协议说明页面 URL,则会将许可显示为链接;输出还包含一行 Sources:,以及条款要求展示的提供方声明(Openverse:“made with Openverse, not endorsed or certified by Openverse”;Pexels:“Photos provided by Pexels”);相同声明也会列在 structuredContent.notices 中。工具描述会告知 Agent 原样展示署名,包括其中的链接,并在提供方网站上打开图片。不会存储任何内容,也不会记录查询:查询只通过 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 数据点,供仪表板中仅限员工访问的 KPI 页面使用。记录的值仅包括工具名称(blob1)、环境(blob2)和 Firebase uid(blob3 和 index1),以及用于计数的 double1 = 1。缺失的环境/uid 值会分别变为 unknown 和 anonymous。不会向 Analytics Engine 发送查询文本、提示词、图片数据、令牌或其他工具数据。内部诊断日志不会增加使用情况数据点。分析绑定缺失或发生故障都不会影响工具响应。
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),根据允许列表构建。事件会发送到 ANALYTICS_SINKS 中指定的接收端(以逗号分隔)。无论该变量的值是什么,log 接收端始终启用(none、off 和未设置都表示仅启用 log),因此请保留它;未知名称会被忽略,并输出一行警告。
| 接收端 | 功能 | 位置 |
|---|---|---|
log | 输出一行 schema-v1 日志 msg:"mcp_tool_call"(route = tool、status、ms、err_code、uid_hash、client = 名称/版本)。通过 request_id 与 msg:"request" 日志行关联。默认值。 | R2 日志存储(scry-logs.py --request-id) |
posthog | 通过 PostHog 的 MCP analytics SDK 发送 $mcp_tool_call、$mcp_initialize、$mcp_tools_list、$mcp_missing_capability 和 $exception。没有 POSTHOG_PROJECT_TOKEN 密钥时不执行任何操作;没有 SCRY_LOG_SALT 密钥时会拒绝启动(输出一行警告),因为所有事件都会是匿名的。SDK 加载失败后会在 60 秒后重试,每次失败输出一行警告。 | PostHog 项目 625583(预发布)。在 Gate B 之前,生产环境不会发送任何数据 |
收集的内容:工具名称、结果、延迟、错误代码、已确认的项目 ID、用户 ID 的加盐哈希(uid_hash,绝不收集 uid 或电子邮件)、MCP 客户端名称/版本/协议、客户端提供时的调用模型(仅 Codex)、参数名称、响应大小,以及会话 ID(Durable Object ID)。PostHog 还会接收 Agent 可选的 context 句子;其中的电子邮件、URL 和密钥格式字符串会被清理,且长度限制为 300 个字符。日志绝不会包含它。
绝不收集的内容:参数值、响应正文、图像数据、查询、原始 uid、电子邮件、令牌。
客户端标签(客户端名称/版本、协议版本、模型)仅在属于简短的安全令牌时发送(包含字母、数字和 . _ - / ( ) + 及空格,最多 64 个字符,且不会被日志清理器修改);其他任何内容都会在日志和 PostHog 中统一报告为 other。intent 会截取至 1000 个字符,清理(电子邮件、URL、电话号码、IPv4/IPv6、密钥和较长 ID),再截取至 300 个字符并再次清理。
仅当 ANALYTICS_AGENT_ARGS="on" 时启用 Agent 可见的更改(自 2026-10-08 起在生产和预发布环境中启用;未设置或其他任何值均表示关闭,此时事件仍会流转,但工具不会发生更改):每个工具都会接受两个可选参数 context(Agent 调用工具的原因)和 conversation_id(用于对调用进行分组)。包装器会将它们添加到每个工具的 schema 中,并在处理程序运行前移除,因此工具代码看不到它们。若值不是字符串或长度超过 4096 个字符,则会从分析数据中丢弃,但调用仍会正常进行。若工具自行声明了 context 或 conversation_id,则会保留原样(不会注入或剥离,也绝不会将其作为 intent 读取)。get_more_tools 工具允许 Agent 说明 Scry 未提供的所需功能;它会记录缺少功能的事件,不执行其他操作。
添加或替换接收端:编写实现 AnalyticsSink 的 src/analytics/sinks/<name>.ts(实现 toolCall、initialize、toolsList;绝不抛出异常),将名称添加到 sinks.ts 中的 KNOWN_SINKS,并在 index.ts 的 createSinks 中添加相应分支。若要移除 PostHog,请删除 sinks/posthog.ts、index.ts 中对应的分支,以及 @posthog/mcp 和 posthog-node 依赖。ESLint(no-restricted-imports)只允许在该文件中导入 PostHog。接收端采用即发即弃方式,设有时间预算,并在 waitUntil 下运行,因此接收端变慢或失败都不会更改或延迟工具结果。
紧急停用:从 ANALYTICS_SINKS 中移除 posthog(或删除 POSTHOG_PROJECT_TOKEN 密钥);日志记录会继续运行。操作手册:scry-management/docs/runbooks/mcp-analytics.md。
请求 ID 和错误跟踪
两种 ID,彼此不关联(功能 observability-request-id;契约见
scry-management/features/observability-request-id/briefs/_request-id-contract.md;运行手册见
docs/runbooks/observability-request-id.md):
- Edge id,每个 HTTP 请求一个(
src/lib/edge-request.ts,F32/G3):Worker 为每个请求生成一个 ULID,并将其设置在 EVERY 响应的x-scry-request-id中(错误、重定向、OAuth、SSE;仅限标头,不触碰正文和流),同时写入一行msg:"request",包含路由模式(固定表中的模式,否则为unmatched)、状态、耗时(ms)和client(经过验证的x-scry-client请求标头)。边缘层绝不会读取或修改请求正文。 - Tool id,每次工具调用一个(
src/lib/tool-request.ts):工具包装器为每次工具调用生成一个自己的 ULID,将其记录在工具日志行中(route= 工具名称),在每次 Scry 跳转时转发,并在工具结果中返回(下文的错误正文中包含"request_id")。一个 MCP HTTP 请求可以包含多次工具调用(批处理、同一会话上的并行调用),因此一个 MCP 请求对应一个 edge id,其中的每次工具调用则各自对应不同的 tool id。两者不会关联:edge id 不会出现在工具结果中,tool id 也不会出现在响应标头中。若要追踪用户可见的 tool id,请查询它(scry-logs.py --request-id <tool id>):先找到工具日志行,再找到共享该 id 的下游搜索和积分日志行。x-scry-request-id标头中的 edge id 只能找到 HTTP 请求日志行。 - 入站的
x-scry-request-id始终会被忽略(两个 id 均如此):MCP 服务面向最终用户和 API 客户端,因此根据契约的信任规则,它绝不会接受调用方指定的 id。 - Langfuse 采样按每次调用在服务端随机抽取(
src/telemetry/producer.ts中的serverDraw),绝不使用 id。 - 每次 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…"}。 抛出异常的处理器会变为INTERNAL_ERROR/UPSTREAM_TIMEOUT,并附带该 id。 - 每次工具调用结束时,通过共享的 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 个十六进制字符,在请求路径之外计算;SCRY_LOG_SALT是密钥(wrangler secret put SCRY_LOG_SALT [--env staging],每个环境层级使用一个随机值,绝不能作为变量提交到 git),若在 staging 或 production 中未设置,则不包含uid_hash(没有公开的回退值)。client只出现在 edge 日志行中(它来自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 trace(metadata.request_id;trace id 是 ULID 的 128 位十六进制形式,由src/telemetry/ids.ts中的traceIdFor生成)。请求 id 仅用于追踪,绝不会用作计费或幂等键:积分预留中的ref_id仍是每次调用由服务端生成的 UUID(mcp-image:<uuid>),因此重复使用的入站 id 无法重放预留,也无法合并两笔扣费。 - Sentry(
src/lib/sentry-options.ts):environment = SCRY_ENV(staging|production)、sendDefaultPii: false,不包含正文,并在事件和面包屑上使用清理器(src/lib/sentry-scrub.ts,从 build-processing 复制)。工具 Durable Object 也会进行监控,因此发生异常的工具会带有request_id和tool标签进入 Sentry。若未设置SENTRY_DSN,SDK 不执行任何操作。Staging DSN:wrangler secret put SENTRY_DSN --env staging(共享的 Workers 项目,标记为service=scry-mcp)。
项目结构
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 用于传输身份验证:它证明请求来自此 worker。SCRY_CALLER_ASSERTION_SECRET 用于对短时有效的 X-Scry-Caller JWT 签名,该 JWT 会告知搜索 API 此 worker 代表哪位用户执行操作;这是唯一携带 uid 的渠道,搜索 API 会使用相同的密钥验证 JWT,然后才信任 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、widget 构建和测试,但不会部署。功能 PR 以 stage 为目标;
晋级操作会将 main 更新至已测试的暂存提交。
| 分支 | 环境 | Worker URL | Wrangler 目标 |
|---|---|---|---|
stage | staging | https://mcp-stage.scrymore.com | --env staging |
main | production | https://mcp.scrymore.com | 顶层(不带 --env) |
连接器 URL 为 https://mcp.scrymore.com/mcp(暂存环境:
https://mcp-stage.scrymore.com/mcp)。*.epinnock.workers.dev 名称指向相同的
worker,仍可正常解析;不会停用这些名称。每个 scrymore.com 主机名都是 Workers 自定义域名,
并且配置了显式的 <host>/* 路由,因为 scry-cdn-service 拥有
*.scrymore.com/* 路由,否则它会响应区域中每个经代理的主机名。完整表格和接线规则:
scry-management/ENDPOINTS.md。
这些分支上的推送会在检查通过后触发部署;仅修改 Markdown 和 docs/** 不会触发推送 CI。
手动运行时,请在工作流的 environment 选项中选择要部署的提交所在分支。部署按 ref 串行执行,
并包含 Phase 0 提交、分支、构建时间、运行 ID、执行者和 Sentry 发布标记。冒烟测试关卡会以
10 秒为间隔重试 /healthz 六次,直到其提交与运行 SHA 匹配;然后要求未经过身份验证的
/mcp 返回 401,且 /.well-known/oauth-authorization-server 返回 200。
对于已授权的手动部署,npm run deploy:staging 会选择暂存环境,
npm run deploy:production(或 npm run deploy)会保留现有生产 worker 和标记参数。生产环境
有意不设置 env.production 块;请使用顶层部署或 dry-run,不要使用 --env production。
CI 会复用仓库中现有的
CLOUDFLARE_API_TOKEN 和 CLOUDFLARE_ACCOUNT_ID。GitHub 部署环境为
staging 和 production,并分别配置对应的 worker URL。现有的
staging-OAUTH_KV 命名空间会被复用,Wrangler 会将迁移 v1 应用到新 worker 的
ScryMCP Durable Object;无需创建 KV 或手动迁移 DO。
Phase 1 的健康检查和 OAuth 冒烟测试无需 worker 密钥:健康检查只读取标记变量,提供方会根据请求源和端点配置构建元数据,而缺少 bearer 身份验证时会在访问 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 保护,直接请求它的 Worker 会收到登录页面(自定义域名也无法改变这一点:分配给分支的域名会提供预览部署,按域名设置例外需要付费方案);只有存在此密钥时,Worker 才会将令牌作为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过渡标志,然后部署 Worker,最后关闭该标志。 - 将
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。在 Phase 2 中,使用wrangler secret put <NAME> --env staging;生产环境密钥仍使用顶层配置。 - AI 积分(
generate_image会在 scry-diff-service 账本中占用调用者钱包的 40 个快速生成积分/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)通过经过身份验证的 AI Gateway(scry-stage/scry-prod)路由 Gemini,并需要CF_AIG_TOKEN密钥;删除该变量即可直接调用 Google(紧急停用开关)。LANGFUSE_ENABLED/LANGFUSE_SAMPLE_RATE控制 Langfuse spans,这些 spans 会进入TELEMETRY_QUEUE生产者(scry-telemetry-staging/-production);消费者是 scry-diff-service,它会将遥测归档到 R2 并发送至 Langfuse。采样采用自适应方式:启用遥测后,Worker 会从 diff-service 的单位预算任务中读取采样率(rates.mcp),请求地址为GET $CREDITS_API_URL/api/telemetry/sampling(使用 bearerCREDITS_API_TOKEN,与积分功能使用相同的服务凭据;无需额外密钥);该值会按响应中的ttl_s缓存于每个 isolate 中(默认 300 秒)。如果请求超时(1.5 秒)、发生错误、返回非 200 状态码或rates: null,则回退到LANGFUSE_SAMPLE_RATE(失败结果缓存 60 秒)。设置LANGFUSE_DYNAMIC_SAMPLING=0即可仅使用环境变量。采样决策对每个运行 ID 保持确定性。 - 在暂存环境的 Firebase 控制台中,授权
scry-mcp-staging.epinnock.workers.dev,并启用预期的登录提供方(Google 和电子邮件/密码,以及 GitHub)。 不要在已部署的 Worker 上启用DEV_BYPASS_AUTH。这些密钥和 Firebase 设置就绪后,将在第 2 阶段进行完整的已认证暂存环境验证。
Firebase 设置
- 在 console.firebase.google.com 创建 Firebase 项目
- 启用 Authentication > Sign-in method > Google 和/或 Email/Password
- 将 Worker 的域名添加到授权域名:Authentication > Settings > Authorized domains > 添加
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 中使用滑动窗口)。
- 请求超时:所有上游调用均设置 30s
AbortController超时。 - 输入验证:Zod 模式会限制查询长度(500 个字符)、图像大小(10MB)和项目 ID 长度(128 个字符)。
- 预签名 URL:有效期为 1 小时,由服务器端生成。R2 凭据绝不会离开 Next.js 服务。预签名请求会携带调用方断言,因此 Next.js 服务只会为用户有权读取的键签名。
- 调用方身份:Worker 会发送
X-Scry-Caller,这是一个基于SCRY_CALLER_ASSERTION_SECRET签名的 HS256 JWT({sub: uid, aud: "scry-search", iat, exp ≤ 60s}),因此共享的SCRY_SEARCH_API_KEY无法用于冒充用户。此断言是唯一的身份通道:搜索 API 中的过渡标志已移除,不再读取未签名的X-User-Id请求头,Worker 也不再发送该请求头。 - 搜索范围:两个搜索工具都显式设置
scope,默认值为project,不会扩大范围。只有当其他项目启用了discoverableByOrg且用户有权读取该项目时,org才会返回该项目的行。
MCP 注册表
server.json 会将服务发布到 MCP 注册表,名称为
io.github.epinnock/scry-mcp(远程:位于
https://mcp.scrymore.com/mcp 的可流式 HTTP)。
可用脚本
| 脚本 | 描述 |
|---|---|
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)会在 PR 中检测到 vendored 的 src/lib/scry-log 领先于阶段日志 Worker 时失败(“先部署 logs-service”);运行 scry-management/lib/scry-log/sync.sh <this repo> 重新同步。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。