Skip to main content
Glama
tasks.ts9.49 kB
import { z } from 'zod'; import { createApiHandler, createBatchApiHandler, createSyncApiHandler, } from '../utils/handlers.js'; import { createHandler } from '../utils/handlers.js'; import { todoistApi } from '../clients.js'; /// Common fields for create and update tasks const create_fields = { content: z .string() .describe('Task title (brief). May contain markdown-formatted text and hyperlinks'), description: z .string() .optional() .describe('Description (detailed). May contain markdown-formatted text and hyperlinks'), labels: z.array(z.string()).optional(), priority: z.number().int().min(1).max(4).optional().describe('From 1 (urgent) to 4 (normal)'), due_string: z .string() .optional() .describe( 'Human defined task due date (ex.: "next Monday", "Tomorrow"). Value is set using local (not UTC) time, if not in english provided, due_lang should be set to the language of the string' ), due_date: z .string() .optional() .describe( 'Due date in YYYY-MM-DD format relative to user timezone (when you plan to work on task)' ), due_datetime: z.string().optional().describe('Specific date and time in RFC3339 format in UTC'), due_lang: z .string() .optional() .describe('2-letter code specifying language in case due_string is not written in english'), assignee_id: z .string() .optional() .describe('The responsible user ID (only applies to shared tasks)'), duration: z .number() .int() .positive() .optional() .describe( 'A positive (greater than zero) integer for the amount of duration_unit the task will take' ), duration_unit: z .enum(['minute', 'day']) .optional() .describe( 'The unit of time that the duration field represents. Must be either minute or day' ), deadline_date: z .string() .optional() .describe( 'Deadline date in YYYY-MM-DD format relative to user timezone (fixed date when task must be completed, for tasks with external consequences)' ), deadline_lang: z.string().optional().describe('2-letter code specifying language of deadline'), }; createApiHandler({ name: 'get_tasks_list', description: 'Get tasks list from Todoist', schemaShape: { project_id: z.string().optional().describe('Filter by project'), section_id: z.string().optional().describe('Filter by section'), label: z.string().optional().describe('Filter by label'), filter: z .string() .optional() .describe( 'Natural language english filter like "search: keyword", "today", "date before: +4 hours", "date after: May 5", "no date", "no time", "overdue", "7 days & @waiting", "created before: -365 days", "assigned to: person", "added by: me", "#Project & !assigned", "subtask", "!subtask", "P1 | P2", "today & @email", "@work | @office", "(today | overdue) & #Work", "all & 7 days", "!assigned", "Today & !#Work"' ), ids: z.string().optional().describe('Comma-separated list of task IDs'), limit: z.number().optional().default(50), }, method: 'GET', path: '/tasks', }); createBatchApiHandler({ name: 'create_tasks', description: 'Create new tasks in Todoist', itemSchema: { ...create_fields, project_id: z .string() .optional() .describe("Task project ID. If not set, task is put to user's Inbox"), section_id: z.string().optional(), parent_id: z.string().optional(), }, method: 'POST', path: '/tasks', mode: 'create', }); createBatchApiHandler({ name: 'get_tasks', description: 'Get tasks from Todoist', itemSchema: { task_id: z.string().optional().describe('ID of the task to retrieve (preferred)'), task_name: z .string() .optional() .describe('Name of the task to retrieve (if ID not provided)'), }, method: 'GET', path: '/tasks/{id}', mode: 'read', idField: 'task_id', nameField: 'task_name', findByName: (name, items) => items.find(item => item.content.toLowerCase().includes(name.toLowerCase())), }); createBatchApiHandler({ name: 'update_tasks', description: 'Update tasks in Todoist', itemSchema: { task_id: z.string().optional(), task_name: z.string().optional(), ...create_fields, }, method: 'POST', path: '/tasks/{id}', mode: 'update', idField: 'task_id', nameField: 'task_name', findByName: (name, items) => items.find(item => item.content.toLowerCase().includes(name.toLowerCase())), }); createBatchApiHandler({ name: 'close_tasks', description: 'Close tasks in Todoist', itemSchema: { task_id: z.string().optional(), task_name: z.string().optional(), }, method: 'POST', path: '/tasks/{id}/close', mode: 'update', idField: 'task_id', nameField: 'task_name', findByName: (name, items) => items.find(item => item.content.toLowerCase().includes(name.toLowerCase())), }); createBatchApiHandler({ name: 'reopen_tasks', description: 'Reopen tasks in Todoist', itemSchema: { task_id: z.string().optional(), task_name: z.string().optional(), }, method: 'POST', path: '/tasks/{id}/reopen', mode: 'update', idField: 'task_id', nameField: 'task_name', findByName: (name, items) => items.find(item => item.content.toLowerCase().includes(name.toLowerCase())), }); createBatchApiHandler({ name: 'delete_tasks', description: 'Delete tasks from Todoist', itemSchema: { task_id: z.string().optional(), task_name: z.string().optional(), }, method: 'DELETE', path: '/tasks/{id}', mode: 'delete', idField: 'task_id', nameField: 'task_name', findByName: (name, items) => items.find(item => item.content.toLowerCase().includes(name.toLowerCase())), }); createSyncApiHandler({ name: 'move_tasks', description: 'Move tasks to a different parent or section in Todoist. Exactly one of parent_id, section_id, or project_id must be provided', itemSchema: { task_id: z.string().optional(), task_name: z.string().optional(), parent_id: z.string().optional(), section_id: z.string().optional(), project_id: z.string().optional(), }, commandType: 'item_move', idField: 'task_id', nameField: 'task_name', lookupPath: '/tasks', findByName: (name, items) => items.find(item => item.content && item.content.toLowerCase().includes(name.toLowerCase())), buildCommandArgs: (item, itemId) => { const args: { id: string; parent_id?: string; section_id?: string; project_id?: string; } = { id: itemId }; // Only one destination option if (item.parent_id) args.parent_id = item.parent_id; else if (item.section_id) args.section_id = item.section_id; else if (item.project_id) args.project_id = item.project_id; return args; }, validateItem: item => { // Ensure exactly one destination is specified const destinationCount = [item.parent_id, item.section_id, item.project_id].filter( Boolean ).length; if (destinationCount !== 1) { return { valid: false, error: 'Exactly one of parent_id, section_id, or project_id must be provided', }; } return { valid: true }; }, }); createHandler( 'get_completed_tasks', 'Get completed tasks from Todoist with filtering options', { project_id: z.string().optional().describe('Filter by specific project ID'), section_id: z.string().optional().describe('Filter by specific section ID'), parent_id: z.string().optional().describe('Filter by specific parent task ID'), since: z .string() .optional() .describe('Return tasks completed since this date (YYYY-MM-DD format)'), until: z .string() .optional() .describe('Return tasks completed until this date (YYYY-MM-DD format)'), limit: z .number() .int() .min(1) .max(200) .optional() .default(50) .describe('Number of tasks to return (max 200)'), offset: z.number().int().min(0).optional().describe('Offset for pagination'), annotation_type: z.string().optional().describe('Filter by annotation type'), }, async args => { const params: Record<string, string> = {}; Object.entries(args).forEach(([key, value]) => { if (value !== undefined) { params[key] = typeof value === 'number' ? value.toString() : value; } }); const response = await todoistApi.getCompletedTasks(params); return { completed_tasks: response.items || [], projects: response.projects || {}, sections: response.sections || {}, total: response.items?.length || 0, }; } );

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