diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2025-12-19 17:24:44 +0100 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2025-12-19 17:24:44 +0100 |
| commit | dc0ff6c1a870bb0e01f35c653303da08fc19485b (patch) | |
| tree | 47f3138ad0fc545b6b052b4c5e9c0e8b26e2e412 /servers/taskwarrior/src/mcp_server_taskwarrior/server.py | |
| parent | bedbd86e8c70d8d8cfa964842e1eab314384271d (diff) | |
feat: implement task modification functionality in TaskWarrior server
- Added `modify_task` method to allow modification of tasks based on a filter expression, supporting parameters for project, priority, due date, description, and tags.
- Implemented validation for modification parameters, ensuring at least one parameter is provided and that priority and tag formats are correct.
- Introduced corresponding tests to validate the functionality of task modifications, including edge cases for invalid inputs and multiple modifications.
- Enhanced docstrings for clarity and detailed usage examples.
Diffstat (limited to 'servers/taskwarrior/src/mcp_server_taskwarrior/server.py')
| -rw-r--r-- | servers/taskwarrior/src/mcp_server_taskwarrior/server.py | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/servers/taskwarrior/src/mcp_server_taskwarrior/server.py b/servers/taskwarrior/src/mcp_server_taskwarrior/server.py index 2f66dac..c561d2a 100644 --- a/servers/taskwarrior/src/mcp_server_taskwarrior/server.py +++ b/servers/taskwarrior/src/mcp_server_taskwarrior/server.py @@ -235,6 +235,102 @@ class TaskWarriorServer: "output": stdout.strip(), } + async def modify_task( + self, + filter_expr: str, + project: str | None = None, + priority: str | None = None, + due: str | None = None, + description: str | None = None, + tags: list[str] | None = None, + ) -> dict[str, Any]: + """Modify one or more tasks matching the filter expression. + + Args: + filter_expr: Filter expression to identify tasks to modify + (e.g., "uuid:abc123", "+work", "project:Home status:pending") + project: New project name (use empty string "" to clear project) + priority: New priority (H, M, L, or empty string "" to clear) + due: New due date (ISO format or TaskWarrior format like "tomorrow", + or empty string "" to clear) + description: New description text + tags: List of tags with +/- prefixes (e.g., ["+newtag", "-oldtag"]) + + Returns: + Dictionary with modification information + + Raises: + ValueError: If no modification parameters provided, invalid priority, + or invalid tag format + """ + # Validate that at least one modification parameter is provided + if not any( + [ + project is not None, + priority is not None, + due is not None, + description is not None, + tags, + ] + ): + raise ValueError( + "At least one modification parameter must be provided " + "(project, priority, due, description, or tags)" + ) + + # Validate priority if provided + if priority is not None: + priority_upper = priority.upper() if priority else "" + if priority_upper and priority_upper not in ["H", "M", "L"]: + raise ValueError(f"Priority must be H, M, or L, got: {priority}") + + # Validate tags format if provided + if tags: + for tag in tags: + if not tag.startswith(("+", "-")): + raise ValueError(f"Tags must start with + or -, got: {tag}") + + # Build command: task <filter_expr> modify <modifications> + args = [filter_expr, "modify"] + + if project is not None: + if project == "": + args.append("project:") + else: + args.append(f"project:{project}") + + if priority is not None: + if priority == "": + args.append("priority:") + else: + args.append(f"priority:{priority.upper()}") + + if due is not None: + if due == "": + args.append("due:") + else: + args.append(f"due:{due}") + + if description is not None: + args.append(f"description:{description}") + + if tags: + args.extend(tags) + + stdout, stderr, _ = await self._run_task_command(*args) + + return { + "filter_expr": filter_expr, + "modifications": { + "project": project if project is not None else None, + "priority": priority.upper() if priority and priority != "" else None, + "due": due if due != "" else None, + "description": description, + "tags": tags, + }, + "output": stdout.strip(), + } + async def manage_context( self, action: str, name: str | None = None ) -> dict[str, Any]: @@ -398,6 +494,44 @@ def create_server(host: str = "127.0.0.1", port: int = 8080) -> FastMCP: return json.dumps({"error": str(e)}) @mcp.tool() + async def modify_task( + filter_expr: str, + project: str | None = None, + priority: str | None = None, + due: str | None = None, + description: str | None = None, + tags: list[str] | None = None, + ) -> str: + """Modify one or more tasks matching the filter expression. + + Args: + filter_expr: Filter expression to identify tasks to modify + (e.g., "uuid:abc123", "+work", "project:Home status:pending") + project: New project name (use empty string "" to clear project) + priority: New priority (H, M, L, or empty string "" to clear) + due: New due date (ISO format or TaskWarrior format like "tomorrow", + or empty string "" to clear) + description: New description text + tags: List of tags with +/- prefixes (e.g., ["+newtag", "-oldtag"]) + + Returns: + JSON string with modification information + """ + try: + result = await taskwarrior.modify_task( + filter_expr=filter_expr, + project=project, + priority=priority, + due=due, + description=description, + tags=tags, + ) + return json.dumps(result, indent=2) + except Exception as e: + logger.error(f"Error modifying task: {e}") + return json.dumps({"error": str(e)}) + + @mcp.tool() async def context(action: str, name: str | None = None) -> str: """Manage TaskWarrior contexts. |
