Skip to content

Commit 09071c3

Browse files
Merge branch 'main' into feature-text-search-linq
2 parents 195ab98 + 898b9fe commit 09071c3

7 files changed

Lines changed: 2194 additions & 2001 deletions

File tree

dotnet/src/IntegrationTests/Plugins/OpenApiManifest/example-apimanifest-local.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
]
1616
},
1717
"MicrosoftGraph": {
18-
"apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/DirectoryObjects.yml",
18+
"apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/main/openApiDocs/v1.0/DirectoryObjects.yml",
1919
"auth": {
2020
"clientIdentifier": "some-uuid-here",
2121
"access": [

dotnet/src/IntegrationTests/Plugins/OpenApiManifest/example-apimanifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
]
2828
},
2929
"MicrosoftGraph": {
30-
"apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/DirectoryObjects.yml",
30+
"apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/main/openApiDocs/v1.0/DirectoryObjects.yml",
3131
"auth": {
3232
"clientIdentifier": "some-uuid-here",
3333
"access": [

python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dependencies = [
5757
"protobuf",
5858
# explicit typing extensions
5959
"typing-extensions>=4.13",
60+
"mcp>=1.26.0",
6061
]
6162

6263
### Optional dependencies

python/semantic_kernel/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from semantic_kernel.kernel import Kernel
44

5-
__version__ = "1.39.3"
5+
__version__ = "1.39.4"
66

77
DEFAULT_RC_VERSION = f"{__version__}-rc9"
88

python/semantic_kernel/connectors/in_memory.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,63 @@ class InMemoryCollection(
166166
"items",
167167
}
168168

169+
# Blocklist of dangerous attribute names that cannot be accessed in filter expressions.
170+
# These attributes can be used to escape the sandbox and execute arbitrary code.
171+
blocked_filter_attributes: ClassVar[set[str]] = {
172+
# Object introspection - can lead to class/module access
173+
"__class__",
174+
"__bases__",
175+
"__mro__",
176+
"__subclasses__",
177+
# Code and function internals
178+
"__code__",
179+
"__globals__",
180+
"__closure__",
181+
"__func__",
182+
"__self__",
183+
"__dict__",
184+
"__slots__",
185+
# Attribute access hooks
186+
"__getattr__",
187+
"__getattribute__",
188+
"__setattr__",
189+
"__delattr__",
190+
# Import and builtins
191+
"__builtins__",
192+
"__import__",
193+
"__loader__",
194+
"__spec__",
195+
# Module attributes
196+
"__name__",
197+
"__qualname__",
198+
"__module__",
199+
"__file__",
200+
"__path__",
201+
"__package__",
202+
# Descriptor protocol
203+
"__get__",
204+
"__set__",
205+
"__delete__",
206+
# Metaclass and creation
207+
"__new__",
208+
"__init__",
209+
"__init_subclass__",
210+
"__prepare__",
211+
"__call__",
212+
# Other dangerous attributes
213+
"__reduce__",
214+
"__reduce_ex__",
215+
"__getstate__",
216+
"__setstate__",
217+
"func_globals", # Python 2 compatibility name
218+
"gi_frame", # Generator frame access
219+
"gi_code",
220+
"f_globals", # Frame globals
221+
"f_locals",
222+
"f_builtins",
223+
"co_consts", # Code object constants
224+
}
225+
169226
def __init__(
170227
self,
171228
record_type: type[TModel],
@@ -358,6 +415,13 @@ def _parse_and_validate_filter(self, filter_str: str) -> Callable:
358415
f"AST node type '{node_type.__name__}' is not allowed in filter expressions."
359416
)
360417

418+
# For Attribute nodes, validate that dangerous dunder attributes are not accessed
419+
if isinstance(node, ast.Attribute) and node.attr in self.blocked_filter_attributes:
420+
raise VectorStoreOperationException(
421+
f"Access to attribute '{node.attr}' is not allowed in filter expressions. "
422+
"This attribute could be used to escape the filter sandbox."
423+
)
424+
361425
# For Name nodes, only allow the lambda parameter
362426
if isinstance(node, ast.Name) and node.id not in lambda_param_names:
363427
raise VectorStoreOperationException(

python/semantic_kernel/connectors/mcp.py

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from contextlib import AbstractAsyncContextManager, AsyncExitStack, _AsyncGeneratorContextManager
1111
from datetime import timedelta
1212
from functools import partial
13+
from itertools import chain
1314
from typing import TYPE_CHECKING, Any
1415

1516
from mcp import types
@@ -42,6 +43,9 @@
4243
from semantic_kernel.prompt_template.prompt_template_base import PromptTemplateBase
4344
from semantic_kernel.utils.feature_stage_decorator import experimental
4445

46+
from ..contents.function_call_content import FunctionCallContent
47+
from ..contents.function_result_content import FunctionResultContent
48+
4549
if sys.version_info >= (3, 11):
4650
from typing import Self # pragma: no cover
4751
else:
@@ -72,50 +76,78 @@ def _mcp_prompt_message_to_kernel_content(
7276
mcp_type: types.PromptMessage | types.SamplingMessage,
7377
) -> ChatMessageContent:
7478
"""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+
)
7584
return ChatMessageContent(
7685
role=AuthorRole(mcp_type.role),
77-
items=[_mcp_content_types_to_kernel_content(mcp_type.content)],
86+
items=items, # type: ignore
7887
inner_content=mcp_type,
7988
)
8089

8190

8291
@experimental
8392
def _mcp_call_tool_result_to_kernel_contents(
8493
mcp_type: types.CallToolResult,
85-
) -> list[TextContent | ImageContent | BinaryContent | AudioContent]:
94+
) -> list[TextContent | ImageContent | BinaryContent | AudioContent | FunctionResultContent | FunctionCallContent]:
8695
"""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]))
8897

8998

9099
@experimental
91100
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]:
94105
"""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]))
95108
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)]
97110
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)]
99112
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)]
101114
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+
]
107135
# subtypes of EmbeddedResource
108136
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,
111147
inner_content=mcp_type,
112148
metadata=mcp_type.annotations.model_dump() if mcp_type.annotations else {},
113149
)
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+
]
119151

120152

121153
@experimental
@@ -464,7 +496,9 @@ def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
464496
"""Get an MCP client."""
465497
pass
466498

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]:
468502
"""Call a tool with the given arguments."""
469503
if not self.session:
470504
raise KernelPluginInvalidConfigurationError(

0 commit comments

Comments
 (0)