#
tokens: 17022/50000 12/12 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── GUIDE.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── client.ts
│   ├── config.ts
│   ├── index.ts
│   ├── models
│   │   └── Todo.ts
│   ├── services
│   │   ├── DatabaseService.ts
│   │   └── TodoService.ts
│   └── utils
│       └── formatters.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | dist/
3 | contexts/
4 | .specstory/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Todo List MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server that provides a comprehensive API for managing todo items.
  4 | 
  5 | <a href="https://glama.ai/mcp/servers/kh39rjpplx">
  6 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/kh39rjpplx/badge" alt="Todo List Server MCP server" />
  7 | </a>
  8 | 
  9 | > **📚 Learning Resource**: This project is designed as an educational example of MCP implementation. See [GUIDE.md](GUIDE.md) for a comprehensive explanation of how the project works and why things are implemented the way they are.
 10 | 
 11 | ## Features
 12 | 
 13 | - **Create todos**: Add new tasks with title and markdown description
 14 | - **Update todos**: Modify existing tasks
 15 | - **Complete todos**: Mark tasks as done
 16 | - **Delete todos**: Remove tasks from the list
 17 | - **Search todos**: Find tasks by title or creation date
 18 | - **Summarize todos**: Get a quick overview of active tasks
 19 | 
 20 | ## Tools
 21 | 
 22 | This MCP server exposes the following tools:
 23 | 
 24 | 1. `create-todo`: Create a new todo item
 25 | 2. `list-todos`: List all todos
 26 | 3. `get-todo`: Get a specific todo by ID
 27 | 4. `update-todo`: Update a todo's title or description
 28 | 5. `complete-todo`: Mark a todo as completed
 29 | 6. `delete-todo`: Delete a todo
 30 | 7. `search-todos-by-title`: Search todos by title (case-insensitive partial match)
 31 | 8. `search-todos-by-date`: Search todos by creation date (format: YYYY-MM-DD)
 32 | 9. `list-active-todos`: List all non-completed todos
 33 | 10. `summarize-active-todos`: Generate a summary of all active (non-completed) todos
 34 | 
 35 | ## Installation
 36 | 
 37 | ```bash
 38 | # Clone the repository
 39 | git clone https://github.com/RegiByte/todo-list-mcp.git
 40 | cd todo-list-mcp
 41 | 
 42 | # Install dependencies
 43 | npm install
 44 | 
 45 | # Build the project
 46 | npm run build
 47 | ```
 48 | 
 49 | ## Usage
 50 | 
 51 | ### Starting the Server
 52 | 
 53 | ```bash
 54 | npm start
 55 | ```
 56 | 
 57 | ### Configuring with Claude for Desktop
 58 | 
 59 | #### Claude Desktop
 60 | 
 61 | Add this to your `claude_desktop_config.json`:
 62 | 
 63 | ```json
 64 | {
 65 |   "mcpServers": {
 66 |     "todo": {
 67 |       "command": "node",
 68 |       "args": ["/absolute/path/to/todo-list-mcp/dist/index.js"]
 69 |     }
 70 |   }
 71 | }
 72 | ```
 73 | 
 74 | #### Cursor
 75 | 
 76 | - Go to "Cursor Settings" -> MCP
 77 | - Add a new MCP server with a "command" type
 78 | - Add the absolute path of the server and run it with node
 79 | - Example: node /absolute/path/to/todo-list-mcp/dist/index.js
 80 | 
 81 | ### Example Commands
 82 | 
 83 | When using with Claude for Desktop or Cursor, you can try:
 84 | 
 85 | - "Create a todo to learn MCP with a description explaining why MCP is useful"
 86 | - "List all my active todos"
 87 | - "Create a todo for tomorrow's meeting with details about the agenda in markdown"
 88 | - "Mark my learning MCP todo as completed"
 89 | - "Summarize all my active todos"
 90 | 
 91 | ## Project Structure
 92 | 
 93 | This project follows a clear separation of concerns to make the code easy to understand:
 94 | 
 95 | ```
 96 | src/
 97 | ├── models/       # Data structures and validation schemas
 98 | ├── services/     # Business logic and database operations
 99 | ├── utils/        # Helper functions and formatters
100 | ├── config.ts     # Configuration settings
101 | ├── client.ts     # Test client for local testing
102 | └── index.ts      # Main entry point with MCP tool definitions
103 | ```
104 | 
105 | ## Learning from This Project
106 | 
107 | This project is designed as an educational resource. To get the most out of it:
108 | 
109 | 1. Read the [GUIDE.md](GUIDE.md) for a comprehensive explanation of the design
110 | 2. Study the heavily commented source code to understand implementation details
111 | 3. Use the test client to see how the server works in practice
112 | 4. Experiment with adding your own tools or extending the existing ones
113 | 
114 | ## Development
115 | 
116 | ### Building
117 | 
118 | ```bash
119 | npm run build
120 | ```
121 | 
122 | ### Running in Development Mode
123 | 
124 | ```bash
125 | npm run dev
126 | ```
127 | 
128 | ## License
129 | 
130 | MIT
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./dist",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | } 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "todo-list-mcp",
 3 |   "version": "1.0.0",
 4 |   "main": "dist/index.js",
 5 |   "type": "module",
 6 |   "scripts": {
 7 |     "build": "tsc",
 8 |     "start": "node dist/index.js",
 9 |     "dev": "npm run build && npm start",
10 |     "test": "npm run build && node dist/client.js",
11 |     "inspector": "npx @modelcontextprotocol/inspector node dist/index.js"
12 |   },
13 |   "keywords": [
14 |     "mcp",
15 |     "todo",
16 |     "api"
17 |   ],
18 |   "author": "",
19 |   "license": "ISC",
20 |   "description": "Todo list MCP server",
21 |   "dependencies": {
22 |     "@modelcontextprotocol/sdk": "^1.6.1",
23 |     "better-sqlite3": "^9.4.1",
24 |     "typescript": "^5.3.3",
25 |     "uuid": "^9.0.1",
26 |     "zod": "^3.22.4"
27 |   },
28 |   "devDependencies": {
29 |     "@modelcontextprotocol/inspector": "^0.5.1",
30 |     "@types/better-sqlite3": "^7.6.9",
31 |     "@types/node": "^20.11.28",
32 |     "@types/uuid": "^9.0.8"
33 |   }
34 | }
35 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * config.ts
 3 |  * 
 4 |  * This file manages the application configuration settings.
 5 |  * It provides a centralized place for all configuration values,
 6 |  * making them easier to change and maintain.
 7 |  * 
 8 |  * WHY A SEPARATE CONFIG FILE?
 9 |  * - Single source of truth for configuration values
10 |  * - Easy to update settings without searching through the codebase
11 |  * - Allows for environment-specific overrides
12 |  * - Makes configuration values available throughout the application
13 |  */
14 | import path from 'path';
15 | import os from 'os';
16 | import fs from 'fs';
17 | 
18 | /**
19 |  * Database configuration defaults
20 |  * 
21 |  * We use the user's home directory for database storage by default,
22 |  * which provides several advantages:
23 |  * - Works across different operating systems
24 |  * - Available without special permissions
25 |  * - Persists across application restarts
26 |  * - Doesn't get deleted when updating the application
27 |  */
28 | const DEFAULT_DB_FOLDER = path.join(os.homedir(), '.todo-list-mcp');
29 | const DEFAULT_DB_FILE = 'todos.sqlite';
30 | 
31 | /**
32 |  * Application configuration object
33 |  * 
34 |  * This object provides access to all configuration settings.
35 |  * It uses environment variables when available, falling back to defaults.
36 |  * 
37 |  * WHY USE ENVIRONMENT VARIABLES?
38 |  * - Allows configuration without changing code
39 |  * - Follows the 12-factor app methodology for configuration
40 |  * - Enables different settings per environment (dev, test, prod)
41 |  * - Keeps sensitive information out of the code
42 |  */
43 | export const config = {
44 |   db: {
45 |     // Allow overriding through environment variables
46 |     folder: process.env.TODO_DB_FOLDER || DEFAULT_DB_FOLDER,
47 |     filename: process.env.TODO_DB_FILE || DEFAULT_DB_FILE,
48 |     
49 |     /**
50 |      * Full path to the database file
51 |      * 
52 |      * This getter computes the complete path dynamically,
53 |      * ensuring consistency even if the folder or filename change.
54 |      */
55 |     get path() {
56 |       return path.join(this.folder, this.filename);
57 |     }
58 |   }
59 | };
60 | 
61 | /**
62 |  * Ensure the database folder exists
63 |  * 
64 |  * This utility function makes sure the folder for the database file exists,
65 |  * creating it if necessary. This prevents errors when trying to open the
66 |  * database file in a non-existent directory.
67 |  */
68 | export function ensureDbFolder() {
69 |   if (!fs.existsSync(config.db.folder)) {
70 |     fs.mkdirSync(config.db.folder, { recursive: true });
71 |   }
72 | } 
```

--------------------------------------------------------------------------------
/src/services/DatabaseService.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * DatabaseService.ts
 3 |  * 
 4 |  * This file implements a lightweight SQLite database service for the Todo application.
 5 |  * 
 6 |  * WHY SQLITE?
 7 |  * - SQLite is perfect for small to medium applications like this one
 8 |  * - Requires no separate database server (file-based)
 9 |  * - ACID compliant and reliable
10 |  * - Minimal configuration required
11 |  * - Easy to install with minimal dependencies
12 |  */
13 | import Database from 'better-sqlite3';
14 | import { config, ensureDbFolder } from '../config.js';
15 | 
16 | /**
17 |  * DatabaseService Class
18 |  * 
19 |  * This service manages the SQLite database connection and schema.
20 |  * It follows the singleton pattern to ensure only one database connection exists.
21 |  * 
22 |  * WHY SINGLETON PATTERN?
23 |  * - Prevents multiple database connections which could lead to conflicts
24 |  * - Provides a central access point to the database throughout the application
25 |  * - Makes it easier to manage connection lifecycle (open/close)
26 |  */
27 | class DatabaseService {
28 |   private db: Database.Database;
29 | 
30 |   constructor() {
31 |     // Ensure the database folder exists before trying to create the database
32 |     ensureDbFolder();
33 |     
34 |     // Initialize the database with the configured path
35 |     this.db = new Database(config.db.path);
36 |     
37 |     /**
38 |      * Set pragmas for performance and safety:
39 |      * - WAL (Write-Ahead Logging): Improves concurrent access performance
40 |      * - foreign_keys: Ensures referential integrity (useful for future expansion)
41 |      */
42 |     this.db.pragma('journal_mode = WAL');
43 |     this.db.pragma('foreign_keys = ON');
44 |     
45 |     // Initialize the database schema when service is created
46 |     this.initSchema();
47 |   }
48 | 
49 |   /**
50 |    * Initialize the database schema
51 |    * 
52 |    * This creates the todos table if it doesn't already exist.
53 |    * The schema design incorporates:
54 |    * - TEXT primary key for UUID compatibility
55 |    * - NULL completedAt to represent incomplete todos
56 |    * - Timestamp fields for tracking creation and updates
57 |    */
58 |   private initSchema(): void {
59 |     // Create todos table if it doesn't exist
60 |     this.db.exec(`
61 |       CREATE TABLE IF NOT EXISTS todos (
62 |         id TEXT PRIMARY KEY,
63 |         title TEXT NOT NULL,
64 |         description TEXT NOT NULL,
65 |         completedAt TEXT NULL, -- ISO timestamp, NULL if not completed
66 |         createdAt TEXT NOT NULL,
67 |         updatedAt TEXT NOT NULL
68 |       )
69 |     `);
70 |   }
71 | 
72 |   /**
73 |    * Get the database instance
74 |    * 
75 |    * This allows other services to access the database for operations.
76 |    * 
77 |    * @returns The SQLite database instance
78 |    */
79 |   getDb(): Database.Database {
80 |     return this.db;
81 |   }
82 | 
83 |   /**
84 |    * Close the database connection
85 |    * 
86 |    * This should be called when shutting down the application to ensure
87 |    * all data is properly saved and resources are released.
88 |    */
89 |   close(): void {
90 |     this.db.close();
91 |   }
92 | }
93 | 
94 | // Create a singleton instance that will be used throughout the application
95 | export const databaseService = new DatabaseService(); 
```

--------------------------------------------------------------------------------
/src/utils/formatters.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * formatters.ts
  3 |  * 
  4 |  * This file contains utility functions for formatting data in the application.
  5 |  * These utilities handle the transformation of internal data structures into
  6 |  * human-readable formats appropriate for display to LLMs and users.
  7 |  * 
  8 |  * WHY SEPARATE FORMATTERS?
  9 |  * - Keeps formatting logic separate from business logic
 10 |  * - Allows consistent formatting across the application
 11 |  * - Makes it easier to change display formats without affecting core functionality
 12 |  * - Centralizes presentation concerns in one place
 13 |  */
 14 | import { Todo } from "../models/Todo.js";
 15 | 
 16 | /**
 17 |  * Format a todo item to a readable string representation
 18 |  * 
 19 |  * This formatter converts a Todo object into a markdown-formatted string
 20 |  * with clear visual indicators for completion status (emojis).
 21 |  * 
 22 |  * WHY USE MARKDOWN?
 23 |  * - Provides structured, readable output
 24 |  * - Works well with LLMs which understand markdown syntax
 25 |  * - Allows rich formatting like headers, lists, and emphasis
 26 |  * - Can be displayed directly in many UI contexts
 27 |  * 
 28 |  * @param todo The Todo object to format
 29 |  * @returns A markdown-formatted string representation
 30 |  */
 31 | export function formatTodo(todo: Todo): string {
 32 |   return `
 33 | ## ${todo.title} ${todo.completed ? '✅' : '⏳'}
 34 | 
 35 | ID: ${todo.id}
 36 | Created: ${new Date(todo.createdAt).toLocaleString()}
 37 | Updated: ${new Date(todo.updatedAt).toLocaleString()}
 38 | 
 39 | ${todo.description}
 40 |   `.trim();
 41 | }
 42 | 
 43 | /**
 44 |  * Format a list of todos to a readable string representation
 45 |  * 
 46 |  * This formatter takes an array of Todo objects and creates a complete
 47 |  * markdown document with a title and formatted entries.
 48 |  * 
 49 |  * @param todos Array of Todo objects to format
 50 |  * @returns A markdown-formatted string with the complete list
 51 |  */
 52 | export function formatTodoList(todos: Todo[]): string {
 53 |   if (todos.length === 0) {
 54 |     return "No todos found.";
 55 |   }
 56 | 
 57 |   const todoItems = todos.map(formatTodo).join('\n\n---\n\n');
 58 |   return `# Todo List (${todos.length} items)\n\n${todoItems}`;
 59 | }
 60 | 
 61 | /**
 62 |  * Create success response for MCP tool calls
 63 |  * 
 64 |  * This utility formats successful responses according to the MCP protocol.
 65 |  * It wraps the message in the expected content structure.
 66 |  * 
 67 |  * WHY THIS FORMAT?
 68 |  * - Follows the MCP protocol's expected response structure
 69 |  * - Allows the message to be properly displayed by MCP clients
 70 |  * - Clearly indicates success status
 71 |  * 
 72 |  * @param message The success message to include
 73 |  * @returns A properly formatted MCP response object
 74 |  */
 75 | export function createSuccessResponse(message: string) {
 76 |   return {
 77 |     content: [
 78 |       {
 79 |         type: "text" as const,
 80 |         text: message,
 81 |       },
 82 |     ],
 83 |   };
 84 | }
 85 | 
 86 | /**
 87 |  * Create error response for MCP tool calls
 88 |  * 
 89 |  * This utility formats error responses according to the MCP protocol.
 90 |  * It includes the isError flag to indicate failure.
 91 |  * 
 92 |  * @param message The error message to include
 93 |  * @returns A properly formatted MCP error response object
 94 |  */
 95 | export function createErrorResponse(message: string) {
 96 |   return {
 97 |     content: [
 98 |       {
 99 |         type: "text" as const,
100 |         text: message,
101 |       },
102 |     ],
103 |     isError: true,
104 |   };
105 | } 
```

--------------------------------------------------------------------------------
/src/models/Todo.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Todo.ts
  3 |  * 
  4 |  * This file defines the core data model for our Todo application, along with validation
  5 |  * schemas and a factory function for creating new Todo instances.
  6 |  * 
  7 |  * WHY USE ZOD?
  8 |  * - Zod provides runtime type validation, ensuring our data meets specific requirements
  9 |  * - Using schemas creates a clear contract for each operation's input requirements
 10 |  * - Error messages are automatically generated with clear validation feedback
 11 |  * - TypeScript integration gives us both compile-time and runtime type safety
 12 |  * - Schemas can be converted to JSON Schema, which is useful for MCP clients
 13 |  */
 14 | import { z } from 'zod';
 15 | import { v4 as uuidv4 } from 'uuid';
 16 | 
 17 | /**
 18 |  * Todo Interface
 19 |  * 
 20 |  * This defines the structure of a Todo item in our application.
 21 |  * We've designed it with several important considerations:
 22 |  * - IDs use UUID for uniqueness across systems
 23 |  * - Timestamps track creation and updates for data lifecycle management
 24 |  * - Description supports markdown for rich text formatting
 25 |  * - Completion status is tracked both as a boolean flag and with a timestamp
 26 |  */
 27 | export interface Todo {
 28 |   id: string;
 29 |   title: string;
 30 |   description: string; // Markdown format
 31 |   completed: boolean; // Computed from completedAt for backward compatibility
 32 |   completedAt: string | null; // ISO timestamp when completed, null if not completed
 33 |   createdAt: string;
 34 |   updatedAt: string;
 35 | }
 36 | 
 37 | /**
 38 |  * Input Validation Schemas
 39 |  * 
 40 |  * These schemas define the requirements for different operations.
 41 |  * Each schema serves as both documentation and runtime validation.
 42 |  * 
 43 |  * WHY SEPARATE SCHEMAS?
 44 |  * - Different operations have different validation requirements
 45 |  * - Keeps validation focused on only what's needed for each operation
 46 |  * - Makes the API more intuitive by clearly defining what each operation expects
 47 |  */
 48 | 
 49 | // Schema for creating a new todo - requires title and description
 50 | export const CreateTodoSchema = z.object({
 51 |   title: z.string().min(1, "Title is required"),
 52 |   description: z.string().min(1, "Description is required"),
 53 | });
 54 | 
 55 | // Schema for updating a todo - requires ID, title and description are optional
 56 | export const UpdateTodoSchema = z.object({
 57 |   id: z.string().uuid("Invalid Todo ID"),
 58 |   title: z.string().min(1, "Title is required").optional(),
 59 |   description: z.string().min(1, "Description is required").optional(),
 60 | });
 61 | 
 62 | // Schema for completing a todo - requires only ID
 63 | export const CompleteTodoSchema = z.object({
 64 |   id: z.string().uuid("Invalid Todo ID"),
 65 | });
 66 | 
 67 | // Schema for deleting a todo - requires only ID
 68 | export const DeleteTodoSchema = z.object({
 69 |   id: z.string().uuid("Invalid Todo ID"),
 70 | });
 71 | 
 72 | // Schema for searching todos by title - requires search term
 73 | export const SearchTodosByTitleSchema = z.object({
 74 |   title: z.string().min(1, "Search term is required"),
 75 | });
 76 | 
 77 | // Schema for searching todos by date - requires date in YYYY-MM-DD format
 78 | export const SearchTodosByDateSchema = z.object({
 79 |   date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format"),
 80 | });
 81 | 
 82 | /**
 83 |  * Factory Function: createTodo
 84 |  * 
 85 |  * WHY USE A FACTORY FUNCTION?
 86 |  * - Centralizes the creation logic in one place
 87 |  * - Ensures all required fields are set with proper default values
 88 |  * - Guarantees all todos have the same structure
 89 |  * - Makes it easy to change the implementation without affecting code that creates todos
 90 |  * 
 91 |  * @param data The validated input data
 92 |  * @returns A fully formed Todo object with generated ID and timestamps
 93 |  */
 94 | export function createTodo(data: z.infer<typeof CreateTodoSchema>): Todo {
 95 |   const now = new Date().toISOString();
 96 |   return {
 97 |     id: uuidv4(),
 98 |     title: data.title,
 99 |     description: data.description,
100 |     completed: false,
101 |     completedAt: null,
102 |     createdAt: now,
103 |     updatedAt: now,
104 |   };
105 | } 
```

--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * client.ts
  3 |  * 
  4 |  * This file implements a test client for the Todo MCP server.
  5 |  * It demonstrates how to connect to the server, call various tools,
  6 |  * and handle the responses.
  7 |  * 
  8 |  * WHY HAVE A TEST CLIENT?
  9 |  * - Validates that the server works correctly
 10 |  * - Provides a working example of how to use the MCP client SDK
 11 |  * - Makes it easy to test changes without needing an LLM
 12 |  * - Serves as documentation for how to interact with the server
 13 |  */
 14 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
 15 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
 16 | 
 17 | /**
 18 |  * Response content type definition
 19 |  * 
 20 |  * The MCP protocol returns content as an array of typed objects.
 21 |  * This interface defines the structure of text content items.
 22 |  */
 23 | interface ContentText {
 24 |   type: "text";
 25 |   text: string;
 26 | }
 27 | 
 28 | /**
 29 |  * Main function that runs the test client
 30 |  * 
 31 |  * This function:
 32 |  * 1. Connects to the Todo MCP server
 33 |  * 2. Demonstrates all the available tools
 34 |  * 3. Creates, updates, completes, and deletes a test todo
 35 |  */
 36 | async function main() {
 37 |   console.log("Starting Todo MCP Test Client...");
 38 | 
 39 |   try {
 40 |     /**
 41 |      * Create a client transport to the server
 42 |      * 
 43 |      * The StdioClientTransport launches the server as a child process
 44 |      * and communicates with it via standard input/output.
 45 |      * 
 46 |      * WHY STDIO TRANSPORT?
 47 |      * - Simple to set up and use
 48 |      * - Works well for local testing
 49 |      * - Doesn't require network configuration
 50 |      * - Similar to how Claude Desktop launches MCP servers
 51 |      */
 52 |     const transport = new StdioClientTransport({
 53 |       command: "node",
 54 |       args: ["dist/index.js"],
 55 |     });
 56 | 
 57 |     /**
 58 |      * Create and connect the client
 59 |      * 
 60 |      * We configure the client with basic identity information
 61 |      * and the capabilities it needs (tools in this case).
 62 |      */
 63 |     const client = new Client(
 64 |       {
 65 |         name: "todo-test-client",
 66 |         version: "1.0.0",
 67 |       },
 68 |       {
 69 |         capabilities: {
 70 |           tools: {}
 71 |         }
 72 |       }
 73 |     );
 74 | 
 75 |     // Connect to the server through the transport
 76 |     await client.connect(transport);
 77 |     console.log("Connected to Todo MCP Server");
 78 | 
 79 |     /**
 80 |      * List available tools
 81 |      * 
 82 |      * This demonstrates how to query what tools the server provides,
 83 |      * which is useful for discovery and documentation.
 84 |      */
 85 |     const toolsResult = await client.listTools();
 86 |     console.log("\nAvailable tools:", toolsResult.tools.map(tool => tool.name));
 87 | 
 88 |     /**
 89 |      * Create a test todo
 90 |      * 
 91 |      * This demonstrates the create-todo tool, which takes a title
 92 |      * and markdown description as arguments.
 93 |      */
 94 |     console.log("\nCreating a test todo...");
 95 |     const createTodoResult = await client.callTool({
 96 |       name: "create-todo",
 97 |       arguments: {
 98 |         title: "Learn about MCP",
 99 |         description: "# Model Context Protocol\n\n- Understand core concepts\n- Build a simple server\n- Test with Claude"
100 |       }
101 |     });
102 |     
103 |     // Type assertion to access the content
104 |     const createContent = createTodoResult.content as ContentText[];
105 |     console.log(createContent[0].text);
106 | 
107 |     /**
108 |      * Extract the todo ID from the response
109 |      * 
110 |      * We use a simple regex to parse the ID from the formatted response.
111 |      * In a real application, you might want a more structured response format.
112 |      */
113 |     const idMatch = createContent[0].text.match(/ID: ([0-9a-f-]+)/);
114 |     const todoId = idMatch ? idMatch[1] : null;
115 | 
116 |     // Only proceed if we successfully created a todo and extracted its ID
117 |     if (todoId) {
118 |       /**
119 |        * List all todos
120 |        * 
121 |        * This demonstrates the list-todos tool, which takes no arguments
122 |        * and returns a formatted list of all todos.
123 |        */
124 |       console.log("\nListing all todos...");
125 |       const listTodosResult = await client.callTool({
126 |         name: "list-todos",
127 |         arguments: {}
128 |       });
129 |       const listContent = listTodosResult.content as ContentText[];
130 |       console.log(listContent[0].text);
131 | 
132 |       /**
133 |        * Update the todo
134 |        * 
135 |        * This demonstrates the update-todo tool, which takes an ID
136 |        * and optional title/description fields to update.
137 |        */
138 |       console.log("\nUpdating the test todo...");
139 |       const updateTodoResult = await client.callTool({
140 |         name: "update-todo",
141 |         arguments: {
142 |           id: todoId,
143 |           description: "# Updated MCP Learning Plan\n\n- Learn MCP core concepts\n- Build a server with tools\n- Connect to Claude\n- Create amazing AI experiences"
144 |         }
145 |       });
146 |       const updateContent = updateTodoResult.content as ContentText[];
147 |       console.log(updateContent[0].text);
148 | 
149 |       /**
150 |        * Mark todo as completed
151 |        * 
152 |        * This demonstrates the complete-todo tool, which takes an ID
153 |        * and marks the corresponding todo as completed.
154 |        */
155 |       console.log("\nCompleting the test todo...");
156 |       const completeTodoResult = await client.callTool({
157 |         name: "complete-todo",
158 |         arguments: {
159 |           id: todoId
160 |         }
161 |       });
162 |       const completeContent = completeTodoResult.content as ContentText[];
163 |       console.log(completeContent[0].text);
164 | 
165 |       /**
166 |        * Summarize active todos
167 |        * 
168 |        * This demonstrates the summarize-active-todos tool, which
169 |        * generates a summary of all non-completed todos.
170 |        */
171 |       console.log("\nSummarizing active todos...");
172 |       const summaryResult = await client.callTool({
173 |         name: "summarize-active-todos",
174 |         arguments: {}
175 |       });
176 |       const summaryContent = summaryResult.content as ContentText[];
177 |       console.log(summaryContent[0].text);
178 | 
179 |       /**
180 |        * Delete the todo
181 |        * 
182 |        * This demonstrates the delete-todo tool, which permanently
183 |        * removes a todo from the database.
184 |        */
185 |       console.log("\nDeleting the test todo...");
186 |       const deleteTodoResult = await client.callTool({
187 |         name: "delete-todo",
188 |         arguments: {
189 |           id: todoId
190 |         }
191 |       });
192 |       const deleteContent = deleteTodoResult.content as ContentText[];
193 |       console.log(deleteContent[0].text);
194 |     }
195 | 
196 |     // Close the client connection
197 |     await client.close();
198 |     console.log("\nTest completed successfully!");
199 |   } catch (error) {
200 |     console.error("Error in test client:", error);
201 |     process.exit(1);
202 |   }
203 | }
204 | 
205 | // Start the test client
206 | main(); 
```

--------------------------------------------------------------------------------
/GUIDE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Todo List MCP Server: A Learning Guide
  2 | 
  3 | ## Introduction to Model Context Protocol (MCP)
  4 | 
  5 | The Model Context Protocol (MCP) is a specification that enables AI models like Claude to interact with external tools and services. It creates a standardized way for LLMs to discover, understand, and use tools provided by separate processes.
  6 | 
  7 | ### Why MCP Matters
  8 | 
  9 | 1. **Extended Capabilities**: MCP allows AI models to perform actions beyond just generating text (database operations, file management, API calls, etc.)
 10 | 2. **Standardization**: Creates a consistent interface for tools regardless of implementation
 11 | 3. **Controlled Access**: Provides a secure way to expose specific functionality to AI models
 12 | 4. **Real-time Integration**: Enables AI to access up-to-date information and perform real-world actions
 13 | 
 14 | ## About This Project
 15 | 
 16 | This Todo List MCP Server is designed to be a clear, educational example of how to build an MCP server. It implements a complete todo list management system that can be used by Claude or other MCP-compatible systems.
 17 | 
 18 | ### Learning Objectives
 19 | 
 20 | By studying this codebase, you can learn:
 21 | 
 22 | 1. How to structure an MCP server project
 23 | 2. How to implement CRUD operations via MCP tools
 24 | 3. Best practices for error handling and validation
 25 | 4. How to format responses for AI consumption
 26 | 5. How the MCP protocol works in practice
 27 | 
 28 | ## Codebase Structure and Design Philosophy
 29 | 
 30 | The project follows several key design principles:
 31 | 
 32 | ### 1. Clear Separation of Concerns
 33 | 
 34 | The codebase is organized into distinct layers:
 35 | 
 36 | - **Models** (`src/models/`): Data structures and validation schemas
 37 | - **Services** (`src/services/`): Business logic and data access
 38 | - **Utils** (`src/utils/`): Helper functions and formatters
 39 | - **Entry Point** (`src/index.ts`): MCP server definition and tool implementations
 40 | 
 41 | This separation makes the code easier to understand, maintain, and extend.
 42 | 
 43 | ### 2. Type Safety and Validation
 44 | 
 45 | The project uses TypeScript and Zod for comprehensive type safety:
 46 | 
 47 | - **TypeScript Interfaces**: Define data structures with static typing
 48 | - **Zod Schemas**: Provide runtime validation with descriptive error messages
 49 | - **Consistent Validation**: Each operation validates its inputs before processing
 50 | 
 51 | ### 3. Error Handling
 52 | 
 53 | A consistent error handling approach is used throughout:
 54 | 
 55 | - **Central Error Processing**: The `safeExecute` function standardizes error handling
 56 | - **Descriptive Error Messages**: All errors provide clear context about what went wrong
 57 | - **Proper Error Responses**: Errors are formatted according to MCP requirements
 58 | 
 59 | ### 4. Data Persistence
 60 | 
 61 | The project uses SQLite for simple but effective data storage:
 62 | 
 63 | - **File-based Database**: Easy to set up with no external dependencies
 64 | - **SQL Operations**: Demonstrates parameterized queries and basic CRUD operations
 65 | - **Singleton Pattern**: Ensures a single database connection throughout the application
 66 | 
 67 | ## Key Implementation Patterns
 68 | 
 69 | ### The Tool Definition Pattern
 70 | 
 71 | Every MCP tool follows the same pattern:
 72 | 
 73 | ```typescript
 74 | server.tool(
 75 |   "tool-name",            // Name: How the tool is identified
 76 |   "Tool description",     // Description: What the tool does
 77 |   { /* parameter schema */ },  // Schema: Expected inputs with validation
 78 |   async (params) => {     // Handler: The implementation function
 79 |     // 1. Validate inputs
 80 |     // 2. Execute business logic
 81 |     // 3. Format and return response
 82 |   }
 83 | );
 84 | ```
 85 | 
 86 | ### Error Handling Pattern
 87 | 
 88 | The error handling pattern ensures consistent behavior:
 89 | 
 90 | ```typescript
 91 | const result = await safeExecute(() => {
 92 |   // Operation that might fail
 93 | }, "Descriptive error message");
 94 | 
 95 | if (result instanceof Error) {
 96 |   return createErrorResponse(result.message);
 97 | }
 98 | 
 99 | return createSuccessResponse(formattedResult);
100 | ```
101 | 
102 | ### Response Formatting Pattern
103 | 
104 | Responses are consistently formatted for easy consumption by LLMs:
105 | 
106 | ```typescript
107 | // Success responses
108 | return createSuccessResponse(`✅ Success message with ${formattedData}`);
109 | 
110 | // Error responses
111 | return createErrorResponse(`Error: ${errorMessage}`);
112 | ```
113 | 
114 | ## How to Learn from This Project
115 | 
116 | ### For Beginners
117 | 
118 | 1. Start by understanding the `Todo` model in `src/models/Todo.ts`
119 | 2. Look at how tools are defined in `src/index.ts`
120 | 3. Explore the basic CRUD operations in `src/services/TodoService.ts`
121 | 4. See how responses are formatted in `src/utils/formatters.ts`
122 | 
123 | ### For Intermediate Developers
124 | 
125 | 1. Study the error handling patterns throughout the codebase
126 | 2. Look at how validation is implemented with Zod schemas
127 | 3. Examine the database operations and SQL queries
128 | 4. Understand how the MCP tools are organized and structured
129 | 
130 | ### For Advanced Developers
131 | 
132 | 1. Consider how this approach could be extended for more complex applications
133 | 2. Think about how to add authentication, caching, or more advanced features
134 | 3. Look at the client implementation to understand the full MCP communication cycle
135 | 4. Consider how to implement testing for an MCP server
136 | 
137 | ## Running and Testing
138 | 
139 | ### Local Testing
140 | 
141 | Use the provided test client to see the server in action:
142 | 
143 | ```bash
144 | npm run build
145 | node dist/client.js
146 | ```
147 | 
148 | This will run through a complete lifecycle of creating, updating, completing, and deleting a todo.
149 | 
150 | ### Integration with Claude for Desktop
151 | 
152 | To use this server with Claude for Desktop, add it to your `claude_desktop_config.json`:
153 | 
154 | ```json
155 | {
156 |   "mcpServers": {
157 |     "todo": {
158 |       "command": "node",
159 |       "args": ["/absolute/path/to/todo-list-mcp/dist/index.js"]
160 |     }
161 |   }
162 | }
163 | ```
164 | 
165 | ## Common Patterns and Best Practices Demonstrated
166 | 
167 | 1. **Singleton Pattern**: Used for database and service access
168 | 2. **Repository Pattern**: Abstracts data access operations
169 | 3. **Factory Pattern**: Creates new Todo objects with consistent structure
170 | 4. **Validation Pattern**: Validates inputs before processing
171 | 5. **Error Handling Pattern**: Centralizes and standardizes error handling
172 | 6. **Formatting Pattern**: Consistently formats outputs for consumption
173 | 7. **Configuration Pattern**: Centralizes application settings
174 | 
175 | ## Conclusion
176 | 
177 | This Todo List MCP Server demonstrates a clean, well-structured approach to building an MCP server. By studying the code and comments, you can gain a deep understanding of how MCP works and how to implement your own MCP servers for various use cases.
178 | 
179 | The project emphasizes not just what code to write, but why specific approaches are taken, making it an excellent learning resource for understanding both MCP and general best practices in TypeScript application development. 
```

--------------------------------------------------------------------------------
/src/services/TodoService.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * TodoService.ts
  3 |  * 
  4 |  * This service implements the core business logic for managing todos.
  5 |  * It acts as an intermediary between the data model and the database,
  6 |  * handling all CRUD operations and search functionality.
  7 |  * 
  8 |  * WHY A SERVICE LAYER?
  9 |  * - Separates business logic from database operations
 10 |  * - Provides a clean API for the application to work with
 11 |  * - Makes it easier to change the database implementation later
 12 |  * - Encapsulates complex operations into simple method calls
 13 |  */
 14 | import { Todo, createTodo, CreateTodoSchema, UpdateTodoSchema } from '../models/Todo.js';
 15 | import { z } from 'zod';
 16 | import { databaseService } from './DatabaseService.js';
 17 | 
 18 | /**
 19 |  * TodoService Class
 20 |  * 
 21 |  * This service follows the repository pattern to provide a clean
 22 |  * interface for working with todos. It encapsulates all database
 23 |  * operations and business logic in one place.
 24 |  */
 25 | class TodoService {
 26 |   /**
 27 |    * Create a new todo
 28 |    * 
 29 |    * This method:
 30 |    * 1. Uses the factory function to create a new Todo object
 31 |    * 2. Persists it to the database
 32 |    * 3. Returns the created Todo
 33 |    * 
 34 |    * @param data Validated input data (title and description)
 35 |    * @returns The newly created Todo
 36 |    */
 37 |   createTodo(data: z.infer<typeof CreateTodoSchema>): Todo {
 38 |     // Use the factory function to create a Todo with proper defaults
 39 |     const todo = createTodo(data);
 40 |     
 41 |     // Get the database instance
 42 |     const db = databaseService.getDb();
 43 |     
 44 |     // Prepare the SQL statement for inserting a new todo
 45 |     const stmt = db.prepare(`
 46 |       INSERT INTO todos (id, title, description, completedAt, createdAt, updatedAt)
 47 |       VALUES (?, ?, ?, ?, ?, ?)
 48 |     `);
 49 |     
 50 |     // Execute the statement with the todo's data
 51 |     stmt.run(
 52 |       todo.id,
 53 |       todo.title,
 54 |       todo.description,
 55 |       todo.completedAt,
 56 |       todo.createdAt,
 57 |       todo.updatedAt
 58 |     );
 59 |     
 60 |     // Return the created todo
 61 |     return todo;
 62 |   }
 63 | 
 64 |   /**
 65 |    * Get a todo by ID
 66 |    * 
 67 |    * This method:
 68 |    * 1. Queries the database for a todo with the given ID
 69 |    * 2. Converts the database row to a Todo object if found
 70 |    * 
 71 |    * @param id The UUID of the todo to retrieve
 72 |    * @returns The Todo if found, undefined otherwise
 73 |    */
 74 |   getTodo(id: string): Todo | undefined {
 75 |     const db = databaseService.getDb();
 76 |     
 77 |     // Use parameterized query to prevent SQL injection
 78 |     const stmt = db.prepare('SELECT * FROM todos WHERE id = ?');
 79 |     const row = stmt.get(id) as any;
 80 |     
 81 |     // Return undefined if no todo was found
 82 |     if (!row) return undefined;
 83 |     
 84 |     // Convert the database row to a Todo object
 85 |     return this.rowToTodo(row);
 86 |   }
 87 | 
 88 |   /**
 89 |    * Get all todos
 90 |    * 
 91 |    * This method returns all todos in the database without filtering.
 92 |    * 
 93 |    * @returns Array of all Todos
 94 |    */
 95 |   getAllTodos(): Todo[] {
 96 |     const db = databaseService.getDb();
 97 |     const stmt = db.prepare('SELECT * FROM todos');
 98 |     const rows = stmt.all() as any[];
 99 |     
100 |     // Convert each database row to a Todo object
101 |     return rows.map(row => this.rowToTodo(row));
102 |   }
103 | 
104 |   /**
105 |    * Get all active (non-completed) todos
106 |    * 
107 |    * This method returns only todos that haven't been marked as completed.
108 |    * A todo is considered active when its completedAt field is NULL.
109 |    * 
110 |    * @returns Array of active Todos
111 |    */
112 |   getActiveTodos(): Todo[] {
113 |     const db = databaseService.getDb();
114 |     const stmt = db.prepare('SELECT * FROM todos WHERE completedAt IS NULL');
115 |     const rows = stmt.all() as any[];
116 |     
117 |     // Convert each database row to a Todo object
118 |     return rows.map(row => this.rowToTodo(row));
119 |   }
120 | 
121 |   /**
122 |    * Update a todo
123 |    * 
124 |    * This method:
125 |    * 1. Checks if the todo exists
126 |    * 2. Updates the specified fields
127 |    * 3. Returns the updated todo
128 |    * 
129 |    * @param data The update data (id required, title/description optional)
130 |    * @returns The updated Todo if found, undefined otherwise
131 |    */
132 |   updateTodo(data: z.infer<typeof UpdateTodoSchema>): Todo | undefined {
133 |     // First check if the todo exists
134 |     const todo = this.getTodo(data.id);
135 |     if (!todo) return undefined;
136 | 
137 |     // Create a timestamp for the update
138 |     const updatedAt = new Date().toISOString();
139 |     
140 |     const db = databaseService.getDb();
141 |     const stmt = db.prepare(`
142 |       UPDATE todos
143 |       SET title = ?, description = ?, updatedAt = ?
144 |       WHERE id = ?
145 |     `);
146 |     
147 |     // Update with new values or keep existing ones if not provided
148 |     stmt.run(
149 |       data.title || todo.title,
150 |       data.description || todo.description,
151 |       updatedAt,
152 |       todo.id
153 |     );
154 |     
155 |     // Return the updated todo
156 |     return this.getTodo(todo.id);
157 |   }
158 | 
159 |   /**
160 |    * Mark a todo as completed
161 |    * 
162 |    * This method:
163 |    * 1. Checks if the todo exists
164 |    * 2. Sets the completedAt timestamp to the current time
165 |    * 3. Returns the updated todo
166 |    * 
167 |    * @param id The UUID of the todo to complete
168 |    * @returns The updated Todo if found, undefined otherwise
169 |    */
170 |   completeTodo(id: string): Todo | undefined {
171 |     // First check if the todo exists
172 |     const todo = this.getTodo(id);
173 |     if (!todo) return undefined;
174 | 
175 |     // Create a timestamp for the completion and update
176 |     const now = new Date().toISOString();
177 |     
178 |     const db = databaseService.getDb();
179 |     const stmt = db.prepare(`
180 |       UPDATE todos
181 |       SET completedAt = ?, updatedAt = ?
182 |       WHERE id = ?
183 |     `);
184 |     
185 |     // Set the completedAt timestamp
186 |     stmt.run(now, now, id);
187 |     
188 |     // Return the updated todo
189 |     return this.getTodo(id);
190 |   }
191 | 
192 |   /**
193 |    * Delete a todo
194 |    * 
195 |    * This method removes a todo from the database permanently.
196 |    * 
197 |    * @param id The UUID of the todo to delete
198 |    * @returns true if deleted, false if not found or not deleted
199 |    */
200 |   deleteTodo(id: string): boolean {
201 |     const db = databaseService.getDb();
202 |     const stmt = db.prepare('DELETE FROM todos WHERE id = ?');
203 |     const result = stmt.run(id);
204 |     
205 |     // Check if any rows were affected
206 |     return result.changes > 0;
207 |   }
208 | 
209 |   /**
210 |    * Search todos by title
211 |    * 
212 |    * This method performs a case-insensitive partial match search
213 |    * on todo titles.
214 |    * 
215 |    * @param title The search term to look for in titles
216 |    * @returns Array of matching Todos
217 |    */
218 |   searchByTitle(title: string): Todo[] {
219 |     // Add wildcards to the search term for partial matching
220 |     const searchTerm = `%${title}%`;
221 |     
222 |     const db = databaseService.getDb();
223 |     
224 |     // COLLATE NOCASE makes the search case-insensitive
225 |     const stmt = db.prepare('SELECT * FROM todos WHERE title LIKE ? COLLATE NOCASE');
226 |     const rows = stmt.all(searchTerm) as any[];
227 |     
228 |     return rows.map(row => this.rowToTodo(row));
229 |   }
230 | 
231 |   /**
232 |    * Search todos by date
233 |    * 
234 |    * This method finds todos created on a specific date.
235 |    * It matches the start of the ISO string with the given date.
236 |    * 
237 |    * @param dateStr The date to search for in YYYY-MM-DD format
238 |    * @returns Array of matching Todos
239 |    */
240 |   searchByDate(dateStr: string): Todo[] {
241 |     // Add wildcard to match the time portion of ISO string
242 |     const datePattern = `${dateStr}%`;
243 |     
244 |     const db = databaseService.getDb();
245 |     const stmt = db.prepare('SELECT * FROM todos WHERE createdAt LIKE ?');
246 |     const rows = stmt.all(datePattern) as any[];
247 |     
248 |     return rows.map(row => this.rowToTodo(row));
249 |   }
250 | 
251 |   /**
252 |    * Generate a summary of active todos
253 |    * 
254 |    * This method creates a markdown-formatted summary of all active todos.
255 |    * 
256 |    * WHY RETURN FORMATTED STRING?
257 |    * - Provides ready-to-display content for the MCP client
258 |    * - Encapsulates formatting logic in the service
259 |    * - Makes it easy for LLMs to present a readable summary
260 |    * 
261 |    * @returns Markdown-formatted summary string
262 |    */
263 |   summarizeActiveTodos(): string {
264 |     const activeTodos = this.getActiveTodos();
265 |     
266 |     // Handle the case when there are no active todos
267 |     if (activeTodos.length === 0) {
268 |       return "No active todos found.";
269 |     }
270 |     
271 |     // Create a bulleted list of todo titles
272 |     const summary = activeTodos.map(todo => `- ${todo.title}`).join('\n');
273 |     return `# Active Todos Summary\n\nThere are ${activeTodos.length} active todos:\n\n${summary}`;
274 |   }
275 |   
276 |   /**
277 |    * Helper to convert a database row to a Todo object
278 |    * 
279 |    * This private method handles the conversion between the database
280 |    * representation and the application model.
281 |    * 
282 |    * WHY SEPARATE THIS LOGIC?
283 |    * - Avoids repeating the conversion code in multiple methods
284 |    * - Creates a single place to update if the model changes
285 |    * - Isolates database-specific knowledge from the rest of the code
286 |    * 
287 |    * @param row The database row data
288 |    * @returns A properly formatted Todo object
289 |    */
290 |   private rowToTodo(row: any): Todo {
291 |     return {
292 |       id: row.id,
293 |       title: row.title,
294 |       description: row.description,
295 |       completedAt: row.completedAt,
296 |       completed: row.completedAt !== null, // Computed from completedAt
297 |       createdAt: row.createdAt,
298 |       updatedAt: row.updatedAt
299 |     };
300 |   }
301 | }
302 | 
303 | // Create a singleton instance for use throughout the application
304 | export const todoService = new TodoService(); 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * index.ts
  3 |  * 
  4 |  * This is the main entry point for the Todo MCP server.
  5 |  * It defines all the tools provided by the server and handles
  6 |  * connecting to clients.
  7 |  * 
  8 |  * WHAT IS MCP?
  9 |  * The Model Context Protocol (MCP) allows AI models like Claude
 10 |  * to interact with external tools and services. This server implements
 11 |  * the MCP specification to provide a Todo list functionality that
 12 |  * Claude can use.
 13 |  * 
 14 |  * HOW THE SERVER WORKS:
 15 |  * 1. It creates an MCP server instance with identity information
 16 |  * 2. It defines a set of tools for managing todos
 17 |  * 3. It connects to a transport (stdio in this case)
 18 |  * 4. It handles incoming tool calls from clients (like Claude)
 19 |  */
 20 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 21 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 22 | import { z } from "zod";
 23 | 
 24 | // Import models and schemas
 25 | import {
 26 |   CreateTodoSchema,
 27 |   UpdateTodoSchema,
 28 |   CompleteTodoSchema,
 29 |   DeleteTodoSchema,
 30 |   SearchTodosByTitleSchema,
 31 |   SearchTodosByDateSchema
 32 | } from "./models/Todo.js";
 33 | 
 34 | // Import services
 35 | import { todoService } from "./services/TodoService.js";
 36 | import { databaseService } from "./services/DatabaseService.js";
 37 | 
 38 | // Import utilities
 39 | import { createSuccessResponse, createErrorResponse, formatTodo, formatTodoList } from "./utils/formatters.js";
 40 | import { config } from "./config.js";
 41 | 
 42 | /**
 43 |  * Create the MCP server
 44 |  * 
 45 |  * We initialize with identity information that helps clients
 46 |  * understand what they're connecting to.
 47 |  */
 48 | const server = new McpServer({
 49 |   name: "Todo-MCP-Server",
 50 |   version: "1.0.0",
 51 | });
 52 | 
 53 | /**
 54 |  * Helper function to safely execute operations
 55 |  * 
 56 |  * This function:
 57 |  * 1. Attempts to execute an operation
 58 |  * 2. Catches any errors
 59 |  * 3. Returns either the result or an Error object
 60 |  * 
 61 |  * WHY USE THIS PATTERN?
 62 |  * - Centralizes error handling
 63 |  * - Prevents crashes from uncaught exceptions
 64 |  * - Makes error reporting consistent across all tools
 65 |  * - Simplifies the tool implementations
 66 |  * 
 67 |  * @param operation The function to execute
 68 |  * @param errorMessage The message to include if an error occurs
 69 |  * @returns Either the operation result or an Error
 70 |  */
 71 | async function safeExecute<T>(operation: () => T, errorMessage: string) {
 72 |   try {
 73 |     const result = operation();
 74 |     return result;
 75 |   } catch (error) {
 76 |     console.error(errorMessage, error);
 77 |     if (error instanceof Error) {
 78 |       return new Error(`${errorMessage}: ${error.message}`);
 79 |     }
 80 |     return new Error(errorMessage);
 81 |   }
 82 | }
 83 | 
 84 | /**
 85 |  * Tool 1: Create a new todo
 86 |  * 
 87 |  * This tool:
 88 |  * 1. Validates the input (title and description)
 89 |  * 2. Creates a new todo using the service
 90 |  * 3. Returns the formatted todo
 91 |  * 
 92 |  * PATTERN FOR ALL TOOLS:
 93 |  * - Register with server.tool()
 94 |  * - Define name, description, and parameter schema
 95 |  * - Implement the async handler function
 96 |  * - Use safeExecute for error handling
 97 |  * - Return properly formatted response
 98 |  */
 99 | server.tool(
100 |   "create-todo",
101 |   "Create a new todo item",
102 |   {
103 |     title: z.string().min(1, "Title is required"),
104 |     description: z.string().min(1, "Description is required"),
105 |   },
106 |   async ({ title, description }) => {
107 |     const result = await safeExecute(() => {
108 |       const validatedData = CreateTodoSchema.parse({ title, description });
109 |       const newTodo = todoService.createTodo(validatedData);
110 |       return formatTodo(newTodo);
111 |     }, "Failed to create todo");
112 | 
113 |     if (result instanceof Error) {
114 |       return createErrorResponse(result.message);
115 |     }
116 | 
117 |     return createSuccessResponse(`✅ Todo Created:\n\n${result}`);
118 |   }
119 | );
120 | 
121 | /**
122 |  * Tool 2: List all todos
123 |  * 
124 |  * This tool:
125 |  * 1. Retrieves all todos from the service
126 |  * 2. Formats them as a list
127 |  * 3. Returns the formatted list
128 |  */
129 | server.tool(
130 |   "list-todos",
131 |   "List all todos",
132 |   {},
133 |   async () => {
134 |     const result = await safeExecute(() => {
135 |       const todos = todoService.getAllTodos();
136 |       return formatTodoList(todos);
137 |     }, "Failed to list todos");
138 | 
139 |     if (result instanceof Error) {
140 |       return createErrorResponse(result.message);
141 |     }
142 | 
143 |     return createSuccessResponse(result);
144 |   }
145 | );
146 | 
147 | /**
148 |  * Tool 3: Get a specific todo by ID
149 |  * 
150 |  * This tool:
151 |  * 1. Validates the input ID
152 |  * 2. Retrieves the specific todo
153 |  * 3. Returns the formatted todo
154 |  */
155 | server.tool(
156 |   "get-todo",
157 |   "Get a specific todo by ID",
158 |   {
159 |     id: z.string().uuid("Invalid Todo ID"),
160 |   },
161 |   async ({ id }) => {
162 |     const result = await safeExecute(() => {
163 |       const todo = todoService.getTodo(id);
164 |       if (!todo) {
165 |         throw new Error(`Todo with ID ${id} not found`);
166 |       }
167 |       return formatTodo(todo);
168 |     }, "Failed to get todo");
169 | 
170 |     if (result instanceof Error) {
171 |       return createErrorResponse(result.message);
172 |     }
173 | 
174 |     return createSuccessResponse(result);
175 |   }
176 | );
177 | 
178 | /**
179 |  * Tool 4: Update a todo
180 |  * 
181 |  * This tool:
182 |  * 1. Validates the input (id required, title/description optional)
183 |  * 2. Ensures at least one field is being updated
184 |  * 3. Updates the todo using the service
185 |  * 4. Returns the formatted updated todo
186 |  */
187 | server.tool(
188 |   "update-todo",
189 |   "Update a todo title or description",
190 |   {
191 |     id: z.string().uuid("Invalid Todo ID"),
192 |     title: z.string().min(1, "Title is required").optional(),
193 |     description: z.string().min(1, "Description is required").optional(),
194 |   },
195 |   async ({ id, title, description }) => {
196 |     const result = await safeExecute(() => {
197 |       const validatedData = UpdateTodoSchema.parse({ id, title, description });
198 |       
199 |       // Ensure at least one field is being updated
200 |       if (!title && !description) {
201 |         throw new Error("At least one field (title or description) must be provided");
202 |       }
203 | 
204 |       const updatedTodo = todoService.updateTodo(validatedData);
205 |       if (!updatedTodo) {
206 |         throw new Error(`Todo with ID ${id} not found`);
207 |       }
208 | 
209 |       return formatTodo(updatedTodo);
210 |     }, "Failed to update todo");
211 | 
212 |     if (result instanceof Error) {
213 |       return createErrorResponse(result.message);
214 |     }
215 | 
216 |     return createSuccessResponse(`✅ Todo Updated:\n\n${result}`);
217 |   }
218 | );
219 | 
220 | /**
221 |  * Tool 5: Complete a todo
222 |  * 
223 |  * This tool:
224 |  * 1. Validates the todo ID
225 |  * 2. Marks the todo as completed using the service
226 |  * 3. Returns the formatted completed todo
227 |  * 
228 |  * WHY SEPARATE FROM UPDATE?
229 |  * - Provides a dedicated semantic action for completion
230 |  * - Simplifies the client interaction model
231 |  * - It's easier for the LLM to match the user intent with the completion action
232 |  * - Makes it clear in the UI that the todo is done
233 |  */
234 | server.tool(
235 |   "complete-todo",
236 |   "Mark a todo as completed",
237 |   {
238 |     id: z.string().uuid("Invalid Todo ID"),
239 |   },
240 |   async ({ id }) => {
241 |     const result = await safeExecute(() => {
242 |       const validatedData = CompleteTodoSchema.parse({ id });
243 |       const completedTodo = todoService.completeTodo(validatedData.id);
244 |       
245 |       if (!completedTodo) {
246 |         throw new Error(`Todo with ID ${id} not found`);
247 |       }
248 | 
249 |       return formatTodo(completedTodo);
250 |     }, "Failed to complete todo");
251 | 
252 |     if (result instanceof Error) {
253 |       return createErrorResponse(result.message);
254 |     }
255 | 
256 |     return createSuccessResponse(`✅ Todo Completed:\n\n${result}`);
257 |   }
258 | );
259 | 
260 | /**
261 |  * Tool 6: Delete a todo
262 |  * 
263 |  * This tool:
264 |  * 1. Validates the todo ID
265 |  * 2. Retrieves the todo to be deleted (for the response)
266 |  * 3. Deletes the todo using the service
267 |  * 4. Returns a success message with the deleted todo's title
268 |  */
269 | server.tool(
270 |   "delete-todo",
271 |   "Delete a todo",
272 |   {
273 |     id: z.string().uuid("Invalid Todo ID"),
274 |   },
275 |   async ({ id }) => {
276 |     const result = await safeExecute(() => {
277 |       const validatedData = DeleteTodoSchema.parse({ id });
278 |       const todo = todoService.getTodo(validatedData.id);
279 |       
280 |       if (!todo) {
281 |         throw new Error(`Todo with ID ${id} not found`);
282 |       }
283 |       
284 |       const success = todoService.deleteTodo(validatedData.id);
285 |       
286 |       if (!success) {
287 |         throw new Error(`Failed to delete todo with ID ${id}`);
288 |       }
289 |       
290 |       return todo.title;
291 |     }, "Failed to delete todo");
292 | 
293 |     if (result instanceof Error) {
294 |       return createErrorResponse(result.message);
295 |     }
296 | 
297 |     return createSuccessResponse(`✅ Todo Deleted: "${result}"`);
298 |   }
299 | );
300 | 
301 | /**
302 |  * Tool 7: Search todos by title
303 |  * 
304 |  * This tool:
305 |  * 1. Validates the search term
306 |  * 2. Searches todos by title using the service
307 |  * 3. Returns a formatted list of matching todos
308 |  * 
309 |  * WHY HAVE SEARCH?
310 |  * - Makes it easy to find specific todos when the list grows large
311 |  * - Allows partial matching without requiring exact title
312 |  * - Case-insensitive for better user experience
313 |  */
314 | server.tool(
315 |   "search-todos-by-title",
316 |   "Search todos by title (case insensitive partial match)",
317 |   {
318 |     title: z.string().min(1, "Search term is required"),
319 |   },
320 |   async ({ title }) => {
321 |     const result = await safeExecute(() => {
322 |       const validatedData = SearchTodosByTitleSchema.parse({ title });
323 |       const todos = todoService.searchByTitle(validatedData.title);
324 |       return formatTodoList(todos);
325 |     }, "Failed to search todos");
326 | 
327 |     if (result instanceof Error) {
328 |       return createErrorResponse(result.message);
329 |     }
330 | 
331 |     return createSuccessResponse(result);
332 |   }
333 | );
334 | 
335 | /**
336 |  * Tool 8: Search todos by date
337 |  * 
338 |  * This tool:
339 |  * 1. Validates the date format (YYYY-MM-DD)
340 |  * 2. Searches todos created on that date
341 |  * 3. Returns a formatted list of matching todos
342 |  * 
343 |  * WHY DATE SEARCH?
344 |  * - Allows finding todos created on a specific day
345 |  * - Useful for reviewing what was added on a particular date
346 |  * - Complements title search for different search needs
347 |  */
348 | server.tool(
349 |   "search-todos-by-date",
350 |   "Search todos by creation date (format: YYYY-MM-DD)",
351 |   {
352 |     date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format"),
353 |   },
354 |   async ({ date }) => {
355 |     const result = await safeExecute(() => {
356 |       const validatedData = SearchTodosByDateSchema.parse({ date });
357 |       const todos = todoService.searchByDate(validatedData.date);
358 |       return formatTodoList(todos);
359 |     }, "Failed to search todos by date");
360 | 
361 |     if (result instanceof Error) {
362 |       return createErrorResponse(result.message);
363 |     }
364 | 
365 |     return createSuccessResponse(result);
366 |   }
367 | );
368 | 
369 | /**
370 |  * Tool 9: List active todos
371 |  * 
372 |  * This tool:
373 |  * 1. Retrieves all non-completed todos
374 |  * 2. Returns a formatted list of active todos
375 |  * 
376 |  * WHY SEPARATE FROM LIST ALL?
377 |  * - Active todos are typically what users most often want to see
378 |  * - Reduces noise by filtering out completed items
379 |  * - Provides a clearer view of outstanding work
380 |  */
381 | server.tool(
382 |   "list-active-todos",
383 |   "List all non-completed todos",
384 |   {},
385 |   async () => {
386 |     const result = await safeExecute(() => {
387 |       const todos = todoService.getActiveTodos();
388 |       return formatTodoList(todos);
389 |     }, "Failed to list active todos");
390 | 
391 |     if (result instanceof Error) {
392 |       return createErrorResponse(result.message);
393 |     }
394 | 
395 |     return createSuccessResponse(result);
396 |   }
397 | );
398 | 
399 | /**
400 |  * Tool 10: Summarize active todos
401 |  * 
402 |  * This tool:
403 |  * 1. Generates a summary of all active todos
404 |  * 2. Returns a formatted markdown summary
405 |  * 
406 |  * WHY HAVE A SUMMARY?
407 |  * - Provides a quick overview without details
408 |  * - Perfect for a quick status check
409 |  * - Easier to read than a full list when there are many todos
410 |  * - Particularly useful for LLM interfaces where conciseness matters
411 |  */
412 | server.tool(
413 |   "summarize-active-todos",
414 |   "Generate a summary of all active (non-completed) todos",
415 |   {},
416 |   async () => {
417 |     const result = await safeExecute(() => {
418 |       return todoService.summarizeActiveTodos();
419 |     }, "Failed to summarize active todos");
420 | 
421 |     if (result instanceof Error) {
422 |       return createErrorResponse(result.message);
423 |     }
424 | 
425 |     return createSuccessResponse(result);
426 |   }
427 | );
428 | 
429 | /**
430 |  * Main function to start the server
431 |  * 
432 |  * This function:
433 |  * 1. Initializes the server
434 |  * 2. Sets up graceful shutdown handlers
435 |  * 3. Connects to the transport
436 |  * 
437 |  * WHY USE STDIO TRANSPORT?
438 |  * - Works well with the MCP protocol
439 |  * - Simple to integrate with LLM platforms like Claude Desktop
440 |  * - No network configuration required
441 |  * - Easy to debug and test
442 |  */
443 | async function main() {
444 |   console.error("Starting Todo MCP Server...");
445 |   console.error(`SQLite database path: ${config.db.path}`);
446 |   
447 |   try {
448 |     // Database is automatically initialized when the service is imported
449 |     
450 |     /**
451 |      * Set up graceful shutdown to close the database
452 |      * 
453 |      * This ensures data is properly saved when the server is stopped.
454 |      * Both SIGINT (Ctrl+C) and SIGTERM (kill command) are handled.
455 |      */
456 |     process.on('SIGINT', () => {
457 |       console.error('Shutting down...');
458 |       databaseService.close();
459 |       process.exit(0);
460 |     });
461 |     
462 |     process.on('SIGTERM', () => {
463 |       console.error('Shutting down...');
464 |       databaseService.close();
465 |       process.exit(0);
466 |     });
467 |     
468 |     /**
469 |      * Connect to stdio transport
470 |      * 
471 |      * The StdioServerTransport uses standard input/output for communication,
472 |      * which is how Claude Desktop and other MCP clients connect to the server.
473 |      */
474 |     const transport = new StdioServerTransport();
475 |     await server.connect(transport);
476 |     
477 |     console.error("Todo MCP Server running on stdio transport");
478 |   } catch (error) {
479 |     console.error("Failed to start Todo MCP Server:", error);
480 |     databaseService.close();
481 |     process.exit(1);
482 |   }
483 | }
484 | 
485 | // Start the server
486 | main(); 
```