moon
Detail card. A date's moon phase with a 7-day strip; buttons call the tool again through the host.
MCP App framework · ChatGPT Apps · Claude connectors
Write an MCP tool and the UI it shows once. moonpeak renders it in ChatGPT, Claude, VS Code, Goose and every MCP Apps host, from the same code.
An MCP App is an MCP tool plus a ui:// resource the host renders next to the conversation. Doing that by hand means a server, a bundler that inlines everything into one HTML file, host metadata for every client and a fake host to look at it. moonpeak does all of it from two folders.
structuredContent; throw and it becomes a tool error.openai/* keys, so ChatGPT renders them too.my-app/ ├─ src/ │ ├─ tools/show-moon.ts # one MCP tool │ ├─ resources/moon/moon.tsx # its View │ └─ lib/ └─ tests/ ├─ simulations/show-moon.json ├─ e2e/moon.spec.ts └─ evals/moon.eval.json
import { z } from "zod";
export const tool = {
title: "Show moon phase",
description: "Show the moon phase for a date.",
resource: "moon", // render src/resources/moon
};
export const schema = { date: z.string().optional() };
export default async function ({ date }) {
return { date, phase: "Full Moon", illumination: 1 };
}Export the tool's metadata, a zod schema and a handler. Point resource at a View folder to give it a UI, or leave it out for a text tool. Mark helper tools visibility: ["app"] and only your View can call them; the model never sees them.
Twenty-seven typed hooks cover everything a View needs: the tool's input and output, theme, locale, safe areas, container size, display mode requests, per-View state that survives reloads, calling other tools, posting into the chat and updating model context.
Every hook →import { useToolData, useTheme, useCallTool } from "moonpeak";
export default function Moon() {
const { output, isPending } = useToolData();
const theme = useTheme();
const callTool = useCallTool();
if (isPending) return <p>Reading the sky…</p>;
return (
<div className={theme}>
<h1>{output.phase}</h1>
<button onClick={() => callTool("show-moon", next)}>Next week</button>
</div>
);
}npx moonpeak new asks which resources to start with. Each one is a working View with simulations, e2e tests and evals.
Detail card. A date's moon phase with a 7-day strip; buttons call the tool again through the host.
Carousel. Swipeable cards of the upcoming phases; "load more" pages through the host, selection is shared with the model.
Review and confirm. The model proposes a stargazing night, the user confirms it through an app-only tool.
npx moonpeak new my-app calendar,reviewpick resources up frontmoonpeak build # dist/resources/*.html + dist/server.mjs
moonpeak start # http://localhost:8000/mcp
# or mount it yourself (Workers, Hono, Deno, Bun, Next.js)
import { createHandler } from "moonpeak/server";moonpeak build inlines every View and bundles your tools. Serve the result with moonpeak start, or mount createHandler on any runtime that speaks Fetch, or createNodeHandler on Express and node:http. Then add the HTTPS URL as a Claude connector or a ChatGPT app.