Skip to main content
Glama
billy-yoyo

React MCP SPA

by billy-yoyo

React MCP SPA

A minimal React single-page app that is bundled into one HTML blob and served by an MCP Apps server. The SPA doesn't use URL path routing — it picks which page to render by reading the current tool name from the MCP host context.

Layout

  • packages/ui/ — React + Vite SPA, built to a single-file HTML blob via vite-plugin-singlefile.

  • packages/mcp/ — MCP server using @modelcontextprotocol/ext-apps/server. Registers tools (show-home, show-counter, show-profile) that all point to the same ui:// resource.

  • packages/playground/ — dev-only harness that renders the SPA inside a mock chat UI with JSON inputs for hostContext and toolResult, so pages can be exercised without an MCP host.

Related MCP server: @businessmaps/mcp

Install

Requires Node.js ≥ 20 and pnpm ≥ 9.

pnpm install

Develop the UI in isolation

pnpm run dev:ui

Open the printed URL (e.g. http://localhost:5173). The SPA detects that there's no MCP host and shows a route picker so you can preview each page.

Playground — test rendering without MCP

pnpm run dev:playground

Opens on http://localhost:5174. The playground reuses the SPA's real route renderer (RouteRenderer from packages/ui/src/Router.tsx) and wraps it in a mock chat UI. The header has two JSON textareas — one for the hostContext (drives which page renders via toolInfo.tool.name) and one for the toolResult passed to the page. Preset buttons load ready-made context+result pairs for each registered tool; edits to either textarea re-render the assistant's tool-output bubble live.

Build and run the MCP server

pnpm run build       # builds packages/ui → dist/index.html, then packages/mcp
pnpm run serve:mcp   # starts Streamable HTTP server on http://localhost:3001/mcp

For stdio transport:

pnpm --filter @react-mcp-spa/mcp run serve:stdio

Exposing the server with cloudflared

Some MCP clients (including hosted ones) can't reach localhost. Use cloudflared to open a quick tunnel that assigns a public HTTPS URL.

Install cloudflared via your OS package manager:

# macOS (Homebrew)
brew install cloudflared

# Linux (Debian/Ubuntu)
# See https://pkg.cloudflare.com/ for the apt repo, or grab the .deb from
# https://github.com/cloudflare/cloudflared/releases

# Windows (winget)
winget install --id Cloudflare.cloudflared

Start the server locally:

pnpm run serve:mcp

In another terminal, run:

cloudflared tunnel --url http://localhost:3001
# or: pnpm run tunnel

cloudflared prints a URL like https://<random>.trycloudflare.com. Append /mcp and use it as your MCP server URL:

https://<random>.trycloudflare.com/mcp

The tunnel stays up until you kill cloudflared. For a stable hostname, configure a named Cloudflare tunnel instead of a quick tunnel.

Packaging as a Claude Desktop extension (.mcpb)

The repo ships a packer that produces an installable MCP Bundle for Claude Desktop. The extension runs the server over stdio — no tunnel required.

pnpm run pack:mcpb

This builds both packages, stages the compiled server + UI HTML + prod node_modules under build/mcpb-staging/, and invokes mcpb pack to produce:

build/react-mcp-spa.mcpb

To install it, double-click the .mcpb in Finder/Explorer (or drag it onto Claude Desktop). Claude validates the manifest and registers the server; after installation the three tools (show-home, show-counter, show-profile) are available and each renders its page as an inline React UI.

The bundle manifest lives at mcpb/manifest.json — bump version there (and in packages/mcp/package.json) when cutting a new extension release.

How routing works

The SPA never reads window.location. Instead, packages/ui/src/App.tsx uses useApp() from @modelcontextprotocol/ext-apps/react and:

  1. Reads app.getHostContext().toolInfo.tool.name to know which tool the host invoked — this is the "route".

  2. Subscribes to app.ontoolresult to receive the CallToolResult from the server and pass it to the page as data.

  3. Watches app.onhostcontextchanged so theme / safe-area / locale updates re-render correctly.

Adding a new page is a two-step change:

  1. Register a tool in packages/mcp/src/server.ts with _meta.ui.resourceUri pointing at the shared ui://react-mcp-spa/app.html resource.

  2. Add a case for the tool name in renderRoute() inside packages/ui/src/App.tsx.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol (MCP) server built with the mcp-framework for developing and managing custom tools. It provides a structured foundation for building and integrating modular components like data processors and API clients into Claude Desktop.
    5 npm
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP bridge server that allows AI tools to call functions and execute code directly within a web browser tab. It enables developers to register custom browser-side tools with full access to the DOM, web APIs, and local application state.
    10 npm
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal todo app MCP server for ChatGPT that exposes tools for managing tasks and provides an interactive HTML widget interface. It demonstrates how to build MCP servers with React-based UIs and includes development OAuth for ChatGPT connector setup.
    -