MentorAgent Docs
Menu

How-to guidePackage: Novior.MentorAgent

Blazor Server app

Configure MentorAgent in a Blazor Web App with Server interactivity: services, the options that matter, the widget and authentication.

View as Markdown
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.

  • 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.

.NET CLI

Terminal
dotnet add package Novior.MentorAgent

PackageReference

YourApp.csproj
<ItemGroup>
  <PackageReference Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>

Central Package Management

Directory.Packages.props
<ItemGroup>
  <PackageVersion Include="Novior.MentorAgent" Version="1.0.0" />
</ItemGroup>
YourApp.csproj
<ItemGroup>
  <PackageReference Include="Novior.MentorAgent" />
</ItemGroup>

File-based app

app.cs
#:package Novior.MentorAgent@1.0.0

Novior.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:

Terminal
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.6.0

For 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.

  1. Keep the model key out of the source code:

    Terminal
    dotnet user-secrets init
    dotnet user-secrets set OpenAI:Key <your-key>
  2. Register MentorAgent in Program.cs, next to the template's AddRazorComponents:

    Program.cs
    using Novior.MentorAgent.Abstractions.Models;   // MentorLanguage, MentorTheme, ChatPosition, MentorshipLevel
    using Novior.MentorAgent.Extensions;            // AddMentorAgent
    using Microsoft.Extensions.AI;           // AsIChatClient
    using OpenAI;                            // OpenAIClient
    Program.cs
    builder.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 and ScanAssemblies are required. The ChatClient line connects OpenAI: for Azure OpenAI, Ollama or Foundry, take the line from Model providers. ScanAssemblies is where MentorAgent looks for your actions, agents and pages. Register the services that hold your actions as usual, for example with AddScoped.

  3. Make the app interactive as a whole. In Components/App.razor these are the two lines the template writes when you choose Interactivity location: Global (dotnet new blazor --interactivity Server --all-interactive):

    Components/App.razor
    <HeadOutlet @rendermode="InteractiveServer" />
    <Routes @rendermode="InteractiveServer" />
  4. Add the widget to the layout:

    Components/Layout/MainLayout.razor
    @using Novior.MentorAgent.Abstractions.Components
    <ChatWidget />

    The widget injects its own stylesheet and script. You do not change App.razor for 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.

A configuration for an application with real users, still in Program.cs:

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();
});
OptionWhy it matters on Blazor Server
AppDescriptionA 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
LanguageThe language of the answers and of the widget's own texts
BotName, WelcomeMessage, Theme, PrimaryColor, PositionThe 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
MentorshipLevelHow much the agent explains and suggests: Minimal, Standard or Proactive
MaxMessageLengthLonger messages are refused before the model is called, so they cost nothing
RateLimitPerUserCounted per signed-in user. Without sign-in it is counted per circuit, so a reload starts a new count
EnableSafetyCheckOne 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
IncludeWorkflowExceptionDetailsPuts 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:

CSS
: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.

  • Roles. MentorAgent reads the circuit's signed-in user through AuthenticationStateProvider and checks RequiredRoles against the user's role claims. Set up authentication as in any Blazor Web App. Without it every user is anonymous, and every action with RequiredRoles is 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 neither ReadOnly = true nor RequiresConfirmation asks too. Declare ReadOnly = true on an action that only reads. If the circuit closes while the box is open, the turn ends with it. RequireConfirmation = false turns 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 AnonymousIdentity at its default, PerSession, each circuit has its own memory and its own rate-limit count. Use Shared only 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 neither ReadOnly = true nor RequiresConfirmation. An A2A task runs as the user of its request, signed in only when the endpoint requires authentication: see Where nobody can answer.
  • 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 Development environment. Elsewhere, read the key from an environment variable or a secret store.
  • Every action with RequiredRoles is refused. Nobody is signed in, or the user's roles are not role claims. Check what AuthenticationStateProvider returns for that user.
  • 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.