Commit Graph
22 Commits
Author SHA1 Message Date
Chris CoutinhoandClaude Opus 4.7 a995155bd4 fix(deck): address PR #759 round-3 review feedback
- Drop "(in-place)" from filter-helper docstrings; callers should
  consume the return value, mutation is an implementation detail.
- Document that deck_get_archived_stacks always returns cards (an
  archived stack without its cards has no audit value); point to
  description_max_length for size control.
- Document that deck_get_cards applies filtering client-side, so it
  is network-equivalent to deck_get_stack(include_cards=True).
- Pin the empty-list contract: a stack with all-archived cards and
  include_archived_cards=False yields cards == [] (loaded but empty),
  not cards is None (explicitly suppressed).
- Add explicit one-character-over-limit truncation test alongside the
  existing exact-boundary test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:09:54 +02:00
Chris CoutinhoandClaude Opus 4.7 b7805c2180 fix(deck): address PR #759 round-2 review feedback
- Move description_max_length validation to tool layer
  (_validate_description_max_length), matching the existing
  _validate_comment_message pattern; helper now trusts callers per
  CLAUDE.md ("validate at system boundaries only").
- Fix mutation/return inconsistency: deck_get_stacks now uses a list
  comprehension to capture _apply_stack_filters' return, matching
  deck_get_stack / deck_get_archived_stacks.
- Rename include_archived -> include_archived_cards on deck_get_cards
  and _apply_card_filters for consistency with deck_get_stacks.
- Route deck_get_archived_stacks through _apply_stack_filters so
  future filters apply uniformly to active + archived paths.
- Trim _truncate_card_descriptions docstring to one line; add inline
  comment in _apply_stack_filters explaining the breaking-change
  default (mirrors Deck UI archived-card filtering).
- Replace fragile call_args[0][1] with call_args.args[1] in the
  archived-stacks client test.
- Modernize Optional[X] -> X | None throughout deck.py (adjacent
  cleanup called out in the review).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:50:56 +02:00
Chris CoutinhoandClaude Opus 4.7 7d633a945d fix(deck): address PR #759 review feedback
- Validate description_max_length is positive (raises ValueError on 0
  or negative); the prior code would have wiped descriptions to a
  single ellipsis character on description_max_length=0.
- Extract filter logic into testable module-level helpers
  (_apply_board_filters, _apply_stack_filters, _apply_card_filters)
  and replace the dense `continue`-based loop in deck_get_stacks with
  the reviewer's elif form.
- Document the truncation length quirk in the helper docstring (result
  is description_max_length + 1 chars when truncation fires).
- Add 13 new unit tests covering the include/exclude flags on board,
  stacks, and flat card lists, plus the new validation paths and an
  explicit "description fits within limit, no ellipsis" case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:41:14 +02:00
Chris CoutinhoandClaude Opus 4.7 d8cd073e66 feat(deck): add response filters and archived stacks tool
Add filtering options to deck read tools to keep responses compact on
boards with accumulated cards/comments, and expose archived stacks so
agents can audit completed work that has been archived off the active
board.

- deck_get_board: include_acl, include_users, include_labels
- deck_get_stacks/deck_get_stack: include_cards, include_archived_cards,
  description_max_length
- deck_get_cards: include_archived, description_max_length
- New deck_get_archived_stacks tool wrapping the existing client method

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:00:09 +02:00
Chris CoutinhoandClaude Opus 4.7 2129bd6fac fix(deck): address review feedback on card comment tools
- Wrap raw DeckComment returns in CardCommentResponse(BaseResponse) for
  create/update so the success/timestamp envelope matches other deck tools
  (#737 review issue 2).
- Rename ListCardCommentsResponse.total → count and clarify in the
  description that it's the page size, not a server-side total — the Deck
  list endpoint does not expose one (#737 review issue 3).
- Validate the documented 1000-character limit on create/update with an
  inline length check + ValueError, matching the pattern in
  api/management.py (#737 review issue 4).
- Use modern int | None union syntax for the new parent_id parameter
  (#737 review issue 1); rest of the file is left in the existing
  Optional[...] style.

Also add an MCP-level test that the >1000 char message is rejected, and
update the existing comment tests to unwrap the new comment field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:57:46 +02:00
Chris CoutinhoandClaude Opus 4.7 454f6912bc feat(deck): add card comment tools
Expose four new MCP tools backed by existing DeckClient comment methods:

- deck_get_card_comments — list with limit/offset pagination
- deck_create_card_comment — top-level or threaded (via parent_id)
- deck_update_card_comment — author-only on the server
- deck_delete_card_comment — author-only, destructive, idempotent

Adds ListCardCommentsResponse and CardCommentOperationResponse models, and
extends the client unit tests to cover replies, deletion, pagination, and
the request shape for updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:39:24 +02:00
Chris CoutinhoandClaude Opus 4.6 29fd0486c9 refactor: change OAuth scope separator from colon to dot for IDP compatibility
Many identity providers (AWS Cognito, Okta, Azure AD) reject or mishandle
colons in OAuth scope names. This migrates all custom scopes from
`resource:action` to `resource.action` format (e.g., `notes:read` →
`notes.read`), which is universally accepted and aligns with industry
conventions (Microsoft, Google).

Includes Alembic migration 004 for stored scope strings and ADR-024
documenting the rationale and RFC references.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:07:02 +02:00
Chris CoutinhoandClaude Opus 4.6 44a27bd9e9 fix: address PR review feedback (round 2)
Bug fixes:
- Catch OCSError/HTTPStatusError in all server tools, convert to McpError
- Guard update_collective against empty body (raise ValueError)
- Use restore_page response data in status message

ADR-017 annotation fix:
- Distinguish "remove" (reversible association) from "delete" (permanent):
  remove_tag and deck_remove_label_from_card no longer set destructiveHint
- Update annotation test to exclude "remove" from destructive keywords

Data model improvements:
- Add trashTimestamp field to PageInfo
- Create ListTrashedPagesResponse with is_trash context flag
- Add collective_id to ListTagsResponse

Test robustness:
- Read NC credentials from environment variables (not hardcoded)
- Filter landing page by parentId == 0 instead of assuming pages[0]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:08:13 +01:00
Chris CoutinhoandClaude Opus 4.6 f51b27ba19 fix: address PR #574 third review round
- Guard board.labels against None in deck_get_labels and resource
- Add TODO comments for calendar_display_name in single-calendar paths
- Document _raw_contact_to_model scope limitation (maps only what the
  client returns; expanding requires changes to vCard parsing)
- Log debug warning when event has no start_datetime
- Verified Table model is safe with extra fields (Pydantic v2 ignores)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:49:55 +01:00
Chris CoutinhoandClaude Opus 4.6 8887aa241a fix: wrap raw list returns in response models to produce single TextContent block
MCP tools returning raw lists caused FastMCP's _convert_to_content() to create
one TextContent block per element. Most MCP clients only read content[0], so
they saw a single result instead of the full list.

Wrapped 9 tool functions in proper response objects:
- deck: deck_get_boards, deck_get_stacks, deck_get_cards, deck_get_labels
- calendar: nc_calendar_list_events, nc_calendar_get_upcoming_events
- contacts: nc_contacts_list_addressbooks, nc_contacts_list_contacts
- tables: nc_tables_list_tables

Closes #568

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 09:22:16 +01:00
Chris CoutinhoandClaude Opus 4.5 e4cddef343 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 <noreply@anthropic.com>
2026-01-14 20:02:20 +01:00
Chris CoutinhoandClaude Sonnet 4.5 e1412320a7 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>
2025-12-11 12:45:02 +01:00
Chris Coutinho c3023d2cc3 feat: Complete Phase 5 - Instrument all 93 MCP tools
Applied @instrument_tool decorator to all 86 remaining tools
across 8 server files.

Instrumented files:
- calendar.py: 16 tools
- contacts.py: 7 tools
- deck.py: 25 tools
- webdav.py: 11 tools
- tables.py: 6 tools
- sharing.py: 5 tools
- cookbook.py: 13 tools
- semantic.py: 3 tools

Total: 93 tools instrumented (7 in notes.py + 86 in other files)

These metrics populate:
- MCP Tool Calls panel (by tool name and status)
- MCP Tool Duration panel (histogram)
- MCP Tool Errors panel (by tool name and error type)

This completes PR #295 - All 5 phases of metrics instrumentation done:
 Phase 1: Queue size metrics (2 locations)
 Phase 2: Health checks (1 location)
 Phase 3: Database operations (3 methods)
 Phase 4: OAuth token metrics (3 locations)
 Phase 5: MCP tool metrics (93 tools)

All 34 dashboard panels now have data sources.
2025-11-13 16:58:44 +01:00
Chris CoutinhoandClaude 71e77e95bc refactor: integrate token exchange into unified get_client() pattern
Resolves the token exchange implementation gap where get_session_client()
was implemented but never used by tools. Unifies token acquisition into a
single async get_client() method that handles both pass-through and token
exchange modes transparently.

Core Changes:
- Make get_client() async and merge token exchange logic into it
- Remove scopes parameter from token exchange (Nextcloud doesn't support OAuth scopes)
- Update all 8 tool modules to use await get_client(ctx)
- Fix provisioning decorator to skip checks in BasicAuth mode

Token Acquisition Modes:
1. BasicAuth: Returns shared client (no token operations)
2. OAuth pass-through (default): Verifies and passes Flow 1 token to Nextcloud
3. OAuth token exchange (opt-in): Exchanges Flow 1 token for ephemeral token via RFC 8693

Key Architectural Clarifications:
- Progressive Consent (Flow 1/2) = Authorization architecture
- Token Exchange = Token acquisition pattern during tool execution
- Refresh tokens from Flow 2 are NEVER used for tool calls (only background jobs)
- Nextcloud scopes are "soft-scopes" enforced by MCP server, not IdP

Documentation Updates:
- ADR-004: Added comprehensive token acquisition patterns section
- CRITICAL-TOKEN-EXCHANGE-PATTERN.md: Updated to reflect implementation status
- CLAUDE.md: Updated architectural patterns with async get_client()

Testing:
- All 36 unit tests passing
- All 4 smoke tests passing (BasicAuth mode)
- Linting issues fixed (ruff)

Configuration:
ENABLE_TOKEN_EXCHANGE=false (default) - pass-through mode
ENABLE_TOKEN_EXCHANGE=true (opt-in) - token exchange mode

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 20:33:56 +01:00
Chris Coutinho d452684535 feat: Split read/write scopes into app:read/write scopes 2025-10-24 04:38:49 +02:00
Chris Coutinho c069d78f80 feat: Initialize JWT-scoped tools 2025-10-22 06:21:16 +02:00
Chris Coutinho 13e4915e38 test: Remove unused pytest fixtures 2025-10-14 01:23:39 +02:00
Chris Coutinho 4d7e4b9a4b feat(server): Experimental support for OAuth2/OIDC authentication 2025-10-14 01:22:15 +02:00
Chris Coutinho cc9650b077 refactor: Add tools for all resources to enable tool-only workflows 2025-09-24 00:13:24 +02:00
Chris Coutinho 7498b501eb chore: Remove remaining tools 2025-09-11 09:31:13 +02:00
Chris Coutinho d2d413afcd feat(deck): Add support for stack, cards, labels 2025-09-11 00:35:02 +02:00
Chris Coutinho 167053578d feat(deck): Initialize Deck app client/server 2025-09-11 00:10:25 +02:00