feishu-mcp
Version:
Model Context Protocol server for Feishu integration
129 lines (128 loc) • 6.01 kB
JavaScript
import { Logger } from '../../../utils/logger.js';
import { TaskCreateBatchSchema, TaskDeleteBatchSchema, } from '../../../types/taskSchema.js';
function toNested(item) {
const members = [];
if (item.assigneeIds?.length) {
members.push(...item.assigneeIds.map((id) => ({ id, type: 'user', role: 'assignee' })));
}
if (item.followerIds?.length) {
members.push(...item.followerIds.map((id) => ({ id, type: 'user', role: 'follower' })));
}
return {
summary: item.summary,
...(item.parentTaskGuid !== undefined && { parentTaskGuid: item.parentTaskGuid }),
...(item.description !== undefined && { description: item.description }),
...(item.dueTimestamp !== undefined && { due: { timestamp: item.dueTimestamp, is_all_day: item.isDueAllDay } }),
...(item.completedAt !== undefined && { completed_at: item.completedAt }),
...(members.length > 0 && { members }),
...(item.repeatRule !== undefined && { repeat_rule: item.repeatRule }),
...(item.startTimestamp !== undefined && {
start: { timestamp: item.startTimestamp, is_all_day: item.isStartAllDay },
}),
...(item.mode !== undefined && { mode: item.mode }),
...(item.isMilestone !== undefined && { is_milestone: item.isMilestone }),
...(item.subTasks?.length && { subTasks: item.subTasks.map(toNested) }),
};
}
/**
* 批量创建飞书任务(含嵌套子任务)
*/
export async function createTasks(taskItems, api) {
const parsed = TaskCreateBatchSchema.safeParse(taskItems);
if (!parsed.success) {
throw new Error(`参数校验失败: ${parsed.error.message}`);
}
Logger.info(`createTasks invoked: ${parsed.data.length} top-level items`);
const rootItems = parsed.data.map(toNested);
const result = await api.createTasksNested(rootItems);
return result;
}
/**
* 列取飞书任务(我负责的)
*/
export async function listTasks(options, api) {
Logger.info(`listTasks invoked: pageToken=${options?.pageToken ? 'yes' : 'no'}, completed=${options?.completed ?? 'all'}`);
return api.listTasks(options?.pageToken, options?.completed);
}
/**
* 更新飞书任务
*/
export async function updateTask(params, api) {
const { taskGuid, ...rest } = params;
const hasFieldUpdate = rest.summary !== undefined ||
rest.description !== undefined ||
rest.dueTimestamp !== undefined ||
rest.completedAt !== undefined ||
rest.repeatRule !== undefined ||
rest.startTimestamp !== undefined ||
rest.mode !== undefined ||
rest.isMilestone !== undefined;
const hasAdd = (rest.addAssigneeIds?.length ?? 0) + (rest.addFollowerIds?.length ?? 0) > 0;
const hasRemove = (rest.removeAssigneeIds?.length ?? 0) + (rest.removeFollowerIds?.length ?? 0) > 0;
const hasAddReminder = rest.addReminderRelativeMinutes !== undefined;
const hasRemoveReminder = (rest.removeReminderIds?.length ?? 0) > 0;
if (!hasFieldUpdate && !hasAdd && !hasRemove && !hasAddReminder && !hasRemoveReminder) {
throw new Error('至少需要一项更新:summary/description/due/completed_at/repeat_rule/start/mode/is_milestone、成员 add/remove、或提醒 addReminderRelativeMinutes/removeReminderIds');
}
Logger.info(`updateTask invoked: taskGuid=${taskGuid}`);
let result;
if (hasFieldUpdate) {
const updateParams = {};
if (rest.summary !== undefined)
updateParams.summary = rest.summary;
if (rest.description !== undefined)
updateParams.description = rest.description;
if (rest.dueTimestamp !== undefined)
updateParams.due = { timestamp: rest.dueTimestamp, is_all_day: rest.isDueAllDay };
if (rest.completedAt !== undefined)
updateParams.completed_at = rest.completedAt;
if (rest.repeatRule !== undefined)
updateParams.repeat_rule = rest.repeatRule;
if (rest.startTimestamp !== undefined)
updateParams.start = { timestamp: rest.startTimestamp, is_all_day: rest.isStartAllDay };
if (rest.mode !== undefined)
updateParams.mode = rest.mode;
if (rest.isMilestone !== undefined)
updateParams.is_milestone = rest.isMilestone;
result = await api.updateTask(taskGuid, updateParams);
}
if (hasAdd) {
const members = [];
if (rest.addAssigneeIds?.length) {
members.push(...rest.addAssigneeIds.map((id) => ({ id, type: 'user', role: 'assignee' })));
}
if (rest.addFollowerIds?.length) {
members.push(...rest.addFollowerIds.map((id) => ({ id, type: 'user', role: 'follower' })));
}
result = await api.addTaskMembers(taskGuid, members);
}
if (hasRemove) {
const members = [];
if (rest.removeAssigneeIds?.length) {
members.push(...rest.removeAssigneeIds.map((id) => ({ id, type: 'user', role: 'assignee' })));
}
if (rest.removeFollowerIds?.length) {
members.push(...rest.removeFollowerIds.map((id) => ({ id, type: 'user', role: 'follower' })));
}
result = await api.removeTaskMembers(taskGuid, members);
}
if (hasRemoveReminder) {
result = await api.removeTaskReminders(taskGuid, rest.removeReminderIds);
}
if (hasAddReminder) {
result = await api.addTaskReminder(taskGuid, rest.addReminderRelativeMinutes);
}
const taskEntity = result != null && typeof result === 'object' && 'task' in result && result.task != null ? result.task : result;
return taskEntity;
}
/**
* 批量删除飞书任务
*/
export async function deleteTasks(taskGuids, api) {
const parsed = TaskDeleteBatchSchema.safeParse(taskGuids);
if (!parsed.success) {
throw new Error(`参数校验失败: ${parsed.error.message}`);
}
Logger.info(`deleteTasks invoked: ${parsed.data.length} tasks`);
return api.deleteTasks(parsed.data);
}