Skip to main content

Documentation Index

Fetch the complete documentation index at: https://sunpeak.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

sunpeak API

Overview

Simulations define test scenarios for your resources—combining tool definitions, mock data, and platform state. They can be used with the Inspector for local development or runMCPServer for platform testing with hosts like ChatGPT and Claude. Simulations are JSON files that live in the tests/simulations/ directory. Each simulation references a tool file by name — tool metadata and resource linkage are handled by the tool files in src/tools/.

File Convention

Simulation files live in a flat tests/simulations/ directory:
tests/simulations/*.json
For example:
  • tests/simulations/show-albums.json
  • tests/simulations/review-diff.json
  • tests/simulations/review-post.json
Any *.json filename works. Multiple simulations can reference the same tool with different fixture data. Tool files live in src/tools/:
  • Each .ts file exports tool (metadata with optional resource link), schema (Zod), and a default handler.
Resource files live in src/resources/{name}/:
  • Component + Metadata: src/resources/{name}/{name}.tsx (exports both the React component and export const resource: ResourceConfig)

JSON Schema

Each simulation JSON file contains:
// tests/simulations/show-example.json
{
  "tool": "show-example",
  "userMessage": "Show me an example",
  "toolInput": {
    "query": "example search"
  },
  "toolResult": {
    "structuredContent": {
      "items": ["item1", "item2"]
    }
  }
}
The tool field is a string referencing a tool filename (without .ts) in src/tools/. Tool metadata (name, description, schema, annotations, visibility) lives in the tool file, not in the simulation.

Auto-Discovery

The framework automatically discovers and links everything:
  1. Tools: Scans src/tools/*.ts for tool files with metadata, schemas, and handlers
  2. Simulations: Scans tests/simulations/*.json and links each to its tool via the tool string field
  3. Resources: Each tool file references its resource by directory name (e.g., resource: 'albums')
This means you never need to:
  • Duplicate tool metadata in simulations
  • Import resource metadata into simulations
  • Maintain an index file of simulations or tools

Multiple Scenarios

Create multiple simulations for the same tool to test different scenarios:
src/resources/albums/
  albums.tsx                       # Component + resource metadata

src/tools/
  show-albums.ts                   # Tool metadata, schema, handler

tests/simulations/
  show-albums.json                 # Default view
  show-albums-empty.json           # Empty state
  show-albums-error.json           # Error state
Each simulation can have different toolInput and toolResult.structuredContent to test various data scenarios.

Mocking Server Tool Calls

When a resource calls callServerTool (e.g., the review resource calling a backend-only “review” tool), the inspector needs mock responses. Instead of separate simulation files for each backend tool response, you define mock responses inline using the serverTools field on the resource’s simulation.
// tests/simulations/review-purchase.json
{
  "tool": "review-purchase",
  "toolResult": { "structuredContent": { "..." } },
  "serverTools": {
    "review": [
      {
        "when": { "confirmed": true },
        "result": {
          "content": [{ "type": "text", "text": "Completed." }],
          "structuredContent": { "status": "success", "message": "Completed." }
        }
      },
      {
        "when": { "confirmed": false },
        "result": {
          "content": [{ "type": "text", "text": "Cancelled." }],
          "structuredContent": { "status": "cancelled", "message": "Cancelled." }
        }
      }
    ]
  }
}
The serverTools map supports two forms: Simple form — always return the same result:
"serverTools": {
  "review": {
    "content": [{ "type": "text", "text": "Done." }],
    "structuredContent": { "status": "success", "message": "Done." }
  }
}
Conditional form — match against call arguments:
"serverTools": {
  "review": [
    { "when": { "confirmed": true }, "result": { "..." } },
    { "when": { "confirmed": false }, "result": { "..." } }
  ]
}
The when object does shallow key matching against the tool call arguments. The first entry whose keys all match wins. Use structuredContent in the result to return structured data to the calling resource. For example, the review tool returns { status: 'success', message: '...' } or { status: 'cancelled', message: '...' } — the review resource reads status to determine success/error styling and displays message. Use isError: true only for actual tool execution failures.

Simulations vs real server calls

When testing an external MCP server (Python, Go, etc.), you have two testing modes: Simulation fixtures (fast, no server needed): Write JSON files with pre-baked toolResult data. The inspector renders the result without calling your server. Use this for visual regression tests and UI state testing where you want fast, deterministic results.
// Uses fixture data from tests/simulations/search.json
const result = await inspector.renderTool('search');
const app = result.app();
await expect(app.getByText('Results')).toBeVisible();
Real server calls (integration testing): Pass input to inspector.renderTool. The inspector calls your MCP server with those arguments and renders the response. Use this to verify that your server returns correct data and the UI handles it properly. You can also use mcp.callTool for protocol-level assertions without rendering.
// Calls the real server with { query: 'headphones' } and renders
const result = await inspector.renderTool('search', { query: 'headphones' });
expect(result).not.toBeError();
const app = result.app();
await expect(app.getByText('headphones')).toBeVisible();

// Or test at the protocol level without rendering
const raw = await mcp.callTool('search', { query: 'headphones' });
expect(raw.isError).toBeFalsy();
Both modes work for external servers. Without any simulation fixtures, inspector.renderTool falls back to real server calls by default.

Simulation fixtures for external servers

External servers don’t have src/tools/ directories. Instead, tools are auto-discovered via listTools() when the inspector connects. You can still write simulation fixtures to test specific UI states without calling the server. Create a tests/simulations/ directory and pass it to your config:
export default defineConfig({
  server: { command: 'python', args: ['server.py'] },
  simulationsDir: 'tests/simulations',
});
The JSON format is the same as for sunpeak projects. The tool field references the tool name as reported by listTools():
{
  "tool": "search",
  "toolInput": { "query": "headphones" },
  "toolResult": {
    "structuredContent": {
      "results": [{ "name": "Sony WH-1000XM5", "price": 348 }]
    }
  }
}

Properties

name
string
required
Unique identifier for the simulation. Auto-generated from the filename.
userMessage
string
A decorative message shown in the inspector interface. Has no functional purpose.
tool
string
required
A string referencing a tool filename (without .ts) in src/tools/.
{ "tool": "show-albums" }
toolInput
Record<string, unknown>
Mock input parameters for the tool call. Accessible via useToolData().
toolResult
object
Mock data for the tool response. The structuredContent property is passed to your component via useToolData().
{
  content?: Array<{ type: string; text: string }>;
  structuredContent?: unknown;
  isError?: boolean;
}
hostContext
Partial<McpUiHostContext>
Initial host context for the simulation. Accessible via useHostContext().
serverTools
Record<string, ServerToolMock>
Mock responses for callServerTool calls made by the resource. Keys are tool names. Values are either a single CallToolResult (always returned) or an array of { when, result } entries for argument-based conditional matching.
"serverTools": {
  "review": [
    { "when": { "confirmed": true }, "result": { "structuredContent": { "status": "success" } } },
    { "when": { "confirmed": false }, "result": { "structuredContent": { "status": "cancelled" } } }
  ]
}

MCP SDK Types

The simulation interface uses official types from @modelcontextprotocol/sdk:

Tool

interface Tool {
  name: string;
  description?: string;
  inputSchema: JSONSchema;
  title?: string;
  annotations?: {
    readOnlyHint?: boolean;
    destructiveHint?: boolean;
    idempotentHint?: boolean;
    openWorldHint?: boolean;
  };
  _meta?: Record<string, unknown>;
}

Tool Visibility

The tool._meta.ui.visibility field controls which contexts can invoke the tool:
type McpUiToolVisibility = ('model' | 'app')[];
  • "model" — The AI model can call this tool
  • "app" — The app can call this tool (via useCallServerTool)
When set in the simulation JSON, the MCP server preserves this metadata when registering tools with the host.

Resource

interface Resource {
  name: string;
  uri: string;
  title?: string;
  description?: string;
  mimeType?: string;
  _meta?: Record<string, unknown>;
}

Resource Metadata (_meta.ui)

The resource._meta.ui field configures resource rendering behavior:
interface McpUiResourceMeta {
  /** Whether the host should render a border around the resource */
  prefersBorder?: boolean;

  /** Origin isolation domain for the resource iframe (used by web hosts) */
  domain?: string;

  /** Sandbox permissions for the resource iframe */
  permissions?: McpUiResourcePermissions;

  /** Content Security Policy for the resource iframe */
  csp?: McpUiResourceCsp;
}
Permissions
interface McpUiResourcePermissions {
  camera?: boolean;
  microphone?: boolean;
  geolocation?: boolean;
  clipboardWrite?: boolean;
}
The inspector maps these to iframe allow attribute directives. Only declared permissions are enabled.
CSP
interface McpUiResourceCsp {
  connectDomains?: string[];     // Allowed fetch/XHR/WebSocket origins
  resourceDomains?: string[];    // Allowed image/media/font origins
  frameDomains?: string[];       // Allowed nested iframe origins
  baseUriDomains?: string[];     // Allowed base URI origins
}
Example resource config with metadata:
// src/resources/map/map.tsx — name auto-derived as 'map' from directory
export const resource: ResourceConfig = {
  description: 'Interactive map',
  _meta: {
    ui: {
      prefersBorder: true,
      permissions: { geolocation: true },
      csp: {
        connectDomains: ['https://api.mapbox.com'],
        resourceDomains: ['https://tiles.mapbox.com'],
        frameDomains: ['https://embed.mapbox.com'],
      },
    },
  },
};

See Also

Inspector

Component API reference.

runMCPServer

MCP server API reference.