mirror of
https://github.com/dnlbauer/cordra-mcp.git
synced 2026-09-10 13:45:30 +00:00
feat: add tool to count objects
This commit is contained in:
@@ -31,6 +31,9 @@ ensuring safe exploration without risk of data modification or corruption.
|
||||
- `type` - Optional filter by object type
|
||||
- `limit` - Number of results per page (default: 1)
|
||||
- `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
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -87,6 +87,51 @@ async def search_objects(
|
||||
raise RuntimeError(f"Search failed: {e}") from e
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
name="count_objects",
|
||||
title="Count Cordra Objects matching a query",
|
||||
description="""Count the total number of digital objects matching a search query.
|
||||
|
||||
Examples:
|
||||
- /title:report - Count objects with 'report' in title
|
||||
- /author:smith - Count objects by author Smith
|
||||
- /name:John AND type:Person - Complex queries
|
||||
|
||||
Returns the count of objects as integer.
|
||||
""",
|
||||
)
|
||||
async def count_objects(
|
||||
query: str,
|
||||
type: str | None = None,
|
||||
) -> str:
|
||||
"""Count digital objects in the Cordra repository matching a search query.
|
||||
|
||||
Args:
|
||||
query: The search query string (Lucene/Solr compatible). Examples:
|
||||
- "/title:report" - Count objects with "report" in title
|
||||
- "/author:smith" - Count objects by author Smith
|
||||
- "/name:John AND type:Person" - Complex queries
|
||||
type: Optional filter by object type (e.g., "Person", "Document", "Project")
|
||||
|
||||
Returns:
|
||||
integer with the number of objects matching the criteria.
|
||||
"""
|
||||
try:
|
||||
# Use page_size=1 to get minimal data, we only need the total count
|
||||
search_result = await cordra_client.find(
|
||||
query, object_type=type, page_size=1, page_num=0
|
||||
)
|
||||
|
||||
total_size: int = search_result["total_size"]
|
||||
return str(total_size)
|
||||
except ValueError as e:
|
||||
raise RuntimeError(f"Invalid search parameters: {e}") from e
|
||||
except CordraAuthenticationError as e:
|
||||
raise RuntimeError(f"Authentication failed: {e}") from e
|
||||
except CordraClientError as e:
|
||||
raise RuntimeError(f"Count failed: {e}") from e
|
||||
|
||||
|
||||
@mcp.resource(
|
||||
"cordra://objects/{prefix}/{suffix}",
|
||||
name="cordra-object",
|
||||
@@ -220,7 +265,6 @@ async def register_schema_resources() -> None:
|
||||
logger.warning(f"Failed to register schema resources: {e}")
|
||||
|
||||
|
||||
|
||||
async def initialize_server() -> None:
|
||||
"""Initialize server resources before starting."""
|
||||
logger.info("Initializing Cordra MCP server...")
|
||||
|
||||
@@ -604,3 +604,110 @@ class TestGetCordraDesign:
|
||||
assert "type" in parsed_result
|
||||
assert "content" in parsed_result
|
||||
assert "metadata" in parsed_result
|
||||
|
||||
|
||||
class TestCountObjects:
|
||||
"""Test the count_objects tool."""
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_success(self, mock_client):
|
||||
"""Test successful object count."""
|
||||
mock_search_result = {
|
||||
"results": [{"id": "people/john-doe", "type": "Person"}],
|
||||
"total_size": 42,
|
||||
"page_num": 0,
|
||||
"page_size": 1,
|
||||
}
|
||||
mock_client.find = AsyncMock(return_value=mock_search_result)
|
||||
|
||||
result = await count_objects("name:John")
|
||||
|
||||
# Verify the result is a string representation of the count
|
||||
assert result == "42"
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_with_type_filter(self, mock_client):
|
||||
"""Test object count with type filter."""
|
||||
mock_search_result = {
|
||||
"results": [{"id": "people/john-doe", "type": "Person"}],
|
||||
"total_size": 15,
|
||||
"page_num": 0,
|
||||
"page_size": 1,
|
||||
}
|
||||
mock_client.find = AsyncMock(return_value=mock_search_result)
|
||||
|
||||
result = await count_objects("name:John", type="Person")
|
||||
|
||||
# Verify the result is a string representation of the count
|
||||
assert result == "15"
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_zero_results(self, mock_client):
|
||||
"""Test object count with zero results."""
|
||||
mock_search_result = {
|
||||
"results": [],
|
||||
"total_size": 0,
|
||||
"page_num": 0,
|
||||
"page_size": 1,
|
||||
}
|
||||
mock_client.find = AsyncMock(return_value=mock_search_result)
|
||||
|
||||
result = await count_objects("nonexistent:data")
|
||||
|
||||
# Verify the result is "0"
|
||||
assert result == "0"
|
||||
|
||||
mock_client.find.assert_called_once_with(
|
||||
"nonexistent:data", object_type=None, page_size=1, page_num=0
|
||||
)
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_client_error(self, mock_client):
|
||||
"""Test object count with client error."""
|
||||
mock_client.find = AsyncMock(side_effect=CordraClientError("Search failed"))
|
||||
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
await count_objects("test:query")
|
||||
|
||||
assert "Count failed:" in str(exc_info.value)
|
||||
mock_client.find.assert_called_once_with(
|
||||
"test:query", object_type=None, page_size=1, page_num=0
|
||||
)
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_value_error(self, mock_client):
|
||||
"""Test object count with value error."""
|
||||
mock_client.find = AsyncMock(side_effect=ValueError("Invalid query"))
|
||||
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
await count_objects("invalid:query")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@patch("cordra_mcp.server.cordra_client")
|
||||
async def test_count_objects_authentication_error(self, mock_client):
|
||||
"""Test object count with authentication error."""
|
||||
mock_client.find = AsyncMock(
|
||||
side_effect=CordraAuthenticationError("Authentication failed")
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
await count_objects("test:query")
|
||||
|
||||
assert "Authentication failed:" in str(exc_info.value)
|
||||
mock_client.find.assert_called_once_with(
|
||||
"test:query", object_type=None, page_size=1, page_num=0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user