给 OpenClaw 飞书插件补齐“创建云文档文件夹”能力
OpenClaw 的飞书插件openclaw-lark已经支持不少云空间操作,比如列出文件、上传文件、复制、移动、删除等。
但我最近遇到一个小缺口:它不能直接创建飞书云空间文件夹。
也就是说,如果你想让 AI 自动完成:
创建项目文件夹
-> 在文件夹里创建文档
-> 上传附件
-> 整理归档
第一步会卡住。
原因不是飞书开放平台不支持,而是当前插件没有把“新建文件夹”这个 API 封装成 OpenClaw tool。
先定位工具文件
飞书云空间文件工具在这里:
~/.openclaw/extensions/openclaw-lark/src/tools/oapi/drive/file.js
这个工具名是:
feishu_drive_file
原本支持的 action 大概是:
list
get_meta
copy
move
delete
upload
download
我们要新增:
create_folder
它不是 MCP Server,而是openclaw-lark插件注册给 OpenClaw Gateway 的 tool。
调用链路是:
飞书消息
-> OpenClaw Gateway
-> Agent
-> feishu_drive_file tool
-> @larksuiteoapi/node-sdk
-> 飞书 Drive API
飞书 SDK 已经支持
在@larksuiteoapi/node-sdk里已经有方法:
sdk.drive.file.createFolder(…)
对应飞书接口:
POST /open-apis/drive/v1/files/create_folder
参数结构:
{data: {name:‘文件夹名称’,folder_token:‘父文件夹 token’}}
返回值里会有:
{token:‘新文件夹 token’,url:‘新文件夹链接’}
第一步:更新文件头说明
找到file.js开头的注释,把 action 列表补上create_folder。
修改前:
/*** feishu_drive_file tool — Manage Feishu Drive files.** Actions: list, get_meta, copy, move, delete, upload, download** Uses the Feishu Drive API:* – list: GET /open-apis/drive/v1/files* – get_meta: POST /open-apis/drive/v1/metas/batch_query* – copy: POST /open-apis/drive/v1/files/:file_token/copy* – move: POST /open-apis/drive/v1/files/:file_token/move* – delete: DELETE /open-apis/drive/v1/files/:file_token* – upload: POST /open-apis/drive/v1/files/upload_all* – download: GET /open-apis/drive/v1/files/:file_token/download*/
修改后:
/*** feishu_drive_file tool — Manage Feishu Drive files.** Actions: list, get_meta, create_folder, copy, move, delete, upload, download** Uses the Feishu Drive API:* – list: GET /open-apis/drive/v1/files* – get_meta: POST /open-apis/drive/v1/metas/batch_query* – create_folder: POST /open-apis/drive/v1/files/create_folder* – copy: POST /open-apis/drive/v1/files/:file_token/copy* – move: POST /open-apis/drive/v1/files/:file_token/move* – delete: DELETE /open-apis/drive/v1/files/:file_token* – upload: POST /open-apis/drive/v1/files/upload_all* – download: GET /open-apis/drive/v1/files/:file_token/download*/
第二步:给 Schema 增加 create_folder
找到:
constFeishuDriveFileSchema= typebox_1.Type.Union([
在get_meta这个Type.Object后面,COPY FILE前面,插入:
// CREATE FOLDERtypebox_1.Type.Object({action: typebox_1.Type.Literal(‘create_folder’),name: typebox_1.Type.String({description:‘New folder name.’, }),folder_token: typebox_1.Type.String({description:‘Parent folder token. Use list to find an existing folder token first.’, }),}),
放进去后大概是这样:
constFeishuDriveFileSchema= typebox_1.Type.Union([// LIST FILEStypebox_1.Type.Object({action: typebox_1.Type.Literal(‘list’),folder_token: typebox_1.Type.Optional(typebox_1.Type.String({description:‘文件夹 token,可选。’, })),page_size: typebox_1.Type.Optional(typebox_1.Type.Integer({minimum:1,maximum:200, })),page_token: typebox_1.Type.Optional(typebox_1.Type.String()), }),// GET METAtypebox_1.Type.Object({action: typebox_1.Type.Literal(‘get_meta’),request_docs: typebox_1.Type.Array(typebox_1.Type.Object({doc_token: typebox_1.Type.String(),doc_type: typebox_1.Type.Union([ typebox_1.Type.Literal(‘doc’), typebox_1.Type.Literal(‘sheet’), typebox_1.Type.Literal(‘file’), typebox_1.Type.Literal(‘bitable’), typebox_1.Type.Literal(‘docx’), typebox_1.Type.Literal(‘folder’), typebox_1.Type.Literal(‘mindnote’), typebox_1.Type.Literal(‘slides’), ]), }), {minItems:1,maxItems:50, }), }),// CREATE FOLDERtypebox_1.Type.Object({action: typebox_1.Type.Literal(‘create_folder’),name: typebox_1.Type.String({description:‘New folder name.’, }),folder_token: typebox_1.Type.String({description:‘Parent folder token. Use list to find an existing folder token first.’, }), }),// COPY FILEtypebox_1.Type.Object({action: typebox_1.Type.Literal(‘copy’),file_token: typebox_1.Type.String(),name: typebox_1.Type.String(),type: typebox_1.Type.Union([ typebox_1.Type.Literal(‘doc’), typebox_1.Type.Literal(‘sheet’), typebox_1.Type.Literal(‘file’), typebox_1.Type.Literal(‘bitable’), typebox_1.Type.Literal(‘docx’), typebox_1.Type.Literal(‘folder’), typebox_1.Type.Literal(‘mindnote’), typebox_1.Type.Literal(‘slides’), ]),folder_token: typebox_1.Type.Optional(typebox_1.Type.String()),parent_node: typebox_1.Type.Optional(typebox_1.Type.String()), }),]);
这里的关键点是:
action: typebox_1.Type.Literal(‘create_folder’)
这会让 Agent 能够识别一个新的工具动作。
第三步:增加执行逻辑
找到执行函数里的:
switch(p.action) {
在get_meta的case后面,copy前面,插入:
// —————————————————————–// CREATE FOLDER// —————————————————————–case’create_folder’: { log.info(`create_folder: name=${p.name}, folder_token=${p.folder_token}`);constres =awaitclient.invoke(‘feishu_drive_file.create_folder’,(sdk, opts) => sdk.drive.file.createFolder({data: {name: p.name,folder_token: p.folder_token, }, }, opts), {as:‘user’} ); (0, helpers_1.assertLarkOk)(res);constdata = res.data; log.info(`create_folder: token=${data?.token ??’unknown’}`);return(0, helpers_1.json)({folder: data,folder_token: data?.token,url: data?.url, });}
上下文大概是这样:
switch(p.action) {case’get_meta’: {if(!p.request_docs || !Array.isArray(p.request_docs) || p.request_docs.length ===0) {return(0, helpers_1.json)({error:“request_docs must be a non-empty array.”, }); }constres =awaitclient.invoke(‘feishu_drive_file.get_meta’,(sdk, opts) => sdk.drive.meta.batchQuery({data: {request_docs: p.request_docs, }, }, opts), {as:‘user’} ); (0, helpers_1.assertLarkOk)(res);return(0, helpers_1.json)({metas: res.data?.metas ?? [], }); }// —————————————————————–// CREATE FOLDER// —————————————————————–case’create_folder’: { log.info(`create_folder: name=${p.name}, folder_token=${p.folder_token}`);constres =awaitclient.invoke(‘feishu_drive_file.create_folder’,(sdk, opts) => sdk.drive.file.createFolder({data: {name: p.name,folder_token: p.folder_token, }, }, opts), {as:‘user’} ); (0, helpers_1.assertLarkOk)(res);constdata = res.data; log.info(`create_folder: token=${data?.token ??’unknown’}`);return(0, helpers_1.json)({folder: data,folder_token: data?.token,url: data?.url, }); }case’copy’: {// 原有 copy 逻辑}}
第四步:语法检查
改完后先检查 JS 语法:
node –check C:\Users\Lenovo\.openclaw\extensions\openclaw-lark\src\tools\oapi\drive\file.js
没有输出通常就表示语法通过。
第五步:重启 OpenClaw
插件代码改完后,需要重启 OpenClaw Gateway。
先找到进程:
Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq ‘node.exe’ -and $_.CommandLine -match ‘openclaw’ } |
Select-Object ProcessId,CommandLine
停止当前 Gateway:
Stop-Process -Id -Force
再启动:
Start-Process -FilePath “C:\Program Files\nodejs\node.exe” `
-ArgumentList “C:\Users\Lenovo\AppData\Local\pnpm\global\5\.pnpm\openclaw@2026.4.20_@napi-rs+canvas@0.1.97\node_modules\openclaw\dist\index.js”,”gateway”,”–port”,”18789″ `
-WindowStyle Hidden
确认端口起来:
netstat -ano | Select-String ‘:18789’
如果看到类似:
TCP 127.0.0.1:18789 0.0.0.0:0 LISTENING
说明网关已经恢复。
第六步:调用示例
现在 Agent 可以这样调用:
{“action”:“create_folder”,“name”:“项目资料”,“folder_token”:“fldcnxxxxxxxxxxxxxxxx”}
返回示例:
{“folder”:{“token”:“fldcn_new_xxxxxxxx”,“url”:“https://xxx.feishu.cn/drive/folder/xxxx”},“folder_token”:“fldcn_new_xxxxxxxx”,“url”:“https://xxx.feishu.cn/drive/folder/xxxx”}
之后就可以把这个folder_token传给创建文档、上传文件等工具。
注意点
飞书这个接口要求传父级folder_token。所以更稳的流程是:
先 list 云空间文件夹
-> 拿到目标父文件夹 folder_token
-> create_folder 创建子文件夹
-> 用返回的新 folder_token 继续创建文档或上传文件
另外,飞书官方接口说明里提到该接口不支持并发创建,并且有调用频率限制。所以批量创建文件夹时建议串行执行,不要并发猛打。
总结
这次补的不是 MCP Server,而是 OpenClaw 插件 tool。
补完后能力链路变成:
OpenClaw Agent
-> feishu_drive_file tool
-> create_folder action
-> sdk.drive.file.createFolder
-> 飞书云空间创建文件夹
这样 OpenClaw 就可以真正完成:
创建文件夹
-> 创建文档
-> 上传资料
-> 移动归档
这一整套飞书云空间自动化流程。
夜雨聆风