@shin1ohno/sage 0.7.9 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +26 -8
  2. package/dist/cli/mcp-handler.d.ts.map +1 -1
  3. package/dist/cli/mcp-handler.js +141 -987
  4. package/dist/cli/mcp-handler.js.map +1 -1
  5. package/dist/config/loader.d.ts.map +1 -1
  6. package/dist/config/loader.js +30 -1
  7. package/dist/config/loader.js.map +1 -1
  8. package/dist/config/update-validation.d.ts +52 -0
  9. package/dist/config/update-validation.d.ts.map +1 -0
  10. package/dist/config/update-validation.js +133 -0
  11. package/dist/config/update-validation.js.map +1 -0
  12. package/dist/config/validation.d.ts +130 -0
  13. package/dist/config/validation.d.ts.map +1 -0
  14. package/dist/config/validation.js +53 -0
  15. package/dist/config/validation.js.map +1 -0
  16. package/dist/index.js +443 -1632
  17. package/dist/index.js.map +1 -1
  18. package/dist/integrations/calendar-event-creator.d.ts +2 -3
  19. package/dist/integrations/calendar-event-creator.d.ts.map +1 -1
  20. package/dist/integrations/calendar-event-creator.js +3 -4
  21. package/dist/integrations/calendar-event-creator.js.map +1 -1
  22. package/dist/integrations/calendar-event-deleter.d.ts +2 -3
  23. package/dist/integrations/calendar-event-deleter.d.ts.map +1 -1
  24. package/dist/integrations/calendar-event-deleter.js +3 -4
  25. package/dist/integrations/calendar-event-deleter.js.map +1 -1
  26. package/dist/integrations/calendar-event-response.d.ts +4 -17
  27. package/dist/integrations/calendar-event-response.d.ts.map +1 -1
  28. package/dist/integrations/calendar-event-response.js +3 -4
  29. package/dist/integrations/calendar-event-response.js.map +1 -1
  30. package/dist/integrations/calendar-service.d.ts +6 -3
  31. package/dist/integrations/calendar-service.d.ts.map +1 -1
  32. package/dist/integrations/calendar-service.js +26 -4
  33. package/dist/integrations/calendar-service.js.map +1 -1
  34. package/dist/integrations/calendar-source-manager.d.ts +302 -0
  35. package/dist/integrations/calendar-source-manager.d.ts.map +1 -0
  36. package/dist/integrations/calendar-source-manager.js +862 -0
  37. package/dist/integrations/calendar-source-manager.js.map +1 -0
  38. package/dist/integrations/google-calendar-service.d.ts +176 -0
  39. package/dist/integrations/google-calendar-service.d.ts.map +1 -0
  40. package/dist/integrations/google-calendar-service.js +745 -0
  41. package/dist/integrations/google-calendar-service.js.map +1 -0
  42. package/dist/integrations/notion-mcp.d.ts +28 -3
  43. package/dist/integrations/notion-mcp.d.ts.map +1 -1
  44. package/dist/integrations/notion-mcp.js +21 -5
  45. package/dist/integrations/notion-mcp.js.map +1 -1
  46. package/dist/integrations/reminder-manager.d.ts.map +1 -1
  47. package/dist/integrations/reminder-manager.js +2 -0
  48. package/dist/integrations/reminder-manager.js.map +1 -1
  49. package/dist/oauth/google-oauth-handler.d.ts +149 -0
  50. package/dist/oauth/google-oauth-handler.d.ts.map +1 -0
  51. package/dist/oauth/google-oauth-handler.js +365 -0
  52. package/dist/oauth/google-oauth-handler.js.map +1 -0
  53. package/dist/services/container.d.ts +56 -0
  54. package/dist/services/container.d.ts.map +1 -0
  55. package/dist/services/container.js +76 -0
  56. package/dist/services/container.js.map +1 -0
  57. package/dist/tools/calendar/handlers.d.ts +186 -0
  58. package/dist/tools/calendar/handlers.d.ts.map +1 -0
  59. package/dist/tools/calendar/handlers.js +525 -0
  60. package/dist/tools/calendar/handlers.js.map +1 -0
  61. package/dist/tools/calendar/index.d.ts +11 -0
  62. package/dist/tools/calendar/index.d.ts.map +1 -0
  63. package/dist/tools/calendar/index.js +10 -0
  64. package/dist/tools/calendar/index.js.map +1 -0
  65. package/dist/tools/index.d.ts +23 -0
  66. package/dist/tools/index.d.ts.map +1 -0
  67. package/dist/tools/index.js +24 -0
  68. package/dist/tools/index.js.map +1 -0
  69. package/dist/tools/integrations/handlers.d.ts +57 -0
  70. package/dist/tools/integrations/handlers.d.ts.map +1 -0
  71. package/dist/tools/integrations/handlers.js +159 -0
  72. package/dist/tools/integrations/handlers.js.map +1 -0
  73. package/dist/tools/integrations/index.d.ts +11 -0
  74. package/dist/tools/integrations/index.d.ts.map +1 -0
  75. package/dist/tools/integrations/index.js +10 -0
  76. package/dist/tools/integrations/index.js.map +1 -0
  77. package/dist/tools/registry.d.ts +8 -0
  78. package/dist/tools/registry.d.ts.map +1 -0
  79. package/dist/tools/registry.js +10 -0
  80. package/dist/tools/registry.js.map +1 -0
  81. package/dist/tools/reminders/handlers.d.ts +61 -0
  82. package/dist/tools/reminders/handlers.d.ts.map +1 -0
  83. package/dist/tools/reminders/handlers.js +148 -0
  84. package/dist/tools/reminders/handlers.js.map +1 -0
  85. package/dist/tools/reminders/index.d.ts +11 -0
  86. package/dist/tools/reminders/index.d.ts.map +1 -0
  87. package/dist/tools/reminders/index.js +10 -0
  88. package/dist/tools/reminders/index.js.map +1 -0
  89. package/dist/tools/setup/handlers.d.ts +81 -0
  90. package/dist/tools/setup/handlers.d.ts.map +1 -0
  91. package/dist/tools/setup/handlers.js +172 -0
  92. package/dist/tools/setup/handlers.js.map +1 -0
  93. package/dist/tools/setup/index.d.ts +11 -0
  94. package/dist/tools/setup/index.d.ts.map +1 -0
  95. package/dist/tools/setup/index.js +10 -0
  96. package/dist/tools/setup/index.js.map +1 -0
  97. package/dist/tools/tasks/handlers.d.ts +95 -0
  98. package/dist/tools/tasks/handlers.d.ts.map +1 -0
  99. package/dist/tools/tasks/handlers.js +197 -0
  100. package/dist/tools/tasks/handlers.js.map +1 -0
  101. package/dist/tools/tasks/index.d.ts +11 -0
  102. package/dist/tools/tasks/index.d.ts.map +1 -0
  103. package/dist/tools/tasks/index.js +10 -0
  104. package/dist/tools/tasks/index.js.map +1 -0
  105. package/dist/tools/types.d.ts +54 -0
  106. package/dist/tools/types.d.ts.map +1 -0
  107. package/dist/tools/types.js +9 -0
  108. package/dist/tools/types.js.map +1 -0
  109. package/dist/types/calendar.d.ts +41 -0
  110. package/dist/types/calendar.d.ts.map +1 -0
  111. package/dist/types/calendar.js +18 -0
  112. package/dist/types/calendar.js.map +1 -0
  113. package/dist/types/config.d.ts +15 -0
  114. package/dist/types/config.d.ts.map +1 -1
  115. package/dist/types/config.js +21 -0
  116. package/dist/types/config.js.map +1 -1
  117. package/dist/types/google-calendar-types.d.ts +139 -0
  118. package/dist/types/google-calendar-types.d.ts.map +1 -0
  119. package/dist/types/google-calendar-types.js +46 -0
  120. package/dist/types/google-calendar-types.js.map +1 -0
  121. package/dist/types/index.d.ts +1 -0
  122. package/dist/types/index.d.ts.map +1 -1
  123. package/dist/types/index.js +1 -0
  124. package/dist/types/index.js.map +1 -1
  125. package/dist/utils/estimation.d.ts +34 -0
  126. package/dist/utils/estimation.d.ts.map +1 -1
  127. package/dist/utils/estimation.js +38 -1
  128. package/dist/utils/estimation.js.map +1 -1
  129. package/dist/utils/mcp-response.d.ts +89 -0
  130. package/dist/utils/mcp-response.d.ts.map +1 -0
  131. package/dist/utils/mcp-response.js +103 -0
  132. package/dist/utils/mcp-response.js.map +1 -0
  133. package/dist/utils/task-splitter.d.ts +65 -4
  134. package/dist/utils/task-splitter.d.ts.map +1 -1
  135. package/dist/utils/task-splitter.js +69 -5
  136. package/dist/utils/task-splitter.js.map +1 -1
  137. package/dist/version.d.ts +2 -2
  138. package/dist/version.d.ts.map +1 -1
  139. package/dist/version.js +22 -4
  140. package/dist/version.js.map +1 -1
  141. package/package.json +4 -3
  142. package/dist/cli/http-server.d.ts +0 -74
  143. package/dist/cli/http-server.d.ts.map +0 -1
  144. package/dist/cli/http-server.js +0 -407
  145. package/dist/cli/http-server.js.map +0 -1
  146. package/dist/remote/remote-mcp-server.d.ts +0 -244
  147. package/dist/remote/remote-mcp-server.d.ts.map +0 -1
  148. package/dist/remote/remote-mcp-server.js +0 -507
  149. package/dist/remote/remote-mcp-server.js.map +0 -1
@@ -6,8 +6,6 @@
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';
@@ -17,6 +15,11 @@ import { CalendarEventResponseService } from '../integrations/calendar-event-res
17
15
  import { CalendarEventCreatorService } from '../integrations/calendar-event-creator.js';
18
16
  import { CalendarEventDeleterService } from '../integrations/calendar-event-deleter.js';
19
17
  import { WorkingCadenceService } from '../services/working-cadence.js';
18
+ // Extracted tool handlers
19
+ import { handleCheckSetupStatus, handleStartSetupWizard, handleAnswerWizardQuestion, handleSaveConfig, } from '../tools/setup/index.js';
20
+ import { handleAnalyzeTasks, handleUpdateTaskStatus, handleSyncTasks, handleDetectDuplicates, } from '../tools/tasks/index.js';
21
+ import { handleSetReminder, handleListTodos, } from '../tools/reminders/index.js';
22
+ import { handleSyncToNotion, handleUpdateConfig, } from '../tools/integrations/index.js';
20
23
  // Protocol version
21
24
  const PROTOCOL_VERSION = '2024-11-05';
22
25
  /**
@@ -76,6 +79,57 @@ class MCPHandlerImpl {
76
79
  this.calendarEventDeleterService = new CalendarEventDeleterService();
77
80
  this.workingCadenceService = new WorkingCadenceService();
78
81
  }
82
+ /**
83
+ * Create SetupContext for setup tool handlers
84
+ */
85
+ createSetupContext() {
86
+ return {
87
+ getConfig: () => this.config,
88
+ setConfig: (config) => {
89
+ this.config = config;
90
+ },
91
+ getWizardSession: () => this.wizardSession,
92
+ setWizardSession: (session) => {
93
+ this.wizardSession = session;
94
+ },
95
+ initializeServices: (config) => this.initializeServices(config),
96
+ };
97
+ }
98
+ /**
99
+ * Create TaskToolsContext for task tool handlers
100
+ */
101
+ createTaskToolsContext() {
102
+ return {
103
+ getConfig: () => this.config,
104
+ getTodoListManager: () => this.todoListManager,
105
+ getTaskSynchronizer: () => this.taskSynchronizer,
106
+ initializeServices: (config) => this.initializeServices(config),
107
+ };
108
+ }
109
+ /**
110
+ * Create ReminderTodoContext for reminder/todo tool handlers
111
+ */
112
+ createReminderTodoContext() {
113
+ return {
114
+ getConfig: () => this.config,
115
+ getReminderManager: () => this.reminderManager,
116
+ getTodoListManager: () => this.todoListManager,
117
+ initializeServices: (config) => this.initializeServices(config),
118
+ };
119
+ }
120
+ /**
121
+ * Create IntegrationToolsContext for integration tool handlers
122
+ */
123
+ createIntegrationToolsContext() {
124
+ return {
125
+ getConfig: () => this.config,
126
+ setConfig: (config) => {
127
+ this.config = config;
128
+ },
129
+ getNotionService: () => this.notionService,
130
+ initializeServices: (config) => this.initializeServices(config),
131
+ };
132
+ }
79
133
  /**
80
134
  * Handle an MCP request
81
135
  */
@@ -209,7 +263,7 @@ class MCPHandlerImpl {
209
263
  * Register all tools
210
264
  */
211
265
  registerTools() {
212
- // check_setup_status
266
+ // check_setup_status - uses extracted handler
213
267
  this.registerTool({
214
268
  name: 'check_setup_status',
215
269
  description: 'Check if sage has been configured. Returns setup status and guidance.',
@@ -217,61 +271,8 @@ class MCPHandlerImpl {
217
271
  type: 'object',
218
272
  properties: {},
219
273
  },
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
274
+ }, async () => handleCheckSetupStatus(this.createSetupContext()));
275
+ // start_setup_wizard - uses extracted handler
275
276
  this.registerTool({
276
277
  name: 'start_setup_wizard',
277
278
  description: 'Start the interactive setup wizard for sage. Returns the first question.',
@@ -285,34 +286,10 @@ class MCPHandlerImpl {
285
286
  },
286
287
  },
287
288
  },
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
289
+ }, async (args) => handleStartSetupWizard(this.createSetupContext(), {
290
+ mode: args.mode,
291
+ }));
292
+ // answer_wizard_question - uses extracted handler
316
293
  this.registerTool({
317
294
  name: 'answer_wizard_question',
318
295
  description: 'Answer a question in the setup wizard and get the next question.',
@@ -330,77 +307,11 @@ class MCPHandlerImpl {
330
307
  },
331
308
  required: ['questionId', 'answer'],
332
309
  },
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
310
+ }, async (args) => handleAnswerWizardQuestion(this.createSetupContext(), {
311
+ questionId: args.questionId,
312
+ answer: args.answer,
313
+ }));
314
+ // save_config - uses extracted handler
404
315
  this.registerTool({
405
316
  name: 'save_config',
406
317
  description: 'Save the configuration after completing the setup wizard.',
@@ -414,74 +325,10 @@ class MCPHandlerImpl {
414
325
  },
415
326
  required: ['confirm'],
416
327
  },
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
328
+ }, async (args) => handleSaveConfig(this.createSetupContext(), {
329
+ confirm: args.confirm,
330
+ }));
331
+ // analyze_tasks - uses extracted handler
485
332
  this.registerTool({
486
333
  name: 'analyze_tasks',
487
334
  description: 'Analyze tasks to determine priority, estimate time, and identify stakeholders.',
@@ -507,61 +354,10 @@ class MCPHandlerImpl {
507
354
  },
508
355
  required: ['tasks'],
509
356
  },
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
357
+ }, async (args) => handleAnalyzeTasks(this.createTaskToolsContext(), {
358
+ tasks: args.tasks,
359
+ }));
360
+ // set_reminder - uses extracted handler
565
361
  this.registerTool({
566
362
  name: 'set_reminder',
567
363
  description: 'Set a reminder for a task in Apple Reminders or Notion.',
@@ -600,99 +396,14 @@ class MCPHandlerImpl {
600
396
  },
601
397
  required: ['taskTitle'],
602
398
  },
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
- });
399
+ }, async (args) => handleSetReminder(this.createReminderTodoContext(), {
400
+ taskTitle: args.taskTitle,
401
+ dueDate: args.dueDate,
402
+ reminderType: args.reminderType,
403
+ list: args.list,
404
+ priority: args.priority,
405
+ notes: args.notes,
406
+ }));
696
407
  // find_available_slots
697
408
  this.registerTool({
698
409
  name: 'find_available_slots',
@@ -928,7 +639,7 @@ class MCPHandlerImpl {
928
639
  };
929
640
  }
930
641
  });
931
- // sync_to_notion
642
+ // sync_to_notion - uses extracted handler
932
643
  this.registerTool({
933
644
  name: 'sync_to_notion',
934
645
  description: 'Sync a task to Notion database for long-term tracking.',
@@ -955,233 +666,45 @@ class MCPHandlerImpl {
955
666
  },
956
667
  required: ['taskTitle'],
957
668
  },
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
669
+ }, async (args) => handleSyncToNotion(this.createIntegrationToolsContext(), {
670
+ taskTitle: args.taskTitle,
671
+ description: args.description,
672
+ priority: args.priority,
673
+ dueDate: args.dueDate,
674
+ stakeholders: args.stakeholders,
675
+ estimatedMinutes: args.estimatedMinutes,
676
+ }));
677
+ // update_config - uses extracted handler
1093
678
  this.registerTool({
1094
679
  name: 'update_config',
1095
- description: 'Update sage configuration settings.',
1096
- inputSchema: {
1097
- type: 'object',
1098
- properties: {
1099
- section: {
1100
- type: 'string',
1101
- enum: [
1102
- 'user',
1103
- 'calendar',
1104
- 'priorityRules',
1105
- 'integrations',
1106
- 'team',
1107
- 'preferences',
1108
- ],
1109
- description: 'Configuration section to update',
1110
- },
1111
- updates: {
1112
- type: 'object',
1113
- description: 'Key-value pairs to update',
1114
- },
1115
- },
1116
- required: ['section', 'updates'],
1117
- },
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
- },
680
+ description: 'Update sage configuration settings.',
681
+ inputSchema: {
682
+ type: 'object',
683
+ properties: {
684
+ section: {
685
+ type: 'string',
686
+ enum: [
687
+ 'user',
688
+ 'calendar',
689
+ 'priorityRules',
690
+ 'integrations',
691
+ 'team',
692
+ 'preferences',
1147
693
  ],
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
694
+ description: 'Configuration section to update',
695
+ },
696
+ updates: {
697
+ type: 'object',
698
+ description: 'Key-value pairs to update',
699
+ },
700
+ },
701
+ required: ['section', 'updates'],
702
+ },
703
+ }, async (args) => handleUpdateConfig(this.createIntegrationToolsContext(), {
704
+ section: args.section,
705
+ updates: args.updates,
706
+ }));
707
+ // list_todos - uses extracted handler
1185
708
  this.registerTool({
1186
709
  name: 'list_todos',
1187
710
  description: 'List TODO items from Apple Reminders and Notion with optional filtering.',
@@ -1214,90 +737,14 @@ class MCPHandlerImpl {
1214
737
  },
1215
738
  },
1216
739
  },
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
740
+ }, async (args) => handleListTodos(this.createReminderTodoContext(), {
741
+ priority: args.priority,
742
+ status: args.status,
743
+ source: args.source,
744
+ todayOnly: args.todayOnly,
745
+ tags: args.tags,
746
+ }));
747
+ // update_task_status - uses extracted handler
1301
748
  this.registerTool({
1302
749
  name: 'update_task_status',
1303
750
  description: 'Update the status of a task in Apple Reminders or Notion.',
@@ -1322,80 +769,13 @@ class MCPHandlerImpl {
1322
769
  },
1323
770
  required: ['taskId', 'status', 'source'],
1324
771
  },
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
772
+ }, async (args) => handleUpdateTaskStatus(this.createTaskToolsContext(), {
773
+ taskId: args.taskId,
774
+ status: args.status,
775
+ source: args.source,
776
+ syncAcrossSources: args.syncAcrossSources,
777
+ }));
778
+ // sync_tasks - uses extracted handler
1399
779
  this.registerTool({
1400
780
  name: 'sync_tasks',
1401
781
  description: 'Synchronize tasks between Apple Reminders and Notion, detecting and resolving conflicts.',
@@ -1403,59 +783,8 @@ class MCPHandlerImpl {
1403
783
  type: 'object',
1404
784
  properties: {},
1405
785
  },
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
786
+ }, async () => handleSyncTasks(this.createTaskToolsContext()));
787
+ // detect_duplicates - uses extracted handler
1459
788
  this.registerTool({
1460
789
  name: 'detect_duplicates',
1461
790
  description: 'Detect duplicate tasks between Apple Reminders and Notion.',
@@ -1468,82 +797,9 @@ class MCPHandlerImpl {
1468
797
  },
1469
798
  },
1470
799
  },
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
- });
800
+ }, async (args) => handleDetectDuplicates(this.createTaskToolsContext(), {
801
+ autoMerge: args.autoMerge,
802
+ }));
1547
803
  // get_working_cadence
1548
804
  this.registerTool({
1549
805
  name: 'get_working_cadence',
@@ -2154,108 +1410,6 @@ class MCPHandlerImpl {
2154
1410
  registerTool(definition, handler) {
2155
1411
  this.tools.set(definition.name, { definition, handler });
2156
1412
  }
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
1413
  }
2260
1414
  /**
2261
1415
  * Create an MCP handler