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 anappblock that names itsasset_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, thenui/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), andnotifications/message. - Answered but not granted:
ui/request-display-modealways answersinline, the only mode, andtools/callis refused with-32601, because the web client does not relay tool calls to your agent. - Refused by the sandbox:
localStorage,sessionStorage, IndexedDB anddocument.cookie(all throwSecurityError) and popups (window.openreturnsnull). 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">.