summaryrefslogtreecommitdiff
path: root/servers/taskwarrior/src
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-12-19 17:24:44 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2025-12-19 17:24:44 +0100
commitdc0ff6c1a870bb0e01f35c653303da08fc19485b (patch)
tree47f3138ad0fc545b6b052b4c5e9c0e8b26e2e412 /servers/taskwarrior/src
parentbedbd86e8c70d8d8cfa964842e1eab314384271d (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')
-rw-r--r--servers/taskwarrior/src/mcp_server_taskwarrior/server.py134
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.