From 477bb1ac8fb9bd2b686c96b1b15bd27b855de583 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Tue, 11 Nov 2025 02:25:22 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20MCP=E5=A2=9E=E5=8A=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=87=E4=BB=B6=E8=AE=BF=E9=97=AEURL?= =?UTF-8?q?=E3=80=81=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E5=92=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/FileController.php | 16 +- app/Models/FileContent.php | 17 ++ electron/lib/mcp.js | 182 +++++++++++++++++++- 3 files changed, 206 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/FileController.php b/app/Http/Controllers/Api/FileController.php index c28270439..12c65d212 100755 --- a/app/Http/Controllers/Api/FileController.php +++ b/app/Http/Controllers/Api/FileController.php @@ -64,6 +64,9 @@ class FileController extends AbstractController * @apiParam {Number|String} id * - Number 文件ID(需要登录) * - String 链接码(不需要登录,用于预览) + * @apiParam {String} [with_url] 是否返回文件访问URL + * - no: 不返回(默认) + * - yes: 返回content_url字段 * * @apiSuccess {Number} ret 返回状态码(1正确、0错误) * @apiSuccess {String} msg 返回信息(错误描述) @@ -72,11 +75,12 @@ class FileController extends AbstractController public function one() { $id = Request::input('id'); + $with_url = Request::input('with_url', 'no'); // $permission = 0; if (Base::isNumber($id)) { $user = User::auth(); - $file = File::permissionFind(intval($id), $user, 0, $permission); + $file = File::permissionFind(intval($id), $user, $with_url === 'yes' ? 1 : 0, $permission); } elseif ($id) { $fileLink = FileLink::whereCode($id)->first(); $file = $fileLink?->file; @@ -88,12 +92,12 @@ class FileController extends AbstractController } return Base::retError($msg, $data); } - + // 如果文件不允许游客访问,则需要登录 if (!$file->guest_access) { User::auth(); } - + $fileLink->increment("num"); } else { return Base::retError('参数错误'); @@ -101,6 +105,12 @@ class FileController extends AbstractController // $array = $file->toArray(); $array['permission'] = $permission; + + // 如果请求返回文件URL + if ($with_url === 'yes') { + $array['content_url'] = FileContent::getFileUrl($file->id); + } + return Base::retSuccess('success', $array); } diff --git a/app/Models/FileContent.php b/app/Models/FileContent.php index 96c49f9ac..fdaf8d1d0 100644 --- a/app/Models/FileContent.php +++ b/app/Models/FileContent.php @@ -152,6 +152,23 @@ class FileContent extends AbstractModel return Base::retSuccess('success', [ 'content' => $content ]); } + /** + * 获取文件访问URL + * @param int $fileId 文件ID + * @return string|null 返回完整的文件URL,如果文件无内容则返回null + */ + public static function getFileUrl($fileId) + { + $content = self::whereFid($fileId)->orderByDesc('id')->first(); + if ($content) { + $contentData = Base::json2array($content->content ?: []); + if (!empty($contentData['url'])) { + return Base::fillUrl($contentData['url']); + } + } + return null; + } + /** * 获取文件内容 * @param $id diff --git a/electron/lib/mcp.js b/electron/lib/mcp.js index cc6d5e72a..54fe6418b 100644 --- a/electron/lib/mcp.js +++ b/electron/lib/mcp.js @@ -4,7 +4,7 @@ * DooTask 的 Electron 客户端集成了 Model Context Protocol (MCP) 服务, * 允许 AI 助手(如 Claude)直接与 DooTask 任务进行交互。 * - * 提供的工具(共 21 个): + * 提供的工具(共 24 个): * * === 用户管理 === * - get_users_basic - 根据用户ID列表获取基础信息,便于匹配负责人/协助人 @@ -26,6 +26,11 @@ * - create_project - 创建新项目 * - update_project - 修改项目信息(名称、描述等) * + * === 文件管理 === + * - list_files - 获取项目文件列表,支持按父级文件夹筛选 + * - search_files - 按关键词搜索文件,支持搜索文件名称或ID + * - get_file_detail - 获取文件详情,返回 content_url 可配合 WebFetch 读取文件内容 + * * === 工作报告 === * - list_received_reports - 获取我接收的汇报列表,支持按类型/状态/部门/时间筛选 * - get_report_detail - 获取汇报详情,包括完整内容、汇报人、接收人、AI分析等 @@ -61,6 +66,12 @@ * - "我有哪些项目?" * - "查看项目5的详情,包括所有列和成员" * + * 文件管理: + * - "查看我的文件列表" + * - "搜索包含'设计稿'的文件" + * - "显示文件123的详细信息" + * - "帮我分析这个文档的内容" + * * 工作报告: * - "查看未读的工作汇报" * - "生成本周周报" @@ -797,7 +808,7 @@ class DooTaskMCP { url: file.path, thumb: file.thumb, userid: file.userid, - download: file.download, + download_count: file.download, created_at: file.created_at, })); @@ -1397,15 +1408,28 @@ class DooTaskMCP { // 工作报告:获取汇报详情 this.mcp.addTool({ name: 'get_report_detail', - description: '获取指定工作汇报的详细信息,包括完整内容、汇报人、接收人列表、AI分析等。返回的 content 字段为 Markdown 格式。', + description: '获取指定工作汇报的详细信息,包括完整内容、汇报人、接收人列表、AI分析等。返回的 content 字段为 Markdown 格式。支持通过报告ID或分享码访问。', parameters: z.object({ report_id: z.number() + .optional() .describe('报告ID'), + share_code: z.string() + .optional() + .describe('报告分享码'), }), execute: async (params) => { - const result = await this.request('GET', 'report/detail', { - id: params.report_id, - }); + if (!params.report_id && !params.share_code) { + throw new Error('必须提供 report_id 或 share_code 参数之一'); + } + + const requestData = {}; + if (params.report_id) { + requestData.id = params.report_id; + } else if (params.share_code) { + requestData.code = params.share_code; + } + + const result = await this.request('GET', 'report/detail', requestData); if (result.error) { throw new Error(result.error); @@ -1687,6 +1711,152 @@ class DooTaskMCP { }; } }); + + // 文件管理:获取文件列表 + this.mcp.addTool({ + name: 'list_files', + description: '获取项目文件列表,支持按父级文件夹筛选。可以浏览文件夹结构,查看所有文件和子文件夹。', + parameters: z.object({ + pid: z.number() + .optional() + .describe('父级文件夹ID,0或不传表示根目录'), + }), + execute: async (params) => { + const pid = params.pid !== undefined ? params.pid : 0; + + const result = await this.request('GET', 'file/lists', { + pid: pid, + }); + + if (result.error) { + throw new Error(result.error); + } + + const files = Array.isArray(result.data) ? result.data : []; + + const simplified = files.map(file => ({ + id: file.id, + name: file.name, + type: file.type, + ext: file.ext || '', + size: file.size || 0, + pid: file.pid, + userid: file.userid, + created_id: file.created_id, + share: file.share ? true : false, + created_at: file.created_at, + updated_at: file.updated_at, + })); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + pid: pid, + total: simplified.length, + files: simplified, + }, null, 2) + }] + }; + } + }); + + // 文件管理:搜索文件 + this.mcp.addTool({ + name: 'search_files', + description: '按关键词搜索文件,支持搜索文件名称或ID。可以快速定位文件位置。', + parameters: z.object({ + keyword: z.string() + .min(1) + .describe('搜索关键词,支持文件名称或文件ID'), + take: z.number() + .optional() + .describe('返回数量,默认50,最大100'), + }), + execute: async (params) => { + const take = params.take && params.take > 0 ? Math.min(params.take, 100) : 50; + + const result = await this.request('GET', 'file/search', { + key: params.keyword, + take: take, + }); + + if (result.error) { + throw new Error(result.error); + } + + const files = Array.isArray(result.data) ? result.data : []; + + const simplified = files.map(file => ({ + id: file.id, + name: file.name, + type: file.type, + ext: file.ext || '', + size: file.size || 0, + pid: file.pid, + userid: file.userid, + created_id: file.created_id, + share: file.share ? true : false, + created_at: file.created_at, + updated_at: file.updated_at, + })); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + keyword: params.keyword, + total: simplified.length, + files: simplified, + }, null, 2) + }] + }; + } + }); + + // 文件管理:获取文件详情 + this.mcp.addTool({ + name: 'get_file_detail', + description: '获取指定文件的详细信息,包括类型、大小、共享状态、创建者等。返回的 content_url 可以配合 WebFetch 工具读取文件内容进行分析。支持通过文件ID或分享码访问。', + parameters: z.object({ + file_id: z.union([z.number(), z.string()]) + .describe('文件ID(数字)或分享码(字符串)'), + }), + execute: async (params) => { + const result = await this.request('GET', 'file/one', { + id: params.file_id, + with_url: 'yes', + }); + + if (result.error) { + throw new Error(result.error); + } + + const file = result.data; + + const fileDetail = { + id: file.id, + name: file.name, + type: file.type, + ext: file.ext || '', + size: file.size || 0, + pid: file.pid, + userid: file.userid, + created_id: file.created_id, + share: file.share ? true : false, + content_url: file.content_url || null, + created_at: file.created_at, + updated_at: file.updated_at, + }; + + return { + content: [{ + type: 'text', + text: JSON.stringify(fileDetail, null, 2) + }] + }; + } + }); } // 启动 MCP 服务器