├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_RECALL.md ├── README_RECALL_ZH.md ├── README_ZH.md ├── docs ├── tools-en.md └── tools-zh.md ├── jest.config.js ├── package.json ├── src ├── cli.ts ├── index.ts ├── mcp-server │ ├── index.ts │ ├── shared │ │ ├── index.ts │ │ ├── init.ts │ │ └── types.ts │ ├── sse.ts │ └── stdio.ts ├── mcp-tool │ ├── constants.ts │ ├── document-tool │ │ └── recall │ │ │ ├── index.ts │ │ │ ├── request.ts │ │ │ └── type.ts │ ├── index.ts │ ├── mcp-tool.ts │ ├── tools │ │ ├── en │ │ │ ├── builtin-tools │ │ │ │ ├── docx │ │ │ │ │ └── builtin.ts │ │ │ │ ├── im │ │ │ │ │ └── buildin.ts │ │ │ │ └── index.ts │ │ │ └── gen-tools │ │ │ │ ├── index.ts │ │ │ │ └── zod │ │ │ │ ├── acs_v1.ts │ │ │ │ ├── admin_v1.ts │ │ │ │ ├── aily_v1.ts │ │ │ │ ├── apaas_v1.ts │ │ │ │ ├── application_v5.ts │ │ │ │ ├── application_v6.ts │ │ │ │ ├── approval_v4.ts │ │ │ │ ├── attendance_v1.ts │ │ │ │ ├── auth_v3.ts │ │ │ │ ├── authen_v1.ts │ │ │ │ ├── baike_v1.ts │ │ │ │ ├── base_v2.ts │ │ │ │ ├── bitable_v1.ts │ │ │ │ ├── board_v1.ts │ │ │ │ ├── calendar_v4.ts │ │ │ │ ├── cardkit_v1.ts │ │ │ │ ├── compensation_v1.ts │ │ │ │ ├── contact_v3.ts │ │ │ │ ├── corehr_v1.ts │ │ │ │ ├── corehr_v2.ts │ │ │ │ ├── docs_v1.ts │ │ │ │ ├── docx_v1.ts │ │ │ │ ├── drive_v1.ts │ │ │ │ ├── drive_v2.ts │ │ │ │ ├── ehr_v1.ts │ │ │ │ ├── event_v1.ts │ │ │ │ ├── helpdesk_v1.ts │ │ │ │ ├── hire_v1.ts │ │ │ │ ├── hire_v2.ts │ │ │ │ ├── human_authentication_v1.ts │ │ │ │ ├── im_v1.ts │ │ │ │ ├── im_v2.ts │ │ │ │ ├── lingo_v1.ts │ │ │ │ ├── mail_v1.ts │ │ │ │ ├── mdm_v1.ts │ │ │ │ ├── minutes_v1.ts │ │ │ │ ├── moments_v1.ts │ │ │ │ ├── okr_v1.ts │ │ │ │ ├── optical_char_recognition_v1.ts │ │ │ │ ├── passport_v1.ts │ │ │ │ ├── payroll_v1.ts │ │ │ │ ├── performance_v1.ts │ │ │ │ ├── performance_v2.ts │ │ │ │ ├── personal_settings_v1.ts │ │ │ │ ├── report_v1.ts │ │ │ │ ├── search_v2.ts │ │ │ │ ├── security_and_compliance_v1.ts │ │ │ │ ├── sheets_v3.ts │ │ │ │ ├── speech_to_text_v1.ts │ │ │ │ ├── task_v1.ts │ │ │ │ ├── task_v2.ts │ │ │ │ ├── tenant_v2.ts │ │ │ │ ├── translation_v1.ts │ │ │ │ ├── vc_v1.ts │ │ │ │ ├── verification_v1.ts │ │ │ │ ├── wiki_v1.ts │ │ │ │ ├── wiki_v2.ts │ │ │ │ └── workplace_v1.ts │ │ ├── index.ts │ │ └── zh │ │ │ ├── builtin-tools │ │ │ ├── docx │ │ │ │ └── builtin.ts │ │ │ ├── im │ │ │ │ └── buildin.ts │ │ │ └── index.ts │ │ │ └── gen-tools │ │ │ ├── index.ts │ │ │ └── zod │ │ │ ├── acs_v1.ts │ │ │ ├── admin_v1.ts │ │ │ ├── aily_v1.ts │ │ │ ├── apaas_v1.ts │ │ │ ├── application_v5.ts │ │ │ ├── application_v6.ts │ │ │ ├── approval_v4.ts │ │ │ ├── attendance_v1.ts │ │ │ ├── auth_v3.ts │ │ │ ├── authen_v1.ts │ │ │ ├── baike_v1.ts │ │ │ ├── base_v2.ts │ │ │ ├── bitable_v1.ts │ │ │ ├── board_v1.ts │ │ │ ├── calendar_v4.ts │ │ │ ├── cardkit_v1.ts │ │ │ ├── compensation_v1.ts │ │ │ ├── contact_v3.ts │ │ │ ├── corehr_v1.ts │ │ │ ├── corehr_v2.ts │ │ │ ├── docs_v1.ts │ │ │ ├── docx_v1.ts │ │ │ ├── drive_v1.ts │ │ │ ├── drive_v2.ts │ │ │ ├── ehr_v1.ts │ │ │ ├── event_v1.ts │ │ │ ├── helpdesk_v1.ts │ │ │ ├── hire_v1.ts │ │ │ ├── hire_v2.ts │ │ │ ├── human_authentication_v1.ts │ │ │ ├── im_v1.ts │ │ │ ├── im_v2.ts │ │ │ ├── lingo_v1.ts │ │ │ ├── mail_v1.ts │ │ │ ├── mdm_v1.ts │ │ │ ├── minutes_v1.ts │ │ │ ├── moments_v1.ts │ │ │ ├── okr_v1.ts │ │ │ ├── optical_char_recognition_v1.ts │ │ │ ├── passport_v1.ts │ │ │ ├── payroll_v1.ts │ │ │ ├── performance_v1.ts │ │ │ ├── performance_v2.ts │ │ │ ├── personal_settings_v1.ts │ │ │ ├── report_v1.ts │ │ │ ├── search_v2.ts │ │ │ ├── security_and_compliance_v1.ts │ │ │ ├── sheets_v3.ts │ │ │ ├── speech_to_text_v1.ts │ │ │ ├── task_v1.ts │ │ │ ├── task_v2.ts │ │ │ ├── tenant_v2.ts │ │ │ ├── translation_v1.ts │ │ │ ├── vc_v1.ts │ │ │ ├── verification_v1.ts │ │ │ ├── wiki_v1.ts │ │ │ ├── wiki_v2.ts │ │ │ └── workplace_v1.ts │ ├── types │ │ └── index.ts │ └── utils │ │ ├── case-transf.ts │ │ ├── filter-tools.ts │ │ ├── get-should-use-uat.ts │ │ └── handler.ts └── utils │ ├── constants.ts │ ├── http-instance.ts │ ├── noop.ts │ └── version.ts ├── tests ├── cli.test.ts ├── mcp-server │ ├── shared │ │ └── init.test.ts │ ├── sse.test.ts │ └── stdio.test.ts ├── mcp-tool │ ├── constants.test.ts │ ├── document-tool │ │ └── recall │ │ │ ├── index.test.ts │ │ │ └── request.test.ts │ ├── mcp-tool.test.ts │ └── utils │ │ ├── case-transf.test.ts │ │ ├── filter-tools.test.ts │ │ ├── get-should-use-uat.test.ts │ │ └── handler.test.ts ├── setup.ts └── utils │ ├── http-instance.test.ts │ ├── noop.test.ts │ └── version.test.ts ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't allow people to merge changes to these generated files, because the result 2 | # may be invalid. You need to run "rush update" again. 3 | pnpm-lock.yaml merge=text 4 | shrinkwrap.yaml merge=binary 5 | npm-shrinkwrap.json merge=binary 6 | yarn.lock merge=binary 7 | 8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic 9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor 10 | # may also require a special configuration to allow comments in JSON. 11 | # 12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088 13 | # 14 | *.json linguist-language=JSON-with-Comments 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build 5 | dist/ 6 | build/ 7 | lib/ 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Environment variables 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | # IDE 24 | .idea/ 25 | .vscode/ 26 | *.swp 27 | *.swo 28 | 29 | # OS 30 | .DS_Store 31 | Thumbs.db 32 | 33 | # Test 34 | coverage/ 35 | .nyc_output/ 36 | 37 | api-json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/CHANGELOG.* 2 | # 包管理文件 3 | **/pnpm-lock.yaml 4 | **/yarn.lock 5 | **/package-lock.json 6 | **/shrinkwrap.json 7 | 8 | # 构建产物 9 | **/dist 10 | **/lib 11 | 12 | # 在 Markdown 中,Prettier 将会对代码块进行格式化,这会影响输出 13 | *.md 14 | *.svg 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "endOfLine": "auto", 6 | "tabWidth": 2, 7 | "overrides": [ 8 | { 9 | "files": ".prettierrc", 10 | "options": { 11 | "parser": "json" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.1 2 | Fix: 修复使用 configFile 配置 mode 参数不生效的问题 3 | Fix: 修复由于使用了z.record(z.any())类型的字段导致直接传给豆包模型无法使用的问题 4 | Feat: 新增 preset.light 预设 5 | 6 | Fix: Fix the problem that the mode parameter configured by configFile does not take effect 7 | Fix: Fix the problem that the z.record(z.any()) type field is passed directly to the doubao model and cannot be used 8 | Feat: Add preset.light preset 9 | 10 | # 0.3.0 11 | 12 | New: 开放平台开发文档检索 MCP,旨在帮助用户输入自身诉求后迅速检索到自己需要的开发文档,帮助开发者在AI IDE中编写与飞书集成的代码 13 | New: 新增--token-mode,现在可以在启动的时候指定调用API的token类型,支持auto/tenant_access_token/user_access_token 14 | New: -t 支持配置 preset.default preset.im.default preset.bitable.default preset.doc.default 等默认预设 15 | Bump: 升级 @modelcontextprotocol/sdk 到 1.11.0 16 | 17 | New:Retrieval of Open Platform Development Documents in MCP aims to enable users to quickly find the development documents they need after inputting their own requirements, and assist developers in writing code integrated with Feishu in the AI IDE. 18 | New: Added --token-mode, now you can specify the API token type when starting, supporting auto/tenant_access_token/user_access_token 19 | New: -t supports configuring preset.default preset.im.default preset.bitable.default preset.doc.default etc. 20 | Bump: Upgraded @modelcontextprotocol/sdk to 1.11.0 21 | 22 | # 0.2.0 23 | 24 | 飞书/Lark OpenAPI MCP 工具,可以帮助你快速开始使用MCP协议连接飞书/Lark,实现 Agent 与飞书/Lark平台的高效协作 25 | 26 | Feishu/Lark OpenAPI MCP tool helps you quickly start using the MCP protocol to connect with Feishu/Lark, enabling efficient collaboration between Agent and the Feishu/Lark platform -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Lark Technologies Pte. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice, shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README_RECALL.md: -------------------------------------------------------------------------------- 1 | # Feishu/Lark Open Platform Developer Documentation Retrieval MCP 2 | 3 | [![npm version](https://img.shields.io/npm/v/@larksuiteoapi/lark-mcp.svg)](https://www.npmjs.com/package/@larksuiteoapi/lark-mcp) 4 | [![npm downloads](https://img.shields.io/npm/dm/@larksuiteoapi/lark-mcp.svg)](https://www.npmjs.com/package/@larksuiteoapi/lark-mcp) 5 | [![Node.js Version](https://img.shields.io/node/v/@larksuiteoapi/lark-mcp.svg)](https://nodejs.org/) 6 | 7 | English | [中文](./README_RECALL_ZH.md) 8 | 9 | > **⚠️ Beta Version Notice**: This tool is currently in Beta. Features and APIs may change, so please pay close attention to version updates. 10 | 11 | This is the Feishu/Lark official Open Platform Developer Documentation Retrieval MCP (Model Context Protocol) tool, designed to help users quickly find the documentation they need after entering their requirements. It can also be used with [Feishu/Lark OpenAPI MCP](./README_ZH.md) to enable AI assistants to run automated scenarios. 12 | 13 | > The scope of documentation retrieval covers all developer guides, tutorials, server-side APIs, and client-side APIs under the [Developer Documentation](https://open.feishu.cn/document/home/index) or [Lark Developer Documentation](https://open.larksuite.com/document/home/index), helping users quickly find the corresponding OpenAPI or other developer documentation. It does not search "Lark Docs" documents. 14 | 15 | ## Getting Started 16 | 17 | ### Install Node.js 18 | 19 | Before using the lark-mcp tool, you need to install the Node.js environment. If you have already installed Node.js, you can skip this step. 20 | 1. **Install with Homebrew (Recommended):** 21 | 22 | ```bash 23 | brew install node 24 | ``` 25 | 26 | 2. **Use the official installer:** 27 | - Visit the [Node.js official website](https://nodejs.org/) 28 | - Download and install the LTS version 29 | - After installation, open the terminal to verify: 30 | ```bash 31 | node -v 32 | npm -v 33 | ``` 34 | 35 | #### Install Node.js on Windows 36 | 37 | 1. **Use the official installer:** 38 | 39 | - Visit the [Node.js official website](https://nodejs.org/) 40 | - Download and run the Windows installer (.msi file) 41 | - Follow the installation wizard to complete the installation 42 | - After installation, open the command prompt to verify: 43 | ```bash 44 | node -v 45 | npm -v 46 | ``` 47 | 48 | 2. **Use nvm-windows:** 49 | - Download [nvm-windows](https://github.com/coreybutler/nvm-windows/releases) 50 | - Install nvm-windows 51 | - Use nvm to install Node.js: 52 | ```bash 53 | nvm install latest 54 | nvm use 55 | ``` 56 | 57 | ## Installation 58 | 59 | Install the lark-mcp tool globally: 60 | 61 | ```bash 62 | npm install -g @larksuiteoapi/lark-mcp 63 | ``` 64 | 65 | ## User Guide 66 | 67 | ### Use in Trae/Cursor/Claude 68 | 69 | To integrate Lark features in AI tools such as Trae, Cursor or Claude, you can add the following to your configuration file: 70 | 71 | ```json 72 | { 73 | "mcpServers": { 74 | "lark-mcp": { 75 | "command": "npx", 76 | "args": [ 77 | "-y", 78 | "@larksuiteoapi/lark-mcp", 79 | "recall-developer-documents", 80 | ] 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ### Advanced Configuration 87 | 88 | #### Command Line Arguments 89 | 90 | The `lark-mcp recall-developer-documents` tool provides a variety of command line arguments for flexible MCP service configuration: 91 | 92 | | Parameter | Short | Description | Example | 93 | |------|------|------|------| 94 | | `--mode` | `-m` | Transfer mode, options are stdio or sse, default is stdio | `-m sse` | 95 | | `--host` | | Listening host in SSE mode, default is localhost | `--host 0.0.0.0` | 96 | | `--port` | `-p` | Listening port in SSE mode, default is 3000 | `-p 3000` | 97 | | `--version` | `-V` | Show version number | `-V` | 98 | | `--help` | `-h` | Show help information | `-h` | 99 | 100 | #### Example Usage of Arguments 101 | 102 | 1. **Transfer Modes**: 103 | 104 | recall-developer-documents supports two transfer modes: 105 | 106 | 1. **stdio mode (default/recommended):** Suitable for integration with AI tools such as Cursor or Claude, communicating via standard input and output streams. 107 | ```bash 108 | lark-mcp recall-developer-documents -m stdio 109 | ``` 110 | 111 | 2. **SSE mode:** Provides an HTTP interface based on Server-Sent Events, suitable for web applications or scenarios requiring a network interface. 112 | 113 | ```bash 114 | # By default, only listens on localhost 115 | lark-mcp recall-developer-documents -m sse -p 3000 116 | 117 | # Listen on all network interfaces (allow remote access) 118 | lark-mcp recall-developer-documents -m sse --host 0.0.0.0 -p 3000 119 | ``` 120 | 121 | After starting, the SSE endpoint can be accessed at `http://:/sse`. 122 | 123 | ## Related Links 124 | 125 | - [Feishu Open Platform](https://open.feishu.cn/) 126 | - [Lark International Open Platform](https://open.larksuite.com/) 127 | - [Node.js Official Website](https://nodejs.org/) 128 | - [npm Documentation](https://docs.npmjs.com/) 129 | 130 | ## Feedback 131 | 132 | You are welcome to submit Issues to help improve this tool. If you have any questions or suggestions, please raise them in the GitHub repository. 133 | -------------------------------------------------------------------------------- /README_RECALL_ZH.md: -------------------------------------------------------------------------------- 1 | # 飞书/Lark 开放平台开发文档检索 MCP 2 | 3 | [![npm version](https://img.shields.io/npm/v/@larksuiteoapi/lark-mcp.svg)](https://www.npmjs.com/package/@larksuiteoapi/lark-mcp) 4 | [![npm downloads](https://img.shields.io/npm/dm/@larksuiteoapi/lark-mcp.svg)](https://www.npmjs.com/package/@larksuiteoapi/lark-mcp) 5 | [![Node.js Version](https://img.shields.io/node/v/@larksuiteoapi/lark-mcp.svg)](https://nodejs.org/) 6 | 7 | 中文 | [English](./README_RECALL.md) 8 | 9 | > **⚠️ Beta版本提示**:当前工具处于Beta版本阶段,功能和API可能会有变更,请密切关注版本更新。 10 | 11 | 这是飞书/Lark官方 开放平台开发文档检索 MCP(Model Context Protocol)工具,旨在帮助用户输入自身诉求后迅速检索到自己需要的开发文档,帮助开发者在AI IDE中编写与飞书集成的代码。也可搭配 [飞书/Lark OpenAPI MCP](./README_ZH.md) 来让 AI 助手运行自动化场景 12 | 13 | >**说明**: 开放平台开发文档检索,检索范围是 [开发文档](https://open.feishu.cn/document/home/index) 下所有的开发指南、开发教程、服务端 API、客户端 API,帮助用户迅速检索到对应的 OpenApi 或者其他开发文档,非「飞书云文档」的检索。 14 | 15 | ## 使用准备 16 | 17 | ### 安装Node.js 18 | 19 | 在使用lark-mcp工具之前,您需要先安装Node.js环境。如已安装过 Node.js,可以跳过本步骤 20 | 1. **使用Homebrew安装(推荐)**: 21 | 22 | ```bash 23 | brew install node 24 | ``` 25 | 26 | 2. **使用官方安装包**: 27 | - 访问[Node.js官网](https://nodejs.org/) 28 | - 下载并安装LTS版本 29 | - 安装完成后,打开终端验证: 30 | ```bash 31 | node -v 32 | npm -v 33 | ``` 34 | 35 | #### Windows安装Node.js 36 | 37 | 1. **使用官方安装包**: 38 | 39 | - 访问[Node.js官网](https://nodejs.org/) 40 | - 下载并运行Windows安装程序(.msi文件) 41 | - 按照安装向导操作,完成安装 42 | - 安装完成后,打开命令提示符验证: 43 | ```bash 44 | node -v 45 | npm -v 46 | ``` 47 | 48 | 2. **使用nvm-windows**: 49 | - 下载[nvm-windows](https://github.com/coreybutler/nvm-windows/releases) 50 | - 安装nvm-windows 51 | - 使用nvm安装Node.js: 52 | ```bash 53 | nvm install latest 54 | nvm use <版本号> 55 | ``` 56 | 57 | ## 安装 58 | 59 | 全局安装lark-mcp工具: 60 | 61 | ```bash 62 | npm install -g @larksuiteoapi/lark-mcp 63 | ``` 64 | 65 | ## 使用指南 66 | 67 | ### 在Trae/Cursor/Claude中使用 68 | 69 | 如需在Trae/Cursor或Claude等AI工具中集成飞书/Lark功能,可以在配置文件中添加以下内容: 70 | 71 | ```json 72 | { 73 | "mcpServers": { 74 | "lark-mcp": { 75 | "command": "npx", 76 | "args": [ 77 | "-y", 78 | "@larksuiteoapi/lark-mcp", 79 | "recall-developer-documents", 80 | ] 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ### 高级配置 87 | 88 | #### 命令行参数说明 89 | 90 | `lark-mcp recall-developer-documents`工具提供了多种命令行参数,以便您灵活配置MCP服务: 91 | 92 | | 参数 | 简写 | 描述 | 示例 | 93 | |------|------|------|------| 94 | | `--mode` | `-m` | 传输模式,可选值为stdio或sse,默认为stdio | `-m sse` | 95 | | `--host` | | SSE模式下的监听主机,默认为localhost | `--host 0.0.0.0` | 96 | | `--port` | `-p` | SSE模式下的监听端口,默认为3000 | `-p 3000` | 97 | | `--version` | `-V` | 显示版本号 | `-V` | 98 | | `--help` | `-h` | 显示帮助信息 | `-h` | 99 | 100 | #### 参数使用示例 101 | 102 | 1. **传输模式**: 103 | 104 | recall-developer-documents 支持两种传输模式: 105 | 106 | 1. **stdio模式(默认/推荐)**:适用于与Cursor或Claude等AI工具集成,通过标准输入输出流进行通信。 107 | ```bash 108 | lark-mcp recall-developer-documents -m stdio 109 | ``` 110 | 111 | 2. **SSE模式**:提供基于Server-Sent Events的HTTP接口,适用于Web应用或需要网络接口的场景。 112 | 113 | ```bash 114 | # 默认只监听localhost 115 | lark-mcp recall-developer-documents -m sse -p 3000 116 | 117 | # 监听所有网络接口(允许远程访问) 118 | lark-mcp recall-developer-documents -m sse --host 0.0.0.0 -p 3000 119 | ``` 120 | 121 | 启动后,SSE端点将可在 `http://:/sse` 访问。 122 | 123 | ## 相关链接 124 | 125 | - [飞书开放平台](https://open.feishu.cn/) 126 | - [Lark国际版开放平台](https://open.larksuite.com/) 127 | - [Node.js官网](https://nodejs.org/) 128 | - [npm文档](https://docs.npmjs.com/) 129 | 130 | ## 反馈 131 | 132 | 欢迎提交Issues来帮助改进这个工具。如有问题或建议,请在GitHub仓库中提出。 -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/tests/**/*.test.ts'], 5 | collectCoverage: true, 6 | collectCoverageFrom: [ 7 | 'src/**/*.ts', 8 | '!src/cli.ts', 9 | '!src/mcp-tool/tools/**/*.ts', 10 | '!src/**/*.d.ts', 11 | '!src/**/index.ts', 12 | ], 13 | coverageDirectory: 'coverage', 14 | coverageReporters: ['text', 'lcov'], 15 | moduleNameMapper: { 16 | '^@/(.*)$': '/src/$1', 17 | }, 18 | moduleFileExtensions: ['ts', 'js', 'json'], 19 | transform: { 20 | '^.+\\.ts$': [ 21 | 'ts-jest', 22 | { 23 | tsconfig: 'tsconfig.json', 24 | }, 25 | ], 26 | }, 27 | transformIgnorePatterns: ['/node_modules/(?!@modelcontextprotocol)'], 28 | setupFilesAfterEnv: ['./tests/setup.ts'], 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@larksuiteoapi/lark-mcp", 3 | "version": "0.3.1", 4 | "description": "Feishu/Lark OpenAPI MCP", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "author": "chenli.idevlab", 8 | "license": "MIT", 9 | "bin": { 10 | "lark-mcp": "dist/cli.js" 11 | }, 12 | "files": [ 13 | "dist", 14 | "docs", 15 | "CHANGELOG.md", 16 | "README.md", 17 | "README_ZH.md", 18 | "README_RECALL.md", 19 | "README_RECALL_ZH.md", 20 | "LICENSE" 21 | ], 22 | "scripts": { 23 | "build": "tsc", 24 | "dev": "ts-node src/index.ts", 25 | "format": "prettier --write \"src/**/*.ts\"", 26 | "prepare": "yarn build", 27 | "test": "jest", 28 | "test:watch": "jest --watch", 29 | "test:coverage": "jest --coverage" 30 | }, 31 | "keywords": [ 32 | "feishu", 33 | "lark", 34 | "mcp", 35 | "open-api", 36 | "ai" 37 | ], 38 | "homepage": "https://github.com/larksuite/lark-openapi-mcp", 39 | "engines": { 40 | "node": ">=16.20.0" 41 | }, 42 | "dependencies": { 43 | "@larksuiteoapi/node-sdk": "^1.47.0", 44 | "@modelcontextprotocol/sdk": "^1.11.0", 45 | "axios": "^1.8.4", 46 | "commander": "^13.1.0", 47 | "dotenv": "^16.4.7", 48 | "express": "^5.1.0" 49 | }, 50 | "devDependencies": { 51 | "@types/jest": "^29.5.14", 52 | "@types/node": "^20.4.5", 53 | "@types/express": "^5.0.1", 54 | "jest": "^29.7.0", 55 | "prettier": "^3.5.3", 56 | "ts-jest": "^29.3.2", 57 | "ts-node": "^10.9.1", 58 | "typescript": "^5.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | import dotenv from 'dotenv'; 5 | import { Command } from 'commander'; 6 | import { currentVersion } from './utils/version'; 7 | import { initStdioServer, initSSEServer, initMcpServer } from './mcp-server'; 8 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 9 | import { RecallTool } from './mcp-tool/document-tool/recall'; 10 | import { OAPI_MCP_DEFAULT_ARGS, OAPI_MCP_ENV_ARGS } from './utils/constants'; 11 | 12 | dotenv.config(); 13 | 14 | const program = new Command(); 15 | 16 | program.name('lark-mcp').description('Feishu/Lark MCP Tool').version(currentVersion); 17 | 18 | program 19 | .command('mcp') 20 | .description('Start Feishu/Lark MCP Service') 21 | .option('-a, --app-id ', 'Feishu/Lark App ID') 22 | .option('-s, --app-secret ', 'Feishu/Lark App Secret') 23 | .option('-d, --domain ', 'Feishu/Lark Domain (default: "https://open.feishu.cn")') 24 | .option('-t, --tools ', 'Allowed Tools List, separated by commas') 25 | .option('-c, --tool-name-case ', 'Tool Name Case, snake or camel or kebab or dot (default: "snake")') 26 | .option('-l, --language ', 'Tools Language, zh or en (default: "en")') 27 | .option('-u, --user-access-token ', 'User Access Token (beta)') 28 | .option('--token-mode ', 'Token Mode, auto or user_access_token or tenant_access_token (default: "auto")') 29 | .option('-m, --mode ', 'Transport Mode, stdio or sse (default: "stdio")') 30 | .option('--host ', 'Host to listen (default: "localhost")') 31 | .option('-p, --port ', 'Port to listen in sse mode (default: "3000")') 32 | .option('--config ', 'Config file path (JSON)') 33 | .action(async (options) => { 34 | let fileOptions = {}; 35 | if (options.config) { 36 | try { 37 | const configContent = fs.readFileSync(options.config, 'utf-8'); 38 | fileOptions = JSON.parse(configContent); 39 | } catch (err) { 40 | console.error('Failed to read config file:', err); 41 | process.exit(1); 42 | } 43 | } 44 | const mergedOptions = { ...OAPI_MCP_DEFAULT_ARGS, ...OAPI_MCP_ENV_ARGS, ...fileOptions, ...options }; 45 | const { mcpServer } = initMcpServer(mergedOptions); 46 | if (mergedOptions.mode === 'stdio') { 47 | initStdioServer(mcpServer); 48 | } else if (mergedOptions.mode === 'sse') { 49 | initSSEServer(mcpServer, mergedOptions); 50 | } else { 51 | console.error('Invalid mode:', mergedOptions.mode); 52 | process.exit(1); 53 | } 54 | }); 55 | 56 | program 57 | .command('recall-developer-documents') 58 | .description('Start Feishu/Lark Open Platform Recall MCP Service') 59 | .option('-d, --domain ', 'Feishu Open Platform Domain', 'https://open.feishu.cn') 60 | .option('-m, --mode ', 'Transport Mode, stdio or sse', 'stdio') 61 | .option('--host ', 'Host to listen', 'localhost') 62 | .option('-p, --port ', 'Port to listen in sse mode', '3001') 63 | .action((options) => { 64 | const server = new McpServer({ 65 | id: 'lark-recall-mcp-server', 66 | name: 'Lark Recall MCP Service', 67 | version: currentVersion, 68 | }); 69 | server.tool(RecallTool.name, RecallTool.description, RecallTool.schema, (params) => 70 | RecallTool.handler(params, options), 71 | ); 72 | if (options.mode === 'stdio') { 73 | initStdioServer(server); 74 | } else if (options.mode === 'sse') { 75 | initSSEServer(server, options); 76 | } else { 77 | console.error('Invalid mode:', options.mode); 78 | process.exit(1); 79 | } 80 | }); 81 | 82 | if (process.argv.length === 2) { 83 | program.help(); 84 | } 85 | 86 | program.parse(process.argv); 87 | 88 | export { program }; 89 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cli.js'; 2 | export * from './mcp-tool/mcp-tool.js'; 3 | export * from './mcp-tool/constants.js'; 4 | -------------------------------------------------------------------------------- /src/mcp-server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stdio'; 2 | export * from './sse'; 3 | export * from './shared'; 4 | -------------------------------------------------------------------------------- /src/mcp-server/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './init'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/mcp-server/shared/init.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 2 | import { noop } from '../../utils/noop'; 3 | import { currentVersion } from '../../utils/version'; 4 | import { McpServerOptions } from './types'; 5 | import * as larkmcp from '../../mcp-tool'; 6 | import { oapiHttpInstance } from '../../utils/http-instance'; 7 | 8 | export function initMcpServer(options: McpServerOptions) { 9 | const { appId, appSecret, userAccessToken } = options; 10 | 11 | if (!appId || !appSecret) { 12 | console.error( 13 | 'Error: Missing App Credentials, please provide APP_ID and APP_SECRET or specify them via command line arguments', 14 | ); 15 | process.exit(1); 16 | } 17 | 18 | let allowTools = Array.isArray(options.tools) ? options.tools : options.tools?.split(',') || []; 19 | 20 | for (const [presetName, presetTools] of Object.entries(larkmcp.presetTools)) { 21 | if (allowTools.includes(presetName)) { 22 | allowTools = [...presetTools, ...allowTools]; 23 | } 24 | } 25 | 26 | // Unique 27 | allowTools = Array.from(new Set(allowTools)); 28 | 29 | // Create MCP Server 30 | const mcpServer = new McpServer({ id: 'lark-mcp-server', name: 'Feishu/Lark MCP Server', version: currentVersion }); 31 | 32 | const larkClient = new larkmcp.LarkMcpTool({ 33 | appId, 34 | appSecret, 35 | logger: { warn: noop, error: noop, debug: noop, info: noop, trace: noop }, 36 | httpInstance: oapiHttpInstance, 37 | domain: options.domain, 38 | toolsOptions: allowTools.length 39 | ? { allowTools: allowTools as larkmcp.ToolName[], language: options.language } 40 | : { language: options.language }, 41 | tokenMode: options.tokenMode, 42 | }); 43 | 44 | if (userAccessToken) { 45 | larkClient.updateUserAccessToken(userAccessToken); 46 | } 47 | 48 | larkClient.registerMcpServer(mcpServer, { toolNameCase: options.toolNameCase }); 49 | 50 | return { mcpServer, larkClient }; 51 | } 52 | -------------------------------------------------------------------------------- /src/mcp-server/shared/types.ts: -------------------------------------------------------------------------------- 1 | import * as larkmcp from '../../mcp-tool'; 2 | export interface McpServerOptions { 3 | appId?: string; 4 | appSecret?: string; 5 | domain?: string; 6 | tools?: string | string[]; 7 | userAccessToken?: string; 8 | language?: 'zh' | 'en'; 9 | toolNameCase?: larkmcp.ToolNameCase; 10 | tokenMode?: larkmcp.TokenMode; 11 | host: string; 12 | port: number; 13 | } 14 | -------------------------------------------------------------------------------- /src/mcp-server/sse.ts: -------------------------------------------------------------------------------- 1 | import { initMcpServer, McpServerOptions } from './shared'; 2 | import express, { Request, Response } from 'express'; 3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; 4 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; 5 | 6 | export function initSSEServer(server: McpServer, options: McpServerOptions) { 7 | const app = express(); 8 | const transports: { [sessionId: string]: SSEServerTransport } = {}; 9 | 10 | app.get('/sse', async (_: Request, res: Response) => { 11 | const transport = new SSEServerTransport('/messages', res); 12 | transports[transport.sessionId] = transport; 13 | res.on('close', () => { 14 | delete transports[transport.sessionId]; 15 | }); 16 | await server.connect(transport); 17 | }); 18 | 19 | app.post('/messages', async (req: Request, res: Response) => { 20 | const sessionId = req.query.sessionId as string; 21 | const transport = transports[sessionId]; 22 | if (!transport) { 23 | res.status(400).send('No transport found for sessionId'); 24 | return; 25 | } 26 | await transport.handlePostMessage(req, res); 27 | }); 28 | 29 | app.listen(options.port, options.host, (error) => { 30 | if (error) { 31 | console.error('Server error:', error); 32 | process.exit(1); 33 | } 34 | console.log(`Server is running on port ${options.port}`); 35 | console.log(`SSE endpoint: http://${options.host}:${options.port}/sse`); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/mcp-server/stdio.ts: -------------------------------------------------------------------------------- 1 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 3 | 4 | export function initStdioServer(server: McpServer) { 5 | const transport = new StdioServerTransport(); 6 | server.connect(transport).catch((error) => { 7 | console.error('MCP Connect Error:', error); 8 | process.exit(1); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/mcp-tool/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Commonly used tools in MCP 3 | */ 4 | 5 | import { ToolName } from './tools'; 6 | 7 | export enum PresetName { 8 | /** 9 | * Default preset including IM, Bitable, Doc and Contact tools 10 | */ 11 | LIGHT = 'preset.light', 12 | /** 13 | * Default preset including IM, Bitable, Doc and Contact tools 14 | */ 15 | DEFAULT = 'preset.default', 16 | /** 17 | * IM related tools for chat and message operations 18 | */ 19 | IM_DEFAULT = 'preset.im.default', 20 | /** 21 | * Base preset for base operations 22 | */ 23 | BASE_DEFAULT = 'preset.base.default', 24 | /** 25 | * Base tools with batch operations 26 | */ 27 | BASE_BATCH = 'preset.base.batch', 28 | /** 29 | * Document related tools for content and permission operations 30 | */ 31 | DOC_DEFAULT = 'preset.doc.default', 32 | /** 33 | * Task management related tools 34 | */ 35 | TASK_DEFAULT = 'preset.task.default', 36 | /** 37 | * Calendar event management tools 38 | */ 39 | CALENDAR_DEFAULT = 'preset.calendar.default', 40 | } 41 | 42 | export const presetLightToolNames: ToolName[] = [ 43 | 'im.v1.message.list', 44 | 'im.v1.message.create', 45 | 'im.v1.chat.search', 46 | 'contact.v3.user.batchGetId', 47 | 'docx.v1.document.rawContent', 48 | 'docx.builtin.import', 49 | 'docx.builtin.search', 50 | 'wiki.v2.space.getNode', 51 | 'bitable.v1.appTableRecord.search', 52 | 'bitable.v1.appTableRecord.batchCreate', 53 | ]; 54 | 55 | export const presetContactToolNames: ToolName[] = ['contact.v3.user.batchGetId']; 56 | 57 | export const presetImToolNames: ToolName[] = [ 58 | 'im.v1.chat.create', 59 | 'im.v1.chat.list', 60 | 'im.v1.chatMembers.get', 61 | 'im.v1.message.create', 62 | 'im.v1.message.list', 63 | ]; 64 | 65 | export const presetBaseCommonToolNames: ToolName[] = [ 66 | 'bitable.v1.app.create', 67 | 'bitable.v1.appTable.create', 68 | 'bitable.v1.appTable.list', 69 | 'bitable.v1.appTableField.list', 70 | 'bitable.v1.appTableRecord.search', 71 | ]; 72 | 73 | export const presetBaseToolNames: ToolName[] = [ 74 | ...presetBaseCommonToolNames, 75 | 'bitable.v1.appTableRecord.create', 76 | 'bitable.v1.appTableRecord.update', 77 | ]; 78 | 79 | export const presetBaseRecordBatchToolNames: ToolName[] = [ 80 | ...presetBaseCommonToolNames, 81 | 'bitable.v1.appTableRecord.batchCreate', 82 | 'bitable.v1.appTableRecord.batchUpdate', 83 | ]; 84 | 85 | export const presetDocToolNames: ToolName[] = [ 86 | 'docx.v1.document.rawContent', 87 | 'docx.builtin.import', 88 | 'docx.builtin.search', 89 | 'drive.v1.permissionMember.create', 90 | 'wiki.v2.space.getNode', 91 | 'wiki.v1.node.search', 92 | ]; 93 | 94 | export const presetTaskToolNames: ToolName[] = [ 95 | 'task.v2.task.create', 96 | 'task.v2.task.patch', 97 | 'task.v2.task.addMembers', 98 | 'task.v2.task.addReminders', 99 | ]; 100 | 101 | export const presetCalendarToolNames: ToolName[] = [ 102 | 'calendar.v4.calendarEvent.create', 103 | 'calendar.v4.calendarEvent.patch', 104 | 'calendar.v4.calendarEvent.get', 105 | 'calendar.v4.freebusy.list', 106 | 'calendar.v4.calendar.primary', 107 | ]; 108 | 109 | export const defaultToolNames: ToolName[] = [ 110 | ...presetImToolNames, 111 | ...presetBaseToolNames, 112 | ...presetDocToolNames, 113 | ...presetContactToolNames, 114 | ]; 115 | 116 | export const presetTools: Record = { 117 | [PresetName.LIGHT]: presetLightToolNames, 118 | [PresetName.DEFAULT]: defaultToolNames, 119 | [PresetName.IM_DEFAULT]: presetImToolNames, 120 | [PresetName.BASE_DEFAULT]: presetBaseToolNames, 121 | [PresetName.BASE_BATCH]: presetBaseRecordBatchToolNames, 122 | [PresetName.DOC_DEFAULT]: presetDocToolNames, 123 | [PresetName.TASK_DEFAULT]: presetTaskToolNames, 124 | [PresetName.CALENDAR_DEFAULT]: presetCalendarToolNames, 125 | }; 126 | -------------------------------------------------------------------------------- /src/mcp-tool/document-tool/recall/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { DocumentRecallTool } from "./type"; 3 | import { recallDeveloperDocument } from "./request"; 4 | 5 | const searchParamsSchema = { 6 | query: z.string().describe("user input"), 7 | }; 8 | 9 | export const RecallTool: DocumentRecallTool = { 10 | name: "openplatform_developer_document_recall", 11 | description: "Recall for relevant documents in all of the Feishu/Lark Open Platform Developer Documents based on user input.", 12 | schema: searchParamsSchema, 13 | handler: async (params, options) => { 14 | const { query } = params; 15 | try { 16 | const results = await recallDeveloperDocument(query, options); 17 | // Return results 18 | return { 19 | content: [ 20 | { 21 | type: "text", 22 | text: results.length ? `Find ${results.length} results:\n${results.join("\n\n")}` : "No results found", 23 | } 24 | ], 25 | }; 26 | } catch (error: Error | unknown) { 27 | return { 28 | isError: true, 29 | content: [ 30 | { 31 | type: "text", 32 | text: `Search failed:${error instanceof Error ? error.message : 'Unknown error'}`, 33 | }, 34 | ], 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/mcp-tool/document-tool/recall/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { DocumentRecallToolOptions } from './type'; 3 | import { USER_AGENT } from '../../../utils/constants'; 4 | 5 | export const recallDeveloperDocument = async (query: string, options: DocumentRecallToolOptions) => { 6 | try { 7 | const { domain, count = 3 } = options; 8 | // Get Feishu search API endpoint 9 | const searchEndpoint = `${domain}/document_portal/v1/recall`; 10 | const payload = { 11 | question: query, 12 | }; 13 | // Send network request to Feishu search API 14 | const response = await axios.post(searchEndpoint, payload, { 15 | timeout: 10000, 16 | headers: { 'User-Agent': USER_AGENT }, 17 | }); 18 | 19 | // Process search results 20 | let results = response.data.chunks || []; 21 | return results.slice(0, count); 22 | } catch (error) { 23 | throw error; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/mcp-tool/document-tool/recall/type.ts: -------------------------------------------------------------------------------- 1 | import type { CallToolResult } from "@modelcontextprotocol/sdk/types"; 2 | import type { z } from "zod"; 3 | 4 | export interface DocumentRecallToolOptions { 5 | domain?: string; 6 | smallToBig?: boolean; 7 | count?: number; 8 | multiQuery?: boolean; 9 | timeout?: number; 10 | } 11 | 12 | export interface DocumentRecallTool { 13 | name: string; 14 | description: string; 15 | schema: { query: z.ZodType }; 16 | handler: (params: { query: string }, options: DocumentRecallToolOptions) => Promise; 17 | } -------------------------------------------------------------------------------- /src/mcp-tool/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mcp-tool'; 2 | export * from './tools'; 3 | export * from './types'; 4 | export * from './constants'; -------------------------------------------------------------------------------- /src/mcp-tool/mcp-tool.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@larksuiteoapi/node-sdk'; 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 3 | import { LarkMcpToolOptions, McpTool, ToolNameCase, TokenMode } from './types'; 4 | import { AllTools, AllToolsZh } from './tools'; 5 | import { filterTools } from './utils/filter-tools'; 6 | import { defaultToolNames } from './constants'; 7 | import { larkOapiHandler } from './utils/handler'; 8 | import { caseTransf } from './utils/case-transf'; 9 | import { getShouldUseUAT } from './utils/get-should-use-uat'; 10 | 11 | /** 12 | * Feishu/Lark MCP 13 | */ 14 | export class LarkMcpTool { 15 | // Lark Client 16 | private client: Client | null = null; 17 | 18 | // User Access Token 19 | private userAccessToken: string | undefined; 20 | 21 | // Token Mode 22 | private tokenMode: TokenMode = TokenMode.AUTO; 23 | 24 | // All Tools 25 | private allTools: McpTool[] = []; 26 | 27 | /** 28 | * Feishu/Lark MCP 29 | * @param options Feishu/Lark Client Options 30 | */ 31 | constructor(options: LarkMcpToolOptions) { 32 | if (options.client) { 33 | this.client = options.client; 34 | } else if (options.appId && options.appSecret) { 35 | this.client = new Client({ 36 | appId: options.appId, 37 | appSecret: options.appSecret, 38 | ...options, 39 | }); 40 | } 41 | this.tokenMode = options.tokenMode || TokenMode.AUTO; 42 | const isZH = options.toolsOptions?.language === 'zh'; 43 | 44 | const filterOptions = { 45 | allowTools: defaultToolNames, 46 | tokenMode: this.tokenMode, 47 | ...options.toolsOptions, 48 | }; 49 | this.allTools = filterTools(isZH ? AllToolsZh : AllTools, filterOptions); 50 | } 51 | 52 | /** 53 | * Update User Access Token 54 | * @param userAccessToken User Access Token 55 | */ 56 | updateUserAccessToken(userAccessToken: string) { 57 | this.userAccessToken = userAccessToken; 58 | } 59 | 60 | /** 61 | * Get MCP Tools 62 | * @returns MCP Tool Definition Array 63 | */ 64 | getTools(): McpTool[] { 65 | return this.allTools; 66 | } 67 | 68 | /** 69 | * Register Tools to MCP Server 70 | * @param server MCP Server Instance 71 | */ 72 | registerMcpServer(server: McpServer, options?: { toolNameCase?: ToolNameCase }): void { 73 | for (const tool of this.allTools) { 74 | server.tool(caseTransf(tool.name, options?.toolNameCase), tool.description, tool.schema, (params: any) => { 75 | try { 76 | if (!this.client) { 77 | return { 78 | isError: true, 79 | content: [{ type: 'text' as const, text: 'Client not initialized' }], 80 | }; 81 | } 82 | const handler = tool.customHandler || larkOapiHandler; 83 | if (this.tokenMode == TokenMode.USER_ACCESS_TOKEN && !this.userAccessToken) { 84 | return { 85 | isError: true, 86 | content: [{ type: 'text' as const, text: 'Invalid UserAccessToken' }], 87 | }; 88 | } 89 | const shouldUseUAT = getShouldUseUAT(this.tokenMode, this.userAccessToken, params?.useUAT); 90 | return handler( 91 | this.client, 92 | { ...params, useUAT: shouldUseUAT }, 93 | { userAccessToken: this.userAccessToken, tool }, 94 | ); 95 | } catch (error) { 96 | return { 97 | isError: true, 98 | content: [{ type: 'text' as const, text: `Error: ${JSON.stringify((error as Error)?.message)}` }], 99 | }; 100 | } 101 | }); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/builtin-tools/docx/builtin.ts: -------------------------------------------------------------------------------- 1 | import { McpTool } from '../../../../types'; 2 | import * as lark from '@larksuiteoapi/node-sdk'; 3 | import { ReadStream } from 'fs'; 4 | import { Readable } from 'stream'; 5 | import { z } from 'zod'; 6 | 7 | // Tool name type 8 | export type docxBuiltinToolName = 'docx.builtin.search' | 'docx.builtin.import'; 9 | 10 | export const larkDocxBuiltinSearchTool: McpTool = { 11 | project: 'docx', 12 | name: 'docx.builtin.search', 13 | accessTokens: ['user'], 14 | description: '[Feishu/Lark]-Docs-Document-Search Document-Search cloud documents, only supports user_access_token', 15 | schema: { 16 | data: z.object({ 17 | search_key: z.string().describe('Search keyword'), 18 | count: z 19 | .number() 20 | .describe('Specify the number of files returned in the search. Value range is [0,50].') 21 | .optional(), 22 | offset: z 23 | .number() 24 | .describe( 25 | 'Specifies the search offset. The minimum value is 0, which means no offset. The sum of this parameter and the number of returned files must not be greater than or equal to 200 (i.e., offset + count < 200).', 26 | ) 27 | .optional(), 28 | owner_ids: z.array(z.string()).describe('Open ID of the file owner').optional(), 29 | chat_ids: z.array(z.string()).describe('ID of the group where the file is located').optional(), 30 | docs_types: z 31 | .array(z.enum(['doc', 'sheet', 'slides', 'bitable', 'mindnote', 'file'])) 32 | .describe( 33 | 'File types, supports the following enumerations: doc: old version document; sheet: spreadsheet; slides: slides; bitable: multi-dimensional table; mindnote: mind map; file: file', 34 | ) 35 | .optional(), 36 | }), 37 | useUAT: z 38 | .boolean() 39 | .describe('Whether to use user identity for the request, false means using application identity') 40 | .optional(), 41 | }, 42 | customHandler: async (client, params, options): Promise => { 43 | try { 44 | const { userAccessToken } = options || {}; 45 | 46 | if (!userAccessToken) { 47 | return { 48 | isError: true, 49 | content: [{ type: 'text' as const, text: 'User access token is not configured' }], 50 | }; 51 | } 52 | 53 | const response = await client.request( 54 | { 55 | method: 'POST', 56 | url: '/open-apis/suite/docs-api/search/object', 57 | data: params.data, 58 | }, 59 | lark.withUserAccessToken(userAccessToken), 60 | ); 61 | 62 | return { 63 | content: [ 64 | { 65 | type: 'text' as const, 66 | text: `Document search request successful: ${JSON.stringify(response.data ?? response)}`, 67 | }, 68 | ], 69 | }; 70 | } catch (error) { 71 | return { 72 | isError: true, 73 | content: [ 74 | { 75 | type: 'text' as const, 76 | text: `Document search request failed: ${JSON.stringify((error as any).response.data)}`, 77 | }, 78 | ], 79 | }; 80 | } 81 | }, 82 | }; 83 | 84 | export const larkDocxBuiltinImportTool: McpTool = { 85 | project: 'docx', 86 | name: 'docx.builtin.import', 87 | accessTokens: ['user', 'tenant'], 88 | description: '[Feishu/Lark]-Docs-Document-Import Document-Import cloud document, maximum 20MB', 89 | schema: { 90 | data: z 91 | .object({ 92 | markdown: z.string().describe('Markdown file content'), 93 | file_name: z.string().describe('File name').max(27).optional(), 94 | }) 95 | .describe('Request body'), 96 | useUAT: z.boolean().describe('Use user identity for the request, otherwise use application identity').optional(), 97 | }, 98 | customHandler: async (client, params, options): Promise => { 99 | try { 100 | const { userAccessToken } = options || {}; 101 | const file = Readable.from(params.data.markdown) as ReadStream; 102 | 103 | const data = { 104 | file_name: 'docx.md', 105 | parent_type: 'ccm_import_open' as const, 106 | parent_node: '/', 107 | size: Buffer.byteLength(params.data.markdown), 108 | file, 109 | extra: JSON.stringify({ obj_type: 'docx', file_extension: 'md' }), 110 | }; 111 | 112 | const response = 113 | userAccessToken && params.useUAT 114 | ? await client.drive.media.uploadAll({ data }, lark.withUserAccessToken(userAccessToken)) 115 | : await client.drive.media.uploadAll({ data }); 116 | 117 | if (!response?.file_token) { 118 | return { 119 | isError: true, 120 | content: [{ type: 'text' as const, text: 'Document import failed, please check the markdown file content' }], 121 | }; 122 | } 123 | 124 | const importData = { 125 | file_extension: 'md', 126 | file_name: params.data.file_name, 127 | file_token: response?.file_token, 128 | type: 'docx', 129 | point: { 130 | mount_type: 1, 131 | mount_key: '', 132 | }, 133 | }; 134 | 135 | const importResponse = 136 | userAccessToken && params.useUAT 137 | ? await client.drive.importTask.create({ data: importData }, lark.withUserAccessToken(userAccessToken)) 138 | : await client.drive.importTask.create({ data: importData }); 139 | 140 | const taskId = importResponse.data?.ticket; 141 | if (!taskId) { 142 | return { 143 | isError: true, 144 | content: [{ type: 'text' as const, text: 'Document import failed, please check the markdown file content' }], 145 | }; 146 | } 147 | 148 | for (let i = 0; i < 5; i++) { 149 | const taskResponse = 150 | userAccessToken && params.useUAT 151 | ? await client.drive.importTask.get({ path: { ticket: taskId } }, lark.withUserAccessToken(userAccessToken)) 152 | : await client.drive.importTask.get({ path: { ticket: taskId } }); 153 | 154 | if (taskResponse.data?.result?.job_status === 0) { 155 | return { 156 | content: [ 157 | { 158 | type: 'text' as const, 159 | text: `Document import request successful: ${JSON.stringify(taskResponse.data ?? taskResponse)}`, 160 | }, 161 | ], 162 | }; 163 | } else if (taskResponse.data?.result?.job_status !== 1 && taskResponse.data?.result?.job_status !== 2) { 164 | return { 165 | content: [ 166 | { 167 | type: 'text' as const, 168 | text: 'Document import failed, please try again later' + JSON.stringify(taskResponse.data), 169 | }, 170 | ], 171 | }; 172 | } 173 | await new Promise((resolve) => setTimeout(resolve, 1000)); 174 | } 175 | 176 | return { 177 | content: [ 178 | { 179 | type: 'text' as const, 180 | text: 'Document import failed, please try again later', 181 | }, 182 | ], 183 | }; 184 | } catch (error) { 185 | return { 186 | isError: true, 187 | content: [ 188 | { 189 | type: 'text' as const, 190 | text: `Document import request failed: ${JSON.stringify((error as any)?.response?.data || error)}`, 191 | }, 192 | ], 193 | }; 194 | } 195 | }, 196 | }; 197 | 198 | export const docxBuiltinTools = [larkDocxBuiltinSearchTool, larkDocxBuiltinImportTool]; 199 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/builtin-tools/im/buildin.ts: -------------------------------------------------------------------------------- 1 | import { McpTool } from '../../../../types'; 2 | import { z } from 'zod'; 3 | 4 | export type imBuiltinToolName = 'im.builtin.batchSend'; 5 | 6 | export const larkImBuiltinBatchSendTool: McpTool = { 7 | project: 'im', 8 | name: 'im.builtin.batchSend', 9 | accessTokens: ['tenant'], 10 | description: 11 | '[Feishu/Lark] - Batch send messages - Supports batch sending messages to multiple users and departments, supports text and card', 12 | schema: { 13 | data: z.object({ 14 | msg_type: z 15 | .enum(['text', 'post', 'image', 'interactive', 'share_chat']) 16 | .describe( 17 | 'Message type. If msg_type is text, image, post, or share_chat, the message content should be passed in the content parameter. If msg_type is interactive, the message content should be passed in the card parameter. Rich text type (post) messages do not support md tags.', 18 | ), 19 | content: z 20 | .any() 21 | .describe( 22 | 'Message content, JSON structure. The value of this parameter corresponds to msg_type. For example, if msg_type is text, this parameter should be the text content.', 23 | ) 24 | .optional(), 25 | card: z 26 | .any() 27 | .describe( 28 | 'Card content, JSON structure. The value of this parameter corresponds to msg_type. Only when msg_type is interactive, the card content should be passed in this parameter. When msg_type is not interactive, the message content should be passed in the content parameter.', 29 | ) 30 | .optional(), 31 | open_ids: z.array(z.string()).describe('List of recipient open_ids').optional(), 32 | user_ids: z.array(z.string()).describe('List of recipient user_ids').optional(), 33 | union_ids: z.array(z.string()).describe('List of recipient union_ids').optional(), 34 | department_ids: z 35 | .array(z.string()) 36 | .describe('List of department IDs. The list supports both department_id and open_department_id') 37 | .optional(), 38 | }), 39 | }, 40 | customHandler: async (client, params): Promise => { 41 | try { 42 | const { data } = params; 43 | const response = await client.request({ 44 | method: 'POST', 45 | url: '/open-apis/message/v4/batch_send', 46 | data, 47 | }); 48 | return { 49 | content: [ 50 | { 51 | type: 'text' as const, 52 | text: `Batch send message request succeeded: ${JSON.stringify(response.data ?? response)}`, 53 | }, 54 | ], 55 | }; 56 | } catch (error) { 57 | return { 58 | isError: true, 59 | content: [ 60 | { 61 | type: 'text' as const, 62 | text: `Batch send message request failed: ${JSON.stringify((error as any)?.response?.data || error)}`, 63 | }, 64 | ], 65 | }; 66 | } 67 | }, 68 | }; 69 | 70 | export const imBuiltinTools = [larkImBuiltinBatchSendTool]; 71 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/builtin-tools/index.ts: -------------------------------------------------------------------------------- 1 | import { docxBuiltinToolName, docxBuiltinTools } from './docx/builtin'; 2 | import { imBuiltinToolName, imBuiltinTools } from './im/buildin'; 3 | 4 | export const BuiltinTools = [...docxBuiltinTools, ...imBuiltinTools]; 5 | 6 | export type BuiltinToolName = docxBuiltinToolName | imBuiltinToolName; 7 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/application_v5.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type applicationV5ToolName = 'application.v5.application.favourite' | 'application.v5.application.recommend'; 3 | export const applicationV5ApplicationFavourite = { 4 | project: 'application', 5 | name: 'application.v5.application.favourite', 6 | sdkName: 'application.v5.application.favourite', 7 | path: '/open-apis/application/v5/applications/favourite', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-Workplace-My favorite-获取用户常用的应用', 10 | accessTokens: ['user'], 11 | schema: { 12 | params: z.object({ 13 | language: z 14 | .enum(['zh_cn', 'en_us', 'ja_jp']) 15 | .describe('Options:zh_cn(Chinese 中文),en_us(English 英文),ja_jp(Japanese 日文)') 16 | .optional(), 17 | page_token: z.string().optional(), 18 | page_size: z.number().optional(), 19 | }), 20 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 21 | }, 22 | }; 23 | export const applicationV5ApplicationRecommend = { 24 | project: 'application', 25 | name: 'application.v5.application.recommend', 26 | sdkName: 'application.v5.application.recommend', 27 | path: '/open-apis/application/v5/applications/recommend', 28 | httpMethod: 'GET', 29 | description: '[Feishu/Lark]-Workplace-My favorite-获取企业推荐的应用', 30 | accessTokens: ['user'], 31 | schema: { 32 | params: z.object({ 33 | language: z 34 | .enum(['zh_cn', 'en_us', 'ja_jp']) 35 | .describe('Options:zh_cn(Chinese 中文),en_us(English 英文),ja_jp(Japanese 日文)') 36 | .optional(), 37 | recommend_type: z 38 | .enum(['user_unremovable', 'user_removable']) 39 | .describe( 40 | 'Options:user_unremovable(UserUnremovable 用户不可移除的推荐应用列表),user_removable(UserRemovable 用户可移除的推荐应用列表)', 41 | ) 42 | .optional(), 43 | page_token: z.string().optional(), 44 | page_size: z.number().optional(), 45 | }), 46 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 47 | }, 48 | }; 49 | export const applicationV5Tools = [applicationV5ApplicationFavourite, applicationV5ApplicationRecommend]; 50 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/auth_v3.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type authV3ToolName = 3 | | 'auth.v3.auth.appAccessToken' 4 | | 'auth.v3.auth.appAccessTokenInternal' 5 | | 'auth.v3.auth.appTicketResend' 6 | | 'auth.v3.auth.tenantAccessToken' 7 | | 'auth.v3.auth.tenantAccessTokenInternal'; 8 | export const authV3AuthAppAccessToken = { 9 | project: 'auth', 10 | name: 'auth.v3.auth.appAccessToken', 11 | sdkName: 'auth.v3.auth.appAccessToken', 12 | path: '/open-apis/auth/v3/app_access_token', 13 | httpMethod: 'POST', 14 | description: '[Feishu/Lark]-Authenticate and Authorize-Get Access Tokens-Store applications get app_access_token', 15 | accessTokens: undefined, 16 | schema: { 17 | data: z.object({ app_id: z.string(), app_secret: z.string(), app_ticket: z.string() }), 18 | }, 19 | }; 20 | export const authV3AuthAppAccessTokenInternal = { 21 | project: 'auth', 22 | name: 'auth.v3.auth.appAccessTokenInternal', 23 | sdkName: 'auth.v3.auth.appAccessTokenInternal', 24 | path: '/open-apis/auth/v3/app_access_token/internal', 25 | httpMethod: 'POST', 26 | description: '[Feishu/Lark]-Authenticate and Authorize-Get Access Tokens-Get custom app app_access_token', 27 | accessTokens: undefined, 28 | schema: { 29 | data: z.object({ app_id: z.string(), app_secret: z.string() }), 30 | }, 31 | }; 32 | export const authV3AuthAppTicketResend = { 33 | project: 'auth', 34 | name: 'auth.v3.auth.appTicketResend', 35 | sdkName: 'auth.v3.auth.appTicketResend', 36 | path: '/open-apis/auth/v3/app_ticket/resend', 37 | httpMethod: 'POST', 38 | description: '[Feishu/Lark]-Authenticate and Authorize-Get Access Tokens-Retrieve app_ticket', 39 | accessTokens: undefined, 40 | schema: { 41 | data: z.object({ app_id: z.string(), app_secret: z.string() }), 42 | }, 43 | }; 44 | export const authV3AuthTenantAccessToken = { 45 | project: 'auth', 46 | name: 'auth.v3.auth.tenantAccessToken', 47 | sdkName: 'auth.v3.auth.tenantAccessToken', 48 | path: '/open-apis/auth/v3/tenant_access_token', 49 | httpMethod: 'POST', 50 | description: '[Feishu/Lark]-Authenticate and Authorize-Get Access Tokens-Store applications get tenant_access_token', 51 | accessTokens: undefined, 52 | schema: { 53 | data: z.object({ app_access_token: z.string(), tenant_key: z.string() }), 54 | }, 55 | }; 56 | export const authV3AuthTenantAccessTokenInternal = { 57 | project: 'auth', 58 | name: 'auth.v3.auth.tenantAccessTokenInternal', 59 | sdkName: 'auth.v3.auth.tenantAccessTokenInternal', 60 | path: '/open-apis/auth/v3/tenant_access_token/internal', 61 | httpMethod: 'POST', 62 | description: '[Feishu/Lark]-Authenticate and Authorize-Get Access Tokens-Get custom app tenant_access_token', 63 | accessTokens: undefined, 64 | schema: { 65 | data: z.object({ app_id: z.string(), app_secret: z.string() }), 66 | }, 67 | }; 68 | export const authV3Tools = [ 69 | authV3AuthAppAccessToken, 70 | authV3AuthAppAccessTokenInternal, 71 | authV3AuthAppTicketResend, 72 | authV3AuthTenantAccessToken, 73 | authV3AuthTenantAccessTokenInternal, 74 | ]; 75 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/authen_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type authenV1ToolName = 'authen.v1.userInfo.get'; 3 | export const authenV1UserInfoGet = { 4 | project: 'authen', 5 | name: 'authen.v1.userInfo.get', 6 | sdkName: 'authen.v1.userInfo.get', 7 | path: '/open-apis/authen/v1/user_info', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-Authenticate and Authorize-Login state management-Get User Information-Get related user info via `user_access_token`', 11 | accessTokens: ['user'], 12 | schema: { 13 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 14 | }, 15 | }; 16 | export const authenV1Tools = [authenV1UserInfoGet]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/board_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type boardV1ToolName = 'board.v1.whiteboardNode.list'; 3 | export const boardV1WhiteboardNodeList = { 4 | project: 'board', 5 | name: 'board.v1.whiteboardNode.list', 6 | sdkName: 'board.v1.whiteboardNode.list', 7 | path: '/open-apis/board/v1/whiteboards/:whiteboard_id/nodes', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-Docs-Board-nodes-list nodes-Obtain all nodes of a board', 10 | accessTokens: ['tenant', 'user'], 11 | schema: { 12 | path: z.object({ whiteboard_id: z.string().describe('The unique identification of the board') }), 13 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 14 | }, 15 | }; 16 | export const boardV1Tools = [boardV1WhiteboardNodeList]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/compensation_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type compensationV1ToolName = 3 | | 'compensation.v1.archive.query' 4 | | 'compensation.v1.changeReason.list' 5 | | 'compensation.v1.indicator.list' 6 | | 'compensation.v1.itemCategory.list' 7 | | 'compensation.v1.item.list' 8 | | 'compensation.v1.plan.list'; 9 | export const compensationV1ArchiveQuery = { 10 | project: 'compensation', 11 | name: 'compensation.v1.archive.query', 12 | sdkName: 'compensation.v1.archive.query', 13 | path: '/open-apis/compensation/v1/archives/query', 14 | httpMethod: 'POST', 15 | description: 16 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation Profile-Batch query compensation profile-Batch query compensation profile', 17 | accessTokens: ['tenant', 'user'], 18 | schema: { 19 | data: z.object({ 20 | user_id_list: z 21 | .array(z.string()) 22 | .describe('User ID list, the acquisition method can refer to the "user_id_type" field in the query parameters'), 23 | tid_list: z.array(z.string()).describe('Archive Tid List').optional(), 24 | effective_start_date: z.string().describe('Effective start time').optional(), 25 | effective_end_date: z.string().describe('Effective end time').optional(), 26 | }), 27 | params: z.object({ 28 | page_size: z.number().describe('paging size'), 29 | page_token: z 30 | .string() 31 | .describe( 32 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 33 | ) 34 | .optional(), 35 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_corehr_id']).describe('User ID type'), 36 | }), 37 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 38 | }, 39 | }; 40 | export const compensationV1ChangeReasonList = { 41 | project: 'compensation', 42 | name: 'compensation.v1.changeReason.list', 43 | sdkName: 'compensation.v1.changeReason.list', 44 | path: '/open-apis/compensation/v1/change_reasons', 45 | httpMethod: 'GET', 46 | description: 47 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation setting and adjustment-Batch query reason for compensation setting and adjustment-Batch query reason for compensation setting and adjustment', 48 | accessTokens: ['tenant'], 49 | schema: { 50 | params: z.object({ 51 | page_size: z.number().describe('paging size'), 52 | page_token: z 53 | .string() 54 | .describe( 55 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 56 | ) 57 | .optional(), 58 | }), 59 | }, 60 | }; 61 | export const compensationV1IndicatorList = { 62 | project: 'compensation', 63 | name: 'compensation.v1.indicator.list', 64 | sdkName: 'compensation.v1.indicator.list', 65 | path: '/open-apis/compensation/v1/indicators', 66 | httpMethod: 'GET', 67 | description: 68 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation component / metric-Batch query compensation metric-Batch query compensation metric', 69 | accessTokens: ['tenant'], 70 | schema: { 71 | params: z.object({ 72 | page_size: z.number().describe('paging size'), 73 | page_token: z 74 | .string() 75 | .describe( 76 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 77 | ) 78 | .optional(), 79 | }), 80 | }, 81 | }; 82 | export const compensationV1ItemCategoryList = { 83 | project: 'compensation', 84 | name: 'compensation.v1.itemCategory.list', 85 | sdkName: 'compensation.v1.itemCategory.list', 86 | path: '/open-apis/compensation/v1/item_categories', 87 | httpMethod: 'GET', 88 | description: 89 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation component / metric-Batch query compensation component type-Batch query compensation component type', 90 | accessTokens: ['tenant'], 91 | schema: { 92 | params: z.object({ 93 | page_size: z.number().describe('paging size').optional(), 94 | page_token: z 95 | .string() 96 | .describe( 97 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 98 | ) 99 | .optional(), 100 | }), 101 | }, 102 | }; 103 | export const compensationV1ItemList = { 104 | project: 'compensation', 105 | name: 'compensation.v1.item.list', 106 | sdkName: 'compensation.v1.item.list', 107 | path: '/open-apis/compensation/v1/items', 108 | httpMethod: 'GET', 109 | description: 110 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation component / metric-Batch query compensation component-Batch query compensation component', 111 | accessTokens: ['tenant'], 112 | schema: { 113 | params: z.object({ 114 | page_size: z.number().describe('paging size'), 115 | page_token: z 116 | .string() 117 | .describe( 118 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 119 | ) 120 | .optional(), 121 | }), 122 | }, 123 | }; 124 | export const compensationV1PlanList = { 125 | project: 'compensation', 126 | name: 'compensation.v1.plan.list', 127 | sdkName: 'compensation.v1.plan.list', 128 | path: '/open-apis/compensation/v1/plans', 129 | httpMethod: 'GET', 130 | description: 131 | '[Feishu/Lark]-Feishu People(Enterprise Edition)-Basic Compensation-Compensation Plan-Batch query compensation plan-- This interface will return all salary plan information, including salary plan ID, effective date, salary item/salary statistics indicator, etc', 132 | accessTokens: ['tenant'], 133 | schema: { 134 | params: z.object({ 135 | page_size: z.number().describe('paging size'), 136 | page_token: z 137 | .string() 138 | .describe( 139 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 140 | ) 141 | .optional(), 142 | }), 143 | }, 144 | }; 145 | export const compensationV1Tools = [ 146 | compensationV1ArchiveQuery, 147 | compensationV1ChangeReasonList, 148 | compensationV1IndicatorList, 149 | compensationV1ItemCategoryList, 150 | compensationV1ItemList, 151 | compensationV1PlanList, 152 | ]; 153 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/docs_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type docsV1ToolName = 'docs.v1.content.get'; 3 | export const docsV1ContentGet = { 4 | project: 'docs', 5 | name: 'docs.v1.content.get', 6 | sdkName: 'docs.v1.content.get', 7 | path: '/open-apis/docs/v1/content', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-Docs-Common-Get docs content-You can obtain the docs content. Currently, only upgraded document content in markdown format is supported', 11 | accessTokens: ['tenant', 'user'], 12 | schema: { 13 | params: z.object({ 14 | doc_token: z 15 | .string() 16 | .describe( 17 | 'The unique identification of the docs. Click to learn how to get `doc_token`', 18 | ), 19 | doc_type: z.literal('docx').describe('Docs type Options:docx(Upgraded Document)'), 20 | content_type: z.literal('markdown').describe('Content type Options:markdown(Markdown format)'), 21 | lang: z 22 | .enum(['zh', 'en', 'ja']) 23 | .describe( 24 | 'Specifies the language of the user name when the @user element exists in the docs. Default `zh` Options:zh(Chinese),en(English),ja(Japanese)', 25 | ) 26 | .optional(), 27 | }), 28 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 29 | }, 30 | }; 31 | export const docsV1Tools = [docsV1ContentGet]; 32 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/drive_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type driveV2ToolName = 3 | | 'drive.v2.fileLike.list' 4 | | 'drive.v2.permissionPublic.get' 5 | | 'drive.v2.permissionPublic.patch'; 6 | export const driveV2FileLikeList = { 7 | project: 'drive', 8 | name: 'drive.v2.fileLike.list', 9 | sdkName: 'drive.v2.fileLike.list', 10 | path: '/open-apis/drive/v2/files/:file_token/likes', 11 | httpMethod: 'GET', 12 | description: 13 | "[Feishu/Lark]-Docs-Space-Like-List Document's Likes-Get the list of likes for the specified document and returns by like time from near to far. This API supports paging", 14 | accessTokens: ['tenant', 'user'], 15 | schema: { 16 | params: z.object({ 17 | file_type: z 18 | .enum(['doc', 'docx', 'file']) 19 | .describe( 20 | 'Document type, the API will return a failure if the value is empty or does not match the actual type of the document. Options:doc(Docs),docx(Upgraded Docs),file(File)', 21 | ), 22 | page_size: z.number().describe('paging size').optional(), 23 | page_token: z 24 | .string() 25 | .describe( 26 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 27 | ) 28 | .optional(), 29 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional(), 30 | }), 31 | path: z.object({ 32 | file_token: z 33 | .string() 34 | .describe( 35 | 'The document token specified to query the like list. ', 36 | ), 37 | }), 38 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 39 | }, 40 | }; 41 | export const driveV2PermissionPublicGet = { 42 | project: 'drive', 43 | name: 'drive.v2.permissionPublic.get', 44 | sdkName: 'drive.v2.permissionPublic.get', 45 | path: '/open-apis/drive/v2/permissions/:token/public', 46 | httpMethod: 'GET', 47 | description: 48 | '[Feishu/Lark]-Docs-Permission-Setting v2-GetPermissionPublic-This interface is used to obtain permission settings for cloud documents according to filetoken', 49 | accessTokens: ['tenant', 'user'], 50 | schema: { 51 | params: z.object({ 52 | type: z 53 | .enum(['doc', 'sheet', 'file', 'wiki', 'bitable', 'docx', 'mindnote', 'minutes', 'slides']) 54 | .describe( 55 | 'File type, which needs to match the token of the file Options:doc(Doc),sheet(Sheet),file(File in My Space),wiki(Wiki node),bitable(Bitable),docx(Upgraded Docs),mindnote(Mindnote),minutes(Minutes),slides(Slides)', 56 | ), 57 | }), 58 | path: z.object({ 59 | token: z 60 | .string() 61 | .describe( 62 | 'Token of the file. For more information about how to obtain the token, see ', 63 | ), 64 | }), 65 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 66 | }, 67 | }; 68 | export const driveV2PermissionPublicPatch = { 69 | project: 'drive', 70 | name: 'drive.v2.permissionPublic.patch', 71 | sdkName: 'drive.v2.permissionPublic.patch', 72 | path: '/open-apis/drive/v2/permissions/:token/public', 73 | httpMethod: 'PATCH', 74 | description: 75 | '[Feishu/Lark]-Docs-Permission-Setting v2-UpdatePermissionPublic-This API is used to update the common settings of a document based on a filetoken', 76 | accessTokens: ['tenant', 'user'], 77 | schema: { 78 | data: z.object({ 79 | external_access_entity: z 80 | .enum(['open', 'closed', 'allow_share_partner_tenant']) 81 | .describe( 82 | "Allow content to be shared outside the organization Options:open(open),closed(close),allow_share_partner_tenant(AllowSharePartnerTenant Allow sharing with affiliated organizations(This value can only be set if the tenant's background setting allows only associated organizations to share))", 83 | ) 84 | .optional(), 85 | security_entity: z 86 | .enum(['anyone_can_view', 'anyone_can_edit', 'only_full_access']) 87 | .describe( 88 | 'Who can create copies, print, download Options:anyone_can_view(AnyoneCanView Users with viewing permissions),anyone_can_edit(AnyoneCanEdit Users with editable permissions),only_full_access(OnlyFullAccess Users with manageable permissions (including me))', 89 | ) 90 | .optional(), 91 | comment_entity: z 92 | .enum(['anyone_can_view', 'anyone_can_edit']) 93 | .describe( 94 | 'Who can comment Options:anyone_can_view(AnyoneCanView Users with viewing permissions),anyone_can_edit(AnyoneCanEdit Users with editable permissions)', 95 | ) 96 | .optional(), 97 | share_entity: z 98 | .enum(['anyone', 'same_tenant']) 99 | .describe( 100 | 'Who can add and manage collaborators - organizational dimension Options:anyone(All users who can read or edit this document),same_tenant(SameTenant All users in your organization who can read or edit this document)', 101 | ) 102 | .optional(), 103 | manage_collaborator_entity: z 104 | .enum(['collaborator_can_view', 'collaborator_can_edit', 'collaborator_full_access']) 105 | .describe( 106 | 'Who can add and manage collaborators - collaborator dimension Options:collaborator_can_view(CollaboratorCanView Collaborators with viewing permissions),collaborator_can_edit(CollaboratorCanEdit Collaborators with editable permissions),collaborator_full_access(CollaboratorFullAccess Collaborators with manageable permissions (including me))', 107 | ) 108 | .optional(), 109 | link_share_entity: z 110 | .enum([ 111 | 'tenant_readable', 112 | 'tenant_editable', 113 | 'partner_tenant_readable', 114 | 'partner_tenant_editable', 115 | 'anyone_readable', 116 | 'anyone_editable', 117 | 'closed', 118 | ]) 119 | .describe( 120 | 'Link sharing settings Options:tenant_readable(TenantReadable People in the organization who get the link can read it),tenant_editable(TenantEditable People in the organization who get the link can edit),partner_tenant_readable(PartnerTenantReadable People from affiliated organizations can read(This value can only be set if the tenant\'s background setting allows only associated organizations to share)),partner_tenant_editable(PartnerTenantEditable People from affiliated organizations can edit(This value can only be set if the tenant\'s background setting allows only associated organizations to share)),anyone_readable(AnyoneReadable Anyone with a link on the Internet can read it (only external_access_entity = "open")),anyone_editable(AnyoneEditable Editable by anyone with a link on the internet (only external_access_entity = "open")),closed(Close link share)', 121 | ) 122 | .optional(), 123 | copy_entity: z 124 | .enum(['anyone_can_view', 'anyone_can_edit', 'only_full_access']) 125 | .describe( 126 | 'Who can copy the content Options:anyone_can_view(AnyoneCanView Users with viewing permissions),anyone_can_edit(AnyoneCanEdit Users with editable permissions),only_full_access(OnlyFullAccess Collaborators with manageable permissions (including me))', 127 | ) 128 | .optional(), 129 | }), 130 | params: z.object({ 131 | type: z 132 | .enum(['doc', 'sheet', 'file', 'wiki', 'bitable', 'docx', 'mindnote', 'minutes', 'slides']) 133 | .describe( 134 | 'File type, which needs to match the token of the file Options:doc(Doc),sheet(Sheet),file(File in My Space),wiki(Wiki node),bitable(Bitable),docx(Upgraded Docs),mindnote(Mindnote),minutes(Minutes),slides(Slides)', 135 | ), 136 | }), 137 | path: z.object({ 138 | token: z 139 | .string() 140 | .describe( 141 | 'Token of the file. For more information about how to obtain the token, see ', 142 | ), 143 | }), 144 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 145 | }, 146 | }; 147 | export const driveV2Tools = [driveV2FileLikeList, driveV2PermissionPublicGet, driveV2PermissionPublicPatch]; 148 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/ehr_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type ehrV1ToolName = 'ehr.v1.employee.list'; 3 | export const ehrV1EmployeeList = { 4 | project: 'ehr', 5 | name: 'ehr.v1.employee.list', 6 | sdkName: 'ehr.v1.employee.list', 7 | path: '/open-apis/ehr/v1/employees', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-Feishu CoreHR - (Standard version)-Employee directory information batch retrieval-You can batch retrieve employee directory fields with filters, such as Feishu user ID, employee status, and employee type. Directory fields can be divided into system fields (system_fields) and custom fields (custom_fields)', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | params: z.object({ 14 | view: z 15 | .enum(['basic', 'full']) 16 | .describe( 17 | 'Data range. If no value is passed, it defaults to basic. Options:basic(Overview. Only the ID, name, and other basic information will be returned.),full(Details. A combination of system fields and custom fields will be returned.)', 18 | ) 19 | .optional(), 20 | status: z 21 | .array( 22 | z 23 | .number() 24 | .describe( 25 | 'Options:1(to_be_onboarded To be onboarded),2(active),3(onboarding_cancelled Onboarding cancelle),4(offboarding),5(offboarded)', 26 | ), 27 | ) 28 | .describe( 29 | 'Employee statusAll employee statuses will be returned by default if this attribute is not specified.Currently active employees = 2&4Multiple status records can be queried at the same time, such as status=2&status=4', 30 | ) 31 | .optional(), 32 | type: z 33 | .array( 34 | z 35 | .number() 36 | .describe( 37 | 'Options:1(regular To be onboarded),2(intern Active),3(consultant Onboard canceled),4(outsourcing Offboarding),5(contractor Offboarded)', 38 | ), 39 | ) 40 | .describe( 41 | 'Employee typeAll employee types will be returned by default if this attribute is not specified.You can use the int value of the custom employee type for search, and the name of the custom employee type for the tenant can be obtained through the API: ', 42 | ) 43 | .optional(), 44 | start_time: z.string().describe('Query start time(Hire time >= this time)').optional(), 45 | end_time: z.string().describe('Query end time(Hire time >= this time)').optional(), 46 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional(), 47 | user_ids: z 48 | .array(z.string()) 49 | .describe( 50 | 'user_id, open_id, or union_id. The "open_id" will be returned by default.If the passed value is not an "open_id" , you need to pass the "user_id_type" parameter together.You can query users with multiple ids at once, for example: user_ids=ou_8ebd4f35d7101ffdeb4771d7c8ec517e&user_ids=ou_7abc4f35d7101ffdeb4771dabcde', 51 | ) 52 | .optional(), 53 | page_token: z 54 | .string() 55 | .describe( 56 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 57 | ) 58 | .optional(), 59 | page_size: z.number().describe('Page size, value range 1~ 100, default 10').optional(), 60 | }), 61 | }, 62 | }; 63 | export const ehrV1Tools = [ehrV1EmployeeList]; 64 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/event_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type eventV1ToolName = 'event.v1.outboundIp.list'; 3 | export const eventV1OutboundIpList = { 4 | project: 'event', 5 | name: 'event.v1.outboundIp.list', 6 | sdkName: 'event.v1.outboundIp.list', 7 | path: '/open-apis/event/v1/outbound_ip', 8 | httpMethod: 'GET', 9 | description: 10 | "[Feishu/Lark]-Events and callbacks-Event subscriptions-Get event's outbound IP-When Feishu Open Platform pushes events to the callback address configured by the application, it is sent out through a specific IP, and the application can get all relevant IP addresses through this interface", 11 | accessTokens: ['tenant'], 12 | schema: { 13 | params: z.object({ page_size: z.number().optional(), page_token: z.string().optional() }), 14 | }, 15 | }; 16 | export const eventV1Tools = [eventV1OutboundIpList]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/hire_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type hireV2ToolName = 'hire.v2.interviewRecord.get' | 'hire.v2.interviewRecord.list' | 'hire.v2.talent.get'; 3 | export const hireV2InterviewRecordGet = { 4 | project: 'hire', 5 | name: 'hire.v2.interviewRecord.get', 6 | sdkName: 'hire.v2.interviewRecord.get', 7 | path: '/open-apis/hire/v2/interview_records/:interview_record_id', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-Candidate management-Delivery process-Interview-Get Interview Feedback Detail(new version)-Get interview feedback details', 11 | accessTokens: ['tenant', 'user'], 12 | schema: { 13 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 14 | path: z.object({ interview_record_id: z.string().describe('Interview record ID') }), 15 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 16 | }, 17 | }; 18 | export const hireV2InterviewRecordList = { 19 | project: 'hire', 20 | name: 'hire.v2.interviewRecord.list', 21 | sdkName: 'hire.v2.interviewRecord.list', 22 | path: '/open-apis/hire/v2/interview_records', 23 | httpMethod: 'GET', 24 | description: 25 | '[Feishu/Lark]-Candidate management-Delivery process-Interview-Batch Get Interview Feedback Details(new version)-Batch get interview feedback', 26 | accessTokens: ['tenant', 'user'], 27 | schema: { 28 | params: z.object({ 29 | ids: z.array(z.string()).describe('List interview feedback by IDs, page param will not be used').optional(), 30 | page_size: z.number().describe('Paging size').optional(), 31 | page_token: z 32 | .string() 33 | .describe( 34 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 35 | ) 36 | .optional(), 37 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional(), 38 | }), 39 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 40 | }, 41 | }; 42 | export const hireV2TalentGet = { 43 | project: 'hire', 44 | name: 'hire.v2.talent.get', 45 | sdkName: 'hire.v2.talent.get', 46 | path: '/open-apis/hire/v2/talents/:talent_id', 47 | httpMethod: 'GET', 48 | description: 49 | '[Feishu/Lark]-Hire-Candidate management-Talent-Get talent details-Obtain talent information according to talent ID', 50 | accessTokens: ['tenant'], 51 | schema: { 52 | params: z.object({ 53 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_admin_id']).describe('User ID type').optional(), 54 | }), 55 | path: z.object({ talent_id: z.string().describe('Talent ID') }), 56 | }, 57 | }; 58 | export const hireV2Tools = [hireV2InterviewRecordGet, hireV2InterviewRecordList, hireV2TalentGet]; 59 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/human_authentication_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type humanAuthenticationV1ToolName = 'human_authentication.v1.identity.create'; 3 | export const humanAuthenticationV1IdentityCreate = { 4 | project: 'human_authentication', 5 | name: 'human_authentication.v1.identity.create', 6 | sdkName: 'human_authentication.v1.identity.create', 7 | path: '/open-apis/human_authentication/v1/identities', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-Identity Authentication-Upload Identity Information-This interface is used to upload the identity information for real-name authentication. Before arousing active living body authentication, this interface needs to be used for real-name authentication', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | identity_name: z.string().describe('Name'), 15 | identity_code: z.string().describe('User identification number'), 16 | mobile: z.string().describe('Mobile phone').optional(), 17 | }), 18 | params: z.object({ 19 | user_id: z 20 | .string() 21 | .describe( 22 | 'The unique identifier of the user(For the ID type used, see the next parameter description, the difference and acquisition of different ID types, refer to the document: )', 23 | ), 24 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional(), 25 | }), 26 | }, 27 | }; 28 | export const humanAuthenticationV1Tools = [humanAuthenticationV1IdentityCreate]; 29 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/mdm_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type mdmV1ToolName = 'mdm.v1.userAuthDataRelation.bind' | 'mdm.v1.userAuthDataRelation.unbind'; 3 | export const mdmV1UserAuthDataRelationBind = { 4 | project: 'mdm', 5 | name: 'mdm.v1.userAuthDataRelation.bind', 6 | sdkName: 'mdm.v1.userAuthDataRelation.bind', 7 | path: '/open-apis/mdm/v1/user_auth_data_relations/bind', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-Feishu Master Data Management-Data dimension-User data dimension binding-Through this interface, a type of data dimension can be bound to users under a specified application, and batch authorization can be granted to multiple users at the same time', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | root_dimension_type: z.string().describe('data type encoding'), 15 | sub_dimension_types: z.array(z.string()).describe('data encoding list'), 16 | authorized_user_ids: z.array(z.string()).describe("authorizer's lark id"), 17 | uams_app_id: z.string().describe('application id in uams system'), 18 | }), 19 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 20 | }, 21 | }; 22 | export const mdmV1UserAuthDataRelationUnbind = { 23 | project: 'mdm', 24 | name: 'mdm.v1.userAuthDataRelation.unbind', 25 | sdkName: 'mdm.v1.userAuthDataRelation.unbind', 26 | path: '/open-apis/mdm/v1/user_auth_data_relations/unbind', 27 | httpMethod: 'POST', 28 | description: 29 | '[Feishu/Lark]-Feishu Master Data Management-Data dimension-User data dimension unbinding-Through this interface, a type of data dimension can be released for a specified user under a specified application', 30 | accessTokens: ['tenant'], 31 | schema: { 32 | data: z.object({ 33 | root_dimension_type: z.string().describe('data type encoding'), 34 | sub_dimension_types: z.array(z.string()).describe('data encoding list'), 35 | authorized_user_ids: z.array(z.string()).describe("authorizer's lark id"), 36 | uams_app_id: z.string().describe('application id in uams system'), 37 | }), 38 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 39 | }, 40 | }; 41 | export const mdmV1Tools = [mdmV1UserAuthDataRelationBind, mdmV1UserAuthDataRelationUnbind]; 42 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/minutes_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type minutesV1ToolName = 3 | | 'minutes.v1.minute.get' 4 | | 'minutes.v1.minuteMedia.get' 5 | | 'minutes.v1.minuteStatistics.get'; 6 | export const minutesV1MinuteGet = { 7 | project: 'minutes', 8 | name: 'minutes.v1.minute.get', 9 | sdkName: 'minutes.v1.minute.get', 10 | path: '/open-apis/minutes/v1/minutes/:minute_token', 11 | httpMethod: 'GET', 12 | description: 13 | '[Feishu/Lark]-Minutes-Minutes Meta-Get minutes meta-Through this api, you can get a basic overview of Lark Minutes, including `owner_id`, `create_time`, title, cover picture, duration and URL', 14 | accessTokens: ['tenant', 'user'], 15 | schema: { 16 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 17 | path: z.object({ 18 | minute_token: z 19 | .string() 20 | .describe( 21 | 'Minute uniquely identifies,it can be obtained from the minute link, usually the last string of characters in the link', 22 | ) 23 | .optional(), 24 | }), 25 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 26 | }, 27 | }; 28 | export const minutesV1MinuteMediaGet = { 29 | project: 'minutes', 30 | name: 'minutes.v1.minuteMedia.get', 31 | sdkName: 'minutes.v1.minuteMedia.get', 32 | path: '/open-apis/minutes/v1/minutes/:minute_token/media', 33 | httpMethod: 'GET', 34 | description: 35 | '[Feishu/Lark]-Minutes-Minutes audio or video file-Download minutes audio or video file-Get the audio or video file of minutes', 36 | accessTokens: ['tenant', 'user'], 37 | schema: { 38 | path: z.object({ minute_token: z.string().describe('Minute unique identifier') }), 39 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 40 | }, 41 | }; 42 | export const minutesV1MinuteStatisticsGet = { 43 | project: 'minutes', 44 | name: 'minutes.v1.minuteStatistics.get', 45 | sdkName: 'minutes.v1.minuteStatistics.get', 46 | path: '/open-apis/minutes/v1/minutes/:minute_token/statistics', 47 | httpMethod: 'GET', 48 | description: 49 | '[Feishu/Lark]-Minutes-Minutes statistics-Get minutes statistics-Through this API, you can get access statistics of Lark Minutes, including PV, UV, visited user id, visited user timestamp', 50 | accessTokens: ['tenant', 'user'], 51 | schema: { 52 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 53 | path: z.object({ 54 | minute_token: z 55 | .string() 56 | .describe( 57 | 'Minute uniquely identifies,it can be obtained from the minute link, usually the last string of characters in the link', 58 | ) 59 | .optional(), 60 | }), 61 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 62 | }, 63 | }; 64 | export const minutesV1Tools = [minutesV1MinuteGet, minutesV1MinuteMediaGet, minutesV1MinuteStatisticsGet]; 65 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/moments_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type momentsV1ToolName = 'moments.v1.post.get'; 3 | export const momentsV1PostGet = { 4 | project: 'moments', 5 | name: 'moments.v1.post.get', 6 | sdkName: 'moments.v1.post.get', 7 | path: '/open-apis/moments/v1/posts/:post_id', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-Moments-Post-Query post information-Query post entity data information by post id', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 13 | path: z.object({ 14 | post_id: z 15 | .string() 16 | .describe( 17 | 'Post ID, which can be got from the data returned by the "Publish moment" interface or the "Moment posted" event', 18 | ), 19 | }), 20 | }, 21 | }; 22 | export const momentsV1Tools = [momentsV1PostGet]; 23 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/optical_char_recognition_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type opticalCharRecognitionV1ToolName = 'optical_char_recognition.v1.image.basicRecognize'; 3 | export const opticalCharRecognitionV1ImageBasicRecognize = { 4 | project: 'optical_char_recognition', 5 | name: 'optical_char_recognition.v1.image.basicRecognize', 6 | sdkName: 'optical_char_recognition.v1.image.basicRecognize', 7 | path: '/open-apis/optical_char_recognition/v1/image/basic_recognize', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-AI-Optical character recognition-Recognize text in pictures-Basic picture recognition interface, recognize the text in the picture, and return the text list by area.File size must be less than 5M', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ image: z.string().describe('Picture data after base64').optional() }), 14 | }, 15 | }; 16 | export const opticalCharRecognitionV1Tools = [opticalCharRecognitionV1ImageBasicRecognize]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/passport_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type passportV1ToolName = 'passport.v1.session.logout' | 'passport.v1.session.query'; 3 | export const passportV1SessionLogout = { 4 | project: 'passport', 5 | name: 'passport.v1.session.logout', 6 | sdkName: 'passport.v1.session.logout', 7 | path: '/open-apis/passport/v1/sessions/logout', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-Authenticate and Authorize-Login state management-Log out-This interface is used to log out of the user login state', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | idp_credential_id: z 15 | .string() 16 | .describe('The unique identifier of the idp, required when logout_type = 2') 17 | .optional(), 18 | logout_type: z 19 | .number() 20 | .describe( 21 | 'Used to identify the type of logout Options:1(UserID UserID),2(IdpCredentialID),3(SessionUUID Session uuid)', 22 | ), 23 | terminal_type: z 24 | .array(z.number()) 25 | .describe( 26 | 'Logout terminal_type, default all logout.- 1:pc- 2:web- 3:android- 4:iOS- 5:server- 6:old version mini program- 8:other mobile', 27 | ) 28 | .optional(), 29 | user_id: z 30 | .string() 31 | .describe( 32 | 'User ID categories, is consistent with the query parameter user_id_type.required when logout_type = 1', 33 | ) 34 | .optional(), 35 | logout_reason: z 36 | .number() 37 | .describe( 38 | 'Logout prompt, optional.- 34:You have changed your login password, please log in again.- 35:Your login status has expired, please log in again. - 36:Your password has expired. Please use the Forgot Password function on the login page to change your password and log in again', 39 | ) 40 | .optional(), 41 | sid: z 42 | .string() 43 | .describe('The session that needs to be logged out accurately, required when logout_type = 3') 44 | .optional(), 45 | }), 46 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 47 | }, 48 | }; 49 | export const passportV1SessionQuery = { 50 | project: 'passport', 51 | name: 'passport.v1.session.query', 52 | sdkName: 'passport.v1.session.query', 53 | path: '/open-apis/passport/v1/sessions/query', 54 | httpMethod: 'POST', 55 | description: 56 | "[Feishu/Lark]-Authenticate and Authorize-Login state management-Obtain desensitized user login information in batches-This interface is used to query the user's login information", 57 | accessTokens: ['tenant'], 58 | schema: { 59 | data: z.object({ user_ids: z.array(z.string()).describe('User ID').optional() }), 60 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 61 | }, 62 | }; 63 | export const passportV1Tools = [passportV1SessionLogout, passportV1SessionQuery]; 64 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/report_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type reportV1ToolName = 'report.v1.rule.query' | 'report.v1.ruleView.remove' | 'report.v1.task.query'; 3 | export const reportV1RuleQuery = { 4 | project: 'report', 5 | name: 'report.v1.rule.query', 6 | sdkName: 'report.v1.rule.query', 7 | path: '/open-apis/report/v1/rules/query', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-Report-Rule-Query rules-Query rules', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | params: z.object({ 13 | rule_name: z.string().describe('Rule name'), 14 | include_deleted: z 15 | .number() 16 | .describe( 17 | 'Whether to include deleted, not deleted by default Options:0(Exclude Does not include deleted),1(Include Include deleted)', 18 | ) 19 | .optional(), 20 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional(), 21 | }), 22 | }, 23 | }; 24 | export const reportV1RuleViewRemove = { 25 | project: 'report', 26 | name: 'report.v1.ruleView.remove', 27 | sdkName: 'report.v1.ruleView.remove', 28 | path: '/open-apis/report/v1/rules/:rule_id/views/remove', 29 | httpMethod: 'POST', 30 | description: '[Feishu/Lark]-Report-Rule view-Remove rule board-Remove rule board', 31 | accessTokens: ['tenant'], 32 | schema: { 33 | data: z.object({ 34 | user_ids: z 35 | .array(z.string()) 36 | .describe( 37 | 'If the list is empty and, deletes the full user view under the rule. If the list is not empty, then deletes the specified user view. The size limit is 200', 38 | ) 39 | .optional(), 40 | }), 41 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 42 | path: z.object({ rule_id: z.string().describe('Reporting tule ID') }), 43 | }, 44 | }; 45 | export const reportV1TaskQuery = { 46 | project: 'report', 47 | name: 'report.v1.task.query', 48 | sdkName: 'report.v1.task.query', 49 | path: '/open-apis/report/v1/tasks/query', 50 | httpMethod: 'POST', 51 | description: '[Feishu/Lark]-Report-Task-Query tasks-Query tasks', 52 | accessTokens: ['tenant'], 53 | schema: { 54 | data: z.object({ 55 | commit_start_time: z.number().describe('Commit start time timestamp'), 56 | commit_end_time: z.number().describe('End of submission timestamp'), 57 | rule_id: z.string().describe('Reporting rule ID').optional(), 58 | user_id: z.string().describe('User ID').optional(), 59 | page_token: z 60 | .string() 61 | .describe( 62 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 63 | ), 64 | page_size: z.number().describe('Number of items returned by a single page'), 65 | }), 66 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('User ID type').optional() }), 67 | }, 68 | }; 69 | export const reportV1Tools = [reportV1RuleQuery, reportV1RuleViewRemove, reportV1TaskQuery]; 70 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/security_and_compliance_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type securityAndComplianceV1ToolName = 'security_and_compliance.v1.openapiLog.listData'; 3 | export const securityAndComplianceV1OpenapiLogListData = { 4 | project: 'security_and_compliance', 5 | name: 'security_and_compliance.v1.openapiLog.listData', 6 | sdkName: 'security_and_compliance.v1.openapiLog.listData', 7 | path: '/open-apis/security_and_compliance/v1/openapi_logs/list_data', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-security_and_compliance-OpenAPI Audit Log-Obtain OpenAPI audit log-This api is used to obtain OpenAPI audit log', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | api_keys: z 15 | .array(z.string()) 16 | .describe( 17 | 'Feishu OpenAPI definition, reference: ', 18 | ) 19 | .optional(), 20 | start_time: z.number().describe('Starting timestamp in seconds').optional(), 21 | end_time: z.number().describe('Termination timestamp in seconds').optional(), 22 | app_id: z 23 | .string() 24 | .describe( 25 | 'The unique identifier of the application calling OpenAPI, can be obtained by going to > Application details page > Certificate and Basic Information', 26 | ) 27 | .optional(), 28 | page_size: z.number().describe('paging size').optional(), 29 | page_token: z 30 | .string() 31 | .describe( 32 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 33 | ) 34 | .optional(), 35 | }), 36 | }, 37 | }; 38 | export const securityAndComplianceV1Tools = [securityAndComplianceV1OpenapiLogListData]; 39 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/speech_to_text_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type speechToTextV1ToolName = 3 | | 'speech_to_text.v1.speech.fileRecognize' 4 | | 'speech_to_text.v1.speech.streamRecognize'; 5 | export const speechToTextV1SpeechFileRecognize = { 6 | project: 'speech_to_text', 7 | name: 'speech_to_text.v1.speech.fileRecognize', 8 | sdkName: 'speech_to_text.v1.speech.fileRecognize', 9 | path: '/open-apis/speech_to_text/v1/speech/file_recognize', 10 | httpMethod: 'POST', 11 | description: 12 | '[Feishu/Lark]-AI-Speech recognition-Audio file speech recognition-An audio speech recognition API is provided to recognize the entire audio file (less than 60s) at one time', 13 | accessTokens: ['tenant'], 14 | schema: { 15 | data: z.object({ 16 | speech: z 17 | .object({ speech: z.string().describe('Content of the base64-encoded audio file').optional() }) 18 | .describe('Audio resources'), 19 | config: z 20 | .object({ 21 | file_id: z 22 | .string() 23 | .describe( 24 | 'A 16-bit string generated by a user to identify a file. The string can only contain letters, numbers, and underscores', 25 | ), 26 | format: z.string().describe('Audio format. Only pcm supported'), 27 | engine_type: z 28 | .string() 29 | .describe('Engine type. Only 16k_auto that allows a mix of Chinese and English is supported'), 30 | }) 31 | .describe('Configures properties'), 32 | }), 33 | }, 34 | }; 35 | export const speechToTextV1SpeechStreamRecognize = { 36 | project: 'speech_to_text', 37 | name: 'speech_to_text.v1.speech.streamRecognize', 38 | sdkName: 'speech_to_text.v1.speech.streamRecognize', 39 | path: '/open-apis/speech_to_text/v1/speech/stream_recognize', 40 | httpMethod: 'POST', 41 | description: 42 | '[Feishu/Lark]-AI-Speech recognition-Streaming speech recognition-A streaming speech recognition API is provided to input an audio clip by clip and receive recognition results in real time. Each audio clip is recommended to be within 100 to 200 ms', 43 | accessTokens: ['tenant'], 44 | schema: { 45 | data: z.object({ 46 | speech: z 47 | .object({ speech: z.string().describe('Content of the base64-encoded audio file').optional() }) 48 | .describe('Audio resources'), 49 | config: z 50 | .object({ 51 | stream_id: z 52 | .string() 53 | .describe( 54 | 'A 16-bit string generated by a user to identify the same data stream. The string can only contain letters, numbers, and underscores', 55 | ), 56 | sequence_id: z 57 | .number() 58 | .describe('Sequence number of a data stream clip, starting from 0 and incremented by 1 for each request'), 59 | action: z 60 | .number() 61 | .describe( 62 | 'Indicates the data packet sent to the API in a request. 1: The first packet. 2: The last packet. A result will be returned. 3: The cancelled data packets,no final result is returned. 0: Transfer data packets in the middle of voice transmission', 63 | ), 64 | format: z.string().describe('Audio format. Only pcm supported'), 65 | engine_type: z 66 | .string() 67 | .describe('Engine type. Only 16k_auto that allows a mix of Chinese and English is supported'), 68 | }) 69 | .describe('Configures properties'), 70 | }), 71 | }, 72 | }; 73 | export const speechToTextV1Tools = [speechToTextV1SpeechFileRecognize, speechToTextV1SpeechStreamRecognize]; 74 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/tenant_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type tenantV2ToolName = 'tenant.v2.tenantProductAssignInfo.query' | 'tenant.v2.tenant.query'; 3 | export const tenantV2TenantProductAssignInfoQuery = { 4 | project: 'tenant', 5 | name: 'tenant.v2.tenantProductAssignInfo.query', 6 | sdkName: 'tenant.v2.tenantProductAssignInfo.query', 7 | path: '/open-apis/tenant/v2/tenant/assign_info_list/query', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-Company Information-Tenant Product Assign Info-Obtain company assign information-Obtain the seat list to be allocated under the tenant, including seat name, subscription ID, quantity and validity period', 11 | accessTokens: ['tenant'], 12 | schema: {}, 13 | }; 14 | export const tenantV2TenantQuery = { 15 | project: 'tenant', 16 | name: 'tenant.v2.tenant.query', 17 | sdkName: 'tenant.v2.tenant.query', 18 | path: '/open-apis/tenant/v2/tenant/query', 19 | httpMethod: 'GET', 20 | description: 21 | '[Feishu/Lark]-Company Information-Obtain company information-Obtains company information such as the company name and the company ID', 22 | accessTokens: ['tenant'], 23 | schema: {}, 24 | }; 25 | export const tenantV2Tools = [tenantV2TenantProductAssignInfoQuery, tenantV2TenantQuery]; 26 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/translation_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type translationV1ToolName = 'translation.v1.text.detect' | 'translation.v1.text.translate'; 3 | export const translationV1TextDetect = { 4 | project: 'translation', 5 | name: 'translation.v1.text.detect', 6 | sdkName: 'translation.v1.text.detect', 7 | path: '/open-apis/translation/v1/text/detect', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-AI-Machine Translation-Text language recognition-Machine translation (MT), supporting recognition of over 100 languages. The return value is ISO 639-1 compliant', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ text: z.string().describe('Text whose language is to be recognized') }), 14 | }, 15 | }; 16 | export const translationV1TextTranslate = { 17 | project: 'translation', 18 | name: 'translation.v1.text.translate', 19 | sdkName: 'translation.v1.text.translate', 20 | path: '/open-apis/translation/v1/text/translate', 21 | httpMethod: 'POST', 22 | description: 23 | '[Feishu/Lark]-AI-Machine Translation-Translate text-The following languages are supported for translation: "Zh": Chinese ; "Zh-Hant": Traditional Chinese ; "En": English; " Ja ": Japanese ; " Ru ": Russian ; " de ": German ; " Fr ": French ; "It": Italian ; " pl ": Polish ; " Th ": Thai ; "Hi": Hindi ; "Id": Indonesian ; " es ": Spanish ; " Pt ": Portuguese ; " Ko ": Korean ; " vi ": Vietnamese', 24 | accessTokens: ['tenant'], 25 | schema: { 26 | data: z.object({ 27 | source_language: z.string().describe('Source language'), 28 | text: z.string().describe('Source text, character limit is 1,000'), 29 | target_language: z.string().describe('Target language'), 30 | glossary: z 31 | .array(z.object({ from: z.string().describe('Associated text'), to: z.string().describe('Translation') })) 32 | .describe('Request level glossary with at most 128 terms, valid only in this translation') 33 | .optional(), 34 | }), 35 | }, 36 | }; 37 | export const translationV1Tools = [translationV1TextDetect, translationV1TextTranslate]; 38 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/verification_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type verificationV1ToolName = 'verification.v1.verification.get'; 3 | export const verificationV1VerificationGet = { 4 | project: 'verification', 5 | name: 'verification.v1.verification.get', 6 | sdkName: 'verification.v1.verification.get', 7 | path: '/open-apis/verification/v1/verification', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-Verification Information-Obtain verification information-获取认证状态', 10 | accessTokens: ['tenant'], 11 | schema: {}, 12 | }; 13 | export const verificationV1Tools = [verificationV1VerificationGet]; 14 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/wiki_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type wikiV1ToolName = 'wiki.v1.node.search'; 3 | export const wikiV1NodeSearch = { 4 | project: 'wiki', 5 | name: 'wiki.v1.node.search', 6 | sdkName: 'wiki.v1.node.search', 7 | path: '/open-apis/wiki/v1/nodes/search', 8 | httpMethod: 'POST', 9 | description: '[Feishu/Lark]-Docs-Wiki-Search Wiki', 10 | accessTokens: ['user'], 11 | schema: { 12 | data: z.object({ query: z.string(), space_id: z.string().optional(), node_id: z.string().optional() }), 13 | params: z.object({ page_token: z.string().optional(), page_size: z.number().optional() }), 14 | useUAT: z.boolean().describe('Use user access token, otherwise use tenant access token').optional(), 15 | }, 16 | }; 17 | export const wikiV1Tools = [wikiV1NodeSearch]; 18 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/en/gen-tools/zod/workplace_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type workplaceV1ToolName = 3 | | 'workplace.v1.customWorkplaceAccessData.search' 4 | | 'workplace.v1.workplaceAccessData.search' 5 | | 'workplace.v1.workplaceBlockAccessData.search'; 6 | export const workplaceV1CustomWorkplaceAccessDataSearch = { 7 | project: 'workplace', 8 | name: 'workplace.v1.customWorkplaceAccessData.search', 9 | sdkName: 'workplace.v1.customWorkplaceAccessData.search', 10 | path: '/open-apis/workplace/v1/custom_workplace_access_data/search', 11 | httpMethod: 'POST', 12 | description: 13 | '[Feishu/Lark]-Workplace-workplace access data-Get Custom Workplace Access Data-Get Custom Workplace Access Data', 14 | accessTokens: ['tenant'], 15 | schema: { 16 | params: z.object({ 17 | from_date: z.string(), 18 | to_date: z.string(), 19 | page_size: z.number(), 20 | page_token: z 21 | .string() 22 | .describe( 23 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 24 | ) 25 | .optional(), 26 | custom_workplace_id: z 27 | .string() 28 | .describe( 29 | 'Custom workplace ID,which is not mandatory. When empty, all custom workplace data is responsed. How to obtain a custom workplace ID: You can go to Feishu Admin>Workplace>Custom Workplace, click on the settings of the specified workplace to enter the settings page; Click the "Settings" button at the top three times with the mouse to display the ID, and then copy the ID', 30 | ) 31 | .optional(), 32 | }), 33 | }, 34 | }; 35 | export const workplaceV1WorkplaceAccessDataSearch = { 36 | project: 'workplace', 37 | name: 'workplace.v1.workplaceAccessData.search', 38 | sdkName: 'workplace.v1.workplaceAccessData.search', 39 | path: '/open-apis/workplace/v1/workplace_access_data/search', 40 | httpMethod: 'POST', 41 | description: 42 | '[Feishu/Lark]-Workplace-workplace access data-search workplace access data-Get Workplace Access Data, including default workplace and custom workplace', 43 | accessTokens: ['tenant'], 44 | schema: { 45 | params: z.object({ 46 | from_date: z.string(), 47 | to_date: z.string(), 48 | page_size: z.number(), 49 | page_token: z 50 | .string() 51 | .describe( 52 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 53 | ) 54 | .optional(), 55 | }), 56 | }, 57 | }; 58 | export const workplaceV1WorkplaceBlockAccessDataSearch = { 59 | project: 'workplace', 60 | name: 'workplace.v1.workplaceBlockAccessData.search', 61 | sdkName: 'workplace.v1.workplaceBlockAccessData.search', 62 | path: '/open-apis/workplace/v1/workplace_block_access_data/search', 63 | httpMethod: 'POST', 64 | description: 65 | '[Feishu/Lark]-Workplace-workplace access data-Get Block Access Data-Get Custom Workplace Block Access Data', 66 | accessTokens: ['tenant'], 67 | schema: { 68 | params: z.object({ 69 | from_date: z.string(), 70 | to_date: z.string(), 71 | page_size: z.number(), 72 | page_token: z 73 | .string() 74 | .describe( 75 | 'Page identifier. It is not filled in the first request, indicating traversal from the beginning; when there will be more groups, the new page_token will be returned at the same time, and the next traversal can use the page_token to get more groups', 76 | ) 77 | .optional(), 78 | block_id: z 79 | .string() 80 | .describe( 81 | 'BlockID. You can go to Feishu Admin>Workplace>Custom Workplace, select the specified workplace and enter the Workplace Builder. Click on a block to view the "BlockID" below the block name in the right panel of the page', 82 | ) 83 | .optional(), 84 | }), 85 | }, 86 | }; 87 | export const workplaceV1Tools = [ 88 | workplaceV1CustomWorkplaceAccessDataSearch, 89 | workplaceV1WorkplaceAccessDataSearch, 90 | workplaceV1WorkplaceBlockAccessDataSearch, 91 | ]; 92 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/index.ts: -------------------------------------------------------------------------------- 1 | import { BuiltinToolName, BuiltinTools } from './en/builtin-tools'; 2 | import { BuiltinTools as BuiltinToolsZh } from './zh/builtin-tools'; 3 | 4 | import { ToolName as GenToolName, GenTools as GenToolsEn, ProjectName as GenProjectName } from './en/gen-tools'; 5 | import { GenTools as GenToolsZh } from './zh/gen-tools'; 6 | 7 | export type ToolName = GenToolName | BuiltinToolName; 8 | export type ProjectName = GenProjectName; 9 | 10 | export const AllTools = [...GenToolsEn, ...BuiltinTools]; 11 | export const AllToolsZh = [...GenToolsZh, ...BuiltinToolsZh]; 12 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/builtin-tools/docx/builtin.ts: -------------------------------------------------------------------------------- 1 | import { McpTool } from '../../../../types'; 2 | import * as lark from '@larksuiteoapi/node-sdk'; 3 | import { ReadStream } from 'fs'; 4 | import { Readable } from 'stream'; 5 | import { z } from 'zod'; 6 | 7 | // 工具名称类型 8 | export type docxBuiltinToolName = 'docx.builtin.search' | 'docx.builtin.import'; 9 | 10 | export const larkDocxBuiltinSearchTool: McpTool = { 11 | project: 'docx', 12 | name: 'docx.builtin.search', 13 | accessTokens: ['user'], 14 | description: '[飞书/Lark] - 云文档-文档 - 搜索文档 - 搜索云文档,只支持user_access_token', 15 | schema: { 16 | data: z.object({ 17 | search_key: z.string().describe('搜索关键词'), 18 | count: z.number().describe('指定搜索返回的文件数量。取值范围为 [0,50]。').optional(), 19 | offset: z 20 | .number() 21 | .describe( 22 | '指定搜索的偏移量,该参数最小为 0,即不偏移。该参数的值与返回的文件数量之和不得大于或等于 200(即 offset + count < 200)。', 23 | ) 24 | .optional(), 25 | owner_ids: z.array(z.string()).describe('文件所有者的 Open ID').optional(), 26 | chat_ids: z.array(z.string()).describe('文件所在群的 ID').optional(), 27 | docs_types: z 28 | .array(z.enum(['doc', 'sheet', 'slides', 'bitable', 'mindnote', 'file'])) 29 | .describe( 30 | '文件类型,支持以下枚举:doc:旧版文档;sheet:电子表格;slides:幻灯片;bitable:多维表格;mindnote:思维笔记;file:文件', 31 | ) 32 | .optional(), 33 | }), 34 | useUAT: z.boolean().describe('是否使用用户身份请求,false则使用应用身份请求').optional(), 35 | }, 36 | customHandler: async (client, params, options): Promise => { 37 | try { 38 | const { userAccessToken } = options || {}; 39 | 40 | if (!userAccessToken) { 41 | return { 42 | isError: true, 43 | content: [{ type: 'text' as const, text: '当前未配置 userAccessToken' }], 44 | }; 45 | } 46 | 47 | const response = await client.request( 48 | { 49 | method: 'POST', 50 | url: '/open-apis/suite/docs-api/search/object', 51 | data: params.data, 52 | }, 53 | lark.withUserAccessToken(userAccessToken), 54 | ); 55 | 56 | return { 57 | content: [ 58 | { 59 | type: 'text' as const, 60 | text: `搜索文档请求成功: ${JSON.stringify(response.data ?? response)}`, 61 | }, 62 | ], 63 | }; 64 | } catch (error) { 65 | return { 66 | isError: true, 67 | content: [ 68 | { 69 | type: 'text' as const, 70 | text: `搜索文档请求失败: ${JSON.stringify((error as any).response.data)}`, 71 | }, 72 | ], 73 | }; 74 | } 75 | }, 76 | }; 77 | 78 | export const larkDocxBuiltinImportTool: McpTool = { 79 | project: 'docx', 80 | name: 'docx.builtin.import', 81 | accessTokens: ['user', 'tenant'], 82 | description: '[飞书/Lark] - 云文档-文档 - 导入文档 - 导入云文档,最大20MB', 83 | schema: { 84 | data: z 85 | .object({ 86 | markdown: z.string().describe('markdown文件内容'), 87 | file_name: z.string().describe('文件名').max(27).optional(), 88 | }) 89 | .describe('请求体'), 90 | useUAT: z.boolean().describe('使用用户身份请求,否则为应用身份').optional(), 91 | }, 92 | customHandler: async (client, params, options): Promise => { 93 | try { 94 | const { userAccessToken } = options || {}; 95 | const file = Readable.from(params.data.markdown) as ReadStream; 96 | 97 | const data = { 98 | file_name: 'docx.md', 99 | parent_type: 'ccm_import_open' as const, 100 | parent_node: '/', 101 | size: Buffer.byteLength(params.data.markdown), 102 | file, 103 | extra: JSON.stringify({ obj_type: 'docx', file_extension: 'md' }), 104 | }; 105 | 106 | const response = 107 | userAccessToken && params.useUAT 108 | ? await client.drive.media.uploadAll({ data }, lark.withUserAccessToken(userAccessToken)) 109 | : await client.drive.media.uploadAll({ data }); 110 | 111 | if (!response?.file_token) { 112 | return { 113 | isError: true, 114 | content: [{ type: 'text' as const, text: '导入文档失败,请检查markdown文件内容' }], 115 | }; 116 | } 117 | 118 | const importData = { 119 | file_extension: 'md', 120 | file_name: params.data.file_name, 121 | file_token: response?.file_token, 122 | type: 'docx', 123 | point: { 124 | mount_type: 1, 125 | mount_key: '', 126 | }, 127 | }; 128 | 129 | const importResponse = 130 | userAccessToken && params.useUAT 131 | ? await client.drive.importTask.create({ data: importData }, lark.withUserAccessToken(userAccessToken)) 132 | : await client.drive.importTask.create({ data: importData }); 133 | 134 | const taskId = importResponse.data?.ticket; 135 | if (!taskId) { 136 | return { 137 | isError: true, 138 | content: [{ type: 'text' as const, text: '导入文档失败,请检查markdown文件内容' }], 139 | }; 140 | } 141 | 142 | for (let i = 0; i < 5; i++) { 143 | const taskResponse = 144 | userAccessToken && params.useUAT 145 | ? await client.drive.importTask.get({ path: { ticket: taskId } }, lark.withUserAccessToken(userAccessToken)) 146 | : await client.drive.importTask.get({ path: { ticket: taskId } }); 147 | 148 | if (taskResponse.data?.result?.job_status === 0) { 149 | return { 150 | content: [ 151 | { 152 | type: 'text' as const, 153 | text: `导入文档请求成功: ${JSON.stringify(taskResponse.data ?? taskResponse)}`, 154 | }, 155 | ], 156 | }; 157 | } else if (taskResponse.data?.result?.job_status !== 1 && taskResponse.data?.result?.job_status !== 2) { 158 | return { 159 | content: [{ type: 'text' as const, text: '导入文档失败,请稍后再试' + JSON.stringify(taskResponse.data) }], 160 | }; 161 | } 162 | await new Promise((resolve) => setTimeout(resolve, 1000)); 163 | } 164 | 165 | return { 166 | content: [ 167 | { 168 | type: 'text' as const, 169 | text: '导入文档失败,请稍后再试', 170 | }, 171 | ], 172 | }; 173 | } catch (error) { 174 | return { 175 | isError: true, 176 | content: [ 177 | { 178 | type: 'text' as const, 179 | text: `导入文档请求失败: ${JSON.stringify((error as any)?.response?.data || error)}`, 180 | }, 181 | ], 182 | }; 183 | } 184 | }, 185 | }; 186 | 187 | export const docxBuiltinTools = [larkDocxBuiltinSearchTool, larkDocxBuiltinImportTool]; 188 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/builtin-tools/im/buildin.ts: -------------------------------------------------------------------------------- 1 | import { McpTool } from '../../../../types'; 2 | import { z } from 'zod'; 3 | 4 | // 工具名称类型 5 | export type imBuiltinToolName = 'im.builtin.batchSend'; 6 | 7 | export const larkImBuiltinBatchSendTool: McpTool = { 8 | project: 'im', 9 | name: 'im.builtin.batchSend', 10 | accessTokens: ['tenant'], 11 | description: '[飞书/Lark] - 批量发送消息 - 支持给多个用户、部门批量发送消息,支持文本和卡片', 12 | schema: { 13 | data: z.object({ 14 | msg_type: z 15 | .enum(['text', 'post', 'image', 'interactive', 'share_chat']) 16 | .describe( 17 | '消息类型,如果 msg_type 取值为 text、image、post 或者 share_chat,则消息内容需要传入 content 参数内。如果 msg_type 取值为 interactive,则消息内容需要传入 card 参数内。富文本类型(post)的消息,不支持使用 md 标签。', 18 | ), 19 | content: z 20 | .any() 21 | .describe( 22 | '消息内容,JSON 结构。该参数的取值与 msg_type 对应,例如 msg_type 取值为 text,则该参数需要传入文本类型的内容。', 23 | ) 24 | .optional(), 25 | card: z 26 | .any() 27 | .describe( 28 | '卡片内容,JSON 结构。该参数的取值与 msg_type 对应,仅当 msg_type 取值为 interactive 时,需要将卡片内容传入当前参数。当 msg_type 取值不为 interactive 时,消息内容需要传入到 content 参数。', 29 | ) 30 | .optional(), 31 | open_ids: z.array(z.string()).describe('接收者open_id列表').optional(), 32 | user_ids: z.array(z.string()).describe('接收者user_id列表').optional(), 33 | union_ids: z.array(z.string()).describe('接收者union_id列表').optional(), 34 | department_ids: z 35 | .array(z.string()) 36 | .describe('部门 ID 列表。列表内支持传入部门 department_id 和 open_department_id') 37 | .optional(), 38 | }), 39 | }, 40 | customHandler: async (client, params): Promise => { 41 | try { 42 | const { data } = params; 43 | const response = await client.request({ 44 | method: 'POST', 45 | url: '/open-apis/message/v4/batch_send', 46 | data, 47 | }); 48 | return { 49 | content: [ 50 | { 51 | type: 'text' as const, 52 | text: `批量发送消息请求成功: ${JSON.stringify(response.data ?? response)}`, 53 | }, 54 | ], 55 | }; 56 | } catch (error) { 57 | return { 58 | isError: true, 59 | content: [ 60 | { 61 | type: 'text' as const, 62 | text: `批量发送消息请求失败: ${JSON.stringify((error as any)?.response?.data || error)}`, 63 | }, 64 | ], 65 | }; 66 | } 67 | }, 68 | }; 69 | 70 | export const imBuiltinTools = [larkImBuiltinBatchSendTool]; 71 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/builtin-tools/index.ts: -------------------------------------------------------------------------------- 1 | import { docxBuiltinToolName, docxBuiltinTools } from './docx/builtin'; 2 | import { imBuiltinToolName, imBuiltinTools } from './im/buildin'; 3 | 4 | export const BuiltinTools = [...docxBuiltinTools, ...imBuiltinTools]; 5 | 6 | export type BuiltinToolName = docxBuiltinToolName | imBuiltinToolName; 7 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/application_v5.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type applicationV5ToolName = 'application.v5.application.favourite' | 'application.v5.application.recommend'; 3 | export const applicationV5ApplicationFavourite = { 4 | project: 'application', 5 | name: 'application.v5.application.favourite', 6 | sdkName: 'application.v5.application.favourite', 7 | path: '/open-apis/application/v5/applications/favourite', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-工作台-我的常用-获取用户自定义常用的应用', 10 | accessTokens: ['user'], 11 | schema: { 12 | params: z.object({ 13 | language: z 14 | .enum(['zh_cn', 'en_us', 'ja_jp']) 15 | .describe('应用信息的语言版本 Options:zh_cn(Chinese 中文),en_us(English 英文),ja_jp(Japanese 日文)') 16 | .optional(), 17 | page_token: z 18 | .string() 19 | .describe( 20 | '分页标记,不填表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 21 | ) 22 | .optional(), 23 | page_size: z.number().describe('单页需求最大个数(最大 100),不传默认10个').optional(), 24 | }), 25 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 26 | }, 27 | }; 28 | export const applicationV5ApplicationRecommend = { 29 | project: 'application', 30 | name: 'application.v5.application.recommend', 31 | sdkName: 'application.v5.application.recommend', 32 | path: '/open-apis/application/v5/applications/recommend', 33 | httpMethod: 'GET', 34 | description: '[Feishu/Lark]-工作台-我的常用-获取管理员推荐的应用', 35 | accessTokens: ['user'], 36 | schema: { 37 | params: z.object({ 38 | language: z 39 | .enum(['zh_cn', 'en_us', 'ja_jp']) 40 | .describe('应用信息的语言版本 Options:zh_cn(Chinese 中文),en_us(English 英文),ja_jp(Japanese 日文)') 41 | .optional(), 42 | recommend_type: z 43 | .enum(['user_unremovable', 'user_removable']) 44 | .describe( 45 | '推荐应用类型,默认为用户不可移除的推荐应用列表 Options:user_unremovable(UserUnremovable 用户不可移除的推荐应用列表),user_removable(UserRemovable 用户可移除的推荐应用列表)', 46 | ) 47 | .optional(), 48 | page_token: z 49 | .string() 50 | .describe( 51 | '分页标记,不填表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 52 | ) 53 | .optional(), 54 | page_size: z.number().describe('单页需求最大个数(最大 100),不传默认10个').optional(), 55 | }), 56 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 57 | }, 58 | }; 59 | export const applicationV5Tools = [applicationV5ApplicationFavourite, applicationV5ApplicationRecommend]; 60 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/auth_v3.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type authV3ToolName = 3 | | 'auth.v3.auth.appAccessToken' 4 | | 'auth.v3.auth.appAccessTokenInternal' 5 | | 'auth.v3.auth.appTicketResend' 6 | | 'auth.v3.auth.tenantAccessToken' 7 | | 'auth.v3.auth.tenantAccessTokenInternal'; 8 | export const authV3AuthAppAccessToken = { 9 | project: 'auth', 10 | name: 'auth.v3.auth.appAccessToken', 11 | sdkName: 'auth.v3.auth.appAccessToken', 12 | path: '/open-apis/auth/v3/app_access_token', 13 | httpMethod: 'POST', 14 | description: '[Feishu/Lark]-认证及授权-获取访问凭证-商店应用获取 app_access_token', 15 | accessTokens: undefined, 16 | schema: { 17 | data: z.object({ 18 | app_id: z.string().describe('应用唯一标识,创建应用后获得'), 19 | app_secret: z.string().describe('应用秘钥,创建应用后获得'), 20 | app_ticket: z.string().describe('平台定时推送给应用的临时凭证,通过事件监听机制获得'), 21 | }), 22 | }, 23 | }; 24 | export const authV3AuthAppAccessTokenInternal = { 25 | project: 'auth', 26 | name: 'auth.v3.auth.appAccessTokenInternal', 27 | sdkName: 'auth.v3.auth.appAccessTokenInternal', 28 | path: '/open-apis/auth/v3/app_access_token/internal', 29 | httpMethod: 'POST', 30 | description: '[Feishu/Lark]-认证及授权-获取访问凭证-自建应用获取 app_access_token', 31 | accessTokens: undefined, 32 | schema: { 33 | data: z.object({ 34 | app_id: z.string().describe('应用唯一标识,创建应用后获得'), 35 | app_secret: z.string().describe('应用秘钥,创建应用后获得'), 36 | }), 37 | }, 38 | }; 39 | export const authV3AuthAppTicketResend = { 40 | project: 'auth', 41 | name: 'auth.v3.auth.appTicketResend', 42 | sdkName: 'auth.v3.auth.appTicketResend', 43 | path: '/open-apis/auth/v3/app_ticket/resend', 44 | httpMethod: 'POST', 45 | description: '[Feishu/Lark]-认证及授权-获取访问凭证-重新获取 app_ticket', 46 | accessTokens: undefined, 47 | schema: { 48 | data: z.object({ 49 | app_id: z.string().describe('应用唯一标识,创建应用后获得'), 50 | app_secret: z.string().describe('应用秘钥,创建应用后获得'), 51 | }), 52 | }, 53 | }; 54 | export const authV3AuthTenantAccessToken = { 55 | project: 'auth', 56 | name: 'auth.v3.auth.tenantAccessToken', 57 | sdkName: 'auth.v3.auth.tenantAccessToken', 58 | path: '/open-apis/auth/v3/tenant_access_token', 59 | httpMethod: 'POST', 60 | description: '[Feishu/Lark]-认证及授权-获取访问凭证-商店应用获取 tenant_access_token', 61 | accessTokens: undefined, 62 | schema: { 63 | data: z.object({ 64 | app_access_token: z.string().describe('应用唯一标识,创建应用'), 65 | tenant_key: z.string().describe('应用秘钥,创建应用后获得'), 66 | }), 67 | }, 68 | }; 69 | export const authV3AuthTenantAccessTokenInternal = { 70 | project: 'auth', 71 | name: 'auth.v3.auth.tenantAccessTokenInternal', 72 | sdkName: 'auth.v3.auth.tenantAccessTokenInternal', 73 | path: '/open-apis/auth/v3/tenant_access_token/internal', 74 | httpMethod: 'POST', 75 | description: '[Feishu/Lark]-认证及授权-获取访问凭证-自建应用获取 tenant_access_token', 76 | accessTokens: undefined, 77 | schema: { 78 | data: z.object({ 79 | app_id: z.string().describe('应用唯一标识,创建应用后获得'), 80 | app_secret: z.string().describe('应用秘钥,创建应用后获得'), 81 | }), 82 | }, 83 | }; 84 | export const authV3Tools = [ 85 | authV3AuthAppAccessToken, 86 | authV3AuthAppAccessTokenInternal, 87 | authV3AuthAppTicketResend, 88 | authV3AuthTenantAccessToken, 89 | authV3AuthTenantAccessTokenInternal, 90 | ]; 91 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/authen_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type authenV1ToolName = 'authen.v1.userInfo.get'; 3 | export const authenV1UserInfoGet = { 4 | project: 'authen', 5 | name: 'authen.v1.userInfo.get', 6 | sdkName: 'authen.v1.userInfo.get', 7 | path: '/open-apis/authen/v1/user_info', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-认证及授权-登录态管理-获取用户信息-通过 `user_access_token` 获取相关用户信息', 10 | accessTokens: ['user'], 11 | schema: { 12 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 13 | }, 14 | }; 15 | export const authenV1Tools = [authenV1UserInfoGet]; 16 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/board_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type boardV1ToolName = 'board.v1.whiteboardNode.list'; 3 | export const boardV1WhiteboardNodeList = { 4 | project: 'board', 5 | name: 'board.v1.whiteboardNode.list', 6 | sdkName: 'board.v1.whiteboardNode.list', 7 | path: '/open-apis/board/v1/whiteboards/:whiteboard_id/nodes', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-云文档-画板-节点-获取所有节点-获取画板内所有的节点', 10 | accessTokens: ['tenant', 'user'], 11 | schema: { 12 | path: z.object({ whiteboard_id: z.string().describe('画板唯一标识') }), 13 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 14 | }, 15 | }; 16 | export const boardV1Tools = [boardV1WhiteboardNodeList]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/compensation_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type compensationV1ToolName = 3 | | 'compensation.v1.archive.query' 4 | | 'compensation.v1.changeReason.list' 5 | | 'compensation.v1.indicator.list' 6 | | 'compensation.v1.itemCategory.list' 7 | | 'compensation.v1.item.list' 8 | | 'compensation.v1.plan.list'; 9 | export const compensationV1ArchiveQuery = { 10 | project: 'compensation', 11 | name: 'compensation.v1.archive.query', 12 | sdkName: 'compensation.v1.archive.query', 13 | path: '/open-apis/compensation/v1/archives/query', 14 | httpMethod: 'POST', 15 | description: '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-薪资档案-批量查询员工薪资档案-批量查询员工薪资档案', 16 | accessTokens: ['tenant', 'user'], 17 | schema: { 18 | data: z.object({ 19 | user_id_list: z.array(z.string()).describe('用户ID列表,获取方式可参考查询参数中的「user_id_type」字段'), 20 | tid_list: z.array(z.string()).describe('档案Tid列表').optional(), 21 | effective_start_date: z.string().describe('生效开始时间').optional(), 22 | effective_end_date: z.string().describe('生效结束时间').optional(), 23 | }), 24 | params: z.object({ 25 | page_size: z.number().describe('分页大小'), 26 | page_token: z 27 | .string() 28 | .describe( 29 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 30 | ) 31 | .optional(), 32 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_corehr_id']).describe('用户ID类型'), 33 | }), 34 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 35 | }, 36 | }; 37 | export const compensationV1ChangeReasonList = { 38 | project: 'compensation', 39 | name: 'compensation.v1.changeReason.list', 40 | sdkName: 'compensation.v1.changeReason.list', 41 | path: '/open-apis/compensation/v1/change_reasons', 42 | httpMethod: 'GET', 43 | description: '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-定调薪-批量查询定调薪原因-批量查询定调薪原因', 44 | accessTokens: ['tenant'], 45 | schema: { 46 | params: z.object({ 47 | page_size: z.number().describe('分页大小'), 48 | page_token: z 49 | .string() 50 | .describe( 51 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 52 | ) 53 | .optional(), 54 | }), 55 | }, 56 | }; 57 | export const compensationV1IndicatorList = { 58 | project: 'compensation', 59 | name: 'compensation.v1.indicator.list', 60 | sdkName: 'compensation.v1.indicator.list', 61 | path: '/open-apis/compensation/v1/indicators', 62 | httpMethod: 'GET', 63 | description: '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-薪资项/统计指标-批量查询薪资统计指标-批量查询薪资统计指标', 64 | accessTokens: ['tenant'], 65 | schema: { 66 | params: z.object({ 67 | page_size: z.number().describe('分页大小'), 68 | page_token: z 69 | .string() 70 | .describe( 71 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 72 | ) 73 | .optional(), 74 | }), 75 | }, 76 | }; 77 | export const compensationV1ItemCategoryList = { 78 | project: 'compensation', 79 | name: 'compensation.v1.itemCategory.list', 80 | sdkName: 'compensation.v1.itemCategory.list', 81 | path: '/open-apis/compensation/v1/item_categories', 82 | httpMethod: 'GET', 83 | description: 84 | '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-薪资项/统计指标-批量获取薪资项分类信息-批量获取薪资项分类信息', 85 | accessTokens: ['tenant'], 86 | schema: { 87 | params: z.object({ 88 | page_size: z.number().describe('分页大小').optional(), 89 | page_token: z 90 | .string() 91 | .describe( 92 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 93 | ) 94 | .optional(), 95 | }), 96 | }, 97 | }; 98 | export const compensationV1ItemList = { 99 | project: 'compensation', 100 | name: 'compensation.v1.item.list', 101 | sdkName: 'compensation.v1.item.list', 102 | path: '/open-apis/compensation/v1/items', 103 | httpMethod: 'GET', 104 | description: '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-薪资项/统计指标-批量查询薪资项-批量查询薪资项', 105 | accessTokens: ['tenant'], 106 | schema: { 107 | params: z.object({ 108 | page_size: z.number().describe('分页大小'), 109 | page_token: z 110 | .string() 111 | .describe( 112 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 113 | ) 114 | .optional(), 115 | }), 116 | }, 117 | }; 118 | export const compensationV1PlanList = { 119 | project: 'compensation', 120 | name: 'compensation.v1.plan.list', 121 | sdkName: 'compensation.v1.plan.list', 122 | path: '/open-apis/compensation/v1/plans', 123 | httpMethod: 'GET', 124 | description: 125 | '[Feishu/Lark]-飞书人事(企业版)-基础薪酬-薪资方案-批量查询薪资方案-- 此接口将返回全部薪资方案信息,包括薪资方案 ID、生效日期、薪资项/薪资统计指标等', 126 | accessTokens: ['tenant'], 127 | schema: { 128 | params: z.object({ 129 | page_size: z.number().describe('分页大小'), 130 | page_token: z 131 | .string() 132 | .describe( 133 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 134 | ) 135 | .optional(), 136 | }), 137 | }, 138 | }; 139 | export const compensationV1Tools = [ 140 | compensationV1ArchiveQuery, 141 | compensationV1ChangeReasonList, 142 | compensationV1IndicatorList, 143 | compensationV1ItemCategoryList, 144 | compensationV1ItemList, 145 | compensationV1PlanList, 146 | ]; 147 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/docs_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type docsV1ToolName = 'docs.v1.content.get'; 3 | export const docsV1ContentGet = { 4 | project: 'docs', 5 | name: 'docs.v1.content.get', 6 | sdkName: 'docs.v1.content.get', 7 | path: '/open-apis/docs/v1/content', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-云文档-通用-获取云文档内容-可获取云文档内容,当前只支持获取新版文档 Markdown 格式的内容', 10 | accessTokens: ['tenant', 'user'], 11 | schema: { 12 | params: z.object({ 13 | doc_token: z 14 | .string() 15 | .describe( 16 | '云文档的唯一标识。点击了解如何获取文档的 `doc_token`', 17 | ), 18 | doc_type: z.literal('docx').describe('云文档类型 Options:docx(新版文档)'), 19 | content_type: z.literal('markdown').describe('内容类型 Options:markdown(Markdown 格式)'), 20 | lang: z 21 | .enum(['zh', 'en', 'ja']) 22 | .describe( 23 | '云文档中存在 @用户 元素时,指定该用户名称的语言。默认 `zh`,即中文 Options:zh(中文),en(英文),ja(日文)', 24 | ) 25 | .optional(), 26 | }), 27 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 28 | }, 29 | }; 30 | export const docsV1Tools = [docsV1ContentGet]; 31 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/drive_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type driveV2ToolName = 3 | | 'drive.v2.fileLike.list' 4 | | 'drive.v2.permissionPublic.get' 5 | | 'drive.v2.permissionPublic.patch'; 6 | export const driveV2FileLikeList = { 7 | project: 'drive', 8 | name: 'drive.v2.fileLike.list', 9 | sdkName: 'drive.v2.fileLike.list', 10 | path: '/open-apis/drive/v2/files/:file_token/likes', 11 | httpMethod: 'GET', 12 | description: 13 | '[Feishu/Lark]-云文档-云空间-点赞-获取云文档的点赞者列表-获取指定云文档的点赞者列表并按点赞时间由近到远分页返回', 14 | accessTokens: ['tenant', 'user'], 15 | schema: { 16 | params: z.object({ 17 | file_type: z 18 | .enum(['doc', 'docx', 'file']) 19 | .describe( 20 | '云文档类型,如果该值为空或者与云文档实际类型不匹配,接口会返回失败。 Options:doc(旧版文档),docx(新版文档),file(文件)', 21 | ), 22 | page_size: z.number().describe('分页大小').optional(), 23 | page_token: z 24 | .string() 25 | .describe( 26 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 27 | ) 28 | .optional(), 29 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 30 | }), 31 | path: z.object({ 32 | file_token: z 33 | .string() 34 | .describe( 35 | '需要查询点赞者列表的云文档 token。', 36 | ), 37 | }), 38 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 39 | }, 40 | }; 41 | export const driveV2PermissionPublicGet = { 42 | project: 'drive', 43 | name: 'drive.v2.permissionPublic.get', 44 | sdkName: 'drive.v2.permissionPublic.get', 45 | path: '/open-apis/drive/v2/permissions/:token/public', 46 | httpMethod: 'GET', 47 | description: '[Feishu/Lark]-云文档-权限-设置 v2-获取云文档权限设置-该接口用于根据 filetoken 获取云文档的权限设置', 48 | accessTokens: ['tenant', 'user'], 49 | schema: { 50 | params: z.object({ 51 | type: z 52 | .enum(['doc', 'sheet', 'file', 'wiki', 'bitable', 'docx', 'mindnote', 'minutes', 'slides']) 53 | .describe( 54 | '文件类型,需要与文件的 token 相匹配 Options:doc(旧版文档),sheet(电子表格),file(云空间文件),wiki(知识库节点),bitable(多维表格),docx(新版文档),mindnote(思维笔记),minutes(妙记),slides(幻灯片)', 55 | ), 56 | }), 57 | path: z.object({ 58 | token: z 59 | .string() 60 | .describe( 61 | '文件的 token,获取方式见 ', 62 | ), 63 | }), 64 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 65 | }, 66 | }; 67 | export const driveV2PermissionPublicPatch = { 68 | project: 'drive', 69 | name: 'drive.v2.permissionPublic.patch', 70 | sdkName: 'drive.v2.permissionPublic.patch', 71 | path: '/open-apis/drive/v2/permissions/:token/public', 72 | httpMethod: 'PATCH', 73 | description: '[Feishu/Lark]-云文档-权限-设置 v2-更新云文档权限设置-该接口用于根据 filetoken 更新云文档的权限设置', 74 | accessTokens: ['tenant', 'user'], 75 | schema: { 76 | data: z.object({ 77 | external_access_entity: z 78 | .enum(['open', 'closed', 'allow_share_partner_tenant']) 79 | .describe( 80 | '允许内容被分享到组织外 Options:open(打开),closed(关闭),allow_share_partner_tenant(AllowSharePartnerTenant 允许分享给关联组织(只有租户后台设置仅允许关联组织分享,才能设置为该值))', 81 | ) 82 | .optional(), 83 | security_entity: z 84 | .enum(['anyone_can_view', 'anyone_can_edit', 'only_full_access']) 85 | .describe( 86 | '谁可以创建副本、打印、下载 Options:anyone_can_view(AnyoneCanView 拥有可阅读权限的用户),anyone_can_edit(AnyoneCanEdit 拥有可编辑权限的用户),only_full_access(OnlyFullAccess 拥有可管理权限(包括我)的用户)', 87 | ) 88 | .optional(), 89 | comment_entity: z 90 | .enum(['anyone_can_view', 'anyone_can_edit']) 91 | .describe( 92 | '谁可以评论 Options:anyone_can_view(AnyoneCanView 拥有可阅读权限的用户),anyone_can_edit(AnyoneCanEdit 拥有可编辑权限的用户)', 93 | ) 94 | .optional(), 95 | share_entity: z 96 | .enum(['anyone', 'same_tenant']) 97 | .describe( 98 | '谁可以添加和管理协作者-组织维度 Options:anyone(所有可阅读或编辑此文档的用户),same_tenant(SameTenant 组织内所有可阅读或编辑此文档的用户)', 99 | ) 100 | .optional(), 101 | manage_collaborator_entity: z 102 | .enum(['collaborator_can_view', 'collaborator_can_edit', 'collaborator_full_access']) 103 | .describe( 104 | '谁可以添加和管理协作者-协作者维度 Options:collaborator_can_view(CollaboratorCanView 拥有可阅读权限的协作者),collaborator_can_edit(CollaboratorCanEdit 拥有可编辑权限的协作者),collaborator_full_access(CollaboratorFullAccess 拥有可管理权限(包括我)的协作者)', 105 | ) 106 | .optional(), 107 | link_share_entity: z 108 | .enum([ 109 | 'tenant_readable', 110 | 'tenant_editable', 111 | 'partner_tenant_readable', 112 | 'partner_tenant_editable', 113 | 'anyone_readable', 114 | 'anyone_editable', 115 | 'closed', 116 | ]) 117 | .describe( 118 | '链接分享设置 Options:tenant_readable(TenantReadable 组织内获得链接的人可阅读),tenant_editable(TenantEditable 组织内获得链接的人可编辑),partner_tenant_readable(PartnerTenantReadable 关联组织的人可阅读(只有租户后台设置仅允许关联组织分享,才能设置为该值)),partner_tenant_editable(PartnerTenantEditable 关联组织的人可编辑(只有租户后台设置仅允许关联组织分享,才能设置为该值)),anyone_readable(AnyoneReadable 互联网上获得链接的任何人可阅读(仅external_access_entity=“open”时有效)),anyone_editable(AnyoneEditable 互联网上获得链接的任何人可编辑(仅external_access_entity=“open”时有效)),closed(关闭链接分享)', 119 | ) 120 | .optional(), 121 | copy_entity: z 122 | .enum(['anyone_can_view', 'anyone_can_edit', 'only_full_access']) 123 | .describe( 124 | '谁可以复制内容 Options:anyone_can_view(AnyoneCanView 拥有可阅读权限的用户),anyone_can_edit(AnyoneCanEdit 拥有可编辑权限的用户),only_full_access(OnlyFullAccess 拥有可管理权限(包括我)的协作者)', 125 | ) 126 | .optional(), 127 | }), 128 | params: z.object({ 129 | type: z 130 | .enum(['doc', 'sheet', 'file', 'wiki', 'bitable', 'docx', 'mindnote', 'minutes', 'slides']) 131 | .describe( 132 | '文件类型,需要与文件的 token 相匹配 Options:doc(旧版文档),sheet(电子表格),file(云空间文件),wiki(知识库节点),bitable(多维表格),docx(新版文档),mindnote(思维笔记),minutes(妙记),slides(幻灯片)', 133 | ), 134 | }), 135 | path: z.object({ 136 | token: z 137 | .string() 138 | .describe( 139 | '文件的 token,获取方式见 ', 140 | ), 141 | }), 142 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 143 | }, 144 | }; 145 | export const driveV2Tools = [driveV2FileLikeList, driveV2PermissionPublicGet, driveV2PermissionPublicPatch]; 146 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/ehr_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type ehrV1ToolName = 'ehr.v1.employee.list'; 3 | export const ehrV1EmployeeList = { 4 | project: 'ehr', 5 | name: 'ehr.v1.employee.list', 6 | sdkName: 'ehr.v1.employee.list', 7 | path: '/open-apis/ehr/v1/employees', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-飞书人事(标准版)-批量获取员工花名册信息-根据员工飞书用户 ID / 员工状态 / 雇员类型等搜索条件 ,批量获取员工花名册字段信息。字段包括「系统标准字段 / system_fields」和「自定义字段 / custom_fields」', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | params: z.object({ 14 | view: z 15 | .enum(['basic', 'full']) 16 | .describe( 17 | '返回数据类型,不传值默认为 basic。 Options:basic(概览,只返回 id、name 等基本信息),full(明细,返回系统标准字段和自定义字段集合)', 18 | ) 19 | .optional(), 20 | status: z 21 | .array( 22 | z 23 | .number() 24 | .describe( 25 | 'Options:1(to_be_onboarded 待入职),2(active 在职),3(onboarding_cancelled 已取消入职),4(offboarding 待离职),5(offboarded 已离职)', 26 | ), 27 | ) 28 | .describe('员工状态,不传代表查询所有员工状态实际在职 = 2&4可同时查询多个状态的记录,如 status=2&status=4') 29 | .optional(), 30 | type: z 31 | .array( 32 | z 33 | .number() 34 | .describe( 35 | 'Options:1(regular 全职),2(intern 实习),3(consultant 顾问),4(outsourcing 外包),5(contractor 劳务)', 36 | ), 37 | ) 38 | .describe( 39 | '人员类型,不传代表查询所有人员类型同时可使用自定义员工类型的 int 值进行查询,可通过下方接口获取到该租户的自定义员工类型的名称,参见 ', 40 | ) 41 | .optional(), 42 | start_time: z.string().describe('查询开始时间(入职时间 >= 此时间)').optional(), 43 | end_time: z.string().describe('查询结束时间(入职时间 <= 此时间)').optional(), 44 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 45 | user_ids: z 46 | .array(z.string()) 47 | .describe( 48 | 'user_id、open_id 或 union_id,默认为 open_id。如果传入的值不是 open_id,需要一并传入 user_id_type 参数。可一次查询多个 id 的用户,例如:user_ids=ou_8ebd4f35d7101ffdeb4771d7c8ec517e&user_ids=ou_7abc4f35d7101ffdeb4771dabcde', 49 | ) 50 | .optional(), 51 | page_token: z 52 | .string() 53 | .describe( 54 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 55 | ) 56 | .optional(), 57 | page_size: z.number().describe('分页大小,取值范围 1~100,默认 10').optional(), 58 | }), 59 | }, 60 | }; 61 | export const ehrV1Tools = [ehrV1EmployeeList]; 62 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/event_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type eventV1ToolName = 'event.v1.outboundIp.list'; 3 | export const eventV1OutboundIpList = { 4 | project: 'event', 5 | name: 'event.v1.outboundIp.list', 6 | sdkName: 'event.v1.outboundIp.list', 7 | path: '/open-apis/event/v1/outbound_ip', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-事件与回调-事件订阅-获取事件出口 IP-飞书开放平台向应用配置的回调地址推送事件时,是通过特定的 IP 发送出去的,应用可以通过本接口获取所有相关的 IP 地址', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | params: z.object({ 14 | page_size: z.number().describe('分页大小,默认10,取值范围 10-50').optional(), 15 | page_token: z 16 | .string() 17 | .describe( 18 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 19 | ) 20 | .optional(), 21 | }), 22 | }, 23 | }; 24 | export const eventV1Tools = [eventV1OutboundIpList]; 25 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/hire_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type hireV2ToolName = 'hire.v2.interviewRecord.get' | 'hire.v2.interviewRecord.list' | 'hire.v2.talent.get'; 3 | export const hireV2InterviewRecordGet = { 4 | project: 'hire', 5 | name: 'hire.v2.interviewRecord.get', 6 | sdkName: 'hire.v2.interviewRecord.get', 7 | path: '/open-apis/hire/v2/interview_records/:interview_record_id', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-候选人管理-投递流程-面试-获取面试评价详细信息(新版)-获取面试评价详细信息,如面试结论、面试得分和面试官等信息', 11 | accessTokens: ['tenant', 'user'], 12 | schema: { 13 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 14 | path: z.object({ 15 | interview_record_id: z 16 | .string() 17 | .describe( 18 | '面试评价 ID,可通过接口获取', 19 | ), 20 | }), 21 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 22 | }, 23 | }; 24 | export const hireV2InterviewRecordList = { 25 | project: 'hire', 26 | name: 'hire.v2.interviewRecord.list', 27 | sdkName: 'hire.v2.interviewRecord.list', 28 | path: '/open-apis/hire/v2/interview_records', 29 | httpMethod: 'GET', 30 | description: 31 | '[Feishu/Lark]-候选人管理-投递流程-面试-批量获取面试评价详细信息(新版)-批量获取面试评价详细信息,如面试结论、面试得分和面试官等信息', 32 | accessTokens: ['tenant', 'user'], 33 | schema: { 34 | params: z.object({ 35 | ids: z 36 | .array(z.string()) 37 | .describe( 38 | '面试评价 ID 列表,可通过接口获取,使用该筛选项时不会分页', 39 | ) 40 | .optional(), 41 | page_size: z.number().describe('分页大小**注意**:若不传该参数,则默认根据 `ids` 参数获取数据').optional(), 42 | page_token: z 43 | .string() 44 | .describe( 45 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 46 | ) 47 | .optional(), 48 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 49 | }), 50 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 51 | }, 52 | }; 53 | export const hireV2TalentGet = { 54 | project: 'hire', 55 | name: 'hire.v2.talent.get', 56 | sdkName: 'hire.v2.talent.get', 57 | path: '/open-apis/hire/v2/talents/:talent_id', 58 | httpMethod: 'GET', 59 | description: 60 | '[Feishu/Lark]-招聘-候选人管理-人才-获取人才详情-根据人才 ID 获取人才详情,包含人才加入文件夹列表、标签、人才库、备注以及屏蔽名单等信息', 61 | accessTokens: ['tenant'], 62 | schema: { 63 | params: z.object({ 64 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_admin_id']).describe('用户ID类型').optional(), 65 | }), 66 | path: z.object({ 67 | talent_id: z 68 | .string() 69 | .describe( 70 | '人才 ID,可通过接口获取', 71 | ), 72 | }), 73 | }, 74 | }; 75 | export const hireV2Tools = [hireV2InterviewRecordGet, hireV2InterviewRecordList, hireV2TalentGet]; 76 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/human_authentication_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type humanAuthenticationV1ToolName = 'human_authentication.v1.identity.create'; 3 | export const humanAuthenticationV1IdentityCreate = { 4 | project: 'human_authentication', 5 | name: 'human_authentication.v1.identity.create', 6 | sdkName: 'human_authentication.v1.identity.create', 7 | path: '/open-apis/human_authentication/v1/identities', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-实名认证-录入身份信息-该接口用于录入实名认证的身份信息,在唤起有源活体认证前,需要使用该接口进行实名认证', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | identity_name: z.string().describe('姓名'), 15 | identity_code: z.string().describe('身份证号'), 16 | mobile: z.string().describe('手机号').optional(), 17 | }), 18 | params: z.object({ 19 | user_id: z 20 | .string() 21 | .describe( 22 | '用户的唯一标识(使用的ID类型见下一参数描述,不同ID类型的区别和获取,参考文档:)', 23 | ), 24 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 25 | }), 26 | }, 27 | }; 28 | export const humanAuthenticationV1Tools = [humanAuthenticationV1IdentityCreate]; 29 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/mdm_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type mdmV1ToolName = 'mdm.v1.userAuthDataRelation.bind' | 'mdm.v1.userAuthDataRelation.unbind'; 3 | export const mdmV1UserAuthDataRelationBind = { 4 | project: 'mdm', 5 | name: 'mdm.v1.userAuthDataRelation.bind', 6 | sdkName: 'mdm.v1.userAuthDataRelation.bind', 7 | path: '/open-apis/mdm/v1/user_auth_data_relations/bind', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-飞书主数据-数据维度-用户数据维度绑定-通过该接口,可为指定应用下的用户绑定一类数据维度,支持批量给多个用户同时增量授权', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ 14 | root_dimension_type: z.string().describe('数据类型编码'), 15 | sub_dimension_types: z.array(z.string()).describe('数据编码列表'), 16 | authorized_user_ids: z.array(z.string()).describe('授权人的lark id'), 17 | uams_app_id: z.string().describe('uams系统中应用id'), 18 | }), 19 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 20 | }, 21 | }; 22 | export const mdmV1UserAuthDataRelationUnbind = { 23 | project: 'mdm', 24 | name: 'mdm.v1.userAuthDataRelation.unbind', 25 | sdkName: 'mdm.v1.userAuthDataRelation.unbind', 26 | path: '/open-apis/mdm/v1/user_auth_data_relations/unbind', 27 | httpMethod: 'POST', 28 | description: 29 | '[Feishu/Lark]-飞书主数据-数据维度-用户数据维度解绑-通过该接口,可为指定应用下的指定用户解除一类数据维度', 30 | accessTokens: ['tenant'], 31 | schema: { 32 | data: z.object({ 33 | root_dimension_type: z.string().describe('数据类型编码'), 34 | sub_dimension_types: z.array(z.string()).describe('数据编码列表'), 35 | authorized_user_ids: z.array(z.string()).describe('授权人的lark id'), 36 | uams_app_id: z.string().describe('uams系统中应用id'), 37 | }), 38 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 39 | }, 40 | }; 41 | export const mdmV1Tools = [mdmV1UserAuthDataRelationBind, mdmV1UserAuthDataRelationUnbind]; 42 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/minutes_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type minutesV1ToolName = 3 | | 'minutes.v1.minute.get' 4 | | 'minutes.v1.minuteMedia.get' 5 | | 'minutes.v1.minuteStatistics.get'; 6 | export const minutesV1MinuteGet = { 7 | project: 'minutes', 8 | name: 'minutes.v1.minute.get', 9 | sdkName: 'minutes.v1.minute.get', 10 | path: '/open-apis/minutes/v1/minutes/:minute_token', 11 | httpMethod: 'GET', 12 | description: 13 | '[Feishu/Lark]-妙记-妙记信息-获取妙记信息-通过这个接口,可以得到一篇妙记的基础概述信息,包含 `owner_id`、`create_time`、标题、封面、时长和 URL', 14 | accessTokens: ['tenant', 'user'], 15 | schema: { 16 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 17 | path: z.object({ 18 | minute_token: z.string().describe('妙记唯一标识。可从妙记链接中获取,一般为链接中最后一串字符').optional(), 19 | }), 20 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 21 | }, 22 | }; 23 | export const minutesV1MinuteMediaGet = { 24 | project: 'minutes', 25 | name: 'minutes.v1.minuteMedia.get', 26 | sdkName: 'minutes.v1.minuteMedia.get', 27 | path: '/open-apis/minutes/v1/minutes/:minute_token/media', 28 | httpMethod: 'GET', 29 | description: '[Feishu/Lark]-妙记-妙记音视频文件-下载妙记音视频文件-获取妙记的音视频文件', 30 | accessTokens: ['tenant', 'user'], 31 | schema: { 32 | path: z.object({ minute_token: z.string().describe('妙记唯一标识') }), 33 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 34 | }, 35 | }; 36 | export const minutesV1MinuteStatisticsGet = { 37 | project: 'minutes', 38 | name: 'minutes.v1.minuteStatistics.get', 39 | sdkName: 'minutes.v1.minuteStatistics.get', 40 | path: '/open-apis/minutes/v1/minutes/:minute_token/statistics', 41 | httpMethod: 'GET', 42 | description: 43 | '[Feishu/Lark]-妙记-妙记统计数据-获取妙记统计数据-通过这个接口,可以获得妙记的访问情况统计,包含PV、UV、访问过的 user id、访问过的 user timestamp', 44 | accessTokens: ['tenant', 'user'], 45 | schema: { 46 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 47 | path: z.object({ 48 | minute_token: z.string().describe('妙记唯一标识。可从妙记链接中获取,一般为链接中最后一串字符').optional(), 49 | }), 50 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 51 | }, 52 | }; 53 | export const minutesV1Tools = [minutesV1MinuteGet, minutesV1MinuteMediaGet, minutesV1MinuteStatisticsGet]; 54 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/moments_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type momentsV1ToolName = 'moments.v1.post.get'; 3 | export const momentsV1PostGet = { 4 | project: 'moments', 5 | name: 'moments.v1.post.get', 6 | sdkName: 'moments.v1.post.get', 7 | path: '/open-apis/moments/v1/posts/:post_id', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-公司圈-帖子-查询帖子信息-通过 ID 查询帖子实体数据信息', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 13 | path: z.object({ post_id: z.string().describe('帖子的ID,可从发布帖子接口返回数据或发布帖子事件中获取') }), 14 | }, 15 | }; 16 | export const momentsV1Tools = [momentsV1PostGet]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/optical_char_recognition_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type opticalCharRecognitionV1ToolName = 'optical_char_recognition.v1.image.basicRecognize'; 3 | export const opticalCharRecognitionV1ImageBasicRecognize = { 4 | project: 'optical_char_recognition', 5 | name: 'optical_char_recognition.v1.image.basicRecognize', 6 | sdkName: 'optical_char_recognition.v1.image.basicRecognize', 7 | path: '/open-apis/optical_char_recognition/v1/image/basic_recognize', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-AI 能力-光学字符识别-识别图片中的文字-可识别图片中的文字,按图片中的区域划分,分段返回文本列表。文件大小需小于5M', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ image: z.string().describe('base64 后的图片数据').optional() }), 14 | }, 15 | }; 16 | export const opticalCharRecognitionV1Tools = [opticalCharRecognitionV1ImageBasicRecognize]; 17 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/passport_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type passportV1ToolName = 'passport.v1.session.logout' | 'passport.v1.session.query'; 3 | export const passportV1SessionLogout = { 4 | project: 'passport', 5 | name: 'passport.v1.session.logout', 6 | sdkName: 'passport.v1.session.logout', 7 | path: '/open-apis/passport/v1/sessions/logout', 8 | httpMethod: 'POST', 9 | description: '[Feishu/Lark]-认证及授权-登录态管理-退出登录-该接口用于退出用户的登录态', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | data: z.object({ 13 | idp_credential_id: z.string().describe('idp 侧的唯一标识,logout_type = 2 时必填').optional(), 14 | logout_type: z 15 | .number() 16 | .describe( 17 | '登出的方式 Options:1(UserID UserID,使用开放平台的维度登出),2(IdpCredentialID IdpCredentialID,使用 idp 侧的唯一标识登出),3(SessionUUID Session 标识符,基于session uuid 登出)', 18 | ), 19 | terminal_type: z 20 | .array(z.number()) 21 | .describe( 22 | '登出的客户端类型,默认全部登出。可选值:- 1:PC 端- 2:Web 端- 3:Android 端- 4:iOS 端- 5:服务端- 6:旧版小程序端- 8:其他移动端', 23 | ) 24 | .optional(), 25 | user_id: z 26 | .string() 27 | .describe('开放平台的数据标识,用户 ID 类型与查询参数 user_id_type 一致,logout_type = 1 时必填') 28 | .optional(), 29 | logout_reason: z 30 | .number() 31 | .describe( 32 | '登出提示语,非必填,不传时默认提示:你已在其他客户端上退出了当前设备,请重新登录。可选值:- 34:您已修改登录密码,请重新登录- 35:您的登录态已失效,请重新登录- 36:您的密码已过期,请在登录页面通过忘记密码功能修改密码后重新登录', 33 | ) 34 | .optional(), 35 | sid: z.string().describe('需要精确登出的 session 标识符,logout_type = 3 时必填').optional(), 36 | }), 37 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 38 | }, 39 | }; 40 | export const passportV1SessionQuery = { 41 | project: 'passport', 42 | name: 'passport.v1.session.query', 43 | sdkName: 'passport.v1.session.query', 44 | path: '/open-apis/passport/v1/sessions/query', 45 | httpMethod: 'POST', 46 | description: '[Feishu/Lark]-认证及授权-登录态管理-批量获取脱敏的用户登录信息-该接口用于查询用户的登录信息', 47 | accessTokens: ['tenant'], 48 | schema: { 49 | data: z.object({ user_ids: z.array(z.string()).describe('用户 ID').optional() }), 50 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 51 | }, 52 | }; 53 | export const passportV1Tools = [passportV1SessionLogout, passportV1SessionQuery]; 54 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/performance_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type performanceV1ToolName = 3 | | 'performance.v1.reviewData.query' 4 | | 'performance.v1.semester.list' 5 | | 'performance.v1.stageTask.findByPage' 6 | | 'performance.v1.stageTask.findByUserList'; 7 | export const performanceV1ReviewDataQuery = { 8 | project: 'performance', 9 | name: 'performance.v1.reviewData.query', 10 | sdkName: 'performance.v1.reviewData.query', 11 | path: '/open-apis/performance/v1/review_datas/query', 12 | httpMethod: 'POST', 13 | description: 14 | '[Feishu/Lark]-绩效-评估数据-获取绩效结果-获取被评估人在指定周期、指定项目中各个环节的评估结果信息,包含绩效所在的周期、项目、评估项、评估模版以及各环节评估数据等信息', 15 | accessTokens: ['tenant', 'user'], 16 | schema: { 17 | data: z.object({ 18 | start_time: z 19 | .string() 20 | .describe( 21 | '周期开始时间最小值,毫秒时间戳,小于该时间开始的周期会被过滤掉**注意**:当填写了 `semester_id_list` 参数时,此参数无效', 22 | ), 23 | end_time: z 24 | .string() 25 | .describe( 26 | '周期结束时间最大值,毫秒时间戳,大于该时间结束的周期会被过滤掉**注意**:当填写了 `semester_id_list` 参数时,此参数无效', 27 | ), 28 | stage_types: z 29 | .array( 30 | z 31 | .enum(['leader_review', 'communication_and_open_result', 'view_result']) 32 | .describe( 33 | 'Options:leader_review(终评环节),communication_and_open_result(结果沟通环节),view_result(查看绩效结果环节)', 34 | ), 35 | ) 36 | .describe('环节类型,目前仅支持终评环节、结果沟通环节、查看绩效结果环节(不传默认包含所有的环节)'), 37 | stage_progress: z 38 | .array( 39 | z 40 | .number() 41 | .describe( 42 | 'Options:0(未开始,任务的开始时间未到达),1(待完成,任务的开始时间到达而截止时间未到达,且任务未完成),2(已截止,任务的截止时间已到达,且任务未完成),3(已完成,任务已完成),4(已复议)', 43 | ), 44 | ) 45 | .describe('环节状态,填写时按照指定状态获取绩效结果,不填查询所有状态的绩效结果') 46 | .optional(), 47 | semester_id_list: z 48 | .array(z.string()) 49 | .describe( 50 | '评估周期 ID 列表,可通过接口获取', 51 | ) 52 | .optional(), 53 | reviewee_user_id_list: z.array(z.string()).describe('被评估人 ID 列表,与入参 `user_id_type` 类型一致'), 54 | updated_later_than: z 55 | .string() 56 | .describe('环节更新时间最早时间,毫秒时间戳,可筛选出在此时间之后,有内容提交的环节数据') 57 | .optional(), 58 | }), 59 | params: z.object({ 60 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_admin_id']).describe('用户ID类型').optional(), 61 | }), 62 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 63 | }, 64 | }; 65 | export const performanceV1SemesterList = { 66 | project: 'performance', 67 | name: 'performance.v1.semester.list', 68 | sdkName: 'performance.v1.semester.list', 69 | path: '/open-apis/performance/v1/semesters', 70 | httpMethod: 'GET', 71 | description: 72 | '[Feishu/Lark]-后台配置-周期与项目-周期-获取周期列表-批量获取周期的基本信息,如周期的名称、类型等信息。支持根据时间段、周期年份、周期类型等过滤条件进行筛选', 73 | accessTokens: ['tenant'], 74 | schema: { 75 | params: z.object({ 76 | start_time: z.string().describe('周期开始时间最小值,毫秒时间戳,小于该时间开始的周期会被过滤掉').optional(), 77 | end_time: z.string().describe('周期结束时间最大值,毫秒时间戳,大于该时间结束的周期会被过滤掉').optional(), 78 | year: z.number().describe('周期年份,填写时按照周期年份筛选').optional(), 79 | type_group: z 80 | .enum(['Annual', 'Semi-annual', 'Quarter', 'Bimonth', 'Month', 'Non-standard']) 81 | .describe( 82 | '周期类型分组,填写时按照周期类型分组 Options:Annual(年),Semi-annual(SemiAnnual 半年),Quarter(季度),Bimonth(双月),Month(月),Non-standard(NonStandard 非标准周期)', 83 | ) 84 | .optional(), 85 | type: z 86 | .enum([ 87 | 'Annual', 88 | 'H1', 89 | 'H2', 90 | 'Q1', 91 | 'Q2', 92 | 'Q3', 93 | 'Q4', 94 | 'January-February', 95 | 'March-April', 96 | 'May-June', 97 | 'July-August', 98 | 'September-October', 99 | 'November-December', 100 | 'January', 101 | 'February', 102 | 'March', 103 | 'April', 104 | 'May', 105 | 'June', 106 | 'July', 107 | 'August', 108 | 'September', 109 | 'October', 110 | 'November', 111 | 'December', 112 | 'Custom', 113 | ]) 114 | .describe( 115 | '周期类型,填写时按照周期类型筛选 Options:Annual(全年),H1(上半年),H2(下半年),Q1(第一季度),Q2(第二季度),Q3(第三季度),Q4(第四季度),January-February(January2February 1-2 双月),March-April(March2April 3-4 双月),May-June(May2June 5-6 双月),July-August(July2August 7-8 双月),September-October(September2October 9-10 双月),November-December(November2December 11-12 双月),January(1月份),February(2月份),March(3月份),April(4月份),May(5月份),June(6月份),July(7月份),August(8月份),September(9月份),October(10月份),November(11月份),December(12月份),Custom(自定义)', 116 | ) 117 | .optional(), 118 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 119 | }), 120 | }, 121 | }; 122 | export const performanceV1StageTaskFindByPage = { 123 | project: 'performance', 124 | name: 'performance.v1.stageTask.findByPage', 125 | sdkName: 'performance.v1.stageTask.findByPage', 126 | path: '/open-apis/performance/v1/stage_tasks/find_by_page', 127 | httpMethod: 'POST', 128 | description: 129 | '[Feishu/Lark]-绩效-评估任务-获取周期任务(全部用户)-批量获取周期下所有用户的任务信息。支持传入任务分类、任务截止时间参数删选周期内任务数据', 130 | accessTokens: ['tenant'], 131 | schema: { 132 | data: z.object({ 133 | semester_id: z 134 | .string() 135 | .describe('周期 ID,可通过接口获取'), 136 | task_option_lists: z 137 | .array(z.number()) 138 | .describe( 139 | '任务分类,填写则获取指定分类的任务**可选项有**:- `1`:待完成- `2`:已完成- `3`:已逾期(仅当租户设置不允许逾期提交时才有此分类)', 140 | ) 141 | .optional(), 142 | after_time: z.string().describe('任务截止时间最小值,毫秒时间戳,填写则查询在此时间之后截止的任务').optional(), 143 | before_time: z.string().describe('任务截止时间最大值,毫秒时间戳,填写则查询在此时间之前截止的任务').optional(), 144 | page_token: z 145 | .string() 146 | .describe( 147 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 148 | ) 149 | .optional(), 150 | page_size: z.number().describe('分页大小').optional(), 151 | }), 152 | params: z.object({ 153 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_admin_id']).describe('用户ID类型').optional(), 154 | }), 155 | }, 156 | }; 157 | export const performanceV1StageTaskFindByUserList = { 158 | project: 'performance', 159 | name: 'performance.v1.stageTask.findByUserList', 160 | sdkName: 'performance.v1.stageTask.findByUserList', 161 | path: '/open-apis/performance/v1/stage_tasks/find_by_user_list', 162 | httpMethod: 'POST', 163 | description: 164 | '[Feishu/Lark]-绩效-评估任务-获取周期任务(指定用户)-根据用户 ID 批量获取指定周期的任务信息。支持传入任务分类、任务截止时间参数删选周期内任务数据', 165 | accessTokens: ['tenant', 'user'], 166 | schema: { 167 | data: z.object({ 168 | semester_id: z 169 | .string() 170 | .describe('周期 ID,可通过接口获取'), 171 | user_id_lists: z 172 | .array(z.string()) 173 | .describe( 174 | '用户 ID 列表,与入参 `user_id_type` 类型一致。如果以用户身份访问(user_access_token)时,仅能填写本人用户 ID', 175 | ), 176 | task_option_lists: z 177 | .array(z.number()) 178 | .describe( 179 | '任务分类,填写则获取指定分类的任务**可选项有**:- `1`:待完成- `2`:已完成- `3`:已逾期(仅当租户设置不允许逾期提交时才有此分类)', 180 | ) 181 | .optional(), 182 | after_time: z.string().describe('任务截止时间最小值,毫秒时间戳,填写则查询在此时间之后截止的任务').optional(), 183 | before_time: z.string().describe('任务截止时间最大值,毫秒时间戳,填写则查询在此时间之前截止的任务').optional(), 184 | }), 185 | params: z.object({ 186 | user_id_type: z.enum(['open_id', 'union_id', 'user_id', 'people_admin_id']).describe('用户ID类型').optional(), 187 | }), 188 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 189 | }, 190 | }; 191 | export const performanceV1Tools = [ 192 | performanceV1ReviewDataQuery, 193 | performanceV1SemesterList, 194 | performanceV1StageTaskFindByPage, 195 | performanceV1StageTaskFindByUserList, 196 | ]; 197 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/report_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type reportV1ToolName = 'report.v1.rule.query' | 'report.v1.ruleView.remove' | 'report.v1.task.query'; 3 | export const reportV1RuleQuery = { 4 | project: 'report', 5 | name: 'report.v1.rule.query', 6 | sdkName: 'report.v1.rule.query', 7 | path: '/open-apis/report/v1/rules/query', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-汇报-规则-查询规则-查询规则', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | params: z.object({ 13 | rule_name: z.string().describe('规则名称'), 14 | include_deleted: z 15 | .number() 16 | .describe('是否包括已删除,默认未删除 Options:0(Exclude 不包括已删除),1(Include 包括已删除)') 17 | .optional(), 18 | user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional(), 19 | }), 20 | }, 21 | }; 22 | export const reportV1RuleViewRemove = { 23 | project: 'report', 24 | name: 'report.v1.ruleView.remove', 25 | sdkName: 'report.v1.ruleView.remove', 26 | path: '/open-apis/report/v1/rules/:rule_id/views/remove', 27 | httpMethod: 'POST', 28 | description: '[Feishu/Lark]-汇报-规则看板-移除规则看板-移除规则看板', 29 | accessTokens: ['tenant'], 30 | schema: { 31 | data: z.object({ 32 | user_ids: z 33 | .array(z.string()) 34 | .describe('列表为空删除规则下全用户视图,列表不为空删除指定用户视图,大小限制200') 35 | .optional(), 36 | }), 37 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 38 | path: z.object({ rule_id: z.string().describe('汇报规则ID') }), 39 | }, 40 | }; 41 | export const reportV1TaskQuery = { 42 | project: 'report', 43 | name: 'report.v1.task.query', 44 | sdkName: 'report.v1.task.query', 45 | path: '/open-apis/report/v1/tasks/query', 46 | httpMethod: 'POST', 47 | description: '[Feishu/Lark]-汇报-任务-查询任务-查询任务', 48 | accessTokens: ['tenant', 'user'], 49 | schema: { 50 | data: z.object({ 51 | commit_start_time: z.number().describe('提交开始时间时间戳'), 52 | commit_end_time: z.number().describe('提交结束时间时间戳'), 53 | rule_id: z.string().describe('汇报规则ID').optional(), 54 | user_id: z.string().describe('用户ID').optional(), 55 | page_token: z 56 | .string() 57 | .describe( 58 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 59 | ), 60 | page_size: z.number().describe('单次分页返回的条数'), 61 | }), 62 | params: z.object({ user_id_type: z.enum(['open_id', 'union_id', 'user_id']).describe('用户ID类型').optional() }), 63 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 64 | }, 65 | }; 66 | export const reportV1Tools = [reportV1RuleQuery, reportV1RuleViewRemove, reportV1TaskQuery]; 67 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/security_and_compliance_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type securityAndComplianceV1ToolName = 'security_and_compliance.v1.openapiLog.listData'; 3 | export const securityAndComplianceV1OpenapiLogListData = { 4 | project: 'security_and_compliance', 5 | name: 'security_and_compliance.v1.openapiLog.listData', 6 | sdkName: 'security_and_compliance.v1.openapiLog.listData', 7 | path: '/open-apis/security_and_compliance/v1/openapi_logs/list_data', 8 | httpMethod: 'POST', 9 | description: '[Feishu/Lark]-安全合规-OpenAPI审计日志-获取OpenAPI审计日志数据-该接口用于获取OpenAPI审计日志数据', 10 | accessTokens: ['tenant'], 11 | schema: { 12 | data: z.object({ 13 | api_keys: z 14 | .array(z.string()) 15 | .describe( 16 | '飞书开放平台定义的API,参考:', 17 | ) 18 | .optional(), 19 | start_time: z.number().describe('以秒为单位的起始时间戳').optional(), 20 | end_time: z.number().describe('以秒为单位的终止时间戳').optional(), 21 | app_id: z 22 | .string() 23 | .describe( 24 | '调用OpenAPI的应用唯一标识,可以前往 > 应用详情页 > 凭证与基础信息中获取 app_id', 25 | ) 26 | .optional(), 27 | page_size: z.number().describe('分页大小').optional(), 28 | page_token: z 29 | .string() 30 | .describe( 31 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 32 | ) 33 | .optional(), 34 | }), 35 | }, 36 | }; 37 | export const securityAndComplianceV1Tools = [securityAndComplianceV1OpenapiLogListData]; 38 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/speech_to_text_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type speechToTextV1ToolName = 3 | | 'speech_to_text.v1.speech.fileRecognize' 4 | | 'speech_to_text.v1.speech.streamRecognize'; 5 | export const speechToTextV1SpeechFileRecognize = { 6 | project: 'speech_to_text', 7 | name: 'speech_to_text.v1.speech.fileRecognize', 8 | sdkName: 'speech_to_text.v1.speech.fileRecognize', 9 | path: '/open-apis/speech_to_text/v1/speech/file_recognize', 10 | httpMethod: 'POST', 11 | description: 12 | '[Feishu/Lark]-AI 能力-语音识别-识别语音文件-语音文件识别接口,上传整段语音文件进行一次性识别。接口适合 60 秒以内音频识别', 13 | accessTokens: ['tenant'], 14 | schema: { 15 | data: z.object({ 16 | speech: z 17 | .object({ 18 | speech: z 19 | .string() 20 | .describe('pcm格式音频文件(文件识别)或音频分片(流式识别)经base64编码后的内容') 21 | .optional(), 22 | }) 23 | .describe('语音资源'), 24 | config: z 25 | .object({ 26 | file_id: z.string().describe('仅包含字母数字和下划线的 16 位字符串作为文件的标识,用户生成'), 27 | format: z.string().describe('语音格式,目前仅支持:pcm'), 28 | engine_type: z.string().describe('引擎类型,目前仅支持:16k_auto 中英混合'), 29 | }) 30 | .describe('配置属性'), 31 | }), 32 | }, 33 | }; 34 | export const speechToTextV1SpeechStreamRecognize = { 35 | project: 'speech_to_text', 36 | name: 'speech_to_text.v1.speech.streamRecognize', 37 | sdkName: 'speech_to_text.v1.speech.streamRecognize', 38 | path: '/open-apis/speech_to_text/v1/speech/stream_recognize', 39 | httpMethod: 'POST', 40 | description: 41 | '[Feishu/Lark]-AI 能力-语音识别-识别流式语音-语音流式接口,将整个音频文件分片进行传入模型。能够实时返回数据。建议每个音频分片的大小为 100-200ms', 42 | accessTokens: ['tenant'], 43 | schema: { 44 | data: z.object({ 45 | speech: z 46 | .object({ 47 | speech: z 48 | .string() 49 | .describe('pcm格式音频文件(文件识别)或音频分片(流式识别)经base64编码后的内容') 50 | .optional(), 51 | }) 52 | .describe('语音资源'), 53 | config: z 54 | .object({ 55 | stream_id: z.string().describe('仅包含字母数字和下划线的 16 位字符串作为同一数据流的标识,用户生成'), 56 | sequence_id: z.number().describe('数据流分片的序号,序号从 0 开始,每次请求递增 1'), 57 | action: z 58 | .number() 59 | .describe( 60 | '数据流标记:1 首包,2 正常结束,等待结果返回,3 中断数据流不返回最终结果,0 传输语音中间的数据包', 61 | ), 62 | format: z.string().describe('语音格式,目前仅支持:pcm'), 63 | engine_type: z.string().describe('引擎类型,目前仅支持:16k_auto 中英混合'), 64 | }) 65 | .describe('配置属性'), 66 | }), 67 | }, 68 | }; 69 | export const speechToTextV1Tools = [speechToTextV1SpeechFileRecognize, speechToTextV1SpeechStreamRecognize]; 70 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/tenant_v2.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type tenantV2ToolName = 'tenant.v2.tenantProductAssignInfo.query' | 'tenant.v2.tenant.query'; 3 | export const tenantV2TenantProductAssignInfoQuery = { 4 | project: 'tenant', 5 | name: 'tenant.v2.tenantProductAssignInfo.query', 6 | sdkName: 'tenant.v2.tenantProductAssignInfo.query', 7 | path: '/open-apis/tenant/v2/tenant/assign_info_list/query', 8 | httpMethod: 'GET', 9 | description: 10 | '[Feishu/Lark]-企业信息-企业席位信息-获取企业席位信息接口-获取租户下待分配的席位列表,包含席位名称、席位ID、数量及对应有效期', 11 | accessTokens: ['tenant'], 12 | schema: {}, 13 | }; 14 | export const tenantV2TenantQuery = { 15 | project: 'tenant', 16 | name: 'tenant.v2.tenant.query', 17 | sdkName: 'tenant.v2.tenant.query', 18 | path: '/open-apis/tenant/v2/tenant/query', 19 | httpMethod: 'GET', 20 | description: '[Feishu/Lark]-企业信息-获取企业信息-获取企业名称、企业编号等企业信息', 21 | accessTokens: ['tenant'], 22 | schema: {}, 23 | }; 24 | export const tenantV2Tools = [tenantV2TenantProductAssignInfoQuery, tenantV2TenantQuery]; 25 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/translation_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type translationV1ToolName = 'translation.v1.text.detect' | 'translation.v1.text.translate'; 3 | export const translationV1TextDetect = { 4 | project: 'translation', 5 | name: 'translation.v1.text.detect', 6 | sdkName: 'translation.v1.text.detect', 7 | path: '/open-apis/translation/v1/text/detect', 8 | httpMethod: 'POST', 9 | description: 10 | '[Feishu/Lark]-AI 能力-机器翻译-识别文本语种-机器翻译 (MT),支持 100 多种语言识别,返回符合 ISO 639-1 标准', 11 | accessTokens: ['tenant'], 12 | schema: { 13 | data: z.object({ text: z.string().describe('需要被识别语种的文本') }), 14 | }, 15 | }; 16 | export const translationV1TextTranslate = { 17 | project: 'translation', 18 | name: 'translation.v1.text.translate', 19 | sdkName: 'translation.v1.text.translate', 20 | path: '/open-apis/translation/v1/text/translate', 21 | httpMethod: 'POST', 22 | description: 23 | '[Feishu/Lark]-AI 能力-机器翻译-翻译文本-机器翻译 (MT),支持以下语种互译:"zh": 汉语;"zh-Hant": 繁体汉语;"en": 英语;"ja": 日语;"ru": 俄语;"de": 德语;"fr": 法语;"it": 意大利语;"pl": 波兰语;"th": 泰语;"hi": 印地语;"id": 印尼语;"es": 西班牙语;"pt": 葡萄牙语;"ko": 朝鲜语;"vi": 越南语;', 24 | accessTokens: ['tenant'], 25 | schema: { 26 | data: z.object({ 27 | source_language: z.string().describe('源语言'), 28 | text: z.string().describe('源文本,字符上限为 1,000'), 29 | target_language: z.string().describe('目标语言'), 30 | glossary: z 31 | .array(z.object({ from: z.string().describe('原文'), to: z.string().describe('译文') })) 32 | .describe('请求级术语表,携带术语,仅在本次翻译中生效(最多能携带 128个术语词)') 33 | .optional(), 34 | }), 35 | }, 36 | }; 37 | export const translationV1Tools = [translationV1TextDetect, translationV1TextTranslate]; 38 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/verification_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type verificationV1ToolName = 'verification.v1.verification.get'; 3 | export const verificationV1VerificationGet = { 4 | project: 'verification', 5 | name: 'verification.v1.verification.get', 6 | sdkName: 'verification.v1.verification.get', 7 | path: '/open-apis/verification/v1/verification', 8 | httpMethod: 'GET', 9 | description: '[Feishu/Lark]-认证信息-获取认证信息-获取认证状态', 10 | accessTokens: ['tenant'], 11 | schema: {}, 12 | }; 13 | export const verificationV1Tools = [verificationV1VerificationGet]; 14 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/wiki_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type wikiV1ToolName = 'wiki.v1.node.search'; 3 | export const wikiV1NodeSearch = { 4 | project: 'wiki', 5 | name: 'wiki.v1.node.search', 6 | sdkName: 'wiki.v1.node.search', 7 | path: '/open-apis/wiki/v1/nodes/search', 8 | httpMethod: 'POST', 9 | description: '[Feishu/Lark]-云文档-知识库-搜索 Wiki', 10 | accessTokens: ['user'], 11 | schema: { 12 | data: z.object({ 13 | query: z.string().describe('搜索关键词'), 14 | space_id: z.string().describe('文档所属的知识空间ID,为空搜索所有 wiki').optional(), 15 | node_id: z 16 | .string() 17 | .describe('wiki token,不为空搜索该节点及其所有子节点,为空搜索所有 wiki(根据 space_id 选择 space)') 18 | .optional(), 19 | }), 20 | params: z.object({ 21 | page_token: z 22 | .string() 23 | .describe( 24 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该page_token 获取查询结果', 25 | ) 26 | .optional(), 27 | page_size: z.number().describe('分页大小').optional(), 28 | }), 29 | useUAT: z.boolean().describe('使用用户身份请求, 否则使用应用身份').optional(), 30 | }, 31 | }; 32 | export const wikiV1Tools = [wikiV1NodeSearch]; 33 | -------------------------------------------------------------------------------- /src/mcp-tool/tools/zh/gen-tools/zod/workplace_v1.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export type workplaceV1ToolName = 3 | | 'workplace.v1.customWorkplaceAccessData.search' 4 | | 'workplace.v1.workplaceAccessData.search' 5 | | 'workplace.v1.workplaceBlockAccessData.search'; 6 | export const workplaceV1CustomWorkplaceAccessDataSearch = { 7 | project: 'workplace', 8 | name: 'workplace.v1.customWorkplaceAccessData.search', 9 | sdkName: 'workplace.v1.customWorkplaceAccessData.search', 10 | path: '/open-apis/workplace/v1/custom_workplace_access_data/search', 11 | httpMethod: 'POST', 12 | description: '[Feishu/Lark]-工作台-工作台访问数据-获取定制工作台访问数据-获取定制工作台访问数据', 13 | accessTokens: ['tenant'], 14 | schema: { 15 | params: z.object({ 16 | from_date: z.string().describe('数据检索开始时间,精确到日。格式yyyy-MM-dd'), 17 | to_date: z.string().describe('数据检索结束时间,精确到日。格式yyyy-MM-dd'), 18 | page_size: z.number().describe('分页大小,最小为 1,最大为 200,默认为 20'), 19 | page_token: z 20 | .string() 21 | .describe( 22 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 23 | ) 24 | .optional(), 25 | custom_workplace_id: z 26 | .string() 27 | .describe( 28 | '定制工作台id,非必填。不填时,返回所有定制工作台数据。如何获取定制工作台ID:可前往 飞书管理后台 > 工作台 > 定制工作台,点击指定工作台的 设置 进入设置页面;鼠标连续点击三次顶部的 设置 字样即可出现 ID,复制 ID 即可', 29 | ) 30 | .optional(), 31 | }), 32 | }, 33 | }; 34 | export const workplaceV1WorkplaceAccessDataSearch = { 35 | project: 'workplace', 36 | name: 'workplace.v1.workplaceAccessData.search', 37 | sdkName: 'workplace.v1.workplaceAccessData.search', 38 | path: '/open-apis/workplace/v1/workplace_access_data/search', 39 | httpMethod: 'POST', 40 | description: 41 | '[Feishu/Lark]-工作台-工作台访问数据-获取工作台访问数据-获取工作台访问数据(包含默认工作台与定制工作台)', 42 | accessTokens: ['tenant'], 43 | schema: { 44 | params: z.object({ 45 | from_date: z.string().describe('数据检索开始时间,精确到日。格式yyyy-MM-dd'), 46 | to_date: z.string().describe('数据检索结束时间,精确到日。格式yyyy-MM-dd'), 47 | page_size: z.number().describe('分页大小,最小为 1,最大为 200,默认为 20'), 48 | page_token: z 49 | .string() 50 | .describe( 51 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 52 | ) 53 | .optional(), 54 | }), 55 | }, 56 | }; 57 | export const workplaceV1WorkplaceBlockAccessDataSearch = { 58 | project: 'workplace', 59 | name: 'workplace.v1.workplaceBlockAccessData.search', 60 | sdkName: 'workplace.v1.workplaceBlockAccessData.search', 61 | path: '/open-apis/workplace/v1/workplace_block_access_data/search', 62 | httpMethod: 'POST', 63 | description: '[Feishu/Lark]-工作台-工作台访问数据-获取定制工作台小组件访问数据-获取定制工作台小组件访问数据', 64 | accessTokens: ['tenant'], 65 | schema: { 66 | params: z.object({ 67 | from_date: z.string().describe('数据检索开始时间,精确到日。格式yyyy-MM-dd'), 68 | to_date: z.string().describe('数据检索结束时间,精确到日。格式yyyy-MM-dd'), 69 | page_size: z.number().describe('分页大小,最小为 1,最大为 200,默认为 20'), 70 | page_token: z 71 | .string() 72 | .describe( 73 | '分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果', 74 | ) 75 | .optional(), 76 | block_id: z 77 | .string() 78 | .describe( 79 | '小组件id(BlockID)。可前往 飞书管理后台 > 工作台 > 定制工作台,选择指定的工作台并进入工作台编辑器,点击某个小组件,即可查看页面右侧面板中该小组件名称下方的“BlockID”', 80 | ) 81 | .optional(), 82 | }), 83 | }, 84 | }; 85 | export const workplaceV1Tools = [ 86 | workplaceV1CustomWorkplaceAccessDataSearch, 87 | workplaceV1WorkplaceAccessDataSearch, 88 | workplaceV1WorkplaceBlockAccessDataSearch, 89 | ]; 90 | -------------------------------------------------------------------------------- /src/mcp-tool/types/index.ts: -------------------------------------------------------------------------------- 1 | import * as lark from '@larksuiteoapi/node-sdk'; 2 | import { ProjectName, ToolName } from '../tools'; 3 | import { CallToolResult } from '@modelcontextprotocol/sdk/types'; 4 | 5 | export type ToolNameCase = 'snake' | 'camel' | 'kebab' | 'dot'; 6 | 7 | export enum TokenMode { 8 | AUTO = 'auto', 9 | USER_ACCESS_TOKEN = 'user_access_token', 10 | TENANT_ACCESS_TOKEN = 'tenant_access_token', 11 | } 12 | 13 | export interface McpHandlerOptions { 14 | userAccessToken?: string; 15 | tool?: McpTool; 16 | } 17 | 18 | export type McpHandler = ( 19 | client: lark.Client, 20 | params: any, 21 | options: McpHandlerOptions, 22 | ) => Promise | CallToolResult; 23 | /** 24 | * MCP工具类型定义 25 | */ 26 | export interface McpTool { 27 | // 业务 28 | project: string; 29 | // 工具名称 30 | name: string; 31 | // 工具描述 32 | description: string; 33 | // 工具参数 34 | schema: any; 35 | // node sdk 调用名称 36 | sdkName?: string; 37 | // API 路径 38 | path?: string; 39 | // API http方法 40 | httpMethod?: string; 41 | // 令牌类型 42 | accessTokens?: string[]; 43 | // 是否支持文件上传 44 | supportFileUpload?: boolean; 45 | // 是否支持文件下载 46 | supportFileDownload?: boolean; 47 | // 自定义处理函数 48 | customHandler?: McpHandler; 49 | } 50 | 51 | /** 52 | * 注册工具选项 53 | */ 54 | export interface ToolsFilterOptions { 55 | // 语言 56 | language?: 'zh' | 'en'; 57 | // 允许的工具 58 | allowTools?: ToolName[]; 59 | // 允许的业务域 60 | allowProjects?: ProjectName[]; 61 | // 令牌类型 62 | tokenMode?: TokenMode; 63 | } 64 | 65 | export type LarkClientOptions = Partial[0]>; 66 | 67 | export interface LarkMcpToolOptions extends LarkClientOptions { 68 | client?: lark.Client; 69 | appId?: string; 70 | appSecret?: string; 71 | // 工具选项 72 | toolsOptions?: ToolsFilterOptions; 73 | tokenMode?: TokenMode; 74 | } 75 | -------------------------------------------------------------------------------- /src/mcp-tool/utils/case-transf.ts: -------------------------------------------------------------------------------- 1 | import { ToolNameCase } from '../types'; 2 | 3 | export function caseTransf(toolName: string, caseType?: ToolNameCase) { 4 | if (caseType === 'snake') { 5 | return toolName.replace(/\./g, '_'); 6 | } 7 | if (caseType === 'camel') { 8 | return toolName.replace(/\./g, '_').replace(/_(\w)/g, (_, letter) => letter.toUpperCase()); 9 | } 10 | if (caseType === 'kebab') { 11 | return toolName.replace(/\./g, '-'); 12 | } 13 | return toolName; 14 | } 15 | -------------------------------------------------------------------------------- /src/mcp-tool/utils/filter-tools.ts: -------------------------------------------------------------------------------- 1 | import { ToolName, ProjectName } from '../tools'; 2 | import { McpTool, ToolsFilterOptions, TokenMode } from '../types'; 3 | 4 | export function filterTools(tools: McpTool[], options: ToolsFilterOptions) { 5 | let filteredTools = tools.filter( 6 | (tool) => 7 | options.allowTools?.includes(tool.name as ToolName) || 8 | options.allowProjects?.includes(tool.project as ProjectName), 9 | ); 10 | 11 | // Filter by token mode 12 | if (options.tokenMode && options.tokenMode !== TokenMode.AUTO) { 13 | filteredTools = filteredTools.filter((tool) => { 14 | if (!tool.accessTokens) { 15 | return false; 16 | } 17 | if (options.tokenMode === TokenMode.USER_ACCESS_TOKEN) { 18 | return tool.accessTokens.includes('user'); 19 | } 20 | if (options.tokenMode === TokenMode.TENANT_ACCESS_TOKEN) { 21 | return tool.accessTokens.includes('tenant'); 22 | } 23 | return true; 24 | }); 25 | } 26 | 27 | return filteredTools; 28 | } 29 | -------------------------------------------------------------------------------- /src/mcp-tool/utils/get-should-use-uat.ts: -------------------------------------------------------------------------------- 1 | import { TokenMode } from '../types'; 2 | 3 | export function getShouldUseUAT(tokenMode: TokenMode, userAccessToken?: string, useUAT?: boolean) { 4 | switch (tokenMode) { 5 | case TokenMode.USER_ACCESS_TOKEN: { 6 | if (!userAccessToken) { 7 | throw new Error('Invalid UserAccessToken'); 8 | } 9 | return true; 10 | } 11 | case TokenMode.TENANT_ACCESS_TOKEN: { 12 | return false; 13 | } 14 | case TokenMode.AUTO: 15 | default: { 16 | if (userAccessToken && useUAT) { 17 | return true; 18 | } else { 19 | return false; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/mcp-tool/utils/handler.ts: -------------------------------------------------------------------------------- 1 | import * as lark from '@larksuiteoapi/node-sdk'; 2 | import { McpHandler, McpHandlerOptions } from '../types'; 3 | 4 | const sdkFuncCall = async (client: lark.Client, params: any, options: McpHandlerOptions) => { 5 | const { tool, userAccessToken } = options || {}; 6 | const { sdkName, path, httpMethod } = tool || {}; 7 | 8 | if (!sdkName) { 9 | throw new Error('Invalid sdkName'); 10 | } 11 | 12 | const chain = sdkName.split('.'); 13 | let func: any = client; 14 | for (const element of chain) { 15 | func = func[element as keyof typeof func]; 16 | if (!func) { 17 | func = async (params: any, ...args: any) => 18 | await client.request({ method: httpMethod, url: path, ...params }, ...args); 19 | break; 20 | } 21 | } 22 | if (!(func instanceof Function)) { 23 | func = async (params: any, ...args: any) => 24 | await client.request({ method: httpMethod, url: path, ...params }, ...args); 25 | } 26 | 27 | if (params?.useUAT) { 28 | if (!userAccessToken) { 29 | throw new Error('Invalid UserAccessToken'); 30 | } 31 | return await func(params, lark.withUserAccessToken(userAccessToken)); 32 | } 33 | return await func(params); 34 | }; 35 | 36 | export const larkOapiHandler: McpHandler = async (client, params, options) => { 37 | try { 38 | const response = await sdkFuncCall(client, params, options); 39 | return { 40 | content: [ 41 | { 42 | type: 'text' as const, 43 | text: `Success: ${JSON.stringify(response?.data ?? response)}`, 44 | }, 45 | ], 46 | }; 47 | } catch (error) { 48 | return { 49 | isError: true, 50 | content: [ 51 | { 52 | type: 'text' as const, 53 | text: `Error: ${JSON.stringify((error as any)?.response?.data || (error as any)?.message || error)}`, 54 | }, 55 | ], 56 | }; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { currentVersion } from './version'; 2 | 3 | export const USER_AGENT = `oapi-sdk-mcp/${currentVersion}`; 4 | 5 | export const OAPI_MCP_DEFAULT_ARGS = { 6 | domain: 'https://open.feishu.cn', 7 | toolNameCase: 'snake', 8 | language: 'en', 9 | tokenMode: 'auto', 10 | mode: 'stdio', 11 | host: 'localhost', 12 | port: '3000', 13 | }; 14 | 15 | export const OAPI_MCP_ENV_ARGS = { 16 | appId: process.env.APP_ID, 17 | appSecret: process.env.APP_SECRET, 18 | userAccessToken: process.env.USER_ACCESS_TOKEN, 19 | tokenMode: process.env.LARK_TOKEN_MODE, 20 | tools: process.env.LARK_TOOLS, 21 | domain: process.env.LARK_DOMAIN, 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/http-instance.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { USER_AGENT } from './constants'; 3 | 4 | export const oapiHttpInstance = axios.create(); 5 | 6 | oapiHttpInstance.interceptors.request.use( 7 | (request) => { 8 | if (request.headers) { 9 | request.headers['User-Agent'] = USER_AGENT; 10 | } 11 | return request; 12 | }, 13 | undefined, 14 | { synchronous: true }, 15 | ); 16 | 17 | oapiHttpInstance.interceptors.response.use((response) => response.data); 18 | -------------------------------------------------------------------------------- /src/utils/noop.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-empty-function 2 | export const noop = () => {}; 3 | -------------------------------------------------------------------------------- /src/utils/version.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8')); 5 | export const currentVersion = packageJson.version; 6 | -------------------------------------------------------------------------------- /tests/cli.test.ts: -------------------------------------------------------------------------------- 1 | import * as server from '../src/mcp-server'; 2 | 3 | interface MockCommand { 4 | options: any[]; 5 | description: jest.Mock; 6 | option: jest.Mock; 7 | action: jest.Mock; 8 | actionCallback?: (options: any) => void; 9 | } 10 | 11 | // 模拟cli模块 12 | jest.mock( 13 | '../src/cli', 14 | () => { 15 | // 创建mock程序实例,以便不会调用原始程序逻辑 16 | const mockProgram = { 17 | parse: jest.fn(), 18 | help: jest.fn(), 19 | commands: [] as MockCommand[], 20 | command: function (name: string) { 21 | const cmd: MockCommand = { 22 | options: [], 23 | description: jest.fn().mockReturnThis(), 24 | option: jest.fn().mockReturnThis(), 25 | action: jest.fn().mockImplementation((callback) => { 26 | // 存储action回调以便测试可以调用它 27 | cmd.actionCallback = callback; 28 | return cmd; 29 | }), 30 | }; 31 | this.commands.push(cmd); 32 | return cmd; 33 | }, 34 | }; 35 | 36 | return { 37 | program: mockProgram, 38 | }; 39 | }, 40 | { virtual: true }, 41 | ); 42 | 43 | // 模拟server模块 44 | jest.mock('../src/mcp-server', () => ({ 45 | initStdioServer: jest.fn(), 46 | initSSEServer: jest.fn(), 47 | initStreamableHTTPServer: jest.fn(), 48 | })); 49 | 50 | // 模拟 McpServer 51 | jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ 52 | McpServer: jest.fn().mockImplementation(() => ({ 53 | connect: jest.fn().mockResolvedValue(undefined), 54 | server: {}, 55 | _registeredResources: {}, 56 | _registeredResourceTemplates: {}, 57 | _registeredTools: {}, 58 | })), 59 | })); 60 | 61 | // 模拟process.exit 62 | const mockExit = jest.fn(); 63 | process.exit = mockExit as any; 64 | 65 | // 模拟console.error 66 | const originalConsoleError = console.error; 67 | console.error = jest.fn(); 68 | 69 | describe('CLI', () => { 70 | // 创建模拟的action回调函数 71 | const actionCallback = jest.fn((options) => { 72 | if (options.mode === 'stdio') { 73 | server.initStdioServer(options); 74 | } else if (options.mode === 'sse') { 75 | // 使用从 @modelcontextprotocol/sdk/server/mcp.js 导入的 McpServer 76 | const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); 77 | const mockMcpServer = new McpServer(); 78 | server.initSSEServer(mockMcpServer, options); 79 | } else { 80 | console.error('Invalid mode:', options.mode); 81 | process.exit(1); 82 | } 83 | }); 84 | 85 | beforeEach(() => { 86 | jest.clearAllMocks(); 87 | // 重置console.error模拟 88 | (console.error as jest.Mock).mockReset(); 89 | // 重置process.exit模拟 90 | mockExit.mockReset(); 91 | // 重置action回调 92 | actionCallback.mockClear(); 93 | }); 94 | 95 | afterAll(() => { 96 | // 恢复原始的console.error 97 | console.error = originalConsoleError; 98 | }); 99 | 100 | it('应该初始化stdio服务器', () => { 101 | const options = { 102 | appId: 'test-app-id', 103 | appSecret: 'test-app-secret', 104 | mode: 'stdio', 105 | }; 106 | 107 | // 调用action回调 108 | actionCallback(options); 109 | 110 | // 验证 111 | expect(server.initStdioServer).toHaveBeenCalledWith( 112 | expect.objectContaining({ 113 | appId: 'test-app-id', 114 | appSecret: 'test-app-secret', 115 | mode: 'stdio', 116 | }), 117 | ); 118 | }); 119 | 120 | it('应该初始化SSE服务器', () => { 121 | const options = { 122 | appId: 'test-app-id', 123 | appSecret: 'test-app-secret', 124 | mode: 'sse', 125 | port: '3001', 126 | }; 127 | 128 | // 调用action回调 129 | actionCallback(options); 130 | 131 | // 验证 132 | expect(server.initSSEServer).toHaveBeenCalledWith( 133 | expect.anything(), // 验证第一个参数(mockServer)是任何值 134 | expect.objectContaining({ 135 | appId: 'test-app-id', 136 | appSecret: 'test-app-secret', 137 | mode: 'sse', 138 | port: '3001', 139 | }), 140 | ); 141 | }); 142 | 143 | it('应该在模式无效时退出', () => { 144 | const options = { 145 | appId: 'test-app-id', 146 | appSecret: 'test-app-secret', 147 | mode: 'invalid', 148 | }; 149 | 150 | // 调用action回调 151 | actionCallback(options); 152 | 153 | // 验证 154 | expect(console.error).toHaveBeenCalledWith('Invalid mode:', 'invalid'); 155 | expect(mockExit).toHaveBeenCalledWith(1); 156 | }); 157 | 158 | it('应该使用从配置文件加载的选项', () => { 159 | const fileOptions = { 160 | appId: 'config-app-id', 161 | appSecret: 'config-app-secret', 162 | }; 163 | 164 | // 调用action回调 165 | actionCallback({ 166 | ...fileOptions, 167 | mode: 'stdio', 168 | config: 'config.json', 169 | }); 170 | 171 | // 验证 172 | expect(server.initStdioServer).toHaveBeenCalledWith( 173 | expect.objectContaining({ 174 | appId: 'config-app-id', 175 | appSecret: 'config-app-secret', 176 | mode: 'stdio', 177 | config: 'config.json', 178 | }), 179 | ); 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /tests/mcp-server/shared/init.test.ts: -------------------------------------------------------------------------------- 1 | import { initMcpServer } from '../../../src/mcp-server/shared/init'; 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 3 | 4 | // 模拟依赖项 5 | jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ 6 | McpServer: jest.fn().mockImplementation(() => ({ 7 | connect: jest.fn().mockResolvedValue(undefined), 8 | })), 9 | })); 10 | 11 | // 模拟mcp-tool模块 12 | jest.mock('../../../src/mcp-tool', () => { 13 | return { 14 | LarkMcpTool: jest.fn().mockImplementation(() => ({ 15 | updateUserAccessToken: jest.fn(), 16 | registerMcpServer: jest.fn(), 17 | })), 18 | defaultToolNames: ['default-tool-1', 'default-tool-2'], 19 | presetTools: { 20 | 'preset.default': ['default-tool-1', 'default-tool-2'], 21 | }, 22 | }; 23 | }); 24 | 25 | // 保存原始的环境变量和console.error 26 | const originalEnv = process.env; 27 | const originalConsoleError = console.error; 28 | const originalProcessExit = process.exit; 29 | 30 | describe('initMcpServer', () => { 31 | beforeEach(() => { 32 | // 重置模拟 33 | jest.clearAllMocks(); 34 | 35 | // 模拟环境变量 36 | process.env = { ...originalEnv }; 37 | 38 | // 模拟 console.error 39 | console.error = jest.fn(); 40 | 41 | // 模拟 process.exit 42 | process.exit = jest.fn() as any; 43 | }); 44 | 45 | afterEach(() => { 46 | // 恢复原始环境变量和函数 47 | process.env = originalEnv; 48 | console.error = originalConsoleError; 49 | process.exit = originalProcessExit; 50 | }); 51 | 52 | it('应该使用提供的凭证初始化服务器', () => { 53 | const options = { 54 | appId: 'test-app-id', 55 | appSecret: 'test-app-secret', 56 | host: 'localhost', 57 | port: 3000, 58 | }; 59 | 60 | const { mcpServer, larkClient } = initMcpServer(options); 61 | 62 | expect(McpServer).toHaveBeenCalled(); 63 | // 从mcp-tool模块导入LarkMcpTool 64 | const { LarkMcpTool } = require('../../../src/mcp-tool'); 65 | expect(LarkMcpTool).toHaveBeenCalledWith( 66 | expect.objectContaining({ 67 | appId: 'test-app-id', 68 | appSecret: 'test-app-secret', 69 | }), 70 | ); 71 | }); 72 | 73 | it('应该使用环境变量中的凭证', () => { 74 | process.env.APP_ID = 'env-app-id'; 75 | process.env.APP_SECRET = 'env-app-secret'; 76 | 77 | const options = { 78 | host: 'localhost', 79 | port: 3000, 80 | }; 81 | 82 | const { mcpServer, larkClient } = initMcpServer(options); 83 | 84 | // 从mcp-tool模块导入LarkMcpTool 85 | const { LarkMcpTool } = require('../../../src/mcp-tool'); 86 | expect(LarkMcpTool).toHaveBeenCalledWith( 87 | expect.objectContaining({ 88 | appId: 'env-app-id', 89 | appSecret: 'env-app-secret', 90 | }), 91 | ); 92 | }); 93 | 94 | it('如果提供了userAccessToken,应该调用updateUserAccessToken', () => { 95 | const options = { 96 | appId: 'test-app-id', 97 | appSecret: 'test-app-secret', 98 | userAccessToken: 'test-user-access-token', 99 | host: 'localhost', 100 | port: 3000, 101 | }; 102 | 103 | const { larkClient } = initMcpServer(options); 104 | 105 | expect(larkClient.updateUserAccessToken).toHaveBeenCalledWith('test-user-access-token'); 106 | }); 107 | 108 | it('应该处理数组形式的tools参数', () => { 109 | const options = { 110 | appId: 'test-app-id', 111 | appSecret: 'test-app-secret', 112 | tools: ['tool1', 'tool2'], 113 | host: 'localhost', 114 | port: 3000, 115 | }; 116 | 117 | const { larkClient } = initMcpServer(options); 118 | 119 | // 从mcp-tool模块导入LarkMcpTool 120 | const { LarkMcpTool } = require('../../../src/mcp-tool'); 121 | expect(LarkMcpTool).toHaveBeenCalledWith( 122 | expect.objectContaining({ 123 | toolsOptions: expect.objectContaining({ 124 | allowTools: ['tool1', 'tool2'], 125 | }), 126 | }), 127 | ); 128 | }); 129 | 130 | it('应该处理字符串形式的tools参数', () => { 131 | const options = { 132 | appId: 'test-app-id', 133 | appSecret: 'test-app-secret', 134 | tools: 'tool1,tool2', 135 | host: 'localhost', 136 | port: 3000, 137 | }; 138 | 139 | const { larkClient } = initMcpServer(options); 140 | 141 | // 从mcp-tool模块导入LarkMcpTool 142 | const { LarkMcpTool } = require('../../../src/mcp-tool'); 143 | expect(LarkMcpTool).toHaveBeenCalledWith( 144 | expect.objectContaining({ 145 | toolsOptions: expect.objectContaining({ 146 | allowTools: ['tool1', 'tool2'], 147 | }), 148 | }), 149 | ); 150 | }); 151 | 152 | it('如果凭证缺失,应该退出程序', () => { 153 | const options = { 154 | host: 'localhost', 155 | port: 3000, 156 | }; 157 | 158 | // 清除环境变量 159 | delete process.env.APP_ID; 160 | delete process.env.APP_SECRET; 161 | 162 | initMcpServer(options); 163 | 164 | expect(console.error).toHaveBeenCalled(); 165 | expect(process.exit).toHaveBeenCalledWith(1); 166 | }); 167 | 168 | it('应该处理preset.default工具集', () => { 169 | const options = { 170 | appId: 'test-app-id', 171 | appSecret: 'test-app-secret', 172 | tools: ['preset.default', 'extra-tool'], 173 | host: 'localhost', 174 | port: 3000, 175 | }; 176 | 177 | // 从模块导入默认工具列表 178 | const { defaultToolNames } = require('../../../src/mcp-tool'); 179 | 180 | const { larkClient } = initMcpServer(options); 181 | 182 | // 从mcp-tool模块导入LarkMcpTool 183 | const { LarkMcpTool } = require('../../../src/mcp-tool'); 184 | // 检查是否合并了默认工具和额外的工具 185 | expect(LarkMcpTool).toHaveBeenCalledWith( 186 | expect.objectContaining({ 187 | toolsOptions: expect.objectContaining({ 188 | allowTools: expect.arrayContaining([...defaultToolNames, 'preset.default', 'extra-tool']), 189 | }), 190 | }), 191 | ); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /tests/mcp-server/stdio.test.ts: -------------------------------------------------------------------------------- 1 | import { initStdioServer } from '../../src/mcp-server/stdio'; 2 | import { initMcpServer } from '../../src/mcp-server/shared/init'; 3 | import { McpServerOptions } from '../../src/mcp-server/shared/types'; 4 | 5 | // 创建可追踪的模拟函数 6 | const connectMock = jest.fn().mockResolvedValue(undefined); 7 | const connectErrorMock = jest.fn().mockRejectedValue(new Error('Connection error')); 8 | 9 | // 模拟依赖 10 | jest.mock('../../src/mcp-server/shared/init', () => { 11 | return { 12 | initMcpServer: jest.fn().mockImplementation(() => ({ 13 | mcpServer: { 14 | connect: connectMock, 15 | }, 16 | larkClient: {}, 17 | })), 18 | }; 19 | }); 20 | 21 | // 模拟 McpServer 22 | jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ 23 | McpServer: jest.fn().mockImplementation(() => ({ 24 | connect: connectMock, 25 | server: {}, 26 | _registeredResources: {}, 27 | _registeredResourceTemplates: {}, 28 | _registeredTools: {}, 29 | })), 30 | })); 31 | 32 | jest.mock('@modelcontextprotocol/sdk/server/stdio', () => ({ 33 | StdioServerTransport: jest.fn().mockImplementation(() => ({ 34 | connect: jest.fn(), 35 | })), 36 | })); 37 | 38 | // 保存原始console 39 | const originalConsole = console; 40 | 41 | // 创建用于stdin的模拟函数 42 | const setEncodingMock = jest.fn().mockReturnValue(process.stdin); 43 | const resumeMock = jest.fn().mockReturnValue(process.stdin); 44 | const onMock = jest.fn().mockReturnValue(process.stdin); 45 | 46 | describe('initStdioServer', () => { 47 | beforeEach(() => { 48 | // 重置所有模拟 49 | jest.clearAllMocks(); 50 | 51 | // 模拟console方法 52 | console.log = jest.fn(); 53 | console.error = jest.fn(); 54 | 55 | // 模拟process方法,但不直接替换对象 56 | jest.spyOn(process.stdout, 'write').mockImplementation(() => true); 57 | jest.spyOn(process.stdin, 'setEncoding').mockImplementation(setEncodingMock); 58 | jest.spyOn(process.stdin, 'resume').mockImplementation(resumeMock); 59 | jest.spyOn(process.stdin, 'on').mockImplementation(onMock); 60 | jest.spyOn(process.stderr, 'write').mockImplementation(() => true); 61 | jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null) => { 62 | return undefined as never; 63 | }); 64 | }); 65 | 66 | afterEach(() => { 67 | // 恢复原始console 68 | console = originalConsole; 69 | 70 | // 清除所有模拟 71 | jest.restoreAllMocks(); 72 | }); 73 | 74 | it('应该初始化MCP服务器并连接', () => { 75 | const options: McpServerOptions = { 76 | appId: 'test-app-id', 77 | appSecret: 'test-app-secret', 78 | host: 'localhost', 79 | port: 3000, 80 | }; 81 | 82 | // 首先初始化MCP服务器 83 | const { mcpServer } = initMcpServer(options); 84 | 85 | // 然后使用mcpServer调用initStdioServer 86 | initStdioServer(mcpServer); 87 | 88 | // 验证调用 89 | expect(initMcpServer).toHaveBeenCalledWith(options); 90 | expect(connectMock).toHaveBeenCalled(); 91 | }); 92 | 93 | it('应该处理连接错误', () => { 94 | const options: McpServerOptions = { 95 | appId: 'test-app-id', 96 | appSecret: 'test-app-secret', 97 | host: 'localhost', 98 | port: 3000, 99 | }; 100 | 101 | // 修改connect的实现以模拟错误 102 | (initMcpServer as jest.Mock).mockImplementationOnce(() => ({ 103 | mcpServer: { 104 | connect: connectErrorMock, 105 | }, 106 | larkClient: {}, 107 | })); 108 | 109 | // 首先初始化MCP服务器 110 | const { mcpServer } = initMcpServer(options); 111 | 112 | // 然后使用mcpServer调用initStdioServer 113 | initStdioServer(mcpServer); 114 | 115 | // 假设错误处理是异步的,我们需要等待Promise rejecting 116 | return new Promise(process.nextTick).then(() => { 117 | // 验证错误处理 118 | expect(connectErrorMock).toHaveBeenCalled(); 119 | expect(console.error).toHaveBeenCalledWith('MCP Connect Error:', expect.any(Error)); 120 | expect(process.exit).toHaveBeenCalledWith(1); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /tests/mcp-tool/constants.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultToolNames } from '../../src/mcp-tool/constants'; 2 | 3 | describe('constants', () => { 4 | describe('defaultToolNames', () => { 5 | it('should be an array', () => { 6 | expect(Array.isArray(defaultToolNames)).toBe(true); 7 | }); 8 | 9 | it('should contain expected tool names', () => { 10 | // 检查是否包含一些核心工具 11 | expect(defaultToolNames).toContain('im.v1.message.create'); 12 | expect(defaultToolNames).toContain('im.v1.chat.create'); 13 | expect(defaultToolNames).toContain('bitable.v1.app.create'); 14 | expect(defaultToolNames).toContain('wiki.v1.node.search'); 15 | }); 16 | 17 | it('should have more than 5 tools', () => { 18 | expect(defaultToolNames.length).toBeGreaterThan(5); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/mcp-tool/document-tool/recall/index.test.ts: -------------------------------------------------------------------------------- 1 | import { RecallTool } from '../../../../src/mcp-tool/document-tool/recall'; 2 | import * as request from '../../../../src/mcp-tool/document-tool/recall/request'; 3 | import { DocumentRecallToolOptions } from '../../../../src/mcp-tool/document-tool/recall/type'; 4 | 5 | // 模拟请求模块 6 | jest.mock('../../../../src/mcp-tool/document-tool/recall/request'); 7 | 8 | describe('RecallTool', () => { 9 | // 获取模拟函数 10 | const mockedRecallDeveloperDocument = request.recallDeveloperDocument as jest.Mock; 11 | 12 | beforeEach(() => { 13 | // 重置所有模拟 14 | jest.clearAllMocks(); 15 | }); 16 | 17 | it('应该具有正确的名称和描述', () => { 18 | expect(RecallTool.name).toBe('openplatform_developer_document_recall'); 19 | expect(RecallTool.description).toMatch(/Recall for relevant documents/); 20 | expect(RecallTool.schema).toBeDefined(); 21 | }); 22 | 23 | it('应该处理正常结果', async () => { 24 | // 模拟搜索结果 25 | const mockResults = ['result1', 'result2', 'result3']; 26 | mockedRecallDeveloperDocument.mockResolvedValueOnce(mockResults); 27 | 28 | // 调用参数 29 | const params = { query: 'test query' }; 30 | const options: DocumentRecallToolOptions = { domain: 'https://example.com' }; 31 | 32 | // 执行处理程序 33 | const result = await RecallTool.handler(params, options); 34 | 35 | // 验证结果 36 | expect(mockedRecallDeveloperDocument).toHaveBeenCalledWith('test query', options); 37 | expect(result).toEqual({ 38 | content: [ 39 | { 40 | type: 'text', 41 | text: `Find 3 results:\n${mockResults.join('\n\n')}`, 42 | }, 43 | ], 44 | }); 45 | }); 46 | 47 | it('应该处理空结果', async () => { 48 | // 模拟空搜索结果 49 | mockedRecallDeveloperDocument.mockResolvedValueOnce([]); 50 | 51 | // 调用参数 52 | const params = { query: 'test query' }; 53 | const options: DocumentRecallToolOptions = { domain: 'https://example.com' }; 54 | 55 | // 执行处理程序 56 | const result = await RecallTool.handler(params, options); 57 | 58 | // 验证结果 59 | expect(result).toEqual({ 60 | content: [ 61 | { 62 | type: 'text', 63 | text: 'No results found', 64 | }, 65 | ], 66 | }); 67 | }); 68 | 69 | it('应该处理错误情况', async () => { 70 | // 模拟错误 71 | const mockError = new Error('API error'); 72 | mockedRecallDeveloperDocument.mockRejectedValueOnce(mockError); 73 | 74 | // 调用参数 75 | const params = { query: 'test query' }; 76 | const options: DocumentRecallToolOptions = { domain: 'https://example.com' }; 77 | 78 | // 执行处理程序 79 | const result = await RecallTool.handler(params, options); 80 | 81 | // 验证结果 82 | expect(result).toEqual({ 83 | isError: true, 84 | content: [ 85 | { 86 | type: 'text', 87 | text: 'Search failed:API error', 88 | }, 89 | ], 90 | }); 91 | }); 92 | 93 | it('应该处理未知错误类型', async () => { 94 | // 模拟非Error类型的错误 95 | mockedRecallDeveloperDocument.mockRejectedValueOnce('String error'); 96 | 97 | // 调用参数 98 | const params = { query: 'test query' }; 99 | const options: DocumentRecallToolOptions = { domain: 'https://example.com' }; 100 | 101 | // 执行处理程序 102 | const result = await RecallTool.handler(params, options); 103 | 104 | // 验证结果 105 | expect(result).toEqual({ 106 | isError: true, 107 | content: [ 108 | { 109 | type: 'text', 110 | text: 'Search failed:Unknown error', 111 | }, 112 | ], 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /tests/mcp-tool/document-tool/recall/request.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { recallDeveloperDocument } from '../../../../src/mcp-tool/document-tool/recall/request'; 3 | import { DocumentRecallToolOptions } from '../../../../src/mcp-tool/document-tool/recall/type'; 4 | import { USER_AGENT } from '../../../../src/utils/constants'; 5 | 6 | // 模拟axios 7 | jest.mock('axios'); 8 | const mockedAxios = axios as jest.Mocked; 9 | 10 | describe('recallDeveloperDocument', () => { 11 | // 每个测试前重置模拟 12 | beforeEach(() => { 13 | jest.clearAllMocks(); 14 | }); 15 | 16 | it('应该使用正确的URL和参数发送请求', async () => { 17 | // 模拟响应数据 18 | const mockResponse = { 19 | data: { 20 | chunks: ['result1', 'result2', 'result3', 'result4'], 21 | }, 22 | }; 23 | mockedAxios.post.mockResolvedValueOnce(mockResponse); 24 | 25 | // 测试参数 26 | const query = 'test query'; 27 | const options: DocumentRecallToolOptions = { 28 | domain: 'https://example.com', 29 | count: 3, 30 | }; 31 | 32 | // 调用函数 33 | await recallDeveloperDocument(query, options); 34 | 35 | // 验证请求参数 36 | expect(mockedAxios.post).toHaveBeenCalledWith( 37 | 'https://example.com/document_portal/v1/recall', 38 | { question: query }, 39 | { 40 | timeout: 10000, 41 | headers: { 'User-Agent': USER_AGENT }, 42 | }, 43 | ); 44 | }); 45 | 46 | it('应该返回指定数量的结果', async () => { 47 | // 模拟响应数据 48 | const mockResponse = { 49 | data: { 50 | chunks: ['result1', 'result2', 'result3', 'result4'], 51 | }, 52 | }; 53 | mockedAxios.post.mockResolvedValueOnce(mockResponse); 54 | 55 | // 测试参数 56 | const query = 'test query'; 57 | const options: DocumentRecallToolOptions = { 58 | domain: 'https://example.com', 59 | count: 2, 60 | }; 61 | 62 | // 调用函数并获取结果 63 | const results = await recallDeveloperDocument(query, options); 64 | 65 | // 验证返回结果数量 66 | expect(results).toHaveLength(2); 67 | expect(results).toEqual(['result1', 'result2']); 68 | }); 69 | 70 | it('应该使用默认数量(3)当未指定count时', async () => { 71 | // 模拟响应数据 72 | const mockResponse = { 73 | data: { 74 | chunks: ['result1', 'result2', 'result3', 'result4', 'result5'], 75 | }, 76 | }; 77 | mockedAxios.post.mockResolvedValueOnce(mockResponse); 78 | 79 | // 测试参数,不包含count 80 | const query = 'test query'; 81 | const options: DocumentRecallToolOptions = { 82 | domain: 'https://example.com', 83 | }; 84 | 85 | // 调用函数并获取结果 86 | const results = await recallDeveloperDocument(query, options); 87 | 88 | // 验证返回结果数量为默认值3 89 | expect(results).toHaveLength(3); 90 | expect(results).toEqual(['result1', 'result2', 'result3']); 91 | }); 92 | 93 | it('应该返回空数组当chunks为空时', async () => { 94 | // 模拟响应数据 95 | const mockResponse = { 96 | data: { 97 | chunks: [], 98 | }, 99 | }; 100 | mockedAxios.post.mockResolvedValueOnce(mockResponse); 101 | 102 | // 测试参数 103 | const query = 'test query'; 104 | const options: DocumentRecallToolOptions = { 105 | domain: 'https://example.com', 106 | count: 3, 107 | }; 108 | 109 | // 调用函数并获取结果 110 | const results = await recallDeveloperDocument(query, options); 111 | 112 | // 验证返回结果为空数组 113 | expect(results).toEqual([]); 114 | }); 115 | 116 | it('应该返回空数组当响应中没有chunks字段时', async () => { 117 | // 模拟响应数据 118 | const mockResponse = { 119 | data: {}, 120 | }; 121 | mockedAxios.post.mockResolvedValueOnce(mockResponse); 122 | 123 | // 测试参数 124 | const query = 'test query'; 125 | const options: DocumentRecallToolOptions = { 126 | domain: 'https://example.com', 127 | count: 3, 128 | }; 129 | 130 | // 调用函数并获取结果 131 | const results = await recallDeveloperDocument(query, options); 132 | 133 | // 验证返回结果为空数组 134 | expect(results).toEqual([]); 135 | }); 136 | 137 | it('应该抛出错误当请求失败时', async () => { 138 | // 模拟网络错误 139 | const errorMessage = 'Network Error'; 140 | mockedAxios.post.mockRejectedValueOnce(new Error(errorMessage)); 141 | 142 | // 测试参数 143 | const query = 'test query'; 144 | const options: DocumentRecallToolOptions = { 145 | domain: 'https://example.com', 146 | count: 3, 147 | }; 148 | 149 | // 验证函数抛出预期的错误 150 | await expect(recallDeveloperDocument(query, options)).rejects.toThrow(errorMessage); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /tests/mcp-tool/utils/case-transf.test.ts: -------------------------------------------------------------------------------- 1 | import { caseTransf } from '../../../src/mcp-tool/utils/case-transf'; 2 | 3 | describe('caseTransf', () => { 4 | const testToolName = 'im.v1.chat.create'; 5 | 6 | it('应该将点号转换为下划线 (snake case)', () => { 7 | const result = caseTransf(testToolName, 'snake'); 8 | expect(result).toBe('im_v1_chat_create'); 9 | }); 10 | 11 | it('应该将点号转换为驼峰命名 (camel case)', () => { 12 | const result = caseTransf(testToolName, 'camel'); 13 | expect(result).toBe('imV1ChatCreate'); 14 | }); 15 | 16 | it('应该将点号转换为短横线 (kebab case)', () => { 17 | const result = caseTransf(testToolName, 'kebab'); 18 | expect(result).toBe('im-v1-chat-create'); 19 | }); 20 | 21 | it('不指定时应该保持原样 (dot case)', () => { 22 | const result = caseTransf(testToolName); 23 | expect(result).toBe('im.v1.chat.create'); 24 | }); 25 | 26 | it('处理已经是目标格式的工具名', () => { 27 | const kebabCaseName = 'im-v1-chat-create'; 28 | const result = caseTransf(kebabCaseName, 'kebab'); 29 | expect(result).toBe('im-v1-chat-create'); 30 | }); 31 | 32 | it('处理空字符串', () => { 33 | const result = caseTransf('', 'camel'); 34 | expect(result).toBe(''); 35 | }); 36 | 37 | it('处理没有点号的工具名', () => { 38 | const noDotName = 'createchat'; 39 | const result = caseTransf(noDotName, 'snake'); 40 | expect(result).toBe('createchat'); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/mcp-tool/utils/filter-tools.test.ts: -------------------------------------------------------------------------------- 1 | import { filterTools } from '../../../src/mcp-tool/utils/filter-tools'; 2 | import { McpTool, ToolsFilterOptions, TokenMode } from '../../../src/mcp-tool/types'; 3 | import { ProjectName, ToolName } from '../../../src/mcp-tool/tools'; 4 | 5 | describe('filterTools', () => { 6 | const mockTools: McpTool[] = [ 7 | { 8 | name: 'im.v1.message.create', 9 | description: '发送消息', 10 | schema: {}, 11 | project: 'im', 12 | accessTokens: ['user', 'tenant'], 13 | }, 14 | { 15 | name: 'im.v1.chat.create', 16 | description: '创建群', 17 | schema: {}, 18 | project: 'im', 19 | accessTokens: ['user', 'tenant'], 20 | }, 21 | { 22 | name: 'wiki.v1.node.search', 23 | description: '搜索知识库节点', 24 | schema: {}, 25 | project: 'wiki', 26 | accessTokens: ['user'], 27 | }, 28 | { 29 | name: 'docx.v1.document.rawContent', 30 | description: '获取文档内容', 31 | schema: {}, 32 | project: 'docx', 33 | accessTokens: ['tenant'], 34 | }, 35 | ]; 36 | 37 | it('应该按工具名称过滤', () => { 38 | const options: ToolsFilterOptions = { 39 | allowTools: ['im.v1.message.create', 'docx.v1.document.rawContent'] as ToolName[], 40 | }; 41 | 42 | const result = filterTools(mockTools, options); 43 | expect(result).toHaveLength(2); 44 | expect(result[0].name).toBe('im.v1.message.create'); 45 | expect(result[1].name).toBe('docx.v1.document.rawContent'); 46 | }); 47 | 48 | it('应该按项目名称过滤', () => { 49 | const options: ToolsFilterOptions = { 50 | allowProjects: ['im'] as ProjectName[], 51 | }; 52 | 53 | const result = filterTools(mockTools, options); 54 | expect(result).toHaveLength(2); 55 | expect(result[0].project).toBe('im'); 56 | expect(result[1].project).toBe('im'); 57 | }); 58 | 59 | it('应该按token模式过滤 - user_access_token', () => { 60 | const options: ToolsFilterOptions = { 61 | tokenMode: TokenMode.USER_ACCESS_TOKEN, 62 | allowTools: mockTools.map((tool) => tool.name) as ToolName[], 63 | }; 64 | 65 | const result = filterTools(mockTools, options); 66 | expect(result).toHaveLength(3); 67 | expect(result).toContainEqual(expect.objectContaining({ name: 'im.v1.message.create' })); 68 | expect(result).toContainEqual(expect.objectContaining({ name: 'im.v1.chat.create' })); 69 | expect(result).toContainEqual(expect.objectContaining({ name: 'wiki.v1.node.search' })); 70 | expect(result).not.toContainEqual(expect.objectContaining({ name: 'docx.v1.document.rawContent' })); 71 | }); 72 | 73 | it('应该按token模式过滤 - tenant_access_token', () => { 74 | const options: ToolsFilterOptions = { 75 | tokenMode: TokenMode.TENANT_ACCESS_TOKEN, 76 | allowTools: mockTools.map((tool) => tool.name) as ToolName[], 77 | }; 78 | 79 | const result = filterTools(mockTools, options); 80 | expect(result).toHaveLength(3); 81 | expect(result).toContainEqual(expect.objectContaining({ name: 'im.v1.message.create' })); 82 | expect(result).toContainEqual(expect.objectContaining({ name: 'im.v1.chat.create' })); 83 | expect(result).toContainEqual(expect.objectContaining({ name: 'docx.v1.document.rawContent' })); 84 | expect(result).not.toContainEqual(expect.objectContaining({ name: 'wiki.v1.node.search' })); 85 | }); 86 | 87 | it('应该按token模式过滤 - unknown', () => { 88 | const options: ToolsFilterOptions = { 89 | tokenMode: 'unknown' as TokenMode, 90 | allowTools: mockTools.map((tool) => tool.name) as ToolName[], 91 | }; 92 | 93 | const result = filterTools(mockTools, options); 94 | expect(result).toHaveLength(4); 95 | }); 96 | 97 | it('应该按多个条件组合过滤', () => { 98 | const options: ToolsFilterOptions = { 99 | allowProjects: ['im'] as ProjectName[], 100 | tokenMode: TokenMode.USER_ACCESS_TOKEN, 101 | }; 102 | 103 | const result = filterTools(mockTools, options); 104 | expect(result).toHaveLength(2); 105 | expect(result[0].name).toBe('im.v1.message.create'); 106 | expect(result[1].name).toBe('im.v1.chat.create'); 107 | }); 108 | 109 | it('当使用auto模式时应该返回所有工具', () => { 110 | const options: ToolsFilterOptions = { 111 | tokenMode: TokenMode.AUTO, 112 | allowTools: mockTools.map((tool) => tool.name) as ToolName[], 113 | }; 114 | 115 | const result = filterTools(mockTools, options); 116 | expect(result).toHaveLength(4); 117 | }); 118 | 119 | it('应该处理工具和令牌类型同时过滤', () => { 120 | const options: ToolsFilterOptions = { 121 | allowTools: ['im.v1.message.create', 'docx.v1.document.rawContent'], 122 | tokenMode: TokenMode.USER_ACCESS_TOKEN, 123 | }; 124 | 125 | const result = filterTools(mockTools, options); 126 | expect(result).toHaveLength(1); 127 | expect(result[0].name).toBe('im.v1.message.create'); 128 | }); 129 | 130 | it('应该处理工具没有accessTokens属性的情况', () => { 131 | const toolsWithoutAccessTokens: McpTool[] = [ 132 | { 133 | name: 'test.tool', 134 | description: '测试工具', 135 | schema: {}, 136 | project: 'test', 137 | // 没有accessTokens属性 138 | }, 139 | ]; 140 | 141 | // 测试user_access_token模式 142 | const filteredUser = filterTools(toolsWithoutAccessTokens, { 143 | tokenMode: TokenMode.USER_ACCESS_TOKEN, 144 | allowTools: ['test.tool'] as unknown as ToolName[], 145 | }); 146 | expect(filteredUser.length).toBe(0); 147 | 148 | // 测试tenant_access_token模式 149 | const filteredTenant = filterTools(toolsWithoutAccessTokens, { 150 | tokenMode: TokenMode.TENANT_ACCESS_TOKEN, 151 | allowTools: ['test.tool'] as unknown as ToolName[], 152 | }); 153 | expect(filteredTenant.length).toBe(0); 154 | 155 | // 测试auto模式 156 | const filteredAuto = filterTools(toolsWithoutAccessTokens, { 157 | tokenMode: TokenMode.AUTO, 158 | allowTools: ['test.tool'] as unknown as ToolName[], 159 | }); 160 | expect(filteredAuto.length).toBe(1); 161 | }); 162 | 163 | it('应该处理不包含任何过滤条件的情况', () => { 164 | const filtered = filterTools(mockTools, {}); 165 | expect(filtered.length).toBe(0); // 因为没有指定allowTools和allowProjects 166 | }); 167 | 168 | it('应该组合多个过滤条件 - 允许项目和名称', () => { 169 | const options: ToolsFilterOptions = { 170 | allowProjects: ['docx'], 171 | allowTools: ['im.v1.message.create'], 172 | }; 173 | 174 | const result = filterTools(mockTools, options); 175 | expect(result).toHaveLength(2); 176 | expect(result).toEqual( 177 | expect.arrayContaining([ 178 | expect.objectContaining({ name: 'im.v1.message.create' }), 179 | expect.objectContaining({ name: 'docx.v1.document.rawContent' }), 180 | ]), 181 | ); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /tests/mcp-tool/utils/get-should-use-uat.test.ts: -------------------------------------------------------------------------------- 1 | import { getShouldUseUAT } from '../../../src/mcp-tool/utils/get-should-use-uat'; 2 | import { TokenMode } from '../../../src/mcp-tool/types'; 3 | 4 | describe('getShouldUseUAT', () => { 5 | describe('USER_ACCESS_TOKEN 模式', () => { 6 | it('当有 userAccessToken 时应返回 true', () => { 7 | expect(getShouldUseUAT(TokenMode.USER_ACCESS_TOKEN, 'token', false)).toBe(true); 8 | expect(getShouldUseUAT(TokenMode.USER_ACCESS_TOKEN, 'token', true)).toBe(true); 9 | }); 10 | 11 | it('当没有 userAccessToken 时应抛出错误', () => { 12 | expect(() => getShouldUseUAT(TokenMode.USER_ACCESS_TOKEN, undefined, false)).toThrow('Invalid UserAccessToken'); 13 | expect(() => getShouldUseUAT(TokenMode.USER_ACCESS_TOKEN, undefined, true)).toThrow('Invalid UserAccessToken'); 14 | }); 15 | }); 16 | 17 | describe('TENANT_ACCESS_TOKEN 模式', () => { 18 | it('无论参数如何都应返回 false', () => { 19 | expect(getShouldUseUAT(TokenMode.TENANT_ACCESS_TOKEN, undefined, false)).toBe(false); 20 | expect(getShouldUseUAT(TokenMode.TENANT_ACCESS_TOKEN, undefined, true)).toBe(false); 21 | expect(getShouldUseUAT(TokenMode.TENANT_ACCESS_TOKEN, 'token', false)).toBe(false); 22 | expect(getShouldUseUAT(TokenMode.TENANT_ACCESS_TOKEN, 'token', true)).toBe(false); 23 | }); 24 | }); 25 | 26 | describe('AUTO 模式', () => { 27 | it('当有 userAccessToken 且 useUAT 为 true 时应返回 true', () => { 28 | expect(getShouldUseUAT(TokenMode.AUTO, 'token', true)).toBe(true); 29 | }); 30 | 31 | it('在其他情况下应返回 false', () => { 32 | expect(getShouldUseUAT(TokenMode.AUTO, undefined, true)).toBe(false); 33 | expect(getShouldUseUAT(TokenMode.AUTO, undefined, false)).toBe(false); 34 | expect(getShouldUseUAT(TokenMode.AUTO, 'token', false)).toBe(false); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- 1 | jest.mock('@larksuiteoapi/node-sdk', () => { 2 | return { 3 | Client: jest.fn().mockImplementation(() => ({ 4 | request: jest.fn(), 5 | im: { 6 | message: { 7 | create: jest.fn(), 8 | }, 9 | chat: { 10 | create: jest.fn(), 11 | list: jest.fn(), 12 | }, 13 | }, 14 | })), 15 | withUserAccessToken: jest.fn((token) => ({ userAccessToken: token })), 16 | }; 17 | }); 18 | 19 | beforeEach(() => { 20 | jest.clearAllMocks(); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/utils/http-instance.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { USER_AGENT } from '../../src/utils/constants'; 3 | import { oapiHttpInstance } from '../../src/utils/http-instance'; 4 | 5 | // 我们需要在导入被测模块前先模拟axios 6 | jest.mock('axios', () => { 7 | return { 8 | create: jest.fn(() => ({ 9 | interceptors: { 10 | request: { 11 | use: jest.fn((successFn) => { 12 | // 保存拦截器函数以便测试 13 | (axios as any).mockRequestInterceptor = successFn; 14 | }), 15 | }, 16 | response: { 17 | use: jest.fn((successFn) => { 18 | // 保存拦截器函数以便测试 19 | (axios as any).mockResponseInterceptor = successFn; 20 | }), 21 | }, 22 | }, 23 | })), 24 | }; 25 | }); 26 | 27 | describe('http-instance', () => { 28 | it('应该创建axios实例', () => { 29 | const { oapiHttpInstance } = require('../../src/utils/http-instance'); 30 | expect(axios.create).toHaveBeenCalled(); 31 | expect(oapiHttpInstance).toBeDefined(); 32 | }); 33 | 34 | it('应该正确设置请求拦截器', () => { 35 | // 测试请求拦截器逻辑 36 | const mockRequest = { headers: {} }; 37 | const interceptor = (axios as any).mockRequestInterceptor; 38 | 39 | expect(typeof interceptor).toBe('function'); 40 | const result = interceptor(mockRequest); 41 | 42 | expect(result).toBe(mockRequest); 43 | expect(result.headers['User-Agent']).toBe(USER_AGENT); 44 | }); 45 | 46 | it('当请求没有headers属性时应该正确处理', () => { 47 | // 测试请求拦截器处理没有headers的情况 48 | const mockRequest = {}; 49 | const interceptor = (axios as any).mockRequestInterceptor; 50 | 51 | const result = interceptor(mockRequest); 52 | 53 | expect(result).toBe(mockRequest); 54 | // 不应该添加任何headers 55 | expect(mockRequest).not.toHaveProperty('headers'); 56 | }); 57 | 58 | it('应该正确设置响应拦截器', () => { 59 | // 测试响应拦截器逻辑 60 | const mockResponse = { data: { key: 'value' } }; 61 | const interceptor = (axios as any).mockResponseInterceptor; 62 | 63 | expect(typeof interceptor).toBe('function'); 64 | const result = interceptor(mockResponse); 65 | 66 | expect(result).toBe(mockResponse.data); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /tests/utils/noop.test.ts: -------------------------------------------------------------------------------- 1 | import { noop } from '../../src/utils/noop'; 2 | 3 | describe('noop', () => { 4 | it('should be a function', () => { 5 | expect(typeof noop).toBe('function'); 6 | }); 7 | 8 | it('should return undefined', () => { 9 | expect(noop()).toBeUndefined(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/utils/version.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | jest.mock('fs', () => ({ 5 | readFileSync: jest.fn(() => '{"version":"1.0.0"}'), 6 | })); 7 | 8 | jest.mock('path', () => ({ 9 | join: jest.fn(() => '/mock/path/package.json'), 10 | })); 11 | 12 | describe('version', () => { 13 | beforeEach(() => { 14 | jest.clearAllMocks(); 15 | }); 16 | 17 | it('应该从package.json读取当前版本', () => { 18 | // 加载模块 19 | const { currentVersion } = require('../../src/utils/version'); 20 | 21 | // 验证结果 22 | expect(currentVersion).toBe('1.0.0'); 23 | expect(path.join).toHaveBeenCalled(); 24 | expect(fs.readFileSync).toHaveBeenCalledWith('/mock/path/package.json', 'utf8'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules", "dist"] 15 | } --------------------------------------------------------------------------------