--- description: Guidelines for implementing and interacting with the Task Master MCP Server globs: mcp-server/src/**/*, scripts/modules/**/* alwaysApply: false --- # Task Master MCP Server Guidelines This document outlines the architecture and implementation patterns for the Task Master Model Context Protocol (MCP) server, designed for integration with tools like Cursor. ## Architecture Overview (See also: [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc)) The MCP server acts as a bridge between external tools (like Cursor) and the core Task Master CLI logic. It leverages FastMCP for the server framework. - **Flow**: `External Tool (Cursor)` <-> `FastMCP Server` <-> `MCP Tools` (`mcp-server/src/tools/*.js`) <-> `Core Logic Wrappers` (`mcp-server/src/core/direct-functions/*.js`, exported via `task-master-core.js`) <-> `Core Modules` (`scripts/modules/*.js`) - **Goal**: Provide a performant and reliable way for external tools to interact with Task Master functionality without directly invoking the CLI for every operation. ## Direct Function Implementation Best Practices When implementing a new direct function in `mcp-server/src/core/direct-functions/`, follow these critical guidelines: 1. **Verify Function Dependencies**: - ✅ **DO**: Check that all helper functions your direct function needs are properly exported from their source modules - ✅ **DO**: Import these dependencies explicitly at the top of your file - ❌ **DON'T**: Assume helper functions like `findTaskById` or `taskExists` are automatically available - **Example**: ```javascript // At top of direct-function file import { removeTask, findTaskById, taskExists } from '../../../../scripts/modules/task-manager.js'; ``` 2. **Parameter Verification and Completeness**: - ✅ **DO**: Verify the signature of core functions you're calling and ensure all required parameters are provided - ✅ **DO**: Pass explicit values for required parameters rather than relying on defaults - ✅ **DO**: Double-check parameter order against function definition - ❌ **DON'T**: Omit parameters assuming they have default values - **Example**: ```javascript // Correct parameter handling in direct function async function generateTaskFilesDirect(args, log) { const tasksPath = findTasksJsonPath(args, log); const outputDir = args.output || path.dirname(tasksPath); try { // Pass all required parameters const result = await generateTaskFiles(tasksPath, outputDir); return { success: true, data: result, fromCache: false }; } catch (error) { // Error handling... } } ``` 3. **Consistent File Path Handling**: - ✅ **DO**: Use `path.join()` instead of string concatenation for file paths - ✅ **DO**: Follow established file naming conventions (`task_001.txt` not `1.md`) - ✅ **DO**: Use `path.dirname()` and other path utilities for manipulating paths - ✅ **DO**: When paths relate to task files, follow the standard format: `task_${id.toString().padStart(3, '0')}.txt` - ❌ **DON'T**: Create custom file path handling logic that diverges from established patterns - **Example**: ```javascript // Correct file path handling const taskFilePath = path.join( path.dirname(tasksPath), `task_${taskId.toString().padStart(3, '0')}.txt` ); ``` 4. **Comprehensive Error Handling**: - ✅ **DO**: Wrap core function calls in try/catch blocks - ✅ **DO**: Log errors with appropriate severity and context - ✅ **DO**: Return standardized error objects with code and message - ✅ **DO**: Handle file system errors separately from function-specific errors - **Example**: ```javascript try { // Core function call } catch (error) { log.error(`Failed to execute command: ${error.message}`); return { success: false, error: { code: error.code || 'DIRECT_FUNCTION_ERROR', message: error.message, details: error.stack }, fromCache: false }; } ``` 5. **Silent Mode Implementation**: - ✅ **DO**: Import silent mode utilities at the top of your file ```javascript import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; ``` - ✅ **DO**: Wrap core function calls with silent mode control ```javascript // Enable silent mode before the core function call enableSilentMode(); // Execute core function const result = await coreFunction(param1, param2); // Restore normal logging disableSilentMode(); ``` - ✅ **DO**: Add proper error handling to ensure silent mode is disabled ```javascript try { enableSilentMode(); // Core function execution const result = await coreFunction(param1, param2); disableSilentMode(); return { success: true, data: result }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in function: ${error.message}`); return { success: false, error: { code: 'ERROR_CODE', message: error.message } }; } ``` - ❌ **DON'T**: Forget to disable silent mode when errors occur - ❌ **DON'T**: Leave silent mode enabled outside a direct function's scope - ❌ **DON'T**: Skip silent mode for core function calls that generate logs ## Tool Definition and Execution ### Tool Structure MCP tools must follow a specific structure to properly interact with the FastMCP framework: ```javascript server.addTool({ name: "tool_name", // Use snake_case for tool names description: "Description of what the tool does", parameters: z.object({ // Define parameters using Zod param1: z.string().describe("Parameter description"), param2: z.number().optional().describe("Optional parameter description"), // IMPORTANT: For file operations, always include these optional parameters file: z.string().optional().describe("Path to the tasks file"), projectRoot: z.string().optional().describe("Root directory of the project (typically derived from session)") }), // The execute function is the core of the tool implementation execute: async (args, context) => { // Implementation goes here // Return response in the appropriate format } }); ``` ### Execute Function Signature The `execute` function receives validated arguments and the FastMCP context: ```javascript execute: async (args, context) => { // Tool implementation } ``` - **args**: The first parameter contains all the validated parameters defined in the tool's schema. - **context**: The second parameter is an object containing `{ log, reportProgress, session }` provided by FastMCP. - ✅ **DO**: `execute: async (args, { log, reportProgress, session }) => {}` ### Standard Tool Execution Pattern The `execute` method within each MCP tool (in `mcp-server/src/tools/*.js`) should follow this standard pattern: 1. **Log Entry**: Log the start of the tool execution with relevant arguments. 2. **Get Project Root**: Use the `getProjectRootFromSession(session, log)` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) to extract the project root path from the client session. Fall back to `args.projectRoot` if the session doesn't provide a root. 3. **Call Direct Function**: Invoke the corresponding `*Direct` function wrapper (e.g., `listTasksDirect` from [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js)), passing an updated `args` object that includes the resolved `projectRoot`, along with the `log` object: `await someDirectFunction({ ...args, projectRoot: resolvedRootFolder }, log);` 4. **Handle Result**: Receive the result object (`{ success, data/error, fromCache }`) from the `*Direct` function. 5. **Format Response**: Pass this result object to the `handleApiResult` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) for standardized MCP response formatting and error handling. 6. **Return**: Return the formatted response object provided by `handleApiResult`. ```javascript // Example execute method structure import { getProjectRootFromSession, handleApiResult, createErrorResponse } from './utils.js'; import { someDirectFunction } from '../core/task-master-core.js'; // ... inside server.addTool({...}) execute: async (args, { log, reportProgress, session }) => { try { log.info(`Starting tool execution with args: ${JSON.stringify(args)}`); // 1. Get Project Root let rootFolder = getProjectRootFromSession(session, log); if (!rootFolder && args.projectRoot) { // Fallback if needed rootFolder = args.projectRoot; log.info(`Using project root from args as fallback: ${rootFolder}`); } // 2. Call Direct Function (passing resolved root) const result = await someDirectFunction({ ...args, projectRoot: rootFolder // Ensure projectRoot is explicitly passed }, log); // 3. Handle and Format Response return handleApiResult(result, log); } catch (error) { log.error(`Error during tool execution: ${error.message}`); return createErrorResponse(error.message); } } ``` ### Using AsyncOperationManager for Background Tasks For tools that execute long-running operations, use the AsyncOperationManager to run them in the background: ```javascript import { asyncOperationManager } from '../core/utils/async-manager.js'; import { getProjectRootFromSession, createContentResponse, createErrorResponse } from './utils.js'; import { someIntensiveDirect } from '../core/task-master-core.js'; // ... inside server.addTool({...}) execute: async (args, { log, reportProgress, session }) => { try { log.info(`Starting background operation with args: ${JSON.stringify(args)}`); // 1. Get Project Root let rootFolder = getProjectRootFromSession(session, log); if (!rootFolder && args.projectRoot) { rootFolder = args.projectRoot; log.info(`Using project root from args as fallback: ${rootFolder}`); } // 2. Add operation to the async manager const operationId = asyncOperationManager.addOperation( someIntensiveDirect, // The direct function to execute { ...args, projectRoot: rootFolder }, // Args to pass { log, reportProgress, session } // Context to preserve ); // 3. Return immediate response with operation ID return createContentResponse({ message: "Operation started successfully", operationId, status: "pending" }); } catch (error) { log.error(`Error starting background operation: ${error.message}`); return createErrorResponse(error.message); } } ``` Clients should then use the `get_operation_status` tool to check on operation progress: ```javascript // In get-operation-status.js import { asyncOperationManager } from '../core/utils/async-manager.js'; import { createContentResponse, createErrorResponse } from './utils.js'; // ... inside server.addTool({...}) execute: async (args, { log }) => { try { const { operationId } = args; log.info(`Checking status of operation: ${operationId}`); const status = asyncOperationManager.getStatus(operationId); if (status.status === 'not_found') { return createErrorResponse(status.error.message); } return createContentResponse({ ...status, message: `Operation status: ${status.status}` }); } catch (error) { log.error(`Error checking operation status: ${error.message}`); return createErrorResponse(error.message); } } ``` ### Project Initialization Tool The `initialize_project` tool allows integrated clients like Cursor to set up a new Task Master project: ```javascript // In initialize-project.js import { z } from "zod"; import { initializeProjectDirect } from "../core/task-master-core.js"; import { handleApiResult, createErrorResponse } from "./utils.js"; export function registerInitializeProjectTool(server) { server.addTool({ name: "initialize_project", description: "Initialize a new Task Master project", parameters: z.object({ projectName: z.string().optional().describe("The name for the new project"), projectDescription: z.string().optional().describe("A brief description"), projectVersion: z.string().optional().describe("Initial version (e.g., '0.1.0')"), authorName: z.string().optional().describe("The author's name"), skipInstall: z.boolean().optional().describe("Skip installing dependencies"), addAliases: z.boolean().optional().describe("Add shell aliases"), yes: z.boolean().optional().describe("Skip prompts and use defaults") }), execute: async (args, { log, reportProgress }) => { try { // Since we're initializing, we don't need project root const result = await initializeProjectDirect(args, log); return handleApiResult(result, log, 'Error initializing project'); } catch (error) { log.error(`Error in initialize_project: ${error.message}`); return createErrorResponse(`Failed to initialize project: ${error.message}`); } } }); } ``` ### Logging Convention The `log` object (destructured from `context`) provides standardized logging methods. Use it within both the `execute` method and the `*Direct` functions. ```javascript // Proper logging usage log.info(`Starting ${toolName} with parameters: ${JSON.stringify(sanitizedArgs)}`); log.debug("Detailed operation info", { data }); log.warn("Potential issue detected"); log.error(`Error occurred: ${error.message}`, { stack: error.stack }); ``` ### Progress Reporting Convention Use `reportProgress` (destructured from `context`) for long-running operations. It expects an object `{ progress: number, total?: number }`. ```javascript await reportProgress({ progress: 0 }); // Start // ... work ... await reportProgress({ progress: 50 }); // Intermediate (total optional) // ... more work ... await reportProgress({ progress: 100 }); // Complete ``` ### Session Usage Convention The `session` object (destructured from `context`) contains authenticated session data and client information. - **Authentication**: Access user-specific data (`session.userId`, etc.) if authentication is implemented. - **Project Root**: The primary use in Task Master is accessing `session.roots` to determine the client's project root directory via the `getProjectRootFromSession` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)). See the Standard Tool Execution Pattern above. - **Capabilities**: Can be used to check client capabilities (`session.clientCapabilities`). ## Direct Function Wrappers (`*Direct`) These functions, located in `mcp-server/src/core/direct-functions/`, form the core logic execution layer for MCP tools. - **Purpose**: Bridge MCP tools and core Task Master modules (`scripts/modules/*`). - **Responsibilities**: - Receive `args` (including the `projectRoot` determined by the tool) and `log` object. - **Find `tasks.json`**: Use `findTasksJsonPath(args, log)` from [`core/utils/path-utils.js`](mdc:mcp-server/src/core/utils/path-utils.js). This function prioritizes the provided `args.projectRoot`. - Validate arguments specific to the core logic. - **Implement Silent Mode**: Import and use `enableSilentMode` and `disableSilentMode` around core function calls. - **Implement Caching**: Use `getCachedOrExecute` from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) for read operations. - Call the underlying function from the core Task Master modules. - Handle errors gracefully. - Return a standardized result object: `{ success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }`. ## Key Principles - **Prefer Direct Function Calls**: MCP tools should always call `*Direct` wrappers instead of `executeTaskMasterCommand`. - **Standardized Execution Flow**: Follow the pattern: MCP Tool -> `getProjectRootFromSession` -> `*Direct` Function -> Core Logic. - **Path Resolution via Direct Functions**: The `*Direct` function is responsible for finding the exact `tasks.json` path using `findTasksJsonPath`, relying on the `projectRoot` passed in `args`. - **Silent Mode in Direct Functions**: Wrap all core function calls with `enableSilentMode()` and `disableSilentMode()` to prevent logs from interfering with JSON responses. - **Async Processing for Intensive Operations**: Use AsyncOperationManager for CPU-intensive or long-running operations. - **Project Initialization**: Use the initialize_project tool for setting up new projects in integrated environments. - **Centralized Utilities**: Use helpers from `mcp-server/src/tools/utils.js` (like `handleApiResult`, `getProjectRootFromSession`, `getCachedOrExecute`) and `mcp-server/src/core/utils/path-utils.js` (`findTasksJsonPath`). See [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc). - **Caching in Direct Functions**: Caching logic resides *within* the `*Direct` functions using `getCachedOrExecute`. ## Resources and Resource Templates Resources provide LLMs with static or dynamic data without executing tools. - **Implementation**: Use `@mcp.resource()` decorator pattern or `server.addResource`/`server.addResourceTemplate` in `mcp-server/src/core/resources/`. - **Registration**: Register resources during server initialization in [`mcp-server/src/index.js`](mdc:mcp-server/src/index.js). - **Best Practices**: Organize resources, validate parameters, use consistent URIs, handle errors. See [`fastmcp-core.txt`](docs/fastmcp-core.txt) for underlying SDK details. *(Self-correction: Removed detailed Resource implementation examples as they were less relevant to the current user focus on tool execution flow and project roots. Kept the overview.)* ## Implementing MCP Support for a Command Follow these steps to add MCP support for an existing Task Master command (see [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for more detail): 1. **Ensure Core Logic Exists**: Verify the core functionality is implemented and exported from the relevant module in `scripts/modules/`. 2. **Create Direct Function File in `mcp-server/src/core/direct-functions/`**: - Create a new file (e.g., `your-command.js`) using **kebab-case** naming. - Import necessary core functions, **`findTasksJsonPath` from `../utils/path-utils.js`**, and **silent mode utilities**. - Implement `async function yourCommandDirect(args, log)` using **camelCase** with `Direct` suffix: - **Path Resolution**: Obtain the tasks file path using `const tasksPath = findTasksJsonPath(args, log);`. This handles project root detection automatically based on `args.projectRoot`. - Parse other `args` and perform necessary validation. - **Implement Silent Mode**: Wrap core function calls with enableSilentMode/disableSilentMode. - **If Caching**: Implement caching using `getCachedOrExecute` from `../../tools/utils.js`. - **If Not Caching**: Directly call the core logic function within a try/catch block. - Format the return as `{ success: true/false, data/error, fromCache: boolean }`. - Export the wrapper function. 3. **Update `task-master-core.js` with Import/Export**: Import and re-export your `*Direct` function and add it to the `directFunctions` map. 4. **Create MCP Tool (`mcp-server/src/tools/`)**: - Create a new file (e.g., `your-command.js`) using **kebab-case**. - Import `zod`, `handleApiResult`, `createErrorResponse`, **`getProjectRootFromSession`**, and your `yourCommandDirect` function. - Implement `registerYourCommandTool(server)`. - Define the tool `name` using **snake_case** (e.g., `your_command`). - Define the `parameters` using `zod`. **Crucially, define `projectRoot` as optional**: `projectRoot: z.string().optional().describe(...)`. Include `file` if applicable. - Implement the standard `async execute(args, { log, reportProgress, session })` method: - Get `rootFolder` using `getProjectRootFromSession` (with fallback to `args.projectRoot`). - Call `yourCommandDirect({ ...args, projectRoot: rootFolder }, log)`. - Pass the result to `handleApiResult(result, log, 'Error Message')`. 5. **Register Tool**: Import and call `registerYourCommandTool` in `mcp-server/src/tools/index.js`. 6. **Update `mcp.json`**: Add the new tool definition to the `tools` array in `.cursor/mcp.json`. ## Handling Responses - MCP tools should return the object generated by `handleApiResult`. - `handleApiResult` uses `createContentResponse` or `createErrorResponse` internally. - `handleApiResult` also uses `processMCPResponseData` by default to filter potentially large fields (`details`, `testStrategy`) from task data. Provide a custom processor function to `handleApiResult` if different filtering is needed. - The final JSON response sent to the MCP client will include the `fromCache` boolean flag (obtained from the `*Direct` function's result) alongside the actual data (e.g., `{ "fromCache": true, "data": { ... } }` or `{ "fromCache": false, "data": { ... } }`). ## Parameter Type Handling - **Prefer Direct Function Calls**: For optimal performance and error handling, MCP tools should utilize direct function wrappers defined in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js). These wrappers call the underlying logic from the core modules (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)). - **Standard Tool Execution Pattern**: - The `execute` method within each MCP tool (in `mcp-server/src/tools/*.js`) should: 1. Call the corresponding `*Direct` function wrapper (e.g., `listTasksDirect`) from [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js), passing necessary arguments and the logger. 2. Receive the result object (typically `{ success, data/error, fromCache }`). 3. Pass this result object to the `handleApiResult` utility (from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js)) for standardized response formatting and error handling. 4. Return the formatted response object provided by `handleApiResult`. - **CLI Execution as Fallback**: The `executeTaskMasterCommand` utility in [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) allows executing commands via the CLI (`task-master ...`). This should **only** be used as a fallback if a direct function wrapper is not yet implemented or if a specific command intrinsically requires CLI execution. - **Centralized Utilities** (See also: [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc)): - Use `findTasksJsonPath` (in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js)) *within direct function wrappers* to locate the `tasks.json` file consistently. - **Leverage MCP Utilities**: The file [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js) contains essential helpers for MCP tool implementation: - `getProjectRoot`: Normalizes project paths. - `handleApiResult`: Takes the raw result from a `*Direct` function and formats it into a standard MCP success or error response, automatically handling data processing via `processMCPResponseData`. This is called by the tool's `execute` method. - `createContentResponse`/`createErrorResponse`: Used by `handleApiResult` to format successful/error MCP responses. - `processMCPResponseData`: Filters/cleans data (e.g., removing `details`, `testStrategy`) before it's sent in the MCP response. Called by `handleApiResult`. - `getCachedOrExecute`: **Used inside `*Direct` functions** in `task-master-core.js` to implement caching logic. - `executeTaskMasterCommand`: Fallback for executing CLI commands. - **Caching**: To improve performance for frequently called read operations (like `listTasks`, `showTask`, `nextTask`), a caching layer using `lru-cache` is implemented. - **Caching logic resides *within* the direct function wrappers** in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js) using the `getCachedOrExecute` utility from [`tools/utils.js`](mdc:mcp-server/src/tools/utils.js). - Generate unique cache keys based on function arguments that define a distinct call (e.g., file path, filters). - The `getCachedOrExecute` utility handles checking the cache, executing the core logic function on a cache miss, storing the result, and returning the data along with a `fromCache` flag. - Cache statistics can be monitored using the `cacheStats` MCP tool (implemented via `getCacheStatsDirect`). - **Caching should generally be applied to read-only operations** that don't modify the `tasks.json` state. Commands like `set-status`, `add-task`, `update-task`, `parse-prd`, `add-dependency` should *not* be cached as they change the underlying data. **MCP Tool Implementation Checklist**: 1. **Core Logic Verification**: - [ ] Confirm the core function is properly exported from its module (e.g., `task-manager.js`) - [ ] Identify all required parameters and their types 2. **Direct Function Wrapper**: - [ ] Create the `*Direct` function in the appropriate file in `mcp-server/src/core/direct-functions/` - [ ] Import silent mode utilities and implement them around core function calls - [ ] Handle all parameter validations and type conversions - [ ] Implement path resolving for relative paths - [ ] Add appropriate error handling with standardized error codes - [ ] Add to imports/exports in `task-master-core.js` 3. **MCP Tool Implementation**: - [ ] Create new file in `mcp-server/src/tools/` with kebab-case naming - [ ] Define zod schema for all parameters - [ ] Implement the `execute` method following the standard pattern - [ ] Consider using AsyncOperationManager for long-running operations - [ ] Register tool in `mcp-server/src/tools/index.js` 4. **Testing**: - [ ] Write unit tests for the direct function wrapper - [ ] Write integration tests for the MCP tool ## Standard Error Codes - **Standard Error Codes**: Use consistent error codes across direct function wrappers - `INPUT_VALIDATION_ERROR`: For missing or invalid required parameters - `FILE_NOT_FOUND_ERROR`: For file system path issues - `CORE_FUNCTION_ERROR`: For errors thrown by the core function - `UNEXPECTED_ERROR`: For all other unexpected errors - **Error Object Structure**: ```javascript { success: false, error: { code: 'ERROR_CODE', message: 'Human-readable error message' }, fromCache: false } ``` - **MCP Tool Logging Pattern**: - ✅ DO: Log the start of execution with arguments (sanitized if sensitive) - ✅ DO: Log successful completion with result summary - ✅ DO: Log all error conditions with appropriate log levels - ✅ DO: Include the cache status in result logs - ❌ DON'T: Log entire large data structures or sensitive information - The MCP server integrates with Task Master core functions through three layers: 1. Tool Definitions (`mcp-server/src/tools/*.js`) - Define parameters and validation 2. Direct Functions (`mcp-server/src/core/direct-functions/*.js`) - Handle core logic integration 3. Core Functions (`scripts/modules/*.js`) - Implement the actual functionality - This layered approach provides: - Clear separation of concerns - Consistent parameter validation - Centralized error handling - Performance optimization through caching (for read operations) - Standardized response formatting ## MCP Naming Conventions - **Files and Directories**: - ✅ DO: Use **kebab-case** for all file names: `list-tasks.js`, `set-task-status.js` - ✅ DO: Use consistent directory structure: `mcp-server/src/tools/` for tool definitions, `mcp-server/src/core/direct-functions/` for direct function implementations - **JavaScript Functions**: - ✅ DO: Use **camelCase** with `Direct` suffix for direct function implementations: `listTasksDirect`, `setTaskStatusDirect` - ✅ DO: Use **camelCase** with `Tool` suffix for tool registration functions: `registerListTasksTool`, `registerSetTaskStatusTool` - ✅ DO: Use consistent action function naming inside direct functions: `coreActionFn` or similar descriptive name - **MCP Tool Names**: - ✅ DO: Use **snake_case** for tool names exposed to MCP clients: `list_tasks`, `set_task_status`, `parse_prd_document` - ✅ DO: Include the core action in the tool name without redundant words: Use `list_tasks` instead of `list_all_tasks` - **Examples**: - File: `list-tasks.js` - Direct Function: `listTasksDirect` - Tool Registration: `registerListTasksTool` - MCP Tool Name: `list_tasks` - **Mapping**: - The `directFunctions` map in `task-master-core.js` maps the core function name (in camelCase) to its direct implementation: ```javascript export const directFunctions = { list: listTasksDirect, setStatus: setTaskStatusDirect, // Add more functions as implemented }; ```