关于 Tenant Pair
Tenant Pair 是面向伴侣、家庭和小组共享状态场景的 TypeScript 库及参考 MCP 服务,支持身份隔离。它提供双时态存储、可配置冲突处理,以及 SQLite 和 Postgres 适配器。
主要功能
- 01创建关系组并邀请成员
- 02区分成员状态与共享状态
- 03记录双时态状态历史
- 04可插拔的冲突处理
- 05SQLite 和 Postgres 存储适配器
适用场景
- 01为消费类 MCP 服务加入多成员状态
- 02追踪共享状态的时间变化
- 03选择策略处理并发更新
MCP 相关
参考实现通过 stdio 暴露 12 个工具,涵盖关系组管理、成员偏好和共享状态操作。该库本身不依赖传输方式。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
属于 StudioMeyer MCP Stack 的一部分 — 在 Mallorca 🌴 构建 · 如果你在使用,欢迎点 ⭐
mcp-tenant-pair
面向消费级 MCP 服务中多用户租户管理(伴侣、家庭、小型群组)的基础库和参考 MCP 服务。支持身份隔离状态、双时态存储和冲突解决器接口。
规范: MCP 2025-06-18 许可证: MIT 仓库布局: monorepo(npm workspaces)
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)
我们想说的话
过去两年,我们一直在为自己构建工具和系统。这个仓库规模小、星标少,并不是因为它刚刚诞生,而是因为我们最近才决定分享自己的成果。这不是一次全新的实验,而是一段漫长历程的最新提交。
我们喜欢构建和分享东西,但不喜欢社交媒体策略、增长技巧,也不热衷于追逐星标和关注者。所以这个仓库规模不大。代码是真实的,也有人在用,问题也会得到回复。请自行判断。
如果它对你有帮助,分享、测试和反馈都会给我们带来帮助。如果你觉得它还有改进空间,提一个 issue 会更有用。如果你用它构建了什么,请发邮件告诉我们:[email protected]。这真的会让我们开心一整天。
来自 Palma de Mallorca 的一家小型工作室。
为什么要做这个
如今大多数 MCP 服务都是单用户的。一旦你想要在两个或更多人(伴侣、家庭、小型群组)之间共享状态,就会一次又一次遇到同样的五个子问题:创建配对、邀请流程、身份隔离状态、冲突解决、自愿退出 / 移除成员。这个库一次性解决这些问题,让下游 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);
命令行界面
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 个)
| 工具 | 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 |
兼容性矩阵
| 方面 | 支持情况 |
|---|---|
| MCP 规范 | 2025-06-18 |
| Node | 使用需要 >= 20.0.0。构建此仓库需要 20.19+ 或 22.12+(Vite 8)。 |
| 存储 | SQLite(默认)、Postgres(peer-dep) |
| 冲突解决器 | LWWResolver(默认)、ManualResolver、自定义(接口) |
| 传输方式 | stdio(演示版),库不依赖传输方式 |
存储适配器
- SqliteTenantStore — 默认选项,嵌入式,单进程。使用
better-sqlite3和 WAL 模式。传入{ path: "./pair.sqlite" }或:memory:。 - PostgresTenantStore — 支持多进程、多租户。接受任何形状符合
pg.Pool的客户端({ query<R>(text, params): Promise<{rows: R[]}>, end?(): Promise<void> })。pg是同级依赖。
冲突解决
ConflictResolver 是一个接口。提供了两种实现:
LWWResolver— 选择validFrom最新的行;若相同,则选择version最高的行。候选列表为空时抛出异常。ManualResolver— 不返回任何解决结果;冲突会保持待处理状态,直到由外部人员解决。
实现 { name: string; resolve(conflicts: Conflict[]): Resolution[] } 即可注入自定义实现。
双时态模型
每次覆盖 (pairId, namespace, key) 三元组时,都会为之前的活动行设置 valid_to,并插入一条具有新 valid_from 的新行。version 在每个键内单调递增。这样即可重放历史记录(getPairStateHistory),并以确定性的方式检测并发写入。
身份隔离
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,可注入以便测试)。 - 每个 pair 的待处理邀请数量上限(默认为 25,可通过
maxPendingInvites配置)。 - 仅所有者可以移出成员。所有者不能移出自己;请改用离开操作。
- DSGVO 第 17 条擦除路径:
forgetMember(pairId, memberId)会硬删除该成员的member_state行(过敏信息、饮食偏好),并将display_name设为 null。成员关系记录(left_at)会保留以供审计。 - Postgres 适配器在构造时会拒绝不匹配
/^[a-z_][a-z0-9_]{0,62}$/的 schema 名称(防止通过集成方提供的 schema 字符串进行 SQL 注入)。 acceptInvite具备竞态安全性 — 两种适配器都使用原子事务(better-sqlite3 即时事务;pgBEGIN+SELECT ... FOR UPDATE)。- 时间可注入(
now: () => Date),以便进行确定性测试。
开发
npm install
npm run build
npm test
测试覆盖(142 项 + 1 项条件测试):
- Pair 创建(8)
- 邀请流程(10)
- 邀请过期边界(6)— 在
expires_at的精确时刻,两种适配器的 accept/pending 结果一致 - 成员偏好(8)
- 共享状态(8)
- 冲突解决(13)
- 解决器(11)
- 错误处理(17)— 全面覆盖
TenantPairError代码 - 存储适配器(6)
- Postgres 适配器(26)— schema 标识符验证、原子事务、回退路径(模拟池,无实时数据库)
- 适配器一致性(6 + 1)— 对 SQLite 和 Postgres 执行相同操作会产生相同结果;仅当
TENANT_PAIR_TEST_PG_URL/DATABASE_URL指向可连接的数据库时才会运行 Postgres 部分,否则跳过(绝不会误报失败) - Demo 服务集成(9)
- 第一轮回归修复(14)— SQL 注入 schema 验证、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 项测试通过(+1 项 Postgres 条件跳过)。生产依赖树中没有已知安全公告(npm audit --omit=dev)。
关于 StudioMeyer
StudioMeyer 是一家位于马略卡岛帕尔马、服务全球客户的 AI 与设计工作室。我们为中小型企业打造定制网站和 AI 基础设施。生产技术栈基于 Claude Agent SDK、MCP 和 n8n,并使用 Sentry、Langfuse 和 LangGraph 实现可观测性,同时配备自研防护层。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。