@alanse/clickup-multi-mcp-server 1.0.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/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +470 -0
- package/build/config.js +237 -0
- package/build/index.js +87 -0
- package/build/logger.js +163 -0
- package/build/middleware/security.js +231 -0
- package/build/server.js +288 -0
- package/build/services/clickup/base.js +432 -0
- package/build/services/clickup/bulk.js +180 -0
- package/build/services/clickup/document.js +159 -0
- package/build/services/clickup/folder.js +136 -0
- package/build/services/clickup/index.js +76 -0
- package/build/services/clickup/list.js +191 -0
- package/build/services/clickup/tag.js +239 -0
- package/build/services/clickup/task/index.js +32 -0
- package/build/services/clickup/task/task-attachments.js +105 -0
- package/build/services/clickup/task/task-comments.js +114 -0
- package/build/services/clickup/task/task-core.js +604 -0
- package/build/services/clickup/task/task-custom-fields.js +107 -0
- package/build/services/clickup/task/task-search.js +986 -0
- package/build/services/clickup/task/task-service.js +104 -0
- package/build/services/clickup/task/task-tags.js +113 -0
- package/build/services/clickup/time.js +244 -0
- package/build/services/clickup/types.js +33 -0
- package/build/services/clickup/workspace.js +397 -0
- package/build/services/shared.js +61 -0
- package/build/sse_server.js +277 -0
- package/build/tools/documents.js +489 -0
- package/build/tools/folder.js +331 -0
- package/build/tools/index.js +16 -0
- package/build/tools/list.js +428 -0
- package/build/tools/member.js +106 -0
- package/build/tools/tag.js +833 -0
- package/build/tools/task/attachments.js +357 -0
- package/build/tools/task/attachments.types.js +9 -0
- package/build/tools/task/bulk-operations.js +338 -0
- package/build/tools/task/handlers.js +919 -0
- package/build/tools/task/index.js +30 -0
- package/build/tools/task/main.js +233 -0
- package/build/tools/task/single-operations.js +469 -0
- package/build/tools/task/time-tracking.js +575 -0
- package/build/tools/task/utilities.js +310 -0
- package/build/tools/task/workspace-operations.js +258 -0
- package/build/tools/tool-enhancer.js +37 -0
- package/build/tools/utils.js +12 -0
- package/build/tools/workspace-helper.js +44 -0
- package/build/tools/workspace.js +73 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +248 -0
- package/build/utils/date-utils.js +542 -0
- package/build/utils/resolver-utils.js +135 -0
- package/build/utils/sponsor-service.js +93 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +77 -0
- package/smithery.yaml +23 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* SSE and HTTP Streamable Transport Server
|
|
6
|
+
*
|
|
7
|
+
* This module provides HTTP Streamable and legacy SSE transport support
|
|
8
|
+
* for the ClickUp MCP Server. It reuses the unified server configuration
|
|
9
|
+
* from server.ts to avoid code duplication.
|
|
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 https from 'https';
|
|
16
|
+
import http from 'http';
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import { server, configureServer } from './server.js';
|
|
19
|
+
import configuration from './config.js';
|
|
20
|
+
import { createOriginValidationMiddleware, createRateLimitMiddleware, createCorsMiddleware, createSecurityHeadersMiddleware, createSecurityLoggingMiddleware, createInputValidationMiddleware } from './middleware/security.js';
|
|
21
|
+
import { Logger } from './logger.js';
|
|
22
|
+
const app = express();
|
|
23
|
+
const logger = new Logger('SSEServer');
|
|
24
|
+
export function startSSEServer() {
|
|
25
|
+
// Configure the unified server first
|
|
26
|
+
configureServer();
|
|
27
|
+
// Apply security middleware (all are opt-in via environment variables)
|
|
28
|
+
logger.info('Configuring security middleware', {
|
|
29
|
+
securityFeatures: configuration.enableSecurityFeatures,
|
|
30
|
+
originValidation: configuration.enableOriginValidation,
|
|
31
|
+
rateLimit: configuration.enableRateLimit,
|
|
32
|
+
cors: configuration.enableCors
|
|
33
|
+
});
|
|
34
|
+
// Always apply input validation (reasonable defaults)
|
|
35
|
+
app.use(createInputValidationMiddleware());
|
|
36
|
+
// Apply optional security middleware
|
|
37
|
+
app.use(createSecurityLoggingMiddleware());
|
|
38
|
+
app.use(createSecurityHeadersMiddleware());
|
|
39
|
+
app.use(createCorsMiddleware());
|
|
40
|
+
app.use(createOriginValidationMiddleware());
|
|
41
|
+
app.use(createRateLimitMiddleware());
|
|
42
|
+
// Configure JSON parsing with configurable size limit
|
|
43
|
+
app.use(express.json({
|
|
44
|
+
limit: configuration.maxRequestSize,
|
|
45
|
+
verify: (req, res, buf) => {
|
|
46
|
+
// Additional validation can be added here if needed
|
|
47
|
+
if (buf.length === 0) {
|
|
48
|
+
logger.debug('Empty request body received');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
const transports = {
|
|
53
|
+
streamable: {},
|
|
54
|
+
sse: {},
|
|
55
|
+
};
|
|
56
|
+
// Streamable HTTP endpoint - handles POST requests for client-to-server communication
|
|
57
|
+
app.post('/mcp', async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
60
|
+
logger.debug('MCP request received', {
|
|
61
|
+
sessionId,
|
|
62
|
+
hasBody: !!req.body,
|
|
63
|
+
contentType: req.headers['content-type'],
|
|
64
|
+
origin: req.headers.origin
|
|
65
|
+
});
|
|
66
|
+
let transport;
|
|
67
|
+
if (sessionId && transports.streamable[sessionId]) {
|
|
68
|
+
transport = transports.streamable[sessionId];
|
|
69
|
+
}
|
|
70
|
+
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
71
|
+
transport = new StreamableHTTPServerTransport({
|
|
72
|
+
sessionIdGenerator: () => `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`,
|
|
73
|
+
onsessioninitialized: (sessionId) => {
|
|
74
|
+
transports.streamable[sessionId] = transport;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
transport.onclose = () => {
|
|
78
|
+
if (transport.sessionId) {
|
|
79
|
+
delete transports.streamable[transport.sessionId];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
await server.connect(transport);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
res.status(400).json({
|
|
86
|
+
jsonrpc: '2.0',
|
|
87
|
+
error: {
|
|
88
|
+
code: -32000,
|
|
89
|
+
message: 'Bad Request: No valid session ID provided',
|
|
90
|
+
},
|
|
91
|
+
id: null,
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await transport.handleRequest(req, res, req.body);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error('Error handling MCP request:', error);
|
|
99
|
+
if (!res.headersSent) {
|
|
100
|
+
res.status(500).json({
|
|
101
|
+
jsonrpc: '2.0',
|
|
102
|
+
error: {
|
|
103
|
+
code: -32603,
|
|
104
|
+
message: 'Internal server error',
|
|
105
|
+
},
|
|
106
|
+
id: null,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const handleSessionRequest = async (req, res) => {
|
|
112
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
113
|
+
if (!sessionId || !transports.streamable[sessionId]) {
|
|
114
|
+
res.status(400).send('Invalid or missing session ID');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const transport = transports.streamable[sessionId];
|
|
118
|
+
await transport.handleRequest(req, res);
|
|
119
|
+
};
|
|
120
|
+
app.get('/mcp', handleSessionRequest);
|
|
121
|
+
app.delete('/mcp', handleSessionRequest);
|
|
122
|
+
// Legacy SSE endpoints (for backwards compatibility)
|
|
123
|
+
app.get('/sse', async (req, res) => {
|
|
124
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
125
|
+
transports.sse[transport.sessionId] = transport;
|
|
126
|
+
logger.info('New SSE connection established', {
|
|
127
|
+
sessionId: transport.sessionId,
|
|
128
|
+
origin: req.headers.origin,
|
|
129
|
+
userAgent: req.headers['user-agent']
|
|
130
|
+
});
|
|
131
|
+
res.on('close', () => {
|
|
132
|
+
delete transports.sse[transport.sessionId];
|
|
133
|
+
});
|
|
134
|
+
await server.connect(transport);
|
|
135
|
+
});
|
|
136
|
+
app.post('/messages', async (req, res) => {
|
|
137
|
+
const sessionId = req.query.sessionId;
|
|
138
|
+
const transport = transports.sse[sessionId];
|
|
139
|
+
if (transport) {
|
|
140
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
res.status(400).send('No transport found for sessionId');
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// Health check endpoint
|
|
147
|
+
app.get('/health', (req, res) => {
|
|
148
|
+
res.json({
|
|
149
|
+
status: 'healthy',
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
version: '0.8.3',
|
|
152
|
+
security: {
|
|
153
|
+
featuresEnabled: configuration.enableSecurityFeatures,
|
|
154
|
+
originValidation: configuration.enableOriginValidation,
|
|
155
|
+
rateLimit: configuration.enableRateLimit,
|
|
156
|
+
cors: configuration.enableCors
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// Server creation and startup
|
|
161
|
+
const PORT = Number(configuration.port ?? '3231');
|
|
162
|
+
const HTTPS_PORT = Number(configuration.httpsPort ?? '3443');
|
|
163
|
+
// Function to create and start HTTP server
|
|
164
|
+
function startHttpServer() {
|
|
165
|
+
const httpServer = http.createServer(app);
|
|
166
|
+
httpServer.listen(PORT, '127.0.0.1', () => {
|
|
167
|
+
logger.info('ClickUp MCP Server (HTTP) started', {
|
|
168
|
+
port: PORT,
|
|
169
|
+
protocol: 'http',
|
|
170
|
+
endpoints: {
|
|
171
|
+
streamableHttp: `http://127.0.0.1:${PORT}/mcp`,
|
|
172
|
+
legacySSE: `http://127.0.0.1:${PORT}/sse`,
|
|
173
|
+
health: `http://127.0.0.1:${PORT}/health`
|
|
174
|
+
},
|
|
175
|
+
security: {
|
|
176
|
+
featuresEnabled: configuration.enableSecurityFeatures,
|
|
177
|
+
originValidation: configuration.enableOriginValidation,
|
|
178
|
+
rateLimit: configuration.enableRateLimit,
|
|
179
|
+
cors: configuration.enableCors,
|
|
180
|
+
httpsEnabled: configuration.enableHttps
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
console.log(`✅ ClickUp MCP Server started on http://127.0.0.1:${PORT}`);
|
|
184
|
+
console.log(`📡 Streamable HTTP endpoint: http://127.0.0.1:${PORT}/mcp`);
|
|
185
|
+
console.log(`🔄 Legacy SSE endpoint: http://127.0.0.1:${PORT}/sse`);
|
|
186
|
+
console.log(`❤️ Health check: http://127.0.0.1:${PORT}/health`);
|
|
187
|
+
if (configuration.enableHttps) {
|
|
188
|
+
console.log(`⚠️ HTTP server running alongside HTTPS - consider disabling HTTP in production`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return httpServer;
|
|
192
|
+
}
|
|
193
|
+
// Function to create and start HTTPS server
|
|
194
|
+
function startHttpsServer() {
|
|
195
|
+
if (!configuration.sslKeyPath || !configuration.sslCertPath) {
|
|
196
|
+
logger.error('HTTPS enabled but SSL certificate paths not provided', {
|
|
197
|
+
sslKeyPath: configuration.sslKeyPath,
|
|
198
|
+
sslCertPath: configuration.sslCertPath
|
|
199
|
+
});
|
|
200
|
+
console.log(`❌ HTTPS enabled but SSL_KEY_PATH and SSL_CERT_PATH not provided`);
|
|
201
|
+
console.log(` Set SSL_KEY_PATH and SSL_CERT_PATH environment variables`);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
// Check if certificate files exist
|
|
206
|
+
if (!fs.existsSync(configuration.sslKeyPath)) {
|
|
207
|
+
throw new Error(`SSL key file not found: ${configuration.sslKeyPath}`);
|
|
208
|
+
}
|
|
209
|
+
if (!fs.existsSync(configuration.sslCertPath)) {
|
|
210
|
+
throw new Error(`SSL certificate file not found: ${configuration.sslCertPath}`);
|
|
211
|
+
}
|
|
212
|
+
const httpsOptions = {
|
|
213
|
+
key: fs.readFileSync(configuration.sslKeyPath),
|
|
214
|
+
cert: fs.readFileSync(configuration.sslCertPath)
|
|
215
|
+
};
|
|
216
|
+
// Add CA certificate if provided
|
|
217
|
+
if (configuration.sslCaPath && fs.existsSync(configuration.sslCaPath)) {
|
|
218
|
+
httpsOptions.ca = fs.readFileSync(configuration.sslCaPath);
|
|
219
|
+
}
|
|
220
|
+
const httpsServer = https.createServer(httpsOptions, app);
|
|
221
|
+
httpsServer.listen(HTTPS_PORT, '127.0.0.1', () => {
|
|
222
|
+
logger.info('ClickUp MCP Server (HTTPS) started', {
|
|
223
|
+
port: HTTPS_PORT,
|
|
224
|
+
protocol: 'https',
|
|
225
|
+
endpoints: {
|
|
226
|
+
streamableHttp: `https://127.0.0.1:${HTTPS_PORT}/mcp`,
|
|
227
|
+
legacySSE: `https://127.0.0.1:${HTTPS_PORT}/sse`,
|
|
228
|
+
health: `https://127.0.0.1:${HTTPS_PORT}/health`
|
|
229
|
+
},
|
|
230
|
+
security: {
|
|
231
|
+
featuresEnabled: configuration.enableSecurityFeatures,
|
|
232
|
+
originValidation: configuration.enableOriginValidation,
|
|
233
|
+
rateLimit: configuration.enableRateLimit,
|
|
234
|
+
cors: configuration.enableCors,
|
|
235
|
+
httpsEnabled: true
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
console.log(`🔒 ClickUp MCP Server (HTTPS) started on https://127.0.0.1:${HTTPS_PORT}`);
|
|
239
|
+
console.log(`📡 Streamable HTTPS endpoint: https://127.0.0.1:${HTTPS_PORT}/mcp`);
|
|
240
|
+
console.log(`🔄 Legacy SSE HTTPS endpoint: https://127.0.0.1:${HTTPS_PORT}/sse`);
|
|
241
|
+
console.log(`❤️ Health check HTTPS: https://127.0.0.1:${HTTPS_PORT}/health`);
|
|
242
|
+
});
|
|
243
|
+
return httpsServer;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
logger.error('Failed to start HTTPS server', {
|
|
247
|
+
error: 'An error occurred while starting HTTPS server.',
|
|
248
|
+
sslKeyPath: 'REDACTED',
|
|
249
|
+
sslCertPath: 'REDACTED'
|
|
250
|
+
});
|
|
251
|
+
console.log(`❌ Failed to start HTTPS server. Please check the server configuration and logs for details.`);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Start servers based on configuration
|
|
256
|
+
const servers = [];
|
|
257
|
+
// Always start HTTP server (for backwards compatibility)
|
|
258
|
+
servers.push(startHttpServer());
|
|
259
|
+
// Start HTTPS server if enabled
|
|
260
|
+
if (configuration.enableHttps) {
|
|
261
|
+
const httpsServer = startHttpsServer();
|
|
262
|
+
if (httpsServer) {
|
|
263
|
+
servers.push(httpsServer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Security status logging
|
|
267
|
+
if (configuration.enableSecurityFeatures) {
|
|
268
|
+
console.log(`🔒 Security features enabled`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
console.log(`⚠️ Security features disabled (set ENABLE_SECURITY_FEATURES=true to enable)`);
|
|
272
|
+
}
|
|
273
|
+
if (!configuration.enableHttps) {
|
|
274
|
+
console.log(`⚠️ HTTPS disabled (set ENABLE_HTTPS=true with SSL certificates to enable)`);
|
|
275
|
+
}
|
|
276
|
+
return servers;
|
|
277
|
+
}
|