Skip to main content

Overview

The Simulator component provides a local development environment that replicates the MCP App runtime used by hosts like ChatGPT and Claude. It renders your resources inside iframes, matching the production hosting behavior. The simulator supports multiple host shells — switch between ChatGPT and Claude conversation chrome, theming, and reported capabilities from the sidebar or via URL parameters.

Import

import 'sunpeak/style.css'; // Required to style the simulator.
import { Simulator } from 'sunpeak/simulator';
ChatGPTSimulator is available as a backwards-compatible alias: import { ChatGPTSimulator } from 'sunpeak'.

Basic Usage

For projects using the sunpeak framework, simulations are auto-discovered. Just run:
sunpeak dev
For custom setups, you can manually configure the simulator:
import { Simulator } from 'sunpeak/simulator';
import { resource } from './resources/example/example-resource';
import exampleSimulation from './resources/example/example-show-simulation.json';

// Combine simulation with resource
const simulations = [{
  ...exampleSimulation,
  resource,
  resourceUrl: 'http://localhost:3000/resources/example',
}];

<Simulator
  simulations={simulations}
  appName="My App"
  appIcon="🌄"
/>

Props

simulations
Record<string, Simulation>
Object mapping simulation names to their definitions. See Simulation API Reference for details.
appName
string
default:"Sunpeak App"
Name of the app displayed in the simulator UI.
appIcon
string
Optional icon (emoji) displayed in the simulator UI.
children
React.ReactNode
Optional children to render when no simulations are selected.

URL Parameters

The Simulator supports URL parameters for configuring the initial state. This is especially useful for automated testing and sharing specific configurations.

Supported Parameters

ParameterTypeDefaultDescription
simulationstringFirst simulationSimulation name to load
host'chatgpt' | 'claude''chatgpt'Host shell (conversation chrome, theming, capabilities)
theme'light' | 'dark''dark'Color theme
displayMode'inline' | 'pip' | 'fullscreen''inline'App display mode
localestring'en-US'Locale for i18n
maxHeightnumber600Max height in PiP mode
deviceType'mobile' | 'tablet' | 'desktop' | 'unknown''desktop'Device type
hover'true' | 'false''true'Hover capability
touch'true' | 'false''false'Touch capability
safeAreaTopnumber0Top safe area inset
safeAreaBottomnumber0Bottom safe area inset
safeAreaLeftnumber0Left safe area inset
safeAreaRightnumber0Right safe area inset

Example URLs

# Load albums simulation in light mode
http://localhost:3000/?simulation=albums-show&theme=light

# Fullscreen dark mode
http://localhost:3000/?simulation=review-diff&theme=dark&displayMode=fullscreen

# Mobile simulation with touch
http://localhost:3000/?simulation=map-show&deviceType=mobile&touch=true&hover=false

# Claude host in light mode
http://localhost:3000/?simulation=albums-show&host=claude&theme=light

createSimulatorUrl

For type-safe URL generation in tests, use the createSimulatorUrl utility:

Import

import { createSimulatorUrl } from 'sunpeak/simulator';

Usage

// Basic usage
createSimulatorUrl({ simulation: 'albums-show', theme: 'light' })
// Returns: '/?simulation=albums-show&theme=light'

// With display mode
createSimulatorUrl({
  simulation: 'review-diff',
  theme: 'dark',
  displayMode: 'fullscreen',
})
// Returns: '/?simulation=review-diff&theme=dark&displayMode=fullscreen'

// With device simulation
createSimulatorUrl({
  simulation: 'map-show',
  deviceType: 'mobile',
  touch: true,
  hover: false,
})
// Returns: '/?simulation=map-show&deviceType=mobile&touch=true&hover=false'

// With safe area insets (for notch simulation)
createSimulatorUrl({
  simulation: 'carousel-show',
  safeAreaTop: 44,
  safeAreaBottom: 34,
})
// Returns: '/?simulation=carousel-show&safeAreaTop=44&safeAreaBottom=34'

SimulatorUrlParams Interface

interface SimulatorUrlParams {
  simulation?: string;
  host?: string;
  theme?: 'light' | 'dark';
  displayMode?: 'inline' | 'pip' | 'fullscreen';
  locale?: string;
  maxHeight?: number;
  deviceType?: 'mobile' | 'tablet' | 'desktop' | 'unknown';
  hover?: boolean;
  touch?: boolean;
  safeAreaTop?: number;
  safeAreaBottom?: number;
  safeAreaLeft?: number;
  safeAreaRight?: number;
}

E2E Testing Example

import { test, expect } from '@playwright/test';
import { createSimulatorUrl } from 'sunpeak/simulator';

test.describe('Albums Resource', () => {
  test('should render in light mode', async ({ page }) => {
    await page.goto(createSimulatorUrl({
      simulation: 'albums-show',
      theme: 'light',
    }));

    const albumCard = page.locator('button:has-text("Summer Slice")');
    await expect(albumCard).toBeVisible();
  });

  test('should render in fullscreen dark mode', async ({ page }) => {
    await page.goto(createSimulatorUrl({
      simulation: 'albums-show',
      theme: 'dark',
      displayMode: 'fullscreen',
    }));

    // Verify fullscreen behavior
    const expandButton = page.locator('button[aria-label="Enter fullscreen"]');
    await expect(expandButton).not.toBeVisible();
  });
});
The simulator sidebar provides interactive controls for:
  • Host: Switch between ChatGPT and Claude conversation shells
  • Simulation: Select which simulation to display
  • Simulation Width: Toggle between mobile (375px, 425px), tablet (768px), and full width (1024px)
  • Theme: Toggle between light and dark themes
  • Display Mode: Switch between inline, PiP, and fullscreen modes
  • Locale: Set the locale string
  • Max Height: Configure PiP mode height
  • Device Type: Configure device type and capabilities (hover, touch)
  • Time Zone: Set the IANA time zone (e.g. America/New_York)
  • User Agent: Set the user agent string
  • Safe Area Insets: Configure safe area insets for notch simulation
  • JSON Editors: Edit tool input, tool result, and app context (model context) directly
  • Send as Partial (Streaming): Send the current tool input JSON as a tool-input-partial notification to test streaming input handling

ChatGPT Mock Runtime

When the ChatGPT host is selected, the simulator automatically injects a mock window.openai runtime into the resource iframe. This enables:
  • isChatGPT() returns true — platform detection works correctly
  • ChatGPT-specific hooks (useUploadFile, useGetFileDownloadUrl, useRequestModal, useRequestCheckout) work with stub implementations
  • All mock method calls are logged to the browser console with a [Simulator] prefix
When a different host (e.g. Claude) is selected, window.openai is not injected and ChatGPT hooks will throw if called. See ChatGPT Hooks for the full API reference.