1. Create a Vite project
Run the Vite CLI with its React and TypeScript template:latest tag and save exact versions in your application.
2. Define the tool
Create aserver folder and add this file. The tool’s registration key, weather, is also the name React will use.
server/tools.ts
3. Create the React bindings
Add one small module that connects your components to the tool types:src/mcp.ts
import type is important: it gives React the tool’s input/output types without bundling the server code. Create these bindings once, outside your components.
4. Replace the starter component
Replacesrc/App.tsx:
src/App.tsx
useToolResult() receives the initiating tool result. The host creates the widget when the tool is called.
Replace the starter’s full-page styles with a widget layout:
src/index.css
5. Configure one HTML build
Rename the generatedindex.html to weather.html and replace its contents:
weather.html
src/main.tsx renders the App you replaced. Now replace vite.config.ts:
vite.config.ts
package.json, set the build script and add start:mcp:
scripts object. In tsconfig.node.json, add server to include so the build also checks your server files:
--mode weather selects weather.html. The plugin inlines its JavaScript and CSS into dist/widgets/weather.html. publicDir: false skips the starter’s public assets; the HTML above also removes its favicon/logo references.
6. Serve the HTML as a resource
Create the MCP server. Keep the widget’s presentation and permission settings in the namedwidgetUi object:
server/index.ts
resourceUri and the resource’s uri must match exactly. The resource reads your built file; a ui:// URI is an identifier, not a filesystem location.
The HTML MIME type appears on both the resource definition and the returned content. To request permissions or allow network origins later, edit widgetUi using the permissions and metadata guide.
7. Build and connect a host
dist/widgets/weather.html, with its script and styles inside it. The server runs with npm run start:mcp. It waits for MCP messages over stdio; it does not open a browser or print a startup message to stdout. Node 24 runs these TypeScript server files directly.
In Claude Desktop, open Settings → Developer → Edit Config (host setup guide). Add the following entry to its mcpServers object, using your Node executable and the absolute server entry path:
node -p "process.execPath". Replace both paths, save the configuration, and restart Claude Desktop to load it. Ask Claude to call weather with { "city": "Dubai" }. An Apps-capable host should load the widget and supply that tool’s result.
Opening weather.html directly, or visiting Vite’s dev server, does not create an MCP host connection. Test the connected widget inside a compatible host. See host testing and troubleshooting for the recorded environments, setup details, and resource-cache behavior.
Next steps
- Add a refresh button with
useToolCall. - Add a second widget with one HTML file and resource per widget.
- Request permissions or network access in
widgetUi. - Use host actions when you need links, messages, or display-mode requests.