fix: propagate CancellationToken through async MCP tooling and observability call chains#224
fix: propagate CancellationToken through async MCP tooling and observability call chains#224Copilot wants to merge 10 commits into
Conversation
Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Agent365-dotnet/sessions/2bd402aa-e691-40be-8866-7ab931a2f87f
…ability call chains (fixes #223) Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Agent365-dotnet/sessions/2bd402aa-e691-40be-8866-7ab931a2f87f
…null' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
…null' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
… IDisposable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…ect' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
|
@copilot fix the CI pipeline |
…broke CI build Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Agent365-dotnet/sessions/7825aa1c-1b48-439e-9e3b-2500cba9fd81
Fixed in 9802b82. Three Copilot Autofix commits ( |
There was a problem hiding this comment.
Pull request overview
Propagates CancellationToken through the MCP tooling registration/enumeration flow and the observability exporter token-resolution flow to fix issue #223 (CancellationTokenSource has been disposed) and improve end-to-end cancellation behavior across async call chains.
Changes:
- Add
CancellationToken(with defaults) to key MCP server/configuration and tool registration async methods, and pass it through to HTTP + MCP client calls. - Update observability token cache + exporter token resolver delegate/signatures to accept and propagate
CancellationToken. - Update unit/integration tests and Moq setups to match the new signatures.
Reviewed changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Tooling/Extensions/SemanticKernel/Services/McpToolRegistrationService.cs | Passes CancellationToken into MCP tool enumeration from Semantic Kernel integration. |
| src/Tooling/Extensions/SemanticKernel/Services/IMcpToolRegistrationService.cs | Adds CancellationToken parameter to public Semantic Kernel MCP registration API. |
| src/Tooling/Extensions/AzureAIFoundry/Services/McpToolRegistrationService.cs | Propagates CancellationToken and removes blocking .GetAwaiter().GetResult() usage. |
| src/Tooling/Extensions/AzureAIFoundry/Services/IMcpToolRegistrationService.cs | Adds CancellationToken to Azure AI Foundry MCP registration APIs. |
| src/Tooling/Extensions/AgentFramework/Services/McpToolRegistrationService.cs | Propagates CancellationToken through Agent Framework MCP tool registration APIs. |
| src/Tooling/Extensions/AgentFramework/Services/IMcpToolRegistrationService.cs | Adds CancellationToken parameters to public Agent Framework MCP registration API. |
| src/Tooling/Core/Services/McpToolServerConfigurationService.cs | Threads CancellationToken through server listing, gateway fetch, MCP client creation, and tool listing. |
| src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs | Threads CancellationToken through parallel tool enumeration across servers. |
| src/Tooling/Core/Services/IMcpToolServerConfigurationService.cs | Adds CancellationToken parameters to public MCP server configuration interface. |
| src/Tests/Microsoft.Agents.A365.Tooling.Extensions.SemanticKernel.Tests/Services/McpToolRegistrationServiceTests.cs | Updates Moq setups/verifications for new CancellationToken parameter. |
| src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AzureAIFoundry.Tests/McpToolRegistrationServiceTests.cs | Updates Moq setups/verifications for new CancellationToken parameter. |
| src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/McpToolRegistrationServiceTestBase.cs | Updates common test mock wiring for CancellationToken propagation. |
| src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/GetMcpToolsAsync_Tests.cs | Updates verification to include CancellationToken. |
| src/Tests/Microsoft.Agents.A365.Tooling.Extensions.AgentFramework.Tests/Services/McpToolRegistrationServiceTests/AddToolServersToAgent_Tests.cs | Updates verification to include CancellationToken. |
| src/Tests/Microsoft.Agents.A365.Tooling.Core.Tests/McpToolServerConfigurationService_ToolEnumerationTests.cs | Updates mocks for new CancellationToken parameters in server/tool enumeration. |
| src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/ObservabilityBuilderExtensionsTests.cs | Updates exporter option TokenResolver lambdas to new delegate signature. |
| src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/Agent365ExporterTests.cs | Updates token resolver setups/callbacks to accept CancellationToken. |
| src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/BuilderTests.cs | Updates token resolver lambdas for builder tests. |
| src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterE2ETests.cs | Updates token resolver lambda to new signature. |
| src/Tests/Microsoft.Agents.A365.Observability.Runtime.IntegrationTests/Agent365ExporterAsyncE2ETests.cs | Refactors integration test and updates token resolver lambda to new signature. |
| src/Tests/Microsoft.Agents.A365.Observability.Hosting.Tests/ObservabilityBuilderExtensionsTests.cs | Updates token resolver lambdas to new signature. |
| src/Tests/Microsoft.Agents.A365.Observability.Hosting.Tests/Extensions/ObservabilityServiceCollectionExtensionsTests.cs | Updates token resolver invocation to include CancellationToken. |
| src/Observability/Runtime/Tracing/Exporters/Agent365ExporterOptions.cs | Updates AsyncAuthTokenResolver delegate to accept CancellationToken. |
| src/Observability/Runtime/Tracing/Exporters/Agent365ExporterCore.cs | Updates core export path to pass CancellationToken into token resolver. |
| src/Observability/Runtime/Tracing/Exporters/Agent365ExporterAsync.cs | Wires new token resolver signature and passes CancellationToken through. |
| src/Observability/Runtime/Tracing/Exporters/Agent365Exporter.cs | Updates sync exporter’s resolver adapter to new signature. |
| src/Observability/Hosting/ObservabilityServiceCollectionExtensions.cs | Updates DI registration to produce new 3-arg token resolver delegate. |
| src/Observability/Hosting/Caching/ServiceTokenCache.cs | Adds CancellationToken parameter and simplifies async anti-pattern. |
| src/Observability/Hosting/Caching/IExporterTokenCache.cs | Adds CancellationToken parameter to token cache interface method. |
| src/Observability/Hosting/Caching/AgenticTokenCache.cs | Adds CancellationToken parameter to agentic token cache interface implementation. |
|
@copilot apply changes based on the comments in this thread |
…estCleanup disposal, XML doc fix Agent-Logs-Url: https://github.com/microsoft/Agent365-dotnet/sessions/bf4ab31c-8d71-4206-bb8c-d96357a4d305 Co-authored-by: rido-min <14916339+rido-min@users.noreply.github.com>
Applied all 8 reviewer suggestions in ef5e191:
Build: 0 errors/warnings. All 628 tests pass. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot Fix merge conflicts. If cancellationToken can be optional, make it optional. Make sure that adding cancellationToken does not constitute a breaking change - if that is not possible, explain why not. |
Done in 781b7be.
Is this a breaking change? For source callers, no — every existing call site that omitted the token still compiles unchanged. There are two narrow caveats, both fully contained within this repo and addressed in this PR:
So within this repo it is non-breaking for callers and follows the established |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (1)
src/Tooling/Core/Services/McpToolServerConfigurationService.ToolEnumeration.cs:57
- CancellationToken is currently swallowed when listing tool servers: the catch-all
catch (Exception ex)will also catchOperationCanceledException/TaskCanceledExceptioncaused by the provided token and convert cancellation into an empty result. This defeats the goal of end-to-end cancellation propagation (and can log false errors). Add a cancellation-specific catch that rethrows whencancellationToken.IsCancellationRequested.
catch (Exception ex)
{
_logger.LogError(ex, "Failed to list MCP tool servers for AgentInstanceId={AgentInstanceId}", agentInstanceId);
return (new List<MCPServerConfig>(), toolsByServer);
}
| turnContext, | ||
| server, | ||
| authToken, | ||
| toolOptions).ConfigureAwait(false); | ||
| toolOptions, | ||
| cancellationToken).ConfigureAwait(false); | ||
|
|
||
| _logger.LogInformation( | ||
| "Successfully loaded {ToolCount} tools from MCP server '{ServerName}'", |
| servers = await ListToolServersAsync( | ||
| agentInstanceId, | ||
| authToken, | ||
| toolOptions).ConfigureAwait(false); | ||
| toolOptions, | ||
| cancellationToken).ConfigureAwait(false); | ||
| } | ||
| catch (Exception ex) | ||
| { |
| turnContext, | ||
| server, | ||
| authToken, | ||
| toolOptions).ConfigureAwait(false); | ||
| toolOptions, | ||
| cancellationToken).ConfigureAwait(false); | ||
|
|
||
| _logger.LogInformation( | ||
| "Successfully loaded {ToolCount} tools from MCP server '{ServerName}'", |
| var mcpClient = await CreateMcpClientWithAuthHandlers(turnContext, new Uri(mCPServerConfig.url), effectiveToken, toolOptions, cancellationToken); | ||
| var tools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken); |
| /// <inheritdoc/> | ||
| public Task<string?> GetObservabilityToken(string agentId, string tenantId, CancellationToken cancellationToken) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(agentId) || string.IsNullOrWhiteSpace(tenantId)) | ||
| return null; | ||
| return Task.FromResult<string?>(null); | ||
|
|
| /// <inheritdoc/> | ||
| public async Task<string?> GetObservabilityToken(string agentId, string tenantId, CancellationToken cancellationToken) | ||
| { | ||
| if (!_map.TryGetValue($"{agentId}:{tenantId}", out var entry)) | ||
| return null; |
| Task<List<MCPServerConfig>> ListToolServersAsync(string agentInstanceId, string authToken); | ||
|
|
||
| /// <summary> | ||
| /// Gets the list of MCP Servers that are configured for the agent. | ||
| /// </summary> | ||
| /// <param name="agentInstanceId">Agent instance Id for the agent.</param> | ||
| /// <param name="authToken">Auth token to access the MCP servers</param> | ||
| /// <param name="cancellationToken">A cancellation token to cancel the operation.</param> | ||
| /// <returns>Returns the list of MCP Servers that are configured.</returns> | ||
| Task<List<MCPServerConfig>> ListToolServersAsync(string agentInstanceId, string authToken, CancellationToken cancellationToken); |
Propagates
CancellationTokenthrough the MCP tooling registration/enumeration flow to fix theCancellationTokenSource has been disposedfailure and improve end-to-end cancellation behavior across async call chains.Changes Made
.Subjectanti-pattern introduced by Copilot Autofix back to the original null-forgiving pattern.ServiceProvideris now disposed beforeHttpClientinAgent365ExporterAsyncE2ETests.<returns>text on SKIMcpToolRegistrationService.AddToolServersToAgentAsync(was "Returns a new object of the kernel").CancellationTokenoverloads — instead of adding an optionalCancellationToken cancellationToken = defaultparameter to existing public methods (which is binary-breaking), the original signatures are kept unchanged and a newCancellationToken-bearing overload is added alongside each one. The original no-CT overload forwards to the CT overload withCancellationToken.None. This applies to:IExporterTokenCache.GetObservabilityToken(+AgenticTokenCache,ServiceTokenCache)IMcpToolServerConfigurationService(ListToolServersAsync×2,GetMcpClientToolsAsync,EnumerateToolsFromServersAsync×2,EnumerateAllToolsAsync) + implementationIMcpToolRegistrationService+ implementations (including AzureAIFoundry's public 5-argGetMcpToolDefinitionsAndResourcesAsync)AsyncAuthTokenResolverdelegate reverted tomain— a delegate type cannot be overloaded, so to avoid a binary break the delegate and the entire exporter token-resolution chain (Agent365ExporterOptions,Agent365ExporterCore,Agent365Exporter,Agent365ExporterAsync,ObservabilityServiceCollectionExtensions) were reverted to their originalmainsignatures. Cancellation still propagates through the exporter's HTTPSendAsyncpath.main— resolved all conflicts, reconciling main's newContextualTokenResolvertoken-precedence logic andIMcpTokenProviderper-audience tokens with this PR'sCancellationTokenpropagation. The result is a proper two-parent merge commit.Breaking-change note
All original public signatures are preserved verbatim, so the change is both source- and binary-compatible for existing callers and pre-compiled consumers — the new behavior is opt-in via the added overloads. The single API that could not be made binary-compatible via overloading was the
AsyncAuthTokenResolverdelegate (delegates cannot be overloaded); it was reverted to its originalmainsignature rather than altered.Testing
mainpublic signatures are preserved verbatim (binary compatible)📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.