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.
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.
The channels at a glance
Section titled “The channels at a glance”| Channel | Client | Packages | Transport | Who confirms | Configure |
|---|---|---|---|---|---|
| Blazor Server | <ChatWidget /> in your layout | Novior.MentorAgent | In-process, inside the Blazor circuit | The user, in the widget | Blazor Server app |
| Blazor WebAssembly and Auto | <ChatWidget /> in the client project | Novior.MentorAgent.Server on the server, Novior.MentorAgent.Blazor in the client | SignalR hub, and HTTP for confirmations | The user, in the widget | Blazor WebAssembly and Auto |
| .NET MAUI | <ChatWidget /> in the Blazor Hybrid layout | Novior.MentorAgent | In-process, inside the app | The user, in the widget | .NET MAUI |
| SignalR hub | Your own UI: React, Angular, Vue or any JavaScript app, or a .NET, Java or other SignalR client | Novior.MentorAgent.Server | SignalR at /mentor-hub | The user, in your client, through POST /mentor/approve | Any front end |
| Server-Sent Events | fetch, curl or any HTTP client, in any language | Novior.MentorAgent.Server | GET or POST /mentor/chat | The user, in your client, through POST /mentor/approve | Any front end |
| MCP server | Claude Desktop, VS Code or any MCP client | Novior.MentorAgent.Server | MCP over HTTP at /mcp | Nobody: actions that need confirmation are not published | MCP server |
| Agent-to-Agent (A2A) server | Other agents, and orchestrators that coordinate several agents | Novior.MentorAgent.Server | A2A at /a2a, agent card at /.well-known/agent-card.json | Nobody: actions that need confirmation are refused | A2A server |
| MCP and A2A clients | Your agent, calling other systems | Novior.MentorAgent | Outgoing MCP and A2A | Your user, for the MCP tools you put behind a confirmation | McpServers, 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.
Which channel fits your client
Section titled “Which channel fits your client”- 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 channels one by one
Section titled “The channels one by one”Blazor Server
Section titled “Blazor Server”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.
Blazor WebAssembly and Auto
Section titled “Blazor WebAssembly and Auto”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.
.NET MAUI
Section titled “.NET MAUI”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
RequiredRolesis refused unless the app registers anAuthenticationStateProviderthat 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.
SignalR hub
Section titled “SignalR hub”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.
Server-Sent Events
Section titled “Server-Sent Events”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.
MCP server
Section titled “MCP server”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 neitherReadOnly = truenorRequiresConfirmation: see Confirmation by default; - actions with
RequiredRolesare published only to a caller thatMcpCallerPrincipalidentifies 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.
A2A server
Section titled “A2A 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
RequiredRolesrun 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.
MCP and A2A clients
Section titled “MCP and A2A clients”In the other direction, your agent uses what other systems offer:
- MCP servers. The servers in
McpServersadd their tools to the coordinator, next to your actions. To ask your user for a confirmation before one of their tools runs, useMentorMcpServer.RequiresConfirmationorRequiresApproval. - Remote agents. The agents in
RemoteAgentsjoin the handoff workflow like specialists, and need aChatClient. A remote agent has no role check of its own: any user who reaches your agent can reach it. To restrict it, protect the/a2aendpoint 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 host, several channels
Section titled “One host, several channels”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:
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.jsonFor 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.
See also
Section titled “See also”- 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.
