@frontmcp/ui

中文文档
1.12.2发布于 2026/11/15Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 5230

React components, bridge hooks and content renderers (built on MUI) for FrontMCP tool widgets in ChatGPT, Claude and other MCP Apps hosts

原始使用文档
文档目录12 节
  1. Install
  2. Optional peers for the renderers
  3. Usage
  4. Calling a tool from a widget
  5. Entry points
  6. Hooks
  7. Components
  8. Renderers
  9. Docs
  10. Related packages
  11. Contributing
  12. License

@frontmcp/ui

@frontmcp/ui is the React side of FrontMCP widgets: the UI a tool shows inside an AI host such as ChatGPT or Claude. You write a widget as a React component in a file of its own and point the tool's ui.template at it; FrontMCP bundles the file, wraps it in McpBridgeProvider and mounts it with the call's data. This package gives that component hooks that read the call and call tools through the host, eleven components built on MUI (Material UI), a theme, and renderers for content such as charts, PDFs, maps and diagrams. A widget written as an HTML template doesn't need it (Tools with a UI). To make a React app that an agent can drive, use @frontmcp/react instead.

npm License

Install

npm install @frontmcp/ui react react-dom @mui/material @emotion/react @emotion/styled
  • For widget files, install it in the server's project, even when a widget doesn't import it: the code FrontMCP adds to mount a widget file imports McpBridgeProvider from @frontmcp/ui/react.
  • The required peers are react and react-dom 18 or 19, @mui/material 7, 8 or 9, and @emotion/react and @emotion/styled 11. npm 7 and later install them with the package, along with @types/react and @types/react-dom.
  • FrontMCP bundles widget files with esbuild, an optional peer of @frontmcp/uipack that nothing installs for you. A project from frontmcp create has it; anywhere else, run npm install esbuild and keep it a dependency, not a dev dependency, since it's loaded when the tool is called.
  • The server needs Node.js 24 or later.

Optional peers for the renderers

The renderers' libraries are optional peers, not installed with the package:

RendererEntry pointOptional peers
Charts@frontmcp/ui/renderer/chartsrecharts
PDF@frontmcp/ui/renderer/pdfreact-pdf, pdfjs-dist
Flow diagrams@frontmcp/ui/renderer/flow@xyflow/react
Maps@frontmcp/ui/renderer/mapsreact-leaflet, leaflet
Mermaid diagrams@frontmcp/ui/renderer/mermaidmermaid
Math@frontmcp/ui/renderer/mathkatex
Video and audio@frontmcp/ui/renderer/mediareact-player
Markdown and MDX@frontmcp/ui/renderer/mdxreact-markdown, remark-gfm, rehype-highlight, rehype-raw
HTML@frontmcp/ui/renderer/htmldompurify; without it the HTML is shown escaped
CSV, images, React JSX/renderer/csv, /renderer/image, /renderer/reactNone. JSX is compiled with Babel, loaded from esm.sh.

Each renderer loads its library at runtime, the first time it renders: by package name where the page can resolve it (an installed peer under Node or a dev server, or an import map), and otherwise from esm.sh in the browser. The stylesheets for maps, math and flow diagrams, and the PDF.js worker, always come from esm.sh.

Usage

Point the tool's ui.template at the widget file with an absolute path:

import { join } from 'node:path';

import { Tool, ToolContext, z } from '@frontmcp/sdk';

@Tool({
  name: 'get_weather',
  description: 'Current weather for a city',
  inputSchema: { city: z.string() },
  outputSchema: { city: z.string(), tempC: z.number(), conditions: z.string() },
  ui: { template: { file: join(__dirname, 'weather.widget.tsx') } },
})
export class GetWeather extends ToolContext {
  async execute({ city }: { city: string }) {
    return { city, tempC: 21, conditions: 'Sunny' };
  }
}

Export the component as the default from weather.widget.tsx:

import { Badge, Card } from '@frontmcp/ui/components';
import { FrontMcpThemeProvider } from '@frontmcp/ui/theme';

type Weather = { city: string; tempC: number; conditions: string };

export default function WeatherWidget({ output }: { output: Weather | null }) {
  if (!output?.city) return <p>Loading…</p>;
  return (
    <FrontMcpThemeProvider>
      <Card title={output.city} subtitle="Current weather">
        <p>{output.tempC}°C</p>
        <Badge label={output.conditions} variant="success" />
      </Card>
    </FrontMcpThemeProvider>
  );
}

The widget gets three props: output, input and loading. Check for a field you need, like output?.city, rather than for output: a failed result's error text is truthy too. In an ES module server, use join(import.meta.dirname, 'weather.widget.tsx'). Name widget files *.widget.tsx and leave them out of the server's own tsc build; frontmcp build copies them into its output. Building a widget file covers relative paths, resourceMode and what each host gets.

Calling a tool from a widget

import { useCallTool } from '@frontmcp/ui/react';

type Weather = { city: string; tempC: number };

export default function Refreshable({ output }: { output: Weather | null }) {
  const [refresh, { data, loading, error }] = useCallTool<{ city: string }, Weather>('get_weather');

  const weather = data?.structuredContent ?? (output?.city ? output : null);
  return (
    <div>
      <p>{weather ? `${weather.city}: ${weather.tempC}°C` : 'No data yet'}</p>
      <button disabled={loading} onClick={() => refresh({ city: 'Bergen' })}>
        Bergen
      </button>
      {error && <p role="alert">{error.message}</p>}
    </div>
  );
}

useCallTool() returns an array, not an object. call(args) asks the host to call the tool and resolves with the host's whole result, so the tool's output is in data.structuredContent. A tool that fails usually comes back as a result with isError: true in data; error is for calls that couldn't be made, like Tool calls not supported on this platform (ext-apps) from a host that never answered the handshake.

Entry points

ImportWhat's in it
@frontmcp/ui/reactMcpBridgeProvider and the hooks. @frontmcp/ui exports them too, with the bridge.
@frontmcp/ui/componentsThe eleven components. Each also has its own entry point, like @frontmcp/ui/components/Card.
@frontmcp/ui/themeFrontMcpThemeProvider, createFrontMcpTheme, defaultTheme, darkTheme, useFrontMcpTheme.
@frontmcp/ui/rendererContentView, renderContent, detectContentType, registerAllRenderers, registerRenderer, and every renderer. Each renderer also has its own entry point; /renderer/common has the shared helpers.
@frontmcp/ui/bridgeThe bridge as a class, FrontMcpBridge and createBridge(), with its adapters and generateBridgeIIFE(), for pages FrontMCP doesn't build.
@frontmcp/ui/runtimedetectContentType, and transpileJsx() with the Babel loader it uses.
@frontmcp/ui/auth, @frontmcp/ui/auth/vanillaComponents and helpers for a custom login UI; the vanilla entry has no React.

Hooks

A widget file is already inside McpBridgeProvider. Outside a provider the hooks return empty values, and call() fails with Bridge not initialized.

HookReturns
useToolOutput<T>()The call's output: the output embedded in the page, then each result the host sends.
useToolInput<T>()The call's arguments.
useStructuredContent<T>()The structuredContent of the result the host sent; empty until one arrives.
useCallTool<In, Out>(name, options?)[call, { data, loading, error, called }, reset]. Options: onSuccess, onError, resetOnToolChange (default true).
useToolCalls({ key: toolName, ... }){ key: { call, data, loading, error, reset }, ... }: useCallTool for several tools.
useSendMessage()[send(text), { loading, error, sent }]: adds a message to the conversation, where the host allows it.
useOpenLink()open(url), through the host.
useTheme(), useDisplayMode()"light" or "dark", and "inline", "fullscreen" or "pip", as the host reports them.
useHostContext()What the host said about itself: { theme, displayMode, locale, userAgent, safeArea, viewport?, ... }.
useCapability(name)Whether the host can do name, like "canCallTools". false until the host answers the handshake.
useMcpBridge(), useMcpBridgeContext()The bridge, and { bridge, loading, error, ready, adapterId, capabilities, revision }.

Components

@frontmcp/ui/components are React components built on MUI. Put them inside FrontMcpThemeProvider.

ComponentMain propsRenders
Buttonvariant (primary, secondary, danger, ghost), size, loading, disabled, onClickAn MUI Button; loading adds a spinner.
Cardtitle, subtitle, headerActions, footer, elevation, clickable, slotPropsAn MUI Card.
Alertseverity, title, dismissible, onDismiss, iconAn MUI Alert.
Badgelabel (required), variant, size, dot, removable, onRemoveAn MUI Chip.
Avatarsrc, alt, size, variantAn MUI Avatar.
Modalopen and onClose (required), title, actions, maxWidth, slotPropsAn MUI Dialog.
Tablecolumns ({ key, label, align? }[]), rows, size, stickyHeader, maxHeightAn MUI Table.
TextFieldlabel, value, onChange(value), type, multiline, error, helperTextAn MUI TextField; onChange gets the value.
Selectoptions ({ value, label }[]), label, value, onChange(value)An MUI select field.
Listitems ({ id, primary, secondary?, icon?, onClick?, divider? }[]), denseAn MUI List.
Loadervariant (spinner, bar, skeleton, overlay), label, determinate, valueA spinner, progress bar, skeleton or backdrop.

LoaderProvider replaces every Loader under it with your own render function. The theme comes from createFrontMcpTheme({ palette?, typography?, shape?, mode? }), or the ready-made defaultTheme and darkTheme, passed to FrontMcpThemeProvider as theme.

Renderers

import { ContentView, registerAllRenderers } from '@frontmcp/ui/renderer';

registerAllRenderers();

export function Report({ content }: { content: string }) {
  return <ContentView content={content} />;
}

ContentView detects the kind of content in a string, such as CSV, a Mermaid diagram, a chart's JSON or an image URL, and renders it with the matching renderer, wrapped in Suspense. renderContent(content, options?) does the same without the Suspense boundary, and detectContentType(content) only names the kind. When more than one renderer matches, the higher priority wins, in this order: pdf, chart, flow, map, mermaid, math, image, video, audio, csv, jsx, mdx, html. registerRenderer() adds your own { type, priority?, canHandle(content), render(content, options?) }.

Docs

  • @frontmcp/sdk: the server framework; a tool's ui option comes from it.
  • @frontmcp/uipack: the React-free core that builds every widget page on the server, with the html template helpers.
  • @frontmcp/testing: test helpers, with matchers for a tool's rendered widget.
  • @frontmcp/react: hooks and components for running a FrontMCP server inside a React app.

Contributing

Source: libs/ui. Issues and pull requests are welcome; see CONTRIBUTING.md.

The source has one folder per entry point under src/: react/ (provider and hooks), components/ (one folder per component), theme/, renderer/ (one folder per renderer, plus common/), bridge/ (adapters, the registry and the bridge class), runtime/ (content detection and the Babel loader) and auth/. The bridge script the server puts in each page comes from @frontmcp/uipack. In the monorepo:

nx build ui
nx test ui
nx lint ui

License

Apache-2.0. See LICENSE.

版本与发布记录

@frontmcp/ui 共发布 116 个版本,首次发布于 2025/12/12;过去 12 个月发布了 116 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.2latest2026/11/151 天前
1.12.12026/11/132 天前
1.12.02026/11/114 天前
1.12.0-rc.1rc2026/11/114 天前
1.11.32026/11/106 天前
1.11.22026/11/096 天前

其他发布通道:beta → 1.1.2-beta.1,rc → 1.12.0-rc.1。

关于 @frontmcp/ui 的常见问题

@frontmcp/ui 是什么?

React components, bridge hooks and content renderers (built on MUI) for FrontMCP tool widgets in ChatGPT, Claude and other MCP Apps hosts

如何安装 @frontmcp/ui?

在项目目录运行 npm install @frontmcp/ui,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、react-dom(^18.0.0 || ^19.0.0)、@types/react(^18.0.0 || ^19.0.0)、@mui/material(^7.0.0 || ^8.0.0 || ^9.0.0)、@emotion/react(^11.0.0)、@emotion/styled(^11.0.0)、@types/react-dom(^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@frontmcp/ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@frontmcp/ui 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@frontmcp/ui 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@frontmcp/ui 可以商用吗?

@frontmcp/ui 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@frontmcp/ui 的下载量有多少?

根据 npm 官方下载统计,@frontmcp/ui 在 2026/10/16 – 2026/11/14 这 30 天内被下载 5,230 次,日均约 174 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@frontmcp/ui 还在维护吗?

最新版本 1.12.2 发布于 2026/11/15(1 天前);至今共发布 116 个版本,其中过去 12 个月发布了 116 个;首次发布于 2025/12/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。