Skip to main content
Glama
list.py15.7 kB
# cmcp/tools/list.py """List tools module. This module contains tools for managing org-mode based lists and todo items. """ from typing import Dict, Any, Optional, List, Literal import logging from mcp.server.fastmcp import FastMCP from cmcp.managers.list_manager import ListManager logger = logging.getLogger(__name__) def create_list_tools(mcp: FastMCP, list_manager: ListManager) -> None: """Create and register list tools. Args: mcp: The MCP instance list_manager: The list manager instance """ @mcp.tool() async def list_create( name: str, title: Optional[str] = None, list_type: str = "todo", description: str = "", tags: Optional[List[str]] = None ) -> Dict[str, Any]: """Create a new organized list for tasks, notes, shopping, or any collection. This tool creates org-mode based lists that support various item statuses (TODO, DONE, WAITING, etc.) and can be tagged for organization. Perfect for project management, shopping lists, or general note-taking. Examples: Request: {"name": "list_create", "parameters": {"name": "work-tasks", "title": "Q1 Work Tasks", "list_type": "todo", "description": "Quarterly objectives and tasks", "tags": ["work", "q1-2025"]}} Response: {"success": true, "name": "work-tasks", "metadata": {"title": "Q1 Work Tasks", "type": "todo", "created": "2024-01-01T10:00:00Z"}} Request: {"name": "list_create", "parameters": {"name": "groceries", "title": "Weekly Groceries", "list_type": "shopping", "tags": ["weekly", "essentials"]}} Response: {"success": true, "name": "groceries", "metadata": {"title": "Weekly Groceries", "type": "shopping", "created": "2024-01-01T10:30:00Z"}} """ return await list_manager.create_list( name=name, title=title, list_type=list_type, description=description, tags=tags ) @mcp.tool() async def list_get( name: Optional[str] = None, include_items: bool = True, summary_only: bool = False, status_filter: Optional[str] = None, tag_filter: Optional[List[str]] = None ) -> Dict[str, Any]: """Retrieve and browse lists with flexible filtering options. This tool can browse all your lists, get details of specific lists, filter items by status or tags, and provide completion statistics. Perfect for checking progress or finding specific items. Examples: Request: {"name": "list_get", "parameters": {}} Response: {"success": true, "lists": [{"name": "work-tasks", "title": "Q1 Work Tasks", "type": "todo"}], "count": 3} Request: {"name": "list_get", "parameters": {"name": "work-tasks", "status_filter": "TODO"}} Response: {"success": true, "name": "work-tasks", "items": [{"index": 0, "text": "Review code", "status": "TODO"}], "statistics": {"total_items": 5, "completion_percentage": 60.0}} """ # Get all lists if name is None: return await list_manager.list_all_lists() # Get specific list try: list_info = await list_manager.get_list(name) if not list_info["success"]: return list_info # Calculate statistics items = list_info["items"] status_counts = {} for item in items: status = item["status"] status_counts[status] = status_counts.get(status, 0) + 1 done_items = status_counts.get("DONE", 0) total_items = len(items) completion_percentage = (done_items / total_items * 100) if total_items > 0 else 0 result = { "success": True, "name": name, "metadata": list_info["metadata"], "statistics": { "total_items": total_items, "status_counts": status_counts, "completion_percentage": round(completion_percentage, 1) } } # Apply filters and include items if requested if not summary_only and include_items: filtered_items = items # Apply status filter if status_filter: filtered_items = [item for item in filtered_items if item["status"] == status_filter] # Apply tag filter (items must have ALL specified tags) if tag_filter: filtered_items = [ item for item in filtered_items if all(tag in item.get("tags", []) for tag in tag_filter) ] result["items"] = filtered_items if status_filter or tag_filter: result["filters_applied"] = { "status": status_filter, "tags": tag_filter, "filtered_count": len(filtered_items) } return result except Exception as e: logger.error(f"Error getting list '{name}': {e}") return {"success": False, "error": str(e)} @mcp.tool() async def list_modify( list_name: str, action: Literal["add", "update", "remove"], item_text: Optional[str] = None, item_index: Optional[int] = None, status: Optional[str] = None, tags: Optional[List[str]] = None ) -> Dict[str, Any]: """Modify list items by adding, updating, or removing them. This flexible tool handles all item operations within a list. Add new tasks, update existing items (change text, status, or tags), mark items as done, or remove completed items. Perfect for managing dynamic lists. Examples: Request: {"name": "list_modify", "parameters": {"list_name": "work-tasks", "action": "add", "item_text": "Review Q1 report", "status": "TODO", "tags": ["urgent", "reports"]}} Response: {"success": true, "action": "add", "item": {"index": 3, "text": "Review Q1 report", "status": "TODO", "tags": ["urgent", "reports"]}} Request: {"name": "list_modify", "parameters": {"list_name": "work-tasks", "action": "update", "item_index": 2, "status": "DONE"}} Response: {"success": true, "action": "update", "item": {"index": 2, "text": "Complete project setup", "status": "DONE"}} """ try: if action == "add": if not item_text: return {"success": False, "error": "item_text is required for add action"} return await list_manager.add_item( list_name=list_name, item_text=item_text, status=status or "TODO", tags=tags ) elif action == "update": if item_index is None: return {"success": False, "error": "item_index is required for update action"} return await list_manager.update_item( list_name=list_name, item_index=item_index, new_text=item_text, new_status=status, new_tags=tags ) elif action == "remove": if item_index is None: return {"success": False, "error": "item_index is required for remove action"} return await list_manager.remove_item( list_name=list_name, item_index=item_index ) else: return {"success": False, "error": f"Invalid action: {action}. Use 'add', 'update', or 'remove'"} except Exception as e: logger.error(f"Error in list_modify: {e}") return {"success": False, "error": str(e)} @mcp.tool() async def list_delete(name: str) -> Dict[str, Any]: """Permanently delete an entire list and all its items. This tool completely removes a list from the system. Use with caution as this action cannot be undone. The list file will be archived for safety but the list will no longer be accessible through normal operations. Examples: Request: {"name": "list_delete", "parameters": {"name": "old-shopping-list"}} Response: {"success": true, "name": "old-shopping-list", "items_count": 5, "archived": true} Request: {"name": "list_delete", "parameters": {"name": "completed-project-tasks"}} Response: {"success": true, "name": "completed-project-tasks", "items_count": 12, "archived": true} """ return await list_manager.delete_list(name) @mcp.tool() async def list_search( query: str, list_names: Optional[List[str]] = None, search_in: List[Literal["text", "tags"]] = ["text"], case_sensitive: bool = False ) -> Dict[str, Any]: """Search for items across multiple lists by text or tags. This powerful search tool finds items by content or tags across all your lists or specific ones. Get comprehensive results showing exactly where each match was found, perfect for locating tasks or items across your organization system. Examples: Request: {"name": "list_search", "parameters": {"query": "meeting", "search_in": ["text"]}} Response: {"success": true, "query": "meeting", "matches": [{"list_name": "work-tasks", "item_text": "Prepare for team meeting", "item_status": "TODO", "match_type": "text"}], "total_matches": 3} Request: {"name": "list_search", "parameters": {"query": "urgent", "list_names": ["work-tasks", "projects"], "search_in": ["tags"], "case_sensitive": false}} Response: {"success": true, "query": "urgent", "matches": [{"list_name": "work-tasks", "item_text": "Fix critical bug", "item_tags": ["urgent", "bug"], "match_type": "tag"}], "total_matches": 2} """ try: matching_items = [] lists_searched = 0 # Determine which lists to search if list_names: lists_to_search = [] for name in list_names: list_info = await list_manager.get_list(name) if list_info["success"]: lists_to_search.append((name, list_info)) else: all_lists = await list_manager.list_all_lists() if not all_lists["success"]: return all_lists lists_to_search = [] for list_summary in all_lists["lists"]: list_info = await list_manager.get_list(list_summary["name"]) if list_info["success"]: lists_to_search.append((list_summary["name"], list_info)) lists_searched = len(lists_to_search) # Prepare search query search_query = query if case_sensitive else query.lower() # Search through items for search_list_name, list_info in lists_to_search: for item in list_info["items"]: match_found = False match_type = None # Search in text if "text" in search_in: item_text = item["text"] if case_sensitive else item["text"].lower() if search_query in item_text: match_found = True match_type = "text" # Search in tags if "tags" in search_in and not match_found: item_tags = item.get("tags", []) if not case_sensitive: item_tags = [tag.lower() for tag in item_tags] if search_query in item_tags: match_found = True match_type = "tag" if match_found: matching_items.append({ "list_name": search_list_name, "list_title": list_info["metadata"]["title"], "item_index": item["index"], "item_text": item["text"], "item_status": item["status"], "item_tags": item.get("tags", []), "match_type": match_type }) return { "success": True, "query": query, "matches": matching_items, "total_matches": len(matching_items), "lists_searched": lists_searched, "search_options": { "list_names": list_names, "search_in": search_in, "case_sensitive": case_sensitive } } except Exception as e: logger.error(f"Error searching with query '{query}': {e}") return {"success": False, "error": str(e)} # Register list resource handler @mcp.resource("list://{name}") async def get_list_resource(name: str) -> str: """Get list contents as a resource. Args: name: Name of the list Returns: List contents as formatted text """ try: list_info = await list_manager.get_list(name) if not list_info["success"]: return f"Error: {list_info['error']}" # Format list as readable text lines = [] metadata = list_info["metadata"] lines.append(f"# {metadata['title']}") lines.append(f"Type: {metadata['type']}") lines.append(f"Created: {metadata['created']}") lines.append(f"Modified: {metadata['modified']}") if metadata['description']: lines.append(f"Description: {metadata['description']}") if metadata['tags']: lines.append(f"Tags: {', '.join(metadata['tags'])}") lines.append("") # Empty line lines.append("## Items") if list_info["items"]: for i, item in enumerate(list_info["items"]): status_symbol = "✓" if item["status"] == "DONE" else "○" item_line = f"{i}. {status_symbol} {item['text']}" if item.get("tags"): item_line += f" ({', '.join(item['tags'])})" lines.append(item_line) else: lines.append("No items") return "\n".join(lines) except Exception as e: logger.error(f"Error accessing list resource {name}: {str(e)}") return f"Error: {str(e)}"

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/54rt1n/container-mcp'

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