You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Abstractions for input requests and responses, and concrete types for handling function approvals req/resp.
Abstractions for interacting with MCP capabilities available in service providers like OpenAI and Anthropic, depends on the one above for approval flows.
FunctionCallContent/FunctionResultContent include polymorphic attributes in reaction to now-derived MCP contents
API Proposal
namespaceMicrosoft.Extensions.AI;// Contents[JsonPolymorphic][JsonDerivedType(typeof(FunctionApprovalRequestContent),"functionApprovalRequest")]publicabstractclassInputRequestContent:AIContent{publicabstractstringRequestId{get;}}[JsonPolymorphic][JsonDerivedType(typeof(FunctionApprovalResponseContent),"functionApprovalResponse")]publicabstractclassInputResponseContent:AIContent{publicabstractstringRequestId{get;}}publicsealedclassFunctionApprovalRequestContent:InputRequestContent{publicFunctionApprovalRequestContent(stringrequestId,FunctionCallContentfunctionCall);publicFunctionCallContentFunctionCall{get;}publicFunctionApprovalResponseContentCreateResponse(boolapproved,string?reason=null);}publicsealedclassFunctionApprovalResponseContent:InputResponseContent{publicFunctionApprovalResponseContent(stringrequestId,boolapproved,FunctionCallContentfunctionCall);publicboolApproved{get;}publicFunctionCallContentFunctionCall{get;}publicstring?Reason{get;set;}}publicsealedclassMcpServerToolCallContent:FunctionCallContent{publicMcpServerToolCallContent(stringcallId,stringname,string?serverName);publicstring?ServerName{get;}}publicsealedclassMcpServerToolResultContent:FunctionResultContent{publicMcpServerToolResultContent(stringcallId);}[JsonPolymorphic][JsonDerivedType(typeof(McpServerToolCallContent),"mcpServerToolCall")]publicclassFunctionCallContent:AIContent// Existing type, attributes are new.{[JsonConstructor]publicFunctionCallContent(stringcallId,stringname,IDictionary<string,object?>?arguments=null);publicstringCallId{get;}publicstringName{get;}publicIDictionary<string,object?>?Arguments{get;set;}[JsonIgnore]publicException?Exception{get;set;}publicboolInformationalOnly{get;set;}publicstaticFunctionCallContentCreateFromParsedArguments<TEncoding>(TEncodingencodedArguments,stringcallId,stringname,Func<TEncoding,IDictionary<string,object?>?>argumentParser);[DebuggerBrowsable(DebuggerBrowsableState.Never)]privatestringDebuggerDisplay{get;}}[JsonPolymorphic][JsonDerivedType(typeof(McpServerToolResultContent),"mcpServerToolResult")]publicclassFunctionResultContent:AIContent// Existing type, attributes are new.{[JsonConstructor]publicFunctionResultContent(stringcallId,object?result);publicstringCallId{get;}publicobject?Result{get;set;}[JsonIgnore]publicException?Exception{get;set;}[DebuggerBrowsable(DebuggerBrowsableState.Never)]privatestringDebuggerDisplay{get;}}// Tools.publicsealedclassApprovalRequiredAIFunction:DelegatingAIFunction{publicApprovalRequiredAIFunction(AIFunctioninnerFunction);}publicclassHostedMcpServerTool:AITool{publicHostedMcpServerTool(stringserverName,stringserverAddress);publicHostedMcpServerTool(stringserverName,stringserverAddress,IReadOnlyDictionary<string,object?>?additionalProperties);publicHostedMcpServerTool(stringserverName,UriserverUrl);publicHostedMcpServerTool(stringserverName,UriserverUrl,IReadOnlyDictionary<string,object?>?additionalProperties);publicoverrideIReadOnlyDictionary<string,object?>AdditionalProperties{get;}publicIList<string>?AllowedTools{get;set;}publicHostedMcpServerToolApprovalMode?ApprovalMode{get;set;}publicstring?AuthorizationToken{get;set;}publicIDictionary<string,string>Headers{get;}publicoverridestringName{get;}publicstringServerAddress{get;}publicstring?ServerDescription{get;set;}publicstringServerName{get;}}// HostedMcpServerTool utilities.[JsonPolymorphic][JsonDerivedType(typeof(HostedMcpServerToolAlwaysRequireApprovalMode),"always")][JsonDerivedType(typeof(HostedMcpServerToolNeverRequireApprovalMode),"never")][JsonDerivedType(typeof(HostedMcpServerToolRequireSpecificApprovalMode),"requireSpecific")]publicclassHostedMcpServerToolApprovalMode// Follows the pattern of `ChatToolMode` https://source.dot.net/#Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatToolMode.cs,854705957c5b4bd1.{publicstaticHostedMcpServerToolAlwaysRequireApprovalModeAlwaysRequire{get;}publicstaticHostedMcpServerToolNeverRequireApprovalModeNeverRequire{get;}publicstaticHostedMcpServerToolRequireSpecificApprovalModeRequireSpecific(IList<string>?alwaysRequireApprovalToolNames,IList<string>?neverRequireApprovalToolNames);}[DebuggerDisplay("AlwaysRequire")]publicsealedclassHostedMcpServerToolAlwaysRequireApprovalMode:HostedMcpServerToolApprovalMode{publicHostedMcpServerToolAlwaysRequireApprovalMode();publicoverrideboolEquals(object?obj);publicoverrideintGetHashCode();}[DebuggerDisplay("NeverRequire")]publicsealedclassHostedMcpServerToolNeverRequireApprovalMode:HostedMcpServerToolApprovalMode{publicHostedMcpServerToolNeverRequireApprovalMode();publicoverrideboolEquals(object?obj);publicoverrideintGetHashCode();}publicsealedclassHostedMcpServerToolRequireSpecificApprovalMode:HostedMcpServerToolApprovalMode{publicHostedMcpServerToolRequireSpecificApprovalMode(IList<string>?alwaysRequireApprovalToolNames,IList<string>?neverRequireApprovalToolNames);publicIList<string>?AlwaysRequireApprovalToolNames{get;set;}publicIList<string>?NeverRequireApprovalToolNames{get;set;}publicoverrideboolEquals(object?obj);publicoverrideintGetHashCode();}
API Usage
usingMicrosoft.Extensions.AI;IChatClientclient=GetMyChatClient();varcalendarMcp=newHostedMcpServerTool("google-calendar",newUri("https://mcp.google.example/calendar")){ApprovalMode=HostedMcpServerToolApprovalMode.RequireSpecific(alwaysRequireApprovalToolNames:["create_event","delete_event"],neverRequireApprovalToolNames:["get_free_busy","list_events"])};varbookFlight=newApprovalRequiredAIFunction(AIFunctionFactory.Create((stringorigin,stringdestination,stringdate)=>new{Confirmation=$"UA-{Random.Shared.Next(100000,999999)}"},name:"book_flight"));ChatOptionsoptions=new(){Tools=[calendarMcp,bookFlight]};List<ChatMessage>messages=[new(ChatRole.User,"Book a Seattle to NYC flight next week when I'm free")];while(true){ChatResponseresponse=awaitclient.GetResponseAsync(messages,options);varapprovalRequests=response.Messages.SelectMany(m =>m.Contents).OfType<FunctionApprovalRequestContent>().ToList();if(approvalRequests.Count==0){Console.WriteLine(response.Text);// Final response.break;}// In production: prompt user. Here we auto-approve.messages.AddRange(response.Messages);messages.Add(new(ChatRole.User,approvalRequests.Select(a =>a.CreateResponse(true)).ToArray()));}
Alternative Designs
Do not extend FCC/FRC
Instead of extending FunctionCallContent/FunctionResultContent, MCP contents could remain as standalone AIContent types—aligning with CodeInterpreterToolCallContent and ImageGenerationToolCallContent which represent similar hosted service capabilities. This maintains a cleaner semantic separation between client-invokable functions and hosted tool invocations, avoiding inherited properties (InvocationRequired, Exception) that aren't meaningful for server-side tools. The trade-off is that approvals would require either dedicated MCP approval types (as currently exists) or generalizing FunctionApprovalRequestContent.FunctionCall to accept a broader AIContent/CallContent base type.
This is symmetric, but may be error-prone: users must decide which ChatRole to use when wrapping the response in a ChatMessage, typically ChatRole.User, since approvals represent user decisions in most cases (not all, that's why we are dropping User from InputRequestContent). It's also problematic for IChatClients that handle contents differently per role. However, if CreateResponse returned a ChatMessage instead, approvals would be forced to one per message, which is cumbersome for multi-approvals scenarios.
We could add CreateResponseMessage(bool, string?, ChatRole?).
Notes
McpServerToolCallContent/McpServerToolResultContent were originally prefixed with Hosted to follow convention with HostedFileContent and HostedVectorStoreContent, but the prefix was later dropped. The distinction is that Hosted*Content types represent references to provider-hosted resources (file IDs, vector store IDs), while *ToolCallContent/*ToolResultContent types represent tool invocation data—describing what the tool was asked to do and what it returned. This pattern is consistent with CodeInterpreterToolCallContent/CodeInterpreterToolResultContent and ImageGenerationToolCallContent/ImageGenerationToolResultContent, which also omit the Hosted prefix despite being invoked by hosted services. The Hosted prefix remains on HostedMcpServerTool (and similar AITool types like HostedCodeInterpreterTool) because these are configuration markers telling the provider to use a remotely-hosted capability, not actual tool implementations.
Background and motivation
Two proposals in one:
These APIs are already shipping as
Experimental.Differences with
main.API Proposal
API Usage
Alternative Designs
Do not extend FCC/FRC
Instead of extending
FunctionCallContent/FunctionResultContent, MCP contents could remain as standaloneAIContenttypes—aligning withCodeInterpreterToolCallContentandImageGenerationToolCallContentwhich represent similar hosted service capabilities. This maintains a cleaner semantic separation between client-invokable functions and hosted tool invocations, avoiding inherited properties (InvocationRequired,Exception) that aren't meaningful for server-side tools. The trade-off is that approvals would require either dedicated MCP approval types (as currently exists) or generalizingFunctionApprovalRequestContent.FunctionCallto accept a broader AIContent/CallContent base type.FunctionApprovalRequestContent.CreateResponse(bool, string?)returnsFunctionApprovalResponseContentThis is symmetric, but may be error-prone: users must decide which ChatRole to use when wrapping the response in a
ChatMessage, typicallyChatRole.User, since approvals represent user decisions in most cases (not all, that's why we are droppingUserfromInputRequestContent). It's also problematic forIChatClients that handle contents differently per role. However, ifCreateResponsereturned a ChatMessage instead, approvals would be forced to one per message, which is cumbersome for multi-approvals scenarios.We could add
CreateResponseMessage(bool, string?, ChatRole?).Notes
McpServerToolCallContent/McpServerToolResultContentwere originally prefixed withHostedto follow convention withHostedFileContentandHostedVectorStoreContent, but the prefix was later dropped. The distinction is thatHosted*Contenttypes represent references to provider-hosted resources (file IDs, vector store IDs), while*ToolCallContent/*ToolResultContenttypes represent tool invocation data—describing what the tool was asked to do and what it returned. This pattern is consistent withCodeInterpreterToolCallContent/CodeInterpreterToolResultContentandImageGenerationToolCallContent/ImageGenerationToolResultContent, which also omit theHostedprefix despite being invoked by hosted services. TheHostedprefix remains onHostedMcpServerTool(and similarAITooltypes likeHostedCodeInterpreterTool) because these are configuration markers telling the provider to use a remotely-hosted capability, not actual tool implementations.Risks
No response
cc @westey-m