内容
# figjam-context-mcp

*Illustrative overview of the ingest pipeline — not a screen recording.*
MCP 服务器将 FigJam 看板转换为可查询的上下文,供 LLMs 使用 — 直接通过 Figma REST API 读取,无需手动 PDF 导出。它提供了五种工具:
- **ingest_board** — 读取 FigJam/Figma 文件,根据空间位置进行聚类,使用视觉模型验证和标记每个聚类,提取连接箭头作为聚类间关系,并将结果缓存在 `boardId`(= Figma 文件密钥)下。
- **get_board_context** — 返回一个紧凑、可以直接粘贴的上下文块(聚类 + 连接关系),适用于已摄入的看板,可选地限定在某个主题下。
- **answer_from_board** — 回答关于已摄入看板的自由式问题,引用了得出答案的聚类。
- **diff_board** — 比较同一个看板的两个摄入快照,并报告发生了什么变化:新增/删除/修改的聚类、编辑的节点和连接变化(“自上次研讨会以来发生了什么?”)。
- **diagnose_llm_config** — 对活跃的模型设置运行小型文本 + 视觉 JSON 检查,并报告可行的故障。
已摄入的看板在服务器重启后仍然存活:`get_board_context` 和 `answer_from_board` 在内存存储为空时,会从 `.cache/figjam-mcp/` 透明地恢复上一次完成的摄入。
重新摄入是增量的:每个聚类的成员内容都会被哈希处理,未更改的聚类会重用其之前的标签/摘要 — 只有新的或编辑过的聚类才会命中视觉模型。因此,重新摄入一个几乎未更改的看板几乎是免费的。传递 `forceFullIngest: true` 以绕过所有缓存和重用(例如,在切换模型后)。
## 工作原理
FigJam 看板在空间上是混乱的:旋转的便利贴,重叠的形状,嵌入的截图,没有阅读顺序。因此,管道结合了几何和视觉:
1. `fetchFileTree` + `flattenNodeTree` — 拉取原始节点树,并将其展平成规范化的节点(位置,大小,旋转,文本,图像引用,连接端点),丢弃空的结构噪音。
2. `geometricPreCluster` — 旋转感知的距离聚类为粗略的组。邻近搜索在空间网格上运行(近线性而不是 O(n²)),间隙阈值根据看板的密度(中位数最近邻间隙)进行调整,因此密集和稀疏的看板都能合理聚类。大足迹使用有界溢出路径,超过 250 个节点的连接组件在达到 LLM 之前在空间上进行二分。
3. `extractConnectorEdges` + `buildClusterRelations` — 连接箭头从几何聚类中排除(它们故意跨越组),但捕获为图:“聚类 A → 聚类 B(标签)”。这些关系包含在 `get_board_context` 输出和 `answer_from_board` 提示中 — 箭头是看板的语义结构。
4. `refineClusterWithVision` — 每个聚类,节点截图 + 提取的文本发送到视觉模型,确认哪些节点属于一起,标记组,描述嵌入的图像,并编写 3-5 个句子的摘要。聚类并发细化(`INGEST_BOARD_VISION_CONCURRENCY`,默认为 3)以达到视觉预算。
5. `mapClustersToPhases`(可选)— 将每个聚类分配给所选框架的阶段:`double_diamond`,`lean_canvas`,`retro`,`user_journey` 或自定义 `customPhases` 列表(或“不清楚”)。
6. 结果缓存在内存中并按文件密钥持久化;`get_board_context` 和 `answer_from_board` 从缓存中读取,并在重启后从磁盘恢复。
## 设置
```bash
npm install
cp .env.example .env
```
在 `.env` 中填写:
**`FIGMA_ACCESS_TOKEN`** — 登录 [figma.com](https://www.figma.com),前往 **设置 → 安全 → 个人访问令牌**,生成令牌。(也可以通过 `ingest_board` 上的 `figmaAccessToken` 输入项按调用传递。)
**`LLM_BASE_URL` / `LLM_API_KEY` / `LLM_MODEL_PRESET`** — 任何 OpenAI 兼容的端点。免费选项:
- **OpenRouter**(`.env.example` 中的默认值):在 [openrouter.ai/keys](https://openrouter.ai/keys) 获取密钥。默认的 `student-free` 预设对每个角色使用明确的免费模型:
`google/gemma-4-26b-a4b-it:free` 用于视觉,`qwen/qwen3-next-80b-a3b-instruct:free` + `nvidia/nemotron-nano-9b-v2:free` 用于文本/问答。`openrouter/free` 仍然是一个最后的备用方案,而不是主要模型。
- **GitHub 模型**:免费使用任何 GitHub 帐户 — 在 [github.com/marketplace/models](https://github.com/marketplace/models) 创建令牌,设置 `LLM_BASE_URL=https://models.github.ai/inference`。
可选覆盖:
- `LLM_MODEL_PRESET` — 目前支持:`student-free`。
- `LLM_VISION_MODELS` — 以逗号分隔的视觉模型候选列表。
- `LLM_TEXT_MODELS` — 以逗号分隔的文本/问答候选列表。
- `LLM_FAST_TEXT_MODELS` — 以逗号分隔的小型/快速文本候选列表。
- 旧版 `LLM_VISION_MODEL` / `LLM_TEXT_MODEL` 仍然作为首选候选覆盖。
## 运行
```bash
npm run dev
```
这使用 `tsx watch` 启动 MCP 服务器。通过 `npx @modelcontextprotocol/inspector npx tsx src/index.ts` 交互式尝试工具:
> **注意:** 不要将纯 `npm run dev` 传递给检查器(或任何 MCP 客户端)— npm 在服务器启动前向 stdout 打印生命周期横幅,这会破坏客户端期望的 JSON-RPC 流。直接调用 `tsx` 或添加 `--silent`:`npx @modelcontextprotocol/inspector npm run dev --silent`。
### MCP UI 超时
`ingest_board` 可能很慢,因为它调用了 Figma 和视觉 LLM 进行看板聚类。如果 MCP UI 显示 `MCP error -32001: Request timed out`,则客户端在外部调用完成之前超时。
服务器现在默认将提供者调用限制在:
- `FIGMA_REQUEST_TIMEOUT_MS=15000`
- `FIGMA_FILE_REQUEST_TIMEOUT_MS=60000`
- `LLM_REQUEST_TIMEOUT_MS=20000`
- `LLM_RATE_LIMIT_RETRIES=1`
- `LLM_ANSWER_MAX_OUTPUT_TOKENS=800`
- `LLM_VISION_MAX_OUTPUT_TOKENS=4096`
- `LLM_ANSWER_TOP_K=6`
- `LLM_ANSWER_PROMPT_MAX_CHARS=24000`
- `INGEST_BOARD_VISION_BUDGET_MS=35000`
- `INGEST_BOARD_VISION_CONCURRENCY=3`
- `FIGMA_SCREENSHOT_DOWNLOAD_CONCURRENCY=3`
- `FIGJAM_MCP_MEMORY_CACHE_MAX_BOARDS=10`
`ingest_board` 默认使用 `ingestMode: "balanced"`:富含文本的聚类使用确定性摘要,而富含图像或低文本的聚类在预算内使用视觉。`max_speed` 跳过视觉;`max_quality` 尝试每个聚类的视觉。完成的摄入存储在 `.cache/figjam-mcp/` 下,按文件状态、节点哈希、模型预设、文档提示和摄入模式键控。
视觉候选按信息增益优先,而不是画布位置。每个请求都有有界的节点/文本库存,阶段在其配置的截止日期返回,即使提供者停滞。`answer_from_board` 检索最相关的聚类加上直接连接邻居,并将完整提示保持在其配置的字符预算下。内存缓存使用 LRU 驱逐;持久历史记录 20 个状态,并删除变得安全无引用的快照。
在更改模型环境变量后运行 `diagnose_llm_config`。它通过小型算术挑战验证结构化文本回复,并通过已知颜色图像检查实际图像理解,而无需摄入看板。
## 使用示例
粘贴 Figma 看板链接并摄入它:
```jsonc
// 工具:ingest_board
{
"figmaFileUrl": "https://www.figma.com/board/AbC123XyZ456/Semester-Project-Research",
"docStructureHint": "double_diamond"
}
// → { "boardId": "AbC123XyZ456", "clusterCount": 5, "relationCount": 3,
// "summary": "摄入看板 AbC123XyZ456:5 个聚类 — \"用户访谈引用\",\"问题框架\",…" }
```
而不是内置框架(`double_diamond`,`lean_canvas`,`retro`,`user_journey`),您可以传递自己的阶段名称 — 然后聚类通过关键字匹配映射到它们:
```jsonc
{ "figmaFileUrl": "…", "customPhases": ["想法", "反馈", "开放问题"] }
```
`boardId` 是文件密钥本身 — 重新运行 `ingest_board` 刷新缓存条目。然后拉取上下文,可选地限定在主题上:
```jsonc
// 工具:get_board_context
{ "boardId": "AbC123XyZ456", "topic": "用户研究" }
// → contextText:
// FigJam 看板 AbC123XyZ456 — 2 of 5 个聚类(主题:用户研究)://
// ## 用户访谈引用 [发现]
// 包含六个学生访谈关于考试压力的逐字引用。两个嵌入的截图显示调查结果(条形图的学习习惯)。主要痛点:不明确的要求和延迟的反馈。 …
//
// ## 聚类之间的连接(来自连接箭头)
// - \"用户访谈引用\" → \"问题框架\" — \"告知\"
```
`contextText` 块故意精简 — 直接粘贴到文档编写聊天中(例如,用于学期报告)。或者直接提问:
```jsonc
// 工具:answer_from_board
{ "boardId": "AbC123XyZ456", "question": "主要的用户痛点是什么?" }
// → { "answer": "不明确的要求和延迟的反馈 …",
// "citedClusters": ["用户访谈引用", "问题框架"] }
```
在看板演变后(例如,研讨会 2),再次摄入 — 未更改的聚类被重用,因此这很快 — 并对快照进行差异:
```jsonc
// 工具:diff_board
{ "boardId": "AbC123XyZ456" }
// → summaryText:
// FigJam 看板 AbC123XyZ456 — 从 2026-07-03T14:02:11Z 到 2026-07-10T09:41:52Z 的更改://
// 新增聚类(1 个):
// - \"反馈轮次 2\": 包含第二轮可用性测试的粘性笔记。
// 修改的聚类(1 个):
// - \"用户访谈引用\": +3 节点,1 个编辑
// 连接:+1 / -0
// - 新增:\"反馈轮次 2\" → \"问题框架\" — \"确认\"
// 节点:+9 添加 / -0 删除 / 1 编辑。
// 未更改的聚类:4。
```
`compareTo` 选择一个较旧的基线(2 = 两个摄入之前,…);历史记录保留每个文件密钥的最后 20 个不同看板状态。
## 脚本
- `npm run dev` — 使用 `tsx watch` 运行服务器(自动重启)。
- `npm run build` — 清洁并将 TypeScript 编译到 `dist/`,保留可执行的 CLI。
- `npm start` — 从 `dist/` 运行编译后的服务器。
- `npm test` — 运行 Vitest 测试套件。
- `npm run typecheck` — 类型检查源代码和测试,不发出文件。
- `npm run check` — 类型检查、测试、构建并验证包元数据/二进制文件。
- `npm run package:smoke` — 打包 npm tarball,执行其 CLI 并验证 MCP 初始化/工具发现。
发布会自动通过 `prepack` 运行相同的检查;CI 在最小支持的 Node 版本和 LTS 行上执行完整的包路径。
## 项目布局
```
src/
├── index.ts # stdio 入口
├── server.ts # McpServer 设置 + 工具注册
├── tools/ # 工具处理程序(摄入管道,上下文,问答)
├── schemas/ # 每个工具的 Zod 输入/输出模式
├── lib/ # Figma API,节点树,聚类,视觉,LLM,缓存
└── types.ts # 共享域类型
```