Skip to main content
Glama

delete_comments

Remove comments from Todoist tasks to clean up project discussions and maintain organized workflows.

Instructions

Delete comments in Todoist

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
itemsYes

Implementation Reference

  • Tool registration for 'delete_comments'. Defines the tool name, description, input schema requiring 'id', DELETE HTTP method to Todoist API endpoint '/comments/{id}', with idField and delete mode.
    createBatchApiHandler({
        name: 'delete_comments',
        description: 'Delete comments in Todoist',
        itemSchema: {
            id: z.string(),
        },
        method: 'DELETE',
        path: '/comments/{id}',
        idField: 'id',
        mode: 'delete',
    });
  • Generic handler creator for batch API operations. For 'delete_comments', it processes batch of items, resolves ID, constructs path '/comments/{id}', and executes todoistApi.delete(finalPath) for each valid item.
    export function createBatchApiHandler<T extends z.ZodRawShape>(
        options: {
            name: string;
            description: string;
            itemSchema: T;
            method: HttpMethod;
            mode?: 'read' | 'create' | 'update' | 'delete';
            idField?: string;
            nameField?: string;
            findByName?: (name: string, items: any[]) => any | undefined;
            validateItem?: (item: any) => { valid: boolean; error?: string };
        } & ( // Or we specify full path
            | {
                  path: string;
                  basePath?: never;
                  pathSuffix?: never;
              }
            // Or we specify base path and path suffix
            | {
                  path?: never;
                  basePath: string;
                  pathSuffix: string;
              }
        )
    ) {
        // Create basic description, we cant properly use 'anyOf' here so for now we will add info to description
        let finalDescription = options.description;
        const requiresIdOrName = options.idField && options.nameField && options.mode !== 'create';
    
        if (requiresIdOrName) {
            const requirementText = `\nEither '${options.idField}' or the '${options.nameField}' to identify the target.`;
            finalDescription += requirementText;
        }
    
        const itemSchemaObject = z.object(options.itemSchema);
    
        const enhancedItemSchema: z.ZodTypeAny = requiresIdOrName
            ? itemSchemaObject.refine(
                  (data: any) =>
                      data[options.idField!] !== undefined || data[options.nameField!] !== undefined,
                  {
                      message: `Either ${options.idField} or ${options.nameField} must be provided`,
                      path: [options.idField!, options.nameField!],
                  }
              )
            : itemSchemaObject;
    
        const batchSchema = z.object({
            items: z.array(enhancedItemSchema),
        });
    
        const handler = async (args: z.infer<typeof batchSchema>): Promise<any> => {
            const { items } = args;
    
            // For modes other than create, check if name lookup is needed
            let allItems: any[] = [];
    
            const needsNameLookup =
                options.mode !== 'create' &&
                options.nameField &&
                options.findByName &&
                items.some(item => item[options.nameField!] && !item[options.idField!]);
    
            if (needsNameLookup) {
                // Determine the base path for fetching all items
                // Example: /tasks from /tasks/{id}
                const lookupPath =
                    options.basePath || (options.path ? options.path.split('/{')[0] : '');
                allItems = await todoistApi.get(lookupPath, {});
            }
    
            const results = await Promise.all(
                items.map(async item => {
                    if (options.validateItem) {
                        const validation = options.validateItem(item);
    
                        if (!validation.valid) {
                            return {
                                success: false,
                                error: validation.error || 'Validation failed',
                                item,
                            };
                        }
                    }
    
                    try {
                        let finalPath = '';
                        const apiParams = { ...item };
    
                        // For modes where need id
                        if (options.mode !== 'create' && options.idField) {
                            let itemId = item[options.idField];
                            let matchedName = null;
                            let matchedContent = null;
    
                            // If no ID but name is provided, search by name
                            if (!itemId && item[options.nameField!] && options.findByName) {
                                const searchName = item[options.nameField!];
                                const matchedItem = options.findByName(searchName, allItems);
    
                                if (!matchedItem) {
                                    return {
                                        success: false,
                                        error: `Item not found with name: ${searchName}`,
                                        item,
                                    };
                                }
    
                                itemId = matchedItem.id;
                                matchedName = searchName;
                                matchedContent = matchedItem.content;
                            }
    
                            if (!itemId) {
                                return {
                                    success: false,
                                    error: `Either ${options.idField} or ${options.nameField} must be provided`,
                                    item,
                                };
                            }
    
                            // Apply security validation to itemId before using in path
                            const safeItemId = validatePathParameter(itemId, options.idField || 'id');
    
                            if (options.basePath && options.pathSuffix) {
                                finalPath = `${options.basePath}${options.pathSuffix.replace('{id}', safeItemId)}`;
                            } else if (options.path) {
                                finalPath = options.path.replace('{id}', safeItemId);
                            }
    
                            delete apiParams[options.idField];
                            if (options.nameField) {
                                delete apiParams[options.nameField];
                            }
    
                            let result;
                            switch (options.method) {
                                case 'GET':
                                    result = await todoistApi.get(finalPath, apiParams);
                                    break;
                                case 'POST':
                                    result = await todoistApi.post(finalPath, apiParams);
                                    break;
                                case 'DELETE':
                                    result = await todoistApi.delete(finalPath);
                                    break;
                            }
    
                            const response: any = {
                                success: true,
                                id: itemId,
                                result,
                            };
    
                            if (matchedName) {
                                response.found_by_name = matchedName;
                                response.matched_content = matchedContent;
                            }
    
                            return response;
                        }
                        // Create mode
                        else {
                            finalPath = options.path || options.basePath || '';
    
                            let result;
                            switch (options.method) {
                                case 'GET':
                                    result = await todoistApi.get(finalPath, apiParams);
                                    break;
                                case 'POST':
                                    result = await todoistApi.post(finalPath, apiParams);
                                    break;
                                case 'DELETE':
                                    result = await todoistApi.delete(finalPath);
                                    break;
                            }
    
                            return {
                                success: true,
                                created_item: result,
                            };
                        }
                    } catch (error) {
                        return {
                            success: false,
                            error: error instanceof Error ? error.message : String(error),
                            item,
                        };
                    }
                })
            );
    
            const successCount = results.filter(r => r.success).length;
            return {
                success: successCount === items.length,
                summary: {
                    total: items.length,
                    succeeded: successCount,
                    failed: items.length - successCount,
                },
                results,
            };
        };
    
        return createHandler(options.name, finalDescription, batchSchema.shape, handler);
    }
  • Input schema for delete_comments tool: requires 'id' string for the comment to delete. Used in batch as array of items.
    itemSchema: {
        id: z.string(),
    },
  • Helper function that wraps the tool handler with MCP formatting and error handling, and registers it via server.tool.
    export function createHandler<T extends z.ZodRawShape>(
        name: string,
        description: string,
        paramsSchema: T,
        handler: (args: HandlerArgs<T>) => Promise<any>
    ): void {
        const mcpToolCallback = async (args: HandlerArgs<T>): Promise<CallToolResult> => {
            try {
                const result = await handler(args);
    
                return {
                    content: [
                        {
                            type: 'text',
                            text: JSON.stringify(result ?? null, null, 2).trim(),
                        },
                    ],
                };
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
    
                console.error(`Error in tool ${name}:`, error);
    
                return {
                    isError: true,
                    content: [
                        {
                            type: 'text',
                            text: `Error executing tool '${name}': ${errorMessage}`,
                        },
                    ],
                };
            }
        };
    
        // Crazy cast, if you can do it better, please, let me knows
        server.tool(name, description, paramsSchema, mcpToolCallback as unknown as ToolCallback<T>);
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stanislavlysenko0912/todoist-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server