|
10 | 10 | from contextlib import AbstractAsyncContextManager, AsyncExitStack, _AsyncGeneratorContextManager |
11 | 11 | from datetime import timedelta |
12 | 12 | from functools import partial |
| 13 | +from itertools import chain |
13 | 14 | from typing import TYPE_CHECKING, Any |
14 | 15 |
|
15 | 16 | from mcp import types |
|
42 | 43 | from semantic_kernel.prompt_template.prompt_template_base import PromptTemplateBase |
43 | 44 | from semantic_kernel.utils.feature_stage_decorator import experimental |
44 | 45 |
|
| 46 | +from ..contents.function_call_content import FunctionCallContent |
| 47 | +from ..contents.function_result_content import FunctionResultContent |
| 48 | + |
45 | 49 | if sys.version_info >= (3, 11): |
46 | 50 | from typing import Self # pragma: no cover |
47 | 51 | else: |
@@ -72,50 +76,78 @@ def _mcp_prompt_message_to_kernel_content( |
72 | 76 | mcp_type: types.PromptMessage | types.SamplingMessage, |
73 | 77 | ) -> ChatMessageContent: |
74 | 78 | """Convert a MCP container type to a Semantic Kernel type.""" |
| 79 | + items = list( |
| 80 | + chain( |
| 81 | + *[_mcp_content_types_to_kernel_content(mcp_type.content)], |
| 82 | + ) |
| 83 | + ) |
75 | 84 | return ChatMessageContent( |
76 | 85 | role=AuthorRole(mcp_type.role), |
77 | | - items=[_mcp_content_types_to_kernel_content(mcp_type.content)], |
| 86 | + items=items, # type: ignore |
78 | 87 | inner_content=mcp_type, |
79 | 88 | ) |
80 | 89 |
|
81 | 90 |
|
82 | 91 | @experimental |
83 | 92 | def _mcp_call_tool_result_to_kernel_contents( |
84 | 93 | mcp_type: types.CallToolResult, |
85 | | -) -> list[TextContent | ImageContent | BinaryContent | AudioContent]: |
| 94 | +) -> list[TextContent | ImageContent | BinaryContent | AudioContent | FunctionResultContent | FunctionCallContent]: |
86 | 95 | """Convert a MCP container type to a Semantic Kernel type.""" |
87 | | - return [_mcp_content_types_to_kernel_content(item) for item in mcp_type.content] |
| 96 | + return list(chain(*[_mcp_content_types_to_kernel_content(item) for item in mcp_type.content])) |
88 | 97 |
|
89 | 98 |
|
90 | 99 | @experimental |
91 | 100 | def _mcp_content_types_to_kernel_content( |
92 | | - mcp_type: types.ImageContent | types.TextContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink, |
93 | | -) -> TextContent | ImageContent | BinaryContent | AudioContent: |
| 101 | + mcp_type: types.SamplingMessageContentBlock |
| 102 | + | types.ContentBlock |
| 103 | + | Sequence[types.SamplingMessageContentBlock | types.ContentBlock], |
| 104 | +) -> list[TextContent | ImageContent | BinaryContent | AudioContent | FunctionCallContent | FunctionResultContent]: |
94 | 105 | """Convert a MCP type to a Semantic Kernel type.""" |
| 106 | + if isinstance(mcp_type, Sequence): |
| 107 | + return list(chain(*[_mcp_content_types_to_kernel_content(item) for item in mcp_type])) |
95 | 108 | if isinstance(mcp_type, types.TextContent): |
96 | | - return TextContent(text=mcp_type.text, inner_content=mcp_type) |
| 109 | + return [TextContent(text=mcp_type.text, inner_content=mcp_type)] |
97 | 110 | if isinstance(mcp_type, types.ImageContent): |
98 | | - return ImageContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type) |
| 111 | + return [ImageContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)] |
99 | 112 | if isinstance(mcp_type, types.AudioContent): |
100 | | - return AudioContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type) |
| 113 | + return [AudioContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)] |
101 | 114 | if isinstance(mcp_type, types.ResourceLink): |
102 | | - return BinaryContent( |
103 | | - uri=mcp_type.uri, # type: ignore |
104 | | - mime_type=mcp_type.mimeType, |
105 | | - inner_content=mcp_type, |
106 | | - ) |
| 115 | + return [ |
| 116 | + BinaryContent( |
| 117 | + uri=mcp_type.uri, # type: ignore |
| 118 | + mime_type=mcp_type.mimeType, |
| 119 | + inner_content=mcp_type, |
| 120 | + ) |
| 121 | + ] |
| 122 | + if isinstance(mcp_type, types.ToolUseContent): |
| 123 | + return [ |
| 124 | + FunctionCallContent(inner_content=mcp_type, name=mcp_type.name, arguments=mcp_type.input, id=mcp_type.id) |
| 125 | + ] |
| 126 | + if isinstance(mcp_type, types.ToolResultContent): |
| 127 | + return [ |
| 128 | + FunctionResultContent( |
| 129 | + inner_content=mcp_type, |
| 130 | + name=mcp_type.type, |
| 131 | + result=list(chain(*[_mcp_content_types_to_kernel_content(mcp_type.content)])), |
| 132 | + call_id=mcp_type.toolUseId, |
| 133 | + ) |
| 134 | + ] |
107 | 135 | # subtypes of EmbeddedResource |
108 | 136 | if isinstance(mcp_type.resource, types.TextResourceContents): |
109 | | - return TextContent( |
110 | | - text=mcp_type.resource.text, |
| 137 | + return [ |
| 138 | + TextContent( |
| 139 | + text=mcp_type.resource.text, |
| 140 | + inner_content=mcp_type, |
| 141 | + metadata=mcp_type.annotations.model_dump() if mcp_type.annotations else {}, |
| 142 | + ) |
| 143 | + ] |
| 144 | + return [ |
| 145 | + BinaryContent( |
| 146 | + data=mcp_type.resource.blob, |
111 | 147 | inner_content=mcp_type, |
112 | 148 | metadata=mcp_type.annotations.model_dump() if mcp_type.annotations else {}, |
113 | 149 | ) |
114 | | - return BinaryContent( |
115 | | - data=mcp_type.resource.blob, |
116 | | - inner_content=mcp_type, |
117 | | - metadata=mcp_type.annotations.model_dump() if mcp_type.annotations else {}, |
118 | | - ) |
| 150 | + ] |
119 | 151 |
|
120 | 152 |
|
121 | 153 | @experimental |
@@ -464,7 +496,9 @@ def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]: |
464 | 496 | """Get an MCP client.""" |
465 | 497 | pass |
466 | 498 |
|
467 | | - async def call_tool(self, tool_name: str, **kwargs: Any) -> list[TextContent | ImageContent | BinaryContent]: |
| 499 | + async def call_tool( |
| 500 | + self, tool_name: str, **kwargs: Any |
| 501 | + ) -> list[TextContent | ImageContent | BinaryContent | AudioContent | FunctionResultContent | FunctionCallContent]: |
468 | 502 | """Call a tool with the given arguments.""" |
469 | 503 | if not self.session: |
470 | 504 | raise KernelPluginInvalidConfigurationError( |
|
0 commit comments