乐于分享
好东西不私藏

给 OpenClaw 飞书插件补齐“创建云文档文件夹”能力

给 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_metacase后面,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 就可以真正完成:

创建文件夹

-> 创建文档

-> 上传资料

-> 移动归档

这一整套飞书云空间自动化流程。