feat: category assignment in create_task / update_task (v0.4.10)

Verified via FW_DEBUG=1 + systematic param-name probing that the
correct parameter is `taskCategoryId` with value = full metaId from
get_categories (e.g. taskCategory/23431854_200).  Numeric systemCategoryId
alone causes API error; full metaId is accepted and stored.

Changes:
- create_task: add optional category_id parameter → sent as taskCategoryId
- update_task: add optional category_id parameter → sent as taskCategoryId;
  guard now accepts category_id-only updates
- get_tasks: expose category_id field in returned task objects
- get_categories: update docstring (param name now known)
- SPEC.md: document verified taskCategoryId param + clarify categories[]
  vs taskCategoryId field distinction
- scripts/find_category_param.py: discovery script used to find param name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 06:54:10 +02:00
parent 9bc6a54783
commit a76dc0fd51
6 changed files with 262 additions and 26 deletions
+29 -8
View File
@@ -325,6 +325,7 @@ def get_tasks(list_id: str, only_open: bool = True):
"text": task.get("text"),
"description": task.get("description"),
"completed": completed,
"category_id": task.get("taskCategoryId"),
}
)
@@ -344,8 +345,8 @@ def get_categories(list_id: str, locale: str = "de") -> str:
lists return an empty list. Categories are filtered by locale so only
the language-appropriate names are returned (default: German).
Note: the parameter name for assigning a category when creating or
updating a task is not yet verified via FW_DEBUG=1. See SPEC.md.
Use the returned ``id`` values as the ``category_id`` parameter in
``create_task`` and ``update_task``.
Args:
list_id: List ID from get_lists (e.g. ``taskList/23431854_29740942``).
@@ -517,7 +518,12 @@ def _authenticated_call(endpoint: str, params: dict[str, Any]) -> dict[str, Any]
@mcp.tool()
def create_task(list_id: str, text: str, description: str | None = None) -> str:
def create_task(
list_id: str,
text: str,
description: str | None = None,
category_id: str | None = None,
) -> str:
"""Create a new task in the given list.
IMPORTANT: Ask the user for confirmation before calling this tool.
@@ -526,6 +532,9 @@ def create_task(list_id: str, text: str, description: str | None = None) -> str:
list_id: Target list ID from get_lists (e.g. ``taskList/123_456``).
text: Task title / main text.
description: Optional longer description.
category_id: Optional category metaId from get_categories
(e.g. ``taskCategory/23431854_200``). Only meaningful for
shopping lists; ignored for TODO lists.
Returns:
JSON with the new task's metaId on success, or an error message.
@@ -533,6 +542,8 @@ def create_task(list_id: str, text: str, description: str | None = None) -> str:
params: dict[str, Any] = {"taskListId": list_id, "text": text}
if description:
params["description"] = description
if category_id:
params["taskCategoryId"] = category_id
try:
data = _authenticated_call("taskcreate2", params)
@@ -559,28 +570,38 @@ def create_task(list_id: str, text: str, description: str | None = None) -> str:
@mcp.tool()
def update_task(task_id: str, text: str | None = None, description: str | None = None) -> str:
"""Update the text and/or description of an existing task.
def update_task(
task_id: str,
text: str | None = None,
description: str | None = None,
category_id: str | None = None,
) -> str:
"""Update the text, description, and/or category of an existing task.
IMPORTANT: Ask the user for confirmation before calling this tool.
At least one of *text* or *description* must be provided.
At least one of *text*, *description*, or *category_id* must be provided.
Args:
task_id: Task metaId from get_tasks.
text: New title text (omit to leave unchanged).
description: New description (omit to leave unchanged).
category_id: New category metaId from get_categories
(e.g. ``taskCategory/23431854_200``). Only meaningful for
shopping lists; ignored for TODO lists.
Returns:
JSON success indicator or an error message.
"""
if text is None and description is None:
return "Error: At least one of 'text' or 'description' must be provided."
if text is None and description is None and category_id is None:
return "Error: At least one of 'text', 'description', or 'category_id' must be provided."
params: dict[str, Any] = {"metaId": task_id}
if text is not None:
params["text"] = text
if description is not None:
params["description"] = description
if category_id is not None:
params["taskCategoryId"] = category_id
try:
_authenticated_call("taskupdate2", params)