Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion python/packages/kagent-adk/src/kagent/adk/_mcp_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,39 @@ def __len__(self) -> int:
return len(self._names)


def _result_has_ui_resource(response: dict) -> bool:
"""Whether this specific result, not just the tool's definition, carries a UI resource.

Mirrors the ``_meta.ui.resourceUri`` / ``_meta["ui/resourceUri"]`` parsing already
used to classify a tool's definition (``go/adk/pkg/mcp/mcp_ui.go:parseMCPUIMetadata``).
"""
meta = response.get("_meta")
if not isinstance(meta, dict):
return False
ui = meta.get("ui")
if isinstance(ui, dict) and ui.get("resourceUri"):
return True
return bool(meta.get("ui/resourceUri"))


def compact_mcp_app_response(response: dict) -> dict:
"""Rewrite an MCP App tool result (a JSON ``CallToolResult``) for the model.

On error, keep the content so the model can diagnose/recover but drop the
heavy structured payload. On success, collapse the render payload into a
terminal directive so the model stops re-invoking the rendering tool,
preserving ``_meta`` (e.g. resourceUri) for any downstream tooling.
preserving ``_meta`` (e.g. resourceUri) for any downstream tooling. A tool
can be UI-capable by definition yet return a plain result for a given
call; only compact when this result itself carries a UI resource.
"""
if response.get("isError") is True or response.get("error") is True:
compacted = dict(response)
compacted.pop("structuredContent", None)
return compacted

if not _result_has_ui_resource(response):
return response

compacted: dict = {"content": [{"type": "text", "text": MCP_APP_RENDERED_NOTICE}]}
if "_meta" in response:
compacted["_meta"] = response["_meta"]
Expand Down
24 changes: 23 additions & 1 deletion python/packages/kagent-adk/tests/unittests/test_mcp_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ def test_compact_success_replaces_payload_with_notice():
assert compacted["_meta"] == response["_meta"]


def test_compact_skips_data_only_result_from_a_ui_capable_tool():
# A UI-capable tool's result with no UI resource of its own.
response = {
"content": [{"type": "text", "text": '{"issues": [{"key": "GF-3687"}]}'}],
"structuredContent": {"issues": [{"key": "GF-3687"}]},
"_meta": {},
}
compacted = compact_mcp_app_response(response)
assert compacted == response


def test_compact_skips_result_with_no_meta_at_all():
# Servers that never set _meta on a call result (as opposed to an empty one).
response = {"content": [{"type": "text", "text": "hello"}], "structuredContent": {"x": 1}}
compacted = compact_mcp_app_response(response)
assert compacted == response


def test_compact_error_keeps_content_drops_structured():
response = {
"content": [{"type": "text", "text": "boom"}],
Expand All @@ -48,7 +66,11 @@ def test_callback_compacts_only_app_tool_responses():
app_tool_names.add("show-weather-dashboard")
callback = make_mcp_app_model_result_callback(app_tool_names)

weather = {"structuredContent": {"temperature": 36}, "content": [{"type": "text", "text": "36C"}]}
weather = {
"structuredContent": {"temperature": 36},
"content": [{"type": "text", "text": "36C"}],
"_meta": {"ui": {"resourceUri": "ui://server-everything/weather-dashboard"}},
}
echo = {"content": [{"type": "text", "text": "hello"}]}
request = LlmRequest(
contents=[
Expand Down
Loading