@hauptsache.net/clickup-mcp 1.0.0 → 1.0.2

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 CHANGED
@@ -1,124 +1,73 @@
1
- # ClickUp MCP (Model Context Protocol)
1
+ # ClickUp MCP for AI Assistants
2
2
 
3
- A minimal implementation of a Model Context Protocol (MCP) server for ClickUp integration, designed to allow Large Language Models (LLMs) to read ClickUp tasks and data.
3
+ A Model Context Protocol (MCP) server that enables AI assistants like Claude, Windsurf, and Cursor to interact with your ClickUp tasks.
4
4
 
5
- The main focus is to represent tickets in a way that is easy for LLMs to process and understand.
6
- This especially includes images in the description and in comments of tickets,
7
- which are not that easy to understand by just passing the api responses.
5
+ ## Why This MCP Is Great
8
6
 
9
- ## Overview
7
+ This MCP excels at providing AI assistants with rich access to your ClickUp tasks:
10
8
 
11
- This MCP provides a lightweight interface for LLMs to:
12
- - Retrieve detailed task information by ID
13
- - Search for tasks using keywords
14
- - List open tasks assigned to the current user
9
+ - **Complete Task Information**: View detailed task data including status, creation date, and assignees
10
+ - **Full Comment History**: Access the entire conversation thread for context
11
+ - **Inline Images Support**: View images embedded in task descriptions and comments
12
+ - **Task Search**: Find relevant tasks using keywords
13
+ - **Todo Management**: List all your open tasks
15
14
 
16
- The implementation includes image handling for task descriptions, allowing LLMs to process both text content and visual information from ClickUp tasks.
15
+ The standout feature is the ability to retrieve individual tickets with their complete comment history and inline images, giving your AI assistant the full context of your work.
17
16
 
18
- ## Prerequisites
17
+ ## Limitations
19
18
 
20
- - Node.js (v16 or higher)
21
- - A ClickUp account with API access
22
- - ClickUp API key and Team ID
19
+ - **Todo Management**: The todo management feature is currently limited to 50 tasks since it would otherwise flood the context.
20
+ - **No List/Space Support**: This MCP does not currently support browsing ClickUp Lists or Spaces due to performance issues with large instances.
21
+ - **Image Limit**: The MCP processes only the 4 most recent images per task to prevent exceeding context limits. You can adjust this number by setting the `MAX_IMAGES` environment variable, though most AI tools have constraints that prevent using more than 4 images.
23
22
 
24
- ## Installation
23
+ ## Setup for Claude Desktop, Windsurf, or Cursor
25
24
 
26
- 1. Clone this repository
27
- 2. Install dependencies:
25
+ 1. **Prerequisites**:
26
+ - A ClickUp account with API access (Profile Icon > Settings > Apps > API Token ~ usually starts with pk_)
27
+ - Your ClickUp API key and Team ID (The ~7 digit number in the url when you are in the settings)
28
28
 
29
- ```bash
30
- npm install
31
- ```
29
+ 2. **Configuration**:
30
+ Add the following to your MCP configuration file:
32
31
 
33
- 3. Build the TypeScript code:
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "clickup": {
36
+ "command": "npx",
37
+ "args": [
38
+ "-y",
39
+ "@hauptsache.net/clickup-mcp"
40
+ ],
41
+ "env": {
42
+ "CLICKUP_API_KEY": "your_api_key",
43
+ "CLICKUP_TEAM_ID": "your_team_id"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
34
49
 
35
- ```bash
36
- npm run build
37
- ```
50
+ Replace `your_api_key` and `your_team_id` with your actual ClickUp credentials.
38
51
 
39
- ## Configuration
52
+ 3. **Connect Your AI Assistant**:
53
+ - **Claude Desktop**: Add this configuration in Settings > MCPs
54
+ - **Windsurf**: Add to your MCP configuration file
55
+ - **Cursor**: Configure through the MCP settings panel
40
56
 
41
- Set the following environment variables:
57
+ ## Using with Your AI Assistant
42
58
 
43
- - `CLICKUP_API_KEY`: Your ClickUp API key
44
- - `CLICKUP_TEAM_ID`: Your ClickUp team ID
59
+ Once connected, your AI assistant can:
45
60
 
46
- You can set these variables in your environment or create a `.env` file at the root of the project.
61
+ 1. **View Task Details**:
62
+ Ask: "Show me details for task CU-123456"
47
63
 
48
- ## Usage
64
+ 2. **Search Tasks**:
65
+ Ask: "Find tasks related to login functionality"
49
66
 
50
- ### As an MCP Server
67
+ 3. **Check Your Todo List**:
68
+ Ask: "What tasks are assigned to me?"
51
69
 
52
- To use this as an MCP server with an LLM:
53
-
54
- ```bash
55
- npm start
56
- ```
57
-
58
- This will start the server using the standard input/output for communication, following the Model Context Protocol.
59
-
60
- ### CLI Usage
61
-
62
- For testing or manual usage, you can use the CLI interface:
63
-
64
- ```bash
65
- # List all available tools
66
- npm run cli
67
-
68
- # Get a task by ID
69
- npm run cli getTaskById id=abc1234
70
-
71
- # Search for tasks
72
- npm run cli searchTask terms="feature|bug|enhancement"
73
-
74
- # List open tasks for the current user
75
- npm run cli listTodo
76
- ```
77
-
78
- ## Available Tools
79
-
80
- ### getTaskById
81
-
82
- Retrieves a complete ClickUp task with its description, comments, and embedded images.
83
-
84
- Parameters:
85
- - `id`: The 7-9 character ClickUp task ID (without "#" or "CU-" prefix)
86
-
87
- ### searchTask
88
-
89
- Searches for tasks by name with support for multiple search terms (using OR logic).
90
-
91
- Parameters:
92
- - `terms`: Search terms separated by '|' (e.g., 'term1|term2|term3')
93
-
94
- ### listTodo
95
-
96
- Lists all open tasks assigned to the current user.
97
-
98
- Parameters: None
99
-
100
- ## Development
101
-
102
- For development with hot reloading:
103
-
104
- ```bash
105
- npm run dev
106
- ```
107
-
108
- Format code with Prettier:
109
-
110
- ```bash
111
- npm run prettier
112
- ```
113
-
114
- ## How It Works
115
-
116
- This MCP implementation:
117
-
118
- 1. Connects to the ClickUp API using your API key
119
- 2. Provides tools for LLMs to query and process ClickUp data
120
- 3. Handles image processing in Markdown descriptions
121
- 4. Returns structured data in a format optimized for LLM consumption
70
+ The AI will retrieve the information directly from ClickUp, including all text content, comments, and images, providing you with comprehensive assistance on your tasks.
122
71
 
123
72
  ## License
124
73
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  export declare const server: McpServer;
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgBpE,eAAO,MAAM,MAAM,WAGjB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBpE,eAAO,MAAM,MAAM,WAGjB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.server = void 0;
@@ -8,6 +9,7 @@ const clickup_text_1 = require("./clickup-text");
8
9
  const CONFIG = {
9
10
  apiKey: process.env.CLICKUP_API_KEY,
10
11
  teamId: process.env.CLICKUP_TEAM_ID,
12
+ maxImages: process.env.MAX_IMAGES ? parseInt(process.env.MAX_IMAGES) : 4,
11
13
  };
12
14
  if (!CONFIG.apiKey || !CONFIG.teamId) {
13
15
  throw new Error("Missing Clickup API key or team ID");
@@ -22,40 +24,59 @@ exports.server.tool("getTaskById", "Get a Clickup task with images and comments
22
24
  .string()
23
25
  .min(7)
24
26
  .max(9)
25
- .describe(`The 7-9 character ID of the task to get without a prefix like "#" or "CU-"`),
27
+ .describe(`The 7-9 character ID of the task to get without a prefix like "#", "CU-" or "https://app.clickup.com/t/"`),
26
28
  }, async ({ id }) => {
27
29
  const [content, comments] = await Promise.all([
28
30
  loadTaskContent(id),
29
31
  loadTaskComments(id),
30
32
  ]);
33
+ // Combine all content and limit the number of images
34
+ const allContent = [...content, ...comments];
35
+ const limitedContent = limitImages(allContent, CONFIG.maxImages);
31
36
  return {
32
- content: [...content, ...comments],
37
+ content: limitedContent,
33
38
  };
34
39
  });
40
+ /**
41
+ * Helper function to generate consistent task metadata
42
+ */
43
+ function generateTaskMetadata(task) {
44
+ const metadataLines = [
45
+ `task_id: ${task.id}`,
46
+ `name: ${task.name}`,
47
+ `status: ${task.status.status}`,
48
+ `date_created: ${new Date(+task.date_created)}`,
49
+ `date_updated: ${new Date(+task.date_updated)}`,
50
+ `creator: ${task.creator.username}`,
51
+ `list: ${task.list.name} (${task.list.id})`,
52
+ ];
53
+ // Add parent task information if it exists
54
+ if (typeof task.parent === "string") {
55
+ metadataLines.push(`parent_task_id: ${task.parent}`);
56
+ }
57
+ // Add child task information if it exists
58
+ if (task.subtasks && task.subtasks.length > 0) {
59
+ metadataLines.push(`child_task_ids: ${task.subtasks.map((st) => st.id).join(', ')}`);
60
+ }
61
+ return {
62
+ type: "text",
63
+ text: metadataLines.join("\n"),
64
+ };
65
+ }
35
66
  async function loadTaskContent(id) {
36
- const response = await fetch(`https://api.clickup.com/api/v2/task/${id}?include_markdown_description=true`, { headers: { Authorization: CONFIG.apiKey } });
67
+ const response = await fetch(`https://api.clickup.com/api/v2/task/${id}?include_markdown_description=true&include_subtasks=true`, { headers: { Authorization: CONFIG.apiKey } });
37
68
  const task = await response.json();
38
69
  const content = await (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments);
39
- // Create the task metadata block
40
- const taskMetadata = {
41
- type: "text",
42
- text: [
43
- `task_id: ${task.id}`,
44
- `name: ${task.name}`,
45
- `status: ${task.status.status}`,
46
- `date_created: ${new Date(+task.date_created)}`,
47
- `date_updated: ${new Date(+task.date_updated)}`,
48
- `creator: ${task.creator.username}`,
49
- `list: ${task.list.name} (${task.list.id})`,
50
- ].join("\n"),
51
- };
70
+ // Create the task metadata block using the helper function
71
+ const taskMetadata = generateTaskMetadata(task);
52
72
  return [taskMetadata, ...content];
53
73
  }
54
74
  async function loadTaskComments(id) {
55
75
  const response = await fetch(`https://api.clickup.com/api/v2/task/${id}/comment`, { headers: { Authorization: CONFIG.apiKey } });
56
76
  const comments = await response.json();
57
77
  return Promise.all(comments.comments
58
- .sort((a, b) => +a.date - +b.date)
78
+ // Sort comments by date, newest first to prioritize recent images
79
+ .sort((a, b) => +b.date - +a.date)
59
80
  .map(async (comment) => {
60
81
  // Create a header for the comment
61
82
  const commentHeader = {
@@ -79,6 +100,41 @@ async function loadTaskComments(id) {
79
100
  }
80
101
  }));
81
102
  }
103
+ /**
104
+ * Limits the number of images in the content array, replacing excess images with text placeholders
105
+ * Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
106
+ *
107
+ * @param content Array of content blocks that may contain images
108
+ * @param maxImages Maximum number of images to keep
109
+ * @returns Modified content array with limited images
110
+ */
111
+ function limitImages(content, maxImages) {
112
+ // Count how many images we have
113
+ const imageIndices = [];
114
+ // Find all image blocks
115
+ content.forEach((block, index) => {
116
+ if (block.type === "image") {
117
+ imageIndices.push(index);
118
+ }
119
+ });
120
+ // If we have fewer images than the limit, return the original content
121
+ if (imageIndices.length <= maxImages) {
122
+ return content;
123
+ }
124
+ // Determine which images to keep (the most recent ones)
125
+ // We want to keep the last 'maxImages' images
126
+ const imagesToRemove = imageIndices.slice(0, imageIndices.length - maxImages);
127
+ // Create a new content array with excess images replaced by text
128
+ return content.map((block, index) => {
129
+ if (block.type === "image" && imagesToRemove.includes(index)) {
130
+ return {
131
+ type: "text",
132
+ text: "[Image removed due to size limitations. Only the most recent images are shown.]",
133
+ };
134
+ }
135
+ return block;
136
+ });
137
+ }
82
138
  let cachedTasks = [];
83
139
  let lastTaskCacheUpdate = 0;
84
140
  exports.server.tool("searchTask", [
@@ -117,18 +173,7 @@ exports.server.tool("searchTask", [
117
173
  };
118
174
  }
119
175
  return {
120
- content: tasks.map((task) => ({
121
- type: "text",
122
- text: [
123
- `task_id: ${task.id}`,
124
- `name: ${task.name}`,
125
- `status: ${task.status.status}`,
126
- `date_created: ${new Date(+task.date_created)}`,
127
- `date_updated: ${new Date(+task.date_updated)}`,
128
- `creator: ${task.creator.username}`,
129
- `list: ${task.list.name} (${task.list.id})`,
130
- ].join("\n"),
131
- })),
176
+ content: tasks.map((task) => generateTaskMetadata(task)),
132
177
  };
133
178
  });
134
179
  exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}, async () => {
@@ -142,8 +187,8 @@ exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}
142
187
  const tasks = taskLists.flatMap((tl) => tl.tasks);
143
188
  // filter out closed tasks
144
189
  const openTasks = tasks
145
- .filter((task) => task.status.type !== "done")
146
- .slice(0, 100);
190
+ .filter((task) => task.status.type !== "done") // done is not closed but also not a todo
191
+ .slice(0, 50);
147
192
  if (openTasks.length === 0) {
148
193
  return {
149
194
  content: [
@@ -152,18 +197,7 @@ exports.server.tool("listTodo", "Lists all open tasks for the current user.", {}
152
197
  };
153
198
  }
154
199
  return {
155
- content: openTasks.map((task) => ({
156
- type: "text",
157
- text: [
158
- `task_id: ${task.id}`,
159
- `name: ${task.name}`,
160
- `status: ${task.status.status}`,
161
- `date_created: ${new Date(+task.date_created)}`,
162
- `date_updated: ${new Date(+task.date_updated)}`,
163
- `creator: ${task.creator.username}`,
164
- `list: ${task.list.name} (${task.list.id})`,
165
- ].join("\n"),
166
- })),
200
+ content: openTasks.map((task) => generateTaskMetadata(task)),
167
201
  };
168
202
  });
169
203
  // Only connect to the transport if this file is being run directly (not imported)
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@hauptsache.net/clickup-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A minimal implementation of a Model Context Protocol (MCP) server for ClickUp integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "bin": "dist/index.js",
7
8
  "files": [
8
9
  "dist",
9
10
  "README.md"
@@ -14,7 +15,8 @@
14
15
  "dev": "tsc -w & nodemon dist/index.js",
15
16
  "cli": "npx ts-node src/cli.ts",
16
17
  "prettier": "prettier --write src/**/*.ts",
17
- "prepublishOnly": "npm run build"
18
+ "prepublishOnly": "rm -r dist && npm run build",
19
+ "release": "npm run build && npm publish --access public && git add . && git commit -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git tag -a v$(node -p 'require(\"./package.json\").version') -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git push && git push --tags"
18
20
  },
19
21
  "keywords": [
20
22
  "clickup",