内容
# 知识门户
<!-- mcp-name: io.github.fszalaj/知识门户 -->
一个单独的MCP服务器,为代理(Claude Code、Codex、Cursor、Gemini、Copilot、Antigravity)提供三个功能:
- **知识库** - 读取、搜索和**编辑**一个基于git的Markdown/Obsidian知识库(无Obsidian GUI),git作为真实来源。
- **代码图谱** *(可选 `[graph]` / `[graph-all]`)* - 构建和查询一个代码/Ansible知识图谱(函数、调用、角色、任务、处理程序、`task -> filter` 边);仅限AST,当地,无LLM。
- **转换** *(可选 `[convert]`)* - 将PDF / Office / 图片 / HTML文件转换为Markdown。
知识库层存在是因为Obsidian *Local REST API* 插件只服务于一个运行中的桌面实例打开的知识库,提供无锁写入(沉默的更新丢失),每个客户端需要令牌,并且将git视为次要存储。这个网关直接操作文件,使用git作为记录系统,并添加图谱和转换工具:一个服务器,可选功能,无需新增服务器。
## 架构
```mermaid
flowchart LR
subgraph clients [代理]
A1[Claude Code]
A2[Codex]
A3[Gemini]
A4[Copilot - VS Code]
A5[Cursor]
A6[Antigravity]
end
A1 --- M(( MCP ))
A2 --- M
A3 --- M
A4 --- M
A5 --- M
A6 --- M
M -->|stdio, per repo, no auth| L[本地网关]
M -->|HTTP + bearer + ACL| S[共享网关]
L --> V[/知识库:Markdown文件/]
S --> V
V <-->|原子写入 + 范围提交| G[(git)]
```
两种模式运行相同的工具实现,相同的路径守卫;它们在传输、身份验证/ACL、知识库加载和错误掩蔽方面有所不同。
## 两种运行方式
| | **本地模式** (每个仓库) | **共享服务器** (团队) |
|---|---|---|
| 使用场景 | 一个仓库为其代理拥有自己的知识库 | 许多人/知识库通过一个始终在线的端点 |
| 传输 | stdio子进程(由 `.mcp.json` 启动) | HTTP(置于Tailscale/HTTPS后面) |
| 密钥/令牌 | **无** - 无需生成 | 每用户 bearer 令牌(管理员生成) |
| 信任边界 | 本地文件系统访问权限 | tailnet + HTTPS + 每知识库ACL |
| 需要Obsidian | 否 | 否 |
大多数仓库希望使用**本地模式**。共享服务器仅适用于中央、始终在线的团队网关。
## 分发 - `稳定`分支(“更新一次”)
网关从一个移动分支发布,因此版本发布到每个消费者和服务器,无需手动重新固定。
```mermaid
flowchart LR
PR[合并PR到main] --> TAG[标记vX.Y.Z]
TAG --> MV[移动稳定 -> vX.Y.Z]
MV --> C["消费者<br/>uvx --refresh @稳定<br/>(下次会话更新)"]
MV --> S["服务器<br/>每日uv工具重新安装<br/>(如果稳定移动则重启)"]
```
- **消费者**固定 `@稳定` 使用 `uvx --refresh` -> 引用在每次启动时重新获取,因此新版本在代理启动时自动传播。无需每个仓库重新固定。
- **服务器**(长期运行)运行固定的 `uv tool install @稳定` 加上每日作业,当`稳定`实际移动时重新安装和重启。
- 每个版本也是一个不可变的 `vX.Y.Z` 标签 - 固定标签而不是`稳定`,当您需要一个冻结的、可审计的版本时。
> 移动的*标签*不起作用(uvx缓存解析的提交);*分支* + `--refresh` 起作用。
## 快速入门 - 本地模式(零秘密)
将以下内容添加到仓库根目录的 `.mcp.json` 中:
```jsonc
{
"mcpServers": {
"wiki": {
"command": "uvx",
"args": ["--refresh", "--from", "git+https://github.com/fszalaj/知识门户@稳定", "知识门户", "--local"]
}
}
}
```
- `--local` 自动检测当前工作目录中的知识库,顺序为:当前工作目录本身如果有 `.obsidian/`,则为 `./wiki`,然后为单个 `*-obsidian-vault/`,然后为具有 `.obsidian/` 的单个子目录(模糊匹配错误)。传递 `--vault ./<dir>` 以显式指定。
- `--refresh` 在每次启动时重新获取 `@稳定`,因此版本会自动应用(增加约1-2秒启动时间)。
- 提交被限定在知识库的git子目录中,并归因于您自己的 `git config user.name/email`。无需令牌:信任边界是本地文件系统访问权限。
在仓库中打开代理,批准 `wiki` 服务器一次即可完成。
## 工具
| 工具 | |
|---|---|
| `list_vaults` | 此处可达的知识库 |
| `list_notes` | 知识库中的Markdown路径 |
| `read_note` | 原始笔记内容 |
| `list_attachments` / `read_attachment` | 列表/读取二进制附件(图像 -> 内联图像,否则文件) |
| `list_canvases` / `read_canvas` / `write_canvas` | 列表/读取/写入Obsidian画布(节点、组、颜色) |
| `search` | ripgrep字面/正则全文搜索 |
| `backlinks` | 链接到笔记的笔记 |
| `list_tags` | 带有计数的内联 `#tags` |
| `query_notes` | 按frontmatter `type` / `tag`查找笔记(无头Dataview-lite) |
| `write_note` | 原子写入(+可选提交) |
| `patch_note` | 在标题后或顶部/底部插入,无需完全重写(+提交) |
| `patch_frontmatter` | 更新YAML frontmatter键,主体完整(+提交) |
| `delete_note` | 删除笔记(+可选提交) |
| `rename_note` | 重命名/移动 + 重写入站扁平 `[[wikilinks]]` 当名称更改(+可选提交) |
| `git_status` / `git_commit` | 待处理更改 / 提交(子目录范围,有归因) |
| `list_graphs` / `graph_query` / `graph_neighbors` / `god_nodes` / `graph_shortest_path` / `graph_stats` | 查询构建的代码图谱(可选 `[graph]`) |
| `graph_build` | 从源树构建代码/Ansible图谱到 `.graph/<name>.json` (仅本地模式) |
| `convert_to_markdown` | 将知识库中的文件转换为Markdown(可选 `[convert]`) |
编辑是原子的(临时文件 + `rename`)。每个路径都经过 `safe_note_path`,它阻止遍历、符号链接逃逸、隐藏/点文件、非 `.md` 目标和 `.git`/`.obsidian` - 调用者永远无法读取或写入知识库以外的笔记。
## 代码图谱和转换(可选)
两个可选功能,通过额外功能启用,因此核心安装保持无依赖:
| 额外功能 | 添加 |
|---|---|
| `[graph]` | Python (`ast`) + Ansible (PyYAML) 代码图谱 + 查询工具 |
| `[graph-all]` | 上述 + 对 ~30 种语言进行广泛的 tree-sitter 遍历:定义、`imports` 和文件内 `calls` |
| `[convert]` | 附件 -> Markdown 通过 markitdown |
**构建图谱** (仅限AST - 本地,无网络,无LLM):
```bash
知识门户-graph /path/to/code-repo -o /path/to/vault/.graph/myrepo.json
# 在本地模式会话中,graph_build 工具执行相同的操作,写入 .graph/<name>.json
```
**查询** 使用 `graph_query` / `graph_neighbors` / `god_nodes` / `graph_shortest_path` / `graph_stats`。图谱捕获函数/类/导入/调用和 - 独特地适用于Ansible - 角色、任务、处理程序、`include_role`/`import_tasks`/`notify` 和 `task -> filter plugin` 边。图谱文件位于知识库的 `.graph/` 中,是知识库内限定的(解析 + 检查以保持在知识库内),并且对网关来说是只读的 - 知识库工具永远不依赖它们。
## 共享服务器模式
仅为中央、始终在线的网关运行此模式,以通过网络访问。
**1. 映射知识库** - `cp vaults.example.yaml vaults.yaml`,然后设置 `name -> path / repo_root / subdir`。`repo_root` + `subdir` 路径限定提交到位于较大仓库内的知识库。
**2. 为每个用户生成令牌** (管理员执行此操作):
```bash
cp tokens.example.yaml tokens.yaml
openssl rand -hex 32 # 每个用户一次 -> 密钥
chmod 0600 tokens.yaml # 如果组/世界可读,则在加载时拒绝
```
```yaml
tokens:
"8f3c…hex…":
sub: alice # 用户的身份记录在其提交上
vaults: [teamwiki] # 该令牌可以查看/触摸的 ONLY 知识库
write: true # false = 只读
```
令牌只能看到其 `vaults` 列表中的知识库;其他知识库返回不透明的 `vault_forbidden`。`vaults.yaml` + `tokens.yaml` 被git忽略。
**3. 运行** - `uv run 知识门户` (127.0.0.1:8765,路径 `/mcp/`)。对于团队盒,请作为服务在Tailscale Serve后面运行 - 参见 `deploy/` 和 *操作* 下面。
**4. 连接** - 管理员通过密码管理器(不是聊天)共享令牌:
```bash
claude mcp add --transport http --scope project teamwiki \
https://YOUR-HOST.<tailnet>.ts.net/mcp/ --header "Authorization: Bearer $GW_TOKEN"
```
## 安全模型
- **仓库中没有秘密**。`vaults.yaml` / `tokens.yaml` 被git忽略;只有 `*.example.yaml` 被发布。 如果 `tokens.yaml` 可被组/世界读取,则在加载时拒绝。
- **本地模式没有凭证表面** - 本地stdio子进程;信任边界是用户已经具有的文件系统访问权限。
- **服务器模式是深度防御,而不是公共端点** - tailnet ACL + HTTPS + 每用户 `StaticTokenVerifier` bearer令牌 + 每知识库ACL。 bearer层是 **在受信任的tailnet后面使用的共享秘密**;不要公开暴露服务器。
- **所有笔记I/O的路径守卫** 通过 `safe_note_path` (遍历、符号链接、隐藏/点文件包括 `.env`,非 `.md`,`.git`/`.obsidian`)。搜索/反向链接/标签绑定到 `*.md`。
- **服务器模式错误掩蔽** - HTTP服务器运行 `mask_error_details=True`:只有网关自己的预期失败作为 `ToolError` 显示;意外OS/git错误被隐藏。本地模式保持详细信息可见。
- **提交归因于** 请求用户(服务器)或本地git身份(本地),并且路径限定在知识库子目录下。
## 使用AI设置
将以下内容粘贴到代理的仓库根目录中,以启用本地模式:
```
添加知识门户到此仓库,以便代理可以通过MCP使用零令牌读取/编辑我们的知识库:
1. 创建或合并 `.mcp.json` 在仓库根目录中,包含一个mcpServers."wiki"条目,运行:
uvx --refresh --from git+https://github.com/fszalaj/知识门户@稳定 知识门户 --local
(`--local` 自动检测知识库:./wiki,一个 *-obsidian-vault 目录,或具有 `.obsidian/` 的目录。如果检测不明确,请使用 `--vault ./<vault dir>` 代替 `--local`。)
2. 验证:`uvx --refresh --from git+https://github.com/fszalaj/知识门户@稳定 \
知识门户 --help` 解析;然后在代理中调用 list_vaults 和 read_note。
分支 + PR,无直接推送,无AI归因。
```
对于共享服务器,请向网关管理员索取令牌,然后运行 `claude mcp add …` 从上面的 *连接*。
## 操作(服务器)
服务器运行 `@稳定` 版本作为 `uv tool`,每日作业重新安装并重启仅当 `稳定` 移动时。参考单元在 `deploy/` 中:
```bash
uv tool install --from git+https://github.com/fszalaj/知识门户@稳定 知识门户
# 二进制文件位于uv缓存中,因此在配置文件中指向活动文件:
# KNOWLEDGE_GATEWAY_VAULTS=<dir>/vaults.yaml KNOWLEDGE_GATEWAY_TOKENS=<dir>/tokens.yaml
```
- `deploy/知识门户.service` - 服务(systemd `--user`)。
- `deploy/知识门户-更新.{service,timer}` + `deploy/auto-update.sh` - 每日自动更新。
现在更新而不是等待定时器:`uv tool install --reinstall --from git+https://github.com/fszalaj/知识门户@稳定 知识门户`,然后重启服务。健康检查:`curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8765/mcp/` -> `401`.
## 发布(维护者)
1. PR -> 合并到 `main` (CI:`uv lock --check`,pytest矩阵)。
2. 更改 `pyproject.toml` 版本 + `CHANGELOG.md`。
3. 标签 `vX.Y.Z` 并推送标签(发布工作流构建它)。
4. 移动 `稳定`:`git branch -f stable vX.Y.Z && git push --force-with-lease origin stable`。
消费者下次会话时获取它;服务器在一天内(或现在重启)获取它。
## 开发
```bash
uv venv && uv pip install -e ".[dev]"
uv run pytest # ACL + 路径守卫 + 编辑/frontmatter + 检测 + 掩蔽
```