Add an MCP App to a tool
For agent developers who build servers, MCP Apps let you show an HTML UI inside an MCP client. A can show a chart, a result view, or an editor that calls the same tool again or another tool in the same gateway. This guide shows how to attach a UI to a tool and publish the UI with Arcade Deploy.
Prerequisites
- An Arcade MCP server.
arcade-mcp-serverversion 1.32.1 or later and the Arcade CLI.- An Arcade and for Arcade Deploy.
- An client that supports the MCP Apps extension. A client that only supports calls does not render the UI.
Start with the runnable example
The MCP Apps example server includes a greeting editor, tools that the editor calls, and a text resource. Run the example from its directory:
git clone https://github.com/ArcadeAI/arcade-mcp.git
cd arcade-mcp/examples/mcp_servers/tools_with_mcp_apps
uv sync --extra dev
uv run python src/tools_with_mcp_apps/server.pyThe server runs over stdio. Connect an Apps client to the server to test the UI locally. Opening editor.html in a browser does not test the host connection: the UI needs the MCP client’s message bridge.
The preview and uppercase tools need no third-party authorization. The Google profile is an optional, read-only example of authorization from an open App.
Attach an HTML resource to a tool
Declare the UI with resource from the public arcade_mcp_server package. Keep the HTML file beside the Python module that declares the resource:
from arcade_mcp_server import resource
@resource(file="editor.html", title="Greeting editor")
def editor() -> None:
"""Edit a greeting and call tools through the connected MCP client."""The example’s UI-linked imports the resource declaration and passes the declaration to @tool(ui=...). The following excerpt omits the example’s other tools:
from typing import Annotated
from arcade_mcp_server import tool
from tools_with_mcp_apps.ui import editor
@tool(ui=editor)
def preview_greeting(name: Annotated[str, "The name to greet"]) -> str:
"""Show a greeting editor."""
return f"Hello, {name}!"Load the package’s in the server entrypoint:
from importlib.metadata import version
from arcade_mcp_server import MCPApp
import tools_with_mcp_apps
app = MCPApp(name="tools_with_mcp_apps", version=version("tools_with_mcp_apps"))
app.add_tools_from_module(tools_with_mcp_apps)
if __name__ == "__main__":
app.run()Include editor.html in the Python package you deploy. The example’s wheel configuration includes the HTML file in src/tools_with_mcp_apps/.
The framework publishes the resource with the ui:// scheme and the text/html;profile=mcp-app MIME type. The framework also sets the ’s _meta.ui.resourceUri. Do not construct that metadata by hand. Use the standalone @resource declaration for Arcade Deploy, rather than the native--only @app.resource API.
The client reads the linked resource and renders the HTML in a sandbox. and UI loading are separate requests. Keep end-user data in results, not in the published HTML: the same HTML document serves different end-.
Deploy and connect through a gateway
From the example’s directory containing pyproject.toml, run:
uv run arcade login
uv run arcade deploy -e src/tools_with_mcp_apps/server.pyBefore deploying a changed release, increase project.version in pyproject.toml and run uv sync --extra dev. When you change the HTML, also update the App’s appInfo.version in editor.html.
After deployment:
- Create an MCP gateway in the same .
- Select
ToolsWithMcpApps.PreviewGreetingandToolsWithMcpApps.UppercaseGreeting. AddToolsWithMcpApps.GoogleProfileif you want to test authorization. - Connect your Apps client to the gateway URL from the Dashboard.
- Ask the client to call
ToolsWithMcpApps_PreviewGreetingwithnameset toAda. - Change the name in the editor and select Uppercase greeting. The editor calls the other and shows the updated greeting.
You do not add the HTML as a separate server or select a resource in the Dashboard. The already points to the UI resource. The gateway gives the resource a unique address. Use the exact address returned by the gateway, rather than constructing an address from the toolkit name or version.
For a self-hosted Arcade installation, use an release and deployment runtime that support managed-server resources. Ask your platform operator to confirm that support. See Arcade Deploy on your own cluster for the deployment setup.
Call another tool from the App
App JavaScript sends tool calls through the client’s existing gateway connection. Do not put an Arcade API key or OAuth token in the HTML, or call the API from the iframe to execute .
In the example’s editor.html, request handles the Apps message bridge and available maps identities to the names returned by discovery. The cross-tool call uses:
const name = available.get("ToolsWithMcpApps.UppercaseGreeting");
const result = await request("tools/call", {
name,
arguments: { name: document.getElementById("name").value },
});Call the exact discovered name. The greeting editor checks availability before enabling the editor and before each action. An App does not gain permission to call a because the App references the tool.
Check availability before the user starts editing
An action’s may be absent from the gateway or unavailable to the connected end-. Hide the action or keep the action’s button inactive before the end-user spends time editing inputs for an action that cannot run.
The example first tries App-to-host tools/list, which is in the draft MCP Apps specification . The example reads every page and respects the ’s App visibility.
If the host returns method-not-found, or replace the complete list, the example asks the host to read Arcade’s availability resource:
arcade://gateway/tool-availability/v1?tool=ToolsWithMcpApps.PreviewGreeting&tool=ToolsWithMcpApps.UppercaseGreeting&request=<fresh-uuid>This temporary Arcade-specific fallback requires host support for server resource reads. It returns the exact names of the requested tools available to the end-user in the gateway. Keep the native-first check and fallback when adapting the example. Treat a failed check as unknown availability, not as confirmation that a is absent.
Test the missing- case with a second gateway that excludes ToolsWithMcpApps.UppercaseGreeting. The editor must not show the uppercase button.
Handle authorization without losing edits
Availability does not mean that OAuth consent or required secrets are ready. Let the ordinary call check those requirements. Do not execute a tool to probe availability.
The host may handle URL elicitation during a call. If the host instead forwards an unresolved authorization requirement, the example shows an authorization prompt and keeps the edited inputs. After authorization, the end- selects Retry action. Completing OAuth does not trigger another call from the App.
For an initial call that has not executed, show the authorization prompt instead of an empty editor. The host must retry the original call after authorization. An error about a missing secret is not an OAuth authorization prompt.
Configure external assets and requests
The bundled greeting editor uses inline JavaScript and CSS, with no direct network requests. If you add external scripts, styles, images, or fonts, declare the origins in the resource’s meta argument. Direct API requests also need declared origins. Replace the existing resource declaration with a declaration such as:
@resource(
file="editor.html",
meta={
"ui": {
"csp": {
"resourceDomains": ["https://cdn.example.com"],
"connectDomains": ["https://api.example.com"],
}
}
},
)
def editor() -> None:
"""Show the greeting editor."""Use resourceDomains and connectDomains, not browser directive names such as script-src and connect-src. The host reads _meta.ui.csp from the resource-read response. Putting the policy only on the does not configure the resource.
For local assets, include each origin’s scheme and port, such as http://localhost:5173. localhost and 127.0.0.1 are different origins. Remove development origins before deployment. The host’s message bridge does not need a connectDomains entry.
Troubleshoot a missing UI
| Symptom | Check |
|---|---|
| The tool returns text, but no UI appears | Use a client with MCP Apps support. Confirm that the tool definition includes _meta.ui.resourceUri. |
| The client reports resource-not-found | Read the exact linked URI from the current tool definition. Confirm that the deployed package includes the HTML file and uses the standalone @resource declaration. |
| The UI is blank | Check the host’s browser console for blocked assets. Check resourceDomains and connectDomains on the resource. |
| An action is missing or inactive | Check tool availability for the connected end-user and gateway. A failed discovery request is not an authorization check. |
Next steps
- Adapt the complete example server and HTML .
- Add authorization to a tool.
- Read the official MCP Apps documentation for frontend SDK APIs and host requirements.