Tenant Pair について
Tenant Pair は、カップルや家族、小グループ向けサービスに共有状態と個別のID管理を加える TypeScript ライブラリ兼リファレンス MCP サーバーです。二時点ストレージ、設定可能な競合解決、SQLite と Postgres のアダプターを備えています。
主な機能
- 01ペアの作成とメンバー招待
- 02メンバー別状態と共有状態
- 03二時点状態履歴
- 04交換可能な競合解決
- 05SQLite と Postgres のストレージアダプター
活用シーン
- 01コンシューマー向け MCP サービスに複数メンバーの状態を追加
- 02共有状態の変化を時系列で追跡
- 03方針を選んで同時更新を解決
MCP 対応
stdio を使うリファレンス MCP サーバーは、ペア管理、メンバー設定、共有状態操作など12個のツールを公開します。ライブラリ自体はトランスポートに依存しません。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
StudioMeyer MCP Stack の一部 — マヨルカ島で開発 🌴 · 使用したら ⭐
mcp-tenant-pair
コンシューマー向け MCP サーバー(カップル、家族、小グループ)でのマルチユーザーテナント管理を実現する基盤ライブラリとリファレンス MCP サーバーです。IDごとに分離された状態、双時制ストレージ、競合解決インターフェースを備えています。
仕様: MCP 2025-06-18 ライセンス: MIT リポジトリ構成: モノレポ(npm ワークスペース)
packages/
lib/ mcp-tenant-pair (TypeScript library)
cli/ mcp-tenant-pair-cli (commander CLI)
demo/ mcp-tenant-pair-demo (reference Low-Level MCP server, stdio)
開発者からのメッセージ
私たちは過去2年間、自分たちのためにツールやシステムを作ってきました。このリポジトリが小さく、スターが少ないのは、新しいからではありません。作ってきたものを公開することにしたのが、つい最近だからです。これは新たな実験ではなく、最近のコミットに至るまで長く続いてきた取り組みです。
私たちはものづくりと共有が大好きです。ソーシャルメディアの戦術や成長ハック、スターやフォロワーの追求は好みません。そのため、このリポジトリは小規模です。コードは実際に使われており、問題には回答しています。ご自身で判断してください。
役に立った場合は、共有、テスト、フィードバックで応援していただけるとうれしいです。改善できそうな点があれば、イシューをお寄せいただくのが一番役立ちます。これを使って何かを作った場合は、[email protected] までお知らせください。本当にうれしいです。
マヨルカ島パルマの小さなスタジオより。
このプロジェクトの目的
現在、ほとんどの MCP サーバーはシングルユーザー向けです。2人以上の人間(カップル、家族、小グループ)で状態を共有しようとすると、毎回同じ5つの課題に直面します。ペアの作成、招待フロー、IDごとに分離された状態、競合解決、自主的な退出/メンバーの除名です。このライブラリはこれらを一度解決するため、下流の MCP サーバー(Pet-Platform、レシピ共有、家庭、カレンダー)が同じ仕組みを再実装せずに済みます。
5分で始める
ライブラリ
import { TenantPair, SqliteTenantStore, LWWResolver } from "mcp-tenant-pair";
const tp = new TenantPair({
store: new SqliteTenantStore({ path: "./tenant-pair.sqlite" }),
resolver: new LWWResolver(),
});
const { pairId, inviteToken } = await tp.createPair({ creatorMemberId: "alice" });
await tp.acceptInvite({ inviteToken, memberId: "bob" });
await tp.setMemberPreference(pairId, "alice", "allergy", ["nuts"]);
await tp.setSharedState(pairId, "alice", "tonight", "pizza");
const aliceConstraints = await tp.getMemberConstraints(pairId, "alice");
const shared = await tp.getSharedState(pairId);
CLI
npx mcp-tenant-pair-cli pair create --member-id alice
npx mcp-tenant-pair-cli pair invite --pair-id <id> --member-id alice
npx mcp-tenant-pair-cli state set --pair-id <id> --member-id alice --key tonight --value pizza
npx mcp-tenant-pair-cli state get --pair-id <id>
デモ MCP サーバー
node packages/demo/dist/server.js
# or via stdio in your MCP client config
MCP_TENANT_PAIR_DB 環境変数を読み取ります(デフォルトは :memory:)。
ツール(12)
| Tool | readOnlyHint | destructiveHint |
|---|---|---|
| create_pair | false | false |
| invite_member | false | false |
| accept_invite | false | false |
| list_members | true | false |
| set_member_preferences | false | false |
| get_member_constraints | true | false |
| get_shared_state | true | false |
| set_shared_state | false | false |
| resolve_conflicts | false | true |
| kick_member | false | true |
| leave_pair | false | true |
| forget_member | false | true |
互換性マトリックス
| Concern | Supported |
|---|---|
| MCP Spec | 2025-06-18 |
| Node | 使用には >= 20.0.0 が必要です。このリポジトリのビルドには 20.19+ または 22.12+ が必要です(Vite 8)。 |
| Storage | SQLite(デフォルト)、Postgres(peer-dep) |
| Conflict Resolver | LWWResolver(デフォルト)、ManualResolver、カスタム(インターフェース) |
| Transport | stdio(デモ)、ライブラリはトランスポート非依存 |
ストレージアダプター
- SqliteTenantStore — デフォルトの組み込み型、単一プロセス向けです。WAL モードで
better-sqlite3を使用します。{ path: "./pair.sqlite" }または:memory:を渡します。 - PostgresTenantStore — マルチプロセス、マルチテナント向けです。
pg.Poolと同じ形状のクライアント({ query<R>(text, params): Promise<{rows: R[]}>, end?(): Promise<void> })を受け付けます。pgはピア依存関係です。
競合解決
ConflictResolver はインターフェースです。次の 2 つの実装を同梱しています。
LWWResolver— 最新のvalidFromを持つ行を選び、同値の場合は最も大きいversionを選びます。候補リストが空の場合は例外をスローします。ManualResolver— 解決結果を返しません。人が外部で解決するまで、競合は保留状態のままです。
{ name: string; resolve(conflicts: Conflict[]): Resolution[] } を実装して、独自のリゾルバーを挿入できます。
バイテンポラルモデル
(pairId, namespace, key) の組み合わせを上書きするたびに、以前のアクティブな行に valid_to を設定し、新しい valid_from を持つ新しい行を挿入します。version はキーごとに単調増加します。これにより、履歴を再生(getPairStateHistory)し、同時書き込みを決定論的に検出できます。
ID の分離
member_state はメンバーごとのデータです(get_member_constraints を通じて、そのメンバーのみが読み取れます)。
pair_state は共有データです(すべてのアクティブなメンバーが get_shared_state を通じて読み取れます)。
後に脱退または追放されたメンバーによる書き込みも、共有状態で引き続き確認できます。
セキュリティに関する注意事項
- 招待トークンは
<uuid v4>.<XXXX-XXXX>の形式です。uuid v4(node:cryptoによる 122 ビットの CSPRNG エントロピー)と、音声で読み上げやすい base32 の短縮コード(デフォルトの RNG はnode:crypto.randomInt。テスト用に挿入可能)を組み合わせています。 - ペアごとの保留中の招待数には上限があります(デフォルトは 25、
maxPendingInvitesで設定可能)。 - キックできるのはオーナーのみです。オーナーは自分自身をキックできません。代わりに脱退してください。
- DSGVO Art. 17 の消去処理:
forgetMember(pairId, memberId)は、メンバーのmember_stateの行(アレルギー、食事の好み)を完全に削除し、display_nameを null にします。監査用にメンバーシップの記録(left_at)は保持されます。 - Postgres アダプターは、構築時に
/^[a-z_][a-z0-9_]{0,62}$/に一致しないスキーマ名を拒否します(インテグレーターが指定したスキーマ文字列を介した SQL インジェクションへの対策)。 acceptInviteは競合状態に安全です。両方のアダプターがアトミックなトランザクションを使用します(better-sqlite3 の即時トランザクション、または pg のBEGIN+SELECT ... FOR UPDATE)。- 時刻はテストを決定論的にするために挿入可能です(
now: () => Date)。
開発
npm install
npm run build
npm test
テスト対象(142 + 条件付き 1 件):
- ペア作成(8)
- 招待フロー(10)
- 招待の有効期限境界(6)— 2 つのアダプターとも、
expires_atと完全に同じ時刻では accept と pending の結果が一致します - メンバー設定(8)
- 共有状態(8)
- 競合解決(13)
- リゾルバー(11)
- エラー処理(17)—
TenantPairErrorのコードを網羅的にカバー - ストアアダプター(6)
- Postgres アダプター(26)— スキーマ識別子の検証、アトミックなトランザクション、フォールバック経路(モックプール、実データベースなし)
- アダプター間の同等性(6 + 1)— SQLite と Postgres で同じ操作を実行し、同一の結果が得られることを検証します。Postgres のテストは、
TENANT_PAIR_TEST_PG_URL/DATABASE_URLが接続可能なデータベースを指す場合のみ実行され、それ以外の場合はスキップされます(誤って失敗することはありません) - デモサーバーの統合(9)
- 第 1 ラウンドのリグレッション修正(14)— SQL インジェクションに対するスキーマ検証、TOCTOU による acceptInvite の競合、競合解決時の勝者マーカー、asOf クロック、DSGVO 消去、CSPRNG のデフォルト設定
実際の Postgres に対して、アダプター間の同等性テストを実行します。
TENANT_PAIR_TEST_PG_URL=postgres://user:pass@host:5432/db npm test
ステータス
基盤ビルドです。レビュー済み(Cold-Cross-Review + MCP Factory Reviewer + Tester)。HIGH/MEDIUM の指摘事項はすべて修正済みです。tsc はクリーン(strict)、テスト 142 件が成功(Postgres 条件付きテスト 1 件はスキップ)。本番用の依存関係ツリーに既知のアドバイザリはありません(npm audit --omit=dev)。
StudioMeyer について
StudioMeyer は、パルマ・デ・マヨルカを拠点に世界中のクライアントと協業する、AI とデザインのスタジオです。中小企業向けに、カスタムウェブサイトと AI インフラストラクチャを構築しています。本番スタックには Claude Agent SDK、MCP、n8n を使用し、可観測性のために Sentry、Langfuse、LangGraph を、自社開発のガードレイヤーとともに採用しています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。