import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { v4 as uuidv4 } from 'uuid';
import { RedisClient, Todo } from './redis-client.js';
import { AIService } from './ai-service.js';
export interface MCPToolsConfig {
redisClient: RedisClient;
aiService: AIService;
}
export class MCPTools {
private server: Server;
constructor(
private config: MCPToolsConfig,
serverName: string = 'todo-mcp-server',
serverVersion: string = '1.0.0'
) {
this.server = new Server(
{
name: serverName,
version: serverVersion,
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'todo_add',
description: 'Add a new todo item',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier',
},
title: {
type: 'string',
description: 'Todo title',
},
description: {
type: 'string',
description: 'Todo description',
},
priority: {
type: 'string',
enum: ['low', 'medium', 'high'],
description: 'Priority level',
},
},
required: ['sessionId', 'title', 'description'],
},
},
{
name: 'todo_list',
description: 'List todos for a session',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier',
},
status: {
type: 'string',
enum: ['all', 'pending', 'completed'],
description: 'Filter by status',
},
},
required: ['sessionId'],
},
},
{
name: 'todo_remove',
description: 'Remove a todo by ID',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier for the user',
},
todoId: {
type: 'string',
description: 'ID of the todo to remove',
},
},
required: ['sessionId', 'todoId'],
},
},
{
name: 'todo_clear',
description: 'Clear all todos for a session',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier for the user',
},
},
required: ['sessionId'],
},
},
{
name: 'todo_mark_done',
description: 'Mark a todo as completed',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier for the user',
},
todoId: {
type: 'string',
description: 'ID of the todo to mark as done',
},
},
required: ['sessionId', 'todoId'],
},
},
{
name: 'todo_analyze',
description: 'Get AI task prioritization analysis',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Session identifier',
},
},
required: ['sessionId'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'todo_add':
return await this.handleAddTodo(args);
case 'todo_list':
return await this.handleListTodos(args);
case 'todo_remove':
return await this.handleRemoveTodo(args);
case 'todo_clear':
return await this.handleClearTodos(args);
case 'todo_mark_done':
return await this.handleMarkDone(args);
case 'todo_analyze':
return await this.handleAnalyzeTodos(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
};
}
});
}
private async handleAddTodo(args: any) {
const { sessionId, title, description, priority = 'medium' } = args;
const newTodo: Todo = {
id: uuidv4(),
title,
description,
status: 'pending',
priority: priority as 'low' | 'medium' | 'high',
createdAt: new Date().toISOString(),
};
await this.config.redisClient.ensureSession(sessionId);
await this.config.redisClient.addTodo(sessionId, newTodo);
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
message: 'Todo added successfully',
todo: newTodo,
},
null,
2
),
},
],
};
}
private async handleListTodos(args: any) {
const { sessionId, status = 'all' } = args;
await this.config.redisClient.ensureSession(sessionId);
let todos = await this.config.redisClient.getTodos(sessionId);
if (status !== 'all') {
todos = todos.filter((t) => t.status === status);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
count: todos.length,
todos,
},
null,
2
),
},
],
};
}
private async handleRemoveTodo(args: any) {
const { sessionId, todoId } = args;
await this.config.redisClient.ensureSession(sessionId);
const todos = await this.config.redisClient.removeTodo(sessionId, todoId);
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
message: 'Todo removed successfully',
remainingCount: todos.length,
},
null,
2
),
},
],
};
}
private async handleClearTodos(args: any) {
const { sessionId } = args;
await this.config.redisClient.clearTodos(sessionId);
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
message: 'All todos cleared successfully',
},
null,
2
),
},
],
};
}
private async handleMarkDone(args: any) {
const { sessionId, todoId } = args;
await this.config.redisClient.ensureSession(sessionId);
const todos = await this.config.redisClient.markTodoDone(sessionId, todoId);
const todo = todos.find((t) => t.id === todoId);
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
message: 'Todo marked as done',
todo,
},
null,
2
),
},
],
};
}
private async handleAnalyzeTodos(args: any) {
const { sessionId } = args;
await this.config.redisClient.ensureSession(sessionId);
const todos = await this.config.redisClient.getTodos(sessionId);
const analysis = await this.config.aiService.analyzeTodos(todos);
return {
content: [
{
type: 'text',
text: analysis,
},
],
};
}
getServer(): Server {
return this.server;
}
async runStdio() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('MCP Todo Server running on stdio');
}
}