1. 项目概述一个让飞书文档“活”起来的智能写作助手如果你和我一样日常工作中需要大量撰写飞书文档——无论是产品需求文档、技术方案、会议纪要还是项目周报那你肯定体会过那种面对空白文档的“创作焦虑”。构思结构、填充内容、调整格式一套流程下来时间消耗巨大而且内容质量还常常因为精力分散而参差不齐。最近我在GitHub上发现了一个名为“bert995/feishu-mcp-doc-write-stable”的项目它精准地戳中了这个痛点。简单来说这是一个基于MCPModel Context Protocol协议专门为飞书文档打造的、稳定可靠的AI智能写作助手。这个项目的核心价值在于它不是一个简单的文本生成工具而是一个能够深度理解飞书文档结构、上下文语义并能根据你的指令进行精准内容创作和编辑的“副驾驶”。想象一下你只需要在文档里一下这个助手或者通过一个简单的指令它就能帮你续写段落、总结会议要点、润色语言风格甚至将一段潦草的笔记整理成结构清晰的报告。这背后依赖的正是MCP协议所倡导的“模型上下文”理念让AI模型能够安全、可控地访问和操作你的文档数据从而实现真正意义上的“人机协作写作”。对于产品经理、技术写作者、项目经理以及任何需要高频产出高质量文档的职场人来说这个工具的价值不言而喻。它不仅能将你从重复性的文案工作中解放出来更能通过AI的辅助提升文档的逻辑性、专业性和一致性。接下来我将从项目设计思路、核心功能实现、具体操作流程以及我踩过的坑和解决方案这几个方面为你深度拆解这个项目让你不仅能理解它如何工作更能亲手部署和使用它让它成为你飞书工作流中的得力助手。2. 项目整体设计与思路拆解2.1 为什么是MCP协议选型的深层考量要理解这个项目首先得弄明白MCPModel Context Protocol是什么。你可以把它想象成AI模型和应用之间的一座“标准化的桥梁”。在MCP出现之前如果我们想让一个AI模型比如ChatGPT去操作飞书文档通常需要开发者自己写一大堆胶水代码处理OAuth认证、解析飞书复杂的API、将文档内容转换成模型能理解的格式再把模型的输出结果转译回飞书的操作指令。这个过程不仅繁琐而且每个应用都需要重复造轮子安全和权限控制也容易出问题。MCP协议的核心思想是“资源Resources”和“工具Tools”的抽象。在这个项目中“飞书文档”被抽象为一种“资源”而“写入内容”、“总结内容”、“翻译内容”等操作被抽象为一个个“工具”。项目本身实现了一个MCP服务器Server这个服务器专门负责与飞书云文档API进行安全通信。而像Claude Desktop、Cursor这类支持MCP协议的AI客户端Client则可以通过标准的MCP协议来调用这个服务器提供的“工具”无需关心飞书API的具体细节。选择MCP协议带来了几个关键优势解耦与标准化AI模型端和飞书服务端完全解耦。模型只需要学会调用标准的MCP工具而无需适配特定的飞书SDK。这意味著未来如果模型升级或更换只要它支持MCP就能无缝使用这个文档写作工具。安全可控权限控制被集中到了MCP服务器这一层。用户授权的是MCP服务器访问其飞书文档的权限而不是直接将飞书API密钥暴露给AI模型。服务器可以实现更精细的权限检查和操作审计。生态兼容由于MCP是一个开放协议这个“飞书文档写作工具”可以同时被多个支持MCP的AI客户端使用极大地扩展了其应用场景。开发者无需为每个客户端单独开发插件。2.2 核心功能架构从指令到文档的旅程这个项目的架构可以清晰地分为三层交互层、逻辑层和驱动层。交互层这是用户直接接触的部分。通常你是在一个集成了MCP客户端的应用里工作比如在Claude Desktop的聊天界面或者Cursor的AI指令面板。你以自然语言发出指令例如“帮我把当前飞书文档的第三段润色得更正式一些”。逻辑层MCP Server这是项目的核心大脑。它接收来自客户端的标准化MCP请求。这个请求里包含了要调用的工具名如write_to_feishu_doc和参数如文档ID、要写入的内容、目标位置。Server的逻辑需要完成以下几件事指令解析与验证检查参数是否完整、合法例如文档ID是否存在写入位置是否有效。上下文获取根据需求可能需要先调用飞书API读取文档当前的部分内容作为AI模型生成或修改的上下文。例如当要求“总结第二章节”时Server需要先获取第二章节的原始文本。AI模型调度本项目的一个关键设计是文本的生成和优化工作是由外部的AI模型如GPT-4, Claude 3完成的而非Server自身。Server在需要时会将“原始文本”和“用户指令”组合成一个新的Prompt通过MCP协议发回给客户端请求客户端背后的主模型进行处理然后将模型返回的结果用于后续操作。这保持了核心文本能力的灵活性。飞书API调用将处理好的最终内容可能是AI生成的新文本也可能是修改后的文本通过飞书官方的API精准地写入或更新到指定的文档位置。驱动层即飞书开放平台提供的API。Server通过飞书提供的SDK或直接调用RESTful API执行具体的文档读写操作。这一层封装了所有与飞书服务器通信的细节包括处理分页、富文本格式Block转换等。整个数据流是这样的用户自然语言指令 - MCP客户端 - MCP协议请求 - 本项目Server - (可选)获取文档上下文 - 请求主模型生成内容 - 接收模型结果 - 调用飞书API写入 - 返回操作结果给用户。2.3 稳定性设计为何强调“Stable”项目名称中的“stable”稳定并非虚言。在涉及生产环境的文档操作时稳定性是生命线。这个项目从几个方面进行了加固请求重试与退避网络波动或飞书API瞬时故障不可避免。项目中会对所有飞书API调用实现指数退避重试机制。例如第一次失败后等待1秒重试第二次失败后等待2秒以此类推避免因临时故障导致操作失败。操作幂等性处理对于“写入”这类操作设计上要保证同一请求重复执行多次的效果与执行一次相同。这可以通过在请求中携带唯一ID或由Server检查目标位置是否已存在预期内容来实现防止重复写入或内容错乱。内容分段与流式写入如果需要写入很长的内容如AI生成的一篇完整报告直接一次性调用API可能存在超时或请求过大的风险。成熟的实现会将长内容按段落或章节分段进行多次API调用确保每个小操作都成功。全面的错误处理与日志对飞书API返回的各种错误码如无权限、文档不存在、内容冲突等都有明确的处理逻辑和用户友好的错误信息反馈。同时记录详细的操作日志便于在出现问题时快速定位。注意这里的“稳定”指的是服务间调用的可靠性和数据操作的准确性并不能完全避免因AI模型生成内容本身不合规而带来的业务风险。因此重要的文档在AI辅助生成后仍然需要人工进行最终审核。3. 核心细节解析与实操要点3.1 飞书文档API的“Block”模型操作的基础单元与操作普通文本文件不同飞书文档的API是基于“Block”模型的。理解这个概念至关重要否则你无法精准控制内容写入的位置和格式。飞书将文档中的每一个独立元素都视为一个“块”Block。一个段落是一个块一个标题是一个块一个任务列表项是一个块甚至一个图片也是一个块。每个块都有一个唯一的block_id和类型如paragraph,heading1,bullet等。当你想要在某个段落后面插入新内容或者替换某个标题时你必须找到对应块的block_id。本项目在实现“写入”工具时内部逻辑需要处理以下步骤定位父节点用户指令可能是“在‘项目背景’这个标题后面添加一段”。Server需要先遍历文档找到内容为“项目背景”的标题块获取其block_id。确定操作类型是作为该标题块的子块追加使其成为标题下的内容还是作为该标题块的后继兄弟块插入使其成为同一层级的下一个部分这对应了不同的API参数。构建块内容将需要写入的纯文本按照目标格式段落、有序列表等包装成飞书API要求的JSON结构。例如一个段落块的内容体是{elements:[{text_run:{content:这里是文本内容}}]}。执行API调用调用飞书的POST /open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children或类似的API完成插入。实操心得在开发或调试时一个非常有用的技巧是先用飞书API的“获取文档内容”接口把整个文档的Block树结构拉取下来并打印成JSON格式看看。你会对文档的微观结构有非常直观的认识也能快速找到你想要操作的块的ID。很多“写入位置不对”的问题都是因为对Block树层级关系理解有误。3.2 权限申请与配置安全的第一步要让这个MCP Server能够访问和修改你的飞书文档你必须为其创建一个飞书自建应用并授权。这个过程虽然步骤固定但有几个细节容易踩坑。第一步创建飞书自建应用登录 飞书开放平台 进入“开发者后台”。点击“创建企业自建应用”填写应用名称如“我的AI文档助手”、描述等。创建成功后进入应用详情页你需要记录两个关键信息App ID和App Secret。这相当于应用的账号密码MCP Server将使用它们来获取访问令牌。第二步配置权限在应用的“权限管理”页面你需要为应用添加以下关键权限contact:user.id:readonly(以用户身份访问)用于获取用户身份。drive:drive:readonly和drive:drive:write前者用于读取用户云空间信息后者用于写入。通常需要前者才能申请后者。drive:file:readonly和drive:file:write这是核心权限。分别用于读取文档内容和写入/修改文档内容。必须勾选。drive:file:comment:write可选如果你希望AI还能处理评论可以添加。添加权限后切记要点击“申请线上发布”或“版本管理与发布”创建一个新版本并申请发布。仅仅保存权限设置应用是没有相应能力的。第三步获取访问令牌MCP Server不能直接使用App Secret它需要用一个“临时通行证”——Access Token。项目代码中会使用App ID和App Secret调用飞书的/open-apis/auth/v3/tenant_access_token_internal/接口来获取这个Token。这个Token通常有效期为2小时因此Server需要实现自动刷新的逻辑。常见踩坑点权限未生效最常见的错误是配置了权限但忘了“发布”新版本。务必检查应用版本号是否已更新。Token获取失败检查App ID和App Secret是否正确是否有空格。飞书的App Secret在生成后只显示一次务必妥善保存丢失需要重置。文档访问范围应用默认只能访问“自己作为创建者”的文档。如果需要访问企业内其他同事创建的文档需要在调用API时使用user_access_token代表具体用户并且该用户必须已经授权了该应用。项目通常设计为使用“租户访问令牌”tenant_access_token来操作应用自身有权限的文档因此在分享文档时可能需要将应用也添加为协作者。3.3 与AI客户端的集成以Claude Desktop为例目前体验这个项目最便捷的方式是通过Anthropic官方推出的Claude Desktop应用它原生支持MCP。定位配置文件Claude Desktop的MCP配置通常位于一个JSON文件中。在macOS上路径是~/Library/Application Support/Claude/claude_desktop_config.json。在Windows上可能在%APPDATA%\Claude\claude_desktop_config.json。配置MCP Server你需要编辑这个JSON文件添加一个指向你本地运行的feishu-mcp-doc-write-stable服务器的配置项。配置看起来像这样{ mcpServers: { feishu-doc-writer: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/index.mjs ], env: { FEISHU_APP_ID: 你的App ID, FEISHU_APP_SECRET: 你的App Secret, DEFAULT_DOC_ID: 你的默认文档ID可选 } } } }command: 启动服务器的命令这里是node。args: 命令的参数即项目主入口文件的绝对路径。env: 传递给服务器的环境变量包含关键的飞书应用凭证。重启与验证保存配置文件后完全重启Claude Desktop。如果配置成功在Claude的输入框里你应该能看到一个新的“羽毛笔”或“文档”图标点击它可以看到可用的工具列表例如“Write to Feishu Doc”、“Summarize Feishu Doc”等。此时你就可以在聊天中直接使用这些工具了。重要提示确保你本地已经通过Node.js环境成功启动了MCP Server。通常你需要先在项目目录下运行npm install安装依赖然后通过node index.mjs运行服务。配置文件中args的路径必须指向这个正在运行的服务入口文件。4. 实操过程与核心环节实现4.1 环境准备与项目启动假设你已经在本地克隆了bert995/feishu-mcp-doc-write-stable项目代码我们从头开始走一遍流程。第一步检查与安装Node.js打开终端运行node --version和npm --version确保Node.js版本在16以上npm版本在7以上。如果未安装请去Node.js官网下载LTS版本安装。第二步安装项目依赖进入项目根目录你会看到package.json文件。运行npm install这个命令会根据package.json中的定义下载所有必需的第三方库例如用于HTTP请求的axios、用于处理MCP协议通信的modelcontextprotocol/sdk等。安装过程可能会持续一两分钟取决于网络速度。第三步配置环境变量项目不会硬编码你的飞书密钥。通常它通过读取环境变量或配置文件来获取。根据项目README的说明最常见的方式是创建一个.env文件在项目根目录。内容如下FEISHU_APP_IDcli_xxxxxx FEISHU_APP_SECRETxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # 可选设置一个默认操作的文档ID方便测试 DEFAULT_DOCUMENT_IDxxxxxx请务必将cli_xxxxxx和xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx替换成你在飞书开放平台获取的真实App ID和App Secret。.env文件包含敏感信息切记不要将其提交到Git等版本控制系统。你应该在.gitignore文件中添加.env。第四步启动MCP服务器在终端中运行启动命令。根据项目入口文件的不同可能是node index.js # 或 node index.mjs # 或 npm start如果一切正常终端会输出类似Feishu MCP Server is running on stdio或Server initialized的信息这表明你的MCP服务器已经在本地标准输入输出上启动等待MCP客户端如Claude Desktop来连接。4.2 核心工具的实现逻辑剖析我们深入看一下项目中一个核心工具比如write_to_feishu_doc其代码实现逻辑是怎样的。这能帮助你理解如何扩展或自定义工具。1. 工具定义与注册在MCP Server的初始化代码中会定义一个工具列表。每个工具都是一个对象包含name名称、description描述这个描述很重要AI模型靠它来理解工具用途和inputSchema输入参数的模式定义。// 示例代码结构 const tools [ { name: write_to_feishu_doc, description: 将指定的文本内容写入到飞书文档的指定位置。可以指定在某个标题后、段落前或者文档末尾追加。, inputSchema: { type: object, properties: { documentId: { type: string, description: 飞书文档的唯一标识ID。如果未提供将使用环境变量中的默认文档ID。 }, content: { type: string, description: 要写入的纯文本内容。 }, insertAfterBlockId: { type: string, description: 可选。将新内容插入到指定Block ID的块之后。如果未提供则添加到文档末尾。 }, blockType: { type: string, enum: [paragraph, heading1, heading2, bullet], description: 新内容的块类型默认为paragraph普通段落。 } }, required: [content] // content是必填参数 } }, // ... 其他工具 ];然后在服务器初始化时调用类似server.setRequestHandler([...])的方法将这些工具注册到MCP框架中。2. 请求处理与飞书API调用当Claude Desktop发送一个MCP调用请求过来框架会路由到对应的处理函数。这个函数大致会做以下事情async function handleWriteToDoc(request) { const { documentId, content, insertAfterBlockId, blockType paragraph } request.params; // 1. 参数验证与补全 const targetDocId documentId || process.env.DEFAULT_DOCUMENT_ID; if (!targetDocId) { throw new Error(未指定文档ID且无默认文档ID); } if (!content || content.trim() ) { throw new Error(写入内容不能为空); } // 2. 获取飞书API访问令牌内部会处理缓存和刷新 const accessToken await getFeishuAccessToken(); // 3. 构建要创建的Block数据 const newBlockData { block_type: blockType, // 根据blockType构建不同的content结构 [blockType]: buildContentByType(content, blockType) }; // 4. 决定API调用端点 let apiEndpoint, apiData; if (insertAfterBlockId) { // 在指定块后插入 apiEndpoint https://open.feishu.cn/open-apis/docx/v1/documents/${targetDocId}/blocks/${insertAfterBlockId}/children; apiData { children: [newBlockData], index: -1 }; // -1 表示插入到末尾 } else { // 追加到文档末尾 apiEndpoint https://open.feishu.cn/open-apis/docx/v1/documents/${targetDocId}/blocks; apiData { children: [newBlockData] }; } // 5. 调用飞书API const response await axios.post(apiEndpoint, apiData, { headers: { Authorization: Bearer ${accessToken}, Content-Type: application/json; charsetutf-8 } }); // 6. 处理响应并返回结果给MCP客户端 if (response.data.code 0) { const newBlockId response.data.data.children[0]?.block_id; return { isError: false, content: [{ type: text, text: 成功写入内容到文档。新块的ID是${newBlockId} }] }; } else { throw new Error(飞书API调用失败${response.data.msg}); } }以上是一个高度简化的逻辑示意真实项目会有更完善的错误处理、日志记录和可能的内容预处理比如过长内容的分段。4.3 一个完整的使用案例从构思到成文让我们模拟一个真实场景看看如何利用这个工具链完成一次高效的文档创作。场景我需要准备一份新产品功能“智能数据看板”的技术评审会议纪要。第一步创建文档框架我手动在飞书创建一个新文档写下标题“【技术评审】智能数据看板V1.0”并简单列出几个一级标题会议信息、背景与目标、方案概述、评审要点、待办事项。我复制这个文档的ID。第二步启动AI协作我打开Claude Desktop它已经配置好了我们的飞书MCP工具。我对Claude说“请使用飞书文档工具在文档ID为[我的文档ID]的‘会议信息’标题下帮我生成一个标准的会议纪要模板包括会议时间、地点、主持人、参会人、记录人。”Claude理解指令后会调用write_to_feishu_doc工具将一段格式化的文本插入到“会议信息”这个标题块之后。我立刻在飞书文档里看到了生成的内容。第三步填充核心内容我继续对Claude说“基于我们刚才讨论的在‘方案概述’部分写一段关于采用WebSocket实现实时数据推送的技术选型理由要求语言技术化、有说服力。”Claude可能会先调用read_from_feishu_doc如果项目实现了这个工具来获取“方案概述”部分的现有内容然后结合我的指令生成一段专业的技术描述并再次调用写入工具将内容添加到正确位置。第四步总结与整理会议讨论产生了大量碎片化意见。我把这些意见复制到Claude的输入框然后说“请将下面这些零散意见归纳整理到‘评审要点’部分分为‘一致通过项’、‘需修改项’、‘待决议项’三个子列表。” Claude便能出色地完成分类、归纳和格式化列表的工作。第五步生成待办最后我说“根据评审要点中的‘需修改项’在‘待办事项’部分生成一个任务列表每条任务格式为‘【负责人】任务描述截止日期YYYY-MM-DD’日期统一设为下周。”通过这样一轮人机交互一份结构清晰、内容详实、格式规范的会议纪要就快速诞生了。我只需要进行最后的微调和确认即可节省了至少80%的格式调整和文字组织时间。5. 常见问题与排查技巧实录在实际部署和使用过程中你几乎一定会遇到一些问题。下面是我在搭建和测试过程中遇到的一些典型问题及解决方法希望能帮你快速排雷。5.1 连接与权限类问题问题1Claude Desktop中看不到飞书文档工具图标。排查步骤检查配置文件首先确认claude_desktop_config.json文件路径和内容完全正确特别是args中的项目入口文件路径必须是绝对路径。一个常见的错误是使用了相对路径。检查服务器进程在终端中运行ps aux | grep nodeLinux/macOS或查看任务管理器Windows确认你的MCP Server进程node index.mjs正在运行并且没有报错退出。重启Claude Desktop修改配置文件后必须完全退出并重启Claude Desktop它只在启动时读取配置。查看日志在启动MCP Server的终端里查看是否有错误输出。同时Claude Desktop通常也有日志文件可以在其设置或帮助菜单中找到日志位置查看其中是否有关于加载MCP Server的错误信息。问题2工具调用失败提示“无权限”或“Authentication failed”。排查步骤核对App ID和Secret这是最可能的原因。请仔细检查.env文件或环境变量中的FEISHU_APP_ID和FEISHU_APP_SECRET是否与飞书开放平台上的完全一致注意不要有多余的空格或换行。确认权限已发布登录飞书开放平台进入你的应用查看“权限管理”页面确保你需要的所有权限特别是drive:file:read/write的状态是“已获得”或“已申请”。如果只是“已添加”请点击“申请发布”创建一个新版本并提交。检查Token获取逻辑在MCP Server的代码中打印出获取到的tenant_access_token的前几位或者直接调用一个简单的飞书API如获取用户信息测试Token是否有效。Token失效时间通常是2小时检查服务器是否有自动刷新机制。文档访问范围确认你操作的文档其所有者或所属知识空间是否在你的飞书应用授权的范围内。如果是个人文档确保应用创建者是你自己如果是团队文档可能需要将应用添加为协作者。5.2 文档操作类问题问题3内容成功写入但位置不对没有在我指定的标题后面。根本原因对飞书文档的Block树状结构理解不准确或者传入的insertAfterBlockId有误。解决方案获取文档结构编写一个简单的测试脚本或者利用飞书API调试工具调用GET /open-apis/docx/v1/documents/{document_id}/blocks这个接口获取整个文档的Block列表。仔细研究返回的JSON找到你目标标题的block_id并观察它的parent_id和children关系。理解“之后”的含义在Block树中“在某个块之后插入”通常意味着作为该块的兄弟节点且位于其后。你需要确认你找到的block_id确实是那个标题块本身而不是其父节点或其他节点。使用“获取块子元素”接口定位有时直接使用标题块的block_id作为insertAfterBlockId可能不会达到预期效果。你可以尝试先调用GET /open-apis/docx/v1/documents/{document_id}/blocks/{heading_block_id}/children获取该标题下的子块然后将新内容插入到这些子块的末尾index: -1这通常能确保内容属于该标题章节。问题4写入的内容丢失了换行符或格式变成了一大段。原因分析飞书文档的Block内容是以特定结构存储的。如果你直接将包含\n的纯文本传入而构建Block时没有正确处理换行符会被忽略。解决方案在构建paragraph类型的Block内容时飞书API支持通过多个text_run元素来模拟复杂格式但简单的换行需要特殊处理。一个段落内的换行需要用单独的text_run元素或者使用\n并确保格式正确。更可靠的做法是如果你需要多段内容就直接创建多个连续的paragraph类型的Block。在写入工具的逻辑里可以对输入文本按\n\n空行进行分割为每一段生成一个独立的段落Block然后一次性通过API的children数组提交。这样既能保留段落结构又符合飞书的原生格式。5.3 性能与稳定性优化建议1. 实现请求队列与限流飞书API对调用频率有限制。如果你的工具被频繁调用可能会触发限流导致失败。在Server端实现一个简单的请求队列和速率限制器是很有必要的。例如使用p-queue这样的库将所有飞书API调用放入队列并设置并发数为1或2间隔100-200毫秒可以平滑请求避免触发429错误。2. 添加操作确认与撤销机制进阶对于“覆盖写入”或“删除”这类高风险操作一个友好的设计是在工具调用前让AI模型Claude向用户确认。例如当用户说“把第二段删了”Claude可以先回复“我即将删除文档‘XXX’中的第二段内容预览是‘……’。请确认是否继续” 用户确认后再执行操作。更进一步可以实现一个简单的操作日志记录每次写入的旧内容和新内容为潜在的“撤销”功能提供可能。3. 内容安全过滤AI生成的内容是不可控的。在将内容写入公司重要文档前最好能加入一层基础的内容安全过滤。可以集成一个轻量级的敏感词库检查或者调用内容安全API如果公司有对AI生成的结果进行快速扫描过滤掉明显不合规的内容。这虽然不能100%保证安全但能规避大部分低级风险。4. 日志记录与监控为MCP Server添加详细的日志记录包括接收到的请求、调用的飞书API、API响应状态、以及发生的任何错误。这不仅是排查问题的利器也能用于监控工具的使用情况。可以将日志输出到文件或者发送到像ELK这样的日志平台。当发现大量“权限错误”或“内容过长”错误时就能及时知道配置或使用方式上出现了普遍性问题。