@taazkareem/clickup-mcp-server 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  /**
2
3
  * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
4
  * SPDX-License-Identifier: MIT
@@ -0,0 +1,64 @@
1
+ import { Logger } from "./logger.js";
2
+ // Create a logger instance
3
+ const logger = new Logger('MCPTools');
4
+ /**
5
+ * Register a handler for a tool that may receive JSON string parameters
6
+ * This wrapper ensures that array and object parameters are properly parsed
7
+ *
8
+ * @param server MCP Server instance
9
+ * @param name Tool name
10
+ * @param handler Handler function
11
+ */
12
+ export function registerToolHandler(server, name, handler) {
13
+ // Create a wrapper handler that pre-processes parameters
14
+ const wrappedHandler = async (params) => {
15
+ logger.debug(`Processing parameters for tool ${name}`, { params });
16
+ try {
17
+ // Process the parameters before passing them to the actual handler
18
+ const processedParams = {};
19
+ // Process each parameter - try to parse strings that might be JSON
20
+ for (const [key, value] of Object.entries(params)) {
21
+ if (typeof value === 'string') {
22
+ try {
23
+ // Check if this might be a JSON array or object
24
+ if ((value.startsWith('[') && value.endsWith(']')) ||
25
+ (value.startsWith('{') && value.endsWith('}'))) {
26
+ try {
27
+ processedParams[key] = JSON.parse(value);
28
+ logger.debug(`Parsed JSON parameter: ${key}`, { original: value, parsed: processedParams[key] });
29
+ }
30
+ catch (parseError) {
31
+ // If parsing fails, use the original string
32
+ processedParams[key] = value;
33
+ logger.debug(`Failed to parse JSON for parameter: ${key}, using original`, { error: parseError.message });
34
+ }
35
+ }
36
+ else {
37
+ processedParams[key] = value;
38
+ }
39
+ }
40
+ catch (error) {
41
+ // If there's any error, use the original value
42
+ processedParams[key] = value;
43
+ logger.debug(`Error processing parameter: ${key}`, { error: error.message });
44
+ }
45
+ }
46
+ else {
47
+ // Non-string values are used as-is
48
+ processedParams[key] = value;
49
+ }
50
+ }
51
+ logger.debug(`Processed parameters for tool ${name}`, { processedParams });
52
+ // Call the original handler with processed parameters
53
+ return handler(processedParams);
54
+ }
55
+ catch (error) {
56
+ logger.error(`Error in wrapped handler for tool ${name}:`, { error: error.stack || error.message });
57
+ throw error;
58
+ }
59
+ };
60
+ // Use setRequestHandler to register the wrapped handler
61
+ logger.info(`Registering wrapped handler for tool: ${name}`);
62
+ // Override the tool's handler in the CallTool switch statement
63
+ // The server.ts file will use the switch case to call this handler
64
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Server State Management Module
3
+ *
4
+ * This module provides shared state management for the MCP server,
5
+ * particularly for controlling shutdown behavior and tracking busy states.
6
+ */
7
+ // State variables
8
+ let serverBusyState = false; // Tracks if server is doing critical work
9
+ let gracePeriodActive = false; // Tracks if we're in post-initialization grace period
10
+ let gracePeriodTimer = null;
11
+ export const GRACE_PERIOD_MS = 10000; // 10 second grace period after startup
12
+ /**
13
+ * Logging helper that avoids circular dependency
14
+ */
15
+ function safeLog(level, message, data) {
16
+ // Use console as a fallback to avoid circular dependency with logger
17
+ const timestamp = new Date().toISOString();
18
+ if (level === 'error') {
19
+ console.error(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
20
+ }
21
+ else if (level === 'debug' && process.env.DEBUG) {
22
+ console.debug(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
23
+ }
24
+ else if (level !== 'debug') {
25
+ console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
26
+ }
27
+ }
28
+ /**
29
+ * Set the server as busy doing critical work that shouldn't be interrupted
30
+ * @param busy Whether the server is currently busy with critical operations
31
+ */
32
+ export function setServerBusy(busy) {
33
+ serverBusyState = busy;
34
+ safeLog('debug', `Server busy state set to: ${busy}`);
35
+ }
36
+ /**
37
+ * Start grace period after initialization to prevent immediate shutdown
38
+ */
39
+ export function startGracePeriod() {
40
+ gracePeriodActive = true;
41
+ safeLog('debug', `Starting ${GRACE_PERIOD_MS}ms grace period to prevent premature shutdown`);
42
+ if (gracePeriodTimer) {
43
+ clearTimeout(gracePeriodTimer);
44
+ }
45
+ gracePeriodTimer = setTimeout(() => {
46
+ gracePeriodActive = false;
47
+ safeLog('debug', 'Grace period ended, server will now respond to shutdown signals');
48
+ gracePeriodTimer = null;
49
+ }, GRACE_PERIOD_MS);
50
+ }
51
+ /**
52
+ * Cancel the grace period if needed
53
+ */
54
+ export function cancelGracePeriod() {
55
+ if (gracePeriodTimer) {
56
+ clearTimeout(gracePeriodTimer);
57
+ gracePeriodTimer = null;
58
+ }
59
+ gracePeriodActive = false;
60
+ safeLog('debug', 'Grace period canceled, server will now respond to shutdown signals');
61
+ }
62
+ /**
63
+ * Check if the server should ignore shutdown signals
64
+ * @returns true if shutdown signals should be ignored
65
+ */
66
+ export function shouldIgnoreShutdown() {
67
+ // Ignore shutdown if explicitly configured via environment variable
68
+ if (process.env.FORCE_KEEP_ALIVE === 'true') {
69
+ return true;
70
+ }
71
+ // Ignore shutdown during the grace period after startup
72
+ if (gracePeriodActive) {
73
+ return true;
74
+ }
75
+ // Ignore shutdown if the server is doing critical work
76
+ if (serverBusyState) {
77
+ return true;
78
+ }
79
+ // Otherwise, allow normal shutdown
80
+ return false;
81
+ }
82
+ /**
83
+ * Check if grace period is currently active
84
+ */
85
+ export function isGracePeriodActive() {
86
+ return gracePeriodActive;
87
+ }
88
+ /**
89
+ * Check if server is currently in busy state
90
+ */
91
+ export function isServerBusy() {
92
+ return serverBusyState;
93
+ }
package/build/server.js CHANGED
@@ -19,7 +19,7 @@ const logger = new Logger('Server');
19
19
  const { workspace } = clickUpServices;
20
20
  export const server = new Server({
21
21
  name: "clickup-mcp-server",
22
- version: "0.6.1",
22
+ version: "0.6.2",
23
23
  }, {
24
24
  capabilities: {
25
25
  tools: {},
File without changes
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Alternative implementation for bulk tasks creation
3
+ */
4
+ import { handleCreateBulkTasks } from './task.js';
5
+ /**
6
+ * Alternative tool definition for bulk task creation to work around MCP validation issues
7
+ */
8
+ export const createTasksBulkTool = {
9
+ name: "create_tasks_bulk",
10
+ description: "Create multiple tasks in a list efficiently. You MUST provide:\n1. An array of tasks with required properties\n2. Either listId or listName to specify the target list",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ listId: {
15
+ type: "string",
16
+ description: "ID of list for new tasks (preferred). Use this instead of listName if you have it."
17
+ },
18
+ listName: {
19
+ type: "string",
20
+ description: "Name of list for new tasks. Only use if you don't have listId."
21
+ },
22
+ tasks: {
23
+ // Define minimally to avoid validation issues
24
+ description: "Array of tasks to create. Each task must have at least a name."
25
+ }
26
+ },
27
+ required: ["tasks"]
28
+ }
29
+ };
30
+ /**
31
+ * Handler for create_tasks_bulk tool
32
+ */
33
+ export async function handleCreateTasksBulk(parameters) {
34
+ // Use the same implementation as handleCreateBulkTasks
35
+ return handleCreateBulkTasks(parameters);
36
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Debug Tools
3
+ *
4
+ * This module provides tools for debugging and monitoring the ClickUp MCP server.
5
+ */
6
+ // In-memory log storage
7
+ const DEBUG_LOGS = [];
8
+ /**
9
+ * Log a message to the in-memory debug logs
10
+ */
11
+ export function logDebug(message, level = 'info') {
12
+ const timestamp = new Date().toISOString();
13
+ DEBUG_LOGS.push({ timestamp, message, level });
14
+ // Optional: Also print to console for development
15
+ if (level === 'error') {
16
+ console.error(`[${timestamp}] ERROR: ${message}`);
17
+ }
18
+ else if (level === 'warn') {
19
+ console.warn(`[${timestamp}] WARN: ${message}`);
20
+ }
21
+ else {
22
+ console.log(`[${timestamp}] INFO: ${message}`);
23
+ }
24
+ }
25
+ /**
26
+ * Tool definition for checking debug logs
27
+ */
28
+ export const checkDebugLogsTool = {
29
+ name: 'check_debug_logs',
30
+ description: 'Check the server debug logs collected since the server started.',
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {}
34
+ }
35
+ };
36
+ /**
37
+ * Tool definition for clearing debug logs
38
+ */
39
+ export const clearDebugLogsTool = {
40
+ name: 'clear_debug_logs',
41
+ description: 'Clear all server debug logs.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {}
45
+ }
46
+ };
47
+ /**
48
+ * Handler for the check_debug_logs tool
49
+ */
50
+ export function handleCheckDebugLogs() {
51
+ return {
52
+ content: [
53
+ {
54
+ type: "text",
55
+ text: DEBUG_LOGS.length > 0
56
+ ? DEBUG_LOGS.map(log => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`).join('\n')
57
+ : "No debug logs available."
58
+ }
59
+ ]
60
+ };
61
+ }
62
+ /**
63
+ * Handler for the clear_debug_logs tool
64
+ */
65
+ export function handleClearDebugLogs() {
66
+ const count = DEBUG_LOGS.length;
67
+ DEBUG_LOGS.length = 0;
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `Cleared ${count} debug log entries.`
73
+ }
74
+ ]
75
+ };
76
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Simple Logs Tool
3
+ *
4
+ * Provides a basic tool for reading server logs.
5
+ */
6
+ import { promises as fs } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ // Define the log file path - using __dirname to get absolute path
9
+ const LOG_FILE = join(dirname(__dirname), 'server.log');
10
+ // Tool definition
11
+ export const checkLogsTool = {
12
+ name: "check_logs",
13
+ description: "Check server logs with optional filtering by log level",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ level: {
18
+ type: "string",
19
+ enum: ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"],
20
+ description: "Filter logs by level"
21
+ },
22
+ limit: {
23
+ type: "number",
24
+ description: "Maximum number of log entries to return (default: 50)"
25
+ }
26
+ }
27
+ }
28
+ };
29
+ // Simple handler implementation using async/await with promises
30
+ export async function handleCheckLogs(params) {
31
+ try {
32
+ // Read log file using promises
33
+ const content = await fs.readFile(LOG_FILE, 'utf-8');
34
+ // Split into lines and filter out empty lines
35
+ let lines = content.split('\n').filter(Boolean);
36
+ // Apply level filtering if specified
37
+ if (params?.level) {
38
+ lines = lines.filter(line => line.includes(`${params.level}:`));
39
+ }
40
+ // Apply limit (default to 50)
41
+ const limit = params?.limit || 50;
42
+ const result = lines.slice(-limit);
43
+ // Return results
44
+ return {
45
+ logs: result.length > 0 ? result : ['No matching logs found.']
46
+ };
47
+ }
48
+ catch (error) {
49
+ // Simple error handling
50
+ if (error.code === 'ENOENT') {
51
+ return { logs: ['Log file not found.'] };
52
+ }
53
+ return { logs: [`Error reading logs: ${error.message}`] };
54
+ }
55
+ }