@vibetasks/mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -0
- package/config/claude-code.json +38 -0
- package/dist/index.js +565 -0
- package/dist/session-end-6BUUHSB7.js +56 -0
- package/dist/session-start-OIAJ7YIL.js +49 -0
- package/package.json +29 -0
- package/scripts/install.js +119 -0
- package/scripts/install.sh +104 -0
- package/scripts/uninstall.js +107 -0
- package/src/hooks/session-end.ts +75 -0
- package/src/hooks/session-start.ts +74 -0
- package/src/index.ts +165 -0
- package/src/resources/index.ts +115 -0
- package/src/tools/index.ts +414 -0
- package/tsconfig.json +21 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema,
|
|
9
|
+
ListResourcesRequestSchema,
|
|
10
|
+
ReadResourceRequestSchema
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { AuthManager, TaskOperations } from "@vibetasks/core";
|
|
13
|
+
|
|
14
|
+
// src/tools/index.ts
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
function setupTools(taskOps) {
|
|
17
|
+
return [
|
|
18
|
+
// create_task
|
|
19
|
+
{
|
|
20
|
+
name: "create_task",
|
|
21
|
+
description: "Create a new task in TaskFlow. Can optionally be a subtask by providing parent_task_id.",
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
title: z.string().describe("Task title (required)"),
|
|
24
|
+
notes: z.string().optional().describe("Task notes in markdown"),
|
|
25
|
+
due_date: z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
26
|
+
priority: z.enum(["none", "low", "medium", "high"]).default("none").describe("Task priority"),
|
|
27
|
+
tags: z.array(z.string()).optional().describe("Tag names to attach"),
|
|
28
|
+
parent_task_id: z.string().optional().describe("Parent task ID to create this as a subtask")
|
|
29
|
+
}),
|
|
30
|
+
handler: async (args, taskOps2) => {
|
|
31
|
+
let tagIds = [];
|
|
32
|
+
if (args.tags && args.tags.length > 0) {
|
|
33
|
+
for (const tagName of args.tags) {
|
|
34
|
+
const tag = await taskOps2.findOrCreateTag(tagName);
|
|
35
|
+
tagIds.push(tag.id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const task = await taskOps2.createTask({
|
|
39
|
+
title: args.title,
|
|
40
|
+
notes: args.notes,
|
|
41
|
+
notes_format: "markdown",
|
|
42
|
+
due_date: args.due_date,
|
|
43
|
+
priority: args.priority,
|
|
44
|
+
parent_task_id: args.parent_task_id
|
|
45
|
+
});
|
|
46
|
+
if (tagIds.length > 0) {
|
|
47
|
+
await taskOps2.linkTaskTags(task.id, tagIds);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
success: true,
|
|
56
|
+
task: {
|
|
57
|
+
id: task.id,
|
|
58
|
+
title: task.title,
|
|
59
|
+
priority: task.priority,
|
|
60
|
+
due_date: task.due_date,
|
|
61
|
+
parent_task_id: task.parent_task_id,
|
|
62
|
+
created_at: task.created_at
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
null,
|
|
66
|
+
2
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
// get_tasks
|
|
74
|
+
{
|
|
75
|
+
name: "get_tasks",
|
|
76
|
+
description: "Get tasks by filter",
|
|
77
|
+
inputSchema: z.object({
|
|
78
|
+
filter: z.enum(["all", "today", "upcoming", "completed"]).default("all").describe("Task filter")
|
|
79
|
+
}),
|
|
80
|
+
handler: async (args, taskOps2) => {
|
|
81
|
+
const tasks = await taskOps2.getTasks(args.filter);
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify(
|
|
87
|
+
{
|
|
88
|
+
success: true,
|
|
89
|
+
tasks: tasks.map((t) => ({
|
|
90
|
+
id: t.id,
|
|
91
|
+
title: t.title,
|
|
92
|
+
priority: t.priority,
|
|
93
|
+
completed: t.completed,
|
|
94
|
+
due_date: t.due_date,
|
|
95
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
96
|
+
})),
|
|
97
|
+
count: tasks.length
|
|
98
|
+
},
|
|
99
|
+
null,
|
|
100
|
+
2
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
// update_task
|
|
108
|
+
{
|
|
109
|
+
name: "update_task",
|
|
110
|
+
description: "Update an existing task",
|
|
111
|
+
inputSchema: z.object({
|
|
112
|
+
task_id: z.string().describe("Task ID"),
|
|
113
|
+
title: z.string().optional().describe("New title"),
|
|
114
|
+
notes: z.string().optional().describe("New notes"),
|
|
115
|
+
due_date: z.string().optional().describe("New due date (ISO 8601)"),
|
|
116
|
+
priority: z.enum(["none", "low", "medium", "high"]).optional().describe("New priority"),
|
|
117
|
+
completed: z.boolean().optional().describe("Completion status")
|
|
118
|
+
}),
|
|
119
|
+
handler: async (args, taskOps2) => {
|
|
120
|
+
const updates = {};
|
|
121
|
+
if (args.title !== void 0) updates.title = args.title;
|
|
122
|
+
if (args.notes !== void 0) updates.notes = args.notes;
|
|
123
|
+
if (args.due_date !== void 0) updates.due_date = args.due_date;
|
|
124
|
+
if (args.priority !== void 0) updates.priority = args.priority;
|
|
125
|
+
if (args.completed !== void 0) updates.completed = args.completed;
|
|
126
|
+
const task = await taskOps2.updateTask(args.task_id, updates);
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: JSON.stringify(
|
|
132
|
+
{
|
|
133
|
+
success: true,
|
|
134
|
+
task: {
|
|
135
|
+
id: task.id,
|
|
136
|
+
title: task.title,
|
|
137
|
+
completed: task.completed,
|
|
138
|
+
priority: task.priority
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
null,
|
|
142
|
+
2
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
// complete_task
|
|
150
|
+
{
|
|
151
|
+
name: "complete_task",
|
|
152
|
+
description: "Mark a task as complete",
|
|
153
|
+
inputSchema: z.object({
|
|
154
|
+
task_id: z.string().describe("Task ID")
|
|
155
|
+
}),
|
|
156
|
+
handler: async (args, taskOps2) => {
|
|
157
|
+
const task = await taskOps2.completeTask(args.task_id);
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: JSON.stringify(
|
|
163
|
+
{
|
|
164
|
+
success: true,
|
|
165
|
+
task: {
|
|
166
|
+
id: task.id,
|
|
167
|
+
title: task.title,
|
|
168
|
+
completed: true,
|
|
169
|
+
completed_at: task.completed_at
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
null,
|
|
173
|
+
2
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
// delete_task
|
|
181
|
+
{
|
|
182
|
+
name: "delete_task",
|
|
183
|
+
description: "Delete a task",
|
|
184
|
+
inputSchema: z.object({
|
|
185
|
+
task_id: z.string().describe("Task ID")
|
|
186
|
+
}),
|
|
187
|
+
handler: async (args, taskOps2) => {
|
|
188
|
+
await taskOps2.deleteTask(args.task_id);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: JSON.stringify(
|
|
194
|
+
{
|
|
195
|
+
success: true,
|
|
196
|
+
message: "Task deleted successfully"
|
|
197
|
+
},
|
|
198
|
+
null,
|
|
199
|
+
2
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
// search_tasks
|
|
207
|
+
{
|
|
208
|
+
name: "search_tasks",
|
|
209
|
+
description: "Search tasks by title",
|
|
210
|
+
inputSchema: z.object({
|
|
211
|
+
query: z.string().describe("Search query"),
|
|
212
|
+
limit: z.number().default(20).describe("Maximum results")
|
|
213
|
+
}),
|
|
214
|
+
handler: async (args, taskOps2) => {
|
|
215
|
+
const tasks = await taskOps2.searchTasks(args.query, args.limit);
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: JSON.stringify(
|
|
221
|
+
{
|
|
222
|
+
success: true,
|
|
223
|
+
tasks: tasks.map((t) => ({
|
|
224
|
+
id: t.id,
|
|
225
|
+
title: t.title,
|
|
226
|
+
priority: t.priority,
|
|
227
|
+
due_date: t.due_date,
|
|
228
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
229
|
+
})),
|
|
230
|
+
count: tasks.length
|
|
231
|
+
},
|
|
232
|
+
null,
|
|
233
|
+
2
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
// create_task_with_subtasks
|
|
241
|
+
{
|
|
242
|
+
name: "create_task_with_subtasks",
|
|
243
|
+
description: "Create a parent task with multiple subtasks in one call. Use this when using TodoWrite - it automatically mirrors your todo list to TaskFlow for persistent tracking.",
|
|
244
|
+
inputSchema: z.object({
|
|
245
|
+
title: z.string().describe("Parent task title (the overall goal)"),
|
|
246
|
+
subtasks: z.array(z.string()).describe("Array of subtask titles (one for each todo item)"),
|
|
247
|
+
notes: z.string().optional().describe("Notes for parent task"),
|
|
248
|
+
due_date: z.string().optional().describe("Due date for parent task (ISO 8601 format)"),
|
|
249
|
+
priority: z.enum(["none", "low", "medium", "high"]).default("none").describe("Priority for parent task"),
|
|
250
|
+
tags: z.array(z.string()).optional().describe("Tag names to attach to parent task")
|
|
251
|
+
}),
|
|
252
|
+
handler: async (args, taskOps2) => {
|
|
253
|
+
let tagIds = [];
|
|
254
|
+
if (args.tags && args.tags.length > 0) {
|
|
255
|
+
for (const tagName of args.tags) {
|
|
256
|
+
const tag = await taskOps2.findOrCreateTag(tagName);
|
|
257
|
+
tagIds.push(tag.id);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const parentTask = await taskOps2.createTask({
|
|
261
|
+
title: args.title,
|
|
262
|
+
notes: args.notes,
|
|
263
|
+
notes_format: "markdown",
|
|
264
|
+
due_date: args.due_date,
|
|
265
|
+
priority: args.priority
|
|
266
|
+
});
|
|
267
|
+
if (tagIds.length > 0) {
|
|
268
|
+
await taskOps2.linkTaskTags(parentTask.id, tagIds);
|
|
269
|
+
}
|
|
270
|
+
const subtasks = [];
|
|
271
|
+
for (const subtaskTitle of args.subtasks) {
|
|
272
|
+
const subtask = await taskOps2.createTask({
|
|
273
|
+
title: subtaskTitle,
|
|
274
|
+
notes_format: "markdown",
|
|
275
|
+
priority: "none",
|
|
276
|
+
parent_task_id: parentTask.id
|
|
277
|
+
});
|
|
278
|
+
subtasks.push(subtask);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify(
|
|
285
|
+
{
|
|
286
|
+
success: true,
|
|
287
|
+
parent_task: {
|
|
288
|
+
id: parentTask.id,
|
|
289
|
+
title: parentTask.title,
|
|
290
|
+
priority: parentTask.priority,
|
|
291
|
+
subtask_count: subtasks.length
|
|
292
|
+
},
|
|
293
|
+
subtasks: subtasks.map((s) => ({
|
|
294
|
+
id: s.id,
|
|
295
|
+
title: s.title
|
|
296
|
+
}))
|
|
297
|
+
},
|
|
298
|
+
null,
|
|
299
|
+
2
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
// log_ai_session
|
|
307
|
+
{
|
|
308
|
+
name: "log_ai_session",
|
|
309
|
+
description: "Manually log an AI session as a completed task",
|
|
310
|
+
inputSchema: z.object({
|
|
311
|
+
summary: z.string().describe("What was accomplished"),
|
|
312
|
+
files: z.array(z.string()).optional().describe("Files changed"),
|
|
313
|
+
duration_minutes: z.number().optional().describe("Session duration")
|
|
314
|
+
}),
|
|
315
|
+
handler: async (args, taskOps2) => {
|
|
316
|
+
const notes = `# AI Session Summary
|
|
317
|
+
|
|
318
|
+
${args.summary}
|
|
319
|
+
|
|
320
|
+
${args.files && args.files.length > 0 ? `## Files Changed
|
|
321
|
+
${args.files.map((f) => `- ${f}`).join("\n")}` : ""}
|
|
322
|
+
|
|
323
|
+
${args.duration_minutes ? `Duration: ${args.duration_minutes} minutes` : ""}
|
|
324
|
+
|
|
325
|
+
Generated by TaskFlow MCP Server`;
|
|
326
|
+
const task = await taskOps2.createTask({
|
|
327
|
+
title: `AI Session: ${args.summary.substring(0, 50)}${args.summary.length > 50 ? "..." : ""}`,
|
|
328
|
+
notes,
|
|
329
|
+
notes_format: "markdown",
|
|
330
|
+
completed: true,
|
|
331
|
+
priority: "none"
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
content: [
|
|
335
|
+
{
|
|
336
|
+
type: "text",
|
|
337
|
+
text: JSON.stringify(
|
|
338
|
+
{
|
|
339
|
+
success: true,
|
|
340
|
+
task: {
|
|
341
|
+
id: task.id,
|
|
342
|
+
title: task.title,
|
|
343
|
+
completed: true
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
null,
|
|
347
|
+
2
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/resources/index.ts
|
|
358
|
+
function setupResources(taskOps) {
|
|
359
|
+
return [
|
|
360
|
+
// Active tasks resource
|
|
361
|
+
{
|
|
362
|
+
uri: "taskflow://tasks/active",
|
|
363
|
+
name: "Active Tasks",
|
|
364
|
+
description: "All incomplete tasks",
|
|
365
|
+
mimeType: "application/json",
|
|
366
|
+
handler: async (taskOps2) => {
|
|
367
|
+
const tasks = await taskOps2.getTasks("all");
|
|
368
|
+
return {
|
|
369
|
+
contents: [
|
|
370
|
+
{
|
|
371
|
+
uri: "taskflow://tasks/active",
|
|
372
|
+
mimeType: "application/json",
|
|
373
|
+
text: JSON.stringify(
|
|
374
|
+
{
|
|
375
|
+
tasks: tasks.map((t) => ({
|
|
376
|
+
id: t.id,
|
|
377
|
+
title: t.title,
|
|
378
|
+
priority: t.priority,
|
|
379
|
+
due_date: t.due_date,
|
|
380
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
381
|
+
})),
|
|
382
|
+
count: tasks.length
|
|
383
|
+
},
|
|
384
|
+
null,
|
|
385
|
+
2
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
// Today's tasks resource
|
|
393
|
+
{
|
|
394
|
+
uri: "taskflow://tasks/today",
|
|
395
|
+
name: "Today's Tasks",
|
|
396
|
+
description: "Tasks due today",
|
|
397
|
+
mimeType: "application/json",
|
|
398
|
+
handler: async (taskOps2) => {
|
|
399
|
+
const tasks = await taskOps2.getTasks("today");
|
|
400
|
+
return {
|
|
401
|
+
contents: [
|
|
402
|
+
{
|
|
403
|
+
uri: "taskflow://tasks/today",
|
|
404
|
+
mimeType: "application/json",
|
|
405
|
+
text: JSON.stringify(
|
|
406
|
+
{
|
|
407
|
+
tasks: tasks.map((t) => ({
|
|
408
|
+
id: t.id,
|
|
409
|
+
title: t.title,
|
|
410
|
+
priority: t.priority,
|
|
411
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
412
|
+
})),
|
|
413
|
+
count: tasks.length,
|
|
414
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
415
|
+
},
|
|
416
|
+
null,
|
|
417
|
+
2
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
// Upcoming tasks resource
|
|
425
|
+
{
|
|
426
|
+
uri: "taskflow://tasks/upcoming",
|
|
427
|
+
name: "Upcoming Tasks",
|
|
428
|
+
description: "Tasks due in the future",
|
|
429
|
+
mimeType: "application/json",
|
|
430
|
+
handler: async (taskOps2) => {
|
|
431
|
+
const tasks = await taskOps2.getTasks("upcoming");
|
|
432
|
+
return {
|
|
433
|
+
contents: [
|
|
434
|
+
{
|
|
435
|
+
uri: "taskflow://tasks/upcoming",
|
|
436
|
+
mimeType: "application/json",
|
|
437
|
+
text: JSON.stringify(
|
|
438
|
+
{
|
|
439
|
+
tasks: tasks.map((t) => ({
|
|
440
|
+
id: t.id,
|
|
441
|
+
title: t.title,
|
|
442
|
+
priority: t.priority,
|
|
443
|
+
due_date: t.due_date,
|
|
444
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
445
|
+
})),
|
|
446
|
+
count: tasks.length
|
|
447
|
+
},
|
|
448
|
+
null,
|
|
449
|
+
2
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/index.ts
|
|
460
|
+
var hookType = process.env.CLAUDE_HOOK_TYPE;
|
|
461
|
+
if (hookType === "SessionStart") {
|
|
462
|
+
const { handleSessionStart } = await import("./session-start-OIAJ7YIL.js");
|
|
463
|
+
await handleSessionStart();
|
|
464
|
+
process.exit(0);
|
|
465
|
+
} else if (hookType === "SessionEnd" || hookType === "Stop") {
|
|
466
|
+
const { handleSessionEnd } = await import("./session-end-6BUUHSB7.js");
|
|
467
|
+
await handleSessionEnd();
|
|
468
|
+
process.exit(0);
|
|
469
|
+
}
|
|
470
|
+
async function main() {
|
|
471
|
+
try {
|
|
472
|
+
const authManager = new AuthManager();
|
|
473
|
+
const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || await authManager.getConfig("supabase_url") || "https://cbkkztbcoitrfcleghfd.supabase.co";
|
|
474
|
+
const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || await authManager.getConfig("supabase_key") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
|
|
475
|
+
const accessToken = await authManager.getAccessToken();
|
|
476
|
+
if (!accessToken) {
|
|
477
|
+
console.error("ERROR: Not authenticated.", { severity: "error" });
|
|
478
|
+
console.error("Run: taskflow login", { severity: "error" });
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
if (!accessToken) {
|
|
482
|
+
console.error("ERROR: Not authenticated.", { severity: "error" });
|
|
483
|
+
console.error("Run: taskflow login", { severity: "error" });
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
487
|
+
const server = new Server(
|
|
488
|
+
{
|
|
489
|
+
name: "taskflow-mcp-server",
|
|
490
|
+
version: "1.0.0"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
capabilities: {
|
|
494
|
+
tools: {},
|
|
495
|
+
resources: {}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
const tools = setupTools(taskOps);
|
|
500
|
+
const resources = setupResources(taskOps);
|
|
501
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
502
|
+
return {
|
|
503
|
+
tools: tools.map((t) => ({
|
|
504
|
+
name: t.name,
|
|
505
|
+
description: t.description,
|
|
506
|
+
inputSchema: t.inputSchema
|
|
507
|
+
}))
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
511
|
+
const tool = tools.find((t) => t.name === request.params.name);
|
|
512
|
+
if (!tool) {
|
|
513
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
return await tool.handler(request.params.arguments || {}, taskOps);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
return {
|
|
519
|
+
content: [
|
|
520
|
+
{
|
|
521
|
+
type: "text",
|
|
522
|
+
text: JSON.stringify(
|
|
523
|
+
{
|
|
524
|
+
success: false,
|
|
525
|
+
error: error.message
|
|
526
|
+
},
|
|
527
|
+
null,
|
|
528
|
+
2
|
|
529
|
+
)
|
|
530
|
+
}
|
|
531
|
+
],
|
|
532
|
+
isError: true
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
537
|
+
return {
|
|
538
|
+
resources: resources.map((r) => ({
|
|
539
|
+
uri: r.uri,
|
|
540
|
+
name: r.name,
|
|
541
|
+
description: r.description,
|
|
542
|
+
mimeType: r.mimeType
|
|
543
|
+
}))
|
|
544
|
+
};
|
|
545
|
+
});
|
|
546
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
547
|
+
const resource = resources.find((r) => r.uri === request.params.uri);
|
|
548
|
+
if (!resource) {
|
|
549
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
return await resource.handler(taskOps);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
throw new Error(`Failed to read resource: ${error.message}`);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
const transport = new StdioServerTransport();
|
|
558
|
+
await server.connect(transport);
|
|
559
|
+
console.error("TaskFlow MCP server started", { severity: "info" });
|
|
560
|
+
} catch (error) {
|
|
561
|
+
console.error("Fatal error:", error, { severity: "error" });
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
main();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/hooks/session-end.ts
|
|
2
|
+
import { AuthManager, TaskOperations } from "@vibetasks/core";
|
|
3
|
+
async function handleSessionEnd() {
|
|
4
|
+
try {
|
|
5
|
+
const authManager = new AuthManager();
|
|
6
|
+
const isAuth = await authManager.isAuthenticated();
|
|
7
|
+
if (!isAuth) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const metadataJson = process.env.CLAUDE_SESSION_METADATA || "{}";
|
|
11
|
+
const metadata = JSON.parse(metadataJson);
|
|
12
|
+
const filesEdited = metadata.filesEdited || [];
|
|
13
|
+
const duration = metadata.duration || 0;
|
|
14
|
+
const aiActions = metadata.aiActions || [];
|
|
15
|
+
const shouldLog = filesEdited.length > 0 && duration >= 6e4 && aiActions.length > 0;
|
|
16
|
+
if (!shouldLog) {
|
|
17
|
+
console.error("TaskFlow: Session too short, not logging");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
21
|
+
const durationMinutes = Math.round(duration / 6e4);
|
|
22
|
+
const primaryAction = aiActions[0] || "Code changes";
|
|
23
|
+
const summary = `# AI Session Completed
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
${primaryAction}
|
|
27
|
+
|
|
28
|
+
## Duration
|
|
29
|
+
${durationMinutes} minutes
|
|
30
|
+
|
|
31
|
+
## Files Edited (${filesEdited.length})
|
|
32
|
+
${filesEdited.slice(0, 15).map((f) => `- ${f}`).join("\n")}
|
|
33
|
+
${filesEdited.length > 15 ? `
|
|
34
|
+
...and ${filesEdited.length - 15} more files` : ""}
|
|
35
|
+
|
|
36
|
+
## Actions
|
|
37
|
+
${aiActions.map((a) => `- ${a}`).join("\n")}
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
*Generated by TaskFlow MCP Server*
|
|
41
|
+
`.trim();
|
|
42
|
+
await taskOps.createTask({
|
|
43
|
+
title: `AI Session: ${primaryAction}`,
|
|
44
|
+
notes: summary,
|
|
45
|
+
notes_format: "markdown",
|
|
46
|
+
completed: true,
|
|
47
|
+
priority: "none"
|
|
48
|
+
});
|
|
49
|
+
console.error("TaskFlow: Logged AI session as completed task");
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("TaskFlow SessionEnd hook error:", error.message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
handleSessionEnd
|
|
56
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/hooks/session-start.ts
|
|
2
|
+
import { AuthManager, TaskOperations } from "@vibetasks/core";
|
|
3
|
+
async function handleSessionStart() {
|
|
4
|
+
try {
|
|
5
|
+
const authManager = new AuthManager();
|
|
6
|
+
const isAuth = await authManager.isAuthenticated();
|
|
7
|
+
if (!isAuth) {
|
|
8
|
+
console.log(JSON.stringify({ additionalContext: "" }));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
12
|
+
const todayTasks = await taskOps.getTasks("today");
|
|
13
|
+
const activeTasks = await taskOps.getTasks("all");
|
|
14
|
+
const context = `# TaskFlow - Your Tasks
|
|
15
|
+
|
|
16
|
+
## Today's Tasks (${todayTasks.length})
|
|
17
|
+
${todayTasks.length === 0 ? "No tasks due today." : todayTasks.map((t) => {
|
|
18
|
+
const priority = t.priority && t.priority !== "none" ? ` [${t.priority.toUpperCase()}]` : "";
|
|
19
|
+
const tags = t.tags && t.tags.length > 0 ? ` #${t.tags.map((tag) => tag.name).join(" #")}` : "";
|
|
20
|
+
return `- [ ] ${t.title}${priority}${tags}`;
|
|
21
|
+
}).join("\n")}
|
|
22
|
+
|
|
23
|
+
## All Active Tasks (${activeTasks.length})
|
|
24
|
+
${activeTasks.length === 0 ? "No active tasks." : activeTasks.slice(0, 10).map((t) => {
|
|
25
|
+
const priority = t.priority && t.priority !== "none" ? ` [${t.priority.toUpperCase()}]` : "";
|
|
26
|
+
const dueDate = t.due_date ? ` (Due: ${t.due_date.split("T")[0]})` : "";
|
|
27
|
+
return `- [ ] ${t.title}${priority}${dueDate}`;
|
|
28
|
+
}).join("\n")}${activeTasks.length > 10 ? `
|
|
29
|
+
...and ${activeTasks.length - 10} more` : ""}
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
You can manage tasks using these MCP tools:
|
|
33
|
+
- create_task: Add new tasks
|
|
34
|
+
- get_tasks: View tasks by filter
|
|
35
|
+
- complete_task: Mark tasks done
|
|
36
|
+
- search_tasks: Find tasks
|
|
37
|
+
- update_task: Modify tasks
|
|
38
|
+
- delete_task: Remove tasks
|
|
39
|
+
- log_ai_session: Log what we accomplish together
|
|
40
|
+
`.trim();
|
|
41
|
+
console.log(JSON.stringify({ additionalContext: context }));
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("TaskFlow SessionStart hook error:", error.message);
|
|
44
|
+
console.log(JSON.stringify({ additionalContext: "" }));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
handleSessionStart
|
|
49
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibetasks/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "VibeTasks MCP Server for Claude Code and other AI coding tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibetasks-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "tsx src/index.ts",
|
|
11
|
+
"build": "tsup src/index.ts --format esm --clean --outDir dist --shims",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
20
|
+
"@vibetasks/core": "^0.1.0",
|
|
21
|
+
"zod": "^3.22.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"tsx": "^4.7.0",
|
|
26
|
+
"tsup": "^8.0.0",
|
|
27
|
+
"typescript": "^5.3.3"
|
|
28
|
+
}
|
|
29
|
+
}
|