---
title: Audience routes
description: Every MCP tool carries an audience tag that determines which route it surfaces on — /mcp, /protocol, or /ops. This page explains the four audiences and the routing rules.
---

A single ggui server exposes several distinct MCP surfaces, not one. Each tool the server registers carries an **audience tag** that decides which HTTP route the tool appears on. The agent runtime sees one slice of the tool set; design-time clients see another; operators see a third. The audience tag is the structural mechanism that keeps those slices honest.

This page explains the four audiences, the three routes they map to, and the placement rules for every new handler.

## Why audiences exist

Different callers connect to a ggui server for different reasons:

- An **LLM agent** in the middle of a chat turn needs `ggui_render`, `ggui_handshake`, blueprint search, and not much else.
- The **view runtime** — the iframe-runtime, relayed through the host's `tools/call` — and first-party backend libraries (e.g. `@ggui-ai/agent-server`) need callbacks like `ggui_runtime_sync_context` and `ggui_runtime_submit_action`.
- A **design-time client** authoring blueprints needs static spec/discovery tools like `ggui_protocol_describe_blueprint_format` once, then never again.
- An **operator** managing apps, keys, and orgs needs administrative tools that should never appear in an LLM's `tools/list`.

Putting all of those tools on one `/mcp` surface burns agent context on tools the agent will never call, and exposes operator surfaces to runtimes that shouldn't see them. The audience tag splits the surface so each caller's `tools/list` is exactly the tools that caller cares about.

:::note
The audience tag is the structural source of truth. The wire-name prefix (`ggui_*`, `ggui_runtime_*`, `ggui_protocol_*`, `ggui_ops_*`) is a parallel signal for agent UX — see [Wire-name prefix discipline](#wire-name-prefix-discipline) below.
:::

## The four audiences

| Audience   | Surfaces on | Wire-name prefix  | Who calls                                                                     |
| ---------- | ----------- | ----------------- | ----------------------------------------------------------------------------- |
| `agent`    | `/mcp`      | `ggui_*`          | The LLM agent itself during a chat turn (render, handshake, blueprint search) |
| `runtime`  | `/mcp`      | `ggui_runtime_*`  | The view runtime — iframe-runtime (via the host's `tools/call` relay) + first-party backend libraries |
| `protocol` | `/protocol` | `ggui_protocol_*` | Design-time spec/discovery clients (conformance suites, registry browsers)    |
| `ops`      | `/ops`      | `ggui_ops_*`      | Operator agents — an LLM acting as a console operator, dashboards, CI         |

Each tag answers exactly one question: **who is calling this tool, and on what time-scale?**

### `agent`

Surfaced on `/mcp`. The LLM agent calls these tools live, inside a chat turn. They typically mutate render state, render contracts, or look up blueprints by intent.

Wire-name prefix: bare `ggui_*` (e.g. `ggui_render`, `ggui_handshake`, `ggui_update`). The bare prefix is reserved for the canonical agent route — these are the tools an agent calls most often, and they don't need a route disambiguator.

### `runtime`

Surfaced on `/mcp` alongside `agent` tools. Called by the view runtime — the iframe-runtime, relayed through the host's MCP-Apps `tools/call` — and by first-party backend libraries (e.g. `@ggui-ai/agent-server` declaring the per-app tool catalog via `ggui_runtime_declare_tool_catalog`) — not by the LLM directly. They handle things like syncing renderer state back to the server or submitting user actions.

Wire-name prefix: `ggui_runtime_*` (e.g. `ggui_runtime_sync_context`, `ggui_runtime_submit_action`).

:::note
`/mcp` is a **union** of `agent` and `runtime` handlers. Both audiences live on the same HTTP route, but the visibility model layered on top (MCP-Apps' `_meta.ui.visibility`) hides `runtime` tools from the agent's `tools/list` while still letting iframe code invoke them through the same MCP connection.
:::

### `protocol`

Surfaced on `/protocol`. Static design-time tools that describe the protocol itself — example blueprints, format references, schema validators. A client calls these **once** while authoring against the protocol, not during runtime.

Wire-name prefix: `ggui_protocol_*` (e.g. `ggui_protocol_describe_blueprint_format`, `ggui_protocol_validate_blueprint`, `ggui_protocol_get_example_blueprints`).

The litmus test: would the result change if the same caller invoked the tool again five minutes later? If no — the tool returns a static format reference or immutable example set — it belongs on `/protocol`. If yes, it's a runtime lookup and belongs on `/mcp`.

### `ops`

Surfaced on `/ops`. Operator-facing tools an LLM operator (or dashboard, or CI script) uses to manage apps, register blueprints, issue connector keys, redeem coupons, list orgs. Never visible to the agent runtime, never invoked from inside a rendered UI.

Wire-name prefix: `ggui_ops_*` (e.g. `ggui_ops_create_app`, `ggui_ops_list_orgs`, `ggui_ops_issue_connector_key`).

## Routes table

The four audiences map onto three HTTP routes:

| Route       | Audiences mounted   | Typical caller                          | Auth posture                 |
| ----------- | ------------------- | --------------------------------------- | ---------------------------- |
| `/mcp`      | `agent` ∪ `runtime` | LLM agent + view runtime                | Bearer token or session auth |
| `/protocol` | `protocol`          | Conformance suites, design-time clients | Same auth chain as `/mcp`    |
| `/ops`      | `ops`               | Operator agents, dashboards, CI         | Same auth chain as `/mcp`    |

The mounting logic reads each handler's `audience` array and includes it on every route whose audience set intersects the handler's tags. A handler tagged `audience: ['agent']` lands on `/mcp` only. A handler tagged `audience: ['agent', 'runtime']` also lands on `/mcp` (the union doesn't change membership). A handler tagged `audience: ['ops']` lands on `/ops` only.

When per-app routing is configured, the same `agent` ∪ `runtime` surface also mounts at a per-app path (`/apps/<appId>` on hosted deployments); the audience model is identical — only the tenancy resolution differs.

:::caution
A handler with **no** audience tag is mounted on `/mcp` by default. This is a backward-compatibility behavior — handlers added before audience tagging existed default to `agent`. New handlers should always declare an explicit `audience` array.
:::

## Placement decision tree

When you add a new MCP handler, walk this tree before picking an audience:

1. **Does the LLM agent invoke this during a chat turn?**
   Yes → `audience: ['agent']`.
2. **Is this a tool declared with `_meta.ui.visibility: ['app']`** that the rendered view invokes through the host's MCP-Apps `tools/call` relay?
   Yes → `audience: ['runtime']`.
3. **Is this a static spec or discovery tool a client reads once while authoring against the protocol?**
   Yes → `audience: ['protocol']`.
4. **Is this an administrative operation a human, dashboard, CI, or operator agent performs out-of-band?**
   Yes → `audience: ['ops']`.

The placement test is exclusive — if more than one branch fires, pick the **most-frequent caller** and tag that audience. Multi-audience tags are rare; see below.

### The "is this a runtime lookup?" trap

Tools like `ggui_search_blueprints` _sound_ like spec/discovery — they discover blueprints. But their results change per-app and per-session: the agent calls them at chat time to decide what to build, not to learn the protocol's format. They are runtime lookups, tagged `agent`, surfaced on `/mcp`.

The litmus test repeats: **would the result change if the same caller invoked this tool again five minutes later?**

- Yes → runtime lookup → `agent` (or `runtime` if called from the iframe).
- No → static spec/discovery → `protocol`.

## Wire-name prefix discipline

The prefix encodes the audience at the wire-name level so a tool reader can infer the route without consulting documentation. An LLM scanning `tools/list` on `/protocol` sees `ggui_protocol_describe_blueprint_format` and immediately understands the routing.

| Prefix            | Implied route | Implied audience |
| ----------------- | ------------- | ---------------- |
| `ggui_*` (bare)   | `/mcp`        | `agent`          |
| `ggui_runtime_*`  | `/mcp`        | `runtime`        |
| `ggui_protocol_*` | `/protocol`   | `protocol`       |
| `ggui_ops_*`      | `/ops`        | `ops`            |

Bare `ggui_*` is the exception, not the rule. It is reserved for agent runtime essentials — the canonical chat-turn tools that don't need a route disambiguator. Every other audience requires the explicit prefix.

:::caution
The prefix and the `audience` tag must agree. A handler named `ggui_protocol_foo` with `audience: ['ops']` is a discipline violation — the prefix promises `/protocol`, the tag mounts it on `/ops`, and any agent scanning `/protocol` for `ggui_protocol_*` tools will miss it. Keep them in sync.
:::

## How a handler declares its audience

The `audience` field lives on every `SharedHandler`. It is a `ReadonlyArray<'agent' | 'runtime' | 'protocol' | 'ops'>` — an array because multi-audience tagging is structurally permitted (see below).

```ts
import type { SharedHandler } from '@ggui-ai/mcp-server-handlers';

export function createListOrgsHandler(): SharedHandler<…> {
  return {
    name: 'ggui_ops_list_orgs',
    title: 'List organizations',
    audience: ['ops'],
    description: 'Enumerate orgs visible to the calling operator.',
    inputSchema: { /* … */ },
    outputSchema: { /* … */ },
    async handler(input, ctx) {
      /* … */
    },
  };
}
```

That's the entire contract. Once a handler is registered through the normal channel (the `handlers` array passed to `createGguiServer`), the route mounter reads the `audience` field at compose time and decides which routes the handler appears on. There is no separate route-registration step.

## Multi-audience handlers

The `audience` field is an array, not a scalar, because a handler can legally surface on more than one route. In practice this is rare and intentional:

```ts
export function createSomeBoundaryToolHandler(): SharedHandler<…> {
  return {
    name: 'ggui_some_boundary_tool',
    audience: ['agent', 'runtime'],
    // …
  };
}
```

Such a handler appears in both the agent's `tools/list` and the iframe runtime's view. The canonical example is a tool that has to land on the agent's wire AND accept calls from inside the rendered UI — the runtime essentials that genuinely span both callers.

Multi-audience is a tool that lives on multiple routes simultaneously. If you find yourself reaching for it, double-check the placement test first — most "multi-audience" tools are actually two tools wearing a trench coat, and splitting them sharpens both surfaces.

## Relation to MCP services

The audience model governs **shared** routes — `/mcp`, `/protocol`, `/ops` — where handlers from different sources are aggregated under audience filtering. A separate concept, **MCP services**, lets a server expose isolated, complete MCP servers at their own HTTP paths (e.g. `https://your-server/docs`, `https://your-server/playground/todos`).

| Concept      | Routing mechanism                  | Tool isolation        | When to use                                 |
| ------------ | ---------------------------------- | --------------------- | ------------------------------------------- |
| **Audience** | Tag filters tool onto shared route | Tools share namespace | Tool belongs alongside ggui-native tools    |
| **Service**  | Path mounts an isolated MCP server | Per-path namespace    | Tool set is conceptually its own MCP server |

A service handler **must not** set an `audience` tag — the path IS the audience. The compose-time validator rejects services with audience-tagged handlers loudly: services bypass audience filtering entirely, so a tag would be silently meaningless.

→ See [MCP services](/architecture/mcp-services/) for the full service model.

## Placement anti-patterns

Audience tagging makes it cheap to add new tools, which means the _placement_ discipline matters more than ever. Some patterns to avoid:

- **`ops`-audience tools that mutate non-tenant data.** The `/ops` route is operator-bounded but still scoped to the calling operator's tenant. A tool that lets one operator probe another tenant's apps is a confused-deputy bug, not a feature.
- **Cross-tenant probing.** `ggui_ops_list_orgs` should enumerate orgs the caller can see, not all orgs. If a tool needs admin-level visibility, gate it on an explicit role check at the handler boundary, not at the route boundary.
- **`protocol`-audience tools that return runtime data.** If a result depends on which session is calling, it is a runtime lookup. Move it to `agent`.
- **`runtime`-audience tools that the agent should also call.** If an LLM agent needs the data, tag it `agent` (or `['agent', 'runtime']` if the iframe legitimately calls it too). Tagging it `runtime`-only hides it from the agent's `tools/list`.
- **Untagged handlers.** The default-to-`agent` behavior is a backward-compatibility convenience, not a recommendation. Always declare `audience` explicitly on new handlers.

## See also

- [MCP services](/architecture/mcp-services/) — isolated per-path MCP servers
- [MCP protocol](/api/mcp-protocol/) — the `/mcp` surface in detail
- [Ops MCP](/api/ops-mcp/) — the `/ops` surface in detail
- [Architecture overview](/architecture/overview/) — three channels and the capability model