Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit b476ee1

Browse files
CopilotMatanga1-2
andauthored
Extend get_entities tool to support minimized context with detailed parameter (#43)
* Initial plan for issue * Initial assessment: Plan to implement detailed parameter support for get_entities Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * Implement detailed parameter support for get_entities tool Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * debug logs * Address reviewer feedback: change default detailed=False, remove redundant comments, upgrade to v0.2.17 Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * Work around PyPort bug by reverting to get_entities method with client-side filtering Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * upgrade pyport * Move business logic from client to tool layer and update detailed parameter description Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * Implement search_entities API with query building in tool layer Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> * Remove comments in get_entities.py Remove comments * Fix failing tests by adding missing search_entities AsyncMock to test client Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Matanga1-2 <24847398+Matanga1-2@users.noreply.github.com> Co-authored-by: Matanga1-2 <matangrady@gmail.com>
1 parent 3291483 commit b476ee1

File tree

8 files changed

+92
-17
lines changed

8 files changed

+92
-17
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
=======
78

8-
## [0.2.16] - 2025-01-17
9+
## [0.2.17] - 2025-06-17
10+
11+
### Added
12+
- Added `detailed` parameter support to `get_entities` tool for minimized context
13+
- Tool now returns only identifier and title when `detailed=False` for reduced context usage
14+
15+
### Changed
16+
- Switched from `get_entities` to `search_blueprint_entities` API call for field filtering support
17+
- Upgrade Pyport pacackeg to 0.3.3.
18+
19+
## [0.2.16] - 2025-06-17
920

1021
### Added
1122
- Added custom User-Agent header to all HTTP requests sent via the MCP server

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-server-port"
3-
version = "0.2.16"
3+
version = "0.2.17"
44
authors = [
55
{ name = "Matan Grady", email = "matan.grady@getport.io" }
66
]
@@ -11,7 +11,7 @@ dependencies = [
1111
"httpx>=0.28.1",
1212
"mcp[cli]>=1.6.0",
1313
"python-dotenv>=1.0.1",
14-
"pyport (>=0.3.2,<=0.3.2)",
14+
"pyport (>=0.3.3,<=0.3.3)",
1515
"loguru>=0.7.2",
1616
"pydantic (>=2.11.3,<3.0.0)"
1717
]
@@ -32,7 +32,7 @@ include = [
3232
]
3333
[tool.poetry]
3434
name = "mcp-server-port"
35-
version = "0.2.16"
35+
version = "0.2.17"
3636
description = "Port's MCP server"
3737
packages = [
3838
{ include = "src" }

src/client/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ async def get_entity(self, blueprint_identifier: str, entity_identifier: str) ->
122122
async def get_entities(self, blueprint_identifier: str) -> list[EntityResult]:
123123
return await self.wrap_request(lambda: self.entities.get_entities(blueprint_identifier))
124124

125+
async def search_entities(self, blueprint_identifier: str, search_query: dict[str, Any]) -> list[EntityResult]:
126+
return await self.wrap_request(lambda: self.entities.search_entities(blueprint_identifier, search_query))
127+
125128
async def create_entity(
126129
self, blueprint_identifier: str, entity_data: dict[str, Any], query: dict[str, Any]
127130
) -> EntityResult:

src/client/entities.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ async def get_entities(self, blueprint_identifier: str) -> list[EntityResult]:
2929
logger.debug("Skipping API validation for entities")
3030
return [EntityResult.construct(**entity_data) for entity_data in entities_data]
3131

32+
async def search_entities(self, blueprint_identifier: str, search_query: dict[str, Any]) -> list[EntityResult]:
33+
logger.info(f"Searching entities for blueprint '{blueprint_identifier}' from Port")
34+
logger.debug(f"Search query: {search_query}")
35+
36+
entities_data = self._client.entities.search_blueprint_entities(blueprint_identifier, search_query)
37+
38+
logger.info(f"Got {len(entities_data)} entities for blueprint '{blueprint_identifier}' from Port")
39+
logger.debug(f"Response for search entities: {entities_data}")
40+
if config.api_validation_enabled:
41+
logger.debug("Validating entities")
42+
return [EntityResult(**entity_data) for entity_data in entities_data]
43+
else:
44+
logger.debug("Skipping API validation for entities")
45+
return [EntityResult.construct(**entity_data) for entity_data in entities_data]
46+
3247
async def get_entity(self, blueprint_identifier: str, entity_identifier: str) -> EntityResult:
3348
logger.info(f"Getting entity '{entity_identifier}' from blueprint '{blueprint_identifier}' from Port")
3449

src/tools/entity/get_entities.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class GetEntitiesToolSchema(BaseModel):
1313
blueprint_identifier: str = Field(..., description="The identifier of the blueprint to get entities for")
1414
detailed: bool = Field(
1515
default=False,
16-
description="If True, returns complete entity details including properties. If False (default), returns summary information only.",
16+
description="Controls whether to return extended entity information. If True, returns complete entity details including all properties. If False (default), returns only identifier and title to keep context minimal. Prefer False unless specifically asked for detailed information.",
1717
)
1818

1919

@@ -48,7 +48,18 @@ async def get_entities(self, props: GetEntitiesToolSchema) -> dict[str, Any]:
4848
if not blueprint_identifier:
4949
raise ValueError("Blueprint identifier is required")
5050

51-
raw_entities = await self.port_client.get_entities(blueprint_identifier)
51+
detailed = args.get("detailed", False)
52+
53+
search_query = {
54+
"query": {
55+
"$blueprint": {"=": blueprint_identifier}
56+
}
57+
}
58+
59+
if not detailed:
60+
search_query["include"] = ["$identifier", "$title"]
61+
62+
raw_entities = await self.port_client.search_entities(blueprint_identifier, search_query)
5263
processed_entities = [entity.model_dump(exclude_unset=True, exclude_none=True) for entity in raw_entities]
5364

5465
response = GetEntitiesToolResponse.construct(entities=processed_entities)

tests/unit/tools/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def mock_client():
1717

1818
client.get_entity = AsyncMock()
1919
client.get_entities = AsyncMock()
20+
client.search_entities = AsyncMock()
2021
client.create_entity = AsyncMock()
2122
client.update_entity = AsyncMock()
2223
client.delete_entity = AsyncMock()

tests/unit/tools/entity/test_get_entities.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,56 @@
77
@pytest.fixture
88
def mock_client_with_entities(mock_client):
99
"""Add specific return values for this test"""
10-
mock_client.get_entities.return_value = [EntityResult(identifier="test-entity", blueprint="test-blueprint")]
10+
mock_client.search_entities.return_value = [EntityResult(identifier="test-entity", blueprint="test-blueprint")]
1111
return mock_client
1212

1313

1414
@pytest.mark.asyncio
15-
async def test_get_entities_tool(mock_client_with_entities):
16-
"""Test the GetEntitiesTool's metadata and function execution."""
17-
# Create the tool
15+
async def test_get_entities_tool_detailed_true(mock_client_with_entities):
16+
"""Test the GetEntitiesTool's metadata and function execution with detailed=True."""
1817
tool = GetEntitiesTool(mock_client_with_entities)
1918

20-
# Test tool metadata
2119
assert tool.name == "get_entities"
2220
assert "entities" in tool.description.lower()
2321

24-
# Test function execution
2522
schema = {"blueprint_identifier": "test-blueprint", "detailed": True}
2623
result = await tool.get_entities(tool.validate_input(schema))
27-
mock_client_with_entities.get_entities.assert_awaited_once_with("test-blueprint")
24+
25+
# Verify search_entities was called with correct query (no include filter for detailed=True)
26+
expected_query = {"query": {"$blueprint": {"=": "test-blueprint"}}}
27+
mock_client_with_entities.search_entities.assert_awaited_once_with("test-blueprint", expected_query)
28+
assert result is not None
29+
30+
31+
@pytest.mark.asyncio
32+
async def test_get_entities_tool_detailed_false(mock_client_with_entities):
33+
"""Test the GetEntitiesTool's function execution with detailed=False."""
34+
tool = GetEntitiesTool(mock_client_with_entities)
35+
36+
schema = {"blueprint_identifier": "test-blueprint", "detailed": False}
37+
result = await tool.get_entities(tool.validate_input(schema))
38+
39+
# Verify search_entities was called with correct query (include filter for detailed=False)
40+
expected_query = {
41+
"query": {"$blueprint": {"=": "test-blueprint"}},
42+
"include": ["$identifier", "$title"]
43+
}
44+
mock_client_with_entities.search_entities.assert_awaited_once_with("test-blueprint", expected_query)
45+
assert result is not None
46+
47+
48+
@pytest.mark.asyncio
49+
async def test_get_entities_tool_default_detailed(mock_client_with_entities):
50+
"""Test the GetEntitiesTool's function execution with default detailed value."""
51+
tool = GetEntitiesTool(mock_client_with_entities)
52+
53+
schema = {"blueprint_identifier": "test-blueprint"}
54+
result = await tool.get_entities(tool.validate_input(schema))
55+
56+
# Verify search_entities was called with correct query (include filter for default detailed=False)
57+
expected_query = {
58+
"query": {"$blueprint": {"=": "test-blueprint"}},
59+
"include": ["$identifier", "$title"]
60+
}
61+
mock_client_with_entities.search_entities.assert_awaited_once_with("test-blueprint", expected_query)
2862
assert result is not None

0 commit comments

Comments
 (0)