@shin1ohno/sage 0.3.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/http-server-with-config.d.ts +38 -0
- package/dist/cli/http-server-with-config.d.ts.map +1 -0
- package/dist/cli/http-server-with-config.js +458 -0
- package/dist/cli/http-server-with-config.js.map +1 -0
- package/dist/cli/http-server.d.ts +74 -0
- package/dist/cli/http-server.d.ts.map +1 -0
- package/dist/cli/http-server.js +407 -0
- package/dist/cli/http-server.js.map +1 -0
- package/dist/cli/jwt-middleware.d.ts +36 -0
- package/dist/cli/jwt-middleware.d.ts.map +1 -0
- package/dist/cli/jwt-middleware.js +99 -0
- package/dist/cli/jwt-middleware.js.map +1 -0
- package/dist/cli/main-entry.d.ts +41 -0
- package/dist/cli/main-entry.d.ts.map +1 -0
- package/dist/cli/main-entry.js +80 -0
- package/dist/cli/main-entry.js.map +1 -0
- package/dist/cli/mcp-handler.d.ts +56 -0
- package/dist/cli/mcp-handler.d.ts.map +1 -0
- package/dist/cli/mcp-handler.js +2189 -0
- package/dist/cli/mcp-handler.js.map +1 -0
- package/dist/cli/parser.d.ts +43 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +162 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/remote-config-loader.d.ts +85 -0
- package/dist/cli/remote-config-loader.d.ts.map +1 -0
- package/dist/cli/remote-config-loader.js +129 -0
- package/dist/cli/remote-config-loader.js.map +1 -0
- package/dist/cli/secret-auth.d.ts +47 -0
- package/dist/cli/secret-auth.d.ts.map +1 -0
- package/dist/cli/secret-auth.js +165 -0
- package/dist/cli/secret-auth.js.map +1 -0
- package/dist/cli/sse-stream-handler.d.ts +45 -0
- package/dist/cli/sse-stream-handler.d.ts.map +1 -0
- package/dist/cli/sse-stream-handler.js +125 -0
- package/dist/cli/sse-stream-handler.js.map +1 -0
- package/dist/index.js +885 -209
- package/dist/index.js.map +1 -1
- package/dist/integrations/calendar-event-creator.d.ts +152 -0
- package/dist/integrations/calendar-event-creator.d.ts.map +1 -0
- package/dist/integrations/calendar-event-creator.js +507 -0
- package/dist/integrations/calendar-event-creator.js.map +1 -0
- package/dist/integrations/calendar-event-deleter.d.ts +137 -0
- package/dist/integrations/calendar-event-deleter.d.ts.map +1 -0
- package/dist/integrations/calendar-event-deleter.js +378 -0
- package/dist/integrations/calendar-event-deleter.js.map +1 -0
- package/dist/integrations/calendar-event-response.d.ts +213 -0
- package/dist/integrations/calendar-event-response.d.ts.map +1 -0
- package/dist/integrations/calendar-event-response.js +560 -0
- package/dist/integrations/calendar-event-response.js.map +1 -0
- package/dist/integrations/calendar-service.d.ts +66 -1
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js +223 -0
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/oauth/client-store.d.ts +36 -0
- package/dist/oauth/client-store.d.ts.map +1 -0
- package/dist/oauth/client-store.js +104 -0
- package/dist/oauth/client-store.js.map +1 -0
- package/dist/oauth/code-store.d.ts +48 -0
- package/dist/oauth/code-store.d.ts.map +1 -0
- package/dist/oauth/code-store.js +89 -0
- package/dist/oauth/code-store.js.map +1 -0
- package/dist/oauth/index.d.ts +13 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +21 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/oauth-handler.d.ts +101 -0
- package/dist/oauth/oauth-handler.d.ts.map +1 -0
- package/dist/oauth/oauth-handler.js +577 -0
- package/dist/oauth/oauth-handler.js.map +1 -0
- package/dist/oauth/oauth-server.d.ts +165 -0
- package/dist/oauth/oauth-server.d.ts.map +1 -0
- package/dist/oauth/oauth-server.js +489 -0
- package/dist/oauth/oauth-server.js.map +1 -0
- package/dist/oauth/pkce.d.ts +48 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +106 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/refresh-token-store.d.ts +45 -0
- package/dist/oauth/refresh-token-store.d.ts.map +1 -0
- package/dist/oauth/refresh-token-store.js +98 -0
- package/dist/oauth/refresh-token-store.js.map +1 -0
- package/dist/oauth/token-service.d.ts +46 -0
- package/dist/oauth/token-service.d.ts.map +1 -0
- package/dist/oauth/token-service.js +199 -0
- package/dist/oauth/token-service.js.map +1 -0
- package/dist/oauth/types.d.ts +264 -0
- package/dist/oauth/types.d.ts.map +1 -0
- package/dist/oauth/types.js +37 -0
- package/dist/oauth/types.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- 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
|