内容
<div align="center">
<h1>@cyanheads/docgen-mcp-server</h1>
<p><b>将HTML/markdown渲染成PDF,导出行到xlsx,并通过MCP填充AcroForm PDF。STDIO或可流式HTTP。</b>
<div>4 个工具 • 1 个资源</div>
</p>
</div>
<div align="center">
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/docgen-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/docgen-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
</div>
<div align="center">
[](https://github.com/cyanheads/docgen-mcp-server/releases/latest/download/docgen-mcp-server.mcpb) [](https://cursor.com/en/install-mcp?name=docgen-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvZG9jZ2VuLW1jcC1zZXJ2ZXIiXX0=) [](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22docgen-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A5B%22-y%22%2C%22%40cyanheads%2Fdocgen-mcp-server%22%5D%7D)
[](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
</div>
---
## 为什么是docgen
代理可以编写完美的发票HTML,干净的数据表或填充的表单字段映射为令牌 - 但它不能发出字节。docgen是渲染器,填补了空白:结构化内容输入,可下载的二进制文档输出。它不包装外部API;“服务”是一个捆绑的渲染堆栈(`pdf-lib`用于PDF和表单填充,`exceljs`用于电子表格,`marked`用于markdown路径)。每个输出都存储在租户范围内,具有短暂的TTL,并作为稳定的资源URI返回,加上内联base64(当足够小时)。
## 工具
四个工具共享一个交付形状 - 三个编写存储文档的渲染器和一个读取文档的读取器。每个渲染/导出/填充调用都返回一个`DocumentEnvelope`(`documentId`,`docgen://document/{id}`资源URI,字节大小,TTL和内联base64,当工件足够小时);`docgen_get_document`为已经持有的ID返回相同的信封。
| 工具 | 描述 |
|:---|:---|
| `docgen_render_pdf` | 将HTML,markdown或`{{key}}`模板+数据对象渲染为可下载的PDF。 |
| `docgen_export_spreadsheet` | 将一行或多行对象渲染为可下载的`.xlsx`工作簿。 |
| `docgen_fill_form` | 填充提供的PDF(base64或https URL)的AcroForm字段,并可选地展平它。 |
| `docgen_get_document` | 通过渲染/导出/填充工具返回的ID重新获取存储的文档。 |
### `docgen_render_pdf`
将内容渲染为可下载的PDF。
- 提供确切的一个`source`: `{ html }` (您编写的原始HTML - 推荐路径), `{ markdown }` (转换为HTML,然后渲染),或 `{ template, data }` (用数据对象填充`{{key}}`模板,服务器拥有布局)
- `pageOptions`控制大小(`A4` / `Letter` / `Legal` / `A3` / `A5`,默认为`Letter`),方向,每页边距(CSS长度,如`"10mm"`,`"0.5in"`,`"72pt"`),页眉/页脚文本(支持`{{page}}`,`{{total}}`,`{{date}}`令牌)和自动页码
- 轻量级引擎渲染结构化布局(标题,段落,列表,表格)但不支持任意CSS - 当不支持的样式被丢弃时,设置`degraded`丰富度标志,以便代理不会对其忠实度产生误解
- 返回带有`pageCount`的`DocumentEnvelope`
---
### `docgen_export_spreadsheet`
将表格数据渲染为`.xlsx`工作簿 - 自然导出阶段,用于从另一个服务器提取的行。
- `sheets[]`中的每个条目都是工作表`name`和行对象数组(属性→标量字符串/数字/布尔值/空值)
- 可选的每列规范设置标题标签,值`type`(`string` / `number` / `date` / `boolean`),列宽和Excel数字/日期格式字符串(例如`"#,##0.00"`,`"yyyy-mm-dd"`);如果省略,则列源自第一行的键
- 空的`rows`数组产生仅限标题的工作表;空的`sheets[]`被拒绝(`empty_workbook`)
- 返回带有`sheetCount`的`DocumentEnvelope`
---
### `docgen_fill_form`
填充提供的PDF的AcroForm字段,并可选地展平它。
- 提供源作为确切的 `{ base64 }` 或 `{ url }` - https URL在SSRF防护后面获取(阻止私有/环回/本地链接范围,`application/pdf`必填,响应大小上限);base64完全避免获取
- `fields`是AcroForm字段名称→值映射;名称必须与PDF的内部字段名称完全匹配(区分大小写),由提供表单的人提供(docgen不暴露它们)
- 没有AcroForm对应关系的名称返回在`unmatchedFields[]`中,而不是使调用失败 - 更正它们并重新渲染
- 设置`flatten: true`以烘焙值,使结果不再可编辑
- 仅AcroForm - 基于XFA的PDF(一些政府表单)报告`not_a_form`
- 返回带有`pageCount`和`unmatchedFields[]`的`DocumentEnvelope`
---
### `docgen_get_document`
通过ID重新获取存储的文档。
- `documentId` **仅**可从早期的`docgen_render_pdf`,`docgen_export_spreadsheet`或`docgen_fill_form`结果中获取 - 它不能被猜测或构造
- 使用它来恢复内联副本已超出(超过内联大小限制)但仍在TTL内的文档
- 过期或未知的ID返回`document_expired`;ID是单个渲染的,不可重用
## 资源
| 类型 | 名称 | 描述 |
|:---|:---|:---|
| 资源 | `docgen://document/{documentId}` | 通过ID渲染的文档 - 原始字节作为`blob`(具有文档的真实MIME类型)+ JSON元数据块。 可读直到文档TTL过期。 |
该资源是主机支持资源的稳定URI交付表面。 所有文档数据也可通过工具表面访问 - `docgen_get_document`是工具专属的双胞胎,与此资源一起读取相同的存储库并返回相同的信封。 两者都不重新渲染。
## 功能
构建于[`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
- 声明式工具和资源定义 - 每个基元一个文件,框架处理注册和验证
- 统一错误处理 - 处理程序抛出,框架捕获,分类和格式化
- 可插拔身份验证:`none`,`jwt`,`oauth`
- 可交换存储后端:`in-memory`,`filesystem`,`Supabase`,`Cloudflare KV/R2/D1`
- 带有可选OpenTelemetry跟踪的结构化日志记录
- STDIO和可流式HTTP传输
docgen特定:
- 没有外部API - 捆绑的渲染堆栈(`pdf-lib`,`exceljs`,`marked`),因此渲染是本地和确定的,没有上游失败
- 所有四个工具共用一个`DocumentEnvelope` - 三个编写器和读取器对代理是可互换的,并且资源URI与内联base64的交付在同一位置决定
- 有界渲染 - 文档字节上限(`DOCGEN_MAX_DOCUMENT_BYTES`)和时钟超时(`DOCGEN_RENDER_TIMEOUT_MS`)将失控的渲染转换为类型化的,可恢复的错误而不是挂起
- 租户范围,TTL绑定存储 - 为一个租户生成的文档ID仅对该租户解析;输出是下载,而不是记录,因此它们过期而不是累积
- SSRF防护表单获取 - `docgen_fill_form`带有URL源的解析DNS并检查目标IP,然后获取,阻止私有/环回/本地链接范围
代理友好输出:
- 双表面交付 - 每个信封字段都位于`structuredContent`和`format()` markdown双胞胎中,因此工具专属和资源专属客户端都看到`documentId`,资源URI,内联可用性状态,大小和TTL
- 按大小内联与资源 - `inlineBase64`仅在或低于`DOCGEN_INLINE_MAX_BYTES`时填充,因此大型工作簿不会内联base64到工具结果中;在阈值以上,通过资源URI交付
- 部分填充报告 - `docgen_fill_form`返回`unmatchedFields[]`,因此代理了解哪些字段名称没有落地,可以纠正和重新渲染而不是假设干净填充
- 类型化错误合约与恢复提示 - 每个工具声明其故障表面(`invalid_source`,`template_render_failed`,`document_too_large`,`render_timeout`,`not_a_form`,`source_unfetchable`,`document_expired`,...)并带有可操作的下一步文本
## 快速开始
将以下内容添加到您的MCP客户端配置文件中。不需要API密钥。
```json
{
"mcpServers": {
"docgen-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/docgen-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
```
或者使用npx(不需要Bun):
```json
{
"mcpServers": {
"docgen-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/docgen-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
```
或者使用Docker:
```json
{
"mcpServers": {
"docgen-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/docgen-mcp-server:latest"
]
}
}
}
```
对于可流式HTTP,设置传输并启动服务器:
```sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# 服务器监听 http://localhost:3010/mcp
```
文档通过`docgen://document/{id}`资源(和内联base64,当足够小)通过每个传输交付。信封的`downloadUrl`字段保留用于未来的HTTP下载路由,在此版本中未发出。
### 先决条件
- [Bun v1.3.2](https://bun.sh/) 或更高版本(或Node.js v24+)。
### 安装
1. **克隆仓库:**
```sh
git clone https://github.com/cyanheads/docgen-mcp-server.git
```
2. **导航到目录:**
```sh
cd docgen-mcp-server
```
3. **安装依赖:**
```sh
bun install
```
4. **配置环境:**
```sh
cp .env.example .env
# 编辑.env以覆盖任何默认值
```
## 配置
所有配置都是可选的 - docgen在没有所需的环境变量的情况下运行。
| 变量 | 描述 | 默认值 |
|:---------|:------------|:--------|
| `DOCGEN_DOCUMENT_TTL_SECONDS` | 已渲染的文档在可检索之前过期,单位为秒。 | `900` |
| `DOCGEN_MAX_DOCUMENT_BYTES` | 单个渲染工件的硬上限,单位为字节;超过上限会中止渲染。 | `26214400` |
| `DOCGEN_RENDER_TIMEOUT_MS` | 每个渲染的时钟预算,单位为毫秒;超过上限会中止渲染。 | `30000` |
| `DOCGEN_INLINE_MAX_BYTES` | 小于或等于该字节大小的工件会作为base64返回内联;较大的工件省略它。 | `5242880` |
| `DOCGEN_PDF_ENGINE` | PDF渲染引擎。仅`lightweight`已实现;`chromium`保留并在启动时被拒绝。 | `lightweight` |
| `MCP_TRANSPORT_TYPE` | 传输:`stdio`或`http`。 | `stdio` |
| `MCP_HTTP_PORT` | HTTP服务器的端口。 | `3010` |
| `MCP_AUTH_MODE` | 身份验证模式:`none`,`jwt`或`oauth`。 | `none` |
| `MCP_PUBLIC_URL` | TLS代理后面的公共源。(`downloadUrl`信封字段保留用于未来的HTTP下载路由,在此版本中未发出。) | — |
| `MCP_LOG_LEVEL` | 日志级别(RFC 5424)。 | `info` |
| `STORAGE_PROVIDER_TYPE` | 文档字节+元数据的存储后端。 | `in-memory` |
| `OTEL_ENABLED` | 启用[OpenTelemetry仪表盘](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry)(跨度,指标,完成日志)。 | `false` |
参见[`.env.example`](./.env.example)以获取完整的覆盖列表。
> 文档存储在`ctx.state`中,内存提供者将其保存在进程内存中 - 重启会丢弃每个存储的文档,并且在重启前生成的ID返回`document_expired`。这是预期的(输出是下载项,不是记录);要跨重启实现持久保留,请将`STORAGE_PROVIDER_TYPE`指向持久后端。
## 运行服务器
### 本地开发
- **构建并运行:
```sh
# 一次构建
bun run rebuild
# 运行构建的服务器
bun run start:stdio
# 或
bun run start:http
```
- **运行检查和测试:
```sh
bun run devcheck # 校验、格式化、类型检查、安全、更改日志同步
bun run test # Vitest测试套件
bun run lint:mcp # 验证MCP定义对抗规范
```
### Docker
```sh
docker build -t docgen-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 docgen-mcp-server
```
Dockerfile默认为HTTP传输、无状态会话模式和日志记录到`/var/log/docgen-mcp-server`。OpenTelemetry对等依赖项默认安装 - 使用 `--build-arg OTEL_ENABLED=false` 忽略它们。
## 项目结构
| 目录 | 目的 |
|:----------|:--------|
| `src/index.ts` | `createApp()`入口点 - 注册工具/资源并初始化渲染 + 存储服务。 |
| `src/config` | 服务器特定的环境变量解析和Zod验证。 |
| `src/mcp-server/tools` | 工具定义(`*.tool.ts)。 |
| `src/mcp-server/resources` | 资源定义(`*.resource.ts)。 |
| `src/services/document` | 渲染堆栈(`RenderService`)和工件存储(`DocumentStore`),共享类型和SSRF获取防护。 |
| `tests/` | 镜像`src/`的单元和集成测试。 |
## 开发指南
参见[`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md)以获取开发指南和架构规则。简版:
- 处理程序抛出,框架捕获 - 工具逻辑中没有`try/catch`
- 使用`ctx.log`进行请求范围的日志记录,使用`ctx.state`进行租户范围的存储
- 在`src/index.ts`的`createApp()`数组中注册新工具和资源
- 共享的`DocumentEnvelope`跨所有交付工具 - 保持编写器和读取器可互换
## 贡献
问题和拉取请求是欢迎的。运行检查和测试后再提交:
```sh
bun run devcheck
bun run test
```