feat: implement ADR-009 - refactor semantic search to use generic semantic:read scope
This implements ADR-009, which documents the decision to use a generic
`semantic:read` OAuth scope instead of requiring all app-specific scopes
for semantic search functionality.
Changes:
- Created new `nextcloud_mcp_server/models/semantic.py` with semantic search models
- SemanticSearchResult (with new doc_type field for multi-app support)
- SemanticSearchResponse
- SamplingSearchResponse
- VectorSyncStatusResponse
- Created new `nextcloud_mcp_server/server/semantic.py` with semantic search tools
- nc_semantic_search (renamed from nc_notes_semantic_search)
- nc_semantic_search_answer (renamed from nc_notes_semantic_search_answer)
- nc_get_vector_sync_status (renamed from nc_notes_get_vector_sync_status)
- All tools now use @require_scopes("semantic:read") instead of "notes:read"
- Updated `nextcloud_mcp_server/server/notes.py`
- Removed semantic search tools (moved to semantic.py)
- Removed semantic search model imports
- Removed unused MCP imports (ModelHint, ModelPreferences, etc.)
- Updated `nextcloud_mcp_server/models/notes.py`
- Removed semantic search models (moved to semantic.py)
- Updated `nextcloud_mcp_server/app.py`
- Import configure_semantic_tools
- Register semantic tools when VECTOR_SYNC_ENABLED=true
- Updated `nextcloud_mcp_server/server/__init__.py`
- Export configure_semantic_tools
- Updated tests
- tests/integration/test_sampling.py: Use new tool names
- tests/unit/test_response_models.py: Import from semantic.py, add doc_type field
Architecture:
- Semantic search is now a cross-app feature, not tied to Notes
- Uses dual-phase authorization: semantic:read scope + per-document verification
- Supports future multi-app indexing (notes, calendar, deck, files, contacts)
Test results:
- All 69 unit tests passing
- All 5 smoke tests passing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,10 @@ from nextcloud_mcp_server.models.notes import (
|
||||
CreateNoteResponse,
|
||||
Note,
|
||||
NoteSearchResult,
|
||||
SamplingSearchResponse,
|
||||
SearchNotesResponse,
|
||||
)
|
||||
from nextcloud_mcp_server.models.semantic import (
|
||||
SamplingSearchResponse,
|
||||
SemanticSearchResult,
|
||||
)
|
||||
|
||||
@@ -131,6 +133,7 @@ def test_sampling_search_response_with_answer():
|
||||
sources = [
|
||||
SemanticSearchResult(
|
||||
id=1,
|
||||
doc_type="note",
|
||||
title="Python Guide",
|
||||
category="Development",
|
||||
excerpt="Use async/await for asynchronous programming",
|
||||
@@ -140,6 +143,7 @@ def test_sampling_search_response_with_answer():
|
||||
),
|
||||
SemanticSearchResult(
|
||||
id=2,
|
||||
doc_type="note",
|
||||
title="Best Practices",
|
||||
category="Development",
|
||||
excerpt="Always use context managers with async operations",
|
||||
@@ -189,6 +193,7 @@ def test_sampling_search_response_fallback():
|
||||
sources = [
|
||||
SemanticSearchResult(
|
||||
id=1,
|
||||
doc_type="note",
|
||||
title="Note 1",
|
||||
category="Work",
|
||||
excerpt="Some content",
|
||||
|
||||
Reference in New Issue
Block a user