motionmcp 2.2.4 → 2.4.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 +121 -164
- package/dist/handlers/ProjectHandler.js +4 -4
- package/dist/handlers/ProjectHandler.js.map +1 -1
- package/dist/handlers/RecurringTaskHandler.js +2 -2
- package/dist/handlers/RecurringTaskHandler.js.map +1 -1
- package/dist/handlers/SearchHandler.d.ts.map +1 -1
- package/dist/handlers/SearchHandler.js +20 -4
- package/dist/handlers/SearchHandler.js.map +1 -1
- package/dist/handlers/TaskHandler.d.ts.map +1 -1
- package/dist/handlers/TaskHandler.js +31 -5
- package/dist/handlers/TaskHandler.js.map +1 -1
- package/dist/mcp-server-old.d.ts +29 -0
- package/dist/mcp-server-old.d.ts.map +1 -0
- package/dist/mcp-server-old.js +1304 -0
- package/dist/mcp-server-old.js.map +1 -0
- package/dist/mcp-server.js +0 -0
- package/dist/services/motionApi.d.ts +11 -9
- package/dist/services/motionApi.d.ts.map +1 -1
- package/dist/services/motionApi.js +182 -120
- package/dist/services/motionApi.js.map +1 -1
- package/dist/tools/ToolDefinitions.d.ts.map +1 -1
- package/dist/tools/ToolDefinitions.js +9 -2
- package/dist/tools/ToolDefinitions.js.map +1 -1
- package/dist/types/mcp-tool-args.d.ts +2 -1
- package/dist/types/mcp-tool-args.d.ts.map +1 -1
- package/dist/types/mcp.d.ts +10 -0
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/jsonSchemaToZod.d.ts +33 -0
- package/dist/utils/jsonSchemaToZod.d.ts.map +1 -0
- package/dist/utils/jsonSchemaToZod.js +110 -0
- package/dist/utils/jsonSchemaToZod.js.map +1 -0
- package/dist/utils/paginationNew.d.ts +2 -0
- package/dist/utils/paginationNew.d.ts.map +1 -1
- package/dist/utils/paginationNew.js +10 -1
- package/dist/utils/paginationNew.js.map +1 -1
- package/dist/utils/responseFormatters.d.ts +9 -1
- package/dist/utils/responseFormatters.d.ts.map +1 -1
- package/dist/utils/responseFormatters.js +33 -7
- package/dist/utils/responseFormatters.js.map +1 -1
- package/dist/worker.d.ts +17 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +61 -0
- package/dist/worker.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,1304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
38
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
40
|
+
const motionApi_1 = require("./services/motionApi");
|
|
41
|
+
const utils_1 = require("./utils");
|
|
42
|
+
const sanitize_1 = require("./utils/sanitize");
|
|
43
|
+
const validator_1 = require("./utils/validator");
|
|
44
|
+
const dotenv = __importStar(require("dotenv"));
|
|
45
|
+
dotenv.config();
|
|
46
|
+
class MotionMCPServer {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.server = new index_js_1.Server({
|
|
49
|
+
name: "motion-mcp-server",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
}, {
|
|
52
|
+
capabilities: {
|
|
53
|
+
tools: {},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
this.motionService = null;
|
|
57
|
+
this.workspaceResolver = null;
|
|
58
|
+
this.validator = new validator_1.InputValidator();
|
|
59
|
+
this.toolsConfig = process.env.MOTION_MCP_TOOLS || 'essential';
|
|
60
|
+
this.setupHandlers();
|
|
61
|
+
}
|
|
62
|
+
async initialize() {
|
|
63
|
+
try {
|
|
64
|
+
// Validate tools configuration
|
|
65
|
+
this.validateToolsConfig();
|
|
66
|
+
this.motionService = new motionApi_1.MotionApiService();
|
|
67
|
+
this.workspaceResolver = new utils_1.WorkspaceResolver(this.motionService);
|
|
68
|
+
// Initialize validators with tool definitions
|
|
69
|
+
this.validator.initializeValidators(this.getEnabledTools());
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.ERROR, "Failed to initialize Motion API service", { error: errorMessage });
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
validateToolsConfig() {
|
|
78
|
+
const validConfigs = ['minimal', 'essential', 'complete'];
|
|
79
|
+
// Check if it's a valid preset or custom configuration
|
|
80
|
+
if (!validConfigs.includes(this.toolsConfig) && !this.toolsConfig.startsWith('custom:')) {
|
|
81
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.ERROR, `Invalid MOTION_MCP_TOOLS configuration: "${this.toolsConfig}"`, {
|
|
82
|
+
validOptions: [...validConfigs, 'custom:tool1,tool2,...'],
|
|
83
|
+
defaulting: 'essential'
|
|
84
|
+
});
|
|
85
|
+
// Still default to essential, but log it prominently
|
|
86
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.WARN, 'Defaulting to "essential" configuration');
|
|
87
|
+
this.toolsConfig = 'essential';
|
|
88
|
+
}
|
|
89
|
+
// For custom configurations, validate tool names exist
|
|
90
|
+
if (this.toolsConfig.startsWith('custom:')) {
|
|
91
|
+
const customTools = this.toolsConfig.substring(7).split(',').map(s => s.trim());
|
|
92
|
+
const allToolNames = this.getAllToolDefinitions().map(t => t.name);
|
|
93
|
+
const invalidTools = customTools.filter(name => !allToolNames.includes(name));
|
|
94
|
+
if (invalidTools.length > 0) {
|
|
95
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.ERROR, 'Invalid tool names in custom configuration', {
|
|
96
|
+
invalidTools,
|
|
97
|
+
availableTools: allToolNames
|
|
98
|
+
});
|
|
99
|
+
throw new Error(`Invalid tool names in custom configuration: ${invalidTools.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (customTools.length === 0) {
|
|
102
|
+
throw new Error('Custom configuration must specify at least one tool');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.INFO, `Tool configuration validated: ${this.toolsConfig}`);
|
|
106
|
+
}
|
|
107
|
+
setupHandlers() {
|
|
108
|
+
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
109
|
+
return {
|
|
110
|
+
tools: this.getEnabledTools()
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
114
|
+
if (!this.motionService || !this.workspaceResolver) {
|
|
115
|
+
return (0, utils_1.formatMcpError)(new Error("Server not initialized"));
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const { name, arguments: args } = request.params;
|
|
119
|
+
// Runtime validation
|
|
120
|
+
const validation = this.validator.validateInput(name, args);
|
|
121
|
+
if (!validation.valid) {
|
|
122
|
+
return (0, utils_1.formatMcpError)(new Error(`Invalid arguments for ${name}: ${validation.errors}`));
|
|
123
|
+
}
|
|
124
|
+
switch (name) {
|
|
125
|
+
// Consolidated tools
|
|
126
|
+
case "motion_projects":
|
|
127
|
+
return await this.handleMotionProjects(args);
|
|
128
|
+
case "motion_tasks":
|
|
129
|
+
return await this.handleMotionTasks(args);
|
|
130
|
+
case "motion_workspaces":
|
|
131
|
+
return await this.handleMotionWorkspaces(args);
|
|
132
|
+
case "motion_users":
|
|
133
|
+
return await this.handleMotionUsers(args);
|
|
134
|
+
case "motion_search":
|
|
135
|
+
return await this.handleMotionSearch(args);
|
|
136
|
+
case "motion_comments":
|
|
137
|
+
return await this.handleMotionComments(args);
|
|
138
|
+
case "motion_custom_fields":
|
|
139
|
+
return await this.handleMotionCustomFields(args);
|
|
140
|
+
case "motion_recurring_tasks":
|
|
141
|
+
return await this.handleMotionRecurringTasks(args);
|
|
142
|
+
case "motion_schedules":
|
|
143
|
+
return await this.handleMotionSchedules(args);
|
|
144
|
+
case "motion_statuses":
|
|
145
|
+
return await this.handleMotionStatuses(args);
|
|
146
|
+
default:
|
|
147
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown tool: ${name}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
getAllToolDefinitions() {
|
|
156
|
+
return [
|
|
157
|
+
// Consolidated tools
|
|
158
|
+
{
|
|
159
|
+
name: "motion_projects",
|
|
160
|
+
description: "Manage Motion projects - supports create, list, and get operations",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
operation: {
|
|
165
|
+
type: "string",
|
|
166
|
+
enum: ["create", "list", "get"],
|
|
167
|
+
description: "Operation to perform"
|
|
168
|
+
},
|
|
169
|
+
projectId: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Project ID (required for get operation)"
|
|
172
|
+
},
|
|
173
|
+
workspaceId: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Workspace ID"
|
|
176
|
+
},
|
|
177
|
+
workspaceName: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Workspace name (alternative to ID)"
|
|
180
|
+
},
|
|
181
|
+
name: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "Project name"
|
|
184
|
+
},
|
|
185
|
+
description: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Project description"
|
|
188
|
+
},
|
|
189
|
+
color: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "Hex color code"
|
|
192
|
+
},
|
|
193
|
+
status: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Project status"
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
required: ["operation"]
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "motion_tasks",
|
|
203
|
+
description: "Manage Motion tasks - supports create, list, get, update, delete, move, and unassign operations",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
operation: {
|
|
208
|
+
type: "string",
|
|
209
|
+
enum: ["create", "list", "get", "update", "delete", "move", "unassign"],
|
|
210
|
+
description: "Operation to perform"
|
|
211
|
+
},
|
|
212
|
+
taskId: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Task ID (required for get/update/delete/move/unassign)"
|
|
215
|
+
},
|
|
216
|
+
workspaceId: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "Filter by workspace (for list)"
|
|
219
|
+
},
|
|
220
|
+
workspaceName: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Filter by workspace name (for list)"
|
|
223
|
+
},
|
|
224
|
+
projectId: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Filter by project (for list)"
|
|
227
|
+
},
|
|
228
|
+
projectName: {
|
|
229
|
+
type: "string",
|
|
230
|
+
description: "Project name (alternative to projectId)"
|
|
231
|
+
},
|
|
232
|
+
status: {
|
|
233
|
+
type: "string",
|
|
234
|
+
description: "Filter by status (for list)"
|
|
235
|
+
},
|
|
236
|
+
assigneeId: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Filter by assignee (for list)"
|
|
239
|
+
},
|
|
240
|
+
name: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Task name (required for create)"
|
|
243
|
+
},
|
|
244
|
+
description: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Task description"
|
|
247
|
+
},
|
|
248
|
+
priority: {
|
|
249
|
+
type: "string",
|
|
250
|
+
enum: ["ASAP", "HIGH", "MEDIUM", "LOW"],
|
|
251
|
+
description: "Task priority"
|
|
252
|
+
},
|
|
253
|
+
dueDate: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "ISO 8601 format"
|
|
256
|
+
},
|
|
257
|
+
duration: {
|
|
258
|
+
oneOf: [
|
|
259
|
+
{ type: "string", enum: ["NONE", "REMINDER"] },
|
|
260
|
+
{ type: "number", minimum: 0 }
|
|
261
|
+
],
|
|
262
|
+
description: "Minutes (as number) or 'NONE'/'REMINDER' (as string)"
|
|
263
|
+
},
|
|
264
|
+
labels: {
|
|
265
|
+
type: "array",
|
|
266
|
+
items: { type: "string" },
|
|
267
|
+
description: "Task labels"
|
|
268
|
+
},
|
|
269
|
+
autoScheduled: {
|
|
270
|
+
oneOf: [
|
|
271
|
+
{ type: "object" },
|
|
272
|
+
{ type: "null" }
|
|
273
|
+
],
|
|
274
|
+
description: "Auto-scheduling configuration"
|
|
275
|
+
},
|
|
276
|
+
targetProjectId: {
|
|
277
|
+
type: "string",
|
|
278
|
+
description: "Target project for move operation"
|
|
279
|
+
},
|
|
280
|
+
targetWorkspaceId: {
|
|
281
|
+
type: "string",
|
|
282
|
+
description: "Target workspace for move operation"
|
|
283
|
+
},
|
|
284
|
+
limit: {
|
|
285
|
+
type: "number",
|
|
286
|
+
description: "Maximum number of tasks to return (for list)"
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
required: ["operation"]
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
// New consolidated tools for workspace and search management
|
|
293
|
+
{
|
|
294
|
+
name: "motion_workspaces",
|
|
295
|
+
description: "Manage Motion workspaces - supports list, get, and set_default operations",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: "object",
|
|
298
|
+
properties: {
|
|
299
|
+
operation: {
|
|
300
|
+
type: "string",
|
|
301
|
+
enum: ["list", "get", "set_default"],
|
|
302
|
+
description: "Operation to perform"
|
|
303
|
+
},
|
|
304
|
+
workspaceId: {
|
|
305
|
+
type: "string",
|
|
306
|
+
description: "Workspace ID (required for get and set_default operations)"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
required: ["operation"]
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "motion_search",
|
|
314
|
+
description: "Search and context utilities for Motion - supports content, context, and smart operations",
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: "object",
|
|
317
|
+
properties: {
|
|
318
|
+
operation: {
|
|
319
|
+
type: "string",
|
|
320
|
+
enum: ["content", "context", "smart"],
|
|
321
|
+
description: "Operation to perform"
|
|
322
|
+
},
|
|
323
|
+
query: {
|
|
324
|
+
type: "string",
|
|
325
|
+
description: "Search query (required for content and smart operations)"
|
|
326
|
+
},
|
|
327
|
+
searchScope: {
|
|
328
|
+
type: "string",
|
|
329
|
+
enum: ["tasks", "projects", "both"],
|
|
330
|
+
description: "What to search for content operation (default: both)"
|
|
331
|
+
},
|
|
332
|
+
workspaceId: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "Workspace ID to limit search/context"
|
|
335
|
+
},
|
|
336
|
+
workspaceName: {
|
|
337
|
+
type: "string",
|
|
338
|
+
description: "Workspace name (alternative to workspaceId)"
|
|
339
|
+
},
|
|
340
|
+
limit: {
|
|
341
|
+
type: "number",
|
|
342
|
+
description: "Maximum number of results for content operation"
|
|
343
|
+
},
|
|
344
|
+
includeProjects: {
|
|
345
|
+
type: "boolean",
|
|
346
|
+
description: "Include project information for context operation"
|
|
347
|
+
},
|
|
348
|
+
includeTasks: {
|
|
349
|
+
type: "boolean",
|
|
350
|
+
description: "Include task information for context operation"
|
|
351
|
+
},
|
|
352
|
+
includeUsers: {
|
|
353
|
+
type: "boolean",
|
|
354
|
+
description: "Include user information for context operation"
|
|
355
|
+
},
|
|
356
|
+
entityType: {
|
|
357
|
+
type: "string",
|
|
358
|
+
enum: ["project", "task"],
|
|
359
|
+
description: "Entity type for smart operation"
|
|
360
|
+
},
|
|
361
|
+
entityId: {
|
|
362
|
+
type: "string",
|
|
363
|
+
description: "Entity ID for smart operation"
|
|
364
|
+
},
|
|
365
|
+
includeRelated: {
|
|
366
|
+
type: "boolean",
|
|
367
|
+
description: "Include related entities for smart operation"
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
required: ["operation"]
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "motion_users",
|
|
375
|
+
description: "Manage users and get current user information",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
operation: {
|
|
380
|
+
type: "string",
|
|
381
|
+
enum: ["list", "current"],
|
|
382
|
+
description: "Operation to perform"
|
|
383
|
+
},
|
|
384
|
+
workspaceId: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Workspace ID (optional for list operation, ignored for current)"
|
|
387
|
+
},
|
|
388
|
+
workspaceName: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "Workspace name (alternative to workspaceId, ignored for current)"
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
required: ["operation"]
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "motion_comments",
|
|
398
|
+
description: "Manage comments on tasks",
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
operation: {
|
|
403
|
+
type: "string",
|
|
404
|
+
enum: ["list", "create"],
|
|
405
|
+
description: "Operation to perform"
|
|
406
|
+
},
|
|
407
|
+
taskId: {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "Task ID to comment on or fetch comments from (required)"
|
|
410
|
+
},
|
|
411
|
+
content: {
|
|
412
|
+
type: "string",
|
|
413
|
+
description: "Comment content (required for create operation)"
|
|
414
|
+
},
|
|
415
|
+
cursor: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description: "Pagination cursor for list operation (optional)"
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
required: ["operation", "taskId"]
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "motion_custom_fields",
|
|
425
|
+
description: "Manage custom fields for tasks and projects",
|
|
426
|
+
inputSchema: {
|
|
427
|
+
type: "object",
|
|
428
|
+
properties: {
|
|
429
|
+
operation: {
|
|
430
|
+
type: "string",
|
|
431
|
+
enum: ["list", "create", "delete", "add_to_project", "remove_from_project", "add_to_task", "remove_from_task"],
|
|
432
|
+
description: "Operation to perform"
|
|
433
|
+
},
|
|
434
|
+
fieldId: {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: "Custom field ID"
|
|
437
|
+
},
|
|
438
|
+
workspaceId: {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "Workspace ID"
|
|
441
|
+
},
|
|
442
|
+
name: {
|
|
443
|
+
type: "string",
|
|
444
|
+
description: "Field name (for create)"
|
|
445
|
+
},
|
|
446
|
+
field: {
|
|
447
|
+
type: "string",
|
|
448
|
+
enum: ["text", "url", "date", "person", "multiPerson", "phone", "select", "multiSelect", "number", "email", "checkbox", "relatedTo"],
|
|
449
|
+
description: "Field type (for create)"
|
|
450
|
+
},
|
|
451
|
+
options: {
|
|
452
|
+
type: "array",
|
|
453
|
+
items: { type: "string" },
|
|
454
|
+
description: "Options for select/multiselect fields"
|
|
455
|
+
},
|
|
456
|
+
required: {
|
|
457
|
+
type: "boolean",
|
|
458
|
+
description: "Whether field is required"
|
|
459
|
+
},
|
|
460
|
+
projectId: {
|
|
461
|
+
type: "string",
|
|
462
|
+
description: "Project ID (for add/remove operations)"
|
|
463
|
+
},
|
|
464
|
+
taskId: {
|
|
465
|
+
type: "string",
|
|
466
|
+
description: "Task ID (for add/remove operations)"
|
|
467
|
+
},
|
|
468
|
+
value: {
|
|
469
|
+
type: ["string", "number", "boolean", "array", "null"],
|
|
470
|
+
description: "Field value"
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
required: ["operation", "workspaceId"]
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: "motion_recurring_tasks",
|
|
478
|
+
description: "Manage recurring tasks",
|
|
479
|
+
inputSchema: {
|
|
480
|
+
type: "object",
|
|
481
|
+
properties: {
|
|
482
|
+
operation: {
|
|
483
|
+
type: "string",
|
|
484
|
+
enum: ["list", "create", "delete"],
|
|
485
|
+
description: "Operation to perform"
|
|
486
|
+
},
|
|
487
|
+
recurringTaskId: {
|
|
488
|
+
type: "string",
|
|
489
|
+
description: "Recurring task ID (for delete)"
|
|
490
|
+
},
|
|
491
|
+
workspaceId: {
|
|
492
|
+
type: "string",
|
|
493
|
+
description: "Workspace ID"
|
|
494
|
+
},
|
|
495
|
+
name: {
|
|
496
|
+
type: "string",
|
|
497
|
+
description: "Task name (for create)"
|
|
498
|
+
},
|
|
499
|
+
description: {
|
|
500
|
+
type: "string",
|
|
501
|
+
description: "Task description"
|
|
502
|
+
},
|
|
503
|
+
projectId: {
|
|
504
|
+
type: "string",
|
|
505
|
+
description: "Project ID"
|
|
506
|
+
},
|
|
507
|
+
assigneeId: {
|
|
508
|
+
type: "string",
|
|
509
|
+
description: "User ID to assign the recurring task to (required for create)"
|
|
510
|
+
},
|
|
511
|
+
frequency: {
|
|
512
|
+
type: "object",
|
|
513
|
+
properties: {
|
|
514
|
+
type: {
|
|
515
|
+
type: "string",
|
|
516
|
+
enum: ["daily", "weekly", "monthly", "yearly"],
|
|
517
|
+
description: "Frequency type"
|
|
518
|
+
},
|
|
519
|
+
interval: {
|
|
520
|
+
type: "number",
|
|
521
|
+
description: "Repeat every N periods"
|
|
522
|
+
},
|
|
523
|
+
daysOfWeek: {
|
|
524
|
+
type: "array",
|
|
525
|
+
items: { type: "number" },
|
|
526
|
+
description: "0-6 for Sunday-Saturday (for weekly)"
|
|
527
|
+
},
|
|
528
|
+
dayOfMonth: {
|
|
529
|
+
type: "number",
|
|
530
|
+
description: "1-31 for monthly recurrence"
|
|
531
|
+
},
|
|
532
|
+
endDate: {
|
|
533
|
+
type: "string",
|
|
534
|
+
description: "ISO 8601 format end date"
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
required: ["type"],
|
|
538
|
+
description: "Frequency configuration (required for create)"
|
|
539
|
+
},
|
|
540
|
+
deadlineType: {
|
|
541
|
+
type: "string",
|
|
542
|
+
enum: ["HARD", "SOFT"],
|
|
543
|
+
description: "Deadline type (default: SOFT)"
|
|
544
|
+
},
|
|
545
|
+
duration: {
|
|
546
|
+
oneOf: [
|
|
547
|
+
{ type: "number" },
|
|
548
|
+
{ type: "string", enum: ["REMINDER"] }
|
|
549
|
+
],
|
|
550
|
+
description: "Task duration in minutes or REMINDER"
|
|
551
|
+
},
|
|
552
|
+
startingOn: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Start date (ISO 8601 format)"
|
|
555
|
+
},
|
|
556
|
+
idealTime: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "Ideal time in HH:mm format"
|
|
559
|
+
},
|
|
560
|
+
schedule: {
|
|
561
|
+
type: "string",
|
|
562
|
+
description: "Schedule name (default: Work Hours)"
|
|
563
|
+
},
|
|
564
|
+
priority: {
|
|
565
|
+
type: "string",
|
|
566
|
+
enum: ["ASAP", "HIGH", "MEDIUM", "LOW"],
|
|
567
|
+
description: "Task priority (default: MEDIUM)"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
required: ["operation"]
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "motion_schedules",
|
|
575
|
+
description: "Get user schedules showing their weekly working hours and time zones",
|
|
576
|
+
inputSchema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {
|
|
579
|
+
userId: {
|
|
580
|
+
type: "string",
|
|
581
|
+
description: "User ID to get schedule for (optional, returns all schedules if not specified)"
|
|
582
|
+
},
|
|
583
|
+
startDate: {
|
|
584
|
+
type: "string",
|
|
585
|
+
description: "Start date for filtering schedules (optional)"
|
|
586
|
+
},
|
|
587
|
+
endDate: {
|
|
588
|
+
type: "string",
|
|
589
|
+
description: "End date for filtering schedules (optional)"
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
additionalProperties: false
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
name: "motion_statuses",
|
|
597
|
+
description: "Get available task/project statuses for a workspace",
|
|
598
|
+
inputSchema: {
|
|
599
|
+
type: "object",
|
|
600
|
+
properties: {
|
|
601
|
+
workspaceId: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "Workspace ID to get statuses for (optional, returns all statuses if not specified)"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
additionalProperties: false
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
getEnabledTools() {
|
|
612
|
+
const allTools = this.getAllToolDefinitions();
|
|
613
|
+
const toolsMap = new Map(allTools.map(tool => [tool.name, tool]));
|
|
614
|
+
switch (this.toolsConfig) {
|
|
615
|
+
case 'minimal':
|
|
616
|
+
// Only core consolidated tools
|
|
617
|
+
return [
|
|
618
|
+
toolsMap.get('motion_tasks'),
|
|
619
|
+
toolsMap.get('motion_projects'),
|
|
620
|
+
toolsMap.get('motion_workspaces')
|
|
621
|
+
].filter((tool) => tool !== undefined);
|
|
622
|
+
case 'essential':
|
|
623
|
+
// Consolidated tools plus commonly needed tools
|
|
624
|
+
return [
|
|
625
|
+
toolsMap.get('motion_tasks'),
|
|
626
|
+
toolsMap.get('motion_projects'),
|
|
627
|
+
toolsMap.get('motion_workspaces'),
|
|
628
|
+
toolsMap.get('motion_users'),
|
|
629
|
+
toolsMap.get('motion_search'),
|
|
630
|
+
toolsMap.get('motion_comments'),
|
|
631
|
+
toolsMap.get('motion_schedules')
|
|
632
|
+
].filter((tool) => tool !== undefined);
|
|
633
|
+
case 'complete':
|
|
634
|
+
// All consolidated tools
|
|
635
|
+
return [
|
|
636
|
+
toolsMap.get('motion_tasks'),
|
|
637
|
+
toolsMap.get('motion_projects'),
|
|
638
|
+
toolsMap.get('motion_workspaces'),
|
|
639
|
+
toolsMap.get('motion_users'),
|
|
640
|
+
toolsMap.get('motion_search'),
|
|
641
|
+
toolsMap.get('motion_comments'),
|
|
642
|
+
toolsMap.get('motion_custom_fields'),
|
|
643
|
+
toolsMap.get('motion_recurring_tasks'),
|
|
644
|
+
toolsMap.get('motion_schedules'),
|
|
645
|
+
toolsMap.get('motion_statuses')
|
|
646
|
+
].filter((tool) => tool !== undefined);
|
|
647
|
+
default:
|
|
648
|
+
// Handle custom configuration (already validated in validateToolsConfig)
|
|
649
|
+
if (this.toolsConfig.startsWith('custom:')) {
|
|
650
|
+
const customTools = this.toolsConfig.substring(7).split(',').map(s => s.trim());
|
|
651
|
+
return customTools
|
|
652
|
+
.map(name => toolsMap.get(name))
|
|
653
|
+
.filter((tool) => tool !== undefined);
|
|
654
|
+
}
|
|
655
|
+
// This should never happen since we validate in initialize()
|
|
656
|
+
// But if it does, throw an error instead of silently defaulting
|
|
657
|
+
throw new Error(`Unexpected tools configuration: ${this.toolsConfig}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// Handler methods
|
|
661
|
+
// Consolidated handlers for resource-based tools
|
|
662
|
+
async handleMotionProjects(args) {
|
|
663
|
+
const motionService = this.motionService;
|
|
664
|
+
const workspaceResolver = this.workspaceResolver;
|
|
665
|
+
if (!motionService || !workspaceResolver) {
|
|
666
|
+
return (0, utils_1.formatMcpError)(new Error("Services not available"));
|
|
667
|
+
}
|
|
668
|
+
const { operation, ...params } = args;
|
|
669
|
+
try {
|
|
670
|
+
switch (operation) {
|
|
671
|
+
case 'create':
|
|
672
|
+
if (!params.name) {
|
|
673
|
+
return (0, utils_1.formatMcpError)(new Error("Project name is required for create operation"));
|
|
674
|
+
}
|
|
675
|
+
const projectData = (0, utils_1.parseProjectArgs)(params);
|
|
676
|
+
const workspace = await workspaceResolver.resolveWorkspace({
|
|
677
|
+
workspaceId: projectData.workspaceId,
|
|
678
|
+
workspaceName: projectData.workspaceName
|
|
679
|
+
});
|
|
680
|
+
const project = await motionService.createProject({
|
|
681
|
+
...projectData,
|
|
682
|
+
workspaceId: workspace.id
|
|
683
|
+
});
|
|
684
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully created project "${project.name}" (ID: ${project.id})`);
|
|
685
|
+
case 'list':
|
|
686
|
+
const listWorkspace = await workspaceResolver.resolveWorkspace({
|
|
687
|
+
workspaceId: params.workspaceId,
|
|
688
|
+
workspaceName: params.workspaceName
|
|
689
|
+
});
|
|
690
|
+
const projects = await motionService.getProjects(listWorkspace.id);
|
|
691
|
+
return (0, utils_1.formatProjectList)(projects, listWorkspace.name);
|
|
692
|
+
case 'get':
|
|
693
|
+
if (!params.projectId) {
|
|
694
|
+
return (0, utils_1.formatMcpError)(new Error("Project ID is required for get operation"));
|
|
695
|
+
}
|
|
696
|
+
const projectDetails = await motionService.getProject(params.projectId);
|
|
697
|
+
return (0, utils_1.formatDetailResponse)(projectDetails, `Project details for "${projectDetails.name}"`);
|
|
698
|
+
default:
|
|
699
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
async handleMotionTasks(args) {
|
|
707
|
+
const motionService = this.motionService;
|
|
708
|
+
const workspaceResolver = this.workspaceResolver;
|
|
709
|
+
if (!motionService || !workspaceResolver) {
|
|
710
|
+
return (0, utils_1.formatMcpError)(new Error("Services not available"));
|
|
711
|
+
}
|
|
712
|
+
const { operation, ...params } = args;
|
|
713
|
+
try {
|
|
714
|
+
switch (operation) {
|
|
715
|
+
case 'create':
|
|
716
|
+
if (!params.name) {
|
|
717
|
+
return (0, utils_1.formatMcpError)(new Error("Task name is required for create operation"));
|
|
718
|
+
}
|
|
719
|
+
const taskData = (0, utils_1.parseTaskArgs)(params);
|
|
720
|
+
const workspace = await workspaceResolver.resolveWorkspace(taskData);
|
|
721
|
+
const task = await motionService.createTask({
|
|
722
|
+
...taskData,
|
|
723
|
+
workspaceId: workspace.id
|
|
724
|
+
});
|
|
725
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully created task "${task.name}" (ID: ${task.id})`);
|
|
726
|
+
case 'list':
|
|
727
|
+
const listWorkspace = await workspaceResolver.resolveWorkspace({
|
|
728
|
+
workspaceId: params.workspaceId,
|
|
729
|
+
workspaceName: params.workspaceName
|
|
730
|
+
});
|
|
731
|
+
const tasks = await motionService.getTasks(listWorkspace.id, params.projectId, 5, // maxPages
|
|
732
|
+
params.limit);
|
|
733
|
+
return (0, utils_1.formatTaskList)(tasks, {
|
|
734
|
+
workspaceName: listWorkspace.name,
|
|
735
|
+
projectName: params.projectName,
|
|
736
|
+
status: params.status,
|
|
737
|
+
limit: params.limit
|
|
738
|
+
});
|
|
739
|
+
case 'get':
|
|
740
|
+
if (!params.taskId) {
|
|
741
|
+
return (0, utils_1.formatMcpError)(new Error("Task ID is required for get operation"));
|
|
742
|
+
}
|
|
743
|
+
const taskDetails = await motionService.getTask(params.taskId);
|
|
744
|
+
return (0, utils_1.formatDetailResponse)(taskDetails, `Task details for "${taskDetails.name}"`);
|
|
745
|
+
case 'update':
|
|
746
|
+
if (!params.taskId) {
|
|
747
|
+
return (0, utils_1.formatMcpError)(new Error("Task ID is required for update operation"));
|
|
748
|
+
}
|
|
749
|
+
// Create update object with only valid MotionTask fields
|
|
750
|
+
const updateData = {};
|
|
751
|
+
if (params.name !== undefined)
|
|
752
|
+
updateData.name = params.name;
|
|
753
|
+
if (params.description !== undefined)
|
|
754
|
+
updateData.description = params.description;
|
|
755
|
+
if (params.status !== undefined)
|
|
756
|
+
updateData.status = params.status;
|
|
757
|
+
if (params.priority !== undefined)
|
|
758
|
+
updateData.priority = params.priority;
|
|
759
|
+
if (params.dueDate !== undefined)
|
|
760
|
+
updateData.dueDate = params.dueDate;
|
|
761
|
+
if (params.duration !== undefined) {
|
|
762
|
+
// Convert string duration to number or keep special values
|
|
763
|
+
if (typeof params.duration === 'string') {
|
|
764
|
+
if (params.duration === 'NONE' || params.duration === 'REMINDER') {
|
|
765
|
+
updateData.duration = params.duration;
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
const numDuration = parseInt(params.duration, 10);
|
|
769
|
+
if (!isNaN(numDuration)) {
|
|
770
|
+
updateData.duration = numDuration;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
updateData.duration = params.duration;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (params.labels !== undefined)
|
|
779
|
+
updateData.labels = params.labels?.map(name => ({ name }));
|
|
780
|
+
if (params.autoScheduled !== undefined)
|
|
781
|
+
updateData.autoScheduled = params.autoScheduled;
|
|
782
|
+
const updatedTask = await motionService.updateTask(params.taskId, updateData);
|
|
783
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully updated task "${updatedTask.name}" (ID: ${updatedTask.id})`);
|
|
784
|
+
case 'delete':
|
|
785
|
+
if (!params.taskId) {
|
|
786
|
+
return (0, utils_1.formatMcpError)(new Error("Task ID is required for delete operation"));
|
|
787
|
+
}
|
|
788
|
+
await motionService.deleteTask(params.taskId);
|
|
789
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully deleted task ${params.taskId}`);
|
|
790
|
+
case 'move':
|
|
791
|
+
if (!params.taskId) {
|
|
792
|
+
return (0, utils_1.formatMcpError)(new Error("Task ID is required for move operation"));
|
|
793
|
+
}
|
|
794
|
+
if (!params.targetProjectId && !params.targetWorkspaceId) {
|
|
795
|
+
return (0, utils_1.formatMcpError)(new Error("Either target project ID or target workspace ID is required for move operation"));
|
|
796
|
+
}
|
|
797
|
+
const movedTask = await motionService.moveTask(params.taskId, params.targetProjectId, params.targetWorkspaceId);
|
|
798
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully moved task "${movedTask.name}" (ID: ${movedTask.id})`);
|
|
799
|
+
case 'unassign':
|
|
800
|
+
if (!params.taskId) {
|
|
801
|
+
return (0, utils_1.formatMcpError)(new Error("Task ID is required for unassign operation"));
|
|
802
|
+
}
|
|
803
|
+
const unassignedTask = await motionService.unassignTask(params.taskId);
|
|
804
|
+
return (0, utils_1.formatMcpSuccess)(`Successfully unassigned task "${unassignedTask.name}" (ID: ${unassignedTask.id})`);
|
|
805
|
+
default:
|
|
806
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async handleSearchContent(args) {
|
|
814
|
+
const motionService = this.motionService;
|
|
815
|
+
const workspaceResolver = this.workspaceResolver;
|
|
816
|
+
if (!motionService || !workspaceResolver) {
|
|
817
|
+
return (0, utils_1.formatMcpError)(new Error("Services not available"));
|
|
818
|
+
}
|
|
819
|
+
const { query, entityTypes = ['projects', 'tasks'] } = args;
|
|
820
|
+
// Use configurable limit to prevent resource exhaustion
|
|
821
|
+
const limit = utils_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
822
|
+
const workspace = await workspaceResolver.resolveWorkspace(args);
|
|
823
|
+
let results = [];
|
|
824
|
+
if (entityTypes?.includes('tasks')) {
|
|
825
|
+
// Pass limit to prevent fetching unlimited results
|
|
826
|
+
const tasks = await motionService.searchTasks(query, workspace.id, limit);
|
|
827
|
+
results.push(...tasks);
|
|
828
|
+
}
|
|
829
|
+
if (entityTypes?.includes('projects')) {
|
|
830
|
+
// Pass limit to prevent fetching unlimited results
|
|
831
|
+
const projects = await motionService.searchProjects(query, workspace.id, limit);
|
|
832
|
+
results.push(...projects);
|
|
833
|
+
}
|
|
834
|
+
return (0, utils_1.formatSearchResults)(results.slice(0, limit), query, { limit, searchScope: entityTypes?.join(',') || 'both' });
|
|
835
|
+
}
|
|
836
|
+
async handleGetContext(args) {
|
|
837
|
+
const { entityType, entityId, includeRelated = false } = args;
|
|
838
|
+
let contextText = `Context for ${entityType} ${entityId}:\n\n`;
|
|
839
|
+
// For now, return a simple context message as Motion API doesn't have specific context endpoints
|
|
840
|
+
if (entityType === 'project') {
|
|
841
|
+
contextText += `Project ID: ${entityId}\n`;
|
|
842
|
+
if (includeRelated) {
|
|
843
|
+
contextText += `Related tasks would be listed here (when available)\n`;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
else if (entityType === 'task') {
|
|
847
|
+
contextText += `Task ID: ${entityId}\n`;
|
|
848
|
+
if (includeRelated) {
|
|
849
|
+
contextText += `Related project and subtasks would be listed here (when available)\n`;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return (0, utils_1.formatMcpSuccess)(contextText);
|
|
853
|
+
}
|
|
854
|
+
async handleMotionUsers(args) {
|
|
855
|
+
const motionService = this.motionService;
|
|
856
|
+
const workspaceResolver = this.workspaceResolver;
|
|
857
|
+
if (!motionService) {
|
|
858
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service is not available"));
|
|
859
|
+
}
|
|
860
|
+
const { operation, workspaceId, workspaceName } = args;
|
|
861
|
+
try {
|
|
862
|
+
switch (operation) {
|
|
863
|
+
case 'list':
|
|
864
|
+
if (!workspaceResolver) {
|
|
865
|
+
return (0, utils_1.formatMcpError)(new Error("Workspace resolver not available"));
|
|
866
|
+
}
|
|
867
|
+
const workspace = await workspaceResolver.resolveWorkspace({ workspaceId, workspaceName });
|
|
868
|
+
const users = await motionService.getUsers(workspace.id);
|
|
869
|
+
const userList = users.map(u => `- ${u.name} (${u.email}) [ID: ${u.id}]`).join('\n');
|
|
870
|
+
return (0, utils_1.formatMcpSuccess)(`Users in workspace "${workspace.name}":\n${userList}`);
|
|
871
|
+
case 'current':
|
|
872
|
+
const currentUser = await motionService.getCurrentUser();
|
|
873
|
+
const userInfo = `Current user: ${currentUser.name || 'No name'} (${currentUser.email}) [ID: ${currentUser.id}]`;
|
|
874
|
+
return (0, utils_1.formatMcpSuccess)(userInfo);
|
|
875
|
+
default:
|
|
876
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async handleMotionComments(args) {
|
|
884
|
+
const motionService = this.motionService;
|
|
885
|
+
if (!motionService) {
|
|
886
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service is not available"));
|
|
887
|
+
}
|
|
888
|
+
const { operation, taskId, content, cursor } = args;
|
|
889
|
+
try {
|
|
890
|
+
switch (operation) {
|
|
891
|
+
case 'list':
|
|
892
|
+
if (!taskId) {
|
|
893
|
+
return (0, utils_1.formatMcpError)(new Error('taskId is required for list operation'));
|
|
894
|
+
}
|
|
895
|
+
const commentsResponse = await motionService.getComments(taskId, cursor);
|
|
896
|
+
// Format the comments list
|
|
897
|
+
const commentsResult = (0, utils_1.formatCommentList)(commentsResponse.data);
|
|
898
|
+
// Add pagination info if there's more data
|
|
899
|
+
if (commentsResponse.meta.nextCursor && commentsResult.content[0]) {
|
|
900
|
+
// Type guard to safely check if content[0] has text property
|
|
901
|
+
function hasTextProperty(obj) {
|
|
902
|
+
return typeof obj === 'object' && obj !== null && 'text' in obj && typeof obj.text === 'string';
|
|
903
|
+
}
|
|
904
|
+
if (hasTextProperty(commentsResult.content[0])) {
|
|
905
|
+
commentsResult.content[0].text += `\n\n📄 More comments available. Use cursor: ${commentsResponse.meta.nextCursor}`;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return commentsResult;
|
|
909
|
+
case 'create':
|
|
910
|
+
if (!taskId) {
|
|
911
|
+
return (0, utils_1.formatMcpError)(new Error('taskId is required for create operation'));
|
|
912
|
+
}
|
|
913
|
+
if (!content) {
|
|
914
|
+
return (0, utils_1.formatMcpError)(new Error('content is required for create operation'));
|
|
915
|
+
}
|
|
916
|
+
// Sanitize and validate comment content
|
|
917
|
+
const sanitizationResult = (0, sanitize_1.sanitizeCommentContent)(content);
|
|
918
|
+
if (!sanitizationResult.isValid) {
|
|
919
|
+
return (0, utils_1.formatMcpError)(new Error(sanitizationResult.error || 'Invalid comment content'));
|
|
920
|
+
}
|
|
921
|
+
const sanitizedContent = sanitizationResult.sanitized;
|
|
922
|
+
if (sanitizedContent.length > utils_1.LIMITS.COMMENT_MAX_LENGTH) {
|
|
923
|
+
return (0, utils_1.formatMcpError)(new Error(`Comment content exceeds maximum length of ${utils_1.LIMITS.COMMENT_MAX_LENGTH} characters`));
|
|
924
|
+
}
|
|
925
|
+
const commentData = {
|
|
926
|
+
taskId,
|
|
927
|
+
content: sanitizedContent
|
|
928
|
+
};
|
|
929
|
+
const newComment = await motionService.createComment(commentData);
|
|
930
|
+
return (0, utils_1.formatCommentDetail)(newComment);
|
|
931
|
+
default:
|
|
932
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async handleMotionCustomFields(args) {
|
|
940
|
+
const motionService = this.motionService;
|
|
941
|
+
if (!motionService) {
|
|
942
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service is not available"));
|
|
943
|
+
}
|
|
944
|
+
const { operation, fieldId, workspaceId, name, field, options, projectId, taskId, value } = args;
|
|
945
|
+
try {
|
|
946
|
+
switch (operation) {
|
|
947
|
+
case 'list':
|
|
948
|
+
if (!workspaceId) {
|
|
949
|
+
return (0, utils_1.formatMcpError)(new Error('Workspace ID is required for list operation'));
|
|
950
|
+
}
|
|
951
|
+
const fields = await motionService.getCustomFields(workspaceId);
|
|
952
|
+
return (0, utils_1.formatCustomFieldList)(fields);
|
|
953
|
+
case 'create':
|
|
954
|
+
if (!workspaceId) {
|
|
955
|
+
return (0, utils_1.formatMcpError)(new Error('Workspace ID is required for create operation'));
|
|
956
|
+
}
|
|
957
|
+
if (!name || !field) {
|
|
958
|
+
return (0, utils_1.formatMcpError)(new Error('Name and field are required for create operation'));
|
|
959
|
+
}
|
|
960
|
+
// Validate custom field name length
|
|
961
|
+
if (name.length > utils_1.LIMITS.CUSTOM_FIELD_NAME_MAX_LENGTH) {
|
|
962
|
+
return (0, utils_1.formatMcpError)(new Error(`Field name exceeds ${utils_1.LIMITS.CUSTOM_FIELD_NAME_MAX_LENGTH} characters`));
|
|
963
|
+
}
|
|
964
|
+
// Validate options parameter - only allowed for select/multiSelect fields
|
|
965
|
+
if (['select', 'multiSelect'].includes(field) !== Boolean(options)) {
|
|
966
|
+
return (0, utils_1.formatMcpError)(new Error('Options parameter only allowed for select/multiSelect field types'));
|
|
967
|
+
}
|
|
968
|
+
const fieldData = {
|
|
969
|
+
name,
|
|
970
|
+
field,
|
|
971
|
+
...(options && { metadata: { options } })
|
|
972
|
+
};
|
|
973
|
+
const newField = await motionService.createCustomField(workspaceId, fieldData);
|
|
974
|
+
return (0, utils_1.formatCustomFieldDetail)(newField);
|
|
975
|
+
case 'delete':
|
|
976
|
+
if (!workspaceId) {
|
|
977
|
+
return (0, utils_1.formatMcpError)(new Error('Workspace ID is required for delete operation'));
|
|
978
|
+
}
|
|
979
|
+
if (!fieldId) {
|
|
980
|
+
return (0, utils_1.formatMcpError)(new Error('Field ID is required for delete operation'));
|
|
981
|
+
}
|
|
982
|
+
await motionService.deleteCustomField(workspaceId, fieldId);
|
|
983
|
+
return (0, utils_1.formatCustomFieldSuccess)('deleted');
|
|
984
|
+
case 'add_to_project':
|
|
985
|
+
if (!projectId || !fieldId) {
|
|
986
|
+
return (0, utils_1.formatMcpError)(new Error('Project ID and field ID are required for add_to_project operation'));
|
|
987
|
+
}
|
|
988
|
+
await motionService.addCustomFieldToProject(projectId, fieldId, value);
|
|
989
|
+
return (0, utils_1.formatCustomFieldSuccess)('added', 'project', projectId);
|
|
990
|
+
case 'remove_from_project':
|
|
991
|
+
if (!projectId || !fieldId) {
|
|
992
|
+
return (0, utils_1.formatMcpError)(new Error('Project ID and field ID are required for remove_from_project operation'));
|
|
993
|
+
}
|
|
994
|
+
await motionService.removeCustomFieldFromProject(projectId, fieldId);
|
|
995
|
+
return (0, utils_1.formatCustomFieldSuccess)('removed', 'project', projectId);
|
|
996
|
+
case 'add_to_task':
|
|
997
|
+
if (!taskId || !fieldId) {
|
|
998
|
+
return (0, utils_1.formatMcpError)(new Error('Task ID and field ID are required for add_to_task operation'));
|
|
999
|
+
}
|
|
1000
|
+
await motionService.addCustomFieldToTask(taskId, fieldId, value);
|
|
1001
|
+
return (0, utils_1.formatCustomFieldSuccess)('added', 'task', taskId);
|
|
1002
|
+
case 'remove_from_task':
|
|
1003
|
+
if (!taskId || !fieldId) {
|
|
1004
|
+
return (0, utils_1.formatMcpError)(new Error('Task ID and field ID are required for remove_from_task operation'));
|
|
1005
|
+
}
|
|
1006
|
+
await motionService.removeCustomFieldFromTask(taskId, fieldId);
|
|
1007
|
+
return (0, utils_1.formatCustomFieldSuccess)('removed', 'task', taskId);
|
|
1008
|
+
default:
|
|
1009
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
catch (error) {
|
|
1013
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async handleMotionRecurringTasks(args) {
|
|
1017
|
+
const motionService = this.motionService;
|
|
1018
|
+
const workspaceResolver = this.workspaceResolver;
|
|
1019
|
+
if (!motionService || !workspaceResolver) {
|
|
1020
|
+
return (0, utils_1.formatMcpError)(new Error("Services not available"));
|
|
1021
|
+
}
|
|
1022
|
+
const { operation, recurringTaskId, workspaceId, name, description, projectId, assigneeId, frequency, deadlineType, duration, startingOn, idealTime, schedule, priority } = args;
|
|
1023
|
+
try {
|
|
1024
|
+
switch (operation) {
|
|
1025
|
+
case 'list':
|
|
1026
|
+
if (!workspaceId) {
|
|
1027
|
+
return (0, utils_1.formatMcpError)(new Error('Workspace ID is required for list operation'));
|
|
1028
|
+
}
|
|
1029
|
+
const recurringTasks = await motionService.getRecurringTasks(workspaceId);
|
|
1030
|
+
return (0, utils_1.formatRecurringTaskList)(recurringTasks);
|
|
1031
|
+
case 'create':
|
|
1032
|
+
if (!name) {
|
|
1033
|
+
return (0, utils_1.formatMcpError)(new Error('Name is required for create operation'));
|
|
1034
|
+
}
|
|
1035
|
+
if (!workspaceId) {
|
|
1036
|
+
return (0, utils_1.formatMcpError)(new Error('Workspace ID is required for create operation'));
|
|
1037
|
+
}
|
|
1038
|
+
if (!assigneeId) {
|
|
1039
|
+
return (0, utils_1.formatMcpError)(new Error('Assignee ID is required for create operation'));
|
|
1040
|
+
}
|
|
1041
|
+
if (!frequency) {
|
|
1042
|
+
return (0, utils_1.formatMcpError)(new Error('Frequency is required for create operation'));
|
|
1043
|
+
}
|
|
1044
|
+
// Validate frequency
|
|
1045
|
+
if (!frequency.type || !['daily', 'weekly', 'monthly', 'yearly'].includes(frequency.type)) {
|
|
1046
|
+
return (0, utils_1.formatMcpError)(new Error('Frequency type must be one of: daily, weekly, monthly, yearly'));
|
|
1047
|
+
}
|
|
1048
|
+
// Validate frequency-specific fields
|
|
1049
|
+
if (frequency.interval && (!Number.isInteger(frequency.interval) || frequency.interval < 1)) {
|
|
1050
|
+
return (0, utils_1.formatMcpError)(new Error('Frequency interval must be a positive integer'));
|
|
1051
|
+
}
|
|
1052
|
+
if (frequency.daysOfWeek && !frequency.daysOfWeek.every(day => Number.isInteger(day) && day >= 0 && day <= 6)) {
|
|
1053
|
+
return (0, utils_1.formatMcpError)(new Error('daysOfWeek must contain integers between 0-6 (Sunday-Saturday)'));
|
|
1054
|
+
}
|
|
1055
|
+
if (frequency.dayOfMonth && (!Number.isInteger(frequency.dayOfMonth) || frequency.dayOfMonth < 1 || frequency.dayOfMonth > 31)) {
|
|
1056
|
+
return (0, utils_1.formatMcpError)(new Error('dayOfMonth must be an integer between 1-31'));
|
|
1057
|
+
}
|
|
1058
|
+
if (frequency.endDate) {
|
|
1059
|
+
const endDate = new Date(frequency.endDate);
|
|
1060
|
+
if (isNaN(endDate.getTime())) {
|
|
1061
|
+
return (0, utils_1.formatMcpError)(new Error('frequency.endDate must be a valid ISO 8601 date format'));
|
|
1062
|
+
}
|
|
1063
|
+
if (endDate <= new Date()) {
|
|
1064
|
+
return (0, utils_1.formatMcpError)(new Error('frequency.endDate must be in the future'));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
// Validate optional fields
|
|
1068
|
+
if (priority && !['ASAP', 'HIGH', 'MEDIUM', 'LOW'].includes(priority)) {
|
|
1069
|
+
return (0, utils_1.formatMcpError)(new Error('Priority must be one of: ASAP, HIGH, MEDIUM, LOW'));
|
|
1070
|
+
}
|
|
1071
|
+
if (deadlineType && !['HARD', 'SOFT'].includes(deadlineType)) {
|
|
1072
|
+
return (0, utils_1.formatMcpError)(new Error('Deadline type must be one of: HARD, SOFT'));
|
|
1073
|
+
}
|
|
1074
|
+
if (duration !== undefined) {
|
|
1075
|
+
if (typeof duration === 'number') {
|
|
1076
|
+
if (!Number.isInteger(duration) || duration <= 0) {
|
|
1077
|
+
return (0, utils_1.formatMcpError)(new Error('Duration must be a positive integer'));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
else if (typeof duration === 'string' && duration !== 'REMINDER') {
|
|
1081
|
+
return (0, utils_1.formatMcpError)(new Error('Duration string must be "REMINDER"'));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (startingOn) {
|
|
1085
|
+
const startDate = new Date(startingOn);
|
|
1086
|
+
if (isNaN(startDate.getTime())) {
|
|
1087
|
+
return (0, utils_1.formatMcpError)(new Error('startingOn must be a valid ISO 8601 date format'));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (idealTime && !/^\d{2}:\d{2}$/.test(idealTime)) {
|
|
1091
|
+
return (0, utils_1.formatMcpError)(new Error('idealTime must be in HH:mm format'));
|
|
1092
|
+
}
|
|
1093
|
+
const workspace = await workspaceResolver.resolveWorkspace({ workspaceId });
|
|
1094
|
+
const taskData = {
|
|
1095
|
+
name,
|
|
1096
|
+
workspaceId: workspace.id,
|
|
1097
|
+
assigneeId,
|
|
1098
|
+
frequency,
|
|
1099
|
+
...(description && { description }),
|
|
1100
|
+
...(projectId && { projectId }),
|
|
1101
|
+
...(deadlineType && { deadlineType }),
|
|
1102
|
+
...(duration && { duration }),
|
|
1103
|
+
...(startingOn && { startingOn }),
|
|
1104
|
+
...(idealTime && { idealTime }),
|
|
1105
|
+
...(schedule && { schedule }),
|
|
1106
|
+
...(priority && { priority })
|
|
1107
|
+
};
|
|
1108
|
+
const newTask = await motionService.createRecurringTask(taskData);
|
|
1109
|
+
return (0, utils_1.formatRecurringTaskDetail)(newTask);
|
|
1110
|
+
case 'delete':
|
|
1111
|
+
if (!recurringTaskId) {
|
|
1112
|
+
return (0, utils_1.formatMcpError)(new Error('Recurring task ID is required for delete operation'));
|
|
1113
|
+
}
|
|
1114
|
+
await motionService.deleteRecurringTask(recurringTaskId);
|
|
1115
|
+
return (0, utils_1.formatMcpSuccess)(`Recurring task ${recurringTaskId} deleted successfully`);
|
|
1116
|
+
default:
|
|
1117
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
async handleMotionSchedules(args) {
|
|
1125
|
+
const motionService = this.motionService;
|
|
1126
|
+
if (!motionService) {
|
|
1127
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service not available"));
|
|
1128
|
+
}
|
|
1129
|
+
const { userId, startDate, endDate } = args;
|
|
1130
|
+
// Validate date formats if provided using Date constructor
|
|
1131
|
+
const isValidDate = (dateStr) => {
|
|
1132
|
+
if (!dateStr)
|
|
1133
|
+
return true; // Optional fields are valid when not provided
|
|
1134
|
+
const date = new Date(dateStr);
|
|
1135
|
+
return !isNaN(date.getTime());
|
|
1136
|
+
};
|
|
1137
|
+
if (!isValidDate(startDate)) {
|
|
1138
|
+
return (0, utils_1.formatMcpError)(new Error('startDate must be a valid date string (e.g., "2024-01-15" or "2024-01-15T10:30:00Z")'));
|
|
1139
|
+
}
|
|
1140
|
+
if (!isValidDate(endDate)) {
|
|
1141
|
+
return (0, utils_1.formatMcpError)(new Error('endDate must be a valid date string (e.g., "2024-01-15" or "2024-01-15T10:30:00Z")'));
|
|
1142
|
+
}
|
|
1143
|
+
// Validate date range logic if both dates are provided
|
|
1144
|
+
if (startDate && endDate) {
|
|
1145
|
+
const start = new Date(startDate);
|
|
1146
|
+
const end = new Date(endDate);
|
|
1147
|
+
if (start > end) {
|
|
1148
|
+
return (0, utils_1.formatMcpError)(new Error('startDate must be before or equal to endDate'));
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
const schedules = await motionService.getSchedules(userId, startDate, endDate);
|
|
1153
|
+
return (0, utils_1.formatScheduleList)(schedules);
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async handleMotionStatuses(args) {
|
|
1160
|
+
const motionService = this.motionService;
|
|
1161
|
+
if (!motionService) {
|
|
1162
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service not available"));
|
|
1163
|
+
}
|
|
1164
|
+
const { workspaceId } = args;
|
|
1165
|
+
try {
|
|
1166
|
+
const statuses = await motionService.getStatuses(workspaceId);
|
|
1167
|
+
return (0, utils_1.formatStatusList)(statuses);
|
|
1168
|
+
}
|
|
1169
|
+
catch (error) {
|
|
1170
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// New consolidated handlers
|
|
1174
|
+
async handleMotionWorkspaces(args) {
|
|
1175
|
+
const motionService = this.motionService;
|
|
1176
|
+
if (!motionService) {
|
|
1177
|
+
return (0, utils_1.formatMcpError)(new Error("Motion service not available"));
|
|
1178
|
+
}
|
|
1179
|
+
const { operation, workspaceId } = args;
|
|
1180
|
+
try {
|
|
1181
|
+
switch (operation) {
|
|
1182
|
+
case 'list':
|
|
1183
|
+
const workspaces = await motionService.getWorkspaces();
|
|
1184
|
+
return (0, utils_1.formatWorkspaceList)(workspaces);
|
|
1185
|
+
case 'get':
|
|
1186
|
+
if (!workspaceId) {
|
|
1187
|
+
return (0, utils_1.formatMcpError)(new Error("Workspace ID is required for get operation"));
|
|
1188
|
+
}
|
|
1189
|
+
const workspaces_all = await motionService.getWorkspaces();
|
|
1190
|
+
const workspace = workspaces_all.find(w => w.id === workspaceId);
|
|
1191
|
+
if (!workspace) {
|
|
1192
|
+
return (0, utils_1.formatMcpError)(new Error(`Workspace not found: ${workspaceId}`));
|
|
1193
|
+
}
|
|
1194
|
+
return (0, utils_1.formatDetailResponse)(workspace, `Workspace details for "${workspace.name}"`);
|
|
1195
|
+
case 'set_default':
|
|
1196
|
+
if (!workspaceId) {
|
|
1197
|
+
return (0, utils_1.formatMcpError)(new Error("Workspace ID is required for set_default operation"));
|
|
1198
|
+
}
|
|
1199
|
+
// For now, we'll just return a success message since Motion API doesn't have a set default endpoint
|
|
1200
|
+
return (0, utils_1.formatMcpSuccess)(`Default workspace would be set to: ${workspaceId} (Note: This is a placeholder - actual implementation depends on Motion API support)`);
|
|
1201
|
+
default:
|
|
1202
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
async handleMotionSearch(args) {
|
|
1210
|
+
const motionService = this.motionService;
|
|
1211
|
+
const workspaceResolver = this.workspaceResolver;
|
|
1212
|
+
if (!motionService || !workspaceResolver) {
|
|
1213
|
+
return (0, utils_1.formatMcpError)(new Error("Services not available"));
|
|
1214
|
+
}
|
|
1215
|
+
const { operation } = args;
|
|
1216
|
+
try {
|
|
1217
|
+
switch (operation) {
|
|
1218
|
+
case 'content':
|
|
1219
|
+
if (!args.query) {
|
|
1220
|
+
return (0, utils_1.formatMcpError)(new Error("Query is required for content search"));
|
|
1221
|
+
}
|
|
1222
|
+
return await this.handleSearchContent({
|
|
1223
|
+
query: args.query,
|
|
1224
|
+
entityTypes: args.searchScope === 'tasks' ? ['tasks'] :
|
|
1225
|
+
args.searchScope === 'projects' ? ['projects'] :
|
|
1226
|
+
['tasks', 'projects'],
|
|
1227
|
+
workspaceId: args.workspaceId,
|
|
1228
|
+
workspaceName: args.workspaceName
|
|
1229
|
+
});
|
|
1230
|
+
case 'context':
|
|
1231
|
+
if (!args.entityType || !args.entityId) {
|
|
1232
|
+
return (0, utils_1.formatMcpError)(new Error("EntityType and entityId are required for context operation"));
|
|
1233
|
+
}
|
|
1234
|
+
return await this.handleGetContext({
|
|
1235
|
+
entityType: args.entityType,
|
|
1236
|
+
entityId: args.entityId,
|
|
1237
|
+
includeRelated: args.includeRelated
|
|
1238
|
+
});
|
|
1239
|
+
case 'smart':
|
|
1240
|
+
// Smart search combines content search with contextual information
|
|
1241
|
+
const { query, entityType, entityId, includeRelated = false } = args;
|
|
1242
|
+
if (!query) {
|
|
1243
|
+
return (0, utils_1.formatMcpError)(new Error("Query is required for smart search"));
|
|
1244
|
+
}
|
|
1245
|
+
let contextText = `Smart search results for "${query}":\n\n`;
|
|
1246
|
+
// Perform content search
|
|
1247
|
+
const searchResults = await this.handleSearchContent({
|
|
1248
|
+
query,
|
|
1249
|
+
entityTypes: args.searchScope === 'tasks' ? ['tasks'] :
|
|
1250
|
+
args.searchScope === 'projects' ? ['projects'] :
|
|
1251
|
+
['tasks', 'projects'],
|
|
1252
|
+
workspaceId: args.workspaceId,
|
|
1253
|
+
workspaceName: args.workspaceName
|
|
1254
|
+
});
|
|
1255
|
+
contextText += "Content Results:\n";
|
|
1256
|
+
// Extract text from search results
|
|
1257
|
+
if ('content' in searchResults && searchResults.content && Array.isArray(searchResults.content)) {
|
|
1258
|
+
const textContent = searchResults.content.find(item => item.type === 'text');
|
|
1259
|
+
if (textContent && 'text' in textContent) {
|
|
1260
|
+
contextText += textContent.text + "\n\n";
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
// Add contextual information if entity is specified
|
|
1264
|
+
if (entityType && entityId) {
|
|
1265
|
+
contextText += `Context for ${entityType} ${entityId}:\n`;
|
|
1266
|
+
if (entityType === 'project') {
|
|
1267
|
+
contextText += `Project ID: ${entityId}\n`;
|
|
1268
|
+
if (includeRelated) {
|
|
1269
|
+
contextText += `Related tasks would be listed here (when available)\n`;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
else if (entityType === 'task') {
|
|
1273
|
+
contextText += `Task ID: ${entityId}\n`;
|
|
1274
|
+
if (includeRelated) {
|
|
1275
|
+
contextText += `Related project and subtasks would be listed here (when available)\n`;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return (0, utils_1.formatMcpSuccess)(contextText);
|
|
1280
|
+
default:
|
|
1281
|
+
return (0, utils_1.formatMcpError)(new Error(`Unknown operation: ${operation}`));
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
catch (error) {
|
|
1285
|
+
return (0, utils_1.formatMcpError)(error instanceof Error ? error : new Error(String(error)));
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
async run() {
|
|
1289
|
+
await this.initialize();
|
|
1290
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
1291
|
+
await this.server.connect(transport);
|
|
1292
|
+
(0, utils_1.mcpLog)(utils_1.LOG_LEVELS.INFO, "Motion MCP Server running on stdio");
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
// Main execution
|
|
1296
|
+
if (require.main === module) {
|
|
1297
|
+
const server = new MotionMCPServer();
|
|
1298
|
+
server.run().catch((error) => {
|
|
1299
|
+
console.error("Failed to run server:", error);
|
|
1300
|
+
process.exit(1);
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
exports.default = MotionMCPServer;
|
|
1304
|
+
//# sourceMappingURL=mcp-server-old.js.map
|