@shin1ohno/sage 0.8.4 → 0.8.7

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.
Files changed (110) hide show
  1. package/dist/cli/mcp-handler.d.ts.map +1 -1
  2. package/dist/cli/mcp-handler.js +202 -1575
  3. package/dist/cli/mcp-handler.js.map +1 -1
  4. package/dist/config/update-validation.d.ts +52 -0
  5. package/dist/config/update-validation.d.ts.map +1 -0
  6. package/dist/config/update-validation.js +133 -0
  7. package/dist/config/update-validation.js.map +1 -0
  8. package/dist/index.js +163 -1815
  9. package/dist/index.js.map +1 -1
  10. package/dist/integrations/calendar-event-creator.d.ts +2 -3
  11. package/dist/integrations/calendar-event-creator.d.ts.map +1 -1
  12. package/dist/integrations/calendar-event-creator.js +3 -4
  13. package/dist/integrations/calendar-event-creator.js.map +1 -1
  14. package/dist/integrations/calendar-event-deleter.d.ts +2 -3
  15. package/dist/integrations/calendar-event-deleter.d.ts.map +1 -1
  16. package/dist/integrations/calendar-event-deleter.js +3 -4
  17. package/dist/integrations/calendar-event-deleter.js.map +1 -1
  18. package/dist/integrations/calendar-event-response.d.ts +4 -17
  19. package/dist/integrations/calendar-event-response.d.ts.map +1 -1
  20. package/dist/integrations/calendar-event-response.js +3 -4
  21. package/dist/integrations/calendar-event-response.js.map +1 -1
  22. package/dist/integrations/notion-mcp.d.ts +28 -3
  23. package/dist/integrations/notion-mcp.d.ts.map +1 -1
  24. package/dist/integrations/notion-mcp.js +21 -5
  25. package/dist/integrations/notion-mcp.js.map +1 -1
  26. package/dist/integrations/reminder-manager.d.ts.map +1 -1
  27. package/dist/integrations/reminder-manager.js +2 -0
  28. package/dist/integrations/reminder-manager.js.map +1 -1
  29. package/dist/services/container.d.ts +56 -0
  30. package/dist/services/container.d.ts.map +1 -0
  31. package/dist/services/container.js +76 -0
  32. package/dist/services/container.js.map +1 -0
  33. package/dist/tools/calendar/handlers.d.ts +186 -0
  34. package/dist/tools/calendar/handlers.d.ts.map +1 -0
  35. package/dist/tools/calendar/handlers.js +525 -0
  36. package/dist/tools/calendar/handlers.js.map +1 -0
  37. package/dist/tools/calendar/index.d.ts +11 -0
  38. package/dist/tools/calendar/index.d.ts.map +1 -0
  39. package/dist/tools/calendar/index.js +10 -0
  40. package/dist/tools/calendar/index.js.map +1 -0
  41. package/dist/tools/index.d.ts +23 -0
  42. package/dist/tools/index.d.ts.map +1 -0
  43. package/dist/tools/index.js +24 -0
  44. package/dist/tools/index.js.map +1 -0
  45. package/dist/tools/integrations/handlers.d.ts +57 -0
  46. package/dist/tools/integrations/handlers.d.ts.map +1 -0
  47. package/dist/tools/integrations/handlers.js +159 -0
  48. package/dist/tools/integrations/handlers.js.map +1 -0
  49. package/dist/tools/integrations/index.d.ts +11 -0
  50. package/dist/tools/integrations/index.d.ts.map +1 -0
  51. package/dist/tools/integrations/index.js +10 -0
  52. package/dist/tools/integrations/index.js.map +1 -0
  53. package/dist/tools/registry.d.ts +8 -0
  54. package/dist/tools/registry.d.ts.map +1 -0
  55. package/dist/tools/registry.js +10 -0
  56. package/dist/tools/registry.js.map +1 -0
  57. package/dist/tools/reminders/handlers.d.ts +61 -0
  58. package/dist/tools/reminders/handlers.d.ts.map +1 -0
  59. package/dist/tools/reminders/handlers.js +148 -0
  60. package/dist/tools/reminders/handlers.js.map +1 -0
  61. package/dist/tools/reminders/index.d.ts +11 -0
  62. package/dist/tools/reminders/index.d.ts.map +1 -0
  63. package/dist/tools/reminders/index.js +10 -0
  64. package/dist/tools/reminders/index.js.map +1 -0
  65. package/dist/tools/setup/handlers.d.ts +81 -0
  66. package/dist/tools/setup/handlers.d.ts.map +1 -0
  67. package/dist/tools/setup/handlers.js +172 -0
  68. package/dist/tools/setup/handlers.js.map +1 -0
  69. package/dist/tools/setup/index.d.ts +11 -0
  70. package/dist/tools/setup/index.d.ts.map +1 -0
  71. package/dist/tools/setup/index.js +10 -0
  72. package/dist/tools/setup/index.js.map +1 -0
  73. package/dist/tools/tasks/handlers.d.ts +95 -0
  74. package/dist/tools/tasks/handlers.d.ts.map +1 -0
  75. package/dist/tools/tasks/handlers.js +197 -0
  76. package/dist/tools/tasks/handlers.js.map +1 -0
  77. package/dist/tools/tasks/index.d.ts +11 -0
  78. package/dist/tools/tasks/index.d.ts.map +1 -0
  79. package/dist/tools/tasks/index.js +10 -0
  80. package/dist/tools/tasks/index.js.map +1 -0
  81. package/dist/tools/types.d.ts +54 -0
  82. package/dist/tools/types.d.ts.map +1 -0
  83. package/dist/tools/types.js +9 -0
  84. package/dist/tools/types.js.map +1 -0
  85. package/dist/types/calendar.d.ts +41 -0
  86. package/dist/types/calendar.d.ts.map +1 -0
  87. package/dist/types/calendar.js +18 -0
  88. package/dist/types/calendar.js.map +1 -0
  89. package/dist/utils/estimation.d.ts +34 -0
  90. package/dist/utils/estimation.d.ts.map +1 -1
  91. package/dist/utils/estimation.js +38 -1
  92. package/dist/utils/estimation.js.map +1 -1
  93. package/dist/utils/mcp-response.d.ts +89 -0
  94. package/dist/utils/mcp-response.d.ts.map +1 -0
  95. package/dist/utils/mcp-response.js +103 -0
  96. package/dist/utils/mcp-response.js.map +1 -0
  97. package/dist/utils/task-splitter.d.ts +65 -4
  98. package/dist/utils/task-splitter.d.ts.map +1 -1
  99. package/dist/utils/task-splitter.js +69 -5
  100. package/dist/utils/task-splitter.js.map +1 -1
  101. package/dist/version.js +1 -1
  102. package/package.json +1 -1
  103. package/dist/cli/http-server.d.ts +0 -74
  104. package/dist/cli/http-server.d.ts.map +0 -1
  105. package/dist/cli/http-server.js +0 -407
  106. package/dist/cli/http-server.js.map +0 -1
  107. package/dist/remote/remote-mcp-server.d.ts +0 -244
  108. package/dist/remote/remote-mcp-server.d.ts.map +0 -1
  109. package/dist/remote/remote-mcp-server.js +0 -507
  110. package/dist/remote/remote-mcp-server.js.map +0 -1
@@ -6,17 +6,22 @@
6
6
  */
7
7
  import { VERSION, SERVER_NAME } from '../version.js';
8
8
  import { ConfigLoader } from '../config/loader.js';
9
- import { SetupWizard } from '../setup/wizard.js';
10
- import { TaskAnalyzer } from '../tools/analyze-tasks.js';
11
9
  import { ReminderManager } from '../integrations/reminder-manager.js';
12
10
  import { CalendarService } from '../integrations/calendar-service.js';
13
11
  import { NotionMCPService } from '../integrations/notion-mcp.js';
14
12
  import { TodoListManager } from '../integrations/todo-list-manager.js';
15
13
  import { TaskSynchronizer } from '../integrations/task-synchronizer.js';
16
14
  import { CalendarEventResponseService } from '../integrations/calendar-event-response.js';
17
- import { CalendarEventCreatorService } from '../integrations/calendar-event-creator.js';
18
- import { CalendarEventDeleterService } from '../integrations/calendar-event-deleter.js';
19
15
  import { WorkingCadenceService } from '../services/working-cadence.js';
16
+ import { CalendarSourceManager } from '../integrations/calendar-source-manager.js';
17
+ import { GoogleCalendarService } from '../integrations/google-calendar-service.js';
18
+ import { GoogleOAuthHandler } from '../oauth/google-oauth-handler.js';
19
+ // Extracted tool handlers
20
+ import { handleCheckSetupStatus, handleStartSetupWizard, handleAnswerWizardQuestion, handleSaveConfig, } from '../tools/setup/index.js';
21
+ import { handleAnalyzeTasks, handleUpdateTaskStatus, handleSyncTasks, handleDetectDuplicates, } from '../tools/tasks/index.js';
22
+ import { handleSetReminder, handleListTodos, } from '../tools/reminders/index.js';
23
+ import { handleSyncToNotion, handleUpdateConfig, } from '../tools/integrations/index.js';
24
+ import { handleListCalendarSources, handleListCalendarEvents, handleFindAvailableSlots, handleCreateCalendarEvent, handleRespondToCalendarEvent, handleRespondToCalendarEventsBatch, handleDeleteCalendarEvent, handleDeleteCalendarEventsBatch, } from '../tools/calendar/handlers.js';
20
25
  // Protocol version
21
26
  const PROTOCOL_VERSION = '2024-11-05';
22
27
  /**
@@ -31,9 +36,9 @@ class MCPHandlerImpl {
31
36
  todoListManager = null;
32
37
  taskSynchronizer = null;
33
38
  calendarEventResponseService = null;
34
- calendarEventCreatorService = null;
35
- calendarEventDeleterService = null;
36
39
  workingCadenceService = null;
40
+ calendarSourceManager = null;
41
+ googleCalendarService = null;
37
42
  initialized = false;
38
43
  tools = new Map();
39
44
  constructor() {
@@ -68,14 +73,92 @@ class MCPHandlerImpl {
68
73
  notionDatabaseId: userConfig.integrations.notion.databaseId,
69
74
  });
70
75
  this.calendarService = new CalendarService();
76
+ // Initialize Google Calendar service with OAuth handler
77
+ const oauthConfig = {
78
+ clientId: process.env.GOOGLE_CLIENT_ID || '',
79
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
80
+ redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth/callback',
81
+ };
82
+ const oauthHandler = new GoogleOAuthHandler(oauthConfig);
83
+ this.googleCalendarService = new GoogleCalendarService(oauthHandler);
84
+ this.calendarSourceManager = new CalendarSourceManager({
85
+ calendarService: this.calendarService,
86
+ googleCalendarService: this.googleCalendarService,
87
+ config: userConfig,
88
+ });
71
89
  this.notionService = new NotionMCPService();
72
90
  this.todoListManager = new TodoListManager();
73
91
  this.taskSynchronizer = new TaskSynchronizer();
74
92
  this.calendarEventResponseService = new CalendarEventResponseService();
75
- this.calendarEventCreatorService = new CalendarEventCreatorService();
76
- this.calendarEventDeleterService = new CalendarEventDeleterService();
77
93
  this.workingCadenceService = new WorkingCadenceService();
78
94
  }
95
+ /**
96
+ * Create SetupContext for setup tool handlers
97
+ */
98
+ createSetupContext() {
99
+ return {
100
+ getConfig: () => this.config,
101
+ setConfig: (config) => {
102
+ this.config = config;
103
+ },
104
+ getWizardSession: () => this.wizardSession,
105
+ setWizardSession: (session) => {
106
+ this.wizardSession = session;
107
+ },
108
+ initializeServices: (config) => this.initializeServices(config),
109
+ };
110
+ }
111
+ /**
112
+ * Create TaskToolsContext for task tool handlers
113
+ */
114
+ createTaskToolsContext() {
115
+ return {
116
+ getConfig: () => this.config,
117
+ getTodoListManager: () => this.todoListManager,
118
+ getTaskSynchronizer: () => this.taskSynchronizer,
119
+ initializeServices: (config) => this.initializeServices(config),
120
+ };
121
+ }
122
+ /**
123
+ * Create ReminderTodoContext for reminder/todo tool handlers
124
+ */
125
+ createReminderTodoContext() {
126
+ return {
127
+ getConfig: () => this.config,
128
+ getReminderManager: () => this.reminderManager,
129
+ getTodoListManager: () => this.todoListManager,
130
+ initializeServices: (config) => this.initializeServices(config),
131
+ };
132
+ }
133
+ /**
134
+ * Create IntegrationToolsContext for integration tool handlers
135
+ */
136
+ createIntegrationToolsContext() {
137
+ return {
138
+ getConfig: () => this.config,
139
+ setConfig: (config) => {
140
+ this.config = config;
141
+ },
142
+ getNotionService: () => this.notionService,
143
+ initializeServices: (config) => this.initializeServices(config),
144
+ };
145
+ }
146
+ /**
147
+ * Create CalendarToolsContext for calendar tool handlers
148
+ */
149
+ createCalendarToolsContext() {
150
+ return {
151
+ getConfig: () => this.config,
152
+ getCalendarSourceManager: () => this.calendarSourceManager,
153
+ getCalendarEventResponseService: () => this.calendarEventResponseService,
154
+ getGoogleCalendarService: () => this.googleCalendarService,
155
+ getWorkingCadenceService: () => this.workingCadenceService,
156
+ setWorkingCadenceService: (service) => {
157
+ this.workingCadenceService = service;
158
+ },
159
+ initializeServices: (config) => this.initializeServices(config),
160
+ };
161
+ }
79
162
  /**
80
163
  * Handle an MCP request
81
164
  */
@@ -209,7 +292,7 @@ class MCPHandlerImpl {
209
292
  * Register all tools
210
293
  */
211
294
  registerTools() {
212
- // check_setup_status
295
+ // check_setup_status - uses extracted handler
213
296
  this.registerTool({
214
297
  name: 'check_setup_status',
215
298
  description: 'Check if sage has been configured. Returns setup status and guidance.',
@@ -217,61 +300,8 @@ class MCPHandlerImpl {
217
300
  type: 'object',
218
301
  properties: {},
219
302
  },
220
- }, async () => {
221
- const exists = await ConfigLoader.exists();
222
- const isValid = this.config !== null;
223
- if (!exists) {
224
- return {
225
- content: [
226
- {
227
- type: 'text',
228
- text: JSON.stringify({
229
- setupComplete: false,
230
- configExists: false,
231
- message: 'sageの初期設定が必要です。start_setup_wizardを実行してセットアップを開始してください。',
232
- nextAction: 'start_setup_wizard',
233
- }, null, 2),
234
- },
235
- ],
236
- };
237
- }
238
- if (!isValid) {
239
- return {
240
- content: [
241
- {
242
- type: 'text',
243
- text: JSON.stringify({
244
- setupComplete: false,
245
- configExists: true,
246
- message: '設定ファイルが見つかりましたが、読み込みに失敗しました。設定を再作成してください。',
247
- nextAction: 'start_setup_wizard',
248
- }, null, 2),
249
- },
250
- ],
251
- };
252
- }
253
- return {
254
- content: [
255
- {
256
- type: 'text',
257
- text: JSON.stringify({
258
- setupComplete: true,
259
- configExists: true,
260
- userName: this.config?.user.name,
261
- message: 'sageは設定済みです。タスク分析やリマインド設定を開始できます。',
262
- availableTools: [
263
- 'analyze_tasks',
264
- 'set_reminder',
265
- 'find_available_slots',
266
- 'sync_to_notion',
267
- 'update_config',
268
- ],
269
- }, null, 2),
270
- },
271
- ],
272
- };
273
- });
274
- // start_setup_wizard
303
+ }, async () => handleCheckSetupStatus(this.createSetupContext()));
304
+ // start_setup_wizard - uses extracted handler
275
305
  this.registerTool({
276
306
  name: 'start_setup_wizard',
277
307
  description: 'Start the interactive setup wizard for sage. Returns the first question.',
@@ -285,34 +315,10 @@ class MCPHandlerImpl {
285
315
  },
286
316
  },
287
317
  },
288
- }, async (args) => {
289
- const mode = args.mode || 'full';
290
- this.wizardSession = SetupWizard.createSession(mode);
291
- const question = SetupWizard.getCurrentQuestion(this.wizardSession);
292
- return {
293
- content: [
294
- {
295
- type: 'text',
296
- text: JSON.stringify({
297
- sessionId: this.wizardSession.sessionId,
298
- currentStep: this.wizardSession.currentStep,
299
- totalSteps: this.wizardSession.totalSteps,
300
- progress: Math.round((this.wizardSession.currentStep / this.wizardSession.totalSteps) * 100),
301
- question: {
302
- id: question.id,
303
- text: question.text,
304
- type: question.type,
305
- options: question.options,
306
- defaultValue: question.defaultValue,
307
- helpText: question.helpText,
308
- },
309
- message: 'セットアップを開始します。以下の質問に回答してください。',
310
- }, null, 2),
311
- },
312
- ],
313
- };
314
- });
315
- // answer_wizard_question
318
+ }, async (args) => handleStartSetupWizard(this.createSetupContext(), {
319
+ mode: args.mode,
320
+ }));
321
+ // answer_wizard_question - uses extracted handler
316
322
  this.registerTool({
317
323
  name: 'answer_wizard_question',
318
324
  description: 'Answer a question in the setup wizard and get the next question.',
@@ -330,77 +336,11 @@ class MCPHandlerImpl {
330
336
  },
331
337
  required: ['questionId', 'answer'],
332
338
  },
333
- }, async (args) => {
334
- const questionId = args.questionId;
335
- const answer = args.answer;
336
- if (!this.wizardSession) {
337
- return {
338
- content: [
339
- {
340
- type: 'text',
341
- text: JSON.stringify({
342
- error: true,
343
- message: 'セットアップセッションが見つかりません。start_setup_wizardを実行してください。',
344
- }, null, 2),
345
- },
346
- ],
347
- };
348
- }
349
- const result = SetupWizard.answerQuestion(this.wizardSession, questionId, answer);
350
- if (!result.success) {
351
- return {
352
- content: [
353
- {
354
- type: 'text',
355
- text: JSON.stringify({
356
- error: true,
357
- message: result.error,
358
- currentQuestion: result.currentQuestion,
359
- }, null, 2),
360
- },
361
- ],
362
- };
363
- }
364
- if (result.isComplete) {
365
- return {
366
- content: [
367
- {
368
- type: 'text',
369
- text: JSON.stringify({
370
- isComplete: true,
371
- sessionId: this.wizardSession.sessionId,
372
- answers: this.wizardSession.answers,
373
- message: 'すべての質問に回答しました。save_configを実行して設定を保存してください。',
374
- nextAction: 'save_config',
375
- }, null, 2),
376
- },
377
- ],
378
- };
379
- }
380
- const nextQuestion = SetupWizard.getCurrentQuestion(this.wizardSession);
381
- return {
382
- content: [
383
- {
384
- type: 'text',
385
- text: JSON.stringify({
386
- success: true,
387
- currentStep: this.wizardSession.currentStep,
388
- totalSteps: this.wizardSession.totalSteps,
389
- progress: Math.round((this.wizardSession.currentStep / this.wizardSession.totalSteps) * 100),
390
- question: {
391
- id: nextQuestion.id,
392
- text: nextQuestion.text,
393
- type: nextQuestion.type,
394
- options: nextQuestion.options,
395
- defaultValue: nextQuestion.defaultValue,
396
- helpText: nextQuestion.helpText,
397
- },
398
- }, null, 2),
399
- },
400
- ],
401
- };
402
- });
403
- // save_config
339
+ }, async (args) => handleAnswerWizardQuestion(this.createSetupContext(), {
340
+ questionId: args.questionId,
341
+ answer: args.answer,
342
+ }));
343
+ // save_config - uses extracted handler
404
344
  this.registerTool({
405
345
  name: 'save_config',
406
346
  description: 'Save the configuration after completing the setup wizard.',
@@ -414,74 +354,10 @@ class MCPHandlerImpl {
414
354
  },
415
355
  required: ['confirm'],
416
356
  },
417
- }, async (args) => {
418
- const confirm = args.confirm;
419
- if (!confirm) {
420
- return {
421
- content: [
422
- {
423
- type: 'text',
424
- text: JSON.stringify({
425
- saved: false,
426
- message: '設定の保存がキャンセルされました。',
427
- }, null, 2),
428
- },
429
- ],
430
- };
431
- }
432
- if (!this.wizardSession) {
433
- return {
434
- content: [
435
- {
436
- type: 'text',
437
- text: JSON.stringify({
438
- error: true,
439
- message: 'セットアップセッションが見つかりません。start_setup_wizardを実行してください。',
440
- }, null, 2),
441
- },
442
- ],
443
- };
444
- }
445
- try {
446
- const newConfig = SetupWizard.buildConfig(this.wizardSession);
447
- await ConfigLoader.save(newConfig);
448
- this.config = newConfig;
449
- this.wizardSession = null;
450
- return {
451
- content: [
452
- {
453
- type: 'text',
454
- text: JSON.stringify({
455
- saved: true,
456
- configPath: ConfigLoader.getConfigPath(),
457
- userName: newConfig.user.name,
458
- message: `設定を保存しました。${newConfig.user.name}さん、sageをご利用いただきありがとうございます!`,
459
- availableTools: [
460
- 'analyze_tasks',
461
- 'set_reminder',
462
- 'find_available_slots',
463
- 'sync_to_notion',
464
- ],
465
- }, null, 2),
466
- },
467
- ],
468
- };
469
- }
470
- catch (error) {
471
- return {
472
- content: [
473
- {
474
- type: 'text',
475
- text: JSON.stringify({
476
- error: true,
477
- message: `設定の保存に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
478
- }, null, 2),
479
- },
480
- ],
481
- };
482
- }
483
- });
484
- // analyze_tasks
357
+ }, async (args) => handleSaveConfig(this.createSetupContext(), {
358
+ confirm: args.confirm,
359
+ }));
360
+ // analyze_tasks - uses extracted handler
485
361
  this.registerTool({
486
362
  name: 'analyze_tasks',
487
363
  description: 'Analyze tasks to determine priority, estimate time, and identify stakeholders.',
@@ -507,61 +383,10 @@ class MCPHandlerImpl {
507
383
  },
508
384
  required: ['tasks'],
509
385
  },
510
- }, async (args) => {
511
- if (!this.config) {
512
- return {
513
- content: [
514
- {
515
- type: 'text',
516
- text: JSON.stringify({
517
- error: true,
518
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
519
- }, null, 2),
520
- },
521
- ],
522
- };
523
- }
524
- try {
525
- const tasks = args.tasks;
526
- const result = await TaskAnalyzer.analyzeTasks(tasks, this.config);
527
- return {
528
- content: [
529
- {
530
- type: 'text',
531
- text: JSON.stringify({
532
- success: true,
533
- summary: result.summary,
534
- tasks: result.analyzedTasks.map((t) => ({
535
- title: t.original.title,
536
- description: t.original.description,
537
- deadline: t.original.deadline,
538
- priority: t.priority,
539
- estimatedMinutes: t.estimatedMinutes,
540
- stakeholders: t.stakeholders,
541
- tags: t.tags,
542
- reasoning: t.reasoning,
543
- suggestedReminders: t.suggestedReminders,
544
- })),
545
- }, null, 2),
546
- },
547
- ],
548
- };
549
- }
550
- catch (error) {
551
- return {
552
- content: [
553
- {
554
- type: 'text',
555
- text: JSON.stringify({
556
- error: true,
557
- message: `タスク分析に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
558
- }, null, 2),
559
- },
560
- ],
561
- };
562
- }
563
- });
564
- // set_reminder
386
+ }, async (args) => handleAnalyzeTasks(this.createTaskToolsContext(), {
387
+ tasks: args.tasks,
388
+ }));
389
+ // set_reminder - uses extracted handler
565
390
  this.registerTool({
566
391
  name: 'set_reminder',
567
392
  description: 'Set a reminder for a task in Apple Reminders or Notion.',
@@ -600,100 +425,15 @@ class MCPHandlerImpl {
600
425
  },
601
426
  required: ['taskTitle'],
602
427
  },
603
- }, async (args) => {
604
- if (!this.config) {
605
- return {
606
- content: [
607
- {
608
- type: 'text',
609
- text: JSON.stringify({
610
- error: true,
611
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
612
- }, null, 2),
613
- },
614
- ],
615
- };
616
- }
617
- if (!this.reminderManager) {
618
- this.initializeServices(this.config);
619
- }
620
- try {
621
- const result = await this.reminderManager.setReminder({
622
- taskTitle: args.taskTitle,
623
- targetDate: args.dueDate,
624
- reminderType: args.reminderType,
625
- list: args.list ?? this.config.integrations.appleReminders.defaultList,
626
- priority: args.priority,
627
- notes: args.notes,
628
- });
629
- if (result.success) {
630
- if (result.delegateToNotion && result.notionRequest) {
631
- return {
632
- content: [
633
- {
634
- type: 'text',
635
- text: JSON.stringify({
636
- success: true,
637
- destination: 'notion_mcp',
638
- method: 'delegate',
639
- delegateToNotion: true,
640
- notionRequest: result.notionRequest,
641
- message: `Notionへの追加はClaude Codeが直接notion-create-pagesツールを使用してください。`,
642
- }, null, 2),
643
- },
644
- ],
645
- };
646
- }
647
- return {
648
- content: [
649
- {
650
- type: 'text',
651
- text: JSON.stringify({
652
- success: true,
653
- destination: result.destination,
654
- method: result.method,
655
- reminderId: result.reminderId,
656
- reminderUrl: result.reminderUrl ?? result.pageUrl,
657
- message: result.destination === 'apple_reminders'
658
- ? `Apple Remindersにリマインダーを作成しました: ${args.taskTitle}`
659
- : `Notionにタスクを作成しました: ${args.taskTitle}`,
660
- }, null, 2),
661
- },
662
- ],
663
- };
664
- }
665
- return {
666
- content: [
667
- {
668
- type: 'text',
669
- text: JSON.stringify({
670
- success: false,
671
- destination: result.destination,
672
- error: result.error,
673
- fallbackText: result.fallbackText,
674
- message: result.fallbackText
675
- ? '自動作成に失敗しました。以下のテキストを手動でコピーしてください。'
676
- : `リマインダー作成に失敗しました: ${result.error}`,
677
- }, null, 2),
678
- },
679
- ],
680
- };
681
- }
682
- catch (error) {
683
- return {
684
- content: [
685
- {
686
- type: 'text',
687
- text: JSON.stringify({
688
- error: true,
689
- message: `リマインダー設定に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
690
- }, null, 2),
691
- },
692
- ],
693
- };
694
- }
695
- });
696
- // find_available_slots
428
+ }, async (args) => handleSetReminder(this.createReminderTodoContext(), {
429
+ taskTitle: args.taskTitle,
430
+ dueDate: args.dueDate,
431
+ reminderType: args.reminderType,
432
+ list: args.list,
433
+ priority: args.priority,
434
+ notes: args.notes,
435
+ }));
436
+ // find_available_slots - uses extracted handler
697
437
  this.registerTool({
698
438
  name: 'find_available_slots',
699
439
  description: 'Find available time slots in the calendar for scheduling tasks.',
@@ -719,110 +459,13 @@ class MCPHandlerImpl {
719
459
  },
720
460
  required: ['durationMinutes'],
721
461
  },
722
- }, async (args) => {
723
- if (!this.config) {
724
- return {
725
- content: [
726
- {
727
- type: 'text',
728
- text: JSON.stringify({
729
- error: true,
730
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
731
- }, null, 2),
732
- },
733
- ],
734
- };
735
- }
736
- if (!this.calendarService) {
737
- this.initializeServices(this.config);
738
- }
739
- try {
740
- const durationMinutes = args.durationMinutes;
741
- const startDate = args.startDate;
742
- const endDate = args.endDate;
743
- const preferDeepWork = args.preferDeepWork;
744
- const platformInfo = await this.calendarService.detectPlatform();
745
- const isAvailable = await this.calendarService.isAvailable();
746
- if (!isAvailable) {
747
- const searchStart = startDate ?? new Date().toISOString().split('T')[0];
748
- const searchEnd = endDate ??
749
- new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
750
- const manualPrompt = this.calendarService.generateManualInputPrompt(searchStart, searchEnd);
751
- return {
752
- content: [
753
- {
754
- type: 'text',
755
- text: JSON.stringify({
756
- success: false,
757
- platform: platformInfo.platform,
758
- method: platformInfo.recommendedMethod,
759
- message: 'カレンダー統合がこのプラットフォームで利用できません。手動で予定を入力してください。',
760
- manualPrompt,
761
- }, null, 2),
762
- },
763
- ],
764
- };
765
- }
766
- const searchStart = startDate ?? new Date().toISOString().split('T')[0];
767
- const searchEnd = endDate ??
768
- new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
769
- const events = await this.calendarService.fetchEvents(searchStart, searchEnd);
770
- const workingHours = {
771
- start: this.config.calendar.workingHours.start,
772
- end: this.config.calendar.workingHours.end,
773
- };
774
- const slots = this.calendarService.findAvailableSlotsFromEvents(events, durationMinutes, workingHours, searchStart);
775
- const suitabilityConfig = {
776
- deepWorkDays: this.config.calendar.deepWorkDays,
777
- meetingHeavyDays: this.config.calendar.meetingHeavyDays,
778
- };
779
- const scoredSlots = slots.map((slot) => this.calendarService.calculateSuitability(slot, suitabilityConfig));
780
- const filteredSlots = preferDeepWork
781
- ? scoredSlots.filter((s) => s.dayType === 'deep-work')
782
- : scoredSlots;
783
- const suitabilityOrder = { excellent: 0, good: 1, acceptable: 2 };
784
- filteredSlots.sort((a, b) => suitabilityOrder[a.suitability] - suitabilityOrder[b.suitability]);
785
- return {
786
- content: [
787
- {
788
- type: 'text',
789
- text: JSON.stringify({
790
- success: true,
791
- platform: platformInfo.platform,
792
- method: platformInfo.recommendedMethod,
793
- searchRange: { start: searchStart, end: searchEnd },
794
- eventsFound: events.length,
795
- slots: filteredSlots.slice(0, 10).map((slot) => ({
796
- start: slot.start,
797
- end: slot.end,
798
- durationMinutes: slot.durationMinutes,
799
- suitability: slot.suitability,
800
- dayType: slot.dayType,
801
- reason: slot.reason,
802
- })),
803
- message: filteredSlots.length > 0
804
- ? `${filteredSlots.length}件の空き時間が見つかりました。`
805
- : '指定した条件に合う空き時間が見つかりませんでした。',
806
- }, null, 2),
807
- },
808
- ],
809
- };
810
- }
811
- catch (error) {
812
- return {
813
- content: [
814
- {
815
- type: 'text',
816
- text: JSON.stringify({
817
- error: true,
818
- message: `カレンダー検索に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
819
- }, null, 2),
820
- },
821
- ],
822
- };
823
- }
824
- });
825
- // list_calendar_events
462
+ }, async (args) => handleFindAvailableSlots(this.createCalendarToolsContext(), {
463
+ durationMinutes: args.durationMinutes,
464
+ startDate: args.startDate,
465
+ endDate: args.endDate,
466
+ preferDeepWork: args.preferDeepWork,
467
+ }));
468
+ // list_calendar_events - uses extracted handler
826
469
  this.registerTool({
827
470
  name: 'list_calendar_events',
828
471
  description: 'List calendar events for a specified period. Returns events with details including calendar name and location.',
@@ -837,98 +480,19 @@ class MCPHandlerImpl {
837
480
  type: 'string',
838
481
  description: 'End date in ISO 8601 format (e.g., 2025-01-20)',
839
482
  },
840
- calendarName: {
483
+ calendarId: {
841
484
  type: 'string',
842
- description: 'Optional: filter events by calendar name',
485
+ description: 'Optional: filter events by calendar ID',
843
486
  },
844
487
  },
845
488
  required: ['startDate', 'endDate'],
846
489
  },
847
- }, async (args) => {
848
- if (!this.config) {
849
- return {
850
- content: [
851
- {
852
- type: 'text',
853
- text: JSON.stringify({
854
- error: true,
855
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
856
- }, null, 2),
857
- },
858
- ],
859
- };
860
- }
861
- if (!this.calendarService) {
862
- this.initializeServices(this.config);
863
- }
864
- try {
865
- const startDate = args.startDate;
866
- const endDate = args.endDate;
867
- const calendarName = args.calendarName;
868
- const platformInfo = await this.calendarService.detectPlatform();
869
- const isAvailable = await this.calendarService.isAvailable();
870
- if (!isAvailable) {
871
- return {
872
- content: [
873
- {
874
- type: 'text',
875
- text: JSON.stringify({
876
- success: false,
877
- platform: platformInfo.platform,
878
- method: platformInfo.recommendedMethod,
879
- message: 'カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。',
880
- }, null, 2),
881
- },
882
- ],
883
- };
884
- }
885
- const result = await this.calendarService.listEvents({
886
- startDate,
887
- endDate,
888
- calendarName,
889
- });
890
- return {
891
- content: [
892
- {
893
- type: 'text',
894
- text: JSON.stringify({
895
- success: true,
896
- platform: platformInfo.platform,
897
- method: platformInfo.recommendedMethod,
898
- events: result.events.map((event) => ({
899
- id: event.id,
900
- title: event.title,
901
- start: event.start,
902
- end: event.end,
903
- isAllDay: event.isAllDay,
904
- calendar: event.calendar,
905
- location: event.location,
906
- })),
907
- period: result.period,
908
- totalEvents: result.totalEvents,
909
- message: result.totalEvents > 0
910
- ? `${result.totalEvents}件のイベントが見つかりました。`
911
- : '指定した期間にイベントが見つかりませんでした。',
912
- }, null, 2),
913
- },
914
- ],
915
- };
916
- }
917
- catch (error) {
918
- return {
919
- content: [
920
- {
921
- type: 'text',
922
- text: JSON.stringify({
923
- error: true,
924
- message: `カレンダーイベントの取得に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
925
- }, null, 2),
926
- },
927
- ],
928
- };
929
- }
930
- });
931
- // sync_to_notion
490
+ }, async (args) => handleListCalendarEvents(this.createCalendarToolsContext(), {
491
+ startDate: args.startDate,
492
+ endDate: args.endDate,
493
+ calendarId: args.calendarId,
494
+ }));
495
+ // sync_to_notion - uses extracted handler
932
496
  this.registerTool({
933
497
  name: 'sync_to_notion',
934
498
  description: 'Sync a task to Notion database for long-term tracking.',
@@ -955,141 +519,15 @@ class MCPHandlerImpl {
955
519
  },
956
520
  required: ['taskTitle'],
957
521
  },
958
- }, async (args) => {
959
- if (!this.config) {
960
- return {
961
- content: [
962
- {
963
- type: 'text',
964
- text: JSON.stringify({
965
- error: true,
966
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
967
- }, null, 2),
968
- },
969
- ],
970
- };
971
- }
972
- if (!this.config.integrations.notion.enabled) {
973
- return {
974
- content: [
975
- {
976
- type: 'text',
977
- text: JSON.stringify({
978
- error: true,
979
- message: 'Notion統合が有効になっていません。update_configでNotion設定を更新してください。',
980
- }, null, 2),
981
- },
982
- ],
983
- };
984
- }
985
- if (!this.notionService) {
986
- this.initializeServices(this.config);
987
- }
988
- try {
989
- const taskTitle = args.taskTitle;
990
- const description = args.description;
991
- const priority = args.priority;
992
- const dueDate = args.dueDate;
993
- const stakeholders = args.stakeholders;
994
- const estimatedMinutes = args.estimatedMinutes;
995
- const isAvailable = await this.notionService.isAvailable();
996
- const properties = this.notionService.buildNotionProperties({
997
- title: taskTitle,
998
- priority,
999
- deadline: dueDate,
1000
- stakeholders,
1001
- estimatedMinutes,
1002
- description,
1003
- });
1004
- if (!isAvailable) {
1005
- const fallbackText = this.notionService.generateFallbackTemplate({
1006
- title: taskTitle,
1007
- priority,
1008
- deadline: dueDate,
1009
- stakeholders,
1010
- estimatedMinutes,
1011
- description,
1012
- });
1013
- return {
1014
- content: [
1015
- {
1016
- type: 'text',
1017
- text: JSON.stringify({
1018
- success: false,
1019
- method: 'fallback',
1020
- message: 'Notion MCP統合が利用できません。以下のテンプレートを手動でNotionにコピーしてください。',
1021
- fallbackText,
1022
- task: {
1023
- taskTitle,
1024
- priority: priority ?? 'P3',
1025
- dueDate,
1026
- stakeholders: stakeholders ?? [],
1027
- estimatedMinutes,
1028
- },
1029
- }, null, 2),
1030
- },
1031
- ],
1032
- };
1033
- }
1034
- const result = await this.notionService.createPage({
1035
- databaseId: this.config.integrations.notion.databaseId,
1036
- title: taskTitle,
1037
- properties,
1038
- });
1039
- if (result.success) {
1040
- return {
1041
- content: [
1042
- {
1043
- type: 'text',
1044
- text: JSON.stringify({
1045
- success: true,
1046
- method: 'mcp',
1047
- pageId: result.pageId,
1048
- pageUrl: result.pageUrl,
1049
- message: `Notionにタスクを同期しました: ${taskTitle}`,
1050
- }, null, 2),
1051
- },
1052
- ],
1053
- };
1054
- }
1055
- const fallbackText = this.notionService.generateFallbackTemplate({
1056
- title: taskTitle,
1057
- priority,
1058
- deadline: dueDate,
1059
- stakeholders,
1060
- estimatedMinutes,
1061
- description,
1062
- });
1063
- return {
1064
- content: [
1065
- {
1066
- type: 'text',
1067
- text: JSON.stringify({
1068
- success: false,
1069
- method: 'fallback',
1070
- error: result.error,
1071
- message: 'Notion MCP呼び出しに失敗しました。以下のテンプレートを手動でコピーしてください。',
1072
- fallbackText,
1073
- }, null, 2),
1074
- },
1075
- ],
1076
- };
1077
- }
1078
- catch (error) {
1079
- return {
1080
- content: [
1081
- {
1082
- type: 'text',
1083
- text: JSON.stringify({
1084
- error: true,
1085
- message: `Notion同期に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1086
- }, null, 2),
1087
- },
1088
- ],
1089
- };
1090
- }
1091
- });
1092
- // update_config
522
+ }, async (args) => handleSyncToNotion(this.createIntegrationToolsContext(), {
523
+ taskTitle: args.taskTitle,
524
+ description: args.description,
525
+ priority: args.priority,
526
+ dueDate: args.dueDate,
527
+ stakeholders: args.stakeholders,
528
+ estimatedMinutes: args.estimatedMinutes,
529
+ }));
530
+ // update_config - uses extracted handler
1093
531
  this.registerTool({
1094
532
  name: 'update_config',
1095
533
  description: 'Update sage configuration settings.',
@@ -1115,73 +553,11 @@ class MCPHandlerImpl {
1115
553
  },
1116
554
  required: ['section', 'updates'],
1117
555
  },
1118
- }, async (args) => {
1119
- if (!this.config) {
1120
- return {
1121
- content: [
1122
- {
1123
- type: 'text',
1124
- text: JSON.stringify({
1125
- error: true,
1126
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1127
- }, null, 2),
1128
- },
1129
- ],
1130
- };
1131
- }
1132
- try {
1133
- const section = args.section;
1134
- const updates = args.updates;
1135
- const validationResult = this.validateConfigUpdate(section, updates);
1136
- if (!validationResult.valid) {
1137
- return {
1138
- content: [
1139
- {
1140
- type: 'text',
1141
- text: JSON.stringify({
1142
- error: true,
1143
- message: `設定の検証に失敗しました: ${validationResult.error}`,
1144
- invalidFields: validationResult.invalidFields,
1145
- }, null, 2),
1146
- },
1147
- ],
1148
- };
1149
- }
1150
- const updatedConfig = this.applyConfigUpdates(this.config, section, updates);
1151
- await ConfigLoader.save(updatedConfig);
1152
- this.config = updatedConfig;
1153
- if (section === 'integrations') {
1154
- this.initializeServices(this.config);
1155
- }
1156
- return {
1157
- content: [
1158
- {
1159
- type: 'text',
1160
- text: JSON.stringify({
1161
- success: true,
1162
- section,
1163
- updatedFields: Object.keys(updates),
1164
- message: `設定を更新しました: ${section}`,
1165
- }, null, 2),
1166
- },
1167
- ],
1168
- };
1169
- }
1170
- catch (error) {
1171
- return {
1172
- content: [
1173
- {
1174
- type: 'text',
1175
- text: JSON.stringify({
1176
- error: true,
1177
- message: `設定の更新に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1178
- }, null, 2),
1179
- },
1180
- ],
1181
- };
1182
- }
1183
- });
1184
- // list_todos
556
+ }, async (args) => handleUpdateConfig(this.createIntegrationToolsContext(), {
557
+ section: args.section,
558
+ updates: args.updates,
559
+ }));
560
+ // list_todos - uses extracted handler
1185
561
  this.registerTool({
1186
562
  name: 'list_todos',
1187
563
  description: 'List TODO items from Apple Reminders and Notion with optional filtering.',
@@ -1214,90 +590,14 @@ class MCPHandlerImpl {
1214
590
  },
1215
591
  },
1216
592
  },
1217
- }, async (args) => {
1218
- if (!this.config) {
1219
- return {
1220
- content: [
1221
- {
1222
- type: 'text',
1223
- text: JSON.stringify({
1224
- error: true,
1225
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1226
- }, null, 2),
1227
- },
1228
- ],
1229
- };
1230
- }
1231
- if (!this.todoListManager) {
1232
- this.initializeServices(this.config);
1233
- }
1234
- try {
1235
- const priority = args.priority;
1236
- const status = args.status;
1237
- const source = args.source;
1238
- const todayOnly = args.todayOnly;
1239
- const tags = args.tags;
1240
- let todos;
1241
- if (todayOnly) {
1242
- todos = await this.todoListManager.getTodaysTasks();
1243
- }
1244
- else {
1245
- todos = await this.todoListManager.listTodos({
1246
- priority,
1247
- status,
1248
- source,
1249
- tags,
1250
- });
1251
- }
1252
- const formattedTodos = todos.map((todo) => ({
1253
- id: todo.id,
1254
- title: todo.title,
1255
- priority: todo.priority,
1256
- status: todo.status,
1257
- dueDate: todo.dueDate,
1258
- source: todo.source,
1259
- tags: todo.tags,
1260
- estimatedMinutes: todo.estimatedMinutes,
1261
- stakeholders: todo.stakeholders,
1262
- }));
1263
- return {
1264
- content: [
1265
- {
1266
- type: 'text',
1267
- text: JSON.stringify({
1268
- success: true,
1269
- totalCount: todos.length,
1270
- todos: formattedTodos,
1271
- message: todos.length > 0
1272
- ? `${todos.length}件のタスクが見つかりました。`
1273
- : 'タスクが見つかりませんでした。',
1274
- filters: {
1275
- priority,
1276
- status,
1277
- source,
1278
- todayOnly,
1279
- tags,
1280
- },
1281
- }, null, 2),
1282
- },
1283
- ],
1284
- };
1285
- }
1286
- catch (error) {
1287
- return {
1288
- content: [
1289
- {
1290
- type: 'text',
1291
- text: JSON.stringify({
1292
- error: true,
1293
- message: `TODOリストの取得に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1294
- }, null, 2),
1295
- },
1296
- ],
1297
- };
1298
- }
1299
- });
1300
- // update_task_status
593
+ }, async (args) => handleListTodos(this.createReminderTodoContext(), {
594
+ priority: args.priority,
595
+ status: args.status,
596
+ source: args.source,
597
+ todayOnly: args.todayOnly,
598
+ tags: args.tags,
599
+ }));
600
+ // update_task_status - uses extracted handler
1301
601
  this.registerTool({
1302
602
  name: 'update_task_status',
1303
603
  description: 'Update the status of a task in Apple Reminders or Notion.',
@@ -1322,80 +622,13 @@ class MCPHandlerImpl {
1322
622
  },
1323
623
  required: ['taskId', 'status', 'source'],
1324
624
  },
1325
- }, async (args) => {
1326
- if (!this.config) {
1327
- return {
1328
- content: [
1329
- {
1330
- type: 'text',
1331
- text: JSON.stringify({
1332
- error: true,
1333
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1334
- }, null, 2),
1335
- },
1336
- ],
1337
- };
1338
- }
1339
- if (!this.todoListManager) {
1340
- this.initializeServices(this.config);
1341
- }
1342
- try {
1343
- const taskId = args.taskId;
1344
- const status = args.status;
1345
- const source = args.source;
1346
- const syncAcrossSources = args.syncAcrossSources;
1347
- const result = await this.todoListManager.updateTaskStatus(taskId, status, source);
1348
- if (!result.success) {
1349
- return {
1350
- content: [
1351
- {
1352
- type: 'text',
1353
- text: JSON.stringify({
1354
- success: false,
1355
- taskId,
1356
- error: result.error,
1357
- message: `タスクステータスの更新に失敗しました: ${result.error}`,
1358
- }, null, 2),
1359
- },
1360
- ],
1361
- };
1362
- }
1363
- let syncResult;
1364
- if (syncAcrossSources) {
1365
- syncResult = await this.todoListManager.syncTaskAcrossSources(taskId);
1366
- }
1367
- return {
1368
- content: [
1369
- {
1370
- type: 'text',
1371
- text: JSON.stringify({
1372
- success: true,
1373
- taskId,
1374
- newStatus: status,
1375
- updatedFields: result.updatedFields,
1376
- syncedSources: result.syncedSources,
1377
- syncResult: syncAcrossSources ? syncResult : undefined,
1378
- message: `タスクのステータスを「${status}」に更新しました。`,
1379
- }, null, 2),
1380
- },
1381
- ],
1382
- };
1383
- }
1384
- catch (error) {
1385
- return {
1386
- content: [
1387
- {
1388
- type: 'text',
1389
- text: JSON.stringify({
1390
- error: true,
1391
- message: `タスクステータスの更新に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1392
- }, null, 2),
1393
- },
1394
- ],
1395
- };
1396
- }
1397
- });
1398
- // sync_tasks
625
+ }, async (args) => handleUpdateTaskStatus(this.createTaskToolsContext(), {
626
+ taskId: args.taskId,
627
+ status: args.status,
628
+ source: args.source,
629
+ syncAcrossSources: args.syncAcrossSources,
630
+ }));
631
+ // sync_tasks - uses extracted handler
1399
632
  this.registerTool({
1400
633
  name: 'sync_tasks',
1401
634
  description: 'Synchronize tasks between Apple Reminders and Notion, detecting and resolving conflicts.',
@@ -1403,59 +636,8 @@ class MCPHandlerImpl {
1403
636
  type: 'object',
1404
637
  properties: {},
1405
638
  },
1406
- }, async () => {
1407
- if (!this.config) {
1408
- return {
1409
- content: [
1410
- {
1411
- type: 'text',
1412
- text: JSON.stringify({
1413
- error: true,
1414
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1415
- }, null, 2),
1416
- },
1417
- ],
1418
- };
1419
- }
1420
- if (!this.taskSynchronizer) {
1421
- this.initializeServices(this.config);
1422
- }
1423
- try {
1424
- const result = await this.taskSynchronizer.syncAllTasks();
1425
- return {
1426
- content: [
1427
- {
1428
- type: 'text',
1429
- text: JSON.stringify({
1430
- success: true,
1431
- totalTasks: result.totalTasks,
1432
- syncedTasks: result.syncedTasks,
1433
- conflicts: result.conflicts,
1434
- errors: result.errors,
1435
- durationMs: result.duration,
1436
- message: result.conflicts.length > 0
1437
- ? `${result.syncedTasks}件のタスクを同期しました。${result.conflicts.length}件の競合が検出されました。`
1438
- : `${result.syncedTasks}件のタスクを同期しました。`,
1439
- }, null, 2),
1440
- },
1441
- ],
1442
- };
1443
- }
1444
- catch (error) {
1445
- return {
1446
- content: [
1447
- {
1448
- type: 'text',
1449
- text: JSON.stringify({
1450
- error: true,
1451
- message: `タスク同期に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1452
- }, null, 2),
1453
- },
1454
- ],
1455
- };
1456
- }
1457
- });
1458
- // detect_duplicates
639
+ }, async () => handleSyncTasks(this.createTaskToolsContext()));
640
+ // detect_duplicates - uses extracted handler
1459
641
  this.registerTool({
1460
642
  name: 'detect_duplicates',
1461
643
  description: 'Detect duplicate tasks between Apple Reminders and Notion.',
@@ -1468,82 +650,9 @@ class MCPHandlerImpl {
1468
650
  },
1469
651
  },
1470
652
  },
1471
- }, async (args) => {
1472
- if (!this.config) {
1473
- return {
1474
- content: [
1475
- {
1476
- type: 'text',
1477
- text: JSON.stringify({
1478
- error: true,
1479
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1480
- }, null, 2),
1481
- },
1482
- ],
1483
- };
1484
- }
1485
- if (!this.taskSynchronizer) {
1486
- this.initializeServices(this.config);
1487
- }
1488
- try {
1489
- const autoMerge = args.autoMerge;
1490
- const duplicates = await this.taskSynchronizer.detectDuplicates();
1491
- const formattedDuplicates = duplicates.map((d) => ({
1492
- tasks: d.tasks.map((t) => ({
1493
- id: t.id,
1494
- title: t.title,
1495
- source: t.source,
1496
- status: t.status,
1497
- priority: t.priority,
1498
- })),
1499
- similarity: Math.round(d.similarity * 100),
1500
- confidence: d.confidence,
1501
- suggestedMerge: {
1502
- title: d.suggestedMerge.title,
1503
- priority: d.suggestedMerge.priority,
1504
- status: d.suggestedMerge.status,
1505
- tags: d.suggestedMerge.tags,
1506
- },
1507
- }));
1508
- let mergeResults;
1509
- if (autoMerge) {
1510
- const highConfidenceDuplicates = duplicates.filter((d) => d.confidence === 'high');
1511
- if (highConfidenceDuplicates.length > 0) {
1512
- mergeResults =
1513
- await this.taskSynchronizer.mergeDuplicates(highConfidenceDuplicates);
1514
- }
1515
- }
1516
- return {
1517
- content: [
1518
- {
1519
- type: 'text',
1520
- text: JSON.stringify({
1521
- success: true,
1522
- duplicatesFound: duplicates.length,
1523
- duplicates: formattedDuplicates,
1524
- mergeResults: autoMerge ? mergeResults : undefined,
1525
- message: duplicates.length > 0
1526
- ? `${duplicates.length}件の重複タスクが検出されました。`
1527
- : '重複タスクは見つかりませんでした。',
1528
- }, null, 2),
1529
- },
1530
- ],
1531
- };
1532
- }
1533
- catch (error) {
1534
- return {
1535
- content: [
1536
- {
1537
- type: 'text',
1538
- text: JSON.stringify({
1539
- error: true,
1540
- message: `重複検出に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1541
- }, null, 2),
1542
- },
1543
- ],
1544
- };
1545
- }
1546
- });
653
+ }, async (args) => handleDetectDuplicates(this.createTaskToolsContext(), {
654
+ autoMerge: args.autoMerge,
655
+ }));
1547
656
  // get_working_cadence
1548
657
  this.registerTool({
1549
658
  name: 'get_working_cadence',
@@ -1643,97 +752,11 @@ class MCPHandlerImpl {
1643
752
  },
1644
753
  required: ['eventId', 'response'],
1645
754
  },
1646
- }, async (args) => {
1647
- if (!this.config) {
1648
- return {
1649
- content: [
1650
- {
1651
- type: 'text',
1652
- text: JSON.stringify({
1653
- error: true,
1654
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1655
- }, null, 2),
1656
- },
1657
- ],
1658
- };
1659
- }
1660
- if (!this.calendarEventResponseService) {
1661
- this.initializeServices(this.config);
1662
- }
1663
- try {
1664
- const eventId = args.eventId;
1665
- const response = args.response;
1666
- const comment = args.comment;
1667
- const isAvailable = await this.calendarEventResponseService.isEventKitAvailable();
1668
- if (!isAvailable) {
1669
- return {
1670
- content: [
1671
- {
1672
- type: 'text',
1673
- text: JSON.stringify({
1674
- success: false,
1675
- message: 'カレンダーイベント返信機能はmacOSでのみ利用可能です。',
1676
- }, null, 2),
1677
- },
1678
- ],
1679
- };
1680
- }
1681
- const result = await this.calendarEventResponseService.respondToEvent({
1682
- eventId,
1683
- response,
1684
- comment,
1685
- });
1686
- if (result.success) {
1687
- return {
1688
- content: [
1689
- {
1690
- type: 'text',
1691
- text: JSON.stringify({
1692
- success: true,
1693
- eventId: result.eventId,
1694
- eventTitle: result.eventTitle,
1695
- newStatus: result.newStatus,
1696
- method: result.method,
1697
- instanceOnly: result.instanceOnly,
1698
- message: result.message,
1699
- }, null, 2),
1700
- },
1701
- ],
1702
- };
1703
- }
1704
- return {
1705
- content: [
1706
- {
1707
- type: 'text',
1708
- text: JSON.stringify({
1709
- success: false,
1710
- eventId: result.eventId,
1711
- eventTitle: result.eventTitle,
1712
- skipped: result.skipped,
1713
- reason: result.reason,
1714
- error: result.error,
1715
- message: result.skipped
1716
- ? `イベントをスキップしました: ${result.reason}`
1717
- : `イベント返信に失敗しました: ${result.error}`,
1718
- }, null, 2),
1719
- },
1720
- ],
1721
- };
1722
- }
1723
- catch (error) {
1724
- return {
1725
- content: [
1726
- {
1727
- type: 'text',
1728
- text: JSON.stringify({
1729
- error: true,
1730
- message: `カレンダーイベント返信に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1731
- }, null, 2),
1732
- },
1733
- ],
1734
- };
1735
- }
1736
- });
755
+ }, async (args) => handleRespondToCalendarEvent(this.createCalendarToolsContext(), {
756
+ eventId: args.eventId,
757
+ response: args.response,
758
+ comment: args.comment,
759
+ }));
1737
760
  // respond_to_calendar_events_batch
1738
761
  this.registerTool({
1739
762
  name: 'respond_to_calendar_events_batch',
@@ -1758,78 +781,11 @@ class MCPHandlerImpl {
1758
781
  },
1759
782
  required: ['eventIds', 'response'],
1760
783
  },
1761
- }, async (args) => {
1762
- if (!this.config) {
1763
- return {
1764
- content: [
1765
- {
1766
- type: 'text',
1767
- text: JSON.stringify({
1768
- error: true,
1769
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1770
- }, null, 2),
1771
- },
1772
- ],
1773
- };
1774
- }
1775
- if (!this.calendarEventResponseService) {
1776
- this.initializeServices(this.config);
1777
- }
1778
- try {
1779
- const eventIds = args.eventIds;
1780
- const response = args.response;
1781
- const comment = args.comment;
1782
- const isAvailable = await this.calendarEventResponseService.isEventKitAvailable();
1783
- if (!isAvailable) {
1784
- return {
1785
- content: [
1786
- {
1787
- type: 'text',
1788
- text: JSON.stringify({
1789
- success: false,
1790
- message: 'カレンダーイベント返信機能はmacOSでのみ利用可能です。',
1791
- }, null, 2),
1792
- },
1793
- ],
1794
- };
1795
- }
1796
- const result = await this.calendarEventResponseService.respondToEventsBatch({
1797
- eventIds,
1798
- response,
1799
- comment,
1800
- });
1801
- return {
1802
- content: [
1803
- {
1804
- type: 'text',
1805
- text: JSON.stringify({
1806
- success: result.success,
1807
- summary: result.summary,
1808
- details: {
1809
- succeeded: result.details.succeeded,
1810
- skipped: result.details.skipped,
1811
- failed: result.details.failed,
1812
- },
1813
- message: result.message,
1814
- }, null, 2),
1815
- },
1816
- ],
1817
- };
1818
- }
1819
- catch (error) {
1820
- return {
1821
- content: [
1822
- {
1823
- type: 'text',
1824
- text: JSON.stringify({
1825
- error: true,
1826
- message: `カレンダーイベント一括返信に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1827
- }, null, 2),
1828
- },
1829
- ],
1830
- };
1831
- }
1832
- });
784
+ }, async (args) => handleRespondToCalendarEventsBatch(this.createCalendarToolsContext(), {
785
+ eventIds: args.eventIds,
786
+ response: args.response,
787
+ comment: args.comment,
788
+ }));
1833
789
  // create_calendar_event
1834
790
  this.registerTool({
1835
791
  name: 'create_calendar_event',
@@ -1860,100 +816,14 @@ class MCPHandlerImpl {
1860
816
  },
1861
817
  required: ['title', 'startDate', 'endDate'],
1862
818
  },
1863
- }, async (args) => {
1864
- if (!this.config) {
1865
- return {
1866
- content: [
1867
- {
1868
- type: 'text',
1869
- text: JSON.stringify({
1870
- error: true,
1871
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1872
- }, null, 2),
1873
- },
1874
- ],
1875
- };
1876
- }
1877
- if (!this.calendarEventCreatorService) {
1878
- this.initializeServices(this.config);
1879
- }
1880
- try {
1881
- const title = args.title;
1882
- const startDate = args.startDate;
1883
- const endDate = args.endDate;
1884
- const location = args.location;
1885
- const notes = args.notes;
1886
- const calendarName = args.calendarName;
1887
- const alarms = args.alarms;
1888
- const isAvailable = await this.calendarEventCreatorService.isEventKitAvailable();
1889
- if (!isAvailable) {
1890
- return {
1891
- content: [
1892
- {
1893
- type: 'text',
1894
- text: JSON.stringify({
1895
- success: false,
1896
- message: 'カレンダーイベント作成機能はmacOSでのみ利用可能です。',
1897
- }, null, 2),
1898
- },
1899
- ],
1900
- };
1901
- }
1902
- const result = await this.calendarEventCreatorService.createEvent({
1903
- title,
1904
- startDate,
1905
- endDate,
1906
- location,
1907
- notes,
1908
- calendarName,
1909
- alarms,
1910
- });
1911
- if (result.success) {
1912
- return {
1913
- content: [
1914
- {
1915
- type: 'text',
1916
- text: JSON.stringify({
1917
- success: true,
1918
- eventId: result.eventId,
1919
- title: result.title,
1920
- startDate: result.startDate,
1921
- endDate: result.endDate,
1922
- calendarName: result.calendarName,
1923
- isAllDay: result.isAllDay,
1924
- message: result.message,
1925
- }, null, 2),
1926
- },
1927
- ],
1928
- };
1929
- }
1930
- return {
1931
- content: [
1932
- {
1933
- type: 'text',
1934
- text: JSON.stringify({
1935
- success: false,
1936
- error: result.error,
1937
- message: result.message,
1938
- }, null, 2),
1939
- },
1940
- ],
1941
- };
1942
- }
1943
- catch (error) {
1944
- return {
1945
- content: [
1946
- {
1947
- type: 'text',
1948
- text: JSON.stringify({
1949
- error: true,
1950
- message: `カレンダーイベント作成に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1951
- }, null, 2),
1952
- },
1953
- ],
1954
- };
1955
- }
1956
- });
819
+ }, async (args) => handleCreateCalendarEvent(this.createCalendarToolsContext(), {
820
+ title: args.title,
821
+ startDate: args.startDate,
822
+ endDate: args.endDate,
823
+ location: args.location,
824
+ notes: args.notes,
825
+ calendarName: args.calendarName,
826
+ }));
1957
827
  // delete_calendar_event
1958
828
  this.registerTool({
1959
829
  name: 'delete_calendar_event',
@@ -1972,88 +842,9 @@ class MCPHandlerImpl {
1972
842
  },
1973
843
  required: ['eventId'],
1974
844
  },
1975
- }, async (args) => {
1976
- if (!this.config) {
1977
- return {
1978
- content: [
1979
- {
1980
- type: 'text',
1981
- text: JSON.stringify({
1982
- error: true,
1983
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
1984
- }, null, 2),
1985
- },
1986
- ],
1987
- };
1988
- }
1989
- if (!this.calendarEventDeleterService) {
1990
- this.initializeServices(this.config);
1991
- }
1992
- try {
1993
- const eventId = args.eventId;
1994
- const calendarName = args.calendarName;
1995
- const isAvailable = await this.calendarEventDeleterService.isEventKitAvailable();
1996
- if (!isAvailable) {
1997
- return {
1998
- content: [
1999
- {
2000
- type: 'text',
2001
- text: JSON.stringify({
2002
- success: false,
2003
- message: 'カレンダーイベント削除機能はmacOSでのみ利用可能です。',
2004
- }, null, 2),
2005
- },
2006
- ],
2007
- };
2008
- }
2009
- const result = await this.calendarEventDeleterService.deleteEvent({
2010
- eventId,
2011
- calendarName,
2012
- });
2013
- if (result.success) {
2014
- return {
2015
- content: [
2016
- {
2017
- type: 'text',
2018
- text: JSON.stringify({
2019
- success: true,
2020
- eventId: result.eventId,
2021
- title: result.title,
2022
- calendarName: result.calendarName,
2023
- message: result.message,
2024
- }, null, 2),
2025
- },
2026
- ],
2027
- };
2028
- }
2029
- return {
2030
- content: [
2031
- {
2032
- type: 'text',
2033
- text: JSON.stringify({
2034
- success: false,
2035
- eventId: result.eventId,
2036
- error: result.error,
2037
- message: result.message,
2038
- }, null, 2),
2039
- },
2040
- ],
2041
- };
2042
- }
2043
- catch (error) {
2044
- return {
2045
- content: [
2046
- {
2047
- type: 'text',
2048
- text: JSON.stringify({
2049
- error: true,
2050
- message: `カレンダーイベント削除に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
2051
- }, null, 2),
2052
- },
2053
- ],
2054
- };
2055
- }
2056
- });
845
+ }, async (args) => handleDeleteCalendarEvent(this.createCalendarToolsContext(), {
846
+ eventId: args.eventId,
847
+ }));
2057
848
  // delete_calendar_events_batch
2058
849
  this.registerTool({
2059
850
  name: 'delete_calendar_events_batch',
@@ -2073,80 +864,18 @@ class MCPHandlerImpl {
2073
864
  },
2074
865
  required: ['eventIds'],
2075
866
  },
2076
- }, async (args) => {
2077
- if (!this.config) {
2078
- return {
2079
- content: [
2080
- {
2081
- type: 'text',
2082
- text: JSON.stringify({
2083
- error: true,
2084
- message: 'sageが設定されていません。check_setup_statusを実行してください。',
2085
- }, null, 2),
2086
- },
2087
- ],
2088
- };
2089
- }
2090
- if (!this.calendarEventDeleterService) {
2091
- this.initializeServices(this.config);
2092
- }
2093
- try {
2094
- const eventIds = args.eventIds;
2095
- const calendarName = args.calendarName;
2096
- const isAvailable = await this.calendarEventDeleterService.isEventKitAvailable();
2097
- if (!isAvailable) {
2098
- return {
2099
- content: [
2100
- {
2101
- type: 'text',
2102
- text: JSON.stringify({
2103
- success: false,
2104
- message: 'カレンダーイベント削除機能はmacOSでのみ利用可能です。',
2105
- }, null, 2),
2106
- },
2107
- ],
2108
- };
2109
- }
2110
- const result = await this.calendarEventDeleterService.deleteEventsBatch({
2111
- eventIds,
2112
- calendarName,
2113
- });
2114
- return {
2115
- content: [
2116
- {
2117
- type: 'text',
2118
- text: JSON.stringify({
2119
- success: result.success,
2120
- totalCount: result.totalCount,
2121
- successCount: result.successCount,
2122
- failedCount: result.failedCount,
2123
- results: result.results.map((r) => ({
2124
- eventId: r.eventId,
2125
- success: r.success,
2126
- title: r.title,
2127
- calendarName: r.calendarName,
2128
- error: r.error,
2129
- })),
2130
- message: result.message,
2131
- }, null, 2),
2132
- },
2133
- ],
2134
- };
2135
- }
2136
- catch (error) {
2137
- return {
2138
- content: [
2139
- {
2140
- type: 'text',
2141
- text: JSON.stringify({
2142
- error: true,
2143
- message: `カレンダーイベント一括削除に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
2144
- }, null, 2),
2145
- },
2146
- ],
2147
- };
2148
- }
2149
- });
867
+ }, async (args) => handleDeleteCalendarEventsBatch(this.createCalendarToolsContext(), {
868
+ eventIds: args.eventIds,
869
+ }));
870
+ // list_calendar_sources - uses extracted handler
871
+ this.registerTool({
872
+ name: 'list_calendar_sources',
873
+ description: 'List available and enabled calendar sources.',
874
+ inputSchema: {
875
+ type: 'object',
876
+ properties: {},
877
+ },
878
+ }, async () => handleListCalendarSources(this.createCalendarToolsContext()));
2150
879
  }
2151
880
  /**
2152
881
  * Register a tool
@@ -2154,108 +883,6 @@ class MCPHandlerImpl {
2154
883
  registerTool(definition, handler) {
2155
884
  this.tools.set(definition.name, { definition, handler });
2156
885
  }
2157
- /**
2158
- * Validate config updates
2159
- */
2160
- validateConfigUpdate(section, updates) {
2161
- const invalidFields = [];
2162
- switch (section) {
2163
- case 'user':
2164
- if (updates.name !== undefined && typeof updates.name !== 'string') {
2165
- invalidFields.push('name');
2166
- }
2167
- if (updates.timezone !== undefined && typeof updates.timezone !== 'string') {
2168
- invalidFields.push('timezone');
2169
- }
2170
- break;
2171
- case 'calendar':
2172
- if (updates.workingHours !== undefined) {
2173
- const wh = updates.workingHours;
2174
- if (!wh.start || !wh.end) {
2175
- invalidFields.push('workingHours');
2176
- }
2177
- }
2178
- if (updates.deepWorkDays !== undefined && !Array.isArray(updates.deepWorkDays)) {
2179
- invalidFields.push('deepWorkDays');
2180
- }
2181
- if (updates.meetingHeavyDays !== undefined &&
2182
- !Array.isArray(updates.meetingHeavyDays)) {
2183
- invalidFields.push('meetingHeavyDays');
2184
- }
2185
- break;
2186
- case 'integrations':
2187
- if (updates.notion !== undefined) {
2188
- const notion = updates.notion;
2189
- if (notion.enabled === true && !notion.databaseId) {
2190
- invalidFields.push('notion.databaseId');
2191
- }
2192
- }
2193
- break;
2194
- case 'team':
2195
- if (updates.members !== undefined && !Array.isArray(updates.members)) {
2196
- invalidFields.push('members');
2197
- }
2198
- if (updates.managers !== undefined && !Array.isArray(updates.managers)) {
2199
- invalidFields.push('managers');
2200
- }
2201
- break;
2202
- }
2203
- if (invalidFields.length > 0) {
2204
- return {
2205
- valid: false,
2206
- error: `無効なフィールド: ${invalidFields.join(', ')}`,
2207
- invalidFields,
2208
- };
2209
- }
2210
- return { valid: true };
2211
- }
2212
- /**
2213
- * Apply config updates
2214
- */
2215
- applyConfigUpdates(currentConfig, section, updates) {
2216
- const newConfig = { ...currentConfig };
2217
- switch (section) {
2218
- case 'user':
2219
- newConfig.user = { ...newConfig.user, ...updates };
2220
- break;
2221
- case 'calendar':
2222
- newConfig.calendar = {
2223
- ...newConfig.calendar,
2224
- ...updates,
2225
- };
2226
- break;
2227
- case 'priorityRules':
2228
- newConfig.priorityRules = {
2229
- ...newConfig.priorityRules,
2230
- ...updates,
2231
- };
2232
- break;
2233
- case 'integrations':
2234
- if (updates.appleReminders) {
2235
- newConfig.integrations.appleReminders = {
2236
- ...newConfig.integrations.appleReminders,
2237
- ...updates.appleReminders,
2238
- };
2239
- }
2240
- if (updates.notion) {
2241
- newConfig.integrations.notion = {
2242
- ...newConfig.integrations.notion,
2243
- ...updates.notion,
2244
- };
2245
- }
2246
- break;
2247
- case 'team':
2248
- newConfig.team = { ...newConfig.team, ...updates };
2249
- break;
2250
- case 'preferences':
2251
- newConfig.preferences = {
2252
- ...newConfig.preferences,
2253
- ...updates,
2254
- };
2255
- break;
2256
- }
2257
- return newConfig;
2258
- }
2259
886
  }
2260
887
  /**
2261
888
  * Create an MCP handler