@taazkareem/clickup-mcp-server 0.4.16 → 0.4.18
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 +3 -0
- package/build/config.js +14 -4
- package/build/handlers/prompts.js +53 -0
- package/build/handlers/tools.js +303 -30
- package/build/index.js +182 -346
- package/build/services/clickup.js +171 -5
- package/build/types/clickup.js +7 -0
- package/build/utils/logger.js +47 -0
- package/build/utils/resolvers.js +50 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -1,38 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ClickUp MCP Server
|
|
4
|
+
* A Model Context Protocol server implementation for ClickUp integration.
|
|
5
|
+
* Provides tools and resources for AI agents to interact with ClickUp tasks,
|
|
6
|
+
* spaces, lists, and folders through a standardized protocol.
|
|
7
|
+
*
|
|
8
|
+
* @module clickup-mcp-server
|
|
9
|
+
*/
|
|
2
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
12
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
13
|
import { ClickUpService } from "./services/clickup.js";
|
|
6
14
|
import config from "./config.js";
|
|
7
|
-
import { handleWorkspaceHierarchy, handleCreateTask } from "./handlers/tools.js";
|
|
15
|
+
import { handleWorkspaceHierarchy, handleCreateTask, handleCreateBulkTasks, handleCreateList, handleCreateFolder, handleCreateListInFolder, handleMoveTask, handleDuplicateTask, handleUpdateTask } from "./handlers/tools.js";
|
|
8
16
|
import { handleSummarizeTasks, handleAnalyzeTaskPriorities } from "./handlers/prompts.js";
|
|
9
17
|
import { getAllTasks } from "./utils/resolvers.js";
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
import { logError, logInfo, logDebug } from "./utils/logger.js";
|
|
19
|
+
// Set up global error handlers
|
|
20
|
+
process.on('uncaughtException', (error) => {
|
|
21
|
+
logError('server.uncaught', error);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
process.on('unhandledRejection', (reason) => {
|
|
25
|
+
logError('server.unhandled_rejection', reason);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
28
|
+
logInfo('server', { status: 'starting' });
|
|
29
|
+
logInfo('config', {
|
|
12
30
|
clickupApiKey: config.clickupApiKey ? '***' : 'missing',
|
|
13
31
|
teamId: config.teamId || 'missing'
|
|
14
32
|
});
|
|
15
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Initialize ClickUp service singleton
|
|
35
|
+
* This must be done before any other ClickUp operations
|
|
36
|
+
*/
|
|
16
37
|
let clickup;
|
|
17
38
|
try {
|
|
18
|
-
|
|
39
|
+
logDebug('clickup', { status: 'initializing' });
|
|
19
40
|
clickup = ClickUpService.initialize(config.clickupApiKey);
|
|
20
|
-
|
|
41
|
+
logInfo('clickup', { status: 'initialized' });
|
|
21
42
|
}
|
|
22
43
|
catch (error) {
|
|
23
|
-
|
|
44
|
+
logError('clickup.initialization', error);
|
|
24
45
|
process.exit(1);
|
|
25
46
|
}
|
|
26
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Create and configure the MCP server instance
|
|
49
|
+
* Sets up capabilities, tools, and error handling
|
|
50
|
+
*/
|
|
51
|
+
logDebug('mcp', { status: 'creating' });
|
|
27
52
|
const server = new Server({
|
|
28
53
|
name: "clickup-mcp-server",
|
|
29
54
|
version: "0.4.14",
|
|
30
|
-
|
|
55
|
+
transport: new StdioServerTransport(),
|
|
31
56
|
capabilities: {
|
|
32
|
-
resources: {
|
|
33
|
-
list: true,
|
|
34
|
-
read: true,
|
|
35
|
-
},
|
|
36
57
|
tools: {
|
|
37
58
|
workspace_hierarchy: {
|
|
38
59
|
description: "List complete hierarchy of the ClickUp workspace",
|
|
@@ -80,17 +101,17 @@ const server = new Server({
|
|
|
80
101
|
}
|
|
81
102
|
},
|
|
82
103
|
create_bulk_tasks: {
|
|
83
|
-
description: "Create multiple tasks in a
|
|
104
|
+
description: "Create multiple tasks simultaneously in a list",
|
|
84
105
|
inputSchema: {
|
|
85
106
|
type: "object",
|
|
86
107
|
properties: {
|
|
87
108
|
listId: {
|
|
88
109
|
type: "string",
|
|
89
|
-
description: "ID of the list to create
|
|
110
|
+
description: "ID of the list to create tasks in (optional if listName is provided)"
|
|
90
111
|
},
|
|
91
112
|
listName: {
|
|
92
113
|
type: "string",
|
|
93
|
-
description: "Name of the list to create
|
|
114
|
+
description: "Name of the list to create tasks in (optional if listId is provided)"
|
|
94
115
|
},
|
|
95
116
|
tasks: {
|
|
96
117
|
type: "array",
|
|
@@ -112,18 +133,11 @@ const server = new Server({
|
|
|
112
133
|
},
|
|
113
134
|
priority: {
|
|
114
135
|
type: "number",
|
|
115
|
-
description: "Priority
|
|
136
|
+
description: "Priority of the task (1-4)"
|
|
116
137
|
},
|
|
117
138
|
dueDate: {
|
|
118
139
|
type: "string",
|
|
119
|
-
description: "Due date (ISO string)"
|
|
120
|
-
},
|
|
121
|
-
assignees: {
|
|
122
|
-
type: "array",
|
|
123
|
-
items: {
|
|
124
|
-
type: "number"
|
|
125
|
-
},
|
|
126
|
-
description: "Array of user IDs to assign to the task"
|
|
140
|
+
description: "Due date of the task (ISO string)"
|
|
127
141
|
}
|
|
128
142
|
},
|
|
129
143
|
required: ["name"]
|
|
@@ -134,17 +148,17 @@ const server = new Server({
|
|
|
134
148
|
}
|
|
135
149
|
},
|
|
136
150
|
create_list: {
|
|
137
|
-
description: "Create a new list in a
|
|
151
|
+
description: "Create a new list in a space",
|
|
138
152
|
inputSchema: {
|
|
139
153
|
type: "object",
|
|
140
154
|
properties: {
|
|
141
155
|
spaceId: {
|
|
142
156
|
type: "string",
|
|
143
|
-
description: "ID of the space
|
|
157
|
+
description: "ID of the space (optional if spaceName is provided)"
|
|
144
158
|
},
|
|
145
159
|
spaceName: {
|
|
146
160
|
type: "string",
|
|
147
|
-
description: "Name of the space
|
|
161
|
+
description: "Name of the space (optional if spaceId is provided)"
|
|
148
162
|
},
|
|
149
163
|
name: {
|
|
150
164
|
type: "string",
|
|
@@ -152,40 +166,24 @@ const server = new Server({
|
|
|
152
166
|
},
|
|
153
167
|
content: {
|
|
154
168
|
type: "string",
|
|
155
|
-
description: "
|
|
156
|
-
},
|
|
157
|
-
dueDate: {
|
|
158
|
-
type: "string",
|
|
159
|
-
description: "Due date for the list (ISO string)"
|
|
160
|
-
},
|
|
161
|
-
priority: {
|
|
162
|
-
type: "number",
|
|
163
|
-
description: "Priority of the list (1-4)"
|
|
164
|
-
},
|
|
165
|
-
assignee: {
|
|
166
|
-
type: "number",
|
|
167
|
-
description: "User ID to assign the list to"
|
|
168
|
-
},
|
|
169
|
-
status: {
|
|
170
|
-
type: "string",
|
|
171
|
-
description: "Status of the list"
|
|
169
|
+
description: "List description"
|
|
172
170
|
}
|
|
173
171
|
},
|
|
174
172
|
required: ["name"]
|
|
175
173
|
}
|
|
176
174
|
},
|
|
177
175
|
create_folder: {
|
|
178
|
-
description: "Create a new folder in a
|
|
176
|
+
description: "Create a new folder in a space",
|
|
179
177
|
inputSchema: {
|
|
180
178
|
type: "object",
|
|
181
179
|
properties: {
|
|
182
180
|
spaceId: {
|
|
183
181
|
type: "string",
|
|
184
|
-
description: "ID of the space
|
|
182
|
+
description: "ID of the space (optional if spaceName is provided)"
|
|
185
183
|
},
|
|
186
184
|
spaceName: {
|
|
187
185
|
type: "string",
|
|
188
|
-
description: "Name of the space
|
|
186
|
+
description: "Name of the space (optional if spaceId is provided)"
|
|
189
187
|
},
|
|
190
188
|
name: {
|
|
191
189
|
type: "string",
|
|
@@ -200,25 +198,25 @@ const server = new Server({
|
|
|
200
198
|
}
|
|
201
199
|
},
|
|
202
200
|
create_list_in_folder: {
|
|
203
|
-
description: "Create a new list
|
|
201
|
+
description: "Create a new list within a folder",
|
|
204
202
|
inputSchema: {
|
|
205
203
|
type: "object",
|
|
206
204
|
properties: {
|
|
207
205
|
folderId: {
|
|
208
206
|
type: "string",
|
|
209
|
-
description: "ID of the folder
|
|
207
|
+
description: "ID of the folder (optional if using folderName)"
|
|
210
208
|
},
|
|
211
209
|
folderName: {
|
|
212
210
|
type: "string",
|
|
213
|
-
description: "Name of the folder
|
|
211
|
+
description: "Name of the folder"
|
|
214
212
|
},
|
|
215
213
|
spaceId: {
|
|
216
214
|
type: "string",
|
|
217
|
-
description: "ID of the space
|
|
215
|
+
description: "ID of the space (required if using folderName)"
|
|
218
216
|
},
|
|
219
217
|
spaceName: {
|
|
220
218
|
type: "string",
|
|
221
|
-
description: "Name of the space
|
|
219
|
+
description: "Name of the space (alternative to spaceId)"
|
|
222
220
|
},
|
|
223
221
|
name: {
|
|
224
222
|
type: "string",
|
|
@@ -226,11 +224,7 @@ const server = new Server({
|
|
|
226
224
|
},
|
|
227
225
|
content: {
|
|
228
226
|
type: "string",
|
|
229
|
-
description: "
|
|
230
|
-
},
|
|
231
|
-
status: {
|
|
232
|
-
type: "string",
|
|
233
|
-
description: "Status of the list"
|
|
227
|
+
description: "List description"
|
|
234
228
|
}
|
|
235
229
|
},
|
|
236
230
|
required: ["name"]
|
|
@@ -247,18 +241,18 @@ const server = new Server({
|
|
|
247
241
|
},
|
|
248
242
|
listId: {
|
|
249
243
|
type: "string",
|
|
250
|
-
description: "ID of
|
|
244
|
+
description: "ID of destination list (optional if listName is provided)"
|
|
251
245
|
},
|
|
252
246
|
listName: {
|
|
253
247
|
type: "string",
|
|
254
|
-
description: "Name of
|
|
248
|
+
description: "Name of destination list (optional if listId is provided)"
|
|
255
249
|
}
|
|
256
250
|
},
|
|
257
251
|
required: ["taskId"]
|
|
258
252
|
}
|
|
259
253
|
},
|
|
260
254
|
duplicate_task: {
|
|
261
|
-
description: "
|
|
255
|
+
description: "Create a copy of a task in a specified list",
|
|
262
256
|
inputSchema: {
|
|
263
257
|
type: "object",
|
|
264
258
|
properties: {
|
|
@@ -268,18 +262,18 @@ const server = new Server({
|
|
|
268
262
|
},
|
|
269
263
|
listId: {
|
|
270
264
|
type: "string",
|
|
271
|
-
description: "ID of
|
|
265
|
+
description: "ID of destination list (optional if listName is provided)"
|
|
272
266
|
},
|
|
273
267
|
listName: {
|
|
274
268
|
type: "string",
|
|
275
|
-
description: "Name of
|
|
269
|
+
description: "Name of destination list (optional if listId is provided)"
|
|
276
270
|
}
|
|
277
271
|
},
|
|
278
272
|
required: ["taskId"]
|
|
279
273
|
}
|
|
280
274
|
},
|
|
281
275
|
update_task: {
|
|
282
|
-
description: "Update an existing task
|
|
276
|
+
description: "Update an existing task",
|
|
283
277
|
inputSchema: {
|
|
284
278
|
type: "object",
|
|
285
279
|
properties: {
|
|
@@ -289,55 +283,106 @@ const server = new Server({
|
|
|
289
283
|
},
|
|
290
284
|
name: {
|
|
291
285
|
type: "string",
|
|
292
|
-
description: "New name
|
|
286
|
+
description: "New task name"
|
|
293
287
|
},
|
|
294
288
|
description: {
|
|
295
289
|
type: "string",
|
|
296
|
-
description: "New description
|
|
290
|
+
description: "New description"
|
|
297
291
|
},
|
|
298
292
|
status: {
|
|
299
293
|
type: "string",
|
|
300
|
-
description: "New status
|
|
294
|
+
description: "New status"
|
|
301
295
|
},
|
|
302
296
|
priority: {
|
|
303
297
|
type: "number",
|
|
304
|
-
description: "New priority
|
|
298
|
+
description: "New priority level (1-4)"
|
|
305
299
|
},
|
|
306
300
|
dueDate: {
|
|
307
301
|
type: "string",
|
|
308
|
-
description: "New due date
|
|
302
|
+
description: "New due date (ISO string)"
|
|
309
303
|
}
|
|
310
304
|
},
|
|
311
305
|
required: ["taskId"]
|
|
312
306
|
}
|
|
313
307
|
}
|
|
314
|
-
}
|
|
315
|
-
prompts: {
|
|
316
|
-
summarize_tasks: {
|
|
317
|
-
description: "Summarize tasks in the workspace",
|
|
318
|
-
inputSchema: {
|
|
319
|
-
type: "object",
|
|
320
|
-
properties: {},
|
|
321
|
-
required: []
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
analyze_priorities: {
|
|
325
|
-
description: "Analyze task priorities",
|
|
326
|
-
inputSchema: {
|
|
327
|
-
type: "object",
|
|
328
|
-
properties: {},
|
|
329
|
-
required: []
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
},
|
|
308
|
+
}
|
|
333
309
|
},
|
|
310
|
+
errorHandler: (error) => {
|
|
311
|
+
logError('mcp', error);
|
|
312
|
+
}
|
|
334
313
|
});
|
|
335
|
-
console.log('MCP server created');
|
|
336
314
|
/**
|
|
337
|
-
*
|
|
315
|
+
* Register handlers for MCP tool requests
|
|
316
|
+
* Each tool is mapped to its corresponding handler function
|
|
317
|
+
*/
|
|
318
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
319
|
+
try {
|
|
320
|
+
logDebug('tool.request', { name: request.params.name });
|
|
321
|
+
switch (request.params.name) {
|
|
322
|
+
case "workspace_hierarchy": {
|
|
323
|
+
return await handleWorkspaceHierarchy(clickup, config.teamId);
|
|
324
|
+
}
|
|
325
|
+
case "create_task": {
|
|
326
|
+
const args = request.params.arguments;
|
|
327
|
+
return await handleCreateTask(clickup, config.teamId, args);
|
|
328
|
+
}
|
|
329
|
+
case "create_bulk_tasks": {
|
|
330
|
+
const args = request.params.arguments;
|
|
331
|
+
return await handleCreateBulkTasks(clickup, config.teamId, args);
|
|
332
|
+
}
|
|
333
|
+
case "create_list": {
|
|
334
|
+
const args = request.params.arguments;
|
|
335
|
+
return await handleCreateList(clickup, config.teamId, args);
|
|
336
|
+
}
|
|
337
|
+
case "create_folder": {
|
|
338
|
+
const args = request.params.arguments;
|
|
339
|
+
return await handleCreateFolder(clickup, config.teamId, args);
|
|
340
|
+
}
|
|
341
|
+
case "create_list_in_folder": {
|
|
342
|
+
const args = request.params.arguments;
|
|
343
|
+
return await handleCreateListInFolder(clickup, config.teamId, args);
|
|
344
|
+
}
|
|
345
|
+
case "move_task": {
|
|
346
|
+
const args = request.params.arguments;
|
|
347
|
+
return await handleMoveTask(clickup, config.teamId, args);
|
|
348
|
+
}
|
|
349
|
+
case "duplicate_task": {
|
|
350
|
+
const args = request.params.arguments;
|
|
351
|
+
return await handleDuplicateTask(clickup, config.teamId, args);
|
|
352
|
+
}
|
|
353
|
+
case "update_task": {
|
|
354
|
+
const args = request.params.arguments;
|
|
355
|
+
return await handleUpdateTask(clickup, config.teamId, args);
|
|
356
|
+
}
|
|
357
|
+
default:
|
|
358
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
logError(`tool.${request.params.name}`, error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
/**
|
|
367
|
+
* Initialize and start the server
|
|
368
|
+
* Connects to the transport and begins listening for requests
|
|
369
|
+
*/
|
|
370
|
+
try {
|
|
371
|
+
logInfo('mcp', { status: 'connecting' });
|
|
372
|
+
const transport = new StdioServerTransport();
|
|
373
|
+
await server.connect(transport);
|
|
374
|
+
logInfo('mcp', { status: 'connected' });
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
logError('mcp.connection', error);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Handler for listing available ClickUp tasks as resources
|
|
382
|
+
* Returns a list of all tasks across all spaces with their metadata
|
|
338
383
|
*/
|
|
339
384
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
340
|
-
|
|
385
|
+
logDebug('resources', { action: 'listing' });
|
|
341
386
|
try {
|
|
342
387
|
const { tasks, spaces } = await getAllTasks(clickup, config.teamId);
|
|
343
388
|
return {
|
|
@@ -351,17 +396,19 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
351
396
|
};
|
|
352
397
|
}
|
|
353
398
|
catch (error) {
|
|
354
|
-
|
|
399
|
+
logError('resources.list', error);
|
|
355
400
|
throw error;
|
|
356
401
|
}
|
|
357
402
|
});
|
|
358
403
|
/**
|
|
359
|
-
* Handler for reading
|
|
404
|
+
* Handler for reading individual ClickUp task contents
|
|
405
|
+
* Returns detailed information about a specific task
|
|
360
406
|
*/
|
|
361
407
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
362
408
|
try {
|
|
363
409
|
const url = new URL(request.params.uri);
|
|
364
410
|
const taskId = url.pathname.replace(/^\/task\//, '');
|
|
411
|
+
logDebug('resources.read', { taskId });
|
|
365
412
|
const task = await clickup.getTask(taskId);
|
|
366
413
|
return {
|
|
367
414
|
contents: [{
|
|
@@ -373,259 +420,28 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
373
420
|
};
|
|
374
421
|
}
|
|
375
422
|
catch (error) {
|
|
376
|
-
|
|
423
|
+
logError('resources.read', error);
|
|
377
424
|
throw error;
|
|
378
425
|
}
|
|
379
426
|
});
|
|
380
427
|
/**
|
|
381
|
-
* Handler for listing available tools
|
|
428
|
+
* Handler for listing available tools
|
|
429
|
+
* Returns metadata about all registered tools
|
|
382
430
|
*/
|
|
383
431
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
384
|
-
|
|
432
|
+
logDebug('tools', { action: 'listing' });
|
|
385
433
|
const toolCapabilities = server.capabilities?.tools || {};
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
393
|
-
});
|
|
394
|
-
/**
|
|
395
|
-
* Handler for executing tools.
|
|
396
|
-
*/
|
|
397
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
398
|
-
try {
|
|
399
|
-
switch (request.params.name) {
|
|
400
|
-
case "workspace_hierarchy": {
|
|
401
|
-
const output = await handleWorkspaceHierarchy(clickup, config.teamId);
|
|
402
|
-
return {
|
|
403
|
-
content: [{
|
|
404
|
-
type: "text",
|
|
405
|
-
text: output
|
|
406
|
-
}]
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
case "create_task": {
|
|
410
|
-
const args = request.params.arguments;
|
|
411
|
-
const task = await handleCreateTask(clickup, config.teamId, args);
|
|
412
|
-
return {
|
|
413
|
-
content: [{
|
|
414
|
-
type: "text",
|
|
415
|
-
text: `Created task ${task.id}: ${task.name}`
|
|
416
|
-
}]
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
case "create_bulk_tasks": {
|
|
420
|
-
const args = request.params.arguments;
|
|
421
|
-
let listId = args.listId;
|
|
422
|
-
if (!listId && args.listName) {
|
|
423
|
-
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
424
|
-
if (!result) {
|
|
425
|
-
throw new Error(`List with name "${args.listName}" not found`);
|
|
426
|
-
}
|
|
427
|
-
listId = result.list.id;
|
|
428
|
-
}
|
|
429
|
-
if (!listId) {
|
|
430
|
-
throw new Error("Either listId or listName is required");
|
|
431
|
-
}
|
|
432
|
-
if (!args.tasks || !args.tasks.length) {
|
|
433
|
-
throw new Error("At least one task is required");
|
|
434
|
-
}
|
|
435
|
-
const { listId: _, listName: __, tasks } = args;
|
|
436
|
-
const createdTasks = await clickup.createBulkTasks(listId, { tasks });
|
|
437
|
-
return {
|
|
438
|
-
content: [{
|
|
439
|
-
type: "text",
|
|
440
|
-
text: `Created ${createdTasks.length} tasks:\n${createdTasks.map(task => `- ${task.id}: ${task.name}`).join('\n')}`
|
|
441
|
-
}]
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
case "create_list": {
|
|
445
|
-
const args = request.params.arguments;
|
|
446
|
-
if (!args.name) {
|
|
447
|
-
throw new Error("name is required");
|
|
448
|
-
}
|
|
449
|
-
// If folder is specified, create list in folder
|
|
450
|
-
if (args.folderName || args.folderId) {
|
|
451
|
-
let folderId = args.folderId;
|
|
452
|
-
if (!folderId && args.folderName) {
|
|
453
|
-
const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
|
|
454
|
-
if (!result) {
|
|
455
|
-
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
456
|
-
}
|
|
457
|
-
folderId = result.folder.id;
|
|
458
|
-
}
|
|
459
|
-
if (!folderId) {
|
|
460
|
-
throw new Error("Either folderId or folderName must be provided");
|
|
461
|
-
}
|
|
462
|
-
const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
|
|
463
|
-
const list = await clickup.createListInFolder(folderId, listData);
|
|
464
|
-
return {
|
|
465
|
-
content: [{
|
|
466
|
-
type: "text",
|
|
467
|
-
text: `Created list ${list.id}: ${list.name} in folder`
|
|
468
|
-
}]
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
// Otherwise, create list in space
|
|
472
|
-
let spaceId = args.spaceId;
|
|
473
|
-
if (!spaceId && args.spaceName) {
|
|
474
|
-
const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
|
|
475
|
-
if (!space) {
|
|
476
|
-
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
477
|
-
}
|
|
478
|
-
spaceId = space.id;
|
|
479
|
-
}
|
|
480
|
-
if (!spaceId) {
|
|
481
|
-
throw new Error("Either spaceId or spaceName must be provided");
|
|
482
|
-
}
|
|
483
|
-
const { spaceId: _, spaceName: __, folderName: ___, folderId: ____, ...listData } = args;
|
|
484
|
-
const list = await clickup.createList(spaceId, listData);
|
|
485
|
-
return {
|
|
486
|
-
content: [{
|
|
487
|
-
type: "text",
|
|
488
|
-
text: `Created list ${list.id}: ${list.name}`
|
|
489
|
-
}]
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
case "create_folder": {
|
|
493
|
-
const args = request.params.arguments;
|
|
494
|
-
if (!args.name) {
|
|
495
|
-
throw new Error("name is required");
|
|
496
|
-
}
|
|
497
|
-
let spaceId = args.spaceId;
|
|
498
|
-
if (!spaceId && args.spaceName) {
|
|
499
|
-
const space = await clickup.findSpaceByName(config.teamId, args.spaceName);
|
|
500
|
-
if (!space) {
|
|
501
|
-
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
502
|
-
}
|
|
503
|
-
spaceId = space.id;
|
|
504
|
-
}
|
|
505
|
-
if (!spaceId) {
|
|
506
|
-
throw new Error("Either spaceId or spaceName must be provided");
|
|
507
|
-
}
|
|
508
|
-
const { spaceId: _, spaceName: __, ...folderData } = args;
|
|
509
|
-
const folder = await clickup.createFolder(spaceId, folderData);
|
|
510
|
-
return {
|
|
511
|
-
content: [{
|
|
512
|
-
type: "text",
|
|
513
|
-
text: `Created folder ${folder.id}: ${folder.name}`
|
|
514
|
-
}]
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
case "create_list_in_folder": {
|
|
518
|
-
const args = request.params.arguments;
|
|
519
|
-
if (!args.name) {
|
|
520
|
-
throw new Error("name is required");
|
|
521
|
-
}
|
|
522
|
-
let folderId = args.folderId;
|
|
523
|
-
if (!folderId && args.folderName) {
|
|
524
|
-
const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
|
|
525
|
-
if (!result) {
|
|
526
|
-
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
527
|
-
}
|
|
528
|
-
folderId = result.folder.id;
|
|
529
|
-
}
|
|
530
|
-
if (!folderId) {
|
|
531
|
-
throw new Error("Either folderId or folderName is required");
|
|
532
|
-
}
|
|
533
|
-
const listData = {
|
|
534
|
-
name: args.name,
|
|
535
|
-
content: args.content,
|
|
536
|
-
status: args.status
|
|
537
|
-
};
|
|
538
|
-
try {
|
|
539
|
-
const list = await clickup.createListInFolder(folderId, listData);
|
|
540
|
-
return {
|
|
541
|
-
content: [{
|
|
542
|
-
type: "text",
|
|
543
|
-
text: `Created list ${list.id}: ${list.name} in folder`
|
|
544
|
-
}]
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
catch (error) {
|
|
548
|
-
throw new Error(`Failed to create list: ${error.message}`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
case "move_task": {
|
|
552
|
-
const args = request.params.arguments;
|
|
553
|
-
if (!args.taskId) {
|
|
554
|
-
throw new Error("taskId is required");
|
|
555
|
-
}
|
|
556
|
-
let listId = args.listId;
|
|
557
|
-
if (!listId && args.listName) {
|
|
558
|
-
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
559
|
-
if (!result) {
|
|
560
|
-
throw new Error(`List with name "${args.listName}" not found`);
|
|
561
|
-
}
|
|
562
|
-
listId = result.list.id;
|
|
563
|
-
}
|
|
564
|
-
if (!listId) {
|
|
565
|
-
throw new Error("Either listId or listName is required");
|
|
566
|
-
}
|
|
567
|
-
const task = await clickup.moveTask(args.taskId, listId);
|
|
568
|
-
return {
|
|
569
|
-
content: [{
|
|
570
|
-
type: "text",
|
|
571
|
-
text: `Moved task ${task.id} to list ${listId}`
|
|
572
|
-
}]
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
case "duplicate_task": {
|
|
576
|
-
const args = request.params.arguments;
|
|
577
|
-
if (!args.taskId) {
|
|
578
|
-
throw new Error("taskId is required");
|
|
579
|
-
}
|
|
580
|
-
let listId = args.listId;
|
|
581
|
-
if (!listId && args.listName) {
|
|
582
|
-
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
583
|
-
if (!result) {
|
|
584
|
-
throw new Error(`List with name "${args.listName}" not found`);
|
|
585
|
-
}
|
|
586
|
-
listId = result.list.id;
|
|
587
|
-
}
|
|
588
|
-
if (!listId) {
|
|
589
|
-
throw new Error("Either listId or listName is required");
|
|
590
|
-
}
|
|
591
|
-
const task = await clickup.duplicateTask(args.taskId, listId);
|
|
592
|
-
return {
|
|
593
|
-
content: [{
|
|
594
|
-
type: "text",
|
|
595
|
-
text: `Duplicated task ${args.taskId} to new task ${task.id} in list ${listId}`
|
|
596
|
-
}]
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
case "update_task": {
|
|
600
|
-
const args = request.params.arguments;
|
|
601
|
-
if (!args.taskId) {
|
|
602
|
-
throw new Error("taskId is required");
|
|
603
|
-
}
|
|
604
|
-
const task = await clickup.updateTask(args.taskId, {
|
|
605
|
-
name: args.name,
|
|
606
|
-
description: args.description,
|
|
607
|
-
status: args.status,
|
|
608
|
-
priority: args.priority,
|
|
609
|
-
due_date: args.due_date
|
|
610
|
-
});
|
|
611
|
-
return {
|
|
612
|
-
content: [{
|
|
613
|
-
type: "text",
|
|
614
|
-
text: `Updated task ${task.id}: ${task.name}`
|
|
615
|
-
}]
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
default:
|
|
619
|
-
throw new Error("Unknown tool");
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
catch (error) {
|
|
623
|
-
console.error('Error executing tool:', error);
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
434
|
+
const tools = Object.entries(toolCapabilities).map(([name, tool]) => ({
|
|
435
|
+
name,
|
|
436
|
+
description: tool.description,
|
|
437
|
+
inputSchema: tool.inputSchema
|
|
438
|
+
}));
|
|
439
|
+
logDebug('tools', { count: tools.length });
|
|
440
|
+
return { tools };
|
|
626
441
|
});
|
|
627
442
|
/**
|
|
628
|
-
* Handler for listing available prompts
|
|
443
|
+
* Handler for listing available prompts
|
|
444
|
+
* Returns metadata about all registered prompts
|
|
629
445
|
*/
|
|
630
446
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
631
447
|
return {
|
|
@@ -642,7 +458,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
642
458
|
};
|
|
643
459
|
});
|
|
644
460
|
/**
|
|
645
|
-
* Handler for
|
|
461
|
+
* Handler for executing specific prompts
|
|
462
|
+
* Maps prompt names to their corresponding handler functions
|
|
646
463
|
*/
|
|
647
464
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
648
465
|
try {
|
|
@@ -670,42 +487,61 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
670
487
|
}
|
|
671
488
|
}
|
|
672
489
|
catch (error) {
|
|
673
|
-
|
|
490
|
+
logError('prompt', error);
|
|
674
491
|
throw error;
|
|
675
492
|
}
|
|
676
493
|
});
|
|
494
|
+
// Server startup logic
|
|
677
495
|
if (process.argv.includes('--stdio')) {
|
|
678
|
-
|
|
496
|
+
logInfo('server', { status: 'stdio.starting' });
|
|
679
497
|
// Set up stdio transport
|
|
680
498
|
const transport = new StdioServerTransport();
|
|
681
499
|
// Connect server with better error handling
|
|
682
500
|
server.connect(transport)
|
|
683
501
|
.then(() => {
|
|
684
|
-
|
|
502
|
+
logInfo('server', { status: 'connected' });
|
|
503
|
+
// Send initial handshake message with more details
|
|
504
|
+
const capabilities = {
|
|
505
|
+
tools: server.capabilities?.tools || {},
|
|
506
|
+
prompts: server.capabilities?.prompts || {},
|
|
507
|
+
resources: {
|
|
508
|
+
list: true,
|
|
509
|
+
read: true
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
logInfo('mcp', { status: 'handshake.preparing' });
|
|
513
|
+
logInfo('mcp', {
|
|
514
|
+
status: 'handshake',
|
|
515
|
+
name: "clickup-mcp-server",
|
|
516
|
+
version: "0.4.17", // Match package.json version
|
|
517
|
+
hasTools: Object.keys(capabilities.tools).length > 0,
|
|
518
|
+
hasPrompts: Object.keys(capabilities.prompts).length > 0
|
|
519
|
+
});
|
|
520
|
+
logInfo('server', { status: 'ready' });
|
|
685
521
|
// Keep the process alive
|
|
686
522
|
process.stdin.resume();
|
|
687
523
|
// Handle process termination
|
|
688
524
|
process.on('SIGINT', () => {
|
|
689
|
-
|
|
525
|
+
logInfo('server', { status: 'shutdown.sigint' });
|
|
690
526
|
transport.close();
|
|
691
527
|
process.exit(0);
|
|
692
528
|
});
|
|
693
529
|
process.on('SIGTERM', () => {
|
|
694
|
-
|
|
530
|
+
logInfo('server', { status: 'shutdown.sigterm' });
|
|
695
531
|
transport.close();
|
|
696
532
|
process.exit(0);
|
|
697
533
|
});
|
|
698
534
|
})
|
|
699
535
|
.catch(error => {
|
|
700
|
-
|
|
536
|
+
logError('server', error);
|
|
701
537
|
process.exit(1);
|
|
702
538
|
});
|
|
703
539
|
}
|
|
704
540
|
else {
|
|
705
|
-
|
|
541
|
+
logInfo('server', { status: 'standard.starting' });
|
|
706
542
|
// Add your non-stdio server initialization here if needed
|
|
707
543
|
}
|
|
708
544
|
// Prevent unhandled promise rejections from crashing the server
|
|
709
545
|
process.on('unhandledRejection', (error) => {
|
|
710
|
-
|
|
546
|
+
logError('server.unhandled', error);
|
|
711
547
|
});
|