Blazor Server app
Configure MentorAgent in a Blazor Web App with Server interactivity: services, the options that matter, the widget and authentication.
On this page
This page adds MentorAgent and its chat widget to a Blazor Web App with Server interactivity. The widget and the agent run in the same server process and talk through in-process events: you install one package and map no endpoint.
When to choose it
Section titled “When to choose it”- Your app is a Blazor Web App with Server interactivity, or you can make the layout that hosts the widget interactive on the server.
- You want the simplest setup. The model key stays on the server, and the browser only receives the rendered UI.
For the other hosts see Blazor WebAssembly and Auto, .NET MAUI and Any front end, for React, Angular, Vue or a native mobile app. Channels compares them all.
Install the packages
Section titled “Install the packages”.NET CLI
dotnet add package Novior.MentorAgentPackageReference
<ItemGroup>
<PackageReference Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>Central Package Management
<ItemGroup>
<PackageVersion Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup><ItemGroup>
<PackageReference Include="Novior.MentorAgent" />
</ItemGroup>File-based app
#:package Novior.MentorAgent@1.0.0Novior.MentorAgent brings the widget's components with it, but it does not include a model provider package. The code on this page uses OpenAI, so add its package too:
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.6.0For another provider, see Model providers. You do not need Novior.MentorAgent.Server to run the widget on this host. Add it only if the same app serves MCP or A2A: see MCP server and A2A server.
Minimal configuration
Section titled “Minimal configuration”Keep the model key out of the source code:
dotnet user-secrets init dotnet user-secrets set OpenAI:Key <your-key>Register MentorAgent in
Program.cs, next to the template'sAddRazorComponents:using Novior.MentorAgent.Abstractions.Models; // MentorLanguage, MentorTheme, ChatPosition, MentorshipLevel using Novior.MentorAgent.Extensions; // AddMentorAgent using Microsoft.Extensions.AI; // AsIChatClient using OpenAI; // OpenAIClientbuilder.Services.AddMentorAgent(options => { options.AppName = "Case management"; options.ChatClient = new OpenAIClient(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Set OpenAI:Key with dotnet user-secrets.")) .GetChatClient("gpt-4o-mini") .AsIChatClient(); options.ScanAssemblies = [typeof(Program).Assembly]; });AppName, a model andScanAssembliesare required. TheChatClientline connects OpenAI: for Azure OpenAI, Ollama or Foundry, take the line from Model providers.ScanAssembliesis where MentorAgent looks for your actions, agents and pages. Register the services that hold your actions as usual, for example withAddScoped.Make the app interactive as a whole. In
Components/App.razorthese are the two lines the template writes when you choose Interactivity location: Global (dotnet new blazor --interactivity Server --all-interactive):<HeadOutlet @rendermode="InteractiveServer" /> <Routes @rendermode="InteractiveServer" />Add the widget to the layout:
@using Novior.MentorAgent.Abstractions.Components <ChatWidget />The widget injects its own stylesheet and script. You do not change
App.razorfor them.
Run the app: the widget button appears in the bottom-right corner of every page that uses the layout.
Each circuit, the connection that Blazor Server keeps with each browser tab, gets its own coordinator, conversation and page context. Memory stores and the rate limiter are shared by the whole process.
Complete configuration
Section titled “Complete configuration”A configuration for an application with real users, still in Program.cs:
builder.Services.AddMentorAgent(options =>
{
// Required
options.AppName = "Case management";
options.ChatClient = new OpenAIClient(builder.Configuration["OpenAI:Key"]
?? throw new InvalidOperationException("Set OpenAI:Key with dotnet user-secrets."))
.GetChatClient("gpt-4o-mini")
.AsIChatClient();
options.ScanAssemblies = [typeof(Program).Assembly];
// What the assistant knows about the application
options.AppDescription = "Support case management: case status, notes and closure";
options.Language = MentorLanguage.English;
options.MentorshipLevel = MentorshipLevel.Standard;
// The widget
options.BotName = "Case assistant";
options.WelcomeMessage = "Ask me about your support cases.";
options.Theme = MentorTheme.Minimal; // the light theme
options.PrimaryColor = "#2563eb";
options.Position = ChatPosition.BottomRight;
// Limits and safety
options.MaxMessageLength = 2000;
options.RateLimitPerUser = 20; // messages per window, per user
options.EnableSafetyCheck = true;
// Exception details in replies: never in production
options.IncludeWorkflowExceptionDetails = builder.Environment.IsDevelopment();
});Options that matter here
Section titled “Options that matter here”| Option | Why it matters on Blazor Server |
|---|---|
AppDescription | A real sentence about your application. The prompt and the safety check use it to tell ordinary questions from attempts to extract the prompt or secrets |
Language | The language of the answers and of the widget's own texts |
BotName, WelcomeMessage, Theme, PrimaryColor, Position | The widget reads them from MentorOptions on this host. Theme = Minimal is the light theme. PrimaryColor takes one six-digit hex color and derives the other shades from it: pressed, glow, accent border and button shadow. A Content Security Policy without style-src 'unsafe-inline' blocks the colors that Theme = Dark, Theme = Minimal and PrimaryColor set: see Content Security Policy |
MentorshipLevel | How much the agent explains and suggests: Minimal, Standard or Proactive |
MaxMessageLength | Longer messages are refused before the model is called, so they cost nothing |
RateLimitPerUser | Counted per signed-in user. Without sign-in it is counted per circuit, so a reload starts a new count |
EnableSafetyCheck | One extra model call checks each message before the answer, for prompt injection, jailbreaks and attempts to extract the system prompt or credentials. Point ClassifierChatClient at a small model to keep it fast |
IncludeWorkflowExceptionDetails | Puts specialist and team errors in the reply the user reads: development only |
Every option, with its type and default, is in Configuration options; the widget's own are under Widget.
Colors, sizes and fonts. Beyond Theme and PrimaryColor, the widget reads CSS variables that start with --bm-. Override them in a stylesheet loaded after the widget's:
:root {
--bm-panel-w: 480px;
--bm-font: 'Inter', system-ui, sans-serif;
}The widget requests nothing from another origin. The package ships its two fonts, Sora and JetBrains Mono, under the SIL Open Font License 1.1. To use your own fonts, override --bm-font and --bm-font-mono: the browser then never downloads the packaged files. The policy that the widget needs is in Content Security Policy.
Security and roles
Section titled “Security and roles”- Roles. MentorAgent reads the circuit's signed-in user through
AuthenticationStateProviderand checksRequiredRolesagainst the user's role claims. Set up authentication as in any Blazor Web App. Without it every user is anonymous, and every action withRequiredRolesis refused. The order of the checks is in Two checks, in this order. - Confirmation. Before an action that asks for confirmation runs, the widget shows the confirmation box. An action asks when it declares
RequiresConfirmation = true. A[MentorAction]that declares neitherReadOnly = truenorRequiresConfirmationasks too. DeclareReadOnly = trueon an action that only reads. If the circuit closes while the box is open, the turn ends with it.RequireConfirmation = falseturns off these boxes, but never the role checks. The only approvals that still appear are those asked by the MCP servers that the model provider calls directly (HostedMcpServers). - Anonymous users. With
AnonymousIdentityat its default,PerSession, each circuit has its own memory and its own rate-limit count. UseSharedonly for an application with a single user. - Other channels. If the same app also serves MCP or A2A, with
Novior.MentorAgent.Server, actions that ask for confirmation are withheld there. That includes a[MentorAction]that declares neitherReadOnly = truenorRequiresConfirmation. An A2A task runs as the user of its request, signed in only when the endpoint requires authentication: see Where nobody can answer.
Common errors
Section titled “Common errors”- The widget button appears, but nothing happens when you click it. The layout is rendered statically, so the widget has no circuit. Make the app interactive (step 3 above), or give the widget its own render mode as in option A of WebAssembly and Auto. A static page also cannot hand the agent its page context or UI actions.
- The app stops at startup with
InvalidOperationException. The message says what is missing: "Configure ChatClient or Agent", "AppName is required" or "ScanAssemblies cannot be empty". MentorAgent checks the options when the host starts, before the first request. - The key is not found outside your machine. User secrets are loaded only in the
Developmentenvironment. Elsewhere, read the key from an environment variable or a secret store. - Every action with
RequiredRolesis refused. Nobody is signed in, or the user's roles are not role claims. Check whatAuthenticationStateProviderreturns for that user.
See also
Section titled “See also”- Channels: how clients reach the agent — Which channel fits your client: the Blazor or MAUI widget, SignalR or SSE for JavaScript and mobile apps, and MCP or A2A for other agents and AI tools.
- Add MentorAgent to an existing app — Add MentorAgent to a Blazor Web App you already have: install the package, register the services, mark your methods and place the widget.
- Model providers — Connect OpenAI, Azure OpenAI, Ollama and other local models, or your own AIAgent, and choose models that support tool calling.
- Configuration options — Every MentorOptions property with its type, default and effect, grouped by area, plus the options of the WebAssembly client and the YAML loader.
