BlazorMentor 0.5.0-preview
dotnet add package BlazorMentor --version 0.5.0-preview
NuGet\Install-Package BlazorMentor -Version 0.5.0-preview
<PackageReference Include="BlazorMentor" Version="0.5.0-preview" />
<PackageVersion Include="BlazorMentor" Version="0.5.0-preview" />
<PackageReference Include="BlazorMentor" />
paket add BlazorMentor --version 0.5.0-preview
#r "nuget: BlazorMentor, 0.5.0-preview"
#:package BlazorMentor@0.5.0-preview
#addin nuget:?package=BlazorMentor&version=0.5.0-preview&prerelease
#tool nuget:?package=BlazorMentor&version=0.5.0-preview&prerelease
BlazorMentor
Preview Release — BlazorMentor is currently in public preview. APIs may change before the stable release.
BlazorMentor is a .NET library that embeds a fully functional AI assistant directly into any Blazor application — with a floating chat widget, multi-agent orchestration, contextual memory, RAG, MCP integration, A2A federation, Agent Skills, and voice support — all configured via a single AddBlazorMentor() call.
Built on top of Microsoft Agent Framework (Microsoft.Agents.AI), BlazorMentor abstracts the complexity of multi-agent systems into a clean, attribute-driven programming model designed specifically for Blazor developers.
Table of Contents
- What is BlazorMentor?
- What can it do?
- Architecture overview
- Getting started
- AI providers
- The three-level agent model
- Page navigation
- Page context and UI actions
- Agent Skills
- Contextual memory
- RAG — Retrieval-Augmented Generation
- MCP — Model Context Protocol
- A2A — Agent-to-Agent
- Persistent conversation history
- Blazor Server vs Blazor WASM
- Built-in AI tools
- Streaming responses
- Session serialize and restore
- Security
- Widget customization
- All configuration options
- Extensibility
- Requirements
What is BlazorMentor?
BlazorMentor turns any Blazor application into an AI-assisted experience. It provides:
- A floating chat widget rendered automatically in the UI (no manual HTML needed).
- A Main Coordinator agent that understands the application context and routes requests.
- An attribute-driven discovery system that scans your assemblies and turns plain C# methods and classes into AI tools and agents — zero boilerplate.
- Full integration with Microsoft Agent Framework, supporting Handoff Workflows, Group Chat teams, and any AI provider compatible with the
IChatClientabstraction. - RAG — inject relevant documents from any vector database into every AI response.
- MCP — consume external MCP servers as additional tools, and expose BlazorMentor's own actions as an MCP server for other AI clients.
- A2A — connect to remote AI agents via the Agent-to-Agent protocol, and expose BlazorMentor as a federatable A2A agent.
The developer defines what the AI can do using attributes. BlazorMentor handles everything else: prompt building, agent wiring, session management, rate limiting, safety checks, memory, RAG retrieval, MCP connections, A2A federation, Agent Skills, and UI.
Before every LLM call, BlazorMentor automatically enriches the system prompt with real-time context: the current URL, the active page name, the authenticated user and their roles, the data registered by the current page via IMentorPageContext, the user's contextual memory, the Agent Skills catalogue, and — when RAG is enabled — the most relevant documents retrieved from your vector store.
UI actions registered by the current page are injected as individually named AI tools (one tool per action, each with its own description and parameter schema), rather than a generic dispatcher. This gives the AI precise knowledge of exactly what it can do on the current page, reducing hallucinations and enabling direct invocation: highlight_row(5) instead of invoke_ui_action("highlight_row", 5).
What can it do?
| Feature | Description |
|---|---|
| 🤖 Multi-agent orchestration | Coordinator + specialized agents connected via Handoff Workflow |
| 👥 Group Chat teams | Multiple agents collaborate in a structured conversation before executing |
| 🛠️ Tool discovery | C# methods become AI tools via [Description] or [MentorAction] |
| 🎯 UI Actions | AI invokes page-level actions (highlight rows, open modals, pre-fill forms) as individually named tools with typed parameters and async support |
| 📖 Agent Skills | Domain knowledge + execution packages — knowledge loaded on demand via load_skill (progressive disclosure), methods registered as L1 tools |
| 🧠 Contextual memory | Remembers user preferences and actions across sessions |
| 🗺️ Page navigation | AI navigates to any page decorated with [MentorPage] |
| 💬 Session management | Conversation history managed by Agent Framework (AgentSession) |
| 💾 Persistent history | Pluggable ChatHistoryProvider (CosmosDB, Redis, custom) |
| 📚 RAG | Retrieval-Augmented Generation — inject relevant documents from any vector DB into every AI response |
| 🔌 MCP Client | Consume external MCP servers as additional Level-1 tools for the coordinator |
| 🖥️ MCP Server | Expose every [MentorAction] method as an MCP tool — any MCP client can connect (Claude Desktop, VS Code, etc.) |
| 🌐 A2A Consumer | Connect to remote A2A agents; they participate in the Handoff workflow like local agents |
| 📡 A2A Server | Expose BlazorMentor as a federatable A2A agent — other orchestrators can discover and call it |
| 🎨 Customizable widget | Themes, colors, position, avatar, bot name |
| 🌍 Multi-language | Configurable language for AI responses and the widget UI |
| 🎤 Voice input/output | Browser-native Speech Recognition and Speech Synthesis |
| 🔒 Safety check | Optional AI-based prompt injection and jailbreak detection |
| ⏱️ Rate limiting | Per-user message limit with configurable time window (requires authentication for true per-user isolation) |
| ⏹️ Stop button | Cancel any in-flight AI request mid-stream — partial response is preserved in the chat |
| ✅ Confirmation dialogs | Destructive actions ask for user confirmation before execution |
| 🔐 Role-based actions | Actions restricted by ASP.NET Core identity roles |
| 📦 Pluggable memory store | Default in-memory, replaceable with Redis/EF Core/MongoDB |
| 🔌 Any AI provider | Azure OpenAI, OpenAI, Ollama, Azure AI Foundry, Anthropic, and more |
Architecture overview
┌──────────────────────────────────────────────────────────────────────┐
│ Blazor Application │
│ │
│ ┌──────────────┐ ┌────────────────────────────────────────┐ │
│ │ ChatWidget │◄────►│ MentorOrchestrator │ │
│ │ (Razor UI) │ │ (Main Coordinator) │ │
│ └──────────────┘ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────┤ │
│ │ Per-call context injection │ │
│ │ · AppContextProvider (page,user) │ │
│ │ · RagContextProvider (documents) │ │
│ │ · SkillsContextProvider (catalogue)│ │
│ │ · UIActionsMiddleware (per-action │ │
│ │ tools from IMentorPageContext) │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ [MentorAgent] [MentorAgent] [MentorTeam] │
│ OrderAgent CustomerAgent AnalysisTeam │
│ (L2 Handoff) (L2 Handoff) (L3 GroupChat) │
│ │ │ │ │
│ [MentorAction] [MentorAction] [TeamMember] │
│ C# methods C# methods sub-agents │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ External Integrations │ │
│ │ MCP Client │ MCP Server │ A2A Consumer │ A2A Server │ │
│ │ (tools from │ ([MentorAct] │ (remote │ (agent-card.json + │ │
│ │ ext. MCPs) │ as MCP) │ agents as L2)│ /a2a endpoint) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Static tools always registered on the Coordinator │ │
│ │ navigate_to │ load_skill │ read_skill_resource │ │
│ │ remember │ forget_all │ invoke_ui_action (Path B only) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
Microsoft Agent Framework
(ChatClientAgent, HandoffWorkflow,
GroupChatWorkflow, AgentSession)
The MentorOrchestrator builds and drives a HandoffWorkflow (or GroupChatWorkflow for teams). Before every LLM call, three AIContextProviders enrich the system prompt: AppContextProvider (page name, URL, user, roles, memory), RagContextProvider (retrieved documents), and SkillsContextProvider (skill catalogue). UI actions registered by the current page are injected as individual named tools by UIActionsMiddleware at the IChatClient level — with their own function-calling loop handled internally before any response reaches the outer pipeline. MCP tools from external servers are added as Level-1 tools alongside local [MentorAction] methods. Remote A2A agents participate in the Handoff graph exactly like local [MentorAgent] classes.
Getting started
Installation
dotnet add package BlazorMentor --prerelease
Minimal setup
In Program.cs:
using BlazorMentor.Extensions;
builder.Services.AddBlazorMentor(options =>
{
options.AppName = "My App";
options.AppDescription = "An order management application";
options.Language = MentorLanguage.Italian;
// Choose your AI provider (see "AI providers" section below)
options.ChatClient = new AzureOpenAIClient(
new Uri("https://myresource.openai.azure.com"),
new AzureKeyCredential(builder.Configuration["AzureOpenAI:Key"]!))
.GetChatClient("gpt-4o-mini")
.AsIChatClient();
// Assemblies to scan for agents, actions, and pages
options.ScanAssemblies = [typeof(Program).Assembly];
});
Add the widget to your layout
In MainLayout.razor (or App.razor):
@using BlazorMentor.Components
<ChatWidget />
That's it. The widget injects its own CSS and JS automatically — no changes to App.razor or _Host.cshtml are required.
AI providers
BlazorMentor supports any provider via two entry points: IChatClient (recommended for most cases) or AIAgent (required for providers that don't expose IChatClient).
// ── Azure OpenAI — Chat Completions
options.ChatClient = new AzureOpenAIClient(endpoint, credential)
.GetChatClient("gpt-4o-mini").AsIChatClient();
// ── Azure OpenAI — Responses API (service-managed history)
options.ChatClient = new AzureOpenAIClient(endpoint, credential)
.GetResponsesClient("gpt-4o-mini").AsIChatClient();
// ── OpenAI direct
options.ChatClient = new OpenAIClient("sk-...")
.GetChatClient("gpt-4o").AsIChatClient();
// ── Ollama (local)
options.ChatClient = new OllamaChatClient(new Uri("http://localhost:11434"), "llama3.2");
// ── Azure AI Foundry (AIProjectClient) — requires AIAgent
options.Agent = new AIProjectClient(endpoint, credential)
.AsAIAgent(model: "gpt-4o-mini", instructions: "You are a helpful assistant.");
// ── Anthropic Claude — requires AIAgent
options.Agent = new AnthropicClient() { APIKey = apiKey }
.AsAIAgent(model: "claude-haiku-4-5", instructions: "...");
⚠️ Set either
ChatClientorAgent, not both.
Path A vs Path B — feature matrix
| Feature | ChatClient (Path A) |
AIAgent (Path B) |
|---|---|---|
| L1 actions | ✅ | ✅ |
| L2 Handoff agents | ✅ | ❌ requires IChatClient |
| L3 Group Chat teams | ✅ | ❌ requires IChatClient |
| A2A remote agents | ✅ | ❌ requires IChatClient |
| MCP client tools | ✅ | ✅ |
| RAG | ✅ | ✅ |
| Agent Skills | ✅ | ✅ |
| UI Actions (per-action tools) | ✅ via UIActionsMiddleware |
⚠️ single generic invoke_ui_action dispatcher |
| Safety check | ✅ | ❌ skipped automatically |
| Session history reduction | ✅ configurable | ✅ configured in the agent |
Custom ChatHistoryProvider |
✅ via options | ⚠️ must be set in the pre-built agent |
When using
AIAgent(Path B),ChatHistoryProvidercannot be added after construction — configure it directly in the agent you pass tooptions.Agent.
The three-level agent model
BlazorMentor organizes AI capabilities in three progressive levels of complexity.
Level 1 — Actions on a service
The simplest level. Decorate C# methods with [Description] (standard .NET, zero dependencies) or [MentorAction] (adds confirmation, roles, navigation, hints).
// Register the service in DI as usual
builder.Services.AddScoped<OrderService>();
public class OrderService
{
// Simple — [Description] from System.ComponentModel, zero dependencies
[Description("Returns the list of active orders")]
public async Task<List<Order>> GetActiveOrdersAsync() { ... }
// Advanced — full [MentorAction] with all parameters
[MentorAction(
Description = "Cancels an existing order",
Category = "Orders", // groups actions in proactive suggestions
RequiresConfirmation = true, // shows confirmation banner before executing
RequiredRoles = ["Manager"], // only users with this role can trigger it
ProactiveHint = "Suggest checking active orders first", // hint injected into AI prompt
NavigateTo = "/orders")] // AI navigates here automatically after success
public async Task<bool> CancelOrderAsync(int orderId, string reason) { ... }
}
BlazorMentor discovers the service via ScanAssemblies and exposes its methods as AI tools automatically. The AI calls the right method based on the user's natural language request.
[MentorAction] parameters summary:
| Parameter | Description |
|---|---|
Description |
Natural language description used as the AI tool description |
Category |
Groups actions in proactive suggestion chips |
RequiresConfirmation |
Shows a confirmation banner before executing. Use for destructive or irreversible operations |
RequiredRoles |
ASP.NET Core identity roles required to invoke the action. Empty = accessible to all |
ProactiveHint |
Hint injected into the AI prompt to guide proactive behaviour |
NavigateTo |
URL the AI navigates to automatically after successful execution |
Level 2 — Specialized agent with Handoff
Decorate a class with [MentorAgent] to create a dedicated AI agent connected to the Main Coordinator via a Handoff Workflow. The coordinator hands off the conversation to the right agent based on the request.
[MentorAgent(
Name = "OrderAgent",
Description = "Handles everything related to orders: creation, tracking, cancellation",
HandoffTo = ["CustomerAgent", "InvoiceAgent"] // can hand off further
)]
public class OrderAgent : IMentorAgent
{
private readonly OrderService _orders;
public OrderAgent(OrderService orders) => _orders = orders;
[Description("Creates a new order for a customer")]
public async Task<Order> CreateOrderAsync(int customerId, List<string> products) { ... }
[Description("Retrieves the status of an order")]
public async Task<OrderStatus> GetOrderStatusAsync(int orderId) { ... }
}
Register the agent in DI:
builder.Services.AddScoped<OrderAgent>();
BlazorMentor builds a ChatClientAgent from the class, wires it into the Handoff graph, and generates the system prompt from Name and Description (or you can supply a custom Instructions).
Implementing IMentorAgent is optional but recommended — it gives compile-time safety, enables injecting the agent in tests, and ensures the DI container validates its registration.
[MentorAgent] parameters:
| Parameter | Required | Description |
|---|---|---|
Name |
✅ | Agent name — used as the key in the Handoff graph and in the coordinator's system prompt |
Description |
✅ | Natural language description of the agent's capabilities. Used by the coordinator to decide when to delegate |
HandoffTo |
— | Names of other [MentorAgent] agents this agent can hand off to. Must match the Name values exactly (case-insensitive) |
Instructions |
— | Custom system prompt for this agent. If omitted, BlazorMentor auto-generates one from Name and Description |
⚠️ Important: agent names in
HandoffTomust match exactly theNamein the target[MentorAgent]attribute (case-insensitive). A mismatch produces a warning in the logs at startup and silently skips that handoff edge.
Level 3 — Collaborative team (Group Chat)
Use [MentorTeam] to define a Group Chat where multiple AI agents collaborate — debating, analyzing, and approving — before a result is returned to the user or handed off to an L2 agent for execution.
[MentorTeam(
Name = "BusinessAnalysisTeam",
Description = "Analyzes business data and produces an approved action plan",
MaxIterations = 8, // forced termination after 8 turns
TriggerOn = ["analysis", "business plan", "budget"], // keywords that activate this team
HandoffTo = ["OrderAgent", "CustomerAgent"])]
public class BusinessAnalysisTeam : IMentorTeam // IMentorTeam is optional, recommended
{
[TeamMember(
Role = "DataAnalyst",
Tools = [typeof(ReportTools), typeof(OrderTools)],
Instructions = "Analyze the data and propose solutions with numbers.")]
public object? Analyst { get; set; }
[TeamMember(
Role = "BusinessApprover",
Instructions = "Review the analyst's proposal. Reply APPROVED or REJECTED with reasons.")]
public object? Approver { get; set; }
[TeamTerminationCondition]
public bool ShouldTerminate(string lastMessage, string lastSpeaker)
=> lastSpeaker == "BusinessApprover" &&
(lastMessage.Contains("APPROVED") || lastMessage.Contains("REJECTED"));
}
BlazorMentor builds a GroupChatWorkflow from this declaration and connects it to the main Handoff graph.
[MentorTeam] parameters:
| Parameter | Required | Description |
|---|---|---|
Name |
✅ | Team name — used as the key in the routing graph and shown in logs |
Description |
✅ | Description of the team's purpose. Used by the coordinator to decide when to activate it |
TriggerOn |
— | Keywords injected into the coordinator's prompt as hints for when to activate this team. Not hard rules — the AI decides |
MaxIterations |
— | Maximum number of turns in the Group Chat before forced termination. Default: 10 |
HandoffTo |
— | L2 agents to delegate execution to after the team approves a plan |
[TeamMember] parameters:
| Parameter | Required | Description |
|---|---|---|
Role |
✅ | Role name within the team (e.g. "DataAnalyst", "Approver") |
Instructions |
✅ | System prompt for this member — defines what it should do and how it should respond |
Tools |
— | Tool classes this member can call during the discussion. Should be read-only — write operations belong in L2 agents via HandoffTo |
[TeamTerminationCondition]is required when you need custom exit logic. Required method signature:bool ShouldTerminate(string lastMessage, string lastSpeaker). Without it, the team runs forMaxIterationsturns with round-robin turn-taking and stops automatically.TriggerOnkeywords are injected into the Coordinator's prompt to help it decide when to activate the team — they are hints, not hard rules.[TeamMember]tools should be read-only (queries, reports). Write operations should be delegated to L2 agents viaHandoffTo.- Implementing
IMentorTeamis optional but recommended for the same reasons asIMentorAgent.
Page navigation
Decorate Blazor pages with [MentorPage] to let the AI navigate to them on user request.
@* Simple page — navigation only *@
@attribute [MentorPage("/orders", "Orders")]
@* Page with UI actions — waits for SignalReady() after navigation *@
@attribute [MentorPage("/orders-interactive", "Interactive Orders",
HasUIActions = true,
ReadyTimeout = 3000,
Description = "Order management with inline editing and filtering")]
Pages are discovered at startup from ScanAssemblies — they don't need to be visited first. When HasUIActions = true, the AI waits for the page to call PageContext.SignalReady() before executing UI actions, ensuring the component is fully mounted.
[MentorPage] parameters:
| Parameter | Required | Description |
|---|---|---|
Url |
✅ | Page URL (e.g. "/orders"). Used by the navigate_to tool |
Name |
✅ | Human-readable page name injected into the system prompt |
Description |
— | Optional description of the page's features shown to the AI |
HasUIActions |
— | If true, the AI waits for PageContext.SignalReady() before executing UI actions. Default: false |
ReadyTimeout |
— | Timeout in milliseconds for SignalReady(). Used only when HasUIActions = true. Default: 2000 |
Page context and UI actions
IMentorPageContext is a scoped service injectable in any Blazor page. It has two responsibilities:
- Share visible data — key/value pairs serialized live into the AI system prompt before every call (filter state, selected item, visible row count, etc.).
- Register UI actions — C# lambdas the AI can invoke directly on the current page without going through a business service (highlight a row, open a modal, pre-fill a form).
How UI actions work (architecture)
Each registered UI action becomes its own named AI tool before every LLM call — injected dynamically via UIActionsMiddleware (a DelegatingChatClient wrapping the raw model). The AI sees highlight_row(parameter: integer: order ID) directly in its tool list, not a generic dispatcher. This:
- Eliminates hallucinated action names (the AI sees exact names, not strings to guess)
- Provides proper parameter schemas for each action
- Enables reliable multi-action chains
UI actions are scoped to the current page: they appear in the tool list only while the page is mounted, and disappear the moment the page calls PageContext.Clear() in Dispose().
Registering UI actions
Four overloads are available, from simple to fully typed and async:
@inject IMentorPageContext PageContext
@implements IDisposable
protected override void OnInitialized()
{
PageContext
.SetPageName("Orders")
// ── Share current UI state with the AI ───────────────────────────────
.Set("ActiveFilter", "Pending")
.Set("VisibleRows", _orders.Count)
.Set("SelectedOrder", _selectedOrder)
// ── 1. Simple action — no parameter ─────────────────────────────────
.RegisterUIAction(
"open_create_modal",
"Opens the new order creation dialog",
_ => OpenCreateModal())
// ── 2. Typed parameter — no manual casting required ──────────────────
.RegisterUIAction<int>(
"highlight_row",
"Highlights an order row by its ID",
id => HighlightRow(id), // id is int, not object?
parameterHint: "integer: order ID")
// ── 3. Typed async — awaited before the AI continues ─────────────────
.RegisterUIActionAsync<int>(
"select_order",
"Selects an order and loads its details panel",
async id => {
await InvokeAsync(() => { _selectedId = id; StateHasChanged(); });
},
parameterHint: "integer: order ID")
// ── 4. Complex typed parameter (DTO deserialized from JSON) ──────────
.RegisterUIActionAsync<OrderFormModel>(
"prefill_form",
"Pre-fills the order form with the provided data",
async model => {
await InvokeAsync(() => { _form = model; StateHasChanged(); });
},
parameterHint: "JSON: { customerId, items, notes }");
// Signal the AI that the page is ready (required when HasUIActions = true)
PageContext.SignalReady();
}
// Signal the AI that the page is ready (required when HasUIActions = true)
PageContext.SignalReady();
}
// Always clear in Dispose — removes context and actions from the AI's view
public void Dispose() => PageContext.Clear();
UI action overloads reference
| Overload | Parameter | Execution | Use when |
|---|---|---|---|
RegisterUIAction(name, desc, Action<object?>) |
Raw object? (cast manually) |
Synchronous | Simple no-param or legacy code |
RegisterUIAction<TParam>(name, desc, Action<TParam>) |
Auto-deserialized from JSON | Synchronous | Typed param, sync handler |
RegisterUIActionAsync(name, desc, Func<object?, Task>) |
Raw object? |
Async | No-param async actions |
RegisterUIActionAsync<TParam>(name, desc, Func<TParam, Task>) |
Auto-deserialized from JSON | Async | Typed param, async handler (recommended) |
UnregisterUIAction(name) |
— | — | Remove a specific action dynamically (e.g. when a feature becomes unavailable) |
Context data methods reference
| Method | Description |
|---|---|
SetPageName(name) |
Sets the current page name injected into the system prompt |
Set(key, value) |
Adds or updates a context entry (any serializable value) |
Remove(key) |
Removes a single context entry without clearing everything |
Clear() |
Removes all context data and all registered UI actions. Always call from Dispose() |
Automatic parameter schema generation
For the two typed overloads (RegisterUIAction<TParam> and RegisterUIActionAsync<TParam>), the parameterHint argument is automatically generated from the type via reflection when you omit it. You only need to provide it if you want to override the generated text.
TParam |
Auto-generated parameterHint |
|---|---|
int, long, short, byte |
"integer" |
float, double, decimal |
"number" |
bool |
"boolean" |
string |
"string" |
Guid |
"string (GUID)" |
DateTime, DateTimeOffset |
"string (ISO 8601 date)" |
Status (enum) |
"string (Active\|Inactive\|Pending)" |
int? |
"integer?" |
List<int> |
"array<integer>" |
Dictionary<string,int> |
"object<string,integer>" |
OrderFormModel (class) |
"{ customerId: integer, productName: string, quantity: integer }" |
This means the AI sees a precise, field-by-field description of what to pass — even for complex DTOs — without any manual work:
// parameterHint omitted → auto-generated as "{ customerId: integer, productName: string, quantity: integer }"
.RegisterUIActionAsync<OrderFormModel>(
"prefill_form",
"Pre-fills the order form with the provided data",
async model => {
await InvokeAsync(() => { _form = model; StateHasChanged(); });
})
// Override only when the auto-generated hint is not descriptive enough
.RegisterUIAction<int>(
"highlight_row",
"Highlights an order row by its ID",
id => HighlightRow(id),
parameterHint: "integer: order ID") // ← manual override for extra clarity
Async handlers are awaited — the AI waits for the handler to complete before generating its next response. This makes multi-action chains reliable: the AI can call
select_order(42)and thenhighlight_row(42), knowing each step is done before it proceeds.
The AI automatically receives current page context (URL, page name, registered data) injected into its system prompt on every call — with no extra configuration required.
Agent Skills
Agent Skills are domain knowledge + execution packages with two complementary roles:
| Role | What it provides | When used |
|---|---|---|
| Knowledge | Instructions, policy, rules, workflows (markdown) | Loaded on demand via load_skill |
| Tools | [Description]/[MentorAction] methods on the skill class |
Always available as L1 tools |
Rather than dumping all domain knowledge into the system prompt, BlazorMentor uses progressive disclosure:
- The skill catalogue (name + one-line description, ~100 tokens per skill) is always injected into the system prompt.
- When the AI needs expertise, it calls
load_skill("skill-name")to receive the full instructions — then uses that knowledge to call the right methods with the right parameters. - Attached resource files (policy docs, FAQ pages, templates) are accessible via
read_skill_resource.
10 registered skills cost roughly 1,000 tokens in the system prompt instead of the 50,000+ tokens you'd need to inline everything upfront.
Setup
builder.Services.AddBlazorMentor(options =>
{
options.EnableSkills = true;
options.SkillsFolder = "Skills"; // relative to content root, or absolute path
options.ScanAssemblies = [typeof(Program).Assembly];
});
Dual-role skill class (knowledge + tools)
This is the most powerful pattern. The class provides both the domain instructions and the executable tools:
// Register in DI — required when the class has methods
builder.Services.AddScoped<ExpenseReportSkill>();
[MentorSkill(
Name = "expense-report",
Description = "Handles expense report filing, validation, and policy checks",
InstructionsFile = "Skills/expense-report/SKILL.md")] // or use Instructions = "..."
public class ExpenseReportSkill
{
private readonly ExpenseRepository _repo;
public ExpenseReportSkill(ExpenseRepository repo) => _repo = repo;
// ── Methods become L1 tools — always registered on the coordinator ──────
[Description("Submits a new expense report for the current user")]
public async Task<string> SubmitExpenseReportAsync(
string category, decimal amount, string notes) { ... }
[MentorAction(
Description = "Approves a pending expense report",
RequiresConfirmation = true,
RequiredRoles = ["Manager"])]
public async Task<bool> ApproveExpenseReportAsync(int reportId) { ... }
[Description("Returns the reimbursement policy for a given expense category")]
public string GetPolicyForCategory(string category) { ... }
}
The flow when a user asks "Submit a €47 expense for today's business lunch":
- AI sees
submit_expense_report_asyncin its tool list - Before calling it, AI calls
load_skill("expense-report")— learns that meals have a €50 limit, require a category and description - AI calls
submit_expense_report_async("Meals", 47, "Business lunch")with policy-correct parameters
Without the skill, the AI guesses. With the skill, the AI knows the rules before it acts.
Knowledge-only skill (no methods)
Use this when the skill provides guidance but all execution is handled by existing service tools:
// No DI registration needed — no methods, no instance required
[MentorSkill(
Name = "refund-policy",
Description = "Return and refund rules for the online store",
Instructions = """
## Refund Policy
- Items can be returned within 30 days of purchase.
- Digital products are non-refundable once downloaded.
- Damaged items: customer submits a photo via the support ticket tool.
- Refunds are processed within 5–7 business days to the original payment method.
""")]
public class RefundPolicySkill { }
File-based skills (auto-discovery)
BlazorMentor also discovers skills automatically from the filesystem — no class needed:
Skills/
expense-report/
SKILL.md ← required: skill instructions in markdown
policy.md ← optional: resource accessible via read_skill_resource
examples.md ← optional: additional resource
refund-policy/
SKILL.md
shipping-rules/
SKILL.md
carrier-list.txt
SKILL.md format — the first paragraph after the heading becomes the catalogue description:
# Expense Report Filing
Handles expense report submission, validation, approval workflow, and policy enforcement.
## Eligible Expenses
- Meals: up to €50/day domestic, €80/day international
- Travel: economy class only for flights under 4 hours
...
Class-based overrides file-based — if both define a skill with the same name, the class attribute wins.
How the AI sees skills
System prompt (catalogue only — always injected, ~100 tokens total):
## Available Skills
When a request requires specialised expertise in any area below,
call load_skill with the skill name to get full instructions before responding:
- **expense-report**: Handles expense report filing, validation, and policy checks
- **refund-policy**: Return and refund rules for the online store
- **shipping-rules**: Shipping carrier rules and delivery SLAs
Full instructions and resources are never in the prompt by default — only loaded when the AI decides it needs them.
[MentorSkill] parameters
| Parameter | Description |
|---|---|
Name |
Unique skill name in kebab-case (e.g. "expense-report"). Key for load_skill |
Description |
One-sentence description shown to the AI in the skill catalogue |
InstructionsFile |
Path to a markdown file (absolute or relative to content root). Falls back to Skills/{Name}/SKILL.md |
Instructions |
Inline markdown text. Takes precedence over InstructionsFile |
Agent Skills configuration options
| Option | Type | Default | Description |
|---|---|---|---|
EnableSkills |
bool |
false |
Enables skill discovery and the load_skill / read_skill_resource tools |
SkillsFolder |
string |
"Skills" |
Folder to scan for file-based skills. Relative to content root or absolute |
Contextual memory
Enable the Mentor's ability to remember preferences and past actions across sessions:
options.UseMemoryContext = true;
options.MemoryContextCount = 10; // last N memories injected into the system prompt
By default, memories are stored in RAM (InMemoryMentorMemoryStore). For real persistence, register your own store before AddBlazorMentor():
// Redis
builder.Services.AddSingleton<IMentorMemoryStore, RedisMemoryStore>();
// EF Core
builder.Services.AddScoped<IMentorMemoryStore, EfCoreMemoryStore>();
// MongoDB
builder.Services.AddSingleton<IMentorMemoryStore, MongoMemoryStore>();
builder.Services.AddBlazorMentor(options => {
options.UseMemoryContext = true;
...
});
⚠️ User isolation: With authentication configured, each user has their own memory (keyed by
ClaimTypes.NameIdentifier). Without authentication, all users share the"anonymous"key — suitable only for single-user apps or development.
How automatic memory works
When UseMemoryContext = true, the Coordinator's system prompt includes explicit instructions to call the remember tool immediately and silently whenever the user reveals a preference, name, role, or goal — without asking for confirmation. The developer does not need to do anything: the AI handles it automatically.
Examples of what the AI saves automatically:
| User says | AI saves |
|---|---|
"My name is Antonio" |
remember("user_name", "Antonio") |
"I work in the sales team" |
remember("user_role", "sales team") |
"Always show me Pending orders first" |
remember("preference_filter", "Pending") |
At the start of each session, memories are injected into the prompt so the AI can greet the user by name or honour their preferences immediately.
InMemoryMentorMemoryStore — production limitations
The default store has two hard limitations:
- Data is lost on app restart — memories do not survive deployments.
- Does not scale across multiple instances — in a load-balanced environment each server has its own isolated dictionary.
For any production deployment with more than one server instance, or where persistence across restarts is required, register a custom IMentorMemoryStore implementation.
RAG — Retrieval-Augmented Generation
RAG enriches every AI response with documents retrieved from your vector store. Before the LLM call, BlazorMentor searches for the most relevant documents and injects them into the coordinator's system prompt — grounding the AI's answers in your actual data.
Setup
Step 1 — Register your RAG source before AddBlazorMentor():
// Implement IMentorRagSource with your preferred vector DB
builder.Services.AddScoped<IMentorRagSource, MyVectorDbRagSource>();
// Examples:
// Azure AI Search
builder.Services.AddScoped<IMentorRagSource, AzureSearchRagSource>();
// Qdrant
builder.Services.AddScoped<IMentorRagSource, QdrantRagSource>();
// Any custom implementation
builder.Services.AddScoped<IMentorRagSource, MyCustomRagSource>();
Step 2 — Enable RAG in options:
builder.Services.AddBlazorMentor(options =>
{
options.UseRag = true;
options.RagResultCount = 5; // number of documents to inject
options.RagSystemPromptTemplate = "Use the following documents to answer:\n{documents}";
options.ShowRagSources = true; // show citation chips below AI messages
});
Implement IMentorRagSource
public class MyVectorDbRagSource : IMentorRagSource
{
private readonly MyVectorDb _db;
public MyVectorDbRagSource(MyVectorDb db) => _db = db;
public async Task<IReadOnlyList<MentorRagResult>> SearchAsync(
string query, int maxResults, CancellationToken ct = default)
{
var hits = await _db.SearchAsync(query, maxResults, ct);
return hits.Select(h => new MentorRagResult(
Content: h.Text,
SourceUrl: h.Url,
Title: h.Title,
Score: h.Score)).ToList();
}
}
RAG source citations in the widget
When ShowRagSources = true, citation chips appear below each AI message:
[AI response]
──────────────────────
📄 Product Manual v2 ↗
📄 Support FAQ ↗
The chips link to the original SourceUrl of each retrieved document. If more than 3 sources are returned, a "+N more" button collapses the rest.
Architecture note
RAG is applied only at the coordinator level — not on L2/L3 specialist agents. The coordinator performs the vector search once per user message; retrieved documents flow naturally into the conversation context passed to any delegated agent. This avoids redundant searches and keeps costs low.
RAG configuration options
| Option | Default | Description |
|---|---|---|
UseRag |
false |
Enables RAG. Requires a registered IMentorRagSource |
RagResultCount |
5 |
Number of documents retrieved per query |
RagMinScore |
2 |
Minimum relevance score a document must reach to be injected into the prompt. Documents below this threshold are discarded. Scale depends on the IMentorRagSource implementation: for keyword search, 2 ≈ two content matches or one title match; for vector/cosine similarity, typical values are 0.5–0.75. Set to 0 to disable filtering |
RagSystemPromptTemplate |
"Use the following documents to answer:\n{documents}" |
Template injected into the system prompt. Use {documents} as placeholder |
ShowRagSources |
false |
Show citation chips below AI messages in the widget |
MCP — Model Context Protocol
BlazorMentor supports MCP in both directions: as a client (consuming tools from external MCP servers) and as a server (exposing [MentorAction] methods to any MCP-compatible client).
MCP Client — consuming external MCP servers
External MCP servers (filesystem, databases, APIs, custom tools) become additional Level-1 tools for the coordinator — indistinguishable from local [MentorAction] methods.
HTTP transport (remote server)
builder.Services.AddBlazorMentor(options =>
{
options.McpServers = [
new MentorMcpServer
{
Name = "MyApiTools",
ServerUrl = "https://mcp.example.com/mcp",
}
];
});
stdio transport (local process)
options.McpServers = [
new MentorMcpServer
{
Name = "filesystem",
Command = "npx",
Arguments = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
AllowedTools = ["read_file", "list_directory"], // null = all tools
RequiresConfirmation = true, // shows confirmation banner for every call
},
new MentorMcpServer
{
Name = "database",
Command = "my-db-mcp-server",
Arguments = ["--connection-string", "Server=..."],
EnvironmentVariables = new Dictionary<string, string>
{
["DB_PASS"] = builder.Configuration["DbPassword"]!
}
}
];
MCP status badge in the widget
When ShowMcpStatus = true, a 🔌 N badge appears in the widget header showing the number of connected servers. Clicking it opens a dropdown with the connection status of each server.
options.ShowMcpStatus = true; // default: false — recommended for development
MentorMcpServer properties:
| Property | Description |
|---|---|
Name |
Display name for the server |
ServerUrl |
HTTP/SSE endpoint URL (HTTP transport) |
Command |
Executable to launch (stdio transport, e.g. "npx") |
Arguments |
Command-line arguments for the process |
EnvironmentVariables |
Environment variables injected into the process |
AllowedTools |
Whitelist of tool names to expose. null = expose all |
RequiresConfirmation |
If true, every call to a tool from this server shows a confirmation banner |
MCP Server — exposing BlazorMentor as an MCP server
Every [MentorAction] method discovered by BlazorMentor can be exposed as an MCP tool, allowing any MCP-compatible client (Claude Desktop, VS Code Copilot, custom agents) to call your application's business logic directly.
Step 1 — Enable the MCP server in options:
builder.Services.AddBlazorMentor(options =>
{
options.McpServerEnabled = true;
options.AppName = "MyApp"; // becomes the MCP server name
});
Step 2 — Map the MCP endpoint in Program.cs:
app.MapBlazorMentorMcp(); // default path: /mcp
app.MapBlazorMentorMcp("/my-mcp"); // custom path
MCP clients can now connect to https://yourapp/mcp and discover all [MentorAction] tools.
ℹ️
[MentorAction]classes registered as Scoped are fully supported by the MCP server — BlazorMentor creates a dedicated DI scope internally to resolve them. For classes that hold stateful resources (e.g. openDbContext), prefer Transient or Singleton registration.
A2A — Agent-to-Agent
BlazorMentor supports the Agent-to-Agent (A2A) protocol in both directions: as a consumer (calling remote A2A agents) and as a server (exposing BlazorMentor as a federatable agent).
A2A Consumer — calling remote A2A agents
Remote A2A agents participate in the Handoff workflow exactly like local [MentorAgent] classes. The coordinator can hand off to them; they return results to the coordinator when done. This lets you federate specialized agents deployed as separate services.
builder.Services.AddBlazorMentor(options =>
{
options.RemoteAgents = [
new MentorRemoteAgent
{
Name = "InventoryAgent",
Description = "Manages warehouse stock and inventory levels",
AgentCardUrl = "https://inventory.example.com", // base URL only — SDK auto-appends /.well-known/agent-card.json
},
new MentorRemoteAgent
{
Name = "BillingAgent",
Description = "Handles invoicing and payment processing",
AgentCardUrl = "https://billing.example.com", // base URL only
Headers = new Dictionary<string, string>
{
["Authorization"] = $"Bearer {builder.Configuration["BillingApiKey"]}"
}
}
];
});
BlazorMentor automatically resolves each agent's Agent Card (/.well-known/agent-card.json) at startup, establishes an A2A connection, and adds the agent to the Handoff graph. The coordinator's system prompt is automatically enriched with each remote agent's name and description, so the AI knows when to delegate. The coordinator can then delegate requests to remote agents using natural language — no extra configuration needed.
MentorRemoteAgent properties:
| Property | Description |
|---|---|
Name |
Display name used in the Handoff graph and in the coordinator's system prompt |
Description |
Human-readable description of what this agent does. Injected into the coordinator's system prompt so the AI knows when to route to it |
AgentCardUrl |
Base URL of the remote agent (e.g. https://inventory.example.com). The SDK automatically appends /.well-known/agent-card.json — do NOT include the path |
Headers |
Optional custom HTTP headers (auth tokens, API keys, etc.) injected into all A2A requests to this agent |
RequiredRoles |
Optional ASP.NET Core roles required to use this agent. ⚠️ Per-agent role enforcement is not yet fully implemented |
A2A Server — exposing BlazorMentor as an A2A agent
BlazorMentor can expose itself as a fully compliant A2A agent, discoverable and callable by any A2A-compatible orchestrator.
Step 1 — Enable the A2A server in options:
builder.Services.AddBlazorMentor(options =>
{
options.A2AServerEnabled = true;
options.AppName = "MyApp";
options.AppDescription = "My Blazor AI assistant for order management";
options.AgentCard = new AgentCardInfo { Version = "2.0.0" };
});
Step 2 — Map the A2A endpoints in Program.cs:
app.MapBlazorMentorA2A(); // default path: /a2a
app.MapBlazorMentorA2A("/agent"); // custom path
This registers two endpoints:
GET /.well-known/agent-card.json— Agent Card describing the agent's name, description, version, and supported interfacesPOST /a2a— A2A task handler that receives messages, routes them throughMentorOrchestrator, and streams back the response
Remote orchestrators can now discover this agent via https://yourapp/.well-known/agent-card.json and send A2A tasks to it.
⚠️
A2AServerUrlis required when other A2A instances will call this agent as a remote. Set it to the full public URL of your A2A endpoint (e.g.http://localhost:5001/a2a). Without it, the Agent Card'sSupportedInterfacescontains only a relative path (/a2a), which causes aUriFormatExceptionin remote consumers when they try to resolve the endpoint.options.A2AServerUrl = "https://myapp.example.com/a2a";
AgentCardInfo properties:
| Property | Default | Description |
|---|---|---|
Version |
"1.0.0" |
Agent version string exposed in the Agent Card |
IconUrl |
null |
URL to the agent's icon image |
Provider |
null |
Provider or organization name |
DocumentationUrl |
null |
URL to the agent's documentation |
Tags |
null |
Additional tags describing capabilities — included in the Agent Card for discovery |
How streaming works: Each incoming A2A task is processed by
BlazorMentorA2AHandler, which creates a dedicated DI scope, runsMentorOrchestrator.SendMessageAsync, collects streaming chunks, and returns the complete response viaTaskUpdater.CompleteAsync.
Persistent conversation history
By default, conversation history lives in memory and is lost on app restart.
// CosmosDB
options.ChatHistoryProvider = new CosmosChatHistoryProvider(cosmosClient, "my-db", "conversations");
// Custom (implement ChatHistoryProvider from Microsoft Agent Framework)
options.ChatHistoryProvider = new MyRedisChatHistoryProvider(redisConnection);
See the Agent Framework documentation for available providers.
Built-in AI tools
BlazorMentor automatically registers internal tools on the Coordinator. The developer does not declare or register them — they activate based on configuration.
Always-active tools
| Tool | When it is used | Notes |
|---|---|---|
navigate_to |
User asks to go to a page, or an agent needs to navigate before executing UI actions | Discovers pages from [MentorPage] at startup; waits for SignalReady() if HasUIActions = true |
route_to_specialist |
Coordinator delegates a request to a Level-2 agent or remote A2A agent | Auto-registered when at least one [MentorAgent] or MentorRemoteAgent is configured. The coordinator calls it with the target agent's name — the Handoff Workflow routes execution accordingly |
{action_name} |
AI invokes a UI action registered via PageContext.RegisterUIAction*() |
One tool per action — each has its own name, description, and parameter schema. Injected dynamically per-call by UIActionsMiddleware. Only visible when the page is mounted. (Path A only) |
invoke_ui_action |
Generic UI action dispatcher (legacy) | Used only on Path B (pre-built AIAgent). On Path A (ChatClient), each action is its own named tool — this dispatcher is not added. |
Conditional tools (activated by options)
| Tool | Activated by | Notes |
|---|---|---|
remember |
UseMemoryContext = true |
AI saves a user preference or fact silently |
forget_all |
UseMemoryContext = true |
User explicitly asks to reset their memory |
load_skill |
EnableSkills = true |
Returns full markdown instructions for the requested skill |
read_skill_resource |
EnableSkills = true + at least one skill has resource files |
Returns a specific resource file attached to a skill |
Streaming responses
All AI responses are streamed token by token — no waiting for the full response. This is handled automatically by the Orchestrator via RunStreamingAsync. The developer does not need to configure anything.
Response chunks flow through IMentorStateService.OnStreamingChunk → ChatWidget → UI thread-safe update via InvokeAsync(StateHasChanged). When voice output is enabled, the full response is read aloud after streaming completes.
Stop button
While the AI is processing, a ■ Stop button appears above the chat input. Clicking it cancels the current request immediately — the CancellationToken propagated through the entire agent pipeline (RAG, LLM call, tool execution) is cancelled, and any partial response already streamed is kept in the chat.
This works for all request types: standard messages, team deliberations (L3), and post-confirmation responses.
Blazor Server vs Blazor WASM
BlazorMentor registers its core services as Scoped, which behaves differently depending on the hosting model:
| Service | Blazor Web App (Server,AUTO) | Blazor Hybrid (MAUI) |
|---|---|---|
MentorOrchestrator |
One per circuit | One per app instance |
IMentorSessionManager |
One per circuit | One per app instance |
IMentorPageContext |
One per circuit | One per app instance |
MentorMemoryService |
One per circuit | One per app instance |
IMentorMemoryStore |
Singleton (shared) | Singleton (shared) |
MentorDiscoveryService |
Singleton (shared) | Singleton (shared) |
MentorRateLimiter |
Singleton (shared) | Singleton (shared) |
✅ Blazor Web App — Auto render mode (Server + WebAssembly)
The Blazor Web App template with Auto render mode is fully supported, with one constraint: ChatWidget must always render with InteractiveServer.
Why: ChatWidget injects MentorOrchestrator and subscribes to IMentorStateService via in-memory C# events. Both services run on the server. If ChatWidget migrates to WASM (as Auto mode does after the first visit), those injections would fail because the services are not available in the browser process.
The fix: place <ChatWidget /> in the server-side layout and pin it to InteractiveServer explicitly. The rest of your app — pages, components, other features — can freely use Auto, WebAssembly, or Server render modes.
@* MainLayout.razor — server project *@
@using BlazorMentor.Components
<ChatWidget @rendermode="InteractiveServer" />
// Program.cs — server project only
builder.Services.AddBlazorMentor(options =>
{
options.AppName = "My App";
options.ChatClient = new AzureOpenAIClient(endpoint, credential)
.GetChatClient("gpt-4o-mini").AsIChatClient();
options.ScanAssemblies = [typeof(Program).Assembly];
});
⚠️ Never call
AddBlazorMentor()in the client (WASM) project — only in the server project.
Summary — supported render modes
| Template / Render mode | Support | Notes |
|---|---|---|
| Blazor Web App — Server | ✅ | Default. No extra configuration needed. |
| Blazor Web App — Auto | ✅ | Use @rendermode="InteractiveServer" on <ChatWidget />. |
| Blazor Web App — WebAssembly | ❌ | All interactive components run in WASM — no server circuit available. 🚧 Planned via BlazorMentor.Client/BlazorMentor.Server. |
| Blazor Hybrid (MAUI) | ✅ | Fully supported. |
| Blazor WASM Standalone | ❌ | No server process. 🚧 Planned via BlazorMentor.Client/BlazorMentor.Server. |
❌ Blazor WebAssembly — not supported (Standalone and pure WebAssembly render mode)
Blazor WASM is architecturally incompatible with the current version of BlazorMentor. This is not a credentials problem — it is a co-location constraint.
🚧 In development:
BlazorMentor.ClientandBlazorMentor.Servercompanion packages are currently under development to make the library compatible with Blazor WebAssembly. These packages will split the widget (WASM-side) from the orchestrator (server-side) and bridge them via a SignalR-based transport layer, eliminating the in-process coupling described below. No release date is set yet.
Why: the widget and the orchestrator must run in the same process
ChatWidget is injected directly with MentorOrchestrator and communicates with it via an in-memory C# event bus (IMentorStateService). Streaming chunks, busy state, navigation events, and confirmation dialogs all flow as in-process C# events — there is no network layer between them.
Today (same process — works on Blazor Server / Hybrid)
ChatWidget
├─ @inject MentorOrchestrator ← direct DI
└─ subscribe IMentorStateService ← in-memory C# events
├─ OnStreamingChunk
├─ OnBusyChanged
├─ OnNavigationRequested
└─ OnConfirmationRequired
In Blazor WASM, the browser process cannot host MentorOrchestrator — it needs IChatClient which makes direct HTTP calls to the AI endpoint (Azure OpenAI, OpenAI, etc.), exposing credentials in the browser. Moving MentorOrchestrator to the API server breaks the in-process coupling: the widget and the orchestrator would be in different processes, on different machines, and C# events cannot cross that boundary.
The planned BlazorMentor.Client / BlazorMentor.Server split will solve this by replacing the in-memory event bus with a SignalR hub: the widget runs in WASM and subscribes to server-sent events over SignalR, while the orchestrator remains on the server with no credential exposure.
Recommended alternatives (today)
- Blazor Server — widget and orchestrator run in the same server process. Credentials never leave the server. ✅
- Blazor Hybrid (MAUI) — runs natively, no browser sandbox. ✅
Session serialize and restore
IMentorSessionManager exposes two methods for saving and resuming the conversation state (e.g. across page reloads or server restarts):
@inject IMentorSessionManager SessionManager
// Serialize the current session to a JsonElement (e.g. save to localStorage or DB)
JsonElement? snapshot = await SessionManager.SerializeCurrentSessionAsync(agent);
// Restore a previously saved session
if (snapshot.HasValue)
await SessionManager.RestoreSessionAsync(agent, snapshot.Value);
The widget's reset button (inside the chat header) calls ResetSessionAsync() and clears the message list — starting a brand new conversation.
Security
Input safety check
options.EnableSafetyCheck = true; // adds ~200-500ms per message
Before processing each message, the AI evaluates whether it is a prompt injection, jailbreak attempt, or sensitive data extraction. Works in any language automatically.
Rate limiting
options.RateLimitPerUser = 20; // max 20 messages...
options.RateLimitWindowSecs = 60; // ...per minute, per user
⚠️ User identification: With ASP.NET Core authentication configured, each user is identified by their
ClaimTypes.NameIdentifierclaim and gets an independent counter. Without authentication, all sessions share the key"anonymous"— the limit applies globally across all browsers and users, not per individual. For true per-user rate limiting, configure ASP.NET Core authentication.
Role-based actions
[MentorAction(Description = "Approves a budget request", RequiredRoles = ["Finance", "Admin"])]
public async Task<bool> ApproveBudgetAsync(int requestId) { ... }
Roles are verified against the current user's ClaimTypes.Role claim via AuthenticationStateProvider.
Confirmation dialogs
[MentorAction(Description = "Deletes all orders for a customer", RequiresConfirmation = true)]
public async Task<bool> DeleteAllOrdersAsync(int customerId) { ... }
The widget shows a confirmation banner before executing the action. Disable globally with options.RequireConfirmation = false.
Widget customization
Themes
| Value | Description |
|---|---|
MentorTheme.Default |
Light with blue accents (--mentor-primary: #2563EB). Suits most apps. |
MentorTheme.Dark |
Dark background (#1E1E2E). Auto-adapts to dark-mode apps. |
MentorTheme.Minimal |
No shadows, thin borders. Ideal for flat/clean designs. |
MentorTheme.Custom |
No theme applied at all. You define every CSS variable manually in your own stylesheet — full control. |
When using MentorTheme.Custom, override these CSS variables in your stylesheet:
:root {
--bm-bg-base: #ffffff;
--bm-bg-panel: rgba(255,255,255,0.97);
--bm-bg-surface: #f8fafc;
--bm-bg-elevated: #f1f5f9;
--bm-bg-hover: #e2e8f0;
--bm-text-primary: #0f172a;
--bm-text-secondary:#475569;
--bm-text-muted: #94a3b8;
--bm-accent: #2563EB; /* primary accent color */
--bm-accent-dark: #1d4ed8;
--bm-accent-glow: rgba(37,99,235,.35);
--bm-border: rgba(0,0,0,.1);
--bm-border-accent: rgba(37,99,235,.3);
--bm-shadow-panel: 0 8px 40px rgba(0,0,0,.15);
--bm-shadow-fab: 0 8px 32px rgba(37,99,235,.4);
}
Mentorship level
Controls the AI's verbosity and proactivity:
| Value | Behaviour |
|---|---|
MentorshipLevel.Minimal |
Executes silently. No unsolicited explanations or suggestions. Best for expert users. |
MentorshipLevel.Standard |
Balanced. Full responses with contextual suggestions when relevant. (default) |
MentorshipLevel.Proactive |
Guides the user: explains what it did and why, suggests next steps, warns of risks, proposes alternatives. Best for onboarding or less-experienced users. |
Full example
options.Theme = MentorTheme.Dark;
options.Position = ChatPosition.BottomRight; // BottomLeft, TopRight, TopLeft, SideRight, SideLeft
options.PrimaryColor = "#2563EB"; // overrides theme accent color
options.BotName = "Aria";
options.AvatarUrl = "/images/aria-avatar.png";
options.WelcomeMessage = "Hi! I'm Aria, your assistant. How can I help?";
options.InputPlaceholder = "Ask me anything...";
options.MentorshipLevel = MentorshipLevel.Proactive;
options.EnableSuggestions = true; // proactive suggestion chips below the input
options.EnableActionFeedback = true; // "Executing..." visual feedback during tool calls
options.EnableVoiceInput = true; // microphone — Chrome/Edge only, requires HTTPS in production
options.EnableVoiceOutput = true; // text-to-speech — all modern browsers
Built-in widget features (always active, no configuration needed)
| Feature | Description |
|---|---|
| Unread badge | When the widget is closed and the AI responds, a numeric badge appears on the FAB button (capped at 9+) |
| Typing indicator | Animated dots shown while the AI is processing and no streaming text has arrived yet |
| Action bar | While a tool is executing, a pulsing bar shows the tool display name |
| Stop button | A ■ Stop button appears above the input while the AI is processing. Clicking it cancels the current request immediately — the partial response (if any) is kept in the chat |
| Reset button | Button in the widget header that clears the message list and starts a new AgentSession |
| Voice toggle | When EnableVoiceOutput = true and the browser supports Speech Synthesis, a speaker toggle button appears in the header |
| MCP status badge | When ShowMcpStatus = true, a 🔌 N badge in the header shows connected MCP servers. Click to see details |
| A2A status badge | When ShowA2AStatus = true, a badge in the header shows configured remote A2A agents (name + URL). Click to expand the detail bar |
| RAG citations | When ShowRagSources = true, source chips appear below AI messages linking to the retrieved documents |
All configuration options
Core
| Property | Type | Default | Description |
|---|---|---|---|
AppName |
string |
(required) | Application name injected into the system prompt |
AppDescription |
string |
"" |
Domain description for richer AI context |
Language |
MentorLanguage |
English |
Language for AI responses and widget UI (see supported values below) |
MentorshipLevel |
MentorshipLevel |
Standard |
Proactivity level of the AI |
ChatClient |
IChatClient? |
null |
AI provider via IChatClient (Azure OpenAI, OpenAI, Ollama…) |
Agent |
AIAgent? |
null |
Pre-built AIAgent (Foundry, Anthropic…) |
ScanAssemblies |
Assembly[] |
(required) | Assemblies to scan for agents, actions, and pages |
Supported MentorLanguage values:
| Value | Language |
|---|---|
MentorLanguage.English |
English |
MentorLanguage.Italian |
Italian |
MentorLanguage.French |
French |
MentorLanguage.German |
German |
MentorLanguage.Spanish |
Spanish |
MentorLanguage.Portuguese |
Portuguese |
MentorLanguage.Dutch |
Dutch |
MentorLanguage.Polish |
Polish |
MentorLanguage.Japanese |
Japanese |
MentorLanguage.Chinese |
Chinese |
Widget
| Property | Type | Default | Description |
|---|---|---|---|
Theme |
MentorTheme |
Default |
Widget visual theme |
Position |
ChatPosition |
BottomRight |
Widget position |
PrimaryColor |
string? |
null |
Custom hex color |
BotName |
string |
"Mentor AI" |
Bot name in widget header |
AvatarUrl |
string? |
null |
Bot avatar image URL |
WelcomeMessage |
string? |
null |
Initial welcome message |
InputPlaceholder |
string |
"Type a message..." |
Input box placeholder |
EnableSuggestions |
bool |
true |
Proactive suggestion chips |
EnableActionFeedback |
bool |
true |
Visual feedback during tool calls |
EnableVoiceInput |
bool |
false |
Microphone via browser Speech Recognition API |
EnableVoiceOutput |
bool |
false |
Text-to-speech via browser Speech Synthesis API |
Session & History
| Property | Type | Default | Description |
|---|---|---|---|
MaxSessionMessages |
int |
50 |
Max messages in session history |
ChatHistoryProvider |
ChatHistoryProvider? |
null |
Persistent conversation history provider |
Memory
| Property | Type | Default | Description |
|---|---|---|---|
UseMemoryContext |
bool |
false |
Contextual memory across sessions |
MemoryContextCount |
int |
10 |
Number of recent memories in the prompt |
Agent Skills
| Property | Type | Default | Description |
|---|---|---|---|
EnableSkills |
bool |
false |
Enables skill discovery and the load_skill / read_skill_resource tools |
SkillsFolder |
string |
"Skills" |
Folder to scan for file-based skills (SKILL.md). Relative to content root or absolute |
RAG
| Property | Type | Default | Description |
|---|---|---|---|
UseRag |
bool |
false |
Enable RAG. Requires a registered IMentorRagSource |
RagResultCount |
int |
5 |
Number of documents retrieved per query |
RagMinScore |
float |
2 |
Minimum relevance score to include a document. 0 = no filtering. Keyword search: integer-like (2 = two matches); vector/cosine: 0.5–0.75 |
RagSystemPromptTemplate |
string |
"Use the following documents to answer:\n{documents}" |
Prompt template. Use {documents} as placeholder |
ShowRagSources |
bool |
false |
Show citation chips below AI messages in the widget |
MCP
| Property | Type | Default | Description |
|---|---|---|---|
McpServers |
MentorMcpServer[]? |
null |
External MCP servers to connect as L1 tools |
McpServerEnabled |
bool |
false |
Expose BlazorMentor as an MCP server. Also call app.MapBlazorMentorMcp() |
McpServerPath |
string |
"/mcp" |
Default path used as a reference — pass to MapBlazorMentorMcp(path) |
ShowMcpStatus |
bool |
false |
Show MCP server connection status badge in the widget header |
A2A
| Property | Type | Default | Description |
|---|---|---|---|
RemoteAgents |
MentorRemoteAgent[]? |
null |
Remote A2A agents to add to the Handoff workflow. Base URL only — SDK auto-appends /.well-known/agent-card.json |
A2AServerEnabled |
bool |
false |
Expose BlazorMentor as an A2A agent. Also call app.MapBlazorMentorA2A() |
A2AServerPath |
string |
"/a2a" |
Default path used as a reference — pass to MapBlazorMentorA2A(path) |
A2AServerUrl |
string? |
null |
Full public URL of this agent's A2A endpoint (e.g. http://localhost:5001/a2a). Written into the Agent Card SupportedInterfaces so remote consumers can resolve the absolute endpoint. Required when this instance is used as a remote agent by other BlazorMentor instances |
AgentCard |
AgentCardInfo? |
null |
Metadata for the A2A Agent Card (/.well-known/agent-card.json) |
ShowA2AStatus |
bool |
false |
Show a badge in the widget header listing configured remote A2A agents. Click the badge to see agent names and URLs |
Security & Limits
| Property | Type | Default | Description |
|---|---|---|---|
EnableSafetyCheck |
bool |
false |
AI-based prompt injection detection |
MaxMessageLength |
int |
4000 |
Max message length in characters (0 = unlimited) |
RateLimitPerUser |
int |
0 |
Max messages per user per window (0 = disabled). ⚠️ Without authentication all sessions share "anonymous" — limit is global, not per-user |
RateLimitWindowSecs |
int |
60 |
Rate limiting window in seconds |
RequireConfirmation |
bool |
true |
Global on/off for confirmation dialogs |
IncludeWorkflowExceptionDetails |
bool |
false |
Include exception stack traces in agent responses. Never enable in production |
Extensibility
| Extension point | How |
|---|---|
| Custom memory store | Implement IMentorMemoryStore, register before AddBlazorMentor() |
| Custom history provider | Set options.ChatHistoryProvider |
| Custom AI provider | Set options.ChatClient or options.Agent |
| Custom agent instructions | Set Instructions on [MentorAgent] or [TeamMember] |
| Custom RAG source | Implement IMentorRagSource, register before AddBlazorMentor() |
| Custom MCP tools | Add entries to options.McpServers (HTTP or stdio transport) |
| Custom remote agents | Add entries to options.RemoteAgents (A2A protocol) |
| File-based skills | Create Skills/{name}/SKILL.md in the project root with EnableSkills = true |
| Dual-role skill class | Decorate with [MentorSkill] + add [Description]/[MentorAction] methods — knowledge + tools in one class. Register in DI when the class has methods |
| Knowledge-only skill | Decorate with [MentorSkill] — no methods, no DI registration needed |
| Page UI actions (typed) | Use RegisterUIAction<TParam> or RegisterUIActionAsync<TParam> for automatic JSON deserialization |
Requirements
| Requirement | Version |
|---|---|
| .NET | 10.0 |
| Blazor | Web App (Server or Auto render mode), or Hybrid (MAUI) |
| Microsoft.Agents.AI | 1.6.1 |
| Microsoft.Agents.AI.Workflows | 1.6.1 |
| Microsoft.Agents.AI.Hosting.A2A.AspNetCore | 1.6.1-preview |
| Microsoft.Extensions.AI | 10.6.0 |
| ModelContextProtocol.AspNetCore | 1.3.0 |
❌ Blazor WebAssembly (Standalone or pure WebAssembly render mode) is not supported in the current version. 🚧 Support is in development via
BlazorMentor.Client/BlazorMentor.Server. ✅ Blazor Web App with Auto render mode is supported — see Blazor Server vs Blazor WASM for the one required constraint.
License
MIT — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Azure.AI.OpenAI (>= 2.9.0-beta.1)
- Azure.AI.Projects (>= 2.1.0-beta.2)
- Azure.Identity (>= 1.21.0)
- Microsoft.Agents.AI (>= 1.6.1)
- Microsoft.Agents.AI.Foundry (>= 1.6.1-preview.260514.1)
- Microsoft.Agents.AI.Hosting.A2A.AspNetCore (>= 1.6.1-preview.260514.1)
- Microsoft.Agents.AI.Workflows (>= 1.6.1)
- Microsoft.AspNetCore.Components.Authorization (>= 10.0.3)
- Microsoft.AspNetCore.Components.Web (>= 10.0.3)
- Microsoft.Extensions.AI (>= 10.6.0)
- Microsoft.Extensions.AI.OpenAI (>= 10.6.0)
- ModelContextProtocol.AspNetCore (>= 1.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0-preview | 49 | 6/1/2026 |
| 0.1.0-preview.4 | 53 | 5/19/2026 |
| 0.1.0-preview.3 | 46 | 5/19/2026 |
| 0.1.0-preview.2 | 53 | 5/19/2026 |
| 0.1.0-preview.1 | 51 | 5/17/2026 |
## New Features
* Added MCP Server support — expose BlazorMentor as an MCP server (`McpServerEnabled`, `McpServerPath`, `ShowMcpStatus`)
* Added MCP Client support — consume external MCP servers configured via `McpServers`
* Added A2A Server support — expose BlazorMentor as an A2A agent (`A2AServerEnabled`, `A2AServerPath`, `A2AServerUrl`, `ShowA2AStatus`)
* Added A2A Consumer support — connect to remote A2A agents via the Handoff workflow (`RemoteAgents`, `MentorRemoteAgent`)
* Added Agent Skills — progressive disclosure of domain expertise (`EnableSkills`, `SkillsFolder`, `[MentorSkill]`)
* Added RAG support — Retrieval-Augmented Generation via `IMentorRagSource` (`UseRag`, `RagResultCount`, `RagMinScore`, `ShowRagSources`)
* Added Level 3 GroupChat Teams — multi-agent deliberation before action (`[MentorTeam]`, `[TeamMember]`)
* Added Safety Check — LLM-based prompt injection and jailbreak detection (`EnableSafetyCheck`)
* Added Rate Limiting per user (`RateLimitPerUser`)
* Added Memory Context — automatic user preference persistence (`UseMemoryContext`, `MemoryContextCount`)
* Added typed and async UI Action overloads — `RegisterUIAction<T>`, `RegisterUIActionAsync`, `RegisterUIActionAsync<T>`
* Added `IncludeWorkflowExceptionDetails` for surfacing internal errors in development
## Improvements
* Improved system prompt — added Communication Style block, natural memory instructions, cleaner L2/L3 agent prompts
* Remote agents now appear in coordinator system prompt so the LLM knows when to delegate
* `MentorRemoteAgent` now supports per-agent auth headers (`Headers`) and role gating (`RequiredRoles`)
## Bug Fixes
* Fixed `UriFormatException` in A2A consumer when remote agent card contains relative `SupportedInterfaces` URLs
* Fixed coordinator not routing to remote A2A agents after successful connection
## Documentation
* Improved README — full coverage of all options, attributes, built-in tools, enums, models, and WASM roadmap note