feat: change search_objects to return only object IDs

- Changed search_objects function to return only object IDs instead of full objects
- Updated response format to include object_ids, total_count, page_num, and page_size
- Increased default page size from 1 to 25 for better performance
- Updated documentation to reflect new return format

This improves performance by reducing response size and encourages proper
use of cordra:// resources for full object retrieval.
This commit is contained in:
Daniel Bauer
2025-07-09 10:55:45 +02:00
parent 1eb7285132
commit 3dc4e664af
2 changed files with 66 additions and 30 deletions

View File

@@ -37,15 +37,21 @@ Examples:
Pagination: Pagination:
- Results are paginated with 0-based page numbering - Results are paginated with 0-based page numbering
- Use 'limit' to control page size (default: 1) - Use 'limit' to control page size (default: 25)
- Use 'page_num' to specify which page to retrieve (default: 0) - Use 'page_num' to specify which page to retrieve (default: 0)
Returns a JSON list of matching objects with their full metadata.""" 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
Use the cordra://objects/{prefix}/{suffix} resources to retrieve full object details.""",
) )
async def search_objects( async def search_objects(
query: str, query: str,
type: str | None = None, type: str | None = None,
limit: int = 1, limit: int = 25,
page_num: int = 0, page_num: int = 0,
) -> str: ) -> str:
"""Search for digital objects in the Cordra repository with pagination support. """Search for digital objects in the Cordra repository with pagination support.
@@ -56,16 +62,22 @@ async def search_objects(
- "/author:smith" - Find objects by author Smith - "/author:smith" - Find objects by author Smith
- "/name:John AND type:Person" - Complex queries - "/name:John AND type:Person" - Complex queries
type: Optional filter by object type (e.g., "Person", "Document", "Project") type: Optional filter by object type (e.g., "Person", "Document", "Project")
limit: Page size - number of results per page (default: 1) limit: Page size - number of results per page (default: 25)
page_num: Page number to retrieve, 0-based (default: 0 for first page) page_num: Page number to retrieve, 0-based (default: 0 for first page)
Returns: Returns:
JSON string containing list of matching objects with their full metadata JSON string containing object IDs and pagination info
""" """
try: try:
search_result = await cordra_client.find(query, object_type=type, page_size=limit, page_num=page_num) search_result = await cordra_client.find(
results = search_result["results"] query, object_type=type, page_size=limit, page_num=page_num
return json.dumps(results, indent=2) )
# Extract only the IDs from the results
search_result["results"] = [obj["id"] for obj in search_result["results"]]
# Rename for consistency with documentation
search_result["total_count"] = search_result.pop("total_size")
return json.dumps(search_result, indent=2)
except ValueError as e: except ValueError as e:
raise RuntimeError(f"Invalid search parameters: {e}") from e raise RuntimeError(f"Invalid search parameters: {e}") from e
@@ -168,7 +180,9 @@ async def register_schema_resources() -> None:
page_size = 20 page_size = 20
while True: while True:
search_result = await cordra_client.find("type:Schema", page_size=page_size, page_num=page_num) search_result = await cordra_client.find(
"type:Schema", page_size=page_size, page_num=page_num
)
schemas = search_result["results"] schemas = search_result["results"]
all_schemas.extend(schemas) all_schemas.extend(schemas)

View File

@@ -306,12 +306,15 @@ class TestSearchObjects:
# Verify the result is valid JSON # Verify the result is valid JSON
parsed_result = json.loads(result) parsed_result = json.loads(result)
assert len(parsed_result) == 2 assert parsed_result["results"] == ["people/john-doe", "people/jane-smith"]
assert parsed_result[0]["id"] == "people/john-doe" assert parsed_result["total_count"] == 2
assert parsed_result[1]["id"] == "people/jane-smith" assert parsed_result["page_num"] == 0
assert parsed_result["page_size"] == 1000
# Verify the client was called with correct parameters # Verify the client was called with correct parameters
mock_client.find.assert_called_once_with("name:John", object_type=None, page_size=1, page_num=0) mock_client.find.assert_called_once_with(
"name:John", object_type=None, page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_with_type_filter(self, mock_client): async def test_search_objects_with_type_filter(self, mock_client):
@@ -330,11 +333,13 @@ class TestSearchObjects:
# Verify the result is valid JSON # Verify the result is valid JSON
parsed_result = json.loads(result) parsed_result = json.loads(result)
assert len(parsed_result) == 1 assert parsed_result["results"] == ["people/john-doe"]
assert parsed_result[0]["type"] == "Person" assert parsed_result["total_count"] == 1
# Verify the client was called with type filter # Verify the client was called with type filter
mock_client.find.assert_called_once_with("name:John", object_type="Person", page_size=1, page_num=0) mock_client.find.assert_called_once_with(
"name:John", object_type="Person", page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_with_limit(self, mock_client): async def test_search_objects_with_limit(self, mock_client):
@@ -353,7 +358,8 @@ class TestSearchObjects:
# Verify the result is valid JSON # Verify the result is valid JSON
parsed_result = json.loads(result) parsed_result = json.loads(result)
assert len(parsed_result) == 1 assert parsed_result["results"] == ["people/john-doe"]
assert parsed_result["page_size"] == 50
# Verify the client was called with custom limit # Verify the client was called with custom limit
mock_client.find.assert_called_once_with("name:John", object_type=None, page_size=50, page_num=0) mock_client.find.assert_called_once_with("name:John", object_type=None, page_size=50, page_num=0)
@@ -375,11 +381,13 @@ class TestSearchObjects:
# Verify the result is valid JSON # Verify the result is valid JSON
parsed_result = json.loads(result) parsed_result = json.loads(result)
assert len(parsed_result) == 1 assert parsed_result["results"] == ["documents/report-123"]
assert parsed_result[0]["type"] == "Document" assert parsed_result["total_count"] == 1
# Verify the client was called with all parameters # Verify the client was called with all parameters
mock_client.find.assert_called_once_with("title:Report", object_type="Document", page_size=25, page_num=0) mock_client.find.assert_called_once_with(
"title:Report", object_type="Document", page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_empty_results(self, mock_client): async def test_search_objects_empty_results(self, mock_client):
@@ -396,9 +404,12 @@ class TestSearchObjects:
# Verify the result is valid JSON with empty array # Verify the result is valid JSON with empty array
parsed_result = json.loads(result) parsed_result = json.loads(result)
assert parsed_result == [] assert parsed_result["results"] == []
assert parsed_result["total_count"] == 0
mock_client.find.assert_called_once_with("nonexistent:data", object_type=None, page_size=1, page_num=0) mock_client.find.assert_called_once_with(
"nonexistent:data", object_type=None, page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_client_error(self, mock_client): async def test_search_objects_client_error(self, mock_client):
@@ -409,7 +420,9 @@ class TestSearchObjects:
await search_objects("test:query") await search_objects("test:query")
assert "Search failed:" in str(exc_info.value) assert "Search failed:" in str(exc_info.value)
mock_client.find.assert_called_once_with("test:query", object_type=None, page_size=1, page_num=0) mock_client.find.assert_called_once_with(
"test:query", object_type=None, page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_value_error(self, mock_client): async def test_search_objects_value_error(self, mock_client):
@@ -420,7 +433,9 @@ class TestSearchObjects:
await search_objects("invalid:query") await search_objects("invalid:query")
assert "Invalid search parameters:" in str(exc_info.value) assert "Invalid search parameters:" in str(exc_info.value)
mock_client.find.assert_called_once_with("invalid:query", object_type=None, page_size=1, page_num=0) mock_client.find.assert_called_once_with(
"invalid:query", object_type=None, page_size=25, page_num=0
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_json_formatting(self, mock_client): async def test_search_objects_json_formatting(self, mock_client):
@@ -445,9 +460,8 @@ class TestSearchObjects:
assert " " in result # Should have 2-space indentation assert " " in result # Should have 2-space indentation
# Verify the content is correctly formatted # Verify the content is correctly formatted
assert parsed_result[0]["id"] == "test/object" assert parsed_result["results"] == ["test/object"]
assert parsed_result[0]["type"] == "Test" assert parsed_result["total_count"] == 1
assert parsed_result[0]["content"]["data"] == "value"
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_with_page_num(self, mock_client): async def test_search_objects_with_page_num(self, mock_client):
@@ -465,14 +479,20 @@ class TestSearchObjects:
await search_objects("type:Document", page_num=1) await search_objects("type:Document", page_num=1)
# Verify the client was called with correct page number # Verify the client was called with correct page number
mock_client.find.assert_called_once_with("type:Document", object_type=None, page_size=1, page_num=1) mock_client.find.assert_called_once_with(
"type:Document", object_type=None, page_size=25, page_num=1
)
@patch('cordra_mcp.server.cordra_client') @patch('cordra_mcp.server.cordra_client')
async def test_search_objects_with_all_pagination_params(self, mock_client): async def test_search_objects_with_all_pagination_params(self, mock_client):
"""Test object search with all pagination parameters.""" """Test object search with all pagination parameters."""
mock_search_result = { mock_search_result = {
"results": [ "results": [
{"id": "reports/report-51", "type": "Report", "content": {"title": "Report 51"}}, {
"id": "reports/report-51",
"type": "Report",
"content": {"title": "Report 51"},
},
], ],
"total_size": 100, "total_size": 100,
"page_num": 5, "page_num": 5,
@@ -483,7 +503,9 @@ class TestSearchObjects:
await search_objects("type:Report", type="Report", limit=10, page_num=5) await search_objects("type:Report", type="Report", limit=10, page_num=5)
# Verify the client was called with all parameters # Verify the client was called with all parameters
mock_client.find.assert_called_once_with("type:Report", object_type="Report", page_size=10, page_num=5) mock_client.find.assert_called_once_with(
"type:Report", object_type="Report", page_size=10, page_num=5
)
class TestGetCordraDesign: class TestGetCordraDesign: