From e4cddef343af9623228ca038f36c744799e01095 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Wed, 14 Jan 2026 20:02:20 +0100 Subject: [PATCH] fix: Add missing annotations for deck remove/unassign operations - Add destructiveHint=True to deck_remove_label_from_card and deck_unassign_user_from_card (ADR-017 compliance) - Set idempotentHint=True since remove operations produce same end state - Update test_annotations.py to exclude nc_webdav_create_directory from non-idempotent check (MKCOL is idempotent by design - returns 405 if exists) Co-Authored-By: Claude Opus 4.5 --- nextcloud_mcp_server/server/deck.py | 8 ++++++-- tests/server/test_annotations.py | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/nextcloud_mcp_server/server/deck.py b/nextcloud_mcp_server/server/deck.py index 65759c9..93b5d64 100644 --- a/nextcloud_mcp_server/server/deck.py +++ b/nextcloud_mcp_server/server/deck.py @@ -637,7 +637,9 @@ def configure_deck_tools(mcp: FastMCP): @mcp.tool( title="Remove Label from Deck Card", - annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True), + annotations=ToolAnnotations( + destructiveHint=True, idempotentHint=True, openWorldHint=True + ), ) @require_scopes("deck:write") @instrument_tool @@ -692,7 +694,9 @@ def configure_deck_tools(mcp: FastMCP): @mcp.tool( title="Unassign User from Deck Card", - annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True), + annotations=ToolAnnotations( + destructiveHint=True, idempotentHint=True, openWorldHint=True + ), ) @require_scopes("deck:write") @instrument_tool diff --git a/tests/server/test_annotations.py b/tests/server/test_annotations.py index 2a387ff..5d770e8 100644 --- a/tests/server/test_annotations.py +++ b/tests/server/test_annotations.py @@ -89,8 +89,13 @@ async def test_create_operations_not_idempotent(nc_mcp_client: ClientSession): """Verify create operations are marked as non-idempotent.""" tools = await nc_mcp_client.list_tools() + # Exceptions: operations that are actually idempotent + # - calendar_create_meeting: creates or returns existing meeting + # - nc_webdav_create_directory: MKCOL returns 405 if exists (same end state) + idempotent_exceptions = {"calendar_create_meeting", "nc_webdav_create_directory"} + for tool in tools.tools: - if "create" in tool.name.lower() and "calendar_create_meeting" not in tool.name: + if "create" in tool.name.lower() and tool.name not in idempotent_exceptions: assert tool.annotations is not None, f"Tool {tool.name} missing annotations" assert tool.annotations.idempotentHint is not True, ( f"Create tool {tool.name} should not be idempotent (creates new resources)"