内容
# sharedoc-mcp
> **由代理生成的 Markdown → 一个您可以交给任何人的链接。今天在 GitHub gists 上,明天在您自己的服务器上。**
简体中文 | [繁體中文](./README.zh-TW.md)
[](https://www.npmjs.com/package/sharedoc-mcp)
[](https://github.com/AugustusW/sharedoc-mcp/releases)
[](LICENSE)
[](https://nodejs.org/)
[](https://modelcontextprotocol.io/)
[](https://claude.com/claude-code)
[](https://developers.openai.com/codex/)
一个 [MCP](https://modelcontextprotocol.io/) stdio 服务器 — 在 [Claude Code](https://claude.com/claude-code)、Codex CLI 和任何 MCP 客户端中工作 — 为您的代理提供 **8 种工具来发布、更新、搜索和撤销可共享的文档**。一个接口后面有两个可插拔的后端:**gist**(零设置,依赖您的登录的 `gh` CLI)和 **selfhost**(您的机器上的 SQLite,密码,强制过期)。
> 当后端无法满足参数时(例如 `password` 在 gist 上),它会返回一个明确的错误,而不是默默忽略它。
## 为什么?
AI 代理不断生成 Markdown — 报告、研究摘要、会议笔记。将这些内容传递给另一个人通常意味着复制粘贴一大段文本到聊天窗口。
```text
没有 sharedoc-mcp 使用 sharedoc-mcp
──────────────────── ─────────────────
复制一堵墙的文本到聊天中 "分享此文档"
为每个人粘贴一次 每个人一个链接
内容存在于聊天记录中 后来可以撤销/扩展/追加
"可以加密码吗?" …没有 selfhost 后端:bcrypt + 过期
```
## 功能
- ✓ 8 种 MCP 工具:创建/追加/扩展/重置密码/重命名/撤销/删除/搜索
- ✓ `sharedoc-mcp serve` 守护进程模式 — selfhost 链接在 MCP 客户端关闭后仍可工作
- ✓ 内容搜索:根据文档内容查找旧分享链接,而不仅仅是标题
- ✓ `GET /healthz` — 面向外部监控/重启自动化的身份验证健康探测
- ✓ 两个后端,一个接口 — 切换时只需设置一个环境变量,工具模式保持不变
- ✓ **Gist 后端**(默认):通过登录的 `gh` CLI 进行秘密 gist — 无需管理令牌,无需托管新服务
- ✓ **Selfhost 后端**:文档存储在您的机器上(使用内置的 `node:sqlite` — 零原生模块)
- ✓ 服务器验证的密码(bcrypt),速率限制尝试 — 5 次/分钟,HTTP 429,计数器在 SQLite 中持久化(selfhost)
- ✓ 强制过期(410)和撤销,7 天内容清除宽限期(selfhost);懒惰过期清除(gist)
- ✓ Markdown 通过 `marked` + `sanitize-html` 渲染 — 共享内容中的脚本、事件处理程序和 `javascript:` URL 被剥离
- ✓ 查看器仅绑定到 **127.0.0.1**,使用严格的安全头设置(CSP `default-src 'none'`,nosniff,DENY framing,no-referrer,no-store)— 暴露是一个您控制的隧道(下面的配方)
- ✓ `search_shared_docs` 的本地索引 + 创建去重(在 5 分钟内尝试相同的未保护重试会返回相同的 URL;添加密码/过期始终会创建新文档)
- ✓ 两个 MCP 客户端可以共享一个数据目录:SQLite WAL + 忙碌超时,端口共享正常
- ✓ 70 个离线测试;`npm test` 在干净的检出后通过
## 安装
需要 Node.js ≥ 22.13.0。Gist 后端还需要登录的 [GitHub CLI](https://cli.github.com) (`gh auth login`)。
**选项 A — Claude Code(一行命令):**
```bash
claude mcp add sharedoc --scope user -- npx -y sharedoc-mcp@^2
```
**选项 B — Codex CLI** (`~/.codex/config.toml`):
```toml
[mcp_servers.sharedoc]
command = "npx"
args = ["-y", "sharedoc-mcp@^2"]
```
**选项 C — 任何其他 MCP 客户端:** 以 stdio 服务器的身份运行 `npx -y sharedoc-mcp@^2`。
> **为什么使用 `@^2`?** 裸 `npx -y sharedoc-mcp` 解析 **最新发布的版本**,每次冷启动时 — 将来的 3.0 可能会在您不知情的情况下更改行为(或删除工具)。`@^2` 跟随 2.x 修补程序但永远不会跨越破坏性重大版本;如果您想要零漂移,请固定确切版本(`@2.1.0`)
## 选择您的后端
| | 🅰 `gist`(默认) | 🅱 `selfhost` |
|---|---|---|
| 设置 | 无 — 使用您登录的 `gh` CLI | 无额外 — 数据保留在您的机器上 |
| 文档存储位置 | GitHub(秘密 gist) | 您的机器(SQLite) |
| 链接可达性 | 任何地方,立即 | localhost — 添加隧道以在外部共享 |
| 密码 | ✗ (秘密 URL 是保护) | ✓ 服务器验证(bcrypt),速率限制 |
| 过期 | 惰性 — 过期 gist 在下次使用时删除 | 强制 — 过期链接返回 410 |
| 撤销 | gist 立即删除,不可逆转 | 立即 410,内容在 7 天宽限期内清除 |
### Gist 快速入门
让您的代理“分享此文档” — 它调用 `create_shared_doc` 并返回一个秘密 gist URL。秘密 gist 不会公开列出,URL 也不可猜测,但 **任何拥有链接的人都可以阅读** — 这是此后端的整个安全模型。如果需要密码,请使用 `selfhost`。
本地索引(`~/.config/sharedoc-mcp/index.json`)跟踪您分享的内容,使搜索和过期清理功能正常运行。这里的过期是 *惰性* 的:过期 gist 在下次任何工具运行时删除,而不是在确切的过期时刻。
### Selfhost 快速入门
```bash
claude mcp add sharedoc --scope user --env SHAREDOC_BACKEND=selfhost -- npx -y sharedoc-mcp@^2
```
文档存储在 `~/.local/share/sharedoc-mcp/` 的 SQLite 中;查看器在 `http://127.0.0.1:8377` 提供服务。要在您的机器之外分享,请在前面加上隧道并设置 `SHAREDOC_PUBLIC_URL`:
> **链接在编辑器之外存活:** 在 MCP 模式下,查看器在 MCP 客户端关闭时也会关闭 — 关闭 Claude Code 后,selfhost 链接将停止响应,直到下一个会话(数据安全存储在 SQLite 中)。运行独立守护进程以保持链接全天候存活:
>
> ```bash
> npx -y sharedoc-mcp@^2 serve # 仅查看器,相同数据库 — 通过 launchd/systemd/tmux 运行(Windows:任务调度器或 NSSM)
> ```
>
> MCP 客户端检测到守护进程已经拥有该端口,并简单地使用它。
>
> **何时设置此功能:** 在您第一次将链接交给他人时 — 请与您的隧道一起设置(两者都应长期运行,例如在 launchd/systemd 下)。在此之前,MCP 模式下的查看器已经足够,gist 后端用户永远不需要它。
| 配方 | 适合您 | 设置 |
|---|---|---|
| **Tailscale 私有**(推荐) | 收件人是您的设备/您可以邀请到您的 tailnet 的人 | `tailscale serve --bg 8377` → `https://<machine>.<tailnet>.ts.net`,**仅在您的 tailnet 内可访问** — 没有公共互联网暴露 |
| **Tailscale Funnel** | 与任何人分享,无需域名 | `tailscale funnel 8377` → 相同的稳定 URL,但公开 |
| **Cloudflare 命名隧道** | 您拥有域名 | Cloudflare 域名,`cloudflared tunnel create` + 将主机名路由到 `http://127.0.0.1:8377` |
| **cloudflared 快速隧道** | 一次性分享 | `cloudflared tunnel --url http://127.0.0.1:8377` → 随机 URL,每次重启时更改 |
#### 拥有域名?Cloudflare 命名隧道,步骤一步
一个品牌化的稳定分享 URL,如 `https://docs.example.com/docs/<uuid>` — 由 Cloudflare 处理 TLS,从 NAT 后面工作:
```bash
# 一次性设置(域名已添加到 Cloudflare — 免费计划足够)
cloudflared tunnel login
cloudflared tunnel create sharedoc
cloudflared tunnel route dns sharedoc docs.example.com
```
`~/.cloudflared/config.yml`:
```yaml
tunnel: sharedoc
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
- hostname: docs.example.com
service: http://127.0.0.1:8377
- service: http_status:404
```
运行 `cloudflared tunnel run sharedoc`(或将其作为服务安装以始终在线),并使用公共 URL 注册 MCP 服务器:
```bash
claude mcp add sharedoc --scope user \
--env SHAREDOC_BACKEND=selfhost \
--env SHAREDOC_PUBLIC_URL=https://docs.example.com \
-- npx -y sharedoc-mcp
```
这解锁了额外的功能:Cloudflare 的 DDoS 保护是免费的;您可以分层 WAF 规则,或在所有内容前面放置 [Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) (SSO)— 除了分享路径之外的所有内容 — 一种“内部 SSO,外部密码保护分享”的分离。
**替代方案 — 没有家用机器时保持在线:** 在 VPS 上运行 sharedoc-mcp(您的代理也运行在那里),并将 nginx/caddy 指向 `127.0.0.1:8377`,使用您的域名和自动 TLS;无需隧道。
环境变量:
| 变量 | 默认值 | 含义 |
|---|---|---|
| `SHAREDOC_BACKEND` | `gist` | `gist` 或 `selfhost` |
| `SHAREDOC_PORT` | `8377` | 查看器端口(selfhost) |
| `SHAREDOC_PUBLIC_URL` | `http://127.0.0.1:<port>` | 分享链接中的 URL 前缀 — 设置为您的隧道主机名 |
| `SHAREDOC_DATA_DIR` | `~/.local/share/sharedoc-mcp` | SQLite 位置(selfhost) |
| `SHAREDOC_INDEX_PATH` | `~/.config/sharedoc-mcp/index.json` | 本地索引(gist) |
| `MCP_CALLER` | — | 创建文档的默认作者归属 |
## 8 种工具
| 工具 | 功能 |
|---|---|
| `create_shared_doc` | 标题 + Markdown(+ 可选密码 / `expires_in_hours` / 作者)→ 分享 URL |
| `append_to_shared_doc` | 追加 Markdown(不可幂等 — 重试会追加两次) |
| `extend_shared_doc` | 将过期时间延长 N 小时 |
| `reset_shared_doc_password` | 设置/更改/删除(空)密码(仅 selfhost) |
| `update_shared_doc_title` | 重命名 |
| `revoke_shared_doc` | 终止链接,保持记录(查看后端表格的语义) |
| `delete_shared_doc` | 终止链接 **和** 擦除记录 — 不可逆转;需要 `confirm: true`(代理应首先获得明确的用户批准) |
| `search_shared_docs` | 无参数 = 列出最新链接;标题子字符串,正文搜索(selfhost:完整内容;gist:打开摘录),状态过滤 |
## 隐私
数据流,按后端:
- **Gist 后端**:您的文档内容作为秘密 gist 上传到 GitHub,在您的帐户下 — GitHub 的条款和保留政策适用。本地索引保留在 `~/.config/sharedoc-mcp/` — 它存储标题,URL,时间戳和每个文档的前 200 个字符(用于本地内容搜索);永远不会发送完整内容。除通过您自己的 `gh` CLI 发送到 GitHub 外,任何内容都不会发送到任何地方。
- **Selfhost 后端**:内容在未经隧道的情况下永远不会离开您的机器 — 然后它被提供给您给予链接的人(和隧道提供商中继流量)。密码仅存储为 bcrypt 哈希。
- sharedoc-mcp 本身没有遥测数据,也不调用任何第三方服务。
## 安全语义,诚实
- **Gist 链接是持有者令牌**:任何拥有 URL 的人都可以读取文档。撤销立即且不可逆转地删除 gist。
- Selfhost 密码在内容提供服务前在服务器端验证;**错误尝试次数限制为 5 次/分钟(每个源 + 文档;正确解锁清除计数器)**,计数器存储在 SQLite 中 — 重启服务器不会重置它们。**在隧道后面,所有外部访客共享一个源地址**,因此实际限制为每个文档 5 次/分钟 — 比按访客更严格;一个人输入错误密码可以暂时锁定文档,其他人也会受到影响。
- 故意 **没有文件分享工具**:任意路径的“分享此文件”工具是 prompt-injection 渗漏向量(`.env`,密钥)— 劫持的代理可以发布秘密。删除了该功能,而不是允许列入白名单。
- 查看器永远不会绑定到 127.0.0.1 以外的地址。它的互联网访问方式和配置完全由您的隧道决定。
## 开发
```bash
git clone https://github.com/AugustusW/sharedoc-mcp.git
cd sharedoc-mcp
npm install
npm test # 构建,然后运行 70 个离线测试 — gh CLI 被模拟,HTTP 测试仅命中 127.0.0.1
```
版本控制:每次发布都会更新 `package.json` 中的 `version`,添加 [CHANGELOG](./CHANGELOG.md) 条目,并发布为 git 标签 + [GitHub Release](https://github.com/AugustusW/sharedoc-mcp/releases) + [npm](https://www.npmjs.com/package/sharedoc-mcp)。
**获取更新通知**:关注此仓库(自定义 → 版本)。`npx -y` 每次冷运行都会获取最新发布的版本;您的索引和文档数据库存储在包之外 — 更新永远不会影响它们。
## 状态
v2.1.0 ([CHANGELOG](./CHANGELOG.md)) — 核心逻辑涵盖 70 个离线单元/集成测试(`gh` CLI 被模拟;HTTP 测试仅运行在 127.0.0.1 上;无需网络)。完整的流程已手动验证(2026-07-25:在构建的服务器上通过 stdio JSON-RPC 进行真正的秘密 gist 创建/索引/删除,以及 selfhost 密码流程端到端 — 表单 → 错误密码 401 → 正确密码 200 → 速率限制 429 → 撤销 410 — 加 `lsof` 确认仅绑定到 127.0.0.1):
- macOS(Apple Silicon),Node v25 — gist + selfhost 后端
隧道配方从工具的标准行为中记录;Windows/Linux 和真实隧道端到端运行 **尚未验证** — 欢迎报告。
## 许可证
MIT © AugustusW