@creature-ai/sdk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Core types for the MCP SDK.
3
+ * These are framework-agnostic and shared between vanilla JS and React.
4
+ */
5
+ /**
6
+ * Environment types for host detection.
7
+ */
8
+ type Environment = "chatgpt" | "mcp-apps" | "standalone";
9
+ /**
10
+ * Log severity levels matching MCP protocol LoggingLevel.
11
+ * These are displayed in the host's DevConsole with appropriate colors.
12
+ */
13
+ type LogLevel = "debug" | "info" | "notice" | "warning" | "error";
14
+ /**
15
+ * Structured widget state format (ChatGPT-compatible).
16
+ * Allows separating model-visible content from private UI state.
17
+ */
18
+ interface StructuredWidgetState {
19
+ /** Content visible to the AI model on follow-up turns */
20
+ modelContent?: string | Record<string, unknown> | null;
21
+ /** UI-only state, hidden from model */
22
+ privateContent?: Record<string, unknown> | null;
23
+ /** File IDs for images the model can see */
24
+ imageIds?: string[];
25
+ }
26
+ /**
27
+ * Widget state can be structured (with modelContent/privateContent)
28
+ * or a simple key-value object.
29
+ */
30
+ type WidgetState = StructuredWidgetState | Record<string, unknown>;
31
+ /**
32
+ * Unified state shape for AppSession.
33
+ * Provides a consistent state model across MCP Apps and ChatGPT Apps.
34
+ *
35
+ * - `internal`: Private to AppSession implementation (not synced anywhere)
36
+ * - `backend`: Available in the backend server (not mirrored to UI)
37
+ * - `ui`: Maps to widgetState on the frontend (synced via host binding)
38
+ */
39
+ interface AppSessionState<TInternal extends Record<string, unknown> = Record<string, unknown>, TBackend extends Record<string, unknown> = Record<string, unknown>, TUi extends WidgetState | null = WidgetState | null> {
40
+ internal: TInternal;
41
+ backend: TBackend;
42
+ ui: TUi;
43
+ }
44
+ /**
45
+ * Options for creating an AppSession.
46
+ */
47
+ interface AppSessionOptions {
48
+ /** Optional AppSession ID (auto-generated if not provided) */
49
+ id?: string;
50
+ /** Enable WebSocket channel for this AppSession (server-side only) */
51
+ websockets?: boolean;
52
+ }
53
+ /**
54
+ * Listener for AppSession state changes.
55
+ */
56
+ type AppSessionListener<TState extends AppSessionState = AppSessionState> = (state: TState, prevState: TState) => void;
57
+ /**
58
+ * Tool call result structure.
59
+ */
60
+ interface ToolResult<T = Record<string, unknown>> {
61
+ content?: Array<{
62
+ type: string;
63
+ text: string;
64
+ }>;
65
+ structuredContent?: T;
66
+ isError?: boolean;
67
+ source?: "agent" | "ui";
68
+ }
69
+ /**
70
+ * Host context sent from MCP Apps host.
71
+ * Follows MCP Apps spec with Creature extensions via [key: string]: unknown.
72
+ */
73
+ interface HostContext {
74
+ theme?: "light" | "dark";
75
+ styles?: {
76
+ variables?: Record<string, string>;
77
+ };
78
+ displayMode?: "pip" | "inline";
79
+ viewport?: {
80
+ width: number;
81
+ height: number;
82
+ };
83
+ platform?: string;
84
+ /**
85
+ * Widget state restored from previous widget instance.
86
+ * Creature extension - passed via hostContext on ui/initialize.
87
+ */
88
+ widgetState?: WidgetState;
89
+ }
90
+ /**
91
+ * Configuration for creating a host client.
92
+ */
93
+ interface HostClientConfig {
94
+ /** Name of the client (for protocol handshake) */
95
+ name: string;
96
+ /** Version of the client */
97
+ version: string;
98
+ }
99
+ /**
100
+ * State managed by the host client.
101
+ */
102
+ interface HostClientState {
103
+ /** Whether the host connection is ready */
104
+ isReady: boolean;
105
+ /** The detected environment */
106
+ environment: Environment;
107
+ /** Current widget state */
108
+ widgetState: WidgetState | null;
109
+ }
110
+ /**
111
+ * Event handlers that can be registered on the host client.
112
+ */
113
+ interface HostClientEvents {
114
+ /** Called when tool input is received (before execution) */
115
+ "tool-input": (args: Record<string, unknown>) => void;
116
+ /** Called when tool result is received */
117
+ "tool-result": (result: ToolResult) => void;
118
+ /** Called when theme changes (MCP Apps only) */
119
+ "theme-change": (theme: "light" | "dark") => void;
120
+ /** Called when host requests teardown (MCP Apps only) */
121
+ teardown: () => Promise<void> | void;
122
+ /** Called when widget state changes (restored or updated) */
123
+ "widget-state-change": (widgetState: WidgetState | null) => void;
124
+ }
125
+ /**
126
+ * Listener for state changes.
127
+ */
128
+ type StateListener = (state: HostClientState, prevState: HostClientState) => void;
129
+ /**
130
+ * Host client interface.
131
+ * Implemented by McpHostClient and ChatGPTHostClient.
132
+ */
133
+ interface HostClient {
134
+ /** Get current state */
135
+ getState(): HostClientState;
136
+ /** Subscribe to state changes. Returns unsubscribe function. */
137
+ subscribe(listener: StateListener): () => void;
138
+ /** Call a tool on the MCP server */
139
+ callTool<T = Record<string, unknown>>(toolName: string, args: Record<string, unknown>): Promise<ToolResult<T>>;
140
+ /** Send a notification to the host (MCP Apps only, no-op on ChatGPT) */
141
+ sendNotification(method: string, params: unknown): void;
142
+ /** Set widget state */
143
+ setWidgetState(state: WidgetState | null): void;
144
+ /**
145
+ * Send a log message to the host's DevConsole.
146
+ *
147
+ * Logs are sent via the MCP protocol's `notifications/message` notification
148
+ * and displayed in the host's unified log viewer alongside server logs.
149
+ *
150
+ * @param level - Log severity level
151
+ * @param message - Log message
152
+ * @param data - Optional structured data to include
153
+ */
154
+ log(level: LogLevel, message: string, data?: Record<string, unknown>): void;
155
+ /** Register an event handler. Returns unsubscribe function. */
156
+ on<K extends keyof HostClientEvents>(event: K, handler: HostClientEvents[K]): () => void;
157
+ /** Start listening for host messages */
158
+ connect(): void;
159
+ /** Stop listening for host messages */
160
+ disconnect(): void;
161
+ }
162
+
163
+ export type { AppSessionOptions as A, Environment as E, HostClient as H, LogLevel as L, StructuredWidgetState as S, ToolResult as T, WidgetState as W, AppSessionState as a, AppSessionListener as b, HostContext as c, HostClientConfig as d, HostClientState as e, HostClientEvents as f, StateListener as g };
@@ -0,0 +1,51 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * HMR Client Script
5
+ *
6
+ * This script is injected into MCP App HTML during development mode.
7
+ * It connects to Vite's HMR WebSocket and notifies the parent frame
8
+ * when a full reload is needed.
9
+ *
10
+ * The parent frame (Creature host) will then:
11
+ * 1. Save current widget state
12
+ * 2. Re-fetch fresh HTML from the MCP server
13
+ * 3. Reload the iframe with new content
14
+ * 4. Restore widget state
15
+ */
16
+ /**
17
+ * Generate the HMR client script as a string.
18
+ * The port is injected at generation time.
19
+ */
20
+ declare function generateHmrClientScript(port: number): string;
21
+ /**
22
+ * Generate a script tag with the HMR client code.
23
+ */
24
+ declare function generateHmrClientScriptTag(port: number): string;
25
+
26
+ interface CreaturePluginOptions {
27
+ uiDir?: string;
28
+ outDir?: string;
29
+ hmrPort?: number;
30
+ }
31
+ interface HmrConfig {
32
+ port: number;
33
+ }
34
+ /**
35
+ * Vite plugin for Creature MCP Apps.
36
+ *
37
+ * Just write page.tsx files - no HTML or entry files needed.
38
+ *
39
+ * ```
40
+ * src/ui/
41
+ * ├── page.tsx → dist/ui/main.html
42
+ * ├── inline/page.tsx → dist/ui/inline.html
43
+ * └── _components/ → ignored
44
+ * ```
45
+ *
46
+ * When using vite-plugin-singlefile, multiple pages are built automatically
47
+ * via sequential builds (singlefile requires single entry per build).
48
+ */
49
+ declare function creature(options?: CreaturePluginOptions): Plugin;
50
+
51
+ export { type CreaturePluginOptions, type HmrConfig, creature, creature as default, generateHmrClientScript, generateHmrClientScriptTag };
@@ -0,0 +1,332 @@
1
+ // src/vite/index.ts
2
+ import { resolve, join, relative } from "path";
3
+ import { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync } from "fs";
4
+ import { createServer as createNetServer } from "net";
5
+ import { createServer as createHttpServer } from "http";
6
+ import { createHash } from "crypto";
7
+ import { spawnSync } from "child_process";
8
+
9
+ // src/vite/hmr-client.ts
10
+ function generateHmrClientScript(port) {
11
+ return `
12
+ (function() {
13
+ if (window.parent === window) {
14
+ console.log('[Creature HMR] Not in iframe, skipping HMR client');
15
+ return;
16
+ }
17
+ if (window.__CREATURE_HMR_CONNECTED__) {
18
+ console.log('[Creature HMR] Already connected, skipping');
19
+ return;
20
+ }
21
+ window.__CREATURE_HMR_CONNECTED__ = true;
22
+
23
+ var HMR_PORT = ${port};
24
+ var reconnectAttempts = 0;
25
+ var maxReconnectAttempts = 10;
26
+ var reconnectDelay = 1000;
27
+
28
+ console.log('[Creature HMR] Initializing HMR client, will connect to port ' + HMR_PORT);
29
+
30
+ function connect() {
31
+ if (reconnectAttempts >= maxReconnectAttempts) {
32
+ console.log('[Creature HMR] Max reconnection attempts reached, giving up');
33
+ return;
34
+ }
35
+
36
+ console.log('[Creature HMR] Attempting to connect to ws://localhost:' + HMR_PORT + ' (attempt ' + (reconnectAttempts + 1) + ')');
37
+ var ws = new WebSocket('ws://localhost:' + HMR_PORT);
38
+
39
+ ws.onopen = function() {
40
+ console.log('[Creature HMR] Connected to HMR server on port ' + HMR_PORT);
41
+ reconnectAttempts = 0;
42
+ };
43
+
44
+ ws.onmessage = function(event) {
45
+ console.log('[Creature HMR] Received message:', event.data);
46
+ try {
47
+ var data = JSON.parse(event.data);
48
+
49
+ if (data.type === 'full-reload') {
50
+ console.log('[Creature HMR] Full reload triggered, notifying parent');
51
+ notifyParent();
52
+ } else if (data.type === 'update') {
53
+ console.log('[Creature HMR] Update detected, notifying parent');
54
+ notifyParent();
55
+ } else if (data.type === 'connected') {
56
+ console.log('[Creature HMR] Server acknowledged connection');
57
+ }
58
+ } catch (e) {
59
+ console.log('[Creature HMR] Failed to parse message:', e);
60
+ }
61
+ };
62
+
63
+ ws.onclose = function() {
64
+ console.log('[Creature HMR] Disconnected, reconnecting in ' + reconnectDelay + 'ms...');
65
+ reconnectAttempts++;
66
+ setTimeout(connect, reconnectDelay);
67
+ };
68
+
69
+ ws.onerror = function(err) {
70
+ console.log('[Creature HMR] WebSocket error:', err);
71
+ };
72
+ }
73
+
74
+ function notifyParent() {
75
+ console.log('[Creature HMR] Sending hmr-reload to parent frame');
76
+ window.parent.postMessage({
77
+ jsonrpc: '2.0',
78
+ method: 'ui/notifications/hmr-reload',
79
+ params: {}
80
+ }, '*');
81
+ }
82
+
83
+ // Start connection
84
+ connect();
85
+ })();
86
+ `.trim();
87
+ }
88
+ function generateHmrClientScriptTag(port) {
89
+ return `<script>${generateHmrClientScript(port)}</script>`;
90
+ }
91
+
92
+ // src/vite/index.ts
93
+ function findAvailablePort(startPort) {
94
+ return new Promise((resolve2) => {
95
+ const server = createNetServer();
96
+ server.listen(startPort, () => {
97
+ const port = server.address().port;
98
+ server.close(() => resolve2(port));
99
+ });
100
+ server.on("error", () => {
101
+ resolve2(findAvailablePort(startPort + 1));
102
+ });
103
+ });
104
+ }
105
+ function findPages(dir, baseDir) {
106
+ const entries = [];
107
+ if (!existsSync(dir)) return entries;
108
+ const items = readdirSync(dir);
109
+ if (items.includes("page.tsx")) {
110
+ const relativePath = dir.slice(baseDir.length + 1);
111
+ entries.push({
112
+ name: relativePath || "main",
113
+ pagePath: join(dir, "page.tsx")
114
+ });
115
+ }
116
+ for (const item of items) {
117
+ const fullPath = join(dir, item);
118
+ if (statSync(fullPath).isDirectory() && !item.startsWith("_") && item !== "node_modules") {
119
+ entries.push(...findPages(fullPath, baseDir));
120
+ }
121
+ }
122
+ return entries;
123
+ }
124
+ var hmrServer = null;
125
+ var hmrClients = /* @__PURE__ */ new Set();
126
+ function sendWebSocketFrame(socket, data) {
127
+ const payload = Buffer.from(data);
128
+ const length = payload.length;
129
+ let frame;
130
+ if (length < 126) {
131
+ frame = Buffer.alloc(2 + length);
132
+ frame[0] = 129;
133
+ frame[1] = length;
134
+ payload.copy(frame, 2);
135
+ } else if (length < 65536) {
136
+ frame = Buffer.alloc(4 + length);
137
+ frame[0] = 129;
138
+ frame[1] = 126;
139
+ frame.writeUInt16BE(length, 2);
140
+ payload.copy(frame, 4);
141
+ } else {
142
+ frame = Buffer.alloc(10 + length);
143
+ frame[0] = 129;
144
+ frame[1] = 127;
145
+ frame.writeBigUInt64BE(BigInt(length), 2);
146
+ payload.copy(frame, 10);
147
+ }
148
+ socket.write(frame);
149
+ }
150
+ function startHmrServer(port) {
151
+ if (hmrServer) return;
152
+ hmrServer = createHttpServer((_req, res) => {
153
+ res.writeHead(200);
154
+ res.end("Creature HMR Server");
155
+ });
156
+ hmrServer.on("upgrade", (req, socket, head) => {
157
+ const key = req.headers["sec-websocket-key"];
158
+ if (!key) {
159
+ socket.destroy();
160
+ return;
161
+ }
162
+ const acceptKey = createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
163
+ socket.write(
164
+ `HTTP/1.1 101 Switching Protocols\r
165
+ Upgrade: websocket\r
166
+ Connection: Upgrade\r
167
+ Sec-WebSocket-Accept: ${acceptKey}\r
168
+ \r
169
+ `
170
+ );
171
+ hmrClients.add(socket);
172
+ sendWebSocketFrame(socket, JSON.stringify({ type: "connected" }));
173
+ socket.on("close", () => {
174
+ hmrClients.delete(socket);
175
+ });
176
+ socket.on("error", () => {
177
+ hmrClients.delete(socket);
178
+ });
179
+ });
180
+ hmrServer.listen(port);
181
+ }
182
+ function notifyHmrClients() {
183
+ const message = JSON.stringify({ type: "full-reload" });
184
+ const toRemove = [];
185
+ for (const client of hmrClients) {
186
+ try {
187
+ if (!client.destroyed) {
188
+ sendWebSocketFrame(client, message);
189
+ } else {
190
+ toRemove.push(client);
191
+ }
192
+ } catch {
193
+ toRemove.push(client);
194
+ }
195
+ }
196
+ for (const client of toRemove) {
197
+ hmrClients.delete(client);
198
+ }
199
+ if (hmrClients.size > 0) {
200
+ console.log("App UI reloaded");
201
+ }
202
+ }
203
+ function creature(options = {}) {
204
+ const uiDir = options.uiDir || "src/ui";
205
+ const outDir = options.outDir || "dist/ui";
206
+ const preferredHmrPort = options.hmrPort || 5899;
207
+ let root;
208
+ let tempDir;
209
+ let entries = [];
210
+ let hasSingleFilePlugin = false;
211
+ let hmrPort = null;
212
+ let isWatchMode = false;
213
+ let remainingPages = [];
214
+ return {
215
+ name: "creature",
216
+ async config(config) {
217
+ root = config.root || process.cwd();
218
+ const uiPath = resolve(root, uiDir);
219
+ entries = findPages(uiPath, uiPath);
220
+ if (entries.length === 0) {
221
+ return;
222
+ }
223
+ const plugins = config.plugins?.flat() || [];
224
+ hasSingleFilePlugin = plugins.some((p) => p && typeof p === "object" && "name" in p && p.name === "vite:singlefile");
225
+ tempDir = resolve(root, "node_modules/.creature");
226
+ const selectedPage = process.env.CREATURE_PAGE;
227
+ if (!selectedPage) {
228
+ rmSync(tempDir, { recursive: true, force: true });
229
+ }
230
+ mkdirSync(tempDir, { recursive: true });
231
+ for (const entry of entries) {
232
+ const relativePagePath = relative(tempDir, entry.pagePath).replace(/\\/g, "/");
233
+ writeFileSync(
234
+ join(tempDir, `${entry.name}.entry.tsx`),
235
+ `import { createElement } from "react";
236
+ import { createRoot } from "react-dom/client";
237
+ import Page from "${relativePagePath.replace(/\.tsx$/, "")}";
238
+ createRoot(document.getElementById("root")!).render(createElement(Page));
239
+ `
240
+ );
241
+ writeFileSync(
242
+ join(tempDir, `${entry.name}.html`),
243
+ `<!DOCTYPE html>
244
+ <html lang="en">
245
+ <head>
246
+ <meta charset="UTF-8" />
247
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
248
+ <title>${entry.name}</title>
249
+ </head>
250
+ <body>
251
+ <div id="root"></div>
252
+ <script type="module" src="./${entry.name}.entry.tsx"></script>
253
+ </body>
254
+ </html>`
255
+ );
256
+ }
257
+ let inputs;
258
+ if (hasSingleFilePlugin) {
259
+ const targetEntry = selectedPage ? entries.find((e) => e.name === selectedPage) : entries[0];
260
+ if (!targetEntry) {
261
+ console.error(`Page "${selectedPage}" not found`);
262
+ return;
263
+ }
264
+ inputs = { [targetEntry.name]: join(tempDir, `${targetEntry.name}.html`) };
265
+ if (!selectedPage && entries.length > 1) {
266
+ remainingPages = entries.slice(1).map((e) => e.name);
267
+ }
268
+ } else {
269
+ inputs = Object.fromEntries(
270
+ entries.map((e) => [e.name, join(tempDir, `${e.name}.html`)])
271
+ );
272
+ }
273
+ return {
274
+ root: tempDir,
275
+ publicDir: false,
276
+ logLevel: "silent",
277
+ build: {
278
+ outDir: resolve(root, outDir),
279
+ emptyOutDir: !selectedPage,
280
+ rollupOptions: { input: inputs },
281
+ reportCompressedSize: false
282
+ }
283
+ };
284
+ },
285
+ async buildStart() {
286
+ if (!tempDir) return;
287
+ isWatchMode = this.meta.watchMode === true;
288
+ if (isWatchMode && !hmrServer) {
289
+ hmrPort = await findAvailablePort(preferredHmrPort);
290
+ startHmrServer(hmrPort);
291
+ mkdirSync(tempDir, { recursive: true });
292
+ const hmrConfig = { port: hmrPort };
293
+ writeFileSync(
294
+ join(tempDir, "hmr.json"),
295
+ JSON.stringify(hmrConfig, null, 2)
296
+ );
297
+ }
298
+ },
299
+ writeBundle() {
300
+ if (!hasSingleFilePlugin || remainingPages.length === 0) {
301
+ if (isWatchMode) notifyHmrClients();
302
+ return;
303
+ }
304
+ for (const pageName of remainingPages) {
305
+ spawnSync("npx", ["vite", "build"], {
306
+ cwd: root,
307
+ env: { ...process.env, CREATURE_PAGE: pageName },
308
+ stdio: "inherit"
309
+ });
310
+ }
311
+ if (isWatchMode) {
312
+ notifyHmrClients();
313
+ } else {
314
+ remainingPages = [];
315
+ }
316
+ },
317
+ closeBundle() {
318
+ if (isWatchMode) return;
319
+ if (tempDir && existsSync(tempDir) && !process.env.CREATURE_PAGE) {
320
+ rmSync(tempDir, { recursive: true, force: true });
321
+ }
322
+ }
323
+ };
324
+ }
325
+ var vite_default = creature;
326
+ export {
327
+ creature,
328
+ vite_default as default,
329
+ generateHmrClientScript,
330
+ generateHmrClientScriptTag
331
+ };
332
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vite/index.ts","../../src/vite/hmr-client.ts"],"sourcesContent":["import { resolve, join, relative } from \"node:path\";\nimport { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync } from \"node:fs\";\nimport { createServer as createNetServer } from \"node:net\";\nimport { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash } from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\nimport type { Duplex } from \"node:stream\";\nimport type { Plugin, UserConfig } from \"vite\";\n\nexport interface CreaturePluginOptions {\n uiDir?: string;\n outDir?: string;\n hmrPort?: number;\n}\n\nexport interface HmrConfig {\n port: number;\n}\n\nfunction findAvailablePort(startPort: number): Promise<number> {\n return new Promise((resolve) => {\n const server = createNetServer();\n server.listen(startPort, () => {\n const port = (server.address() as { port: number }).port;\n server.close(() => resolve(port));\n });\n server.on(\"error\", () => {\n resolve(findAvailablePort(startPort + 1));\n });\n });\n}\n\ninterface EntryPoint {\n name: string;\n pagePath: string;\n}\n\nfunction findPages(dir: string, baseDir: string): EntryPoint[] {\n const entries: EntryPoint[] = [];\n if (!existsSync(dir)) return entries;\n\n const items = readdirSync(dir);\n\n if (items.includes(\"page.tsx\")) {\n const relativePath = dir.slice(baseDir.length + 1);\n entries.push({\n name: relativePath || \"main\",\n pagePath: join(dir, \"page.tsx\"),\n });\n }\n\n for (const item of items) {\n const fullPath = join(dir, item);\n if (statSync(fullPath).isDirectory() && !item.startsWith(\"_\") && item !== \"node_modules\") {\n entries.push(...findPages(fullPath, baseDir));\n }\n }\n\n return entries;\n}\n\nlet hmrServer: ReturnType<typeof createHttpServer> | null = null;\nlet hmrClients: Set<Duplex> = new Set();\n\nfunction sendWebSocketFrame(socket: Duplex, data: string): void {\n const payload = Buffer.from(data);\n const length = payload.length;\n \n let frame: Buffer;\n if (length < 126) {\n frame = Buffer.alloc(2 + length);\n frame[0] = 0x81;\n frame[1] = length;\n payload.copy(frame, 2);\n } else if (length < 65536) {\n frame = Buffer.alloc(4 + length);\n frame[0] = 0x81;\n frame[1] = 126;\n frame.writeUInt16BE(length, 2);\n payload.copy(frame, 4);\n } else {\n frame = Buffer.alloc(10 + length);\n frame[0] = 0x81;\n frame[1] = 127;\n frame.writeBigUInt64BE(BigInt(length), 2);\n payload.copy(frame, 10);\n }\n \n socket.write(frame);\n}\n\nfunction startHmrServer(port: number): void {\n if (hmrServer) return;\n \n hmrServer = createHttpServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200);\n res.end(\"Creature HMR Server\");\n });\n \n hmrServer.on(\"upgrade\", (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n const key = req.headers[\"sec-websocket-key\"];\n if (!key) {\n socket.destroy();\n return;\n }\n \n const acceptKey = createHash(\"sha1\")\n .update(key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")\n .digest(\"base64\");\n \n socket.write(\n \"HTTP/1.1 101 Switching Protocols\\r\\n\" +\n \"Upgrade: websocket\\r\\n\" +\n \"Connection: Upgrade\\r\\n\" +\n `Sec-WebSocket-Accept: ${acceptKey}\\r\\n` +\n \"\\r\\n\"\n );\n \n hmrClients.add(socket);\n sendWebSocketFrame(socket, JSON.stringify({ type: \"connected\" }));\n \n socket.on(\"close\", () => {\n hmrClients.delete(socket);\n });\n \n socket.on(\"error\", () => {\n hmrClients.delete(socket);\n });\n });\n \n hmrServer.listen(port);\n}\n\nfunction notifyHmrClients(): void {\n const message = JSON.stringify({ type: \"full-reload\" });\n const toRemove: Duplex[] = [];\n \n for (const client of hmrClients) {\n try {\n if (!client.destroyed) {\n sendWebSocketFrame(client, message);\n } else {\n toRemove.push(client);\n }\n } catch {\n toRemove.push(client);\n }\n }\n \n for (const client of toRemove) {\n hmrClients.delete(client);\n }\n \n if (hmrClients.size > 0) {\n console.log(\"App UI reloaded\");\n }\n}\n\n/**\n * Vite plugin for Creature MCP Apps.\n * \n * Just write page.tsx files - no HTML or entry files needed.\n * \n * ```\n * src/ui/\n * ├── page.tsx → dist/ui/main.html\n * ├── inline/page.tsx → dist/ui/inline.html\n * └── _components/ → ignored\n * ```\n * \n * When using vite-plugin-singlefile, multiple pages are built automatically\n * via sequential builds (singlefile requires single entry per build).\n */\nexport function creature(options: CreaturePluginOptions = {}): Plugin {\n const uiDir = options.uiDir || \"src/ui\";\n const outDir = options.outDir || \"dist/ui\";\n const preferredHmrPort = options.hmrPort || 5899;\n \n let root: string;\n let tempDir: string;\n let entries: EntryPoint[] = [];\n let hasSingleFilePlugin = false;\n let hmrPort: number | null = null;\n let isWatchMode = false;\n let remainingPages: string[] = [];\n\n return {\n name: \"creature\",\n \n async config(config) {\n root = config.root || process.cwd();\n const uiPath = resolve(root, uiDir);\n entries = findPages(uiPath, uiPath);\n\n if (entries.length === 0) {\n return;\n }\n\n const plugins = config.plugins?.flat() || [];\n hasSingleFilePlugin = plugins.some(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite:singlefile');\n\n tempDir = resolve(root, \"node_modules/.creature\");\n \n const selectedPage = process.env.CREATURE_PAGE;\n \n if (!selectedPage) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n mkdirSync(tempDir, { recursive: true });\n\n for (const entry of entries) {\n const relativePagePath = relative(tempDir, entry.pagePath).replace(/\\\\/g, \"/\");\n writeFileSync(join(tempDir, `${entry.name}.entry.tsx`), \n`import { createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport Page from \"${relativePagePath.replace(/\\.tsx$/, \"\")}\";\ncreateRoot(document.getElementById(\"root\")!).render(createElement(Page));\n`);\n\n writeFileSync(join(tempDir, `${entry.name}.html`),\n`<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${entry.name}</title>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"./${entry.name}.entry.tsx\"></script>\n</body>\n</html>`);\n }\n \n let inputs: Record<string, string>;\n \n if (hasSingleFilePlugin) {\n const targetEntry = selectedPage \n ? entries.find(e => e.name === selectedPage)\n : entries[0];\n \n if (!targetEntry) {\n console.error(`Page \"${selectedPage}\" not found`);\n return;\n }\n \n inputs = { [targetEntry.name]: join(tempDir, `${targetEntry.name}.html`) };\n \n if (!selectedPage && entries.length > 1) {\n remainingPages = entries.slice(1).map(e => e.name);\n }\n } else {\n inputs = Object.fromEntries(\n entries.map(e => [e.name, join(tempDir, `${e.name}.html`)])\n );\n }\n\n return {\n root: tempDir,\n publicDir: false,\n logLevel: \"silent\" as const,\n build: {\n outDir: resolve(root, outDir),\n emptyOutDir: !selectedPage,\n rollupOptions: { input: inputs },\n reportCompressedSize: false,\n },\n } satisfies UserConfig;\n },\n\n async buildStart() {\n if (!tempDir) return;\n \n isWatchMode = this.meta.watchMode === true;\n \n if (isWatchMode && !hmrServer) {\n hmrPort = await findAvailablePort(preferredHmrPort);\n startHmrServer(hmrPort);\n \n mkdirSync(tempDir, { recursive: true });\n const hmrConfig: HmrConfig = { port: hmrPort };\n writeFileSync(\n join(tempDir, \"hmr.json\"),\n JSON.stringify(hmrConfig, null, 2)\n );\n }\n },\n\n writeBundle() {\n if (!hasSingleFilePlugin || remainingPages.length === 0) {\n if (isWatchMode) notifyHmrClients();\n return;\n }\n \n for (const pageName of remainingPages) {\n spawnSync(\"npx\", [\"vite\", \"build\"], {\n cwd: root,\n env: { ...process.env, CREATURE_PAGE: pageName },\n stdio: \"inherit\",\n });\n }\n \n if (isWatchMode) {\n notifyHmrClients();\n } else {\n remainingPages = [];\n }\n },\n\n closeBundle() {\n if (isWatchMode) return;\n \n if (tempDir && existsSync(tempDir) && !process.env.CREATURE_PAGE) {\n rmSync(tempDir, { recursive: true, force: true });\n }\n },\n };\n}\n\nexport { generateHmrClientScript, generateHmrClientScriptTag } from \"./hmr-client.js\";\n\nexport default creature;\n","/**\n * HMR Client Script\n * \n * This script is injected into MCP App HTML during development mode.\n * It connects to Vite's HMR WebSocket and notifies the parent frame\n * when a full reload is needed.\n * \n * The parent frame (Creature host) will then:\n * 1. Save current widget state\n * 2. Re-fetch fresh HTML from the MCP server\n * 3. Reload the iframe with new content\n * 4. Restore widget state\n */\n\n/**\n * Generate the HMR client script as a string.\n * The port is injected at generation time.\n */\nexport function generateHmrClientScript(port: number): string {\n return `\n(function() {\n if (window.parent === window) {\n console.log('[Creature HMR] Not in iframe, skipping HMR client');\n return;\n }\n if (window.__CREATURE_HMR_CONNECTED__) {\n console.log('[Creature HMR] Already connected, skipping');\n return;\n }\n window.__CREATURE_HMR_CONNECTED__ = true;\n\n var HMR_PORT = ${port};\n var reconnectAttempts = 0;\n var maxReconnectAttempts = 10;\n var reconnectDelay = 1000;\n\n console.log('[Creature HMR] Initializing HMR client, will connect to port ' + HMR_PORT);\n\n function connect() {\n if (reconnectAttempts >= maxReconnectAttempts) {\n console.log('[Creature HMR] Max reconnection attempts reached, giving up');\n return;\n }\n\n console.log('[Creature HMR] Attempting to connect to ws://localhost:' + HMR_PORT + ' (attempt ' + (reconnectAttempts + 1) + ')');\n var ws = new WebSocket('ws://localhost:' + HMR_PORT);\n\n ws.onopen = function() {\n console.log('[Creature HMR] Connected to HMR server on port ' + HMR_PORT);\n reconnectAttempts = 0;\n };\n\n ws.onmessage = function(event) {\n console.log('[Creature HMR] Received message:', event.data);\n try {\n var data = JSON.parse(event.data);\n \n if (data.type === 'full-reload') {\n console.log('[Creature HMR] Full reload triggered, notifying parent');\n notifyParent();\n } else if (data.type === 'update') {\n console.log('[Creature HMR] Update detected, notifying parent');\n notifyParent();\n } else if (data.type === 'connected') {\n console.log('[Creature HMR] Server acknowledged connection');\n }\n } catch (e) {\n console.log('[Creature HMR] Failed to parse message:', e);\n }\n };\n\n ws.onclose = function() {\n console.log('[Creature HMR] Disconnected, reconnecting in ' + reconnectDelay + 'ms...');\n reconnectAttempts++;\n setTimeout(connect, reconnectDelay);\n };\n\n ws.onerror = function(err) {\n console.log('[Creature HMR] WebSocket error:', err);\n };\n }\n\n function notifyParent() {\n console.log('[Creature HMR] Sending hmr-reload to parent frame');\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/hmr-reload',\n params: {}\n }, '*');\n }\n\n // Start connection\n connect();\n})();\n`.trim();\n}\n\n/**\n * Generate a script tag with the HMR client code.\n */\nexport function generateHmrClientScriptTag(port: number): string {\n return `<script>${generateHmrClientScript(port)}</script>`;\n}\n"],"mappings":"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","names":["resolve"]}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@creature-ai/sdk",
3
+ "version": "0.1.1",
4
+ "description": "SDK for building MCP Apps that work on both Creature and ChatGPT",
5
+ "type": "module",
6
+ "exports": {
7
+ "./server": {
8
+ "types": "./dist/server/index.d.ts",
9
+ "import": "./dist/server/index.js",
10
+ "require": "./dist/server/index.js",
11
+ "default": "./dist/server/index.js"
12
+ },
13
+ "./core": {
14
+ "types": "./dist/core/index.d.ts",
15
+ "import": "./dist/core/index.js",
16
+ "require": "./dist/core/index.js",
17
+ "default": "./dist/core/index.js"
18
+ },
19
+ "./react": {
20
+ "types": "./dist/react/index.d.ts",
21
+ "import": "./dist/react/index.js",
22
+ "require": "./dist/react/index.js",
23
+ "default": "./dist/react/index.js"
24
+ },
25
+ "./vite": {
26
+ "types": "./dist/vite/index.d.ts",
27
+ "import": "./dist/vite/index.js",
28
+ "require": "./dist/vite/index.js",
29
+ "default": "./dist/vite/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "dev": "tsup --watch",
39
+ "typecheck": "tsc --noEmit",
40
+ "clean": "rm -rf dist"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/ext-apps": "^0.2.2",
44
+ "@modelcontextprotocol/sdk": "^1.12.1",
45
+ "express": "^5.1.0",
46
+ "ws": "^8.18.0",
47
+ "zod": "^3.24.4"
48
+ },
49
+ "devDependencies": {
50
+ "@types/express": "^5.0.2",
51
+ "@types/node": "^22.15.21",
52
+ "@types/react": "^19.1.6",
53
+ "@types/ws": "^8.5.13",
54
+ "cac": "^6.7.14",
55
+ "react": "^19.1.0",
56
+ "tsup": "^8.5.1",
57
+ "typescript": "^5.8.3",
58
+ "vite": "^6.3.5"
59
+ },
60
+ "peerDependencies": {
61
+ "react": ">=18.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "react": {
65
+ "optional": true
66
+ }
67
+ },
68
+ "keywords": [
69
+ "mcp",
70
+ "model-context-protocol",
71
+ "chatgpt",
72
+ "creature",
73
+ "claude",
74
+ "ai-tools"
75
+ ],
76
+ "license": "MIT",
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "https://github.com/serverlessinc/creature.git",
80
+ "directory": "desktop/sdk"
81
+ }
82
+ }