mcpField
S
Scry

Scry

远程服务
docs.scrymore.com开发工具02 周前更新

关于 Scry

Scry 是面向团队的远程 MCP 服务,让 Agent 搜索并复用现有 Storybook 组件。它支持文本和图像检索,另有用于设计问题和截图的分阶段工具。

主要功能

  1. 01语义与关键词混合组件搜索
  2. 02通过图像输入进行视觉搜索
  3. 03获取组件截图
  4. 04可选的设计问题工具
  5. 05可选的截图捕获工具

适用场景

  1. 01按文本描述查找可复用 UI 组件
  2. 02根据截图查找视觉相似组件
  3. 03通过 Agent 查看已提升的设计问题
  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 服务

部署在 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_imageGemini 图像生成(快速 / 高质量),消耗 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 URLWrangler 目标
stagestaginghttps://mcp-stage.scrymore.com--env staging
mainproductionhttps://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(使用 bearer CREDITS_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 设置

  1. 在 console.firebase.google.com 创建 Firebase 项目
  2. 启用 Authentication > Sign-in method > Google 和/或 Email/Password
  3. 将 Worker 的域名添加到授权域名:Authentication > Settings > Authorized domains > 添加 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 中使用滑动窗口)。
  • 请求超时:所有上游调用均设置 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 typecheckTypeScript 类型检查
npm run lintESLint
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> 重新同步。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。