MentorAgent Docs
Menu

ConceptPackages: Novior.MentorAgent, Novior.MentorAgent.Server, Novior.MentorAgent.Blazor

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.

View as Markdown
On this page

A channel is the way a client reaches your agent. The agent always runs in your application: in your ASP.NET Core host, or inside your .NET MAUI app. The client does not have to be .NET: a React, Angular or Vue app, a native mobile app, a script or another agent reaches the same actions.

Your actions and their checks are the same on every channel. On every channel except one, the coordinator, the main agent, answers the request. The exception is the Model Context Protocol (MCP) server: there the client's own model calls your actions directly. What changes from channel to channel is the client, the transport, where the conversation lives and who can confirm an action.

ChannelClientPackagesTransportWho confirmsConfigure
Blazor Server<ChatWidget /> in your layoutNovior.MentorAgentIn-process, inside the Blazor circuitThe user, in the widgetBlazor Server app
Blazor WebAssembly and Auto<ChatWidget /> in the client projectNovior.MentorAgent.Server on the server, Novior.MentorAgent.Blazor in the clientSignalR hub, and HTTP for confirmationsThe user, in the widgetBlazor WebAssembly and Auto
.NET MAUI<ChatWidget /> in the Blazor Hybrid layoutNovior.MentorAgentIn-process, inside the appThe user, in the widget.NET MAUI
SignalR hubYour own UI: React, Angular, Vue or any JavaScript app, or a .NET, Java or other SignalR clientNovior.MentorAgent.ServerSignalR at /mentor-hubThe user, in your client, through POST /mentor/approveAny front end
Server-Sent Eventsfetch, curl or any HTTP client, in any languageNovior.MentorAgent.ServerGET or POST /mentor/chatThe user, in your client, through POST /mentor/approveAny front end
MCP serverClaude Desktop, VS Code or any MCP clientNovior.MentorAgent.ServerMCP over HTTP at /mcpNobody: actions that need confirmation are not publishedMCP server
Agent-to-Agent (A2A) serverOther agents, and orchestrators that coordinate several agentsNovior.MentorAgent.ServerA2A at /a2a, agent card at /.well-known/agent-card.jsonNobody: actions that need confirmation are refusedA2A server
MCP and A2A clientsYour agent, calling other systemsNovior.MentorAgentOutgoing MCP and A2AYour user, for the MCP tools you put behind a confirmationMcpServers, RemoteAgents

The server side of the MCP and A2A channels is in the Novior.MentorAgent.Server package, together with the SignalR hub, Server-Sent Events and the HTTP endpoints that go with them. A Blazor Server app that publishes MCP or A2A, but has no hub, adds Novior.MentorAgent.Server and registers only what it needs: AddMentorAgentMcp() or AddMentorAgentA2A(). The MCP and A2A clients stay in Novior.MentorAgent.

Choose your packages lists what each package contains.

  • A Blazor app: the widget. Blazor Server is the simplest; choose WebAssembly when the widget must run in the browser.
  • A .NET MAUI app: the widget in a Blazor Hybrid app, built for Android and Windows. The app can instead be a .NET client of the SignalR hub on a server you host.
  • A React, Angular or Vue front end: the SignalR hub for a chat, or Server-Sent Events for single questions.
  • A native mobile or desktop app, or a script: the hub, with the SignalR client of its platform, or Server-Sent Events from any language.
  • An AI assistant such as Claude Desktop or VS Code: the MCP server, which publishes your actions to it.
  • Another agent or orchestrator: the A2A server, which answers it with your agent.
  • Your agent needs tools or agents of other systems: the MCP and A2A clients.

The widget and the agent run in the same server process and talk through in-process events. The browser reaches the widget through the Blazor circuit, like any interactive component. A circuit is the connection that Blazor Server keeps with each browser tab. So the widget must be in a layout that renders interactively: on a page rendered statically, its button appears but does nothing. Each circuit has its own coordinator and its own conversation, and the model's key never leaves the server.

In an app with Auto interactivity, the simplest option is to give the widget its own InteractiveServer render mode, that is, to pin it to the server. The widget then uses this channel, while the rest of the app can run in WebAssembly.

This needs a layout that the server renders, so it works with the template's Interactivity location set to Per page/component. With Global, the layout runs in WebAssembly and cannot host a server component: use Novior.MentorAgent.Blazor, described next.

When the widget itself runs in the browser, the client project uses Novior.MentorAgent.Blazor and the server project Novior.MentorAgent.Server. The agent still runs on the server: the widget reaches it over the SignalR hub, and sends confirmations and Stop over HTTP. Specialists, retrieval-augmented generation (RAG) over your documents, MCP, A2A, memory and confirmation all run on the server, and the model's key never reaches the browser. The hub reads the signed-in user from the connection, so role checks work in a Blazor Web App too: see Blazor WebAssembly and Auto.

AddMentorAgent() goes only in the server project. In a standalone app the host page links the widget's stylesheet and script itself; in a Blazor Web App the prerendered widget adds them. A client served from another origin needs CORS on the server and an absolute hub URL.

In a .NET MAUI Blazor Hybrid app, the whole of MentorAgent runs inside the app, with the Novior.MentorAgent package alone. Such an app builds for Android and Windows. iOS and Mac Catalyst have not been checked. The widget needs no render mode, because a BlazorWebView is always interactive. There is no web server and no ASP.NET Core user, which has consequences:

  • an action with RequiredRoles is refused unless the app registers an AuthenticationStateProvider that returns the signed-in user;
  • there is one coordinator and one conversation for the whole app, until it restarts;
  • the dashboard, the MCP server and the A2A agent need a web host and are not available: the MCP server and the A2A agent are in Novior.MentorAgent.Server, which a MAUI app does not use. The MCP and A2A clients work.

The MAUI template needs the widget's stylesheet and script in wwwroot/index.html: .NET MAUI gives the steps. A MAUI app can instead be a client of the SignalR hub on a server you host.

Novior.MentorAgent.Server maps a SignalR hub at /mentor-hub for your own clients: JavaScript with @microsoft/signalr, .NET with Microsoft.AspNetCore.SignalR.Client, or the Java and Swift clients of native apps, listed in Any front end. The hub carries the widget's events: streamed text, actions, confirmation requests, navigation, UI actions, team activity and status.

The conversation lasts as long as the connection. A reconnect is a new connection and a new conversation, unless the client saves and restores it through /mentor/session. The snapshot that GET /mentor/session returns is opaque. The client stores it as text and sends it back unchanged, and only the user who saved it can restore it.

Confirmations and Stop go over HTTP, to POST /mentor/approve and POST /mentor/cancel, never as hub calls. The reason: SignalR runs one call per connection at a time, so a hub call made during an answer would wait forever. While an authenticated connection is open, only its user can answer. When the connection closes, the turn running in it ends too, with its confirmation. POST /mentor/approve answers 404 when no turn waits for the answer any more: treat it as "nothing left to answer" and close your dialog.

GET /mentor/chat streams the answer as Server-Sent Events, with no client library: fetch or curl is enough. POST to the same path takes a JSON body, for messages with images. Each request is its own conversation: the model does not see earlier turns, and there is no page context and no UI actions.

The frames carry the text, the end of the turn, errors, cards, navigation and confirmation requests. A confirmation is answered on POST /mentor/approve, and the stream resumes. Use Server-Sent Events for single questions; for a chat, use the hub.

With McpServerEnabled on, AddMentorAgentMcp() and MapMentorAgentMcp(), your application is an MCP server at /mcp. Both methods are in Novior.MentorAgent.Server, and AddMentorAgentServer() calls the first one. MCP clients, such as Claude Desktop, VS Code or Cursor, see your actions as tools and call them directly. The client's model decides which tool to call: your agent and your model are not involved.

Only Level 1 actions are published: the methods of specialists are not. Of those:

  • actions that need confirmation are never published, because nobody can answer on the other side. This includes a [MentorAction] that declares neither ReadOnly = true nor RequiresConfirmation: see Confirmation by default;
  • actions with RequiredRoles are published only to a caller that McpCallerPrincipal identifies as holding the role.

MentorAgent creates each action class once for the whole application, and every MCP caller shares that instance: keep action classes stateless.

Each call goes through OnToolResult and counts against RateLimitPerUser, like a tool that the model calls. The endpoint has no CORS policy, because MCP clients are not browsers. A client that runs in a browser needs an opt-in: see MCP server.

With A2AServerEnabled on, AddMentorAgentA2A() and MapMentorAgentA2A(), other agents can discover your agent and call it. Both methods are in Novior.MentorAgent.Server, and AddMentorAgentServer() calls the first one. The agent card at /.well-known/agent-card.json describes your agent to them. It lists each action that needs neither a role nor a confirmation, as an A2A skill. /a2a receives tasks: A2A calls each request a task.

Each task runs a full turn on your model, in a new session. The caller receives the complete reply at the end, not a stream. Nobody can confirm on a task:

  • actions that need confirmation are refused at once;
  • actions with RequiredRoles run only for an authenticated caller who holds the role. An anonymous caller is refused.

A task runs as the user of the request. That user is signed in only if your host authenticates the /a2a endpoint: see Secure the endpoints. By default, an anonymous caller gets a new identity for each task, so RateLimitPerUser does not hold across tasks.

A request that arrived over A2A is answered by your agent, never passed on to the remote agents it knows.

In the other direction, your agent uses what other systems offer:

  • MCP servers. The servers in McpServers add their tools to the coordinator, next to your actions. To ask your user for a confirmation before one of their tools runs, use MentorMcpServer.RequiresConfirmation or RequiresApproval.
  • Remote agents. The agents in RemoteAgents join the handoff workflow like specialists, and need a ChatClient. A remote agent has no role check of its own: any user who reaches your agent can reach it. To restrict it, protect the /a2a endpoint on the remote host and give roles to the actions there, or do not configure it on hosts whose users may not use it.

One ASP.NET Core host can serve several channels at once. This one serves the SignalR hub and Server-Sent Events for your own clients, the MCP server for AI assistants and the A2A server for other agents. AddMentorAgentServer() also registers the MCP server and the A2A agent, because their options are on:

Program.cs
builder.Services.AddScoped<CaseService>();
builder.Services.AddMentorAgent(options =>
{
    options.AppName = "Case management";
    options.AppDescription = "Tracks customer support cases and their status";
    options.ChatClient = chatClient;
    options.ScanAssemblies = [typeof(CaseService).Assembly];
    options.McpServerEnabled = true;    // MCP server
    options.A2AServerEnabled = true;    // A2A server
});
builder.Services.AddMentorAgentServer();   // SignalR hub, Server-Sent Events, MCP server and A2A agent

var app = builder.Build();
app.MapMentorAgentServer();     // /mentor-hub, /mentor/chat, /mentor/approve
app.MapMentorAgentMcp();        // /mcp
app.MapMentorAgentA2A();        // /a2a and /.well-known/agent-card.json

For the Blazor channels, you need only AddMentorAgent and the widget in the layout. Roles, confirmation and governance summarizes what each channel does with an action that needs a role or a confirmation.

  • Roles, confirmation and governance — How MentorAgent checks roles and asks for confirmation before an action runs, on every channel, and what it leaves to your code.
  • Choose your packages — Which MentorAgent package to install for Blazor, MAUI, or a backend for React, Angular, Vue and mobile apps, and what each one contains.
  • Secure the endpoints — Protect the SignalR hub, Server-Sent Events, /mcp and /a2a endpoints with authentication and CORS before you expose them.