Anchor MCP
Anchor MCP
一个小型 MCP 边车(sidecar)的实现计划,通过运行在 Anchor 同一 Docker Compose 栈中的隧道客户端,向 ChatGPT 暴露安全的 Anchor Notes 工具。
研究依据:Anchor 上游仓库 ZhFahim/anchor,默认分支 main,检查日期 2026-08-20。Anchor 是一个 Nest.js 后端,在 /api/* 下提供经过认证的 REST 端点。
目标
在 Anchor 旁边运行一个 MCP 服务器,使外部助手能够列出、搜索、读取、创建、更新、导入 Anchor 笔记并为其附加文件,而无需直接暴露 Anchor 的数据库或私有 API。
Related MCP server: NotesBridge
当前状态
第一个里程碑已实现:
位于
POST /mcp的 Streamable HTTP MCP 端点。位于
GET /healthz的健康检查端点。只读 Anchor 工具:
anchor_list_notes、anchor_search_notes、anchor_get_note、anchor_list_tags、anchor_list_attachments。使用
ANCHOR_MCP_TOKEN的可选 MCP bearer 守卫。Anchor API 调用使用
ANCHOR_TOKEN和ANCHOR_BASE_URL。已包含 Dockerfile。
写入工具有意尚未实现。
开发
在 NixOS 上,使用 nix-shell 执行 Node/npm 命令:
nix-shell -p nodejs --run 'npm install'
nix-shell -p nodejs --run 'npm run typecheck'
nix-shell -p nodejs --run 'npm run build'本地运行:
ANCHOR_BASE_URL=https://anchor.cri.su \
ANCHOR_TOKEN=... \
ANCHOR_MCP_TOKEN=... \
nix-shell -p nodejs --run 'npm run dev'MCP 端点为 http://localhost:8000/mcp。如果设置了 ANCHOR_MCP_TOKEN,调用方必须发送 Authorization: Bearer <token>。
部署模型
预期的栈包含三个服务:
services:
anchor:
# Existing Anchor service.
anchor-mcp:
build: /path/to/anchor-mcp
environment:
ANCHOR_BASE_URL: http://anchor:3000
ANCHOR_TOKEN: ${ANCHOR_TOKEN}
ANCHOR_MCP_TOKEN: ${ANCHOR_MCP_TOKEN}
expose:
- "8000"
depends_on:
- anchor
chatgpt-tunnel-client:
# Outbound tunnel client.
environment:
MCP_TARGET_URL: http://anchor-mcp:8000/mcp
MCP_TARGET_TOKEN: ${ANCHOR_MCP_TOKEN}
depends_on:
- anchor-mcpMCP 服务器应仅在 Docker 网络上可达。隧道客户端是唯一的外部桥梁。
已确认的 Anchor API 表面
以下所有端点均由 Anchor 的 AuthGuard 保护,并期望 Authorization: Bearer <token>。该守卫接受可解析为活动用户的 Anchor 令牌。
笔记:
POST /api/notesGET /api/notes?search=<query>&tagId=<tagId>&limit=<limit>GET /api/notes/:idPATCH /api/notes/:idDELETE /api/notes/:idDELETE /api/notes/:id/permanentPATCH /api/notes/:id/restoreGET /api/notes/trashGET /api/notes/archivePOST /api/notes/bulk/deletePOST /api/notes/bulk/archivePOST /api/notes/bulk/pinPOST /api/notes/bulk/tags
标签:
POST /api/tagsGET /api/tagsGET /api/tags/:idGET /api/tags/:id/notesPATCH /api/tags/:idDELETE /api/tags/:id
附件:
POST /api/notes/:noteId/attachmentsGET /api/notes/:noteId/attachmentsGET /api/notes/:noteId/attachments/:idDELETE /api/notes/:noteId/attachments/:idPATCH /api/notes/:noteId/attachments/reorder
导入/导出:
POST /api/import/notesPOST /api/import/notes/:noteId/attachmentsGET /api/export
同步 API:
POST /api/syncGET /api/sync/events作为服务器发送事件(server-sent events)
共享:
POST /api/notes/:id/sharesGET /api/notes/:id/sharesPATCH /api/notes/:id/shares/:shareIdDELETE /api/notes/:id/shares/:shareId
MCP 服务器应从常规的笔记/标签/附件/导入端点开始。同步 API 对需要冲突感知的离线客户端很有用,但 MCP 边车可以暂不涉及。
数据形状
创建笔记请求体:
{
"title": "string",
"content": "optional string",
"isPinned": false,
"isArchived": false,
"background": "optional string",
"tagIds": ["tag-id"]
}更新笔记请求体是部分创建请求体加上可选的乐观锁:
{
"title": "optional string",
"content": "optional string",
"isPinned": false,
"isArchived": false,
"background": "optional string",
"tagIds": ["tag-id"],
"baseVersion": 1
}Anchor 返回转换后的笔记,包含以下重要字段:
{
"id": "uuid",
"title": "string",
"content": "string or null",
"version": 1,
"isPinned": false,
"isArchived": false,
"background": null,
"state": "active",
"createdAt": "iso timestamp",
"updatedAt": "iso timestamp",
"userId": "uuid",
"tagIds": ["tag-id"],
"permission": "owner",
"attachmentCount": 0,
"imagePreviewIds": []
}导入笔记请求体:
{
"notes": [
{
"ref": "external stable reference, max 256 chars",
"id": "optional uuid",
"title": "string",
"content": "stringified Quill Delta JSON",
"isPinned": false,
"isArchived": false,
"isTrashed": false,
"background": "optional background id",
"tagNames": ["tag name"],
"createdAt": "iso timestamp",
"updatedAt": "iso timestamp"
}
],
"tags": [{ "name": "tag", "color": "#8B5CF6" }],
"skipExisting": true
}导入结果形状:
{
"results": [
{
"ref": "external reference",
"status": "created | skipped | remapped | failed",
"noteId": "uuid",
"warning": "optional string",
"error": "optional string"
}
],
"tags": { "created": 0, "reused": 0 }
}附件上传形状:
普通笔记上传:multipart
file字段,发送到POST /api/notes/:noteId/attachments。导入附件上传:multipart
file加上position表单字段,发送到POST /api/import/notes/:noteId/attachments。附件响应包含
id、noteId、type、originalFilename、mimeType、fileSize、position、uploadedByUserId和createdAt。
限制与验证
笔记列表限制:
GET /api/notes将limit限制在1..200范围内。
批量限制:
noteIds:最多 200 个。tagIds:最多 50 个。
导入限制:
每批笔记数:50。
字符串化 Delta 内容长度:1,000,000 字节/字符。
标题长度:1000。
每篇笔记的标签数:50。
每批导入的标签数:500。
标签名称长度:100。
附件限制:
最大文件大小:50 MB。
允许的图片:
image/jpeg、image/png、image/webp、image/gif。允许的音频:
audio/mpeg、audio/wav、audio/mp4、audio/x-m4a、audio/ogg、audio/aac、audio/webm。当前源码拒绝 PDF、JSON、ZIP 和通用的
application/octet-stream。
导入允许的背景 ID:
color_red、color_orange、color_yellow、color_green、color_teal、color_blue、color_dark_blue、color_purple、color_pink、color_brown。pattern_dots、pattern_grid、pattern_lines、pattern_waves、pattern_groceries、pattern_music、pattern_travel、pattern_code。
内容格式
Anchor 将笔记 content 存储为字符串。现有的导入工作确认,对于富文本导入,这应该是字符串化的 Quill Delta JSON。
MCP 服务器应暴露对 Markdown 友好的工具,并在内部将 Markdown 转换为 Quill Delta。以后还可以暴露专家模式的原生 Delta 工具。
推荐的转换策略:
anchor_create_note接受 Markdown,转换为 Delta,调用POST /api/notes。anchor_update_note接受 Markdown,转换为 Delta,调用PATCH /api/notes/:id,可带baseVersion。anchor_import_notes接受 Markdown 或原生 Delta,通过POST /api/import/notes分批处理。anchor_get_note返回原始内容,外加一个尽力而为的文本/Markdown 投影,便于 LLM 阅读。
认证模型
Anchor 源码使用从 Authorization: Bearer <token> 提取 bearer 令牌的方式。因此 MCP 边车应维护两层认证:
ANCHOR_TOKEN:anchor-mcp调用 Anchor 时使用的令牌。ANCHOR_MCP_TOKEN:隧道客户端在请求被服务之前必须提供的令牌。
MCP 服务器绝不应将任意调用方令牌转发给 Anchor。
源码参考
上游检查的主要文件:
server/src/notes/controllers/notes.controller.tsserver/src/notes/controllers/note-attachments.controller.tsserver/src/notes/controllers/note-shares.controller.tsserver/src/tags/tags.controller.tsserver/src/import-export/import.controller.tsserver/src/import-export/export.controller.tsserver/src/sync/sync.controller.tsserver/src/sync/sync-events.controller.tsserver/src/notes/dto/create-note.dto.tsserver/src/notes/dto/update-note.dto.tsserver/src/import-export/dto/import-notes.dto.tsserver/src/import-export/dto/import-attachment.dto.tsserver/src/notes/constants/notes.constants.tsserver/src/import-export/constants/import.constants.tsserver/src/notes/utils/note-transformer.util.tsserver/src/notes/utils/attachment-storage.util.ts
MCP 工具
第一阶段只读工具:
anchor_list_notes(limit, offset)anchor_search_notes(query, limit)anchor_get_note(note_id)anchor_list_tags()anchor_list_attachments(note_id)
已实现工具详情:
anchor_list_notes支持limit、offset、include_content和tag_id。由于 Anchor 只暴露基于 limit 的列表,offset + limit最多必须为 200。anchor_search_notes支持query、limit、include_content和tag_id。anchor_get_note支持note_id和include_content。anchor_list_tags不接受任何输入。anchor_list_attachments仅返回元数据,不下载附件字节。
第二阶段写入工具:
anchor_create_note(title, markdown)anchor_update_note(note_id, markdown, base_version)anchor_import_notes(notes)anchor_create_tag(name, color)anchor_upload_attachment(note_id, file, filename, mime_type)
第三阶段管理工具:
anchor_archive_notes(note_ids)anchor_pin_notes(note_ids, is_pinned)anchor_add_tags(note_ids, tag_ids)anchor_export()(如果隧道客户端能处理流式归档)
避免或限制破坏性工具:
anchor_delete_note(note_id, confirm)映射到软删除,应要求confirm=true。anchor_permanent_delete_note(note_id, confirm)最初应省略。anchor_delete_tag(tag_id, confirm)最初应省略。不要暴露任意的原始 HTTP 代理工具。
安全
仅将
ANCHOR_TOKEN存储在 Docker 栈环境或.env中;不要将其烘焙到镜像中。为从隧道客户端到
anchor-mcp的调用添加单独的ANCHOR_MCP_TOKEN。仅将 MCP 服务器绑定到容器网络;除非有意暴露,否则不要添加 Traefik 标签。
保持工具窄而类型化。不允许调用方选择任意的 Anchor API 路径。
记录请求元数据,而不是笔记内容或令牌。
在隧道认证路径验证之前,默认只读工具。
对软删除和批量破坏性操作要求显式的
confirm=true。除非存在单独的
ENABLE_DANGEROUS_TOOLS=true设置,否则拒绝永久删除。
实现阶段
创建一个最小的 TypeScript MCP HTTP 服务器。
从环境添加配置:
ANCHOR_BASE_URL、ANCHOR_TOKEN、ANCHOR_MCP_TOKEN、绑定主机/端口。为 Docker 和隧道诊断实现
/healthz。实现一个小型 Anchor API 客户端,具有类型化方法且没有任意路径逃生口。
实现
anchor_list_notes、anchor_search_notes、anchor_get_note和anchor_list_tags。添加响应整形,除非显式请求,否则剥离重字段。
实现 Markdown 到 Delta 的转换辅助函数和测试。
实现创建/更新,通过
baseVersion支持可选的乐观锁。按照已知的导入限制实现导入批处理。
仅对允许的图片/音频实现附件上传。
添加 Dockerfile 和 Compose 示例,包括隧道客户端占位符。
使用模拟的 Anchor 响应和验证失败添加测试。
添加关于轮换令牌和接线 ChatGPT 隧道客户端的运维文档。
未决问题
确切的隧道客户端镜像、环境变量和认证头格式。
Anchor 是否可以配置或修补以允许 PDF 和其他文件类型。
笔记内容应接受 Markdown 并转换为 Quill Delta,还是 MCP 应直接暴露 Anchor 的原生内容格式。
隧道客户端是否能足够好地传递二进制负载以支持附件上传和导出下载。
是否应在客户端模拟
offset,因为GET /api/notes只暴露limit,而不暴露偏移分页。
推荐的第一里程碑
构建一个只读 MCP 服务器,包含 anchor_list_notes、anchor_search_notes、anchor_get_note 和 anchor_list_tags。将其私有部署在 Anchor 栈中、隧道客户端之后。仅在读取路径和认证模型验证通过后,再添加创建/更新/导入。
This server cannot be deployed
Maintenance
Related MCP Connectors
Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
MCP-native notes and memory for ChatGPT, Claude, and other AI tools.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceMCP server for AI agents to read, write, and organize notes in a local-first, human-in-the-loop note-taking app.1 npm2MIT
- AlicenseNot gradedqualityBmaintenanceMCP server enabling ChatGPT to search, read, and write Apple Notes via a local Mac agent with a privacy-preserving relay.MIT
- AlicenseNot gradedqualityBmaintenanceSelf-hosted MCP server for private Obsidian vaults on GitHub, exposing tools to search, read, write, and analyze Markdown notes and their link graph.MIT
- AlicenseNot gradedqualityBmaintenanceRead-only MCP bridge that exposes secure search and fetch tools over an Obsidian-compatible Markdown vault, enabling ChatGPT to query notes without write access.1Apache 2.0