Skip to main content

Decorators & GPT Apps

This page covers server-side decorators for linking tools to UI components, and ChatGPT-specific providers and hooks.

@UIApp Decorator

Links an MCP tool to a React UI component for ext-apps hosts (Claude Desktop, MCP-compatible hosts). When a tool decorated with @UIApp is called, the host will:
  1. Execute the tool and get the result
  2. Fetch the linked UI component as an HTML resource
  3. Render the UI in an iframe with the tool result

Basic Usage

import { Tool } from '@leanmcp/core';
import { UIApp } from '@leanmcp/ui';

class WeatherService {
  @Tool({ description: 'Get weather for a city' })
  @UIApp({ component: './WeatherCard' })
  async getWeather(args: { city: string }) {
    const weather = await fetchWeatherAPI(args.city);
    return {
      city: args.city,
      temperature: weather.temp,
      condition: weather.condition
    };
  }
}
Use a path string (e.g., './WeatherCard') for the component to avoid importing browser code in your server bundle. The CLI will resolve and build the component separately.

With Custom URI

@Tool({ description: 'Show dashboard' })
@UIApp({ 
  component: './DashboardView',
  uri: 'ui://analytics/dashboard',  // Custom resource URI
  title: 'Analytics Dashboard'       // HTML document title
})
async getDashboard() {
  return { /* dashboard data */ };
}

Options

OptionTypeDescription
componentReact.ComponentType | stringComponent or path to component file
uristringCustom resource URI (auto-generated if not provided)
titlestringHTML document title
stylesstringAdditional CSS styles

The UI Component

Your UI component receives the tool result via useToolResult:
// WeatherCard.tsx
import { AppProvider, useToolResult, Card, RequireConnection } from '@leanmcp/ui';
import '@leanmcp/ui/styles.css';

interface WeatherData {
  city: string;
  temperature: number;
  condition: string;
}

function WeatherCardContent() {
  const { result, loading } = useToolResult<WeatherData>();

  if (loading || !result) return <div>Loading...</div>;

  return (
    <Card>
      <h2>{result.city}</h2>
      <p className="text-4xl font-bold">{result.temperature}°C</p>
      <p>{result.condition}</p>
    </Card>
  );
}

export default function WeatherCard() {
  return (
    <AppProvider appInfo={{ name: 'WeatherCard', version: '1.0.0' }}>
      <RequireConnection>
        <WeatherCardContent />
      </RequireConnection>
    </AppProvider>
  );
}

@GPTApp Decorator

Links an MCP tool to a React UI component specifically for ChatGPT GPT Actions.
import { Tool } from '@leanmcp/core';
import { GPTApp } from '@leanmcp/ui';

class AnalyticsService {
  @Tool({ description: 'Show analytics dashboard' })
  @GPTApp({ component: './AnalyticsDashboard' })
  async getAnalytics(args: { period: string }) {
    return { /* analytics data */ };
  }
}

Options

OptionTypeDescription
componentReact.ComponentType | stringComponent or path to component file
uristringCustom resource URI
titlestringHTML document title

GPTAppProvider

Context provider for ChatGPT Apps using the native window.openai SDK.

Basic Setup

import { GPTAppProvider } from '@leanmcp/ui';
import '@leanmcp/ui/styles.css';

function MyGPTApp() {
  return (
    <GPTAppProvider appName="MyApp">
      <MyContent />
    </GPTAppProvider>
  );
}

Props

PropTypeDescription
appNamestringApp name for identification
childrenReactNodeApp content

useGptApp

Access the GPT App context including connection state and theme.
import { useGptApp } from '@leanmcp/ui';

function MyComponent() {
  const { 
    isConnected,   // Whether connected to ChatGPT
    theme,         // 'light' | 'dark'
    displayMode,   // Display mode
    locale,        // User locale
    maxHeight,     // Max height from host
    callTool,      // Call a server tool
    error          // Connection error
  } = useGptApp();

  if (!isConnected) {
    return <div>Connecting to ChatGPT...</div>;
  }

  return <div>Connected! Theme: {theme}</div>;
}

useGptTool

Call MCP tools via ChatGPT’s SDK with loading and error states.

Basic Usage

import { useGptTool } from '@leanmcp/ui';

function DataViewer() {
  const { call, result, loading, error } = useGptTool('get-data');

  useEffect(() => {
    call({ id: '123' });
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <pre>{JSON.stringify(result, null, 2)}</pre>;
}

Return Value

PropertyTypeDescription
call(args?) => Promise<any>Execute the tool
resultanyTool result
loadingbooleanLoading state
errorError | nullError if any
isConnectedbooleanConnection state

Environment Differences

ext-apps vs ChatGPT

Featureext-apps (AppProvider)ChatGPT (GPTAppProvider)
TransportPostMessage iframewindow.openai SDK
Theme syncFull CSS variablesBasic light/dark
Tool callsuseTool, callTooluseGptTool
ResourcesuseResourceNot supported
MessagesuseMessageNot supported
Display modesInline, modal, fullscreenInline only

Shared Features

Both environments support:
  • Tool execution
  • Theme awareness (light/dark)
  • Auto-applied styles via @leanmcp/ui/styles.css
  • Testing with MockAppProvider

Complete GPT App Example

import { GPTAppProvider, useGptTool, Card, Button } from '@leanmcp/ui';
import '@leanmcp/ui/styles.css';

function StockDashboard() {
  const { call, result, loading, error } = useGptTool('get-stock-price');

  return (
    <div className="p-4">
      <Button onClick={() => call({ symbol: 'AAPL' })} disabled={loading}>
        {loading ? 'Fetching...' : 'Get Apple Stock'}
      </Button>

      {error && (
        <div className="text-red-500 mt-2">Error: {error.message}</div>
      )}

      {result && (
        <Card className="mt-4">
          <h3>{result.symbol}</h3>
          <p className="text-2xl font-bold">${result.price}</p>
          <p className={result.change > 0 ? 'text-green-500' : 'text-red-500'}>
            {result.change > 0 ? '+' : ''}{result.change}%
          </p>
        </Card>
      )}
    </div>
  );
}

export default function App() {
  return (
    <GPTAppProvider appName="StockDashboard">
      <StockDashboard />
    </GPTAppProvider>
  );
}