mcpbeat

Bf To Agents SDK Dotnet Migration

microsoft/bf-to-agents-sdk-dotnet-migration

> Use when migrating a Bot Framework .NET SDK bot to Microsoft 365 Agents SDK. Microsoft.Bot.Builder.Integration.AspNet.Core that want to migrate to Agents SDK.

5k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
1022
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/microsoft/Agents --skill bf-to-agents-sdk-dotnet-migration

The instruction itself

26 sections, as written by the author

Bot Framework to Agents SDK Migration (.NET)

Overview

Migrates a Bot Framework SDK bot to Microsoft 365 Agents SDK using the ActivityHandler/TeamsActivityHandler compat layer — minimal code changes, original class names preserved.

This skill stops at the compat layer. After completing this migration, ask the user whether to also convert to AgentApplication (see final step).

Agents DotNet SDK repository: https://github.com/microsoft/agents-for-net

Bot Framework DotNet SDK repository: https://github.com/microsoft/botbuilder-dotnet


Core Rules

  • Retain the original bot class name. Do not rename or create a new class — renaming breaks git history.
  • Only change what is required for a clean migration. Do not refactor, restructure, or improve beyond what is needed to compile and run on Agents SDK.

Package Replacements

Version: Always use the latest stable (non-beta) version of Agents SDK packages by default.

| Remove (Bot Framework) | Add (Agents SDK) |

|------------------------|------------------|

| Microsoft.Bot.Builder.Integration.AspNet.Core | Microsoft.Agents.Hosting.AspNetCore |

| Microsoft.Bot.Builder | Microsoft.Agents.Builder |

| Microsoft.Bot.Builder.Teams | Microsoft.Agents.Extensions.Teams |

| Microsoft.Bot.Builder.Dialogs | Microsoft.Agents.Builder.Dialogs |

| Microsoft.Bot.Connector | Microsoft.Agents.Connector |

| Microsoft.Bot.Connector.Authentication / MSAL auth packages | Microsoft.Agents.Authentication.Msal |

Deprecated Packages — No Agents SDK Equivalent

The following Bot Framework packages have no equivalent in the Agents SDK and are not supported. There is no recommended replacement. Remove them from the .csproj, but do not attempt to migrate the code that uses them — it will cause build errors that cannot be resolved within the Agents SDK migration. Flag this to the customer.

| Deprecated Package |

|--------------------|

| Microsoft.Bot.Builder.AI.Luis |

| Microsoft.Bot.Builder.AI.Orchestrator |

| Microsoft.Bot.Builder.AI.QnA |

| Microsoft.Bot.Builder.Azure.Queues |

| Microsoft.Bot.Builder.Dialogs.Adaptive |

| Microsoft.Bot.Builder.Dialogs.Adaptive.Runtime |

| Microsoft.Bot.Builder.Dialogs.Adaptive.Testing |

| Microsoft.Bot.Builder.Dialogs.Debugging |

| Microsoft.Bot.Builder.LanguageGeneration |

| Microsoft.Bot.Builder.TemplateManager |

| Microsoft.Bot.Configuration |

| Microsoft.Bot.Connector.Streaming |

| Microsoft.Bot.Streaming |

| Microsoft.Bot.Builder.Parsers.LU |

| AdaptiveExpressions |

Migration still proceeds — complete all other migration steps. The build errors from unmigrated code must be communicated to the customer as out-of-scope blockers.


Namespace Replacements

| Old | New |

|-----|-----|

| Microsoft.Bot.Builder | Microsoft.Agents.Builder |

| Microsoft.Bot.Builder (ActivityHandler) | Microsoft.Agents.Builder.Compat |

| Microsoft.Bot.Builder (AutoSaveStateMiddleware) | Microsoft.Agents.Builder.Compat |

| Microsoft.Bot.Builder.Teams (TeamsActivityHandler) | Microsoft.Agents.Extensions.Teams.Compat |

| Microsoft.Bot.Schema | Microsoft.Agents.Core.Models |

| Microsoft.Bot.Schema.Teams | Microsoft.Agents.Extensions.Teams.Models |

| Microsoft.Bot.Builder.Dialogs | Microsoft.Agents.Builder.Dialogs |

| Microsoft.Bot.Builder.Dialogs (PromptValidatorContext, PromptValidatorContext\<T\>) | Microsoft.Agents.Builder.Dialogs.Prompts |

| Microsoft.Bot.Builder.Integration.AspNet.Core | Microsoft.Agents.Hosting.AspNetCore |

| Microsoft.Bot.Builder.TraceExtensions | *(remove)* — TraceActivityAsync is built into ITurnContext |


Migration Steps

Step 1: Update packages and namespaces (see tables above)

Step 2: Keep bot class — retain original class name, only update using statements

ActivityHandler and TeamsActivityHandler exist unchanged in the Compat namespace. All override methods keep the same signatures. Do not rename the class.

// Before
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
public class EchoBot : ActivityHandler { /* unchanged */ }

// After — only using directives change; class name stays EchoBot
using Microsoft.Agents.Builder;
using Microsoft.Agents.Builder.Compat;    // ActivityHandler lives here
using Microsoft.Agents.Core.Models;        // IMessageActivity, ChannelAccount, etc.
public class EchoBot : ActivityHandler { /* unchanged */ }

For Teams:

// Before
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Schema.Teams;
public class MyBot : TeamsActivityHandler { /* unchanged */ }

// After — class name stays MyBot
using Microsoft.Agents.Extensions.Teams.Compat;   // TeamsActivityHandler lives here
using Microsoft.Agents.Extensions.Teams.Models;    // TeamsChannelAccount, TeamInfo, etc.
public class MyBot : TeamsActivityHandler { /* unchanged */ }

Step 3: State management for dialog bots

If the bot overrides OnTurnAsync to call SaveChangesAsync, both patterns work — keep whichever the bot already uses:

// Option A: Keep existing pattern in OnTurnAsync (works as-is)
public override async Task OnTurnAsync(ITurnContext ctx, CancellationToken ct)
{
    await base.OnTurnAsync(ctx, ct);
    await ConversationState.SaveChangesAsync(ctx, false, ct);
    await UserState.SaveChangesAsync(ctx, false, ct);
}

// Option B: Use AutoSaveStateMiddleware (register in adapter setup)
// adapter.Use(new AutoSaveStateMiddleware(conversationState, userState));

Step 4: New Program.cs

Delete BotController.cs and AdapterWithErrorHandler.cs. Create Program.cs:

using Microsoft.Agents.Hosting.AspNetCore;
using Microsoft.Agents.Storage;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddControllers();

// Register bot — AddAgent works because ActivityHandler implements IAgent
// DO NOT call AddAgentApplicationOptions() — that is only for AgentApplication
builder.AddAgent<MyBot>();

// Storage — always required by the Agents SDK
builder.Services.AddSingleton<IStorage, MemoryStorage>();

// Preserve all customer-specific DI registrations:
// builder.Services.AddSingleton<ConversationState>();
// builder.Services.AddSingleton<UserState>();
// builder.Services.AddSingleton<MyDialog>();
// builder.Services.AddSingleton<IMyService, MyService>();

// AddAgentAspNetAuthentication is defined in AspNetExtensions.cs — copy from any Agents SDK sample
builder.Services.AddAgentAspNetAuthentication(builder.Configuration);

WebApplication app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapAgentRootEndpoint();

// ActivityHandler bots use MapAgentEndpoints — NOT MapAgentApplicationEndpoints
// MapAgentApplicationEndpoints only works for AgentApplication subclasses
app.MapAgentEndpoints(requireAuth: !app.Environment.IsDevelopment());

if (app.Environment.IsDevelopment())
{
    app.Urls.Add("http://localhost:3978");
}

app.Run();

If customer had custom logic in BotController, preserve it using the process delegate:

app.MapAgentEndpoints(
    requireAuth: !app.Environment.IsDevelopment(),
    process: async (request, response, adapter, agent, ct) =>
    {
        // Custom controller logic here
        await adapter.ProcessAsync(request, response, agent, ct);
    });

Step 5 or at the very end of the migration: Ask user if they would like to write the learnings from this migration to a markdown file they could submit to use to help us improve the skill.


appsettings.json Changes

Remove Bot Framework auth config and add Agents SDK config. The shape of Connections:ServiceConnection depends on the value of MicrosoftAppType in the existing appsettings.

In all cases: remove MicrosoftAppId, MicrosoftAppPassword, MicrosoftAppType, and MicrosoftAppTenantId — their values are carried forward into the new structure.


Case 1: MicrosoftAppType = "SingleTenant"

{
  "Connections": {
    "ServiceConnection": {
      "Settings": {
        "AuthType": "ClientSecret",
        "AuthorityEndpoint": "https://login.microsoftonline.com/{MicrosoftAppTenantId}",
        "ClientId": "{MicrosoftAppId}",
        "ClientSecret": "{MicrosoftAppPassword}",
        "Scopes": ["https://api.botframework.com/.default"]
      }
    }
  },
  "ConnectionsMap": [
    {
      "ServiceUrl": "*",
      "Connection": "ServiceConnection"
    }
  ],
  "TokenValidation": {
    "Enabled": true,
    "Audiences": ["{MicrosoftAppId}"],
    "TenantId": "{MicrosoftAppTenantId}"
  }
}

Case 2: MicrosoftAppType = "UserAssignedMSI"

{
  "Connections": {
    "ServiceConnection": {
      "Settings": {
        "AuthType": "UserManagedIdentity",
        "ClientId": "{MicrosoftAppId}",
        "Scopes": ["https://api.botframework.com/.default"]
      }
    }
  },
  "ConnectionsMap": [
    {
      "ServiceUrl": "*",
      "Connection": "ServiceConnection"
    }
  ],
  "TokenValidation": {
    "Enabled": true,
    "Audiences": ["{MicrosoftAppId}"],
    "TenantId": "{MicrosoftAppTenantId}"
  }
}

Case 3: MicrosoftAppType = "MultiTenant", missing, or null

{
  "Connections": {
    "ServiceConnection": {
      "Settings": {
        "AuthType": "ClientSecret",
        "AuthorityEndpoint": "https://login.microsoftonline.com/botframework.com",
        "ClientId": "{MicrosoftAppId}",
        "TenantId": "{MicrosoftAppTenantId}",
        "ClientSecret": "{MicrosoftAppPassword}",
        "Scopes": ["https://api.botframework.com/.default"]
      }
    }
  },
  "ConnectionsMap": [
    {
      "ServiceUrl": "*",
      "Connection": "ServiceConnection"
    }
  ],
  "TokenValidation": {
    "Enabled": true,
    "Audiences": ["{MicrosoftAppId}"],
    "TenantId": "{MicrosoftAppTenantId}"
  }
}

Case 4: Any other MicrosoftAppType value

Do not generate Connections:ServiceConnection. Complete all other migration steps, then at the end inform the user:

> "The MicrosoftAppType value {value} is not a standard type recognized by this migration. The Connections:ServiceConnection block was not changed — additional manual configuration will be required. See: https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/microsoft-authentication-library-configuration-options"


Custom BotAdapter

If the source bot subclasses BotAdapter, migrate it to ChannelAdapter (Microsoft.Agents.Builder).

Class hierarchy to choose from

| Use when | Subclass |

|----------|----------|

| Custom transport / non-Azure channel | ChannelAdapter (abstract, minimal pipeline) |

| Azure Bot Service connectivity with custom behavior | CloudAdapter (Microsoft.Agents.Hosting.AspNetCore) |

Most custom BotAdapter subclasses fall into the CloudAdapter case. Prefer that unless the adapter is implementing a completely custom transport.

API mapping

| Bot Framework BotAdapter | Agents SDK |

|---------------------------|------------|

| using Microsoft.Bot.Builder; | using Microsoft.Agents.Builder; |

| : BotAdapter | : ChannelAdapter or : CloudAdapter |

| Activity[] in SendActivitiesAsync | IActivity[] in SendActivitiesAsync |

| BotCallbackHandler callback delegate | AgentCallbackHandler |

| abstract UpdateActivityAsync(...) | virtual UpdateActivityAsync(...) (throws NotImplementedException if not overridden) |

| abstract DeleteActivityAsync(...) | virtual DeleteActivityAsync(...) (throws NotImplementedException if not overridden) |

| protected RunPipelineAsync(ctx, BotCallbackHandler, ct) | protected RunPipelineAsync(ctx, AgentCallbackHandler, ct) |

Registration

Register the custom adapter via the two-type overload of AddAgent. Note AddAgent<TAgent, TAdapter> requires TAdapter : CloudAdapter:

builder.AddAgent<MyBot, MyCustomAdapter>();

If the custom adapter subclasses ChannelAdapter directly (not CloudAdapter), register manually:

builder.AddAgentCore();  // registers IConnections, IChannelServiceClientFactory
builder.Services.AddSingleton<IChannelAdapter, MyCustomAdapter>();
builder.Services.AddSingleton<IAgentHttpAdapter>(sp => (IAgentHttpAdapter)sp.GetRequiredService<IChannelAdapter>());
builder.Services.AddTransient<IAgent, MyAgent>();

Custom IMiddleware

If the source bot implements Microsoft.Bot.Builder.IMiddleware, retain the class and update its namespaces:

| Old | New |

|-----|-----|

| Microsoft.Bot.Builder.IMiddleware | Microsoft.Agents.Builder.IMiddleware |

| Microsoft.Bot.Builder.ITurnContext | Microsoft.Agents.Builder.ITurnContext |

| Microsoft.Bot.Builder.NextDelegate | Microsoft.Agents.Builder.NextDelegate |

The OnTurnAsync(ITurnContext, NextDelegate, CancellationToken) signature is unchanged.

Register custom middleware via a CloudAdapter subclass. Delete AdapterWithErrorHandler.cs only if it has no custom middleware — if it does, rename it or keep it as a custom adapter:

// CustomAdapter.cs
using Microsoft.Agents.Builder;
using Microsoft.Agents.Hosting.AspNetCore;
using Microsoft.Agents.Hosting.AspNetCore.BackgroundQueue;
using Microsoft.Extensions.Logging;

public class CustomAdapter : CloudAdapter
{
    public CustomAdapter(
        IChannelServiceClientFactory channelServiceClientFactory,
        IActivityTaskQueue activityTaskQueue,
        ILogger<CloudAdapter> logger = null)
        : base(channelServiceClientFactory, activityTaskQueue, logger: logger)
    {
        Use(new MyCustomMiddleware());
        // Use(new AnotherMiddleware(...));
    }
}

Register the custom adapter in Program.cs:

builder.AddAgent<MyBot, CustomAdapter>();

Files to Add

Every migrated project needs AspNetExtensions.cs — the AddAgentAspNetAuthentication() extension method is not in any NuGet package; it is a sample-provided file. Copy it from the Agents SDK shared samples (https://github.com/microsoft/Agents-for-net/blob/main/src/samples/Shared/AspNetExtensions.cs).


Files to Delete

Always delete these from the source project:

  • AdapterWithErrorHandler.cs — delete if it only has OnTurnError logic (Agents SDK handles errors internally); retain and rename if it contains custom Use() middleware registrations (see Custom IMiddleware section above)
  • BotController.cs (unless it has custom logic — see preservation note above)
  • Any Startup.cs (merge into Program.cs)

Common Mistakes

| Mistake | Fix |

|---------|-----|

| Renamed the bot class or created a new class | Restore the original class name — renaming breaks git history |

| Changed more than needed (refactored, restructured) | Revert — only change what is required to compile and run |

| Called AddAgentApplicationOptions() for ActivityHandler bot | Remove — only for AgentApplication |

| Used MapAgentApplicationEndpoints() for ActivityHandler bot | Use MapAgentEndpoints() — former only works for AgentApplication |

| Left services.AddTransient<IBot, MyBot>() | Replace with builder.AddAgent<MyBot>() |

| Left services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>() | Delete — handled by AddAgent<>() |

| Left ConfigurationBotFrameworkAuthentication | Delete — use AddAgentAspNetAuthentication() |

| Missing IStorage registration | Add builder.Services.AddSingleton<IStorage, MemoryStorage>(); — always required, not just for dialog bots |

| Left old Bot Framework appsettings (MicrosoftAppId etc.) | Replace with Connections + TokenValidation — shape depends on MicrosoftAppType (see appsettings.json Changes section) |

| Used the wrong Connections:ServiceConnection shape | Check MicrosoftAppType: SingleTenant→ClientSecret+tenant authority; UserAssignedMSI→UserManagedIdentity (no secret); MultiTenant/missing→ClientSecret+botframework.com authority |

| Left BotAdapter as base class of custom adapter | Change to ChannelAdapter or CloudAdapter; update Activity[]IActivity[], BotCallbackHandlerAgentCallbackHandler |

| Deleted AdapterWithErrorHandler.cs that contained custom Use() calls | Retain it (renamed if needed) as a CloudAdapter subclass; register via builder.AddAgent<TBot, TAdapter>() |

| Left Microsoft.Bot.Builder.IMiddleware namespace in custom middleware | Update to Microsoft.Agents.Builder.IMiddleware |

| Attempted to migrate code using a deprecated package (Luis, QnA, Adaptive, etc.) | Stop — no Agents SDK equivalent exists. Remove the package reference, leave the code in place, and flag build errors to the customer |

| Forgot to add AspNetExtensions.cs | Copy from any Agents SDK sample — AddAgentAspNetAuthentication is not in any NuGet package |

| Left conversationState.DeleteAsync(...) in adapter | Rename to conversationState.DeleteStateAsync(...) — method was renamed in Agents SDK |

| New Program.cs fails to compile (WebApplication, AddSingleton, IsDevelopment not found) | BF projects don't have <ImplicitUsings>enable</ImplicitUsings> — add explicit using for Microsoft.AspNetCore.Builder, Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.Hosting |


After Migration: Optional AgentApplication Upgrade

Once the compat-layer migration is complete and the build is clean, ask the user:

> "The bot is now running on Agents SDK using the ActivityHandler compat layer. Would you like to also migrate to the modern AgentApplication routing pattern? This involves converting the bot class to subclass AgentApplication and updating Program.cs — it is handled by the agents-sdk-dotnet-activityhandler-migration skill."

If yes, invoke agents-sdk-dotnet-activityhandler-migration.

If no, migration is complete.

How to use it

Copy the folder

Take microsoft/bf-to-agents-sdk-dotnet-migration from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.