@shin1ohno/sage 0.7.9 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -8
- package/dist/cli/mcp-handler.d.ts.map +1 -1
- package/dist/cli/mcp-handler.js +141 -987
- package/dist/cli/mcp-handler.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +30 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/update-validation.d.ts +52 -0
- package/dist/config/update-validation.d.ts.map +1 -0
- package/dist/config/update-validation.js +133 -0
- package/dist/config/update-validation.js.map +1 -0
- package/dist/config/validation.d.ts +130 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +53 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/index.js +443 -1632
- package/dist/index.js.map +1 -1
- package/dist/integrations/calendar-event-creator.d.ts +2 -3
- package/dist/integrations/calendar-event-creator.d.ts.map +1 -1
- package/dist/integrations/calendar-event-creator.js +3 -4
- package/dist/integrations/calendar-event-creator.js.map +1 -1
- package/dist/integrations/calendar-event-deleter.d.ts +2 -3
- package/dist/integrations/calendar-event-deleter.d.ts.map +1 -1
- package/dist/integrations/calendar-event-deleter.js +3 -4
- package/dist/integrations/calendar-event-deleter.js.map +1 -1
- package/dist/integrations/calendar-event-response.d.ts +4 -17
- package/dist/integrations/calendar-event-response.d.ts.map +1 -1
- package/dist/integrations/calendar-event-response.js +3 -4
- package/dist/integrations/calendar-event-response.js.map +1 -1
- package/dist/integrations/calendar-service.d.ts +6 -3
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js +26 -4
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/integrations/calendar-source-manager.d.ts +302 -0
- package/dist/integrations/calendar-source-manager.d.ts.map +1 -0
- package/dist/integrations/calendar-source-manager.js +862 -0
- package/dist/integrations/calendar-source-manager.js.map +1 -0
- package/dist/integrations/google-calendar-service.d.ts +176 -0
- package/dist/integrations/google-calendar-service.d.ts.map +1 -0
- package/dist/integrations/google-calendar-service.js +745 -0
- package/dist/integrations/google-calendar-service.js.map +1 -0
- package/dist/integrations/notion-mcp.d.ts +28 -3
- package/dist/integrations/notion-mcp.d.ts.map +1 -1
- package/dist/integrations/notion-mcp.js +21 -5
- package/dist/integrations/notion-mcp.js.map +1 -1
- package/dist/integrations/reminder-manager.d.ts.map +1 -1
- package/dist/integrations/reminder-manager.js +2 -0
- package/dist/integrations/reminder-manager.js.map +1 -1
- package/dist/oauth/google-oauth-handler.d.ts +149 -0
- package/dist/oauth/google-oauth-handler.d.ts.map +1 -0
- package/dist/oauth/google-oauth-handler.js +365 -0
- package/dist/oauth/google-oauth-handler.js.map +1 -0
- package/dist/services/container.d.ts +56 -0
- package/dist/services/container.d.ts.map +1 -0
- package/dist/services/container.js +76 -0
- package/dist/services/container.js.map +1 -0
- package/dist/tools/calendar/handlers.d.ts +186 -0
- package/dist/tools/calendar/handlers.d.ts.map +1 -0
- package/dist/tools/calendar/handlers.js +525 -0
- package/dist/tools/calendar/handlers.js.map +1 -0
- package/dist/tools/calendar/index.d.ts +11 -0
- package/dist/tools/calendar/index.d.ts.map +1 -0
- package/dist/tools/calendar/index.js +10 -0
- package/dist/tools/calendar/index.js.map +1 -0
- package/dist/tools/index.d.ts +23 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/integrations/handlers.d.ts +57 -0
- package/dist/tools/integrations/handlers.d.ts.map +1 -0
- package/dist/tools/integrations/handlers.js +159 -0
- package/dist/tools/integrations/handlers.js.map +1 -0
- package/dist/tools/integrations/index.d.ts +11 -0
- package/dist/tools/integrations/index.d.ts.map +1 -0
- package/dist/tools/integrations/index.js +10 -0
- package/dist/tools/integrations/index.js.map +1 -0
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +10 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/reminders/handlers.d.ts +61 -0
- package/dist/tools/reminders/handlers.d.ts.map +1 -0
- package/dist/tools/reminders/handlers.js +148 -0
- package/dist/tools/reminders/handlers.js.map +1 -0
- package/dist/tools/reminders/index.d.ts +11 -0
- package/dist/tools/reminders/index.d.ts.map +1 -0
- package/dist/tools/reminders/index.js +10 -0
- package/dist/tools/reminders/index.js.map +1 -0
- package/dist/tools/setup/handlers.d.ts +81 -0
- package/dist/tools/setup/handlers.d.ts.map +1 -0
- package/dist/tools/setup/handlers.js +172 -0
- package/dist/tools/setup/handlers.js.map +1 -0
- package/dist/tools/setup/index.d.ts +11 -0
- package/dist/tools/setup/index.d.ts.map +1 -0
- package/dist/tools/setup/index.js +10 -0
- package/dist/tools/setup/index.js.map +1 -0
- package/dist/tools/tasks/handlers.d.ts +95 -0
- package/dist/tools/tasks/handlers.d.ts.map +1 -0
- package/dist/tools/tasks/handlers.js +197 -0
- package/dist/tools/tasks/handlers.js.map +1 -0
- package/dist/tools/tasks/index.d.ts +11 -0
- package/dist/tools/tasks/index.d.ts.map +1 -0
- package/dist/tools/tasks/index.js +10 -0
- package/dist/tools/tasks/index.js.map +1 -0
- package/dist/tools/types.d.ts +54 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +9 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/types/calendar.d.ts +41 -0
- package/dist/types/calendar.d.ts.map +1 -0
- package/dist/types/calendar.js +18 -0
- package/dist/types/calendar.js.map +1 -0
- package/dist/types/config.d.ts +15 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +21 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/google-calendar-types.d.ts +139 -0
- package/dist/types/google-calendar-types.d.ts.map +1 -0
- package/dist/types/google-calendar-types.js +46 -0
- package/dist/types/google-calendar-types.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/estimation.d.ts +34 -0
- package/dist/utils/estimation.d.ts.map +1 -1
- package/dist/utils/estimation.js +38 -1
- package/dist/utils/estimation.js.map +1 -1
- package/dist/utils/mcp-response.d.ts +89 -0
- package/dist/utils/mcp-response.d.ts.map +1 -0
- package/dist/utils/mcp-response.js +103 -0
- package/dist/utils/mcp-response.js.map +1 -0
- package/dist/utils/task-splitter.d.ts +65 -4
- package/dist/utils/task-splitter.d.ts.map +1 -1
- package/dist/utils/task-splitter.js +69 -5
- package/dist/utils/task-splitter.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +22 -4
- package/dist/version.js.map +1 -1
- package/package.json +4 -3
- package/dist/cli/http-server.d.ts +0 -74
- package/dist/cli/http-server.d.ts.map +0 -1
- package/dist/cli/http-server.js +0 -407
- package/dist/cli/http-server.js.map +0 -1
- package/dist/remote/remote-mcp-server.d.ts +0 -244
- package/dist/remote/remote-mcp-server.d.ts.map +0 -1
- package/dist/remote/remote-mcp-server.js +0 -507
- package/dist/remote/remote-mcp-server.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -9,135 +9,35 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { ConfigLoader } from "./config/loader.js";
|
|
12
|
-
import { SetupWizard } from "./setup/wizard.js";
|
|
13
|
-
import { TaskAnalyzer } from "./tools/analyze-tasks.js";
|
|
14
12
|
import { ReminderManager } from "./integrations/reminder-manager.js";
|
|
15
13
|
import { CalendarService } from "./integrations/calendar-service.js";
|
|
14
|
+
import { CalendarSourceManager } from "./integrations/calendar-source-manager.js";
|
|
15
|
+
import { GoogleCalendarService } from "./integrations/google-calendar-service.js";
|
|
16
16
|
import { NotionMCPService } from "./integrations/notion-mcp.js";
|
|
17
17
|
import { TodoListManager } from "./integrations/todo-list-manager.js";
|
|
18
18
|
import { TaskSynchronizer } from "./integrations/task-synchronizer.js";
|
|
19
19
|
import { CalendarEventResponseService } from "./integrations/calendar-event-response.js";
|
|
20
|
-
import { CalendarEventCreatorService } from "./integrations/calendar-event-creator.js";
|
|
21
|
-
import { CalendarEventDeleterService } from "./integrations/calendar-event-deleter.js";
|
|
22
20
|
import { WorkingCadenceService } from "./services/working-cadence.js";
|
|
23
21
|
import { VERSION, SERVER_NAME } from "./version.js";
|
|
22
|
+
import { createErrorFromCatch } from "./utils/mcp-response.js";
|
|
23
|
+
// Extracted tool handlers
|
|
24
|
+
import { handleCheckSetupStatus, handleStartSetupWizard, handleAnswerWizardQuestion, handleSaveConfig, } from "./tools/setup/index.js";
|
|
25
|
+
import { handleAnalyzeTasks, handleUpdateTaskStatus, handleSyncTasks, handleDetectDuplicates, } from "./tools/tasks/index.js";
|
|
26
|
+
import { handleFindAvailableSlots, handleListCalendarEvents, handleRespondToCalendarEvent, handleRespondToCalendarEventsBatch, handleCreateCalendarEvent, handleDeleteCalendarEvent, handleDeleteCalendarEventsBatch, handleListCalendarSources, handleGetWorkingCadence, } from "./tools/calendar/index.js";
|
|
27
|
+
import { handleSetReminder, handleListTodos, } from "./tools/reminders/index.js";
|
|
28
|
+
import { handleSyncToNotion, handleUpdateConfig, } from "./tools/integrations/index.js";
|
|
24
29
|
// Global state
|
|
25
30
|
let config = null;
|
|
26
31
|
let wizardSession = null;
|
|
27
32
|
let reminderManager = null;
|
|
28
33
|
let calendarService = null;
|
|
34
|
+
let googleCalendarService = null;
|
|
35
|
+
let calendarSourceManager = null;
|
|
29
36
|
let notionService = null;
|
|
30
37
|
let todoListManager = null;
|
|
31
38
|
let taskSynchronizer = null;
|
|
32
39
|
let calendarEventResponseService = null;
|
|
33
|
-
let calendarEventCreatorService = null;
|
|
34
|
-
let calendarEventDeleterService = null;
|
|
35
40
|
let workingCadenceService = null;
|
|
36
|
-
/**
|
|
37
|
-
* Validate config updates for a specific section
|
|
38
|
-
*/
|
|
39
|
-
function validateConfigUpdate(section, updates) {
|
|
40
|
-
const invalidFields = [];
|
|
41
|
-
switch (section) {
|
|
42
|
-
case "user":
|
|
43
|
-
if (updates.name !== undefined && typeof updates.name !== "string") {
|
|
44
|
-
invalidFields.push("name");
|
|
45
|
-
}
|
|
46
|
-
if (updates.timezone !== undefined &&
|
|
47
|
-
typeof updates.timezone !== "string") {
|
|
48
|
-
invalidFields.push("timezone");
|
|
49
|
-
}
|
|
50
|
-
break;
|
|
51
|
-
case "calendar":
|
|
52
|
-
if (updates.workingHours !== undefined) {
|
|
53
|
-
const wh = updates.workingHours;
|
|
54
|
-
if (!wh.start || !wh.end) {
|
|
55
|
-
invalidFields.push("workingHours");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (updates.deepWorkDays !== undefined &&
|
|
59
|
-
!Array.isArray(updates.deepWorkDays)) {
|
|
60
|
-
invalidFields.push("deepWorkDays");
|
|
61
|
-
}
|
|
62
|
-
if (updates.meetingHeavyDays !== undefined &&
|
|
63
|
-
!Array.isArray(updates.meetingHeavyDays)) {
|
|
64
|
-
invalidFields.push("meetingHeavyDays");
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
case "integrations":
|
|
68
|
-
if (updates.notion !== undefined) {
|
|
69
|
-
const notion = updates.notion;
|
|
70
|
-
if (notion.enabled === true && !notion.databaseId) {
|
|
71
|
-
invalidFields.push("notion.databaseId");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
break;
|
|
75
|
-
case "team":
|
|
76
|
-
if (updates.members !== undefined && !Array.isArray(updates.members)) {
|
|
77
|
-
invalidFields.push("members");
|
|
78
|
-
}
|
|
79
|
-
if (updates.managers !== undefined && !Array.isArray(updates.managers)) {
|
|
80
|
-
invalidFields.push("managers");
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
if (invalidFields.length > 0) {
|
|
85
|
-
return {
|
|
86
|
-
valid: false,
|
|
87
|
-
error: `無効なフィールド: ${invalidFields.join(", ")}`,
|
|
88
|
-
invalidFields,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
return { valid: true };
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Apply config updates to a specific section
|
|
95
|
-
*/
|
|
96
|
-
function applyConfigUpdates(currentConfig, section, updates) {
|
|
97
|
-
const newConfig = { ...currentConfig };
|
|
98
|
-
switch (section) {
|
|
99
|
-
case "user":
|
|
100
|
-
newConfig.user = { ...newConfig.user, ...updates };
|
|
101
|
-
break;
|
|
102
|
-
case "calendar":
|
|
103
|
-
newConfig.calendar = {
|
|
104
|
-
...newConfig.calendar,
|
|
105
|
-
...updates,
|
|
106
|
-
};
|
|
107
|
-
break;
|
|
108
|
-
case "priorityRules":
|
|
109
|
-
newConfig.priorityRules = {
|
|
110
|
-
...newConfig.priorityRules,
|
|
111
|
-
...updates,
|
|
112
|
-
};
|
|
113
|
-
break;
|
|
114
|
-
case "integrations":
|
|
115
|
-
// Deep merge for integrations
|
|
116
|
-
if (updates.appleReminders) {
|
|
117
|
-
newConfig.integrations.appleReminders = {
|
|
118
|
-
...newConfig.integrations.appleReminders,
|
|
119
|
-
...updates.appleReminders,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
if (updates.notion) {
|
|
123
|
-
newConfig.integrations.notion = {
|
|
124
|
-
...newConfig.integrations.notion,
|
|
125
|
-
...updates.notion,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
case "team":
|
|
130
|
-
newConfig.team = { ...newConfig.team, ...updates };
|
|
131
|
-
break;
|
|
132
|
-
case "preferences":
|
|
133
|
-
newConfig.preferences = {
|
|
134
|
-
...newConfig.preferences,
|
|
135
|
-
...updates,
|
|
136
|
-
};
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
return newConfig;
|
|
140
|
-
}
|
|
141
41
|
/**
|
|
142
42
|
* Initialize services with config
|
|
143
43
|
*/
|
|
@@ -149,14 +49,90 @@ function initializeServices(userConfig) {
|
|
|
149
49
|
notionDatabaseId: userConfig.integrations.notion.databaseId,
|
|
150
50
|
});
|
|
151
51
|
calendarService = new CalendarService();
|
|
52
|
+
// Initialize Google Calendar service if configured
|
|
53
|
+
// Note: GoogleCalendarService requires GoogleOAuthHandler which needs OAuth config
|
|
54
|
+
// For now, we initialize with a stub handler. Full OAuth setup will be done in Task 33.
|
|
55
|
+
try {
|
|
56
|
+
const { GoogleOAuthHandler } = require('./oauth/google-oauth-handler.js');
|
|
57
|
+
const oauthConfig = {
|
|
58
|
+
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
|
59
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
|
60
|
+
redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth/callback',
|
|
61
|
+
};
|
|
62
|
+
const oauthHandler = new GoogleOAuthHandler(oauthConfig);
|
|
63
|
+
googleCalendarService = new GoogleCalendarService(oauthHandler);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// If Google Calendar initialization fails, continue without it
|
|
67
|
+
console.error('Google Calendar service initialization failed:', error);
|
|
68
|
+
googleCalendarService = null;
|
|
69
|
+
}
|
|
70
|
+
calendarSourceManager = new CalendarSourceManager({
|
|
71
|
+
calendarService,
|
|
72
|
+
googleCalendarService: googleCalendarService || undefined,
|
|
73
|
+
config: userConfig,
|
|
74
|
+
});
|
|
152
75
|
notionService = new NotionMCPService();
|
|
153
76
|
todoListManager = new TodoListManager();
|
|
154
77
|
taskSynchronizer = new TaskSynchronizer();
|
|
155
78
|
calendarEventResponseService = new CalendarEventResponseService();
|
|
156
|
-
calendarEventCreatorService = new CalendarEventCreatorService();
|
|
157
|
-
calendarEventDeleterService = new CalendarEventDeleterService();
|
|
158
79
|
workingCadenceService = new WorkingCadenceService();
|
|
159
80
|
}
|
|
81
|
+
// ============================================
|
|
82
|
+
// Context Factory Functions
|
|
83
|
+
// ============================================
|
|
84
|
+
function createSetupContext() {
|
|
85
|
+
return {
|
|
86
|
+
getConfig: () => config,
|
|
87
|
+
setConfig: (c) => {
|
|
88
|
+
config = c;
|
|
89
|
+
},
|
|
90
|
+
getWizardSession: () => wizardSession,
|
|
91
|
+
setWizardSession: (session) => {
|
|
92
|
+
wizardSession = session;
|
|
93
|
+
},
|
|
94
|
+
initializeServices,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function createTaskToolsContext() {
|
|
98
|
+
return {
|
|
99
|
+
getConfig: () => config,
|
|
100
|
+
getTodoListManager: () => todoListManager,
|
|
101
|
+
getTaskSynchronizer: () => taskSynchronizer,
|
|
102
|
+
initializeServices,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createCalendarToolsContext() {
|
|
106
|
+
return {
|
|
107
|
+
getConfig: () => config,
|
|
108
|
+
getCalendarSourceManager: () => calendarSourceManager,
|
|
109
|
+
getCalendarEventResponseService: () => calendarEventResponseService,
|
|
110
|
+
getGoogleCalendarService: () => googleCalendarService,
|
|
111
|
+
getWorkingCadenceService: () => workingCadenceService,
|
|
112
|
+
setWorkingCadenceService: (service) => {
|
|
113
|
+
workingCadenceService = service;
|
|
114
|
+
},
|
|
115
|
+
initializeServices,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createReminderTodoContext() {
|
|
119
|
+
return {
|
|
120
|
+
getConfig: () => config,
|
|
121
|
+
getReminderManager: () => reminderManager,
|
|
122
|
+
getTodoListManager: () => todoListManager,
|
|
123
|
+
initializeServices,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function createIntegrationToolsContext() {
|
|
127
|
+
return {
|
|
128
|
+
getConfig: () => config,
|
|
129
|
+
setConfig: (c) => {
|
|
130
|
+
config = c;
|
|
131
|
+
},
|
|
132
|
+
getNotionService: () => notionService,
|
|
133
|
+
initializeServices,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
160
136
|
/**
|
|
161
137
|
* Initialize the MCP server with all tools
|
|
162
138
|
*/
|
|
@@ -176,257 +152,27 @@ async function createServer() {
|
|
|
176
152
|
config = null;
|
|
177
153
|
}
|
|
178
154
|
// ============================================
|
|
179
|
-
// Setup & Configuration Tools
|
|
155
|
+
// Setup & Configuration Tools - uses extracted handlers
|
|
180
156
|
// ============================================
|
|
181
|
-
|
|
182
|
-
* check_setup_status - Check if initial setup is complete
|
|
183
|
-
* Requirement: 1.1, 1.2
|
|
184
|
-
*/
|
|
185
|
-
server.tool("check_setup_status", "Check if sage has been configured. Returns setup status and guidance.", {}, async () => {
|
|
186
|
-
const exists = await ConfigLoader.exists();
|
|
187
|
-
const isValid = config !== null;
|
|
188
|
-
if (!exists) {
|
|
189
|
-
return {
|
|
190
|
-
content: [
|
|
191
|
-
{
|
|
192
|
-
type: "text",
|
|
193
|
-
text: JSON.stringify({
|
|
194
|
-
setupComplete: false,
|
|
195
|
-
configExists: false,
|
|
196
|
-
message: "sageの初期設定が必要です。start_setup_wizardを実行してセットアップを開始してください。",
|
|
197
|
-
nextAction: "start_setup_wizard",
|
|
198
|
-
}, null, 2),
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
if (!isValid) {
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: "text",
|
|
208
|
-
text: JSON.stringify({
|
|
209
|
-
setupComplete: false,
|
|
210
|
-
configExists: true,
|
|
211
|
-
message: "設定ファイルが見つかりましたが、読み込みに失敗しました。設定を再作成してください。",
|
|
212
|
-
nextAction: "start_setup_wizard",
|
|
213
|
-
}, null, 2),
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
content: [
|
|
220
|
-
{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: JSON.stringify({
|
|
223
|
-
setupComplete: true,
|
|
224
|
-
configExists: true,
|
|
225
|
-
userName: config?.user.name,
|
|
226
|
-
message: "sageは設定済みです。タスク分析やリマインド設定を開始できます。",
|
|
227
|
-
availableTools: [
|
|
228
|
-
"analyze_tasks",
|
|
229
|
-
"set_reminder",
|
|
230
|
-
"find_available_slots",
|
|
231
|
-
"sync_to_notion",
|
|
232
|
-
"update_config",
|
|
233
|
-
],
|
|
234
|
-
}, null, 2),
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
/**
|
|
240
|
-
* start_setup_wizard - Begin the interactive setup process
|
|
241
|
-
* Requirement: 1.3
|
|
242
|
-
*/
|
|
157
|
+
server.tool("check_setup_status", "Check if sage has been configured. Returns setup status and guidance.", {}, async () => handleCheckSetupStatus(createSetupContext()));
|
|
243
158
|
server.tool("start_setup_wizard", "Start the interactive setup wizard for sage. Returns the first question.", {
|
|
244
159
|
mode: z
|
|
245
160
|
.enum(["full", "quick"])
|
|
246
161
|
.optional()
|
|
247
162
|
.describe("Setup mode: full (all questions) or quick (essential only)"),
|
|
248
|
-
}, async ({ mode
|
|
249
|
-
wizardSession = SetupWizard.createSession(mode);
|
|
250
|
-
const question = SetupWizard.getCurrentQuestion(wizardSession);
|
|
251
|
-
return {
|
|
252
|
-
content: [
|
|
253
|
-
{
|
|
254
|
-
type: "text",
|
|
255
|
-
text: JSON.stringify({
|
|
256
|
-
sessionId: wizardSession.sessionId,
|
|
257
|
-
currentStep: wizardSession.currentStep,
|
|
258
|
-
totalSteps: wizardSession.totalSteps,
|
|
259
|
-
progress: Math.round((wizardSession.currentStep / wizardSession.totalSteps) * 100),
|
|
260
|
-
question: {
|
|
261
|
-
id: question.id,
|
|
262
|
-
text: question.text,
|
|
263
|
-
type: question.type,
|
|
264
|
-
options: question.options,
|
|
265
|
-
defaultValue: question.defaultValue,
|
|
266
|
-
helpText: question.helpText,
|
|
267
|
-
},
|
|
268
|
-
message: "セットアップを開始します。以下の質問に回答してください。",
|
|
269
|
-
}, null, 2),
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
};
|
|
273
|
-
});
|
|
274
|
-
/**
|
|
275
|
-
* answer_wizard_question - Answer a setup wizard question
|
|
276
|
-
* Requirement: 1.3, 1.4
|
|
277
|
-
*/
|
|
163
|
+
}, async ({ mode }) => handleStartSetupWizard(createSetupContext(), { mode: mode ?? "full" }));
|
|
278
164
|
server.tool("answer_wizard_question", "Answer a question in the setup wizard and get the next question.", {
|
|
279
165
|
questionId: z.string().describe("The ID of the question being answered"),
|
|
280
166
|
answer: z
|
|
281
167
|
.union([z.string(), z.array(z.string())])
|
|
282
168
|
.describe("The answer to the question"),
|
|
283
|
-
}, async ({ questionId, answer }) => {
|
|
284
|
-
if (!wizardSession) {
|
|
285
|
-
return {
|
|
286
|
-
content: [
|
|
287
|
-
{
|
|
288
|
-
type: "text",
|
|
289
|
-
text: JSON.stringify({
|
|
290
|
-
error: true,
|
|
291
|
-
message: "セットアップセッションが見つかりません。start_setup_wizardを実行してください。",
|
|
292
|
-
}, null, 2),
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
const result = SetupWizard.answerQuestion(wizardSession, questionId, answer);
|
|
298
|
-
if (!result.success) {
|
|
299
|
-
return {
|
|
300
|
-
content: [
|
|
301
|
-
{
|
|
302
|
-
type: "text",
|
|
303
|
-
text: JSON.stringify({
|
|
304
|
-
error: true,
|
|
305
|
-
message: result.error,
|
|
306
|
-
currentQuestion: result.currentQuestion,
|
|
307
|
-
}, null, 2),
|
|
308
|
-
},
|
|
309
|
-
],
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
if (result.isComplete) {
|
|
313
|
-
return {
|
|
314
|
-
content: [
|
|
315
|
-
{
|
|
316
|
-
type: "text",
|
|
317
|
-
text: JSON.stringify({
|
|
318
|
-
isComplete: true,
|
|
319
|
-
sessionId: wizardSession.sessionId,
|
|
320
|
-
answers: wizardSession.answers,
|
|
321
|
-
message: "すべての質問に回答しました。save_configを実行して設定を保存してください。",
|
|
322
|
-
nextAction: "save_config",
|
|
323
|
-
}, null, 2),
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
const nextQuestion = SetupWizard.getCurrentQuestion(wizardSession);
|
|
329
|
-
return {
|
|
330
|
-
content: [
|
|
331
|
-
{
|
|
332
|
-
type: "text",
|
|
333
|
-
text: JSON.stringify({
|
|
334
|
-
success: true,
|
|
335
|
-
currentStep: wizardSession.currentStep,
|
|
336
|
-
totalSteps: wizardSession.totalSteps,
|
|
337
|
-
progress: Math.round((wizardSession.currentStep / wizardSession.totalSteps) * 100),
|
|
338
|
-
question: {
|
|
339
|
-
id: nextQuestion.id,
|
|
340
|
-
text: nextQuestion.text,
|
|
341
|
-
type: nextQuestion.type,
|
|
342
|
-
options: nextQuestion.options,
|
|
343
|
-
defaultValue: nextQuestion.defaultValue,
|
|
344
|
-
helpText: nextQuestion.helpText,
|
|
345
|
-
},
|
|
346
|
-
}, null, 2),
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
};
|
|
350
|
-
});
|
|
351
|
-
/**
|
|
352
|
-
* save_config - Save the configuration from the setup wizard
|
|
353
|
-
* Requirement: 1.4, 1.5, 1.6
|
|
354
|
-
*/
|
|
169
|
+
}, async ({ questionId, answer }) => handleAnswerWizardQuestion(createSetupContext(), { questionId, answer }));
|
|
355
170
|
server.tool("save_config", "Save the configuration after completing the setup wizard.", {
|
|
356
171
|
confirm: z.boolean().describe("Confirm saving the configuration"),
|
|
357
|
-
}, async ({ confirm }) => {
|
|
358
|
-
if (!confirm) {
|
|
359
|
-
return {
|
|
360
|
-
content: [
|
|
361
|
-
{
|
|
362
|
-
type: "text",
|
|
363
|
-
text: JSON.stringify({
|
|
364
|
-
saved: false,
|
|
365
|
-
message: "設定の保存がキャンセルされました。",
|
|
366
|
-
}, null, 2),
|
|
367
|
-
},
|
|
368
|
-
],
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
if (!wizardSession) {
|
|
372
|
-
return {
|
|
373
|
-
content: [
|
|
374
|
-
{
|
|
375
|
-
type: "text",
|
|
376
|
-
text: JSON.stringify({
|
|
377
|
-
error: true,
|
|
378
|
-
message: "セットアップセッションが見つかりません。start_setup_wizardを実行してください。",
|
|
379
|
-
}, null, 2),
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
try {
|
|
385
|
-
const newConfig = SetupWizard.buildConfig(wizardSession);
|
|
386
|
-
await ConfigLoader.save(newConfig);
|
|
387
|
-
config = newConfig;
|
|
388
|
-
wizardSession = null;
|
|
389
|
-
return {
|
|
390
|
-
content: [
|
|
391
|
-
{
|
|
392
|
-
type: "text",
|
|
393
|
-
text: JSON.stringify({
|
|
394
|
-
saved: true,
|
|
395
|
-
configPath: ConfigLoader.getConfigPath(),
|
|
396
|
-
userName: newConfig.user.name,
|
|
397
|
-
message: `設定を保存しました。${newConfig.user.name}さん、sageをご利用いただきありがとうございます!`,
|
|
398
|
-
availableTools: [
|
|
399
|
-
"analyze_tasks",
|
|
400
|
-
"set_reminder",
|
|
401
|
-
"find_available_slots",
|
|
402
|
-
"sync_to_notion",
|
|
403
|
-
],
|
|
404
|
-
}, null, 2),
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
return {
|
|
411
|
-
content: [
|
|
412
|
-
{
|
|
413
|
-
type: "text",
|
|
414
|
-
text: JSON.stringify({
|
|
415
|
-
error: true,
|
|
416
|
-
message: `設定の保存に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
417
|
-
}, null, 2),
|
|
418
|
-
},
|
|
419
|
-
],
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
});
|
|
172
|
+
}, async ({ confirm }) => handleSaveConfig(createSetupContext(), { confirm }));
|
|
423
173
|
// ============================================
|
|
424
|
-
// Task Analysis Tools
|
|
174
|
+
// Task Analysis Tools - uses extracted handlers
|
|
425
175
|
// ============================================
|
|
426
|
-
/**
|
|
427
|
-
* analyze_tasks - Analyze tasks and provide prioritization
|
|
428
|
-
* Requirement: 2.1-2.6, 3.1-3.2, 4.1-4.5
|
|
429
|
-
*/
|
|
430
176
|
server.tool("analyze_tasks", "Analyze tasks to determine priority, estimate time, and identify stakeholders.", {
|
|
431
177
|
tasks: z
|
|
432
178
|
.array(z.object({
|
|
@@ -438,63 +184,8 @@ async function createServer() {
|
|
|
438
184
|
.describe("Task deadline (ISO 8601 format)"),
|
|
439
185
|
}))
|
|
440
186
|
.describe("List of tasks to analyze"),
|
|
441
|
-
}, async ({ tasks }) => {
|
|
442
|
-
|
|
443
|
-
return {
|
|
444
|
-
content: [
|
|
445
|
-
{
|
|
446
|
-
type: "text",
|
|
447
|
-
text: JSON.stringify({
|
|
448
|
-
error: true,
|
|
449
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
450
|
-
}, null, 2),
|
|
451
|
-
},
|
|
452
|
-
],
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
try {
|
|
456
|
-
const result = await TaskAnalyzer.analyzeTasks(tasks, config);
|
|
457
|
-
return {
|
|
458
|
-
content: [
|
|
459
|
-
{
|
|
460
|
-
type: "text",
|
|
461
|
-
text: JSON.stringify({
|
|
462
|
-
success: true,
|
|
463
|
-
summary: result.summary,
|
|
464
|
-
tasks: result.analyzedTasks.map((t) => ({
|
|
465
|
-
title: t.original.title,
|
|
466
|
-
description: t.original.description,
|
|
467
|
-
deadline: t.original.deadline,
|
|
468
|
-
priority: t.priority,
|
|
469
|
-
estimatedMinutes: t.estimatedMinutes,
|
|
470
|
-
stakeholders: t.stakeholders,
|
|
471
|
-
tags: t.tags,
|
|
472
|
-
reasoning: t.reasoning,
|
|
473
|
-
suggestedReminders: t.suggestedReminders,
|
|
474
|
-
})),
|
|
475
|
-
}, null, 2),
|
|
476
|
-
},
|
|
477
|
-
],
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
catch (error) {
|
|
481
|
-
return {
|
|
482
|
-
content: [
|
|
483
|
-
{
|
|
484
|
-
type: "text",
|
|
485
|
-
text: JSON.stringify({
|
|
486
|
-
error: true,
|
|
487
|
-
message: `タスク分析に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
488
|
-
}, null, 2),
|
|
489
|
-
},
|
|
490
|
-
],
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
/**
|
|
495
|
-
* set_reminder - Set a reminder for a task
|
|
496
|
-
* Requirement: 5.1-5.6
|
|
497
|
-
*/
|
|
187
|
+
}, async ({ tasks }) => handleAnalyzeTasks(createTaskToolsContext(), { tasks }));
|
|
188
|
+
// set_reminder - uses extracted handler
|
|
498
189
|
server.tool("set_reminder", "Set a reminder for a task in Apple Reminders or Notion.", {
|
|
499
190
|
taskTitle: z.string().describe("Title of the task"),
|
|
500
191
|
dueDate: z
|
|
@@ -523,108 +214,16 @@ async function createServer() {
|
|
|
523
214
|
.string()
|
|
524
215
|
.optional()
|
|
525
216
|
.describe("Additional notes for the reminder"),
|
|
526
|
-
}, async ({ taskTitle, dueDate, reminderType, list, priority, notes }) => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
},
|
|
537
|
-
],
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
if (!reminderManager) {
|
|
541
|
-
initializeServices(config);
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
const result = await reminderManager.setReminder({
|
|
545
|
-
taskTitle,
|
|
546
|
-
targetDate: dueDate,
|
|
547
|
-
reminderType,
|
|
548
|
-
list: list ?? config.integrations.appleReminders.defaultList,
|
|
549
|
-
priority: priority,
|
|
550
|
-
notes,
|
|
551
|
-
});
|
|
552
|
-
if (result.success) {
|
|
553
|
-
// Check if this is a delegation request for Notion
|
|
554
|
-
if (result.delegateToNotion && result.notionRequest) {
|
|
555
|
-
return {
|
|
556
|
-
content: [
|
|
557
|
-
{
|
|
558
|
-
type: "text",
|
|
559
|
-
text: JSON.stringify({
|
|
560
|
-
success: true,
|
|
561
|
-
destination: "notion_mcp",
|
|
562
|
-
method: "delegate",
|
|
563
|
-
delegateToNotion: true,
|
|
564
|
-
notionRequest: result.notionRequest,
|
|
565
|
-
message: `Notionへの追加はClaude Codeが直接notion-create-pagesツールを使用してください。`,
|
|
566
|
-
instruction: `notion-create-pagesツールを以下のパラメータで呼び出してください:
|
|
567
|
-
- parent: { "type": "data_source_id", "data_source_id": "${result.notionRequest.databaseId.replace(/-/g, "")}" }
|
|
568
|
-
- pages: [{ "properties": ${JSON.stringify(result.notionRequest.properties)} }]`,
|
|
569
|
-
}, null, 2),
|
|
570
|
-
},
|
|
571
|
-
],
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
return {
|
|
575
|
-
content: [
|
|
576
|
-
{
|
|
577
|
-
type: "text",
|
|
578
|
-
text: JSON.stringify({
|
|
579
|
-
success: true,
|
|
580
|
-
destination: result.destination,
|
|
581
|
-
method: result.method,
|
|
582
|
-
reminderId: result.reminderId,
|
|
583
|
-
reminderUrl: result.reminderUrl ?? result.pageUrl,
|
|
584
|
-
message: result.destination === "apple_reminders"
|
|
585
|
-
? `Apple Remindersにリマインダーを作成しました: ${taskTitle}`
|
|
586
|
-
: `Notionにタスクを作成しました: ${taskTitle}`,
|
|
587
|
-
}, null, 2),
|
|
588
|
-
},
|
|
589
|
-
],
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
return {
|
|
593
|
-
content: [
|
|
594
|
-
{
|
|
595
|
-
type: "text",
|
|
596
|
-
text: JSON.stringify({
|
|
597
|
-
success: false,
|
|
598
|
-
destination: result.destination,
|
|
599
|
-
error: result.error,
|
|
600
|
-
fallbackText: result.fallbackText,
|
|
601
|
-
message: result.fallbackText
|
|
602
|
-
? "自動作成に失敗しました。以下のテキストを手動でコピーしてください。"
|
|
603
|
-
: `リマインダー作成に失敗しました: ${result.error}`,
|
|
604
|
-
}, null, 2),
|
|
605
|
-
},
|
|
606
|
-
],
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
catch (error) {
|
|
610
|
-
return {
|
|
611
|
-
content: [
|
|
612
|
-
{
|
|
613
|
-
type: "text",
|
|
614
|
-
text: JSON.stringify({
|
|
615
|
-
error: true,
|
|
616
|
-
message: `リマインダー設定に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
617
|
-
}, null, 2),
|
|
618
|
-
},
|
|
619
|
-
],
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
/**
|
|
624
|
-
* find_available_slots - Find available time slots in calendar
|
|
625
|
-
* Requirement: 3.3-3.6, 6.1-6.6
|
|
626
|
-
*/
|
|
627
|
-
server.tool("find_available_slots", "Find available time slots in the calendar for scheduling tasks.", {
|
|
217
|
+
}, async ({ taskTitle, dueDate, reminderType, list, priority, notes }) => handleSetReminder(createReminderTodoContext(), {
|
|
218
|
+
taskTitle,
|
|
219
|
+
dueDate,
|
|
220
|
+
reminderType,
|
|
221
|
+
list,
|
|
222
|
+
priority,
|
|
223
|
+
notes,
|
|
224
|
+
}));
|
|
225
|
+
// find_available_slots - uses extracted handler
|
|
226
|
+
server.tool("find_available_slots", "Find available time slots in the calendar for scheduling tasks from all enabled calendar sources.", {
|
|
628
227
|
durationMinutes: z.number().describe("Required duration in minutes"),
|
|
629
228
|
startDate: z
|
|
630
229
|
.string()
|
|
@@ -638,217 +237,41 @@ async function createServer() {
|
|
|
638
237
|
.boolean()
|
|
639
238
|
.optional()
|
|
640
239
|
.describe("Prefer deep work time slots"),
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// Check platform availability
|
|
660
|
-
const platformInfo = await calendarService.detectPlatform();
|
|
661
|
-
const isAvailable = await calendarService.isAvailable();
|
|
662
|
-
if (!isAvailable) {
|
|
663
|
-
// Return manual input prompt for unsupported platforms
|
|
664
|
-
const manualPrompt = calendarService.generateManualInputPrompt(startDate ?? new Date().toISOString().split("T")[0], endDate ??
|
|
665
|
-
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
666
|
-
.toISOString()
|
|
667
|
-
.split("T")[0]);
|
|
668
|
-
return {
|
|
669
|
-
content: [
|
|
670
|
-
{
|
|
671
|
-
type: "text",
|
|
672
|
-
text: JSON.stringify({
|
|
673
|
-
success: false,
|
|
674
|
-
platform: platformInfo.platform,
|
|
675
|
-
method: platformInfo.recommendedMethod,
|
|
676
|
-
message: "カレンダー統合がこのプラットフォームで利用できません。手動で予定を入力してください。",
|
|
677
|
-
manualPrompt,
|
|
678
|
-
}, null, 2),
|
|
679
|
-
},
|
|
680
|
-
],
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
// Fetch events from calendar
|
|
684
|
-
const searchStart = startDate ?? new Date().toISOString().split("T")[0];
|
|
685
|
-
const searchEnd = endDate ??
|
|
686
|
-
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
|
687
|
-
.toISOString()
|
|
688
|
-
.split("T")[0];
|
|
689
|
-
const events = await calendarService.fetchEvents(searchStart, searchEnd);
|
|
690
|
-
// Find available slots
|
|
691
|
-
const workingHours = {
|
|
692
|
-
start: config.calendar.workingHours.start,
|
|
693
|
-
end: config.calendar.workingHours.end,
|
|
694
|
-
};
|
|
695
|
-
const slots = calendarService.findAvailableSlotsFromEvents(events, durationMinutes, workingHours, searchStart);
|
|
696
|
-
// Apply suitability scoring
|
|
697
|
-
const suitabilityConfig = {
|
|
698
|
-
deepWorkDays: config.calendar.deepWorkDays,
|
|
699
|
-
meetingHeavyDays: config.calendar.meetingHeavyDays,
|
|
700
|
-
};
|
|
701
|
-
const scoredSlots = slots.map((slot) => calendarService.calculateSuitability(slot, suitabilityConfig));
|
|
702
|
-
// Filter for deep work preference if requested
|
|
703
|
-
const filteredSlots = preferDeepWork
|
|
704
|
-
? scoredSlots.filter((s) => s.dayType === "deep-work")
|
|
705
|
-
: scoredSlots;
|
|
706
|
-
// Sort by suitability (excellent > good > acceptable)
|
|
707
|
-
const suitabilityOrder = { excellent: 0, good: 1, acceptable: 2 };
|
|
708
|
-
filteredSlots.sort((a, b) => suitabilityOrder[a.suitability] - suitabilityOrder[b.suitability]);
|
|
709
|
-
return {
|
|
710
|
-
content: [
|
|
711
|
-
{
|
|
712
|
-
type: "text",
|
|
713
|
-
text: JSON.stringify({
|
|
714
|
-
success: true,
|
|
715
|
-
platform: platformInfo.platform,
|
|
716
|
-
method: platformInfo.recommendedMethod,
|
|
717
|
-
searchRange: { start: searchStart, end: searchEnd },
|
|
718
|
-
eventsFound: events.length,
|
|
719
|
-
slots: filteredSlots.slice(0, 10).map((slot) => ({
|
|
720
|
-
start: slot.start,
|
|
721
|
-
end: slot.end,
|
|
722
|
-
durationMinutes: slot.durationMinutes,
|
|
723
|
-
suitability: slot.suitability,
|
|
724
|
-
dayType: slot.dayType,
|
|
725
|
-
reason: slot.reason,
|
|
726
|
-
})),
|
|
727
|
-
message: filteredSlots.length > 0
|
|
728
|
-
? `${filteredSlots.length}件の空き時間が見つかりました。`
|
|
729
|
-
: "指定した条件に合う空き時間が見つかりませんでした。",
|
|
730
|
-
}, null, 2),
|
|
731
|
-
},
|
|
732
|
-
],
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
return {
|
|
737
|
-
content: [
|
|
738
|
-
{
|
|
739
|
-
type: "text",
|
|
740
|
-
text: JSON.stringify({
|
|
741
|
-
error: true,
|
|
742
|
-
message: `カレンダー検索に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
743
|
-
}, null, 2),
|
|
744
|
-
},
|
|
745
|
-
],
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
/**
|
|
750
|
-
* list_calendar_events - List calendar events for a specified period
|
|
751
|
-
* Requirement: 16.1-16.12
|
|
752
|
-
*/
|
|
753
|
-
server.tool("list_calendar_events", "List calendar events for a specified period. Returns events with details including calendar name and location.", {
|
|
240
|
+
minDurationMinutes: z
|
|
241
|
+
.number()
|
|
242
|
+
.optional()
|
|
243
|
+
.describe("Minimum slot duration in minutes (default: 25)"),
|
|
244
|
+
maxDurationMinutes: z
|
|
245
|
+
.number()
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Maximum slot duration in minutes (default: 480)"),
|
|
248
|
+
}, async ({ durationMinutes, startDate, endDate, preferDeepWork, minDurationMinutes, maxDurationMinutes }) => handleFindAvailableSlots(createCalendarToolsContext(), {
|
|
249
|
+
durationMinutes,
|
|
250
|
+
startDate,
|
|
251
|
+
endDate,
|
|
252
|
+
preferDeepWork,
|
|
253
|
+
minDurationMinutes,
|
|
254
|
+
maxDurationMinutes,
|
|
255
|
+
}));
|
|
256
|
+
// list_calendar_events - uses extracted handler
|
|
257
|
+
server.tool("list_calendar_events", "List calendar events for a specified period from enabled sources (EventKit, Google Calendar, or both). Returns events with details including calendar name and location.", {
|
|
754
258
|
startDate: z
|
|
755
259
|
.string()
|
|
756
260
|
.describe("Start date in ISO 8601 format (e.g., 2025-01-15)"),
|
|
757
261
|
endDate: z
|
|
758
262
|
.string()
|
|
759
263
|
.describe("End date in ISO 8601 format (e.g., 2025-01-20)"),
|
|
760
|
-
|
|
264
|
+
calendarId: z
|
|
761
265
|
.string()
|
|
762
266
|
.optional()
|
|
763
|
-
.describe("Optional: filter events by calendar name"),
|
|
764
|
-
}, async ({ startDate, endDate,
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
error: true,
|
|
772
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
773
|
-
}, null, 2),
|
|
774
|
-
},
|
|
775
|
-
],
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
if (!calendarService) {
|
|
779
|
-
initializeServices(config);
|
|
780
|
-
}
|
|
781
|
-
try {
|
|
782
|
-
// Check platform availability
|
|
783
|
-
const platformInfo = await calendarService.detectPlatform();
|
|
784
|
-
const isAvailable = await calendarService.isAvailable();
|
|
785
|
-
if (!isAvailable) {
|
|
786
|
-
return {
|
|
787
|
-
content: [
|
|
788
|
-
{
|
|
789
|
-
type: "text",
|
|
790
|
-
text: JSON.stringify({
|
|
791
|
-
success: false,
|
|
792
|
-
platform: platformInfo.platform,
|
|
793
|
-
method: platformInfo.recommendedMethod,
|
|
794
|
-
message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
|
|
795
|
-
}, null, 2),
|
|
796
|
-
},
|
|
797
|
-
],
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
// List events
|
|
801
|
-
const result = await calendarService.listEvents({
|
|
802
|
-
startDate,
|
|
803
|
-
endDate,
|
|
804
|
-
calendarName,
|
|
805
|
-
});
|
|
806
|
-
return {
|
|
807
|
-
content: [
|
|
808
|
-
{
|
|
809
|
-
type: "text",
|
|
810
|
-
text: JSON.stringify({
|
|
811
|
-
success: true,
|
|
812
|
-
platform: platformInfo.platform,
|
|
813
|
-
method: platformInfo.recommendedMethod,
|
|
814
|
-
events: result.events.map((event) => ({
|
|
815
|
-
id: event.id,
|
|
816
|
-
title: event.title,
|
|
817
|
-
start: event.start,
|
|
818
|
-
end: event.end,
|
|
819
|
-
isAllDay: event.isAllDay,
|
|
820
|
-
calendar: event.calendar,
|
|
821
|
-
location: event.location,
|
|
822
|
-
})),
|
|
823
|
-
period: result.period,
|
|
824
|
-
totalEvents: result.totalEvents,
|
|
825
|
-
message: result.totalEvents > 0
|
|
826
|
-
? `${result.totalEvents}件のイベントが見つかりました。`
|
|
827
|
-
: "指定した期間にイベントが見つかりませんでした。",
|
|
828
|
-
}, null, 2),
|
|
829
|
-
},
|
|
830
|
-
],
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
catch (error) {
|
|
834
|
-
return {
|
|
835
|
-
content: [
|
|
836
|
-
{
|
|
837
|
-
type: "text",
|
|
838
|
-
text: JSON.stringify({
|
|
839
|
-
error: true,
|
|
840
|
-
message: `カレンダーイベントの取得に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
841
|
-
}, null, 2),
|
|
842
|
-
},
|
|
843
|
-
],
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
/**
|
|
848
|
-
* respond_to_calendar_event - Respond to a single calendar event
|
|
849
|
-
* Requirement: 17.1, 17.2, 17.5-17.11
|
|
850
|
-
*/
|
|
851
|
-
server.tool("respond_to_calendar_event", "Respond to a calendar event with accept, decline, or tentative. Use this to RSVP to meeting invitations.", {
|
|
267
|
+
.describe("Optional: filter events by calendar ID or name"),
|
|
268
|
+
}, async ({ startDate, endDate, calendarId }) => handleListCalendarEvents(createCalendarToolsContext(), {
|
|
269
|
+
startDate,
|
|
270
|
+
endDate,
|
|
271
|
+
calendarId,
|
|
272
|
+
}));
|
|
273
|
+
// respond_to_calendar_event - uses extracted handler
|
|
274
|
+
server.tool("respond_to_calendar_event", "Respond to a calendar event with accept, decline, or tentative. Supports both EventKit (macOS) and Google Calendar events. Use this to RSVP to meeting invitations from any enabled calendar source.", {
|
|
852
275
|
eventId: z.string().describe("The ID of the calendar event to respond to"),
|
|
853
276
|
response: z
|
|
854
277
|
.enum(["accept", "decline", "tentative"])
|
|
@@ -856,102 +279,23 @@ async function createServer() {
|
|
|
856
279
|
comment: z
|
|
857
280
|
.string()
|
|
858
281
|
.optional()
|
|
859
|
-
.describe("Optional comment to include with the response (e.g., '年末年始休暇のため')"),
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
// Check platform availability
|
|
879
|
-
const isAvailable = await calendarEventResponseService.isEventKitAvailable();
|
|
880
|
-
if (!isAvailable) {
|
|
881
|
-
return {
|
|
882
|
-
content: [
|
|
883
|
-
{
|
|
884
|
-
type: "text",
|
|
885
|
-
text: JSON.stringify({
|
|
886
|
-
success: false,
|
|
887
|
-
message: "カレンダーイベント返信機能はmacOSでのみ利用可能です。",
|
|
888
|
-
}, null, 2),
|
|
889
|
-
},
|
|
890
|
-
],
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
// Respond to the event
|
|
894
|
-
const result = await calendarEventResponseService.respondToEvent({
|
|
895
|
-
eventId,
|
|
896
|
-
response,
|
|
897
|
-
comment,
|
|
898
|
-
});
|
|
899
|
-
if (result.success) {
|
|
900
|
-
return {
|
|
901
|
-
content: [
|
|
902
|
-
{
|
|
903
|
-
type: "text",
|
|
904
|
-
text: JSON.stringify({
|
|
905
|
-
success: true,
|
|
906
|
-
eventId: result.eventId,
|
|
907
|
-
eventTitle: result.eventTitle,
|
|
908
|
-
newStatus: result.newStatus,
|
|
909
|
-
method: result.method,
|
|
910
|
-
instanceOnly: result.instanceOnly,
|
|
911
|
-
message: result.message,
|
|
912
|
-
}, null, 2),
|
|
913
|
-
},
|
|
914
|
-
],
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
// Handle skipped or failed response
|
|
918
|
-
return {
|
|
919
|
-
content: [
|
|
920
|
-
{
|
|
921
|
-
type: "text",
|
|
922
|
-
text: JSON.stringify({
|
|
923
|
-
success: false,
|
|
924
|
-
eventId: result.eventId,
|
|
925
|
-
eventTitle: result.eventTitle,
|
|
926
|
-
skipped: result.skipped,
|
|
927
|
-
reason: result.reason,
|
|
928
|
-
error: result.error,
|
|
929
|
-
message: result.skipped
|
|
930
|
-
? `イベントをスキップしました: ${result.reason}`
|
|
931
|
-
: `イベント返信に失敗しました: ${result.error}`,
|
|
932
|
-
}, null, 2),
|
|
933
|
-
},
|
|
934
|
-
],
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
catch (error) {
|
|
938
|
-
return {
|
|
939
|
-
content: [
|
|
940
|
-
{
|
|
941
|
-
type: "text",
|
|
942
|
-
text: JSON.stringify({
|
|
943
|
-
error: true,
|
|
944
|
-
message: `カレンダーイベント返信に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
945
|
-
}, null, 2),
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
/**
|
|
952
|
-
* respond_to_calendar_events_batch - Respond to multiple calendar events
|
|
953
|
-
* Requirement: 17.3, 17.4, 17.12
|
|
954
|
-
*/
|
|
282
|
+
.describe("Optional comment to include with the response (e.g., '年末年始休暇のため'). Note: Comments are only supported for EventKit events."),
|
|
283
|
+
source: z
|
|
284
|
+
.enum(["eventkit", "google"])
|
|
285
|
+
.optional()
|
|
286
|
+
.describe("Optional: Specify the calendar source explicitly. If not provided, will try Google Calendar first, then EventKit."),
|
|
287
|
+
calendarId: z
|
|
288
|
+
.string()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe("Optional: Google Calendar ID (defaults to 'primary'). Only used for Google Calendar events."),
|
|
291
|
+
}, async ({ eventId, response, comment, source, calendarId }) => handleRespondToCalendarEvent(createCalendarToolsContext(), {
|
|
292
|
+
eventId,
|
|
293
|
+
response,
|
|
294
|
+
comment,
|
|
295
|
+
source,
|
|
296
|
+
calendarId,
|
|
297
|
+
}));
|
|
298
|
+
// respond_to_calendar_events_batch - uses extracted handler
|
|
955
299
|
server.tool("respond_to_calendar_events_batch", "Respond to multiple calendar events at once. Useful for declining all events during vacation or leave periods.", {
|
|
956
300
|
eventIds: z
|
|
957
301
|
.array(z.string())
|
|
@@ -963,82 +307,13 @@ async function createServer() {
|
|
|
963
307
|
.string()
|
|
964
308
|
.optional()
|
|
965
309
|
.describe("Optional comment to include with all responses (e.g., '年末年始休暇のため')"),
|
|
966
|
-
}, async ({ eventIds, response, comment }) => {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
error: true,
|
|
974
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
975
|
-
}, null, 2),
|
|
976
|
-
},
|
|
977
|
-
],
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
if (!calendarEventResponseService) {
|
|
981
|
-
initializeServices(config);
|
|
982
|
-
}
|
|
983
|
-
try {
|
|
984
|
-
// Check platform availability
|
|
985
|
-
const isAvailable = await calendarEventResponseService.isEventKitAvailable();
|
|
986
|
-
if (!isAvailable) {
|
|
987
|
-
return {
|
|
988
|
-
content: [
|
|
989
|
-
{
|
|
990
|
-
type: "text",
|
|
991
|
-
text: JSON.stringify({
|
|
992
|
-
success: false,
|
|
993
|
-
message: "カレンダーイベント返信機能はmacOSでのみ利用可能です。",
|
|
994
|
-
}, null, 2),
|
|
995
|
-
},
|
|
996
|
-
],
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
// Respond to all events in batch
|
|
1000
|
-
const result = await calendarEventResponseService.respondToEventsBatch({
|
|
1001
|
-
eventIds,
|
|
1002
|
-
response,
|
|
1003
|
-
comment,
|
|
1004
|
-
});
|
|
1005
|
-
return {
|
|
1006
|
-
content: [
|
|
1007
|
-
{
|
|
1008
|
-
type: "text",
|
|
1009
|
-
text: JSON.stringify({
|
|
1010
|
-
success: result.success,
|
|
1011
|
-
summary: result.summary,
|
|
1012
|
-
details: {
|
|
1013
|
-
succeeded: result.details.succeeded,
|
|
1014
|
-
skipped: result.details.skipped,
|
|
1015
|
-
failed: result.details.failed,
|
|
1016
|
-
},
|
|
1017
|
-
message: result.message,
|
|
1018
|
-
}, null, 2),
|
|
1019
|
-
},
|
|
1020
|
-
],
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
catch (error) {
|
|
1024
|
-
return {
|
|
1025
|
-
content: [
|
|
1026
|
-
{
|
|
1027
|
-
type: "text",
|
|
1028
|
-
text: JSON.stringify({
|
|
1029
|
-
error: true,
|
|
1030
|
-
message: `カレンダーイベント一括返信に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1031
|
-
}, null, 2),
|
|
1032
|
-
},
|
|
1033
|
-
],
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
});
|
|
1037
|
-
/**
|
|
1038
|
-
* create_calendar_event - Create a new calendar event
|
|
1039
|
-
* Requirement: 18.1-18.11
|
|
1040
|
-
*/
|
|
1041
|
-
server.tool("create_calendar_event", "Create a new calendar event with optional location, notes, and alarms.", {
|
|
310
|
+
}, async ({ eventIds, response, comment }) => handleRespondToCalendarEventsBatch(createCalendarToolsContext(), {
|
|
311
|
+
eventIds,
|
|
312
|
+
response,
|
|
313
|
+
comment,
|
|
314
|
+
}));
|
|
315
|
+
// create_calendar_event - uses extracted handler
|
|
316
|
+
server.tool("create_calendar_event", "Create a new calendar event in the appropriate calendar source with optional location, notes, and alarms.", {
|
|
1042
317
|
title: z.string().describe("Event title"),
|
|
1043
318
|
startDate: z
|
|
1044
319
|
.string()
|
|
@@ -1056,429 +331,62 @@ async function createServer() {
|
|
|
1056
331
|
.array(z.string())
|
|
1057
332
|
.optional()
|
|
1058
333
|
.describe("Optional: Override default alarms with custom settings (e.g., ['-15m', '-1h']). If omitted, calendar's default alarm settings apply."),
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
try {
|
|
1077
|
-
// Check platform availability
|
|
1078
|
-
const isAvailable = await calendarEventCreatorService.isEventKitAvailable();
|
|
1079
|
-
if (!isAvailable) {
|
|
1080
|
-
return {
|
|
1081
|
-
content: [
|
|
1082
|
-
{
|
|
1083
|
-
type: "text",
|
|
1084
|
-
text: JSON.stringify({
|
|
1085
|
-
success: false,
|
|
1086
|
-
message: "カレンダーイベント作成機能はmacOSでのみ利用可能です。",
|
|
1087
|
-
}, null, 2),
|
|
1088
|
-
},
|
|
1089
|
-
],
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
// Create the event
|
|
1093
|
-
const result = await calendarEventCreatorService.createEvent({
|
|
1094
|
-
title,
|
|
1095
|
-
startDate,
|
|
1096
|
-
endDate,
|
|
1097
|
-
location,
|
|
1098
|
-
notes,
|
|
1099
|
-
calendarName,
|
|
1100
|
-
alarms,
|
|
1101
|
-
});
|
|
1102
|
-
if (result.success) {
|
|
1103
|
-
return {
|
|
1104
|
-
content: [
|
|
1105
|
-
{
|
|
1106
|
-
type: "text",
|
|
1107
|
-
text: JSON.stringify({
|
|
1108
|
-
success: true,
|
|
1109
|
-
eventId: result.eventId,
|
|
1110
|
-
title: result.title,
|
|
1111
|
-
startDate: result.startDate,
|
|
1112
|
-
endDate: result.endDate,
|
|
1113
|
-
calendarName: result.calendarName,
|
|
1114
|
-
isAllDay: result.isAllDay,
|
|
1115
|
-
message: result.message,
|
|
1116
|
-
}, null, 2),
|
|
1117
|
-
},
|
|
1118
|
-
],
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
// Handle creation failure
|
|
1122
|
-
return {
|
|
1123
|
-
content: [
|
|
1124
|
-
{
|
|
1125
|
-
type: "text",
|
|
1126
|
-
text: JSON.stringify({
|
|
1127
|
-
success: false,
|
|
1128
|
-
error: result.error,
|
|
1129
|
-
message: result.message,
|
|
1130
|
-
}, null, 2),
|
|
1131
|
-
},
|
|
1132
|
-
],
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
catch (error) {
|
|
1136
|
-
return {
|
|
1137
|
-
content: [
|
|
1138
|
-
{
|
|
1139
|
-
type: "text",
|
|
1140
|
-
text: JSON.stringify({
|
|
1141
|
-
error: true,
|
|
1142
|
-
message: `カレンダーイベント作成に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1143
|
-
}, null, 2),
|
|
1144
|
-
},
|
|
1145
|
-
],
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
});
|
|
1149
|
-
/**
|
|
1150
|
-
* delete_calendar_event - Delete a calendar event
|
|
1151
|
-
* Requirement: 19.1-19.9
|
|
1152
|
-
*/
|
|
1153
|
-
server.tool("delete_calendar_event", "Delete a calendar event by its ID.", {
|
|
334
|
+
preferredSource: z
|
|
335
|
+
.enum(['eventkit', 'google'])
|
|
336
|
+
.optional()
|
|
337
|
+
.describe("Preferred calendar source to create the event in. If not specified, uses the first enabled source."),
|
|
338
|
+
}, async ({ title, startDate, endDate, location, notes, calendarName, alarms, preferredSource }) => handleCreateCalendarEvent(createCalendarToolsContext(), {
|
|
339
|
+
title,
|
|
340
|
+
startDate,
|
|
341
|
+
endDate,
|
|
342
|
+
location,
|
|
343
|
+
notes,
|
|
344
|
+
calendarName,
|
|
345
|
+
alarms,
|
|
346
|
+
preferredSource,
|
|
347
|
+
}));
|
|
348
|
+
// delete_calendar_event - uses extracted handler
|
|
349
|
+
server.tool("delete_calendar_event", "Delete a calendar event from enabled calendar sources by its ID. If source not specified, attempts deletion from all enabled sources.", {
|
|
1154
350
|
eventId: z.string().describe("Event ID (UUID or full ID from list_calendar_events)"),
|
|
1155
|
-
|
|
1156
|
-
.
|
|
351
|
+
source: z
|
|
352
|
+
.enum(['eventkit', 'google'])
|
|
1157
353
|
.optional()
|
|
1158
|
-
.describe("Calendar
|
|
1159
|
-
}, async ({ eventId,
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
content: [
|
|
1163
|
-
{
|
|
1164
|
-
type: "text",
|
|
1165
|
-
text: JSON.stringify({
|
|
1166
|
-
error: true,
|
|
1167
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
1168
|
-
}, null, 2),
|
|
1169
|
-
},
|
|
1170
|
-
],
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
if (!calendarEventDeleterService) {
|
|
1174
|
-
initializeServices(config);
|
|
1175
|
-
}
|
|
1176
|
-
try {
|
|
1177
|
-
// Check platform availability
|
|
1178
|
-
const isAvailable = await calendarEventDeleterService.isEventKitAvailable();
|
|
1179
|
-
if (!isAvailable) {
|
|
1180
|
-
return {
|
|
1181
|
-
content: [
|
|
1182
|
-
{
|
|
1183
|
-
type: "text",
|
|
1184
|
-
text: JSON.stringify({
|
|
1185
|
-
error: true,
|
|
1186
|
-
message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
|
|
1187
|
-
}, null, 2),
|
|
1188
|
-
},
|
|
1189
|
-
],
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
// Delete the event
|
|
1193
|
-
const result = await calendarEventDeleterService.deleteEvent({
|
|
1194
|
-
eventId,
|
|
1195
|
-
calendarName,
|
|
1196
|
-
});
|
|
1197
|
-
if (result.success) {
|
|
1198
|
-
return {
|
|
1199
|
-
content: [
|
|
1200
|
-
{
|
|
1201
|
-
type: "text",
|
|
1202
|
-
text: JSON.stringify({
|
|
1203
|
-
success: true,
|
|
1204
|
-
eventId: result.eventId,
|
|
1205
|
-
title: result.title,
|
|
1206
|
-
calendarName: result.calendarName,
|
|
1207
|
-
message: result.message,
|
|
1208
|
-
}, null, 2),
|
|
1209
|
-
},
|
|
1210
|
-
],
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
// Handle deletion failure
|
|
1214
|
-
return {
|
|
1215
|
-
content: [
|
|
1216
|
-
{
|
|
1217
|
-
type: "text",
|
|
1218
|
-
text: JSON.stringify({
|
|
1219
|
-
success: false,
|
|
1220
|
-
error: result.error,
|
|
1221
|
-
message: result.message,
|
|
1222
|
-
}, null, 2),
|
|
1223
|
-
},
|
|
1224
|
-
],
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
catch (error) {
|
|
1228
|
-
return {
|
|
1229
|
-
content: [
|
|
1230
|
-
{
|
|
1231
|
-
type: "text",
|
|
1232
|
-
text: JSON.stringify({
|
|
1233
|
-
error: true,
|
|
1234
|
-
message: `カレンダーイベント削除に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1235
|
-
}, null, 2),
|
|
1236
|
-
},
|
|
1237
|
-
],
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
/**
|
|
1242
|
-
* delete_calendar_events_batch - Delete multiple calendar events
|
|
1243
|
-
* Requirement: 19.10-19.11
|
|
1244
|
-
*/
|
|
1245
|
-
server.tool("delete_calendar_events_batch", "Delete multiple calendar events by their IDs.", {
|
|
354
|
+
.describe("Calendar source to delete from. If not specified, attempts deletion from all enabled sources."),
|
|
355
|
+
}, async ({ eventId, source }) => handleDeleteCalendarEvent(createCalendarToolsContext(), { eventId, source }));
|
|
356
|
+
// delete_calendar_events_batch - uses extracted handler
|
|
357
|
+
server.tool("delete_calendar_events_batch", "Delete multiple calendar events from enabled calendar sources by their IDs. If source not specified, attempts deletion from all enabled sources.", {
|
|
1246
358
|
eventIds: z.array(z.string()).describe("Array of event IDs to delete"),
|
|
1247
|
-
|
|
1248
|
-
.
|
|
359
|
+
source: z
|
|
360
|
+
.enum(['eventkit', 'google'])
|
|
1249
361
|
.optional()
|
|
1250
|
-
.describe("Calendar
|
|
1251
|
-
}, async ({ eventIds,
|
|
1252
|
-
|
|
1253
|
-
return {
|
|
1254
|
-
content: [
|
|
1255
|
-
{
|
|
1256
|
-
type: "text",
|
|
1257
|
-
text: JSON.stringify({
|
|
1258
|
-
error: true,
|
|
1259
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
1260
|
-
}, null, 2),
|
|
1261
|
-
},
|
|
1262
|
-
],
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
if (!calendarEventDeleterService) {
|
|
1266
|
-
initializeServices(config);
|
|
1267
|
-
}
|
|
1268
|
-
try {
|
|
1269
|
-
// Check platform availability
|
|
1270
|
-
const isAvailable = await calendarEventDeleterService.isEventKitAvailable();
|
|
1271
|
-
if (!isAvailable) {
|
|
1272
|
-
return {
|
|
1273
|
-
content: [
|
|
1274
|
-
{
|
|
1275
|
-
type: "text",
|
|
1276
|
-
text: JSON.stringify({
|
|
1277
|
-
error: true,
|
|
1278
|
-
message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
|
|
1279
|
-
}, null, 2),
|
|
1280
|
-
},
|
|
1281
|
-
],
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
// Delete events in batch
|
|
1285
|
-
const result = await calendarEventDeleterService.deleteEventsBatch({
|
|
1286
|
-
eventIds,
|
|
1287
|
-
calendarName,
|
|
1288
|
-
});
|
|
1289
|
-
return {
|
|
1290
|
-
content: [
|
|
1291
|
-
{
|
|
1292
|
-
type: "text",
|
|
1293
|
-
text: JSON.stringify({
|
|
1294
|
-
success: result.success,
|
|
1295
|
-
totalCount: result.totalCount,
|
|
1296
|
-
successCount: result.successCount,
|
|
1297
|
-
failedCount: result.failedCount,
|
|
1298
|
-
results: result.results.map((r) => ({
|
|
1299
|
-
eventId: r.eventId,
|
|
1300
|
-
success: r.success,
|
|
1301
|
-
title: r.title,
|
|
1302
|
-
calendarName: r.calendarName,
|
|
1303
|
-
error: r.error,
|
|
1304
|
-
})),
|
|
1305
|
-
message: result.message,
|
|
1306
|
-
}, null, 2),
|
|
1307
|
-
},
|
|
1308
|
-
],
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
return {
|
|
1313
|
-
content: [
|
|
1314
|
-
{
|
|
1315
|
-
type: "text",
|
|
1316
|
-
text: JSON.stringify({
|
|
1317
|
-
error: true,
|
|
1318
|
-
message: `カレンダーイベント一括削除に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1319
|
-
}, null, 2),
|
|
1320
|
-
},
|
|
1321
|
-
],
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
});
|
|
1325
|
-
/**
|
|
1326
|
-
* sync_to_notion - Sync a task to Notion
|
|
1327
|
-
* Requirement: 8.1-8.5
|
|
1328
|
-
*/
|
|
362
|
+
.describe("Calendar source to delete from. If not specified, attempts deletion from all enabled sources."),
|
|
363
|
+
}, async ({ eventIds, source }) => handleDeleteCalendarEventsBatch(createCalendarToolsContext(), { eventIds, source }));
|
|
364
|
+
// sync_to_notion - uses extracted handler
|
|
1329
365
|
server.tool("sync_to_notion", "Sync a task to Notion database for long-term tracking.", {
|
|
1330
366
|
taskTitle: z.string().describe("Title of the task"),
|
|
1331
|
-
description: z.string().optional().describe("Task description"),
|
|
1332
|
-
priority: z
|
|
1333
|
-
.enum(["P0", "P1", "P2", "P3"])
|
|
1334
|
-
.optional()
|
|
1335
|
-
.describe("Task priority"),
|
|
1336
|
-
dueDate: z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
1337
|
-
stakeholders: z
|
|
1338
|
-
.array(z.string())
|
|
1339
|
-
.optional()
|
|
1340
|
-
.describe("List of stakeholders"),
|
|
1341
|
-
estimatedMinutes: z
|
|
1342
|
-
.number()
|
|
1343
|
-
.optional()
|
|
1344
|
-
.describe("Estimated duration in minutes"),
|
|
1345
|
-
}, async ({ taskTitle, description, priority, dueDate, stakeholders, estimatedMinutes
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}, null, 2),
|
|
1355
|
-
},
|
|
1356
|
-
],
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
if (!config.integrations.notion.enabled) {
|
|
1360
|
-
return {
|
|
1361
|
-
content: [
|
|
1362
|
-
{
|
|
1363
|
-
type: "text",
|
|
1364
|
-
text: JSON.stringify({
|
|
1365
|
-
error: true,
|
|
1366
|
-
message: "Notion統合が有効になっていません。update_configでNotion設定を更新してください。",
|
|
1367
|
-
}, null, 2),
|
|
1368
|
-
},
|
|
1369
|
-
],
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1372
|
-
if (!notionService) {
|
|
1373
|
-
initializeServices(config);
|
|
1374
|
-
}
|
|
1375
|
-
try {
|
|
1376
|
-
// Check if Notion MCP is available
|
|
1377
|
-
const isAvailable = await notionService.isAvailable();
|
|
1378
|
-
// Build properties for Notion page
|
|
1379
|
-
const properties = notionService.buildNotionProperties({
|
|
1380
|
-
title: taskTitle,
|
|
1381
|
-
priority,
|
|
1382
|
-
deadline: dueDate,
|
|
1383
|
-
stakeholders,
|
|
1384
|
-
estimatedMinutes,
|
|
1385
|
-
description,
|
|
1386
|
-
});
|
|
1387
|
-
if (!isAvailable) {
|
|
1388
|
-
// Generate fallback template for manual copy
|
|
1389
|
-
const fallbackText = notionService.generateFallbackTemplate({
|
|
1390
|
-
title: taskTitle,
|
|
1391
|
-
priority,
|
|
1392
|
-
deadline: dueDate,
|
|
1393
|
-
stakeholders,
|
|
1394
|
-
estimatedMinutes,
|
|
1395
|
-
description,
|
|
1396
|
-
});
|
|
1397
|
-
return {
|
|
1398
|
-
content: [
|
|
1399
|
-
{
|
|
1400
|
-
type: "text",
|
|
1401
|
-
text: JSON.stringify({
|
|
1402
|
-
success: false,
|
|
1403
|
-
method: "fallback",
|
|
1404
|
-
message: "Notion MCP統合が利用できません。以下のテンプレートを手動でNotionにコピーしてください。",
|
|
1405
|
-
fallbackText,
|
|
1406
|
-
task: {
|
|
1407
|
-
taskTitle,
|
|
1408
|
-
priority: priority ?? "P3",
|
|
1409
|
-
dueDate,
|
|
1410
|
-
stakeholders: stakeholders ?? [],
|
|
1411
|
-
estimatedMinutes,
|
|
1412
|
-
},
|
|
1413
|
-
}, null, 2),
|
|
1414
|
-
},
|
|
1415
|
-
],
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
// Create page in Notion via MCP
|
|
1419
|
-
const result = await notionService.createPage({
|
|
1420
|
-
databaseId: config.integrations.notion.databaseId,
|
|
1421
|
-
title: taskTitle,
|
|
1422
|
-
properties,
|
|
1423
|
-
});
|
|
1424
|
-
if (result.success) {
|
|
1425
|
-
return {
|
|
1426
|
-
content: [
|
|
1427
|
-
{
|
|
1428
|
-
type: "text",
|
|
1429
|
-
text: JSON.stringify({
|
|
1430
|
-
success: true,
|
|
1431
|
-
method: "mcp",
|
|
1432
|
-
pageId: result.pageId,
|
|
1433
|
-
pageUrl: result.pageUrl,
|
|
1434
|
-
message: `Notionにタスクを同期しました: ${taskTitle}`,
|
|
1435
|
-
}, null, 2),
|
|
1436
|
-
},
|
|
1437
|
-
],
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
// MCP call failed, provide fallback
|
|
1441
|
-
const fallbackText = notionService.generateFallbackTemplate({
|
|
1442
|
-
title: taskTitle,
|
|
1443
|
-
priority,
|
|
1444
|
-
deadline: dueDate,
|
|
1445
|
-
stakeholders,
|
|
1446
|
-
estimatedMinutes,
|
|
1447
|
-
description,
|
|
1448
|
-
});
|
|
1449
|
-
return {
|
|
1450
|
-
content: [
|
|
1451
|
-
{
|
|
1452
|
-
type: "text",
|
|
1453
|
-
text: JSON.stringify({
|
|
1454
|
-
success: false,
|
|
1455
|
-
method: "fallback",
|
|
1456
|
-
error: result.error,
|
|
1457
|
-
message: "Notion MCP呼び出しに失敗しました。以下のテンプレートを手動でコピーしてください。",
|
|
1458
|
-
fallbackText,
|
|
1459
|
-
}, null, 2),
|
|
1460
|
-
},
|
|
1461
|
-
],
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
catch (error) {
|
|
1465
|
-
return {
|
|
1466
|
-
content: [
|
|
1467
|
-
{
|
|
1468
|
-
type: "text",
|
|
1469
|
-
text: JSON.stringify({
|
|
1470
|
-
error: true,
|
|
1471
|
-
message: `Notion同期に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1472
|
-
}, null, 2),
|
|
1473
|
-
},
|
|
1474
|
-
],
|
|
1475
|
-
};
|
|
1476
|
-
}
|
|
1477
|
-
});
|
|
1478
|
-
/**
|
|
1479
|
-
* update_config - Update configuration
|
|
1480
|
-
* Requirement: 10.1-10.6
|
|
1481
|
-
*/
|
|
367
|
+
description: z.string().optional().describe("Task description"),
|
|
368
|
+
priority: z
|
|
369
|
+
.enum(["P0", "P1", "P2", "P3"])
|
|
370
|
+
.optional()
|
|
371
|
+
.describe("Task priority"),
|
|
372
|
+
dueDate: z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
373
|
+
stakeholders: z
|
|
374
|
+
.array(z.string())
|
|
375
|
+
.optional()
|
|
376
|
+
.describe("List of stakeholders"),
|
|
377
|
+
estimatedMinutes: z
|
|
378
|
+
.number()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("Estimated duration in minutes"),
|
|
381
|
+
}, async ({ taskTitle, description, priority, dueDate, stakeholders, estimatedMinutes }) => handleSyncToNotion(createIntegrationToolsContext(), {
|
|
382
|
+
taskTitle,
|
|
383
|
+
description,
|
|
384
|
+
priority,
|
|
385
|
+
dueDate,
|
|
386
|
+
stakeholders,
|
|
387
|
+
estimatedMinutes,
|
|
388
|
+
}));
|
|
389
|
+
// update_config - uses extracted handler
|
|
1482
390
|
server.tool("update_config", "Update sage configuration settings.", {
|
|
1483
391
|
section: z
|
|
1484
392
|
.enum([
|
|
@@ -1491,81 +399,13 @@ async function createServer() {
|
|
|
1491
399
|
])
|
|
1492
400
|
.describe("Configuration section to update"),
|
|
1493
401
|
updates: z.record(z.unknown()).describe("Key-value pairs to update"),
|
|
1494
|
-
}, async ({ section, updates }) => {
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
{
|
|
1499
|
-
type: "text",
|
|
1500
|
-
text: JSON.stringify({
|
|
1501
|
-
error: true,
|
|
1502
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
1503
|
-
}, null, 2),
|
|
1504
|
-
},
|
|
1505
|
-
],
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
try {
|
|
1509
|
-
// Validate section-specific updates
|
|
1510
|
-
const validationResult = validateConfigUpdate(section, updates);
|
|
1511
|
-
if (!validationResult.valid) {
|
|
1512
|
-
return {
|
|
1513
|
-
content: [
|
|
1514
|
-
{
|
|
1515
|
-
type: "text",
|
|
1516
|
-
text: JSON.stringify({
|
|
1517
|
-
error: true,
|
|
1518
|
-
message: `設定の検証に失敗しました: ${validationResult.error}`,
|
|
1519
|
-
invalidFields: validationResult.invalidFields,
|
|
1520
|
-
}, null, 2),
|
|
1521
|
-
},
|
|
1522
|
-
],
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
// Apply updates to config
|
|
1526
|
-
const updatedConfig = applyConfigUpdates(config, section, updates);
|
|
1527
|
-
// Save the updated config
|
|
1528
|
-
await ConfigLoader.save(updatedConfig);
|
|
1529
|
-
config = updatedConfig;
|
|
1530
|
-
// Re-initialize services if integrations changed
|
|
1531
|
-
if (section === "integrations") {
|
|
1532
|
-
initializeServices(config);
|
|
1533
|
-
}
|
|
1534
|
-
return {
|
|
1535
|
-
content: [
|
|
1536
|
-
{
|
|
1537
|
-
type: "text",
|
|
1538
|
-
text: JSON.stringify({
|
|
1539
|
-
success: true,
|
|
1540
|
-
section,
|
|
1541
|
-
updatedFields: Object.keys(updates),
|
|
1542
|
-
message: `設定を更新しました: ${section}`,
|
|
1543
|
-
}, null, 2),
|
|
1544
|
-
},
|
|
1545
|
-
],
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
catch (error) {
|
|
1549
|
-
return {
|
|
1550
|
-
content: [
|
|
1551
|
-
{
|
|
1552
|
-
type: "text",
|
|
1553
|
-
text: JSON.stringify({
|
|
1554
|
-
error: true,
|
|
1555
|
-
message: `設定の更新に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1556
|
-
}, null, 2),
|
|
1557
|
-
},
|
|
1558
|
-
],
|
|
1559
|
-
};
|
|
1560
|
-
}
|
|
1561
|
-
});
|
|
402
|
+
}, async ({ section, updates }) => handleUpdateConfig(createIntegrationToolsContext(), {
|
|
403
|
+
section,
|
|
404
|
+
updates: updates,
|
|
405
|
+
}));
|
|
1562
406
|
// ============================================
|
|
1563
|
-
// TODO List Management Tools
|
|
407
|
+
// TODO List Management Tools - uses extracted handlers
|
|
1564
408
|
// ============================================
|
|
1565
|
-
/**
|
|
1566
|
-
* list_todos - List all TODO items with optional filtering
|
|
1567
|
-
* Requirement: 12.1, 12.2, 12.3, 12.4, 12.7, 12.8
|
|
1568
|
-
*/
|
|
1569
409
|
server.tool("list_todos", "List TODO items from Apple Reminders and Notion with optional filtering.", {
|
|
1570
410
|
priority: z
|
|
1571
411
|
.array(z.enum(["P0", "P1", "P2", "P3"]))
|
|
@@ -1581,89 +421,14 @@ async function createServer() {
|
|
|
1581
421
|
.describe("Filter by source"),
|
|
1582
422
|
todayOnly: z.boolean().optional().describe("Show only tasks due today"),
|
|
1583
423
|
tags: z.array(z.string()).optional().describe("Filter by tags"),
|
|
1584
|
-
}, async ({ priority, status, source, todayOnly, tags }) => {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
message: "sageが設定されていません。check_setup_statusを実行してください。",
|
|
1593
|
-
}, null, 2),
|
|
1594
|
-
},
|
|
1595
|
-
],
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
if (!todoListManager) {
|
|
1599
|
-
initializeServices(config);
|
|
1600
|
-
}
|
|
1601
|
-
try {
|
|
1602
|
-
let todos;
|
|
1603
|
-
if (todayOnly) {
|
|
1604
|
-
todos = await todoListManager.getTodaysTasks();
|
|
1605
|
-
}
|
|
1606
|
-
else {
|
|
1607
|
-
todos = await todoListManager.listTodos({
|
|
1608
|
-
priority: priority,
|
|
1609
|
-
status,
|
|
1610
|
-
source,
|
|
1611
|
-
tags,
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
// Format todos for display
|
|
1615
|
-
const formattedTodos = todos.map((todo) => ({
|
|
1616
|
-
id: todo.id,
|
|
1617
|
-
title: todo.title,
|
|
1618
|
-
priority: todo.priority,
|
|
1619
|
-
status: todo.status,
|
|
1620
|
-
dueDate: todo.dueDate,
|
|
1621
|
-
source: todo.source,
|
|
1622
|
-
tags: todo.tags,
|
|
1623
|
-
estimatedMinutes: todo.estimatedMinutes,
|
|
1624
|
-
stakeholders: todo.stakeholders,
|
|
1625
|
-
}));
|
|
1626
|
-
return {
|
|
1627
|
-
content: [
|
|
1628
|
-
{
|
|
1629
|
-
type: "text",
|
|
1630
|
-
text: JSON.stringify({
|
|
1631
|
-
success: true,
|
|
1632
|
-
totalCount: todos.length,
|
|
1633
|
-
todos: formattedTodos,
|
|
1634
|
-
message: todos.length > 0
|
|
1635
|
-
? `${todos.length}件のタスクが見つかりました。`
|
|
1636
|
-
: "タスクが見つかりませんでした。",
|
|
1637
|
-
filters: {
|
|
1638
|
-
priority,
|
|
1639
|
-
status,
|
|
1640
|
-
source,
|
|
1641
|
-
todayOnly,
|
|
1642
|
-
tags,
|
|
1643
|
-
},
|
|
1644
|
-
}, null, 2),
|
|
1645
|
-
},
|
|
1646
|
-
],
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
catch (error) {
|
|
1650
|
-
return {
|
|
1651
|
-
content: [
|
|
1652
|
-
{
|
|
1653
|
-
type: "text",
|
|
1654
|
-
text: JSON.stringify({
|
|
1655
|
-
error: true,
|
|
1656
|
-
message: `TODOリストの取得に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1657
|
-
}, null, 2),
|
|
1658
|
-
},
|
|
1659
|
-
],
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
/**
|
|
1664
|
-
* update_task_status - Update the status of a task
|
|
1665
|
-
* Requirement: 12.5, 12.6
|
|
1666
|
-
*/
|
|
424
|
+
}, async ({ priority, status, source, todayOnly, tags }) => handleListTodos(createReminderTodoContext(), {
|
|
425
|
+
priority,
|
|
426
|
+
status,
|
|
427
|
+
source,
|
|
428
|
+
todayOnly,
|
|
429
|
+
tags,
|
|
430
|
+
}));
|
|
431
|
+
// update_task_status - uses extracted handler
|
|
1667
432
|
server.tool("update_task_status", "Update the status of a task in Apple Reminders or Notion.", {
|
|
1668
433
|
taskId: z.string().describe("ID of the task to update"),
|
|
1669
434
|
status: z
|
|
@@ -1676,7 +441,35 @@ async function createServer() {
|
|
|
1676
441
|
.boolean()
|
|
1677
442
|
.optional()
|
|
1678
443
|
.describe("Whether to sync the status across all sources"),
|
|
1679
|
-
}, async ({ taskId, status, source, syncAcrossSources }) => {
|
|
444
|
+
}, async ({ taskId, status, source, syncAcrossSources }) => handleUpdateTaskStatus(createTaskToolsContext(), {
|
|
445
|
+
taskId,
|
|
446
|
+
status,
|
|
447
|
+
source,
|
|
448
|
+
syncAcrossSources,
|
|
449
|
+
}));
|
|
450
|
+
// sync_tasks - uses extracted handler
|
|
451
|
+
server.tool("sync_tasks", "Synchronize tasks between Apple Reminders and Notion, detecting and resolving conflicts.", {}, async () => handleSyncTasks(createTaskToolsContext()));
|
|
452
|
+
// detect_duplicates - uses extracted handler
|
|
453
|
+
server.tool("detect_duplicates", "Detect duplicate tasks between Apple Reminders and Notion.", {
|
|
454
|
+
autoMerge: z
|
|
455
|
+
.boolean()
|
|
456
|
+
.optional()
|
|
457
|
+
.describe("Whether to automatically merge high-confidence duplicates"),
|
|
458
|
+
}, async ({ autoMerge }) => handleDetectDuplicates(createTaskToolsContext(), { autoMerge }));
|
|
459
|
+
// list_calendar_sources - uses extracted handler
|
|
460
|
+
server.tool("list_calendar_sources", "List available and enabled calendar sources (EventKit, Google Calendar) with their health status. Shows which sources can be used and their current state.", {}, async () => handleListCalendarSources(createCalendarToolsContext()));
|
|
461
|
+
/**
|
|
462
|
+
* set_calendar_source - Enable or disable a calendar source
|
|
463
|
+
* Requirement: 9, 11, Task 33
|
|
464
|
+
*/
|
|
465
|
+
server.tool("set_calendar_source", "Enable or disable a calendar source (EventKit or Google Calendar). When enabling Google Calendar for the first time, this will initiate the OAuth flow. Returns authorization URL if OAuth is required.", {
|
|
466
|
+
source: z
|
|
467
|
+
.enum(['eventkit', 'google'])
|
|
468
|
+
.describe("Calendar source to configure: 'eventkit' (macOS only) or 'google' (all platforms)"),
|
|
469
|
+
enabled: z
|
|
470
|
+
.boolean()
|
|
471
|
+
.describe("Whether to enable (true) or disable (false) the source"),
|
|
472
|
+
}, async ({ source, enabled }) => {
|
|
1680
473
|
if (!config) {
|
|
1681
474
|
return {
|
|
1682
475
|
content: [
|
|
@@ -1690,68 +483,152 @@ async function createServer() {
|
|
|
1690
483
|
],
|
|
1691
484
|
};
|
|
1692
485
|
}
|
|
1693
|
-
if (!
|
|
486
|
+
if (!calendarSourceManager) {
|
|
1694
487
|
initializeServices(config);
|
|
1695
488
|
}
|
|
1696
489
|
try {
|
|
1697
|
-
//
|
|
1698
|
-
const
|
|
1699
|
-
if (!
|
|
490
|
+
// Check if source is available on this platform
|
|
491
|
+
const availableSources = await calendarSourceManager.detectAvailableSources();
|
|
492
|
+
if (source === 'eventkit' && !availableSources.eventkit) {
|
|
1700
493
|
return {
|
|
1701
494
|
content: [
|
|
1702
495
|
{
|
|
1703
496
|
type: "text",
|
|
1704
497
|
text: JSON.stringify({
|
|
1705
498
|
success: false,
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
499
|
+
message: "EventKitはこのプラットフォームでは利用できません。EventKitはmacOSでのみ利用可能です。",
|
|
500
|
+
}, null, 2),
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (enabled) {
|
|
506
|
+
// Enable the source
|
|
507
|
+
await calendarSourceManager.enableSource(source);
|
|
508
|
+
// If enabling Google Calendar for the first time, check if OAuth is needed
|
|
509
|
+
if (source === 'google' && googleCalendarService) {
|
|
510
|
+
try {
|
|
511
|
+
// Check if tokens already exist
|
|
512
|
+
const { GoogleOAuthHandler } = await import('./oauth/google-oauth-handler.js');
|
|
513
|
+
const oauthConfig = {
|
|
514
|
+
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
|
515
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
|
516
|
+
redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth/callback',
|
|
517
|
+
};
|
|
518
|
+
if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
|
|
519
|
+
return {
|
|
520
|
+
content: [
|
|
521
|
+
{
|
|
522
|
+
type: "text",
|
|
523
|
+
text: JSON.stringify({
|
|
524
|
+
success: false,
|
|
525
|
+
message: "Google Calendar OAuth設定が見つかりません。環境変数GOOGLE_CLIENT_IDとGOOGLE_CLIENT_SECRETを設定してください。",
|
|
526
|
+
requiredEnvVars: [
|
|
527
|
+
'GOOGLE_CLIENT_ID',
|
|
528
|
+
'GOOGLE_CLIENT_SECRET',
|
|
529
|
+
'GOOGLE_REDIRECT_URI (optional, defaults to http://localhost:3000/oauth/callback)',
|
|
530
|
+
],
|
|
531
|
+
}, null, 2),
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
const oauthHandler = new GoogleOAuthHandler(oauthConfig);
|
|
537
|
+
// Try to get existing tokens
|
|
538
|
+
const existingTokens = await oauthHandler.getTokens();
|
|
539
|
+
if (!existingTokens) {
|
|
540
|
+
// Need to initiate OAuth flow
|
|
541
|
+
const authUrl = await oauthHandler.getAuthorizationUrl();
|
|
542
|
+
// Save config before OAuth flow
|
|
543
|
+
await ConfigLoader.save(config);
|
|
544
|
+
return {
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
type: "text",
|
|
548
|
+
text: JSON.stringify({
|
|
549
|
+
success: true,
|
|
550
|
+
source,
|
|
551
|
+
enabled: true,
|
|
552
|
+
oauthRequired: true,
|
|
553
|
+
authorizationUrl: authUrl,
|
|
554
|
+
message: `Google Calendarを有効化しました。OAuth認証が必要です。以下のURLにアクセスして認証を完了してください: ${authUrl}`,
|
|
555
|
+
instructions: [
|
|
556
|
+
'1. 上記のURLをブラウザで開く',
|
|
557
|
+
'2. Googleアカウントでログイン',
|
|
558
|
+
'3. sage アプリケーションにカレンダーへのアクセスを許可',
|
|
559
|
+
'4. リダイレクトされたURLから認証コードを取得',
|
|
560
|
+
'5. 認証コードを使用してトークンを取得(別途実装予定)',
|
|
561
|
+
],
|
|
562
|
+
}, null, 2),
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
// OAuth check failed, but source is enabled in config
|
|
570
|
+
await ConfigLoader.save(config);
|
|
571
|
+
return {
|
|
572
|
+
content: [
|
|
573
|
+
{
|
|
574
|
+
type: "text",
|
|
575
|
+
text: JSON.stringify({
|
|
576
|
+
success: true,
|
|
577
|
+
source,
|
|
578
|
+
enabled: true,
|
|
579
|
+
warning: `Google Calendarを有効化しましたが、OAuth設定の確認に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
580
|
+
message: "設定は保存されましたが、OAuth認証が必要な場合があります。",
|
|
581
|
+
}, null, 2),
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// Save the updated config
|
|
588
|
+
await ConfigLoader.save(config);
|
|
589
|
+
return {
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: JSON.stringify({
|
|
594
|
+
success: true,
|
|
595
|
+
source,
|
|
596
|
+
enabled: true,
|
|
597
|
+
message: `${source === 'eventkit' ? 'EventKit' : 'Google Calendar'}を有効化しました。`,
|
|
1709
598
|
}, null, 2),
|
|
1710
599
|
},
|
|
1711
600
|
],
|
|
1712
601
|
};
|
|
1713
602
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
603
|
+
else {
|
|
604
|
+
// Disable the source
|
|
605
|
+
await calendarSourceManager.disableSource(source);
|
|
606
|
+
// Save the updated config
|
|
607
|
+
await ConfigLoader.save(config);
|
|
608
|
+
return {
|
|
609
|
+
content: [
|
|
610
|
+
{
|
|
611
|
+
type: "text",
|
|
612
|
+
text: JSON.stringify({
|
|
613
|
+
success: true,
|
|
614
|
+
source,
|
|
615
|
+
enabled: false,
|
|
616
|
+
message: `${source === 'eventkit' ? 'EventKit' : 'Google Calendar'}を無効化しました。`,
|
|
617
|
+
}, null, 2),
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
};
|
|
1718
621
|
}
|
|
1719
|
-
return {
|
|
1720
|
-
content: [
|
|
1721
|
-
{
|
|
1722
|
-
type: "text",
|
|
1723
|
-
text: JSON.stringify({
|
|
1724
|
-
success: true,
|
|
1725
|
-
taskId,
|
|
1726
|
-
newStatus: status,
|
|
1727
|
-
updatedFields: result.updatedFields,
|
|
1728
|
-
syncedSources: result.syncedSources,
|
|
1729
|
-
syncResult: syncAcrossSources ? syncResult : undefined,
|
|
1730
|
-
message: `タスクのステータスを「${status}」に更新しました。`,
|
|
1731
|
-
}, null, 2),
|
|
1732
|
-
},
|
|
1733
|
-
],
|
|
1734
|
-
};
|
|
1735
622
|
}
|
|
1736
623
|
catch (error) {
|
|
1737
|
-
return
|
|
1738
|
-
content: [
|
|
1739
|
-
{
|
|
1740
|
-
type: "text",
|
|
1741
|
-
text: JSON.stringify({
|
|
1742
|
-
error: true,
|
|
1743
|
-
message: `タスクステータスの更新に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1744
|
-
}, null, 2),
|
|
1745
|
-
},
|
|
1746
|
-
],
|
|
1747
|
-
};
|
|
624
|
+
return createErrorFromCatch('カレンダーソース設定に失敗しました', error);
|
|
1748
625
|
}
|
|
1749
626
|
});
|
|
1750
627
|
/**
|
|
1751
|
-
*
|
|
1752
|
-
* Requirement:
|
|
628
|
+
* sync_calendar_sources - Sync events between EventKit and Google Calendar
|
|
629
|
+
* Requirement: 8, Task 34
|
|
1753
630
|
*/
|
|
1754
|
-
server.tool("
|
|
631
|
+
server.tool("sync_calendar_sources", "Synchronize calendar events between EventKit and Google Calendar. Both sources must be enabled for sync to work. Returns the number of events added, updated, and deleted.", {}, async () => {
|
|
1755
632
|
if (!config) {
|
|
1756
633
|
return {
|
|
1757
634
|
content: [
|
|
@@ -1765,54 +642,73 @@ async function createServer() {
|
|
|
1765
642
|
],
|
|
1766
643
|
};
|
|
1767
644
|
}
|
|
1768
|
-
if (!
|
|
645
|
+
if (!calendarSourceManager) {
|
|
1769
646
|
initializeServices(config);
|
|
1770
647
|
}
|
|
1771
648
|
try {
|
|
1772
|
-
|
|
649
|
+
// Check if both sources are enabled
|
|
650
|
+
const enabledSources = calendarSourceManager.getEnabledSources();
|
|
651
|
+
if (enabledSources.length < 2) {
|
|
652
|
+
return {
|
|
653
|
+
content: [
|
|
654
|
+
{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: JSON.stringify({
|
|
657
|
+
success: false,
|
|
658
|
+
message: "同期を実行するには、EventKitとGoogle Calendarの両方を有効化する必要があります。現在有効なソース: " +
|
|
659
|
+
enabledSources.join(", "),
|
|
660
|
+
enabledSources,
|
|
661
|
+
}, null, 2),
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
// Execute sync
|
|
667
|
+
const result = await calendarSourceManager.syncCalendars();
|
|
668
|
+
if (result.success) {
|
|
669
|
+
return {
|
|
670
|
+
content: [
|
|
671
|
+
{
|
|
672
|
+
type: "text",
|
|
673
|
+
text: JSON.stringify({
|
|
674
|
+
success: true,
|
|
675
|
+
eventsAdded: result.eventsAdded,
|
|
676
|
+
eventsUpdated: result.eventsUpdated,
|
|
677
|
+
eventsDeleted: result.eventsDeleted,
|
|
678
|
+
conflicts: result.conflicts,
|
|
679
|
+
errors: result.errors,
|
|
680
|
+
message: `カレンダー同期が完了しました。追加: ${result.eventsAdded}件、更新: ${result.eventsUpdated}件、削除: ${result.eventsDeleted}件`,
|
|
681
|
+
}, null, 2),
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
};
|
|
685
|
+
}
|
|
1773
686
|
return {
|
|
1774
687
|
content: [
|
|
1775
688
|
{
|
|
1776
689
|
type: "text",
|
|
1777
690
|
text: JSON.stringify({
|
|
1778
|
-
success:
|
|
1779
|
-
|
|
1780
|
-
|
|
691
|
+
success: false,
|
|
692
|
+
eventsAdded: result.eventsAdded,
|
|
693
|
+
eventsUpdated: result.eventsUpdated,
|
|
694
|
+
eventsDeleted: result.eventsDeleted,
|
|
1781
695
|
conflicts: result.conflicts,
|
|
1782
696
|
errors: result.errors,
|
|
1783
|
-
|
|
1784
|
-
message: result.conflicts.length > 0
|
|
1785
|
-
? `${result.syncedTasks}件のタスクを同期しました。${result.conflicts.length}件の競合が検出されました。`
|
|
1786
|
-
: `${result.syncedTasks}件のタスクを同期しました。`,
|
|
697
|
+
message: "カレンダー同期中にエラーが発生しました。",
|
|
1787
698
|
}, null, 2),
|
|
1788
699
|
},
|
|
1789
700
|
],
|
|
1790
701
|
};
|
|
1791
702
|
}
|
|
1792
703
|
catch (error) {
|
|
1793
|
-
return
|
|
1794
|
-
content: [
|
|
1795
|
-
{
|
|
1796
|
-
type: "text",
|
|
1797
|
-
text: JSON.stringify({
|
|
1798
|
-
error: true,
|
|
1799
|
-
message: `タスク同期に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1800
|
-
}, null, 2),
|
|
1801
|
-
},
|
|
1802
|
-
],
|
|
1803
|
-
};
|
|
704
|
+
return createErrorFromCatch('カレンダー同期に失敗しました', error);
|
|
1804
705
|
}
|
|
1805
706
|
});
|
|
1806
707
|
/**
|
|
1807
|
-
*
|
|
1808
|
-
* Requirement:
|
|
708
|
+
* get_calendar_sync_status - Check sync status between calendar sources
|
|
709
|
+
* Requirement: 8, Task 35
|
|
1809
710
|
*/
|
|
1810
|
-
server.tool("
|
|
1811
|
-
autoMerge: z
|
|
1812
|
-
.boolean()
|
|
1813
|
-
.optional()
|
|
1814
|
-
.describe("Whether to automatically merge high-confidence duplicates"),
|
|
1815
|
-
}, async ({ autoMerge }) => {
|
|
711
|
+
server.tool("get_calendar_sync_status", "Check the synchronization status between EventKit and Google Calendar. Returns last sync time, next sync time, and source availability.", {}, async () => {
|
|
1816
712
|
if (!config) {
|
|
1817
713
|
return {
|
|
1818
714
|
content: [
|
|
@@ -1826,72 +722,42 @@ async function createServer() {
|
|
|
1826
722
|
],
|
|
1827
723
|
};
|
|
1828
724
|
}
|
|
1829
|
-
if (!
|
|
725
|
+
if (!calendarSourceManager) {
|
|
1830
726
|
initializeServices(config);
|
|
1831
727
|
}
|
|
1832
728
|
try {
|
|
1833
|
-
const
|
|
1834
|
-
// Format duplicates for display
|
|
1835
|
-
const formattedDuplicates = duplicates.map((d) => ({
|
|
1836
|
-
tasks: d.tasks.map((t) => ({
|
|
1837
|
-
id: t.id,
|
|
1838
|
-
title: t.title,
|
|
1839
|
-
source: t.source,
|
|
1840
|
-
status: t.status,
|
|
1841
|
-
priority: t.priority,
|
|
1842
|
-
})),
|
|
1843
|
-
similarity: Math.round(d.similarity * 100),
|
|
1844
|
-
confidence: d.confidence,
|
|
1845
|
-
suggestedMerge: {
|
|
1846
|
-
title: d.suggestedMerge.title,
|
|
1847
|
-
priority: d.suggestedMerge.priority,
|
|
1848
|
-
status: d.suggestedMerge.status,
|
|
1849
|
-
tags: d.suggestedMerge.tags,
|
|
1850
|
-
},
|
|
1851
|
-
}));
|
|
1852
|
-
// Auto-merge high-confidence duplicates if requested
|
|
1853
|
-
let mergeResults;
|
|
1854
|
-
if (autoMerge) {
|
|
1855
|
-
const highConfidenceDuplicates = duplicates.filter((d) => d.confidence === "high");
|
|
1856
|
-
if (highConfidenceDuplicates.length > 0) {
|
|
1857
|
-
mergeResults = await taskSynchronizer.mergeDuplicates(highConfidenceDuplicates);
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
729
|
+
const status = await calendarSourceManager.getSyncStatus();
|
|
1860
730
|
return {
|
|
1861
731
|
content: [
|
|
1862
732
|
{
|
|
1863
733
|
type: "text",
|
|
1864
734
|
text: JSON.stringify({
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
735
|
+
isEnabled: status.isEnabled,
|
|
736
|
+
lastSyncTime: status.lastSyncTime || "未実行",
|
|
737
|
+
nextSyncTime: status.nextSyncTime || "N/A",
|
|
738
|
+
sources: {
|
|
739
|
+
eventkit: {
|
|
740
|
+
available: status.sources.eventkit.available,
|
|
741
|
+
lastError: status.sources.eventkit.lastError,
|
|
742
|
+
},
|
|
743
|
+
google: {
|
|
744
|
+
available: status.sources.google.available,
|
|
745
|
+
lastError: status.sources.google.lastError,
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
message: status.isEnabled
|
|
749
|
+
? "カレンダー同期が有効です。"
|
|
750
|
+
: "カレンダー同期を有効にするには、EventKitとGoogle Calendarの両方を有効化してください。",
|
|
1872
751
|
}, null, 2),
|
|
1873
752
|
},
|
|
1874
753
|
],
|
|
1875
754
|
};
|
|
1876
755
|
}
|
|
1877
756
|
catch (error) {
|
|
1878
|
-
return
|
|
1879
|
-
content: [
|
|
1880
|
-
{
|
|
1881
|
-
type: "text",
|
|
1882
|
-
text: JSON.stringify({
|
|
1883
|
-
error: true,
|
|
1884
|
-
message: `重複検出に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1885
|
-
}, null, 2),
|
|
1886
|
-
},
|
|
1887
|
-
],
|
|
1888
|
-
};
|
|
757
|
+
return createErrorFromCatch('同期状態の取得に失敗しました', error);
|
|
1889
758
|
}
|
|
1890
759
|
});
|
|
1891
|
-
|
|
1892
|
-
* get_working_cadence - Get user's working rhythm information
|
|
1893
|
-
* Requirement: 32.1-32.10
|
|
1894
|
-
*/
|
|
760
|
+
// get_working_cadence - uses extracted handler
|
|
1895
761
|
server.tool("get_working_cadence", "Get user's working rhythm including deep work days, meeting-heavy days, and scheduling recommendations.", {
|
|
1896
762
|
dayOfWeek: z
|
|
1897
763
|
.enum([
|
|
@@ -1909,62 +775,7 @@ async function createServer() {
|
|
|
1909
775
|
.string()
|
|
1910
776
|
.optional()
|
|
1911
777
|
.describe("Get info for a specific date in ISO 8601 format (e.g., 2025-01-15)"),
|
|
1912
|
-
}, async ({ dayOfWeek, date }) => {
|
|
1913
|
-
// Initialize service if not already done
|
|
1914
|
-
if (!workingCadenceService) {
|
|
1915
|
-
workingCadenceService = new WorkingCadenceService();
|
|
1916
|
-
}
|
|
1917
|
-
try {
|
|
1918
|
-
const result = await workingCadenceService.getWorkingCadence({
|
|
1919
|
-
dayOfWeek,
|
|
1920
|
-
date,
|
|
1921
|
-
});
|
|
1922
|
-
if (!result.success) {
|
|
1923
|
-
return {
|
|
1924
|
-
content: [
|
|
1925
|
-
{
|
|
1926
|
-
type: "text",
|
|
1927
|
-
text: JSON.stringify({
|
|
1928
|
-
error: true,
|
|
1929
|
-
message: result.error || "勤務リズム情報の取得に失敗しました。",
|
|
1930
|
-
}, null, 2),
|
|
1931
|
-
},
|
|
1932
|
-
],
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
return {
|
|
1936
|
-
content: [
|
|
1937
|
-
{
|
|
1938
|
-
type: "text",
|
|
1939
|
-
text: JSON.stringify({
|
|
1940
|
-
success: true,
|
|
1941
|
-
user: result.user,
|
|
1942
|
-
workingHours: result.workingHours,
|
|
1943
|
-
weeklyPattern: result.weeklyPattern,
|
|
1944
|
-
deepWorkBlocks: result.deepWorkBlocks,
|
|
1945
|
-
weeklyReview: result.weeklyReview,
|
|
1946
|
-
specificDay: result.specificDay,
|
|
1947
|
-
recommendations: result.recommendations,
|
|
1948
|
-
summary: result.summary,
|
|
1949
|
-
}, null, 2),
|
|
1950
|
-
},
|
|
1951
|
-
],
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
catch (error) {
|
|
1955
|
-
return {
|
|
1956
|
-
content: [
|
|
1957
|
-
{
|
|
1958
|
-
type: "text",
|
|
1959
|
-
text: JSON.stringify({
|
|
1960
|
-
error: true,
|
|
1961
|
-
message: `勤務リズム情報の取得に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1962
|
-
}, null, 2),
|
|
1963
|
-
},
|
|
1964
|
-
],
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
|
-
});
|
|
778
|
+
}, async ({ dayOfWeek, date }) => handleGetWorkingCadence(createCalendarToolsContext(), { dayOfWeek, date }));
|
|
1968
779
|
return server;
|
|
1969
780
|
}
|
|
1970
781
|
/**
|