内容
# carry
[](https://github.com/emtcmca/carry/actions/workflows/ci.yml)
[](LICENSE)
**在您的Claude表面之间传递上下文包。** 从您桌面上的Claude Code推送语音规则、系统事实或样式指南;从移动设备上读取它们。 一个小型远程MCP服务器,始终在线,无需手动重新同步。
carry之所以存在,是因为没有公共API来编写Claude项目知识,因此在移动设备和桌面上保持Claude同步意味着要么手动重新上传文件(这在您上传后就会改变),要么使用:一个服务器,两个表面都连接到它,持有每个命名空间的一个当前包。
```
Claude Code(您的仓库) Claude 移动版/网页/桌面版
编译并推送包 连接器通过网页一次性连接
│ │
│ push_context(写入令牌) │ get_context(读取:令牌或OAuth)
▼ ▼
┌─────────────────────────────────────────────────────┐
│ carry — 您的命名空间的当前包 │
└─────────────────────────────────────────────────────┘
```
由于Claude的远程MCP连接器是账户代理的(您在claude.ai网页上添加URL一次,它就会出现在您的手机、桌面和网页上,同一账户),每个表面都读取实时的包。 从您的桌面更新包,任何地方的下一次`get_context`都会得到它。
## 包中包含什么
carry持有每个命名空间的一个当前包 — 您**编写**的上下文,而不是它积累的内存。 好的包是您希望在每个表面上都相同的事物:
- **语音、样式和品牌。** 在您的手机上以终端编写的相同语音进行草稿 — 语气、禁止的词语、格式规则。
- **编码标准。** 您的`CLAUDE.md`约定 — 堆栈、命名、测试样式、“永远不要做X” — 在您从任何设备或仓库询问Claude代码时。
- **项目上下文。** 当前架构、领域词汇表、关键决策,以便任何表面都使用您的项目事实而不是通用事实进行回答。
- **您是谁。** 您的简介、堆栈和工具,以便您不再每次移动会话都重新介绍自己。
- **团队的规则。** 一个共享的读取包 — API约定、样式指南、入职事实 — 每个人的Claude都以相同的方式读取。
它不适用于:快速变化的、每会话的内存。 carry是一个您控制的确定性包,而不是累积的内存存储。
## 为什么不使用斜杠命令或CLAUDE.md?
因为那些是本地的和单表面的。 斜杠命令存在于`.claude/commands`中,仅在一台机器上,而`CLAUDE.md`仅被Claude Code读取 — 您的手机和claude.ai永远看不到它们。 carry是跨表面、单一来源的层,它们无法做到:一个包,您在桌面上更新,从移动设备、网页、桌面和CLI按需读取。
## 试用(无需部署)
```bash
npm install
npm run demo
```
启动一个临时内存服务器,推送一个带有写入令牌的示例包,并使用读取令牌读取它 — 整个循环都在localhost上,没有写入磁盘。
## 自托管 — 您运行,它由您托管
carry是**自行部署**的。 没有共享的“carry云”,也没有维护人员为您承担费用。 您建立自己的实例(附带一键式Render Blueprint),设置自己的令牌,并拥有自己的数据。 可选的OAuth模式使用**您的**授权服务器(例如您自己的免费层WorkOS AuthKit租户)— 再次,由您的账户而非维护人员的账户承担。 运行它的成本仅为您的主机收取的费用(通常为$0;试用时为几美元/月;持久磁盘时为几美元/月)。 请参阅[`docs/deploy.md`](docs/deploy.md)。
## 它公开的内容(MCP)
`POST /mcp`上的无状态可流式HTTP(每个请求都有一个新的MCP服务器;无会话状态可泄露)。 另外还有`GET /healthz`。
- **`get_context`** — 返回您命名空间的当前包。 在草拟前读取此内容。
- **`push_context`** — 替换包。 需要**写入**令牌。
- **`carry://context`** — 与MCP资源相同的包。
## 身份验证模型
carry支持两种身份验证路径。 **静态令牌是默认的;OAuth是可选的,除非您配置它。**
**1. 静态承载令牌(默认 — 写入路径和简单读取路径)。** 命名空间拥有两个密钥:一个**读取令牌**和一个**写入令牌**。 它们必须不同,以便泄露的读取连接器永远无法覆盖您的包。 令牌*是*命名空间 — 模型永远不会提供一个,因此它无法触及任何其他人的包。 这是Claude Code(使用写入令牌推送)和任何允许粘贴`Authorization: Bearer`标头的MCP客户端所必需的。 无需外部服务。
**2. OAuth 2.1受保护资源模式(可选 — 用于claude.ai连接器)。** claude.ai **网页**连接器对话框仅在个人账户上支持OAuth(没有粘贴读取令牌的字段)。 要附加carry作为移动/网页/桌面连接器,请启用OAuth:carry成为一个[RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)受保护资源,它根据您的授权服务器的JWKS验证JWT(发行者+受众+到期时间)。 OAuth调用者仅授予**读取**范围 — 推送始终需要静态写入令牌。 OAuth仅通过设置`CARRY_OAUTH_ISSUER`启用;如果不设置,则静态令牌行为不会更改。 请参阅[`docs/connector-auth-spike.md`](docs/connector-auth-spike.md)以了解为什么存在此路径。
> **锁定到您自己(OAuth模式)。** 默认情况下,carry接受任何可以身份验证到您的已配置授权服务器/租户的用户。 对于单用户自托管,租户仅是您,因此这通常没问题。 如果您的租户有其他用户,请设置一个允许列表,以便只有您可以读取包:`CARRY_OAUTH_ALLOWED_SUBS`和/或`CARRY_OAUTH_ALLOWED_EMAILS`(逗号分隔;如果调用者的`sub`或`email`被列出,则调用者通过)。 未设置 = 对整个租户开放。
## 在本地运行
```bash
npm install
cp .env.example .env # 然后使用您自己的令牌设置CARRY_NAMESPACES
npm run dev # 在:8080上启动(tsx观察)
```
验证:
```bash
npm run smoke # 存储和身份验证核心,无需网络
npm run typecheck
npm test # 完整的vitest套件
```
## CLI(编译 + 推送包)
carry附带一个小型CLI(`bin: carry`或`node dist/cli.js`在`npm run build`之后),它将一个或多个Markdown文件编译成单个包,标记其元数据(`source`、`title`、`gitHash`、`builtAt`),并推送:
```bash
# 构建CLI一次
npm run build
# 推送包(来自$CARRY_WRITE_TOKEN或--token的写入令牌)
node dist/cli.js push --url https://your-instance.example.com/mcp \
--from ./context/identity.md ./context/voice.md --title "我的Claude上下文"
# 读取当前包(环境或--token中的读取/写入令牌)
node dist/cli.js get --url https://your-instance.example.com/mcp
```
令牌从环境中的`CARRY_WRITE_TOKEN` / `CARRY_READ_TOKEN`读取(除非您使用 `--token`,否则永远不会在命令行上传递)。
## 配置
所有配置都通过环境变量进行(参见[`.env.example`](.env.example)):
| 变量 | 必需 | 目的 |
|---|---|---|
| `CARRY_NAMESPACES` | **是** | `{namespace, readToken, writeToken}`的JSON数组。 读取 ≠ 写入。 身份验证的单一来源。 服务器在配置错误时会大声报错。 |
| `PORT` | 否 | HTTP端口。 默认`8080`;像Render这样的主机会注入它。 |
| `CARRY_DB_URL` | 否 | 持久存储。 未设置 = 内存中(开发;重启后丢失)。 `file:/data/carry.db`用于磁盘上的SQLite/libSQL文件,或`libsql://…`用于托管的Turso。 |
| `CARRY_DB_AUTH_TOKEN` | 否 | 用于托管的Turso `libsql://` URL的身份验证令牌。 |
| `CARRY_RATE_LIMIT_PER_MIN` | 否 | `POST /mcp`上的固定窗口限制,按IP和令牌键入。 默认`120`。 |
| `CARRY_OAUTH_ISSUER` | 否 | **OAuth模式的开关。** 您的授权服务器/AuthKit域(JWT `iss`)。 未设置 = OAuth禁用。 |
| `CARRY_OAUTH_AUDIENCE` | 如果设置了颁发者 | carry的规范MCP URL,注册为OAuth资源指示器(JWT `aud`)。 |
| `CARRY_OAUTH_NAMESPACE` | 如果> 1个命名空间 | OAuth调用者映射到的命名空间(读取范围)。 默认值为唯一的命名空间,当只有一个命名空间时。 |
| `CARRY_OAUTH_JWKS_URL` | 否 | JWKS端点的覆盖。 默认值为`${issuer}/oauth2/jwks`。 |
| `CARRY_OAUTH_ALLOWED_SUBS` | 否 | 按令牌`sub`(逗号分隔)的用户允许列表。 未设置 = 任何已身份验证的租户用户。 |
| `CARRY_OAUTH_ALLOWED_EMAILS` | 否 | 按`email`声明(逗号分隔,不区分大小写)的用户允许列表。 如果调用者的`sub` **或** `email`被列出,则调用者通过。 |
## 自行部署
附带的[`render.yaml`](render.yaml)蓝图建立了一个带有libSQL文件持久磁盘的Docker Web服务。 分步(分叉 → 令牌 → 部署 → 附加连接器)在[`docs/deploy.md`](docs/deploy.md)中;第二天操作(令牌轮换、备份)在[`ops/runbook.md`](ops/runbook.md)中。
## 路线图
- **锁定到用户** — 在OAuth模式下允许列出特定的`sub`/email(关闭上面的已知限制)。
- **多租户** — 命名空间配置 + 数据库中的每个命名空间令牌,在`ContextStore`接口后面,最小注册,隔离测试。
- 作为单个确定性包的替代方案,累积内存层。
## 构建方式
TypeScript(严格,ESM,Node ≥20)。 Express入口,官方MCP SDK的无状态模式下的可流式HTTP传输,一个`ContextStore`接口,带有内存和libSQL实现,由env选择,结构化请求日志(永不记录令牌或内容),和一个固定窗口速率限制器。 身份验证是一个小型、单独测试的模块(静态令牌 + 可选的`jose`支持的JWT验证)。 测试套件离线运行(为OAuth路径注入本地JWKS)。
## 贡献
欢迎提出问题和PR — 请参阅[`CONTRIBUTING.md`](CONTRIBUTING.md)。
## 许可证
Apache-2.0。 请参阅[`LICENSE`](LICENSE)。