claude-mem Server API:/v1 REST 接口、API Key 鉴权、事件生成管线与远程 MCP 召回全解析
本文以 claude-mem 仓库的 docs/api.md 为主体,逐条解析 Server Beta 的 REST V1 API:从 API Key 鉴权与读写 scope 模型,到限流/配额/计量的三个环境开关,再到事件生成的 generate/wait 语义、远程 MCP 召回端点与数据删除(forget)接口。读完本文,你可以独立完成一个云端记忆服务的接入:签发只读 Key、把 MCP 链接粘贴进 Claude Code、用 REST 写入 agent 事件并追踪生成任务,同时理解每个接口在 ServerV1PostgresRoutes.ts 中的真实实现与保障边界。
API 端点总览
REST V1 挂载在 /v1 下,遗留 worker 路由仍保留在 /api 下(兼容层见 compat 适配层)。当前可用的 beta 端点完整列表如下(源自 api.md):
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /healthz |
健康检查 |
| GET | /v1/info |
服务名称、版本、runtime、authMode |
| GET | /v1/projects |
列出项目(项目级 Key 只返回自己的项目) |
| POST | /v1/projects |
创建项目 |
| GET | /v1/projects/:id |
读取单个项目 |
| POST | /v1/sessions/start |
开启(或幂等查找)一个 server session |
| POST | /v1/sessions/:id/end |
结束 session,入队会话摘要生成任务 |
| GET | /v1/sessions/:id |
读取 session |
| POST | /v1/events |
写入单条 agent 事件,可选触发 observation 生成 |
| POST | /v1/events/batch |
批量写入(1~500 条,事务原子插入) |
| GET | /v1/events/:id |
读取单条事件 |
| POST | /v1/memories |
直接插入一条 observation(手动/兼容路径) |
| GET | /v1/memories/:id |
读取单条记忆 |
| PATCH | /v1/memories/:id |
更新记忆(projectId 不可变更) |
| POST | /v1/search |
全文搜索 observations |
| POST | /v1/context |
搜索并返回可直接注入 prompt 的 context 拼接串 |
| ALL | /v1/mcp |
远程 MCP 召回端点(streamable-HTTP) |
| POST | /v1/keys |
为调用方团队签发只读 API Key,返回可粘贴的 connect 命令 |
| GET | /v1/connect |
返回带 <YOUR_API_KEY> 占位符的 connect 命令(GET 不签发) |
| GET | /v1/usage |
当前团队本月的分类型用量汇总 |
| DELETE | /v1/memories/:id |
删除单条 observation(sources 级联删除) |
| DELETE | /v1/projects/:projectId/memory |
清空整个项目的所有捕获内容 |
| GET | /v1/audit?projectId=<id> |
查询项目级审计日志 |
在 CLAUDE_MEM_AUTH_MODE=api-key(默认)模式下,请求需携带 Authorization: Bearer <key>。读端点要求 memories:read scope,写端点要求 memories:write。
从源码结构看,这套路由由两层组成:
- ServerV1Routes.ts —— 本地 SQLite 运行时版本,其中
hasSearchableContent守卫(第 31–49 行)会在POST /v1/memories时拒绝任何「可搜索字段全空」的记录,避免写入一条对 FTS 索引不可见的"冻结"记忆; - ServerV1PostgresRoutes.ts —— Server Beta 的 Postgres 运行时版本,是本文档描述的限流、配额、
generate/wait语义、远程 MCP、数据删除等付费就绪特性的宿主。
鉴权与 scope 模型:读 Key 不能变出写 Key
Postgres 运行时的鉴权中间件在 postgres-auth.ts 中实现,几个关键设计值得注意:
- 只存哈希:客户端传来的 Key 会被 SHA-256 哈希后按
key_hash查api_keys表(verifyPostgresApiKey,第 124–168 行),数据库不保留明文。被吊销(revoked_at)或已过期的 Key 直接拒绝。 - scope 校验:路由注册时即区分
readAuth/writeAuth(分别绑定memories:read与memories:write),hasRequiredScopes支持*通配。 - 团队/项目双级作用域:Key 行的
team_id与project_id直接进入req.authContext。所有/:id读取都先做 team 作用域探查,跨团队请求统一返回404而不是403——这是刻意设计,避免响应状态泄露「某个资源在别的团队存在」这一事实。项目级 Key 访问其他项目则返回403。 - 本地开发旁路:仅当
authMode === 'local-dev'且CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS=1、请求来自 loopback 且无代理头时才生效,绝不用于生产路径。
这个模型在「签发 Key」接口上体现得最充分:POST /v1/keys 挂在 writeAuth 之后——即必须先拥有写权限才能签发一把只读 Key,从源头阻止读 Key 自我提权(源码注释在 ServerV1PostgresRoutes.ts 第 191–195 行)。
签发 MCP 客户端 Key(key issuance + connect)
POST /v1/keys(write scope)为调用方团队签发一把只读 API Key,并返回可直接粘贴的 connect 命令。Body 支持:
{ "expiresInDays": 30 } // 可选,1–365 天,源码 z.number().int().positive().max(365)
响应示例(原文档示例,与源码第 219–226 行一致):
{
"id": "...",
"apiKey": "cm_...",
"scopes": ["memories:read"],
"expiresAt": null,
"mcpUrl": "https://<host>/v1/mcp",
"connectCommand": "claude mcp add --transport http claude-mem https://<host>/v1/mcp --header \"Authorization: Bearer cm_...\""
}
实现细节:Key 明文以 cm_ 前缀加 24 字节随机 hex 生成,库中只落 SHA-256 哈希(randomBytes/createHash,第 204–205 行)——明文只在响应中出现一次,错过即需重新签发。
GET /v1/connect(read scope)返回同样的命令,但 Key 位置是 <YOUR_API_KEY> 占位符(GET 绝不签发),并附带 hint 提示用 POST /v1/keys 获取真实 Key。mcpUrl 优先取自环境变量 CLAUDE_MEM_PUBLIC_URL(建议置于反向代理之后),否则由请求 Host 推导——见 mcpConnectUrl(第 46–50 行)。
原文档也如实记录了冷启动限制:团队第一把 Key 的签发仍需要会话路径(web dashboard);better-auth 的 apiKey() 插件存在,但它写入的存储与这些路由实际鉴权的 Postgres api_keys 表不是同一份,org → Server Beta 团队的映射接线是遗留工作。该行为的回归保护见 connect-keys.test.ts。
限流、配额与用量计量:全部是 opt-in 环境开关
api.md 明确:这些付费就绪守卫在鉴权之后运行,且全部通过环境变量开启——不设置(默认)就没有限流、没有配额、没有计量,行为与关闭时完全一致。源码在路由注册时集中体现(第 163–180 行):中间件顺序固定为 限流 → 配额 → 计量,只有被放行的请求才会被计数。
| 环境变量 | 语义 | 触顶响应 |
|---|---|---|
CLAUDE_MEM_RATE_LIMIT_PER_MIN |
每个 API Key 每分钟的最大请求数(固定 60 秒窗口,按 API Key id 计数) | 429 + Retry-After,并携带 X-RateLimit-Limit/Remaining/Reset 头;fail-open |
CLAUDE_MEM_MONTHLY_REQUEST_CAP |
每团队每自然月(UTC)最大请求数 | 402 quota_exceeded,body 含 used 与 cap;fail-open |
CLAUDE_MEM_MONTHLY_TOKEN_CAP |
每团队每月最大 provider token 数。只拦截写路径(ingestion 驱动生成 = token 消耗),读永远可用,超预算团队仍可召回 | 402;fail-open |
CLAUDE_MEM_USAGE_METERING=1 |
为每个已认证调用记录一条 request 类型 usage 事件(fire-and-forget) |
— |
两个值得强调的实现事实(rate-limit.ts 与 usage-metering.ts):
- fail-open 是刻意的:限流/配额存储抖动时记录 warn 日志并放行请求,绝不让计量组件拖垮 API 可用性(rate-limit.ts 第 56–64 行)。
- 计量零延迟:
meterRequests用void repo.record(...).catch(...)异步落usage_events表,请求路径不等待写库;token/observation 维度的计量则由生成 worker 写入同一张表,/v1/usage因此能给出分类型汇总。
GET /v1/usage 返回调用方团队本月的分类型累计(since 为当月 UTC 1 号 0 点):
{ "since": "2026-06-01T00:00:00.000Z", "usage": { "request": 1280, "observation": 44 } }
写路径与读路径守卫差异在源码中一目了然:writeGuards 在基础 guards 之后额外追加 kind: 'tokens' 的月度配额中间件(第 174–180 行),这正是"token 配额只卡写、读保持可用"文档承诺的落点。相关测试见 paid-readiness.test.ts。
事件生成语义:generate 与 wait 两个查询开关
POST /v1/events(以及 /v1/events/batch)接受两个控制 observation 生成的查询参数:
generate=false—— 只写事件行,不入队生成任务(响应中无generationJob)。wait=true—— 在返回前轮询 outbox 行直到任务到达终态(completed/failed/cancelled),响应中的generationJob描述符必然填充(仅当显式禁用生成时才为null)。
不加 wait=true 时,响应包含新事件行和一个 best-effort 的 generationJob 字段。真正的 provider 调用发生在独立的 BullMQ worker 进程中(claude-mem server worker start)——HTTP 路径从不阻塞等待 provider 响应。
源码层面这条管线非常完整:
- 事务内 outbox:IngestEventsService.ingestOne 在单个 Postgres 事务中写入
agent_events行、outbox 行(observation_generation_jobs,source_type='agent_event')和生命周期日志行,然后提交后才发布 BullMQ 任务。发布失败时 outbox 行保持queued,由启动对账(startup reconciliation)补发——HTTP 层绝不会谎报"已入队"。 - 确定性 job id:
buildServerJobId由(kind, team_id, project_id, source_type, source_id)派生,天然幂等,重试时重复发布会在队列侧收敛。 - wait 的硬超时:
WAIT_TIMEOUT_MS = 30_000、轮询间隔 100ms(第 81–82 行)。即使 provider 卡死,调用方也总能拿到响应;超时会在响应中附带waitTimedOut: true。 - 批量语义:
/v1/events/batch先对 1–500 条数组做 Zod 校验与项目作用域预校验(preValidateBatch),在事务中原子插入后再逐条发布;wait=true时为每个任务分摊剩余等待预算。
POST /v1/sessions/:id/end 走同一套 outbox 模式:置 ended_at(幂等)并入队 session-summary 生成任务;(team_id, project_id, source_type='session_summary', source_id) 上的 UNIQUE 约束保证重复 end 不会产生第二行任务。
远程 MCP 端点:一条只读、可审计的云端召回链接
/v1/mcp 是一个 streamable-HTTP 的 MCP 服务器——用户粘贴进 Claude Code(或任何 MCP 客户端)来召回云端记忆的"安全链接"。它与 REST 读路由使用同一把 API Key(memories:read scope)鉴权,Key 绑定的团队(以及项目级 Key 的项目)约束所有读取。
连接命令(与 POST /v1/keys 返回的 connectCommand 一致):
claude mcp add --transport http claude-mem <server-base>/v1/mcp \
--header "Authorization: Bearer cm_..."
三个工具定义在 recall-mcp-server.ts 的 TOOLS 常量中,与文档一一对应:
| 工具 | 参数 | 返回 | limit 约束(default/max) |
|---|---|---|---|
search |
{ projectId, query, limit? } |
匹配的 observations(FTS,与 POST /v1/search 同一路径) |
20 / 100 |
context |
{ projectId, query, limit? } |
observations + 用 \n\n 拼接好的 context 串,可直接注入 prompt(同 POST /v1/context) |
10 / 50 |
recent |
{ projectId, limit? } |
该项目最新的 observations | 20 / 100 |
实现要点(对照 ServerV1PostgresRoutes.ts 第 1003–1063 行):
- 无状态传输:每个请求新建一个
StreamableHTTPServerTransport(sessionIdGenerator: undefined)+ 一个 MCP server,响应关闭时即销毁——负载均衡器背后不需要会话亲和。 - 只读是硬约束:
createRecallMcpServer的 backend 只暴露search/context/recent三个方法,不存在任何 mutating 工具——粘贴出去的召回链接不可能写入。 - 审计无例外:MCP 每次工具调用都会以
mode: 'search'/'context'/'recent', via: 'mcp'写审计行,与 REST 读路径同一套审计口径。 - 方法收敛:路由只注册
POST(JSON-RPC)与GET(SSE)到/v1/mcp,DELETE/PUT/PATCH/OPTIONS不会白跑一遍鉴权与传输初始化。
MCP 读路径与 REST 读路径共享同一个 PostgresObservationRepository.search,FTS 基于 GIN tsvector 索引,按 ts_rank 降序再按 updated_at 降序排序(第 906–950 行),因此同一份记忆无论从 REST 还是 MCP 召回,结果与守卫完全一致。
数据删除(forget):受控的右至被遗忘
两个删除接口都要求 write scope 且严格限定在调用方团队内:
DELETE /v1/memories/:id—— 删除单条 observation,其observation_sources级联删除;对该团队不存在时返回404。项目级 Key 只能在自己项目内删除;团队级 Key 按id + team_id跨项目匹配(deleteObservationForScope,第 1294–1308 行)。DELETE /v1/projects/:projectId/memory—— 清空该项目捕获的全部内容(observations、agent events、sessions、generation jobs),保留项目壳(project shell),响应返回逐表counts。
第二个接口的源码里有一段很有价值的防御性注释:ensureProjectAllowed 只校验 Key 的可选项目 scope,若不做额外确认,团队级 Key 就能对任意 projectId 发 purge 请求并以零计数 200 返回——把未授权的清空误报为成功。因此实现中先用 PostgresProjectsRepository.getByIdForTeam 确认项目归属本团队,否则 404(第 1086–1113 行)。
两个操作都会写审计日志:observation.deleted / project.memory_purged,且审计行的 details 携带 via: 'api' 与 requestId,可回溯到具体 HTTP 请求。删除逻辑本体在 data-deletion.ts,行为由 data-deletion.test.ts 覆盖。
验证与测试入口
以上行为均有对应测试可复核,集中在 tests/server/ 目录:
- v1-routes.test.ts —— V1 路由端到端行为;
- connect-keys.test.ts —— Key 签发与 connect 命令契约;
- auth-api-key.test.ts —— API Key 鉴权(哈希查找、吊销、过期、scope);
- paid-readiness.test.ts —— 限流/配额/计量守卫;
- data-deletion.test.ts —— 两条 forget 路径的团队隔离。
小结
docs/api.md 描述的是 claude-mem 云端记忆服务(Server Beta)对外的完整契约:/v1 REST 负责项目的写入与运营(events/sessions/memories/jobs 的生成与删除),/v1/mcp 负责一条只读、无状态、全审计的 MCP 召回链路,/v1/keys + /v1/connect 负责把这两者安全地连接起来。三个计量守卫(CLAUDE_MEM_RATE_LIMIT_PER_MIN / CLAUDE_MEM_MONTHLY_REQUEST_CAP / CLAUDE_MEM_MONTHLY_TOKEN_CAP / CLAUDE_MEM_USAGE_METERING=1)全部 opt-in、fail-open,默认不改变任何行为;token 配额只卡写路径,保证超预算团队仍可读取记忆。生成管线以"事务 outbox + 提交后 BullMQ 发布 + 确定性 job id"保证事件不丢、不重、可重试,wait=true 提供 30 秒硬上限的同步等待。对需要自建团队记忆后端的读者而言,这条从鉴权、计量到审计的完整链路,是一个值得逐文件对照的多租户 API 参考实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00