diff --git a/src/Observability/Hosting/Caching/AgenticTokenCache.cs b/src/Observability/Hosting/Caching/AgenticTokenCache.cs index bfe964e4..86fe99d5 100644 --- a/src/Observability/Hosting/Caching/AgenticTokenCache.cs +++ b/src/Observability/Hosting/Caching/AgenticTokenCache.cs @@ -96,7 +96,11 @@ public void RegisterObservability(string agentId, string tenantId, AgenticTokenS /// /// The observability token if available; otherwise, null. /// - public async Task GetObservabilityToken(string agentId, string tenantId) + public Task GetObservabilityToken(string agentId, string tenantId) + => GetObservabilityToken(agentId, tenantId, CancellationToken.None); + + /// + public async Task GetObservabilityToken(string agentId, string tenantId, CancellationToken cancellationToken) { if (!_map.TryGetValue($"{agentId}:{tenantId}", out var entry)) return null; diff --git a/src/Observability/Hosting/Caching/IExporterTokenCache.cs b/src/Observability/Hosting/Caching/IExporterTokenCache.cs index f2ac5e5c..f0e45eca 100644 --- a/src/Observability/Hosting/Caching/IExporterTokenCache.cs +++ b/src/Observability/Hosting/Caching/IExporterTokenCache.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Agents.A365.Observability.Hosting.Caching @@ -18,5 +19,10 @@ public interface IExporterTokenCache where T : class /// Returns an observability token (cached inside the credential) or null on failure/not registered. /// Task GetObservabilityToken(string agentId, string tenantId); + + /// + /// Returns an observability token (cached inside the credential) or null on failure/not registered, with cancellation support. + /// + Task GetObservabilityToken(string agentId, string tenantId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Observability/Hosting/Caching/ServiceTokenCache.cs b/src/Observability/Hosting/Caching/ServiceTokenCache.cs index fe94a0bc..07424c86 100644 --- a/src/Observability/Hosting/Caching/ServiceTokenCache.cs +++ b/src/Observability/Hosting/Caching/ServiceTokenCache.cs @@ -122,15 +122,19 @@ public void RegisterObservability(string agentId, string tenantId, string token, /// The agent identifier. /// The tenant identifier. /// The observability token if valid; otherwise, null. - public async Task GetObservabilityToken(string agentId, string tenantId) + public Task GetObservabilityToken(string agentId, string tenantId) + => GetObservabilityToken(agentId, tenantId, CancellationToken.None); + + /// + public Task GetObservabilityToken(string agentId, string tenantId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(tenantId)) - return null; + return Task.FromResult(null); var key = GetKey(agentId, tenantId); if (!_map.TryGetValue(key, out var entry)) - return null; + return Task.FromResult(null); // Check if token has expired if (DateTimeOffset.UtcNow >= entry.ExpiresAt) @@ -140,10 +144,10 @@ public void RegisterObservability(string agentId, string tenantId, string token, { removedEntry.ClearToken(); } - return null; + return Task.FromResult(null); } - return await Task.FromResult(entry.Token).ConfigureAwait(false); + return Task.FromResult(entry.Token); } /// diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterAsyncE2ETests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterAsyncE2ETests.cs index d0897a4d..27da9626 100644 --- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterAsyncE2ETests.cs +++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterAsyncE2ETests.cs @@ -17,6 +17,7 @@ public class Agent365ExporterAsyncE2ETests { private TestHttpMessageHandler? _handler; private ServiceProvider? _provider; + private HttpClient? _httpClient; private bool _receivedRequest; private string? _receivedContent; @@ -408,8 +409,17 @@ private void SetupExporterTest() req.Headers.Authorization.Should().NotBeNull(); return new HttpResponseMessage(System.Net.HttpStatusCode.OK); }); - var httpClient = new HttpClient(this._handler); - this._provider = this.CreateTestServiceProvider(httpClient); + this._httpClient = new HttpClient(this._handler); + this._provider = this.CreateTestServiceProvider(this._httpClient); + } + + [TestCleanup] + public void Cleanup() + { + this._provider?.Dispose(); + this._provider = null; + this._httpClient?.Dispose(); + this._httpClient = null; } } } diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_ToolEnumerationTests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_ToolEnumerationTests.cs index 600ef40d..d9b76f15 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_ToolEnumerationTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_ToolEnumerationTests.cs @@ -10,6 +10,7 @@ using ModelContextProtocol.Client; using Moq; using System.Net.Http; +using System.Threading; using Xunit; namespace Microsoft.Agents.A365.Tooling.Core.Tests; @@ -53,7 +54,7 @@ public async Task EnumerateToolsFromServersAsync_WhenListServersFails_ReturnsEmp // Arrange var toolOptions = new ToolOptions(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception("Network error")); // Act @@ -74,7 +75,7 @@ public async Task EnumerateToolsFromServersAsync_WhenNoServersConfigured_Returns // Arrange var toolOptions = new ToolOptions(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); // Act @@ -101,7 +102,7 @@ public async Task EnumerateToolsFromServersAsync_FiltersInvalidServers_WithMissi }; _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -109,7 +110,8 @@ public async Task EnumerateToolsFromServersAsync_FiltersInvalidServers_WithMissi It.IsAny(), It.Is(s => s.mcpServerName == "valid-server"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(new List()); // Act @@ -137,7 +139,7 @@ public async Task EnumerateToolsFromServersAsync_FiltersInvalidServers_WithMissi }; _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -145,7 +147,8 @@ public async Task EnumerateToolsFromServersAsync_FiltersInvalidServers_WithMissi It.IsAny(), It.Is(s => s.mcpServerName == "valid-server"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(new List()); // Act @@ -175,7 +178,7 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesToolsFromMultipleServ var tools2 = new List(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -183,7 +186,8 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesToolsFromMultipleServ It.IsAny(), It.Is(s => s.mcpServerName == "server1"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(tools1); _mockService @@ -191,7 +195,8 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesToolsFromMultipleServ It.IsAny(), It.Is(s => s.mcpServerName == "server2"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(tools2); // Act @@ -223,7 +228,7 @@ public async Task EnumerateToolsFromServersAsync_HandlesIndividualServerFailures var workingTools = new List(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -231,7 +236,8 @@ public async Task EnumerateToolsFromServersAsync_HandlesIndividualServerFailures It.IsAny(), It.Is(s => s.mcpServerName == "failing-server"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ThrowsAsync(new Exception("Server connection failed")); _mockService @@ -239,7 +245,8 @@ public async Task EnumerateToolsFromServersAsync_HandlesIndividualServerFailures It.IsAny(), It.Is(s => s.mcpServerName == "working-server"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(workingTools); // Act @@ -274,7 +281,7 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesInParallel() var tcs3 = new TaskCompletionSource>(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -282,7 +289,8 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesInParallel() It.IsAny(), It.Is(s => s.mcpServerName == "server1"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .Returns(async () => { lock (callOrder) callOrder.Add("server1-start"); @@ -296,7 +304,8 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesInParallel() It.IsAny(), It.Is(s => s.mcpServerName == "server2"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .Returns(async () => { lock (callOrder) callOrder.Add("server2-start"); @@ -310,7 +319,8 @@ public async Task EnumerateToolsFromServersAsync_EnumeratesInParallel() It.IsAny(), It.Is(s => s.mcpServerName == "server3"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .Returns(async () => { lock (callOrder) callOrder.Add("server3-start"); @@ -372,7 +382,7 @@ public async Task EnumerateAllToolsAsync_ReturnsFlatListOfAllTools() var tools2 = new List(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers); _mockService @@ -380,7 +390,8 @@ public async Task EnumerateAllToolsAsync_ReturnsFlatListOfAllTools() It.IsAny(), It.Is(s => s.mcpServerName == "server1"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(tools1); _mockService @@ -388,7 +399,8 @@ public async Task EnumerateAllToolsAsync_ReturnsFlatListOfAllTools() It.IsAny(), It.Is(s => s.mcpServerName == "server2"), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(tools2); // Act @@ -409,7 +421,7 @@ public async Task EnumerateAllToolsAsync_WhenNoServers_ReturnsEmptyList() // Arrange var toolOptions = new ToolOptions(); _mockService - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); // Act diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_V2TokenTests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_V2TokenTests.cs index a894de30..81fb0b9d 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_V2TokenTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_V2TokenTests.cs @@ -58,7 +58,7 @@ private static FakeTokenProvider TokenProvider(params (string scope, string toke private void SetupListServers(IEnumerable servers) => _service - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(servers.ToList()); // ─── AttachPerAudienceTokens (via ListToolServersWithTokensAsync) ───────── @@ -246,16 +246,16 @@ public async Task EnumerateToolsFromServersAsync_WithTokenProvider_AttachesToken .Setup(x => x.GetMcpClientToolsAsync( It.IsAny(), It.Is(s => s.mcpServerName == "v1"), - It.IsAny(), It.IsAny())) - .Callback((_, s, _, _) => capturedV1 = s) + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, s, _, _, _) => capturedV1 = s) .ReturnsAsync(new List()); _service .Setup(x => x.GetMcpClientToolsAsync( It.IsAny(), It.Is(s => s.mcpServerName == "v2"), - It.IsAny(), It.IsAny())) - .Callback((_, s, _, _) => capturedV2 = s) + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, s, _, _, _) => capturedV2 = s) .ReturnsAsync(new List()); // Act @@ -280,7 +280,7 @@ public async Task EnumerateToolsFromServersAsync_WithTokenProvider_WhenListFails { // Arrange _service - .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.ListToolServersAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception("gateway unreachable")); var provider = TokenProvider(); @@ -308,7 +308,7 @@ public async Task EnumerateToolsFromServersAsync_WithTokenProvider_SkipsServersW .Setup(x => x.GetMcpClientToolsAsync( It.IsAny(), It.Is(s => s.mcpServerName == "valid"), - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); // Act @@ -338,7 +338,7 @@ public async Task EnumerateToolsFromServersAsync_LegacyPath_V2Server_ThrowsWithM new Mock().Object) { CallBase = true }; service.Setup(x => x.ListToolServersAsync( - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List { V2Server("mail") }); var act = () => service.Object.EnumerateToolsFromServersAsync( @@ -369,11 +369,11 @@ public async Task EnumerateToolsFromServersAsync_LegacyPath_ApiPrefixedAtgAudien audience = $"api://{AtgAppId}" // equivalent ATG audience form }; service.Setup(x => x.ListToolServersAsync( - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List { v1WithApiAudience }); service.Setup(x => x.GetMcpClientToolsAsync( It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); // Act — should NOT throw; api:// is a V1 server diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/AddToolServersToAgent_Tests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/AddToolServersToAgent_Tests.cs index bad16f30..78b94d58 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/AddToolServersToAgent_Tests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/AddToolServersToAgent_Tests.cs @@ -67,7 +67,8 @@ await service.AddToolServersToAgent( TestAuthToken, It.IsAny(), mockTurnContext.Object, - It.Is(o => o.UserAgentConfiguration == Agent365AgentFrameworkSdkUserAgentConfiguration.Instance)), + It.Is(o => o.UserAgentConfiguration == Agent365AgentFrameworkSdkUserAgentConfiguration.Instance), + It.IsAny()), Times.Once); } diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/GetMcpToolsAsync_Tests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/GetMcpToolsAsync_Tests.cs index 856cbb03..0ee2475d 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/GetMcpToolsAsync_Tests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/GetMcpToolsAsync_Tests.cs @@ -39,7 +39,8 @@ await service.GetMcpToolsAsync( TestAuthToken, It.IsAny(), mockTurnContext.Object, - It.Is(o => o.UserAgentConfiguration == Agent365AgentFrameworkSdkUserAgentConfiguration.Instance)), + It.Is(o => o.UserAgentConfiguration == Agent365AgentFrameworkSdkUserAgentConfiguration.Instance), + It.IsAny()), Times.Once); } diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/McpToolRegistrationServiceTestBase.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/McpToolRegistrationServiceTestBase.cs index c6a35368..99c0aa59 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/McpToolRegistrationServiceTestBase.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/McpToolRegistrationServiceTestBase.cs @@ -79,11 +79,12 @@ protected void SetupMocksForAddToolServers(Action? captureToolOptio It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())); + It.IsAny(), + It.IsAny())); if (captureToolOptions != null) { - setup.Callback((_, _, _, _, options) => captureToolOptions(options)); + setup.Callback((_, _, _, _, options, _) => captureToolOptions(options)); } setup.ReturnsAsync((new List(), new Dictionary>())); @@ -104,11 +105,12 @@ protected void SetupMocksForGetMcpTools(Action? captureToolOptions It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())); + It.IsAny(), + It.IsAny())); if (captureToolOptions != null) { - setup.Callback((_, _, _, _, options) => captureToolOptions(options)); + setup.Callback((_, _, _, _, options, _) => captureToolOptions(options)); } setup.ReturnsAsync((new List(), new Dictionary>())); diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry.Tests/McpToolRegistrationServiceTests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry.Tests/McpToolRegistrationServiceTests.cs index d3ef8ef6..3c7d65cb 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry.Tests/McpToolRegistrationServiceTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry.Tests/McpToolRegistrationServiceTests.cs @@ -94,11 +94,12 @@ private void SetupMocksForEmptyToolEnumeration(Action? captureToolO It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())); + It.IsAny(), + It.IsAny())); if (captureToolOptions != null) { - setup.Callback((_, _, _, options) => captureToolOptions(options)); + setup.Callback((_, _, _, options, _) => captureToolOptions(options)); } setup.ReturnsAsync((new List(), new Dictionary>())); @@ -114,7 +115,8 @@ private void SetupMocksForToolEnumeration(List servers, Diction It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync((servers, toolsByServer)); } @@ -197,7 +199,8 @@ await _service.GetMcpToolDefinitionsAndResourcesAsync( TestAgentInstanceId, TestAuthToken, _mockTurnContext.Object, - It.Is(o => o.UserAgentConfiguration == Agent365AzureAIFoundrySdkUserAgentConfiguration.Instance)), + It.Is(o => o.UserAgentConfiguration == Agent365AzureAIFoundrySdkUserAgentConfiguration.Instance), + It.IsAny()), Times.Once); } diff --git a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Tests/Services/McpToolRegistrationServiceTests.cs b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Tests/Services/McpToolRegistrationServiceTests.cs index d341d451..e66683dd 100644 --- a/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Tests/Services/McpToolRegistrationServiceTests.cs +++ b/src/Tests/Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Tests/Services/McpToolRegistrationServiceTests.cs @@ -112,11 +112,12 @@ private void SetupMocksForEmptyToolEnumeration(Action? captureToolO It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())); + It.IsAny(), + It.IsAny())); if (captureToolOptions != null) { - setup.Callback((_, _, _, _, options) => captureToolOptions(options)); + setup.Callback((_, _, _, _, options, _) => captureToolOptions(options)); } setup.ReturnsAsync((new List(), new Dictionary>())); @@ -170,7 +171,8 @@ await service.AddToolServersToAgentAsync( _testJwtToken, It.IsAny(), mockTurnContext.Object, - It.Is(o => o.UserAgentConfiguration == Agent365SemanticKernelSdkUserAgentConfiguration.Instance)), + It.Is(o => o.UserAgentConfiguration == Agent365SemanticKernelSdkUserAgentConfiguration.Instance), + It.IsAny()), Times.Once); } diff --git a/src/Tooling/Core/Services/IMcpToolServerConfigurationService.cs b/src/Tooling/Core/Services/IMcpToolServerConfigurationService.cs index 9a7f2515..1e01788e 100644 --- a/src/Tooling/Core/Services/IMcpToolServerConfigurationService.cs +++ b/src/Tooling/Core/Services/IMcpToolServerConfigurationService.cs @@ -1,110 +1,178 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Agents.A365.Runtime; -using Microsoft.Agents.A365.Tooling.Models; -using Microsoft.Agents.Builder; -using ModelContextProtocol.Client; - -namespace Microsoft.Agents.A365.Tooling.Services -{ - /// - /// Provides methods for managing MCP server configurations. - /// - public interface IMcpToolServerConfigurationService - { - /// - /// Gets the list of MCP Servers that are configured for the agent. - /// - /// Agent instance Id for the agent. - /// Auth token to access the MCP servers - /// Returns the list of MCP Servers that are configured. - Task> ListToolServersAsync(string agentInstanceId, string authToken); - - /// - /// Gets the list of MCP Servers that are configured for the agent. - /// - /// Agent instance Id for the agent. - /// Auth token to access the MCP servers - /// Tool options for listing servers. - /// Returns the list of MCP Servers that are configured. - Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions); - - /// - /// Gets the MCP Client Tools from the specified MCP server. - /// - /// The turn context. - /// The MCP server configuration. - /// The authentication token. - /// Tool options for listing servers. - /// MCP Client Tools - /// - Task> GetMcpClientToolsAsync(ITurnContext turnContext, MCPServerConfig mCPServerConfig, string authToken, ToolOptions toolOptions); - - /// - /// Sends chat history to the MCP platform for real-time threat protection. - /// - /// The turn context containing conversation information. - /// The chat history messages to send. - /// A cancellation token to cancel the operation. - /// A task representing the asynchronous operation that returns an indicating success or failure. - /// Thrown when or is null. - /// Thrown when required turn context properties (Conversation.Id, Activity.Id, or Activity.Text) are null. - /// - /// HTTP exceptions (HttpRequestException, TaskCanceledException) are caught and logged but not rethrown. - /// Instead, the method returns an indicating whether the operation succeeded or failed. - /// Callers can choose to inspect the result for error handling or ignore it if error details are not needed. - /// - Task SendChatHistoryAsync(ITurnContext turnContext, ChatHistoryMessage[] chatHistoryMessages, CancellationToken cancellationToken = default); - - /// - /// Sends chat history to the MCP platform for real-time threat protection. - /// - /// The turn context containing conversation information. - /// The chat history messages to send. - /// Tool options for sending chat history. - /// A cancellation token to cancel the operation. - /// A task representing the asynchronous operation that returns an indicating success or failure. - /// Thrown when or is null. - /// Thrown when required turn context properties (Conversation.Id, Activity.Id, or Activity.Text) are null. - /// - /// HTTP exceptions (HttpRequestException, TaskCanceledException) are caught and logged but not rethrown. - /// Instead, the method returns an indicating whether the operation succeeded or failed. - /// Callers can choose to inspect the result for error handling or ignore it if error details are not needed. - /// - Task SendChatHistoryAsync(ITurnContext turnContext, ChatHistoryMessage[] chatHistoryMessages, ToolOptions toolOptions, CancellationToken cancellationToken = default); - - /// - /// Enumerates all MCP tools from configured servers for a given agent. - /// - /// The agent instance ID. - /// Authentication token for MCP server access. - /// Turn context for the current request. - /// Tool options including user agent configuration. - /// A tuple containing server configurations and a dictionary mapping server names to their available tools. - Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions); - - /// - /// Enumerates all MCP tools from configured servers, acquiring per-audience tokens for each server. - /// V2 servers (distinct audience) receive audience-scoped tokens via ; - /// V1 servers fall back to . - /// - /// The agent instance ID. - /// Shared authentication token (V1 fallback). - /// Provides per-server Bearer tokens, routing V2 servers to audience-scoped tokens. - /// Turn context for the current request. - /// Tool options including user agent configuration. - /// A tuple containing server configurations and a dictionary mapping server names to their available tools. - Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, IMcpTokenProvider tokenProvider, ITurnContext turnContext, ToolOptions toolOptions); - - /// - /// Enumerates all MCP tools from configured servers, returning a flat list of all tools. - /// - /// The agent instance ID. - /// Authentication token for MCP server access. - /// Turn context for the current request. - /// Tool options including user agent configuration. - /// A flat list of all MCP tools from all configured servers. - Task> EnumerateAllToolsAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Agents.A365.Runtime; +using Microsoft.Agents.A365.Tooling.Models; +using Microsoft.Agents.Builder; +using ModelContextProtocol.Client; +using System.Threading; + +namespace Microsoft.Agents.A365.Tooling.Services +{ + /// + /// Provides methods for managing MCP server configurations. + /// + public interface IMcpToolServerConfigurationService + { + /// + /// Gets the list of MCP Servers that are configured for the agent. + /// + /// Agent instance Id for the agent. + /// Auth token to access the MCP servers + /// Returns the list of MCP Servers that are configured. + Task> ListToolServersAsync(string agentInstanceId, string authToken); + + /// + /// Gets the list of MCP Servers that are configured for the agent. + /// + /// Agent instance Id for the agent. + /// Auth token to access the MCP servers + /// A cancellation token to cancel the operation. + /// Returns the list of MCP Servers that are configured. + Task> ListToolServersAsync(string agentInstanceId, string authToken, CancellationToken cancellationToken); + + /// + /// Gets the list of MCP Servers that are configured for the agent. + /// + /// Agent instance Id for the agent. + /// Auth token to access the MCP servers + /// Tool options for listing servers. + /// Returns the list of MCP Servers that are configured. + Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions); + + /// + /// Gets the list of MCP Servers that are configured for the agent. + /// + /// Agent instance Id for the agent. + /// Auth token to access the MCP servers + /// Tool options for listing servers. + /// A cancellation token to cancel the operation. + /// Returns the list of MCP Servers that are configured. + Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions, CancellationToken cancellationToken); + + /// + /// Gets the MCP Client Tools from the specified MCP server. + /// + /// The turn context. + /// The MCP server configuration. + /// The authentication token. + /// Tool options for listing servers. + /// MCP Client Tools + /// + Task> GetMcpClientToolsAsync(ITurnContext turnContext, MCPServerConfig mCPServerConfig, string authToken, ToolOptions toolOptions); + + /// + /// Gets the MCP Client Tools from the specified MCP server. + /// + /// The turn context. + /// The MCP server configuration. + /// The authentication token. + /// Tool options for listing servers. + /// A cancellation token to cancel the operation. + /// MCP Client Tools + /// + Task> GetMcpClientToolsAsync(ITurnContext turnContext, MCPServerConfig mCPServerConfig, string authToken, ToolOptions toolOptions, CancellationToken cancellationToken); + + /// + /// Sends chat history to the MCP platform for real-time threat protection. + /// + /// The turn context containing conversation information. + /// The chat history messages to send. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous operation that returns an indicating success or failure. + /// Thrown when or is null. + /// Thrown when required turn context properties (Conversation.Id, Activity.Id, or Activity.Text) are null. + /// + /// HTTP exceptions (HttpRequestException, TaskCanceledException) are caught and logged but not rethrown. + /// Instead, the method returns an indicating whether the operation succeeded or failed. + /// Callers can choose to inspect the result for error handling or ignore it if error details are not needed. + /// + Task SendChatHistoryAsync(ITurnContext turnContext, ChatHistoryMessage[] chatHistoryMessages, CancellationToken cancellationToken = default); + + /// + /// Sends chat history to the MCP platform for real-time threat protection. + /// + /// The turn context containing conversation information. + /// The chat history messages to send. + /// Tool options for sending chat history. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous operation that returns an indicating success or failure. + /// Thrown when or is null. + /// Thrown when required turn context properties (Conversation.Id, Activity.Id, or Activity.Text) are null. + /// + /// HTTP exceptions (HttpRequestException, TaskCanceledException) are caught and logged but not rethrown. + /// Instead, the method returns an indicating whether the operation succeeded or failed. + /// Callers can choose to inspect the result for error handling or ignore it if error details are not needed. + /// + Task SendChatHistoryAsync(ITurnContext turnContext, ChatHistoryMessage[] chatHistoryMessages, ToolOptions toolOptions, CancellationToken cancellationToken = default); + + /// + /// Enumerates all MCP tools from configured servers for a given agent. + /// + /// The agent instance ID. + /// Authentication token for MCP server access. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A tuple containing server configurations and a dictionary mapping server names to their available tools. + Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions); + + /// + /// Enumerates all MCP tools from configured servers for a given agent. + /// + /// The agent instance ID. + /// Authentication token for MCP server access. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A cancellation token to cancel the operation. + /// A tuple containing server configurations and a dictionary mapping server names to their available tools. + Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions, CancellationToken cancellationToken); + + /// + /// Enumerates all MCP tools from configured servers, acquiring per-audience tokens for each server. + /// V2 servers (distinct audience) receive audience-scoped tokens via ; + /// V1 servers fall back to . + /// + /// The agent instance ID. + /// Shared authentication token (V1 fallback). + /// Provides per-server Bearer tokens, routing V2 servers to audience-scoped tokens. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A tuple containing server configurations and a dictionary mapping server names to their available tools. + Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, IMcpTokenProvider tokenProvider, ITurnContext turnContext, ToolOptions toolOptions); + + /// + /// Enumerates all MCP tools from configured servers, acquiring per-audience tokens for each server. + /// V2 servers (distinct audience) receive audience-scoped tokens via ; + /// V1 servers fall back to . + /// + /// The agent instance ID. + /// Shared authentication token (V1 fallback). + /// Provides per-server Bearer tokens, routing V2 servers to audience-scoped tokens. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A cancellation token to cancel the operation. + /// A tuple containing server configurations and a dictionary mapping server names to their available tools. + Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync(string agentInstanceId, string authToken, IMcpTokenProvider tokenProvider, ITurnContext turnContext, ToolOptions toolOptions, CancellationToken cancellationToken); + + /// + /// Enumerates all MCP tools from configured servers, returning a flat list of all tools. + /// + /// The agent instance ID. + /// Authentication token for MCP server access. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A flat list of all MCP tools from all configured servers. + Task> EnumerateAllToolsAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions); + + /// + /// Enumerates all MCP tools from configured servers, returning a flat list of all tools. + /// + /// The agent instance ID. + /// Authentication token for MCP server access. + /// Turn context for the current request. + /// Tool options including user agent configuration. + /// A cancellation token to cancel the operation. + /// A flat list of all MCP tools from all configured servers. + Task> EnumerateAllToolsAsync(string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions, CancellationToken cancellationToken); + } +} diff --git a/src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs b/src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs index bc6e7044..34be55bd 100644 --- a/src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs +++ b/src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs @@ -12,6 +12,7 @@ namespace Microsoft.Agents.A365.Tooling.Services using System.Collections.Generic; using System.Linq; using System.Net.Http; + using System.Threading; using System.Threading.Tasks; /// @@ -20,12 +21,22 @@ namespace Microsoft.Agents.A365.Tooling.Services public partial class McpToolServerConfigurationService { /// - public virtual async Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( + public virtual Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( string agentInstanceId, string authToken, IMcpTokenProvider tokenProvider, ITurnContext turnContext, ToolOptions toolOptions) + => EnumerateToolsFromServersAsync(agentInstanceId, authToken, tokenProvider, turnContext, toolOptions, CancellationToken.None); + + /// + public virtual async Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( + string agentInstanceId, + string authToken, + IMcpTokenProvider tokenProvider, + ITurnContext turnContext, + ToolOptions toolOptions, + CancellationToken cancellationToken) { var toolsByServer = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -36,7 +47,8 @@ public partial class McpToolServerConfigurationService agentInstanceId, authToken, tokenProvider, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -73,7 +85,8 @@ public partial class McpToolServerConfigurationService turnContext, server, authToken, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); _logger.LogInformation( "Successfully loaded {ToolCount} tools from MCP server '{ServerName}'", @@ -115,11 +128,20 @@ ex is TaskCanceledException || } /// - public virtual async Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( + public virtual Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions) + => EnumerateToolsFromServersAsync(agentInstanceId, authToken, turnContext, toolOptions, CancellationToken.None); + + /// + public virtual async Task<(List Servers, Dictionary> ToolsByServer)> EnumerateToolsFromServersAsync( + string agentInstanceId, + string authToken, + ITurnContext turnContext, + ToolOptions toolOptions, + CancellationToken cancellationToken) { var toolsByServer = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -129,7 +151,8 @@ ex is TaskCanceledException || servers = await ListToolServersAsync( agentInstanceId, authToken, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -189,7 +212,8 @@ ex is TaskCanceledException || turnContext, server, authToken, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); _logger.LogInformation( "Successfully loaded {ToolCount} tools from MCP server '{ServerName}'", @@ -228,17 +252,27 @@ ex is TaskCanceledException || } /// - public virtual async Task> EnumerateAllToolsAsync( + public virtual Task> EnumerateAllToolsAsync( string agentInstanceId, string authToken, ITurnContext turnContext, ToolOptions toolOptions) + => EnumerateAllToolsAsync(agentInstanceId, authToken, turnContext, toolOptions, CancellationToken.None); + + /// + public virtual async Task> EnumerateAllToolsAsync( + string agentInstanceId, + string authToken, + ITurnContext turnContext, + ToolOptions toolOptions, + CancellationToken cancellationToken) { var (_, toolsByServer) = await EnumerateToolsFromServersAsync( agentInstanceId, authToken, turnContext, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); var allTools = new List(); foreach (var tools in toolsByServer.Values) diff --git a/src/Tooling/Core/Services/McpToolServerConfigurationService.cs b/src/Tooling/Core/Services/McpToolServerConfigurationService.cs index f5179dab..c06de7b5 100644 --- a/src/Tooling/Core/Services/McpToolServerConfigurationService.cs +++ b/src/Tooling/Core/Services/McpToolServerConfigurationService.cs @@ -11,6 +11,7 @@ namespace Microsoft.Agents.A365.Tooling.Services using System.Reflection; using System.Text; using System.Text.Json; + using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.A365.Runtime; using Microsoft.Agents.A365.Tooling.Handlers; @@ -49,15 +50,23 @@ public McpToolServerConfigurationService(ILogger - public virtual async Task> ListToolServersAsync(string agentInstanceId, string authToken) + public virtual Task> ListToolServersAsync(string agentInstanceId, string authToken) + => ListToolServersAsync(agentInstanceId, authToken, CancellationToken.None); + + /// + public virtual async Task> ListToolServersAsync(string agentInstanceId, string authToken, CancellationToken cancellationToken) { - return await ListToolServersAsync(agentInstanceId, authToken, new ToolOptions()); + return await ListToolServersAsync(agentInstanceId, authToken, new ToolOptions(), cancellationToken); } /// - public virtual async Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions) + public virtual Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions) + => ListToolServersAsync(agentInstanceId, authToken, toolOptions, CancellationToken.None); + + /// + public virtual async Task> ListToolServersAsync(string agentInstanceId, string authToken, ToolOptions toolOptions, CancellationToken cancellationToken) { - return IsDevScenario() ? GetMCPServersFromManifest() : await GetMCPServerFromToolingGatewayAsync(agentInstanceId, authToken, toolOptions); + return IsDevScenario() ? GetMCPServersFromManifest() : await GetMCPServerFromToolingGatewayAsync(agentInstanceId, authToken, toolOptions, cancellationToken); } /// @@ -72,17 +81,26 @@ internal virtual async Task> ListToolServersWithTokensAsyn ToolOptions toolOptions, CancellationToken cancellationToken = default) { - var servers = await ListToolServersAsync(agentInstanceId, authToken, toolOptions).ConfigureAwait(false); + var servers = await ListToolServersAsync(agentInstanceId, authToken, toolOptions, cancellationToken).ConfigureAwait(false); await AttachPerAudienceTokensAsync(servers, tokenProvider, cancellationToken).ConfigureAwait(false); return servers; } /// - public virtual async Task> GetMcpClientToolsAsync( + public virtual Task> GetMcpClientToolsAsync( ITurnContext turnContext, MCPServerConfig mCPServerConfig, string authToken, ToolOptions toolOptions) + => GetMcpClientToolsAsync(turnContext, mCPServerConfig, authToken, toolOptions, CancellationToken.None); + + /// + public virtual async Task> GetMcpClientToolsAsync( + ITurnContext turnContext, + MCPServerConfig mCPServerConfig, + string authToken, + ToolOptions toolOptions, + CancellationToken cancellationToken) { try { @@ -99,8 +117,8 @@ public virtual async Task> GetMcpClientToolsAsync( this._logger.LogInformation($"Creating custom MCP client for: {mCPServerConfig.mcpServerName} at {mCPServerConfig.url}"); // Use custom HTTP-based implementation since MCP client library doesn't work - var mcpClient = await CreateMcpClientWithAuthHandlers(turnContext, new Uri(mCPServerConfig.url), effectiveToken, toolOptions); - var tools = await mcpClient.ListToolsAsync(); + var mcpClient = await CreateMcpClientWithAuthHandlers(turnContext, new Uri(mCPServerConfig.url), effectiveToken, toolOptions, cancellationToken); + var tools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken); this._logger.LogInformation($"Successfully retrieved {tools.Count} tools from {mCPServerConfig.mcpServerName}"); @@ -178,7 +196,7 @@ public async Task SendChatHistoryAsync(ITurnContext turnContext } private async Task> GetMCPServerFromToolingGatewayAsync( - string agentInstanceId, string authToken, ToolOptions toolOptions) + string agentInstanceId, string authToken, ToolOptions toolOptions, CancellationToken cancellationToken = default) { string configEndpoint = Utility.GetToolingGatewayForDigitalWorker(agentInstanceId, this._configuration); @@ -194,7 +212,7 @@ private async Task> GetMCPServerFromToolingGatewayAsync( httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken); - var response = await httpClient.GetStringAsync(configEndpoint); + var response = await httpClient.GetStringAsync(configEndpoint, cancellationToken); var options = new JsonSerializerOptions { @@ -480,7 +498,7 @@ private List GetMCPServersFromManifest() /// /// Creates an MCP client with authentication handlers similar to your reference implementation /// - private async Task CreateMcpClientWithAuthHandlers(ITurnContext turnContext, Uri endpoint, string authToken, ToolOptions toolOptions) + private async Task CreateMcpClientWithAuthHandlers(ITurnContext turnContext, Uri endpoint, string authToken, ToolOptions toolOptions, CancellationToken cancellationToken = default) { // Create HTTP client handler chain for MCP service authentication var httpClientHandler = new HttpClientHandler(); @@ -528,7 +546,7 @@ private async Task CreateMcpClientWithAuthHandlers(ITurnContext turn try { - return await McpClientFactory.CreateAsync(clientTransport, loggerFactory: this._loggerFactory); + return await McpClientFactory.CreateAsync(clientTransport, loggerFactory: this._loggerFactory, cancellationToken: cancellationToken); } catch (Exception ex) { diff --git a/src/Tooling/Extensions/AgentFramework/Services/IMcpToolRegistrationService.cs b/src/Tooling/Extensions/AgentFramework/Services/IMcpToolRegistrationService.cs index 8d5ac8a2..e8576bd4 100644 --- a/src/Tooling/Extensions/AgentFramework/Services/IMcpToolRegistrationService.cs +++ b/src/Tooling/Extensions/AgentFramework/Services/IMcpToolRegistrationService.cs @@ -9,6 +9,7 @@ namespace Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Services; using Microsoft.Agents.Builder.App.UserAuth; using Microsoft.Extensions.AI; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; /// @@ -42,6 +43,34 @@ Task AddToolServersToAgent( ITurnContext turnContext, string? authToken = null); + /// + /// Add new MCP servers to the agent by creating a new Agent instance. + /// + /// Note: Due to Microsoft.Extensions.AI framework limitations, MCP tools must be set during + /// Agent creation. If new tools are found, this method creates a new Agent + /// instance with all tools (existing + new) properly initialized. + /// + /// The configured IChatClient to use for creating the agent. + /// The agent instructions. + /// The existing tools to keep and add MCP tools to. + /// Agent User Id for the agent. + /// Turn context for the current request + /// User authorization information + /// Authentication Handler Name for use with the UserAuthorization System + /// Optional auth token to access the MCP servers. + /// A cancellation token to cancel the operation. + /// New Agent instance with all MCP tools, or agent with original tools if no new servers + Task AddToolServersToAgent( + IChatClient chatClient, + string agentInstructions, + IList initialTools, + string agentUserId, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken); + /// /// Returns a List of MCP tools to be added to the agent. /// @@ -58,6 +87,24 @@ Task> GetMcpToolsAsync( ITurnContext turnContext, string? authToken = null); + /// + /// Returns a List of MCP tools to be added to the agent. + /// + /// Agent User Id for the agent. + /// Turn context for the current request + /// User authorization information + /// Authentication Handler Name for use with the UserAuthorization System + /// Optional auth token to access the MCP servers. + /// A cancellation token to cancel the operation. + /// List of AI Tools be added to an agent. + Task> GetMcpToolsAsync( + string agentUserId, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken); + /// /// Sends chat history to the MCP platform. /// diff --git a/src/Tooling/Extensions/AgentFramework/Services/McpToolRegistrationService.cs b/src/Tooling/Extensions/AgentFramework/Services/McpToolRegistrationService.cs index 95f49767..4efc346c 100644 --- a/src/Tooling/Extensions/AgentFramework/Services/McpToolRegistrationService.cs +++ b/src/Tooling/Extensions/AgentFramework/Services/McpToolRegistrationService.cs @@ -49,7 +49,7 @@ public McpToolRegistrationService( } /// - public async Task AddToolServersToAgent( + public Task AddToolServersToAgent( IChatClient chatClient, string agentInstructions, IList initialTools, @@ -58,6 +58,19 @@ public async Task AddToolServersToAgent( string authHandlerName, ITurnContext turnContext, string? authToken = null) + => AddToolServersToAgent(chatClient, agentInstructions, initialTools, agentUserId, userAuthorization, authHandlerName, turnContext, authToken, CancellationToken.None); + + /// + public async Task AddToolServersToAgent( + IChatClient chatClient, + string agentInstructions, + IList initialTools, + string agentUserId, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken) { if (chatClient == null) { @@ -89,7 +102,7 @@ public async Task AddToolServersToAgent( ? new DevMcpTokenProvider(_configuration, _logger) : new AgenticMcpTokenProvider(userAuthorization, authHandlerName, turnContext, _configuration, _logger); - var (_, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentUserId, authToken, tokenProvider, turnContext, toolOptions).ConfigureAwait(false); + var (_, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentUserId, authToken, tokenProvider, turnContext, toolOptions, cancellationToken).ConfigureAwait(false); // Add all MCP tools from all servers foreach (var serverEntry in toolsByServer) @@ -117,12 +130,22 @@ public async Task AddToolServersToAgent( } /// - public async Task> GetMcpToolsAsync( + public Task> GetMcpToolsAsync( string agentUserId, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken = null) + => GetMcpToolsAsync(agentUserId, userAuthorization, authHandlerName, turnContext, authToken, CancellationToken.None); + + /// + public async Task> GetMcpToolsAsync( + string agentUserId, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken) { try { @@ -142,7 +165,7 @@ public async Task> GetMcpToolsAsync( : new AgenticMcpTokenProvider(userAuthorization, authHandlerName, turnContext, _configuration, _logger); var (_, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync( - agentUserId, authToken, tokenProvider, turnContext, toolOptions).ConfigureAwait(false); + agentUserId, authToken, tokenProvider, turnContext, toolOptions, cancellationToken).ConfigureAwait(false); IList mcpTools = toolsByServer.Values.SelectMany(t => t).ToList(); // Convert to AITool list diff --git a/src/Tooling/Extensions/AzureAIFoundry/Services/IMcpToolRegistrationService.cs b/src/Tooling/Extensions/AzureAIFoundry/Services/IMcpToolRegistrationService.cs index 2623c9a1..b26f77b6 100644 --- a/src/Tooling/Extensions/AzureAIFoundry/Services/IMcpToolRegistrationService.cs +++ b/src/Tooling/Extensions/AzureAIFoundry/Services/IMcpToolRegistrationService.cs @@ -33,6 +33,24 @@ Task AddToolServersToAgentAsync( ITurnContext turnContext, string? authToken = null); + /// + /// Loads/initializes configured MCP tool servers for the specified agent with full context. + /// This is the primary method that customers should use in orchestrators with full authentication context. + /// + /// The PersistentAgentsClient instance. + /// User authorization context. + /// Authentication Handler Name for use with the UserAuthorization System + /// Turn context for the conversation. + /// Optional auth token to access the MCP servers. + /// A cancellation token to cancel the operation. + Task AddToolServersToAgentAsync( + PersistentAgentsClient agentClient, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken); + /// /// Get MCP tool definitions and resources asynchronously. /// @@ -45,6 +63,20 @@ Task AddToolServersToAgentAsync( string authToken, ITurnContext turnContext); + /// + /// Get MCP tool definitions and resources asynchronously. + /// + /// Agent Instance Id for the agent. + /// Auth token to access the MCP servers. + /// Turn context for the conversation. + /// A cancellation token to cancel the operation. + /// A tuple containing the list of MCP tool definitions and tool resources. + Task<(IList ToolDefinitions, ToolResources? ToolResources)> GetMcpToolDefinitionsAndResourcesAsync( + string agentInstanceId, + string authToken, + ITurnContext turnContext, + CancellationToken cancellationToken); + /// /// Sends chat history to the MCP platform for real-time threat protection. /// Messages are provided directly by the caller as Azure AI Foundry messages. diff --git a/src/Tooling/Extensions/AzureAIFoundry/Services/McpToolRegistrationService.cs b/src/Tooling/Extensions/AzureAIFoundry/Services/McpToolRegistrationService.cs index 4d3ae2db..82d8120f 100644 --- a/src/Tooling/Extensions/AzureAIFoundry/Services/McpToolRegistrationService.cs +++ b/src/Tooling/Extensions/AzureAIFoundry/Services/McpToolRegistrationService.cs @@ -96,12 +96,22 @@ public void AddToolServersToAgent( } /// - public async Task AddToolServersToAgentAsync( + public Task AddToolServersToAgentAsync( PersistentAgentsClient agentClient, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken = null) + => AddToolServersToAgentAsync(agentClient, userAuthorization, authHandlerName, turnContext, authToken, CancellationToken.None); + + /// + public async Task AddToolServersToAgentAsync( + PersistentAgentsClient agentClient, + UserAuthorization userAuthorization, + string authHandlerName, + ITurnContext turnContext, + string? authToken, + CancellationToken cancellationToken) { if (agentClient == null) { @@ -120,7 +130,7 @@ public async Task AddToolServersToAgentAsync( { // Use V2-aware overload so per-audience tokens are applied to each server. var (toolDefinitions, toolResources) = await GetMcpToolDefinitionsAndResourcesAsync( - agenticAppId, authToken ?? string.Empty, turnContext, userAuthorization, authHandlerName).ConfigureAwait(false); + agenticAppId, authToken ?? string.Empty, turnContext, userAuthorization, authHandlerName, cancellationToken).ConfigureAwait(false); agentClient.Administration.UpdateAgent( agenticAppId, @@ -143,6 +153,14 @@ public async Task AddToolServersToAgentAsync( ITurnContext turnContext) => GetMcpToolDefinitionsAndResourcesAsync(agentInstanceId, authToken, turnContext, userAuthorization: null, authHandlerName: null); + /// + public Task<(IList ToolDefinitions, ToolResources? ToolResources)> GetMcpToolDefinitionsAndResourcesAsync( + string agentInstanceId, + string authToken, + ITurnContext turnContext, + CancellationToken cancellationToken) + => GetMcpToolDefinitionsAndResourcesAsync(agentInstanceId, authToken, turnContext, userAuthorization: null, authHandlerName: null, cancellationToken); + /// /// Get MCP tool definitions and resources, optionally using per-audience tokens for V2 servers. /// @@ -151,12 +169,30 @@ public async Task AddToolServersToAgentAsync( /// Turn context for the request. /// When provided together with , enables per-audience token acquisition for V2 servers. /// Auth handler name used with . - public async Task<(IList ToolDefinitions, ToolResources? ToolResources)> GetMcpToolDefinitionsAndResourcesAsync( + public Task<(IList ToolDefinitions, ToolResources? ToolResources)> GetMcpToolDefinitionsAndResourcesAsync( string agentInstanceId, string authToken, ITurnContext turnContext, UserAuthorization? userAuthorization, string? authHandlerName) + => GetMcpToolDefinitionsAndResourcesAsync(agentInstanceId, authToken, turnContext, userAuthorization, authHandlerName, CancellationToken.None); + + /// + /// Get MCP tool definitions and resources, optionally using per-audience tokens for V2 servers. + /// + /// Agent instance ID. + /// Shared auth token (V1 fallback). + /// Turn context for the request. + /// When provided together with , enables per-audience token acquisition for V2 servers. + /// Auth handler name used with . + /// A cancellation token to cancel the operation. + public async Task<(IList ToolDefinitions, ToolResources? ToolResources)> GetMcpToolDefinitionsAndResourcesAsync( + string agentInstanceId, + string authToken, + ITurnContext turnContext, + UserAuthorization? userAuthorization, + string? authHandlerName, + CancellationToken cancellationToken) { // TODO: Make this method private // Tool resources should ideally be accessible via agentClient after AddToolServersToAgent. @@ -178,7 +214,7 @@ public async Task AddToolServersToAgentAsync( if (ToolingUtility.IsDevScenario(_configuration)) { IMcpTokenProvider tokenProvider = new DevMcpTokenProvider(_configuration, _logger); - (servers, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentInstanceId, authToken, tokenProvider, turnContext, toolOptions).ConfigureAwait(false); + (servers, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentInstanceId, authToken, tokenProvider, turnContext, toolOptions, cancellationToken).ConfigureAwait(false); } else if (userAuthorization is not null && authHandlerName is not null) { @@ -186,7 +222,7 @@ public async Task AddToolServersToAgentAsync( IMcpTokenProvider tokenProvider = new AgenticMcpTokenProvider( userAuthorization, authHandlerName, turnContext, _configuration, _logger); - (servers, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentInstanceId, authToken, tokenProvider, turnContext, toolOptions).ConfigureAwait(false); + (servers, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agentInstanceId, authToken, tokenProvider, turnContext, toolOptions, cancellationToken).ConfigureAwait(false); } else { @@ -195,7 +231,8 @@ public async Task AddToolServersToAgentAsync( agentInstanceId, authToken, turnContext, - toolOptions).ConfigureAwait(false); + toolOptions, + cancellationToken).ConfigureAwait(false); } if (servers.Count == 0) diff --git a/src/Tooling/Extensions/SemanticKernel/Services/IMcpToolRegistrationService.cs b/src/Tooling/Extensions/SemanticKernel/Services/IMcpToolRegistrationService.cs index d31aa8ac..0f969e45 100644 --- a/src/Tooling/Extensions/SemanticKernel/Services/IMcpToolRegistrationService.cs +++ b/src/Tooling/Extensions/SemanticKernel/Services/IMcpToolRegistrationService.cs @@ -25,10 +25,23 @@ public interface IMcpToolRegistrationService /// Authentication Handler Name for use with the UserAuthorization System /// /// Auth token to access the MCP servers - /// Returns a new object of the kernel + /// A task that completes when the MCP tool servers have been added to the provided kernel. /// Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken = null); + /// + /// Adds the A365 MCP Tool Servers + /// + /// The kernel to which the tools will be added. + /// Agents SDK UserAuthorization System + /// Authentication Handler Name for use with the UserAuthorization System + /// + /// Auth token to access the MCP servers + /// A cancellation token to cancel the operation. + /// A task that completes when the MCP tool servers have been added to the provided kernel. + /// + Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken, CancellationToken cancellationToken); + /// /// Sends chat history to the MCP platform for real-time threat protection. /// diff --git a/src/Tooling/Extensions/SemanticKernel/Services/McpToolRegistrationService.cs b/src/Tooling/Extensions/SemanticKernel/Services/McpToolRegistrationService.cs index 5253be99..a41ee0d0 100644 --- a/src/Tooling/Extensions/SemanticKernel/Services/McpToolRegistrationService.cs +++ b/src/Tooling/Extensions/SemanticKernel/Services/McpToolRegistrationService.cs @@ -51,7 +51,11 @@ public McpToolRegistrationService( } /// - public async Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken = null) + public Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken = null) + => AddToolServersToAgentAsync(kernel, userAuthorization, authHandlerName, turnContext, authToken, CancellationToken.None); + + /// + public async Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization userAuthorization, string authHandlerName, ITurnContext turnContext, string? authToken, CancellationToken cancellationToken) { if (kernel == null) { @@ -78,7 +82,7 @@ public async Task AddToolServersToAgentAsync(Kernel kernel, UserAuthorization us ? new DevMcpTokenProvider(_configuration, _logger) : new AgenticMcpTokenProvider(userAuthorization, authHandlerName, turnContext, _configuration, _logger); - var (_, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agenticAppId, authToken, tokenProvider, turnContext, toolOptions).ConfigureAwait(false); + var (_, toolsByServer) = await _mcpServerConfigurationService.EnumerateToolsFromServersAsync(agenticAppId, authToken, tokenProvider, turnContext, toolOptions, cancellationToken).ConfigureAwait(false); foreach (var serverEntry in toolsByServer) {