Skip to main content
Glama

Athena

一个你的 AI 可以写入、你也可以自行浏览的个人维基。

Athena 在 Wiki.js 前放置了一个 MCP 服务器。你的助手可以搜索维基、阅读页面,并将新内容归档:笔记、文档、完整的对话。它写下的所有内容都是普通的 Markdown 页面,你可以打开、编辑,并在任何特定模型消失后长久保存。

Claude / ChatGPT / Cursor
        │  MCP over HTTPS
        ▼
   athena-mcp ──── search ──▶ Wiki.js (keyword) + Postgres (meaning)
        │          read ────▶ Wiki.js
        └────────  write ───▶ Wiki.js ──▶ athena-indexer ──▶ Postgres

Wiki.js 保存着真实数据。向量索引仅用于辅助查找,可以随时删除并重建。

快速上手

快速开始 · 连接你的 AI

使用它

工具 · 仪表盘

正式部署

部署到服务器 · 备份

参考

配置 · 安全 · 运维 · 开发


快速开始

本地运行,大约五分钟。如需部署到互联网,请先阅读部署到服务器。

git clone https://github.com/jannismilz/athena.git
cd athena
cp .env.example .env
$EDITOR .env          # fill in every CHANGE_ME, one per secret:
                      #   openssl rand -hex 32
docker compose up -d

然后:

  1. 打开 Wiki.js 并完成设置向导。

  2. 在 Wiki.js 中:管理 → API,启用它,创建一个令牌,并将其放入 .env 文件中的 WIKI_API_TOKEN。

  3. 再次运行 docker compose up -d 以使其生效。

  4. 打开仪表盘并使用 DASHBOARD_TOKEN 登录。

数据写入到代码检出目录旁边的 data/ 目录,而非其内部,因此任何 git 操作都无法删除它。如需更改位置,请修改 ATHENA_DATA_DIR。

默认不开放任何端口,因此需要通过你的反向代理访问服务,或在试用时临时添加 ports: 映射。

首次启动会下载一个几百 MB 的嵌入模型。索引器会重试直到模型就绪,因此首次启动时 embeddings 显示不健康一两分钟是正常的。


Related MCP server: wiki-js-mcp

连接你的 AI

所有服务都通过 MCP_PUBLIC_URL 提供,该地址必须是一个裸的 https:// 源,不带路径。不要使用 /mcp。

Claude.ai → 设置 → 连接器 → 添加自定义连接器

  • URL: https://athena-mcp.example.com/mcp

  • 客户端 ID 和密钥留空。Athena 会自行注册客户端。

  • 浏览器页面会要求输入密码。这就是你的 MCP_TOKEN。

Cursor、Claude Desktop 和其他使用标头的客户端

{
  "mcpServers": {
    "athena": {
      "url": "https://athena-mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_MCP_TOKEN" }
    }
  }
}

工具

工具

功能说明

search_knowledge

关键词与语义搜索,结果融合。每条结果都带有路径。

get_page

获取一个页面的完整 Markdown 内容

get_page_structure

获取标题大纲,不含正文

append_to_page

在某个标题下追加内容,其余部分保持不变

create_page

创建新的 Markdown 页面

update_page

替换页面正文

move_page

移动或重命名页面

delete_page

删除页面,并从索引中移除

save_conversation

将对话归档到 conversations/YYYY/MM/ 目录下

capture_note

快速记录到 inbox/ 目录,供日后整理

list_pages

列出所有页面,包含路径和时间戳

get_wiki_stats

获取维基的大小、结构和陈旧程度,以便 AI 判断缺少什么

append_to_page 是最值得了解的工具:添加一个事实只需一个段落,无需重写整个页面。

为什么检索效果好。 精确术语命中 Wiki.js 全文索引,模糊问题命中向量索引,结果通过倒数排序融合(reciprocal rank fusion)合并,确保任一来源都不会被埋没。文本块会记录其上方标题,因此返回的内容保留了上下文。助手访问的每个页面都会标记是哪个助手在何时访问的,这些信息来自经过身份验证的客户端,而非模型自称的信息。


仪表盘

独立服务,运行在 8082 端口。使用 DASHBOARD_TOKEN 登录;URL 中不包含任何令牌。对于脚本,请使用 Bearer 标头:

curl -H "Authorization: Bearer $DASHBOARD_TOKEN" \
  https://wiki.example.com/dashboard/api/metrics?days=30

面板

功能说明

内容

页面数、字数、按区域统计、最大页面、即将过期的页面

AI 活动

每日调用次数、使用的工具、使用的助手、读取 vs 写入

未找到结果的搜索

你的维基无法回答的内容

索引健康

存储的块数、已索引的页面数、落后程度

备份

上次运行完成时间、大小、存储位置

第三行是最有价值的面板。每条记录都值得写成一个页面。

它双重只读:从不写入,并且以 athena_readonly 角色连接 Postgres,该角色仅拥有 SELECT 权限。数据在 Postgres 中聚合并缓存,因此刷新几乎不消耗资源。


部署到服务器

一台 4 GB 的 VPS 即可运行所有服务,包括在 CPU 上运行的嵌入模型。

1. 主机与防火墙

sudo ufw default deny incoming && sudo ufw default allow outgoing
sudo ufw allow 22/tcp && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
sudo ufw enable

安装 Docker,然后创建一个拥有部署权限的用户:

sudo useradd --create-home --shell /bin/bash athena
sudo usermod -aG docker athena

以该用户身份运行 Compose,切勿使用 sudo,否则绑定挂载的文件将归 root 所有。docker 组成员身份等同于主机上的 root 权限,因此请严格控制成员数量。

2. DNS

指向主机的两条 A 记录:

名称

服务内容

wiki.example.com

Wiki.js,以及 /dashboard/ 路径下的仪表盘

athena-mcp.example.com

MCP 端点

3. 布局与配置

Athena 写入的所有内容都通过一个设置 ATHENA_DATA_DIR 控制,因此整个安装可以位于单个目录下。使用两个具有不同生命周期的子目录:

/athena
├── app/     the git repository   replaceable, thrown away on every upgrade
└── data/    postgres, state,     irreplaceable, never touched by git
             uploads, backups

它们是同级目录,而非嵌套,这正是关键所在。data/ 在 .gitignore 中,而 git clean -xdf 会删除被忽略的文件,因此检出目录内的数据可能因一个常规命令而被删除,且无确认和撤销操作。同级目录则无法被任何 git 操作触及。

默认的 ATHENA_DATA_DIR=../data 会自动为你提供此布局,因此无需记忆。

sudo mkdir -p /athena && sudo chown athena:athena /athena
cd /athena
git clone https://github.com/jannismilz/athena.git app
cd app
cp .env.example .env
chmod 600 .env        # it holds every secret

ATHENA_DATA_DIR 默认为 ../data,该路径相对于存放 Compose 文件的目录解析。如上所述克隆到 /athena/app,数据将自动落入 /athena/data,无需额外配置。设置密钥:

POSTGRES_PASSWORD=...
MCP_TOKEN=...
DASHBOARD_TOKEN=...
DASHBOARD_DB_PASSWORD=...
MCP_PUBLIC_URL=https://athena-mcp.example.com
WIKI_PUBLIC_URL=https://wiki.example.com

Compose 会在首次启动时创建 /athena/data 及其子目录。所有 docker compose 命令都从 /athena/app 目录运行。

/athena/data
├── postgres/     the wiki, users, settings, uploads, activity log, vectors
├── wikijs/       Wiki.js config, cache, upload cache
├── mcp/          oauth-state.json, the tokens issued to AI clients
├── indexer/      index bookkeeping, rebuilt automatically if lost
├── embeddings/   the downloaded model
└── backups/      local dumps plus status.json

只有 postgres/ 是不可替代的,备份容器会每小时转储它。其他所有内容要么自动重新生成,要么只需重新连接一次。

如果你更倾向于遵循文件系统层次结构标准,可以将数据放在 /srv/athena,将代码检出放在 /opt/athena。对于只做一项工作的机器,上述单根布局更简单,两种方式都可行:仅由 ATHENA_DATA_DIR 决定。

4. 反向代理

没有容器开放端口。服务位于两个网络上:

  • athena,内部网络。Postgres、嵌入模型和索引器仅在此网络中,因此即使代理被攻破也无法访问数据库。

  • athena-edge,你的反向代理加入的网络。只有以下三个服务在此网络中。

路由配置如下:

主机

目标地址

备注

wiki.example.com

wikijs:3000

WebSocket 升级,100M 请求体限制

wiki.example.com/dashboard/

dashboard:8082

athena-mcp.example.com

mcp:8080

不得缓冲,MCP 流式传输

转发 X-Forwarded-For:登录功能按地址限流,没有此标头,所有尝试看起来都来自代理。

如下所示,将 nginx 作为容器运行并加入 athena-edge 网络,或者在主机上运行并绑定 127.0.0.1 的 ports: 映射。加入边缘网络意味着代理可以访问 Wiki.js、MCP 服务器和仪表盘,而无法访问其他任何内容。

server {
    listen 80;
    server_name wiki.example.com;

    location / {
        proxy_pass http://wikijs:3000;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "upgrade";
        client_max_body_size 100M;
        proxy_read_timeout 120s;
    }

    location /dashboard/ {
        proxy_pass http://dashboard:8082/;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name athena-mcp.example.com;

    location / {
        proxy_pass http://mcp:8080;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # MCP streams responses. Without these, long tool calls appear to hang.
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;
    }
}

然后使用 certbot 颁发证书,或在你已有的任何地方终止 TLS。

这里没有特定于平台的内容。一个运行 Compose 并提供自身代理的 PaaS 需要三个设置,全部在 .env 中:

ATHENA_DATA_DIR=../files          # Dokploy's persistent directory
ATHENA_EDGE_NETWORK=dokploy-network
ATHENA_EDGE_EXTERNAL=true

ATHENA_DATA_DIR 最为重要:Dokploy 会在重新部署时清理绝对绑定挂载路径,因此使用绝对路径会破坏数据库。相对于应用目录的路径则可以保留。

然后在平台 UI 中添加域名,指向服务及其端口:

域名

服务

端口

wiki.example.com

wikijs

3000

athena-mcp.example.com

mcp

8080

wiki.example.com/dashboard

dashboard

8082

平台会生成自己的路由标签并处理 TLS,因此完全跳过 nginx 部分。其他所有内容,包括 Compose 文件,保持不变。

你无需将镜像发布到仓库:Dokploy 会从仓库构建。构建四个镜像确实会与 Postgres 和嵌入模型争夺内存,因此在小型主机上,你可能更倾向于在 CI 中构建并拉取镜像。

5. 启动,然后锁定维基

docker compose up -d && docker compose ps

立即完成 Wiki.js 向导。否则,任何找到该主机的人都可以认领管理员账户。然后,在 Wiki.js 中:

  • 用户组 → 访客:移除读取权限,除非你希望维基公开。

  • 认证:关闭自助注册。

  • API:启用它并创建 WIKI_API_TOKEN 所需的令牌。

6. 验证

curl -s https://athena-mcp.example.com/health

# Must reject unauthenticated calls:
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://athena-mcp.example.com/mcp
# expected: 401

备份

一次 pg_dump 就是完整的备份。 Wiki.js 将页面、历史记录、用户、权限、设置以及所有上传文件的字节都存储在 Postgres 中。上传文件存储在 assetData 表中;data/wikijs/uploads 下的文件只是缓存。Athena 的活动日志和搜索向量存储在同一个服务器上的第二个数据库中。

数据

备份中是否包含

页面、历史记录、用户、设置

是

上传的图片和文件

是

活动日志和搜索向量

是

索引簿记、OAuth 注册信息

否,会重建或重新连接

.env

否,请在密码管理器中保留副本

backup 容器每小时运行一次。每次运行会转储两个数据库,检查每个转储文件是否可读,保留本地副本,推送到你的 rclone 目标,验证上传是否匹配,然后才进行清理。失败的运行永远不会删除你最后一个良好的备份。

docker compose run --rm backup now            # take one now
docker compose run --rm backup restore list   # see what exists
docker compose logs -f backup                 # watch the schedule

完全在 .env 中配置。任何 rclone 目标都适用:S3、Backblaze、Wasabi、MinIO、Hetzner。将 BACKUP_REMOTE 留空则仅将备份保留在宿主机上。

添加一个 crypt remote 并将 BACKUP_REMOTE 指向它。这样目标端只会收到密文,包括文件名。

BACKUP_REMOTE=crypt:
RCLONE_CONFIG_CRYPT_TYPE=crypt
RCLONE_CONFIG_CRYPT_REMOTE=s3:my-bucket/athena
RCLONE_CONFIG_CRYPT_PASSWORD=<rclone obscure ...>
RCLONE_CONFIG_CRYPT_PASSWORD2=<rclone obscure ...>

将两个密码都保存在你的密码管理器中。没有它们,备份将无法读取,包括你自己也无法读取。

恢复

在需要之前练习这个操作。没有人运行过的恢复操作只是猜测。

docker compose run --rm backup restore list
docker compose stop wikijs mcp indexer dashboard
docker compose run --rm backup restore run 2026-08-18T115529Z
docker compose start wikijs mcp indexer dashboard

它会要求你输入数据库名称以确认。restore fetch <stamp> 会下载备份而不恢复它,并报告每个转储文件是否可读。

搜索索引会在之后自行修复:索引器会重新读取每个页面,并重新嵌入内容发生变化的任何内容。


配置

所有内容都来自环境变量。每个服务在启动时都会验证自己的配置,如果出现问题则会退出并列出错误信息,这样拼写错误会立即失败,而不是在凌晨三点才暴露问题。

五个密钥,全部由你生成。Claude、OpenAI 或任何其他人的凭证永远不会存储在 .env 中。

密钥

持有者

保护对象

POSTGRES_PASSWORD

postgres, mcp, indexer

完全数据库访问权限

WIKI_API_TOKEN

mcp, indexer

Wiki.js API

MCP_TOKEN

mcp

MCP 端点

DASHBOARD_TOKEN

dashboard

仪表盘登录

DASHBOARD_DB_PASSWORD

dashboard, mcp, indexer

一个仅具有 SELECT 权限的数据库角色

运行的服务

服务

端口

说明

postgres

内部

Wiki.js 数据、活动日志以及通过 pgvector 存储的向量

wikijs

3000

你阅读和编辑的 Wiki

embeddings

内部

嵌入模型,运行在 CPU 上

mcp

8080

你的 AI 连接的对象

indexer

8081

保持向量索引与 Wiki 同步

dashboard

8082

指标

backup

无

每小时转储、验证、推送

没有单独的向量数据库。向量存储在 Postgres 中,因此一个备份即可覆盖所有内容。

在 ARM 宿主机上,嵌入镜像仅发布为 linux/amd64 版本,无法原生运行。请将 EMBEDDINGS_PROVIDER=openai 指向一个兼容 OpenAI 的端点,例如 Ollama。

变量

默认值

说明

ATHENA_DATA_DIR

../data

所有绑定挂载的根目录,是检出目录的同级目录

ATHENA_INSTANCE_NAME

Athena

显示在登录页面和仪表盘上

ATHENA_EDGE_NETWORK

athena-edge

你的反向代理加入的网络

ATHENA_EDGE_EXTERNAL

false

当平台提供该网络时设为 true

ATHENA_LOG_LEVEL

info

debug、info、warn、error

TZ

UTC

来源时间戳和带日期的路径

POSTGRES_DB

wiki

Wiki.js 数据库

ATHENA_DB

athena

活动日志和向量,自动创建

WIKI_LOCALE

en

内容语言

WIKI_PUBLIC_URL

http://localhost:3000

用于仪表盘链接

MCP_PUBLIC_URL

必需

纯 HTTPS 源,无路径

METRICS_CACHE_SECONDS

60

仪表盘数据重复使用的时长

EMBEDDINGS_MODEL

intfloat/multilingual-e5-small

更改它会重新索引所有内容

EMBEDDINGS_PROVIDER

tei

tei,或用于兼容端点的 openai

INDEX_INTERVAL_SECONDS

300

完全重新协调的间隔

CHUNK_MAX_CHARS

1200

块大小上限

BACKUP_*

参见 .env.example

计划、保留策略、rclone 目标

更改 EMBEDDINGS_MODEL 会改变向量宽度,并且来自两个模型的向量无法比较,因此索引器会重建表并重新嵌入每个页面。Wiki.js 内容不受影响。


安全

每个容器只接收它使用的凭证。仪表盘既不会获得 POSTGRES_PASSWORD 也不会获得 WIKI_API_TOKEN,因此攻破它只能获得读取权限,仅此而已。随时检查:

docker compose exec dashboard env | grep -iE 'PASSWORD|TOKEN'
  • 未经身份验证的 MCP 请求会收到 401 且无任何说明。

  • 两个登录路径在每地址 5 次失败后都会触发限流;登录链接在 3 次尝试后失效。

  • 仪表盘会话是带有过期时间和随机数的签名 Cookie,绝不包含令牌。HttpOnly、SameSite=Strict,并且拒绝跨站 POST 请求。

  • 密钥比较是恒定时间的。

  • 代理头仅信任来自回环地址的请求,因此远程客户端无法伪造其地址以绕过限流。

  • 容器以非 root 用户运行。

故意缺失: 按工具的权限。任何经过身份验证的客户端都可以调用所有工具,包括 delete_page。Wiki.js 保留页面历史记录,因此删除操作是可恢复的,但请将 MCP_TOKEN 视为对你 Wiki 的完全写入权限。Athena 也假设只有一个所有者;Wiki.js 有自己的用户用于阅读 Wiki。

MCP_TOKEN 有两种工作方式,因为 AI 客户端有两种身份验证方式。

Header 客户端,例如 Cursor 和 Claude Desktop,发送 Authorization: Bearer <MCP_TOKEN>。这就是整个机制。

浏览器中的 Claude.ai 无法做到这一点。其自定义连接器仅支持 OAuth,并且 MCP 规范要求动态客户端注册,因此接受浏览器 Claude 的服务器必须是一个授权服务器。Athena 实现了一个:

  1. Claude 注册自身并收到一个生成的客户端 ID。不涉及你的任何秘密。

  2. Claude 将你发送到你自己的服务器上的一个登录页面。

  3. 你输入 MCP_TOKEN 作为密码。这是人工批准步骤。

  4. Athena 颁发由 Athena 自身生成的 Claude 令牌。

这些令牌被写入 data/mcp/oauth-state.json,绝不会写入 .env。使用以下命令撤销它们:

rm data/mcp/oauth-state.json && docker compose restart mcp

如果你从不使用浏览器 Claude,请忽略所有这些。Bearer 路径不涉及它。


操作

docker compose logs -f mcp
curl -s localhost:8081/stats | python3 -m json.tool

# Force a full reconciliation
docker compose exec -T indexer bun -e 'await fetch("http://127.0.0.1:8081/sync",{method:"POST"})'

升级。 始终先备份:Wiki.js 在启动时会运行自己的迁移,而这些迁移无法通过停止容器来逆转。

docker compose run --rm backup now
git pull && docker compose build && docker compose up -d

症状

原因

服务在启动时退出并列出配置

缺少必需的变量或变量值仍为 CHANGE_ME

Claude 无法连接,无登录页面

MCP_PUBLIC_URL 包含路径,或不是 https

登录拒绝正确的密码

5 次失败后触发限流,等待一分钟

无语义搜索结果

embeddings 仍在下载,检查其日志

仪表盘显示页面落后

索引器正在追赶,检查其日志

工具调用失败并返回 401

状态文件被清除或令牌已更改,重新连接客户端

Postgres 退出,显示 "database files are incompatible"

镜像主版本在现有数据下发生了更改

Postgres 无法读取由不同主版本写入的数据目录。转储、清除、恢复:

docker compose run --rm backup now             # on the OLD version
docker compose down
mv data/postgres data/postgres.old             # keep until you are happy
# edit the image tag in docker-compose.yml and the FROM line in
# docker/backup/Dockerfile to the same new major version
docker compose build backup
docker compose up -d postgres
docker compose run --rm backup restore run <stamp>   # once per database
docker compose up -d

向量索引会与所有其他内容一起恢复,因此无需重新嵌入任何内容。


开发

bun install
bun test          # 145 tests
bun run check     # typecheck, lint, test

包

说明

packages/core

Wiki.js 客户端、分块、搜索合并、向量、身份验证、配置

packages/mcp

MCP 服务器、OAuth 授权服务器、工具

packages/indexer

同步循环、嵌入、向量写入、内部搜索 API

packages/dashboard

指标接口

docker/backup

备份和恢复容器

website/

单页网站

themes/wikijs/

可选的 Wiki.js CSS 和 JS

Bun 直接运行 TypeScript,因此没有构建步骤,容器直接运行源代码。bun run --cwd packages/dashboard preview 会生成一个包含示例数据的 preview.html。

各部分如何协同工作:

  • 索引器是增量的。它会对每个页面进行指纹识别,并跳过任何未更改的页面,因此对未修改的 Wiki 进行一次遍历不会产生任何成本。

  • 每个拥有管理员凭证的服务在启动时,会在一个咨询锁下准备数据库,因此启动顺序无关紧要。

  • 仪表盘是服务器端渲染的 HTML,包含内联 SVG 图表。没有客户端 JavaScript,没有图表库,没有构建步骤。

发布网站。 website/index.html 会在每次有推送触及它时部署到 GitHub Pages。首先需要手动启用一次 Pages:设置 → Pages → 构建和部署 → 源:GitHub Actions。这无法自动化,因为创建 Pages 站点需要一个具有管理权限的令牌,而 GITHUB_TOKEN 没有此权限。


许可证

Apache-2.0。参见 LICENSE。

Related MCP Connectors

Related MCP Servers