"""MCP Tools for the AI Todo Chatbot agent. These tools provide task operations that the AI agent can call. Each tool enforces user isolation by requiring and validating user_id. """ from typing import Optional, Dict, Any from uuid import UUID from datetime import datetime from agents import function_tool from ..models.task import Task from ..database import get_db @function_tool def add_task( user_id: str, title: str, description: Optional[str] = None ) -> Dict[str, Any]: """ Create a new task for the user. Args: user_id: The authenticated user's ID (UUID string) title: The title of the task (1-200 characters) description: Optional description of the task (max 1000 characters) Returns: Dictionary with task_id, title, and status """ try: user_uuid = UUID(user_id) with get_db() as db: task = Task( title=title[:200], description=description[:1000] if description else None, completed=False, user_id=user_uuid, created_at=datetime.utcnow(), updated_at=datetime.utcnow() ) db.add(task) db.commit() db.refresh(task) return { "task_id": task.id, "title": task.title, "status": "created", "message": f"Task '{title}' has been created." } except Exception as e: return { "status": "error", "message": f"Failed to create task: {str(e)}" } @function_tool def list_tasks( user_id: str, status: Optional[str] = None ) -> Dict[str, Any]: """ List all tasks for the user. Args: user_id: The authenticated user's ID (UUID string) status: Optional filter - 'pending', 'completed', or None for all Returns: Dictionary with tasks array and summary """ try: user_uuid = UUID(user_id) with get_db() as db: query = db.query(Task).filter(Task.user_id == user_uuid) if status == "pending": query = query.filter(Task.completed == False) elif status == "completed": query = query.filter(Task.completed == True) tasks = query.order_by(Task.created_at.desc()).all() task_list = [ { "id": task.id, "title": task.title, "description": task.description, "completed": task.completed, "created_at": task.created_at.isoformat() if task.created_at else None } for task in tasks ] pending_count = sum(1 for t in tasks if not t.completed) completed_count = sum(1 for t in tasks if t.completed) return { "tasks": task_list, "total": len(tasks), "pending": pending_count, "completed": completed_count, "status": "success", "summary": f"You have {len(tasks)} task(s): {pending_count} pending, {completed_count} completed" } except Exception as e: return { "tasks": [], "status": "error", "message": f"Failed to list tasks: {str(e)}" } @function_tool def complete_task( user_id: str, task_id: int ) -> Dict[str, Any]: """ Toggle the completion status of a task. Args: user_id: The authenticated user's ID (UUID string) task_id: The ID of the task to complete/uncomplete Returns: Dictionary with task_id, title, and new status """ try: user_uuid = UUID(user_id) with get_db() as db: task = ( db.query(Task) .filter(Task.id == task_id, Task.user_id == user_uuid) .first() ) if not task: return { "status": "error", "message": f"Task {task_id} not found. It may have been deleted or doesn't exist." } # Toggle completion status task.completed = not task.completed task.updated_at = datetime.utcnow() db.commit() db.refresh(task) new_status = "completed" if task.completed else "incomplete" return { "task_id": task.id, "title": task.title, "completed": task.completed, "status": new_status, "message": f"Task '{task.title}' marked as {new_status}." } except Exception as e: return { "status": "error", "message": f"Failed to update task: {str(e)}" } @function_tool def update_task( user_id: str, task_id: int, title: Optional[str] = None, description: Optional[str] = None ) -> Dict[str, Any]: """ Update a task's title and/or description. Args: user_id: The authenticated user's ID (UUID string) task_id: The ID of the task to update title: New title for the task (optional) description: New description for the task (optional) Returns: Dictionary with task_id, updated fields, and status """ try: user_uuid = UUID(user_id) with get_db() as db: task = ( db.query(Task) .filter(Task.id == task_id, Task.user_id == user_uuid) .first() ) if not task: return { "status": "error", "message": f"Task {task_id} not found. It may have been deleted or doesn't exist." } updated_fields = [] if title is not None: task.title = title[:200] updated_fields.append("title") if description is not None: task.description = description[:1000] if description else None updated_fields.append("description") if not updated_fields: return { "status": "error", "message": "No fields provided to update." } task.updated_at = datetime.utcnow() db.commit() db.refresh(task) return { "task_id": task.id, "title": task.title, "description": task.description, "updated_fields": updated_fields, "status": "updated", "message": f"Task {task_id} has been updated." } except Exception as e: return { "status": "error", "message": f"Failed to update task: {str(e)}" } @function_tool def delete_task( user_id: str, task_id: int ) -> Dict[str, Any]: """ Delete a task. Args: user_id: The authenticated user's ID (UUID string) task_id: The ID of the task to delete Returns: Dictionary with task_id, title, and status """ try: user_uuid = UUID(user_id) with get_db() as db: task = ( db.query(Task) .filter(Task.id == task_id, Task.user_id == user_uuid) .first() ) if not task: return { "status": "error", "message": f"Task {task_id} not found. It may have been deleted or doesn't exist." } task_title = task.title task_id_deleted = task.id db.delete(task) db.commit() return { "task_id": task_id_deleted, "title": task_title, "status": "deleted", "message": f"Task '{task_title}' has been deleted." } except Exception as e: return { "status": "error", "message": f"Failed to delete task: {str(e)}" }