forkcaster について
forkcasterは、Farcasterのキャスト参照を対応クライアントアプリのディープリンクに変換し、共有用ページを提供します。公開MCPエンドポイントは認証不要で、クライアントURLと作者の署名契約状態を返します。
主な機能
- 01キャストをクライアント別リンクに変換
- 02キャストページをサーバー側で描画
- 03キャスト情報をJSONで返却
- 04作者の署名契約状態を確認
活用シーン
- 01Farcasterアプリを使わずキャストを共有
- 02指定クライアント向けリンクを取得
- 03エージェント用にキャスト情報を取得
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 — エージェント向け概要
対象:コーディングエージェント。説明より事実を優先してください。アーキテクチャやコマンドが変わった場合は、このファイルを更新してください。
目的
Farcaster アプリを使わずに共有できるよう、Farcaster cast URL をミラーリングするマイクロサイトです。
- パターン:
/{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}、常時稼働用 cron を備えた、所有者が運用する Snapchain リーダー)が完全性の基準であり、唯一の禁止状態シグナル依存先(/signed/{fid})です。farcaster.tv は高速ですが、引用を認識しません。生ノードの haatz/snap.farcaster.xyz:3381は完全ですが、fid の取得には shim の/f/{username}が必要で、ハッシュは完全形式である必要があります。すべての上流呼び出しは 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)はアクション行から除外されます。チェックはフェイルオープンで、状態が不明でも何も除外しません。 - cast データソースとしての farcaster.tv は引用を認識せず(
{castId}埋め込みを削除します)、非公式です。そのため、部分データ層としてのみ競合取得に参加します(700ms の HTML ボックス、引用はクライアント側で補完)。react-farcaster-embed のデフォルトのエンドポイントも farcaster.tv です — 決して取得しないよう、必ずcastDataを渡してください。ライブ統計行(返信/リキャスト/いいね)は引き続き farcaster.tv からクライアント側で取得します — 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 |
| リンター | 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 }(少なくとも 1 つのフィールドが必要)→ 204 + Set-Cookie(fc_client);デフォルトの 5s では、delay は Cookie に含まれません。自動オープン設定の書き込み(サーバー Cookie — Safari ITP の適用除外) |
/transparency | Hono | HTML の事実ページ(what/data/tracking/domains — Domains には登録期限と RDAP 確認リンクが含まれます — および lmds スナップショットからサーバー側で入力される race-stats テーブル、「stats as of」行)、または ?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;認証なしで公開)。ツールは 1 つ、get_cast_link:cast の参照(longhash ∥ fid+shorthash ∥ username+shorthash)+ 対象クライアント → クライアント URL + 作成者の署名状態;farcaster.xyz が標準の表示先です。 |
/:username/:hash(0x…) | Hono SSR | HTML:200 の cast カード + OG/Twitter メタデータ(title = DisplayName (@handle)、description = 空白を正規化した cast テキスト、280 文字、image = 最初の画像 embed またはアバター;summary_large_image カードは画像が cast の画像 embed の場合のみ;og:site_name = forkcaster)+ レジストリに基づくクライアントアクション行(下記「クライアントレジストリ」を参照);未署名の作成者(/signed/{fid} が false):requiresSigned クライアント(farcaster)を非表示;Shim が該当 cast がないと報告した場合は 404;Shim に接続できない場合はリンクのみの 200 |
/:username/:hash?format=json(または Accept: application/json) | Hono JSON | 厳格なステータスを返すエージェント向けバリアント — 下記「エージェント向け JSON バリアント」を参照 |
| その他の存在しないパス | worker 404 / SPA フォールバックなし | not_found_handling: none |
複数ドメイン(デュアル配信): 同じ worker が forkcaster.xyz と warpcats.com に対応します(wrangler.jsonc のカスタムドメインルート)。すべてのコードはホストに依存しません(リクエストごとのオリジン、同一オリジンのフィルスクリプト、ホスト専用 Cookie)。すべてのページでプライマリドメインを正規化先とします。cast ページは PRIMARY_ORIGIN 変数から <link rel="canonical"> と og:url を出力します(未設定の場合は出力されません — デュアル配信前の workers.dev の出力を維持します)。ランディングページの canonical は index.html に埋め込まれています。レースのテレメトリには、ドメインごとの内訳を取得できるよう、リクエストホストが blob として含まれます。Cookie とバッジは設計上、ドメインごとに分かれています。
エージェント向け JSON 形式(?format=json)
公開済みです。 一度だけ解決し(Shim fetch + unfurl + quote hydration + signed check → resolveCast())、2 通りにレンダリングします(worker/cast-page.tsx の renderCastHtml() / renderCastJson())。返却形式はリクエスト元に応じて変わります。
ネゴシエーションでは明示的な指定を優先し、UA の判別は行いません。
| シグナル | レスポンス |
|---|---|
?format=json クエリパラメーター | JSON(文書化された仕様。Accept より優先) |
Accept: application/json | JSON |
それ以外(ブラウザー、*/*、チャットのリンクプレビュー用ボット) | HTML |
判断事項:
- JSON のみ — kv 形式/プレーンテキスト形式はありません(kv は情報が失われ、JSON のトークンオーバーヘッドはごくわずかです)。
- JSON では厳格なステータス、HTML では寛容なステータス(意図的な差異です):JSON では、Shim が該当する cast がないと報告した場合は 404、Shim に到達できない場合は
Retry-After: 60を付けて 503 を返します。HTML では従来どおり 404 ページ/リンクのみの 200 ページを返します。エージェントは、レンダリングされたフォールバックよりステータスコードを適切に処理できます。 - 利用可能性に応じたペイロード — データが存在する場合にのみフィールドを含めます:author、ISO 8601 タイムスタンプ、テキスト、画像 embed URL、URL embed(unfurl に成功した場合はタイトルを追加し、それ以外は URL のみ)、quote embed、canonical な farcaster.xyz + Firefly リンク。
- 引用はポインターとして保持し、決して省略しません:quote の hydration に成功した場合は forkcaster URL(hydration 済み cast の username が必要)を返します。hydration に失敗した場合は Firefly の full-hash URL を返します(quote embed には
{fid, hash}が含まれますが、username はありません)。HTML のレンダリング上の制約(未保護のauthor.pfp.url)は JSON には適用されません。hydration に失敗しても、エージェントが取得できる URL にフォールバックします。 - Ban シグナル:
signed: falseの場合は短い注記フィールドを出力します。確認が不明または失敗した場合は、何も出力せず fail-open とします(HTML のアクション行と同じ fail-open セマンティクスです)。 - 発見可能性: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 エンドポイント。公開されており、認証不要、ステートレスです。ツールは 1 つ:get_cast_link — Farcaster cast(40 桁の完全な hex ハッシュ、または fid/username と短いハッシュ)を解決し、対応する任意のクライアント向けディープリンク URL と、author の署名済み契約のステータスを返します。接続設定:worker/mcp.ts(SDK)+ worker/cast-ref.ts(zod コントラクト、shim に直接解決)。
レジストリへの登録情報:server.json(リポジトリのルート、公式レジストリスキーマ 2025-12-11、名前 xyz.forkcaster/mcp)。公開するには、公式 publisher CLI(GitHub リリースのバイナリ)をインストールします。⚠️ 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 の検索では、4 つのプロバイダーを競合させます(worker/cast-source.ts、rpc-racer パターン:プロバイダーごとに中断処理と 5 秒の上限を設けて並列実行し、最初に検証済みとなった結果を採用して、残りは中断します。採用した結果が要求されたハッシュを解決できない場合は、エラーに降格されます)。
十分性マトリクス(2026 年 10 月に測定)。内部の ShimCast 契約(テキスト、ハッシュ、Unix 秒単位の ts、raw {url}/{castId} の埋め込み、インラインのユーザーデータ)を基準としています。
| Provider | (username, hash) を解決できるか? | 引用({castId}) | 評価 |
|---|---|---|---|
| shim | ✅ short と full | ✅ | 完全 — 正規のプロバイダーで、いつでも優先されます |
haatz(haatz.quilibrium.com/v1) | ⚠️ full hash のみ + shim /f/{username} 経由の fid | ✅ | 完全 — full-hash リクエスト向け |
snap(snap.farcaster.xyz:3381、公式ゲートウェイ。443 はハングします) | ⚠️ haatz と同じ | ✅ | 完全 — full-hash リクエスト向け |
| farcaster.tv | ✅ short(プレフィックス一致) | ❌ 黙って欠落 | 部分的 — 引用を認識しません |
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 の raw ノードの 400)で見つからない場合は、権威ある結果として扱われます。部分的な結果が届いていても 404 を返します。farcaster.tv の 404 だけでは、Cast が存在する可能性があるため、404 にはなりません。それ以外のすべてが失敗した場合は、通常どおりフェイルオープンし、リンクのみのページ(HTML)/503(JSON)を返します。
禁止シグナル: 競合の対象外です。/signed/{fid} は引き続き shim への依存関係で、Cast コンテンツとは独立しており、通常どおりフェイルオープンしながら HTML の待機枠内で時間制限を設けます。
テレメトリー: 解決ごとに 1 件の Analytics Engine データポイントを書き込みます(RACE_STATS バインディング、waitUntil による非ブロッキング)。index は勝者のプロバイダー(または結果の種類)、doubles は合計 ms/待機枠/部分フラグ、blob はプロバイダーごとの id:kind:ms 行です(競合の中断で失敗したプロバイダーは error ではなく abort を記録します)。待機枠の調整時は 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 に従います)は、Analytics Engine に SQL API 経由でクエリを実行し、サンプル加重方式(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 分、失敗時のバックオフは 60 秒、最終正常データは ≤24 時間まで提供、読み取り上限は 2 秒)を通じてスナップショットを読み込み、テーブル、「stats as of」の行、および JSON 版の racingStats を埋めます。スナップショットが存在しないか到達できない場合は、何も表示せず「coming soon」のダッシュを表示します。/race-stats.json は生のスナップショットを返します(存在しない場合は 404)。SQL の方言とストアの動作は scripts/probe/ で実環境検証されています(AE の方言には CONCAT がなく、変換には toUInt32/toUInt8 のみ使用できます。SQL エラーはプレーンテキストの 422 として返されます。SQL API は Account Owned Token を拒否するため、Account Analytics:Read 権限を持つ User token を使用してください)。
中断テレメトリ: レースのカットで敗れた処理は error ではなく provider:abort:<ms> として記録されるため、公開される errorRate の値は正確に保たれます。700ms の上限は、遅い処理の完了を意図的に打ち切ります。5s のハードタイムアウトは引き続きエラーとしてカウントされます。raceCast は中断後も保留中の settle を(最大 25ms)排出するため、タイミングの行が未完了のまま取りこぼされることはありません。
クライアントレジストリ(アクション行、最終使用バッジ)
キャストページの「Open in …」ピルは、worker/clients.ts のデータレジストリから描画されます。クライアントの追加はデータの変更だけで済みます。
- スキーマ(
ClientDefinition):id(安定したスラッグ、Cookie の値)、title、logoUrl(public/logos/から配信)、web(キャストごとの https テンプレート)、deeplink(省略可能なネイティブモバイル向けターゲット。指定すると、後述のクリック時のスキーム試行が有効になります。省略時はウェブのみ)、requiresSigned(signed:falseが判明している場合にのみ非表示。不明な場合は表示します)。 - テンプレートでは
{username}、{hash}(ルート形式、短縮または完全)、{fullHash}(Shim による解決)を補間します。エンジンは任意の{fid}プレースホルダーとmissingVarsゲートにも対応しており、それを必要とするクライアントは値がない場合にスキップされます。これは将来のエントリー用です。値は解決時にパーセントエンコードされ、描画時に HTML エスケープされます。 - モバイルのディープリンクとウェブターゲット:クライアントごとにピルは 1 つで、
hrefは常にwebURL です。ユニバーサルリンクにより、モバイルではインストール済みアプリがすでに開きます(クライアントごとに検証済み:Farcaster の AASA はすべての farcaster.xyz パスを対象とし、Firefly の AASA は/post/*を明示的に対象とします(Android:com.farcaster.mobileとio.dimension.fireflyは assetlinks.json で確認済み)。構造化されたdeeplink: { scheme, androidPackage? }を指定すると、モバイルでのみネイティブ起動を試みます。ピルにはdata-schemeとdata-intentが追加され(intent:// 形式にはS.browser_fallback_urlとしてウェブ URL が含まれます)、条件付きスクリプトが coarse-pointer デバイスでのクリックを横取りします。Android では intent を使用します(Chrome ネイティブのフォールバック)。それ以外ではスキームを試し、アプリが開かなかった場合(約 1.8 秒、可視状態を考慮)はウェブの href にフォールバックします。スクリプトが配信されるのは、描画されたクライアントにdeeplinkがある場合のみです。Farcaster では現在farcaster://{username}/{fullHash}を配信しています — オープンソースアプリで確認済みです(farcasterxyz/client):アプリはfarcaster://をhttps://farcaster.xyz/に書き換え、残りをウェブパスとしてルーティングします。キャスト URL は/{username}/{hash}で、完全または短縮(8 桁以上の 16 進数)のハッシュを受け付けます。旧/casts/:fname/:hashルートは廃止されています。Firefly ではスキームを配信していません — どこにも記載がありません。ウェブ URL は両プラットフォームですでにアプリリンクされています。 - クライアント一覧 — 5 つのクライアントすべてでキャストごとの 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}、任意の0xを含む 40 桁の 16 進数、実環境で確認済み。ブラウザー専用クライアントで、ネイティブアプリはありません。vibeshift は?view=cast&castHash={fullHash}&castFid={fid}— キャストの発信者の fid が必要で、{fid}ゲートにより fid のない画面ではスキップされます。オーナーから提供され、ユーザーが確認済みです)。 - 最終使用 Cookie:
data-clientピルをクリックすると、CLIENT_SCRIPTによってfc_client=<id>(1 年間、SameSite=Lax)が書き込まれます。次のリクエストで Worker がこれを読み取り、そのクライアントのピルに小さな「最終使用」バッジを SSR で表示します。不明な Cookie 値では何も表示されません(レジストリの許可リストを使用)。Cookie がない場合はバッジも表示されません。JS がなくてもリンクは機能し、バッジが更新されないだけです。Cookie によって変わる HTML です。エッジキャッシュを導入する場合は、バッジをキャッシュキーに含める必要があります。 - ロゴ:
public/logos/にセルフホストされており、クライアントごとに静的ファイルが 1 つあります — 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 倍します(embed はnew Date(ts)を実行します)embedsは型のない{url}/{castId}の項目なので、images/urls/castsに分類します。画像の判定基準は、ホストがimagedelivery.netであるか、画像拡張子が付いていることです。引用は Shim のGET /h/{fullHash}(深度 1)を通じてハイドレーションし、通常の cast と同様にマッピングします。ハイドレーションに失敗した場合(例:削除済みの cast)は引用を破棄します。URL の埋め込みはopenGraphを使ってリンクカードとしてレンダリングされます(上記の unfurl を参照)。- メッセージレベルのデータにはエンゲージメント数がありません →
replies/reactions/recastsは{ count: 0 }、tagsは[]で補います。embed バンドルはe.recasts.countとe.tags.lengthをガードなしで読み取り、これらがないとクラッシュします(また、quote.author.pfp.urlもガードなしで読み取るため、作成者のハイドレーションなしで引用を出力しないでください)。この補完データではカウンターがゼロで表示されるため、SSR 出力から統計行(STATS_ROW)を取り除きます。同じコンテナー内の Farcaster ロゴは残します。 authorはuserからマッピングします(pfpUrl→pfp.url、bio→profile.bio.text)
エージェント向けの規約
- ファーストパーティーコードには React ではなく Preact を使います。Vite のエイリアスにより、埋め込みライブラリの
react*は Preact に割り当てられます。 - **Cast のルーティングはサーバー側(Hono)**で行い、クライアント側のルーティングは使いません。
- テスト:
./index.tsをインポートする前にmock.module("server-only"|react*)を実行します。globalThis.fetchは Shim の URL と完全一致するようにモックします。想定外の URL では例外が発生し、これによりフォールバック経路をテストします。 - TS worker:
jsxImportSource: preactを使います。相対インポートには.tsx拡張子が必要です(nodenext)。テストはtsconfig.worker.jsonの対象外です。 - シークレット:
.env*/.dev.vars*は gitignore 対象です。
主なファイル
| タスク | 対象 |
|---|---|
| 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 から取得します。エージェント向け JSON バリアント(?format=json)をリリース済みです。レジストリー駆動のアクション行をリリース済みです。最終使用バッジ、Farcaster ネイティブのディープリンク(farcaster://、検証済み)、cast ごとに検証済みの 4 つのクライアント URL が含まれます。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。