@shin1ohno/sage 0.3.0 → 0.5.5

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 (95) hide show
  1. package/dist/cli/http-server-with-config.d.ts +38 -0
  2. package/dist/cli/http-server-with-config.d.ts.map +1 -0
  3. package/dist/cli/http-server-with-config.js +477 -0
  4. package/dist/cli/http-server-with-config.js.map +1 -0
  5. package/dist/cli/http-server.d.ts +74 -0
  6. package/dist/cli/http-server.d.ts.map +1 -0
  7. package/dist/cli/http-server.js +407 -0
  8. package/dist/cli/http-server.js.map +1 -0
  9. package/dist/cli/jwt-middleware.d.ts +36 -0
  10. package/dist/cli/jwt-middleware.d.ts.map +1 -0
  11. package/dist/cli/jwt-middleware.js +99 -0
  12. package/dist/cli/jwt-middleware.js.map +1 -0
  13. package/dist/cli/main-entry.d.ts +45 -0
  14. package/dist/cli/main-entry.d.ts.map +1 -0
  15. package/dist/cli/main-entry.js +192 -0
  16. package/dist/cli/main-entry.js.map +1 -0
  17. package/dist/cli/mcp-handler.d.ts +56 -0
  18. package/dist/cli/mcp-handler.d.ts.map +1 -0
  19. package/dist/cli/mcp-handler.js +2189 -0
  20. package/dist/cli/mcp-handler.js.map +1 -0
  21. package/dist/cli/parser.d.ts +45 -0
  22. package/dist/cli/parser.d.ts.map +1 -0
  23. package/dist/cli/parser.js +172 -0
  24. package/dist/cli/parser.js.map +1 -0
  25. package/dist/cli/remote-config-loader.d.ts +89 -0
  26. package/dist/cli/remote-config-loader.d.ts.map +1 -0
  27. package/dist/cli/remote-config-loader.js +129 -0
  28. package/dist/cli/remote-config-loader.js.map +1 -0
  29. package/dist/cli/secret-auth.d.ts +47 -0
  30. package/dist/cli/secret-auth.d.ts.map +1 -0
  31. package/dist/cli/secret-auth.js +165 -0
  32. package/dist/cli/secret-auth.js.map +1 -0
  33. package/dist/cli/sse-stream-handler.d.ts +45 -0
  34. package/dist/cli/sse-stream-handler.d.ts.map +1 -0
  35. package/dist/cli/sse-stream-handler.js +125 -0
  36. package/dist/cli/sse-stream-handler.js.map +1 -0
  37. package/dist/index.js +897 -209
  38. package/dist/index.js.map +1 -1
  39. package/dist/integrations/calendar-event-creator.d.ts +152 -0
  40. package/dist/integrations/calendar-event-creator.d.ts.map +1 -0
  41. package/dist/integrations/calendar-event-creator.js +507 -0
  42. package/dist/integrations/calendar-event-creator.js.map +1 -0
  43. package/dist/integrations/calendar-event-deleter.d.ts +137 -0
  44. package/dist/integrations/calendar-event-deleter.d.ts.map +1 -0
  45. package/dist/integrations/calendar-event-deleter.js +378 -0
  46. package/dist/integrations/calendar-event-deleter.js.map +1 -0
  47. package/dist/integrations/calendar-event-response.d.ts +213 -0
  48. package/dist/integrations/calendar-event-response.d.ts.map +1 -0
  49. package/dist/integrations/calendar-event-response.js +560 -0
  50. package/dist/integrations/calendar-event-response.js.map +1 -0
  51. package/dist/integrations/calendar-service.d.ts +66 -1
  52. package/dist/integrations/calendar-service.d.ts.map +1 -1
  53. package/dist/integrations/calendar-service.js +223 -0
  54. package/dist/integrations/calendar-service.js.map +1 -1
  55. package/dist/oauth/client-store.d.ts +36 -0
  56. package/dist/oauth/client-store.d.ts.map +1 -0
  57. package/dist/oauth/client-store.js +119 -0
  58. package/dist/oauth/client-store.js.map +1 -0
  59. package/dist/oauth/code-store.d.ts +48 -0
  60. package/dist/oauth/code-store.d.ts.map +1 -0
  61. package/dist/oauth/code-store.js +89 -0
  62. package/dist/oauth/code-store.js.map +1 -0
  63. package/dist/oauth/index.d.ts +13 -0
  64. package/dist/oauth/index.d.ts.map +1 -0
  65. package/dist/oauth/index.js +21 -0
  66. package/dist/oauth/index.js.map +1 -0
  67. package/dist/oauth/oauth-handler.d.ts +101 -0
  68. package/dist/oauth/oauth-handler.d.ts.map +1 -0
  69. package/dist/oauth/oauth-handler.js +577 -0
  70. package/dist/oauth/oauth-handler.js.map +1 -0
  71. package/dist/oauth/oauth-server.d.ts +165 -0
  72. package/dist/oauth/oauth-server.d.ts.map +1 -0
  73. package/dist/oauth/oauth-server.js +489 -0
  74. package/dist/oauth/oauth-server.js.map +1 -0
  75. package/dist/oauth/pkce.d.ts +48 -0
  76. package/dist/oauth/pkce.d.ts.map +1 -0
  77. package/dist/oauth/pkce.js +106 -0
  78. package/dist/oauth/pkce.js.map +1 -0
  79. package/dist/oauth/refresh-token-store.d.ts +45 -0
  80. package/dist/oauth/refresh-token-store.d.ts.map +1 -0
  81. package/dist/oauth/refresh-token-store.js +98 -0
  82. package/dist/oauth/refresh-token-store.js.map +1 -0
  83. package/dist/oauth/token-service.d.ts +46 -0
  84. package/dist/oauth/token-service.d.ts.map +1 -0
  85. package/dist/oauth/token-service.js +199 -0
  86. package/dist/oauth/token-service.js.map +1 -0
  87. package/dist/oauth/types.d.ts +269 -0
  88. package/dist/oauth/types.d.ts.map +1 -0
  89. package/dist/oauth/types.js +53 -0
  90. package/dist/oauth/types.js.map +1 -0
  91. package/dist/version.d.ts +9 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +11 -0
  94. package/dist/version.js.map +1 -0
  95. package/package.json +1 -1
@@ -0,0 +1,2189 @@
1
+ /**
2
+ * MCP Handler for HTTP Server
3
+ * Requirements: 13.1, 13.4, 13.5
4
+ *
5
+ * Handles MCP JSON-RPC requests over HTTP, providing access to all sage tools.
6
+ */
7
+ import { VERSION, SERVER_NAME } from '../version.js';
8
+ import { ConfigLoader } from '../config/loader.js';
9
+ import { SetupWizard } from '../setup/wizard.js';
10
+ import { TaskAnalyzer } from '../tools/analyze-tasks.js';
11
+ import { ReminderManager } from '../integrations/reminder-manager.js';
12
+ import { CalendarService } from '../integrations/calendar-service.js';
13
+ import { NotionMCPService } from '../integrations/notion-mcp.js';
14
+ import { TodoListManager } from '../integrations/todo-list-manager.js';
15
+ import { TaskSynchronizer } from '../integrations/task-synchronizer.js';
16
+ 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
+ // Protocol version
20
+ const PROTOCOL_VERSION = '2024-11-05';
21
+ /**
22
+ * MCP Handler Implementation
23
+ */
24
+ class MCPHandlerImpl {
25
+ config = null;
26
+ wizardSession = null;
27
+ reminderManager = null;
28
+ calendarService = null;
29
+ notionService = null;
30
+ todoListManager = null;
31
+ taskSynchronizer = null;
32
+ calendarEventResponseService = null;
33
+ calendarEventCreatorService = null;
34
+ calendarEventDeleterService = null;
35
+ initialized = false;
36
+ tools = new Map();
37
+ constructor() {
38
+ this.registerTools();
39
+ }
40
+ /**
41
+ * Initialize the handler (load config, etc.)
42
+ */
43
+ async initialize() {
44
+ if (this.initialized) {
45
+ return;
46
+ }
47
+ try {
48
+ this.config = await ConfigLoader.load();
49
+ if (this.config) {
50
+ this.initializeServices(this.config);
51
+ }
52
+ }
53
+ catch {
54
+ this.config = null;
55
+ }
56
+ this.initialized = true;
57
+ }
58
+ /**
59
+ * Initialize services with config
60
+ */
61
+ initializeServices(userConfig) {
62
+ this.reminderManager = new ReminderManager({
63
+ appleRemindersThreshold: 7,
64
+ notionThreshold: userConfig.integrations.notion.threshold,
65
+ defaultList: userConfig.integrations.appleReminders.defaultList,
66
+ notionDatabaseId: userConfig.integrations.notion.databaseId,
67
+ });
68
+ this.calendarService = new CalendarService();
69
+ this.notionService = new NotionMCPService();
70
+ this.todoListManager = new TodoListManager();
71
+ this.taskSynchronizer = new TaskSynchronizer();
72
+ this.calendarEventResponseService = new CalendarEventResponseService();
73
+ this.calendarEventCreatorService = new CalendarEventCreatorService();
74
+ this.calendarEventDeleterService = new CalendarEventDeleterService();
75
+ }
76
+ /**
77
+ * Handle an MCP request
78
+ */
79
+ async handleRequest(request) {
80
+ const { method, id, params } = request;
81
+ try {
82
+ switch (method) {
83
+ case 'initialize':
84
+ return this.handleInitialize(id, params);
85
+ case 'notifications/initialized':
86
+ return { jsonrpc: '2.0', id };
87
+ case 'tools/list':
88
+ return this.handleToolsList(id);
89
+ case 'tools/call':
90
+ return this.handleToolsCall(id, params);
91
+ default:
92
+ return {
93
+ jsonrpc: '2.0',
94
+ id,
95
+ error: {
96
+ code: -32601,
97
+ message: `Method not found: ${method}`,
98
+ },
99
+ };
100
+ }
101
+ }
102
+ catch (error) {
103
+ return {
104
+ jsonrpc: '2.0',
105
+ id,
106
+ error: {
107
+ code: -32603,
108
+ message: error instanceof Error ? error.message : 'Internal error',
109
+ },
110
+ };
111
+ }
112
+ }
113
+ /**
114
+ * Handle initialize request
115
+ */
116
+ handleInitialize(id, _params) {
117
+ return {
118
+ jsonrpc: '2.0',
119
+ id,
120
+ result: {
121
+ protocolVersion: PROTOCOL_VERSION,
122
+ serverInfo: {
123
+ name: SERVER_NAME,
124
+ version: VERSION,
125
+ },
126
+ capabilities: {
127
+ tools: {},
128
+ },
129
+ },
130
+ };
131
+ }
132
+ /**
133
+ * Handle tools/list request
134
+ */
135
+ handleToolsList(id) {
136
+ const tools = this.listTools();
137
+ return {
138
+ jsonrpc: '2.0',
139
+ id,
140
+ result: {
141
+ tools,
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Handle tools/call request
147
+ */
148
+ async handleToolsCall(id, params) {
149
+ if (!params?.name) {
150
+ return {
151
+ jsonrpc: '2.0',
152
+ id,
153
+ error: {
154
+ code: -32602,
155
+ message: 'Invalid params: missing tool name',
156
+ },
157
+ };
158
+ }
159
+ const toolName = params.name;
160
+ const toolArgs = params.arguments || {};
161
+ const tool = this.tools.get(toolName);
162
+ if (!tool) {
163
+ return {
164
+ jsonrpc: '2.0',
165
+ id,
166
+ error: {
167
+ code: -32601,
168
+ message: `Tool not found: ${toolName}`,
169
+ },
170
+ };
171
+ }
172
+ try {
173
+ const result = await tool.handler(toolArgs);
174
+ return {
175
+ jsonrpc: '2.0',
176
+ id,
177
+ result,
178
+ };
179
+ }
180
+ catch (error) {
181
+ // Return error as content (MCP convention for tool errors)
182
+ return {
183
+ jsonrpc: '2.0',
184
+ id,
185
+ result: {
186
+ content: [
187
+ {
188
+ type: 'text',
189
+ text: JSON.stringify({
190
+ error: true,
191
+ message: error instanceof Error ? error.message : 'Unknown error',
192
+ }, null, 2),
193
+ },
194
+ ],
195
+ },
196
+ };
197
+ }
198
+ }
199
+ /**
200
+ * List all available tools
201
+ */
202
+ listTools() {
203
+ return Array.from(this.tools.values()).map((t) => t.definition);
204
+ }
205
+ /**
206
+ * Register all tools
207
+ */
208
+ registerTools() {
209
+ // check_setup_status
210
+ this.registerTool({
211
+ name: 'check_setup_status',
212
+ description: 'Check if sage has been configured. Returns setup status and guidance.',
213
+ inputSchema: {
214
+ type: 'object',
215
+ properties: {},
216
+ },
217
+ }, async () => {
218
+ const exists = await ConfigLoader.exists();
219
+ const isValid = this.config !== null;
220
+ if (!exists) {
221
+ return {
222
+ content: [
223
+ {
224
+ type: 'text',
225
+ text: JSON.stringify({
226
+ setupComplete: false,
227
+ configExists: false,
228
+ message: 'sageの初期設定が必要です。start_setup_wizardを実行してセットアップを開始してください。',
229
+ nextAction: 'start_setup_wizard',
230
+ }, null, 2),
231
+ },
232
+ ],
233
+ };
234
+ }
235
+ if (!isValid) {
236
+ return {
237
+ content: [
238
+ {
239
+ type: 'text',
240
+ text: JSON.stringify({
241
+ setupComplete: false,
242
+ configExists: true,
243
+ message: '設定ファイルが見つかりましたが、読み込みに失敗しました。設定を再作成してください。',
244
+ nextAction: 'start_setup_wizard',
245
+ }, null, 2),
246
+ },
247
+ ],
248
+ };
249
+ }
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: JSON.stringify({
255
+ setupComplete: true,
256
+ configExists: true,
257
+ userName: this.config?.user.name,
258
+ message: 'sageは設定済みです。タスク分析やリマインド設定を開始できます。',
259
+ availableTools: [
260
+ 'analyze_tasks',
261
+ 'set_reminder',
262
+ 'find_available_slots',
263
+ 'sync_to_notion',
264
+ 'update_config',
265
+ ],
266
+ }, null, 2),
267
+ },
268
+ ],
269
+ };
270
+ });
271
+ // start_setup_wizard
272
+ this.registerTool({
273
+ name: 'start_setup_wizard',
274
+ description: 'Start the interactive setup wizard for sage. Returns the first question.',
275
+ inputSchema: {
276
+ type: 'object',
277
+ properties: {
278
+ mode: {
279
+ type: 'string',
280
+ enum: ['full', 'quick'],
281
+ description: 'Setup mode: full (all questions) or quick (essential only)',
282
+ },
283
+ },
284
+ },
285
+ }, async (args) => {
286
+ const mode = args.mode || 'full';
287
+ this.wizardSession = SetupWizard.createSession(mode);
288
+ const question = SetupWizard.getCurrentQuestion(this.wizardSession);
289
+ return {
290
+ content: [
291
+ {
292
+ type: 'text',
293
+ text: JSON.stringify({
294
+ sessionId: this.wizardSession.sessionId,
295
+ currentStep: this.wizardSession.currentStep,
296
+ totalSteps: this.wizardSession.totalSteps,
297
+ progress: Math.round((this.wizardSession.currentStep / this.wizardSession.totalSteps) * 100),
298
+ question: {
299
+ id: question.id,
300
+ text: question.text,
301
+ type: question.type,
302
+ options: question.options,
303
+ defaultValue: question.defaultValue,
304
+ helpText: question.helpText,
305
+ },
306
+ message: 'セットアップを開始します。以下の質問に回答してください。',
307
+ }, null, 2),
308
+ },
309
+ ],
310
+ };
311
+ });
312
+ // answer_wizard_question
313
+ this.registerTool({
314
+ name: 'answer_wizard_question',
315
+ description: 'Answer a question in the setup wizard and get the next question.',
316
+ inputSchema: {
317
+ type: 'object',
318
+ properties: {
319
+ questionId: {
320
+ type: 'string',
321
+ description: 'The ID of the question being answered',
322
+ },
323
+ answer: {
324
+ oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
325
+ description: 'The answer to the question',
326
+ },
327
+ },
328
+ required: ['questionId', 'answer'],
329
+ },
330
+ }, async (args) => {
331
+ const questionId = args.questionId;
332
+ const answer = args.answer;
333
+ if (!this.wizardSession) {
334
+ return {
335
+ content: [
336
+ {
337
+ type: 'text',
338
+ text: JSON.stringify({
339
+ error: true,
340
+ message: 'セットアップセッションが見つかりません。start_setup_wizardを実行してください。',
341
+ }, null, 2),
342
+ },
343
+ ],
344
+ };
345
+ }
346
+ const result = SetupWizard.answerQuestion(this.wizardSession, questionId, answer);
347
+ if (!result.success) {
348
+ return {
349
+ content: [
350
+ {
351
+ type: 'text',
352
+ text: JSON.stringify({
353
+ error: true,
354
+ message: result.error,
355
+ currentQuestion: result.currentQuestion,
356
+ }, null, 2),
357
+ },
358
+ ],
359
+ };
360
+ }
361
+ if (result.isComplete) {
362
+ return {
363
+ content: [
364
+ {
365
+ type: 'text',
366
+ text: JSON.stringify({
367
+ isComplete: true,
368
+ sessionId: this.wizardSession.sessionId,
369
+ answers: this.wizardSession.answers,
370
+ message: 'すべての質問に回答しました。save_configを実行して設定を保存してください。',
371
+ nextAction: 'save_config',
372
+ }, null, 2),
373
+ },
374
+ ],
375
+ };
376
+ }
377
+ const nextQuestion = SetupWizard.getCurrentQuestion(this.wizardSession);
378
+ return {
379
+ content: [
380
+ {
381
+ type: 'text',
382
+ text: JSON.stringify({
383
+ success: true,
384
+ currentStep: this.wizardSession.currentStep,
385
+ totalSteps: this.wizardSession.totalSteps,
386
+ progress: Math.round((this.wizardSession.currentStep / this.wizardSession.totalSteps) * 100),
387
+ question: {
388
+ id: nextQuestion.id,
389
+ text: nextQuestion.text,
390
+ type: nextQuestion.type,
391
+ options: nextQuestion.options,
392
+ defaultValue: nextQuestion.defaultValue,
393
+ helpText: nextQuestion.helpText,
394
+ },
395
+ }, null, 2),
396
+ },
397
+ ],
398
+ };
399
+ });
400
+ // save_config
401
+ this.registerTool({
402
+ name: 'save_config',
403
+ description: 'Save the configuration after completing the setup wizard.',
404
+ inputSchema: {
405
+ type: 'object',
406
+ properties: {
407
+ confirm: {
408
+ type: 'boolean',
409
+ description: 'Confirm saving the configuration',
410
+ },
411
+ },
412
+ required: ['confirm'],
413
+ },
414
+ }, async (args) => {
415
+ const confirm = args.confirm;
416
+ if (!confirm) {
417
+ return {
418
+ content: [
419
+ {
420
+ type: 'text',
421
+ text: JSON.stringify({
422
+ saved: false,
423
+ message: '設定の保存がキャンセルされました。',
424
+ }, null, 2),
425
+ },
426
+ ],
427
+ };
428
+ }
429
+ if (!this.wizardSession) {
430
+ return {
431
+ content: [
432
+ {
433
+ type: 'text',
434
+ text: JSON.stringify({
435
+ error: true,
436
+ message: 'セットアップセッションが見つかりません。start_setup_wizardを実行してください。',
437
+ }, null, 2),
438
+ },
439
+ ],
440
+ };
441
+ }
442
+ try {
443
+ const newConfig = SetupWizard.buildConfig(this.wizardSession);
444
+ await ConfigLoader.save(newConfig);
445
+ this.config = newConfig;
446
+ this.wizardSession = null;
447
+ return {
448
+ content: [
449
+ {
450
+ type: 'text',
451
+ text: JSON.stringify({
452
+ saved: true,
453
+ configPath: ConfigLoader.getConfigPath(),
454
+ userName: newConfig.user.name,
455
+ message: `設定を保存しました。${newConfig.user.name}さん、sageをご利用いただきありがとうございます!`,
456
+ availableTools: [
457
+ 'analyze_tasks',
458
+ 'set_reminder',
459
+ 'find_available_slots',
460
+ 'sync_to_notion',
461
+ ],
462
+ }, null, 2),
463
+ },
464
+ ],
465
+ };
466
+ }
467
+ catch (error) {
468
+ return {
469
+ content: [
470
+ {
471
+ type: 'text',
472
+ text: JSON.stringify({
473
+ error: true,
474
+ message: `設定の保存に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
475
+ }, null, 2),
476
+ },
477
+ ],
478
+ };
479
+ }
480
+ });
481
+ // analyze_tasks
482
+ this.registerTool({
483
+ name: 'analyze_tasks',
484
+ description: 'Analyze tasks to determine priority, estimate time, and identify stakeholders.',
485
+ inputSchema: {
486
+ type: 'object',
487
+ properties: {
488
+ tasks: {
489
+ type: 'array',
490
+ items: {
491
+ type: 'object',
492
+ properties: {
493
+ title: { type: 'string', description: 'Task title' },
494
+ description: { type: 'string', description: 'Task description' },
495
+ deadline: {
496
+ type: 'string',
497
+ description: 'Task deadline (ISO 8601 format)',
498
+ },
499
+ },
500
+ required: ['title'],
501
+ },
502
+ description: 'List of tasks to analyze',
503
+ },
504
+ },
505
+ required: ['tasks'],
506
+ },
507
+ }, async (args) => {
508
+ if (!this.config) {
509
+ return {
510
+ content: [
511
+ {
512
+ type: 'text',
513
+ text: JSON.stringify({
514
+ error: true,
515
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
516
+ }, null, 2),
517
+ },
518
+ ],
519
+ };
520
+ }
521
+ try {
522
+ const tasks = args.tasks;
523
+ const result = await TaskAnalyzer.analyzeTasks(tasks, this.config);
524
+ return {
525
+ content: [
526
+ {
527
+ type: 'text',
528
+ text: JSON.stringify({
529
+ success: true,
530
+ summary: result.summary,
531
+ tasks: result.analyzedTasks.map((t) => ({
532
+ title: t.original.title,
533
+ description: t.original.description,
534
+ deadline: t.original.deadline,
535
+ priority: t.priority,
536
+ estimatedMinutes: t.estimatedMinutes,
537
+ stakeholders: t.stakeholders,
538
+ tags: t.tags,
539
+ reasoning: t.reasoning,
540
+ suggestedReminders: t.suggestedReminders,
541
+ })),
542
+ }, null, 2),
543
+ },
544
+ ],
545
+ };
546
+ }
547
+ catch (error) {
548
+ return {
549
+ content: [
550
+ {
551
+ type: 'text',
552
+ text: JSON.stringify({
553
+ error: true,
554
+ message: `タスク分析に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
555
+ }, null, 2),
556
+ },
557
+ ],
558
+ };
559
+ }
560
+ });
561
+ // set_reminder
562
+ this.registerTool({
563
+ name: 'set_reminder',
564
+ description: 'Set a reminder for a task in Apple Reminders or Notion.',
565
+ inputSchema: {
566
+ type: 'object',
567
+ properties: {
568
+ taskTitle: { type: 'string', description: 'Title of the task' },
569
+ dueDate: {
570
+ type: 'string',
571
+ description: 'Due date for the reminder (ISO 8601 format)',
572
+ },
573
+ reminderType: {
574
+ type: 'string',
575
+ enum: [
576
+ '1_hour_before',
577
+ '3_hours_before',
578
+ '1_day_before',
579
+ '3_days_before',
580
+ '1_week_before',
581
+ ],
582
+ description: 'Type of reminder',
583
+ },
584
+ list: {
585
+ type: 'string',
586
+ description: 'Reminder list name (for Apple Reminders)',
587
+ },
588
+ priority: {
589
+ type: 'string',
590
+ enum: ['P0', 'P1', 'P2', 'P3'],
591
+ description: 'Task priority',
592
+ },
593
+ notes: {
594
+ type: 'string',
595
+ description: 'Additional notes for the reminder',
596
+ },
597
+ },
598
+ required: ['taskTitle'],
599
+ },
600
+ }, async (args) => {
601
+ if (!this.config) {
602
+ return {
603
+ content: [
604
+ {
605
+ type: 'text',
606
+ text: JSON.stringify({
607
+ error: true,
608
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
609
+ }, null, 2),
610
+ },
611
+ ],
612
+ };
613
+ }
614
+ if (!this.reminderManager) {
615
+ this.initializeServices(this.config);
616
+ }
617
+ try {
618
+ const result = await this.reminderManager.setReminder({
619
+ taskTitle: args.taskTitle,
620
+ targetDate: args.dueDate,
621
+ reminderType: args.reminderType,
622
+ list: args.list ?? this.config.integrations.appleReminders.defaultList,
623
+ priority: args.priority,
624
+ notes: args.notes,
625
+ });
626
+ if (result.success) {
627
+ if (result.delegateToNotion && result.notionRequest) {
628
+ return {
629
+ content: [
630
+ {
631
+ type: 'text',
632
+ text: JSON.stringify({
633
+ success: true,
634
+ destination: 'notion_mcp',
635
+ method: 'delegate',
636
+ delegateToNotion: true,
637
+ notionRequest: result.notionRequest,
638
+ message: `Notionへの追加はClaude Codeが直接notion-create-pagesツールを使用してください。`,
639
+ }, null, 2),
640
+ },
641
+ ],
642
+ };
643
+ }
644
+ return {
645
+ content: [
646
+ {
647
+ type: 'text',
648
+ text: JSON.stringify({
649
+ success: true,
650
+ destination: result.destination,
651
+ method: result.method,
652
+ reminderId: result.reminderId,
653
+ reminderUrl: result.reminderUrl ?? result.pageUrl,
654
+ message: result.destination === 'apple_reminders'
655
+ ? `Apple Remindersにリマインダーを作成しました: ${args.taskTitle}`
656
+ : `Notionにタスクを作成しました: ${args.taskTitle}`,
657
+ }, null, 2),
658
+ },
659
+ ],
660
+ };
661
+ }
662
+ return {
663
+ content: [
664
+ {
665
+ type: 'text',
666
+ text: JSON.stringify({
667
+ success: false,
668
+ destination: result.destination,
669
+ error: result.error,
670
+ fallbackText: result.fallbackText,
671
+ message: result.fallbackText
672
+ ? '自動作成に失敗しました。以下のテキストを手動でコピーしてください。'
673
+ : `リマインダー作成に失敗しました: ${result.error}`,
674
+ }, null, 2),
675
+ },
676
+ ],
677
+ };
678
+ }
679
+ catch (error) {
680
+ return {
681
+ content: [
682
+ {
683
+ type: 'text',
684
+ text: JSON.stringify({
685
+ error: true,
686
+ message: `リマインダー設定に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
687
+ }, null, 2),
688
+ },
689
+ ],
690
+ };
691
+ }
692
+ });
693
+ // find_available_slots
694
+ this.registerTool({
695
+ name: 'find_available_slots',
696
+ description: 'Find available time slots in the calendar for scheduling tasks.',
697
+ inputSchema: {
698
+ type: 'object',
699
+ properties: {
700
+ durationMinutes: {
701
+ type: 'number',
702
+ description: 'Required duration in minutes',
703
+ },
704
+ startDate: {
705
+ type: 'string',
706
+ description: 'Start date for search (ISO 8601 format)',
707
+ },
708
+ endDate: {
709
+ type: 'string',
710
+ description: 'End date for search (ISO 8601 format)',
711
+ },
712
+ preferDeepWork: {
713
+ type: 'boolean',
714
+ description: 'Prefer deep work time slots',
715
+ },
716
+ },
717
+ required: ['durationMinutes'],
718
+ },
719
+ }, async (args) => {
720
+ if (!this.config) {
721
+ return {
722
+ content: [
723
+ {
724
+ type: 'text',
725
+ text: JSON.stringify({
726
+ error: true,
727
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
728
+ }, null, 2),
729
+ },
730
+ ],
731
+ };
732
+ }
733
+ if (!this.calendarService) {
734
+ this.initializeServices(this.config);
735
+ }
736
+ try {
737
+ const durationMinutes = args.durationMinutes;
738
+ const startDate = args.startDate;
739
+ const endDate = args.endDate;
740
+ const preferDeepWork = args.preferDeepWork;
741
+ const platformInfo = await this.calendarService.detectPlatform();
742
+ const isAvailable = await this.calendarService.isAvailable();
743
+ if (!isAvailable) {
744
+ const searchStart = startDate ?? new Date().toISOString().split('T')[0];
745
+ const searchEnd = endDate ??
746
+ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
747
+ const manualPrompt = this.calendarService.generateManualInputPrompt(searchStart, searchEnd);
748
+ return {
749
+ content: [
750
+ {
751
+ type: 'text',
752
+ text: JSON.stringify({
753
+ success: false,
754
+ platform: platformInfo.platform,
755
+ method: platformInfo.recommendedMethod,
756
+ message: 'カレンダー統合がこのプラットフォームで利用できません。手動で予定を入力してください。',
757
+ manualPrompt,
758
+ }, null, 2),
759
+ },
760
+ ],
761
+ };
762
+ }
763
+ const searchStart = startDate ?? new Date().toISOString().split('T')[0];
764
+ const searchEnd = endDate ??
765
+ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
766
+ const events = await this.calendarService.fetchEvents(searchStart, searchEnd);
767
+ const workingHours = {
768
+ start: this.config.calendar.workingHours.start,
769
+ end: this.config.calendar.workingHours.end,
770
+ };
771
+ const slots = this.calendarService.findAvailableSlotsFromEvents(events, durationMinutes, workingHours, searchStart);
772
+ const suitabilityConfig = {
773
+ deepWorkDays: this.config.calendar.deepWorkDays,
774
+ meetingHeavyDays: this.config.calendar.meetingHeavyDays,
775
+ };
776
+ const scoredSlots = slots.map((slot) => this.calendarService.calculateSuitability(slot, suitabilityConfig));
777
+ const filteredSlots = preferDeepWork
778
+ ? scoredSlots.filter((s) => s.dayType === 'deep-work')
779
+ : scoredSlots;
780
+ const suitabilityOrder = { excellent: 0, good: 1, acceptable: 2 };
781
+ filteredSlots.sort((a, b) => suitabilityOrder[a.suitability] - suitabilityOrder[b.suitability]);
782
+ return {
783
+ content: [
784
+ {
785
+ type: 'text',
786
+ text: JSON.stringify({
787
+ success: true,
788
+ platform: platformInfo.platform,
789
+ method: platformInfo.recommendedMethod,
790
+ searchRange: { start: searchStart, end: searchEnd },
791
+ eventsFound: events.length,
792
+ slots: filteredSlots.slice(0, 10).map((slot) => ({
793
+ start: slot.start,
794
+ end: slot.end,
795
+ durationMinutes: slot.durationMinutes,
796
+ suitability: slot.suitability,
797
+ dayType: slot.dayType,
798
+ reason: slot.reason,
799
+ })),
800
+ message: filteredSlots.length > 0
801
+ ? `${filteredSlots.length}件の空き時間が見つかりました。`
802
+ : '指定した条件に合う空き時間が見つかりませんでした。',
803
+ }, null, 2),
804
+ },
805
+ ],
806
+ };
807
+ }
808
+ catch (error) {
809
+ return {
810
+ content: [
811
+ {
812
+ type: 'text',
813
+ text: JSON.stringify({
814
+ error: true,
815
+ message: `カレンダー検索に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
816
+ }, null, 2),
817
+ },
818
+ ],
819
+ };
820
+ }
821
+ });
822
+ // list_calendar_events
823
+ this.registerTool({
824
+ name: 'list_calendar_events',
825
+ description: 'List calendar events for a specified period. Returns events with details including calendar name and location.',
826
+ inputSchema: {
827
+ type: 'object',
828
+ properties: {
829
+ startDate: {
830
+ type: 'string',
831
+ description: 'Start date in ISO 8601 format (e.g., 2025-01-15)',
832
+ },
833
+ endDate: {
834
+ type: 'string',
835
+ description: 'End date in ISO 8601 format (e.g., 2025-01-20)',
836
+ },
837
+ calendarName: {
838
+ type: 'string',
839
+ description: 'Optional: filter events by calendar name',
840
+ },
841
+ },
842
+ required: ['startDate', 'endDate'],
843
+ },
844
+ }, async (args) => {
845
+ if (!this.config) {
846
+ return {
847
+ content: [
848
+ {
849
+ type: 'text',
850
+ text: JSON.stringify({
851
+ error: true,
852
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
853
+ }, null, 2),
854
+ },
855
+ ],
856
+ };
857
+ }
858
+ if (!this.calendarService) {
859
+ this.initializeServices(this.config);
860
+ }
861
+ try {
862
+ const startDate = args.startDate;
863
+ const endDate = args.endDate;
864
+ const calendarName = args.calendarName;
865
+ const platformInfo = await this.calendarService.detectPlatform();
866
+ const isAvailable = await this.calendarService.isAvailable();
867
+ if (!isAvailable) {
868
+ return {
869
+ content: [
870
+ {
871
+ type: 'text',
872
+ text: JSON.stringify({
873
+ success: false,
874
+ platform: platformInfo.platform,
875
+ method: platformInfo.recommendedMethod,
876
+ message: 'カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。',
877
+ }, null, 2),
878
+ },
879
+ ],
880
+ };
881
+ }
882
+ const result = await this.calendarService.listEvents({
883
+ startDate,
884
+ endDate,
885
+ calendarName,
886
+ });
887
+ return {
888
+ content: [
889
+ {
890
+ type: 'text',
891
+ text: JSON.stringify({
892
+ success: true,
893
+ platform: platformInfo.platform,
894
+ method: platformInfo.recommendedMethod,
895
+ events: result.events.map((event) => ({
896
+ id: event.id,
897
+ title: event.title,
898
+ start: event.start,
899
+ end: event.end,
900
+ isAllDay: event.isAllDay,
901
+ calendar: event.calendar,
902
+ location: event.location,
903
+ })),
904
+ period: result.period,
905
+ totalEvents: result.totalEvents,
906
+ message: result.totalEvents > 0
907
+ ? `${result.totalEvents}件のイベントが見つかりました。`
908
+ : '指定した期間にイベントが見つかりませんでした。',
909
+ }, null, 2),
910
+ },
911
+ ],
912
+ };
913
+ }
914
+ catch (error) {
915
+ return {
916
+ content: [
917
+ {
918
+ type: 'text',
919
+ text: JSON.stringify({
920
+ error: true,
921
+ message: `カレンダーイベントの取得に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
922
+ }, null, 2),
923
+ },
924
+ ],
925
+ };
926
+ }
927
+ });
928
+ // sync_to_notion
929
+ this.registerTool({
930
+ name: 'sync_to_notion',
931
+ description: 'Sync a task to Notion database for long-term tracking.',
932
+ inputSchema: {
933
+ type: 'object',
934
+ properties: {
935
+ taskTitle: { type: 'string', description: 'Title of the task' },
936
+ description: { type: 'string', description: 'Task description' },
937
+ priority: {
938
+ type: 'string',
939
+ enum: ['P0', 'P1', 'P2', 'P3'],
940
+ description: 'Task priority',
941
+ },
942
+ dueDate: { type: 'string', description: 'Due date (ISO 8601 format)' },
943
+ stakeholders: {
944
+ type: 'array',
945
+ items: { type: 'string' },
946
+ description: 'List of stakeholders',
947
+ },
948
+ estimatedMinutes: {
949
+ type: 'number',
950
+ description: 'Estimated duration in minutes',
951
+ },
952
+ },
953
+ required: ['taskTitle'],
954
+ },
955
+ }, async (args) => {
956
+ if (!this.config) {
957
+ return {
958
+ content: [
959
+ {
960
+ type: 'text',
961
+ text: JSON.stringify({
962
+ error: true,
963
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
964
+ }, null, 2),
965
+ },
966
+ ],
967
+ };
968
+ }
969
+ if (!this.config.integrations.notion.enabled) {
970
+ return {
971
+ content: [
972
+ {
973
+ type: 'text',
974
+ text: JSON.stringify({
975
+ error: true,
976
+ message: 'Notion統合が有効になっていません。update_configでNotion設定を更新してください。',
977
+ }, null, 2),
978
+ },
979
+ ],
980
+ };
981
+ }
982
+ if (!this.notionService) {
983
+ this.initializeServices(this.config);
984
+ }
985
+ try {
986
+ const taskTitle = args.taskTitle;
987
+ const description = args.description;
988
+ const priority = args.priority;
989
+ const dueDate = args.dueDate;
990
+ const stakeholders = args.stakeholders;
991
+ const estimatedMinutes = args.estimatedMinutes;
992
+ const isAvailable = await this.notionService.isAvailable();
993
+ const properties = this.notionService.buildNotionProperties({
994
+ title: taskTitle,
995
+ priority,
996
+ deadline: dueDate,
997
+ stakeholders,
998
+ estimatedMinutes,
999
+ description,
1000
+ });
1001
+ if (!isAvailable) {
1002
+ const fallbackText = this.notionService.generateFallbackTemplate({
1003
+ title: taskTitle,
1004
+ priority,
1005
+ deadline: dueDate,
1006
+ stakeholders,
1007
+ estimatedMinutes,
1008
+ description,
1009
+ });
1010
+ return {
1011
+ content: [
1012
+ {
1013
+ type: 'text',
1014
+ text: JSON.stringify({
1015
+ success: false,
1016
+ method: 'fallback',
1017
+ message: 'Notion MCP統合が利用できません。以下のテンプレートを手動でNotionにコピーしてください。',
1018
+ fallbackText,
1019
+ task: {
1020
+ taskTitle,
1021
+ priority: priority ?? 'P3',
1022
+ dueDate,
1023
+ stakeholders: stakeholders ?? [],
1024
+ estimatedMinutes,
1025
+ },
1026
+ }, null, 2),
1027
+ },
1028
+ ],
1029
+ };
1030
+ }
1031
+ const result = await this.notionService.createPage({
1032
+ databaseId: this.config.integrations.notion.databaseId,
1033
+ title: taskTitle,
1034
+ properties,
1035
+ });
1036
+ if (result.success) {
1037
+ return {
1038
+ content: [
1039
+ {
1040
+ type: 'text',
1041
+ text: JSON.stringify({
1042
+ success: true,
1043
+ method: 'mcp',
1044
+ pageId: result.pageId,
1045
+ pageUrl: result.pageUrl,
1046
+ message: `Notionにタスクを同期しました: ${taskTitle}`,
1047
+ }, null, 2),
1048
+ },
1049
+ ],
1050
+ };
1051
+ }
1052
+ const fallbackText = this.notionService.generateFallbackTemplate({
1053
+ title: taskTitle,
1054
+ priority,
1055
+ deadline: dueDate,
1056
+ stakeholders,
1057
+ estimatedMinutes,
1058
+ description,
1059
+ });
1060
+ return {
1061
+ content: [
1062
+ {
1063
+ type: 'text',
1064
+ text: JSON.stringify({
1065
+ success: false,
1066
+ method: 'fallback',
1067
+ error: result.error,
1068
+ message: 'Notion MCP呼び出しに失敗しました。以下のテンプレートを手動でコピーしてください。',
1069
+ fallbackText,
1070
+ }, null, 2),
1071
+ },
1072
+ ],
1073
+ };
1074
+ }
1075
+ catch (error) {
1076
+ return {
1077
+ content: [
1078
+ {
1079
+ type: 'text',
1080
+ text: JSON.stringify({
1081
+ error: true,
1082
+ message: `Notion同期に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1083
+ }, null, 2),
1084
+ },
1085
+ ],
1086
+ };
1087
+ }
1088
+ });
1089
+ // update_config
1090
+ this.registerTool({
1091
+ name: 'update_config',
1092
+ description: 'Update sage configuration settings.',
1093
+ inputSchema: {
1094
+ type: 'object',
1095
+ properties: {
1096
+ section: {
1097
+ type: 'string',
1098
+ enum: [
1099
+ 'user',
1100
+ 'calendar',
1101
+ 'priorityRules',
1102
+ 'integrations',
1103
+ 'team',
1104
+ 'preferences',
1105
+ ],
1106
+ description: 'Configuration section to update',
1107
+ },
1108
+ updates: {
1109
+ type: 'object',
1110
+ description: 'Key-value pairs to update',
1111
+ },
1112
+ },
1113
+ required: ['section', 'updates'],
1114
+ },
1115
+ }, async (args) => {
1116
+ if (!this.config) {
1117
+ return {
1118
+ content: [
1119
+ {
1120
+ type: 'text',
1121
+ text: JSON.stringify({
1122
+ error: true,
1123
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1124
+ }, null, 2),
1125
+ },
1126
+ ],
1127
+ };
1128
+ }
1129
+ try {
1130
+ const section = args.section;
1131
+ const updates = args.updates;
1132
+ const validationResult = this.validateConfigUpdate(section, updates);
1133
+ if (!validationResult.valid) {
1134
+ return {
1135
+ content: [
1136
+ {
1137
+ type: 'text',
1138
+ text: JSON.stringify({
1139
+ error: true,
1140
+ message: `設定の検証に失敗しました: ${validationResult.error}`,
1141
+ invalidFields: validationResult.invalidFields,
1142
+ }, null, 2),
1143
+ },
1144
+ ],
1145
+ };
1146
+ }
1147
+ const updatedConfig = this.applyConfigUpdates(this.config, section, updates);
1148
+ await ConfigLoader.save(updatedConfig);
1149
+ this.config = updatedConfig;
1150
+ if (section === 'integrations') {
1151
+ this.initializeServices(this.config);
1152
+ }
1153
+ return {
1154
+ content: [
1155
+ {
1156
+ type: 'text',
1157
+ text: JSON.stringify({
1158
+ success: true,
1159
+ section,
1160
+ updatedFields: Object.keys(updates),
1161
+ message: `設定を更新しました: ${section}`,
1162
+ }, null, 2),
1163
+ },
1164
+ ],
1165
+ };
1166
+ }
1167
+ catch (error) {
1168
+ return {
1169
+ content: [
1170
+ {
1171
+ type: 'text',
1172
+ text: JSON.stringify({
1173
+ error: true,
1174
+ message: `設定の更新に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1175
+ }, null, 2),
1176
+ },
1177
+ ],
1178
+ };
1179
+ }
1180
+ });
1181
+ // list_todos
1182
+ this.registerTool({
1183
+ name: 'list_todos',
1184
+ description: 'List TODO items from Apple Reminders and Notion with optional filtering.',
1185
+ inputSchema: {
1186
+ type: 'object',
1187
+ properties: {
1188
+ priority: {
1189
+ type: 'array',
1190
+ items: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
1191
+ description: 'Filter by priority levels',
1192
+ },
1193
+ status: {
1194
+ type: 'array',
1195
+ items: {
1196
+ type: 'string',
1197
+ enum: ['not_started', 'in_progress', 'completed', 'cancelled'],
1198
+ },
1199
+ description: 'Filter by status',
1200
+ },
1201
+ source: {
1202
+ type: 'array',
1203
+ items: { type: 'string', enum: ['apple_reminders', 'notion', 'manual'] },
1204
+ description: 'Filter by source',
1205
+ },
1206
+ todayOnly: { type: 'boolean', description: 'Show only tasks due today' },
1207
+ tags: {
1208
+ type: 'array',
1209
+ items: { type: 'string' },
1210
+ description: 'Filter by tags',
1211
+ },
1212
+ },
1213
+ },
1214
+ }, async (args) => {
1215
+ if (!this.config) {
1216
+ return {
1217
+ content: [
1218
+ {
1219
+ type: 'text',
1220
+ text: JSON.stringify({
1221
+ error: true,
1222
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1223
+ }, null, 2),
1224
+ },
1225
+ ],
1226
+ };
1227
+ }
1228
+ if (!this.todoListManager) {
1229
+ this.initializeServices(this.config);
1230
+ }
1231
+ try {
1232
+ const priority = args.priority;
1233
+ const status = args.status;
1234
+ const source = args.source;
1235
+ const todayOnly = args.todayOnly;
1236
+ const tags = args.tags;
1237
+ let todos;
1238
+ if (todayOnly) {
1239
+ todos = await this.todoListManager.getTodaysTasks();
1240
+ }
1241
+ else {
1242
+ todos = await this.todoListManager.listTodos({
1243
+ priority,
1244
+ status,
1245
+ source,
1246
+ tags,
1247
+ });
1248
+ }
1249
+ const formattedTodos = todos.map((todo) => ({
1250
+ id: todo.id,
1251
+ title: todo.title,
1252
+ priority: todo.priority,
1253
+ status: todo.status,
1254
+ dueDate: todo.dueDate,
1255
+ source: todo.source,
1256
+ tags: todo.tags,
1257
+ estimatedMinutes: todo.estimatedMinutes,
1258
+ stakeholders: todo.stakeholders,
1259
+ }));
1260
+ return {
1261
+ content: [
1262
+ {
1263
+ type: 'text',
1264
+ text: JSON.stringify({
1265
+ success: true,
1266
+ totalCount: todos.length,
1267
+ todos: formattedTodos,
1268
+ message: todos.length > 0
1269
+ ? `${todos.length}件のタスクが見つかりました。`
1270
+ : 'タスクが見つかりませんでした。',
1271
+ filters: {
1272
+ priority,
1273
+ status,
1274
+ source,
1275
+ todayOnly,
1276
+ tags,
1277
+ },
1278
+ }, null, 2),
1279
+ },
1280
+ ],
1281
+ };
1282
+ }
1283
+ catch (error) {
1284
+ return {
1285
+ content: [
1286
+ {
1287
+ type: 'text',
1288
+ text: JSON.stringify({
1289
+ error: true,
1290
+ message: `TODOリストの取得に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1291
+ }, null, 2),
1292
+ },
1293
+ ],
1294
+ };
1295
+ }
1296
+ });
1297
+ // update_task_status
1298
+ this.registerTool({
1299
+ name: 'update_task_status',
1300
+ description: 'Update the status of a task in Apple Reminders or Notion.',
1301
+ inputSchema: {
1302
+ type: 'object',
1303
+ properties: {
1304
+ taskId: { type: 'string', description: 'ID of the task to update' },
1305
+ status: {
1306
+ type: 'string',
1307
+ enum: ['not_started', 'in_progress', 'completed', 'cancelled'],
1308
+ description: 'New status for the task',
1309
+ },
1310
+ source: {
1311
+ type: 'string',
1312
+ enum: ['apple_reminders', 'notion', 'manual'],
1313
+ description: 'Source of the task',
1314
+ },
1315
+ syncAcrossSources: {
1316
+ type: 'boolean',
1317
+ description: 'Whether to sync the status across all sources',
1318
+ },
1319
+ },
1320
+ required: ['taskId', 'status', 'source'],
1321
+ },
1322
+ }, async (args) => {
1323
+ if (!this.config) {
1324
+ return {
1325
+ content: [
1326
+ {
1327
+ type: 'text',
1328
+ text: JSON.stringify({
1329
+ error: true,
1330
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1331
+ }, null, 2),
1332
+ },
1333
+ ],
1334
+ };
1335
+ }
1336
+ if (!this.todoListManager) {
1337
+ this.initializeServices(this.config);
1338
+ }
1339
+ try {
1340
+ const taskId = args.taskId;
1341
+ const status = args.status;
1342
+ const source = args.source;
1343
+ const syncAcrossSources = args.syncAcrossSources;
1344
+ const result = await this.todoListManager.updateTaskStatus(taskId, status, source);
1345
+ if (!result.success) {
1346
+ return {
1347
+ content: [
1348
+ {
1349
+ type: 'text',
1350
+ text: JSON.stringify({
1351
+ success: false,
1352
+ taskId,
1353
+ error: result.error,
1354
+ message: `タスクステータスの更新に失敗しました: ${result.error}`,
1355
+ }, null, 2),
1356
+ },
1357
+ ],
1358
+ };
1359
+ }
1360
+ let syncResult;
1361
+ if (syncAcrossSources) {
1362
+ syncResult = await this.todoListManager.syncTaskAcrossSources(taskId);
1363
+ }
1364
+ return {
1365
+ content: [
1366
+ {
1367
+ type: 'text',
1368
+ text: JSON.stringify({
1369
+ success: true,
1370
+ taskId,
1371
+ newStatus: status,
1372
+ updatedFields: result.updatedFields,
1373
+ syncedSources: result.syncedSources,
1374
+ syncResult: syncAcrossSources ? syncResult : undefined,
1375
+ message: `タスクのステータスを「${status}」に更新しました。`,
1376
+ }, null, 2),
1377
+ },
1378
+ ],
1379
+ };
1380
+ }
1381
+ catch (error) {
1382
+ return {
1383
+ content: [
1384
+ {
1385
+ type: 'text',
1386
+ text: JSON.stringify({
1387
+ error: true,
1388
+ message: `タスクステータスの更新に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1389
+ }, null, 2),
1390
+ },
1391
+ ],
1392
+ };
1393
+ }
1394
+ });
1395
+ // sync_tasks
1396
+ this.registerTool({
1397
+ name: 'sync_tasks',
1398
+ description: 'Synchronize tasks between Apple Reminders and Notion, detecting and resolving conflicts.',
1399
+ inputSchema: {
1400
+ type: 'object',
1401
+ properties: {},
1402
+ },
1403
+ }, async () => {
1404
+ if (!this.config) {
1405
+ return {
1406
+ content: [
1407
+ {
1408
+ type: 'text',
1409
+ text: JSON.stringify({
1410
+ error: true,
1411
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1412
+ }, null, 2),
1413
+ },
1414
+ ],
1415
+ };
1416
+ }
1417
+ if (!this.taskSynchronizer) {
1418
+ this.initializeServices(this.config);
1419
+ }
1420
+ try {
1421
+ const result = await this.taskSynchronizer.syncAllTasks();
1422
+ return {
1423
+ content: [
1424
+ {
1425
+ type: 'text',
1426
+ text: JSON.stringify({
1427
+ success: true,
1428
+ totalTasks: result.totalTasks,
1429
+ syncedTasks: result.syncedTasks,
1430
+ conflicts: result.conflicts,
1431
+ errors: result.errors,
1432
+ durationMs: result.duration,
1433
+ message: result.conflicts.length > 0
1434
+ ? `${result.syncedTasks}件のタスクを同期しました。${result.conflicts.length}件の競合が検出されました。`
1435
+ : `${result.syncedTasks}件のタスクを同期しました。`,
1436
+ }, null, 2),
1437
+ },
1438
+ ],
1439
+ };
1440
+ }
1441
+ catch (error) {
1442
+ return {
1443
+ content: [
1444
+ {
1445
+ type: 'text',
1446
+ text: JSON.stringify({
1447
+ error: true,
1448
+ message: `タスク同期に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1449
+ }, null, 2),
1450
+ },
1451
+ ],
1452
+ };
1453
+ }
1454
+ });
1455
+ // detect_duplicates
1456
+ this.registerTool({
1457
+ name: 'detect_duplicates',
1458
+ description: 'Detect duplicate tasks between Apple Reminders and Notion.',
1459
+ inputSchema: {
1460
+ type: 'object',
1461
+ properties: {
1462
+ autoMerge: {
1463
+ type: 'boolean',
1464
+ description: 'Whether to automatically merge high-confidence duplicates',
1465
+ },
1466
+ },
1467
+ },
1468
+ }, async (args) => {
1469
+ if (!this.config) {
1470
+ return {
1471
+ content: [
1472
+ {
1473
+ type: 'text',
1474
+ text: JSON.stringify({
1475
+ error: true,
1476
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1477
+ }, null, 2),
1478
+ },
1479
+ ],
1480
+ };
1481
+ }
1482
+ if (!this.taskSynchronizer) {
1483
+ this.initializeServices(this.config);
1484
+ }
1485
+ try {
1486
+ const autoMerge = args.autoMerge;
1487
+ const duplicates = await this.taskSynchronizer.detectDuplicates();
1488
+ const formattedDuplicates = duplicates.map((d) => ({
1489
+ tasks: d.tasks.map((t) => ({
1490
+ id: t.id,
1491
+ title: t.title,
1492
+ source: t.source,
1493
+ status: t.status,
1494
+ priority: t.priority,
1495
+ })),
1496
+ similarity: Math.round(d.similarity * 100),
1497
+ confidence: d.confidence,
1498
+ suggestedMerge: {
1499
+ title: d.suggestedMerge.title,
1500
+ priority: d.suggestedMerge.priority,
1501
+ status: d.suggestedMerge.status,
1502
+ tags: d.suggestedMerge.tags,
1503
+ },
1504
+ }));
1505
+ let mergeResults;
1506
+ if (autoMerge) {
1507
+ const highConfidenceDuplicates = duplicates.filter((d) => d.confidence === 'high');
1508
+ if (highConfidenceDuplicates.length > 0) {
1509
+ mergeResults =
1510
+ await this.taskSynchronizer.mergeDuplicates(highConfidenceDuplicates);
1511
+ }
1512
+ }
1513
+ return {
1514
+ content: [
1515
+ {
1516
+ type: 'text',
1517
+ text: JSON.stringify({
1518
+ success: true,
1519
+ duplicatesFound: duplicates.length,
1520
+ duplicates: formattedDuplicates,
1521
+ mergeResults: autoMerge ? mergeResults : undefined,
1522
+ message: duplicates.length > 0
1523
+ ? `${duplicates.length}件の重複タスクが検出されました。`
1524
+ : '重複タスクは見つかりませんでした。',
1525
+ }, null, 2),
1526
+ },
1527
+ ],
1528
+ };
1529
+ }
1530
+ catch (error) {
1531
+ return {
1532
+ content: [
1533
+ {
1534
+ type: 'text',
1535
+ text: JSON.stringify({
1536
+ error: true,
1537
+ message: `重複検出に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1538
+ }, null, 2),
1539
+ },
1540
+ ],
1541
+ };
1542
+ }
1543
+ });
1544
+ // respond_to_calendar_event
1545
+ this.registerTool({
1546
+ name: 'respond_to_calendar_event',
1547
+ description: 'Respond to a calendar event with accept, decline, or tentative. Use this to RSVP to meeting invitations.',
1548
+ inputSchema: {
1549
+ type: 'object',
1550
+ properties: {
1551
+ eventId: {
1552
+ type: 'string',
1553
+ description: 'The ID of the calendar event to respond to',
1554
+ },
1555
+ response: {
1556
+ type: 'string',
1557
+ enum: ['accept', 'decline', 'tentative'],
1558
+ description: 'Response type: accept (承諾), decline (辞退), or tentative (仮承諾)',
1559
+ },
1560
+ comment: {
1561
+ type: 'string',
1562
+ description: "Optional comment to include with the response (e.g., '年末年始休暇のため')",
1563
+ },
1564
+ },
1565
+ required: ['eventId', 'response'],
1566
+ },
1567
+ }, async (args) => {
1568
+ if (!this.config) {
1569
+ return {
1570
+ content: [
1571
+ {
1572
+ type: 'text',
1573
+ text: JSON.stringify({
1574
+ error: true,
1575
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1576
+ }, null, 2),
1577
+ },
1578
+ ],
1579
+ };
1580
+ }
1581
+ if (!this.calendarEventResponseService) {
1582
+ this.initializeServices(this.config);
1583
+ }
1584
+ try {
1585
+ const eventId = args.eventId;
1586
+ const response = args.response;
1587
+ const comment = args.comment;
1588
+ const isAvailable = await this.calendarEventResponseService.isEventKitAvailable();
1589
+ if (!isAvailable) {
1590
+ return {
1591
+ content: [
1592
+ {
1593
+ type: 'text',
1594
+ text: JSON.stringify({
1595
+ success: false,
1596
+ message: 'カレンダーイベント返信機能はmacOSでのみ利用可能です。',
1597
+ }, null, 2),
1598
+ },
1599
+ ],
1600
+ };
1601
+ }
1602
+ const result = await this.calendarEventResponseService.respondToEvent({
1603
+ eventId,
1604
+ response,
1605
+ comment,
1606
+ });
1607
+ if (result.success) {
1608
+ return {
1609
+ content: [
1610
+ {
1611
+ type: 'text',
1612
+ text: JSON.stringify({
1613
+ success: true,
1614
+ eventId: result.eventId,
1615
+ eventTitle: result.eventTitle,
1616
+ newStatus: result.newStatus,
1617
+ method: result.method,
1618
+ instanceOnly: result.instanceOnly,
1619
+ message: result.message,
1620
+ }, null, 2),
1621
+ },
1622
+ ],
1623
+ };
1624
+ }
1625
+ return {
1626
+ content: [
1627
+ {
1628
+ type: 'text',
1629
+ text: JSON.stringify({
1630
+ success: false,
1631
+ eventId: result.eventId,
1632
+ eventTitle: result.eventTitle,
1633
+ skipped: result.skipped,
1634
+ reason: result.reason,
1635
+ error: result.error,
1636
+ message: result.skipped
1637
+ ? `イベントをスキップしました: ${result.reason}`
1638
+ : `イベント返信に失敗しました: ${result.error}`,
1639
+ }, null, 2),
1640
+ },
1641
+ ],
1642
+ };
1643
+ }
1644
+ catch (error) {
1645
+ return {
1646
+ content: [
1647
+ {
1648
+ type: 'text',
1649
+ text: JSON.stringify({
1650
+ error: true,
1651
+ message: `カレンダーイベント返信に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1652
+ }, null, 2),
1653
+ },
1654
+ ],
1655
+ };
1656
+ }
1657
+ });
1658
+ // respond_to_calendar_events_batch
1659
+ this.registerTool({
1660
+ name: 'respond_to_calendar_events_batch',
1661
+ description: 'Respond to multiple calendar events at once. Useful for declining all events during vacation or leave periods.',
1662
+ inputSchema: {
1663
+ type: 'object',
1664
+ properties: {
1665
+ eventIds: {
1666
+ type: 'array',
1667
+ items: { type: 'string' },
1668
+ description: 'Array of event IDs to respond to',
1669
+ },
1670
+ response: {
1671
+ type: 'string',
1672
+ enum: ['accept', 'decline', 'tentative'],
1673
+ description: 'Response type: accept (承諾), decline (辞退), or tentative (仮承諾)',
1674
+ },
1675
+ comment: {
1676
+ type: 'string',
1677
+ description: "Optional comment to include with all responses (e.g., '年末年始休暇のため')",
1678
+ },
1679
+ },
1680
+ required: ['eventIds', 'response'],
1681
+ },
1682
+ }, async (args) => {
1683
+ if (!this.config) {
1684
+ return {
1685
+ content: [
1686
+ {
1687
+ type: 'text',
1688
+ text: JSON.stringify({
1689
+ error: true,
1690
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1691
+ }, null, 2),
1692
+ },
1693
+ ],
1694
+ };
1695
+ }
1696
+ if (!this.calendarEventResponseService) {
1697
+ this.initializeServices(this.config);
1698
+ }
1699
+ try {
1700
+ const eventIds = args.eventIds;
1701
+ const response = args.response;
1702
+ const comment = args.comment;
1703
+ const isAvailable = await this.calendarEventResponseService.isEventKitAvailable();
1704
+ if (!isAvailable) {
1705
+ return {
1706
+ content: [
1707
+ {
1708
+ type: 'text',
1709
+ text: JSON.stringify({
1710
+ success: false,
1711
+ message: 'カレンダーイベント返信機能はmacOSでのみ利用可能です。',
1712
+ }, null, 2),
1713
+ },
1714
+ ],
1715
+ };
1716
+ }
1717
+ const result = await this.calendarEventResponseService.respondToEventsBatch({
1718
+ eventIds,
1719
+ response,
1720
+ comment,
1721
+ });
1722
+ return {
1723
+ content: [
1724
+ {
1725
+ type: 'text',
1726
+ text: JSON.stringify({
1727
+ success: result.success,
1728
+ summary: result.summary,
1729
+ details: {
1730
+ succeeded: result.details.succeeded,
1731
+ skipped: result.details.skipped,
1732
+ failed: result.details.failed,
1733
+ },
1734
+ message: result.message,
1735
+ }, null, 2),
1736
+ },
1737
+ ],
1738
+ };
1739
+ }
1740
+ catch (error) {
1741
+ return {
1742
+ content: [
1743
+ {
1744
+ type: 'text',
1745
+ text: JSON.stringify({
1746
+ error: true,
1747
+ message: `カレンダーイベント一括返信に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1748
+ }, null, 2),
1749
+ },
1750
+ ],
1751
+ };
1752
+ }
1753
+ });
1754
+ // create_calendar_event
1755
+ this.registerTool({
1756
+ name: 'create_calendar_event',
1757
+ description: 'Create a new calendar event with optional location, notes, and alarms.',
1758
+ inputSchema: {
1759
+ type: 'object',
1760
+ properties: {
1761
+ title: { type: 'string', description: 'Event title' },
1762
+ startDate: {
1763
+ type: 'string',
1764
+ description: 'Start date/time in ISO 8601 format (e.g., 2025-01-15T10:00:00+09:00)',
1765
+ },
1766
+ endDate: {
1767
+ type: 'string',
1768
+ description: 'End date/time in ISO 8601 format (e.g., 2025-01-15T11:00:00+09:00)',
1769
+ },
1770
+ location: { type: 'string', description: 'Event location' },
1771
+ notes: { type: 'string', description: 'Event notes/description' },
1772
+ calendarName: {
1773
+ type: 'string',
1774
+ description: 'Calendar name to create the event in (uses default if not specified)',
1775
+ },
1776
+ alarms: {
1777
+ type: 'array',
1778
+ items: { type: 'string' },
1779
+ description: "Optional: Override default alarms with custom settings (e.g., ['-15m', '-1h']). If omitted, calendar's default alarm settings apply.",
1780
+ },
1781
+ },
1782
+ required: ['title', 'startDate', 'endDate'],
1783
+ },
1784
+ }, async (args) => {
1785
+ if (!this.config) {
1786
+ return {
1787
+ content: [
1788
+ {
1789
+ type: 'text',
1790
+ text: JSON.stringify({
1791
+ error: true,
1792
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1793
+ }, null, 2),
1794
+ },
1795
+ ],
1796
+ };
1797
+ }
1798
+ if (!this.calendarEventCreatorService) {
1799
+ this.initializeServices(this.config);
1800
+ }
1801
+ try {
1802
+ const title = args.title;
1803
+ const startDate = args.startDate;
1804
+ const endDate = args.endDate;
1805
+ const location = args.location;
1806
+ const notes = args.notes;
1807
+ const calendarName = args.calendarName;
1808
+ const alarms = args.alarms;
1809
+ const isAvailable = await this.calendarEventCreatorService.isEventKitAvailable();
1810
+ if (!isAvailable) {
1811
+ return {
1812
+ content: [
1813
+ {
1814
+ type: 'text',
1815
+ text: JSON.stringify({
1816
+ success: false,
1817
+ message: 'カレンダーイベント作成機能はmacOSでのみ利用可能です。',
1818
+ }, null, 2),
1819
+ },
1820
+ ],
1821
+ };
1822
+ }
1823
+ const result = await this.calendarEventCreatorService.createEvent({
1824
+ title,
1825
+ startDate,
1826
+ endDate,
1827
+ location,
1828
+ notes,
1829
+ calendarName,
1830
+ alarms,
1831
+ });
1832
+ if (result.success) {
1833
+ return {
1834
+ content: [
1835
+ {
1836
+ type: 'text',
1837
+ text: JSON.stringify({
1838
+ success: true,
1839
+ eventId: result.eventId,
1840
+ title: result.title,
1841
+ startDate: result.startDate,
1842
+ endDate: result.endDate,
1843
+ calendarName: result.calendarName,
1844
+ isAllDay: result.isAllDay,
1845
+ message: result.message,
1846
+ }, null, 2),
1847
+ },
1848
+ ],
1849
+ };
1850
+ }
1851
+ return {
1852
+ content: [
1853
+ {
1854
+ type: 'text',
1855
+ text: JSON.stringify({
1856
+ success: false,
1857
+ error: result.error,
1858
+ message: result.message,
1859
+ }, null, 2),
1860
+ },
1861
+ ],
1862
+ };
1863
+ }
1864
+ catch (error) {
1865
+ return {
1866
+ content: [
1867
+ {
1868
+ type: 'text',
1869
+ text: JSON.stringify({
1870
+ error: true,
1871
+ message: `カレンダーイベント作成に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1872
+ }, null, 2),
1873
+ },
1874
+ ],
1875
+ };
1876
+ }
1877
+ });
1878
+ // delete_calendar_event
1879
+ this.registerTool({
1880
+ name: 'delete_calendar_event',
1881
+ description: 'Delete a calendar event by its ID.',
1882
+ inputSchema: {
1883
+ type: 'object',
1884
+ properties: {
1885
+ eventId: {
1886
+ type: 'string',
1887
+ description: 'Event ID (UUID or full ID from list_calendar_events)',
1888
+ },
1889
+ calendarName: {
1890
+ type: 'string',
1891
+ description: 'Calendar name (searches all calendars if not specified)',
1892
+ },
1893
+ },
1894
+ required: ['eventId'],
1895
+ },
1896
+ }, async (args) => {
1897
+ if (!this.config) {
1898
+ return {
1899
+ content: [
1900
+ {
1901
+ type: 'text',
1902
+ text: JSON.stringify({
1903
+ error: true,
1904
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
1905
+ }, null, 2),
1906
+ },
1907
+ ],
1908
+ };
1909
+ }
1910
+ if (!this.calendarEventDeleterService) {
1911
+ this.initializeServices(this.config);
1912
+ }
1913
+ try {
1914
+ const eventId = args.eventId;
1915
+ const calendarName = args.calendarName;
1916
+ const isAvailable = await this.calendarEventDeleterService.isEventKitAvailable();
1917
+ if (!isAvailable) {
1918
+ return {
1919
+ content: [
1920
+ {
1921
+ type: 'text',
1922
+ text: JSON.stringify({
1923
+ success: false,
1924
+ message: 'カレンダーイベント削除機能はmacOSでのみ利用可能です。',
1925
+ }, null, 2),
1926
+ },
1927
+ ],
1928
+ };
1929
+ }
1930
+ const result = await this.calendarEventDeleterService.deleteEvent({
1931
+ eventId,
1932
+ calendarName,
1933
+ });
1934
+ if (result.success) {
1935
+ return {
1936
+ content: [
1937
+ {
1938
+ type: 'text',
1939
+ text: JSON.stringify({
1940
+ success: true,
1941
+ eventId: result.eventId,
1942
+ title: result.title,
1943
+ calendarName: result.calendarName,
1944
+ message: result.message,
1945
+ }, null, 2),
1946
+ },
1947
+ ],
1948
+ };
1949
+ }
1950
+ return {
1951
+ content: [
1952
+ {
1953
+ type: 'text',
1954
+ text: JSON.stringify({
1955
+ success: false,
1956
+ eventId: result.eventId,
1957
+ error: result.error,
1958
+ message: result.message,
1959
+ }, null, 2),
1960
+ },
1961
+ ],
1962
+ };
1963
+ }
1964
+ catch (error) {
1965
+ return {
1966
+ content: [
1967
+ {
1968
+ type: 'text',
1969
+ text: JSON.stringify({
1970
+ error: true,
1971
+ message: `カレンダーイベント削除に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
1972
+ }, null, 2),
1973
+ },
1974
+ ],
1975
+ };
1976
+ }
1977
+ });
1978
+ // delete_calendar_events_batch
1979
+ this.registerTool({
1980
+ name: 'delete_calendar_events_batch',
1981
+ description: 'Delete multiple calendar events by their IDs.',
1982
+ inputSchema: {
1983
+ type: 'object',
1984
+ properties: {
1985
+ eventIds: {
1986
+ type: 'array',
1987
+ items: { type: 'string' },
1988
+ description: 'Array of event IDs to delete',
1989
+ },
1990
+ calendarName: {
1991
+ type: 'string',
1992
+ description: 'Calendar name (searches all calendars if not specified)',
1993
+ },
1994
+ },
1995
+ required: ['eventIds'],
1996
+ },
1997
+ }, async (args) => {
1998
+ if (!this.config) {
1999
+ return {
2000
+ content: [
2001
+ {
2002
+ type: 'text',
2003
+ text: JSON.stringify({
2004
+ error: true,
2005
+ message: 'sageが設定されていません。check_setup_statusを実行してください。',
2006
+ }, null, 2),
2007
+ },
2008
+ ],
2009
+ };
2010
+ }
2011
+ if (!this.calendarEventDeleterService) {
2012
+ this.initializeServices(this.config);
2013
+ }
2014
+ try {
2015
+ const eventIds = args.eventIds;
2016
+ const calendarName = args.calendarName;
2017
+ const isAvailable = await this.calendarEventDeleterService.isEventKitAvailable();
2018
+ if (!isAvailable) {
2019
+ return {
2020
+ content: [
2021
+ {
2022
+ type: 'text',
2023
+ text: JSON.stringify({
2024
+ success: false,
2025
+ message: 'カレンダーイベント削除機能はmacOSでのみ利用可能です。',
2026
+ }, null, 2),
2027
+ },
2028
+ ],
2029
+ };
2030
+ }
2031
+ const result = await this.calendarEventDeleterService.deleteEventsBatch({
2032
+ eventIds,
2033
+ calendarName,
2034
+ });
2035
+ return {
2036
+ content: [
2037
+ {
2038
+ type: 'text',
2039
+ text: JSON.stringify({
2040
+ success: result.success,
2041
+ totalCount: result.totalCount,
2042
+ successCount: result.successCount,
2043
+ failedCount: result.failedCount,
2044
+ results: result.results.map((r) => ({
2045
+ eventId: r.eventId,
2046
+ success: r.success,
2047
+ title: r.title,
2048
+ calendarName: r.calendarName,
2049
+ error: r.error,
2050
+ })),
2051
+ message: result.message,
2052
+ }, null, 2),
2053
+ },
2054
+ ],
2055
+ };
2056
+ }
2057
+ catch (error) {
2058
+ return {
2059
+ content: [
2060
+ {
2061
+ type: 'text',
2062
+ text: JSON.stringify({
2063
+ error: true,
2064
+ message: `カレンダーイベント一括削除に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`,
2065
+ }, null, 2),
2066
+ },
2067
+ ],
2068
+ };
2069
+ }
2070
+ });
2071
+ }
2072
+ /**
2073
+ * Register a tool
2074
+ */
2075
+ registerTool(definition, handler) {
2076
+ this.tools.set(definition.name, { definition, handler });
2077
+ }
2078
+ /**
2079
+ * Validate config updates
2080
+ */
2081
+ validateConfigUpdate(section, updates) {
2082
+ const invalidFields = [];
2083
+ switch (section) {
2084
+ case 'user':
2085
+ if (updates.name !== undefined && typeof updates.name !== 'string') {
2086
+ invalidFields.push('name');
2087
+ }
2088
+ if (updates.timezone !== undefined && typeof updates.timezone !== 'string') {
2089
+ invalidFields.push('timezone');
2090
+ }
2091
+ break;
2092
+ case 'calendar':
2093
+ if (updates.workingHours !== undefined) {
2094
+ const wh = updates.workingHours;
2095
+ if (!wh.start || !wh.end) {
2096
+ invalidFields.push('workingHours');
2097
+ }
2098
+ }
2099
+ if (updates.deepWorkDays !== undefined && !Array.isArray(updates.deepWorkDays)) {
2100
+ invalidFields.push('deepWorkDays');
2101
+ }
2102
+ if (updates.meetingHeavyDays !== undefined &&
2103
+ !Array.isArray(updates.meetingHeavyDays)) {
2104
+ invalidFields.push('meetingHeavyDays');
2105
+ }
2106
+ break;
2107
+ case 'integrations':
2108
+ if (updates.notion !== undefined) {
2109
+ const notion = updates.notion;
2110
+ if (notion.enabled === true && !notion.databaseId) {
2111
+ invalidFields.push('notion.databaseId');
2112
+ }
2113
+ }
2114
+ break;
2115
+ case 'team':
2116
+ if (updates.members !== undefined && !Array.isArray(updates.members)) {
2117
+ invalidFields.push('members');
2118
+ }
2119
+ if (updates.managers !== undefined && !Array.isArray(updates.managers)) {
2120
+ invalidFields.push('managers');
2121
+ }
2122
+ break;
2123
+ }
2124
+ if (invalidFields.length > 0) {
2125
+ return {
2126
+ valid: false,
2127
+ error: `無効なフィールド: ${invalidFields.join(', ')}`,
2128
+ invalidFields,
2129
+ };
2130
+ }
2131
+ return { valid: true };
2132
+ }
2133
+ /**
2134
+ * Apply config updates
2135
+ */
2136
+ applyConfigUpdates(currentConfig, section, updates) {
2137
+ const newConfig = { ...currentConfig };
2138
+ switch (section) {
2139
+ case 'user':
2140
+ newConfig.user = { ...newConfig.user, ...updates };
2141
+ break;
2142
+ case 'calendar':
2143
+ newConfig.calendar = {
2144
+ ...newConfig.calendar,
2145
+ ...updates,
2146
+ };
2147
+ break;
2148
+ case 'priorityRules':
2149
+ newConfig.priorityRules = {
2150
+ ...newConfig.priorityRules,
2151
+ ...updates,
2152
+ };
2153
+ break;
2154
+ case 'integrations':
2155
+ if (updates.appleReminders) {
2156
+ newConfig.integrations.appleReminders = {
2157
+ ...newConfig.integrations.appleReminders,
2158
+ ...updates.appleReminders,
2159
+ };
2160
+ }
2161
+ if (updates.notion) {
2162
+ newConfig.integrations.notion = {
2163
+ ...newConfig.integrations.notion,
2164
+ ...updates.notion,
2165
+ };
2166
+ }
2167
+ break;
2168
+ case 'team':
2169
+ newConfig.team = { ...newConfig.team, ...updates };
2170
+ break;
2171
+ case 'preferences':
2172
+ newConfig.preferences = {
2173
+ ...newConfig.preferences,
2174
+ ...updates,
2175
+ };
2176
+ break;
2177
+ }
2178
+ return newConfig;
2179
+ }
2180
+ }
2181
+ /**
2182
+ * Create an MCP handler
2183
+ */
2184
+ export async function createMCPHandler() {
2185
+ const handler = new MCPHandlerImpl();
2186
+ await handler.initialize();
2187
+ return handler;
2188
+ }
2189
+ //# sourceMappingURL=mcp-handler.js.map