Skip to content
Closed
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
c73282b
ADR: Supporting user approvals
westey-m Jul 22, 2025
9e3b98b
Add more context.
westey-m Jul 22, 2025
a9a6a97
Add further justification
westey-m Jul 22, 2025
61063c0
Add decision drivers.
westey-m Jul 22, 2025
3a0dabf
Update with Copilot Suggestion
westey-m Jul 22, 2025
786374c
Merge branch 'main' into userapproval-adr
westey-m Jul 23, 2025
eace4e7
Update adr with feedback.
westey-m Jul 23, 2025
4076ed3
Adding another generalized option for all user input.
westey-m Jul 23, 2025
23b4fe5
Merge branch 'main' into userapproval-adr
westey-m Jul 24, 2025
12817f1
Add another base class based UserInput option
westey-m Jul 25, 2025
84e2855
Remove TextContent inheritance.
westey-m Jul 25, 2025
632635c
Change Schema and Strucutured input data types to JsonElement
westey-m Jul 25, 2025
461c8ef
Rename JsonSchema to Schema
westey-m Jul 25, 2025
7de1787
Merge branch 'main' into userapproval-adr
westey-m Jul 29, 2025
aa3c14e
Merge branch 'main' into userapproval-adr
crickman Jul 30, 2025
ab04590
Address some feedback.
westey-m Jul 30, 2025
aba7dc6
Merge branch 'main' into userapproval-adr
westey-m Jul 30, 2025
89ff266
Merge branch 'main' into userapproval-adr
crickman Jul 30, 2025
629204e
Add base user input request and response types with FunctionApproval …
westey-m Jul 31, 2025
3783074
Merge branch 'main' into userapproval-adr
crickman Jul 31, 2025
d3a9319
Merge branch 'main' into userapproval-adr
westey-m Aug 1, 2025
74d1f5d
Add POC ApprovalGeneratingChatClient
westey-m Aug 1, 2025
3c40615
Merge branch 'main' into userapproval-adr
crickman Aug 5, 2025
c068247
Add pre-FunctionInvokingChatClient ApprovalGeneratingChatClient POC
westey-m Aug 5, 2025
d8626a4
Merge branch 'userapproval-adr' of https://github.com/westey-m/agent-…
westey-m Aug 5, 2025
45254f3
Merge branch 'main' into userapproval-adr
westey-m Aug 5, 2025
3300dce
Merge branch 'main' into userapproval-adr
westey-m Aug 6, 2025
c8e42de
Fix namespaces
westey-m Aug 6, 2025
97352a0
Add combined FICC with approvals
westey-m Aug 7, 2025
2c39648
Bug fix.
westey-m Aug 7, 2025
5293568
Add MCP and ApprovalRequiredAIFunction POC
westey-m Aug 8, 2025
c5bedd0
Bug fixes
westey-m Aug 11, 2025
edcad34
Address PR Feedback.
westey-m Aug 11, 2025
0a61294
Add AIFunctionApprovalContext with some small efficiency improvements.
westey-m Aug 11, 2025
436c490
Remove unecessary helpers that are only used in one place.
westey-m Aug 11, 2025
0d5fcbf
Replace dictionary with array and lazy initialize.
westey-m Aug 11, 2025
8beb0f6
Improve sample code
westey-m Aug 11, 2025
9fbd0a7
Make user input id required and rename it to be more general.
westey-m Aug 12, 2025
302d43d
Merge branch 'main' into userapproval-adr
westey-m Aug 12, 2025
8ad466b
Upgrade packages to resolve conflicts.
westey-m Aug 12, 2025
049c9d3
Move some code into methods to simply main GetResponse.
westey-m Aug 12, 2025
39ef6bd
Adding approvals support to streaming
westey-m Aug 13, 2025
14d4773
Address PR comments.
westey-m Aug 13, 2025
b288fed
Change approval response type and naming
westey-m Aug 13, 2025
fc0bf74
Remove commented out validation checks that won't work for server sid…
westey-m Aug 13, 2025
d1939ad
Remove original metadata and read metadata from approval request mess…
westey-m Aug 14, 2025
6d8b20e
Small code refactoring and clarifying comments.
westey-m Aug 14, 2025
2bc90af
Remove failed POCs
westey-m Aug 14, 2025
d12cdf8
Update ADR with latest changes
westey-m Aug 14, 2025
3420337
Explain case when pre FICC AGCC fails
westey-m Aug 14, 2025
6a6f9dd
Add support for mcp servers over http.
westey-m Aug 14, 2025
f05bf90
Merge branch 'main' into userapproval-adr
westey-m Aug 14, 2025
3105941
Addressing PR comments.
westey-m Aug 15, 2025
26eb742
Address PR comments
westey-m Aug 15, 2025
0adf519
Merge branch 'main' into userapproval-adr
westey-m Aug 15, 2025
b2a4915
Merge latest updates from main
westey-m Aug 15, 2025
5e52c11
Add FICC unit tests and fix bugs.
westey-m Aug 19, 2025
b1289e3
Add additional unit test.
westey-m Aug 19, 2025
0b4405e
Refactor ConvertToFunctionCallContentMessages to avoid unecessary all…
westey-m Aug 20, 2025
545c231
Refactor preinvocation logic to share between streaming and non-strea…
westey-m Aug 20, 2025
e6aeebb
Move some approval generation code to a separate method to simplify m…
westey-m Aug 20, 2025
a1b2973
More refactoring, additional unit tests and bug fixes.
westey-m Aug 20, 2025
906ad2f
Remove comments delineating additions.
westey-m Aug 20, 2025
4c59b25
Merge latest changes from main
westey-m Aug 20, 2025
66b904e
Fixing some typos.
westey-m Aug 20, 2025
d3dd02e
Merge branch 'main' into userapproval-adr
westey-m Aug 21, 2025
21680be
Address PR comments.
westey-m Aug 22, 2025
a712408
Address PR comments.
westey-m Aug 22, 2025
c1da46b
Address PR feedback.
westey-m Aug 22, 2025
091f754
Fix bug for server side threads.
westey-m Aug 22, 2025
7518b70
Address PR comments
westey-m Aug 25, 2025
a349c79
Merge from main
westey-m Aug 25, 2025
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
458 changes: 458 additions & 0 deletions docs/decisions/00NN-userapproval-content-types.md

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
<!-- System.* -->
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="9.0.7" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.0-preview.6.25358.103" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.7" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.7" />
Expand All @@ -43,7 +43,7 @@
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0-preview.6.25358.103" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.7.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.7.1-preview.1.25365.4" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.7.1" />
Expand All @@ -65,6 +65,8 @@
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.1.151" />
<!-- Identity -->
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.74.1" />
<!-- Other -->
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.2" />
<!-- Test -->
<PackageVersion Include="FluentAssertions" Version="8.5.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
Expand Down
3 changes: 3 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
<Project Path="src/Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB/Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB.csproj" />
<Project Path="src/Microsoft.Extensions.AI.Agents.Runtime/Microsoft.Extensions.AI.Agents.Runtime.csproj" />
<Project Path="src/Microsoft.Extensions.AI.Agents/Microsoft.Extensions.AI.Agents.csproj" />
<Project Path="src/Microsoft.Extensions.AI.ModelContextProtocol/Microsoft.Extensions.AI.ModelContextProtocol.csproj" Id="4859a869-0c6b-417d-b6d8-6ccbcb60786c">
<BuildType Solution="Publish|*" Project="Release" />
</Project>
</Folder>
<Folder Name="/UnitTests/">
<Project Path="tests/Microsoft.Agents.Orchestration.UnitTests/Microsoft.Agents.Orchestration.UnitTests.csproj" />
Expand Down
1 change: 1 addition & 0 deletions dotnet/samples/GettingStarted/GettingStarted.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.Agents.AzureAI\Microsoft.Extensions.AI.Agents.AzureAI.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Extensions.AI.ModelContextProtocol\Microsoft.Extensions.AI.ModelContextProtocol.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.ComponentModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using Microsoft.Extensions.AI.ModelContextProtocol;
using Microsoft.Extensions.DependencyInjection;

namespace Steps;

Expand Down Expand Up @@ -114,6 +116,96 @@ async Task RunAgentAsync(string input)
await base.AgentCleanUpAsync(provider, agent, thread);
}

[Theory]
[InlineData(ChatClientProviders.AzureOpenAI)]
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
[InlineData(ChatClientProviders.OpenAIAssistant)]
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
[InlineData(ChatClientProviders.OpenAIResponses)]
public async Task ApprovalsWithTools(ChatClientProviders provider)
{
// Creating a MenuTools instance to be used by the agent.
var menuTools = new MenuTools();

// Define the options for the chat client agent.
var agentOptions = new ChatClientAgentOptions(
name: "Host",
instructions: "Answer questions about the menu",
tools: [
AIFunctionFactory.Create(menuTools.GetMenu),
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(menuTools.GetSpecials)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to extend AIFunctionFactory to have a create with approvalRequired: for convenience.

Suggested change
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(menuTools.GetSpecials)),
new AIFunctionFactory.Create(menuTools.GetSpecials, approvalRequired: true)),

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to keep the concepts isolated. I don't want approval being part of the base AIFunction, as it has no impact on how the function actually works and enforces nothing about how folks actually use it. Having it be a higher level type helps with that distinction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having it be a higher level type helps with that distinction.

@stephentoub, Not sure if I follow, this is an Extension method proposal for convenience, not to get rid of the ApprovalRequiredAIFunction.

The extension can live in high level packages (i.e Agents), don't necessarily needs to be in the Abstractions package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an Extension method proposal for convenience

As an extension static method requiring C# 14?

And what does it mean to require approval like that when a caller does:

AIFunction f = AIFunctionFactory.Create(..., approvalRequired: true);
await f.InvokeAsync(...);

?

Copy link
Member

@rogerbarreto rogerbarreto Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an extension static method requiring C# 14?

Ops, obsessed with those future extension methods 😊

Whenever using this extension the type would be a approvalRequired specialization, the parameter would be required as well.

ApprovalRequiredAIFunction f = AIFunctionFactory.Create(..., approvalRequired: true);

Additional to that, helper methods like below could be valid.

aiFunction.AsApprovalRequired(...)

AIFunctionFactory.Create(menuTools.GetItemPrice),
new HostedMcpServerTool("MyService", new Uri("https://mcp-server.example.com"))
{
AllowedTools = ["add"],
ApprovalMode = HostedMcpServerToolApprovalMode.AlwaysRequire,
}
]);

// Create the server-side agent Id when applicable (depending on the provider).
agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);

// Get the chat client to use for the agent.
using var chatClient = base.GetChatClient(provider, agentOptions);

var chatBuilder = chatClient.AsBuilder();
if (chatClient.GetService<HostedMCPChatClient>() is null)
{
chatBuilder.Use((IChatClient innerClient, IServiceProvider services) =>
{
return new HostedMCPChatClient(innerClient);
});
}
if (chatClient.GetService<FunctionInvokingChatClientWithBuiltInApprovals>() is null)
{
chatBuilder.Use((IChatClient innerClient, IServiceProvider services) =>
{
return new FunctionInvokingChatClientWithBuiltInApprovals(innerClient, null, services);
});
}
using var chatClientWithMCPAndApprovals = chatBuilder.Build();

// Define the agent
var agent = new ChatClientAgent(chatClientWithMCPAndApprovals, agentOptions);

// Create the chat history thread to capture the agent interaction.
var thread = agent.GetNewThread();

// Respond to user input, invoking functions where appropriate.
await RunAgentAsync("What is the special soup and its price?");
await RunAgentAsync("What is the special drink?");
await RunAgentAsync("What is 2 + 2?");

async Task RunAgentAsync(string input)
{
this.WriteUserMessage(input);
var response = await agent.RunAsync(input, thread);
this.WriteResponseOutput(response);

var userInputRequests = response.UserInputRequests.ToList();

// Loop until all user input requests are handled.
while (userInputRequests.Count > 0)
{
List<ChatMessage> nextIterationMessages = userInputRequests?.Select((request) => request switch
{
FunctionApprovalRequestContent functionApprovalRequest when functionApprovalRequest.FunctionCall.Name == "GetSpecials" || functionApprovalRequest.FunctionCall.Name == "add" => functionApprovalRequest.Approve(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When approving or rejecting we might consider scenarios where extra information (reason for rejection/approval)

Since we also have an abstraction for FunctionApprovalReponse we may consider optionally providing it to the Approval() or Reject() methods.

Suggested change
FunctionApprovalRequestContent functionApprovalRequest when functionApprovalRequest.FunctionCall.Name == "GetSpecials" || functionApprovalRequest.FunctionCall.Name == "add" => functionApprovalRequest.Approve(),
TextContent rejectionReason = new("refused because xyz");
TextContent approvalReason = new("approved according to the provided evidence");
DataContent imageEvidence = new(byteArray, "image/jpg");
FunctionApprovalResponse approvalResponse = new(approve: true, [approvalReason, imageEvidence]);
FunctionApprovalResponse rejectResponse = new(approve: false, [rejectionReason]);
FunctionApprovalRequestContent functionApprovalRequest when functionApprovalRequest.FunctionCall.Name == "GetSpecials" || functionApprovalRequest.FunctionCall.Name == "add" => functionApprovalRequest.Approve(approvalResponse),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you have in mind in terms of use cases for this reason?

Copy link
Member

@rogerbarreto rogerbarreto Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR for instance (automated by an agent). I can approve it with LGTM or reject with Needs more, etc. Many scenarios in approvals may require more than just yes/no.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question was more about what would use this text? For approval we are just executing the function and returning it's result to the LLM. For rejection we return a standard message to the LLM saying that the function call was not approved by the user.

FunctionApprovalRequestContent functionApprovalRequest => functionApprovalRequest.Reject(),
_ => throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}")
})?.ToList() ?? [];

nextIterationMessages.ForEach(x => Console.WriteLine($"Approval for the {(x.Contents[0] as FunctionApprovalResponseContent)?.FunctionCall.Name} function call is set to {(x.Contents[0] as FunctionApprovalResponseContent)?.Approved}."));

response = await agent.RunAsync(nextIterationMessages, thread);
this.WriteResponseOutput(response);
userInputRequests = response.UserInputRequests.ToList();
}
}

// Clean up the server-side agent after use when applicable (depending on the provider).
await base.AgentCleanUpAsync(provider, agent, thread);
}

private sealed class MenuTools
{
[Description("Get the full menu items.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#endif
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

#if NET9_0_OR_GREATER
using System.Text;
#endif
Expand Down Expand Up @@ -82,6 +84,13 @@ public IList<ChatMessage> Messages
[JsonIgnore]
public string Text => this._messages?.ConcatText() ?? string.Empty;

/// <summary>Gets or sets the user input requests associated with the response.</summary>
/// <remarks>
/// This property concatenates all <see cref="UserInputRequestContent"/> instances in the response.
/// </remarks>
[JsonIgnore]
public IEnumerable<UserInputRequestContent> UserInputRequests => this._messages?.SelectMany(x => x.Contents).OfType<UserInputRequestContent>() ?? Array.Empty<UserInputRequestContent>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User input requests are special enough that they need their own call out via a dedicated property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's valuable to have a dedicated property where developers need to reason about some type of content regularly and in aggregate. Both Text and UserInputRequestContent satisfy that criteria, as developers would want to quickly check if there are any pending user input requests and then handle if there is, or show the result if there isn't. I would argue that we should consider doing something similar for DataContent as well, assuming there are developers who use e.g. images a lot.

For some agent types, the developer may receive a user input request on any run, and when dealing with an unknown agent, the developer doesn't know if the agent may return requests so also need to always check.

Content like FunctionCallContent and FunctionResultContent does not satisfy the criteria (at least from an agent perspective) since agents should always do automatic function calling. Therefore any interest in these types would be for information to the end user, and would fall into the 'everything else' bucket with things like TextReasoningContent.

If developers always want to show a running trace of what is happening to the user, they can of course still loop through all content as well, and handle any user input requests as part of loop, but I think it's valuable to have a simple alternative for devs who don't need the running trace.


/// <summary>Gets or sets the ID of the agent that produced the response.</summary>
public string? AgentId { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.Shared.Diagnostics;

Expand Down Expand Up @@ -92,6 +93,13 @@ public string? AuthorName
[JsonIgnore]
public string Text => this._contents is not null ? this._contents.ConcatText() : string.Empty;

/// <summary>Gets or sets the user input requests associated with the response.</summary>
/// <remarks>
/// This property concatenates all <see cref="UserInputRequestContent"/> instances in the response.
/// </remarks>
[JsonIgnore]
public IEnumerable<UserInputRequestContent> UserInputRequests => this._contents?.OfType<UserInputRequestContent>() ?? Array.Empty<UserInputRequestContent>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto


/// <summary>Gets or sets the agent run response update content items.</summary>
[AllowNull]
public IList<AIContent> Contents
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents a request for user approval of a function call.
/// </summary>
public class FunctionApprovalRequestContent : UserInputRequestContent
{
/// <summary>
/// Initializes a new instance of the <see cref="FunctionApprovalRequestContent"/> class.
/// </summary>
/// <param name="approvalId">The ID to uniquely identify the user input request/response pair.</param>
/// <param name="functionCall">The function call that requires user approval.</param>
public FunctionApprovalRequestContent(string approvalId, FunctionCallContent functionCall)
: base(approvalId)
{
this.FunctionCall = Throw.IfNull(functionCall);
}

/// <summary>
/// Gets or sets the function call that pre-invoke approval is required for.
/// </summary>
public FunctionCallContent FunctionCall { get; }

/// <summary>
/// Creates a <see cref="ChatMessage"/> representing an approval response.
/// </summary>
/// <returns>The <see cref="ChatMessage"/> representing the approval response.</returns>
public ChatMessage Approve()
{
return new ChatMessage(ChatRole.User, [new FunctionApprovalResponseContent(this.Id, true, this.FunctionCall)]);
}

/// <summary>
/// Creates a <see cref="ChatMessage"/> representing a rejection response.
/// </summary>
/// <returns>The <see cref="ChatMessage"/> representing the rejection response.</returns>
public ChatMessage Reject()
{
return new ChatMessage(ChatRole.User, [new FunctionApprovalResponseContent(this.Id, false, this.FunctionCall)]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents a response to a function approval request.
/// </summary>
public class FunctionApprovalResponseContent : UserInputResponseContent
{
/// <summary>
/// Initializes a new instance of the <see cref="FunctionApprovalResponseContent"/> class.
/// </summary>
/// <param name="approvalId">The ID to uniquely identify the user input request/response pair.</param>
/// <param name="approved">Indicates whether the request was approved.</param>
/// <param name="functionCall">The function call that requires user approval.</param>
public FunctionApprovalResponseContent(string approvalId, bool approved, FunctionCallContent functionCall)
: base(approvalId)
{
this.Approved = approved;
this.FunctionCall = Throw.IfNull(functionCall);
}

/// <summary>
/// Gets or sets a value indicating whether the user approved the request.
/// </summary>
public bool Approved { get; set; }

/// <summary>
/// Gets or sets the function call that pre-invoke approval is required for.
/// </summary>
public FunctionCallContent FunctionCall { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Base class for user input request content.
/// </summary>
public abstract class UserInputRequestContent : AIContent
{
/// <summary>
/// Initializes a new instance of the <see cref="UserInputRequestContent"/> class.
/// </summary>
/// <param name="id">The ID to uniquely identify the user input request/response pair.</param>
protected UserInputRequestContent(string id)
{
Id = Throw.IfNullOrWhitespace(id);
}

/// <summary>
/// Gets the ID to uniquely identify the user input request/response pair.
/// </summary>
public string Id { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Base class for user input response content.
/// </summary>
public abstract class UserInputResponseContent : AIContent
{
/// <summary>
/// Initializes a new instance of the <see cref="UserInputRequestContent"/> class.
/// </summary>
/// <param name="id">The ID to uniquely identify the user input request/response pair.</param>
protected UserInputResponseContent(string id)
{
Id = Throw.IfNullOrWhitespace(id);
}

/// <summary>
/// Gets the ID to uniquely identify the user input request/response pair.
/// </summary>
public string Id { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents a hosted MCP server tool that can be specified to an AI service.
/// </summary>
public class HostedMcpServerTool : AITool
{
/// <summary>
/// Initializes a new instance of the <see cref="HostedMcpServerTool"/> class.
/// </summary>
/// <param name="serverName">The name of the remote MCP server.</param>
/// <param name="url">The URL of the remote MCP server.</param>
public HostedMcpServerTool(string serverName, Uri url)
{
ServerName = Throw.IfNullOrWhitespace(serverName);
Url = Throw.IfNull(url);
}

/// <summary>
/// Gets the name of the remote MCP server that is used to identify it.
/// </summary>
public string ServerName { get; }

/// <summary>
/// Gets the URL of the remote MCP server.
/// </summary>
public Uri Url { get; }

/// <summary>
/// Gets or sets the description of the remote MCP server, used to provide more context to the AI service.
/// </summary>
public string? ServerDescription { get; set; }

/// <summary>
/// Gets or sets the list of tools allowed to be used by the AI service.
/// </summary>
public IList<string>? AllowedTools { get; set; }

/// <summary>
/// Gets or sets the approval mode that indicates when the AI service should require user approval for tool calls to the remote MCP server.
/// </summary>
/// <remarks>
/// You can set this property to <see cref="HostedMcpServerToolApprovalMode.AlwaysRequire"/> to require approval for all tool calls,
/// or to <see cref="HostedMcpServerToolApprovalMode.NeverRequire"/> to never require approval.
/// </remarks>
public HostedMcpServerToolApprovalMode? ApprovalMode { get; set; }

/// <summary>
/// Gets or sets the HTTP headers that the AI service should use when making tool calls to the remote MCP server.
/// </summary>
/// <remarks>
/// This property is useful for specifying the authentication header or other headers required by the MCP server.
/// </remarks>
public IDictionary<string, string>? Headers { get; set; }
}
Loading
Loading