diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs index c6a64915cf..7262979207 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -32,6 +33,7 @@ public class AgentRunOptions _ = Throw.IfNull(options); this.ContinuationToken = options.ContinuationToken; this.AllowBackgroundResponses = options.AllowBackgroundResponses; + this.AdditionalProperties = options.AdditionalProperties?.Clone(); } /// @@ -74,4 +76,18 @@ public class AgentRunOptions /// /// public bool? AllowBackgroundResponses { get; set; } + + /// + /// Gets or sets additional properties associated with these options. + /// + /// + /// An containing custom properties, + /// or if no additional properties are present. + /// + /// + /// Additional properties provide a way to include custom metadata or provider-specific + /// information that doesn't fit into the standard options schema. This is useful for + /// preserving implementation-specific details or extending the options with custom data. + /// + public AdditionalPropertiesDictionary? AdditionalProperties { get; set; } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs index 40901a4969..32560949fb 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs @@ -18,7 +18,12 @@ public class AgentRunOptionsTests var options = new AgentRunOptions { ContinuationToken = new object(), - AllowBackgroundResponses = true + AllowBackgroundResponses = true, + AdditionalProperties = new AdditionalPropertiesDictionary + { + ["key1"] = "value1", + ["key2"] = 42 + } }; // Act @@ -28,6 +33,10 @@ public class AgentRunOptionsTests Assert.NotNull(clone); Assert.Same(options.ContinuationToken, clone.ContinuationToken); Assert.Equal(options.AllowBackgroundResponses, clone.AllowBackgroundResponses); + Assert.NotNull(clone.AdditionalProperties); + Assert.NotSame(options.AdditionalProperties, clone.AdditionalProperties); + Assert.Equal("value1", clone.AdditionalProperties["key1"]); + Assert.Equal(42, clone.AdditionalProperties["key2"]); } [Fact] @@ -42,7 +51,12 @@ public class AgentRunOptionsTests var options = new AgentRunOptions { ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), - AllowBackgroundResponses = true + AllowBackgroundResponses = true, + AdditionalProperties = new AdditionalPropertiesDictionary + { + ["key1"] = "value1", + ["key2"] = 42 + } }; // Act @@ -54,5 +68,13 @@ public class AgentRunOptionsTests Assert.NotNull(deserialized); Assert.Equivalent(ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), deserialized!.ContinuationToken); Assert.Equal(options.AllowBackgroundResponses, deserialized.AllowBackgroundResponses); + Assert.NotNull(deserialized.AdditionalProperties); + Assert.Equal(2, deserialized.AdditionalProperties.Count); + Assert.True(deserialized.AdditionalProperties.TryGetValue("key1", out object? value1)); + Assert.IsType(value1); + Assert.Equal("value1", ((JsonElement)value1!).GetString()); + Assert.True(deserialized.AdditionalProperties.TryGetValue("key2", out object? value2)); + Assert.IsType(value2); + Assert.Equal(42, ((JsonElement)value2!).GetInt32()); } } diff --git a/python/packages/core/agent_framework/_mcp.py b/python/packages/core/agent_framework/_mcp.py index 50f6a91c2e..873b7f04cc 100644 --- a/python/packages/core/agent_framework/_mcp.py +++ b/python/packages/core/agent_framework/_mcp.py @@ -19,7 +19,7 @@ from mcp.client.websocket import websocket_client from mcp.shared.context import RequestContext from mcp.shared.exceptions import McpError from mcp.shared.session import RequestResponder -from pydantic import BaseModel, create_model +from pydantic import BaseModel, Field, create_model from ._tools import AIFunction, HostedMCPSpecificApproval from ._types import ChatMessage, Contents, DataContent, Role, TextContent, UriContent @@ -224,13 +224,20 @@ def _get_input_model_from_mcp_tool(tool: types.Tool) -> type[BaseModel]: prop_details = json.loads(prop_details) if isinstance(prop_details, str) else prop_details python_type = resolve_type(prop_details) + description = prop_details.get("description", "") # Create field definition for create_model if prop_name in required: - field_definitions[prop_name] = (python_type, ...) + field_definitions[prop_name] = ( + (python_type, Field(description=description)) if description else (python_type, ...) + ) else: default_value = prop_details.get("default", None) - field_definitions[prop_name] = (python_type, default_value) + field_definitions[prop_name] = ( + (python_type, Field(default=default_value, description=description)) + if description + else (python_type, default_value) + ) return create_model(f"{tool.name}_input", **field_definitions)