diff --git a/README.md b/README.md index aa66102..9f548d6 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,29 @@ ensuring safe exploration without risk of data modification or corruption. - `search_objects` - Search for digital objects using a query string with pagination support. - `query` - Lucene/Solr compatible search query - - `type` - Optional filter by object type - - `limit` - Number of results per page (default: 1) + - `type` - Optional filter by object type + - `limit` - Number of results per page (default: 25) - `page_num` - Page number to retrieve, 0-based (default: 0) - `count_objects` - Count the total number of objects matching a query. - `query` - Lucene/Solr compatible search query - - `type` - Optional filter by object type + - `type` - Optional filter by object type + +#### Query Syntax + +**CRITICAL**: JSON properties MUST be prefixed with `/` + +✅ **Correct Examples:** +- `/title:*report*` - Wildcard search in title field +- `/author/name:Daniel` - Nested property access +- `/status:active AND /priority:high` - Boolean operators +- Use `type` parameter instead of including `type:` in query + +❌ **Wrong (will fail):** +- `name:John` - Missing `/` prefix +- `author/name:Daniel` - Missing leading `/` +- `type:Person` - Use the `type` parameter instead + +**Operators:** `*` (wildcard), `?` (single char), `AND`, `OR`, `NOT`, `"phrases"` ## Configuration diff --git a/src/cordra_mcp/server.py b/src/cordra_mcp/server.py index df8227f..e5b7af3 100644 --- a/src/cordra_mcp/server.py +++ b/src/cordra_mcp/server.py @@ -29,26 +29,26 @@ logger.setLevel(config.log_level) @mcp.tool( name="search_objects", title="Search Cordra Objects", - description="""Search for digital objects in the Cordra repository using Lucene/Solr query syntax. + description="""Search for digital objects using Lucene/Solr query syntax. -Examples: -- /title:report - Find objects with 'report' in title -- type:Person - Find all Persons. Note that "type" is special and uses no slash "/" -- /author/name:Daniel - Find objects with author Daniel as nested property. -- /name:John AND type:Person - Complex queries +CRITICAL SYNTAX RULES: +1. Properties MUST start with '/' - Example: /title:report +2. Nested properties: /parent/child:value +3. Use 'type' parameter - NEVER 'type:' in query +4. Operators: * ? AND OR NOT "phrases" -Pagination: -- Results are paginated with 0-based page numbering -- Use 'limit' to control page size (default: 25) -- Use 'page_num' to specify which page to retrieve (default: 0) +✅ CORRECT: +- /title:*report* /author/name:Daniel +- /status:active AND /priority:high +- query="/title:report", type="Document" -Returns a JSON object containing: -- object_ids: List of object IDs that match the search -- total_count: Total number of objects matching the query -- page_num: Current page number -- page_size: Number of results per page +❌ WRONG: +- name:John (missing /) +- author/name:Daniel (missing /) +- type:Person (use type parameter) -Use the cordra://objects/{prefix}/{suffix} resources to retrieve full object details.""", +Returns: {results: [ids], total_count, page_num, page_size} +Pagination: limit (default 25), page_num (0-based)""", ) async def search_objects( query: str, @@ -59,12 +59,9 @@ async def search_objects( """Search for digital objects in the Cordra repository with pagination support. Args: - query: The search query string (Lucene/Solr compatible). Examples: - - /title:report - Find objects with 'report' in title - - type:Person - Find all Persons. Note that "type" is special and uses no slash "/" - - /author/name:Daniel - Find objects with author Daniel as nested property. - - /name:John AND type:Person - Complex queries - + query: Search query (Lucene/Solr). Properties MUST start with '/'. + ✅ CORRECT: /title:*report*, /author/name:Daniel + ❌ WRONG: name:John, author/name:Daniel, type:Person type: Optional filter by object type (e.g., "Person", "Document", "Project") limit: Page size - number of results per page (default: 25) page_num: Page number to retrieve, 0-based (default: 0 for first page) @@ -112,11 +109,9 @@ async def count_objects( """Count digital objects in the Cordra repository matching a search query. Args: - query: The search query string (Lucene/Solr compatible). Examples: - - /title:report - Find objects with 'report' in title - - type:Person - Find all Persons. Note that "type" is special and uses no slash "/" - - /author/name:Daniel - Find objects with author Daniel as nested property. - - /name:John AND type:Person - Complex queries + query: Search query (Lucene/Solr). Properties MUST start with '/'. + ✅ CORRECT: /title:*report*, /author/name:Daniel + ❌ WRONG: name:John, author/name:Daniel, type:Person type: Optional filter by object type (e.g., "Person", "Document", "Project") Returns: diff --git a/tests/test_server.py b/tests/test_server.py index dc9472e..31bf268 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -464,6 +464,39 @@ class TestSearchObjects: "nonexistent:data", object_type=None, page_size=25, page_num=0 ) + @patch("cordra_mcp.server.cordra_client") + async def test_search_objects_with_slash_prefixed_properties( + self, mock_client: Any + ) -> None: + """Test object search with correct slash-prefixed property syntax.""" + mock_search_result = { + "results": [ + { + "id": "reports/2024-annual", + "type": "Document", + "content": {"title": "Annual Report 2024"}, + }, + ], + "total_size": 1, + "page_num": 0, + "page_size": 25, + } + mock_client.find = AsyncMock(return_value=mock_search_result) + + # Test with slash-prefixed property and nested property + result = await search_objects("/title:*report* AND /author/name:Daniel") + + parsed_result = json.loads(result) + assert parsed_result["results"] == ["reports/2024-annual"] + assert parsed_result["total_count"] == 1 + + mock_client.find.assert_called_once_with( + "/title:*report* AND /author/name:Daniel", + object_type=None, + page_size=25, + page_num=0, + ) + @patch("cordra_mcp.server.cordra_client") async def test_search_objects_client_error(self, mock_client: Any) -> None: """Test object search with client error."""