Skip to main content
Glama

Todoist MCP Server

api-helpers.ts6 kB
/** * Shared API utilities for handling Todoist API responses and common operations */ import { TodoistTaskDueData } from "../types.js"; import { formatDueDetails } from "./datetime-utils.js"; import { fromApiPriority } from "./priority-mapper.js"; /** * Generic interface for Todoist API responses that may return data in different formats */ export interface TodoistAPIResponse<T> { results?: T[]; data?: T[]; } /** * Extracts array data from various Todoist API response formats. * Handles both direct arrays and object responses with 'results' or 'data' properties. * * @param result - The API response which could be an array or an object containing arrays * @returns Array of items of type T, or empty array if no data found * * @example * ```typescript * const tasks = extractArrayFromResponse<TodoistTask>(apiResponse); * const comments = extractArrayFromResponse<TodoistComment>(commentResponse); * ``` */ export function extractArrayFromResponse<T>(result: unknown): T[] { if (Array.isArray(result)) { return result as T[]; } const responseObj = result as TodoistAPIResponse<T>; return responseObj?.results || responseObj?.data || []; } /** * Interface for comment response data from Todoist API */ export interface CommentResponse { content: string; attachment?: { fileName: string; fileType: string; }; postedAt?: string; taskId?: string; projectId?: string; } /** * Interface for comment creation data */ export interface CommentCreationData { content: string; taskId: string; attachment?: { fileName: string; fileUrl: string; fileType: string; }; } /** * Validates that a response object has the expected structure * * @param response - The API response to validate * @param expectedFields - Array of field names that should exist in the response * @returns boolean indicating if the response is valid */ export function validateApiResponse( response: unknown, expectedFields: string[] ): boolean { if (!response || typeof response !== "object") { return false; } const obj = response as Record<string, unknown>; return expectedFields.every((field) => field in obj); } /** * Creates a cache key from an object by serializing its properties * * @param prefix - Prefix for the cache key * @param params - Object containing parameters to include in the key * @returns Standardized cache key string */ export function createCacheKey( prefix: string, params: Record<string, unknown> = {} ): string { const cleanParams = Object.fromEntries( Object.entries(params).filter( ([, value]) => value !== undefined && value !== null ) ); return `${prefix}_${JSON.stringify(cleanParams)}`; } /** * Formats a Todoist task for display in responses * * @param task - The task object to format * @returns Formatted string representation of the task */ export function formatTaskForDisplay(task: { id?: string; content: string; description?: string; due?: { string: string } | null; deadline?: { date: string } | null; priority?: number; labels?: string[]; }): string { const displayPriority = fromApiPriority(task.priority); const dueDetails = formatDueDetails( task.due as TodoistTaskDueData | null | undefined ); return `- ${task.content}${task.id ? ` (ID: ${task.id})` : ""}${ task.description ? `\n Description: ${task.description}` : "" }${dueDetails ? `\n Due: ${dueDetails}` : ""}${ task.deadline ? `\n Deadline: ${task.deadline.date}` : "" }${displayPriority ? `\n Priority: ${displayPriority}` : ""}${ task.labels && task.labels.length > 0 ? `\n Labels: ${task.labels.join(", ")}` : "" }`; } /** * Safely extracts string value from unknown input * * @param value - The value to extract as string * @param defaultValue - Default value if extraction fails * @returns String value or default */ export function safeStringExtract(value: unknown, defaultValue = ""): string { if (typeof value === "string") { return value; } if (value != null) { return String(value); } return defaultValue; } /** * Safely extracts number value from unknown input * * @param value - The value to extract as number * @param defaultValue - Default value if extraction fails * @returns Number value or default */ export function safeNumberExtract(value: unknown, defaultValue = 0): number { if (typeof value === "number" && !isNaN(value)) { return value; } if (typeof value === "string") { const parsed = parseFloat(value); return isNaN(parsed) ? defaultValue : parsed; } return defaultValue; } /** * Resolves a project identifier to a project ID. * If the input is already a valid project ID, returns it as-is. * If the input is a project name, searches for the project and returns its ID. * * @param todoistClient - The Todoist API client * @param projectIdentifier - Either a project ID or project name * @returns The resolved project ID * @throws Error if project name is not found */ export async function resolveProjectIdentifier( todoistClient: { getProjects: () => Promise<unknown> }, projectIdentifier: string ): Promise<string> { if (!projectIdentifier || projectIdentifier.trim().length === 0) { throw new Error("Project identifier cannot be empty"); } // First, try to get all projects const result = await todoistClient.getProjects(); const projects = extractArrayFromResponse<{ id: string; name: string }>( result ); // Check if the identifier matches a project ID exactly const projectById = projects.find((p) => p.id === projectIdentifier); if (projectById) { return projectById.id; } // Try to find by name (case-insensitive) const projectByName = projects.find( (p) => p.name.toLowerCase() === projectIdentifier.toLowerCase() ); if (projectByName) { return projectByName.id; } // If not found, throw an error throw new Error(`Project not found: "${projectIdentifier}"`); }

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/greirson/mcp-todoist'

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