Skip to main content
Glama
index.ts13.3 kB
/** * index.ts * * This is the main entry point for the Todo MCP server. * It defines all the tools provided by the server and handles * connecting to clients. * * WHAT IS MCP? * The Model Context Protocol (MCP) allows AI models like Claude * to interact with external tools and services. This server implements * the MCP specification to provide a Todo list functionality that * Claude can use. * * HOW THE SERVER WORKS: * 1. It creates an MCP server instance with identity information * 2. It defines a set of tools for managing todos * 3. It connects to a transport (stdio in this case) * 4. It handles incoming tool calls from clients (like Claude) */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Import models and schemas import { CreateTodoSchema, UpdateTodoSchema, CompleteTodoSchema, DeleteTodoSchema, SearchTodosByTitleSchema, SearchTodosByDateSchema } from "./models/Todo.js"; // Import services import { todoService } from "./services/TodoService.js"; import { databaseService } from "./services/DatabaseService.js"; // Import utilities import { createSuccessResponse, createErrorResponse, formatTodo, formatTodoList } from "./utils/formatters.js"; import { config } from "./config.js"; /** * Create the MCP server * * We initialize with identity information that helps clients * understand what they're connecting to. */ const server = new McpServer({ name: "Todo-MCP-Server", version: "1.0.0", }); /** * Helper function to safely execute operations * * This function: * 1. Attempts to execute an operation * 2. Catches any errors * 3. Returns either the result or an Error object * * WHY USE THIS PATTERN? * - Centralizes error handling * - Prevents crashes from uncaught exceptions * - Makes error reporting consistent across all tools * - Simplifies the tool implementations * * @param operation The function to execute * @param errorMessage The message to include if an error occurs * @returns Either the operation result or an Error */ async function safeExecute<T>(operation: () => T, errorMessage: string) { try { const result = operation(); return result; } catch (error) { console.error(errorMessage, error); if (error instanceof Error) { return new Error(`${errorMessage}: ${error.message}`); } return new Error(errorMessage); } } /** * Tool 1: Create a new todo * * This tool: * 1. Validates the input (title and description) * 2. Creates a new todo using the service * 3. Returns the formatted todo * * PATTERN FOR ALL TOOLS: * - Register with server.tool() * - Define name, description, and parameter schema * - Implement the async handler function * - Use safeExecute for error handling * - Return properly formatted response */ server.tool( "create-todo", "Create a new todo item", { title: z.string().min(1, "Title is required"), description: z.string().min(1, "Description is required"), }, async ({ title, description }) => { const result = await safeExecute(() => { const validatedData = CreateTodoSchema.parse({ title, description }); const newTodo = todoService.createTodo(validatedData); return formatTodo(newTodo); }, "Failed to create todo"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(`✅ Todo Created:\n\n${result}`); } ); /** * Tool 2: List all todos * * This tool: * 1. Retrieves all todos from the service * 2. Formats them as a list * 3. Returns the formatted list */ server.tool( "list-todos", "List all todos", {}, async () => { const result = await safeExecute(() => { const todos = todoService.getAllTodos(); return formatTodoList(todos); }, "Failed to list todos"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Tool 3: Get a specific todo by ID * * This tool: * 1. Validates the input ID * 2. Retrieves the specific todo * 3. Returns the formatted todo */ server.tool( "get-todo", "Get a specific todo by ID", { id: z.string().uuid("Invalid Todo ID"), }, async ({ id }) => { const result = await safeExecute(() => { const todo = todoService.getTodo(id); if (!todo) { throw new Error(`Todo with ID ${id} not found`); } return formatTodo(todo); }, "Failed to get todo"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Tool 4: Update a todo * * This tool: * 1. Validates the input (id required, title/description optional) * 2. Ensures at least one field is being updated * 3. Updates the todo using the service * 4. Returns the formatted updated todo */ server.tool( "update-todo", "Update a todo title or description", { id: z.string().uuid("Invalid Todo ID"), title: z.string().min(1, "Title is required").optional(), description: z.string().min(1, "Description is required").optional(), }, async ({ id, title, description }) => { const result = await safeExecute(() => { const validatedData = UpdateTodoSchema.parse({ id, title, description }); // Ensure at least one field is being updated if (!title && !description) { throw new Error("At least one field (title or description) must be provided"); } const updatedTodo = todoService.updateTodo(validatedData); if (!updatedTodo) { throw new Error(`Todo with ID ${id} not found`); } return formatTodo(updatedTodo); }, "Failed to update todo"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(`✅ Todo Updated:\n\n${result}`); } ); /** * Tool 5: Complete a todo * * This tool: * 1. Validates the todo ID * 2. Marks the todo as completed using the service * 3. Returns the formatted completed todo * * WHY SEPARATE FROM UPDATE? * - Provides a dedicated semantic action for completion * - Simplifies the client interaction model * - It's easier for the LLM to match the user intent with the completion action * - Makes it clear in the UI that the todo is done */ server.tool( "complete-todo", "Mark a todo as completed", { id: z.string().uuid("Invalid Todo ID"), }, async ({ id }) => { const result = await safeExecute(() => { const validatedData = CompleteTodoSchema.parse({ id }); const completedTodo = todoService.completeTodo(validatedData.id); if (!completedTodo) { throw new Error(`Todo with ID ${id} not found`); } return formatTodo(completedTodo); }, "Failed to complete todo"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(`✅ Todo Completed:\n\n${result}`); } ); /** * Tool 6: Delete a todo * * This tool: * 1. Validates the todo ID * 2. Retrieves the todo to be deleted (for the response) * 3. Deletes the todo using the service * 4. Returns a success message with the deleted todo's title */ server.tool( "delete-todo", "Delete a todo", { id: z.string().uuid("Invalid Todo ID"), }, async ({ id }) => { const result = await safeExecute(() => { const validatedData = DeleteTodoSchema.parse({ id }); const todo = todoService.getTodo(validatedData.id); if (!todo) { throw new Error(`Todo with ID ${id} not found`); } const success = todoService.deleteTodo(validatedData.id); if (!success) { throw new Error(`Failed to delete todo with ID ${id}`); } return todo.title; }, "Failed to delete todo"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(`✅ Todo Deleted: "${result}"`); } ); /** * Tool 7: Search todos by title * * This tool: * 1. Validates the search term * 2. Searches todos by title using the service * 3. Returns a formatted list of matching todos * * WHY HAVE SEARCH? * - Makes it easy to find specific todos when the list grows large * - Allows partial matching without requiring exact title * - Case-insensitive for better user experience */ server.tool( "search-todos-by-title", "Search todos by title (case insensitive partial match)", { title: z.string().min(1, "Search term is required"), }, async ({ title }) => { const result = await safeExecute(() => { const validatedData = SearchTodosByTitleSchema.parse({ title }); const todos = todoService.searchByTitle(validatedData.title); return formatTodoList(todos); }, "Failed to search todos"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Tool 8: Search todos by date * * This tool: * 1. Validates the date format (YYYY-MM-DD) * 2. Searches todos created on that date * 3. Returns a formatted list of matching todos * * WHY DATE SEARCH? * - Allows finding todos created on a specific day * - Useful for reviewing what was added on a particular date * - Complements title search for different search needs */ server.tool( "search-todos-by-date", "Search todos by creation date (format: YYYY-MM-DD)", { date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format"), }, async ({ date }) => { const result = await safeExecute(() => { const validatedData = SearchTodosByDateSchema.parse({ date }); const todos = todoService.searchByDate(validatedData.date); return formatTodoList(todos); }, "Failed to search todos by date"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Tool 9: List active todos * * This tool: * 1. Retrieves all non-completed todos * 2. Returns a formatted list of active todos * * WHY SEPARATE FROM LIST ALL? * - Active todos are typically what users most often want to see * - Reduces noise by filtering out completed items * - Provides a clearer view of outstanding work */ server.tool( "list-active-todos", "List all non-completed todos", {}, async () => { const result = await safeExecute(() => { const todos = todoService.getActiveTodos(); return formatTodoList(todos); }, "Failed to list active todos"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Tool 10: Summarize active todos * * This tool: * 1. Generates a summary of all active todos * 2. Returns a formatted markdown summary * * WHY HAVE A SUMMARY? * - Provides a quick overview without details * - Perfect for a quick status check * - Easier to read than a full list when there are many todos * - Particularly useful for LLM interfaces where conciseness matters */ server.tool( "summarize-active-todos", "Generate a summary of all active (non-completed) todos", {}, async () => { const result = await safeExecute(() => { return todoService.summarizeActiveTodos(); }, "Failed to summarize active todos"); if (result instanceof Error) { return createErrorResponse(result.message); } return createSuccessResponse(result); } ); /** * Main function to start the server * * This function: * 1. Initializes the server * 2. Sets up graceful shutdown handlers * 3. Connects to the transport * * WHY USE STDIO TRANSPORT? * - Works well with the MCP protocol * - Simple to integrate with LLM platforms like Claude Desktop * - No network configuration required * - Easy to debug and test */ async function main() { console.error("Starting Todo MCP Server..."); console.error(`SQLite database path: ${config.db.path}`); try { // Database is automatically initialized when the service is imported /** * Set up graceful shutdown to close the database * * This ensures data is properly saved when the server is stopped. * Both SIGINT (Ctrl+C) and SIGTERM (kill command) are handled. */ process.on('SIGINT', () => { console.error('Shutting down...'); databaseService.close(); process.exit(0); }); process.on('SIGTERM', () => { console.error('Shutting down...'); databaseService.close(); process.exit(0); }); /** * Connect to stdio transport * * The StdioServerTransport uses standard input/output for communication, * which is how Claude Desktop and other MCP clients connect to the server. */ const transport = new StdioServerTransport(); await server.connect(transport); console.error("Todo MCP Server running on stdio transport"); } catch (error) { console.error("Failed to start Todo MCP Server:", error); databaseService.close(); process.exit(1); } } // Start the server main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RegiByte/todo-list-mcp'

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