@hauptsache.net/clickup-mcp 1.1.1 → 1.3.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/LICENSE +21 -0
- package/README.md +20 -21
- package/dist/cli.js +8 -0
- package/dist/clickup-text.d.ts +5 -2
- package/dist/clickup-text.d.ts.map +1 -1
- package/dist/clickup-text.js +133 -61
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -13
- package/dist/shared/config.d.ts +1 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +1 -0
- package/dist/shared/image-processing.d.ts +13 -0
- package/dist/shared/image-processing.d.ts.map +1 -0
- package/dist/shared/image-processing.js +126 -0
- package/dist/shared/types.d.ts +7 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/utils.d.ts +24 -26
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +424 -101
- package/dist/tools/doc-tools.d.ts +4 -0
- package/dist/tools/doc-tools.d.ts.map +1 -0
- package/dist/tools/doc-tools.js +485 -0
- package/dist/tools/list-tools.d.ts.map +1 -1
- package/dist/tools/list-tools.js +26 -149
- package/dist/tools/search-tools.d.ts +1 -1
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +85 -62
- package/dist/tools/space-tools.d.ts.map +1 -1
- package/dist/tools/space-tools.js +153 -27
- package/dist/tools/task-tools.d.ts +1 -2
- package/dist/tools/task-tools.d.ts.map +1 -1
- package/dist/tools/task-tools.js +86 -405
- package/dist/tools/task-write-tools.d.ts +3 -0
- package/dist/tools/task-write-tools.d.ts.map +1 -0
- package/dist/tools/task-write-tools.js +375 -0
- package/dist/tools/time-tools.d.ts.map +1 -1
- package/dist/tools/time-tools.js +2 -30
- package/package.json +15 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Marco Pfeiffer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
# ClickUp MCP for AI Assistants
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Model Context Protocol (MCP) server enabling AI assistants to interact with ClickUp workspaces. Get complete task context with comments and images, search across projects, create and update tasks, collaborate through comments, and track time - all through natural language.
|
|
4
4
|
|
|
5
5
|
> **⚠️ Experimental Software Notice**
|
|
6
6
|
> This MCP is experimental and under active development. While implemented with great care to prevent data loss through append-only description updates and other safety measures, it cannot be guaranteed to be completely safe. Please use with caution, especially in production environments. Always test in a non-critical workspace first. 🧪
|
|
7
7
|
|
|
8
|
-
## Two Powerful Use Cases
|
|
9
|
-
|
|
10
|
-
### 🛠️ **For Developers & Agentic Coding**
|
|
11
|
-
Originally built to supercharge coding sessions by providing complete task context to AI coding assistants:
|
|
12
|
-
|
|
13
|
-
- **Instant Requirements**: *"Get task CU-abc123"* → AI gets full requirements, acceptance criteria, comments, and images
|
|
14
|
-
- **Complete Context**: Pass entire task histories to Claude Code, Windsurf, or Cursor for informed development
|
|
15
|
-
- **Visual Specifications**: Include embedded wireframes, mockups, and screenshots in your coding context
|
|
16
|
-
- **Implementation History**: Access all previous discussions and decisions for better code alignment
|
|
17
|
-
|
|
18
|
-
### 📋 **For Project Management & Productivity**
|
|
19
|
-
Extended capabilities for conversational ClickUp management and daily workflow optimization.
|
|
20
|
-
|
|
21
8
|
## What You Can Do
|
|
22
9
|
|
|
23
10
|
Turn natural language into powerful ClickUp actions:
|
|
@@ -49,6 +36,14 @@ Turn natural language into powerful ClickUp actions:
|
|
|
49
36
|
- *"What's the latest update on the database migration?"*
|
|
50
37
|
- *"Add a comment to the design task about the new wireframes"*
|
|
51
38
|
|
|
39
|
+
**Document Management:**
|
|
40
|
+
- *"Find documents about job posting in hauptsache.net space"*
|
|
41
|
+
- *"Search for API documentation across all spaces"*
|
|
42
|
+
- *"Read the API documentation in the development space"*
|
|
43
|
+
- *"Create a new requirements document for the mobile app project"*
|
|
44
|
+
- *"Update the meeting notes with today's decisions"*
|
|
45
|
+
- *"What documents are in the product strategy space?"*
|
|
46
|
+
|
|
52
47
|
## Key Features
|
|
53
48
|
|
|
54
49
|
### 🔍 **Intelligent Search**
|
|
@@ -60,6 +55,7 @@ Turn natural language into powerful ClickUp actions:
|
|
|
60
55
|
- Full comment histories and team discussions
|
|
61
56
|
- Task descriptions with embedded images
|
|
62
57
|
- List descriptions and project guidelines
|
|
58
|
+
- Document content with page navigation
|
|
63
59
|
- Access to complete task history and decisions
|
|
64
60
|
|
|
65
61
|
### ⏱️ **Time Tracking**
|
|
@@ -67,8 +63,9 @@ Turn natural language into powerful ClickUp actions:
|
|
|
67
63
|
- View historical time logs and entries
|
|
68
64
|
- Query time entries by task or date range
|
|
69
65
|
|
|
70
|
-
### 📋 **Task Management**
|
|
66
|
+
### 📋 **Task & Document Management**
|
|
71
67
|
- Create and update tasks with markdown descriptions
|
|
68
|
+
- Create, read, and update documents and pages
|
|
72
69
|
- Add comments and collaborate with team members
|
|
73
70
|
- Manage priorities, due dates, assignees, and tags
|
|
74
71
|
- Handle time estimates and custom field values
|
|
@@ -92,8 +89,7 @@ Turn natural language into powerful ClickUp actions:
|
|
|
92
89
|
"clickup": {
|
|
93
90
|
"command": "npx",
|
|
94
91
|
"args": [
|
|
95
|
-
"-
|
|
96
|
-
"@hauptsache.net/clickup-mcp@1"
|
|
92
|
+
"@hauptsache.net/clickup-mcp@latest"
|
|
97
93
|
],
|
|
98
94
|
"env": {
|
|
99
95
|
"CLICKUP_API_KEY": "your_api_key",
|
|
@@ -123,15 +119,17 @@ The ClickUp MCP supports three operational modes to balance functionality, secur
|
|
|
123
119
|
|------|:------------:|:----:|:-----:|-------------|
|
|
124
120
|
| `getTaskById` | ✅ | ✅ | ✅ | Get complete task details including comments, images, and metadata |
|
|
125
121
|
| `searchTasks` | ✅ | ✅ | ✅ | Find tasks by content, keywords, assignees, or project context |
|
|
126
|
-
| `
|
|
127
|
-
| `listLists` | ❌ | ✅ | ✅ | Browse lists and folders within spaces |
|
|
122
|
+
| `searchSpaces` | ❌ | ✅ | ✅ | Browse workspace structure, project organization, and documents |
|
|
128
123
|
| `getListInfo` | ❌ | ✅ | ✅ | Get list details and available statuses for task creation |
|
|
129
124
|
| `getTimeEntries` | ❌ | ✅ | ✅ | View time entries and analyze time spent across projects |
|
|
125
|
+
| `readDocument` | ❌ | ✅ | ✅ | Get document details, page structure, and content with navigation |
|
|
126
|
+
| `searchDocuments` | ❌ | ✅ | ✅ | Search documents by name and space with fuzzy matching and space filtering |
|
|
130
127
|
| `createTask` | ❌ | ❌ | ✅ | Create new tasks with full markdown support |
|
|
131
128
|
| `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.) with **SAFE APPEND-ONLY** descriptions |
|
|
132
129
|
| `updateListInfo` | ❌ | ❌ | ✅ | **SAFE APPEND-ONLY** updates to list descriptions (preserves existing content) |
|
|
133
130
|
| `addComment` | ❌ | ❌ | ✅ | Add comments to tasks for collaboration |
|
|
134
131
|
| `createTimeEntry` | ❌ | ❌ | ✅ | Log time entries for task tracking |
|
|
132
|
+
| `writeDocument` | ❌ | ❌ | ✅ | Universal document and page operations with smart document creation |
|
|
135
133
|
|
|
136
134
|
### Setting the Mode
|
|
137
135
|
|
|
@@ -161,6 +159,7 @@ This MCP server can be configured using environment variables:
|
|
|
161
159
|
- `CLICKUP_TEAM_ID`: (Required) Your ClickUp Team ID (formerly Workspace ID).
|
|
162
160
|
- `CLICKUP_MCP_MODE`: (Optional) Controls which tools are available. Options: `read-minimal`, `read`, `write` (default).
|
|
163
161
|
- `MAX_IMAGES`: (Optional) The maximum number of images to return for a task in `getTaskById`. Defaults to 4.
|
|
162
|
+
- `MAX_RESPONSE_SIZE_MB`: (Optional) The maximum response size in megabytes for `getTaskById`. Uses intelligent size budgeting to fit the most important images within the limit. Defaults to 1.
|
|
164
163
|
- `CLICKUP_PRIMARY_LANGUAGE`: (Optional) A hint for the primary language used in your ClickUp tasks (e.g., "de" for German, "en" for English). This helps the `searchTask` tool provide more tailored guidance in its description for multilingual searches.
|
|
165
164
|
- `LANG`: (Optional) If `CLICKUP_PRIMARY_LANGUAGE` is not set, the MCP will check this standard environment variable (e.g., "en_US.UTF-8", "de_DE") as a fallback to infer the primary language.
|
|
166
165
|
|
|
@@ -219,7 +218,7 @@ This ensures no existing content is ever lost while maintaining a clear audit tr
|
|
|
219
218
|
## Performance & Limitations
|
|
220
219
|
|
|
221
220
|
**Optimized for AI Workflows:**
|
|
222
|
-
- **Image Processing**:
|
|
221
|
+
- **Smart Image Processing**: Intelligent size budgeting prioritizes the most recent images while respecting both count (`MAX_IMAGES`, default: 4) and total response size limits (`MAX_RESPONSE_SIZE_MB`, default: 1MB)
|
|
223
222
|
- **Search Scope**: Searches within the most recent 1000-3000 tasks to prevent running into rate limits (exact number varies by endpoint)
|
|
224
223
|
- **Search Results**: Returns up to 50 most relevant matches to prevent flooding the agent with too many results
|
|
225
224
|
|
|
@@ -232,4 +231,4 @@ These limitations ensure reliable performance while covering the most common use
|
|
|
232
231
|
|
|
233
232
|
## License
|
|
234
233
|
|
|
235
|
-
|
|
234
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -7,8 +7,15 @@ async function main() {
|
|
|
7
7
|
// Wait for server initialization to complete
|
|
8
8
|
const server = await index_1.serverPromise;
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
|
+
// Special command to show instructions
|
|
11
|
+
if (args.length === 1 && args[0] === 'instructions') {
|
|
12
|
+
console.log("Server Instructions:");
|
|
13
|
+
console.log(server.server._instructions || "No instructions configured");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
10
16
|
if (args.length < 1) {
|
|
11
17
|
console.error("Usage: npm run cli <tool-name> [param1=value1 param2=value2 ...]");
|
|
18
|
+
console.error(" npm run cli instructions");
|
|
12
19
|
console.error("\nAvailable tools:");
|
|
13
20
|
// @ts-ignore - Accessing private property for testing purposes
|
|
14
21
|
const tools = server._registeredTools;
|
|
@@ -90,6 +97,7 @@ async function main() {
|
|
|
90
97
|
// Call the tool's callback function
|
|
91
98
|
const result = await tool.callback(params);
|
|
92
99
|
console.dir(result.content);
|
|
100
|
+
process.exit(0);
|
|
93
101
|
}
|
|
94
102
|
catch (error) {
|
|
95
103
|
if (error instanceof Error) {
|
package/dist/clickup-text.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types";
|
|
2
|
+
import { ImageMetadataBlock } from "./shared/types";
|
|
2
3
|
/**
|
|
3
4
|
* Represents a ClickUp text item which can be plain text or an image
|
|
4
5
|
*/
|
|
@@ -24,6 +25,8 @@ export interface ClickUpTextItem {
|
|
|
24
25
|
*/
|
|
25
26
|
export interface ClickUpAttachment {
|
|
26
27
|
thumbnail_large?: string;
|
|
28
|
+
thumbnail_medium?: string;
|
|
29
|
+
thumbnail_small?: string;
|
|
27
30
|
url: string;
|
|
28
31
|
[key: string]: any;
|
|
29
32
|
}
|
|
@@ -34,12 +37,12 @@ export interface ClickUpAttachment {
|
|
|
34
37
|
* @param textItems Array of text items from ClickUp API
|
|
35
38
|
* @returns Promise resolving to an array of content blocks (text and images)
|
|
36
39
|
*/
|
|
37
|
-
export declare function processClickUpText(textItems: ClickUpTextItem[]): Promise<CallToolResult["content"]>;
|
|
40
|
+
export declare function processClickUpText(textItems: ClickUpTextItem[]): Promise<(CallToolResult["content"][number] | ImageMetadataBlock)[]>;
|
|
38
41
|
/**
|
|
39
42
|
* Splits markdown text at image references and converts them to image blocks
|
|
40
43
|
* @param markdownText The markdown text to process
|
|
41
44
|
* @param attachments Array of attachments from the Clickup API
|
|
42
45
|
* @returns Array of content blocks (text and images)
|
|
43
46
|
*/
|
|
44
|
-
export declare function processClickUpMarkdown(markdownText: string, attachments: ClickUpAttachment[]):
|
|
47
|
+
export declare function processClickUpMarkdown(markdownText: string, attachments: ClickUpAttachment[] | null | undefined): (CallToolResult["content"][number] | ImageMetadataBlock)[];
|
|
45
48
|
//# sourceMappingURL=clickup-text.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AA4BD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,eAAe,EAAE,GAC3B,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAiErE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAyG5D"}
|
package/dist/clickup-text.js
CHANGED
|
@@ -2,7 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.processClickUpText = processClickUpText;
|
|
4
4
|
exports.processClickUpMarkdown = processClickUpMarkdown;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Extract thumbnail URLs from data-attachment attribute JSON
|
|
7
|
+
* ClickUp API sometimes has broken thumbnail URLs, but data-attachment contains working ones
|
|
8
|
+
*/
|
|
9
|
+
function extractThumbnailsFromDataAttachment(attributes) {
|
|
10
|
+
if (!attributes || !attributes['data-attachment']) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const attachmentData = JSON.parse(attributes['data-attachment']);
|
|
15
|
+
return {
|
|
16
|
+
thumbnail_large: attachmentData.thumbnail_large,
|
|
17
|
+
thumbnail_medium: attachmentData.thumbnail_medium,
|
|
18
|
+
thumbnail_small: attachmentData.thumbnail_small,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error('Error parsing data-attachment:', error);
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
27
|
* Process an array of ClickUp text items into a structured content format
|
|
8
28
|
* that includes both text and images in their original sequence
|
|
@@ -16,23 +36,37 @@ async function processClickUpText(textItems) {
|
|
|
16
36
|
for (let i = 0; i < textItems.length; i++) {
|
|
17
37
|
const item = textItems[i];
|
|
18
38
|
// Handle image items
|
|
19
|
-
if (item.type === "image" && item.image && item.image.
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
if (item.type === "image" && item.image && item.image.url) {
|
|
40
|
+
// Add image URL reference inline to current text block
|
|
41
|
+
const imageFileName = item.image.name || item.image.title || "image";
|
|
42
|
+
currentTextBlock += `\nImage: ${imageFileName} - ${item.image.url}`;
|
|
43
|
+
// Get working thumbnail URLs from data-attachment if available
|
|
44
|
+
const extractedThumbnails = extractThumbnailsFromDataAttachment(item.attributes);
|
|
45
|
+
// Determine best thumbnail URLs (prefer extracted over API thumbnails)
|
|
46
|
+
const thumbnail_large = extractedThumbnails.thumbnail_large || item.image.thumbnail_large;
|
|
47
|
+
const thumbnail_medium = extractedThumbnails.thumbnail_medium || item.image.thumbnail_medium;
|
|
48
|
+
const thumbnail_small = extractedThumbnails.thumbnail_small || item.image.thumbnail_small;
|
|
49
|
+
// Only create image_metadata if we have at least one thumbnail (never use original image)
|
|
50
|
+
if (thumbnail_large || thumbnail_medium || thumbnail_small) {
|
|
51
|
+
// Push accumulated text (including image URL) as a text block
|
|
52
|
+
if (currentTextBlock.trim()) {
|
|
53
|
+
contentBlocks.push({
|
|
54
|
+
type: "text",
|
|
55
|
+
text: currentTextBlock.trim(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Reset current text block after pushing it
|
|
59
|
+
currentTextBlock = "";
|
|
60
|
+
// Create URLs array with largest to smallest preference, filter out undefined
|
|
61
|
+
const urls = [thumbnail_large, thumbnail_medium, thumbnail_small].filter(Boolean);
|
|
62
|
+
// Add image_metadata block for lazy loading
|
|
63
|
+
contentBlocks.push({
|
|
64
|
+
type: "image_metadata",
|
|
65
|
+
urls: urls,
|
|
66
|
+
alt: item.text || imageFileName,
|
|
67
|
+
});
|
|
35
68
|
}
|
|
69
|
+
// If no thumbnails, just treat as a file reference (already added to currentTextBlock)
|
|
36
70
|
}
|
|
37
71
|
// Handle text items
|
|
38
72
|
else if (typeof item.text === "string") {
|
|
@@ -45,12 +79,12 @@ async function processClickUpText(textItems) {
|
|
|
45
79
|
}
|
|
46
80
|
// Add any remaining text
|
|
47
81
|
if (currentTextBlock.trim()) {
|
|
48
|
-
contentBlocks.push(
|
|
82
|
+
contentBlocks.push({
|
|
49
83
|
type: "text",
|
|
50
84
|
text: currentTextBlock.trim(),
|
|
51
|
-
})
|
|
85
|
+
});
|
|
52
86
|
}
|
|
53
|
-
return
|
|
87
|
+
return contentBlocks;
|
|
54
88
|
}
|
|
55
89
|
/**
|
|
56
90
|
* Splits markdown text at image references and converts them to image blocks
|
|
@@ -58,14 +92,14 @@ async function processClickUpText(textItems) {
|
|
|
58
92
|
* @param attachments Array of attachments from the Clickup API
|
|
59
93
|
* @returns Array of content blocks (text and images)
|
|
60
94
|
*/
|
|
61
|
-
|
|
95
|
+
function processClickUpMarkdown(markdownText, attachments) {
|
|
62
96
|
const contentBlocks = [];
|
|
63
97
|
let currentTextBlock = "";
|
|
64
|
-
// Create a map of attachment URLs to their
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
// Create a map of attachment URLs to their full info for easy lookup
|
|
99
|
+
const attachmentMap = new Map();
|
|
100
|
+
if (attachments && Array.isArray(attachments)) {
|
|
101
|
+
for (const attachment of attachments) {
|
|
102
|
+
attachmentMap.set(attachment.url, attachment);
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
105
|
// Regular expression to match markdown image syntax: 
|
|
@@ -77,59 +111,97 @@ async function processClickUpMarkdown(markdownText, attachments) {
|
|
|
77
111
|
// Add text before the image reference to the current text block
|
|
78
112
|
currentTextBlock += markdownText.substring(lastIndex, match.index);
|
|
79
113
|
// Check if this image URL exists in our attachments
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
const attachment = attachmentMap.get(imageUrl);
|
|
115
|
+
if (attachment) {
|
|
116
|
+
// Add image URL reference inline to current text block
|
|
117
|
+
const imageFileName = altText || "image";
|
|
118
|
+
currentTextBlock += `\nImage: ${imageFileName} - ${imageUrl}`;
|
|
119
|
+
// Only create image_metadata if we have at least one thumbnail (never use original image)
|
|
120
|
+
if (attachment.thumbnail_large || attachment.thumbnail_medium || attachment.thumbnail_small) {
|
|
121
|
+
// Push accumulated text (including image URL) as a text block
|
|
122
|
+
if (currentTextBlock.trim()) {
|
|
123
|
+
contentBlocks.push({
|
|
124
|
+
type: "text",
|
|
125
|
+
text: currentTextBlock.trim(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Reset current text block after pushing it
|
|
129
|
+
currentTextBlock = "";
|
|
130
|
+
// Create URLs array with largest to smallest preference, filter out undefined
|
|
131
|
+
const urls = [attachment.thumbnail_large, attachment.thumbnail_medium, attachment.thumbnail_small].filter(Boolean);
|
|
132
|
+
// Add image_metadata block for lazy loading
|
|
133
|
+
contentBlocks.push({
|
|
134
|
+
type: "image_metadata",
|
|
135
|
+
urls: urls,
|
|
136
|
+
alt: altText || imageFileName,
|
|
137
|
+
});
|
|
88
138
|
}
|
|
89
|
-
//
|
|
90
|
-
currentTextBlock = "";
|
|
91
|
-
// Add this image as an image block
|
|
92
|
-
contentBlocks.push(loadImageContentBlock(thumbnailUrl, {
|
|
93
|
-
type: "text",
|
|
94
|
-
text: fullMatch.trim(),
|
|
95
|
-
}));
|
|
139
|
+
// If no thumbnails, just treat as a file reference (already added to currentTextBlock)
|
|
96
140
|
}
|
|
97
141
|
else {
|
|
98
142
|
// If the image URL doesn't match any attachment, keep the original markdown in the current text block
|
|
99
143
|
currentTextBlock += fullMatch;
|
|
100
|
-
console.error(`Image URL ${imageUrl} not found in attachments`,
|
|
144
|
+
console.error(`Image URL ${imageUrl} not found in attachments`, attachmentMap);
|
|
101
145
|
}
|
|
102
146
|
lastIndex = match.index + fullMatch.length;
|
|
103
147
|
}
|
|
104
148
|
// Add any remaining text after the last image
|
|
105
149
|
currentTextBlock += markdownText.substring(lastIndex);
|
|
150
|
+
// Process non-image attachments that weren't referenced in markdown
|
|
151
|
+
const referencedUrls = new Set();
|
|
152
|
+
const imageMatches = markdownText.matchAll(/!\[([^\]]*)\]\(([^\)]+)\)/g);
|
|
153
|
+
for (const match of imageMatches) {
|
|
154
|
+
referencedUrls.add(match[2]);
|
|
155
|
+
}
|
|
156
|
+
// Add non-image files inline to the current text block
|
|
157
|
+
if (attachments && Array.isArray(attachments)) {
|
|
158
|
+
for (const attachment of attachments) {
|
|
159
|
+
if (!referencedUrls.has(attachment.url)) {
|
|
160
|
+
// Determine if this is an image based on URL or type
|
|
161
|
+
const isImage = attachment.thumbnail_large ||
|
|
162
|
+
/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(attachment.url);
|
|
163
|
+
if (!isImage) {
|
|
164
|
+
// This is a non-image file - add inline to current text block
|
|
165
|
+
const fileName = extractFileNameFromUrl(attachment.url) || "file";
|
|
166
|
+
const fileType = extractFileTypeFromUrl(attachment.url);
|
|
167
|
+
const fileTypeText = fileType ? ` (${fileType.toUpperCase()})` : "";
|
|
168
|
+
currentTextBlock += `\nFile: ${fileName}${fileTypeText} - ${attachment.url}`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Add any remaining text (including file references) as final text block
|
|
106
174
|
if (currentTextBlock.trim()) {
|
|
107
|
-
contentBlocks.push(
|
|
175
|
+
contentBlocks.push({
|
|
108
176
|
type: "text",
|
|
109
177
|
text: currentTextBlock.trim(),
|
|
110
|
-
})
|
|
178
|
+
});
|
|
111
179
|
}
|
|
112
|
-
return
|
|
180
|
+
return contentBlocks;
|
|
113
181
|
}
|
|
114
182
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* @param url URL of the image to load
|
|
118
|
-
* @param fallback Fallback content if image loading fails
|
|
119
|
-
* @returns Promise resolving to a content block (either image or fallback text)
|
|
183
|
+
* Extract filename from URL
|
|
120
184
|
*/
|
|
121
|
-
|
|
185
|
+
function extractFileNameFromUrl(url) {
|
|
122
186
|
try {
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
mimeType: response.headers.get("Content-Type") || "image/png",
|
|
128
|
-
data: buffer_1.Buffer.from(imageBuffer).toString("base64"),
|
|
129
|
-
};
|
|
187
|
+
const urlObj = new URL(url);
|
|
188
|
+
const pathname = urlObj.pathname;
|
|
189
|
+
const filename = pathname.split('/').pop();
|
|
190
|
+
return filename && filename !== '' ? filename : null;
|
|
130
191
|
}
|
|
131
|
-
catch
|
|
132
|
-
|
|
133
|
-
return fallback;
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
134
194
|
}
|
|
135
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Extract file extension from URL
|
|
198
|
+
*/
|
|
199
|
+
function extractFileTypeFromUrl(url) {
|
|
200
|
+
const filename = extractFileNameFromUrl(url);
|
|
201
|
+
if (!filename)
|
|
202
|
+
return null;
|
|
203
|
+
const lastDot = filename.lastIndexOf('.');
|
|
204
|
+
if (lastDot === -1)
|
|
205
|
+
return null;
|
|
206
|
+
return filename.substring(lastDot + 1);
|
|
207
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
declare const server: McpServer;
|
|
4
3
|
declare const serverPromise: Promise<McpServer>;
|
|
5
|
-
export {
|
|
4
|
+
export { serverPromise };
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkFpE,QAAA,MAAM,aAAa,oBAAqB,CAAC;AAIzC,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,53 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.serverPromise =
|
|
4
|
+
exports.serverPromise = void 0;
|
|
5
5
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
6
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
7
|
const config_1 = require("./shared/config");
|
|
8
8
|
const utils_1 = require("./shared/utils");
|
|
9
9
|
// Import tool registration functions
|
|
10
10
|
const task_tools_1 = require("./tools/task-tools");
|
|
11
|
+
const task_write_tools_1 = require("./tools/task-write-tools");
|
|
11
12
|
const search_tools_1 = require("./tools/search-tools");
|
|
12
13
|
const space_tools_1 = require("./tools/space-tools");
|
|
13
14
|
const list_tools_1 = require("./tools/list-tools");
|
|
14
15
|
const time_tools_1 = require("./tools/time-tools");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
version: "1.0.0",
|
|
19
|
-
});
|
|
20
|
-
exports.server = server;
|
|
16
|
+
const doc_tools_1 = require("./tools/doc-tools");
|
|
17
|
+
// Create server variable that will be initialized later
|
|
18
|
+
let server;
|
|
21
19
|
// Register tools based on mode with user data for enhanced documentation
|
|
22
20
|
async function initializeServer() {
|
|
23
21
|
console.error(`Starting ClickUp MCP in ${config_1.CONFIG.mode} mode`);
|
|
24
|
-
// Fetch current user for enhanced tool documentation and API health check
|
|
25
|
-
const userData = await
|
|
22
|
+
// Fetch current user and spaces for enhanced tool documentation and API health check
|
|
23
|
+
const [userData, spacesIndex] = await Promise.all([
|
|
24
|
+
(0, utils_1.getCurrentUser)(),
|
|
25
|
+
(0, utils_1.getSpaceSearchIndex)()
|
|
26
|
+
]);
|
|
27
|
+
const spaces = spacesIndex._docs || [];
|
|
26
28
|
console.error(`Connected as: ${userData.user.username} (${userData.user.email})`);
|
|
29
|
+
// Filter out archived spaces and format as simple list
|
|
30
|
+
const activeSpaces = spaces.filter((s) => !s.archived);
|
|
31
|
+
const formattedSpaces = activeSpaces
|
|
32
|
+
.map((s) => `- ${s.name} (space_id: ${s.id})`)
|
|
33
|
+
.join('\n');
|
|
34
|
+
const instructions = [
|
|
35
|
+
`ClickUp is a Ticket system. It is used to track tasks, bugs, and other work items.`,
|
|
36
|
+
`Is you are asked for infos about projects or tasks, search for tasks or documents in ClickUp (this MCP) first.`,
|
|
37
|
+
`The following spaces/projects are available:`,
|
|
38
|
+
formattedSpaces
|
|
39
|
+
].join('\n');
|
|
40
|
+
console.error(`Pre-loaded ${activeSpaces.length} active spaces`);
|
|
41
|
+
// Create the MCP server with instructions
|
|
42
|
+
server = new mcp_js_1.McpServer({
|
|
43
|
+
name: "Clickup MCP",
|
|
44
|
+
version: require('../package.json').version,
|
|
45
|
+
}, {
|
|
46
|
+
instructions
|
|
47
|
+
});
|
|
27
48
|
if (config_1.CONFIG.mode === 'read-minimal') {
|
|
28
49
|
// Core task context tools for AI coding assistance
|
|
29
50
|
// Only getTaskById and searchTasks
|
|
30
51
|
(0, task_tools_1.registerTaskToolsRead)(server, userData);
|
|
31
|
-
(0, search_tools_1.registerSearchTools)(server);
|
|
52
|
+
(0, search_tools_1.registerSearchTools)(server, userData);
|
|
32
53
|
}
|
|
33
54
|
else if (config_1.CONFIG.mode === 'read') {
|
|
34
55
|
// All read-only tools
|
|
35
56
|
(0, task_tools_1.registerTaskToolsRead)(server, userData);
|
|
36
|
-
(0, search_tools_1.registerSearchTools)(server);
|
|
57
|
+
(0, search_tools_1.registerSearchTools)(server, userData);
|
|
37
58
|
(0, space_tools_1.registerSpaceTools)(server);
|
|
38
59
|
(0, list_tools_1.registerListToolsRead)(server);
|
|
39
60
|
(0, time_tools_1.registerTimeToolsRead)(server);
|
|
61
|
+
(0, doc_tools_1.registerDocumentToolsRead)(server);
|
|
40
62
|
}
|
|
41
63
|
else if (config_1.CONFIG.mode === 'write') {
|
|
42
64
|
// All tools (full functionality)
|
|
43
65
|
(0, task_tools_1.registerTaskToolsRead)(server, userData);
|
|
44
|
-
(0,
|
|
45
|
-
(0, search_tools_1.registerSearchTools)(server);
|
|
66
|
+
(0, task_write_tools_1.registerTaskToolsWrite)(server, userData);
|
|
67
|
+
(0, search_tools_1.registerSearchTools)(server, userData);
|
|
46
68
|
(0, space_tools_1.registerSpaceTools)(server);
|
|
47
69
|
(0, list_tools_1.registerListToolsRead)(server);
|
|
48
70
|
(0, list_tools_1.registerListToolsWrite)(server);
|
|
49
71
|
(0, time_tools_1.registerTimeToolsRead)(server);
|
|
50
72
|
(0, time_tools_1.registerTimeToolsWrite)(server);
|
|
73
|
+
(0, doc_tools_1.registerDocumentToolsRead)(server);
|
|
74
|
+
(0, doc_tools_1.registerDocumentToolsWrite)(server);
|
|
51
75
|
}
|
|
52
76
|
return server;
|
|
53
77
|
}
|
package/dist/shared/config.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,oBAA2D,CAAC;AAavF,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAUxD,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,oBAA2D,CAAC;AAavF,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAUxD,eAAO,MAAM,MAAM;;;;;;;CAOlB,CAAC"}
|
package/dist/shared/config.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.CONFIG = {
|
|
|
23
23
|
apiKey: process.env.CLICKUP_API_KEY,
|
|
24
24
|
teamId: process.env.CLICKUP_TEAM_ID,
|
|
25
25
|
maxImages: process.env.MAX_IMAGES ? parseInt(process.env.MAX_IMAGES) : 4,
|
|
26
|
+
maxResponseSizeMB: process.env.MAX_RESPONSE_SIZE_MB ? parseFloat(process.env.MAX_RESPONSE_SIZE_MB) : 1,
|
|
26
27
|
primaryLanguageHint: detectedLanguageHint, // Store the cleaned code directly
|
|
27
28
|
mode: mcpMode,
|
|
28
29
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContentBlock, ImageMetadataBlock } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Downloads images from image_metadata blocks and applies smart size/count limiting
|
|
4
|
+
* Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
|
|
5
|
+
* Uses intelligent size calculation accounting for text content
|
|
6
|
+
*
|
|
7
|
+
* @param content Array of content blocks that may contain image_metadata blocks
|
|
8
|
+
* @param maxImages Maximum number of images to keep (defaults to CONFIG.maxImages)
|
|
9
|
+
* @param maxSizeMB Maximum response size in MB (defaults to CONFIG.maxResponseSizeMB)
|
|
10
|
+
* @returns Promise resolving to content array with downloaded images or placeholders
|
|
11
|
+
*/
|
|
12
|
+
export declare function downloadImages(content: (ContentBlock | ImageMetadataBlock)[], maxImages?: number, maxSizeMB?: number): Promise<ContentBlock[]>;
|
|
13
|
+
//# sourceMappingURL=image-processing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-processing.d.ts","sourceRoot":"","sources":["../../src/shared/image-processing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAIzD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,CAAC,YAAY,GAAG,kBAAkB,CAAC,EAAE,EAAE,SAAS,GAAE,MAAyB,EAAE,SAAS,GAAE,MAAiC,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAwBhM"}
|