> ## 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.

# Simulation

> Define test scenarios for your resources.

<Badge color="yellow">sunpeak API</Badge>

## 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:

```json theme={null}
// 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.

```json theme={null}
// 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:

```json theme={null}
"serverTools": {
  "review": {
    "content": [{ "type": "text", "text": "Done." }],
    "structuredContent": { "status": "success", "message": "Done." }
  }
}
```

**Conditional form** — match against call arguments:

```json theme={null}
"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.

```typescript theme={null}
// 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.

```typescript theme={null}
// 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:

```typescript theme={null}
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()`:

```json theme={null}
{
  "tool": "search",
  "toolInput": { "query": "headphones" },
  "toolResult": {
    "structuredContent": {
      "results": [{ "name": "Sony WH-1000XM5", "price": 348 }]
    }
  }
}
```

## Properties

<ResponseField name="name" type="string" required>
  Unique identifier for the simulation. Auto-generated from the filename.
</ResponseField>

<ResponseField name="userMessage" type="string">
  A decorative message shown in the inspector interface. Has no functional purpose.
</ResponseField>

<ResponseField name="tool" type="string" required>
  A string referencing a tool filename (without `.ts`) in `src/tools/`.

  ```json theme={null}
  { "tool": "show-albums" }
  ```
</ResponseField>

<ResponseField name="toolInput" type="Record<string, unknown>">
  Mock input parameters for the tool call. Accessible via `useToolData()`.
</ResponseField>

<ResponseField name="toolResult" type="object">
  Mock data for the tool response. The `structuredContent` property is passed to your component via `useToolData()`.

  ```typescript theme={null}
  {
    content?: Array<{ type: string; text: string }>;
    structuredContent?: unknown;
    isError?: boolean;
  }
  ```
</ResponseField>

<ResponseField name="hostContext" type="Partial<McpUiHostContext>">
  Initial host context for the simulation. Accessible via `useHostContext()`.
</ResponseField>

<ResponseField name="serverTools" type="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.

  ```json theme={null}
  "serverTools": {
    "review": [
      { "when": { "confirmed": true }, "result": { "structuredContent": { "status": "success" } } },
      { "when": { "confirmed": false }, "result": { "structuredContent": { "status": "cancelled" } } }
    ]
  }
  ```
</ResponseField>

## MCP SDK Types

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

### Tool

```typescript theme={null}
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:

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```tsx theme={null}
// 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

<Card horizontal title="Inspector" icon="desktop" href="/testing/inspector">
  Component API reference.
</Card>

<Card horizontal title="runMCPServer" icon="server" href="/app-framework/run-mcp-server">
  MCP server API reference.
</Card>
