Skip to content

Local-first MCP App framework for ChatGPT Apps, Claude, and more.

License

Notifications You must be signed in to change notification settings

Sunpeak-AI/sunpeak

Repository files navigation

sunpeak

npm version npm downloads stars CI License TypeScript React

Local-first MCP Apps framework.

Quickstart, build, test, and ship your Claude or ChatGPT App!

Demo (Hosted) ~ Demo (Video) ~ Discord (NEW) ~ Documentation ~ GitHub

Quickstart

Requirements: Node (20+), pnpm (10+)

pnpm add -g sunpeak
sunpeak new

To add sunpeak to an existing project, refer to the documentation.

Overview

sunpeak is an npm package that helps you build MCP Apps (interactive UI resources) while keeping your MCP server client-agnostic. Built on the MCP Apps SDK (@modelcontextprotocol/ext-apps). sunpeak consists of:

The sunpeak library

  1. Runtime APIs: Strongly typed React hooks for interacting with the host runtime (useApp, useToolData, useAppState, useHostContext, useUpdateModelContext, useAppTools), architected to support generic and platform-specific features (ChatGPT, Claude, etc.). Platform-specific hooks like useUploadFile, useRequestModal, and useRequestCheckout are available via sunpeak/platform/chatgpt, with isChatGPT() / isClaude() platform detection via sunpeak/platform.
  2. Multi-host simulator: React component replicating host runtimes (ChatGPT, Claude) to test Apps locally and automatically via UI, props, or URL parameters.
  3. MCP server: Serve Resources with mock data to hosts like ChatGPT and Claude with HMR (no more cache issues or 5-click manual refreshes).

The sunpeak framework

Next.js for MCP Apps. Using an example App my-app with a Review UI (MCP resource), sunpeak projects look like:

my-app/
├── src/resources/
│   └── review/
│       └── review-resource.tsx                 # Review UI component + resource metadata.
├── tests/simulations/
│   └── review/
│       ├── review-{scenario1}-simulation.json  # Mock state for testing.
│       └── review-{scenario2}-simulation.json  # Mock state for testing.
└── package.json
  1. Project scaffold: Complete development setup with the sunpeak library.
  2. UI components: Production-ready components following MCP App design guidelines.
  3. Convention over configuration:
    1. Create a UI by creating a -resource.tsx file that exports a ResourceConfig and a React component (example below).
    2. Create test state (Simulations) for local dev, host dev, automated testing, and demos by creating a -simulation.json file. (example below)

The sunpeak CLI

Commands for managing MCP Apps:

  • sunpeak new [name] [resources] - Create a new project
  • sunpeak dev - Start dev server with MCP endpoint and live simulator
  • sunpeak build - Build resources for production
  • sunpeak upgrade - Upgrade sunpeak to latest version

Example App

Example Resource, Simulation, and testing file (using the Simulator) for an MCP resource called "Review".

Resource Component

my-app/
├── src/resources/
│   └── review/
│       └── review-resource.tsx # This!

Each resource .tsx file exports both the React component and the MCP resource metadata:

// src/resources/review/review-resource.tsx

import { useToolData } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';

export const resource: ResourceConfig = {
  name: 'review',
  description: 'Visualize and review a code change',
  _meta: { ui: { csp: { resourceDomains: ['https://cdn.example.com'] } } },
};

export function ReviewResource() {
  const { output: data } = useToolData<unknown, { title: string }>();

  return <h1>Review: {data?.title}</h1>;
}

Simulation

├── tests/simulations/
│   └── review/
│       ├── review-{scenario1}-simulation.json  # These!
│       └── review-{scenario2}-simulation.json  # These!

sunpeak testing object (.json) defining key App-owned states.

Testing an MCP App requires setting a lot of state: state in your backend, MCP tools, and the host runtime.

Simulation files contain an official MCP tool object, toolInput (the arguments sent to CallTool), and toolResult (the CallToolResult response) so you can define backend, tool, and runtime states for testing.

// tests/simulations/review-diff-simulation.json

{
  // Official MCP tool object.
  "tool": {
    "name": "review-diff",
    "description": "Show a review dialog for a proposed code diff",
    "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
    "title": "Diff Review",
    "annotations": { "readOnlyHint": false },
    "_meta": {
      "ui": { "visibility": ["model", "app"] },
    },
  },
  // Tool input arguments (sent to CallTool).
  "toolInput": {
    "changesetId": "cs_789",
    "title": "Refactor Authentication Module",
  },
  // Tool result data (CallToolResult response).
  "toolResult": {
    "structuredContent": {
      "title": "Refactor Authentication Module",
      // ...
    },
  },
}

Simulator

├── tests/e2e/
│   └── review.spec.ts # This! (not pictured above for simplicity)
└── package.json

The Simulator allows you to set host state (like host platform, light/dark mode) via URL params, which can be rendered alongside your Simulations and tested via pre-configured Playwright end-to-end tests (.spec.ts).

Using the Simulator and Simulations, you can test all possible App states locally and automatically across hosts (ChatGPT, Claude)!

// tests/e2e/review.spec.ts

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

const hosts = ['chatgpt', 'claude'] as const;

for (const host of hosts) {
  test.describe(`Review Resource [${host}]`, () => {
    test.describe('Light Mode', () => {
      test('should render review title with correct styles', async ({ page }) => {
        const params = { simulation: 'review-diff', theme: 'light', host }; // Set sim & host state.
        await page.goto(createSimulatorUrl(params));

        // Resource content renders inside an iframe
        const iframe = page.frameLocator('iframe');
        const title = iframe.locator('h1:has-text("Refactor Authentication Module")');
        await expect(title).toBeVisible();

        const color = await title.evaluate((el) => window.getComputedStyle(el).color);

        // Light mode should render dark text.
        expect(color).toBe('rgb(13, 13, 13)');
      });
    });
  });
}

Coding Agent Skill

Install the create-sunpeak-app skill to give your coding agent (Claude Code, Cursor, etc.) built-in knowledge of sunpeak patterns, hooks, simulation files, and testing conventions:

npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app

Resources