1 - 本地 API
本地 API 默认关闭。启用后只监听 127.0.0.1,只读,只能访问当前已启用的资料包。笔记、书签、阅读历史、片段和更正都不对外开放。Crib 自己的搜索和阅读不经过这个接口。
启用
- 打开“设置 → 集成”,在“本地 API”中打开“启用本地 API”。
- “端口”填
0自动选择可用端口(默认),或指定1024–65535中的一个,再点“应用”。 - 启动后,设置页显示“地址”,并提供“复制 Schema 地址”“复制密钥”“更换密钥…”。
关闭开关或退出 Crib 后停止监听。资料库不可用时 API 不会启动,设置页显示原因和“重试”。
发现文件
客户端从用户目录读取地址和密钥,不要缓存自动分配的端口:
~/Library/Application Support/Crib/api/
- status.jsonbase_url、port、pid、instance_id、access_key_file、api_version
- access-key访问密钥
- server.lock正在服务的实例持有这把锁
api/目录权限是0700,两个文件是0600。status.json在服务就绪后才出现,正常关闭时删除。Crib 异常退出可能留下旧的status.json;下次启动时,只要没有实例持有server.lock,就会删掉它。- 密钥文件是符号链接、有多个硬链接、属于其他用户或权限公开时,Crib 拒绝使用。
- “更换密钥…”让旧密钥立即失效。随 App 附带的 MCP 程序自动使用新密钥;手动复制过密钥的工具需要重新复制。
先验证服务端,再发送密钥
Crib 崩溃后,原来的端口可能被其他本地进程占用。发送密钥之前,客户端必须确认对端持有同一把密钥:
- 检查
status.json中的pid进程仍然存在。 - 不带任何凭据请求
GET /auth/challenge?nonce=<nonce>,nonce是 32 个随机字节的 64 位小写十六进制。 - 服务端返回
{"algorithm":"hmac-sha256","proof":"<64 hex>","version":1}。 - 客户端计算
HMAC-SHA256(key = 访问密钥的 ASCII 字节, data = "crib-local-api/server-proof/v1\n127.0.0.1:<端口>\n<nonce>"),用常量时间比较与proof是否相等。相等才发送密钥。
端口写进了证明,占用端口的进程无法把挑战转发给别处的真实 Crib 来冒充。返回 404 的服务端应当拒绝,不能默认信任。
请求规则
/health、/schema、/auth/challenge不需要密钥;其余接口需要Authorization: Bearer <访问密钥>。密钥不要放进查询串。- 只支持
GET。响应是 JSON,带Cache-Control: no-store,发送后关闭连接。 Host必须是127.0.0.1:<端口>或localhost:<端口>。带Origin头的请求返回 403,所以网页里的脚本不能调用它。- 查询串按表单编码解析:
+是空格,字面加号写成%2B。未知参数、重复参数、非法转义都返回 400。
接口
| 路径 | 作用 |
|---|---|
GET /health | 服务状态与 API 版本 |
GET /schema | OpenAPI 描述 |
GET /auth/challenge | 服务端身份证明,见上文 |
GET /docsets/list | 当前搜索世代中已启用的资料包、版本、语言、摘要和修订 |
GET /search | 在已启用的资料中搜索 |
GET /sections/{key} | 读取一个章节的纯文本,必须带 generation |
搜索参数
text,- 搜索文本,支持 Crib 的过滤语法和
PG96:这类版本前缀;文本里的过滤优先于下面的参数 project, ,- 项目
branch, ,- 版本线;
*表示所有已启用版本 locale, ,zh-Hans或en;*表示所有语言domain,- 文档领域
collection,- Wiki 集合
limit, ,- 每页条数,1–50
offset, ,- 偏移,0–10000
generation,- 期望的搜索世代
兼容 Dash 的参数名 query、lang、max_results 分别等同于 text、locale、limit。同时给出一对时,值相同可以接受,值不同返回 400。请求的版本或语言没有安装或启用时返回错误,不会悄悄换成默认版本。
每条结果带 section_key、document_key、path、fragment、docset、pack_digest 等字段,以及两个地址:section_url 是带 generation 的章节接口路径,load_url 是在 Crib 中打开该章节的 crib:// 链接。
响应里的 has_more 和 next_offset 用于翻页;timed_out、cancelled、candidate_truncated 为真时结果不完整,不能当作完整结果使用。
读取章节
GET /sections/{key}?generation=<id>&offset=0&limit=16000
offset和limit按 Unicode 标量计数,不是字节,也不是 UTF-16 单元。默认 16,000,最大 50,000。- 跟随
next_offset继续读,直到它为null。 - 启用、停用或更新资料包后搜索世代会变。用旧 generation 请求返回 409
generation_changed,重新搜索后再读。
时间限制
- 每个请求有 3 秒执行预算。搜索本身超时返回 200 和
timed_out=true,带上已完成的部分结果;准备阶段就耗尽预算返回 504。 - 传输层另有 7 秒处理超时、8 秒连接截止;2 秒内没发完请求头的连接被关闭。最多同时 32 个连接。
错误码
| 状态码 | 含义 |
|---|---|
| 400 | 参数或查询无效 |
| 401 | 缺少密钥或密钥错误 |
| 403 | 来源受限,例如带 Origin 头 |
| 404 | 路径或章节不存在 |
| 409 | 搜索世代已变,或请求的范围没有安装、启用 |
| 503 | 资料库不可用,或请求被取消 |
| 504 | 执行或传输超时 |
响应中的 error 和 message 说明具体原因。
Python 示例
示例从发现文件读取地址和密钥,先验证服务端,再搜索并读取第一条结果。密钥不出现在终端或进程参数里。
与 Dash API 的差异
Crib 与 Dash 8.1.1 的本地 API 都有健康检查、Schema、资料包列表和搜索入口,并兼容 query、max_results 参数名。Crib 不支持 Dash 的 docset_identifiers、search_snippets 和 /docsets/enable_fts,默认范围、结果格式和分页上限也不同。依赖完整 Dash API 的客户端不能直接换成 Crib。
2 - MCP
Crib 随 App 附带一个 stdio MCP 程序 Crib.app/Contents/MacOS/CribMCP,不需要安装 Python、uv 或 Node。它通过本地 API 只读地搜索和读取已启用的文档。
协议已用官方 Python SDK 和一个本地 stdio 客户端验证:工具列表、搜索、读取、分页、别名和错误路径都通过。但还没有在任何真实编辑器或 AI 客户端中完成端到端验收。VS Code 已识别配置并显示 Start,工具发现、搜索和读取尚未完成验证。
前提
- Crib 正在运行。
- “设置 → 集成”中已启用本地 API。
MCP 程序不会自动启动 Crib,也不会替你启用 API。条件不满足时,工具调用返回 local_api_unavailable 和一句说明。
添加配置
- 打开“设置 → 集成”,在“MCP”中把“客户端”选为“通用”或“VS Code”。
- 点“复制配置”,粘贴到客户端的 MCP 设置里;VS Code 粘贴到工作区的
.vscode/mcp.json。已有配置时只合并crib条目,保留其他服务器。 - 客户端用其他配置格式时,点“复制命令”,从中取可执行文件路径和参数。
复制的内容已经带好实际路径,不含访问密钥。注意:
- JSON 里要写绝对路径,
~不会被展开。 - 移动 Crib.app 或换用别的用户目录后,需要重新复制。
- Crib 不会修改任何客户端的配置文件。
- 设置页显示“此版本不含 MCP 程序”时,说明这个构建没有附带
CribMCP。
安装在 /Applications、使用默认用户目录时,“通用”格式如下(<用户主目录> 换成实际路径):
“VS Code”格式用 servers,并带 "type": "stdio":
不传 --user-data 时,程序使用当前用户的 ~/Library/Application Support/Crib。
工具
| 工具 | 作用 |
|---|---|
list_installed_docsets | 列出已启用的资料包、版本、语言和当前搜索世代;可传入期望的 generation |
search_documentation | 在已启用的手册和 Wiki 中搜索 |
load_documentation_page | 用搜索结果中的 section_key 和 generation 读取一个章节的纯文本 |
search_documentation 的参数:
text, , required- 搜索文本,最多 256 个字符;支持 Crib 的过滤语法和
PG96: SELECT这类版本前缀,文本中的过滤优先 project,- 默认
postgresql branch,- 版本线,如
18、9.6;默认 18,*表示所有已启用版本 locale,zh-Hans或en;默认简体中文,*表示所有语言domain,- 文档领域
collection,- Wiki 集合
limit,- 每页条数,1–50,默认 20
offset,- 偏移,0–10000
generation,- 期望的搜索世代
load_documentation_page 的参数:
section_key, , required- 搜索结果里的
section_key generation, , required- 同一次搜索返回的
generation offset,- 起始位置,按 Unicode 标量计数,默认 0
limit,- 最多返回的 Unicode 标量数,默认 16,000,最大 50,000
每个工具同时返回 JSON structuredContent 和等价的文本内容。
分页与搜索世代
- 搜索结果用
has_more和next_offset翻页。timed_out、cancelled、candidate_truncated为真时结果不完整。 - 读取章节时跟随
next_offset,直到它为null。 - 搜索和列表都返回
generation。启用、停用或更新资料包后搜索世代会变,旧坐标的读取返回generation_changed错误;重新搜索,用新的section_key和generation再读。
安全与限制
- 每次调用都重新读取
api/status.json和api/access-key,所以 API 换了端口也能找到。 - 发送密钥前,程序检查记录的进程仍在运行,并完成
/auth/challenge验证;验证失败就不发送密钥,工具返回错误。 - 拒绝符号链接、权限不安全、所有者不对或有多个硬链接的发现文件。只连接与状态文件一致的
127.0.0.1地址和非特权端口;不走代理,不跟随重定向,不保存 cookie。单次请求 8 秒截止,响应最大 4 MiB。 - 工具只读文档:不包含个人资料,不能下载资料包、控制窗口或读取任意 URL。
- 每行一条 JSON-RPC 消息,单行最多 256 KiB,最多同时处理 8 个文档请求。标准输入关闭后取消全部请求并退出。
- 支持 MCP
2026-07-28的 stdio 传输,以及2025-11-25、2025-06-18、2025-03-26、2024-11-05的initialize握手;后两个版本没有用真实 SDK 客户端验证过。不提供 resources、prompts 和 sampling。
工具名称参照 Dash 的官方 MCP 适配器,参数和结果遵循 Crib 自己的约定:读取用 section_key 加 generation,不接受 Dash 的 load_url。依赖 Dash 全部工具的客户端不能直接换成 Crib。
3 - 系统服务与全局快捷键
两个服务
在其他应用中选中文字,从应用菜单或右键菜单的“服务”中选择:
| 服务 | 结果 | 选区上限 |
|---|---|---|
| 在 Crib 中查找 | 打开一个新的 Crib 窗口,用选中的文字(去掉首尾空白)搜索 | 256 个字符 |
| 在 Crib 中创建片段 | 打开“设置 → 片段”,新建一个未保存的片段草稿,正文与选区完全一致,缩进和换行都保留 | 1,000,000 个字符 |
Crib 只读取系统传给服务的文字,不读取也不改写剪贴板。
启用
- 把 Crib.app 放进“应用程序”文件夹。
- 在“系统设置 › 键盘 › 键盘快捷键 › 服务”中找到“在 Crib 中查找”和“在 Crib 中创建片段”,勾选启用。也可以在那里给它们指定快捷键。
“设置 → 集成 → 系统服务”里有同样的提示。
Crib 没在运行时
选择服务时如果 Crib 没有运行,macOS 会启动它。窗口准备好之前到达的请求先排队,最多 16 个,准备好后依次处理。
2026-10-29 在一个独立测试构建上实测通过:TextEdit 中两个服务的发现、已运行时查找、创建片段、连续两次片段请求、关闭最后一个窗口后查找,以及两个服务的冷启动。当前构建还没有重新实测。
被拒绝的请求
以下请求会被拒绝,不会排队重试:
- 没有选中文字,或选区只有空白。
- 选区含有制表符、换行之外的控制字符。
- 查找文字超过 256 个字符,或片段超过 1,000,000 个字符。
- 待处理的请求已有 16 个。
- 片段库是只读的。
被拒绝时,Crib 把原因返回给 macOS,并在 Crib 内记录这条错误。缩小选区或处理完积压后重新选择服务。
实测时在 TextEdit 中选中 257 个字符再调用“在 Crib 中查找”,没有看到任何错误提示,也无法证明这次请求到达了 Crib。单元测试证明超长输入会被拒绝,但用户能否看到提示还没有验证。
全局快捷键
全局快捷键在“设置 → 通用 → 全局快捷键”中,三项都默认关闭。打开右侧开关启用,点“修改”录制新的组合。
| 功能 | 默认组合 |
|---|---|
| 显示窗口 | ⌃⌥⌘Space |
| 打开搜索 | ⌥⌘Space |
| 搜索选中文字 | ⌃⌥⌘D |
- “搜索选中文字”需要辅助功能权限。设置页会显示“授予权限…”和“重新检查”。读不到选中文字时,可以改用服务菜单里的“在 Crib 中查找”。
- 组合被系统或其他应用占用时,设置页显示“…已被系统或其他应用占用,请换一个组合。”和“重试”。
- 快捷键按当前键盘布局解析,切换输入法后重新注册。不支持数字小键盘上的键;按下 ⌘ 后会变成其他字符的键也会被拒绝。
设置页的开关和录制在早期测试构建上实测通过。在其他应用中实际按下快捷键呼出 Crib,还没有实测。
4 - 深链接
Crib 注册了 crib 协议。点击一个 crib:// 链接,Crib 会打开它指向的章节。界面里把这类链接叫“链接”。
复制链接
- 在搜索结果上点右键,选“复制链接”。折叠起来的多版本结果,复制的是代表项那一条。
- 阅读时,从正文上方的“更多”菜单(版本、书签按钮右侧)选“复制链接”,复制正在阅读的章节。
- 本地 API 搜索结果中的
load_url也是这种链接,见本地 API。
复制得到的链接形如:
manifest 记录结果来自哪个资料包。搜索结果和阅读器各自用自己所属的搜索世代生成链接,不会把一个版本的结果链到另一个版本。
两种格式
crib://open
project, required- 项目,如
postgresql branch, required- 版本线,如
18、9.6;Wiki 为空 locale, requiredzh-Hans或ensection- 章节键,必须精确匹配
manifest- 资料包摘要,64 位小写十六进制;给出时必须与已启用资料包的摘要一致
concept、semantic- 没有
section时使用,必须恰好匹配一个章节
带 section 时,该章节必须存在于当前已启用的内容中,且项目、版本线、语言都与参数一致。未知参数、重复参数、超过 4096 字节的值都会让链接被拒绝。
crib://section
路径只有一段,就是精确的章节键;只允许一个可选的 manifest 参数,不能带片段、用户名、端口或其他参数。“复制链接”目前生成的是 crib://open 格式;这种短格式适合手写,或由脚本从本地 API 返回的 section_key 拼出来。
章节键和资料包摘要都来自签名资料包,详见资料包格式。
打不开时
Crib 只打开链接精确指向的章节,不会退而打开相似的页面或其他版本。打不开时,当前页面保持不变,Crib 显示:
| 提示 | 原因 |
|---|---|
| 无法打开链接:页面地址无效 | 链接格式不对,例如多了参数、路径不止一段、manifest 不是 64 位十六进制 |
| 无法打开链接:资料库中没有这个页面或文件 | 链接格式正确,但当前启用的内容里找不到:版本未安装或已停用,或 manifest 指向的修订不是当前启用的那个 |
版本已停用时,在资料库中启用后再点链接。
Crib 没有运行时点击链接、由链接启动 Crib 的情况还没有实测。Crib 打开后如果没有跳到目标章节,再点一次链接。
旧的 pgdash:// 链接
Crib 仍然注册 pgdash 协议,PGDash 时期复制的 pgdash://open?... 链接按上面相同的规则打开。PGDash 的 pgdash://search 没有实现,会被拒绝。Crib 自己只生成 crib:// 链接。
pgdoc:// 不是给用户的
阅读器内部用 pgdoc://<资料包摘要>/<路径> 加载资料包里的文件。这个协议不向系统注册,不能从其他应用打开,也不要把它当作链接保存。