Skip to content
Merged
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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.250.126"
VERSION = "0.250.128"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
27 changes: 27 additions & 0 deletions application/single_app/functions_mcp_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,33 @@
return warnings[:20]


def _mcp_tool_schema_defines_property(tool, property_name):

Check warning on line 480 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
if not isinstance(tool, dict):

Check warning on line 481 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
return False

input_schema = tool.get("input_schema") if isinstance(tool.get("input_schema"), dict) else {}

Check warning on line 484 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
properties = input_schema.get("properties")
return isinstance(properties, dict) and property_name in properties


def normalize_mcp_tool_call_arguments(tool, arguments):

Check warning on line 489 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""Return MCP tool arguments in the top-level shape expected by tools/call."""

Check warning on line 490 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
if arguments is None:
return {}
if not isinstance(arguments, dict):
return arguments

wrapper_value = arguments.get("kwargs")
if (
set(arguments.keys()) == {"kwargs"}
and isinstance(wrapper_value, dict)
and not _mcp_tool_schema_defines_property(tool, "kwargs")

Check warning on line 500 in application/single_app/functions_mcp_operations.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
):
return wrapper_value

return arguments


def validate_mcp_tool_arguments(tool, arguments):
"""Return validation errors for MCP tool arguments against cached input schema."""
if not isinstance(tool, dict):
Expand Down
19 changes: 12 additions & 7 deletions application/single_app/semantic_kernel_plugins/mcp_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
classify_mcp_exception,
normalize_mcp_additional_fields,
normalize_mcp_tool_metadata,
normalize_mcp_tool_call_arguments,

Check warning on line 16 in application/single_app/semantic_kernel_plugins/mcp_plugin.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
validate_mcp_tool_arguments,
)
from semantic_kernel_plugins.base_plugin import BasePlugin
Expand Down Expand Up @@ -157,12 +158,16 @@
"error_type": "not_configured",
"configured_tools": sorted(configured_tool_names),
}
configured_tool = next(

Check warning on line 161 in application/single_app/semantic_kernel_plugins/mcp_plugin.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
(tool for tool in self._tools if tool.get("original_name") == normalized_tool_name),

Check warning on line 162 in application/single_app/semantic_kernel_plugins/mcp_plugin.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
None,
)
normalized_arguments = normalize_mcp_tool_call_arguments(

Check warning on line 165 in application/single_app/semantic_kernel_plugins/mcp_plugin.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
configured_tool,
arguments if arguments is not None else {},
)
if self._additional_fields.get("validate_tool_arguments"):
configured_tool = next(
(tool for tool in self._tools if tool.get("original_name") == normalized_tool_name),
None,
)
validation_errors = validate_mcp_tool_arguments(configured_tool, arguments or {})
validation_errors = validate_mcp_tool_arguments(configured_tool, normalized_arguments)
if validation_errors:
return {
"success": False,
Expand All @@ -171,7 +176,7 @@
"validation_errors": validation_errors,
}

return await self.invoke_tool(normalized_tool_name, arguments or {})
return await self.invoke_tool(normalized_tool_name, normalized_arguments or {})

async def invoke_tool(self, tool_name: str, arguments: Optional[dict] = None) -> dict:
"""Invoke an MCP tool through the factory's native MCP connector."""
Expand All @@ -180,7 +185,7 @@
f"[MCP_PLUGIN] Invoking MCP tool tool_name={tool_name} "
f"transport={self._additional_fields.get('transport')} "
f"endpoint_present={bool(str(self.manifest.get('endpoint') or '').strip())} "
f"argument_keys={sorted((arguments or {}).keys())}"
f"argument_keys={sorted(arguments.keys()) if isinstance(arguments, dict) else []}"
)
from semantic_kernel_plugins.mcp_plugin_factory import McpPluginFactory

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_mcp_custom_header_validation_errors,
is_valid_mcp_header_name,
normalize_mcp_additional_fields,
normalize_mcp_tool_call_arguments,
normalize_mcp_tool_metadata,
validate_mcp_endpoint_for_transport,
)
Expand Down Expand Up @@ -249,10 +250,35 @@ async def call_tool_from_config(
arguments: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Connect to an MCP server, invoke one tool, and normalize the result."""
configured_tool = cls._find_configured_tool(config, tool_name)
normalized_arguments = (
normalize_mcp_tool_call_arguments(configured_tool, arguments)
if configured_tool
else arguments
)
return await cls._run_with_retries(
config,
"tool_call",
lambda: cls._call_tool_once(config, tool_name, arguments),
lambda: cls._call_tool_once(config, tool_name, normalized_arguments),
)

@classmethod
def _find_configured_tool(cls, config: Dict[str, Any], tool_name: str) -> Optional[Dict[str, Any]]:
"""Return cached MCP tool metadata for a factory invocation."""
normalized_tool_name = str(tool_name or "").strip()
if not normalized_tool_name:
return None

additional_fields = normalize_mcp_additional_fields((config or {}).get("additionalFields", {}))
configured_tools = normalize_mcp_tool_metadata(additional_fields.get("mcp_tools", []))
return next(
(
tool
for tool in configured_tools
if tool.get("original_name") == normalized_tool_name
or tool.get("function_name") == normalized_tool_name
),
None,
)

@classmethod
Expand Down
9 changes: 9 additions & 0 deletions docs/explanation/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/).

### **(v0.250.128)**

#### Bug Fixes

* **Outbound MCP Tool Argument Normalization**
* Fixed outbound MCP tool calls that could wrap parameters inside a `kwargs` object, preventing standards-compliant MCP servers from seeing required top-level fields such as `type`.
* Added schema-aware normalization before MCP argument validation and invocation while preserving tools that explicitly define a real top-level `kwargs` property.
* (Ref: #1163, MCP `tools/call` arguments, `functions_mcp_operations.py`, `mcp_plugin.py`, `mcp_plugin_factory.py`)

### **(v0.250.126)**

#### Bug Fixes
Expand Down
Loading
Loading