Skip to main content
Glama

get_projects

Retrieve Todoist projects by ID or name to access and manage your task organization structure.

Instructions

Get projects from Todoist Either 'id' or the 'name' to identify the target.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
itemsYes

Implementation Reference

  • Registers the MCP tool 'get_projects' with batch API handler creator, defining input schema for id or name, GET /projects/{id}, read mode with name-based lookup support.
    createBatchApiHandler({ name: 'get_projects', description: 'Get projects from Todoist', itemSchema: { id: z.string().optional().describe('ID of the project to retrieve (preferred over name)'), name: z.string().optional().describe('Name of the project to retrieve'), }, method: 'GET', path: '/projects/{id}', mode: 'read', idField: 'id', nameField: 'name', findByName: (name, items) => items.find(item => item.name.toLowerCase().includes(name.toLowerCase())), });
  • Zod schema for tool input: optional project id or name.
    itemSchema: { id: z.string().optional().describe('ID of the project to retrieve (preferred over name)'), name: z.string().optional().describe('Name of the project to retrieve'), },
  • Executes the tool logic for batch read operations like get_projects: supports batch input of projects by id or name, fetches all projects for lookup, calls Todoist API GET /projects/{id} for each, returns batched results with success summary.
    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, }; };
  • Registers the generated handler as an MCP tool on the server using server.tool.
    server.tool(name, description, paramsSchema, mcpToolCallback as unknown as ToolCallback<T>); }
  • Helper function to find project by partial name match for id resolution.
    findByName: (name, items) => items.find(item => item.name.toLowerCase().includes(name.toLowerCase())),

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