Skip to content

Audience routes

read as .md

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.

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.

AudienceSurfaces onWire-name prefixWho calls
agent/mcpggui_*The LLM agent itself during a chat turn (render, handshake, blueprint search)
runtime/mcpggui_runtime_*The view runtime — iframe-runtime (via the host’s tools/call relay) + first-party backend libraries
protocol/protocolggui_protocol_*Design-time spec/discovery clients (conformance suites, registry browsers)
ops/opsggui_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?

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.

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

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.

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

The four audiences map onto three HTTP routes:

RouteAudiences mountedTypical callerAuth posture
/mcpagentruntimeLLM agent + view runtimeBearer token or session auth
/protocolprotocolConformance suites, design-time clientsSame auth chain as /mcp
/opsopsOperator agents, dashboards, CISame 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 agentruntime surface also mounts at a per-app path (/apps/<appId> on hosted deployments); the audience model is identical — only the tenancy resolution differs.

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.

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.

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.

PrefixImplied routeImplied audience
ggui_* (bare)/mcpagent
ggui_runtime_*/mcpruntime
ggui_protocol_*/protocolprotocol
ggui_ops_*/opsops

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.

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

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.

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:

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.

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

ConceptRouting mechanismTool isolationWhen to use
AudienceTag filters tool onto shared routeTools share namespaceTool belongs alongside ggui-native tools
ServicePath mounts an isolated MCP serverPer-path namespaceTool 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 for the full service model.

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.