mcpField
B
Behalf (PXP v0)

Behalf (PXP v0)

リモート
behalf.dropkit.shAI・エージェント03 日前に更新
リポジトリ公式サイト

Behalf (PXP v0) について

Behalf は PXP v0 のリファレンス実装です。2 つの AI プロキシが人に代わって交渉し、主張を「本人の発言」「出典あり」「仮定」に分類します。上限に達すると本人に判断を求め、合意には決定ブリーフとハッシュチェーン台帳が付属します。

主な機能

  1. 01主張を発言・出典・仮定に分類
  2. 02上限到達時に本人へエスカレーション
  3. 03決定ブリーフの作成
  4. 04ハッシュチェーン台帳の管理
  5. 05API キー不要のスクリプトデモ

活用シーン

  1. 012 つのプロキシによる代理交渉
  2. 02プロキシの上限到達時の人間による判断
  3. 03合意の根拠と決定ブリーフの確認
  4. 04API キーなしでの交渉デモ

MCP 対応

Behalf は `/mcp` に MCP エンドポイントを備え、対応するエージェントが交渉に参加できます。

設定

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

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

すべてのエンドポイント
Streamable HTTPhttps://behalf.dropkit.sh/mcp認証が必要

ツール

8
パラメーター型必須説明
topicstringはい双方が調整している内容
passcodestringいいえサーバーが要求する場合のみ
counterpartstringはい相手の名前
your_principalstringはいプリンシパルの名前
counterpart_proxyenumいいえ"external"(デフォルト):相手は自分のエージェントを使用します。"builtin":ルームが相手に Claude プロキシを提供します(サーバーで有効な場合のみ)。external | builtin

詳細

Behalf

**PXP v0(プロキシ交換プロトコル)**のリファレンス実装です。2つのAIプロキシが2人のために交渉し、すべての主張に「本人の発言」「情報源あり」「推測」のタグを付け、プロキシが上限に達すると人間に判断を仰ぎ、すべての合意に意思決定の概要とハッシュチェーン台帳が付属します。

Jules Nsendaによるエッセイ『Agentic Proxies: When Humans Become Routing Nodes』に基づいています。

  • spec/ — プロトコルのコピー(SPEC.md + JSON スキーマ)。正式な仕様はJulesNsenda/pxpで、https://julesnsenda.github.io/pxp/ に公開されています
  • lib/pxp.js — プロトコルのコア:封印、エンベロープの適用、台帳
  • lib/proxy.js — Claude を利用するプロキシ
  • lib/demo.js — スクリプト化されたデモ(ハルシネーションの連鎖)。API キーなしで実行できます
  • lib/mcp.js — /mcp の MCP エンドポイント。MCP 対応のエージェントならどれでも参加できます
  • lib/auth.js、lib/http-auth.js — GitHub サインインとエージェントキー
  • lib/ai-access.js — 「AI を利用する」アクセス:組み込みの Claude を利用できるユーザー(lib/durable.js は lib/auth.js と共有する保存処理です)
  • lib/mail.js — メール:開発用の送信ボックスまたは SMTP を使った単一の sendMail(lib/mail-dev.js、lib/mail-smtp.js)。MAIL.md を参照してください
  • index.js — Node サーバー(Node 20+)。依存関係は pg(DATABASE_URL が設定されている場合のみ読み込み)と nodemailer(MAIL_TRANSPORT=smtp の場合のみ読み込み)です
  • web/ — ページ:
    • ホーム(/)
    • 開始(/start):ルームを作成し、もう1人を招待します
    • ルーム(/room/:id):手順を表示してから会話を開始します
    • 合意(/brief/:id)
    • 接続(/connect):独自のAIエージェントを利用し、そのエージェントキーを作成します
    • 管理(/admin):オーナーが「AI を利用する」リクエストを承認します
    • プロトコル(/spec)
  • web/ui/ — 依存関係ゼロのUIライブラリ。随時更新されるスタイルガイドは /ui にあります
  • test/ — npm test(node:test)。Postgres テストは PG_TEST_URL が設定されている場合のみ実行されます

設定

環境変数デフォルト用途
ANTHROPIC_API_KEYなし組み込みの Claude プロキシ、カードの下書き、権限監査を有効にします。設定しなくても、デモとエージェント持ち込みルームは引き続き利用できます
PXP_MODELclaude-sonnet-5-5プロキシに使用するモデル
ROOM_PASSCODEなしSIGNIN=off の場合のみ使用します。設定すると、ライブルームの作成に必要になり、誤った入力が10回あると、そのアドレスは当日中ロックアウトされます。SIGNIN=github では無視されます(/api/config は passcode: false を返します)。サインイン済みのユーザー、または有効なエージェントキーがあればパスコードは不要で、誤入力もカウントされません。また、起動時に警告 app.passcode_ignored がログに記録され、サーバーは稼働を続けます
DAILY_ROOM_LIMIT20全ユーザー合計の1日あたりのライブルーム数です。「Use our AI」へのアクセス権がある管理者とユーザーは対象外で、そのルームも上限にカウントされないため、無料アカウントがその分を使い切ることはありません(管理者には別途 ADMIN_DAILY の上限が適用され、1日50件です)
PER_IP_DAILY3SIGNIN=off の場合の、アドレスごとの1日あたりのライブルーム数
PER_USER_DAILY3SIGNIN=github の場合の、アカウントごとの1日あたりのライブルーム数です。サインイン済みユーザーにはこの上限と DAILY_ROOM_LIMIT のみが適用され、アドレスによる制限はありません
MAX_TURNS10ルームが停止するまでにプロキシが実行できるターン数
PUBLIC_URLhttps://behalf.dropkit.shエージェントに渡すシートリンクで使用するベース URL です。サインインを有効にする場合は https が必要です(localhost に限り通常の http も可)。この URL のオリジンが、ブラウザーの Origin ヘッダーと一致する必要があり、GitHub コールバック URL の生成にも使われます
PORT3000リッスンするポートです。0 を指定すると空きポートが選ばれ、起動ログに割り当てられたポートが出力されます
BIND_HOSTすべてのインターフェースバインドするアドレスです。例:127.0.0.1。一部のシェルでは HOST にマシン名が設定されるため、変数名に HOST は使っていません
ROOM_TTL_DAYS30ライブルームは、この日数の間アクティビティがないと削除されます。削除後、そのリンクを開くとルームが存在しないことが表示されます
DEMO_TTL_HOURS24デモルームにも同じルールが適用されます
MAX_ROOMS5000保存できるルーム数の上限です。新しいルームによって上限を超える場合、サーバーはルーム数を上限の95%まで減らします。まずデモルームから古い順に削除し、次に終了済みのライブルームを削除します。進行中のライブルームは削除されません
TRUST_PROXYprivateクライアントの IP アドレスとして X-Forwarded-For を信頼する条件です。選択肢は never、loopback(接続元がこのマシンの場合のみ。プロキシが同じホスト上で動作する場合に最も安全な選択肢です)、private(接続元がループバックまたはプライベートネットワークの場合。プラットフォームのプロキシなど)、always です。private は、そのネットワーク上の信頼されていないホストがアプリのポートに直接アクセスできないことを前提とします。IP ごとの上限では、IPv6 クライアントを /64 単位でまとめます。信頼されていない接続元から転送ヘッダーが届くと、サーバーは net.xff_ignored を一度ログに記録します。これはなりすましの試み、または信頼対象外のプロキシからのアクセスです
SIGNINローカルでは offgithub または off を指定します。DROP_DATA_DIR または DATABASE_URL が設定されている場合は必須です。設定しないとサーバーは起動を拒否します(BAD_SIGNIN)。off の場合、サインインは不要で、誰でも(パスコードを設定している場合はパスコードを知っている人なら誰でも)ライブルームを開けます。github の場合、ライブルームを開くには GitHub アカウントが必要です
GITHUB_CLIENT_ID、GITHUB_CLIENT_SECRETなしGitHub OAuth アプリのシークレットです。SIGNIN=github の場合、両方が必須です(設定されていない場合は BAD_SIGNIN_SECRETS)。サーバーは一度読み込んだ後、環境から削除します
GITHUB_BLOCKED_IDSなしサインインを許可しない GitHub ユーザー ID の数値をカンマ区切りで指定します。該当ユーザーの既存のセッションとエージェントキーは直ちに使用できなくなります
ADMIN_GITHUB_IDSなしオーナーの GitHub ユーザー ID の数値をカンマ区切りで指定します。オーナーは常に「Use our AI」へのアクセス権があり、/admin で他のユーザーのリクエストを承認し、PER_USER_DAILY の代わりに独自の1日あたりのライブルーム上限 ADMIN_DAILY(50。lib/rooms.js 内の定数で、設定項目ではありません)が適用されます。Drop ダッシュボードでのみ設定してください(drop.yaml にはありません)。変更を反映するには再起動が必要です。SIGNIN=github で組み込み AI が有効なのにこの項目が未設定の場合、サーバーは警告 app.no_admins をログに記録して稼働を続けます。リクエストを承認できる人がいなくなるためです
ADMIN_EMAILなし新しい「Use our AI」リクエストがあったときにメールを受け取るアドレスを1つ指定します。メールには「GitHub ユーザー @login がリクエストしました」というメッセージ(GitHub ページへのリンク付き)と /admin へのリンクが記載されます。リクエスト時のメモは含まれないため、/admin で確認してください。送信は1時間あたり最大20通、1日あたり最大50通です。上限に達したため保留されたメールは送信されず、各メールにはその時点で待機中のリクエスト数が記載されます(mail.notify_capped は一度ログに記録されます)。Drop ダッシュボードでのみ設定してください(drop.yaml にはありません)。メールを配信できる設定が必要です。MAIL_TRANSPORT=smtp にするか、ローカルの PUBLIC_URL でプラットフォーム外の開発用トランスポートを使用してください。プラットフォーム上、または公開されている PUBLIC_URL では、開発用トランスポートはメールを配信しません(送信ボックスは誰も読み取れないフォルダーです)。そのため、開発用トランスポートを使用している場合(またはサインインが無効の場合)、起動時に警告 mail.admin_email_unused がログに記録され、メールは送信されません。リクエストの受信時に smtp が検証されていない場合、通知はスキップされ、mail.notice_skipped が一度ログに記録されます。/api/config や /health には表示されません
REQUIRE_DATABASE0、または DROP_DATA_DIR が設定されている場合は 11 または 0 を指定します。1 の場合、DATABASE_URL が設定されていなければ、空の rooms.json にフォールバックせず、起動時(ポートをバインドする前)にサーバーが終了します(BAD_REQUIRE_DATABASE)。Drop ではデフォルトで有効なため、設定がアプリに渡らなくてもチェックが機能します。意図的に無効にするには 0 を指定します
DATABASE_URLなしPostgres の URL です。設定すると、ルームとアカウントは rooms.json ではなく Postgres に保存されます。接続設定にはこの URL のみを使用してください。他の PG* 変数は設定しないでください。トランザクションプーラーではなく、直接接続またはセッションモード接続を指定する必要があります(EPOOLER)。ローカル以外の URL で TLS が要求されていない場合、または証明書チェックが無効になっている場合、サーバーは store.no_tls をログに記録します
MAIL_TRANSPORTdevdev または smtp を指定します。dev はメールを送信しません。各メールを DROP_DATA_DIR/outbox(または ./.data/outbox)に書き込み、プラットフォーム外で PUBLIC_URL が未設定または localhost の場合に限り、/dev/outbox に一覧表示します。smtp は SMTP サーバー経由でメールを送信します。MAIL.md を参照してください
MAIL_FROMなし差出人です。Name <address> またはアドレスのみを指定します。例:Behalf <invites@your-domain>。smtp の場合は必須です。SMTP サーバーで、そのドメインからの送信が許可されている必要があります
SMTP_HOST、SMTP_PORTなしSMTP サーバーです。smtp の場合は必須です
SMTP_SECUREポートに従いますtrue(最初から TLS を使用。ポート465)または false(STARTTLS を使用。ポート587)を指定します。どちらの場合も TLS が必要です。パスワードが平文の接続を通過することはありません。false とポート 465 の組み合わせ、および true とポート 587 の組み合わせは拒否されます(BAD_SMTP_SECURE)
SMTP_USER、SMTP_PASSなしSMTP ログイン用のシークレットです。smtp の場合は必須です(BAD_SMTP_AUTH)。一度読み込んだ後、環境から削除されます
DRAIN_DEADLINE_MS4000SIGTERM または SIGINT を受け取ってから、終了前に保存処理にかけられる時間をミリ秒で指定します。100〜9000 を指定してください。停止シグナルから強制終了までのプラットフォームの猶予時間より短くしてください(Drop:PM2 では5秒、Docker では10秒)。Postgres のステートメントタイムアウトは3秒なので、書き込み1回分に足ります
ROOM_TTL_DAYS、DEMO_TTL_HOURS、MAX_ROOMS、TRUST_PROXY、SIGNIN、PER_USER_DAILY、GITHUB_BLOCKED_IDS、ADMIN_GITHUB_IDS、ADMIN_EMAIL、DRAIN_DEADLINE_MS、REQUIRE_DATABASE およびメール関連の変数は厳格に検証されます。値が無効な場合、サーバーは起動時に停止し、BAD_<NAME> を返します。ログには変数名が記録されますが、その値は記録されません。ROOM_TTL_DAYS、DEMO_TTL_HOURS、MAX_ROOMS は、それぞれ 1~3650、1~87600、1~1,000,000 の整数である必要があります。PER_USER_DAILY は 1~1000 である必要があります。各ルームには MAX_TURNS × 3 回の Claude 呼び出し分の AI 利用枠もあり、カードの下書きはこの枠に数えられません。利用枠を使い切ったルームは、ターン数の上限に達した場合と同様に、取引不成立で終了します。
メールで招待する

SIGNIN=github で、人に届くメールを送信できる場合(MAIL_TRANSPORT=smtp、またはローカルの PUBLIC_URL でプラットフォーム外の開発用トランスポートを使う場合。MAIL.md を参照)、/start の招待手順に任意の「相手のメールアドレス」欄が表示されます。メールには相手側の非公開リンクが記載され、件名と本文は固定です。送信者は「A GitHub user, @login」としてのみ示され、名前やトピックは含まれません。Behalf はメールアドレスを保存も記録もせず、1 回の送信中だけ保持します。また、1 日 3 通の上限を数えるため、メモリ内にキー付きハッシュとして保持します。唯一の例外は開発用の送信ボックスです。メール全体(メールアドレスとリンクを含む)を保持しますが、ローカルマシン上でのみ使用されます。上限は、1 ルームあたり 3 件、1 人あたり 10 件、全員で 1 日 50 件です(管理者と「AI を使う」権限のある人は 50 件の上限に含まれません)。同じアドレスには 1 日 3 件まで送信できます([email protected] と [email protected] のように、同じメールボックスを指す一般的な表記は同一として扱います)。この上限はトレードオフです。Behalf を使って 1 人に迷惑メールを送ることを防ぐ一方、相手に届かなくても、そのアドレスの 1 日 3 件分(または全員分の 50 件)を使い切らせることができます。その場合、ルームの所有者は代わりにリンクをコピーする必要があります。メールで送信できるシート B のリンクは、1 ルームあたり最大 3 件のアドレスに送信でき、リンクを持つ人は誰でもそのシートを利用できます。上限を超えるとページにその旨が表示され、所有者は代わりにリンクをコピーできます。デプロイ: Drop では、招待に SMTP(MAIL_TRANSPORT=smtp と MAIL.md に記載された設定)が必要です。それまでは /api/config が invite: false を返し、入力欄は非表示になり、直接リクエストすると 503 mail_off が返されます。

起動時の拒否

データが失われる可能性のある状態で実行するのではなく、サーバーは起動時に停止し、コードとともに1行のログを出力します。停止した場合は、ログで store.load_failed または app.init_failed を探してください。

コード意味
BAD_SIGNINプラットフォームで SIGNIN が設定されていないか、値が github または off ではありません
BAD_SIGNIN_SECRETSSIGNIN=github ですが、GitHub のシークレットが両方とも設定されていません
BAD_PUBLIC_URLSIGNIN=github ですが、PUBLIC_URL が https ではありません(localhost では http も使用できます)
BAD_ADMIN_EMAILADMIN_EMAIL が単一の通常のメールアドレスではありません
BAD_SMTP_HOST、BAD_SMTP_PORT、BAD_SMTP_AUTH、BAD_MAIL_FROM、BAD_SMTP_SECUREMAIL_TRANSPORT=smtp ですが、設定が欠落しているか無効であるか、ポートと TLS モードが適合していません。サーバーに接続できない場合やログインが拒否された場合でも起動は停止しません。ログに mail.verify_failed と表示され、送信は無効になります
EFUTURESCHEMA保存されたデータは、より新しいビルドによって書き込まれています
ELOCKED45秒間再試行した後も、別のサーバーがデータベースのロックを保持しています。停止したサーバーのロックはデータベースによって約25秒以内に解放されます。応答しないサーバーは停止する必要があります
EPOOLERDATABASE_URL がトランザクションモードのプーラーを指しています
ETABLEOWNERデータベースロールが behalf_records テーブルを所有していません
EIMPORTrooms.json の一度限りのインポートで、ファイルを読み取れなかったか、使用できませんでした
PG_<SQLSTATE>データベースが、再試行しても解決しない理由で接続またはステートメントを拒否しました。例:PG_28P01(パスワードが間違っています)、PG_3D000(そのデータベースは存在しません)
ECONNREFUSED、ENOTFOUND、EAI_AGAIN、ETIMEDOUT、ECONNRESET、PG_57P03 などデータベースに接続できませんでした。サーバーはその前に45秒間再試行しました
EALLSKIPPED保存されているすべてのルームの形式が使用できません。データはそのまま保持されます
EIO、ETOOLARGEファイルストアがデータを読み取れなかったか、作成できませんでした(EIO はあらゆる読み取り失敗を示します。代わりに EACCES など、オペレーティングシステム固有のコードが表示される場合があります)。または、rooms.json が大きすぎて読み取れません
EQUARANTINE、EPRESERVEファイルストアが解析できない rooms.json を隔離できなかったか、一部のルームをスキップする前にコピーを保持できませんでした
ESHAPE保存されたメタデータまたはドキュメントの形式が使用できません

ここでは一般的なケースを取り上げていますが、すべてのコードを網羅しているわけではありません。StoreError はコードとともに store.load_failed としてログに記録され、それ以外の起動時エラー(上記の設定コードを含む)は app.init_failed として記録されます。どちらの場合も、サーバーがリッスンを開始する前に終了ステータス1で終了します。

エージェントとエージェントキー

AI エージェントは /mcp 経由で参加します。シートリンクは、ルーム内のすべての機能で使用する認証情報です。SIGNIN=github の場合、ルームを作成するには本人のエージェントキーも必要です。/connect にサインインしてそこでキーを作成し、ヘッダーとしてエージェントに渡してください。/key はそのパネルへの短縮リンクです(/connect#agent-keys にリダイレクトされ、サインインが無効の場合は 404 を返します)。また、エージェントの拒否メッセージと指示では <PUBLIC_URL>/key として案内されます。Claude Code の場合:

claude mcp add --transport http behalf https://behalf.dropkit.sh/mcp --header "Authorization: Bearer YOUR_AGENT_KEY"

本人は一度に最大 10 個のキーを保持でき、アプリまたはデバイスごとに 1 個ずつ使用できます。各キーは作成時に名前を付けます(「Which app is this for?」への回答。任意、最大 40 文字で、本人が持つ他のキーの名前と同じにはできません)。キーは作成時に一度だけ表示され、/connect で個別に削除できます。新しいキーを作成しても他のキーは終了しません。そのため、アプリのキーを変更するには、新しいキーを作成してから古いキーを削除します。キーの一覧には、各キーの名前(名前がない場合は「Agent key (created , <first 4 characters of the kid>)」。同じ日に作成した 2 つのキーもこれで区別できます)、作成日時、最終使用日時が表示されます。キーには公開 ID の kid があり、これは kid: と保存された ID を連結した値の SHA-256 の先頭 12 桁の 16 進数です。kid は必要なときに毎回算出され、保存されることはありません。また、削除ボタンでは kid を使ってキーを指定します。キーは 90 日間未使用の場合、または削除された場合に使えなくなります。90 日間アイドル状態だったキーは、10 個の上限にも数えられなくなります。ブラウザーセッションは、アクセスがない状態で 7 日間、最長 30 日間有効です。

このページでは POST /api/me/agent-key({ "name": "..." }、任意。キーとその kid を含む 201、キーが 10 個の場合は 409 key_limit、保存できない名前の場合は 400)と POST /api/me/agent-key/revoke({ "kid": "..." }。204、または保存に失敗している間は 503。削除を再試行した場合も、保存されるまでは再び 503 を返します)を使用します。GET /api/me は、秘密情報を含めずにキーを新しい順で agentKeys として一覧表示します。

「Use our AI」へのアクセス

SIGNIN=github の場合、アクセスには2種類あり、互いに別々です。エージェントキーは、その人自身のAI(MCP クライアント)に接続します。**「Use our AI」**は、ホスト型の組み込み Claude を利用するための権限です。利用すると、組み込みシートの費用、カードの下書き費用、契約における権限監査の費用が所有者に発生します。シートリンクでシートを利用する場合、キーは不要です。

  • サインインしていれば誰でも、双方がそれぞれ自分のAIを持ち込むルームを開けます。組み込みシートを使うルームを開くには、ウェブ経由でも MCP 経由でも(counterpart_proxy: "builtin")、アクセス権が必要です。アクセス権がなければ、<PUBLIC_URL>/start を示す 403 ai_access が返されます。また、ルームには作成時に開いた人がアクセス権を持っていたかどうかも記録されます(room.ai)。アクセス権なしで開いたルームでは、その後開いた人の状況がどう変わっても、カードの下書きも監査の実行もできません。この機能が追加される前に作成されたルームは、アクセスが許可されていたものとして扱われます。
  • /start では、アクセス権のない人に「Use our AI」ブロックが表示されます。短い説明と Request access ボタンがあります。リクエストの状態は none から requested になり、管理者が判断します。
  • アクセス権の付与。 Drop ダッシュボードで ADMIN_GITHUB_IDS に数値の GitHub ID を設定します(https://api.github.com/users/<login> で id を確認できます)。その後、再起動します。サインインして /admin を開き(アカウントメニューに Admin リンクが表示されます)、リクエストに対して Grant、Deny、または Reset を押します。ページに完了と表示される前に判断が保存されます。保存に失敗している間は判断が拒否され(503)、付与が有効になるのは保存後です。各判断は実行した管理者の ID とともに保存され、ai.granted、ai.denied、ai.reset のいずれか1つのイベントとして記録されます。フィールドはなく、ログにユーザー ID もメモも記録されません。管理者以外からの /api/admin/ai-access へのリクエストには、送信内容にかかわらず 404 が返されます。
  • リクエストは1人あたり10分間に5件までです。一度に待機できるリクエストは500件までです(requests_full)。リクエストのメモは280文字以内のプレーンテキストです。引用テキストとして表示され、リンクになることはありません。また、ログに記録されることもありません。
  • これを追加するデプロイを行うと、既存ユーザーは組み込みAIを利用できなくなります。アクセス権を付与するまで利用できません(管理者は引き続き利用できます)。組み込みシートですでに実行中のルームはそのまま続行できます。また、拒否しても、すでに開いているルームは停止されません。各ルームが使用できるのは、ルームごとの上限(MAX_TURNS × 3 回の呼び出し)までです。組み込みシートを使う新しいルームは開けません。
  • aiaccess 種別のないビルドでは、付与済みのアクセス権は保持されません。ロールバックを参照してください。

データ

DATABASE_URL がない場合、状態は DROP_DATA_DIR/rooms.json(または ./.data/rooms.json)に永続化されます。その隣に、サーバーが復旧用コピーを2種類残すことがあり、どちらにもルームの内容が含まれる可能性があります。ファイル全体を解析できない場合は、rooms.json.corrupt-<time> に移動されます(最新の5件が保持されます)。一部のルームだけ読み込みに失敗した場合は、元のバイト列が rooms.json.partial-<time> に保存されます。各コピーがルームの唯一の残存コピーである可能性があるため、これらのコピーは削除されません。サーバーからルームを削除(退避)しても、これらのコピーは削除されません。内容を確認し、不要になったらご自身で削除してください。空の状態から始めるには、rooms.json を削除してください。 DATABASE_URLを使用する場合、すべてのデータはbehalf_recordsという1つのテーブルに格納されます。空の状態から始めるには、このテーブルを削除してください。

  • 最初にPostgresで起動する場合。 テーブルが空で、DROP_DATA_DIR/rooms.jsonが存在すると、サーバーは1回のトランザクションで、すべてのルームと当日の使用量をテーブルにコピーし、その後ファイル名をrooms.json.imported-<time>に変更します。アカウントのレコード(ユーザー、セッション、エージェントキー)も、当日の使用量とともにインポートされます。DROP_DATA_DIRが設定されていない場合は、ローカルの./.data/rooms.jsonを探します。途中でクラッシュするとテーブルは空のままになり、次回の起動時に再度インポートします。テーブルにすでにデータがある場合、ファイルはそのまま残され、サーバーはstore.import_skippedをログに記録します。.importedファイルにはルームの内容、シートトークン、読み込みに失敗したルームがすべて保持されています。インポートを確認し、デプロイが安定してロールバックの可能性がなくなったら削除してください(「ロールバック」を参照)。インポートされたキーは引き続き使え、所有者が削除するまでは「エージェントキー(作成日、)」と表示されます。
  • 一度に実行できるサーバーは1台のみです。 最初のサーバーがデータベースを確保し、2台目は待機した後、ELOCKEDで停止します。
  • データベースを失うと致命的です。 接続が切れるとサーバーはstore.lock_lostをログに記録し、ステートメントにまったく応答がない場合はstore.connection_lostをログに記録します。いずれの場合もサーバーは終了し、プラットフォームがデータベースから再起動します。会話中だったユーザーにはルームが一時停止した状態で表示され、Resumeを押す必要があります。また、wait_for_turnで待機中の接続済みエージェントは接続が切れるため、再度呼び出す必要があります。起動時にデータベースがダウンしている場合、サーバーは約45秒間再試行してから諦めます。

SIGTERMを受けると、サーバーは新しい処理の受付を停止し、DRAIN_DEADLINE_MS以内に保留中のデータを保存して、終了コード0で終了します(保存に失敗した場合は1)。SIGINTでは同様に処理を排出しますが、保存結果にかかわらず終了コード130で終了します。 DockerはアプリをSIGTERMで停止しますが、DropのPM2モードではSIGINT(PM2のデフォルト)が使われるため、PM2では最後の保存が成功したかどうかを終了コードから判別できません。最後の保存に失敗した場合は、代わりにログにstore.write_failed(Postgres)またはstore.save_failed(ファイルストア)が記録され、保存がハングした場合はapp.drain_timeoutも記録されます。/healthにはstore(fileまたはpostgres)とstoreOkが表示されます。

ロールバック。 新しいビルドによって、保存データの形式がアップグレードされることがあります。古いビルドは新しいスキーマで書き込まれたデータを拒否して起動しないため、アップグレード前にコピーを保管してください。サインイン機能より前の古いビルドでは、アカウントを含むファイルを読み込むと、次回の保存時にすべてのアカウント、セッション、エージェントキー、「AIを使う」許可が削除されます。より正確には、aiaccessの種類に対応していないビルドでは、次回の書き込み時にファイルストアから許可が削除されます。Postgresでは許可の行はテーブルに残り、新しいビルドを再度起動すると戻ります。ロールバック後は全員が再度サインインし、新しいエージェントキーを作成する必要があります。アカウント機能はあるものの、名前付きキーより前の古いビルドでは、ユーザーごとに最新のキーだけが保持されます(読み込み時に残りは削除されます)。また、そのビルドの接続ページで新しいキーを作成すると古いキーが置き換えられます。キーの名前は読み込まれず、次回の保存後にほかのキーは失われます。Postgresからファイルストアに戻すには、次の手順を行います。

  1. エクスポート中に書き込みが行われないよう、アプリを停止します。
  2. テーブルをエクスポートします:DATABASE_URL=... node scripts/export-rooms.js /path/to/rooms.json。pgが必要です(npm ci)。読み取りのみを行い、データベースロックは不要です。件数のみを出力し、既存のファイルを上書きしません。出力されるのはファイルストア独自の形式のrooms.jsonで、すべてのルーム、当日の使用量、アカウントのレコードが含まれます。
  3. そのファイルをDROP_DATA_DIRにrooms.jsonとして配置します。すでにそこにrooms.jsonがある場合は、先に別の場所に移動してください。
  4. デプロイするビルドのDropでのREQUIRE_DATABASEのデフォルト値が1の場合は、ダッシュボードでREQUIRE_DATABASE=0を設定してください。設定しないと、データベースなしでは起動できません。Postgresに戻す際には削除してください。
  5. 古いビルドをデプロイします。 rooms.json.imported-<time>には、Postgresの初回起動時点のデータしか含まれていません。それ以降に作成されたルームはデータベースにしかないため、エクスポートが必要です。.partialまたは.corruptのコピーを復元した後は、セッションとエージェントキーをクリアして、アクセス権を取り消した人がアクセスし続けないようにしてください。

Dropへのデプロイ

再デプロイ中、新しいインスタンスは古いインスタンスが停止してデータベースを解放するまで、503「starting」を返します。Dropは古いインスタンスが引き続きサービスを提供している間に新しいインスタンスを起動し、新しいインスタンスがHTTP応答を返してから古いインスタンスを停止します。そのため、index.jsは最初にプレースホルダー(503、{"code":"starting"}、および/healthでの{"ok":false,"starting":true})を使って直ちにポートをバインドし、ストアの読み込みが完了したら実際のサーバーに切り替えます。

drop.yamlはDropにPostgresデータベースを要求し(DATABASE_URLを設定します)、SIGNIN: githubとPUBLIC_URLを設定して、2つのGitHubシークレットを必須として宣言します。Dropはアプリの起動前に宣言済みのシークレットを確認します。シークレットが不足している場合、起動時にアプリをクラッシュさせるのではなく、needs-configの状態にします。DATABASE_URLは意図的に宣言していません。これはDropが自ら設定し、再デプロイ時のチェックでは手動で設定されたシークレットのみを数えるため、宣言すると再デプロイのたびにブロックされるからです。代わりに、デプロイ後に/healthを確認してください(手順5)。DROP_DATA_DIRが設定されている場合、REQUIRE_DATABASEはデフォルトで有効になり、drop.yamlでも"1"に設定されています。ロールバックのように、意図的にファイルストアで実行する場合を除き、ダッシュボードで0に設定しないでください(ダッシュボードの値はdrop.yamlを上書きします)。DATABASE_URLなしでDropがアプリを起動すると、プロセスは直ちに終了し(BAD_REQUIRE_DATABASE)、レディネスプローブは失敗します。これにより、空のファイルストアに切り替わるのではなく、古いバージョンが引き続きサービスを提供します。

  1. GitHub OAuthアプリを作成します。コールバックURLはhttps://behalf.dropkit.sh/auth/github/callback(PUBLIC_URL + /auth/github/callback)と完全に一致する必要があります。
  2. Dropのダッシュボードで、GITHUB_CLIENT_ID、GITHUB_CLIENT_SECRET、ANTHROPIC_API_KEY、ADMIN_GITHUB_IDS(自分の数値形式のGitHub ID。これがないと誰も「Use our AI」を許可できず、サーバーログにapp.no_adminsが記録されます)、および必要な制限を設定します。サインインが有効な場合、ROOM_PASSCODEは使用されません。また、Dropがアプリに接続する方法に合わせてTRUST_PROXYを設定します。PM2の分離環境では、DropのCaddyはループバック(localhost:PORT)経由でアプリに接続するため、TRUST_PROXY=loopbackを設定します。Dockerの分離環境では、ポートはホストのループバック上に公開されますが、アプリにはDockerブリッジ経由のプライベートアドレスからの接続として認識されるため、デフォルトのprivateのままにしてください。(DockerでloopbackにするとX-Forwarded-Forが無視され、アドレスごとの制限ではすべての訪問者が同じアドレスを共有することになります。)
  3. ダッシュボードの値はすべてdrop.yamlを上書きします。再デプロイ前に、ダッシュボードのSIGNINとPUBLIC_URLを確認してください。削除するか、drop.yamlの値(githubとhttps://behalf.dropkit.sh)に設定します。SIGNIN=offが残っているとサインインが無効のままになり、古いPUBLIC_URLが残っているとGitHubコールバックが機能しません。
  4. 再デプロイします。初回起動時に、サーバーはrooms.jsonをインポートします(「データ」を参照)。デプロイが安定し、ロールバックの可能性がなくなるまでrooms.json.imported-<time>を保持し、その後削除してください。このファイルにはルームのコンテンツとシートトークンが含まれています。
  5. /healthを確認します。storeがpostgresで、storeOkがtrueである必要があります。store: fileはデータベースがプロビジョニングされていないことを意味します。ログでstore.file_fallbackを確認してください。また、/api/configでsigninがgithubと表示されていることも確認します。

コメント

MCP は初めてですか?

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

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