MCP App Not Rendering? Fix a Blank UI Step by Step

MCP App Not Rendering? Fix a Blank UI Step by Step

Aident AI

A dark oval unfolds into a broad violet and cream surface edged with teal on a deep indigo field.

MCP App Not Rendering? Fix a Blank UI Step by Step

If an MCP tool succeeds but its app is blank, debug the path in this order: confirm the host supports MCP Apps, inspect the tool's ui:// resource link, read the HTML resource directly, render it in the official basic-host, verify the app bridge receives the tool result, and only then investigate the production host. This sequence separates a server problem from a UI bundle, bridge, security-policy, cache, or host-compatibility problem.

MCP Apps add interactive HTML to MCP tools. The server still owns the tool and its data; the host fetches the declared UI resource and renders it in a sandboxed iframe. A successful tool call proves only the first part of that chain.

Prerequisites

You need:

  • Node.js 18 or newer for the official MCP Apps build guide;

  • an MCP server with one app-backed tool;

  • the exact local or remote Streamable HTTP endpoint;

  • a browser with developer tools; and

  • permission to inspect the server and test host locally.

Keep credentials out of command history and screenshots. If the server requires authentication, use a disposable development credential and redact it from saved diagnostics.

1. Record Which Layer Actually Failed

Start with one tool call and record four observations:

  1. Does the host list the tool?

  2. Does the tool return a successful result?

  3. Does the host request the declared ui:// resource?

  4. Does the iframe show content or a browser-console error?

These observations produce a useful first split:

Observation

Likely boundary

Tool is missing

MCP connection, protocol negotiation, or tool registration

Tool returns an error

Tool implementation or input validation

Tool succeeds but no resource is requested

Tool-to-UI metadata or host support

Resource is requested but the iframe is blank

HTML bundle, app bridge, CSP, or host behavior

App works locally but not in one host

Host compatibility, authentication, caching, or remote delivery

Do not change several layers at once. A tunnel, bundler, UI framework, authentication flow, and host upgrade can each produce the same blank rectangle.

2. Verify the Tool and Resource Contract

Build and start the app using the project's documented commands. The official minimal guide uses:

npm install
npm run build
npm run serve

Its example endpoint is http://localhost:3001/mcp. Use MCP Inspector to query the server without the conversational host:

npx -y @modelcontextprotocol/inspector@latest \
  --cli http://localhost:3001/mcp \
  --transport http \
  --method tools/list

Expected result: the app-backed tool appears, and its metadata names a ui:// resource through _meta.ui.resourceUri.

Then inspect the registered resources:

npx -y @modelcontextprotocol/inspector@latest \
  --cli http://localhost:3001/mcp \
  --transport http \
  --method resources/list

Open the same server in the Inspector UI when you need to read the resource body. Confirm all three facts:

  • the resource URI exactly matches the tool metadata;

  • the response uses the MCP Apps HTML media type, text/html;profile=mcp-app; and

  • the returned HTML contains the built JavaScript and CSS that the page expects.

A successful tools/call cannot compensate for a misspelled URI, a missing resource handler, the wrong media type, or HTML that still points to development-only assets.

3. Remove the Production Host From the Test

The official ext-apps repository includes basic-host, a local reference host that renders the app in a sandboxed iframe.

git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm start

Open http://localhost:8080, select the tool, and call it.

Expected result: basic-host fetches the ui:// resource, creates the iframe, pushes the tool result to the app, and displays an interactive UI. Click one control that calls a server tool and verify the result changes.

This gives you a decisive comparison:

  • Blank in basic-host: stay local and fix the tool, resource, bundle, bridge, or CSP.

  • Works in basic-host but blank in Claude, ChatGPT, VS Code, or another client: preserve the working local evidence and investigate that host's support and delivery path.

Do not use the core MCP protocol date as proof that a host supports MCP Apps. Apps are an extension, and host support varies independently.

4. Check the App Bridge

The UI must connect to the host before it can receive tool results or call server tools. A minimal client using the official SDK follows this shape:

import { App } from "@modelcontextprotocol/ext-apps";

const app = new App({ name: "Status App", version: "1.0.0" });

app.ontoolresult = (result) => {
  const text = result.content?.find((item) => item.type === "text")?.text;
  document.querySelector("#result")!.textContent = text ?? "No text result";
};

await app.connect();

Verify that:

  • app.connect() runs once after the document loads;

  • the result handler is registered before the first result is needed;

  • the handler reads the shape your tool actually returns;

  • UI-initiated calls use the exact registered tool name; and

  • errors are rendered visibly instead of leaving a permanent loading state.

An app can receive structuredContent, text content, or both, depending on the tool and host. Do not assume that a field seen in one client will be present everywhere. Validate the exact result contract you depend on and provide an explicit empty or error state.

5. Make the Bundle Self-Contained First

For the first working version, bundle JavaScript and CSS into the HTML. The official tutorial uses vite-plugin-singlefile for this reason. A single-file build removes external asset paths and most CSP variables from the initial diagnosis.

Inspect the generated HTML, not the source template:

find dist -maxdepth 2 -type f -print
rg -n 'src=|href=|http://|https://' dist

Expected result: the intended app HTML exists. Any remaining script, stylesheet, font, image, or API origin is deliberate and allowed by the resource's CSP metadata.

Do not add wildcard origins or disable the sandbox to make a blank app render. MCP Apps run in a sandboxed iframe specifically so they cannot read the host's cookies, local storage, or parent DOM. Fix the required origin or bundle the asset instead.

6. Test the Real Host Last

After the app passes basic-host, expose the server only if the target host requires a public HTTPS endpoint. The official guide demonstrates a temporary Cloudflare tunnel:

npx cloudflared tunnel --url http://localhost:3001

Add the generated HTTPS URL as a development connector, start a new conversation, and call the same tool. Verify the public endpoint, authentication, tool metadata, resource request, iframe console, and one UI-initiated tool call.

Treat the tunnel as temporary. Do not publish it, attach production credentials, or mistake a successful tunnel for a production deployment.

Common Blank-UI Failures

Symptom

Check

Safe next step

Tool works, but no app frame appears

_meta.ui.resourceUri and host support

Fix the exact URI or test in a supported host

Host requests the resource and gets not found

Resource registration and URI equality

Make the tool and resource share one URI constant

HTML appears as text or is rejected

Resource media type

Return text/html;profile=mcp-app

Frame stays on Loading...

app.connect() and ontoolresult

Connect once and render an explicit missing-result state

Buttons do nothing

Tool name, input shape, and host-granted capability

Inspect the call and show the returned error in the UI

Local host works, remote host is blank

Tunnel URL, auth, cache, or host implementation

Compare network requests and iframe console output

Browser console reports blocked assets

Bundle output and CSP metadata

Bundle locally or allow only the required origins

Inspector hangs on Streamable HTTP

Inspector version or current regression

Save the command and version, then use basic-host to isolate the app path

On July 29, 2026, an open Inspector report described v2 hanging silently on a Streamable HTTP connection. An independent MCP Apps issue described a view becoming blank when a host omitted expected structured content. These reports are reasons to preserve layer-by-layer evidence, not reasons to assume every blank UI has the same cause.

Why This Order Works

The sequence moves from stable contracts toward environment-specific behavior. tools/list proves the app link is advertised. Reading the resource proves the HTML exists with the correct media type. basic-host proves the sandbox and bridge can render it. Only then does a production-host difference become meaningful.

This applies the useful structure behind Aident's Ollama networking guide: name the exact failure, give a direct diagnostic order, use reproducible commands, state expected results, separate look-alike causes, and explain why the checks work. The Ollama post's traffic does not prove this topic will perform the same way.

Give Your Coding Agent One Verifiable MCP App Task

Connect GitHub through Aident Loadout, then ask your coding agent to compare your failure with current MCP Apps and Inspector issues. Require one evidence note containing the tools/list app URI, resource media type, basic-host result, target-host result, and the closest current issue URL. Success means the blank UI is assigned to one layer before any fix is attempted.

Sources

Refresh this guide when the MCP Apps tool metadata, resource media type, bridge API, supported-client matrix, basic-host, or Inspector command surface changes.

Home

Home

Home

Integrations

Integrations

Integrations

Vault

Vault

Vault

Audit

Audit

Audit

Arana Grande

Arana Grande

Arana Grande

Free

Free

Free

30-day audit summary

30-day audit summary

30-day audit summary

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

View Audit

View Audit

View Audit

Loadout usage

Loadout usage

Loadout usage

617 action calls in the last 30 days

617 action calls in the last 30 days

617 action calls in the last 30 days

May 19 - Jun 17

May 19 - Jun 17

May 19 - Jun 17

10 active days

10 active days

10 active days

Less

Less

Less

More

More

More

Recent activity

Recent activity

Recent activity

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Shopify

Shopify

Shopify

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Success

Success

Success

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Success

Success

Success

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Failed

Failed

Failed

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Connected integration coverage

Connected integration coverage

Connected integration coverage

162

162

162

of 753 accessible connected

of 753 accessible connected

of 753 accessible connected

Callable actions

Callable actions

Callable actions

1,126

1,126

1,126

Vault credentials

Vault credentials

Vault credentials

8

8

8

Explore what's possible

Explore what's possible

Explore what's possible

See all Integrations

See all Integrations

See all Integrations

Google Ads

Google Ads

Google Ads

All available Goolge Ads tools via...

All available Goolge Ads tools via...

All available Goolge Ads tools via...

X (twitter)

X (twitter)

X (twitter)

All available X tools via...

All available X tools via...

All available X tools via...

Github

Github

Github

All available Github tools via...

All available Github tools via...

All available Github tools via...

Notion

Notion

Notion

All available Notion tools via...

All available Notion tools via...

All available Notion tools via...

Slack

Slack

Slack

All available Slack tools via...

All available Slack tools via...

All available Slack tools via...

Firecrawl

Firecrawl

Firecrawl

All available Firecrawl tools via...

All available Firecrawl tools via...

All available Firecrawl tools via...

753 integrations are available for loadouts.

753 integrations are available for loadouts.

753 integrations are available for loadouts.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.