Skip to content
51 changes: 47 additions & 4 deletions python/packages/core/agent_framework/_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from ._tools import FunctionTool
from ._types import (
ChatOptions,
Content,
Message,
)
Expand Down Expand Up @@ -146,7 +147,10 @@ def _parse_tool_result_from_mcp(

Converts each content item in the MCP result to its appropriate
Content form. Text items become ``Content(type="text")`` and media
items (images, audio) are preserved as rich Content.
items (images, audio) are preserved as rich Content. When no content
items are produced but ``structuredContent`` is present on the result,
the structured payload is serialised as JSON text so it is still
surfaced to the caller.

Args:
mcp_type: The MCP CallToolResult object to convert.
Expand Down Expand Up @@ -192,6 +196,18 @@ def _parse_tool_result_from_mcp(
case _:
result.append(Content.from_text(str(item)))

if not result and mcp_type.structuredContent is not None:
try:
text = json.dumps(mcp_type.structuredContent)
except (TypeError, ValueError):
text = str(mcp_type.structuredContent)
result.append(
Content.from_text(
text,
additional_properties={"structured_content": mcp_type.structuredContent},
)
)

if not result:
result.append(Content.from_text("null"))
return result
Expand Down Expand Up @@ -649,6 +665,11 @@ async def _connect_on_owner(self, *, reset: bool = False) -> None:
error_msg = f"Failed to connect to MCP server: {ex}"
raise ToolException(error_msg, inner_exception=ex) from ex
try:
sampling_capabilities = None
if self.client is not None:
sampling_capabilities = types.SamplingCapability(
tools=types.SamplingToolsCapability(),
)
session = await self._exit_stack.enter_async_context(
ClientSession(
read_stream=transport[0],
Expand All @@ -659,6 +680,7 @@ async def _connect_on_owner(self, *, reset: bool = False) -> None:
message_handler=self.message_handler,
logging_callback=self.logging_callback,
sampling_callback=self.sampling_callback,
sampling_capabilities=sampling_capabilities,
)
)
except Exception as ex:
Expand Down Expand Up @@ -732,14 +754,35 @@ async def sampling_callback(
messages: list[Message] = []
for msg in params.messages:
messages.append(_parse_message_from_mcp(msg))

options: ChatOptions[None] = {}
if params.systemPrompt is not None:
options["instructions"] = params.systemPrompt
if params.tools is not None:
options["tools"] = [
FunctionTool(
name=tool.name,
description=tool.description or "",
input_model=tool.inputSchema,
)
for tool in params.tools
]
if params.toolChoice is not None and params.toolChoice.mode is not None:
options["tool_choice"] = params.toolChoice.mode

if params.temperature is not None:
options["temperature"] = params.temperature
options["max_tokens"] = params.maxTokens
if params.stopSequences is not None:
options["stop"] = params.stopSequences

try:
response = await self.client.get_response(
messages,
temperature=params.temperature,
max_tokens=params.maxTokens,
stop=params.stopSequences,
options=options or None,
)
except Exception as ex:
logger.debug("Sampling callback error: %s", ex, exc_info=True)
return types.ErrorData(
code=types.INTERNAL_ERROR,
message=f"Failed to get chat message content: {ex}",
Expand Down
Loading
Loading