Skip to main content

Belgie: Build MCP App UIs for Claude and ChatGPT from Python

CI PyPI versions


Documentation: mplemay.github.io/belgie


Belgie lets a Python MCP server ship interactive React UIs that render inside AI hosts. Write the tool in Python and the view in React. Belgie runs Vite with hot reload during development, bundles each view into a single self-contained HTML resource for production, generates typed tool callers from your tool schemas, and gives components the host's theme, layout, and display mode.

Views follow the open MCP Apps standard, so one widget renders in Claude, in ChatGPT apps and plugins, and in other MCP Apps hosts such as VS Code and Goose.

  • Python tools, React views: Attach a widget.tsx to an MCP tool with one decorator argument.
  • One UI across hosts: Target MCP Apps once; use useHostInfo() and isWidget() when a view needs to adapt to Claude, ChatGPT, or a normal web page.
  • Vite without a Node toolchain: Declare JavaScript dependencies in pyproject.toml. Belgie's embedded Deno runtime resolves them and runs Vite, so the Python project does not need Node.js.
  • Typed tool calls: belgie generate turns MCP tool schemas into typed TypeScript callers, and useToolResult wires them to the opening result and later refreshes.
  • Host-native behavior: Read theme, locale, safe areas, and device type; request fullscreen, open modals, send follow-up messages, and update model context from the view.
  • Agent-generated UI: Let Pydantic AI and LangChain agents author React views at runtime with render_widget, alongside a sandboxed JavaScript and TypeScript tool.

Installation

uv add "belgie[mcp,cli]"
uvx library-skills install  # optional: install the use-belgie skill for Cursor, Codex, Claude, etc.

Install the base package with uv add belgie when you only need the runtime or agent integrations.

Build an MCP App

MCP Apps fit tasks where text alone is a poor interface: charts and dashboards over query results, review and approval forms before a write action, pickers for products, files, or records, and viewers or editors for structured data. The model calls the tool, the host renders your view with the result, and the user keeps working in the UI.

Attach a React widget to a Python MCP tool. BelgieExtension starts Vite in the background for development and runs a one-time production build:

from datetime import UTC, datetime
from pathlib import Path

from mcp.server import MCPServer

from belgie.mcp import BelgieExtension

belgie = BelgieExtension(project=".")


@belgie.tool(
    widget=Path("src/widgets/get-time/widget.tsx"),
    name="get-time",
    title="Get Time",
    description="Get the current server time in ISO 8601 format.",
)
def get_time() -> dict[str, str]:
    return {"time": datetime.now(tz=UTC).isoformat()}


mcp = MCPServer(name="Get Time Server", extensions=[belgie])

The widget is a normal React entry. @belgie/mcp connects to the MCP Apps host and surfaces the opening tool result:

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>
  );
}

Declare JS deps under [tool.belgie.dependencies], then:

uv run belgie lock
uv run belgie install
# start your MCP server; Belgie starts Vite with widget HMR

Pass build=False to BelgieExtension when Vite is managed separately or production assets are already built.

Connect the server to Claude as a custom connector, or to ChatGPT through developer mode or a plugin, and the view renders when the model calls the tool.

Runnable projects:

  • mcp: Minimal MCP Apps widget.
  • shadcn: Same pattern with Tailwind CSS and shadcn/ui.
  • tanstack: One React codebase served as a TanStack Start SPA and as an MCP widget through FastAPI.

Agent-generated UI

Pydantic AI and LangChain agents can return a complete inline React widget through the render_widget tool (alongside run_typescript / run_code). Enable rendering on the sandbox or middleware. The model passes a default-export TSX module and does not call render():

export default function Widget() {
  return <main>Hello from Belgie</main>;
}
from belgie.pydantic_ai import BelgieSandbox

capability = BelgieSandbox(enable_rendering=True, plugins=[])

render_widget builds HTML with @belgie/vite on a Belgie-owned renderer side-channel (not in the model-visible Deno worker). The agent Script stays workspace-restricted (no host /etc//proc, allow_sys, or allow_ffi), while Vite runs only in that host-mediated worker (workspace read/write, FFI under node_modules, localhost network, limited allow_sys; host env denied) and returns one self-contained HTML string with inline JavaScript, CSS, and assets. Host-configured Vite plugins run only during the server build; treat them as reviewed application code and use plugins=() for untrusted agents. This API is independent from @belgie/mcp and its path-based widget.tsx development and production flow.

See examples/ui/pydantic-ai for a web app that renders agent-authored UI from a prompt.

AI agents

When an agent needs a browser-style API or a JavaScript transformation, give it the Belgie sandbox tool. Pydantic AI uses run_typescript; LangChain uses run_code. Belgie executes JavaScript, TypeScript, or TSX in the embedded Deno sandbox. The Python runtime does not require a separate Node install.

Pydantic AI

Install with uv add "belgie[pydantic-ai]", set OPENAI_API_KEY, then:

from pydantic_ai import Agent

from belgie.pydantic_ai import BelgieSandbox

agent = Agent("openai:gpt-5", capabilities=[BelgieSandbox()])

result = agent.run_sync(
    "Convert 'foo-bar' to camelCase using TypeScript.",
)
print(result.output)

See examples/ai/pydantic-ai.

LangChain

Install with uv add "belgie[langchain]", set OPENAI_API_KEY, then:

from langchain.agents import create_agent

from belgie.langchain import BelgieMiddleware

agent = create_agent(
    model="openai:gpt-5",
    tools=[],
    middleware=[BelgieMiddleware()],
    system_prompt="You can execute JavaScript or TypeScript in a Deno sandbox with run_code.",
)

result = agent.invoke(
    {
        "messages": [
            (
                "user",
                "Convert 'foo-bar' to camelCase using TypeScript.",
            ),
        ],
    },
)
print(result["messages"][-1].content)

See examples/ai/langchain.

Under the hood: Deno in Python

MCP App builds, agent-generated UI, and both agent sandbox integrations use Belgie's embedded Deno runtime. Call it directly when you need JavaScript or TypeScript from Python without MCP or an agent framework:

  • Scripts: Inline or file-based JS/TS with Runtime and Script, sync or async.
  • Inline dependencies: Import npm, JSR, and URL modules from source.
  • Environments: Lockfiles, custom cache/options, local packages, and Command for npm binaries (Vite, esbuild, etc.).
  • Data bridge: Pass JSON-safe dicts, lists, and primitives across the boundary.

Runtime permissions

RuntimePermissions gates Deno APIs and every host-backed module read, including static and dynamic imports, JSON modules, and Node require(). File entrypoints created with Script.from_file and command entrypoints must be covered by allow_read; inline and in-memory sources do not need a host read grant. Belgie-managed npm packages are available to the module loader without adding their node_modules or cache roots to the runtime's general read grants. Package imports therefore work in restricted runtimes, while Deno.readFile, arbitrary absolute file: URLs, and other direct host reads remain subject to the caller's allow_read and deny_read settings.

import asyncio

from belgie import Runtime, Script

script = Script[[str], str](
    """
import camelcase from "npm:camelcase@8.0.0";

export default function run(input: string): string {
  return camelcase(input);
}
"""
)


async def main() -> None:
    async with Runtime() as run:
        print(await run(script)("foo-bar"))  # prints: fooBar


asyncio.run(main())

Examples

Small, runnable projects. Each focuses on one capability.

UI

  • mcp: MCP Apps extension with a React widget built through Belgie.
  • shadcn: MCP Apps widget styled with Tailwind CSS and shadcn/ui.
  • tanstack: TanStack Start SPA and MCP widget served together through FastAPI.
  • pydantic-ai: FastAPI SPA that renders agent-authored UI from a prompt.

AI

  • pydantic-ai: Pydantic AI agent with BelgieSandbox() for sandboxed JS/TS/TSX execution.
  • langchain: LangChain agent with BelgieMiddleware() for sandboxed JS/TS/TSX execution.

Basic

  • simple: Async Runtime with a TypeScript file on disk.
  • inline-deps: Direct npm:, jsr:, and URL imports in a script.
  • jsr-deps: JSR packages declared through an explicit Environment.
  • pyproject: Manage project package dependencies with belgie[cli] and [tool.belgie.dependencies].
  • environment: Sync and async Environment setup with path.
  • commands: npm package binaries via Runtime and Command.

For deeper integration guidance, optionally install the use-belgie skill with uvx library-skills install.

Metadata

Release files for belgie 0.42.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for belgie 0.42.0
File Size Uploaded
belgie-0.42.0.tar.gz 184.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for belgie 0.42.0
File
belgie-0.42.0-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
belgie-0.42.0-cp312-abi3-manylinux_2_28_x86_64.whl CPython 3.12 abi3 Linux glibc 2.28+ x86-64 Details
belgie-0.42.0-cp312-abi3-manylinux_2_28_aarch64.whl CPython 3.12 abi3 Linux glibc 2.28+ ARM64 Details
belgie-0.42.0-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
belgie-0.42.0-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details

Total release size: 296.0 MB

Release files / belgie-0.42.0.tar.gz

Download URL belgie-0.42.0.tar.gz
Size 184.1 kB
Tags Source
SHA-256 checksum
How to use checksums
55ab06833620d8eac1a46ac329a7ecf872ef9cb1fa29aeec2fb40fe7a8715673
BLAKE2b-256 checksum
How to use checksums
f7f38d2f426bd2fdd296d6349f71d908ae427f814dec6e7e5f8d3633be0fcffc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / belgie-0.42.0-cp312-abi3-win_amd64.whl

Download URL belgie-0.42.0-cp312-abi3-win_amd64.whl
Size 54.6 MB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
1f793228ecce20cd485cad1458299b5a11898e98cb282ffb0ee1c3e1a3bc9e98
BLAKE2b-256 checksum
How to use checksums
a2a45970c6d4e78ca5ca62c02ebc9525c6b57bef3c4e42c7680fb662ea871fda
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / belgie-0.42.0-cp312-abi3-manylinux_2_28_x86_64.whl

Download URL belgie-0.42.0-cp312-abi3-manylinux_2_28_x86_64.whl
Size 61.9 MB
Tags CPython 3.12 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
54a85569031674b7b540fa7b0120d1d5d5ddb37e29ed26397fd71e71eb804891
BLAKE2b-256 checksum
How to use checksums
e0bbf0368854722fa66c9f49521d25889d5e750c5854bbff39007e3e4197ca3b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / belgie-0.42.0-cp312-abi3-manylinux_2_28_aarch64.whl

Download URL belgie-0.42.0-cp312-abi3-manylinux_2_28_aarch64.whl
Size 65.5 MB
Tags CPython 3.12 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
d5ee05e635795441e7a4d3885f20ad82d370646e24283c23c3f8f81dac915ae2
BLAKE2b-256 checksum
How to use checksums
3de69ed881c81ca0f69bdb3cfd814c90dd0b674dfb3a87d26b980877a167e9fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / belgie-0.42.0-cp312-abi3-macosx_11_0_arm64.whl

Download URL belgie-0.42.0-cp312-abi3-macosx_11_0_arm64.whl
Size 56.0 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
52297698bdf71decd12c45a3a5876b54f8b9b2693d46896e2e4fb37a147668dd
BLAKE2b-256 checksum
How to use checksums
b142d98244df8cc2d75d3278e6ec5f8ac4f9d7fd8d5886053190241e41955bf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / belgie-0.42.0-cp312-abi3-macosx_10_12_x86_64.whl

Download URL belgie-0.42.0-cp312-abi3-macosx_10_12_x86_64.whl
Size 57.9 MB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
5a7cf0614df7c6d1d4a9df80a5f0ef50c369b1faeaeccefb36ea5d2b69040409
BLAKE2b-256 checksum
How to use checksums
86165a1893f767d296bb15dbe3daefab1cbe30c5676a374ec1e3b421d979cef5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.42.0 This release

6 release files

0.41.1

6 release files

0.41.0

6 release files

0.37.0

6 release files

0.36.0

6 release files

0.35.0

6 release files

0.34.0

6 release files

0.33.0

6 release files

0.32.0

6 release files

0.27.1

6 release files

0.27.0

6 release files

0.26.0

6 release files

0.25.1

6 release files

0.25.0

6 release files

0.24.0

6 release files

0.23.2

6 release files

0.22.0

6 release files

0.21.0

6 release files

0.20.0

6 release files

0.3.0

5 release files

0.2.3

6 release files

0.1.1

2 release files

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page