内容
# 发布制品 - MCP
安全发布自包含的 **HTML 制品**,通过 **API 密钥** 控制,并通过 **MCP**(标准输入/输出 + 流式 HTTP)进行管理。包含最小化管理 UI。
这里的难点不是“提供 HTML 服务”——而是以 **MCP 服务端** 的方式托管 **不受信任的 HTML**,同时防止其窃取凭证或泄露数据。设计以 **源隔离 + 严格的 CSP + 作用域 API 密钥** 为中心。
## 技术栈
TypeScript · Node 22 · [Hono](https://hono.dev) · `@modelcontextprotocol/sdk` · SQLite (`better-sqlite3`, WAL) · zod。 通过 Docker 部署。
## 工作原理
两个平面共享一个进程和一个 SQLite 文件,但通过 **主机头** 进行路由:
| 平面 | 源(示例) | 提供服务 | 认证 |
|---|---|---|---|
| 控制 | `api.example.com` | REST API,管理 UI(`/admin`),MCP-over-HTTP(`/mcp`) | API 密钥(Bearer) |
| 内容 | `content.example.com` | 发布制品,位于 `/a/:slug` | 公共,无 Cookie |
它们 **必须是不同的源** — 服务器否则拒绝启动(仅在本地开发时设置 `ALLOW_SAME_ORIGIN=1`)。这确保不受信任的制品 HTML 永远无法访问 API/管理界面。
### 安全层
- **源隔离**(上文)+ 基于主机的路由。
- **严格的 CSP**,适用于每个提供的制品:`default-src 'none'`,`connect-src 'none'`,`form-action 'none'`,`frame-ancestors 'none'`,`base-uri 'none'` — 阻止网络/表单外流和框架,同时允许制品自己的内联脚本/样式呈现。`relaxed` 模式(按制品选择加入)允许 https 网络访问。
- **强化标头**:`X-Content-Type-Options`,`Referrer-Policy`,`X-Frame-Options`,`Permissions-Policy`,`Cross-Origin-Resource-Policy`。内容源上永远不会设置 Cookie。
- **不可猜测的斜杠**(~132 位),可选 **过期**,和 **轮换** 以使共享链接失效。
- **API 密钥**:高熵,**静止时进行 SHA-256 哈希**,前缀索引,可撤销,**作用域所有者**(密钥仅查看/修改其自己的制品)。
- **限制**:最大 HTML 大小 + 每个密钥的发布速率限制。可选的 `sanitize` 层通过 DOMPurify 删除脚本。
> **接受的权衡(原始路径模型)**:所有制品共享一个内容源,因此它们可以读取彼此的 `localStorage`。这就是为什么内容源上没有凭证。升级到每个制品的子域(`<slug>.content-domain`)稍后只需要路由 + 通配符 DNS 更改。
## 快速入门(本地开发)
```bash
npm install
cp .env.example .env # 默认情况下使用 content.localhost 进行隔离
npm run build
# 引导一个 API 密钥(打印一次):
npm run create-key -- "我的笔记本电脑"
# 运行服务器:
npm start
# 管理 UI:http://localhost:8787/admin (在提示时粘贴密钥)
```
`content.localhost` 在大多数系统上解析为 127.0.0.1。如果您的系统不同,请将 `127.0.0.1 content.localhost` 添加到 `/etc/hosts`,或仅为开发设置 `ALLOW_SAME_ORIGIN=1`。
## MCP 工具
`publish_artifact`,`update_artifact`,`delete_artifact`,`get_artifact`,`list_artifacts`,`get_share_link`。所有调用密钥都是作用域所有者。
### 标准输入/输出(Claude Code / Desktop)— `.mcp.json`
```json
{
"mcpServers": {
"artifacts": {
"command": "node",
"args": ["/abs/path/dist/mcp/stdio.js"],
"env": {
"ARTIFACTS_API_KEY": "ak_...",
"CONTENT_BASE_URL": "https://content.example.com",
"CONTROL_BASE_URL": "https://api.example.com",
"DB_PATH": "/data/artifacts.db"
}
}
}
}
```
### 远程(流式 HTTP)
`POST https://api.example.com/mcp`,带有标头 `Authorization: Bearer ak_...`。
## 部署(Docker)
将您控制的两个主机名指向容器(反向代理 / DNS),在 `docker-compose.yml` 中将它们设置为 `CONTROL_BASE_URL` / `CONTENT_BASE_URL`,然后:
```bash
docker compose up -d --build
docker compose exec artifacts node dist/cli/createKey.js "生产密钥"
```
SQLite 持久存储在 `artifacts-data` 卷(`/data`)中。
## 测试
```bash
npm test # 单元测试 + MCP 内存 + HTTP 主机隔离集成
```
> 唯一的 `npm audit` 发现位于开发专用的 `vitest`/`vite`/`esbuild` 链中(不会运送到生产环境)。为避免破坏性 vitest v4 升级,保留未固定;升级测试工具时重新审视。