feat: add MCP tool annotations for enhanced UX

Add ToolAnnotations to all 105+ MCP tools across 13 modules to enable
better client-side UX with human-readable titles and behavioral hints.

Changes:
- Add title and ToolAnnotations to all @mcp.tool() decorators
- Apply correct idempotency classification per ADR-017
- Add destructiveHint for delete operations
- Set openWorldHint=False for semantic search (internal data only)

Modules updated:
- OAuth (4 tools): Authentication and provisioning
- Notes (7 tools): Note management
- WebDAV (11 tools): File operations
- Semantic (3 tools): Semantic search and RAG
- Calendar (16 tools): Events and todos
- Contacts (7 tools): Address book management
- Sharing (5 tools): File/folder sharing
- Tables (6 tools): Structured data
- Deck (25 tools): Kanban board management
- Cookbook (13 tools): Recipe management
- News (8 tools): RSS feed reader

Annotation patterns:
- Read operations: readOnlyHint=True, openWorldHint=True
- Create operations: idempotentHint=False, openWorldHint=True
- Update operations: idempotentHint=False, openWorldHint=True
- Delete operations: destructiveHint=True, idempotentHint=True, openWorldHint=True

See docs/ADR-017-mcp-tool-annotations.md for rationale and implementation details.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2025-12-11 12:45:02 +01:00
co-authored by Claude Sonnet 4.5
parent b9c94dfab0
commit e1412320a7
12 changed files with 1013 additions and 104 deletions
+22 -3
View File
@@ -12,6 +12,7 @@ from mcp.types import (
ModelPreferences,
SamplingMessage,
TextContent,
ToolAnnotations,
)
from nextcloud_mcp_server.auth import require_scopes
@@ -34,7 +35,13 @@ logger = logging.getLogger(__name__)
def configure_semantic_tools(mcp: FastMCP):
"""Configure semantic search tools for MCP server."""
@mcp.tool()
@mcp.tool(
title="Semantic Search",
annotations=ToolAnnotations(
readOnlyHint=True, # Search doesn't modify data
openWorldHint=False, # Searches only indexed Nextcloud data
),
)
@require_scopes("semantic:read")
@instrument_tool
async def nc_semantic_search(
@@ -285,7 +292,13 @@ def configure_semantic_tools(mcp: FastMCP):
logger.error(f"Search error: {e}", exc_info=True)
raise McpError(ErrorData(code=-1, message=f"Search failed: {str(e)}"))
@mcp.tool()
@mcp.tool(
title="Search with AI-Generated Answer",
annotations=ToolAnnotations(
readOnlyHint=True, # Search doesn't modify data
openWorldHint=False, # Searches only indexed Nextcloud data
),
)
@require_scopes("semantic:read")
@instrument_tool
async def nc_semantic_search_answer(
@@ -623,7 +636,13 @@ def configure_semantic_tools(mcp: FastMCP):
success=True,
)
@mcp.tool()
@mcp.tool(
title="Check Indexing Status",
annotations=ToolAnnotations(
readOnlyHint=True, # Only checks status
openWorldHint=True,
),
)
@require_scopes("semantic:read")
@instrument_tool
async def nc_get_vector_sync_status(ctx: Context) -> VectorSyncStatusResponse: