Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Microsoft 365 Agents SDK for .NET. Build multichannel agents for Teams/M365/Copilot Studio with ASP.NET Core hosting, AgentApplication routing, and MSAL-based auth.
.claude/skills/m365-agents-dotnet/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
Build enterprise agents for Microsoft 365, Teams, and Copilot Studio using the Microsoft.Agents SDK with ASP.NET Core hosting, agent routing, and MSAL-based authentication.
bashdotnet add package Microsoft.Agents.Hosting.AspNetCore dotnet add package Microsoft.Agents.Authentication.Msal dotnet add package Microsoft.Agents.Storage dotnet add package Microsoft.Agents.CopilotStudio.Client dotnet add package Microsoft.Identity.Client.Extensions.Msal
json{ "TokenValidation": { "Enabled": true, "Audiences": [ "{{ClientId}}" ], "TenantId": "{{TenantId}}" }, "AgentApplication": { "StartTypingTimer": false, "RemoveRecipientMention": false, "NormalizeMentions": false }, "Connections": { "ServiceConnection": { "Settings": { "AuthType": "ClientSecret", "ClientId": "{{ClientId}}", "ClientSecret": "{{ClientSecret}}", "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", "Scopes": [ "https://api.botframework.com/.default" ] } } }, "ConnectionsMap": [ { "ServiceUrl": "*", "Connection": "ServiceConnection" } ], "CopilotStudioClientSettings": { "DirectConnectUrl": "", "EnvironmentId": "", "SchemaName": "", "TenantId": "", "AppClientId": "", "AppClientSecret": "" } }
csharpusing Microsoft.Agents.Builder; using Microsoft.Agents.Hosting.AspNetCore; using Microsoft.Agents.Storage; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(); builder.AddAgentApplicationOptions(); builder.AddAgent<MyAgent>(); builder.Services.AddSingleton<IStorage, MemoryStorage>(); builder.Services.AddControllers(); builder.Services.AddAgentAspNetAuthentication(builder.Configuration); WebApplication app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/", () => "Microsoft Agents SDK Sample"); var incomingRoute = app.MapPost("/api/messages", async (HttpRequest request, HttpResponse response, IAgentHttpAdapter adapter, IAgent agent, CancellationToken ct) => { await adapter.ProcessAsync(request, response, agent, ct); }); if (!app.Environment.IsDevelopment()) { incomingRoute.RequireAuthorization(); } else { app.Urls.Add("http://localhost:3978"); } app.Run();
csharpusing Microsoft.Agents.Builder; using Microsoft.Agents.Builder.App; using Microsoft.Agents.Builder.State; using Microsoft.Agents.Core.Models; using System; using System.Threading; using System.Threading.Tasks; public sealed class MyAgent : AgentApplication { public MyAgent(AgentApplicationOptions options) : base(options) { OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeAsync); OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last); OnTurnError(OnTurnErrorAsync); } private static async Task WelcomeAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken ct) { foreach (ChannelAccount member in turnContext.Activity.MembersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { await turnContext.SendActivityAsync( MessageFactory.Text("Welcome to the agent."), ct); } } } private static async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken ct) { await turnContext.SendActivityAsync( MessageFactory.Text($"You said: {turnContext.Activity.Text}"), ct); } private static async Task OnTurnErrorAsync( ITurnContext turnContext, ITurnState turnState, Exception exception, CancellationToken ct) { await turnState.Conversation.DeleteStateAsync(turnContext, ct); var endOfConversation = Activity.CreateEndOfConversationActivity(); endOfConversation.Code = EndOfConversationCodes.Error; endOfConversation.Text = exception.Message; await turnContext.SendActivityAsync(endOfConversation, ct); } }
csharpusing System.Net.Http.Headers; using Microsoft.Agents.CopilotStudio.Client; using Microsoft.Identity.Client; internal sealed class AddTokenHandler : DelegatingHandler { private readonly SampleConnectionSettings _settings; public AddTokenHandler(SampleConnectionSettings settings) : base(new HttpClientHandler()) { _settings = settings; } protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Headers.Authorization is null) { string[] scopes = [CopilotClient.ScopeFromSettings(_settings)]; IPublicClientApplication app = PublicClientApplicationBuilder .Create(_settings.AppClientId) .WithAuthority(AadAuthorityAudience.AzureAdMyOrg) .WithTenantId(_settings.TenantId) .WithRedirectUri("http://localhost") .Build(); AuthenticationResult authResponse; try { var account = (await app.GetAccountsAsync()).FirstOrDefault(); authResponse = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken); } catch (MsalUiRequiredException) { authResponse = await app.AcquireTokenInteractive(scopes).ExecuteAsync(cancellationToken); } request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResponse.AccessToken); } return await base.SendAsync(request, cancellationToken); } }
csharpusing Microsoft.Agents.CopilotStudio.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); var settings = new SampleConnectionSettings( builder.Configuration.GetSection("CopilotStudioClientSettings")); builder.Services.AddHttpClient("mcs").ConfigurePrimaryHttpMessageHandler(() => { return new AddTokenHandler(settings); }); builder.Services .AddSingleton(settings) .AddTransient<CopilotClient>(sp => { var logger = sp.GetRequiredService<ILoggerFactory>().CreateLogger<CopilotClient>(); return new CopilotClient(settings, sp.GetRequiredService<IHttpClientFactory>(), logger, "mcs"); }); IHost host = builder.Build(); var client = host.Services.GetRequiredService<CopilotClient>(); await foreach (var activity in client.StartConversationAsync(emitStartConversationEvent: true)) { Console.WriteLine(activity.Type); } await foreach (var activity in client.AskQuestionAsync("Hello!", null)) { Console.WriteLine(activity.Type); }
| File | Contents | | --- | --- | | references/acceptance-criteria.md | Import paths, hosting pipeline, Copilot Studio client patterns, anti-patterns |
| Resource | URL | | --- | --- | | Microsoft 365 Agents SDK | https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/ | | AddAgent API | https://learn.microsoft.com/en-us/dotnet/api/microsoft.agents.hosting.aspnetcore.servicecollectionextensions.addagent?view=m365-agents-sdk | | AgentApplication API | https://learn.microsoft.com/en-us/dotnet/api/microsoft.agents.builder.app.agentapplication?view=m365-agents-sdk | | Auth configuration options | https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/microsoft-authentication-library-configuration-options | | Copilot Studio integration | https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/integrate-with-mcs | | GitHub samples | https://github.com/microsoft/agents |
This skill is applicable to execute the workflow or actions described in the overview.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +68 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.