@taazkareem/clickup-mcp-server 0.1.7

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 ADDED
@@ -0,0 +1,122 @@
1
+ # ClickUp MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI applications. This server allows AI agents to interact with ClickUp tasks, spaces, and lists through a standardized protocol.
4
+
5
+ ## Features
6
+
7
+ - 🔄 List and read ClickUp tasks as resources
8
+ - ✨ Create and update tasks through tools
9
+ - 📊 Get spaces and lists with their IDs
10
+ - 📝 Generate task descriptions with AI
11
+ - 📋 Summarize tasks and analyze priorities
12
+ - 🔒 Secure API key management
13
+
14
+ ## Installation
15
+
16
+ ### Using npx (Recommended)
17
+ ```bash
18
+ npx @taazkareem/clickup-mcp-server
19
+ ```
20
+
21
+ ### Global Installation
22
+ ```bash
23
+ npm install -g @taazkareem/clickup-mcp-server
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ 1. Get your ClickUp API key from [ClickUp Settings](https://app.clickup.com/settings/apps)
29
+ 2. Create a `.env` file:
30
+ ```env
31
+ CLICKUP_API_KEY=your_api_key_here
32
+ TEAM_ID=your_team_id_here
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Starting the Server
38
+ ```bash
39
+ clickup-mcp-server
40
+ ```
41
+
42
+ ### Available Tools
43
+
44
+ 1. **list_spaces**
45
+ - Lists all spaces and their lists with IDs
46
+ - No parameters required
47
+
48
+ 2. **create_task**
49
+ - Creates a new task in ClickUp
50
+ - Required parameters:
51
+ - `listId`: ID of the list to create the task in
52
+ - `name`: Name of the task
53
+ - Optional parameters:
54
+ - `description`: Task description
55
+ - `status`: Task status
56
+ - `priority`: Priority level (1-4)
57
+ - `dueDate`: Due date (ISO string)
58
+
59
+ 3. **update_task**
60
+ - Updates an existing task
61
+ - Required parameters:
62
+ - `taskId`: ID of the task to update
63
+ - Optional parameters:
64
+ - Same as create_task
65
+
66
+ ### Available Prompts
67
+
68
+ 1. **summarize_tasks**
69
+ - Provides a summary of all tasks
70
+ - Groups by status and highlights priorities
71
+
72
+ 2. **analyze_priorities**
73
+ - Analyzes task priorities
74
+ - Suggests optimizations and sequencing
75
+
76
+ 3. **generate_description**
77
+ - Helps generate detailed task descriptions
78
+ - Includes objectives, criteria, and dependencies
79
+
80
+ ## Using with Cursor AI Composer
81
+
82
+ To add this server to Cursor AI Composer:
83
+
84
+ ```bash
85
+ npx -y @taazkareem/clickup-mcp-server \
86
+ --env CLICKUP_API_KEY=your_api_key_here \
87
+ --env TEAM_ID=your_team_id_here
88
+ ```
89
+
90
+ You can get these values from:
91
+ - `CLICKUP_API_KEY`: Get from [ClickUp Settings > Apps](https://app.clickup.com/settings/apps)
92
+ - `TEAM_ID`: Your ClickUp Team ID (found in the URL when viewing your workspace or via API)
93
+
94
+ > ⚠️ **Important**: Make sure to replace `your_api_key_here` and `your_team_id_here` with your actual ClickUp credentials.
95
+
96
+ > **Security Note**: Your API key will be stored securely and will not be exposed to AI models.
97
+
98
+ ## Development
99
+
100
+ 1. Clone the repository:
101
+ ```bash
102
+ git clone https://github.com/yourusername/clickup-mcp-server.git
103
+ cd clickup-mcp-server
104
+ ```
105
+
106
+ 2. Install dependencies:
107
+ ```bash
108
+ npm install
109
+ ```
110
+
111
+ 3. Start in development mode:
112
+ ```bash
113
+ npm run dev
114
+ ```
115
+
116
+ ## Contributing
117
+
118
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
119
+
120
+ ## License
121
+
122
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,28 @@
1
+ import dotenv from 'dotenv';
2
+ // Load environment variables from .env file
3
+ dotenv.config();
4
+ // Parse command line arguments for --env flags
5
+ const args = process.argv.slice(2);
6
+ const envArgs = {};
7
+ for (let i = 0; i < args.length; i++) {
8
+ if (args[i] === '--env' && i + 1 < args.length) {
9
+ const [key, value] = args[i + 1].split('=');
10
+ if (key === 'CLICKUP_API_KEY')
11
+ envArgs.clickupApiKey = value;
12
+ if (key === 'TEAM_ID')
13
+ envArgs.teamId = value;
14
+ i++; // Skip the next argument since we used it
15
+ }
16
+ }
17
+ const configuration = {
18
+ clickupApiKey: envArgs.clickupApiKey || process.env.CLICKUP_API_KEY || '',
19
+ teamId: envArgs.teamId || process.env.TEAM_ID || '',
20
+ };
21
+ // Check for missing environment variables
22
+ const missingEnvVars = Object.entries(configuration)
23
+ .filter(([_, value]) => !value)
24
+ .map(([key]) => key);
25
+ if (missingEnvVars.length > 0) {
26
+ throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
27
+ }
28
+ export default configuration;
package/build/index.js ADDED
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * This is a template MCP server that implements a simple ClickUp task management system.
4
+ * It demonstrates core MCP concepts like resources, tools, and prompts by allowing:
5
+ * - Listing ClickUp tasks as resources
6
+ * - Reading individual ClickUp tasks
7
+ * - Creating new ClickUp tasks via a tool
8
+ * - Updating existing ClickUp tasks via a tool
9
+ * - Summarizing all ClickUp tasks via a prompt
10
+ * - Analyzing task priorities via a prompt
11
+ * - Generating detailed descriptions for tasks via a prompt
12
+ */
13
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
+ import { ClickUpService } from "./services/clickup.js";
17
+ import config from "./config.js";
18
+ /**
19
+ * Simple in-memory storage for notes.
20
+ * In a real implementation, this would likely be backed by a database.
21
+ */
22
+ const notes = {
23
+ "1": { title: "First Note", content: "This is note 1" },
24
+ "2": { title: "Second Note", content: "This is note 2" }
25
+ };
26
+ // Initialize ClickUp service
27
+ const clickup = ClickUpService.initialize(config.clickupApiKey);
28
+ /**
29
+ * Create an MCP server with capabilities for resources (to list/read ClickUp tasks),
30
+ * tools (to create/update ClickUp tasks), and prompts (to summarize/analyze ClickUp tasks).
31
+ */
32
+ const server = new Server({
33
+ name: "clickup-mcp-server",
34
+ version: "0.1.0",
35
+ }, {
36
+ capabilities: {
37
+ resources: {},
38
+ tools: {},
39
+ prompts: {},
40
+ },
41
+ });
42
+ /**
43
+ * Handler for listing available ClickUp tasks as resources.
44
+ * Each task is exposed as a resource with:
45
+ * - A clickup:// URI scheme
46
+ * - JSON MIME type
47
+ * - Human readable name and description (including the task name and description)
48
+ */
49
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
50
+ try {
51
+ const spaces = await clickup.getSpaces(config.teamId);
52
+ const resources = [];
53
+ for (const space of spaces) {
54
+ const lists = await clickup.getLists(space.id);
55
+ for (const list of lists) {
56
+ const { tasks } = await clickup.getTasks(list.id);
57
+ resources.push(...tasks.map((task) => ({
58
+ uri: `clickup://task/${task.id}`,
59
+ mimeType: "application/json",
60
+ name: task.name,
61
+ description: task.description || `Task in ${list.name} (${space.name})`,
62
+ tags: []
63
+ })));
64
+ }
65
+ }
66
+ return { resources };
67
+ }
68
+ catch (error) {
69
+ console.error('Error listing resources:', error);
70
+ throw error;
71
+ }
72
+ });
73
+ /**
74
+ * Handler for reading the contents of a specific ClickUp task.
75
+ * Takes a clickup:// URI and returns the task content as JSON.
76
+ */
77
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
78
+ try {
79
+ const url = new URL(request.params.uri);
80
+ const taskId = url.pathname.replace(/^\/task\//, '');
81
+ const task = await clickup.getTask(taskId);
82
+ return {
83
+ contents: [{
84
+ uri: request.params.uri,
85
+ mimeType: "application/json",
86
+ text: JSON.stringify(task, null, 2),
87
+ tags: []
88
+ }]
89
+ };
90
+ }
91
+ catch (error) {
92
+ console.error('Error reading resource:', error);
93
+ throw error;
94
+ }
95
+ });
96
+ /**
97
+ * Handler that lists available tools.
98
+ * Exposes tools for listing spaces, creating tasks, and updating tasks.
99
+ */
100
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
101
+ return {
102
+ tools: [
103
+ {
104
+ name: "list_spaces",
105
+ description: "List all spaces and their lists with IDs",
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {},
109
+ required: []
110
+ }
111
+ },
112
+ {
113
+ name: "create_task",
114
+ description: "Create a new task in ClickUp",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ listId: {
119
+ type: "string",
120
+ description: "ID of the list to create the task in"
121
+ },
122
+ name: {
123
+ type: "string",
124
+ description: "Name of the task"
125
+ },
126
+ description: {
127
+ type: "string",
128
+ description: "Description of the task"
129
+ },
130
+ status: {
131
+ type: "string",
132
+ description: "Status of the task"
133
+ },
134
+ priority: {
135
+ type: "number",
136
+ description: "Priority of the task (1-4)"
137
+ },
138
+ dueDate: {
139
+ type: "string",
140
+ description: "Due date of the task (ISO string)"
141
+ }
142
+ },
143
+ required: ["listId", "name"]
144
+ }
145
+ },
146
+ {
147
+ name: "update_task",
148
+ description: "Update an existing task in ClickUp",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ taskId: {
153
+ type: "string",
154
+ description: "ID of the task to update"
155
+ },
156
+ name: {
157
+ type: "string",
158
+ description: "New name of the task"
159
+ },
160
+ description: {
161
+ type: "string",
162
+ description: "New description of the task"
163
+ },
164
+ status: {
165
+ type: "string",
166
+ description: "New status of the task"
167
+ },
168
+ priority: {
169
+ type: "number",
170
+ description: "New priority of the task (1-4)"
171
+ },
172
+ dueDate: {
173
+ type: "string",
174
+ description: "New due date of the task (ISO string)"
175
+ }
176
+ },
177
+ required: ["taskId"]
178
+ }
179
+ }
180
+ ]
181
+ };
182
+ });
183
+ /**
184
+ * Handler for the CallToolRequestSchema.
185
+ * Handles the execution of tools like listing spaces, creating tasks, and updating tasks.
186
+ */
187
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
+ try {
189
+ switch (request.params.name) {
190
+ case "list_spaces": {
191
+ const spaces = await clickup.getSpaces(config.teamId);
192
+ const allLists = await clickup.getAllLists(config.teamId);
193
+ let output = "Available Spaces and Lists:\n\n";
194
+ for (const space of spaces) {
195
+ output += `Space: ${space.name} (ID: ${space.id})\n`;
196
+ const spaceLists = allLists.filter(list => list.space.id === space.id);
197
+ for (const list of spaceLists) {
198
+ const { statuses } = await clickup.getTasks(list.id);
199
+ output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
200
+ output += ` Available Statuses: ${statuses.join(', ')}\n`;
201
+ }
202
+ output += "\n";
203
+ }
204
+ // Add lists without spaces at the end
205
+ const listsWithoutSpace = allLists.filter(list => !list.space);
206
+ if (listsWithoutSpace.length > 0) {
207
+ output += "Lists without assigned spaces:\n";
208
+ for (const list of listsWithoutSpace) {
209
+ const { statuses } = await clickup.getTasks(list.id);
210
+ output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
211
+ output += ` Available Statuses: ${statuses.join(', ')}\n`;
212
+ }
213
+ }
214
+ return {
215
+ content: [{
216
+ type: "text",
217
+ text: output
218
+ }]
219
+ };
220
+ }
221
+ case "create_task": {
222
+ const args = request.params.arguments;
223
+ if (!args.listId || !args.name) {
224
+ throw new Error("listId and name are required");
225
+ }
226
+ const { listId, ...taskData } = args;
227
+ const task = await clickup.createTask(listId, taskData);
228
+ return {
229
+ content: [{
230
+ type: "text",
231
+ text: `Created task ${task.id}: ${task.name}`
232
+ }]
233
+ };
234
+ }
235
+ case "update_task": {
236
+ const args = request.params.arguments;
237
+ if (!args.taskId) {
238
+ throw new Error("taskId is required");
239
+ }
240
+ const { taskId, ...updateData } = args;
241
+ const task = await clickup.updateTask(taskId, updateData);
242
+ return {
243
+ content: [{
244
+ type: "text",
245
+ text: `Updated task ${task.id}: ${task.name}`
246
+ }]
247
+ };
248
+ }
249
+ default:
250
+ throw new Error("Unknown tool");
251
+ }
252
+ }
253
+ catch (error) {
254
+ console.error('Error handling tool call:', error);
255
+ throw error;
256
+ }
257
+ });
258
+ /**
259
+ * Add handlers for listing and getting prompts.
260
+ * Prompts include summarizing tasks, analyzing priorities, and generating task descriptions.
261
+ */
262
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
263
+ return {
264
+ prompts: [
265
+ {
266
+ name: "summarize_tasks",
267
+ description: "Summarize all tasks in a list",
268
+ },
269
+ {
270
+ name: "analyze_priorities",
271
+ description: "Analyze task priorities and suggest optimizations",
272
+ },
273
+ {
274
+ name: "generate_description",
275
+ description: "Generate a detailed description for a task",
276
+ }
277
+ ]
278
+ };
279
+ });
280
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
281
+ try {
282
+ switch (request.params.name) {
283
+ case "summarize_tasks": {
284
+ const spaces = await clickup.getSpaces(config.teamId);
285
+ const tasks = [];
286
+ // Gather all tasks
287
+ for (const space of spaces) {
288
+ const lists = await clickup.getLists(space.id);
289
+ for (const list of lists) {
290
+ const { tasks: listTasks } = await clickup.getTasks(list.id);
291
+ tasks.push(...listTasks.map((task) => ({
292
+ type: "resource",
293
+ resource: {
294
+ uri: `clickup://task/${task.id}`,
295
+ mimeType: "application/json",
296
+ text: JSON.stringify(task, null, 2)
297
+ }
298
+ })));
299
+ }
300
+ }
301
+ return {
302
+ messages: [
303
+ {
304
+ role: "user",
305
+ content: {
306
+ type: "text",
307
+ text: "Please provide a summary of the following ClickUp tasks:"
308
+ }
309
+ },
310
+ ...tasks.map(task => ({
311
+ role: "user",
312
+ content: task
313
+ })),
314
+ {
315
+ role: "user",
316
+ content: {
317
+ type: "text",
318
+ text: "Please provide:\n1. A high-level overview of all tasks\n2. Group them by status\n3. Highlight any urgent or high-priority items\n4. Suggest any task dependencies or relationships"
319
+ }
320
+ }
321
+ ]
322
+ };
323
+ }
324
+ case "analyze_priorities": {
325
+ const spaces = await clickup.getSpaces(config.teamId);
326
+ const tasks = [];
327
+ for (const space of spaces) {
328
+ const lists = await clickup.getLists(space.id);
329
+ for (const list of lists) {
330
+ const { tasks: listTasks } = await clickup.getTasks(list.id);
331
+ tasks.push(...listTasks.map((task) => ({
332
+ type: "resource",
333
+ resource: {
334
+ uri: `clickup://task/${task.id}`,
335
+ mimeType: "application/json",
336
+ text: JSON.stringify(task, null, 2)
337
+ }
338
+ })));
339
+ }
340
+ }
341
+ return {
342
+ messages: [
343
+ {
344
+ role: "user",
345
+ content: {
346
+ type: "text",
347
+ text: "Please analyze the priorities of the following ClickUp tasks:"
348
+ }
349
+ },
350
+ ...tasks.map(task => ({
351
+ role: "user",
352
+ content: task
353
+ })),
354
+ {
355
+ role: "user",
356
+ content: {
357
+ type: "text",
358
+ text: "Please provide:\n1. Analysis of current priority distribution\n2. Identify any misaligned priorities\n3. Suggest priority adjustments\n4. Recommend task sequencing based on priorities"
359
+ }
360
+ }
361
+ ]
362
+ };
363
+ }
364
+ case "generate_description": {
365
+ return {
366
+ messages: [
367
+ {
368
+ role: "user",
369
+ content: {
370
+ type: "text",
371
+ text: "Please help me generate a detailed description for a ClickUp task. The description should include:\n1. Clear objective\n2. Success criteria\n3. Required resources\n4. Dependencies\n5. Potential risks\n\nPlease ask me about the task details."
372
+ }
373
+ }
374
+ ]
375
+ };
376
+ }
377
+ default:
378
+ throw new Error("Unknown prompt");
379
+ }
380
+ }
381
+ catch (error) {
382
+ console.error('Error handling prompt:', error);
383
+ throw error;
384
+ }
385
+ });
386
+ /**
387
+ * Start the server using stdio transport.
388
+ * This allows the server to communicate via standard input/output streams.
389
+ */
390
+ async function main() {
391
+ const transport = new StdioServerTransport();
392
+ await server.connect(transport);
393
+ }
394
+ main().catch((error) => {
395
+ console.error("Server error:", error);
396
+ process.exit(1);
397
+ });
@@ -0,0 +1,70 @@
1
+ import axios from 'axios';
2
+ export class ClickUpService {
3
+ client;
4
+ static instance;
5
+ constructor(apiKey) {
6
+ this.client = axios.create({
7
+ baseURL: 'https://api.clickup.com/api/v2',
8
+ headers: {
9
+ 'Authorization': apiKey,
10
+ 'Content-Type': 'application/json'
11
+ }
12
+ });
13
+ }
14
+ static initialize(apiKey) {
15
+ if (!ClickUpService.instance) {
16
+ ClickUpService.instance = new ClickUpService(apiKey);
17
+ }
18
+ return ClickUpService.instance;
19
+ }
20
+ static getInstance() {
21
+ if (!ClickUpService.instance) {
22
+ throw new Error('ClickUpService not initialized. Call initialize() first.');
23
+ }
24
+ return ClickUpService.instance;
25
+ }
26
+ // Tasks
27
+ async getTasks(listId) {
28
+ const response = await this.client.get(`/list/${listId}/task`);
29
+ const tasks = response.data.tasks;
30
+ const statuses = [...new Set(tasks.map((task) => task.status.status))];
31
+ return { tasks, statuses };
32
+ }
33
+ async getTask(taskId) {
34
+ const response = await this.client.get(`/task/${taskId}`);
35
+ return response.data;
36
+ }
37
+ async createTask(listId, data) {
38
+ const response = await this.client.post(`/list/${listId}/task`, data);
39
+ return response.data;
40
+ }
41
+ async updateTask(taskId, data) {
42
+ const response = await this.client.put(`/task/${taskId}`, data);
43
+ return response.data;
44
+ }
45
+ async deleteTask(taskId) {
46
+ await this.client.delete(`/task/${taskId}`);
47
+ }
48
+ // Lists
49
+ async getLists(spaceId) {
50
+ const response = await this.client.get(`/space/${spaceId}/list`);
51
+ return response.data.lists;
52
+ }
53
+ async getAllLists(teamId) {
54
+ const response = await this.client.get(`/team/${teamId}/list`);
55
+ return response.data.lists;
56
+ }
57
+ async getList(listId) {
58
+ const response = await this.client.get(`/list/${listId}`);
59
+ return response.data;
60
+ }
61
+ // Spaces
62
+ async getSpaces(teamId) {
63
+ const response = await this.client.get(`/team/${teamId}/space`);
64
+ return response.data.spaces;
65
+ }
66
+ async getSpace(spaceId) {
67
+ const response = await this.client.get(`/space/${spaceId}`);
68
+ return response.data;
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@taazkareem/clickup-mcp-server",
3
+ "version": "0.1.7",
4
+ "description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
5
+ "type": "module",
6
+ "main": "build/index.js",
7
+ "bin": {
8
+ "clickup-mcp-server": "build/index.js"
9
+ },
10
+ "files": [
11
+ "build",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
17
+ "start": "node build/index.js",
18
+ "dev": "tsc -w",
19
+ "prepare": "npm run build",
20
+ "prepublishOnly": "npm test",
21
+ "test": "echo \"No tests specified\" && exit 0"
22
+ },
23
+ "keywords": [
24
+ "clickup",
25
+ "mcp",
26
+ "ai",
27
+ "tasks",
28
+ "project-management",
29
+ "model-context-protocol",
30
+ "clickup-server",
31
+ "clickup-mcp-server"
32
+ ],
33
+ "author": "Talib Kareem",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/taazkareem/clickup-mcp-server.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/taazkareem/clickup-mcp-server/issues"
41
+ },
42
+ "homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "0.6.0",
45
+ "axios": "^1.6.7",
46
+ "dotenv": "^16.4.1"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.11.16",
50
+ "typescript": "^5.3.3"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }