open-mcp-app 0.1.0 → 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,863 @@
1
+ import { z } from 'zod';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+
4
+ /**
5
+ * Interface for external state storage.
6
+ * Implementations can use Redis, DynamoDB, MongoDB, etc.
7
+ */
8
+ interface StateAdapter {
9
+ /**
10
+ * Get state for an instance.
11
+ */
12
+ get<T>(instanceId: string): Promise<T | undefined>;
13
+ /**
14
+ * Set state for an instance.
15
+ */
16
+ set<T>(instanceId: string, state: T): Promise<void>;
17
+ /**
18
+ * Delete state for an instance.
19
+ */
20
+ delete(instanceId: string): Promise<void>;
21
+ }
22
+ /**
23
+ * Interface for external realtime communication.
24
+ * Implementations can use Pusher, Ably, Redis pub/sub, etc.
25
+ */
26
+ interface RealtimeAdapter {
27
+ /**
28
+ * Send a message to all clients subscribed to an instance.
29
+ */
30
+ send<T>(instanceId: string, message: T): Promise<void>;
31
+ /**
32
+ * Subscribe to messages for an instance.
33
+ */
34
+ subscribe(instanceId: string, handler: (message: unknown) => void): () => void;
35
+ /**
36
+ * Get the WebSocket/channel URL for clients to connect.
37
+ */
38
+ getWebSocketUrl(instanceId: string): string;
39
+ }
40
+ /**
41
+ * Options for configuring adapters.
42
+ */
43
+ interface AdapterOptions {
44
+ /** External state adapter (for stateful features in serverless). */
45
+ stateAdapter?: StateAdapter;
46
+ /** External realtime adapter (for WebSocket-like features in serverless). */
47
+ realtimeAdapter?: RealtimeAdapter;
48
+ }
49
+ /**
50
+ * Options for Vercel serverless handler.
51
+ */
52
+ interface VercelMcpOptions extends AdapterOptions {
53
+ }
54
+ /**
55
+ * Options for awsLambda adapter.
56
+ */
57
+ interface AwsLambdaOptions extends AdapterOptions {
58
+ }
59
+ /**
60
+ * MIME types for cross-platform compatibility.
61
+ */
62
+ declare const MIME_TYPES: {
63
+ readonly MCP_APPS: "text/html;profile=mcp-app";
64
+ readonly CHATGPT: "text/html+skybridge";
65
+ };
66
+ /**
67
+ * Display modes for UI resources.
68
+ */
69
+ type DisplayMode = "pip" | "inline" | "fullscreen";
70
+ /**
71
+ * Visibility options for tools.
72
+ * - "model": AI/Agent can call the tool
73
+ * - "app": UI can call the tool
74
+ */
75
+ type ToolVisibility = "model" | "app";
76
+ /**
77
+ * Icon configuration for UI resources.
78
+ */
79
+ interface IconConfig {
80
+ /** SVG string (must use currentColor for theming) */
81
+ svg: string;
82
+ /** Alt text for accessibility */
83
+ alt: string;
84
+ }
85
+ /**
86
+ * Resource configuration.
87
+ */
88
+ interface ResourceConfig {
89
+ /** Display name shown in UI */
90
+ name: string;
91
+ /** Resource URI (must start with ui://) */
92
+ uri: string;
93
+ /** Optional description */
94
+ description?: string;
95
+ /** Supported display modes */
96
+ displayModes: DisplayMode[];
97
+ /**
98
+ * HTML content for the resource.
99
+ *
100
+ * Accepts three formats:
101
+ * 1. **File path** (local development): `"ui/main.html"` - loaded from filesystem
102
+ * 2. **Raw HTML** (serverless-safe): `"<!DOCTYPE html>..."` - used directly
103
+ * 3. **Function** (lazy loading): `() => htmlContent` - called when needed
104
+ *
105
+ * The SDK auto-detects HTML content (starts with `<`) vs file paths.
106
+ * For serverless (Vercel, Lambda), use raw HTML or a function.
107
+ *
108
+ * @example
109
+ * // Local development - file path
110
+ * html: "ui/main.html"
111
+ *
112
+ * @example
113
+ * // Serverless - bundled HTML (import at build time)
114
+ * import { BUNDLED_HTML } from "./ui-bundle.js";
115
+ * html: BUNDLED_HTML
116
+ *
117
+ * @example
118
+ * // Serverless - function (lazy)
119
+ * html: () => fs.readFileSync("./dist/ui/main.html", "utf-8")
120
+ */
121
+ html: string | (() => string | Promise<string>);
122
+ /** Optional icon for pips */
123
+ icon?: IconConfig;
124
+ /** CSP configuration for external API access */
125
+ csp?: {
126
+ connectDomains?: string[];
127
+ resourceDomains?: string[];
128
+ };
129
+ /**
130
+ * Allow multiple instances of this resource.
131
+ * Default: false (singleton - all tools share one instance per resourceUri)
132
+ *
133
+ * - false: Singleton. SDK reuses the same instanceId for all tool calls.
134
+ * - true: Multi-instance. SDK generates new instanceId each time (unless provided in input).
135
+ *
136
+ * Note: multiInstance is only supported on Creature. On ChatGPT, resources always behave as singleton.
137
+ */
138
+ multiInstance?: boolean;
139
+ /**
140
+ * Enable WebSocket for real-time communication with the UI.
141
+ * When true, SDK automatically manages WebSocket lifecycle and provides
142
+ * `context.send()` and `context.onMessage()` in tool handlers.
143
+ */
144
+ websocket?: boolean;
145
+ }
146
+ /**
147
+ * Internal resource definition.
148
+ */
149
+ interface ResourceDefinition {
150
+ config: ResourceConfig;
151
+ }
152
+ /**
153
+ * Tool configuration.
154
+ */
155
+ interface ToolConfig<TInput extends z.ZodType = z.ZodType> {
156
+ /** Tool description (shown to AI) */
157
+ description: string;
158
+ /** Zod schema for input validation */
159
+ input?: TInput;
160
+ /** Resource URI to link this tool to a UI */
161
+ ui?: string;
162
+ /** Who can call this tool */
163
+ visibility?: ToolVisibility[];
164
+ /** Supported display modes for this tool */
165
+ displayModes?: DisplayMode[];
166
+ /** Preferred display mode */
167
+ defaultDisplayMode?: DisplayMode;
168
+ /** Loading message shown while tool is running (used by ChatGPT) */
169
+ loadingMessage?: string;
170
+ /** Completion message shown when tool finishes (used by ChatGPT) */
171
+ completedMessage?: string;
172
+ }
173
+ /**
174
+ * Tool result returned from handler.
175
+ */
176
+ interface ToolResult {
177
+ /** Structured data for UI rendering */
178
+ data?: Record<string, unknown>;
179
+ /** Text content for AI context */
180
+ text?: string;
181
+ /** Title for panel/widget */
182
+ title?: string;
183
+ /** Height hint for inline widgets (60-300px) */
184
+ inlineHeight?: number;
185
+ /** Whether this is an error result */
186
+ isError?: boolean;
187
+ }
188
+ /**
189
+ * Tool handler function type.
190
+ */
191
+ type ToolHandler<TInput> = (input: TInput, context: ToolContext) => ToolResult | Promise<ToolResult>;
192
+ /**
193
+ * Context passed to tool handlers.
194
+ * Provides access to instanceId, state management, and WebSocket communication.
195
+ */
196
+ interface ToolContext {
197
+ /**
198
+ * The instance ID for this tool call.
199
+ * Generated before handler runs. Use for server-side state keying.
200
+ * Automatically attached to tool result for UI routing.
201
+ */
202
+ instanceId: string;
203
+ /**
204
+ * Creature token for identity retrieval.
205
+ * Present when either:
206
+ * 1. Tool call originated from Creature host (injected via `_creatureToken` arg)
207
+ * 2. Tool call includes OAuth bearer token in Authorization header (ChatGPT/other hosts)
208
+ *
209
+ * Use `getIdentity(context.creatureToken)` to retrieve user identity
210
+ * for multi-tenant data access.
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * app.tool("save_note", { ... }, async ({ content }, context) => {
215
+ * if (!context.creatureToken) {
216
+ * return { error: "Authentication required" };
217
+ * }
218
+ * const identity = await getIdentity(context.creatureToken);
219
+ * await db.notes.insert({
220
+ * user_id: identity.user.id,
221
+ * content,
222
+ * });
223
+ * });
224
+ * ```
225
+ */
226
+ creatureToken?: string;
227
+ /**
228
+ * Get server-side state for this instance.
229
+ * State is NOT sent to UI — use for PIDs, connections, handles.
230
+ */
231
+ getState: <T>() => T | undefined;
232
+ /**
233
+ * Set server-side state for this instance.
234
+ * State is NOT sent to UI — use for PIDs, connections, handles.
235
+ */
236
+ setState: <T>(state: T) => void;
237
+ /**
238
+ * Send a message to the UI via WebSocket.
239
+ * Only available if the resource has `websocket: true`.
240
+ * For singleton resources, sends to the single shared WebSocket.
241
+ * For multi-instance resources, sends to this instance's WebSocket.
242
+ */
243
+ send: <T>(message: T) => void;
244
+ /**
245
+ * Register a handler for messages from the UI.
246
+ * Only available if the resource has `websocket: true`.
247
+ */
248
+ onMessage: <T>(handler: (message: T) => void) => void;
249
+ /**
250
+ * Register a handler called when a UI client connects to the WebSocket.
251
+ * Useful for sending buffered data when a client connects.
252
+ * Only available if the resource has `websocket: true`.
253
+ */
254
+ onConnect: (handler: () => void) => void;
255
+ /**
256
+ * WebSocket URL for the UI to connect to.
257
+ * Only available if the resource has `websocket: true`.
258
+ * Automatically included in tool result's structuredContent.
259
+ */
260
+ websocketUrl: string | undefined;
261
+ }
262
+ /**
263
+ * Internal tool definition with handler.
264
+ */
265
+ interface ToolDefinition<TInput = unknown> {
266
+ config: ToolConfig;
267
+ handler: ToolHandler<TInput>;
268
+ }
269
+ /**
270
+ * Context passed to onInstanceDestroy callback.
271
+ */
272
+ interface InstanceDestroyContext {
273
+ /** The instanceId being destroyed */
274
+ instanceId: string;
275
+ /** Last server-side state for this instance (from setState calls) */
276
+ state: unknown;
277
+ }
278
+ /**
279
+ * Supported MCP transport types.
280
+ * Currently only StreamableHTTP is supported by the SDK server.
281
+ * Stdio may be added in the future.
282
+ */
283
+ type TransportType = "streamable-http" | "stdio";
284
+ /**
285
+ * Information about a transport session.
286
+ * Provides details about an active MCP protocol connection.
287
+ */
288
+ interface TransportSessionInfo {
289
+ /** Unique session identifier */
290
+ id: string;
291
+ /** The transport type for this session */
292
+ transport: TransportType;
293
+ }
294
+ /**
295
+ * Creature-managed authentication configuration.
296
+ * When enabled, Creature provides user identity and tokens to your app.
297
+ */
298
+ interface CreatureAuthConfig {
299
+ /**
300
+ * Enable Creature-managed authentication.
301
+ * When true, your app receives user identity (id, email, name) and
302
+ * organization/project context via hostContext.creature.
303
+ *
304
+ * Apps can use this for:
305
+ * - Auto-registering users without login screens
306
+ * - Scoping data to users/orgs/projects
307
+ * - Backend API calls with verified identity
308
+ *
309
+ * @default false
310
+ */
311
+ creatureManaged?: boolean;
312
+ }
313
+ /**
314
+ * App configuration.
315
+ */
316
+ interface AppConfig {
317
+ /** App name (used in protocol handshake) */
318
+ name: string;
319
+ /** App version */
320
+ version: string;
321
+ /**
322
+ * Authentication configuration.
323
+ * Enable Creature-managed auth to receive user identity automatically.
324
+ *
325
+ * @example
326
+ * auth: { creatureManaged: true }
327
+ */
328
+ auth?: CreatureAuthConfig;
329
+ /**
330
+ * High-level instructions for using this MCP.
331
+ * Sent to the model during initialization to provide context about
332
+ * how to use the MCP's tools effectively.
333
+ *
334
+ * @example
335
+ * instructions: `This MCP manages markdown notes.
336
+ * When editing an existing note, ALWAYS use action:"read" first to get current content,
337
+ * then apply your changes with action:"save" to avoid overwriting user edits.`
338
+ */
339
+ instructions?: string;
340
+ /** Port for HTTP transport (default: 3000 or MCP_PORT env) */
341
+ port?: number;
342
+ /** Enable dev mode with HMR support (default: auto-detect from NODE_ENV) */
343
+ dev?: boolean;
344
+ /** HMR port override (default: auto-detect from Vite config or 5173) */
345
+ hmrPort?: number;
346
+ /**
347
+ * Called when a new transport session is created.
348
+ * Transport sessions are MCP protocol connections (not instances).
349
+ */
350
+ onTransportSessionCreated?: (info: TransportSessionInfo) => void;
351
+ /**
352
+ * Called when a transport session is closed.
353
+ * Clients should re-initialize to continue.
354
+ */
355
+ onTransportSessionClosed?: (info: TransportSessionInfo) => void;
356
+ /**
357
+ * Called when a transport session error occurs.
358
+ * Useful for logging and monitoring connection health.
359
+ */
360
+ onTransportSessionError?: (info: TransportSessionInfo, error: Error) => void;
361
+ /**
362
+ * Called when a tool handler throws an error.
363
+ * The error is still returned to the client as an MCP error.
364
+ */
365
+ onToolError?: (toolName: string, error: Error, args: unknown) => void;
366
+ /**
367
+ * Called during graceful shutdown, before closing connections.
368
+ * Use this to clean up resources (e.g., close database connections).
369
+ */
370
+ onShutdown?: () => Promise<void> | void;
371
+ /**
372
+ * Timeout for graceful shutdown in milliseconds (default: 5000).
373
+ * After this timeout, remaining connections are force-closed.
374
+ */
375
+ gracefulShutdownTimeout?: number;
376
+ /**
377
+ * HTTP keep-alive timeout during shutdown in milliseconds (default: 5000).
378
+ * Controls how long to wait for in-flight requests to complete.
379
+ */
380
+ keepAliveTimeout?: number;
381
+ }
382
+ /**
383
+ * WebSocket connection for an instance.
384
+ *
385
+ * Provides real-time bidirectional communication between the server
386
+ * and all UI clients connected to a particular instance.
387
+ */
388
+ interface WebSocketConnection<TServer = unknown, TClient = unknown> {
389
+ /** The instance ID this WebSocket belongs to */
390
+ instanceId: string;
391
+ /** WebSocket URL for clients to connect */
392
+ websocketUrl: string;
393
+ /** Send a message to all connected clients */
394
+ send: (message: TServer) => void;
395
+ /** Register a handler for incoming client messages */
396
+ onMessage: (handler: (message: TClient) => void) => void;
397
+ /** Register a handler called when a new client connects */
398
+ onConnect: (handler: () => void) => void;
399
+ /** Close the WebSocket and disconnect all clients */
400
+ close: () => void;
401
+ /** Number of connected clients */
402
+ readonly clientCount: number;
403
+ }
404
+
405
+ declare class App {
406
+ private config;
407
+ private tools;
408
+ private resources;
409
+ private transports;
410
+ private websocketManager;
411
+ private instanceWebSockets;
412
+ private httpServer;
413
+ private isDev;
414
+ private hmrPort;
415
+ private hmrConfigChecked;
416
+ private callerDir;
417
+ private shutdownRegistered;
418
+ private isShuttingDown;
419
+ /** Server-side instance state, keyed by instanceId. */
420
+ private instanceState;
421
+ /** Callbacks to invoke when an instance is destroyed. */
422
+ private instanceDestroyCallbacks;
423
+ /** Singleton instanceIds per resourceUri (for non-multiInstance resources). */
424
+ private singletonInstances;
425
+ /** Whether the connected host supports multiInstance. ChatGPT doesn't, Creature does. */
426
+ private hostSupportsMultiInstance;
427
+ /** OAuth discovery endpoint configuration. */
428
+ private oauthDiscoveryConfig;
429
+ constructor(config: AppConfig, callerDir?: string);
430
+ /**
431
+ * Define a UI resource.
432
+ */
433
+ resource(config: ResourceConfig): this;
434
+ /**
435
+ * Define a tool.
436
+ */
437
+ tool<TInput extends z.ZodType>(name: string, config: ToolConfig<TInput>, handler: ToolHandler<z.infer<TInput>>): this;
438
+ /**
439
+ * Serve an OAuth discovery endpoint.
440
+ * Used by OAuth clients (like ChatGPT) to discover authorization server metadata.
441
+ *
442
+ * @param config.path - The endpoint path (e.g., "/.well-known/oauth-authorization-server")
443
+ * @param config.[rest] - All other properties become the JSON response body
444
+ *
445
+ * @example
446
+ * app.serveOAuthDiscovery({
447
+ * path: "/.well-known/oauth-authorization-server",
448
+ * issuer: "https://creature.run",
449
+ * authorization_endpoint: "https://creature.run/oauth/authorize",
450
+ * token_endpoint: "https://api.creature.run/apps/v1/oauth/token",
451
+ * response_types_supported: ["code"],
452
+ * grant_types_supported: ["authorization_code", "refresh_token"],
453
+ * code_challenge_methods_supported: ["S256"],
454
+ * });
455
+ */
456
+ serveOAuthDiscovery(config: {
457
+ path: string;
458
+ [key: string]: unknown;
459
+ }): this;
460
+ /**
461
+ * Start the MCP server.
462
+ */
463
+ start(): Promise<void>;
464
+ /**
465
+ * Stop the MCP server gracefully.
466
+ */
467
+ stop(): Promise<void>;
468
+ /**
469
+ * Create an instance with optional WebSocket support.
470
+ *
471
+ * Most tools don't need this — the SDK creates instances automatically.
472
+ * Only call createInstance() when you need a WebSocket URL for real-time updates.
473
+ */
474
+ createInstance<TServer = unknown, TClient = unknown>(options?: {
475
+ websocket?: boolean;
476
+ }): {
477
+ instanceId: string;
478
+ websocketUrl?: string;
479
+ send?: (msg: TServer) => void;
480
+ onMessage?: (handler: (msg: TClient) => void) => void;
481
+ onConnect?: (handler: () => void) => void;
482
+ };
483
+ /**
484
+ * Register a callback to be invoked when an instance is destroyed.
485
+ */
486
+ onInstanceDestroy(callback: (ctx: InstanceDestroyContext) => void): void;
487
+ /**
488
+ * Destroy an instance and clean up its resources.
489
+ */
490
+ destroyInstance(instanceId: string): boolean;
491
+ /**
492
+ * Check if an instance exists.
493
+ */
494
+ hasInstance(instanceId: string): boolean;
495
+ /**
496
+ * Get instance state.
497
+ */
498
+ getInstanceState<T>(instanceId: string): T | undefined;
499
+ /**
500
+ * Set instance state directly.
501
+ */
502
+ setInstanceState<T>(instanceId: string, state: T): void;
503
+ /**
504
+ * Create a WebSocket for an existing instance.
505
+ *
506
+ * Use this when you need real-time communication for an instance
507
+ * that was created by a tool handler (which provides instanceId via context).
508
+ */
509
+ createWebSocketForInstance<TServer = unknown, TClient = unknown>(instanceId: string): WebSocketConnection<TServer, TClient> | null;
510
+ /**
511
+ * Get list of active transport sessions.
512
+ */
513
+ getTransportSessions(): TransportSessionInfo[];
514
+ /**
515
+ * Get the count of active transport sessions.
516
+ */
517
+ getTransportSessionCount(): number;
518
+ /**
519
+ * Get the app configuration.
520
+ */
521
+ getConfig(): AppConfig;
522
+ /**
523
+ * Get all tool definitions.
524
+ */
525
+ getToolDefinitions(): Map<string, ToolDefinition>;
526
+ /**
527
+ * Get all resource definitions.
528
+ */
529
+ getResourceDefinitions(): Map<string, ResourceDefinition>;
530
+ /**
531
+ * Create a Vercel serverless function handler.
532
+ * Works with plain Vercel Serverless Functions - no Next.js or mcp-handler needed.
533
+ *
534
+ * Usage in api/mcp.ts:
535
+ * ```ts
536
+ * import { app } from "../src/app";
537
+ * export default app.toVercelFunctionHandler();
538
+ * ```
539
+ */
540
+ toVercelFunctionHandler(options?: VercelMcpOptions): (reqOrRequest: any, res?: any) => Promise<any>;
541
+ /**
542
+ * Create an AWS Lambda handler.
543
+ *
544
+ * Note: This returns a Lambda handler function. The implementation
545
+ * is inline to avoid circular dependencies.
546
+ */
547
+ toAwsLambda(options?: AwsLambdaOptions): (event: any, _lambdaContext: any) => Promise<{
548
+ statusCode: number;
549
+ headers: {
550
+ "Access-Control-Allow-Origin": string;
551
+ "Access-Control-Allow-Methods": string;
552
+ "Access-Control-Allow-Headers": string;
553
+ "Content-Type": string;
554
+ };
555
+ body: string;
556
+ }>;
557
+ /**
558
+ * Close a specific transport session.
559
+ */
560
+ closeTransportSession(sessionId: string): boolean;
561
+ private getPort;
562
+ private getCallerDir;
563
+ /**
564
+ * Create a ToolContext for serverless environments.
565
+ * Shared between Vercel and Lambda adapters to avoid duplication.
566
+ */
567
+ /**
568
+ * In-memory state for serverless when no external adapter provided.
569
+ * Only useful for local development - won't persist across invocations in production.
570
+ */
571
+ private serverlessMemoryState;
572
+ private serverlessStateWarningLogged;
573
+ private serverlessRealtimeWarningLogged;
574
+ private createServerlessContext;
575
+ /**
576
+ * Format tool result for serverless response.
577
+ * Shared between Vercel and Lambda adapters.
578
+ * Must include all fields that formatToolResult includes (title, inlineHeight, etc.).
579
+ */
580
+ private formatServerlessResult;
581
+ /**
582
+ * Create a Vercel serverless function handler.
583
+ * Handles MCP JSON-RPC protocol directly - no mcp-handler or Next.js needed.
584
+ */
585
+ private createVercelHandler;
586
+ /**
587
+ * Handle an MCP JSON-RPC request.
588
+ * Shared logic for all serverless handlers.
589
+ */
590
+ private handleMcpRequest;
591
+ /**
592
+ * Convert Zod schema to JSON Schema for tool definitions.
593
+ */
594
+ private zodToJsonSchema;
595
+ /**
596
+ * Create an AWS Lambda handler function.
597
+ */
598
+ private createLambdaHandler;
599
+ /**
600
+ * Get the HMR port for injecting the live reload client.
601
+ *
602
+ * When MCP_PORT is set (by Creature), derives the HMR port deterministically
603
+ * as MCP_PORT + HMR_PORT_OFFSET. This eliminates the race condition where
604
+ * the hmr.json file might not exist yet when the server starts.
605
+ *
606
+ * Falls back to reading hmr.json for non-Creature environments (manual npm run dev).
607
+ */
608
+ private getHmrPort;
609
+ private generateInstanceId;
610
+ /**
611
+ * Resolve instanceId for a tool call based on resource configuration.
612
+ *
613
+ * - Singleton resources (default): Reuse same instanceId per resourceUri
614
+ * - Multi-instance resources: Generate new instanceId (unless provided in input)
615
+ *
616
+ * @param resourceUri The resource URI from tool config
617
+ * @param inputInstanceId instanceId from tool call input args (if any)
618
+ */
619
+ private resolveInstanceId;
620
+ /**
621
+ * Extract the shape (properties) from a Zod schema.
622
+ */
623
+ private getSchemaShape;
624
+ /**
625
+ * Check if a field is required in a Zod schema.
626
+ */
627
+ private isFieldRequired;
628
+ private createExpressApp;
629
+ private handleMcpPost;
630
+ private handleMcpGet;
631
+ private handleMcpDelete;
632
+ private createTransport;
633
+ private createMcpServer;
634
+ private registerResources;
635
+ private registerTools;
636
+ /**
637
+ * Get or create a WebSocket for an instance.
638
+ * Used internally by registerTools when resource has websocket: true.
639
+ */
640
+ private getOrCreateWebSocket;
641
+ private buildToolMeta;
642
+ private buildToolDescription;
643
+ /**
644
+ * Format tool result for MCP protocol response.
645
+ *
646
+ * SDK manages instanceId and websocketUrl automatically.
647
+ */
648
+ private formatToolResult;
649
+ private registerShutdownHandlers;
650
+ }
651
+ /**
652
+ * Create a new MCP App.
653
+ */
654
+ declare function createApp(config: AppConfig): App;
655
+
656
+ declare function svgToDataUri(svg: string): string;
657
+ /**
658
+ * Load HTML from a file path.
659
+ * Automatically resolves paths like "ui/main.html" to "dist/ui/main.html"
660
+ * regardless of whether running from src/ or dist/.
661
+ */
662
+ declare function loadHtml(filePath: string, basePath?: string): string;
663
+ /**
664
+ * Check if a string is HTML content (vs a file path).
665
+ *
666
+ * HTML content detection:
667
+ * - Starts with `<` (with optional whitespace/BOM)
668
+ * - Starts with `<!DOCTYPE` (case-insensitive)
669
+ *
670
+ * This enables serverless deployments where HTML is bundled at build time
671
+ * and passed directly as a string, rather than loaded from the filesystem.
672
+ */
673
+ declare function isHtmlContent(str: string): boolean;
674
+ /**
675
+ * Create an HTML loader function for a file path or HTML content.
676
+ *
677
+ * For serverless environments (Vercel, Lambda), developers can:
678
+ * 1. Pass HTML content directly: `html: "<!DOCTYPE html>..."`
679
+ * 2. Use a function: `html: () => BUNDLED_HTML`
680
+ * 3. Import bundled HTML: `html: await import("./ui-bundle.js").then(m => m.HTML)`
681
+ *
682
+ * The SDK automatically detects whether the string is:
683
+ * - HTML content (starts with `<`) → returns as-is
684
+ * - File path → loads via filesystem (local dev only)
685
+ */
686
+ declare function htmlLoader(htmlOrPath: string, basePath?: string): () => string;
687
+
688
+ /**
689
+ * Middleware for adding cross-platform compatibility to the official MCP SDK.
690
+ *
691
+ * Wrap your McpServer to automatically output dual formats for both
692
+ * MCP Apps (Creature) and ChatGPT Apps SDK.
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
697
+ * import { wrapServer } from "open-mcp-app/server";
698
+ *
699
+ * const server = wrapServer(new McpServer({ name: "my-app", version: "1.0.0" }));
700
+ *
701
+ * // Use the standard MCP SDK API - dual format is automatic
702
+ * server.registerResource("Panel", "ui://app/panel", { ... }, handler);
703
+ * server.registerTool("search", { ... }, handler);
704
+ * ```
705
+ */
706
+
707
+ /**
708
+ * Wrap an McpServer to automatically add dual-format support.
709
+ *
710
+ * This intercepts `registerResource` and `registerTool` calls to automatically
711
+ * include both MCP Apps and ChatGPT metadata/MIME types.
712
+ *
713
+ * @param server - An McpServer instance from @modelcontextprotocol/sdk
714
+ * @returns The same server with intercepted registration methods
715
+ *
716
+ * @example
717
+ * ```ts
718
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
719
+ * import { wrapServer } from "open-mcp-app/server";
720
+ *
721
+ * const server = wrapServer(new McpServer({ name: "my-app", version: "1.0.0" }));
722
+ *
723
+ * // Register resources normally - dual MIME types added automatically
724
+ * server.registerResource("Panel", "ui://app/panel", {
725
+ * mimeType: "text/html",
726
+ * description: "My panel",
727
+ * }, async () => ({
728
+ * contents: [{ uri: "ui://app/panel", mimeType: "text/html", text: html }],
729
+ * }));
730
+ *
731
+ * // Register tools normally - dual metadata added automatically
732
+ * server.registerTool("search", {
733
+ * description: "Search",
734
+ * inputSchema: z.object({ query: z.string() }),
735
+ * _meta: {
736
+ * ui: { resourceUri: "ui://app/panel", displayModes: ["pip"] },
737
+ * loadingMessage: "Searching...", // Will become openai/toolInvocation/invoking
738
+ * },
739
+ * }, handler);
740
+ * ```
741
+ */
742
+ declare function wrapServer<T extends McpServer>(server: T): T;
743
+
744
+ /**
745
+ * User identity from a Creature token.
746
+ */
747
+ interface CreatureUser {
748
+ /** Unique, stable user identifier */
749
+ id: string;
750
+ /** User's email address */
751
+ email: string;
752
+ /** User's display name (may be undefined) */
753
+ name?: string;
754
+ }
755
+ /**
756
+ * Organization context from a Creature token.
757
+ */
758
+ interface CreatureOrganization {
759
+ /** Unique organization identifier */
760
+ id: string;
761
+ /** Organization display name */
762
+ name: string;
763
+ /** URL-safe organization slug */
764
+ slug: string;
765
+ }
766
+ /**
767
+ * Project context from a Creature token.
768
+ */
769
+ interface CreatureProject {
770
+ /** Unique project identifier */
771
+ id: string;
772
+ /** Project display name */
773
+ name: string;
774
+ }
775
+ /**
776
+ * Session context from a Creature token.
777
+ */
778
+ interface CreatureSession {
779
+ /** Unique session identifier */
780
+ id: string;
781
+ }
782
+ /**
783
+ * Identity claims from a Creature App Token.
784
+ * Returned by getIdentity() on successful retrieval.
785
+ */
786
+ interface CreatureIdentity {
787
+ /** User identity (always present for valid tokens) */
788
+ user: CreatureUser;
789
+ /** Organization context (present if user was in an org context) */
790
+ organization?: CreatureOrganization;
791
+ /** Project context (present if user was in a project context) */
792
+ project?: CreatureProject;
793
+ /** Session context */
794
+ session?: CreatureSession;
795
+ /** Token expiration time (ISO 8601 string) */
796
+ expiresAt: string;
797
+ }
798
+ /**
799
+ * Error thrown when identity retrieval fails.
800
+ */
801
+ declare class CreatureIdentityError extends Error {
802
+ /** Error code from the identity API */
803
+ code: string;
804
+ constructor({ code, message }: {
805
+ code: string;
806
+ message: string;
807
+ });
808
+ }
809
+ /**
810
+ * Configuration for the Creature identity API.
811
+ */
812
+ interface IdentityConfig {
813
+ /**
814
+ * Base URL for the Creature API.
815
+ * Defaults to https://api.creature.run
816
+ * Can be overridden for testing or custom deployments.
817
+ */
818
+ apiUrl?: string;
819
+ }
820
+ /**
821
+ * Retrieves user identity from a Creature App Token.
822
+ *
823
+ * Use this in your MCP App tool handlers to get the authenticated user's
824
+ * identity. The token is provided via `context.creatureToken` when you
825
+ * opt into Creature-managed auth.
826
+ *
827
+ * @param creatureToken - The App Token from context.creatureToken
828
+ * @param config - Optional configuration (e.g., custom API URL)
829
+ * @returns The identity claims (user, organization, project, session)
830
+ * @throws {CreatureIdentityError} If the token is invalid, expired, or malformed
831
+ *
832
+ * @example
833
+ * ```typescript
834
+ * import { getIdentity } from "open-mcp-app/server";
835
+ *
836
+ * app.tool("save_note", { ... }, async ({ content }, context) => {
837
+ * if (!context.creatureToken) {
838
+ * return { text: "Authentication required", isError: true };
839
+ * }
840
+ *
841
+ * try {
842
+ * const identity = await getIdentity(context.creatureToken);
843
+ *
844
+ * // Use identity to scope data access
845
+ * await db.notes.insert({
846
+ * user_id: identity.user.id,
847
+ * org_id: identity.organization?.id,
848
+ * content,
849
+ * });
850
+ *
851
+ * return { text: "Note saved" };
852
+ * } catch (err) {
853
+ * if (err instanceof CreatureIdentityError) {
854
+ * return { text: err.message, isError: true };
855
+ * }
856
+ * throw err;
857
+ * }
858
+ * });
859
+ * ```
860
+ */
861
+ declare const getIdentity: (creatureToken: string | undefined | null, config?: IdentityConfig) => Promise<CreatureIdentity>;
862
+
863
+ export { type AdapterOptions, App, type AppConfig, type AwsLambdaOptions, type CreatureIdentity, CreatureIdentityError, type CreatureOrganization, type CreatureProject, type CreatureSession, type CreatureUser, type DisplayMode, type IconConfig, type InstanceDestroyContext, MIME_TYPES, type RealtimeAdapter, type ResourceConfig, type StateAdapter, type ToolConfig, type ToolContext, type ToolHandler, type ToolResult, type ToolVisibility, type TransportSessionInfo, type TransportType, type VercelMcpOptions, type WebSocketConnection, createApp, getIdentity, htmlLoader, isHtmlContent, loadHtml, svgToDataUri, wrapServer };