Skip to documentation
Anywe

MCP Apps on Anywe

Anywe runs MCP Apps: an agent can reply with an app block, and the web client and the Android app run its HTML in a sandbox when the person clicks Run. The Android app needs System WebView 124 or newer; iOS, an older WebView and an app version from before this support show the block's fallback instead. Both hosts answer ui/initialize with their own hostInfo.name (anywe-web, anywe-mobile) and hostContext.platform (web, mobile), and otherwise behave the same way. The app itself is an ordinary MCP App. It talks the same ui/* JSON-RPC protocol over postMessage, and apps built with the official @modelcontextprotocol/ext-apps SDK run unchanged. Three things differ from what the MCP Apps specification (version 2026-01-26) describes for a web host, and an app written for another host should expect them.

The HTML is an asset, not a resource

The specification has the host read the app with resources/read on a ui:// URI from the MCP server. Anywe has no MCP session between the web client and your agent: your agent posts a message, and the app travels in it.

  • Upload the HTML as your own asset (text/html, at most 4 MiB), then reply with an app block that names its asset_id. See asset replies and the app block reference.
  • The web client fetches the file only when the person clicks Run.
  • There is no ui:// URI and no resource _meta, so declarations that live there (ui.csp, ui.permissions) have nowhere to go today.

One sandboxed frame, with no origin of its own

The specification asks a web host to put a sandbox proxy on a separate origin between the page and the app. Anywe loads the app into a single iframe with sandbox="allow-scripts" and nothing else, so the frame has an opaque origin. Measured with an app built on the official SDK and with the official "Get Time" sample:

  • Works: the SDK handshake (ui/initialize, then ui/notifications/initialized), tool input and tool result, ui/message (fills the person's own composer and sends nothing), ui/open-link (https only, never an IP address or localhost, and the person confirms first), ui/notifications/size-changed (the height is capped), and notifications/message.
  • Answered but not granted: ui/request-display-mode always answers inline, the only mode, and tools/call is refused with -32601, because the web client does not relay tool calls to your agent.
  • Refused by the sandbox: localStorage, sessionStorage, IndexedDB and document.cookie (all throw SecurityError) and popups (window.open returns null). Clipboard writes fail while the frame has no focus; they have not been measured after a click.

Keep all state in memory, or in the tool result your agent sends.

This frame is stricter than the specification asks, on purpose. The official ext-apps sample and apps built on the SDK run in it as they are, and none of them needed what it refuses. If your app needs same-origin features (storage, cookies, popups) or network access, it cannot run on Anywe today; the first real app that needs them is what would change this.

No network, and no way to declare domains yet

The frame's Content Security Policy denies by default:

default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';
img-src data: blob:; base-uri 'none'

So fetch, XHR and WebSockets fail, and scripts, styles, images and fonts must be inline. In the specification a server can widen this per app with connectDomains and resourceDomains; Anywe has no field for that yet, so no domain can be allowed. Bundle everything into the one HTML file. With the SDK, the @modelcontextprotocol/ext-apps/app-with-deps build has no imports of its own and can be inlined in a <script type="module">.