Skip to main content
Glama

macOS Calendar MCP Server

by xybstone
macos-calendar-mcp.js10.7 kB
#!/usr/bin/env node import { execSync } from 'child_process'; class MacOSCalendarMCP { constructor() { this.name = "macos-calendar"; this.version = "1.0.0"; } // MCP服务器初始化 initialize() { return { protocolVersion: "2024-11-05", capabilities: { tools: { listChanged: true } }, serverInfo: { name: this.name, version: this.version } }; } // 获取可用工具列表 getTools() { return { tools: [ { name: "list-calendars", description: "列出所有macOS日历", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { name: "create-event", description: "在macOS日历中创建新事件", inputSchema: { type: "object", properties: { calendar: { type: "string", description: "日历名称", default: "个人" }, title: { type: "string", description: "事件标题" }, startDate: { type: "string", description: "开始时间,格式:YYYY-MM-DD HH:MM" }, endDate: { type: "string", description: "结束时间,格式:YYYY-MM-DD HH:MM" }, description: { type: "string", description: "事件描述", default: "" }, location: { type: "string", description: "事件地点", default: "" } }, required: ["title", "startDate", "endDate"], additionalProperties: false } }, { name: "list-today-events", description: "列出今天的事件", inputSchema: { type: "object", properties: { calendar: { type: "string", description: "日历名称", default: "个人" } }, additionalProperties: false } }, { name: "search-events", description: "搜索事件", inputSchema: { type: "object", properties: { query: { type: "string", description: "搜索关键词" }, calendar: { type: "string", description: "日历名称", default: "个人" } }, required: ["query"], additionalProperties: false } } ] }; } // 执行工具 async callTool(name, args) { try { switch (name) { case "list-calendars": return this.listCalendars(); case "create-event": return this.createEvent(args); case "list-today-events": return this.listTodayEvents(args); case "search-events": return this.searchEvents(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: "text", text: `错误: ${error.message}` }], isError: true }; } } // 列出所有日历 listCalendars() { try { const script = `tell application "Calendar" to get name of calendars`; const result = execSync(`osascript -e '${script}'`, { encoding: 'utf8' }); const calendars = result.trim().split(', '); return { content: [{ type: "text", text: `📅 可用日历 (${calendars.length}个):\n${calendars.map(cal => `• ${cal}`).join('\n')}` }] }; } catch (error) { throw new Error(`获取日历列表失败: ${error.message}`); } } // 创建事件 createEvent(args) { const { calendar = "个人", title, startDate, endDate, description = "", location = "" } = args; // 转换时间格式 const formatDate = (dateStr) => { const date = new Date(dateStr); return date.toLocaleString('en-US', { month: 'numeric', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }); }; const formattedStart = formatDate(startDate); const formattedEnd = formatDate(endDate); const script = ` tell application "Calendar" set theCalendar to calendar "${calendar}" set startDate to date "${formattedStart}" set endDate to date "${formattedEnd}" make new event at end of events of theCalendar with properties {summary:"${title}", start date:startDate, end date:endDate, description:"${description}", location:"${location}"} end tell `; try { const result = execSync(`osascript -e '${script}'`, { encoding: 'utf8' }); return { content: [{ type: "text", text: `✅ 事件创建成功!\n📅 日历: ${calendar}\n📝 标题: ${title}\n🕒 时间: ${startDate} - ${endDate}\n📍 地点: ${location || '无'}\n📄 描述: ${description || '无'}` }] }; } catch (error) { throw new Error(`创建事件失败: ${error.message}`); } } // 列出今天的事件 listTodayEvents(args) { const { calendar = "个人" } = args; const script = ` tell application "Calendar" set theCalendar to calendar "${calendar}" set todayStart to (current date) - (time of (current date)) set todayEnd to todayStart + (24 * hours) - 1 set todayEvents to every event of theCalendar whose start date ≥ todayStart and start date ≤ todayEnd set eventList to {} repeat with anEvent in todayEvents set eventInfo to (summary of anEvent) & "|" & (start date of anEvent) & "|" & (end date of anEvent) & "|" & (description of anEvent) & "|" & (location of anEvent) set end of eventList to eventInfo end repeat return eventList as string end tell `; try { const result = execSync(`osascript -e '${script}'`, { encoding: 'utf8' }); const events = result.trim(); if (!events || events === '""') { return { content: [{ type: "text", text: `📅 ${calendar} - 今日无事件` }] }; } const eventList = events.split(',').map(event => { const [title, start, end, desc, loc] = event.trim().split('|'); return `📝 ${title}\n🕒 ${start} - ${end}${loc ? `\n📍 ${loc}` : ''}${desc ? `\n📄 ${desc}` : ''}`; }).join('\n\n'); return { content: [{ type: "text", text: `📅 ${calendar} - 今日事件:\n\n${eventList}` }] }; } catch (error) { throw new Error(`获取今日事件失败: ${error.message}`); } } // 搜索事件 searchEvents(args) { const { query, calendar = "个人" } = args; const script = ` tell application "Calendar" set theCalendar to calendar "${calendar}" set allEvents to every event of theCalendar set matchingEvents to {} repeat with anEvent in allEvents if (summary of anEvent) contains "${query}" or (description of anEvent) contains "${query}" then set eventInfo to (summary of anEvent) & "|" & (start date of anEvent) & "|" & (end date of anEvent) & "|" & (description of anEvent) & "|" & (location of anEvent) set end of matchingEvents to eventInfo end if end repeat return matchingEvents as string end tell `; try { const result = execSync(`osascript -e '${script}'`, { encoding: 'utf8' }); const events = result.trim(); if (!events || events === '""') { return { content: [{ type: "text", text: `🔍 在 ${calendar} 中未找到包含 "${query}" 的事件` }] }; } const eventList = events.split(',').map(event => { const [title, start, end, desc, loc] = event.trim().split('|'); return `📝 ${title}\n🕒 ${start} - ${end}${loc ? `\n📍 ${loc}` : ''}${desc ? `\n📄 ${desc}` : ''}`; }).join('\n\n'); return { content: [{ type: "text", text: `🔍 在 ${calendar} 中找到 ${events.split(',').length} 个匹配事件:\n\n${eventList}` }] }; } catch (error) { throw new Error(`搜索事件失败: ${error.message}`); } } } // MCP服务器主函数 async function main() { const server = new MacOSCalendarMCP(); process.stdin.setEncoding('utf8'); process.stdin.on('data', async (data) => { try { const lines = data.trim().split('\n'); for (const line of lines) { if (!line.trim()) continue; const request = JSON.parse(line); let response = {}; switch (request.method) { case 'initialize': response = { jsonrpc: "2.0", id: request.id, result: server.initialize() }; break; case 'tools/list': response = { jsonrpc: "2.0", id: request.id, result: server.getTools() }; break; case 'tools/call': const result = await server.callTool(request.params.name, request.params.arguments || {}); response = { jsonrpc: "2.0", id: request.id, result }; break; default: response = { jsonrpc: "2.0", id: request.id, error: { code: -32601, message: `Method not found: ${request.method}` } }; } console.log(JSON.stringify(response)); } } catch (error) { const response = { jsonrpc: "2.0", id: null, error: { code: -32700, message: `Parse error: ${error.message}` } }; console.log(JSON.stringify(response)); } }); } if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error); } export default MacOSCalendarMCP;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xybstone/macos-calendar-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server