feat: MCP增加文件管理功能,支持获取文件访问URL、文件列表和文件搜索

This commit is contained in:
kuaifan 2025-11-11 02:25:22 +00:00
parent 29df864ecb
commit 477bb1ac8f
3 changed files with 206 additions and 9 deletions

View File

@ -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);
}

View File

@ -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

182
electron/lib/mcp.js vendored
View File

@ -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('父级文件夹ID0或不传表示根目录'),
}),
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 服务器