Socket
Book a DemoInstallSign in
Socket

@creature-run/sdk

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@creature-run/sdk

SDK for building MCP Apps that work on both Creature and ChatGPT

latest
Source
npmnpm
Version
0.1.1
Version published
Maintainers
1
Created
Source

@creature-run/sdk

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.

Quick Start

npx @creature-run/create-app my-app
cd my-app
npm install
npm run dev

Installation

npm install @creature-run/sdk

Packages

The SDK is split into multiple entry points:

Entry PointDescription
@creature-run/sdk/serverMCP server for registering tools and resources
@creature-run/sdk/coreVanilla JS client (no React dependency)
@creature-run/sdk/reactReact hooks (wraps core)
@creature-run/sdk/viteVite plugin for HMR support

Server Setup

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();

Client Usage

Vanilla JS (@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);
});

Core API

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" });

Channels (Real-time Communication)

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();

Direct Client Access

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();

React (@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 server

The channel automatically connects when url is provided and enabled is true, reconnects on disconnect with exponential backoff, and disconnects on unmount.

Accessing Core Client from React

The React package re-exports the core module for convenience:

import { createHost, McpHostClient, ChatGPTHostClient, createChannel } from "@creature-run/sdk/react";

Server API Reference

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

Widget state persists user interactions across sessions:

  • Selected items, active tabs, expanded sections
  • User preferences like view mode or sort order
  • Form input that shouldn't be lost on refresh

Structured State (AI-Visible)

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: [],
});
FieldVisible to AIUse for
modelContentYesSelected items, user queries, important context
privateContentNoView preferences, UI state
imageIdsYesFile IDs from uploaded images

Cross-Platform Compatibility

FeatureMCP AppsChatGPT
Tool callsYesYes
Structured contentYesYes
Widget stateYesYes
WebSocket channelsYesNo
Display modes (pip/inline)YesNo
ThemingYesNo
Panel iconsYesNo
Session teardownYesNo

The SDK automatically handles these differences—your code works in both environments.

Development with HMR

npm run dev

This starts:

  • Vite in watch mode (rebuilds on file changes + HMR WebSocket)
  • TypeScript compiler in watch mode
  • MCP server with nodemon (restarts on server changes)

Configuration

const app = createApp({
  name: "my-app",
  version: "1.0.0",
  dev: true,
  hmrPort: 5899,
});

TypeScript

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";

License

MIT

Keywords

mcp

FAQs

Package last updated on 09 Jan 2026

Did you know?

Socket

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.

Install

Related posts