MCP Apps example
The examples/ui/mcp project connects a Python MCP tool to a React widget. It demonstrates the
complete path from a tool result to a host-connected widget.
Demonstrates
BelgieExtensionregistration with aPathtowidget.tsx.- Vite configuration with
@belgie/mcp/vite. - Generated typed callers and
useToolResult. - A self-contained production widget build.
Run the example
Install the example's dependencies and start its server:
cd examples/ui/mcp
uv sync
uv run main
The example listens on http://127.0.0.1:3001. Its MCP streamable HTTP endpoint is /mcp.
Before starting the server, lock and install the JavaScript dependencies if the checkout does not
already contain a current deno.lock:
uv run belgie lock
uv run belgie install
Python server
The server registers a normal Python function with a widget path. The complete entrypoint is included from the shipped example:
from datetime import UTC, datetime
from pathlib import Path
from typing import Final, TypedDict
import uvicorn
from mcp.server import MCPServer
from belgie.mcp import BelgieExtension
PROJECT_ROOT: Final[Path] = Path(__file__).resolve().parents[2]
WIDGET: Final[Path] = PROJECT_ROOT / "src" / "mcp_app" / "views" / "widgets" / "get-time" / "widget.tsx"
belgie = BelgieExtension(project=PROJECT_ROOT)
class TimeResult(TypedDict):
time: str
@belgie.tool(
widget=WIDGET,
name="get-time",
title="Get Time",
description="Get the current server time in ISO 8601 format.",
)
def get_time() -> TimeResult:
return {"time": datetime.now(tz=UTC).isoformat()}
mcp = MCPServer(name="Get Time Server", extensions=[belgie])
def main() -> None:
uvicorn.run(mcp.streamable_http_app(), host="127.0.0.1", port=3001)
if __name__ == "__main__":
main()
The complete implementation is in
examples/ui/mcp/src/mcp_app/__main__.py.
Widget and generated caller
The widget imports Widget and useToolResult from @belgie/mcp. The generated caller gives the
hook an input and output schema:
import { Widget, useToolResult } from "@belgie/mcp";
import { getTime } from "@widgets/tools";
function AppView() {
const { data, isLoading, execute } = useToolResult(getTime);
return (
<main>
<p>{data?.time ?? (isLoading ? "Waiting..." : "No time returned.")}</p>
<button onClick={() => void execute()}>Refresh</button>
</main>
);
}
export default function GetTime() {
return (
<Widget metadata={{ name: "Get Time", version: "1.0.0" }}>
<AppView />
</Widget>
);
}
Run npx belgie-mcp generate against the running endpoint when the tool schema changes. See
@belgie/mcp for authentication and --check options.
Production build
Set dev=False in BelgieExtension after building the widget assets, or use the project's
production configuration. The extension then reads dist/widgets/get-time/index.html instead of
starting a Vite development server.
Variants
examples/ui/shadcnadds Tailwind CSS and shadcn/ui components.examples/ui/tanstackserves a TanStack Start frontend and the MCP endpoint through FastAPI.