diff --git a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/AgentMcpSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/AgentMcpSkillsSource.cs
index 123c4100b40..d69b798c7d8 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/AgentMcpSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/AgentMcpSkillsSource.cs
@@ -78,7 +78,7 @@ public AgentMcpSkillsSource(McpClient client, AgentMcpSkillsSourceOptions? optio
}
///
- public override async Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override async Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
if (this.TryGetCachedSkills() is { } cached)
{
@@ -99,7 +99,7 @@ public override async Task> GetSkillsAsync(CancellationToken c
{
// The refresh owner uses CancellationToken.None so that a single caller's cancellation
// does not abort the shared refresh for all concurrent waiters.
- var skills = await this.GetCoreSkillsAsync(CancellationToken.None).ConfigureAwait(false);
+ var skills = await this.GetCoreSkillsAsync(context, CancellationToken.None).ConfigureAwait(false);
this.UpdateCache(skills);
@@ -155,7 +155,7 @@ private void UpdateCache(IList skills)
/// Reads the skill index from the MCP server, dispatches entries to registered loaders, and
/// returns the aggregated skill list.
///
- private async Task> GetCoreSkillsAsync(CancellationToken cancellationToken)
+ private async Task> GetCoreSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken)
{
McpSkillIndex? index = await this.TryReadIndexAsync(cancellationToken).ConfigureAwait(false);
@@ -185,7 +185,7 @@ private async Task> GetCoreSkillsAsync(CancellationToken cance
foreach (var loader in this._loaders.Values)
{
var entries = entriesByType.TryGetValue(loader.EntryType, out List? matched) ? matched : [];
- skills.AddRange(await loader.LoadAsync(entries, cancellationToken).ConfigureAwait(false));
+ skills.AddRange(await loader.LoadAsync(entries, context, cancellationToken).ConfigureAwait(false));
}
LogSkillsLoadedTotal(this._logger, skills.Count);
diff --git a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/ArchiveEntryLoader.cs b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/ArchiveEntryLoader.cs
index b483eefa4f2..4617e0bd5a1 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/ArchiveEntryLoader.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/ArchiveEntryLoader.cs
@@ -50,7 +50,7 @@ public ArchiveEntryLoader(McpClient client, AgentMcpSkillsSourceOptions? options
public string EntryType => "archive";
///
- public async Task> LoadAsync(IReadOnlyList entries, CancellationToken cancellationToken)
+ public async Task> LoadAsync(IReadOnlyList entries, AgentSkillsSourceContext context, CancellationToken cancellationToken)
{
// Filter out entries that are missing required fields or have invalid names.
var archiveEntries = this.FilterValidEntries(entries);
@@ -81,7 +81,7 @@ public async Task> LoadAsync(IReadOnlyList
// Delegate discovery of extracted content to a file-based skills source.
AgentFileSkillsSource fileSource = this.CreateFileSkillsSource(skillDirectories);
- return await fileSource.GetSkillsAsync(cancellationToken).ConfigureAwait(false);
+ return await fileSource.GetSkillsAsync(context, cancellationToken).ConfigureAwait(false);
}
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/IMcpSkillEntryLoader.cs b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/IMcpSkillEntryLoader.cs
index 34b678165f5..358ad63623f 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/IMcpSkillEntryLoader.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/IMcpSkillEntryLoader.cs
@@ -30,6 +30,7 @@ internal interface IMcpSkillEntryLoader
/// loaded successfully.
///
/// The index entries of this loader's . May be empty.
+ /// The skills source context for the current invocation.
/// A token to cancel the operation.
- Task> LoadAsync(IReadOnlyList entries, CancellationToken cancellationToken);
+ Task> LoadAsync(IReadOnlyList entries, AgentSkillsSourceContext context, CancellationToken cancellationToken);
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/SkillMdEntryLoader.cs b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/SkillMdEntryLoader.cs
index bf910478485..fa8ff02bd9d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/SkillMdEntryLoader.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mcp/Skills/Loaders/SkillMdEntryLoader.cs
@@ -29,7 +29,7 @@ public SkillMdEntryLoader(McpClient client, ILoggerFactory loggerFactory)
public string EntryType => "skill-md";
///
- public Task> LoadAsync(IReadOnlyList entries, CancellationToken cancellationToken)
+ public Task> LoadAsync(IReadOnlyList entries, AgentSkillsSourceContext context, CancellationToken cancellationToken)
{
var skills = new List();
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentInMemorySkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentInMemorySkillsSource.cs
index 57c9295c249..b4c71cf0b3c 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentInMemorySkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentInMemorySkillsSource.cs
@@ -28,7 +28,7 @@ public AgentInMemorySkillsSource(IEnumerable skills)
}
///
- public override Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
return Task.FromResult>(this._skills);
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
index 5e8d07cc428..f4841a9cc7d 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
@@ -232,7 +232,8 @@ protected override async ValueTask ProvideAIContextAsync(InvokingCont
private async Task CreateContextAsync(InvokingContext context, CancellationToken cancellationToken)
{
- var skills = await this._source.GetSkillsAsync(cancellationToken).ConfigureAwait(false);
+ var skillsContext = new AgentSkillsSourceContext(context.Agent);
+ var skills = await this._source.GetSkillsAsync(skillsContext, cancellationToken).ConfigureAwait(false);
if (skills is not { Count: > 0 })
{
return await base.ProvideAIContextAsync(context, cancellationToken).ConfigureAwait(false);
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSource.cs
index 6a72d0c01a9..ff51ce3cd8d 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSource.cs
@@ -18,7 +18,8 @@ public abstract class AgentSkillsSource
///
/// Gets the skills provided by this source.
///
+ /// The context for the current invocation, exposing the invoking .
/// Cancellation token.
/// A collection of skills from this source.
- public abstract Task> GetSkillsAsync(CancellationToken cancellationToken = default);
+ public abstract Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default);
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSourceContext.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSourceContext.cs
new file mode 100644
index 00000000000..7baacd2c144
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsSourceContext.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Shared.DiagnosticIds;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides context about the current invocation to .
+///
+///
+/// This context is created internally by and passed through the
+/// source pipeline, allowing skill sources to make context-aware decisions such as filtering skills
+/// per agent.
+///
+[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+public sealed class AgentSkillsSourceContext
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The agent that is invoking the skill source.
+ /// is .
+ [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
+ public AgentSkillsSourceContext(AIAgent agent)
+ {
+ this.Agent = Throw.IfNull(agent);
+ }
+
+ ///
+ /// Gets the agent that is invoking the skill source.
+ ///
+ public AIAgent Agent { get; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AggregatingAgentSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AggregatingAgentSkillsSource.cs
index 7dc468742fd..8173b65276b 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AggregatingAgentSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AggregatingAgentSkillsSource.cs
@@ -31,12 +31,12 @@ public AggregatingAgentSkillsSource(IEnumerable sources)
}
///
- public override async Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override async Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
var allSkills = new List();
foreach (var source in this._sources)
{
- var skills = await source.GetSkillsAsync(cancellationToken).ConfigureAwait(false);
+ var skills = await source.GetSkillsAsync(context, cancellationToken).ConfigureAwait(false);
allSkills.AddRange(skills);
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DeduplicatingAgentSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DeduplicatingAgentSkillsSource.cs
index bf943daae55..269b03ec1ff 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DeduplicatingAgentSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DeduplicatingAgentSkillsSource.cs
@@ -31,9 +31,9 @@ public DeduplicatingAgentSkillsSource(AgentSkillsSource innerSource, ILoggerFact
}
///
- public override async Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override async Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
- var allSkills = await this.InnerSource.GetSkillsAsync(cancellationToken).ConfigureAwait(false);
+ var allSkills = await this.InnerSource.GetSkillsAsync(context, cancellationToken).ConfigureAwait(false);
var deduplicated = new List();
var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DelegatingAgentSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DelegatingAgentSkillsSource.cs
index 920ad0428b3..9c1f06694a4 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DelegatingAgentSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/DelegatingAgentSkillsSource.cs
@@ -36,6 +36,6 @@ protected DelegatingAgentSkillsSource(AgentSkillsSource innerSource)
protected AgentSkillsSource InnerSource { get; }
///
- public override Task> GetSkillsAsync(CancellationToken cancellationToken = default)
- => this.InnerSource.GetSkillsAsync(cancellationToken);
+ public override Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
+ => this.InnerSource.GetSkillsAsync(context, cancellationToken);
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/FilteringAgentSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/FilteringAgentSkillsSource.cs
index 2bd26acce26..73c95b635db 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/FilteringAgentSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/Decorators/FilteringAgentSkillsSource.cs
@@ -45,9 +45,9 @@ public FilteringAgentSkillsSource(
}
///
- public override async Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override async Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
- var allSkills = await this.InnerSource.GetSkillsAsync(cancellationToken).ConfigureAwait(false);
+ var allSkills = await this.InnerSource.GetSkillsAsync(context, cancellationToken).ConfigureAwait(false);
var filtered = new List();
foreach (var skill in allSkills)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs b/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs
index 0fc3748983a..9b7770908eb 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs
@@ -114,7 +114,7 @@ public AgentFileSkillsSource(
}
///
- public override Task> GetSkillsAsync(CancellationToken cancellationToken = default)
+ public override Task> GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)
{
var discoveredPaths = DiscoverSkillDirectories(this._skillPaths);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceArchiveTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceArchiveTests.cs
index f3894df8288..2f500e2111d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceArchiveTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceArchiveTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
@@ -56,6 +56,8 @@ Content B.
private readonly string _extractionRoot =
Path.Combine(Path.GetTempPath(), "af-mcp-archive-tests", Guid.NewGuid().ToString("N"));
+ private readonly AgentSkillsSourceContext _context = new(new TestAIAgent());
+
private const int ManyFileArchiveFileCount = 60;
[Fact]
@@ -68,7 +70,7 @@ public async Task GetSkillsAsync_ZipArchive_DiscoversSkillAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
var skill = Assert.Single(skills);
@@ -87,7 +89,7 @@ public async Task GetSkillsAsync_TarGzArchive_DiscoversSkillAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
var skill = Assert.Single(skills);
@@ -110,7 +112,7 @@ public async Task GetSkillsAsync_ArchiveWithScript_SurfacesScriptAsReadableResou
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
var resource = await skill.GetResourceAsync("scripts/run.py");
// Assert - the .py file is readable as a resource (not an executable script).
@@ -133,8 +135,8 @@ public async Task GetSkillsAsync_TwoSourcesWithSeparateDirectories_DoNotCollideA
var sourceB = new AgentMcpSkillsSource(clientB, new() { ArchiveSkillsDirectory = Path.Combine(this._extractionRoot, "b") });
// Act
- var skillA = Assert.Single(await sourceA.GetSkillsAsync());
- var skillB = Assert.Single(await sourceB.GetSkillsAsync());
+ var skillA = Assert.Single(await sourceA.GetSkillsAsync(this._context));
+ var skillB = Assert.Single(await sourceB.GetSkillsAsync(this._context));
// Assert - each source kept its own content despite the shared skill name.
Assert.Equal("shared-skill", skillA.Frontmatter.Name);
@@ -157,7 +159,7 @@ public async Task GetSkillsAsync_FirstRun_PrunesLeftoverSkillDirectoryAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - the leftover directory is pruned and only the advertised skill remains.
Assert.False(Directory.Exists(staleDir));
@@ -172,7 +174,7 @@ public async Task GetSkillsAsync_SkillNoLongerAdvertised_IsPrunedAsync()
await using var fullServer = new InMemoryMcpServer(builder => builder.WithResources());
await using var fullClient = await fullServer.CreateClientAsync();
var firstSource = new AgentMcpSkillsSource(fullClient, new() { ArchiveSkillsDirectory = this._extractionRoot });
- var firstSkills = await firstSource.GetSkillsAsync();
+ var firstSkills = await firstSource.GetSkillsAsync(this._context);
Assert.Equal(2, firstSkills.Count);
Assert.True(Directory.Exists(Path.Combine(this._extractionRoot, "skill-b")));
@@ -180,7 +182,7 @@ public async Task GetSkillsAsync_SkillNoLongerAdvertised_IsPrunedAsync()
await using var partialServer = new InMemoryMcpServer(builder => builder.WithResources());
await using var partialClient = await partialServer.CreateClientAsync();
var secondSource = new AgentMcpSkillsSource(partialClient, new() { ArchiveSkillsDirectory = this._extractionRoot });
- var secondSkills = await secondSource.GetSkillsAsync();
+ var secondSkills = await secondSource.GetSkillsAsync(this._context);
// Assert - skill-b's directory was pruned; only skill-a remains.
var skill = Assert.Single(secondSkills);
@@ -203,7 +205,7 @@ public async Task GetSkillsAsync_ServerListsNoArchives_PrunesLeftoversAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - the leftover directory is pruned and no skills are returned.
Assert.Empty(skills);
@@ -218,14 +220,14 @@ public async Task GetSkillsAsync_SecondDiscovery_ReExtractsContentAsync()
await using (var clientA = await serverA.CreateClientAsync())
{
var firstSource = new AgentMcpSkillsSource(clientA, new() { ArchiveSkillsDirectory = this._extractionRoot });
- Assert.Single(await firstSource.GetSkillsAsync());
+ Assert.Single(await firstSource.GetSkillsAsync(this._context));
}
// Act - a second run over the same directory re-extracts server B's content.
await using var serverB = new InMemoryMcpServer(builder => builder.WithResources());
await using var clientB = await serverB.CreateClientAsync();
var source = new AgentMcpSkillsSource(clientB, new() { ArchiveSkillsDirectory = this._extractionRoot });
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
// Assert - the content was replaced with server B's.
Assert.Contains("Content from server B.", await skill.GetContentAsync());
@@ -258,7 +260,7 @@ Stale content.
var source = new AgentMcpSkillsSource(client, new() { ArchiveSkillsDirectory = this._extractionRoot });
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - failed cleanup prevents the stale directory from being proxied to AgentFileSkillsSource.
Assert.Empty(skills);
@@ -412,7 +414,7 @@ public async Task GetSkillsAsync_ArchiveExceedsDefaultFileCount_SkillSkippedAsyn
var source = new AgentMcpSkillsSource(client, new() { ArchiveSkillsDirectory = this._extractionRoot });
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -432,7 +434,7 @@ public async Task GetSkillsAsync_ArchiveExceedsConfiguredArchiveSize_SkillSkippe
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -452,7 +454,7 @@ public async Task GetSkillsAsync_RaisedFileCountOption_LoadsLargerArchiveAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
// Assert
Assert.Equal("archived-skill", skill.Frontmatter.Name);
@@ -468,7 +470,7 @@ public async Task GetSkillsAsync_EntryMissingName_SkillSkippedAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - the invalid entry is skipped; no skills are surfaced.
Assert.Empty(skills);
@@ -484,7 +486,7 @@ public async Task GetSkillsAsync_EntryWithInvalidNameChars_SkillSkippedAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -500,7 +502,7 @@ public async Task GetSkillsAsync_EntryMissingUrl_SkillSkippedAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -517,7 +519,7 @@ public async Task GetSkillsAsync_ArchiveWithUnsupportedFormat_SkillSkippedAsync(
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -533,7 +535,7 @@ public async Task GetSkillsAsync_ArchiveReturnsTextNotBlob_SkillSkippedAsync()
var source = new AgentMcpSkillsSource(client, options);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -837,3 +839,4 @@ private sealed class TextOnlyArchiveServer
#endregion
}
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceTests.cs
index 891f2a726b4..672ba11e962 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/Skills/AgentMcpSkillsSourceTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
@@ -13,6 +13,7 @@ namespace Microsoft.Agents.AI.Skills.Mcp.UnitTests;
///
public sealed class AgentMcpSkillsSourceTests
{
+ private readonly AgentSkillsSourceContext _context = new(new TestAIAgent());
private const string SampleSkillMd = """
---
name: unit-converter
@@ -47,7 +48,7 @@ public async Task GetSkillsAsync_IndexBasedDiscovery_ReturnsSkillAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - frontmatter comes from index; Content is the actual SKILL.md body from the server.
var skill = Assert.Single(skills);
@@ -71,7 +72,7 @@ public async Task GetSkillsAsync_NoIndex_ReturnsEmptyAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -88,7 +89,7 @@ public async Task GetResourceAsync_SiblingText_ReturnsContentAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
var resource = await skill.GetResourceAsync("references/checklist.md");
// Assert
@@ -107,7 +108,7 @@ public async Task GetResourceAsync_SiblingBinary_ReturnsDataContentAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
var resource = await skill.GetResourceAsync("assets/icon.bin");
// Assert
@@ -130,7 +131,7 @@ public async Task GetResourceAsync_UnknownName_ReturnsNullAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
var resource = await skill.GetResourceAsync("references/does-not-exist.md");
// Assert - resource does not exist on the server, so null is returned
@@ -151,7 +152,7 @@ public async Task GetResourceAsync_PathTraversalName_ReturnsNullAsync(string nam
var source = new AgentMcpSkillsSource(client);
// Act
- var skill = Assert.Single(await source.GetSkillsAsync());
+ var skill = Assert.Single(await source.GetSkillsAsync(this._context));
var resource = await skill.GetResourceAsync(name);
// Assert - resource does not exist on the server, so null is returned
@@ -169,7 +170,7 @@ public async Task GetSkillsAsync_DoesNotReadSkillMdAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert - discovery succeeds from index alone.
var skill = Assert.Single(skills);
@@ -186,7 +187,7 @@ public async Task GetSkillsAsync_IndexEntryWithInvalidName_IsSkippedAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -202,7 +203,7 @@ public async Task GetSkillsAsync_IndexEntryWithMissingRequiredFields_IsSkippedAs
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -219,7 +220,7 @@ public async Task GetSkillsAsync_ArchiveEntryWithUnreadableResource_IsSkippedAsy
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -236,7 +237,7 @@ public async Task GetSkillsAsync_IndexEntryWithTemplateType_IsSkippedAsync()
var source = new AgentMcpSkillsSource(client);
// Act
- var skills = await source.GetSkillsAsync();
+ var skills = await source.GetSkillsAsync(this._context);
// Assert
Assert.Empty(skills);
@@ -391,3 +392,4 @@ public static string Index() => """
#endregion
}
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TestAIAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TestAIAgent.cs
new file mode 100644
index 00000000000..c993626af51
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Mcp.UnitTests/TestAIAgent.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Skills.Mcp.UnitTests;
+
+///
+/// A minimal implementation for unit tests.
+///
+internal sealed class TestAIAgent : AIAgent
+{
+ protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+
+ protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+
+ protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+
+ protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session, AgentRunOptions? options, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentSession? session, AgentRunOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ await Task.CompletedTask;
+ yield break;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs
index 5fd1fa77bab..cbc67bb0f8e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentClassSkillTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
@@ -86,7 +86,7 @@ public async Task AgentInMemorySkillsSource_ReturnsAllSkillsAsync()
var source = new AgentInMemorySkillsSource(skills);
// Act
- var result = await source.GetSkillsAsync(CancellationToken.None);
+ var result = await source.GetSkillsAsync(new AgentSkillsSourceContext(new TestAIAgent()), CancellationToken.None);
// Assert
Assert.Equal(2, result.Count);
@@ -1138,3 +1138,4 @@ private sealed class DuplicateScriptsSkill : AgentClassSkill Task.FromResult