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

- URL: https://docs.mentoragent.net/concepts/channels
- Verified with: MentorAgent 1.0.0
- Last updated: 2026-10-13
- Packages: Novior.MentorAgent, Novior.MentorAgent.Server, Novior.MentorAgent.Blazor

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](https://docs.mentoragent.net/concepts/agent-levels), 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

| 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](https://docs.mentoragent.net/configure/blazor-server) |
| 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](https://docs.mentoragent.net/configure/blazor-webassembly) |
| .NET MAUI | `<ChatWidget />` in the Blazor Hybrid layout | `Novior.MentorAgent` | In-process, inside the app | The user, in the widget | [.NET MAUI](https://docs.mentoragent.net/configure/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](https://docs.mentoragent.net/configure/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](https://docs.mentoragent.net/configure/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](https://docs.mentoragent.net/configure/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](https://docs.mentoragent.net/configure/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`](https://docs.mentoragent.net/reference/options#mcpservers), [`RemoteAgents`](https://docs.mentoragent.net/reference/options#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](https://docs.mentoragent.net/get-started/packages) lists what each package contains.

## 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

### 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

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](https://docs.mentoragent.net/configure/blazor-webassembly#security-and-roles).

`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

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](https://docs.mentoragent.net/configure/maui) gives the steps. A MAUI app can instead be a client of the SignalR hub on a server you host.

### 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](https://docs.mentoragent.net/configure/any-front-end#native-apps-and-other-languages). 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

`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

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](https://docs.mentoragent.net/concepts/agent-levels#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](https://docs.mentoragent.net/concepts/governance#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`](https://docs.mentoragent.net/reference/options#ontoolresult) and counts against [`RateLimitPerUser`](https://docs.mentoragent.net/reference/options#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](https://docs.mentoragent.net/configure/mcp-server#complete-configuration).

### 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 `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](https://docs.mentoragent.net/configure/secure-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

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

- **MCP servers.** The servers in [`McpServers`](https://docs.mentoragent.net/reference/options#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`](https://docs.mentoragent.net/reference/options#remoteagents) join the handoff workflow like [specialists](https://docs.mentoragent.net/concepts/agent-levels#level-2-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 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:

```csharp title="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](https://docs.mentoragent.net/concepts/governance#where-nobody-can-answer) summarizes what each channel does with an action that needs a role or a confirmation.

> **Caution:**
> Every endpoint in this example is open until you protect it. The hub, `/mentor/chat` and `/a2a` run turns on your model for anyone who can reach them, and `/mcp` runs your actions. See [Secure the endpoints](https://docs.mentoragent.net/configure/secure-endpoints).

---

- Previous: [Roles, confirmation and governance](https://docs.mentoragent.net/concepts/governance.md)
- Next: [Model providers](https://docs.mentoragent.net/configure/model-providers.md)
