motionmcp 1.0.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/.claude/settings.local.json +15 -0
- package/.env.example +3 -0
- package/README.md +510 -0
- package/package.json +26 -0
- package/sample.png +0 -0
- package/src/index.js +47 -0
- package/src/mcp-server.js +1137 -0
- package/src/routes/motion.js +152 -0
- package/src/services/motionApi.js +1177 -0
- package/src/worker.js +248 -0
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
4
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const {
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
} = require("@modelcontextprotocol/sdk/types.js");
|
|
9
|
+
const MotionApiService = require('./services/motionApi.js');
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
|
|
12
|
+
class MotionMCPServer {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.server = new Server(
|
|
15
|
+
{
|
|
16
|
+
name: "motion-mcp-server",
|
|
17
|
+
version: "1.0.0",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
capabilities: {
|
|
21
|
+
tools: {},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
this.motionService = null;
|
|
27
|
+
this.setupHandlers();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async initialize() {
|
|
31
|
+
try {
|
|
32
|
+
this.motionService = new MotionApiService();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("Failed to initialize Motion API service:", error.message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupHandlers() {
|
|
40
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
41
|
+
return {
|
|
42
|
+
tools: [
|
|
43
|
+
{
|
|
44
|
+
name: "create_motion_project",
|
|
45
|
+
description: "Create a new project in Motion",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
name: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Project name (required)"
|
|
52
|
+
},
|
|
53
|
+
description: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Project description (optional)"
|
|
56
|
+
},
|
|
57
|
+
color: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Project color in hex format (optional, e.g., #FF5733)"
|
|
60
|
+
},
|
|
61
|
+
status: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Project status (optional)"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
required: ["name"]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "list_motion_projects",
|
|
71
|
+
description: "List all projects in Motion. If no workspace is specified, will use the default workspace. You can ask the user which workspace they prefer.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
workspaceId: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Optional workspace ID to filter projects. If not provided, uses default workspace."
|
|
78
|
+
},
|
|
79
|
+
workspaceName: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Optional workspace name to filter projects (alternative to workspaceId)."
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
additionalProperties: false
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "get_motion_project",
|
|
89
|
+
description: "Get a specific project by ID from Motion",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
projectId: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "The project ID to retrieve"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
required: ["projectId"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "update_motion_project",
|
|
103
|
+
description: "Update an existing project in Motion",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
projectId: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "The project ID to update"
|
|
110
|
+
},
|
|
111
|
+
name: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Updated project name (optional)"
|
|
114
|
+
},
|
|
115
|
+
description: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Updated project description (optional)"
|
|
118
|
+
},
|
|
119
|
+
color: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Updated project color in hex format (optional)"
|
|
122
|
+
},
|
|
123
|
+
status: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Updated project status (optional)"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
required: ["projectId"]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "delete_motion_project",
|
|
133
|
+
description: "Delete a project from Motion",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
projectId: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "The project ID to delete"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
required: ["projectId"]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "create_motion_task",
|
|
147
|
+
description: "Create a new task in Motion. A workspaceId is required - if not provided, will use the default workspace. If projectId is not specified but a project name is mentioned, will try to find the project in the workspace.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
name: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Task name (required)"
|
|
154
|
+
},
|
|
155
|
+
description: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Task description (optional, supports Markdown)"
|
|
158
|
+
},
|
|
159
|
+
workspaceId: {
|
|
160
|
+
type: "string",
|
|
161
|
+
description: "Workspace ID where the task should be created. If not provided, will use the default workspace."
|
|
162
|
+
},
|
|
163
|
+
workspaceName: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "Workspace name (alternative to workspaceId). Will be resolved to workspaceId."
|
|
166
|
+
},
|
|
167
|
+
projectId: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "Project ID to assign task to (optional). If not provided, task will be created without a project."
|
|
170
|
+
},
|
|
171
|
+
projectName: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description: "Project name (alternative to projectId). Will be resolved to projectId within the specified workspace."
|
|
174
|
+
},
|
|
175
|
+
status: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "Task status (optional). If not provided, uses workspace default status."
|
|
178
|
+
},
|
|
179
|
+
priority: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "Task priority: ASAP, HIGH, MEDIUM, or LOW (optional, defaults to MEDIUM)"
|
|
182
|
+
},
|
|
183
|
+
dueDate: {
|
|
184
|
+
type: "string",
|
|
185
|
+
description: "Task due date in ISO 8601 format (optional, required for scheduled tasks)"
|
|
186
|
+
},
|
|
187
|
+
duration: {
|
|
188
|
+
type: ["string", "number"],
|
|
189
|
+
description: "Task duration in minutes (number) or 'NONE' or 'REMINDER' (optional)"
|
|
190
|
+
},
|
|
191
|
+
assigneeId: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "User ID to assign task to (optional)"
|
|
194
|
+
},
|
|
195
|
+
labels: {
|
|
196
|
+
type: "array",
|
|
197
|
+
items: { type: "string" },
|
|
198
|
+
description: "Array of label names to add to the task (optional)"
|
|
199
|
+
},
|
|
200
|
+
autoScheduled: {
|
|
201
|
+
type: ["object", "null"],
|
|
202
|
+
description: "Auto-scheduling settings (optional). Set to null to disable auto-scheduling."
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
required: ["name"]
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "list_motion_tasks",
|
|
210
|
+
description: "List tasks in Motion with optional filters. If no workspace is specified, will use the default workspace.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
workspaceId: {
|
|
215
|
+
type: "string",
|
|
216
|
+
description: "Optional workspace ID to filter tasks. If not provided, uses default workspace."
|
|
217
|
+
},
|
|
218
|
+
workspaceName: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "Optional workspace name to filter tasks (alternative to workspaceId)."
|
|
221
|
+
},
|
|
222
|
+
projectId: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "Filter tasks by project ID (optional)"
|
|
225
|
+
},
|
|
226
|
+
status: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Filter tasks by status (optional)"
|
|
229
|
+
},
|
|
230
|
+
assigneeId: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Filter tasks by assignee ID (optional)"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
additionalProperties: false
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "get_motion_task",
|
|
240
|
+
description: "Get a specific task by ID from Motion",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
taskId: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "The task ID to retrieve"
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required: ["taskId"]
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "update_motion_task",
|
|
254
|
+
description: "Update an existing task in Motion",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
taskId: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "The task ID to update"
|
|
261
|
+
},
|
|
262
|
+
name: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "Updated task name (optional)"
|
|
265
|
+
},
|
|
266
|
+
description: {
|
|
267
|
+
type: "string",
|
|
268
|
+
description: "Updated task description (optional)"
|
|
269
|
+
},
|
|
270
|
+
status: {
|
|
271
|
+
type: "string",
|
|
272
|
+
description: "Updated task status (optional)"
|
|
273
|
+
},
|
|
274
|
+
priority: {
|
|
275
|
+
type: "string",
|
|
276
|
+
description: "Updated task priority (optional)"
|
|
277
|
+
},
|
|
278
|
+
dueDate: {
|
|
279
|
+
type: "string",
|
|
280
|
+
description: "Updated task due date in ISO format (optional)"
|
|
281
|
+
},
|
|
282
|
+
projectId: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Updated project ID (optional)"
|
|
285
|
+
},
|
|
286
|
+
assigneeId: {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Updated assignee ID (optional)"
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
required: ["taskId"]
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "delete_motion_task",
|
|
296
|
+
description: "Delete a task from Motion",
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: "object",
|
|
299
|
+
properties: {
|
|
300
|
+
taskId: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "The task ID to delete"
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
required: ["taskId"]
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "list_motion_workspaces",
|
|
310
|
+
description: "List all workspaces in Motion. Use this to show users available workspaces so they can choose which one to work with.",
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {},
|
|
314
|
+
additionalProperties: false
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "list_motion_users",
|
|
319
|
+
description: "List all users in Motion",
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: "object",
|
|
322
|
+
properties: {},
|
|
323
|
+
additionalProperties: false
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "get_motion_context",
|
|
328
|
+
description: "Get current Motion context and intelligent defaults. This tool provides the LLM with comprehensive context about the user's Motion workspace, including default workspace, recent activity, and smart suggestions. Use this tool first to understand the user's current state.",
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
includeRecentActivity: {
|
|
333
|
+
type: "boolean",
|
|
334
|
+
description: "Include recent tasks and projects (optional, defaults to true)"
|
|
335
|
+
},
|
|
336
|
+
includeWorkloadSummary: {
|
|
337
|
+
type: "boolean",
|
|
338
|
+
description: "Include workload and task distribution summary (optional, defaults to true)"
|
|
339
|
+
},
|
|
340
|
+
includeSuggestions: {
|
|
341
|
+
type: "boolean",
|
|
342
|
+
description: "Include intelligent suggestions for next actions (optional, defaults to true)"
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
additionalProperties: false
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "search_motion_content",
|
|
350
|
+
description: "Perform intelligent search across tasks and projects by content, keywords, or semantic meaning. This goes beyond simple name matching to search task descriptions, project details, and related content.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
query: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Search query - can be keywords, phrases, or semantic descriptions"
|
|
357
|
+
},
|
|
358
|
+
searchScope: {
|
|
359
|
+
type: "string",
|
|
360
|
+
enum: ["tasks", "projects", "both"],
|
|
361
|
+
description: "What to search (optional, defaults to 'both')"
|
|
362
|
+
},
|
|
363
|
+
workspaceId: {
|
|
364
|
+
type: "string",
|
|
365
|
+
description: "Limit search to specific workspace (optional, defaults to all accessible workspaces)"
|
|
366
|
+
},
|
|
367
|
+
limit: {
|
|
368
|
+
type: "number",
|
|
369
|
+
description: "Maximum number of results to return (optional, defaults to 20)"
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
required: ["query"]
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "analyze_motion_workload",
|
|
377
|
+
description: "Analyze current workload, overdue tasks, upcoming deadlines, and provide insights about task distribution and priorities. Helpful for understanding user's current situation and providing intelligent suggestions.",
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
workspaceId: {
|
|
382
|
+
type: "string",
|
|
383
|
+
description: "Workspace to analyze (optional, defaults to all workspaces)"
|
|
384
|
+
},
|
|
385
|
+
timeframe: {
|
|
386
|
+
type: "string",
|
|
387
|
+
enum: ["today", "this_week", "this_month", "next_week"],
|
|
388
|
+
description: "Time period to analyze (optional, defaults to 'this_week')"
|
|
389
|
+
},
|
|
390
|
+
includeProjects: {
|
|
391
|
+
type: "boolean",
|
|
392
|
+
description: "Include project-level analysis (optional, defaults to true)"
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
additionalProperties: false
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "suggest_next_actions",
|
|
400
|
+
description: "Provide intelligent suggestions for next actions based on current workload, priorities, deadlines, and project status. Helps LLM provide proactive assistance.",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
workspaceId: {
|
|
405
|
+
type: "string",
|
|
406
|
+
description: "Workspace to analyze for suggestions (optional, uses default workspace)"
|
|
407
|
+
},
|
|
408
|
+
context: {
|
|
409
|
+
type: "string",
|
|
410
|
+
description: "Current context or goal (optional, e.g., 'daily planning', 'project review', 'end of week')"
|
|
411
|
+
},
|
|
412
|
+
maxSuggestions: {
|
|
413
|
+
type: "number",
|
|
414
|
+
description: "Maximum number of suggestions to return (optional, defaults to 5)"
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
additionalProperties: false
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "create_project_template",
|
|
422
|
+
description: "Create a new project with a predefined template including common tasks, structure, and best practices. Templates can be customized based on project type and user preferences.",
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: "object",
|
|
425
|
+
properties: {
|
|
426
|
+
name: {
|
|
427
|
+
type: "string",
|
|
428
|
+
description: "Project name"
|
|
429
|
+
},
|
|
430
|
+
templateType: {
|
|
431
|
+
type: "string",
|
|
432
|
+
enum: ["software_development", "marketing_campaign", "research_project", "event_planning", "content_creation", "general"],
|
|
433
|
+
description: "Type of project template to use"
|
|
434
|
+
},
|
|
435
|
+
workspaceId: {
|
|
436
|
+
type: "string",
|
|
437
|
+
description: "Workspace where project should be created (optional, uses default)"
|
|
438
|
+
},
|
|
439
|
+
customizations: {
|
|
440
|
+
type: "object",
|
|
441
|
+
description: "Template customizations (optional)",
|
|
442
|
+
properties: {
|
|
443
|
+
includeTaskTemplates: {
|
|
444
|
+
type: "boolean",
|
|
445
|
+
description: "Whether to create template tasks (defaults to true)"
|
|
446
|
+
},
|
|
447
|
+
projectDuration: {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: "Expected project duration (e.g., '2 weeks', '3 months')"
|
|
450
|
+
},
|
|
451
|
+
teamSize: {
|
|
452
|
+
type: "number",
|
|
453
|
+
description: "Expected team size for task assignment"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
required: ["name", "templateType"]
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "bulk_update_tasks",
|
|
463
|
+
description: "Update multiple tasks at once with the same changes. Useful for batch operations like changing status, priority, or assignee for multiple tasks.",
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: {
|
|
467
|
+
taskIds: {
|
|
468
|
+
type: "array",
|
|
469
|
+
items: { type: "string" },
|
|
470
|
+
description: "Array of task IDs to update"
|
|
471
|
+
},
|
|
472
|
+
updates: {
|
|
473
|
+
type: "object",
|
|
474
|
+
description: "Updates to apply to all tasks",
|
|
475
|
+
properties: {
|
|
476
|
+
status: { type: "string" },
|
|
477
|
+
priority: { type: "string" },
|
|
478
|
+
assigneeId: { type: "string" },
|
|
479
|
+
projectId: { type: "string" },
|
|
480
|
+
dueDate: { type: "string" }
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
workspaceId: {
|
|
484
|
+
type: "string",
|
|
485
|
+
description: "Workspace ID for validation (optional, uses default)"
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
required: ["taskIds", "updates"]
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "smart_schedule_tasks",
|
|
493
|
+
description: "Intelligently schedule multiple tasks based on priorities, deadlines, estimated durations, and availability. Provides optimal scheduling suggestions.",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
taskIds: {
|
|
498
|
+
type: "array",
|
|
499
|
+
items: { type: "string" },
|
|
500
|
+
description: "Array of task IDs to schedule (optional, will auto-select unscheduled tasks if not provided)"
|
|
501
|
+
},
|
|
502
|
+
workspaceId: {
|
|
503
|
+
type: "string",
|
|
504
|
+
description: "Workspace to schedule tasks in (optional, uses default)"
|
|
505
|
+
},
|
|
506
|
+
schedulingPreferences: {
|
|
507
|
+
type: "object",
|
|
508
|
+
description: "Scheduling preferences (optional)",
|
|
509
|
+
properties: {
|
|
510
|
+
prioritizeDeadlines: {
|
|
511
|
+
type: "boolean",
|
|
512
|
+
description: "Prioritize tasks with deadlines (defaults to true)"
|
|
513
|
+
},
|
|
514
|
+
respectPriorities: {
|
|
515
|
+
type: "boolean",
|
|
516
|
+
description: "Schedule higher priority tasks first (defaults to true)"
|
|
517
|
+
},
|
|
518
|
+
includeBufferTime: {
|
|
519
|
+
type: "boolean",
|
|
520
|
+
description: "Add buffer time between tasks (defaults to true)"
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
additionalProperties: false
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
]
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
533
|
+
const { name, arguments: args } = request.params;
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
switch (name) {
|
|
537
|
+
case "create_motion_project":
|
|
538
|
+
return await this.handleCreateProject(args);
|
|
539
|
+
|
|
540
|
+
case "list_motion_projects":
|
|
541
|
+
return await this.handleListProjects(args);
|
|
542
|
+
|
|
543
|
+
case "get_motion_project":
|
|
544
|
+
return await this.handleGetProject(args);
|
|
545
|
+
|
|
546
|
+
case "update_motion_project":
|
|
547
|
+
return await this.handleUpdateProject(args);
|
|
548
|
+
|
|
549
|
+
case "delete_motion_project":
|
|
550
|
+
return await this.handleDeleteProject(args);
|
|
551
|
+
|
|
552
|
+
case "create_motion_task":
|
|
553
|
+
return await this.handleCreateTask(args);
|
|
554
|
+
|
|
555
|
+
case "list_motion_tasks":
|
|
556
|
+
return await this.handleListTasks(args);
|
|
557
|
+
|
|
558
|
+
case "get_motion_task":
|
|
559
|
+
return await this.handleGetTask(args);
|
|
560
|
+
|
|
561
|
+
case "update_motion_task":
|
|
562
|
+
return await this.handleUpdateTask(args);
|
|
563
|
+
|
|
564
|
+
case "delete_motion_task":
|
|
565
|
+
return await this.handleDeleteTask(args);
|
|
566
|
+
|
|
567
|
+
case "list_motion_workspaces":
|
|
568
|
+
return await this.handleListWorkspaces();
|
|
569
|
+
|
|
570
|
+
case "list_motion_users":
|
|
571
|
+
return await this.handleListUsers();
|
|
572
|
+
|
|
573
|
+
case "get_motion_context":
|
|
574
|
+
return await this.handleGetContext(args);
|
|
575
|
+
|
|
576
|
+
case "search_motion_content":
|
|
577
|
+
return await this.handleSearchContent(args);
|
|
578
|
+
|
|
579
|
+
case "analyze_motion_workload":
|
|
580
|
+
return await this.handleAnalyzeWorkload(args);
|
|
581
|
+
|
|
582
|
+
case "suggest_next_actions":
|
|
583
|
+
return await this.handleSuggestNextActions(args);
|
|
584
|
+
|
|
585
|
+
case "create_project_template":
|
|
586
|
+
return await this.handleCreateProjectTemplate(args);
|
|
587
|
+
|
|
588
|
+
case "bulk_update_tasks":
|
|
589
|
+
return await this.handleBulkUpdateTasks(args);
|
|
590
|
+
|
|
591
|
+
case "smart_schedule_tasks":
|
|
592
|
+
return await this.handleSmartScheduleTasks(args);
|
|
593
|
+
|
|
594
|
+
default:
|
|
595
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
596
|
+
}
|
|
597
|
+
} catch (error) {
|
|
598
|
+
return {
|
|
599
|
+
content: [
|
|
600
|
+
{
|
|
601
|
+
type: "text",
|
|
602
|
+
text: `Error: ${error.message}`
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
isError: true
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async handleCreateProject(args) {
|
|
612
|
+
const project = await this.motionService.createProject(args);
|
|
613
|
+
return {
|
|
614
|
+
content: [
|
|
615
|
+
{
|
|
616
|
+
type: "text",
|
|
617
|
+
text: `Successfully created project "${project.name}" with ID: ${project.id}`
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async handleListProjects(args = {}) {
|
|
624
|
+
let workspaceId = args.workspaceId;
|
|
625
|
+
let workspaceName = null;
|
|
626
|
+
|
|
627
|
+
// If workspace name provided instead of ID, look it up
|
|
628
|
+
if (!workspaceId && args.workspaceName) {
|
|
629
|
+
try {
|
|
630
|
+
const workspace = await this.motionService.getWorkspaceByName(args.workspaceName);
|
|
631
|
+
workspaceId = workspace.id;
|
|
632
|
+
workspaceName = workspace.name;
|
|
633
|
+
} catch (error) {
|
|
634
|
+
return {
|
|
635
|
+
content: [
|
|
636
|
+
{
|
|
637
|
+
type: "text",
|
|
638
|
+
text: `Error: Could not find workspace "${args.workspaceName}". Available workspaces can be listed with list_motion_workspaces.`
|
|
639
|
+
}
|
|
640
|
+
],
|
|
641
|
+
isError: true
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Get projects for the specified or default workspace
|
|
647
|
+
const projects = await this.motionService.getProjects(workspaceId);
|
|
648
|
+
|
|
649
|
+
// Get workspace info for context
|
|
650
|
+
if (!workspaceName && workspaceId) {
|
|
651
|
+
try {
|
|
652
|
+
const workspaces = await this.motionService.getWorkspaces();
|
|
653
|
+
const workspace = workspaces.find(w => w.id === workspaceId);
|
|
654
|
+
workspaceName = workspace ? workspace.name : 'Unknown';
|
|
655
|
+
} catch (error) {
|
|
656
|
+
workspaceName = 'Unknown';
|
|
657
|
+
}
|
|
658
|
+
} else if (!workspaceName) {
|
|
659
|
+
try {
|
|
660
|
+
const defaultWorkspace = await this.motionService.getDefaultWorkspace();
|
|
661
|
+
workspaceName = defaultWorkspace.name;
|
|
662
|
+
workspaceId = defaultWorkspace.id;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
workspaceName = 'Default';
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const projectList = projects.map(p => `- ${p.name} (ID: ${p.id})`).join('\n');
|
|
669
|
+
|
|
670
|
+
let responseText = `Found ${projects.length} projects in workspace "${workspaceName}"`;
|
|
671
|
+
if (workspaceId) {
|
|
672
|
+
responseText += ` (ID: ${workspaceId})`;
|
|
673
|
+
}
|
|
674
|
+
responseText += `:\n${projectList}`;
|
|
675
|
+
|
|
676
|
+
// If no workspace was specified and there are multiple workspaces, suggest the user can specify one
|
|
677
|
+
if (!args.workspaceId && !args.workspaceName) {
|
|
678
|
+
responseText += `\n\nNote: This shows projects from the default workspace. You can specify a different workspace using the workspaceId or workspaceName parameter, or use list_motion_workspaces to see all available workspaces.`;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return {
|
|
682
|
+
content: [
|
|
683
|
+
{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: responseText
|
|
686
|
+
}
|
|
687
|
+
]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async handleGetProject(args) {
|
|
692
|
+
const project = await this.motionService.getProject(args.projectId);
|
|
693
|
+
return {
|
|
694
|
+
content: [
|
|
695
|
+
{
|
|
696
|
+
type: "text",
|
|
697
|
+
text: `Project Details:\n- Name: ${project.name}\n- ID: ${project.id}\n- Description: ${project.description || 'N/A'}\n- Status: ${project.status || 'N/A'}`
|
|
698
|
+
}
|
|
699
|
+
]
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async handleUpdateProject(args) {
|
|
704
|
+
const { projectId, ...updateData } = args;
|
|
705
|
+
const project = await this.motionService.updateProject(projectId, updateData);
|
|
706
|
+
return {
|
|
707
|
+
content: [
|
|
708
|
+
{
|
|
709
|
+
type: "text",
|
|
710
|
+
text: `Successfully updated project "${project.name}" (ID: ${project.id})`
|
|
711
|
+
}
|
|
712
|
+
]
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async handleDeleteProject(args) {
|
|
717
|
+
await this.motionService.deleteProject(args.projectId);
|
|
718
|
+
return {
|
|
719
|
+
content: [
|
|
720
|
+
{
|
|
721
|
+
type: "text",
|
|
722
|
+
text: `Successfully deleted project with ID: ${args.projectId}`
|
|
723
|
+
}
|
|
724
|
+
]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async handleCreateTask(args) {
|
|
729
|
+
try {
|
|
730
|
+
// Resolve workspace ID if needed
|
|
731
|
+
let workspaceId = args.workspaceId;
|
|
732
|
+
if (!workspaceId && args.workspaceName) {
|
|
733
|
+
const workspace = await this.motionService.getWorkspaceByName(args.workspaceName);
|
|
734
|
+
workspaceId = workspace.id;
|
|
735
|
+
} else if (!workspaceId) {
|
|
736
|
+
const defaultWorkspace = await this.motionService.getDefaultWorkspace();
|
|
737
|
+
workspaceId = defaultWorkspace.id;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Resolve project ID if needed
|
|
741
|
+
let projectId = args.projectId;
|
|
742
|
+
if (!projectId && args.projectName) {
|
|
743
|
+
try {
|
|
744
|
+
const project = await this.motionService.getProjectByName(args.projectName, workspaceId);
|
|
745
|
+
projectId = project.id;
|
|
746
|
+
} catch (projectError) {
|
|
747
|
+
throw new Error(`Project "${args.projectName}" not found in workspace`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Build task data with required workspaceId
|
|
752
|
+
const taskData = {
|
|
753
|
+
name: args.name,
|
|
754
|
+
workspaceId, // Required by Motion API
|
|
755
|
+
...(args.description && { description: args.description }),
|
|
756
|
+
...(projectId && { projectId }),
|
|
757
|
+
...(args.status && { status: args.status }),
|
|
758
|
+
...(args.priority && { priority: args.priority }),
|
|
759
|
+
...(args.dueDate && { dueDate: args.dueDate }),
|
|
760
|
+
...(args.duration && { duration: args.duration }),
|
|
761
|
+
...(args.assigneeId && { assigneeId: args.assigneeId }),
|
|
762
|
+
...(args.labels && { labels: args.labels }),
|
|
763
|
+
...(args.autoScheduled !== undefined && { autoScheduled: args.autoScheduled })
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const task = await this.motionService.createTask(taskData);
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
content: [
|
|
770
|
+
{
|
|
771
|
+
type: "text",
|
|
772
|
+
text: `Successfully created task "${task.name}" with ID: ${task.id}${projectId ? ` in project ${projectId}` : ''} in workspace ${workspaceId}`
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
};
|
|
776
|
+
} catch (error) {
|
|
777
|
+
return {
|
|
778
|
+
content: [
|
|
779
|
+
{
|
|
780
|
+
type: "text",
|
|
781
|
+
text: `Failed to create task: ${error.message}`
|
|
782
|
+
}
|
|
783
|
+
],
|
|
784
|
+
isError: true
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async handleListTasks(args = {}) {
|
|
790
|
+
const tasks = await this.motionService.getTasks(args);
|
|
791
|
+
const taskList = tasks.map(t => `- ${t.name} (ID: ${t.id}) - Status: ${t.status || 'N/A'}`).join('\n');
|
|
792
|
+
return {
|
|
793
|
+
content: [
|
|
794
|
+
{
|
|
795
|
+
type: "text",
|
|
796
|
+
text: `Found ${tasks.length} tasks:\n${taskList}`
|
|
797
|
+
}
|
|
798
|
+
]
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async handleGetTask(args) {
|
|
803
|
+
const task = await this.motionService.getTask(args.taskId);
|
|
804
|
+
return {
|
|
805
|
+
content: [
|
|
806
|
+
{
|
|
807
|
+
type: "text",
|
|
808
|
+
text: `Task Details:\n- Name: ${task.name}\n- ID: ${task.id}\n- Description: ${task.description || 'N/A'}\n- Status: ${task.status || 'N/A'}\n- Priority: ${task.priority || 'N/A'}`
|
|
809
|
+
}
|
|
810
|
+
]
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async handleUpdateTask(args) {
|
|
815
|
+
const { taskId, ...updateData } = args;
|
|
816
|
+
const task = await this.motionService.updateTask(taskId, updateData);
|
|
817
|
+
return {
|
|
818
|
+
content: [
|
|
819
|
+
{
|
|
820
|
+
type: "text",
|
|
821
|
+
text: `Successfully updated task "${task.name}" (ID: ${task.id})`
|
|
822
|
+
}
|
|
823
|
+
]
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async handleDeleteTask(args) {
|
|
828
|
+
await this.motionService.deleteTask(args.taskId);
|
|
829
|
+
return {
|
|
830
|
+
content: [
|
|
831
|
+
{
|
|
832
|
+
type: "text",
|
|
833
|
+
text: `Successfully deleted task with ID: ${args.taskId}`
|
|
834
|
+
}
|
|
835
|
+
]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
async handleListWorkspaces() {
|
|
840
|
+
const workspaces = await this.motionService.getWorkspaces();
|
|
841
|
+
const defaultWorkspace = await this.motionService.getDefaultWorkspace();
|
|
842
|
+
|
|
843
|
+
const workspaceList = workspaces.map(w => {
|
|
844
|
+
const isDefault = w.id === defaultWorkspace.id ? ' (DEFAULT)' : '';
|
|
845
|
+
return `- ${w.name} (ID: ${w.id})${isDefault} - Type: ${w.type}`;
|
|
846
|
+
}).join('\n');
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
content: [
|
|
850
|
+
{
|
|
851
|
+
type: "text",
|
|
852
|
+
text: `Found ${workspaces.length} workspaces:\n${workspaceList}\n\nYou can use either the workspace name or ID when specifying which workspace to work with in other commands.`
|
|
853
|
+
}
|
|
854
|
+
]
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
async handleListUsers() {
|
|
859
|
+
const users = await this.motionService.getUsers();
|
|
860
|
+
const userList = users.map(u => `- ${u.name} (ID: ${u.id}) - ${u.email || 'No email'}`).join('\n');
|
|
861
|
+
return {
|
|
862
|
+
content: [
|
|
863
|
+
{
|
|
864
|
+
type: "text",
|
|
865
|
+
text: `Found ${users.length} users:\n${userList}`
|
|
866
|
+
}
|
|
867
|
+
]
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async handleGetContext(args) {
|
|
872
|
+
const includeRecentActivity = args.includeRecentActivity !== false; // Default to true
|
|
873
|
+
const includeWorkloadSummary = args.includeWorkloadSummary !== false; // Default to true
|
|
874
|
+
const includeSuggestions = args.includeSuggestions !== false; // Default to true
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
const context = await this.motionService.getContext({
|
|
878
|
+
includeRecentActivity,
|
|
879
|
+
includeWorkloadSummary,
|
|
880
|
+
includeSuggestions
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
let responseText = "Current Motion Context:\n";
|
|
884
|
+
|
|
885
|
+
if (context.defaultWorkspace) {
|
|
886
|
+
responseText += `- Default Workspace: ${context.defaultWorkspace.name} (ID: ${context.defaultWorkspace.id})\n`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (context.recentProjects && context.recentProjects.length > 0) {
|
|
890
|
+
responseText += `- Recent Projects:\n`;
|
|
891
|
+
context.recentProjects.forEach(project => {
|
|
892
|
+
responseText += ` - ${project.name} (ID: ${project.id})\n`;
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (context.recentTasks && context.recentTasks.length > 0) {
|
|
897
|
+
responseText += `- Recent Tasks:\n`;
|
|
898
|
+
context.recentTasks.forEach(task => {
|
|
899
|
+
responseText += ` - ${task.name} (ID: ${task.id}) - Status: ${task.status || 'N/A'}\n`;
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (context.suggestions && context.suggestions.length > 0) {
|
|
904
|
+
responseText += `- Suggestions:\n`;
|
|
905
|
+
context.suggestions.forEach(suggestion => {
|
|
906
|
+
responseText += ` - ${suggestion}\n`;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return {
|
|
911
|
+
content: [
|
|
912
|
+
{
|
|
913
|
+
type: "text",
|
|
914
|
+
text: responseText
|
|
915
|
+
}
|
|
916
|
+
]
|
|
917
|
+
};
|
|
918
|
+
} catch (error) {
|
|
919
|
+
return {
|
|
920
|
+
content: [
|
|
921
|
+
{
|
|
922
|
+
type: "text",
|
|
923
|
+
text: `Failed to get context: ${error.message}`
|
|
924
|
+
}
|
|
925
|
+
],
|
|
926
|
+
isError: true
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async handleSearchContent(args) {
|
|
932
|
+
const { query, searchScope = "both", workspaceId, limit = 20 } = args;
|
|
933
|
+
|
|
934
|
+
// Perform the search using the Motion API
|
|
935
|
+
const results = await this.motionService.searchContent({
|
|
936
|
+
query,
|
|
937
|
+
searchScope,
|
|
938
|
+
workspaceId,
|
|
939
|
+
limit
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// Format the search results for response
|
|
943
|
+
const formattedResults = results.map(result => {
|
|
944
|
+
const type = result.projectId ? "task" : "project";
|
|
945
|
+
return `- [${type}] ${result.name} (ID: ${result.id})`;
|
|
946
|
+
}).join('\n');
|
|
947
|
+
|
|
948
|
+
return {
|
|
949
|
+
content: [
|
|
950
|
+
{
|
|
951
|
+
type: "text",
|
|
952
|
+
text: `Search Results for "${query}" (Limit: ${limit}):\n${formattedResults}`
|
|
953
|
+
}
|
|
954
|
+
]
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
async handleAnalyzeWorkload(args) {
|
|
959
|
+
const { workspaceId, timeframe = "this_week", includeProjects = true } = args;
|
|
960
|
+
|
|
961
|
+
// Analyze workload using the Motion API
|
|
962
|
+
const analysis = await this.motionService.analyzeWorkload({
|
|
963
|
+
workspaceId,
|
|
964
|
+
timeframe,
|
|
965
|
+
includeProjects
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
// Format the analysis results for response
|
|
969
|
+
let responseText = `Workload Analysis (${timeframe}):\n`;
|
|
970
|
+
responseText += `- Total Tasks: ${analysis.totalTasks}\n`;
|
|
971
|
+
responseText += `- Overdue Tasks: ${analysis.overdueTasks}\n`;
|
|
972
|
+
responseText += `- Upcoming Deadlines: ${analysis.upcomingDeadlines}\n`;
|
|
973
|
+
responseText += `- Task Distribution: ${JSON.stringify(analysis.taskDistribution, null, 2)}\n`;
|
|
974
|
+
|
|
975
|
+
if (includeProjects) {
|
|
976
|
+
responseText += `- Project Insights: ${JSON.stringify(analysis.projectInsights, null, 2)}\n`;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return {
|
|
980
|
+
content: [
|
|
981
|
+
{
|
|
982
|
+
type: "text",
|
|
983
|
+
text: responseText
|
|
984
|
+
}
|
|
985
|
+
]
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async handleSuggestNextActions(args) {
|
|
990
|
+
const { workspaceId, context, maxSuggestions = 5 } = args;
|
|
991
|
+
|
|
992
|
+
// Get current workload and tasks
|
|
993
|
+
const tasks = await this.motionService.getTasks({ workspaceId });
|
|
994
|
+
const projects = await this.motionService.getProjects(workspaceId);
|
|
995
|
+
|
|
996
|
+
// Analyze tasks and projects to suggest next actions
|
|
997
|
+
const suggestions = this.generateSuggestions(tasks, projects, context, maxSuggestions);
|
|
998
|
+
|
|
999
|
+
return {
|
|
1000
|
+
content: [
|
|
1001
|
+
{
|
|
1002
|
+
type: "text",
|
|
1003
|
+
text: `Suggested Next Actions:\n${suggestions.join('\n')}`
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
generateSuggestions(tasks, projects, context, maxSuggestions) {
|
|
1010
|
+
// Simple heuristic: suggest based on priority and due date
|
|
1011
|
+
const now = new Date();
|
|
1012
|
+
const sortedTasks = tasks
|
|
1013
|
+
.filter(task => task.dueDate) // Only scheduled tasks
|
|
1014
|
+
.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
|
|
1015
|
+
|
|
1016
|
+
const highPriorityTasks = sortedTasks.filter(task => task.priority === 'ASAP' || task.priority === 'HIGH');
|
|
1017
|
+
const upcomingDeadlines = sortedTasks.filter(task => new Date(task.dueDate) <= new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000)); // Due in 3 days
|
|
1018
|
+
|
|
1019
|
+
let suggestions = [];
|
|
1020
|
+
|
|
1021
|
+
// Suggest high priority tasks first
|
|
1022
|
+
suggestions.push(...highPriorityTasks.map(task => `✅ ${task.name} (ID: ${task.id}) - Due: ${task.dueDate}`));
|
|
1023
|
+
|
|
1024
|
+
// Then suggest tasks with upcoming deadlines
|
|
1025
|
+
suggestions.push(...upcomingDeadlines.map(task => `⏰ ${task.name} (ID: ${task.id}) - Due: ${task.dueDate}`));
|
|
1026
|
+
|
|
1027
|
+
// Add project-related suggestions if context is project review
|
|
1028
|
+
if (context && context.includes('project review')) {
|
|
1029
|
+
const stalledProjects = projects.filter(project => project.status === 'stalled' || project.status === 'on hold');
|
|
1030
|
+
suggestions.push(...stalledProjects.map(project => `🔄 Review project: ${project.name} (ID: ${project.id})`));
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Limit to maxSuggestions
|
|
1034
|
+
return suggestions.slice(0, maxSuggestions);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
async handleCreateProjectTemplate(args) {
|
|
1038
|
+
const { name, templateType, workspaceId, customizations } = args;
|
|
1039
|
+
|
|
1040
|
+
// Create project with template logic
|
|
1041
|
+
const project = await this.motionService.createProject({
|
|
1042
|
+
name,
|
|
1043
|
+
workspaceId,
|
|
1044
|
+
description: `Project created from ${templateType} template`,
|
|
1045
|
+
status: 'active'
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
// TODO: Add logic to apply template-specific settings and tasks
|
|
1049
|
+
|
|
1050
|
+
return {
|
|
1051
|
+
content: [
|
|
1052
|
+
{
|
|
1053
|
+
type: "text",
|
|
1054
|
+
text: `Successfully created project "${project.name}" with ID: ${project.id} from ${templateType} template`
|
|
1055
|
+
}
|
|
1056
|
+
]
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async handleBulkUpdateTasks(args) {
|
|
1061
|
+
const { taskIds, updates, workspaceId } = args;
|
|
1062
|
+
|
|
1063
|
+
// Validate workspace ID if provided
|
|
1064
|
+
if (workspaceId) {
|
|
1065
|
+
const workspace = await this.motionService.getWorkspace(workspaceId);
|
|
1066
|
+
if (!workspace) {
|
|
1067
|
+
return {
|
|
1068
|
+
content: [
|
|
1069
|
+
{
|
|
1070
|
+
type: "text",
|
|
1071
|
+
text: `Invalid workspace ID: ${workspaceId}`
|
|
1072
|
+
}
|
|
1073
|
+
],
|
|
1074
|
+
isError: true
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Perform the bulk update
|
|
1080
|
+
await this.motionService.bulkUpdateTasks(taskIds, updates);
|
|
1081
|
+
|
|
1082
|
+
return {
|
|
1083
|
+
content: [
|
|
1084
|
+
{
|
|
1085
|
+
type: "text",
|
|
1086
|
+
text: `Successfully updated ${taskIds.length} tasks`
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
async handleSmartScheduleTasks(args) {
|
|
1093
|
+
const { taskIds, workspaceId, schedulingPreferences } = args;
|
|
1094
|
+
|
|
1095
|
+
// Resolve workspace ID if not provided
|
|
1096
|
+
if (!workspaceId) {
|
|
1097
|
+
const defaultWorkspace = await this.motionService.getDefaultWorkspace();
|
|
1098
|
+
workspaceId = defaultWorkspace.id;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Perform smart scheduling
|
|
1102
|
+
const schedule = await this.motionService.smartScheduleTasks(taskIds, workspaceId, schedulingPreferences);
|
|
1103
|
+
|
|
1104
|
+
let responseText = `Scheduled ${taskIds.length} tasks:\n`;
|
|
1105
|
+
schedule.forEach(s => {
|
|
1106
|
+
responseText += `- Task ID: ${s.taskId} -> Scheduled Time: ${s.scheduledTime}\n`;
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
return {
|
|
1110
|
+
content: [
|
|
1111
|
+
{
|
|
1112
|
+
type: "text",
|
|
1113
|
+
text: responseText
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
async run() {
|
|
1120
|
+
await this.initialize();
|
|
1121
|
+
|
|
1122
|
+
const transport = new StdioServerTransport();
|
|
1123
|
+
await this.server.connect(transport);
|
|
1124
|
+
|
|
1125
|
+
console.error("Motion MCP Server running on stdio");
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (require.main === module) {
|
|
1130
|
+
const server = new MotionMCPServer();
|
|
1131
|
+
server.run().catch((error) => {
|
|
1132
|
+
console.error("Failed to run server:", error);
|
|
1133
|
+
process.exit(1);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
module.exports = MotionMCPServer;
|