get_sections
Retrieve specific sections from Todoist projects by ID or name to organize and manage tasks effectively.
Instructions
Get sections from Todoist Either 'id' or the 'name' to identify the target.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes |
Implementation Reference
- src/utils/handlers.ts:201-353 (handler)The core execution logic for the get_sections tool. This batch handler fetches sections by ID or resolves by name (listing all sections first if needed), performs GET to Todoist API /sections/{id}, and returns batched results.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, }; };
- src/tools/sections.ts:27-41 (registration)Registration of the 'get_sections' tool, configuring it as a batch read operation on Todoist sections with support for ID or name lookup.createBatchApiHandler({ name: 'get_sections', description: 'Get sections from Todoist', itemSchema: { id: z.string().optional().describe('ID of the section to retrieve (preferred over name)'), name: z.string().optional().describe('Name of the section to retrieve'), }, method: 'GET', path: '/sections/{id}', mode: 'read', idField: 'id', nameField: 'name', findByName: (name, items) => items.find(item => item.name.toLowerCase().includes(name.toLowerCase())), });
- src/tools/sections.ts:30-33 (schema)Input schema definition for the get_sections tool items: optional id or name.itemSchema: { id: z.string().optional().describe('ID of the section to retrieve (preferred over name)'), name: z.string().optional().describe('Name of the section to retrieve'), },
- src/utils/handlers.ts:40-77 (helper)createHandler helper that wraps the tool handler with error handling and registers it to the MCP server using 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>); }