关于 forkcaster
forkcaster 将 Farcaster 帖子引用解析为受支持客户端应用的深层链接,并提供可分享的帖子页面。其公开 MCP 端点无需认证即可返回客户端 URL 和作者的签约状态。
主要功能
- 01解析帖子并生成客户端深层链接
- 02服务端渲染帖子页面
- 03以 JSON 返回帖子数据
- 04检查作者签约状态
适用场景
- 01不使用 Farcaster 应用分享帖子
- 02查找指定客户端的帖子链接
- 03为 Agent 获取帖子详情
MCP 相关
公开的 Streamable HTTP 端点提供 `get_cast_link` 工具,无需认证。该工具将帖子引用解析为受支持客户端的 URL,并返回作者的签约状态。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://forkcaster.xyz/mcpclaude mcp add --transport http xyz-forkcaster https://forkcaster.xyz/mcp{"mcpServers":{"xyz-forkcaster":{"type":"http","url":"https://forkcaster.xyz/mcp"}}}在 Cursor 中打开{"servers":{"xyz-forkcaster":{"type":"http","url":"https://forkcaster.xyz/mcp"}}}在 VS Code 中打开{"mcpServers":{"xyz-forkcaster":{"type":"http","url":"https://forkcaster.xyz/mcp"}}}{"mcpServers":{"xyz-forkcaster":{"serverUrl":"https://forkcaster.xyz/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
forkcaster — Agent 简介
受众:编码 Agent。优先记录事实,而非长篇描述。架构或命令变更时,请更新此文件。
意图
一个镜像 Farcaster cast URL 的微型网站,方便用户在不使用 Farcaster 应用的情况下分享。
- 模式:
/{username}/{castHash},其中castHash匹配0x[0-9a-fA-F]+——短格式或完整格式 - 示例:
/zoo/0x895ecc74和/decent-artlu/0xbb54368f→ SSR cast 卡片 + 链接 - 数据:在多个提供方之间竞速(
worker/cast-source.ts,rpc-racer 模式)——见下文“Cast source race”。shim(https://shim.artlu.xyz/wc/{username}/{hash},由所有者运行的 Snapchain 读取器,配有保活 cron)是完整性的基准,也是唯一的封禁信号依赖项(/signed/{fid});farcaster.tv 速度快,但无法识别引用;原始节点 haatz/snap.farcaster.xyz:3381数据完整,但需要通过 shim/f/{username}获取 fid 和完整 hash。所有上游调用都限制为 5 秒。 - Cast 不存在:完整提供方(shim、haatz、snap)给出权威的未命中结果 → 404 页面;没有提供方解析成功 → 仅含链接的页面(200)。所有上游调用都限制为 5 秒(
AbortSignal.timeout)——否则 shim 的冷分片查找可能挂起 15 秒以上;超时 ⇒ 无法访问。严格状态码由 JSON 变体负责(见下文) - 封禁账户:所有人都能看到嵌入内容——但某些客户端会隐藏未签名作者。自 2026 年 10 月 15 日(Network School 接管)起,账户必须接受“social smart contract”(Network Code);接受后会获得链上访问 NFT。Shim 提供此信息:
GET https://shim.artlu.xyz/signed/{fid}→{"signed":true|false}。对于未签名作者:注册表中设置了requiresSigned的客户端(目前为 farcaster)会从操作栏中移除;该检查采用失败开放策略——状态未知时不会移除任何内容 - farcaster.tv 作为 cast 数据来源时无法识别引用(会丢弃
{castId}嵌入),且并非官方来源——它只作为部分数据层参与竞速(700ms HTML 框,引用内容由客户端补齐)。react-farcaster-embed 自带的默认端点是 farcaster.tv——务必传入castData,避免其自行获取。实时统计行(回复/转发/点赞)仍由客户端从 farcaster.tv 获取——该服务会发送access-control-allow-origin: *,也会返回未签名作者的数据;任何失败都只会导致统计行不显示
技术栈
| 层 | 选型 | 说明 |
|---|---|---|
| 运行时 / 包管理器 | Bun | 锁文件:bun.lock;脚本使用 bun run |
| UI(落地页) | Preact 11 | SPA 资源;入口 src/main.tsx |
| Cast 页面 | 基于 Cloudflare Worker 的 Hono | 在 worker/ 中进行 SSR |
| Cast 数据 | 提供方竞速(cast-source.ts) | shim ∥ farcaster.tv ∥ haatz ∥ snap——首个通过验证的结果;HTML 使用完整性来源,JSON 仅使用 shim;见下文“Cast source race” |
| URL 嵌入卡片 | unfurl.artlu.workers.dev | 通过 UNFURL 服务绑定调用(workers.dev→workers.dev 的 fetch 会被拦截,返回 CF 错误 1042);GET /?u={url} → OG 标题/描述/图片(有缓存,失败时回退为仅显示主机名) |
| Cast 嵌入 | react-farcaster-embed | 仅负责渲染——始终提供 castData,不会自行获取 |
| 实时统计 | 客户端脚本 → farcaster.tv | cast-page.tsx 中的 LIVE_STATS_SCRIPT;从浏览器端填充 SSR 移除统计数据后留下的位置;失败时不显示任何内容(无统计行);SSR 数据路径仍仅使用 Shim |
| SSR | preact-render-to-string | 在 await FarcasterEmbed({ castData }) 之后 |
| 打包器 | Vite 8 | @preact/preset-vite + @cloudflare/vite-plugin |
| Lint | Biome | bun run lint |
| 类型检查 | bun check | 兼容 tsc,内置于 Bun;无 typescript 包 |
| 测试 | bun:test | bun test(cast 路由 + 客户端注册表) |
命令
bun install
bun run dev # wrangler dev (local worker + assets)
bun run build # vite build → dist/
bun run typecheck # bun check (tsc-compatible; reads tsconfig*.json)
bun run deploy # build + wrangler deploy
bun run lint # biome lint .
bun test # worker tests (cast route + client registry)
bun run cf-typegen # wrangler types → worker-configuration.d.ts
目录结构
/
├── index.html # SPA shell → /src/main.tsx
├── vite.config.ts # preact + cloudflare + react→preact aliases
├── wrangler.jsonc # main=worker/index.ts; not_found_handling=none
├── public/farcaster-embed.css # linked from SSR HTML
├── public/logos/ # per-client marks for the action row
├── worker/
│ ├── index.ts # Hono app; export { app }; default { fetch }
│ ├── client-registry.ts # client registry (verified templates; zero-dep, vendored-able)
│ ├── clients.ts # fc_client cookie binding (last-used + auto-open prefs)
│ ├── shim.ts # shim identity client (routes, ShimCast, /wc /h /f /signed)
│ ├── cast-source.ts # provider race: shim ∥ haatz ∥ snap ∥ farcaster.tv
│ ├── cast-page.tsx # resolve (race) → CastData mapping → SSR / 404 / link fallback
│ ├── cast-ref.ts # MCP cast-ref: zod contract + shim-direct resolution
│ ├── mcp.ts # MCP SDK v2 server factory (get_cast_link) — only SDK import site
│ ├── cast-route.test.ts # bun:test; mock.module + exact-URL fetch mock
│ ├── clients.test.ts # registry unit tests: templates, gating, signed rule, deeplinks
│ ├── cast-source.test.ts # race unit tests: eligibility, box, miss/error, abort, adapter mapping
│ ├── race-stats-store.ts # lmdis snapshot read-through TTL cache (10min TTL / stale 24h / 2s cap)
│ ├── vendor/lmdis-sdk.ts # vendored lmdis SDK (Upstash-shaped seam; get/set only)
│ └── shims/server-only.ts # empty stub for Next.js marker package
├── scripts/
│ ├── race-stats.ts # offline stats job: AE SQL → snapshot → lmdis (dotenvx env; --dry)
│ ├── race-stats-lib.ts # pure query builders (fixture-tested)
│ └── probe/ # live-service probes (lmdis, AE SQL, shim timing)
└── src/ # landing only (App.tsx)
路由
| 路径 | 处理程序 | 响应 |
|---|---|---|
/ | 静态资源 index.html | 落地页 SPA |
/api/ | Hono | { "name": "Cloudflare" } |
/prefs | Hono | POST JSON { auto?: "on" | "off", delay?: 0–5 }(至少一个字段)→ 204 + Set-Cookie(fc_client);默认值为 5s 时,cookie 中省略 delay。自动打开偏好设置写入(服务器 cookie — Safari ITP 豁免) |
/transparency | Hono | HTML 信息页(内容/数据/跟踪/域名 — 域名包含注册到期时间 + RDAP 确认链接 — 以及从 lmdis 快照在服务器端填充的竞速统计表,还有“统计数据截至”行),或通过 ?format=json/Accept 返回对应 JSON(racingStats.status: live | coming-soon);保留的精确路径 — 用户名为 transparency 的 cast URL 仍可正常使用 |
/race-stats.json | Hono | 原始统计快照(与 /transparency 使用相同的缓存读取);不存在时返回 404;cache-control: public, max-age=21600 |
/mcp | Hono → MCP SDK v2 | MCP Streamable HTTP 端点(POST JSON-RPC;公开,无需认证)。一个工具:get_cast_link:cast 引用(longhash ∥ fid+shorthash ∥ username+shorthash)+ 目标客户端 → 客户端 URL + 作者签名状态;farcaster.xyz 是规范页面。 |
/:username/:hash(0x…) | Hono SSR | HTML:200 cast 卡片 + OG/Twitter 元数据(标题 = DisplayName (@handle),描述 = 合并空白后的 cast 文本,280 个字符,图片 = 第一张图片嵌入内容,否则为头像;仅当图片是 cast 嵌入图片时,卡片才使用 summary_large_image;og:site_name = forkcaster)+ 由注册表驱动的客户端操作栏(见下文“客户端注册表”);作者未签名(/signed/{fid} 返回 false):隐藏 requiresSigned 客户端(farcaster);Shim 报告不存在此 cast 时返回 404;Shim 无法访问时返回仅含链接的页面,状态码为 200 |
/:username/:hash?format=json(或 Accept: application/json) | Hono JSON | 严格状态码的 Agent 变体 — 见下文“Agent JSON 变体” |
| 其他不存在的路径 | worker 404 / 不回退到 SPA | not_found_handling: none |
多域名(双域名服务): 同一个 worker 服务于 forkcaster.xyz 和 warpcats.com(wrangler.jsonc 中的自定义域名路由)。所有代码均与主机名无关(按请求获取 origin、同源填充脚本、仅限主机的 cookie),且每个页面都会将规范地址设为主域名:cast 页面根据 PRIMARY_ORIGIN 变量输出 <link rel="canonical"> + og:url(未设置时不输出 — 纯 workers.dev 仍保持双域名服务之前的输出),落地页的规范地址则写在 index.html 中。竞速遥测将请求主机名作为 blob 携带,以便按域名细分。Cookie/徽章按设计分别属于各个域名。
Agent JSON 变体(?format=json)
已上线。 解析一次(Shim 获取 + 展开链接 + 引用补全 + 签名检查 → resolveCast()),渲染两次——worker/cast-page.tsx 中的 renderCastHtml() / renderCastJson()。返回类型会根据请求方而变化。
内容协商——显式优先,不通过 UA 嗅探:
| 信号 | 响应 |
|---|---|
?format=json 查询参数 | JSON(文档约定;优先级高于 Accept) |
Accept: application/json | JSON |
其他任何情况(浏览器、*/*、聊天链接预览机器人) | HTML |
决策:
- 仅提供 JSON——不提供 kv/纯文本格式(kv 有损;JSON 的 token 开销可以忽略)。
- JSON 使用严格状态码,HTML 使用宽松状态码(有意保持差异):JSON → Shim 报告不存在此 cast 时返回 404,Shim 无法访问时返回 503 +
Retry-After: 60。HTML 保留其 404 页面 / 仅含链接的 200 页面。Agent 处理状态码的能力优于处理渲染后的回退内容。 - 按可用性决定载荷内容——仅当数据存在时才包含字段:作者、ISO 8601 时间戳、文本、图片嵌入 URL、URL 嵌入(展开成功时附加展开标题,否则仅提供原始 URL)、引用嵌入、规范 farcaster.xyz + Firefly 链接。
- 引用始终作为指针保留,不会丢弃:引用补全成功 → forkcaster URL(需要从补全后的 cast 获取用户名);补全失败 → Firefly 完整哈希 URL(引用嵌入包含
{fid, hash},不含用户名)。HTML 的渲染限制(未防护的author.pfp.url)不适用于 JSON——补全失败时会降级为 Agent 可以获取的 URL。 - 封禁信号:
signed: false时会输出简短的备注字段;检查结果未知/失败时会静默放行(与 HTML 操作行采用相同的失败时放行语义)。 - 便于发现:HTML 页面会输出
<link rel="alternate" type="application/json" href="?format=json">。 - 暂缓处理:之后再通过 UA 允许列表(GPTBot、ClaudeBot、……)实现变体切换,作为渐进式增强。必须使用允许列表——Telegram/Discord/iMessage 预览机器人仍须接收 HTML + OG 元数据。
- 目前没有页面级缓存(Shim 即为缓存);若以后添加边缘缓存,变体必须纳入缓存键。
MCP
https://forkcaster.xyz/mcp(也可用 warpcats.com/mcp)——MCP Streamable HTTP 端点,公开、无需身份验证、无状态。提供一个工具:get_cast_link——将 Farcaster cast(完整的 40 位十六进制哈希,或 fid/用户名 + 短哈希)解析为任一受支持客户端的深层链接 URL,并提供作者的签名合约状态。接线位置:worker/mcp.ts(SDK)+ worker/cast-ref.ts(zod 合约,直接通过 shim 解析)。
注册表条目:server.json(仓库根目录,官方注册表架构 2025-12-11,名称为 xyz.forkcaster/mcp)。要发布,请安装官方发布 CLI——它是 GitHub release 提供的二进制文件。⚠️ 切勿对 npm 包 mcp-publisher 使用 bunx/npx——它是名称冲突的无关浏览器自动化项目:
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher && sudo mv mcp-publisher /usr/local/bin/
一次性域名验证:生成 ed25519 密钥对(openssl genpkey -algorithm Ed25519 -out key.pem),添加它输出的 apex TXT 记录(v=MCPv1; k=ed25519; p=<public key>),然后:
mcp-publisher login dns forkcaster.xyz --private-key <hex> # signs the challenge → xyz.forkcaster namespace
mcp-publisher publish # submits server.json to registry.modelcontextprotocol.io
key.pem 是发布凭证——请妥善保管,切勿提交。
Cast 来源竞速
Cast 查询会让四个提供方同时竞速(worker/cast-source.ts,采用 rpc-racer 模式:并行请求,每个提供方可单独取消,超时上限为 5s;第一个通过验证的结果获胜,其他请求会被取消;获胜结果必须能解析出请求的哈希,否则会降级为错误)。
完备性矩阵(根据 2026 年 10 月的测量结果),对照内部 ShimCast 契约(文本、哈希、Unix 秒级时间戳、原始 {url}/{castId} 嵌入内容、内联用户数据):
| 提供方 | 能解析(用户名、哈希)? | 引用({castId}) | 结论 |
|---|---|---|---|
| shim | ✅ 短哈希 + 完整哈希 | ✅ | 完整——规范来源,随时可获胜 |
haatz(haatz.quilibrium.com/v1) | ⚠️ 仅完整哈希 + 通过 shim /f/{username} 获取 fid | ✅ | 完整——适用于完整哈希请求 |
snap(snap.farcaster.xyz:3381,官方网关;443 会卡住) | ⚠️ 与 haatz 相同 | ✅ | 完整——适用于完整哈希请求 |
| farcaster.tv | ✅ 短哈希(前缀匹配) | ❌ 静默丢弃 | 不完整——无法识别引用 |
HTML 路径(700ms 完整性等待窗口): 完整的提供方随时可获胜;如果 farcaster.tv 获胜但无法识别引用,则会暂缓结果,直到等待窗口结束,然后返回不完整页面——这是 SWR 权衡,而 OG 元数据不受影响:标题、描述和图片从不依赖引用,因此聊天机器人的缓存嵌入卡片始终完整。不完整内容只会出现在实时浏览器中,并会自行修复:不完整页面会附带 QUOTE_FILL_SCRIPT,该脚本会请求 ?embeds=quotes(由 shim 解析、已补全引用的 SSR 片段,no-store),然后将其注入页面——与实时统计脚本遵循相同的静默降级契约。不会引入服务端缓存:最近使用徽标和签名抑制功能不受缓存键影响。
JSON 路径: 仅使用 shim(boxMs: null)——始终完整,状态严格区分(404 / 503 + Retry-After),始终遵守绝不丢弃引用的不变量。
未命中语义: shim 未命中时会立即解析为 not-found(快速返回 404);任何完整提供方未命中时(haatz/snap 原始节点返回 400)均具有权威性 ⇒ 即使已返回不完整结果,也返回 404;仅 farcaster.tv 返回 404 时不会产生 404(Cast 可能实际存在)。其他情况全部失败 ⇒ 返回仅含链接的页面(HTML)/ 503(JSON),照常采用失败开放策略。
封禁信号: 不参与竞速——/signed/{fid} 仍依赖 shim,与 Cast 内容相互独立,并受 HTML 等待窗口限制,照常采用失败开放策略。
遥测: 每次解析都会写入一条 Analytics Engine 数据点(RACE_STATS 绑定,通过 waitUntil 非阻塞执行):index = 获胜提供方(或结果类型),doubles = 总耗时(毫秒)/等待窗口/是否不完整,blob = 各提供方的 id:kind:ms 记录(因竞速取消的失败请求记为 abort,而非 error)。调整等待窗口时,可通过 AE SQL API 查询(scripts/probe/、scripts/race-stats.ts);测试和没有 executionCtx 的调用方会跳过遥测。
后续工作:引用补全也可以与 haatz/snap 竞速(/h/{fullHash} 与 castById——castId 嵌入内容中带有 fid);愿意以降低完备性换取低延迟的 ?format=json 使用方可以选择加入竞速。
竞速统计(/transparency)
竞速统计表离线填充——渲染路径不使用缓存,也不处理统计数据。bun run stats(环境变量见 .env.example)通过 SQL API 查询 Analytics Engine——按样本加权(sum(_sample_interval)、quantileExactWeighted;绝不使用原始 count())——组装带版本号的快照(v: 1;每个提供方的 wins/requests/p50/p95/errorRate,以及结果计数),并通过随附的 Upstash 风格 SDK,将其作为一个 lmdis 键(lmdis.artlu.xyz 上的 race-stats,由所有者运行、采用 Redis 风格的 KV)发布。仅在成功时覆盖;--dry 可预览。
透明度路由通过每个隔离实例的 TTL 缓存读取快照(worker/race-stats-store.ts:TTL 为 10 min,失败后退避 60s,最多提供 24h 内的最近一次成功数据,读取上限为 2s),并填充统计表、“统计截至”行以及 JSON 副本中的 racingStats——快照缺失或不可达时,静默显示“即将推出”的短横线。/race-stats.json 提供原始快照(不存在时返回 404)。SQL 方言和存储行为通过 scripts/probe/ 实时验证(AE 方言不支持 CONCAT,且仅支持 toUInt32/toUInt8 转换;SQL 错误以纯文本 422 返回;SQL API 会拒绝 Account Owned Tokens——请使用具有 Account Analytics:Read 权限的 User token)。
**中止遥测:**竞速提前结束时落败的请求会记录为 provider:abort:<ms>(而非 error),以确保公开的 errorRate 如实反映情况——700ms 截止会有意终止缓慢完成的请求;5s 硬超时仍计为错误。raceCast 会在中止后排空待处理的结算(最长 25ms),避免计时记录不完整。
客户端注册表(操作栏、上次使用徽标)
cast 页面上的“Open in …”按钮由数据注册表 worker/clients.ts 渲染——添加客户端只需更改数据。
- 架构(
ClientDefinition):id(稳定的 slug,cookie 值)、title、logoUrl(由public/logos/提供)、web(每个 cast 对应的 https 模板)、deeplink(可选的原生移动端目标——启用下文所述的点击时 scheme 尝试;缺省 ⇒ 仅限 Web)、requiresSigned(仅在已知signed:false时隐藏;未知时默认显示)。 - 模板会插入
{username}、{hash}(路由格式,短哈希或完整哈希)和{fullHash}(由 Shim 解析)。引擎还支持可选的{fid}占位符及missingVars门控(需要该值的客户端在值缺失时会被跳过)——留作未来条目使用。解析时会对值进行百分号编码,渲染时会进行 HTML 转义。 - 移动端深层链接与 Web 目标:每个客户端对应一个按钮;
href始终是webURL。通用链接在移动端已能打开已安装的应用——已逐一验证:Farcaster 的 AASA 声明了所有 farcaster.xyz 路径,Firefly 的 AASA 明确声明了/post/*(Android:已通过 assetlinks.json 确认com.farcaster.mobile和io.dimension.firefly)。结构化的deeplink: { scheme, androidPackage? }仅在移动端添加原生尝试:按钮会获得data-scheme+data-intent(intent:// 格式包含S.browser_fallback_url= Web URL),并由条件脚本在粗指针设备上拦截点击——Android 使用 intent(由 Chrome 原生回退),其他设备则尝试 scheme;若应用未打开,则回退到 Web href(约 1.8 秒,并检测页面可见性)。只有实际渲染的客户端包含deeplink时才会加载该脚本。Farcaster 当前使用farcaster://{username}/{fullHash}——已根据开源应用验证(farcasterxyz/client):应用会将farcaster://重写为https://farcaster.xyz/,并将其余部分作为 Web 路径处理;cast URL 格式为/{username}/{hash},支持完整哈希或短哈希(≥8 位十六进制);旧版/casts/:fname/:hash路由已移除。Firefly 不提供 scheme——没有任何文档记录;其 Web URL 在两个平台上都已关联应用。 - 客户端名单——全部五个客户端均已验证每个 cast 的 URL:farcaster(
/{username}/{hash})、firefly、neynar、quilcast 和 vibeshift(基于完整哈希;neynar 已对照fc-clients-cast-action的src/lib/deeplink.ts验证;quilcast 已对照 backmeupplz/quilcast 的server/index.mjs验证——/c/{fullHash},40 位十六进制,可选带0x,并已在线验证;仅限浏览器的客户端,没有原生应用;vibeshift 使用?view=cast&castHash={fullHash}&castFid={fid}——需要 cast 发布者的 fid,通过{fid}门控,因此在不含 fid 的界面中会跳过;由所有者提供并经用户验证)。 - 上次使用 cookie:点击任何带
data-client的按钮时,都会通过CLIENT_SCRIPT写入fc_client=<id>(有效期 1 年,SameSite=Lax);收到下一次请求时,worker 会读取它,并在服务端渲染(SSR)该客户端按钮上的小型“上次使用”徽标。未知的 cookie 值不会渲染(注册表白名单)。没有 cookie ⇒ 不显示徽标;没有 JS ⇒ 链接仍然可用,只是徽标不会更新。Cookie 条件 HTML:如果将来添加边缘缓存,徽标必须加入缓存键。 - Logo:托管在
public/logos/中,每个客户端对应一个静态文件——farcaster 和 quilcast 使用 SVG,firefly、neynar 和 vibeshift 使用 PNG(vibeshift:其 android-chrome 图标缩小至 64px;站点的 favicon.svg 是无关的模板默认图标,并非品牌标志)。按钮的<img>统一渲染logoUrl;不使用针对单个客户端的代码路径。
Shim → CastData 映射
各提供方返回的数据结构各不相同;cast-source.ts 会将所有数据规范为 ShimCast(Shim 的原始 Snapchain 数据结构)。随后,worker/cast-page.tsx 中的 toCastData() 会将其转换为 react-farcaster-embed 所需的格式:
timestamp是 Unix 秒数 → 乘以 1000(嵌入组件会执行new Date(ts))embeds是无类型的{url}/{castId}项 → 分别归入images/urls/casts;图片判断依据是主机为imagedelivery.net或 URL 带有图片扩展名。引用内容会通过 ShimGET /h/{fullHash}(深度为 1)补全数据,并按普通 cast 映射;补全失败时(例如 cast 已被清除),会丢弃该引用。URL 嵌入会通过openGraph渲染为链接卡片(见上文的链接预览)- 消息级数据不包含互动计数 →
replies/reactions/recasts均填充为{ count: 0 },并将tags设为[]——嵌入组件包会不做检查就读取e.recasts.count和e.tags.length,缺少这些数据时会崩溃(它也会不做检查就读取quote.author.pfp.url——切勿输出作者数据尚未补全的引用)。这些填充值会显示为硬编码的零计数,因此会从 SSR 输出中移除统计行(STATS_ROW);同一容器中的 Farcaster 徽标会保留 author从user映射而来(pfpUrl→pfp.url,bio→profile.bio.text)
Agent 约定
- 第一方代码使用 Preact,而不是 React。Vite 别名会将
react*映射到供嵌入组件库使用的 Preact。 - Cast 路由由服务端处理(Hono),而非客户端路由。
- 测试:导入
./index.ts前,先执行mock.module("server-only"|react*);按精确的 Shim URL 模拟globalThis.fetch——意外 URL 会触发错误,借此测试回退路径。 - TS worker:
jsxImportSource: preact;相对导入需要使用.tsx扩展名(nodenext);测试文件已从tsconfig.worker.json中排除。 - 密钥:
.env*/.dev.vars*已加入 Git 忽略列表。
关键文件
| 任务 | 修改文件 |
|---|---|
| Shim 请求 / 映射 / SSR 回退 | worker/cast-page.tsx |
| 提供方竞速 / cast 数据来源 | worker/cast-source.ts |
| 客户端列表 / 操作行数据 | worker/clients.ts |
| 路由 | worker/index.ts |
| 别名 | vite.config.ts、tsconfig.worker.json 的 paths |
| 着陆页 | src/App.tsx |
| 部署 / CF | wrangler.jsonc |
状态
已实现以 Shim 为首选的 SSR 数据路径,并在 shim / farcaster.tv / haatz / snap 之间进行竞速(cast-source.ts);HTML 会在 700ms 超时窗口内返回不含引用数据的胜出结果,并在客户端自动补全引用;JSON 保持 Shim 数据完整且严格。实时统计数据仍在客户端从 farcaster.tv 获取。Agent JSON 变体(?format=json)已上线。由注册表驱动的操作行已上线:最近使用徽标、Farcaster 原生深层链接(farcaster://,已验证),以及四个已验证的单 cast 客户端 URL。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。