Beancount.io について
Beancount.io Backend は、Node.js と TypeScript で構築された Beancount.io の API とバックグラウンドジョブを担うサービスです。台帳 API、認証、課金、OAuth のデプロイに加え、任意の MCP Registry ドメイン証明を扱います。トークンのイントロスペクションは MCP ツールではないと明記されています。
主な機能
- 01GraphQL と REST API
- 02PostgreSQL によるデータ保存
- 03Redis キャッシュ
- 04バックグラウンドデータ処理
- 05認証と OAuth のサポート
活用シーン
- 01台帳 API との連携
- 02バックエンドデプロイの運用
- 03OAuth と認証情報チェックの設定
MCP 対応
公式 MCP Registry への Beancount.io の公開を検証するドメイン証明を提供できます。ドキュメントでは、トークンのイントロスペクション操作は MCP ツールではないと明記されています。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://beancount.io/api-gateway/mcpclaude mcp add --transport http io-beancount-beancount https://beancount.io/api-gateway/mcp{"mcpServers":{"io-beancount-beancount":{"type":"http","url":"https://beancount.io/api-gateway/mcp"}}}Cursor で開く{"servers":{"io-beancount-beancount":{"type":"http","url":"https://beancount.io/api-gateway/mcp"}}}VS Code で開く{"mcpServers":{"io-beancount-beancount":{"type":"http","url":"https://beancount.io/api-gateway/mcp"}}}{"mcpServers":{"io-beancount-beancount":{"serverUrl":"https://beancount.io/api-gateway/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
Beancount.io バックエンド
概要
バックエンドは、Koa と GraphQL を使用して Node.js と TypeScript で構築されています。Beancount.io の API とバックグラウンドジョブを提供します。主な機能は以下のとおりです。
- Apollo Server と Koa による GraphQL API の提供
- 型安全なコードベースを実現する TypeScript
- データベース操作に PostgreSQL と Drizzle ORM を使用
- キャッシュに Redis を使用
- データ処理のバックグラウンドジョブ
パスワードの登録、API 利用制限、メールによる復旧、デプロイ要件については、 認証とレート制限を参照してください。 サインアップ時にアカウントの存在を明示的に通知する動作は、意図的な製品上の選択です。 頻繁な Git の自動化では、HTTP 経由でアカウントのパスワードを繰り返し送信するのではなく、 SSH キーを使用してください。
課金
サポート対象のアプリケーションクライアントは beancount-web-prod です。これは
STRIPE_PRIVATE_KEY、STRIPE_PUBLIC_KEY、STRIPE_WEBHOOK_SECRET で設定します。
すべての課金ポータルセッションでは、SUBSCRIPTION_CONFIG で固定された設定が使用されます。
この設定では、支払い方法、請求書、請求先情報のみが許可されます。プランの変更は
changePlan を介して行います。課金では、有効なサブスクリプションと、認識されている現在または旧価格を
使用します。古いキャッシュ済み期間や不明な価格から、購入済みプランを特定することはできません。
顧客のキャッシュを更新できるのは、検証済みの有効な Webhook のみです。
Stripe の障害によって保存済み期間が消去されることはありません。
GA4 の purchase イベントは、支払いが確認された後にダッシュボードがブラウザー内で記録します。
バックエンドからアナリティクスイベントは送信されません。有料の Checkout は、ダッシュボードの確認画面
(/settings/billing?checkout=success&session_id=…)に戻ります。また、changePlan は即時変更で請求された
請求書(invoiceId、amountPaid、currency)を報告するため、ダッシュボードではこれを一度だけ計上します。
廃止された beancount-web-dev テナントと STRIPE_DEV_* 変数は無視されます。
これらに代わる、支払い済みを偽装するスクリプトはありません。
Stripe のテストは、本番アプリケーションの課金環境から分離して行ってください。
プロジェクト構成
backend-v2/
├── docs/ # API and operator guides
├── scripts/ # Deployment migrations and developer tools
├── src/
│ ├── config/ # Environment-backed application configuration
│ ├── drizzle/ # PostgreSQL setup and tracked migrations
│ ├── features/ # Domain adapters, workflows, services and models
│ ├── foundation/ # Composition, external clients and Redis
│ ├── scheduler/ # Scheduled jobs, including authentication mail
│ ├── server/ # Koa, GraphQL, REST and MCP transport setup
│ └── shared/ # Errors, caching, logging and utilities
├── server.ts # Application entry point
└── package.json
はじめに
このガイドでは、Unix 系の環境を前提としています。Windows を使用している場合は、Windows での実行を参照してください。
前提条件
- Node.js 20+
- PostgreSQL
- Redis
- Git
セットアップ
- リポジトリをクローンします。
git clone [email protected]:bex-co/beancount-io.git
cd beancount-io/backend-cluster/backend-v2
- 依存関係をインストールします。
yarn install
- 環境変数を設定します。
backend ディレクトリに .env ファイルを作成し、必要な環境変数を設定します。必須の変数は次のとおりです。
POSTGRES_BACKEND_URI=postgresql://user:password@localhost:5432/beancount
REDIS_URI=redis://localhost:6379
FAVA_API_URL=http://localhost:8000
FAVA_API_ADMIN_USER=admin
FAVA_API_ADMIN_PASSWORD=your_password
AUTH_SECRET=replace_with_a_strong_shared_deployment_secret
SERVER_URL=http://localhost:4104
DASHBOARD_URL=http://localhost:5173
# Optional: login/register JWT lifetime in minutes (default 525600 = 365 days)
# AUTH_JWT_EXP_MINUTES=5
# Optional: reverse proxies in front of this server that append to
# X-Forwarded-For (default 1; 0 when clients connect directly)
# TRUSTED_PROXY_HOPS=1
AUTH_SECRET は空にせず、API/ワーカープロセス間および再起動後も同じ値を使用してください。これはセッションの署名と、認証メールの暗号化キーおよび復旧証明キーの導出に使用されます。実際の値は決してコミットしないでください。
- サーバーを起動する前に、データベースのマイグレーションを適用します。
node --env-file=.env scripts/migrate-deploy.js
yarn start
デプロイ時は、環境変数を設定したうえで yarn migrate:deploy を実行します。これには auth_mail のアウトボックスマイグレーションが含まれます。これを適用せずに API を起動すると、復旧メールをキューに登録できません。通常のサーバーは認証メールワーカーを自動的に起動し、5 秒ごとに実行します。
開発/テスト用の ConsoleSendGrid はメールの送信を抑止し、メール本文、アドレス、リンク、OTP を表示しません。実際のメールによる登録/復旧の確認には、メール送信環境が完全に設定されたデプロイが必要です。本番クライアントは SEND_GRID_API_KEY と設定済みの送信元を使用します。自動テストでは、メールプロバイダーに接続せず、テスト用のメールを捕捉します。ワーカー、Redis、復旧に必要な条件については、運用ガイドを参照してください。
本番環境の OAuth には、デプロイのシークレットマネージャーから提供される、完全な秘密鍵付き P-256/ES256 署名用 JWKS である OAUTH_JWKS が必要です。これは決してコミットしたり、.env.example にコピーしたりしないでください。未設定または形式が不正な場合、OAuth エンドポイントは 503 oauth_not_configured を返します。既存の API と従来の JWT ログインは引き続き利用できます。開発環境およびテストでは、プロセス内限定の一時的な鍵を生成します。
OAuth 署名鍵をローテーションするには、置き換え用のシークレットを設定してデプロイし、配信する JWKS から置き換え前の公開鍵を削除します。Git から過去の秘密鍵を削除しても、受け入れ対象の現在の鍵を置き換えなければ、その鍵で署名されたトークンは無効になりません。
REST API クイックスタート
パーソナルアクセストークンは有料プランで利用できます。サインインして、パーソナルアクセストークンを開き、連携に必要な最小限の権限を指定してトークンを作成し、表示されたときにコピーしてください。後から平文を復元することはできません。シークレットマネージャーに保管し、決してコミットしないでください。
まず、安全な台帳一覧エンドポイントから始めましょう。
export BEANCOUNT_API_KEY="bcio_your_token_here"
curl --fail-with-body \
--header "x-api-key: $BEANCOUNT_API_KEY" \
https://api.v3.beancount.io/api-gateway/v1/ledgers
インタラクティブなAPI リファレンスでは、利用可能なエンドポイント、リクエストスキーマ、必要な権限を確認できます。
OAuth のデプロイ要件
本番環境では、DASHBOARD_URL は認可サーバーおよび同意画面の公開エントリーポイントです。SERVER_URL はローカル開発スタックで使用するバックエンドの直接 URL です。どちらも通常の公開デプロイ設定であり、OAuth シークレットではありません。本番環境ではどちらも公式サービスがデフォルト値です。開発環境ではローカルのバックエンドとダッシュボードがデフォルトです。本番環境以外では、localhost、127.0.0.1、::1 を除き、HTTPS が必須です。本番環境のリバースプロキシが Beancount をパスプレフィックス配下で公開する場合は、末尾にスラッシュを付けず、そのプレフィックスを DASHBOARD_URL に含めてください。
localhost スタックではポートが異なりますが、ホスト名は同じです。本番環境では通常、両方を単一の HTTPS エントリーポイント経由で公開してください。
検出は、API リソースから導出される RFC 9728 の URL から始まります。発行者が https://books.example.test/beancount の場合、API リソースと検出チェーンは次のとおりです。
resource: https://books.example.test/beancount/v1
protected resource metadata:
https://books.example.test/.well-known/oauth-protected-resource/beancount/v1
authorization server metadata:
https://books.example.test/.well-known/oauth-authorization-server/beancount
プロキシは、これらの well-known パス、発行者のプレフィックスが付いた /api-gateway/oauth/* エンドポイント、および各インタラクションページを、それぞれバックエンドとダッシュボードにルーティングする必要があります。ダッシュボードのホスト(APP_DOMAIN)では、Apple と Google が /ledger/... リンクと同じオリジンからネイティブアプリのリンク検証用データを取得できるよう、/.well-known/* をこのバックエンドに到達させる必要があります。
外部からの認証情報の検証
Beancount.io が発行した認証情報を確認する方法は 2 つあり、それぞれ確認できる内容が異なります。目的に応じて選んでください。
公開鍵を使ったオフライン検証。 OAuth アクセストークンは、iss、aud、kid を含む ES256 JWT です。公開鍵は /api-gateway/oauth/jwks で提供され、上記の RFC 8414 ドキュメントから検出できます。そのため、標準的なバリデーター(jose の createRemoteJWKSet や、API ゲートウェイの JWT フィルターなど)で、こちらへの問い合わせなしに検証できます。
これにより、トークンが 発行された ものであり、有効期限が切れていないことを確認できます。ただし、許可が 5 分前に取り消されたかどうかはわかりません。また、ほかの 2 種類の認証情報にはまったく使えません。セッショントークンは HS256 で署名されているため、鍵を公開すると発行する能力まで公開されてしまいます。また、bcio_ API キーは JWT ではなく、検証する署名もありません。
問い合わせによるオンライン検証。 POST /api-gateway/v1/token/introspect(RFC 7662)は、認証情報が 現時点で 有効かどうかを含め、3 種類すべてについて回答します。
curl -sS https://beancount.io/api-gateway/v1/token/introspect \
-H 'x-api-key: bcio_your_own_key' \
-H 'content-type: application/json' \
-d '{"token":"bcio_the_key_you_are_checking"}'
{
"active": true,
"sub": "usr_7wXzK9mN",
"scope": "ledger.read ledger.write",
"jti": "akey_7wXzK9mN",
"iat": 1767225600,
"exp": 1798761600,
"bio_credential_kind": "apikey",
"bio_assurance": "delegated",
"bio_ledger_scope": "alice/main-ledger"
}
利用できない認証情報に対しては、次の内容だけが返されます。
{ "active": false }
期限切れ、形式不正、取り消し済み、未発行、別のユーザーに属する認証情報は、すべて意図的に同一のレスポンス本文を返します。異なるレスポンスを返すと、呼び出し元が、以前は実在した認証情報とその取り消し時刻を列挙できてしまうためです。
利用を始める前に、次の 3 点を把握しておいてください。
- 匿名では利用できません。 このエンドポイントには、エンドポイント自体の認証情報が必要です。このルールがなければ、盗まれたトークンを「有効、ユーザー X、台帳 Y への書き込み可能」として扱えてしまいます。認証できない呼び出し元には
401/403が返され、{"active": false}が返されることはありません。 - 検証できるのは自分の認証情報だけです。 他人のトークンは非アクティブとして扱われます。そのため、これは同じユーザーの代理として動作するゲートウェイやエージェント実行環境向けであり、マルチテナント対応の検証サービスではありません。
scopeは有効な権限を示し、許可の生データではありません。 セッショントークンはスコープに制限されず、3 つすべての台帳スコープを報告します。一見空に見える許可を「権限なし」と解釈しないでください。
bio_ が接頭辞に付くフィールドは RFC 7662 ではなく、Beancount.io 独自のものです。それ以外はすべて標準ドキュメントのフィールドです。同じ操作は GraphQL の Query.introspectToken でも利用できます。これは意図的に MCP ツールにはなっておらず、RFC 8414 メタデータの introspection_endpoint としても公開されていません。どちらの理由も ADR 0017 に記載されています。
OAuth が一度も設定されていないデプロイ環境でもイントロスペクションは機能します。API キーとセッショントークンは引き続き解決され、OAuth トークンは active: false を返します。これはその環境では正しい結果です。
ネイティブアプリのリンク
このサーバーは常に次に応答します。
GET /.well-known/apple-app-site-association—/ledger/*におけるio.beancount.ios(チームPTLM7BZQMM)向けの Apple Universal LinksGET /.well-known/assetlinks.json— Play App Signing 証明書の SHA-256 を含む、io.beancount.android向けの Android App Links
どちらも Beancount.io の公式アプリを識別するもので、src/features/well-known/api/well-known-route.ts にハードコードされているため、設定は不要です。アプリが関連付けを要求するのは beancount.io のみなので、セルフホストのドメインで同じファイルを配信しても何も関連付けられません。
OpenAI アプリのドメイン検証
GET /.well-known/openai-apps-challenge は、OpenAI Plugins ダッシュボードが Beancount.io のアプリ申請に対して発行したトークンを text/plain で返します。このトークンは秘密情報ではないため、環境変数ではなく well-known-route.ts の定数として定義されています。DASHBOARD_URL が https://beancount.io のデプロイ環境だけがこのトークンを配信し、それ以外のすべてのデプロイ環境は 404 を返します。
MCP レジストリのドメイン証明
任意です。MCP_REGISTRY_AUTH_PROOF には、Beancount.io による公式 MCP レジストリ(registry.modelcontextprotocol.io)への公開に署名する鍵の公開部分を、レコード全体 v=MCPv1; k=ed25519; p=<base64 public key>(P-384 鍵の場合は k=ecdsap384)として設定します。設定すると、このサーバーは GET /.well-known/mcp-registry-auth に対してその値を返します。これにより、レジストリは io.beancount/* での公開が、このドメインを管理する者によるものかを確認できます。この形式に完全に一致しない値を設定すると、起動に失敗します。
未設定の場合は → 404 です。セルフホスト環境では未設定のままにしてください。Beancount.io の鍵を自分のドメインで提供すると、Beancount.io がそのドメインのレジストリ名前空間で公開できるようになります。リスティング本体は、このパッケージのルートにある server.json です。公開方法については、MCP ガイドで説明しています。
静的な beancount-mobile クライアントは公開クライアント(シークレットなし)で、認可コードグラントとリフレッシュグラントのみを受け付け、S256 PKCE を必須とし、io.beancount.ios:/oauth/callback と io.beancount.android:/oauth/callback のみを登録します。認可エンドポイントは screen_hint=signup も受け付け、これをダッシュボードの /oauth/mobile-consent ページに転送します。これにより、アプリで「サインアップ」をタップすると、ログインフォームではなく登録画面が開きます。これは表示上のヒントであり、ポリシーではありません。このサーバーが認識しない値は拒否されずに破棄されるため、セルフホストサーバーより新しいアプリでもログインフォームが表示されます。このヒントが MCP または ID クライアントのインタラクションページに転送されることはありません。
OAuth の機能は、GraphQL、REST、MCP で共通の閉じた操作マトリックスに従います。読み取りには ledger.read、通常の変更には ledger.write、台帳の削除、共同作業者の管理、公開鍵の管理などの台帳コントロールプレーン操作には ledger.admin が必要です。スコープは累積的です。ledger.write には読み取り機能が含まれ、ledger.admin には書き込み機能と読み取り機能が含まれます。ユーザーアカウントのライフサイクルは対象外です。台帳スコープ、OAuth クライアント ID、台帳との関係のいずれも、user.delete を許可しません。
台帳のライフサイクル、共同作業者、離脱、SSH 公開鍵の操作において、ledger.admin は委任された認証情報の上限にすぎません。中央集約型 PDP は、アプリケーションサービス/ワークフローの境界で、現在の厳密な自己 User 関係、または Gitea に基づく台帳関係も必須とします。操作の admin クラスはレート制限と監査のメタデータであり、別の許可ではありません。関係性の拒否では台帳の存在を秘匿します。関係性の情報源が停止した場合はサービス利用不可を返し、Fava/Gitea の変更、Plaid のクリーンアップ、データベースへの副作用は一切実行しません。既存のダッシュボードおよびモバイルの GraphQL コントラクトに変更はありません。
Ledger データプレーンの認可
Ledger のメタデータ、レポート、ジャーナル、アカウント、ファイル、リポジトリの履歴、BQL、アーカイブ、エントリー、レシート、プルリクエストでは、同じ PDP 内のトランスポートに依存しない
ledger.* アクションを使用します。GraphQL、REST、MCP ツール、MCP リソースは引き続きエイリアスとして機能します。運用クラスでは既存のレート制限が維持され、保護対象のサービス/ワークフローが正規アクションを選択し、データへのアクセスまたは変更の前に、認証情報のスコープ、Ledger のピン、現在の Gitea のオーナー/コラボレーター/公開状態を示す事実を評価します。
現在公開されている Ledger への匿名読み取りは引き続き可能です。非公開 Ledger への匿名読み取りでは認証が必要であることが返され、署名済みの関係性に基づく拒否は、対処可能な禁止エラーとして引き続き返されます。関係性の情報源で障害が発生した場合は、サービス利用不可が返され、エラーとして監査されます。ファイルパスの検証、アーカイブ名の検証、コミットの構築、レシートの所有権/クリーンアップ、エントリーのアトミックな動作は、認可後もドメインの不変条件として維持されます。認可の判定はキャッシュされないため、権限の取り消しや公開状態の変更は次の呼び出しから反映されます。
ログイン済みの閲覧者は、UI がエージェントモードを要求する場合も含め、公開または共有された Ledger について質問できます。エージェントはまず読み取り専用の ai.ledger.ask アクションを認可し、次に書き込み権限へのアップグレードとして ai.ledger.agent を個別に試みます。このアップグレードが拒否されても、編集ツールや書き込み可能な Git リモートを使わず、読み取り専用モードで回答を実行します。認可情報源の障害が発生した場合は、引き続きフェイルクローズで処理します。
ユーザードメインの認可
保護対象となるユーザープロフィール、ライフサイクル、API キー管理、課金操作は、src/server/api/authorization/ にある一元化された TypeScript PDP 内の、トランスポートに依存しない user.* アクションに対応付けられています。GraphQL、REST、MCP のアダプターは、Account/API キー/サブスクリプションのアプリケーションサービスに処理を委譲します。これらのサービスでは、保護対象のパブリックメソッドが、ドメインの読み取りや副作用を伴う処理の前に、最終的な認可判断を一度だけ行います。厳密な自己参照関係の判定には、解決済みの安定したユーザー ID を使用します。API キーの失効時には、現在のデータベース行から所有権を解決し、その情報をタプルストアにコピーしません。
既存の認証情報に関する契約は維持されます。Mutation.deleteAccount は引き続き引数を受け取らず、ブラウザーセッションと OAuth ユーザー認証情報を受け付けるため、モバイルでの削除に関する修正にクライアント変更や追加認証フローは必要ありません。API キーでは、ユーザーを削除したり、後継キーを発行したりできません。ユーザープロフィールの検索と更新は引き続きセッション専用です。プロフィールの読み取りと API キー管理では、従来のスコープ上限を維持します。有料プラン、スコープ/ピンの縮小、期限、一度限りのシークレットの取り扱いは、認可後も引き続き適用されます。2026-10-09 以降、API キーと SSH キーの管理、アカウント削除、プランのティアと使用状況は MCP では一切提供されず、GraphQL と REST のみで提供されます(ADR 019 amendment)。
静的なティア割り当て量のカタログは意図的に公開されており、PDP の対象外です。サブスクリプションのステータス、チェックアウトとポータルのセッション、キャンセル、再開、アップグレードは引き続きブラウザーセッション専用です。この認証情報のルールは PDP カタログが管理します。op-class.ts は別途、公開カタログを含む、すべての課金エイリアスについて、カットオーバー前の 1 分あたり 300 回という上限を維持します。Stripe の顧客紐付け、設定済みの商品/価格、サブスクリプションの所有権は、引き続き決済ドメインのチェックで検証されます。関係情報の取得元が停止した場合、ポリシーによる拒否ではなく、サービス利用不可として通知されます。この処理は、Stripe やローカルの課金処理が始まる前に行われます。既存のダッシュボードとモバイルのフローに、新たなクライアント側の手順は不要です。
OpenFGA のランタイム、SDK、サービス、新しいデータベース、コンテキスト付きタプル、リクエスト間の認可キャッシュはありません。authz/model.fga とその FGA CLI テストが、宣言的な関係の境界です。現在のデータはローカルで評価され、利用できない場合はフェイルクローズします。authz/README.md と ADR 0010 を参照してください。
署名キーのローテーションは、2 段階のデプロイで行います。シークレットで管理される JWKS を置き換えてデプロイし、その後、/api-gateway/oauth/jwks に新しい公開 kid のみが公開されていることを確認します。削除されたキーで署名されたトークンは、直ちに認証できなくなります。秘密鍵の JWK やトークンを、ログ、CI で記録されるコマンド、コミット対象のファイルに決して含めないでください。
リフレッシュトークンを失効させると、その後のリフレッシュはできなくなりますが、すでに発行済みの自己完結型アクセストークンに対する、リクエストごとの拒否リストは維持されません。アクセストークンの有効期間は最長 1 時間(ttl.AccessToken)です。クライアントはログアウト時に、ローカルに保持しているコピーを直ちに消去する必要があります。また、運用担当者は、即時の全体無効化が必要なインシデント対応に限り、署名キーのローテーションを使用してください。
セッションの有効期間
src/features/oauth/data/config.ts は、OAuth クライアント、オーディエンス、有効期間のポリシーを一元管理します。これには、Mobile と Discourse の静的クライアント、MCP クライアントが使用する動的登録プロファイル、発行者を基準とするオーディエンス、トークン/グラントの有効期間、リフレッシュトークンのローテーションが含まれます。AppConfig.oauth には、発行者、インタラクションのオリジン、署名キー、オプションの Discourse シークレットという、デプロイ用の入力だけが含まれます。そのため、テストやデプロイによって、クライアント ID、リダイレクト URI、オーディエンス、有効期間が知らないうちに変更されることはありません。ダッシュボードのブラウザーセッションは OAuth クライアントではないため、このカタログには含まれません。
これらのリソース値は OAuth の識別子であり、HTTP のマウント先ではありません。GraphQL と REST は既存のトランスポート URL を引き続き使用します。歴史的な <issuer>/v1 識別子は、共有認可境界を示すものであり、その URL にはエンドポイントがマウントされていません。この識別子は、リリース済みのネイティブクライアントと永続化されたリフレッシュグラントが、正確に同じオーディエンスに紐付けられているため、引き続き固定されます。
| クライアント/プロファイル | 登録 | トークンエンドポイントでの認証情報 | リソースオーディエンス |
|---|---|---|---|
| Beancount Mobile | 静的 | パブリッククライアント+PKCE | アプリケーション API(<issuer>/v1) |
| Discourse フォーラム | 静的 | client_secret_basic+PKCE | UserInfo のみ(API リソースなし) |
| MCP/エージェント連携 | DCR または CIMD | 登録済みまたは公開済みのメタデータ(通常はパブリック+PKCE) | MCP(<issuer>/api-gateway/mcp) |
| 認証情報 | 有効期間 | 備考 |
|---|---|---|
| アクセストークン(すべてのクライアント) | 1 時間 | 自己完結型のため、失効させても有効期間は短縮されません。 |
| リフレッシュトークン(ネイティブアプリ) | 365 日 | リフレッシュのたびに全期間で再発行されます。 |
| リフレッシュトークン(MCP ホスト) | 45 日 | パブリッククライアントでは、リフレッシュのたびにローテーションされます。 |
| リフレッシュトークン(Discourse) | 30 日 | oidc-provider のデフォルトのローテーションポリシーです。 |
| グラント(ネイティブアプリ) | セッション期間+1 日 | リフレッシュのたびに延長されます。 |
| グラント(MCP ホスト) | 46 日、最長 1 年 | リフレッシュ時に延長されますが、同意から 1 年を超えることはありません。 |
| グラント(Discourse) | 14 日 | 延長されません。 |
| 認可サーバーのセッション | 14 日 | ブラウザーの SSO Cookie であり、アプリのセッションではありません。 |
ネイティブアプリのセッションは、固定期間ではなくアイドル期間です。リフレッシュのたびにリフレッシュトークンがローテーションされ、そのトークンに紐づくグラントは、新たな有効期間を丸ごと設定して再保存されます。そのため、期間内に少なくとも 1 回使用されたデバイスはサインイン状態を無期限に保ち、期間全体にわたって使用されなかったデバイスはシステムブラウザーで再認証する必要があります。これは意図的な設計です。oidc-provider は認可時にのみ Grant を書き込み、グラントの有効期限が切れているリフレッシュを拒否するため、固定グラント有効期間を設定すると、リフレッシュトークンの有効期間ではなくグラントの有効期間がセッションの上限になります。また、oidc-provider 自身のローテーションのデフォルトでは、365.25 日を超えたチェーンのローテーションが停止します。これらはどちらも features/oauth/data/config.ts のポリシー関数で上書きされています。各期間はカタログ内でレビューされるコード値であり、環境変数ではありません。デプロイ先によって認証情報の有効期間が暗黙のうちに異なることを防ぐためです。
MCP ホストの接続(DCR または CIMD クライアント)は、2 つの違いを除き同じように動作します(ADR 019 D5)。アイドル期間は45 日です。帳簿管理コネクターの運用リズムは月次締めであり、締めの間隔が 31 日の利用者は、30 日の期間では期限切れになるためです。また、毎年再承認が必要です。リフレッシュのたびにグラントは延長されますが、利用者の同意から 1 年を超えることはありません。これらのホストは、台帳への書き込み権限を持つリフレッシュトークンを自社サーバーに保存するためです。パブリッククライアントのリフレッシュトークンは、送信者制約付きトークンを除き、MCP 仕様の要件に従って経過期間にかかわらずリフレッシュのたびにローテーションされます。Discourse では oidc-provider のデフォルト設定を使用します。
長期間のセッションを早期に終了させるには、失効させます。ログアウトするとリフレッシュ認証情報が失効し、それに伴ってグラントも失効します。
パスワードをリセットすると、すべてのセッションが一度に終了します。パスワード変更と同じトランザクション内で、すべてのダッシュボードおよび CLI セッション(jwts 行)を削除し、すべてのクライアントについて、ユーザーが保持しているすべての OAuth グラント、リフレッシュトークン、認可サーバーセッション(oauthGrant.revokeAllForUser)を無効にします。これらを無効にできない場合、リセットは失敗し、古いパスワードがそのまま残ります。発行済みのアクセストークンは 1 時間以内に期限切れとなり、更新することはできません。個人用 API キー(bcio_)と SSH キーは無効になりません。これらはユーザーが意図的に作成した長期間有効な認証情報であり、ダッシュボードの Settings → Personal access tokens と Settings → SSH Keys で一覧表示および無効化できます。
これらの有効期間が導入される前に発行された認証情報は、引き続き元の条件で使用できます。既存のリフレッシュトークンは元の有効期限まで受け入れられ、ローテーション後の新しいトークンには新しい有効期間が適用されます。そのため、インストール済みアプリは初回の更新時に自動的に移行され、再認可や強制ログアウトは必要ありません。すでに期限切れとなったグラントは引き続き拒否されます。有効期間を延長しても、期限切れの認可が復活することはありません。
Git over SSH プロキシ(任意、ADR 0004)
Backend-v2 は Gitea の代わりに Git over SSH を自ら提供できるため、main-only ルールとすべてのアプリケーション層のチェックが、HTTP と同様に SSH にも適用されます。クライアントを Gitea が保持するキーで認証した後、そのユーザー自身の認証情報を使って Gitea と Git over HTTP で通信します。当社のキーはどこにも保存・登録されません。SSH_PROXY_ENABLED と SSH_PROXY_HOST_KEY の両方が設定されていない限り、無効のままです。
SSH_PROXY_ENABLED=false
SSH_PROXY_PORT=2222
SSH_PROXY_HOST_KEY=
無料プランのディレクティブ上限(ADR 0005)
このプロキシは無料プランのディレクティブ上限も適用し、最後に残っていた Gitea の pre-receive hook を置き換えます。ledger-v2 に現在の台帳カウントを問い合わせ、台帳がすでに上限を超えている場合は push を拒否します。ただし、push によって上限を超えるかどうかは判断できません。thin pack にはオブジェクトストアが含まれていないためです。
切り替えスイッチはありません。代わりに、エラーが発生した場合は許可する側に倒します。hook がなくなった後、上限超過ユーザーが上限内に戻る唯一の方法はアプリを通じてエントリーを削除することです。そのため、チェックを厳格に失敗させると、両方の入口を同時に塞いでしまいます。
SSH_PROXY_HOST_KEY にはGitea の既存のホスト秘密鍵を設定してください。クライアントが以前から使用しているポートで新しい鍵を提示すると、REMOTE HOST IDENTIFICATION HAS CHANGED が発生します。これは攻撃と見分けがつかないため、Git は処理を続行しません。_infra/print-ssh-host-key.sh で鍵を取得してください。これは秘密鍵なので、コミットしない .env にのみ保存してください。
Windows での実行
Windows Subsystem for Linux(WSL)の使用をおすすめします。WSL をインストールした後、ターミナルを開き、同じインストール手順に従ってください。PowerShell を使用する場合は、コマンドを実行する前に nvm と Git Bash が利用可能であることを確認してください。
開発
サーバーの起動
開発モードでサーバーを実行するには、次のコマンドを実行します。
yarn start
サーバーは、設定で指定されたポート(デフォルト:4104)で利用できます。
利用可能なスクリプト
yarn start:ts-node を使用してサーバーを起動しますyarn build:サーバーをコンパイルしますyarn dist:start:コンパイル済みのサーバーを Node.js で起動します(先にビルドが必要です)yarn typecheck:TypeScript の型をチェックしますyarn test:ユニットテストを実行しますyarn lint:ESLint を実行し、コードスタイルの問題をチェックして修正しますyarn kill:ポート 4104 を使用している node サーバーを終了しますyarn mcp:conformance <base-url>:デプロイ先の MCP エンドポイントに接続できるか確認します(MCP クライアントの接続を参照)yarn mcp:agent-eval:ホストされた MCP エンドポイントに対して、実際の Claude Code および Codex セッションを使ってオンボーディングの一連の操作を実行し、回答と台帳への影響を評価します(課金対象、オプトイン。詳細はMCP エージェントのジャーニーを参照)
ジョブスケジューラー
バックグラウンドジョブは node-cron を使用してスケジュールされ、サーバーの起動時に自動的に実行されます。
- JWT Cleanup:PostgreSQL から期限切れのトークンを削除するため、毎日午前 0 時(00:00)に実行されます
- Authentication Mail:5 秒ごとに実行され、1 バッチあたり最大 5 件の永続アウトボックスジョブを処理します
- Dev Test Job:開発モードでは 5 分ごとに実行されます(console.log テスト)
実装の詳細は src/scheduler/ を、新しいジョブの追加については src/scheduler/README.md を参照してください。
API ドキュメント
バックエンドは Apollo Server を通じて GraphQL API を公開しています。サーバーを実行して GraphQL Playground にアクセスすると、API スキーマを確認できます。
GraphQL createLedger、REST POST /api-gateway/v1/ledgers、
または MCP createLedger で台帳を作成すると、デフォルトで STARTER テンプレートが使用されます(テンプレートが省略されている場合や null の場合も同様です)。Starter にはオプションと勘定科目のひな形が含まれますが、取引や開始残高は含まれません。サンプルデータが入力されたデモ用台帳には SAMPLE を選択してください。テンプレートの変更は新しく作成する台帳にのみ適用され、既存の帳簿は書き換えられません。
OpenAI 互換モデルプロキシ
POST /api-gateway/ai/openai/chat/completions は、
max_completion_tokens または従来の max_tokens を正の整数として受け付け、指定されたフィールドを変更せずに転送します。両方を指定すると、不正なリクエストとして扱われます。どちらも指定されていない場合、プロキシはプロバイダーの上限なしのデフォルトに依存せず、代わりに max_completion_tokens: 1500 を送信します。明示的な上限を指定すると、このデフォルトに置き換わります。なお、選択したモデルの制限は引き続き適用されます。
このルートには、書き込み機能を持つ認証済みの認証情報が必要で、モデルに接続する前に呼び出し元の月間 AI 利用枠を確認します。呼び出しが成功すると、プロバイダーから報告された合計トークン使用量が記録されます。このワイヤー互換エンドポイントには、文書化されている GraphQL および MCP トランスポートの例外が適用されます。
認識されたプロバイダーのクォータ拒否は、HTTP 429 と
error.code: "RATE_LIMITED" および error.metadata.quotaScope: "shared_service" を返します。この制限はデプロイメントで共有されるモデルの処理能力に適用され、呼び出し元の月間 AI 利用枠とは別に追跡されます。プロバイダーが有効な未来のリセット時刻を返す場合、blockedUntil にはその UTC ISO タイムスタンプが含まれ、メッセージにも時刻が記載されます。任意の retryAfter は、秒数を示す正の整数であり、プロバイダーの数値フィールド、整数の Retry-After ヘッダー、またはリセット時刻から、この順序で取得されます。リクエストが失敗した場合、トークン使用量は記録されません。
プロバイダーのクォータに関するドキュメントは、範囲が制限された error/message エンベロープを通じて認識されます。それ以外のプロバイダーエラーでは、汎用の製品エラーとともにプロバイダーの HTTP ステータスが維持されます。プロバイダーのメッセージ、リンク、認証情報がそのエラーにコピーされることはありません。共有処理能力は、現在、月間利用状況 API では報告されません。
公開台帳サイトマップ
匿名アクセス可能な GET /api-gateway/sitemap.xml エンドポイントは、公開台帳ページを一覧表示します。
サイトマップ操作ガイドでは、ページネーション、キャッシュとエラーの処理、正規 URL、およびデプロイ後の検証について詳しく説明しています。
MCP クライアントの接続
バックエンドは、/api-gateway/mcp でステートレスな Streamable HTTP エンドポイントを提供します。このリソースの OAuth グラント、または bcio_ API キーを使って接続してください。台帳ツールでは ledger: "owner/name" を指定します。台帳に固定された認証情報は、その固定先をデフォルトで使用し、別の台帳は選択できません。台帳に固定されていない認証情報では、呼び出しごとに台帳を選択する必要があります。アカウントツールでは台帳の指定は不要です。
{
"mcpServers": {
"beancount": {
"type": "http",
"url": "https://your-deployment/api-gateway/mcp",
"headers": { "Authorization": "Bearer bcio_your_ledger_scoped_key" }
}
}
}
Beancount.io のホスト型エンドポイントは公式 MCP Registry に io.beancount/beancount として登録されているため、レジストリ対応クライアントではリストから追加して OAuth でサインインできます。詳しくは「MCP Registry から」を参照してください。
すべての結果には、読みやすいテキストブロックと型付きの structuredContent が含まれます。すべてのエラーには {code, message, hint} が含まれ、isError が設定されるため、エージェントは文章ではなくコードに基づいて処理を分岐できます。
runBqlQuery は、単一のクエリ実行から得た正規化済みの行を報告しつつ、生の文字列結果も保持します。複数行のセルやディレクティブも結果の行として数えられます。台帳サービスから件数が提供されない場合、読みやすいテキストには row count unavailable と表示されます。テキストと構造化クエリの動作については、結果エンベロープを参照してください。
ナレーションが空の場合は、beancount://{owner}/{name}/narration-transactions?narration= を読み取ります。トランザクション検索の例では、明示的な値と、引き続き使用できる空でないパスエイリアスについて説明しています。
貸借対照表の現在利益の例では、Codex と Claude Code で同じ読み取り URI を使う方法に加え、レポートのみで行う資本の締め処理と勘定科目フィルターの範囲を示しています。
Beancount.io MCP ガイドでは、セットアップ、OAuth と API キーの権限、リクエストが台帳に届くまでの流れ、43 個すべてのツールと 64 個すべてのリソーステンプレート、4 つの会計プロンプトと Claude Code および Codex での使用方法、結果エンベロープとエラーコード、ディレクティブを Beancount テキストとして記述する方法、ファイル編集のプレビュー、銀行データのインポート、プロトコルの例、デプロイ時の診断について説明しています。また、適合性チェックの制限と、REST との現在のクライアント向けの相違点についても記載しています。
実際のコーディングエージェントがこの機能群にどう対応するかを確認するには、MCP エージェントの利用経路をご覧ください。Claude Code と Codex で 3 つのオンボーディングタスクを実行し、回答とその後の台帳を確認して、呼び出し回数、ターン数、所要時間、コストを記録します。ツール、リソース、プロンプト、またはサーバーの指示を変更する前後に実行してください。
設計上の決定については、ADR 0007(トランスポート契約)とADR 0008(ツール、リソース、および機能群の同等性)を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。