@creature-ai/sdk 0.1.2 → 0.1.4

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.
@@ -1,6 +1,4 @@
1
1
  import { z } from 'zod';
2
- import { Server } from 'http';
3
- import { A as AppSessionOptions, W as WidgetState, a as AppSessionState, b as AppSessionListener } from '../types-JBEuUzEi.js';
4
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
3
 
6
4
  /**
@@ -61,6 +59,34 @@ interface ResourceConfig {
61
59
  connectDomains?: string[];
62
60
  resourceDomains?: string[];
63
61
  };
62
+ /**
63
+ * Allow multiple instances of this resource.
64
+ * Default: false (singleton - all tools share one instance per resourceUri)
65
+ *
66
+ * - false: Singleton. SDK reuses the same instanceId for all tool calls.
67
+ * - true: Multi-instance. SDK generates new instanceId each time (unless provided in input).
68
+ *
69
+ * Note: multiInstance is only supported on Creature. On ChatGPT, resources always behave as singleton.
70
+ */
71
+ multiInstance?: boolean;
72
+ /**
73
+ * Enable WebSocket for real-time communication with the UI.
74
+ * When true, SDK automatically manages WebSocket lifecycle and provides
75
+ * `context.send()` and `context.onMessage()` in tool handlers.
76
+ */
77
+ websocket?: boolean;
78
+ }
79
+ /**
80
+ * Resource cache configuration.
81
+ * Controls caching behavior for UI resource HTML content.
82
+ */
83
+ interface ResourceCacheConfig {
84
+ /** Maximum number of cached entries (default: 100) */
85
+ maxSize?: number;
86
+ /** Time-to-live in milliseconds. 0 = no expiry (default: 0) */
87
+ ttlMs?: number;
88
+ /** Whether caching is enabled (default: true) */
89
+ enabled?: boolean;
64
90
  }
65
91
  /**
66
92
  * Tool configuration.
@@ -78,13 +104,6 @@ interface ToolConfig<TInput extends z.ZodType = z.ZodType> {
78
104
  displayModes?: DisplayMode[];
79
105
  /** Preferred display mode */
80
106
  defaultDisplayMode?: DisplayMode;
81
- /**
82
- * Whether this tool creates new sessions (multi-instance MCP).
83
- * When true, the Host will always create a new pip instead of
84
- * reusing an existing one for the same resourceUri.
85
- * Use this for tools that spawn independent sessions (e.g., terminal_run).
86
- */
87
- multiSession?: boolean;
88
107
  /** Loading message shown while tool is running (used by ChatGPT) */
89
108
  loadingMessage?: string;
90
109
  /** Completion message shown when tool finishes (used by ChatGPT) */
@@ -111,21 +130,58 @@ interface ToolResult {
111
130
  type ToolHandler<TInput> = (input: TInput, context: ToolContext) => ToolResult | Promise<ToolResult>;
112
131
  /**
113
132
  * Context passed to tool handlers.
114
- * Reserved for future use.
133
+ * Provides access to instanceId, state management, and WebSocket communication.
115
134
  */
116
135
  interface ToolContext {
136
+ /**
137
+ * The instance ID for this tool call.
138
+ * Generated before handler runs. Use for server-side state keying.
139
+ * Automatically attached to tool result for UI routing.
140
+ */
141
+ instanceId: string;
142
+ /**
143
+ * Get server-side state for this instance.
144
+ * State is NOT sent to UI — use for PIDs, connections, handles.
145
+ */
146
+ getState: <T>() => T | undefined;
147
+ /**
148
+ * Set server-side state for this instance.
149
+ * State is NOT sent to UI — use for PIDs, connections, handles.
150
+ */
151
+ setState: <T>(state: T) => void;
152
+ /**
153
+ * Send a message to the UI via WebSocket.
154
+ * Only available if the resource has `websocket: true`.
155
+ * For singleton resources, sends to the single shared WebSocket.
156
+ * For multi-instance resources, sends to this instance's WebSocket.
157
+ */
158
+ send: <T>(message: T) => void;
159
+ /**
160
+ * Register a handler for messages from the UI.
161
+ * Only available if the resource has `websocket: true`.
162
+ */
163
+ onMessage: <T>(handler: (message: T) => void) => void;
164
+ /**
165
+ * Register a handler called when a UI client connects to the WebSocket.
166
+ * Useful for sending buffered data when a client connects.
167
+ * Only available if the resource has `websocket: true`.
168
+ */
169
+ onConnect: (handler: () => void) => void;
170
+ /**
171
+ * WebSocket URL for the UI to connect to.
172
+ * Only available if the resource has `websocket: true`.
173
+ * Automatically included in tool result's structuredContent.
174
+ */
175
+ websocketUrl: string | undefined;
117
176
  }
118
177
  /**
119
- * Resource cache configuration.
120
- * Controls caching behavior for UI resource HTML content.
178
+ * Context passed to onInstanceDestroy callback.
121
179
  */
122
- interface ResourceCacheConfig {
123
- /** Maximum number of cached entries (default: 100) */
124
- maxSize?: number;
125
- /** Time-to-live in milliseconds. 0 = no expiry (default: 0) */
126
- ttlMs?: number;
127
- /** Whether caching is enabled (default: true) */
128
- enabled?: boolean;
180
+ interface InstanceDestroyContext {
181
+ /** The instanceId being destroyed */
182
+ instanceId: string;
183
+ /** Last server-side state for this instance (from setState calls) */
184
+ state: unknown;
129
185
  }
130
186
  /**
131
187
  * Supported MCP transport types.
@@ -159,7 +215,7 @@ interface AppConfig {
159
215
  hmrPort?: number;
160
216
  /**
161
217
  * Called when a new transport session is created.
162
- * Transport sessions are MCP protocol connections (not AppSessions).
218
+ * Transport sessions are MCP protocol connections (not instances).
163
219
  */
164
220
  onTransportSessionCreated?: (info: TransportSessionInfo) => void;
165
221
  /**
@@ -198,196 +254,114 @@ interface AppConfig {
198
254
  */
199
255
  resourceCache?: ResourceCacheConfig;
200
256
  }
201
-
202
257
  /**
203
- * WebSocket channel support for real-time bidirectional communication.
258
+ * WebSocket connection for an instance.
204
259
  *
205
- * Channels provide a type-safe abstraction over WebSockets, allowing MCP Apps
206
- * to stream data to connected UIs without polling.
260
+ * Provides real-time bidirectional communication between the server
261
+ * and all UI clients connected to a particular instance.
207
262
  */
208
-
209
- /**
210
- * Handler for incoming client messages.
211
- */
212
- type MessageHandler<T> = (message: T) => void;
213
- /**
214
- * Configuration for a channel definition.
215
- */
216
- interface ChannelConfig<_TServer = unknown, _TClient = unknown> {
217
- /** Zod schema for server → client messages (optional, for documentation) */
218
- server?: z.ZodType;
219
- /** Zod schema for client → server messages (validated on receive) */
220
- client?: z.ZodType;
221
- }
222
- /**
223
- * A WebSocket channel instance for a specific AppSession.
224
- *
225
- * This provides real-time bidirectional communication between the server
226
- * and all UI clients connected to a particular AppSession.
227
- */
228
- interface AppSessionChannel<TServer = unknown, TClient = unknown> {
229
- /** The AppSession ID this channel belongs to */
230
- appSessionId: string;
263
+ interface WebSocketConnection<TServer = unknown, TClient = unknown> {
264
+ /** The instance ID this WebSocket belongs to */
265
+ instanceId: string;
231
266
  /** WebSocket URL for clients to connect */
232
- url: string;
267
+ websocketUrl: string;
233
268
  /** Send a message to all connected clients */
234
269
  send: (message: TServer) => void;
235
270
  /** Register a handler for incoming client messages */
236
- onMessage: (handler: MessageHandler<TClient>) => void;
271
+ onMessage: (handler: (message: TClient) => void) => void;
237
272
  /** Register a handler called when a new client connects */
238
273
  onConnect: (handler: () => void) => void;
239
- /** Close the channel and disconnect all clients */
274
+ /** Close the WebSocket and disconnect all clients */
240
275
  close: () => void;
241
276
  /** Number of connected clients */
242
277
  readonly clientCount: number;
243
278
  }
244
- /**
245
- * Manages WebSocket connections and routes messages to AppSession channels.
246
- */
247
- declare class ChannelManager {
248
- private wss;
249
- private channels;
279
+
280
+ declare class App {
281
+ private config;
282
+ private tools;
283
+ private resources;
284
+ private transports;
285
+ private websocketManager;
286
+ private instanceWebSockets;
287
+ private httpServer;
288
+ private isDev;
289
+ private hmrPort;
290
+ private hmrConfigChecked;
291
+ private callerDir;
292
+ private shutdownRegistered;
293
+ private isShuttingDown;
294
+ private resourceCache;
295
+ private resourceCacheConfig;
296
+ /** Server-side instance state, keyed by instanceId. */
297
+ private instanceState;
298
+ /** Callbacks to invoke when an instance is destroyed. */
299
+ private instanceDestroyCallbacks;
300
+ /** Singleton instanceIds per resourceUri (for non-multiInstance resources). */
301
+ private singletonInstances;
302
+ /** Whether the connected host supports multiInstance. ChatGPT doesn't, Creature does. */
303
+ private hostSupportsMultiInstance;
304
+ constructor(config: AppConfig, callerDir?: string);
305
+ /**
306
+ * Define a UI resource.
307
+ */
308
+ resource(config: ResourceConfig): this;
309
+ /**
310
+ * Define a tool.
311
+ */
312
+ tool<TInput extends z.ZodType>(name: string, config: ToolConfig<TInput>, handler: ToolHandler<z.infer<TInput>>): this;
250
313
  /**
251
- * Attach the WebSocket server to an HTTP server.
314
+ * Start the MCP server.
252
315
  */
253
- attach(server: Server): void;
316
+ start(): Promise<void>;
317
+ /**
318
+ * Stop the MCP server gracefully.
319
+ */
320
+ stop(): Promise<void>;
254
321
  /**
255
- * Create a new channel for an AppSession.
322
+ * Create an instance with optional WebSocket support.
256
323
  *
257
- * @param channelName - The channel name (e.g., "updates", "terminal")
258
- * @param appSessionId - The AppSession ID this channel belongs to
259
- * @param config - Channel configuration
260
- * @param port - Server port for WebSocket URL
261
- * @returns An AppSessionChannel for bidirectional communication
324
+ * Most tools don't need this the SDK creates instances automatically.
325
+ * Only call createInstance() when you need a WebSocket URL for real-time updates.
262
326
  */
263
- createChannel<TServer, TClient>(channelName: string, appSessionId: string, config: ChannelConfig<TServer, TClient>, port: number): AppSessionChannel<TServer, TClient>;
327
+ createInstance<TServer = unknown, TClient = unknown>(options?: {
328
+ websocket?: boolean;
329
+ }): {
330
+ instanceId: string;
331
+ websocketUrl?: string;
332
+ send?: (msg: TServer) => void;
333
+ onMessage?: (handler: (msg: TClient) => void) => void;
334
+ onConnect?: (handler: () => void) => void;
335
+ };
264
336
  /**
265
- * Check if a channel exists for an AppSession.
337
+ * Register a callback to be invoked when an instance is destroyed.
266
338
  */
267
- hasChannel(channelName: string, appSessionId: string): boolean;
339
+ onInstanceDestroy(callback: (ctx: InstanceDestroyContext) => void): void;
268
340
  /**
269
- * Check if any channels exist.
341
+ * Destroy an instance and clean up its resources.
270
342
  */
271
- hasAnyChannels(): boolean;
343
+ destroyInstance(instanceId: string): boolean;
272
344
  /**
273
- * Close all channels and shut down the WebSocket server.
345
+ * Check if an instance exists.
274
346
  */
275
- closeAll(): void;
276
- }
277
- /**
278
- * A channel definition that can create AppSession channel instances.
279
- */
280
- declare class ChannelDefinition<TServer = unknown, TClient = unknown> {
281
- private manager;
282
- private name;
283
- private config;
284
- private getPort;
285
- constructor(manager: ChannelManager, name: string, config: ChannelConfig<TServer, TClient>, getPort: () => number);
347
+ hasInstance(instanceId: string): boolean;
286
348
  /**
287
- * Create a channel for a specific AppSession.
288
- *
289
- * @param appSessionId - The AppSession ID (e.g., from a terminal AppSession)
290
- * @returns An AppSessionChannel for bidirectional communication
349
+ * Get instance state.
291
350
  */
292
- forAppSession(appSessionId: string): AppSessionChannel<TServer, TClient>;
351
+ getInstanceState<T>(instanceId: string): T | undefined;
293
352
  /**
294
- * Check if a channel exists for an AppSession.
353
+ * Set instance state directly.
295
354
  */
296
- hasChannel(appSessionId: string): boolean;
355
+ setInstanceState<T>(instanceId: string, state: T): void;
297
356
  /**
298
- * Get the channel name.
357
+ * Create a WebSocket for an existing instance.
358
+ *
359
+ * Use this when you need real-time communication for an instance
360
+ * that was created by a tool handler (which provides instanceId via context).
299
361
  */
300
- get channelName(): string;
301
- }
302
-
303
- type PartialState<TState extends AppSessionState> = {
304
- [K in keyof TState]?: Partial<TState[K]> | TState[K];
305
- };
306
- interface ServerAppSessionOptions extends AppSessionOptions {
307
- websockets?: boolean;
308
- }
309
- /**
310
- * Server-side AppSession for managing application state.
311
- *
312
- * AppSessions are distinct from TransportSessions:
313
- * - AppSession: Application-level state (internal, backend, ui)
314
- * - TransportSession: MCP protocol connection (StreamableHTTP session)
315
- */
316
- declare class ServerAppSession<TInternal extends Record<string, unknown> = Record<string, unknown>, TBackend extends Record<string, unknown> = Record<string, unknown>, TUi extends WidgetState | null = WidgetState | null> {
317
- readonly id: string;
318
- private _state;
319
- private listeners;
320
- private _channel;
321
- constructor(initialState?: Partial<AppSessionState<TInternal, TBackend, TUi>>, options?: {
322
- id?: string;
323
- });
324
- private generateId;
325
- get state(): AppSessionState<TInternal, TBackend, TUi>;
326
- get internal(): TInternal;
327
- get backend(): TBackend;
328
- get ui(): TUi;
329
- get channel(): AppSessionChannel<unknown, unknown> | null;
330
- get channelUrl(): string | undefined;
331
- setChannel(channel: AppSessionChannel<unknown, unknown>): void;
332
- subscribe(listener: AppSessionListener<AppSessionState<TInternal, TBackend, TUi>>): () => void;
333
- private notify;
334
- setState(partial: PartialState<AppSessionState<TInternal, TBackend, TUi>>): void;
335
- setInternal(internal: TInternal | Partial<TInternal>): void;
336
- setBackend(backend: TBackend | Partial<TBackend>): void;
337
- setUi(ui: TUi): void;
338
- updateUi(partial: TUi extends null ? never : Partial<NonNullable<TUi>>): void;
339
- injectAppSessionId<T extends Record<string, unknown>>(data: T): T & {
340
- appSessionId: string;
341
- };
342
- close(): void;
343
- }
344
- /**
345
- * Manages ServerAppSession instances.
346
- *
347
- * This manages AppSessions (application state), not TransportSessions
348
- * (MCP protocol connections).
349
- */
350
- declare class AppSessionManager {
351
- private appSessions;
352
- private channelManager;
353
- private getPort;
354
- private appSessionChannelName;
355
- constructor(config: {
356
- channelManager?: ChannelManager;
357
- getPort: () => number;
358
- });
359
- setChannelManager(channelManager: ChannelManager): void;
360
- create<TInternal extends Record<string, unknown> = Record<string, unknown>, TBackend extends Record<string, unknown> = Record<string, unknown>, TUi extends WidgetState | null = WidgetState | null>(initialState?: Partial<AppSessionState<TInternal, TBackend, TUi>>, options?: ServerAppSessionOptions): ServerAppSession<TInternal, TBackend, TUi>;
361
- get(id: string): ServerAppSession | undefined;
362
- require(id: string): ServerAppSession;
363
- has(id: string): boolean;
364
- delete(id: string): boolean;
365
- closeAll(): void;
366
- get size(): number;
367
- }
368
-
369
- declare class App {
370
- private config;
371
- private tools;
372
- private resources;
373
- private transports;
374
- private channelManager;
375
- private channels;
376
- private appSessionManager;
377
- private httpServer;
378
- private isDev;
379
- private hmrPort;
380
- private hmrConfigChecked;
381
- private callerDir;
382
- private shutdownRegistered;
383
- private isShuttingDown;
384
- private resourceCache;
385
- private resourceCacheConfig;
386
- constructor(config: AppConfig, callerDir?: string);
362
+ createWebSocketForInstance<TServer = unknown, TClient = unknown>(instanceId: string): WebSocketConnection<TServer, TClient> | null;
387
363
  /**
388
364
  * Get list of active transport sessions.
389
- * Transport sessions are MCP protocol connections (not AppSessions).
390
- * Useful for monitoring and debugging connection state.
391
365
  */
392
366
  getTransportSessions(): TransportSessionInfo[];
393
367
  /**
@@ -396,75 +370,70 @@ declare class App {
396
370
  getTransportSessionCount(): number;
397
371
  /**
398
372
  * Close a specific transport session.
399
- *
400
- * @param sessionId - The transport session ID to close
401
- * @returns true if the session was found and closed
402
373
  */
403
374
  closeTransportSession(sessionId: string): boolean;
404
375
  /**
405
376
  * Clear all cached resource content.
406
- * Use after updating UI files during development.
407
377
  */
408
378
  clearResourceCache(): void;
409
379
  /**
410
380
  * Clear a specific resource from the cache.
411
- *
412
- * @param uri - The resource URI to clear
413
- * @returns true if the resource was in the cache and removed
414
381
  */
415
382
  clearResourceCacheEntry(uri: string): boolean;
416
383
  /**
417
384
  * Get resource cache statistics.
418
- * Useful for monitoring cache performance.
419
385
  */
420
386
  getResourceCacheStats(): {
421
387
  size: number;
422
388
  maxSize: number;
423
389
  enabled: boolean;
424
390
  };
391
+ private getPort;
392
+ private getCallerDir;
393
+ private getHmrPort;
394
+ private generateInstanceId;
425
395
  /**
426
- * Get a cached resource if valid, or null if not cached/expired.
427
- */
428
- private getCachedResource;
429
- /**
430
- * Store a resource in the cache with LRU eviction.
431
- */
432
- private setCachedResource;
433
- /**
434
- * Get the HMR port, reading from hmr.json lazily if not already set.
435
- * This handles the timing issue where Vite may not have written hmr.json
436
- * when the server first starts.
396
+ * Resolve instanceId for a tool call based on resource configuration.
397
+ *
398
+ * - Singleton resources (default): Reuse same instanceId per resourceUri
399
+ * - Multi-instance resources: Generate new instanceId (unless provided in input)
400
+ *
401
+ * @param resourceUri The resource URI from tool config
402
+ * @param inputInstanceId instanceId from tool call input args (if any)
437
403
  */
438
- private getHmrPort;
439
- private getCallerDir;
404
+ private resolveInstanceId;
440
405
  /**
441
- * Define a UI resource.
406
+ * Extract the shape (properties) from a Zod schema.
442
407
  */
443
- resource(config: ResourceConfig): this;
444
- tool<TInput extends z.ZodType>(name: string, config: ToolConfig<TInput>, handler: ToolHandler<z.infer<TInput>>): this;
445
- channel<TServer = unknown, TClient = unknown>(name: string, config?: ChannelConfig<TServer, TClient>): ChannelDefinition<TServer, TClient>;
446
- private getPort;
447
- session<TInternal extends Record<string, unknown> = Record<string, unknown>, TBackend extends Record<string, unknown> = Record<string, unknown>, TUi extends WidgetState | null = WidgetState | null>(initialState?: Partial<AppSessionState<TInternal, TBackend, TUi>>, options?: ServerAppSessionOptions): ServerAppSession<TInternal, TBackend, TUi>;
448
- getSession(id: string): ServerAppSession | undefined;
449
- requireSession(id: string): ServerAppSession;
450
- hasSession(id: string): boolean;
451
- closeSession(id: string): boolean;
408
+ private getSchemaShape;
452
409
  /**
453
- * Create an MCP server instance with all registered tools and resources.
410
+ * Check if a field is required in a Zod schema.
454
411
  */
412
+ private isFieldRequired;
413
+ private getCachedResource;
414
+ private setCachedResource;
415
+ private createExpressApp;
416
+ private handleMcpPost;
417
+ private handleMcpGet;
418
+ private handleMcpDelete;
419
+ private createTransport;
455
420
  private createMcpServer;
421
+ private registerResources;
422
+ private registerTools;
456
423
  /**
457
- * Format a tool result into MCP format.
424
+ * Get or create a WebSocket for an instance.
425
+ * Used internally by registerTools when resource has websocket: true.
458
426
  */
459
- private formatToolResult;
460
- start(): Promise<void>;
427
+ private getOrCreateWebSocket;
428
+ private buildToolMeta;
429
+ private buildToolDescription;
461
430
  /**
462
- * Stop the MCP server gracefully.
431
+ * Format tool result for MCP protocol response.
463
432
  *
464
- * Calls the onShutdown callback, closes all sessions and channels,
465
- * then waits for active transports to close (with timeout).
433
+ * SDK manages instanceId and websocketUrl automatically.
466
434
  */
467
- stop(): Promise<void>;
435
+ private formatToolResult;
436
+ private registerShutdownHandlers;
468
437
  }
469
438
  /**
470
439
  * Create a new MCP App.
@@ -540,4 +509,4 @@ declare function htmlLoader(filePath: string, basePath?: string): () => string;
540
509
  */
541
510
  declare function wrapServer<T extends McpServer>(server: T): T;
542
511
 
543
- export { App, type AppConfig, type AppSessionChannel, AppSessionListener, AppSessionManager, AppSessionOptions, AppSessionState, type ChannelConfig, ChannelDefinition, type DisplayMode, type IconConfig, MIME_TYPES, type ResourceCacheConfig, type ResourceConfig, ServerAppSession, type ServerAppSessionOptions, type ToolConfig, type ToolContext, type ToolHandler, type ToolResult, type ToolVisibility, type TransportSessionInfo, type TransportType, createApp, htmlLoader, loadHtml, svgToDataUri, wrapServer };
512
+ export { App, type AppConfig, type DisplayMode, type IconConfig, type InstanceDestroyContext, MIME_TYPES, type ResourceCacheConfig, type ResourceConfig, type ToolConfig, type ToolContext, type ToolHandler, type ToolResult, type ToolVisibility, type TransportSessionInfo, type TransportType, type WebSocketConnection, createApp, htmlLoader, loadHtml, svgToDataUri, wrapServer };