Token导航 LogoToken导航

Agent Skill · 研究检索

create-sunpeak-app(创建日峰应用程序)

create-sunpeak-app 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

create-sunpeak-app 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为操作浏览器;许可证信息还不明确,第三方扫描有提醒项,按安装量在研究检索类的 73,675 个 Skill 里排第 53,392。

总安装 535周安装 23GitHub Stars 172下载 188核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:create-sunpeak-app(创建日峰应用程序)
来源仓库:https://github.com/sunpeak-ai/sunpeak
仓库路径:skills/create-sunpeak-app
安装命令:
npx skills add https://github.com/sunpeak-ai/sunpeak --skill create-sunpeak-app
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/sunpeak-ai/sunpeak --skill create-sunpeak-app

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

create-sunpeak-app 用于构建基于 Model Context Protocol 的交互式 MCP 应用界面。适用于 Codex、Claude、Cursor、Gemini CLI 等宿主环境,适合需要内嵌 UI 的 AI 工具开发。

提供 React 钩子、开发检查器与 CLI 工具链,支持热重载与结构规范。安装前请确认权限范围、维护状态及是否会触发项目模板生成与依赖安装操作。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/create-sunpeak-app

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/sunpeak-ai/sunpeak --skill create-sunpeak-app;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要操作浏览器,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

create-sunpeak-app 怎么安装?

在终端执行 npx skills add https://github.com/sunpeak-ai/sunpeak --skill create-sunpeak-app。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

create-sunpeak-app 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

create-sunpeak-app 需要什么权限?

权限级别是操作浏览器。该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

create-sunpeak-app 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

create-sunpeak-app 用的人多吗?

公开数据是累计安装 535 次,近一周安装 23 次,下载 188 次,GitHub 172 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

create-sunpeak-app 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
create-sunpeak-app

创建日峰应用程序 · 本页

535172操作浏览器未明确有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Create Sunpeak App

Sunpeak is a React framework built on @modelcontextprotocol/ext-apps for building MCP Apps with interactive UIs that run inside AI chat hosts (ChatGPT, Claude). It provides React hooks, a dev inspector, a CLI (sunpeak dev / sunpeak build / sunpeak start), and a structured project convention.

Getting Reference Code

Clone the sunpeak repo for working examples:

git clone --depth 1 https://github.com/Sunpeak-AI/sunpeak /tmp/sunpeak

Template app lives at /tmp/sunpeak/packages/sunpeak/template/. This is the canonical project structure — read it first.

Project Structure

sunpeak-app/
├── src/
│   ├── resources/
│   │   └── {name}/
│   │       └── {name}.tsx            # Resource component + ResourceConfig export
│   ├── tools/
│   │   └── {name}.ts                 # Tool metadata, Zod schema, handler
│   ├── server.ts                     # Optional server entry (auth, config, icons)
│   └── styles/
│       └── globals.css               # Tailwind imports
├── tests/
│   ├── simulations/
│   │   └── *.json                    # Simulation fixture files (flat directory)
│   ├── e2e/
│   │   └── {name}.spec.ts            # Playwright inspector tests
│   ├── evals/
│   │   ├── eval.config.ts            # Eval config (models, runs, defaults)
│   │   ├── .env                      # API keys (gitignored)
│   │   └── {name}.eval.ts            # Eval specs (one per resource or tool)
│   └── live/
│       ├── playwright.config.ts      # Live test config (long timeouts, single worker)
│       └── {name}.spec.ts            # Live tests against real ChatGPT (one per resource)
├── package.json
└── (vite.config.ts, tsconfig.json, etc. managed by sunpeak CLI)

Discovery is convention-based:

  • Resources: src/resources/{name}/{name}.tsx (name derived from directory)
  • Tools: src/tools/{name}.ts (name derived from filename)
  • Simulations: tests/simulations/*.json (flat directory, "tool" string references tool filename)

Resource Component Pattern

Every resource file exports two things:

  1. resource — A ResourceConfig object with MCP resource metadata (name is auto-derived from directory)
  2. A named React component — The UI ({Name}Resource)
import { useToolData, useHostContext, useDisplayMode, SafeArea } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';

// MCP resource metadata (name auto-derived from directory: src/resources/weather/)
export const resource: ResourceConfig = {
  title: 'Weather',
  description: 'Show current weather conditions',
  mimeType: 'text/html;profile=mcp-app',
  _meta: {
    ui: {
      csp: {
        resourceDomains: ['https://cdn.example.com'],
      },
    },
  },
};

// Type definitions
interface WeatherInput {
  city: string;
  units?: 'metric' | 'imperial';
}

interface WeatherOutput {
  temperature: number;
  condition: string;
  humidity: number;
}

// React component
export function WeatherResource() {
  // All hooks must be called before any early return
  const { input, output, isLoading } = useToolData<WeatherInput, WeatherOutput>();
  const context = useHostContext();
  const displayMode = useDisplayMode();

  if (isLoading) return <div className="p-4 text-[var(--color-text-secondary)]">Loading...</div>;

  const isFullscreen = displayMode === 'fullscreen';
  const hasTouch = context?.deviceCapabilities?.touch ?? false;

  return (
    <SafeArea className={isFullscreen ? 'flex flex-col h-screen' : undefined}>
      <div className="p-4">
        <h1 className="text-[var(--color-text-primary)] font-semibold">{input?.city}</h1>
        <p className={`${hasTouch ? 'text-base' : 'text-sm'} text-[var(--color-text-secondary)]`}>
          {output?.temperature}° — {output?.condition}
        </p>
      </div>
    </SafeArea>
  );
}

Rules:

  • Always wrap in <SafeArea> to respect host insets
  • Use MCP standard CSS variables via Tailwind arbitrary values: text-[var(--color-text-primary)], text-[var(--color-text-secondary)], bg-[var(--color-background-primary)], border-[var(--color-border-tertiary)]
  • useToolData<TInput, TOutput>() — provide types for both input and output
  • All hooks must be called before any early return (React rules of hooks)
  • Do NOT mutate app directly inside hooks — use eslint-disable-next-line react-hooks/immutability for class setters

Tool Files

Each tool .ts file exports metadata, a Zod schema, an optional output schema, and a handler. The resource field links a tool to its UI — omit it for data-only tools:

// src/tools/show-weather.ts
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';

// 1. Tool metadata (resource links to src/resources/weather/ — omit for tools without a UI)
export const tool: AppToolConfig = {
  resource: 'weather',
  title: 'Show Weather',
  description: 'Show current weather conditions',
  annotations: { readOnlyHint: true },
  _meta: { ui: { visibility: ['model', 'app'] } },
};

// 2. Zod schema (auto-converted to JSON Schema for MCP)
export const schema = {
  city: z.string().describe('City name'),
  units: z.enum(['metric', 'imperial']).describe('Temperature units'),
};

// 3. Optional output schema (enables structured output validation)
export const outputSchema = {
  temperature: z.number(),
  condition: z.string(),
  humidity: z.number(),
};

// 4. Handler — return structured data for the UI
export default async function (args: { city: string; units?: string }, extra: ToolHandlerExtra) {
  return {
    structuredContent: {
      temperature: 72,
      condition: 'Partly Cloudy',
      humidity: 55,
    },
  };
}

Backend-Only Tools (Confirmation Loop)

A common pattern pairs a UI tool (for review) with a backend-only tool (for execution). The UI tool's structuredContent includes a reviewTool field. The resource component reads it and calls the backend tool via useCallServerTool when the user confirms:

// src/tools/review.ts — no resource field, shared by all review variants
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';

export const tool: AppToolConfig = {
  title: 'Confirm Review',
  description: 'Execute or cancel a reviewed action after user approval',
  annotations: { readOnlyHint: false },
  _meta: { ui: { visibility: ['model', 'app'] } },
};

export const schema = {
  action: z.string().describe('Action identifier (e.g., "place_order", "apply_changes")'),
  confirmed: z.boolean().describe('Whether the user confirmed'),
  decidedAt: z.string().describe('ISO timestamp of decision'),
  payload: z.record(z.unknown()).optional().describe('Domain-specific data'),
};

type Args = z.infer<z.ZodObject<typeof schema>>;

export default async function (args: Args, _extra: ToolHandlerExtra) {
  if (!args.confirmed) {
    return {
      content: [{ type: 'text' as const, text: 'Cancelled.' }],
      structuredContent: { status: 'cancelled', message: 'Cancelled.' },
    };
  }
  return {
    content: [{ type: 'text' as const, text: 'Completed.' }],
    structuredContent: { status: 'success', message: 'Completed.' },
  };
}

The UI tool returns reviewTool in its response, and the resource calls useCallServerTool on accept/reject. The tool returns both content (human-readable text for the host model) and structuredContent (with status and message for the UI). The resource reads structuredContent.status to determine success/error styling and displays structuredContent.message. One review tool handles all review variants (purchases, diffs, posts) via the action field. The inspector returns mock simulation data for callServerTool calls, matching real host behavior. See the template's review resource for the full implementation.

Simulation Files

Simulations are JSON fixtures that power the dev inspector. Place them in tests/simulations/ as flat JSON files:

{
  "tool": "show-weather",
  "userMessage": "Show me the weather in Austin, TX.",
  "toolInput": {
    "city": "Austin",
    "units": "imperial"
  },
  "toolResult": {
    "structuredContent": {
      "temperature": 72,
      "condition": "Partly Cloudy",
      "humidity": 55
    }
  }
}

Key fields:

  • tool — String referencing a tool filename in src/tools/ (without .ts)
  • userMessage — Decorative text shown in inspector (no functional purpose)
  • toolInput — Arguments sent to the tool (shown as input to useToolData)
  • toolResult.structuredContent — The data rendered by useToolData().output
  • toolResult.content[] — Text fallback for non-UI hosts
  • serverTools — Mock responses for callServerTool calls. Keys are tool names. Values are either a single CallToolResult (always returned) or an array of {when, result} entries for conditional matching against call arguments.

Example with serverTools (for resources that call backend-only tools):

{
  "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." } } }
    ]
  }
}

Multiple simulations per tool are supported: review-diff.json, review-post.json sharing the same resource via the same tool's resource field.

Core Hooks Reference

All hooks are imported from sunpeak:

HookReturnsDescription
useToolData<TIn, TOut>(){input, inputPartial, output, isLoading, isError, isCancelled}Reactive tool data from host
useHostContext()`McpUiHostContext \null`Host context (theme, locale, capabilities, etc.)
useTheme()`'light' \'dark' \undefined`Current theme
useDisplayMode()`'inline' \'pip' \'fullscreen'`Current display mode (defaults to 'inline')
useLocale()stringHost locale (e.g. 'en-US', defaults to 'en-US')
useTimeZone()stringIANA time zone (falls back to browser time zone)
usePlatform()`'web' \'desktop' \'mobile' \undefined`Host-reported platform type
useDeviceCapabilities(){touch?, hover?}Device input capabilities
useUserAgent()`string \undefined`Host application identifier
useStyles()`McpUiHostStyles \undefined`Host style configuration (CSS variables, fonts)
useToolInfo()`{id?, tool} \undefined`Metadata about the tool call that created this app
useSafeArea(){top, right, bottom, left}Safe area insets (px)
useViewport(){width, height, maxWidth, maxHeight}Container dimensions (px)
useIsMobile()booleanTrue if viewport is mobile-sized
useApp()`App \null`Raw MCP App instance for direct SDK calls
useCallServerTool()(params) => Promise<result>Returns a function to call a server-side tool by name
useCreateSamplingMessage()(params) => Promise<result>Request LLM completions from the host via sampling/createMessage
useRegisterTool()(name, config, cb) => handleRegister app-side tools the host can call; returns handle with enable/disable/remove
useSendMessage()(params) => Promise<void>Returns a function to send a message to the conversation
useOpenLink()(params) => Promise<void>Returns a function to open a URL through the host
useRequestDisplayMode(){requestDisplayMode, availableModes}Request 'inline', 'pip', or 'fullscreen'; check availableModes first
useDownloadFile()(params) => Promise<result>Download files through the host (works cross-platform)
useReadServerResource()(params) => Promise<result>Read a resource from the MCP server by URI
useListServerResources()(params?) => Promise<result>List available resources on the MCP server
useUpdateModelContext()(params) => Promise<void>Push state to the host's model context directly
useSendLog()(params) => Promise<void>Send debug log to host
useSendToolListChanged()() => Promise<void>Notify host that app's tool list changed (after register/remove/enable/disable)
useHostInfo(){hostVersion, hostCapabilities}Host name, version, and supported capabilities
useTeardown(fn)voidRegister a teardown handler
useAppTools(config)voidRegister tools the app provides to the host (bidirectional tool calling)
useRequestTeardown()() => Promise<void>Request the host to tear down this app instance
useAppState(initial)[state, setState]React state that auto-syncs to host model context via updateModelContext()

useRequestDisplayMode details

const { requestDisplayMode, availableModes } = useRequestDisplayMode();

// Always check availability before requesting
if (availableModes?.includes('fullscreen')) {
  await requestDisplayMode('fullscreen');
}
if (availableModes?.includes('pip')) {
  await requestDisplayMode('pip');
}

useCallServerTool details

const callTool = useCallServerTool();

const result = await callTool({ name: 'get-weather', arguments: { city: 'Austin' } });
// result: { content?: [...], isError?: boolean }

useSendMessage details

const sendMessage = useSendMessage();

await sendMessage({
  role: 'user',
  content: [{ type: 'text', text: 'Please refresh the data.' }],
});

useAppState details

State is preserved in React and automatically sent to the host via updateModelContext() after each update, so the LLM can see the current UI state in its context window.

const [state, setState] = useAppState<{ decision: 'accepted' | 'rejected' | null }>({
  decision: null,
});
// setState triggers a re-render AND pushes state to the model context
setState({ decision: 'accepted' });

useToolData details

const {
  input,         // TInput | null — final tool input arguments
  inputPartial,  // TInput | null — partial (streaming) input as it generates
  output,        // TOutput | null — tool result (structuredContent ?? content)
  isLoading,     // boolean — true until first toolResult arrives
  isError,       // boolean — true if tool returned an error
  isCancelled,   // boolean — true if tool was cancelled
  cancelReason,  // string | null
} = useToolData<MyInput, MyOutput>(defaultInput, defaultOutput);

Use inputPartial for progressive rendering during LLM generation. Use output for the final data.

useDownloadFile details

const downloadFile = useDownloadFile();

// Download embedded text content
await downloadFile({
  contents: [{
    type: 'resource',
    resource: {
      uri: 'file:///export.json',
      mimeType: 'application/json',
      text: JSON.stringify(data, null, 2),
    },
  }],
});

// Download embedded binary content
await downloadFile({
  contents: [{
    type: 'resource',
    resource: {
      uri: 'file:///image.png',
      mimeType: 'image/png',
      blob: base64EncodedPng,
    },
  }],
});

useReadServerResource / useListServerResources details

const readResource = useReadServerResource();
const listResources = useListServerResources();

// List available resources
const result = await listResources();
for (const resource of result?.resources ?? []) {
  console.log(resource.name, resource.uri);
}

// Read a specific resource by URI
const content = await readResource({ uri: 'videos://bunny-1mb' });

useAppTools details

Register tools the app provides to the host for bidirectional tool calling. Requires tools capability.

import { useAppTools } from 'sunpeak';

function MyResource() {
  useAppTools({
    tools: [{
      name: 'get-selection',
      description: 'Get current user selection',
      inputSchema: { type: 'object', properties: {} },
      handler: async () => ({
        content: [{ type: 'text', text: selectedText }],
      }),
    }],
  });
}

Commands

sunpeak new         # Scaffold a new sunpeak app project
sunpeak dev         # Start dev server (Vite + MCP server, port 3000 web / 8000 MCP)
sunpeak build       # Build resources + compile tools to dist/
sunpeak start       # Start production MCP server (real handlers, auth, Zod validation)
sunpeak upgrade     # Upgrade sunpeak to the latest version

The sunpeak dev command starts both the Vite dev server and the MCP server together. The inspector runs at http://localhost:3000. Connect ChatGPT to http://localhost:8000/mcp (or use ngrok for remote testing).

Use sunpeak build && sunpeak start to test production behavior locally with real handlers instead of simulation fixtures.

The sunpeak dev command supports two orthogonal flags for testing different combinations:

  • --prod-tools — Route callServerTool to real tool handlers instead of simulation mocks
  • --prod-resources — Serve production-built HTML from dist/ instead of Vite HMR
  • --prod-tools --prod-resources — Full smoke test: production bundles with real handlers

Production Server Options

sunpeak start                          # Default: port 8000, all interfaces
sunpeak start --port 3000              # Custom port
sunpeak start --host 127.0.0.1         # Bind to localhost only
sunpeak start --json-logs              # Structured JSON logging
PORT=3000 HOST=127.0.0.1 sunpeak start # Via environment variables

The production server provides:

  • /health — Health check endpoint ({"status":"ok","uptime":N}) for load balancer probes and monitoring
  • /mcp — MCP Streamable HTTP endpoint
  • Graceful shutdown on SIGTERM/SIGINT (5-second drain)
  • Structured JSON logging (--json-logs) for log aggregation (Datadog, CloudWatch, etc.)

Production Build Output

sunpeak build generates optimized bundles in dist/:

dist/
├── weather/
│   ├── weather.html   # Self-contained bundle (JS + CSS inlined)
│   └── weather.json   # ResourceConfig with generated uri for cache-busting
├── tools/
│   ├── show-weather.js  # Compiled tool handler + Zod schema
│   └── ...
├── server.js          # Compiled server entry (if src/server.ts exists)
└── ...

sunpeak start loads everything from dist/ and starts a production MCP server with real tool handlers, Zod input validation, and optional auth from src/server.ts.

Host Detection

import { isChatGPT, isClaude, detectHost } from 'sunpeak/host';

// In a resource component
function MyResource() {
  const host = detectHost(); // 'chatgpt' | 'claude' | 'unknown'

  if (isChatGPT()) {
    // Safe to use ChatGPT-specific hooks
  }
}

ChatGPT-Specific Hooks

Import from sunpeak/host/chatgpt. Always feature-detect before use.

import { useUploadFile, useRequestModal, useRequestCheckout } from 'sunpeak/host/chatgpt';
import { isChatGPT } from 'sunpeak/host';

function MyResource() {
  // Only call these when on ChatGPT
  const { upload } = useUploadFile();
  const { open } = useRequestModal();
  const { checkout } = useRequestCheckout();
}
HookDescription
useUploadFile()Upload a file to ChatGPT, returns file ID
useGetFileDownloadUrl(fileId)Deprecated — use useDownloadFile() from sunpeak instead
useRequestModal(params)Open a host-native modal dialog
useRequestCheckout(session)Trigger ChatGPT instant checkout

SafeArea Component

Always wrap resource content in <SafeArea> to respect host insets:

import { SafeArea } from 'sunpeak';

export function MyResource() {
  return (
    <SafeArea>
      {/* your content */}
    </SafeArea>
  );
}

SafeArea applies padding equal to useSafeArea() insets automatically.

Styling with MCP Standard Variables

Use MCP standard CSS variables via Tailwind arbitrary values instead of raw colors. These variables adapt automatically to each host's theme (ChatGPT, Claude):

Tailwind ClassCSS VariableUsage
text-[var(--color-text-primary)]--color-text-primaryPrimary text
text-[var(--color-text-secondary)]--color-text-secondarySecondary/muted text
bg-[var(--color-background-primary)]--color-background-primaryCard/surface background
bg-[var(--color-background-secondary)]--color-background-secondarySecondary/nested surface background
bg-[var(--color-background-tertiary)]--color-background-tertiaryTertiary background
bg-[var(--color-ring-primary)]--color-ring-primaryPrimary action color (e.g. badge fill)
border-[var(--color-border-tertiary)]--color-border-tertiarySubtle border
border-[var(--color-border-primary)]--color-border-primaryDefault border
dark: variant—Dark mode via [data-theme="dark"]

These variables use CSS light-dark() so they respond to theme changes automatically. The dark: Tailwind variant also works via [data-theme="dark"].

Testing

For all testing capabilities (e2e tests, visual regression, live tests against real ChatGPT, multi-model evals, Playwright config), install the test-mcp-server skill:

pnpm dlx skills add Sunpeak-AI/sunpeak@test-mcp-server

The testing skill works with any MCP server (not just sunpeak projects). Simulations (above) are part of the dev workflow and defined here. Tests consume them via the mcp fixture.

For testing commands, see the test-mcp-server skill. Quick reference: sunpeak test (unit + e2e), sunpeak test --visual (visual regression), sunpeak test --live (real ChatGPT), sunpeak test --eval (multi-model evals).

ResourceConfig Fields

import type { ResourceConfig } from 'sunpeak';

// name is auto-derived from the directory (src/resources/my-resource/)
export const resource: ResourceConfig = {
  title: 'My Resource',           // Human-readable title
  description: 'What it shows',   // Description for MCP hosts
  mimeType: 'text/html;profile=mcp-app',  // Required for MCP App resources
  _meta: {
    ui: {
      csp: {
        resourceDomains: ['https://cdn.example.com'],    // Image/script CDNs
        connectDomains: ['https://api.example.com'],     // API fetch targets
      },
    },
  },
};

Common Mistakes

  1. Hooks before early returns — All hooks must run unconditionally. Move useMemo/useEffect above any if (...) return blocks.
  2. Missing <SafeArea> — Always wrap content in <SafeArea> to respect host safe area insets.
  3. Hardcoded colors — Use MCP standard CSS variables via Tailwind arbitrary values (text-[var(--color-text-primary)], bg-[var(--color-background-primary)]) not raw colors.
  4. Simulation tool mismatch — The "tool" field in simulation JSON must match a tool filename in src/tools/ (e.g. "tool": "show-weather" matches src/tools/show-weather.ts).
  5. Mutating hook params — Use eslint-disable-next-line react-hooks/immutability for app.onteardown =... (class setter, not a mutation).
  6. Forgetting text fallback — Include toolResult.content[] in simulations for non-UI hosts.

Troubleshooting: App Not Rendering in ChatGPT/Claude

If the app doesn't show up after the tool is called, follow these steps:

  1. Check your tunnel — verify ngrok (or equivalent) is running, pointing to the right port, and using http not https upstream (ngrok http 8000).
  2. Check your dev server — make sure sunpeak dev is running and the MCP server started on the expected port (watch for "port was in use" messages).
  3. Restart sunpeak dev — stops the dev server (Ctrl+C) and starts fresh. This clears stale connections.
  4. Refresh or re-add the MCP server — in the host's settings, click refresh on the MCP server entry, or remove and re-add it with the tunnel URL.
  5. Hard refresh the host page — Cmd+Shift+R / Ctrl+Shift+R clears cached MCP connections.
  6. Open a new chat — both hosts cache iframe content per-conversation. A new chat forces a fresh connection.

Full troubleshooting guide: https://sunpeak.ai/docs/app-framework/guides/troubleshooting

Export Paths

ImportContents
sunpeakHooks, types, SDK re-exports, SafeArea, inspector + chatgpt namespaces
sunpeak/mcpServer utilities (runMCPServer, createMcpHandler, createProductionMcpServer), tool types (AppToolConfig, ToolHandlerExtra), server config (ServerConfig)
sunpeak/inspectorGeneric Inspector, host shell system, infrastructure
sunpeak/chatgptChatGPT host shell + Inspector re-export
sunpeak/claudeClaude host shell + Inspector re-export
sunpeak/hostHost detection (isChatGPT, isClaude, detectHost)
sunpeak/host/chatgptChatGPT-specific hooks (useUploadFile, useRequestModal, useRequestCheckout)
sunpeak/style.cssMain stylesheet

For testing export paths (sunpeak/test, sunpeak/eval, etc.), see the test-mcp-server skill.

References

平台分布

Codex

35.49%
按下载量换算67

Claude

31.22%
按下载量换算59

Cursor

20.05%
按下载量换算38

Gemini CLI

10.15%
按下载量换算19

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills