@taazkareem/clickup-mcp-server 0.4.74 → 0.5.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.
@@ -38,9 +38,9 @@ function buildUpdateData(params) {
38
38
  /**
39
39
  * Process a task identification validation, returning the task ID
40
40
  */
41
- async function getTaskId(taskId, taskName, listName) {
42
- validateTaskIdentification(taskId, taskName, listName);
43
- return await resolveTaskIdWithValidation(taskId, taskName, listName);
41
+ async function getTaskId(taskId, taskName, listName, customTaskId) {
42
+ validateTaskIdentification(taskId, taskName, listName, customTaskId);
43
+ return await resolveTaskIdWithValidation(taskId, taskName, listName, customTaskId);
44
44
  }
45
45
  /**
46
46
  * Process a list identification validation, returning the list ID
@@ -72,8 +72,8 @@ function buildTaskFilters(params) {
72
72
  */
73
73
  async function mapTaskIds(tasks) {
74
74
  return Promise.all(tasks.map(async (task) => {
75
- validateTaskIdentification(task.taskId, task.taskName, task.listName);
76
- return await resolveTaskIdWithValidation(task.taskId, task.taskName, task.listName);
75
+ validateTaskIdentification(task.taskId, task.taskName, task.listName, task.customTaskId);
76
+ return await resolveTaskIdWithValidation(task.taskId, task.taskName, task.listName, task.customTaskId);
77
77
  }));
78
78
  }
79
79
  //=============================================================================
@@ -129,7 +129,8 @@ export async function duplicateTaskHandler(params) {
129
129
  * Handler for getting a task
130
130
  */
131
131
  export async function getTaskHandler(params) {
132
- const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
132
+ // resolveTaskIdWithValidation now auto-detects whether taskId is a regular ID or custom ID
133
+ const taskId = await getTaskId(params.taskId, params.taskName, params.listName, params.customTaskId);
133
134
  return await taskService.getTask(taskId);
134
135
  }
135
136
  /**
@@ -155,6 +156,36 @@ export async function getTaskCommentsHandler(params) {
155
156
  const { start, startId } = params;
156
157
  return await taskService.getTaskComments(taskId, start, startId);
157
158
  }
159
+ /**
160
+ * Handler for creating a task comment
161
+ */
162
+ export async function createTaskCommentHandler(params) {
163
+ // Validate required parameters
164
+ if (!params.commentText) {
165
+ throw new Error('Comment text is required');
166
+ }
167
+ try {
168
+ // Resolve the task ID
169
+ const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
170
+ // Extract other parameters with defaults
171
+ const { commentText, notifyAll = false, assignee = null } = params;
172
+ // Create the comment
173
+ return await taskService.createTaskComment(taskId, commentText, notifyAll, assignee);
174
+ }
175
+ catch (error) {
176
+ // If this is a task lookup error, provide more helpful message
177
+ if (error.message?.includes('not found') || error.message?.includes('identify task')) {
178
+ if (params.taskName) {
179
+ throw new Error(`Could not find task "${params.taskName}" in list "${params.listName}"`);
180
+ }
181
+ else {
182
+ throw new Error(`Task with ID "${params.taskId}" not found`);
183
+ }
184
+ }
185
+ // Otherwise, rethrow the original error
186
+ throw error;
187
+ }
188
+ }
158
189
  //=============================================================================
159
190
  // BULK TASK OPERATIONS
160
191
  //=============================================================================
@@ -6,13 +6,15 @@
6
6
  // Re-export from main module
7
7
  export * from './main.js';
8
8
  // Re-export single task operation tools
9
- export { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool } from './single-operations.js';
9
+ export { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool } from './single-operations.js';
10
10
  // Re-export bulk task operation tools
11
11
  export { createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool } from './bulk-operations.js';
12
+ // Re-export attachment tool
13
+ export { attachTaskFileTool, handleAttachTaskFile } from './attachments.js';
12
14
  // Re-export handlers
13
15
  export {
14
16
  // Single task operation handlers
15
- createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler,
17
+ createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler, createTaskCommentHandler,
16
18
  // Bulk task operation handlers
17
19
  createBulkTasksHandler, updateBulkTasksHandler, moveBulkTasksHandler, deleteBulkTasksHandler } from './handlers.js';
18
20
  // Re-export utilities
@@ -6,10 +6,10 @@
6
6
  */
7
7
  import { sponsorService } from '../../utils/sponsor-service.js';
8
8
  // Import tool definitions
9
- import { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool } from './single-operations.js';
9
+ import { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool } from './single-operations.js';
10
10
  import { createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool } from './bulk-operations.js';
11
11
  // Import handlers
12
- import { createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler, createBulkTasksHandler, updateBulkTasksHandler, moveBulkTasksHandler, deleteBulkTasksHandler } from './handlers.js';
12
+ import { createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler, createTaskCommentHandler, createBulkTasksHandler, updateBulkTasksHandler, moveBulkTasksHandler, deleteBulkTasksHandler } from './handlers.js';
13
13
  //=============================================================================
14
14
  // HANDLER WRAPPER UTILITY
15
15
  //=============================================================================
@@ -47,6 +47,14 @@ export const handleGetTaskComments = createHandlerWrapper(getTaskCommentsHandler
47
47
  comments,
48
48
  count: comments.length
49
49
  }));
50
+ export const handleCreateTaskComment = createHandlerWrapper(createTaskCommentHandler, (comment) => ({
51
+ success: true,
52
+ message: "Comment added successfully",
53
+ comment: comment && typeof comment === 'object' ? comment : {
54
+ id: `generated-${Date.now()}`,
55
+ comment_text: typeof comment === 'string' ? comment : "Comment text unavailable"
56
+ }
57
+ }));
50
58
  //=============================================================================
51
59
  // BULK TASK OPERATIONS - HANDLER IMPLEMENTATIONS
52
60
  //=============================================================================
@@ -81,6 +89,7 @@ export const taskTools = [
81
89
  { definition: duplicateTaskTool, handler: handleDuplicateTask },
82
90
  { definition: deleteTaskTool, handler: handleDeleteTask },
83
91
  { definition: getTaskCommentsTool, handler: handleGetTaskComments },
92
+ { definition: createTaskCommentTool, handler: handleCreateTaskComment },
84
93
  // Bulk task operations
85
94
  { definition: createBulkTasksTool, handler: handleCreateBulkTasks },
86
95
  { definition: updateBulkTasksTool, handler: handleUpdateBulkTasks },
@@ -119,7 +119,7 @@ Notes:
119
119
  properties: {
120
120
  taskId: {
121
121
  type: "string",
122
- description: "ID of the task to update (preferred). Use this instead of taskName if you have it from a previous response."
122
+ description: "ID of the task to update (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
123
123
  },
124
124
  taskName: {
125
125
  type: "string",
@@ -181,7 +181,7 @@ Warning:
181
181
  properties: {
182
182
  taskId: {
183
183
  type: "string",
184
- description: "ID of the task to move (preferred). Use this instead of taskName if you have it."
184
+ description: "ID of the task to move (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
185
185
  },
186
186
  taskName: {
187
187
  type: "string",
@@ -228,7 +228,7 @@ Warning:
228
228
  properties: {
229
229
  taskId: {
230
230
  type: "string",
231
- description: "ID of task to duplicate (preferred). Use this instead of taskName if you have it."
231
+ description: "ID of task to duplicate (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
232
232
  },
233
233
  taskName: {
234
234
  type: "string",
@@ -258,20 +258,24 @@ export const getTaskTool = {
258
258
  description: `Purpose: Retrieve detailed information about a specific task.
259
259
 
260
260
  Valid Usage:
261
- 1. Use taskId alone (preferred)
261
+ 1. Use taskId alone (preferred) - works with both regular and custom IDs (like "DEV-1234")
262
262
  2. Use taskName + listName
263
+ 3. Use customTaskId for explicit custom ID lookup
263
264
 
264
265
  Requirements:
265
266
  - When using taskName, listName is REQUIRED
267
+ - When using customTaskId, listName is recommended for faster lookup
266
268
 
267
269
  Note:
268
- - Task names are only unique within a list, so the system needs to know which list to search in`,
270
+ - Task names are only unique within a list, so the system needs to know which list to search in
271
+ - Regular task IDs are always 9 characters long (e.g., "86b394eqa")
272
+ - Custom IDs have an uppercase prefix followed by a hyphen and number (e.g., "DEV-1234")`,
269
273
  inputSchema: {
270
274
  type: "object",
271
275
  properties: {
272
276
  taskId: {
273
277
  type: "string",
274
- description: "ID of task to retrieve (preferred). Use this instead of taskName if you have it."
278
+ description: "ID of task to retrieve (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234'). The system automatically detects the ID format."
275
279
  },
276
280
  taskName: {
277
281
  type: "string",
@@ -280,6 +284,10 @@ Note:
280
284
  listName: {
281
285
  type: "string",
282
286
  description: "Name of list containing the task. REQUIRED when using taskName."
287
+ },
288
+ customTaskId: {
289
+ type: "string",
290
+ description: "Custom task ID (e.g., 'DEV-1234'). Only use this if you want to explicitly force custom ID lookup. In most cases, you can just use taskId which auto-detects ID format."
283
291
  }
284
292
  },
285
293
  required: []
@@ -365,7 +373,7 @@ Warning:
365
373
  properties: {
366
374
  taskId: {
367
375
  type: "string",
368
- description: "ID of task to delete (preferred). Use this instead of taskName for safety."
376
+ description: "ID of task to delete (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
369
377
  },
370
378
  taskName: {
371
379
  type: "string",
@@ -398,7 +406,7 @@ Notes:
398
406
  properties: {
399
407
  taskId: {
400
408
  type: "string",
401
- description: "ID of task to retrieve comments for (preferred). Use this instead of taskName if you have it."
409
+ description: "ID of task to retrieve comments for (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
402
410
  },
403
411
  taskName: {
404
412
  type: "string",
@@ -419,3 +427,53 @@ Notes:
419
427
  }
420
428
  }
421
429
  };
430
+ /**
431
+ * Tool definition for creating a comment on a task
432
+ */
433
+ export const createTaskCommentTool = {
434
+ name: "create_task_comment",
435
+ description: `Purpose: Create a comment on a ClickUp task.
436
+
437
+ Valid Usage:
438
+ 1. Use taskId (preferred)
439
+ 2. Use taskName + listName
440
+
441
+ Requirements:
442
+ - EITHER taskId OR (taskName + listName) is REQUIRED
443
+ - commentText is REQUIRED
444
+
445
+ Notes:
446
+ - When using taskName, providing listName helps locate the correct task
447
+ - Set notifyAll to true to send notifications to all task assignees
448
+ - Use assignee to assign the comment to a specific user (optional)`,
449
+ inputSchema: {
450
+ type: "object",
451
+ properties: {
452
+ taskId: {
453
+ type: "string",
454
+ description: "ID of task to comment on (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
455
+ },
456
+ taskName: {
457
+ type: "string",
458
+ description: "Name of task to comment on. When using this parameter, you MUST also provide listName."
459
+ },
460
+ listName: {
461
+ type: "string",
462
+ description: "Name of list containing the task. REQUIRED when using taskName."
463
+ },
464
+ commentText: {
465
+ type: "string",
466
+ description: "REQUIRED: Text content of the comment to create."
467
+ },
468
+ notifyAll: {
469
+ type: "boolean",
470
+ description: "Whether to notify all assignees. Default is false."
471
+ },
472
+ assignee: {
473
+ type: "number",
474
+ description: "Optional user ID to assign the comment to."
475
+ }
476
+ },
477
+ required: ["commentText"]
478
+ }
479
+ };
@@ -32,14 +32,14 @@ export function formatTaskData(task, additional = {}) {
32
32
  //=============================================================================
33
33
  /**
34
34
  * Validates task identification parameters
35
- * Ensures either taskId is provided or both taskName and listName are provided
35
+ * Ensures either taskId, customTaskId, or both taskName and listName are provided
36
36
  */
37
- export function validateTaskIdentification(taskId, taskName, listName) {
38
- if (!taskId && !taskName) {
39
- throw new Error("Either taskId or taskName must be provided");
37
+ export function validateTaskIdentification(taskId, taskName, listName, customTaskId) {
38
+ if (!taskId && !taskName && !customTaskId) {
39
+ throw new Error("Either taskId, customTaskId, or taskName must be provided");
40
40
  }
41
- if (!taskId && taskName && !listName) {
42
- throw new Error("When using taskName, listName is required to locate the task");
41
+ if (!taskId && !customTaskId && taskName && !listName) {
42
+ throw new Error("listName is required when using taskName");
43
43
  }
44
44
  }
45
45
  /**
@@ -84,18 +84,79 @@ export function parseBulkOptions(rawOptions) {
84
84
  return rawOptions;
85
85
  }
86
86
  //=============================================================================
87
+ // ID DETECTION UTILITIES
88
+ //=============================================================================
89
+ /**
90
+ * Determines if an ID is a custom ID based on its format
91
+ * Custom IDs typically have an uppercase prefix followed by a hyphen and number (e.g., DEV-1234)
92
+ * Regular task IDs are always 9 characters long
93
+ *
94
+ * @param id The task ID to check
95
+ * @returns True if the ID appears to be a custom ID
96
+ */
97
+ export function isCustomTaskId(id) {
98
+ if (!id)
99
+ return false;
100
+ // Regular task IDs are exactly 9 characters
101
+ if (id.length === 9) {
102
+ return false;
103
+ }
104
+ // Custom IDs have an uppercase prefix followed by a hyphen and numbers
105
+ const customIdPattern = /^[A-Z]+-\d+$/;
106
+ return customIdPattern.test(id);
107
+ }
108
+ //=============================================================================
87
109
  // ID RESOLUTION UTILITIES
88
110
  //=============================================================================
89
111
  /**
90
- * Resolves a task ID from either direct ID or name+list combination
112
+ * Resolves a task ID from direct ID, custom ID, or name
91
113
  * Handles validation and throws appropriate errors
92
114
  */
93
- export async function resolveTaskIdWithValidation(taskId, taskName, listName) {
115
+ export async function resolveTaskIdWithValidation(taskId, taskName, listName, customTaskId) {
94
116
  // Validate parameters
95
- validateTaskIdentification(taskId, taskName, listName);
96
- // If taskId is provided, use it directly
97
- if (taskId)
117
+ validateTaskIdentification(taskId, taskName, listName, customTaskId);
118
+ // If customTaskId is explicitly provided, use it
119
+ if (customTaskId) {
120
+ const { task: taskService } = clickUpServices;
121
+ try {
122
+ // First try to get the task by custom ID
123
+ // If listName is provided, we can also look up in a specific list for better performance
124
+ let listId;
125
+ if (listName) {
126
+ listId = await resolveListIdWithValidation(undefined, listName);
127
+ }
128
+ // Look up by custom ID
129
+ const foundTask = await taskService.getTaskByCustomId(customTaskId, listId);
130
+ return foundTask.id;
131
+ }
132
+ catch (error) {
133
+ throw new Error(`Task with custom ID "${customTaskId}" not found`);
134
+ }
135
+ }
136
+ // If taskId is provided, check if it looks like a custom ID
137
+ if (taskId) {
138
+ if (isCustomTaskId(taskId)) {
139
+ console.log(`Detected task ID "${taskId}" as a custom ID, using custom ID lookup`);
140
+ // If it looks like a custom ID, try to get it as a custom ID first
141
+ const { task: taskService } = clickUpServices;
142
+ try {
143
+ // Look up by custom ID
144
+ let listId;
145
+ if (listName) {
146
+ listId = await resolveListIdWithValidation(undefined, listName);
147
+ }
148
+ const foundTask = await taskService.getTaskByCustomId(taskId, listId);
149
+ return foundTask.id;
150
+ }
151
+ catch (error) {
152
+ // If it fails as a custom ID, try as a regular ID
153
+ console.log(`Failed to find task with custom ID "${taskId}", falling back to regular ID`);
154
+ return taskId;
155
+ }
156
+ }
157
+ // Regular task ID
98
158
  return taskId;
159
+ }
99
160
  // At this point we know we have taskName and listName (validation ensures this)
100
161
  // Find the list ID from its name
101
162
  const listId = await resolveListIdWithValidation(undefined, listName);
@@ -30,16 +30,35 @@ export class SponsorService {
30
30
  */
31
31
  createResponse(data, includeSponsorMessage = false) {
32
32
  const content = [];
33
+ // Special handling for workspace hierarchy which contains a preformatted tree
34
+ if (data && typeof data === 'object' && 'hierarchy' in data && typeof data.hierarchy === 'string') {
35
+ // Handle workspace hierarchy specially - it contains a preformatted tree
36
+ content.push({
37
+ type: "text",
38
+ text: data.hierarchy
39
+ });
40
+ }
41
+ else if (typeof data === 'string') {
42
+ // If it's already a string, use it directly
43
+ content.push({
44
+ type: "text",
45
+ text: data
46
+ });
47
+ }
48
+ else {
49
+ // Otherwise, stringify the JSON object
50
+ content.push({
51
+ type: "text",
52
+ text: JSON.stringify(data, null, 2)
53
+ });
54
+ }
55
+ // Then add sponsorship message if enabled
33
56
  if (this.isEnabled && includeSponsorMessage) {
34
57
  content.push({
35
58
  type: "text",
36
- text: `❤️ Support this project by sponsoring the developer at ${this.sponsorUrl}\n\n`
59
+ text: `\n\n❤️ Support this project by sponsoring the developer at ${this.sponsorUrl}`
37
60
  });
38
61
  }
39
- content.push({
40
- type: "text",
41
- text: JSON.stringify(data, null, 2)
42
- });
43
62
  return { content };
44
63
  }
45
64
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.4.74",
3
+ "version": "0.5.0",
4
4
  "description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -1,64 +0,0 @@
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
- }
@@ -1,93 +0,0 @@
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
- }