
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
@creature-run/sdk
Advanced tools
SDK for building MCP Apps that work on both Creature and ChatGPT.
Write once, run everywhere—your app automatically works with MCP Apps (Creature) and ChatGPT Apps SDK.
npx @creature-run/create-app my-app
cd my-app
npm install
npm run dev
npm install @creature-run/sdk
The SDK is split into multiple entry points:
| Entry Point | Description |
|---|---|
@creature-run/sdk/server | MCP server for registering tools and resources |
@creature-run/sdk/core | Vanilla JS client (no React dependency) |
@creature-run/sdk/react | React hooks (wraps core) |
@creature-run/sdk/vite | Vite plugin for HMR support |
import { createApp } from "@creature-run/sdk/server";
import { z } from "zod";
const app = createApp({
name: "my-app",
version: "1.0.0",
});
app.resource({
name: "Dashboard",
uri: "ui://my-app/dashboard",
displayModes: ["pip"],
html: "ui/index.html",
icon: { svg: ICON_SVG, alt: "Dashboard" },
});
app.tool(
"show_dashboard",
{
description: "Show the dashboard",
input: z.object({
timeRange: z.string().optional(),
}),
ui: "ui://my-app/dashboard",
},
async ({ timeRange }) => {
const data = await fetchData(timeRange);
return { data, title: "Dashboard" };
}
);
app.start();
@creature-run/sdk/core)Use the core package for vanilla JavaScript, Vue, Svelte, or any non-React framework.
import { createHost } from "@creature-run/sdk/core";
const host = createHost({ name: "my-app", version: "1.0.0" });
host.subscribe((state, prevState) => {
console.log("Ready:", state.isReady);
console.log("Environment:", state.environment);
});
host.on("tool-result", (result) => {
document.getElementById("output").textContent = JSON.stringify(result.structuredContent);
});
host.on("theme-change", (theme) => {
document.body.classList.toggle("dark", theme === "dark");
});
host.connect();
document.getElementById("fetch-btn").addEventListener("click", async () => {
const result = await host.callTool("show_dashboard", { timeRange: "7d" });
console.log("Result:", result);
});
createHost(config)Factory function that auto-detects the environment and returns the appropriate client.
import { createHost } from "@creature-run/sdk/core";
const host = createHost({
name: "my-app",
version: "1.0.0",
});
host.connect() / host.disconnect()Start or stop listening for host messages.
host.connect();
window.addEventListener("beforeunload", () => {
host.disconnect();
});
host.getState()Get the current client state.
const state = host.getState();
// { isReady: boolean, environment: "mcp-apps" | "chatgpt" | "standalone", widgetState: object | null }
host.subscribe(listener)Subscribe to state changes. Returns an unsubscribe function.
const unsubscribe = host.subscribe((state, prevState) => {
if (state.isReady && !prevState.isReady) {
console.log("Host connected!");
}
});
unsubscribe();
host.on(event, handler)Register event handlers. Returns an unsubscribe function.
host.on("tool-input", (args) => {
console.log("Tool called with:", args);
});
host.on("tool-result", (result) => {
console.log("Tool result:", result.structuredContent);
});
host.on("theme-change", (theme) => {
// "light" or "dark" (MCP Apps only)
});
host.on("widget-state-change", (widgetState) => {
// Widget state restored from host
});
host.on("teardown", async () => {
// Cleanup before panel closes (MCP Apps only)
});
host.on("app-state", (appState) => {
// Session restoration data (MCP Apps only)
});
host.callTool(name, args)Call a tool on the MCP server.
const result = await host.callTool<MyDataType>("show_dashboard", {
timeRange: "30d",
});
console.log(result.structuredContent); // Typed as MyDataType
host.setWidgetState(state)Persist UI state across sessions.
host.setWidgetState({
selectedId: "item-1",
viewMode: "grid",
});
host.sendNotification(method, params)Send a notification to the host (MCP Apps only, no-op on ChatGPT).
host.sendNotification("custom/event", { data: "value" });
Use createChannel for real-time bidirectional communication with the server via WebSocket.
import { createChannel } from "@creature-run/sdk/core";
// Create channel client (get channelUrl from tool result)
const channel = createChannel<ClientMessage, ServerMessage>(channelUrl, {
onMessage: (msg) => {
if (msg.type === "output") {
console.log("Received:", msg.data);
}
},
onStatusChange: (status, error) => {
console.log("Status:", status); // "disconnected" | "connecting" | "connected" | "error"
if (error) console.error("Error:", error);
},
reconnect: true, // Auto-reconnect on disconnect (default: true)
reconnectInterval: 1000, // Base interval in ms (default: 1000, max 30s with backoff)
});
// Connect to channel
channel.connect();
// Send messages to server
channel.send({ type: "input", data: "Hello" });
// Check status
console.log(channel.status); // "connected"
// Disconnect when done
channel.disconnect();
For advanced use cases, you can instantiate clients directly:
import { McpHostClient, ChatGPTHostClient, detectEnvironment } from "@creature-run/sdk/core";
const environment = detectEnvironment();
const client = environment === "chatgpt"
? new ChatGPTHostClient({ name: "my-app", version: "1.0.0" })
: new McpHostClient({ name: "my-app", version: "1.0.0" });
client.connect();
@creature-run/sdk/react)React hooks that wrap the core vanilla JS client.
import { useHost, useToolResult } from "@creature-run/sdk/react";
function App() {
const { data, title, onToolResult } = useToolResult<DashboardData>();
const { callTool, isReady, environment } = useHost({
name: "my-app",
version: "1.0.0",
onToolResult,
});
useEffect(() => {
if (isReady) {
callTool("show_dashboard", {});
}
}, [isReady]);
return (
<div>
<h1>{title}</h1>
<span>{environment}</span>
<Dashboard data={data} />
</div>
);
}
useHost(config)Connect to the host (MCP Apps or ChatGPT).
const {
isReady,
callTool,
sendNotification,
environment,
widgetState,
setWidgetState,
} = useHost({
name: "my-app",
version: "1.0.0",
onToolInput: (args) => {},
onToolResult: (result) => {},
onThemeChange: (theme) => {},
onTeardown: async () => {},
onAppState: (state) => {},
onWidgetStateChange: (state) => {},
});
useToolResult<T>()Manage tool result state with type safety.
const { data, title, isError, text, onToolResult, reset } = useToolResult<MyData>();
const { callTool } = useHost({ onToolResult });
useWidgetState<T>()Persist UI state across sessions.
import { useWidgetState } from "@creature-run/sdk/react";
function App() {
const [state, setState] = useWidgetState({
selectedId: null,
viewMode: 'list',
});
return (
<div>
<ViewToggle
mode={state?.viewMode}
onChange={(mode) => setState({ ...state, viewMode: mode })}
/>
<ItemList
selectedId={state?.selectedId}
onSelect={(id) => setState({ ...state, selectedId: id })}
/>
</div>
);
}
useChannel<TSend, TReceive>(url, config)React hook for real-time bidirectional communication with the server.
import { useChannel } from "@creature-run/sdk/react";
function Terminal({ channelUrl }: { channelUrl: string }) {
const terminalRef = useRef<Terminal>(null);
const channel = useChannel<ClientMessage, ServerMessage>(channelUrl, {
onMessage: (msg) => {
if (msg.type === "output") {
terminalRef.current?.write(msg.data);
}
},
enabled: true, // Set to false to delay connection
});
// Send messages to server
const handleInput = (data: string) => {
channel.send({ type: "input", data });
};
return (
<div>
{channel.status === "connecting" && <Spinner />}
{channel.status === "error" && <Error message={channel.error} />}
<TerminalView ref={terminalRef} onInput={handleInput} />
</div>
);
}
The hook returns:
status: "disconnected" | "connecting" | "connected" | "error"error: Error message if status is "error"send(message): Function to send typed messages to the serverThe channel automatically connects when url is provided and enabled is true, reconnects on disconnect with exponential backoff, and disconnects on unmount.
The React package re-exports the core module for convenience:
import { createHost, McpHostClient, ChatGPTHostClient, createChannel } from "@creature-run/sdk/react";
createApp(config)const app = createApp({
name: "my-app",
version: "1.0.0",
port: 3000, // Optional (default: 3000 or MCP_PORT env)
dev: true, // Optional: force dev mode
hmrPort: 5899, // Optional: HMR WebSocket port
});
app.resource(config)app.resource({
name: "My Panel",
uri: "ui://my-app/panel",
description: "Optional description",
displayModes: ["pip"],
html: "ui/index.html",
icon: {
svg: "<svg>...</svg>",
alt: "Panel icon",
},
csp: {
connectDomains: ["https://api.example.com"],
},
});
app.tool(name, config, handler)app.tool(
"my_tool",
{
description: "What this tool does",
input: z.object({
param: z.string(),
}),
ui: "ui://my-app/panel",
visibility: ["model", "app"],
displayModes: ["pip"],
defaultDisplayMode: "pip",
},
async ({ param }) => {
return {
data: { result: "..." },
text: "Text for AI",
title: "Panel Title",
isError: false,
};
}
);
app.start()Start the HTTP server.
app.channel(name, config?)Define a WebSocket channel for real-time bidirectional communication. Useful for streaming data to the UI without polling.
import { createApp } from "@creature-run/sdk/server";
import { z } from "zod";
type ServerMessage =
| { type: "output"; data: string }
| { type: "exit"; exitCode: number };
type ClientMessage =
| { type: "input"; data: string }
| { type: "resize"; cols: number; rows: number };
const app = createApp({ name: "my-app", version: "1.0.0" });
const myChannel = app.channel<ServerMessage, ClientMessage>("my-channel", {
// Optional: Zod schema for validating incoming client messages
client: z.discriminatedUnion("type", [
z.object({ type: z.literal("input"), data: z.string() }),
z.object({ type: z.literal("resize"), cols: z.number(), rows: z.number() }),
]),
});
app.tool(
"start_session",
{ description: "Start a session", ui: "ui://my-app/panel" },
async () => {
const sessionId = crypto.randomUUID();
const channel = myChannel.session(sessionId);
// Handle messages from UI
channel.onMessage((msg) => {
if (msg.type === "input") {
// Process input
}
});
// Push data to UI
channel.send({ type: "output", data: "Hello!" });
return {
data: {
sessionId,
channelUrl: channel.url, // ws://localhost:3000/channels/my-channel/{sessionId}
},
};
}
);
When using channels, add the WebSocket URL to your resource's CSP:
app.resource({
uri: "ui://my-app/panel",
html: "ui/index.html",
csp: {
connectDomains: ["ws://localhost:3000"], // Allow WebSocket connections
},
});
Widget state persists user interactions across sessions:
For ChatGPT compatibility, use structured state to control what the AI can see:
import { useWidgetState, StructuredWidgetState } from "@creature-run/sdk/react";
interface MyWidgetState extends StructuredWidgetState {
modelContent: {
selectedItems: string[];
userQuery: string;
};
privateContent: {
viewMode: 'grid' | 'list';
sortOrder: 'asc' | 'desc';
};
}
const [state, setState] = useWidgetState<MyWidgetState>({
modelContent: { selectedItems: [], userQuery: '' },
privateContent: { viewMode: 'list', sortOrder: 'asc' },
imageIds: [],
});
| Field | Visible to AI | Use for |
|---|---|---|
modelContent | Yes | Selected items, user queries, important context |
privateContent | No | View preferences, UI state |
imageIds | Yes | File IDs from uploaded images |
| Feature | MCP Apps | ChatGPT |
|---|---|---|
| Tool calls | Yes | Yes |
| Structured content | Yes | Yes |
| Widget state | Yes | Yes |
| WebSocket channels | Yes | No |
| Display modes (pip/inline) | Yes | No |
| Theming | Yes | No |
| Panel icons | Yes | No |
| Session teardown | Yes | No |
The SDK automatically handles these differences—your code works in both environments.
npm run dev
This starts:
const app = createApp({
name: "my-app",
version: "1.0.0",
dev: true,
hmrPort: 5899,
});
Full TypeScript support with exported types:
import type {
Environment,
ToolResult,
WidgetState,
StructuredWidgetState,
HostClient,
HostClientConfig,
HostClientState,
HostClientEvents,
// Channel types
ChannelStatus,
ChannelClient,
ChannelClientConfig,
} from "@creature-run/sdk/core";
import type {
UseHostConfig,
UseHostReturn,
UseToolResultReturn,
UseChannelConfig,
UseChannelReturn,
} from "@creature-run/sdk/react";
import type {
ChannelConfig,
ChannelSession,
} from "@creature-run/sdk/server";
MIT
FAQs
SDK for building MCP Apps that work on both Creature and ChatGPT
We found that @creature-run/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.