using a2ui in Gemini Enterprise · Vol. 11 LIVE_ARTIFACT

Adding an MCP App to Gemini Enterprise, Step by Step

2026-09-29 12 min read series using a2ui in Gemini Enterprise · Vol 11 build aec8378

No more refactoring required

The earlier field report reached Gemini Enterprise by refactoring — taking an MCP Apps app and rebuilding it as an A2A agent speaking GE's own native A2UI vocabulary, because at the time that was the only door in. Wall Builder as it exists there is the product of that port: 18 standard-catalog primitives, an A2A agent backend, Drive export via a service account — the last of which is what eventually hit a real wall (Google Chat's attachments:upload flatly refuses app/service-account auth: 403 PERMISSION_DENIED, "This method doesn't support app authentication with a service account. Authenticate with a user account."). Native A2UI in GE is real and it works, but reaching it meant leaving the open standard at the door.

That door has a different lock now. This piece hooks the same MCP Apps server (a2uicatalog.ai/mcp) straight into Gemini Enterprise through its Custom MCP Server connector — no A2A rewrite, no native-vocabulary port, the actual MCP Apps spec server rendering in a sandboxed iframe the same way it already does in Claude and ChatGPT. Same connector mechanism Part 1 of the portability series used to reach a third host with zero adaptation — GE turns out to be a fourth, just with more setup between "the server exists" and "GE will actually call it."

Architecture: Gemini Enterprise's chat UI calls the Custom MCP Server connector, gated by two org policies, which reaches a2uicatalog.ai/mcp (tools.js — render_surface, open_workspace, wall_calc), backed by per-user Durable Object state; the render_surface guardrail hands structuredContent to the MCP Apps view, which renders inline back in Gemini Enterprise's chat

Step 0: the org policy has to allow it

Google ships custom MCP connectors off by default, project-scoped:

text
constraints/discoveryengine.managed.disableCustomMcpServerConnector

Confirmed feature timeline, for anyone hitting this cold: launched April 28 2026 (org-policy-gated from day one), No-Auth support added July 22 (Public Preview), auto-discovery via tools/list on August 6, GA August 7. All four entries live in Google's own release notes.

Check whether it's enforced:

bash
gcloud org-policies describe discoveryengine.managed.disableCustomMcpServerConnector \
  --project=YOUR_PROJECT_ID --effective

If enforce: true, an org admin overrides it — project-scoped if your org allows overrides, org-wide otherwise (roles/orgpolicy.policyAdmin required either way):

yaml
name: organizations/YOUR_ORG_ID/policies/discoveryengine.managed.disableCustomMcpServerConnector
spec:
  rules:
  - enforce: false

A second constraint blocks the connector silently even after the first is cleared — an empty egress FQDN allowlist, enforced by default:

yaml
name: organizations/YOUR_ORG_ID/policies/discoveryengine.managed.allowedEgressFqdns
spec:
  rules:
  - enforce: true
    parameters:
      allowedEgressFqdns: ["a2uicatalog.ai"]
      enforcedProjects: []

Without this one, the connector accepts the registration call and then sits in INITIALIZATION_FAILED with a generic, useless code: 13 "pipeline failure" — no mention of egress anywhere in the error. If you're scripting this against the API rather than clicking through the console, that's the failure mode to recognize.

Step 1: register the connector

Two ways to do this — the console is the supported, documented path; the API call is what's actually running underneath it, useful once you want this scripted or repeatable.

Option A — console. Data Stores → Create data store → Custom MCP Server → Add MCP server:

text
Server URL       https://a2uicatalog.ai/mcp
Authentication   No authentication
Connector modes  Actions (tool-calling)

Save, and GE reads the server's tools/list response once to populate the connector's registry entry.

Option B — API. The real request shape isn't in the API's discovery document anywhere — it took a sanitized HAR capture of the console's own call to find it:

bash
curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "x-goog-user-project: YOUR_PROJECT_ID" \
  -H "Content-Type: application/json" \
  "https://discoveryengine.googleapis.com/v1alpha/projects/YOUR_PROJECT_ID/locations/global:setUpDataConnector" \
  -d '{
    "collectionId": "a2uicatalog-mcp",
    "collectionDisplayName": "a2uicatalog MCP",
    "dataConnector": {
      "dataSource": "custom_mcp",
      "connectorSourceId": "custom_mcp",
      "params": {
        "oauth_access_token": "unused",
        "auth_type": "AUTHORIZATION_TYPE_UNDEFINED"
      },
      "refreshInterval": "86400s",
      "entities": [{"entityName": "mcp_data"}],
      "dataSourceVersion": 1,
      "staticIpEnabled": false,
      "actionConfig": {
        "actionParams": {
          "instance_uri": "https://a2uicatalog.ai/mcp",
          "auth_type": "NO_AUTH",
          "mcp_server_source": "BYO_MCP"
        },
        "createBapConnection": true,
        "isActionConfigured": false
      },
      "connectorModes": ["FEDERATED"]
    }
  }'

The oauth_access_token: "unused" line is not optional even in No-Auth mode — omit it, or put the real server URL there instead, and the API rejects the call with "Data Connector parameters must be one of: oauth_access_token." The actual server URL and auth mode live one level down, in actionConfig.actionParams — a decoy top-level params field next to the real configuration, discoverable only by reading back what the console's own call actually sent.

A GET on the same collection confirms state: ACTIVE once it lands. Tools sync automatically from there — no separate registration step, the connector's own tools/list call is what GE uses to populate the Actions panel.

A word on No-Auth: it's probably not the common case for an enterprise adding a custom MCP server — most real deployments will want the OAuth path instead. But if you want to test this without standing up your own server first (though it's a lot more fun to make your own :P), point at mine — https://a2uicatalog.ai/mcp, No authentication, same as above. It's the same free public endpoint everything in this piece runs against.

Step 2: enable the actions

Data store → Actions tab → Reload custom actions → select the tools to expose → Enable actions. This is a separate step from connector registration; skipping it is why an otherwise-ACTIVE connector can still show zero available tools.

Discovery Engine data store detail page for the registered a2uicatalog MCP connector: Collection ID a2uicatalog-mcp, Type custom_mcp, Instance URL https://a2uicatalog.ai/mcp, Connector state Active

Actions tab with all 32 discovered tools selected and the Enable actions button highlighted, before any of them are turned on

Demo 1: Open Workspace

open_workspace is the same tile-based home screen the MCP Apps portability piece showed rendering identically in Claude and ChatGPT — Profile, History, Linked accounts, Article Analyser, Wall Builder, Composer, all as tool tiles on one surface, all state server-side. Asked for by name ("open the workspace") it renders inline in GE exactly like the other two hosts: same payload, same renderer, zero GE-specific code.

Gemini Enterprise chat rendering the A2UI Workspace home screen inline: Profile, History, Linked accounts, Article Analyser, Wall Builder, and Composer as tool tiles

Demo 2: Wall Builder, the MCP version

Wall Builder here is a deliberate rebuild, not the same code as Part 1's native-A2UI version — same wall-calculation math (course counts, weight, mortar, the crew-size build curve), verified byte-for-byte against the original formulas, but composed from this catalogue's own atom vocabulary instead of GE's native primitives, and without the Drive export leg. That omission is deliberate: Drive export needs a service-account impersonation chain this Workspace doesn't have, and Part 1 already found out what happens when you try to route around that with a service account on the wrong Google Chat endpoint. The comparison is the point — same underlying tool, two different rendering paths into the same host, one open standard and one closed one.

Wall Builder rendered inline in Gemini Enterprise: wall configuration sliders and dropdowns on the left, a live brick-elevation SVG with dimensions and unit count on the right

Demo 3: composing a page from nothing

Open Workspace and Wall Builder are both pre-built views — open_workspace and wall_calc already know exactly which atoms to use. The more interesting test is asking for something the server has no fixed view for at all: "using the a2ui catalog create a call to action page on why Gemini Enterprise with MCP apps is interesting - what can we do? use modern atoms, high impact."

Looping screen capture of the whole Demo 3 exchange in Gemini Enterprise: the prompt, the tool-call chain narrated step by step, and the finished CTA page scrolling past, ending on a pointer to this blog

There's no render_cta_page tool for that. What ran instead was a real schema-introspection chain — find_cta_atoms, find_feature_atoms, get_atom_schemas, check_card_schemas, find_metrics_layout_atoms, get_columns_schema — the model searching the catalogue for atoms that fit the brief, checking their prop schemas before using them, then composing and rendering the result with render_mcp_cta, refining it once with refresh_mcp_surface, and shipping a shareable link with create_preview_link.

Gemini Enterprise chat showing the tool-call chain behind an ad-hoc composed page: find_cta_atoms, find_feature_atoms, get_atom_schemas, check_card_schemas, and more, each one searching or inspecting the catalogue before anything is rendered

What came out the other end was a full dark-themed landing page — hero, a six-card feature grid, a stat callout, a CTA button — assembled from atoms the model had never been told to use for this specific purpose, picked by querying the catalogue rather than by a hardcoded view. Fittingly, the copy it wrote pitches exactly the mechanism that produced it: "MCP Apps and A2UI elevate AI from a conversational novelty to enterprise infrastructure. Instead of just telling you the answer, Gemini Enterprise directly renders the tool and carries out the work."

Gemini Enterprise chat rendering an ad-hoc composed landing page: "The Paradigm Shift: From Chatbot to Operational Cockpit" callout above a six-card feature grid — Live Generative UI, Universal MCP Connectivity, Enterprise-Grade Security, Bi-Directional Workflows, Multi-Agent Delegation, Rapid Zero-Code Rollout

It also came with a shareable preview link and three suggested follow-ups — add a live form, connect internal Jira/GitHub MCP servers, export the surface as standalone HTML — which is the same generality showing up a second time: the model treating "make a page" as a search-and-compose problem over the catalogue, not a fixed menu of views.

Demo 4: a second MCP App, same mechanism

Everything above runs through one MCP App. Diagram Studio — the graph-in, four-formats-out flowchart tool this catalogue also serves — is a completely separate app with its own view, and it works in GE with zero extra wiring: "Using diagram studio plot the architecture for gemini enterprise paying particular attention to constraint that can be controlled on the discovery engine."

GE researched the actual question first — Discovery Engine's real retrieval constraints, ACLs and scopes, TTL and time constraints, boost/bury rules — then called diagram_studio with a graph built from what it found, rendering the flowchart inline: User Request → Gemini Enterprise Orchestration → Discovery Engine (Retrieval), gated through three labelled constraint nodes down to a Grounded Synthesis step. Same search-the-catalogue-then-compose pattern as Demo 3, on a second, unrelated app, with nothing app-specific built to support it.

Diagram Studio rendered fullscreen in Gemini Enterprise: an 8-node, 8-edge flowchart tracing a request from User Request/Prompt through Gemini Enterprise Orchestration and Discovery Engine (Retrieval), down through three labelled constraint nodes, with the studio's own direction/theme/spacing controls and an "Edit in draw.io" button along the top

The studio's "Edit in draw.io" hand-off worked too — the real, self-hosted draw.io editor, opened as a genuinely editable canvas with the same diagram already loaded, not a static export. It survives GE's CSP for a specific reason: it's a plain top-level navigation, not a framed or blob-loaded resource, so none of the restrictions that otherwise block this catalogue's WASM-based engines apply to it.

The real draw.io editor, opened in a new tab from Diagram Studio's "Edit in draw.io" button, with the same eight-node flowchart already loaded and fully editable

Bug handling

Asked to open the Workspace, the model in GE didn't call open_workspace — despite render_surface's own tool description explicitly saying "if the user names one of the Workspace's own pre-built views... call open_workspace(view: ...) instead of composing one here yourself." It called render_surface instead, with a degenerate placeholder payload: {"blocks":[{}]} — no title, one block, no type at all. Every attempt, same shape, confirmed live via a temporary debug log added to the connector for exactly this diagnosis.

Gemini Enterprise chat response: the model's own fabricated "A2UI Workspace Active" narration, describing a control panel that was never actually rendered, sitting directly above the real guardrail notice — "The model proposed undefined — that is not a real atom in this catalogue, so this block was replaced with this note rather than silently dropped."

The payload is schema-valid nonsense, not a malformed call the input schema should have caught: blocks: [{}] satisfies items: {type: 'object'} with room to spare. It's a real, if narrow, tool-selection bug — the model ignoring its own tool's explicit routing instruction and substituting an empty guess for a different, correctly-named tool.

Fixed server-side rather than waiting on a model fix: render_surface now recognizes that exact shape (no title, ≤1 block, no type — a shape no legitimate call could produce) and redirects it to the real Workspace home view instead of surfacing the "not a real atom" notice for an empty guess.

LLM calls still need ironing out

Some Workspace flows — Article Analyser, Composer — need a model call to do their actual work. They don't make that call themselves: the public a2uicatalog.ai/mcp endpoint is free and anonymous, so it has no tokens of its own to spend. Instead the app sends the drafted request to the host platform or client of the MCP App, and lets whichever model is already sitting there — on whatever budget the human's already paying for — take it from here.

That's been tested a while back on Claude and ChatGPT. Wiring the same mechanism up for Gemini Enterprise is where my own app still has testing and debugging left to do — not a GE-wide issue, just my build's own confirm-then-send flow not yet tuned for this particular host. For ChatGPT, sending was automatic. For Claude and for GE alike, a human has to confirm each message before it sends — and in my current build that appears to trigger flickering between full-screen and part-screen mode, not settling cleanly into either. That's mine to fix: I already shipped one real improvement server-side — skipping the automatic fullscreen request for any wired view whose blocks carry a host:message action, so a view that needs the chat box isn't yanked into fullscreen on load — but there's more tuning left on my side before this flow is clean specifically on GE.

Underneath that is a bigger open question: where MCP Apps actually positions itself inside Enterprise. My guess is that app vendors whose clients already need MCP as the protocol to connect to their platform will end up unlocking MCP Apps as part of that same integration — a lower-friction route to sharing a tool, in the MCP sense of the word, that's genuinely multimodal. I need to find some time to dig into this further — more likely a Sunday morning activity. TBC.

What's still open

This is the No-Auth path — anonymous, same as the public demo endpoint, fine for proving the mechanism. GE's connector also supports full OAuth 2.0 (Authorization URL, Token URL, Client ID/Secret, PKCE), which is what turns "a connector calling tools" into "a connector acting as the signed- in user" — per-user Drive/BigQuery/whatever access, no service account in the path at all. That's the natural next piece.

Where that piece would actually plug in depends on where the server itself lives. Mine runs on Cloudflare today — a Worker in front of a Durable Object holding per-user state (profile, history, readings). The OAuth handshake hasn't been built, but the shape is obvious: the Durable Object would hold a refresh token the same way it already holds everything else, and the handshake itself would be a cross-cloud HTTP call out to Google's own authorization server.

Architecture diagram: Gemini Enterprise calling into a Cloudflare Worker (a2uicatalog.ai/mcp, tools.js) backed by a Durable Object, with a dashed not-yet-built extension showing an OAuth 2.0 handshake out to Google's authorization server and Drive/BigQuery API, the refresh token coming back to live in the same Durable Object

Hosting the same server on GCP instead removes that cross-cloud hop entirely — Cloud Run in place of the Worker, Firestore in place of the Durable Object, Secret Manager for the OAuth client secret. Same-project IAM means the authorization handshake never leaves Google's own network, which is a real simplification if Drive/BigQuery access ends up being the main point of adding OAuth in the first place.

Architecture diagram: the same flow hosted entirely on Google Cloud instead — a Cloud Run service running the same MCP server code, Firestore replacing the Durable Object for per-user state, Secret Manager holding the OAuth client secret, and the OAuth 2.0 handshake to Google staying inside the same project's IAM rather than crossing clouds

Further reading

Thanks to Richard Shenghua (升华) He for the heads-up that this was even possible — his own walkthrough is worth a watch if you want a second, more general take on building MCP Apps for Gemini Enterprise: How to Build Interactive MCP Apps in Gemini Enterprise.