@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 CHANGED
@@ -1,21 +1,13 @@
1
- MIT License
1
+ Copyright (c) 2024-2025 Taaz Kareem
2
2
 
3
- Copyright (c) 2025 Talib Kareem
3
+ All Rights Reserved
4
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:
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 above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
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
- 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.
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
- ![Total Supporters](https://img.shields.io/badge/🏆%20Total%20Supporters-4-gold)
4
- [![GitHub Stars](https://img.shields.io/github/stars/TaazKareem/clickup-mcp-server?style=flat&logo=github)](https://github.com/TaazKareem/clickup-mcp-server/stargazers)
5
- [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg)](https://github.com/TaazKareem/clickup-mcp-server/graphs/commit-activity)
3
+ # ClickUp MCP Server
6
4
 
7
- 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.
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
- 1. Get your credentials:
14
- - ClickUp API key from [ClickUp Settings](https://app.clickup.com/settings/apps)
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](https://img.shields.io/badge/License-Proprietary-red.svg)](LICENSE)
8
+ [![Maintained](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/TaazKareem/clickup-mcp-server/graphs/commit-activity)
18
9
 
19
- ## Smithery Installation (Quick Start)
20
-
21
- [![smithery badge](https://smithery.ai/badge/@taazkareem/clickup-mcp-server)](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
- The server is hosted on [Smithery](https://smithery.ai/server/@taazkareem/clickup-mcp-server). There, you can preview the available tools or copy the commands to run on your specific client app.
12
+ ## 🚀 Quick Start
24
13
 
25
- ## NPX Installation
14
+ ### 1. Get Your License Key
26
15
 
27
- [![NPM Version](https://img.shields.io/npm/v/@taazkareem/clickup-mcp-server.svg?style=flat&logo=npm)](https://www.npmjs.com/package/@taazkareem/clickup-mcp-server)
28
- [![Dependency Status](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen)](https://github.com/TaazKareem/clickup-mcp-server/blob/main/package.json)
29
- [![NPM Downloads](https://img.shields.io/npm/dm/@taazkareem/clickup-mcp-server.svg?style=flat&logo=npm)](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
- Add this entry to your client's MCP settings JSON file:
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
- "CLICKUP_API_KEY": "your-api-key",
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
- Or use this npx command:
40
+ ### 3. Restart Your MCP Client
53
41
 
54
- `npx -y @taazkareem/clickup-mcp-server@latest --env CLICKUP_API_KEY=your-api-key --env CLICKUP_TEAM_ID=your-team-id`
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 the Developer
305
+ ## Premium Support
311
306
 
312
- When using this server, you may occasionally see a small sponsor message with a link to this repository included in tool responses. I hope you can support the project!
313
- If you find this project useful, please consider supporting:
307
+ As a premium user, you have access to:
314
308
 
315
- [![Sponsor TaazKareem](https://img.shields.io/badge/Sponsor-TaazKareem-orange?logo=github)](https://github.com/sponsors/TaazKareem)
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
- <a href="https://buymeacoffee.com/taazkareem">
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
- ## Contributing
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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
324
+ [![License](https://img.shields.io/badge/License-Proprietary-red.svg)](LICENSE)
325
+
326
+ This project is licensed under a proprietary license - see the [LICENSE](LICENSE) file for details.
332
327
 
333
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
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: MIT
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();
@@ -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
+ }