关于 Contextator
Contextator 是可自托管的文档服务,为每个项目建立独立的可搜索 MCP 端点。它面向需要让 AI 客户端检索本地文件、代码仓库、文档站点、Notion 或 Confluence 内容的团队。
主要功能
- 01每个项目独立的搜索端点
- 02支持多种文档来源
- 03本地生成嵌入向量
- 04通过仪表板管理索引与访问
- 05增量索引变更文件
- 06提供文档工具、资源和提示词
适用场景
- 01从 AI 客户端搜索项目文档
- 02合并检索仓库、Notion 和站点文档
- 03仅重新索引已更改的文件
MCP 相关
每个项目提供 MCP 端点,包含搜索、列出、读取文档及追踪变更的工具,并发布文档资源和两个提示词。端点支持 Streamable HTTP 和可配置的访问控制。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://{host}/mcp/{project}claude mcp add --transport http contextator-contextator https://{host}/mcp/{project}{"mcpServers":{"contextator-contextator":{"type":"http","url":"https://{host}/mcp/{project}"}}}在 Cursor 中打开{"servers":{"contextator-contextator":{"type":"http","url":"https://{host}/mcp/{project}"}}}在 VS Code 中打开{"mcpServers":{"contextator-contextator":{"type":"http","url":"https://{host}/mcp/{project}"}}}{"mcpServers":{"contextator-contextator":{"serverUrl":"https://{host}/mcp/{project}"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://{host}/mcp/{project}需要授权工具
详细介绍
Contextator
可自托管的 MCP 服务,将你的文档转换为每个项目一个可进行语义搜索的端点。
为项目提供文档来源——挂载的文件夹、Git 仓库、上传的归档文件、Notion 工作区、Confluence Cloud 站点或 Data Center 实例、已发布的文档站点——项目便会成为自己的 Model Context Protocol 端点,供 AI Agent(Cursor、Claude Code、 Claude Desktop 等)搜索:
http://localhost:3444/mcp/<project-name>
它以单个 Docker 容器的形式发布为 contextator/contextator,数据库和应用都包含在其中;它是基于
AGPL-3.0-or-later 许可的免费软件,也提供商业许可证
(许可证)。
功能
- 每个项目都有独立的 URL,且完全隔离。 每个项目都在 PostgreSQL + pgvector 中拥有自己的文档集合和向量嵌入。
连接到
/mcp/billing的客户端永远看不到/mcp/mobile。 - 每个项目可添加多个来源。 本地目录、Git 仓库(或其中的一个子目录)、上传的
文件/文件夹/
.zip/.tar.gz/.rar、Notion 工作区、Confluence Cloud 站点或 Data Center 实例, 或通过其sitemap.xml读取的已发布文档站点,都能合并为一个可搜索的端点。请参阅 文档来源。 - 默认完全本地运行。 嵌入在 CPU 上通过
transformers.js 生成(
Xenova/multilingual-e5-small,一款涵盖 100 种语言(包括土耳其语)的检索模型)。只需设置两个环境变量即可切换为 OpenAI 嵌入。 - 管理控制面板位于
http://localhost:3444/,用于管理项目及其来源——添加仓库、将文件夹或归档文件拖放到页面、测试连接、触发重新索引并查看进度。 - 账户和角色。 用户使用自己的账户登录。
root和admin可以管理所有内容和所有用户;member只能查看分配给自己的项目,可以是只读的viewer,也可以是能够添加来源、上传文件和重新索引的editor。任何账户都可以为脚本或 CI 任务创建自己的具名、限定范围、可撤销的API token;ADMIN_TOKEN仍然可用。请参阅 账户和权限。 - 每个 MCP 端点都有访问控制,可选择三种设置。 新项目需要承载者令牌,由客户端通过普通请求头发送;项目创建时会收到首个令牌,该令牌只显示一次。如果项目文档可供任何能够访问该 URL 的人读取,可将其设为开放;也可要求使用账户,此时访问权限由你已有的成员关系管理。基于浏览器的连接器通过 OAuth 2.1 登录。 请参阅 MCP 访问。
- 增量索引。 文件会经过哈希处理;仅重新嵌入有更改的文件,并删除已移除的文件。
- 支持的不只是 Markdown。
.html、.docx、.csv、.pdf、.pptx、.xlsx、.ipynb、.rst和.adoc/.asciidoc会在 索引时转换为 Markdown,让 Agent 能像阅读文档页面一样阅读 Word 文件或 PDF。请参阅 文件类型。
快速开始
所有内容都在名为 contextator 的单个容器中运行:PostgreSQL 16 + pgvector 和 Node.js
应用由一个小型入口脚本统一启动和停止。数据存储在 Docker 卷中,即使移除容器也会保留(参见数据与持久化)。已发布的镜像为
contextator/contextator;运行 docker compose up -d 即可拉取,因此全新安装只需下面这两个文件,无需整个
代码仓库。
mkdir contextator && cd contextator
curl -fsSLO https://raw.githubusercontent.com/Contextator/Contextator/main/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/Contextator/Contextator/main/.env.example
cp .env.example .env
# in .env, pick the code that /setup will ask for once:
# SETUP_CODE=whatever-you-like
# optional: point DOCS_HOST_PATH at your own docs (it defaults to ./docs, created empty if missing)
docker compose up -d
docker compose logs -f # wait for "embedding model ready"
端口仅发布到 127.0.0.1。 仪表板和所有 MCP 端点只在本机响应,不会在其他地方响应,因此在笔记本电脑或办公室服务器上启动的容器不会出现在它所连接的网络中。若要有意将其发布到网络,请在 .env 中设置 CONTEXTATOR_BIND=0.0.0.0
,并在前面配置一个用于终止 TLS 的反向代理或 VPN——参见
在反向代理后运行。
-
创建第一个账户。 打开 **http://localhost:3444/setup**,输入你选择的
SETUP_CODE,并创建root账户。设置此代码是为了防止在你之前访问服务器的人抢先创建账户;账户创建后,代码就会失效。SETUP_CODE留空了?在账户创建之前,服务器每次启动都会生成一个代码并打印出来——就在你正在查看的日志中:┌─ Contextator first-run setup ───────────────────────────────────────────┐ │ No user accounts exist yet; the dashboard is waiting for its first one. │ │ │ │ Open http://localhost:3444/setup │ │ Code K7QM-92QX-VBHT │ │ │ │ A new code is printed on every start until that first account exists. │ └─────────────────────────────────────────────────────────────────────────┘ -
在 http://localhost:3444/ 登录。
-
按 新建项目(或按
n):将项目命名为demo,目录选择/docs下你自己的某个子目录——主机上的DOCS_HOST_PATH文件夹会挂载到这里,因此主机上的docs/handbook对应这里的/docs/handbook。目录留空会创建空项目; 之后可通过 添加来源 添加来源。 -
在列表中查看项目状态从
indexing → idle变化;文档来源面板会显示每个来源及其文档数量、上次同步时间和任何错误。 -
使用 连接 Agent 选项卡(Claude Code、Cursor、Claude Desktop 和已弃用的旧版 SSE)复制并粘贴代码片段。从 源代码检出目录运行时,内置的冒烟测试也可以通过命令行完成相同操作:
npm install && npm run smoke -- http://localhost:3444/mcp/demo "how do I re-index"
首次启动会初始化数据库并下载嵌入模型到 contextator-models 卷中(默认 fp32 模型约为 470 MB,
EMBEDDING_DTYPE=fp16 时约为 235 MB,EMBEDDING_DTYPE=q8 时约为 120 MB,all-MiniLM-L6-v2 时约为
90 MB);之后启动只需几秒。
不使用 Compose:
docker run -d --name contextator -p 127.0.0.1:3444:3444 \
-e SETUP_CODE=whatever-you-like \
-v contextator-pgdata:/var/lib/postgresql/data \
-v contextator-models:/app/.cache/models \
-v contextator-data:/data \
-v /path/to/your/docs:/docs:ro \
contextator/contextator
从源代码构建镜像,而不是拉取镜像: 自行构建镜像。
标签。 latest 是最新稳定版本;1(从 1.0.0 起)跟踪 1.x 主版本系列中的最新版本;1.0 跟踪 1.0.x 系列中的最新补丁版本;1.0.0 是一个确切且不可变的版本。对于需要主动升级的内容,请在 .env
中设置 CONTEXTATOR_TAG 来固定版本标签(例如 CONTEXTATOR_TAG=1.0.0),然后运行 docker compose up -d——每次启动时都会读取该设置,而不只是在首次启动时读取。从 0.2.0 起的每个此类标签都有对应的 -slim 版本——latest-slim、1-slim、
1.0-slim、1.0.0-slim——详见使用自有
PostgreSQL。
用法
让 Claude Code、Cursor、Claude Desktop 或其他 MCP 客户端连接到 http://localhost:3444/mcp/<project> —
仪表板的 Connect 面板会显示准确的代码片段;如果项目需要令牌,片段中也会包含令牌请求头。对于需要令牌的项目:
claude mcp add --transport http demo-docs http://localhost:3444/mcp/demo --header "Authorization: Bearer ctxm_9f3a…"
这里提供四个只读工具:search_docs(混合搜索,按排名排列的摘录)、list_topics(已索引的文档列表)、read_document(按路径或标题范围读取单个文件的 Markdown)和 whats_new(自某个时间点以来新增、更改或删除的内容)。第五个工具 report_gap 可让 Agent 告知维护者文档未能解答的问题;它会写入自己的表(reported_gaps),绝不会写入文档,并且在关闭查询日志时不可用。除工具外,每个项目还会将文档发布为 MCP 资源,并提供两个只读提示词:answer_from_docs(参数 question)和 explore_topic(参数 topic)。
一个 URL,优先使用 Streamable HTTP。 Streamable HTTP 是连接方式。旧版 HTTP+SSE 传输已弃用:它仍会在同一 URL 上响应并继续可用,目前没有设定移除日期,其响应会携带 Deprecation: true 请求头。同一 URL 除支持 2025-11-25 协议外,也支持无状态 MCP 2026-07-28 协议。
一个项目由一组来源构成——本地目录、git 仓库、上传文件、Notion 工作区、Confluence 站点或文档站点。每个来源都以独立名称在仪表板中添加,因此,名为 handbook 的来源中的 install.md 文件会以 handbook/install.md 的路径进行搜索和读取。Agent 可以将搜索范围限定到某个版本,例如 search_docs(query: "rotate the signing key", version: "v3")。
其余内容——协议阶段、来源类型、访问模式和文件类型——请参阅连接 AI 客户端、文档来源、MCP 访问和文件类型。
工作原理
技术栈:TypeScript · Node.js 24+ · Fastify 5 · PostgreSQL 16 + pgvector · Drizzle ORM · @modelcontextprotocol/server(MCP TypeScript SDK 2.x)· @huggingface/transformers · unpdf / mammoth / turndown。
每次运行都会同步所有来源,对文件进行哈希处理以跳过未更改的文件,将有更改的文件转换并分块(识别 frontmatter,按标题拆分,并为每个块保留面包屑导航),然后为每个块生成嵌入,并与关键词索引一起写入——搜索采用混合方式,将语义与精确措辞按排名融合,因此环境变量名称和问题一样,都能轻松找到对应页面。强制重新索引会在实时索引旁重建所有内容,而不是先将其替换,因此项目在运行期间仍能继续响应。CHUNK_MAX_TOKENS 默认值为经过测量的 96,而非模型大得多的上下文窗口——填满窗口的效果经测量反而更差。
完整运行机制、分块预算的测量方法、混合搜索与排名融合,以及跨语言搜索限制(经测量得出,并非固定值,以及可采取的替代做法):索引和嵌入模型。
配置
所有配置项均通过环境变量设置;完整的注释列表请参见 .env.example。
大多数安装会用到以下变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT / HOST | 3444 / 0.0.0.0 | |
DATABASE_URL | – | 留空时使用内嵌 PostgreSQL;设置该变量即可使用自备数据库——参见自备 PostgreSQL |
ALLOWED_DOC_ROOTS | /docs | 以逗号分隔。项目目录必须位于其中一个目录下 |
SECRET_KEY | – | 至少 32 个字符。用于加密静态存储的 git/Notion/Confluence 令牌和 webhook 密钥 |
ADMIN_TOKEN | – | 使用 root 权限访问 /api/* 的机器令牌 |
EMBEDDING_PROVIDER / EMBEDDING_MODEL | local / Xenova/multilingual-e5-small | 参见更换嵌入模型 |
PUBLIC_BASE_URL / TRUST_PROXY | – / 0 | 在反向代理后运行时必须同时设置——参见在反向代理后运行 |
OIDC_ISSUER_URL | – | 联合登录的开关——参见单点登录 |
完整表格涵盖所有存储、上传、分块、搜索排序、同步、身份验证和可观测性变量,并说明了设置原因,详见配置。
部署
快速入门中的单容器方案是默认方案,大多数安装都应继续使用它。 此外还有两种部署拓扑:
-
使用自备 PostgreSQL。 在
.env中添加一行即可将数据库移出容器:DATABASE_URL=postgres://user:[email protected]:5432/contextator需要 PostgreSQL 16 或更高版本并安装 pgvector,备份也由你负责。
-slim镜像不包含数据库。参见自备 PostgreSQL。 -
Kubernetes。
charts/contextator/是一个 Helm chart,适用于-slim+ 外部数据库的部署拓扑。参见 在 Kubernetes 上运行。
在反向代理后运行时,必须同时调整三个设置: 在反向代理后运行。关于数据的存储位置以及哪些数据会离开本机,请参见数据去向。
数据与持久化
| 内容 | 容器内路径 | 默认卷 | 覆盖方式(.env) |
|---|---|---|---|
| PostgreSQL 集群:项目、文档、嵌入 | /var/lib/postgresql/data | contextator-pgdata | CONTEXTATOR_PGDATA_VOLUME(其他卷名)或 CONTEXTATOR_PGDATA_PATH(主机上的绝对目录) |
| 已下载的嵌入模型 | /app/.cache/models | contextator-models | CONTEXTATOR_MODELS_VOLUME 或 CONTEXTATOR_MODELS_PATH |
| 已物化的数据源:上传的文件、git 检出内容、Notion 拉取内容 | /data | contextator-data | CONTEXTATOR_DATA_VOLUME 或 CONTEXTATOR_DATA_PATH |
| 你的文档(只读) | /docs | – | DOCS_HOST_PATH(默认 ./docs) |
docker compose down、docker compose pull && docker compose up -d 和 docker rm contextator 都会保留这些卷。只有 docker compose down -v 或 docker volume rm 才会删除它们。示例:
CONTEXTATOR_PGDATA_PATH=/srv/contextator/pgdata # Linux server: keep the database on a chosen disk
CONTEXTATOR_MODELS_PATH=D:/contextator/models # Windows host directory (forward slashes)
CONTEXTATOR_PGDATA_VOLUME=contextator-pgdata-v2 # or simply another named volume
主机目录会在首次启动时创建,入口点会修正其所有权。在 Linux 风格路径和使用 Docker Desktop for Windows(WSL 2 后端)时,这两种覆盖方式都已验证;PostgreSQL 在 C:/… 目录上能够正常初始化和建立索引。在 Docker Desktop 上,数据库使用命名卷速度更快;如果 initdb 在主机目录上报告权限错误,请将该挂载改回使用卷。
在设置了 DATABASE_URL 的安装中(自备 PostgreSQL)
数据库是此镜像不会操作的服务器。npm run backup 和 npm run restore 仍然
可以正常工作:它们通过 DATABASE_URL 连接该服务器,因为归档中的上传目录树没有其他
备份——而且 backup 会在输出和归档清单中说明,重要的是服务器自身的备份方案。请从默认镜像运行它们:-slim 镜像没有
pg_dump,会停止并提示它不在 PATH 中(拒绝原因 no_pg_tools)。默认镜像的客户端是 PostgreSQL 16,因此连接到
17+ 服务器时,pg_dump 会因 server version mismatch 停止(而 pg_restore 16 无法读取 17 的转储):
请在容器中安装与服务器主版本匹配的 postgresql-client-<major>,并将
/usr/lib/postgresql/<major>/bin 放在 PATH 的最前面——Helm chart 中的
进行备份展示了具体做法。在这种
拓扑下,不适用的是所有与内嵌集群有关的内容:下文列出的三个手动命令(以用户
contextator 身份运行的 psql、pg_dump、pg_restore)无法连接到任何对象,而 pgdata 卷为空。以下两条关于转储
不包含内容的项目适用于两种拓扑,关于 /data 的段落也同样适用。
**外部数据库的定时备份。**将可选 sidecar 添加到 slim 堆栈中:
docker compose -f docker-compose.slim.yml -f docker-compose.backup.yml up -d
docker compose -f docker-compose.slim.yml -f docker-compose.backup.yml logs -f backup
sidecar 会等待应用变为健康状态,运行 npm run backup,然后在每次尝试后等待 BACKUP_INTERVAL_SECONDS(默认值为
86400,即一天)。通过循环休眠,这种可选方案无需依赖 cron
守护进程;它会在每次运行结束后开始计算间隔,而不承诺按固定时钟时间执行。
在 .env 中设置 BACKUP_INTERVAL_SECONDS 和 BACKUP_KEEP,即可更改间隔和保留数量;两者
都必须是正整数。默认保留 7 个已完成的归档。备份失败时会保留
所有现有归档,并在间隔结束后重试。设置无效时,最多重启容器三次后便会停止;修正 .env 后,运行 up -d backup 即可重新启动。
它使用默认的非 slim 镜像提供 PostgreSQL 工具,覆盖该镜像的入口点,
以 UID/GID 1000:1000 运行以匹配 slim 应用(并将 HOME=/tmp 用于 npm),且只将同一个 data 卷挂载到 /data。它不会启动内嵌 PostgreSQL,也不会挂载
pgdata。它使用应用的 .env,包括 DATABASE_URL 和 SECRET_KEY,因此归档
会记录应用 SECRET_KEY 的指纹,使用该密钥还原时也能通过密钥检查。
PostgreSQL 客户端的主版本必须不低于服务器的主版本。
归档位于 /data/backups/,先写入 .partial 文件,仅在备份成功后才重命名,因此中断的写入不会计入保留数量。停止时,sidecar 会先向备份进程组发送 TERM,等待一秒后再发送 KILL,并删除其部分归档和私有暂存文件。强制终止后,
下一轮会清除 /data/.scheduled-backup-staging 下残留的定时备份 .partial 归档和暂存文件。每个数据卷只运行一个
备份 sidecar。保留策略仅适用于默认的带时间戳
contextator-backup-<timestamp>.tar.gz 文件名;自定义名称的归档不会被清理。也请将备份复制到主机之外:与应用共用数据卷并不能防止该卷丢失。
按需备份也会从默认镜像运行,因为 slim 应用容器没有 pg_dump。复用 backup 服务定义,但替换其循环并清除
BACKUP_STAGING_DIR,使备份暂存到 /data/.backup-*,sidecar 的清理程序不会触及该目录。为归档指定自定义名称,这样它就不会计入保留数量:
docker compose -f docker-compose.slim.yml -f docker-compose.backup.yml run --rm --no-deps \
-e BACKUP_STAGING_DIR= --entrypoint npm backup \
run backup -- /data/backups/contextator-manual-$(date +%F).tar.gz
要将归档恢复到全新安装,请按以下流程运行 npm run restore -- <file> --check 和
npm run restore -- <file>,但两个命令都要像上面的按需备份那样运行(run --rm --no-deps -e BACKUP_STAGING_DIR= --entrypoint npm backup run restore -- …),
而不是在精简版应用容器中使用 docker exec,并使用
docker compose -f docker-compose.slim.yml restart contextator 重启应用。恢复期间请停止备份 sidecar,以免它把正在进行的恢复操作也打包进归档:
docker compose -f docker-compose.slim.yml -f docker-compose.backup.yml stop backup
恢复完成后,使用 up -d backup 再次启动它。
内置 PostgreSQL 仅在容器内部监听 127.0.0.1,不会对外发布,因此这里的所有操作都通过容器进行。
只需一条命令即可完成备份,因为 pg_dump 并不等同于安装内容:
docker exec contextator npm run backup -- /data/backups/contextator-$(date +%F).tar.gz
docker cp contextator:/data/backups/contextator-$(date +%F).tar.gz .
在全新安装上恢复——这才是关键情形——要从该目录开始:
# The directory first — on a fresh installation nothing has created it yet, and `docker cp` into a
# directory that is not there answers `no such directory`.
docker exec contextator sh -c 'mkdir -p /data/backups'
docker cp contextator-2026-09-22.tar.gz contextator:/data/backups/
# Reads the manifest and every refusal, writes nothing:
docker exec contextator npm run restore -- /data/backups/contextator-2026-09-22.tar.gz --check
docker exec contextator npm run restore -- /data/backups/contextator-2026-09-22.tar.gz
docker compose restart contextator
运行 npm run backup 且不指定任何路径,它会将备份写入 <DATA_DIR>/backups/contextator-backup-<timestamp>.tar.gz
——位于数据卷上,绝不会写入容器的工作目录,因为那里属于镜像层。
备份到兼容 S3 的存储桶。 设置 BACKUP_S3_BUCKET、BACKUP_S3_ACCESS_KEY_ID 和
BACKUP_S3_SECRET_ACCESS_KEY(MinIO、Ceph 或 R2 可选设置 BACKUP_S3_ENDPOINT,以及 BACKUP_S3_REGION、
BACKUP_S3_PREFIX、BACKUP_S3_SESSION_TOKEN),并添加 --s3;归档会在暂存目录中创建,上传(超过 16 MiB 时使用分段上传),然后从本地删除;前缀下只保留最新的 BACKUP_KEEP
(默认为 7)个带时间戳的归档:
docker exec contextator npm run backup -- --s3
docker exec contextator npm run restore -- --s3 latest --check
docker exec contextator npm run restore -- --s3 latest
docker compose restart contextator
latest 表示最新的带时间戳归档;指定名称则恢复对应归档。请求采用路径风格,并由脚本自身使用 SigV4 签名,因此无需安装额外软件包。服务端不会安排这些操作:
在 Kubernetes 上,chart 会生成可选的 CronJob
(定时备份到 S3);其他环境请使用 cron 或编排器。使用 --s3 运行 restore --check 时,会先将归档下载到数据卷,再删除该副本。
这些代码块是本流程的唯一副本。 wiki 和文档站点都不包含相关命令,只会链接到此处。restore 会覆盖正在运行的数据库,
因此命令只能在此处修改,不能在其他地方修改。若出现自称为镜像的副本(带有 MIRRORED-IN / MIRRORED-FROM 标记),test/readme-mirrors.test.ts 会报错;它无法发现其他代码库中未标记的副本,这需要通过评审来发现。
归档包含数据库、每个 upload 来源的物化文件——这些文件在其他地方都不存在——以及一个清单,该清单位于归档的第一项,因此即使文件有数 GB,--check 也只需读取一个小文件。
归档中不包含 SECRET_KEY,以后也绝不会包含。 如果备份携带该密钥,那么整个实例就会被装进一个文件,而该文件所在卷的访问安全性低于密钥来源环境的安全性。归档中保存的是密钥的指纹——128 位的带密钥 HMAC——这使 restore 能够在写入任何内容之前,拒绝错误或缺失的密钥;只有当密钥确实有实际价值时才会如此——如果某个归档的来源中没有使用该密钥加密的同步凭据,即使存在相关提示,也会照常恢复,而不会阻止明确安全的情况。只有 git、notion、confluence、s3、gdrive 和 github 来源才保存同步凭据,因此只有这些来源的密钥会被检查。对于在清单记录所检查类型之前创建的归档,会在接触目标环境之前,根据归档自身转储中的记录进行判断:旧版本在 local、upload 或 web 来源中存储的密钥不会导致拒绝恢复;恢复后,这些密钥——从未被读取过——会被清空,并打印其数量。请将密钥保存在归档之外。
该密钥可以轮换——有一份四步操作手册(第 3 步是 npm run rotate-secret)——这也让上文多了一层含义:停用旧密钥——移除
SECRET_KEY_PREVIOUS——然后销毁它,会使所有在轮换前创建、且包含某个来源同步凭据的归档失效;这些凭据可能是私有 git、Notion、Confluence 或 GitHub 令牌、S3 密钥对,或 Google 服务账号密钥,它们都使用该密钥加密。旧归档的指纹指向一个环境中已不再持有的密钥;对于该归档,restore 会拒绝恢复,因为凭据的密文是用已停用并销毁的密钥写入的,无法重新推导出来;没有此类凭据的归档仍可恢复,而拒绝恢复发生在四步中的第 2 步,不是第 4 步。保留停用的密钥而不销毁它,就能在保留期限到期前继续恢复该归档。轮换期间应按以下顺序分三步创建备份:轮换之前,使用当前密钥创建常规备份;npm run rotate-secret 报告所有记录都已转换后、移除 SECRET_KEY_PREVIOUS 之前,使用新密钥创建一个新归档——这是轮换后的实例能够恢复的最早归档;将停用的密钥保存在密钥存储中,做好标记,不要与归档放在一起,直到最早的轮换前归档超过保留期限——届时才能销毁。完整操作手册及每一步的命令见
安全 → 轮换相关项。
恢复操作还会拒绝从比目标服务器 PostgreSQL 主版本更高的版本创建的转储;反过来,从 16 升级到 17 是文档记载的升级方式,也是这条命令的用途。
手动操作,只处理数据库,不处理其他内容——测试套件运行的就是以下两条命令:
docker exec -it contextator psql -U contextator
docker exec contextator pg_dump -U contextator -Fc contextator > contextator.dump
docker exec -i contextator pg_restore -U contextator -d contextator --clean --if-exists < contextator.dump
test/integration/backup-restore.itest.ts 会准备一个真实的 PostgreSQL,创建转储,删除数据库,再恢复数据库,随后断言架构、搜索结果(行完全相同、顺序完全相同、分数完全相同)以及应用的下一次启动都与原来一致。仅检查行数无法发现恢复时丢失了向量索引或词法列,因此这不是断言项。接着,同一个文件会通过 npm run backup 和 npm run restore 执行完整往返流程,其中包含一个上传来源和一个加密凭据,并断言错误的密钥不会改动实例。
转储不包含以下两项,这正是上面命令存在的全部原因:
- **
SECRET_KEY。**它是一个环境变量。没有原始密钥,就必须重新输入所有已存储的来源凭据。将它保存在转储以外的地方——转储加密钥才是完整实例——并保留轮换时停用的密钥,否则轮换前的转储将无法恢复。 /data。Git 检出可以重新克隆,Notion 数据可以重新拉取,但上传来源的current/目录是其内容唯一的副本。/data备份可以仅保留这些内容,跳过可重新克隆的数 GB 数据,这正是npm run backup的做法。
在没有项目处于 indexing 状态时创建备份:重新索引会在现有数据旁写入第二代数据,而 pg_dump 无法筛选记录,因此在运行过程中创建的转储大小会翻倍。预计每个已索引区块的转储大小约为 2 KB——向量会以文本形式转储,再压缩回去——并且恢复所需时间大多花在重建 HNSW 索引上:每个项目一个索引,且每个索引都会扫描实例中的所有区块,以找到所属项目的记录,因此恢复耗时同时取决于实例大小和项目数量。
备份会暂停项目的创建和删除,直到备份结束。 pg_dump 会在一个快照中读取数据库;创建项目时,会通过 CREATE INDEX CONCURRENTLY 构建其 HNSW 索引,删除项目时则会通过 DROP INDEX CONCURRENTLY 删除索引——这两种操作都会等待启动时已打开的所有事务结束。在备份期间创建项目(POST /api/projects 或通过仪表板)、删除项目或导入项目,都会等到备份完成后才返回;不会发生故障,现有项目中的搜索和索引操作也不会暂停。请安排在项目创建较少的时段进行备份。
POSTGRES_PASSWORD 在集群创建时生效。之后如需更改,请通过 psql 运行 ALTER USER contextator PASSWORD '...',并在下次启动前更新 .env。
升级
常规镜像升级只需运行 docker compose pull;它会更新应用,而不会更新集群。唯一需要按步骤进行的升级,是 PostgreSQL 主版本升级。
跨主版本升级 PostgreSQL
常规镜像升级绝不会执行此操作。 docker compose pull 会更新应用;它不会更新集群,因为集群存储在卷中,而镜像中的 PostgreSQL 主版本决定了哪些版本可以读取它。如果某个版本从 PostgreSQL 16 升级到 17,那么这一天就需要执行以下步骤,而且应在拉取镜像之前执行,而不是之后。
如果直接拉取镜像会发生什么:
PostgreSQL Database directory appears to contain a database; Skipping initialization
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with
this version 17.11 (Debian 17.11-1.pgdg12+2).
[contextator] PostgreSQL exited during startup (exit code 1)
容器以 1 退出,restart: unless-stopped 会再次启动它,然后它又以 1 退出。不会造成任何损坏:旧集群未被改动,旧镜像仍能读取它。
如果你现在正处于这种情况,解决办法只有一行。 将 CONTEXTATOR_TAG 改回你之前使用的主版本,运行 docker compose up -d,就能恢复到原来的状态——然后按照顺序,在运行中的旧镜像上执行下方的升级步骤。不要从后面进一步的回滚代码块开始:该代码块会还原集群的暂存副本,而意外拉取镜像时尚未创建副本,因此没有可供还原的内容。
pg_upgrade 并非可行的路径,原因值得了解:它需要同时存在两个主版本的二进制文件,而此镜像中恰好只有一个。因此应采用逻辑转储和还原的方式——也就是像上文一样运行 npm run backup 和 npm run restore,跨越版本边界完成迁移。
首先查明集群卷的实际名称,因为第 3 步会按名称删除卷,而 docker run -v <name>:/from 在该名称不存在时会创建一个空卷,且不会报错。docker-compose.yml 将其命名为 ${CONTEXTATOR_PGDATA_VOLUME:-contextator-pgdata},因此设置了该变量的实例会使用不同的名称;而设置了 CONTEXTATOR_PGDATA_PATH 的实例根本没有卷,只有一个主机目录,需要在主机上通过 cp -a 复制和移动。
第 3 步采用的是一条 && 命令链,而不是在各行之间插入警告的列表;这就是全部保护措施:docker volume inspect 在名称不存在时会失败,cp -a 在磁盘空间不足时会失败,PG_VERSION 比对在复制不完整时会失败——上述任一情况都会在运行 docker volume rm 之前终止命令链。删除操作刻意放在最后一步,所以“已停止”和“什么也没删除”是一回事。实际使用时通常会一次性粘贴整个代码块,因此保护措施写在 shell 命令中,而不是周围的说明文字里。
请仔细阅读 STOPPED: 的实际内容,因为这两个代码块故意表达不同的意思。 每个代码块都是链中各命令的 ||,因此只要任一环节失败就会打印;此时的实际情况取决于链在哪个环节删除操作的前后停止。第 3 步中,删除是最后一个环节,因此停止意味着卷仍然存在。回滚中,删除不可能是最后一个环节——必须先清空卷才能重新填充——所以清空和重新填充是一个容器命令,而提示信息陈述了该链每种失败情况下唯一都成立的事实:暂存副本仅被读取。如果两个地方都提示“没有删除任何内容”,其中一处就会说错。
如果某个实例将集群保存在主机目录(CONTEXTATOR_PGDATA_PATH)中,这些操作就完全不涉及卷:在主机上用 cp -a 将该目录复制到别处,检查副本,然后清空原目录,而不是移除卷。
PGVOL=${CONTEXTATOR_PGDATA_VOLUME:-contextator-pgdata} # from your .env; the default is shown
OLD=16 # the major you are leaving
# 1. On the OLD image, still running. Take it when no project is indexing.
# No mkdir here: `backup` creates the directory it is given. Step 5 needs one, because
# `docker cp` does not.
docker exec contextator npm run backup -- /data/backups/pre-pg17.tar.gz
docker cp contextator:/data/backups/pre-pg17.tar.gz . # off this host, not beside the volume
# 2. Stop the old container. KEEP its cluster — it is the rollback.
docker compose down
# 3. Copy the old cluster aside, CHECK THE COPY, and only then remove the original.
# Step 4 recreates the volume — Compose creates a named volume it does not find — so there is
# no `docker volume create "$PGVOL"` here to be the link after the deletion.
docker volume inspect "$PGVOL" >/dev/null &&
docker volume create "$PGVOL-pg$OLD" &&
docker run --rm -v "$PGVOL":/from -v "$PGVOL-pg$OLD":/to alpine sh -c 'cp -a /from/. /to/' &&
[ "$(docker run --rm -v "$PGVOL-pg$OLD":/to alpine cat /to/PG_VERSION 2>/dev/null)" = "$OLD" ] &&
docker volume rm "$PGVOL" ||
printf '%s\n' \
"STOPPED: see the error above." \
"$PGVOL has NOT been removed: the removal is the last link of that chain, so it was either" \
"never reached or refused by Docker. The parked copy $PGVOL-pg$OLD may or may not exist and" \
"may or may not be complete — nothing depends on it yet." \
"Fix the error and run this block again; it is safe to repeat."
# 4. Pull the new image and start it. It initdb's the empty volume and creates an empty schema.
docker compose pull && docker compose up -d
docker exec contextator psql -U contextator -c 'select version()'
# 5. Put the instance back.
docker exec contextator sh -c 'mkdir -p /data/backups'
docker cp pre-pg17.tar.gz contextator:/data/backups/
docker exec contextator npm run restore -- /data/backups/pre-pg17.tar.gz --check
docker exec contextator npm run restore -- /data/backups/pre-pg17.tar.gz
docker compose restart contextator
回滚是第 3 步的逆向操作,并且采用复制而不是重命名——Docker 没有 volume rename。将 CONTEXTATOR_TAG 改回原先使用的版本,然后:
# Re-stated here on purpose: a rollback is run later, and often in a shell that never saw step 1.
PGVOL=${CONTEXTATOR_PGDATA_VOLUME:-contextator-pgdata}
OLD=16
docker compose down
# The emptying and the refilling are one container command, so no link here deletes something the
# next link then fails to replace. `$PGVOL-pg$OLD` is only ever read.
docker volume inspect "$PGVOL-pg$OLD" >/dev/null &&
docker run --rm -v "$PGVOL-pg$OLD":/from -v "$PGVOL":/to alpine \
sh -c 'find /to -mindepth 1 -delete && cp -a /from/. /to/' &&
docker compose up -d ||
printf '%s\n' \
"STOPPED: see the error above." \
"Nothing in this block writes to $PGVOL-pg$OLD — it is only read — so the cluster you are" \
"rolling back to is whatever it was before you started." \
"$PGVOL may be untouched, emptied, or half-written depending on where this stopped, so do NOT" \
"start the old image against it yet. Fix the error and run this block again: it empties and" \
"refills $PGVOL from scratch, so repeating it is safe."
这会丢弃新主版本中的所有内容,而在第 5 步之后,其中保存的是已恢复的实例——所以这是回滚到第 1 步发生时的状态,而不是回滚到你执行此操作时的状态。期间索引的内容都需要重新编入索引。
开始前需要了解三件事。
- 另一端的
SECRET_KEY必须相同。 归档中不包含它;如果缺失或不同,还原会在写入前停止。请准备好.env文件——如果自创建此归档后轮换过SECRET_KEY,那么现在.env中的密钥就不对:轮换前的归档只能使用创建归档时的密钥还原,因此请在此处使用已停用的密钥,或在开始前重新备份。 - 还原会重建所有索引,而这会占用大部分运行时间。 请根据整个实例的分块数量安排维护窗口。
- 还原会拒绝反向操作。 从 17 创建的转储不能导入 16 服务;它会在写入前明确报错,而不会只导入一部分。因此,回滚针对的是卷,不是转储。
本流程仅在此处保留一份。 它与实现 backup 和 restore 的代码放在一起并随代码版本更新,因为删除集群的流程必须只有一份确切说明其操作的文档。Wiki 和文档网站都链接到此处,不会重复列出命令。
文档
- 文档源 — 来源类型、版本、同步、Webhook
- 账户与权限
- MCP 访问 — 开放访问、需要令牌、需要账户;面向浏览器客户端的 OAuth
- 连接 AI 客户端
- 文件类型
- 配置 — 反向代理、使用自有服务嵌入、更换模型
- 部署拓扑 — 外部 PostgreSQL、Kubernetes
- 管理 API和扩展信号
- 数据去向、安全说明、故障排除、许可证
- 完整指南位于contextator.com,从源代码安装的说明见安装。
- 兼容性政策 —
1.x承诺不破坏哪些内容
项目状态
自 1.0.0 起稳定。 公共契约——MCP 和管理 API、环境变量、CLI、迁移以及备份归档——仅在主版本中变更。涵盖范围、弃用流程、明确列出的例外以及当前已弃用的内容,均见 docs/compatibility.md。变更记录列于 CHANGELOG.md。
贡献
欢迎贡献。CONTRIBUTING.md 详细说明了本项目的协作方式:如何运行项目、变更必须通过的四项检查、同一条说明需要在两个文件中保持一致的五处位置,以及提交记录。文档还说明了外部贡献者需要授予的许可(CLA.md,在拉取请求中签署);由于下文所述的商业许可以完整持有版权为前提,因此需要这项许可授予。CODE_OF_CONDUCT.md 采用 Contributor Covenant,并适用于本项目。
安全
发现安全问题?请勿提交问题。 SECURITY.md 说明了应通过何种方式报告,并列出了看似漏洞、实则属于已记录行为的情况——例如项目被设置为 open,以及 MCP 令牌能够和不能授予的权限。安全说明介绍了默认设置所能保障的内容。
许可证
本项目采用 GNU Affero 通用公共许可证第 3 版或更高版本(AGPL-3.0-or-later)。如果你修改了 Contextator,并通过网络让他人访问修改后的版本,就必须向这些用户提供完整源代码;运行未修改的软件则不会触发此要求。版权由 Muhammet Şafak 完整持有,因此在 AGPL 不适用的情况下,可以授予单独的商业许可证——请通过 tunedness.com 联系。完整说明见 docs/license.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。