首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

前端的AI路其之三:用MCP做一个日程助理

  • 25-04-23 10:41
  • 2277
  • 10065
juejin.cn

前言

话不多说,先演示一下吧。大概功能描述就是,告诉AI“添加日历,今天下午五点到六点,我要去万达吃饭”,然后AI自动将日程同步到日历。

2025-04-1819.25.19-ezgif.com-video-to-gif-converter.gif

准备工作

开发这个日程助理需要用到MCP、Mac(mac的日历能力)、Windsurf(运行mcp)。技术栈是Typescript。

思路

基于MCP我们可以做很多。关于这个日程助理,其实也是很简单一个尝试,其实就是再验证一下我对MCP的使用。因为Siri的原因,让我刚好有了这个想法,尝试一下自己搞个日程助理。关于MCP可以看我前面的分享 # 前端的AI路其之一: MCP与Function Calling# 前端的AI路其之二:初试MCP Server 。

我的思路如下: 让大模型理解一下我的意图,然后执行相关操作。这也是我对MCP的理解(执行相关操作)。因此要做日程助理,那就很简单了。首先搞一个脚本,能够自动调用mac并添加日历,然后再包装成MCP,最后引入大模型就ok了。顺着这个思路,接下来就讲讲如何实现吧

实现

第一步:在mac上添加日历

这里我们需要先明确一个概念。mac上给日历添加日程,其实是就是给对应的日历类型添加日程。举个例子

image.png

左边红框其实就是日历类型,比如我要添加一个开发日程,其实就是先选择"开发"日历,然后在该日历下添加日程。因此如果我们想通过脚本形式创建日程,其实就是先看日历类型存在不存在,如果存在,就在该类型下添加一个日程。

因此这里第一步,我们先获取mac上有没有对应的日历,没有的话就创建一个。

1.1 查找日历

参考文档 mac查找日历

假定我们的日历类型叫做 日程助手。 这里我使用了applescript的语法,因为JavaScript的方式我这运行有问题。

js
代码解读
复制代码
import { execSync } from 'child_process'; function checkCalendarExists(calendarName) { const Script = `tell application "Calendar" set theCalendarName to "${calendarName}" set theCalendar to first calendar where its name = theCalendarName end tell`; // 执行并解析结果 try { const result = execSync(`osascript -e '${Script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); console.log(result); return true; } catch (error) { console.error('检测失败:', error.message); return false; } } // 使用示例 const calendarName = '日程助手'; const exists = checkCalendarExists(calendarName); console.log(`日历 "${calendarName}" 存在:`, exists ? '✅ 是' : '❌ 否');
附赠检验结果

image.png

现在我们知道了怎么判断日历存不存在,那么接下来就是,在日历不存在的时候创建日历

1.2 日历创建

参考文档 mac 创建日历

js
代码解读
复制代码
import { execSync } from 'child_process'; // 创建日历 function createCalendar(calendarName) { const script = `tell application "Calendar" make new calendar with properties {name:"${calendarName}"} end tell`; try { execSync(`osascript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); return true; } catch (e) { console.log('create fail', e) return false; } } // 检查日历是否存在 function checkCalendarExists(calendarName) { .... } // 使用示例 const calendarName = '日程助手'; const exists = checkCalendarExists(calendarName); console.log(`日历 "${calendarName}" 存在:`, exists ? '✅ 是' : '❌ 否'); if (!exists) { const res = createCalendar(calendarName); console.log(res ? '✅ 创建成功' : '❌ 创建失败') }
运行结果

image.png

接下来就是第三步了,在日历“日程助手”下创建日程

1.3 创建日程

js
代码解读
复制代码
import { execSync } from 'child_process'; // 创建日程 function createCalendarEvent(calendarName, config) { const script = `var app = Application.currentApplication() app.includeStandardAdditions = true var Calendar = Application("Calendar") var eventStart = new Date(${config.startTime}) var eventEnd = new Date(${config.endTime}) var projectCalendars = Calendar.calendars.whose({name: "${calendarName}"}) var projectCalendar = projectCalendars[0] var event = Calendar.Event({summary: "${config.title}", startDate: eventStart, endDate: eventEnd, description: "${config.description}"}) projectCalendar.events.push(event) event` try { console.log('开始创建日程'); execSync(` osascript -l JavaScript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); console.log('✅ 日程添加成功'); } catch (error) { console.error('❌ 执行失败:', error); } } // 创建日历 function createCalendar(calendarName) { .... } // 检查日历是否存在 function checkCalendarExists(calendarName) { ... }

这里我们完善一下代码

js
代码解读
复制代码
import { execSync } from 'child_process'; function handleCreateEvent(config) { const calendarName = '日程助手'; const exists = checkCalendarExists(calendarName); // console.log(`日历 "${calendarName}" 存在:`, exists ? '✅ 是' : '❌ 否'); if (!exists) { const createRes = createCalendar(calendarName); console.log(createRes ? '✅ 创建日历成功' : '❌ 创建日历失败') if (createRes) { createCalendarEvent(calendarName, config) } } else { createCalendarEvent(calendarName, config) } } // 创建日程 function createCalendarEvent(calendarName, config) { const script = `var app = Application.currentApplication() app.includeStandardAdditions = true var Calendar = Application("Calendar") var eventStart = new Date(${config.startTime}) var eventEnd = new Date(${config.endTime}) var projectCalendars = Calendar.calendars.whose({name: "${calendarName}"}) var projectCalendar = projectCalendars[0] var event = Calendar.Event({summary: "${config.title}", startDate: eventStart, endDate: eventEnd, description: "${config.description}"}) projectCalendar.events.push(event) event` try { console.log('开始创建日程'); execSync(` osascript -l JavaScript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); console.log('✅ 日程添加成功'); } catch (error) { console.error('❌ 执行失败:', error); } } // 创建日历 function createCalendar(calendarName) { const script = `tell application "Calendar" make new calendar with properties {name:"${calendarName}"} end tell`; try { execSync(`osascript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); return true; } catch (e) { console.log('create fail', e) return false; } } // 检查日历是否存在 function checkCalendarExists(calendarName) { const Script = `tell application "Calendar" set theCalendarName to "${calendarName}" set theCalendar to first calendar where its name = theCalendarName end tell`; // 执行并解析结果 try { const result = execSync(`osascript -e '${Script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); return true; } catch (error) { return false; } } // 运行示例 const eventConfig = { title: '团队周会', startTime: 1744183538021, endTime: 1744442738000, description: '每周项目进度同步', }; handleCreateEvent(eventConfig)
运行结果

image.png

image.png

这就是一个完善的,可以直接在终端运行的创建日程的脚本的。接下来我们要做的就是,让大模型理解这个脚本,并学会使用这个脚本

第二步: 定义MCP

基于第一步,我们已经完成了这个日程助理的基本功能,接下来就是借助MCP的能力,教会大模型知道有这个函数,以及怎么调用这个函数

js
代码解读
复制代码
// 引入 mcp import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // 声明MCP服务 const server = new McpServer({ name: "mcp_calendar", version: "1.0.0" }); ... // 添加日历函数 也就是告诉大模型 有这个东西以及怎么用 server.tool("add_mac_calendar", '给mac日历添加日程, 接受四个参数 startTime, endTime是起止时间(格式为YYYY-MM-DD HH:MM:SS) title是日历标题 description是日历描述', { startTime: z.string(), endTime: z.string(), title: z.string(), description: z.string() }, async ({ startTime, endTime, title, description }) => { const res = handleCreateEvent({ title: title, description: description, startTime: new Date(startTime).getTime(), endTime: new Date(endTime).getTime() }); return { content: [{ type: "text", text: res ? '添加成功' : '添加失败' }] } }) // 初始化服务 const transport = new StdioServerTransport(); await server.connect(transport);

这里附上完整的ts代码

js
代码解读
复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { execSync } from 'child_process'; import { z } from "zod"; export interface EventConfig { // 日程标题 title: string; // 日程开始时间 毫秒时间戳 startTime: number; // 日程结束时间 毫秒时间戳 endTime: number; // 日程描述 description: string; } const server = new McpServer({ name: "mcp_calendar", version: "1.0.0" }); function handleCreateEvent(config: EventConfig) { const calendarName = '日程助手'; const exists = checkCalendarExists(calendarName); // console.log(`日历 "${calendarName}" 存在:`, exists ? '✅ 是' : '❌ 否'); let res = false; if (!exists) { const createRes = createCalendar(calendarName); console.log(createRes ? '✅ 创建日历成功' : '❌ 创建日历失败') if (createRes) { res = createCalendarEvent(calendarName, config) } } else { res = createCalendarEvent(calendarName, config) } return res } // 创建日程 function createCalendarEvent(calendarName: string, config: EventConfig) { const script = `var app = Application.currentApplication() app.includeStandardAdditions = true var Calendar = Application("Calendar") var eventStart = new Date(${config.startTime}) var eventEnd = new Date(${config.endTime}) var projectCalendars = Calendar.calendars.whose({name: "${calendarName}"}) var projectCalendar = projectCalendars[0] var event = Calendar.Event({summary: "${config.title}", startDate: eventStart, endDate: eventEnd, description: "${config.description}"}) projectCalendar.events.push(event) event` try { console.log('开始创建日程'); execSync(` osascript -l JavaScript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); console.log('✅ 日程添加成功'); return true } catch (error) { console.error('❌ 执行失败:', error); return false } } // 创建日历 function createCalendar(calendarName: string) { const script = `tell application "Calendar" make new calendar with properties {name:"${calendarName}"} end tell`; try { execSync(`osascript -e '${script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); return true; } catch (e) { console.log('create fail', e) return false; } } // 检查日历是否存在 function checkCalendarExists(calendarName: string) { const Script = `tell application "Calendar" set theCalendarName to "${calendarName}" set theCalendar to first calendar where its name = theCalendarName end tell`; // 执行并解析结果 try { const result = execSync(`osascript -e '${Script}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] // 忽略错误输出 }); return true; } catch (error) { return false; } } server.tool("add_mac_calendar", '给mac日历添加日程, 接受四个参数 startTime, endTime是起止时间(格式为YYYY-MM-DD HH:MM:SS) title是日历标题 description是日历描述', { startTime: z.string(), endTime: z.string(), title: z.string(), description: z.string() }, async ({ startTime, endTime, title, description }) => { const res = handleCreateEvent({ title: title, description: description, startTime: new Date(startTime).getTime(), endTime: new Date(endTime).getTime() }); return { content: [{ type: "text", text: res ? '添加成功' : '添加失败' }] } }) const transport = new StdioServerTransport(); await server.connect(transport);

第三步: 导入Windsurf

在前文已经讲过如何引入到Windsurf,可以参考前文# 前端的AI路其之二:初试MCP Server ,这里就不过多赘述了。 其实在build之后,完全可以引入其他支持MCP的软件基本都是可以的。

接下来就是愉快的调用时间啦。

总结

这里其实是对前文# 前端的AI路其之二:初试MCP Server 的再次深入。算是大概讲明白了Tool方式怎么用,MCP当然不止这一种用法,后面也会继续输出自己的学习感悟,也欢迎各位大佬的分享和指正。

祝好。

注:本文转载自juejin.cn的justdoit521的文章"https://juejin.cn/post/7495598542405550107"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

109
人工智能
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top