@taazkareem/clickup-mcp-server 0.8.5 → 0.9.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 +9 -17
- package/README.md +33 -38
- package/build/enhanced_server.js +262 -0
- package/build/index.js +9 -3
- package/build/license.js +172 -0
- package/build/middleware/auth.js +211 -0
- package/build/routes/auth.js +306 -0
- package/build/schemas/member.js +13 -0
- package/build/server.js +14 -0
- package/build/server.log +15 -0
- package/build/services/auth/oauth2.js +236 -0
- package/build/services/auth/session.js +337 -0
- package/build/services/clickup/adapter.js +281 -0
- package/build/services/clickup/factory.js +339 -0
- package/build/services/clickup/task/task-attachments.js +20 -12
- package/build/services/clickup/task/task-comments.js +19 -9
- package/build/services/clickup/task/task-custom-fields.js +23 -13
- package/build/services/clickup/task/task-search.js +79 -71
- package/build/services/clickup/task/task-service.js +88 -9
- package/build/services/clickup/task/task-tags.js +25 -13
- package/build/sse_server.js +4 -4
- package/build/tools/documents.js +11 -4
- package/build/tools/health.js +23 -0
- package/build/utils/schema-compatibility.js +222 -0
- package/build/utils/universal-schema-compatibility.js +171 -0
- package/build/virtual-sdk/generator.js +53 -0
- package/build/virtual-sdk/registry.js +45 -0
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright (c) 2024-2025 Taaz Kareem
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All Rights Reserved
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
of
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
This software and associated documentation files (the "Software") are the proprietary
|
|
6
|
+
property of Talib Kareem. Unauthorized copying, modification, distribution, or use of
|
|
7
|
+
this Software, via any medium, is strictly prohibited without express written permission
|
|
8
|
+
from the copyright holder.
|
|
11
9
|
|
|
12
|
-
The
|
|
13
|
-
|
|
10
|
+
The Software is provided to authorized users only. Access is granted exclusively through
|
|
11
|
+
official channels and requires a valid license.
|
|
14
12
|
|
|
15
|
-
|
|
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.
|
|
13
|
+
For licensing inquiries, please visit: https://polar.sh/taazkareem
|
package/README.md
CHANGED
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
<img src="assets/images/clickup_mcp_server_social_image.png" alt="ClickUp MCP Server" width="100%">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/TaazKareem/clickup-mcp-server/stargazers)
|
|
5
|
-
[](https://github.com/TaazKareem/clickup-mcp-server/graphs/commit-activity)
|
|
3
|
+
# ClickUp MCP Server
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
> 🚀 **Status Update:** v0.8.5 is released with comprehensive natural language date parsing and critical bug fixes! Added 47+ natural language patterns (100% accuracy), extended time units (months/years), dynamic number support, fixed task assignment functionality, and resolved time tracking issues. See [Release Notes](release-notes.md) for full details.
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
5
|
+
> **🔒 Premium Version** - Requires license key from [Polar.sh](https://polar.sh/taazkareem)
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- Team ID from your ClickUp workspace URL
|
|
16
|
-
2. Choose either hosted installation (sends webhooks) or NPX installation (downloads to local path and installs dependencies)
|
|
17
|
-
3. Use natural language to manage your workspace!
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/TaazKareem/clickup-mcp-server/graphs/commit-activity)
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[](https://smithery.ai/server/@TaazKareem/clickup-mcp-server)
|
|
10
|
+
A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI applications. This server allows AI agents to interact with ClickUp tasks, spaces, lists, and folders through a standardized protocol.
|
|
22
11
|
|
|
23
|
-
|
|
12
|
+
## 🚀 Quick Start
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
### 1. Get Your License Key
|
|
26
15
|
|
|
27
|
-
[
|
|
28
|
-
[](https://github.com/TaazKareem/clickup-mcp-server/blob/main/package.json)
|
|
29
|
-
[](https://npmcharts.com/compare/@taazkareem/clickup-mcp-server?minimal=true)
|
|
16
|
+
Purchase a license at **[polar.sh/taazkareem](https://polar.sh/taazkareem)**
|
|
30
17
|
|
|
31
|
-
|
|
18
|
+
### 2. Add to Your MCP Configuration
|
|
32
19
|
|
|
33
20
|
```json
|
|
34
21
|
{
|
|
@@ -40,7 +27,8 @@ Add this entry to your client's MCP settings JSON file:
|
|
|
40
27
|
"@taazkareem/clickup-mcp-server@latest"
|
|
41
28
|
],
|
|
42
29
|
"env": {
|
|
43
|
-
"
|
|
30
|
+
"CLICKUP_MCP_LICENSE_KEY": "your-license-key-here",
|
|
31
|
+
"CLICKUP_API_KEY": "your-clickup-api-key",
|
|
44
32
|
"CLICKUP_TEAM_ID": "your-team-id",
|
|
45
33
|
"DOCUMENT_SUPPORT": "true"
|
|
46
34
|
}
|
|
@@ -49,11 +37,18 @@ Add this entry to your client's MCP settings JSON file:
|
|
|
49
37
|
}
|
|
50
38
|
```
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
### 3. Restart Your MCP Client
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
That's it! The server will validate your license key and start automatically.
|
|
43
|
+
|
|
44
|
+
**📖 [Full Installation Guide](INSTALLATION.md)** for detailed setup instructions.
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- **Node.js v18.0.0 or higher** (required for MCP SDK compatibility)
|
|
49
|
+
- Valid license key from [Polar.sh](https://polar.sh/taazkareem)
|
|
50
|
+
- ClickUp API key and Team ID
|
|
55
51
|
|
|
56
|
-
**Obs: if you don't pass "DOCUMENT_SUPPORT": "true", the default is false and document support will not be active.**
|
|
57
52
|
|
|
58
53
|
### Tool Filtering
|
|
59
54
|
|
|
@@ -307,30 +302,30 @@ The server provides clear error messages for:
|
|
|
307
302
|
The `LOG_LEVEL` environment variable can be specified to control the verbosity of server logs. Valid values are `trace`, `debug`, `info`, `warn`, and `error` (default).
|
|
308
303
|
This can be also be specified on the command line as, e.g. `--env LOG_LEVEL=info`.
|
|
309
304
|
|
|
310
|
-
## Support
|
|
305
|
+
## Premium Support
|
|
311
306
|
|
|
312
|
-
|
|
313
|
-
If you find this project useful, please consider supporting:
|
|
307
|
+
As a premium user, you have access to:
|
|
314
308
|
|
|
315
|
-
|
|
309
|
+
- **Priority Support**: Open issues directly in this private repository
|
|
310
|
+
- **Feature Requests**: Request new features and capabilities
|
|
311
|
+
- **Automatic Updates**: Pull the latest features and bug fixes
|
|
312
|
+
- **Direct Communication**: Get help from the maintainer
|
|
316
313
|
|
|
317
|
-
|
|
318
|
-
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="200" alt="Buy Me A Coffee">
|
|
319
|
-
</a>
|
|
314
|
+
To get support, [open an issue](https://github.com/TaazKareem/clickup-mcp-server/issues) in this repository.
|
|
320
315
|
|
|
321
316
|
## Acknowledgements
|
|
322
317
|
|
|
323
318
|
Special thanks to [ClickUp](https://clickup.com) for their excellent API and services that make this integration possible.
|
|
324
319
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
320
|
+
Thank you to all supporters who make continued development possible! 🙏
|
|
328
321
|
|
|
329
322
|
## License
|
|
330
323
|
|
|
331
|
-
[](LICENSE)
|
|
325
|
+
|
|
326
|
+
This project is licensed under a proprietary license - see the [LICENSE](LICENSE) file for details.
|
|
332
327
|
|
|
333
|
-
|
|
328
|
+
**All Rights Reserved** - Unauthorized copying, modification, distribution, or use is strictly prohibited.
|
|
334
329
|
|
|
335
330
|
## Disclaimer
|
|
336
331
|
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Enhanced ClickUp MCP Server with Simplified OAuth2 Support
|
|
6
|
+
*
|
|
7
|
+
* This module extends the existing SSE/HTTP server with simplified OAuth2 authentication
|
|
8
|
+
* capabilities while maintaining backward compatibility with API key authentication.
|
|
9
|
+
* Uses in-memory session management instead of database persistence.
|
|
10
|
+
*/
|
|
11
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
12
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
13
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import express from 'express';
|
|
15
|
+
import cookieParser from 'cookie-parser';
|
|
16
|
+
import { server, configureServer } from './server.js';
|
|
17
|
+
import configuration from './config.js';
|
|
18
|
+
import { SessionService } from './services/auth/session.js';
|
|
19
|
+
import { AuthRoutes } from './routes/auth.js';
|
|
20
|
+
import { AuthMiddleware } from './middleware/auth.js';
|
|
21
|
+
import { Logger } from './logger.js';
|
|
22
|
+
/**
|
|
23
|
+
* Enhanced server class with simplified OAuth2 support
|
|
24
|
+
*/
|
|
25
|
+
export class EnhancedServer {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.sessionService = null;
|
|
28
|
+
this.authRoutes = null;
|
|
29
|
+
this.authMiddleware = null;
|
|
30
|
+
this.transports = {
|
|
31
|
+
streamable: {},
|
|
32
|
+
sse: {},
|
|
33
|
+
};
|
|
34
|
+
this.app = express();
|
|
35
|
+
this.logger = new Logger('EnhancedServer');
|
|
36
|
+
this.setupMiddleware();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Setup Express middleware (simplified)
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
setupMiddleware() {
|
|
43
|
+
// Basic middleware
|
|
44
|
+
this.app.use(express.json());
|
|
45
|
+
this.app.use(cookieParser());
|
|
46
|
+
// CORS middleware for OAuth2 endpoints
|
|
47
|
+
this.app.use((req, res, next) => {
|
|
48
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
49
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
50
|
+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-session-id');
|
|
51
|
+
if (req.method === 'OPTIONS') {
|
|
52
|
+
res.sendStatus(200);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Initialize simplified OAuth2 services if enabled
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
async initializeOAuth2() {
|
|
64
|
+
if (!configuration.enableOAuth2) {
|
|
65
|
+
this.logger.info('OAuth2 disabled, skipping initialization');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.logger.info('Initializing simplified OAuth2 services');
|
|
69
|
+
try {
|
|
70
|
+
// Initialize simple session service (in-memory)
|
|
71
|
+
this.sessionService = new SessionService();
|
|
72
|
+
// Initialize authentication middleware
|
|
73
|
+
this.authMiddleware = new AuthMiddleware(this.sessionService);
|
|
74
|
+
// Initialize OAuth2 routes
|
|
75
|
+
this.authRoutes = new AuthRoutes({
|
|
76
|
+
clientId: configuration.oauth2ClientId,
|
|
77
|
+
clientSecret: configuration.oauth2ClientSecret,
|
|
78
|
+
redirectUri: configuration.oauth2RedirectUri
|
|
79
|
+
}, this.sessionService);
|
|
80
|
+
// Mount OAuth2 routes
|
|
81
|
+
this.app.use('/auth', this.authRoutes.getRouter());
|
|
82
|
+
this.logger.info('Simplified OAuth2 services initialized successfully');
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.logger.error('Failed to initialize OAuth2 services', { error: error.message });
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Setup MCP endpoints (existing functionality)
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
setupMCPEndpoints() {
|
|
94
|
+
// Configure the unified server first
|
|
95
|
+
configureServer();
|
|
96
|
+
// Streamable HTTP endpoint - handles POST requests for client-to-server communication
|
|
97
|
+
this.app.post('/mcp', async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
100
|
+
let transport;
|
|
101
|
+
if (sessionId && this.transports.streamable[sessionId]) {
|
|
102
|
+
transport = this.transports.streamable[sessionId];
|
|
103
|
+
}
|
|
104
|
+
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
105
|
+
transport = new StreamableHTTPServerTransport({
|
|
106
|
+
sessionIdGenerator: () => `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`,
|
|
107
|
+
onsessioninitialized: (sessionId) => {
|
|
108
|
+
this.transports.streamable[sessionId] = transport;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
transport.onclose = () => {
|
|
112
|
+
if (transport.sessionId) {
|
|
113
|
+
delete this.transports.streamable[transport.sessionId];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
await server.connect(transport);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
res.status(400).json({
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
error: {
|
|
122
|
+
code: -32000,
|
|
123
|
+
message: 'Bad Request: No valid session ID provided',
|
|
124
|
+
},
|
|
125
|
+
id: null,
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
await transport.handleRequest(req, res, req.body);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.logger.error('Error handling MCP request', { error });
|
|
133
|
+
if (!res.headersSent) {
|
|
134
|
+
res.status(500).json({
|
|
135
|
+
jsonrpc: '2.0',
|
|
136
|
+
error: {
|
|
137
|
+
code: -32603,
|
|
138
|
+
message: 'Internal server error',
|
|
139
|
+
},
|
|
140
|
+
id: null,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const handleSessionRequest = async (req, res) => {
|
|
146
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
147
|
+
if (!sessionId || !this.transports.streamable[sessionId]) {
|
|
148
|
+
res.status(400).send('Invalid or missing session ID');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const transport = this.transports.streamable[sessionId];
|
|
152
|
+
await transport.handleRequest(req, res);
|
|
153
|
+
};
|
|
154
|
+
this.app.get('/mcp', handleSessionRequest);
|
|
155
|
+
this.app.delete('/mcp', handleSessionRequest);
|
|
156
|
+
// Legacy SSE endpoints (for backwards compatibility)
|
|
157
|
+
this.app.get('/sse', async (req, res) => {
|
|
158
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
159
|
+
this.transports.sse[transport.sessionId] = transport;
|
|
160
|
+
this.logger.info('New SSE connection established', { sessionId: transport.sessionId });
|
|
161
|
+
res.on('close', () => {
|
|
162
|
+
delete this.transports.sse[transport.sessionId];
|
|
163
|
+
});
|
|
164
|
+
await server.connect(transport);
|
|
165
|
+
});
|
|
166
|
+
this.app.post('/messages', async (req, res) => {
|
|
167
|
+
const sessionId = req.query.sessionId;
|
|
168
|
+
const transport = this.transports.sse[sessionId];
|
|
169
|
+
if (transport) {
|
|
170
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
res.status(400).send('No transport found for sessionId');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Setup health check and info endpoints (simplified)
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
setupUtilityEndpoints() {
|
|
182
|
+
// Health check endpoint
|
|
183
|
+
this.app.get('/health', async (req, res) => {
|
|
184
|
+
const health = {
|
|
185
|
+
status: 'healthy',
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
version: process.env.npm_package_version || 'unknown',
|
|
188
|
+
oauth2Enabled: configuration.enableOAuth2
|
|
189
|
+
};
|
|
190
|
+
if (this.sessionService) {
|
|
191
|
+
health.sessions = {
|
|
192
|
+
active: this.sessionService.getSessionCount(),
|
|
193
|
+
type: 'in-memory'
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
res.json(health);
|
|
197
|
+
});
|
|
198
|
+
// Server info endpoint
|
|
199
|
+
this.app.get('/info', (req, res) => {
|
|
200
|
+
res.json({
|
|
201
|
+
name: 'ClickUp MCP Server',
|
|
202
|
+
version: process.env.npm_package_version || 'unknown',
|
|
203
|
+
oauth2Enabled: configuration.enableOAuth2,
|
|
204
|
+
sessionType: configuration.enableOAuth2 ? 'in-memory' : null,
|
|
205
|
+
endpoints: {
|
|
206
|
+
mcp: '/mcp',
|
|
207
|
+
sse: '/sse',
|
|
208
|
+
auth: configuration.enableOAuth2 ? '/auth' : null,
|
|
209
|
+
health: '/health'
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start the enhanced server
|
|
216
|
+
*/
|
|
217
|
+
async start() {
|
|
218
|
+
try {
|
|
219
|
+
this.logger.info('Starting enhanced ClickUp MCP server');
|
|
220
|
+
// Initialize OAuth2 if enabled
|
|
221
|
+
await this.initializeOAuth2();
|
|
222
|
+
// Setup all endpoints
|
|
223
|
+
this.setupMCPEndpoints();
|
|
224
|
+
this.setupUtilityEndpoints();
|
|
225
|
+
const PORT = Number(configuration.port ?? '3231');
|
|
226
|
+
// Start the server
|
|
227
|
+
this.app.listen(PORT, () => {
|
|
228
|
+
this.logger.info('Enhanced server started', { port: PORT });
|
|
229
|
+
console.log(`Enhanced ClickUp MCP Server started on http://127.0.0.1:${PORT}`);
|
|
230
|
+
console.log(`Streamable HTTP endpoint: http://127.0.0.1:${PORT}/mcp`);
|
|
231
|
+
console.log(`Legacy SSE endpoint: http://127.0.0.1:${PORT}/sse`);
|
|
232
|
+
if (configuration.enableOAuth2) {
|
|
233
|
+
console.log(`OAuth2 login: http://127.0.0.1:${PORT}/auth/login`);
|
|
234
|
+
console.log(`OAuth2 callback: http://127.0.0.1:${PORT}/auth/callback`);
|
|
235
|
+
}
|
|
236
|
+
console.log(`Health check: http://127.0.0.1:${PORT}/health`);
|
|
237
|
+
console.log(`Server info: http://127.0.0.1:${PORT}/info`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
this.logger.error('Failed to start enhanced server', { error: error.message });
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Shutdown the server gracefully (simplified)
|
|
247
|
+
*/
|
|
248
|
+
async shutdown() {
|
|
249
|
+
this.logger.info('Shutting down enhanced server');
|
|
250
|
+
if (this.sessionService) {
|
|
251
|
+
this.sessionService.destroy();
|
|
252
|
+
}
|
|
253
|
+
this.logger.info('Enhanced server shutdown complete');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Start the enhanced server
|
|
258
|
+
*/
|
|
259
|
+
export async function startEnhancedServer() {
|
|
260
|
+
const enhancedServer = new EnhancedServer();
|
|
261
|
+
await enhancedServer.start();
|
|
262
|
+
}
|
package/build/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
4
|
-
* SPDX-License-Identifier:
|
|
4
|
+
* SPDX-License-Identifier: Proprietary
|
|
5
5
|
*
|
|
6
|
-
* ClickUp MCP Server
|
|
6
|
+
* ClickUp MCP Server Premium
|
|
7
7
|
*
|
|
8
8
|
* This custom server implements the Model Context Protocol (MCP) specification to enable
|
|
9
9
|
* AI applications to interact with ClickUp workspaces. It provides a standardized
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* - Built-in rate limiting
|
|
21
21
|
* - Multiple transport options (STDIO, SSE, HTTP Streamable)
|
|
22
22
|
*
|
|
23
|
+
* PREMIUM VERSION - Requires valid license key from https://polar.sh/taazkareem
|
|
24
|
+
*
|
|
23
25
|
* For full documentation and usage examples, please refer to the README.md file.
|
|
24
26
|
*/
|
|
25
27
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -29,6 +31,7 @@ import config from './config.js';
|
|
|
29
31
|
import { dirname } from 'path';
|
|
30
32
|
import { fileURLToPath } from 'url';
|
|
31
33
|
import { startSSEServer } from './sse_server.js';
|
|
34
|
+
import { initializeLicense } from './license.js';
|
|
32
35
|
// Get directory name for module paths
|
|
33
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
37
|
// Handle uncaught exceptions
|
|
@@ -42,7 +45,7 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
42
45
|
process.exit(1);
|
|
43
46
|
});
|
|
44
47
|
async function startStdioServer() {
|
|
45
|
-
info('Starting ClickUp MCP Server...');
|
|
48
|
+
info('Starting ClickUp MCP Server Premium...');
|
|
46
49
|
// Log essential information about the environment
|
|
47
50
|
info('Server environment', {
|
|
48
51
|
pid: process.pid,
|
|
@@ -61,9 +64,12 @@ async function startStdioServer() {
|
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
63
66
|
* Application entry point that configures and starts the MCP server.
|
|
67
|
+
* Validates license key at startup (server starts regardless, but tools will fail if invalid).
|
|
64
68
|
*/
|
|
65
69
|
async function main() {
|
|
66
70
|
try {
|
|
71
|
+
// Validate license key (doesn't exit - just sets status for tool calls)
|
|
72
|
+
await initializeLicense();
|
|
67
73
|
if (config.enableSSE) {
|
|
68
74
|
// Start the new SSE server with HTTP Streamable support
|
|
69
75
|
startSSEServer();
|
package/build/license.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: Proprietary
|
|
4
|
+
*
|
|
5
|
+
* License validation for ClickUp MCP Server Premium
|
|
6
|
+
*
|
|
7
|
+
* Uses Polar.sh Customer Portal API for license key validation.
|
|
8
|
+
* This endpoint is public and doesn't require authentication.
|
|
9
|
+
*/
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
// Your Polar.sh organization ID
|
|
12
|
+
const POLAR_ORGANIZATION_ID = 'cd9f6f7c-5b51-4e5e-872b-c92dd9377bcd';
|
|
13
|
+
// Polar.sh API endpoint for license validation (public, no auth required)
|
|
14
|
+
const POLAR_VALIDATE_URL = 'https://api.polar.sh/v1/customer-portal/license-keys/validate';
|
|
15
|
+
// Purchase link for error messages
|
|
16
|
+
const PURCHASE_URL = 'https://buy.polar.sh/polar_cl_3xQojQLgzQXKCLzsxc49YfL6z8hzSBBqh9ivy1qZdwW';
|
|
17
|
+
// Global license status - validated once at startup
|
|
18
|
+
let licenseStatus = {
|
|
19
|
+
isValid: false,
|
|
20
|
+
status: 'missing',
|
|
21
|
+
errorMessage: 'License not yet validated'
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Get the current license status
|
|
25
|
+
*/
|
|
26
|
+
export function getLicenseStatus() {
|
|
27
|
+
return licenseStatus;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if the license is valid
|
|
31
|
+
*/
|
|
32
|
+
export function isLicenseValid() {
|
|
33
|
+
return licenseStatus.isValid;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the license error message for tool calls
|
|
37
|
+
*/
|
|
38
|
+
export function getLicenseErrorMessage() {
|
|
39
|
+
const baseMessage = `
|
|
40
|
+
❌ LICENSE KEY REQUIRED
|
|
41
|
+
|
|
42
|
+
This is a premium version of ClickUp MCP Server.
|
|
43
|
+
A valid license key is required to use these tools.
|
|
44
|
+
|
|
45
|
+
👉 Get your license key at:
|
|
46
|
+
${PURCHASE_URL}
|
|
47
|
+
|
|
48
|
+
After purchase, add the license key to your MCP configuration:
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
"env": {
|
|
52
|
+
"CLICKUP_MCP_LICENSE_KEY": "your-license-key-here",
|
|
53
|
+
"CLICKUP_API_KEY": "...",
|
|
54
|
+
"CLICKUP_TEAM_ID": "..."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Then restart your MCP client.
|
|
59
|
+
`;
|
|
60
|
+
if (licenseStatus.errorMessage) {
|
|
61
|
+
return `${baseMessage}\nError Details: ${licenseStatus.errorMessage}`;
|
|
62
|
+
}
|
|
63
|
+
return baseMessage;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validates a license key with Polar.sh
|
|
67
|
+
*/
|
|
68
|
+
async function validateLicenseKey(licenseKey) {
|
|
69
|
+
if (!licenseKey || licenseKey.trim() === '') {
|
|
70
|
+
return {
|
|
71
|
+
isValid: false,
|
|
72
|
+
status: 'missing',
|
|
73
|
+
errorMessage: 'No license key provided'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const response = await axios.post(POLAR_VALIDATE_URL, {
|
|
78
|
+
key: licenseKey.trim(),
|
|
79
|
+
organization_id: POLAR_ORGANIZATION_ID
|
|
80
|
+
}, {
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json'
|
|
83
|
+
},
|
|
84
|
+
timeout: 10000 // 10 second timeout
|
|
85
|
+
});
|
|
86
|
+
const data = response.data;
|
|
87
|
+
// Check license status
|
|
88
|
+
if (data.status === 'granted') {
|
|
89
|
+
// Check expiration
|
|
90
|
+
if (data.expires_at) {
|
|
91
|
+
const expiresAt = new Date(data.expires_at);
|
|
92
|
+
if (expiresAt < new Date()) {
|
|
93
|
+
return {
|
|
94
|
+
isValid: false,
|
|
95
|
+
status: 'expired',
|
|
96
|
+
expiresAt: data.expires_at,
|
|
97
|
+
errorMessage: 'License key has expired'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
isValid: true,
|
|
103
|
+
status: 'granted',
|
|
104
|
+
customerEmail: data.customer?.email,
|
|
105
|
+
expiresAt: data.expires_at
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
status: data.status,
|
|
111
|
+
errorMessage: `License key status: ${data.status}`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// Handle specific error cases
|
|
116
|
+
if (error.response) {
|
|
117
|
+
const status = error.response.status;
|
|
118
|
+
if (status === 404) {
|
|
119
|
+
return {
|
|
120
|
+
isValid: false,
|
|
121
|
+
status: 'error',
|
|
122
|
+
errorMessage: 'Invalid license key'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (status === 422) {
|
|
126
|
+
return {
|
|
127
|
+
isValid: false,
|
|
128
|
+
status: 'error',
|
|
129
|
+
errorMessage: 'Invalid license key format'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Network or other errors - fail open with warning to avoid blocking due to temporary issues
|
|
134
|
+
console.error('Warning: Could not validate license key due to network error. Allowing access.');
|
|
135
|
+
return {
|
|
136
|
+
isValid: true,
|
|
137
|
+
status: 'granted',
|
|
138
|
+
errorMessage: 'License validation skipped due to network error (allowing access)'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Initialize license validation at startup
|
|
144
|
+
* Call this once when the server starts - it doesn't exit, just sets the status
|
|
145
|
+
*/
|
|
146
|
+
export async function initializeLicense() {
|
|
147
|
+
const licenseKey = process.env.CLICKUP_MCP_LICENSE_KEY;
|
|
148
|
+
if (!licenseKey) {
|
|
149
|
+
licenseStatus = {
|
|
150
|
+
isValid: false,
|
|
151
|
+
status: 'missing',
|
|
152
|
+
errorMessage: 'CLICKUP_MCP_LICENSE_KEY environment variable not set'
|
|
153
|
+
};
|
|
154
|
+
console.error('⚠️ License key not provided. Tools will return license error.');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
licenseStatus = await validateLicenseKey(licenseKey);
|
|
158
|
+
if (licenseStatus.isValid) {
|
|
159
|
+
if (licenseStatus.customerEmail) {
|
|
160
|
+
console.error(`✅ License validated for: ${licenseStatus.customerEmail}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.error('✅ License validated successfully');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error(`⚠️ License validation failed: ${licenseStatus.errorMessage}`);
|
|
168
|
+
console.error(' Tools will return license error until a valid key is provided.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return licenseStatus;
|
|
172
|
+
}
|