@leanmcp/ui 0.3.4 → 0.3.5

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,138 @@
1
+ 'use strict';
2
+
3
+ require('reflect-metadata');
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var UI_APP_COMPONENT_KEY = "ui:app:component";
8
+ var UI_APP_URI_KEY = "ui:app:uri";
9
+ var UI_APP_OPTIONS_KEY = "ui:app:options";
10
+ function UIApp(options) {
11
+ return (target, propertyKey, descriptor) => {
12
+ const methodName = String(propertyKey);
13
+ const className = target.constructor.name.toLowerCase().replace("service", "");
14
+ const uri = options.uri ?? `ui://${className}/${methodName}`;
15
+ Reflect.defineMetadata(UI_APP_COMPONENT_KEY, options.component, descriptor.value);
16
+ Reflect.defineMetadata(UI_APP_URI_KEY, uri, descriptor.value);
17
+ Reflect.defineMetadata(UI_APP_OPTIONS_KEY, options, descriptor.value);
18
+ const existingMeta = Reflect.getMetadata("tool:meta", descriptor.value) || {};
19
+ Reflect.defineMetadata("tool:meta", {
20
+ ...existingMeta,
21
+ // New nested format (preferred by ext-apps 0.2.2)
22
+ ui: {
23
+ resourceUri: uri
24
+ },
25
+ // Legacy flat format (for backwards compatibility)
26
+ "ui/resourceUri": uri
27
+ }, descriptor.value);
28
+ return descriptor;
29
+ };
30
+ }
31
+ __name(UIApp, "UIApp");
32
+ function getUIAppMetadata(target) {
33
+ const component = Reflect.getMetadata(UI_APP_COMPONENT_KEY, target);
34
+ if (!component) return void 0;
35
+ return {
36
+ component,
37
+ uri: Reflect.getMetadata(UI_APP_URI_KEY, target),
38
+ ...Reflect.getMetadata(UI_APP_OPTIONS_KEY, target)
39
+ };
40
+ }
41
+ __name(getUIAppMetadata, "getUIAppMetadata");
42
+ function getUIAppUri(target) {
43
+ return Reflect.getMetadata(UI_APP_URI_KEY, target);
44
+ }
45
+ __name(getUIAppUri, "getUIAppUri");
46
+ var GPT_APP_COMPONENT_KEY = "gptapp:component";
47
+ var GPT_APP_URI_KEY = "gptapp:uri";
48
+ var GPT_APP_OPTIONS_KEY = "gptapp:options";
49
+ function GPTApp(options) {
50
+ return (target, propertyKey, descriptor) => {
51
+ const methodName = String(propertyKey);
52
+ const className = target.constructor.name.toLowerCase().replace("service", "");
53
+ const uri = options.uri ?? `ui://${className}/${methodName}`;
54
+ Reflect.defineMetadata(GPT_APP_COMPONENT_KEY, options.component, descriptor.value);
55
+ Reflect.defineMetadata(GPT_APP_URI_KEY, uri, descriptor.value);
56
+ Reflect.defineMetadata(GPT_APP_OPTIONS_KEY, options, descriptor.value);
57
+ const existingMeta = Reflect.getMetadata("tool:meta", descriptor.value) || {};
58
+ const uiMeta = {
59
+ resourceUri: uri
60
+ };
61
+ if (options.visibility) {
62
+ uiMeta.visibility = options.visibility === "private" ? [
63
+ "app"
64
+ ] : [
65
+ "model",
66
+ "app"
67
+ ];
68
+ }
69
+ const openAiMeta = {
70
+ // New nested format (preferred by ext-apps 0.2.2)
71
+ ui: uiMeta,
72
+ // Legacy flat format (for backwards compatibility)
73
+ "ui/resourceUri": uri,
74
+ "openai/outputTemplate": uri
75
+ };
76
+ if (options.widgetAccessible !== void 0) {
77
+ openAiMeta["openai/widgetAccessible"] = options.widgetAccessible;
78
+ }
79
+ if (options.visibility) {
80
+ openAiMeta["openai/visibility"] = options.visibility;
81
+ }
82
+ if (options.prefersBorder !== void 0) {
83
+ openAiMeta["openai/widgetPrefersBorder"] = options.prefersBorder;
84
+ }
85
+ if (options.widgetDomain) {
86
+ openAiMeta["openai/widgetDomain"] = options.widgetDomain;
87
+ }
88
+ if (options.widgetDescription) {
89
+ openAiMeta["openai/widgetDescription"] = options.widgetDescription;
90
+ }
91
+ if (options.csp) {
92
+ openAiMeta["openai/widgetCSP"] = options.csp;
93
+ }
94
+ if (options.fileParams) {
95
+ openAiMeta["openai/fileParams"] = options.fileParams;
96
+ }
97
+ if (options.invocation) {
98
+ if (options.invocation.invoking) openAiMeta["openai/toolInvocation/invoking"] = options.invocation.invoking;
99
+ if (options.invocation.invoked) openAiMeta["openai/toolInvocation/invoked"] = options.invocation.invoked;
100
+ }
101
+ Reflect.defineMetadata("tool:meta", {
102
+ ...existingMeta,
103
+ ...openAiMeta
104
+ }, descriptor.value);
105
+ return descriptor;
106
+ };
107
+ }
108
+ __name(GPTApp, "GPTApp");
109
+ function getGPTAppMetadata(target) {
110
+ const component = Reflect.getMetadata(GPT_APP_COMPONENT_KEY, target);
111
+ if (!component) return void 0;
112
+ return {
113
+ component,
114
+ uri: Reflect.getMetadata(GPT_APP_URI_KEY, target),
115
+ ...Reflect.getMetadata(GPT_APP_OPTIONS_KEY, target)
116
+ };
117
+ }
118
+ __name(getGPTAppMetadata, "getGPTAppMetadata");
119
+ function getGPTAppUri(target) {
120
+ return Reflect.getMetadata(GPT_APP_URI_KEY, target);
121
+ }
122
+ __name(getGPTAppUri, "getGPTAppUri");
123
+
124
+ exports.GPTApp = GPTApp;
125
+ exports.GPT_APP_COMPONENT_KEY = GPT_APP_COMPONENT_KEY;
126
+ exports.GPT_APP_OPTIONS_KEY = GPT_APP_OPTIONS_KEY;
127
+ exports.GPT_APP_URI_KEY = GPT_APP_URI_KEY;
128
+ exports.UIApp = UIApp;
129
+ exports.UI_APP_COMPONENT_KEY = UI_APP_COMPONENT_KEY;
130
+ exports.UI_APP_OPTIONS_KEY = UI_APP_OPTIONS_KEY;
131
+ exports.UI_APP_URI_KEY = UI_APP_URI_KEY;
132
+ exports.__name = __name;
133
+ exports.getGPTAppMetadata = getGPTAppMetadata;
134
+ exports.getGPTAppUri = getGPTAppUri;
135
+ exports.getUIAppMetadata = getUIAppMetadata;
136
+ exports.getUIAppUri = getUIAppUri;
137
+ //# sourceMappingURL=chunk-57VDL5UC.js.map
138
+ //# sourceMappingURL=chunk-57VDL5UC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/decorator/UIApp.ts","../src/decorator/GPTApp.ts"],"names":["UI_APP_COMPONENT_KEY","UI_APP_URI_KEY","UI_APP_OPTIONS_KEY","UIApp","options","target","propertyKey","descriptor","methodName","String","className","name","toLowerCase","replace","uri","Reflect","defineMetadata","component","value","existingMeta","getMetadata","ui","resourceUri","getUIAppMetadata","undefined","getUIAppUri","GPT_APP_COMPONENT_KEY","GPT_APP_URI_KEY","GPT_APP_OPTIONS_KEY","GPTApp","uiMeta","visibility","openAiMeta","widgetAccessible","prefersBorder","widgetDomain","widgetDescription","csp","fileParams","invocation","invoking","invoked","getGPTAppMetadata","getGPTAppUri"],"mappings":"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","file":"chunk-57VDL5UC.js","sourcesContent":["/**\n * @UIApp Decorator\n * \n * Links an MCP tool to a React UI component.\n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Auto-registers a resource that renders the component to HTML\n * \n * This decorator is designed to work with @leanmcp/core decorators.\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const UI_APP_COMPONENT_KEY = 'ui:app:component';\nexport const UI_APP_URI_KEY = 'ui:app:uri';\nexport const UI_APP_OPTIONS_KEY = 'ui:app:options';\n\n/**\n * Options for @UIApp decorator\n */\nexport interface UIAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n /** Custom resource URI (auto-generated if not provided) */\n uri?: string;\n /** HTML document title */\n title?: string;\n /** Additional CSS styles */\n styles?: string;\n}\n\n\n/**\n * Decorator that links a tool to a UI component.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { UIApp } from '@leanmcp/ui';\n * import { WeatherCard } from './WeatherCard';\n * \n * class WeatherService {\n * @Tool({ description: 'Get weather for a city' })\n * @UIApp({ component: WeatherCard })\n * async getWeather(args: { city: string }) {\n * return { city: args.city, temp: 22 };\n * }\n * }\n * ```\n */\nexport function UIApp(options: UIAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(UI_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(UI_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(UI_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI for the @Tool decorator to pick up\n // The @Tool decorator in @leanmcp/core should check for this\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n // New nested format (preferred by ext-apps 0.2.2)\n ui: {\n resourceUri: uri,\n },\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get UIApp metadata from a method\n */\nexport function getUIAppMetadata(target: Function): UIAppOptions | undefined {\n const component = Reflect.getMetadata(UI_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(UI_APP_URI_KEY, target),\n ...Reflect.getMetadata(UI_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getUIAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(UI_APP_URI_KEY, target);\n}\n","/**\n * @GPTApp Decorator\n * \n * Links an MCP tool to a React UI component compliant with ChatGPT Apps SDK.\n * \n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Adds OpenAI-specific metadata (widgetAccessible, outputTemplate, etc.)\n * 3. Auto-registers a resource that renders the component to HTML with 'text/html+skybridge' mimeType\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const GPT_APP_COMPONENT_KEY = 'gptapp:component';\nexport const GPT_APP_URI_KEY = 'gptapp:uri';\nexport const GPT_APP_OPTIONS_KEY = 'gptapp:options';\n\n/**\n * Options for @GPTApp decorator\n */\nexport interface GPTAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n\n /** Custom resource URI (auto-generated ui://... if not provided) */\n uri?: string;\n\n /** HTML document title */\n title?: string;\n\n /** Additional CSS styles */\n styles?: string;\n\n // ========== GPT-specific options ==========\n\n /** \n * Allow widget to call tools via window.openai.callTool.\n * Maps to _meta[\"openai/widgetAccessible\"]\n */\n widgetAccessible?: boolean;\n\n /** \n * Tool visibility: 'public' (default) or 'private'.\n * 'private' hides the tool from the model but keeps it callable by the widget.\n * Maps to _meta[\"openai/visibility\"]\n */\n visibility?: 'public' | 'private';\n\n /** \n * Widget prefers border around iframe.\n * Maps to _meta[\"openai/widgetPrefersBorder\"]\n */\n prefersBorder?: boolean;\n\n /** \n * Widget domain for API allowlists.\n * Maps to _meta[\"openai/widgetDomain\"]\n */\n widgetDomain?: string;\n\n /** \n * Widget description for the model.\n * Maps to _meta[\"openai/widgetDescription\"]\n */\n widgetDescription?: string;\n\n /** \n * Content Security Policy configuration.\n * Maps to _meta[\"openai/widgetCSP\"]\n */\n csp?: {\n connect_domains?: string[];\n resource_domains?: string[];\n redirect_domains?: string[];\n frame_domains?: string[];\n };\n\n /** \n * File parameters (fields treated as file uploads).\n * Maps to _meta[\"openai/fileParams\"]\n */\n fileParams?: string[];\n\n /** \n * Invocation messages shown during tool call.\n * Maps to _meta[\"openai/toolInvocation\"]\n */\n invocation?: {\n invoking?: string; // e.g., \"Preparing the board…\"\n invoked?: string; // e.g., \"Board ready.\"\n };\n}\n\n\n/**\n * Decorator that links a tool to a UI component for ChatGPT Apps.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML with 'text/html+skybridge'.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { GPTApp } from '@leanmcp/ui';\n * import { KanbanBoard } from './KanbanBoard';\n * \n * class KanbanService {\n * @Tool({ description: 'Show Kanban Board' })\n * @GPTApp({ \n * component: KanbanBoard,\n * widgetAccessible: true,\n * invocation: { invoking: 'Loading board...' }\n * })\n * async showBoard() { ... }\n * }\n * ```\n */\nexport function GPTApp(options: GPTAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(GPT_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI and OpenAI-specific metadata for the @Tool decorator to pick up\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n\n // Build nested ui metadata (preferred by ext-apps 0.2.2)\n const uiMeta: Record<string, any> = {\n resourceUri: uri,\n };\n\n // Map visibility to ext-apps format\n if (options.visibility) {\n uiMeta.visibility = options.visibility === 'private' ? ['app'] : ['model', 'app'];\n }\n\n const openAiMeta: Record<string, any> = {\n // New nested format (preferred by ext-apps 0.2.2)\n ui: uiMeta,\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n 'openai/outputTemplate': uri,\n };\n\n if (options.widgetAccessible !== undefined) {\n openAiMeta['openai/widgetAccessible'] = options.widgetAccessible;\n }\n\n if (options.visibility) {\n openAiMeta['openai/visibility'] = options.visibility;\n }\n\n if (options.prefersBorder !== undefined) {\n openAiMeta['openai/widgetPrefersBorder'] = options.prefersBorder;\n }\n\n if (options.widgetDomain) {\n openAiMeta['openai/widgetDomain'] = options.widgetDomain;\n }\n\n if (options.widgetDescription) {\n openAiMeta['openai/widgetDescription'] = options.widgetDescription;\n }\n\n if (options.csp) {\n openAiMeta['openai/widgetCSP'] = options.csp;\n }\n\n if (options.fileParams) {\n openAiMeta['openai/fileParams'] = options.fileParams;\n }\n\n if (options.invocation) {\n if (options.invocation.invoking) openAiMeta['openai/toolInvocation/invoking'] = options.invocation.invoking;\n if (options.invocation.invoked) openAiMeta['openai/toolInvocation/invoked'] = options.invocation.invoked;\n }\n\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n ...openAiMeta,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get GPTApp metadata from a method\n */\nexport function getGPTAppMetadata(target: Function): GPTAppOptions | undefined {\n const component = Reflect.getMetadata(GPT_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(GPT_APP_URI_KEY, target),\n ...Reflect.getMetadata(GPT_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getGPTAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(GPT_APP_URI_KEY, target);\n}\n"]}
@@ -0,0 +1,124 @@
1
+ import 'reflect-metadata';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ var UI_APP_COMPONENT_KEY = "ui:app:component";
6
+ var UI_APP_URI_KEY = "ui:app:uri";
7
+ var UI_APP_OPTIONS_KEY = "ui:app:options";
8
+ function UIApp(options) {
9
+ return (target, propertyKey, descriptor) => {
10
+ const methodName = String(propertyKey);
11
+ const className = target.constructor.name.toLowerCase().replace("service", "");
12
+ const uri = options.uri ?? `ui://${className}/${methodName}`;
13
+ Reflect.defineMetadata(UI_APP_COMPONENT_KEY, options.component, descriptor.value);
14
+ Reflect.defineMetadata(UI_APP_URI_KEY, uri, descriptor.value);
15
+ Reflect.defineMetadata(UI_APP_OPTIONS_KEY, options, descriptor.value);
16
+ const existingMeta = Reflect.getMetadata("tool:meta", descriptor.value) || {};
17
+ Reflect.defineMetadata("tool:meta", {
18
+ ...existingMeta,
19
+ // New nested format (preferred by ext-apps 0.2.2)
20
+ ui: {
21
+ resourceUri: uri
22
+ },
23
+ // Legacy flat format (for backwards compatibility)
24
+ "ui/resourceUri": uri
25
+ }, descriptor.value);
26
+ return descriptor;
27
+ };
28
+ }
29
+ __name(UIApp, "UIApp");
30
+ function getUIAppMetadata(target) {
31
+ const component = Reflect.getMetadata(UI_APP_COMPONENT_KEY, target);
32
+ if (!component) return void 0;
33
+ return {
34
+ component,
35
+ uri: Reflect.getMetadata(UI_APP_URI_KEY, target),
36
+ ...Reflect.getMetadata(UI_APP_OPTIONS_KEY, target)
37
+ };
38
+ }
39
+ __name(getUIAppMetadata, "getUIAppMetadata");
40
+ function getUIAppUri(target) {
41
+ return Reflect.getMetadata(UI_APP_URI_KEY, target);
42
+ }
43
+ __name(getUIAppUri, "getUIAppUri");
44
+ var GPT_APP_COMPONENT_KEY = "gptapp:component";
45
+ var GPT_APP_URI_KEY = "gptapp:uri";
46
+ var GPT_APP_OPTIONS_KEY = "gptapp:options";
47
+ function GPTApp(options) {
48
+ return (target, propertyKey, descriptor) => {
49
+ const methodName = String(propertyKey);
50
+ const className = target.constructor.name.toLowerCase().replace("service", "");
51
+ const uri = options.uri ?? `ui://${className}/${methodName}`;
52
+ Reflect.defineMetadata(GPT_APP_COMPONENT_KEY, options.component, descriptor.value);
53
+ Reflect.defineMetadata(GPT_APP_URI_KEY, uri, descriptor.value);
54
+ Reflect.defineMetadata(GPT_APP_OPTIONS_KEY, options, descriptor.value);
55
+ const existingMeta = Reflect.getMetadata("tool:meta", descriptor.value) || {};
56
+ const uiMeta = {
57
+ resourceUri: uri
58
+ };
59
+ if (options.visibility) {
60
+ uiMeta.visibility = options.visibility === "private" ? [
61
+ "app"
62
+ ] : [
63
+ "model",
64
+ "app"
65
+ ];
66
+ }
67
+ const openAiMeta = {
68
+ // New nested format (preferred by ext-apps 0.2.2)
69
+ ui: uiMeta,
70
+ // Legacy flat format (for backwards compatibility)
71
+ "ui/resourceUri": uri,
72
+ "openai/outputTemplate": uri
73
+ };
74
+ if (options.widgetAccessible !== void 0) {
75
+ openAiMeta["openai/widgetAccessible"] = options.widgetAccessible;
76
+ }
77
+ if (options.visibility) {
78
+ openAiMeta["openai/visibility"] = options.visibility;
79
+ }
80
+ if (options.prefersBorder !== void 0) {
81
+ openAiMeta["openai/widgetPrefersBorder"] = options.prefersBorder;
82
+ }
83
+ if (options.widgetDomain) {
84
+ openAiMeta["openai/widgetDomain"] = options.widgetDomain;
85
+ }
86
+ if (options.widgetDescription) {
87
+ openAiMeta["openai/widgetDescription"] = options.widgetDescription;
88
+ }
89
+ if (options.csp) {
90
+ openAiMeta["openai/widgetCSP"] = options.csp;
91
+ }
92
+ if (options.fileParams) {
93
+ openAiMeta["openai/fileParams"] = options.fileParams;
94
+ }
95
+ if (options.invocation) {
96
+ if (options.invocation.invoking) openAiMeta["openai/toolInvocation/invoking"] = options.invocation.invoking;
97
+ if (options.invocation.invoked) openAiMeta["openai/toolInvocation/invoked"] = options.invocation.invoked;
98
+ }
99
+ Reflect.defineMetadata("tool:meta", {
100
+ ...existingMeta,
101
+ ...openAiMeta
102
+ }, descriptor.value);
103
+ return descriptor;
104
+ };
105
+ }
106
+ __name(GPTApp, "GPTApp");
107
+ function getGPTAppMetadata(target) {
108
+ const component = Reflect.getMetadata(GPT_APP_COMPONENT_KEY, target);
109
+ if (!component) return void 0;
110
+ return {
111
+ component,
112
+ uri: Reflect.getMetadata(GPT_APP_URI_KEY, target),
113
+ ...Reflect.getMetadata(GPT_APP_OPTIONS_KEY, target)
114
+ };
115
+ }
116
+ __name(getGPTAppMetadata, "getGPTAppMetadata");
117
+ function getGPTAppUri(target) {
118
+ return Reflect.getMetadata(GPT_APP_URI_KEY, target);
119
+ }
120
+ __name(getGPTAppUri, "getGPTAppUri");
121
+
122
+ export { GPTApp, GPT_APP_COMPONENT_KEY, GPT_APP_OPTIONS_KEY, GPT_APP_URI_KEY, UIApp, UI_APP_COMPONENT_KEY, UI_APP_OPTIONS_KEY, UI_APP_URI_KEY, __name, getGPTAppMetadata, getGPTAppUri, getUIAppMetadata, getUIAppUri };
123
+ //# sourceMappingURL=chunk-5RGEQPF3.mjs.map
124
+ //# sourceMappingURL=chunk-5RGEQPF3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/decorator/UIApp.ts","../src/decorator/GPTApp.ts"],"names":["UI_APP_COMPONENT_KEY","UI_APP_URI_KEY","UI_APP_OPTIONS_KEY","UIApp","options","target","propertyKey","descriptor","methodName","String","className","name","toLowerCase","replace","uri","Reflect","defineMetadata","component","value","existingMeta","getMetadata","ui","resourceUri","getUIAppMetadata","undefined","getUIAppUri","GPT_APP_COMPONENT_KEY","GPT_APP_URI_KEY","GPT_APP_OPTIONS_KEY","GPTApp","uiMeta","visibility","openAiMeta","widgetAccessible","prefersBorder","widgetDomain","widgetDescription","csp","fileParams","invocation","invoking","invoked","getGPTAppMetadata","getGPTAppUri"],"mappings":"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","file":"chunk-5RGEQPF3.mjs","sourcesContent":["/**\n * @UIApp Decorator\n * \n * Links an MCP tool to a React UI component.\n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Auto-registers a resource that renders the component to HTML\n * \n * This decorator is designed to work with @leanmcp/core decorators.\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const UI_APP_COMPONENT_KEY = 'ui:app:component';\nexport const UI_APP_URI_KEY = 'ui:app:uri';\nexport const UI_APP_OPTIONS_KEY = 'ui:app:options';\n\n/**\n * Options for @UIApp decorator\n */\nexport interface UIAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n /** Custom resource URI (auto-generated if not provided) */\n uri?: string;\n /** HTML document title */\n title?: string;\n /** Additional CSS styles */\n styles?: string;\n}\n\n\n/**\n * Decorator that links a tool to a UI component.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { UIApp } from '@leanmcp/ui';\n * import { WeatherCard } from './WeatherCard';\n * \n * class WeatherService {\n * @Tool({ description: 'Get weather for a city' })\n * @UIApp({ component: WeatherCard })\n * async getWeather(args: { city: string }) {\n * return { city: args.city, temp: 22 };\n * }\n * }\n * ```\n */\nexport function UIApp(options: UIAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(UI_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(UI_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(UI_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI for the @Tool decorator to pick up\n // The @Tool decorator in @leanmcp/core should check for this\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n // New nested format (preferred by ext-apps 0.2.2)\n ui: {\n resourceUri: uri,\n },\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get UIApp metadata from a method\n */\nexport function getUIAppMetadata(target: Function): UIAppOptions | undefined {\n const component = Reflect.getMetadata(UI_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(UI_APP_URI_KEY, target),\n ...Reflect.getMetadata(UI_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getUIAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(UI_APP_URI_KEY, target);\n}\n","/**\n * @GPTApp Decorator\n * \n * Links an MCP tool to a React UI component compliant with ChatGPT Apps SDK.\n * \n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Adds OpenAI-specific metadata (widgetAccessible, outputTemplate, etc.)\n * 3. Auto-registers a resource that renders the component to HTML with 'text/html+skybridge' mimeType\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const GPT_APP_COMPONENT_KEY = 'gptapp:component';\nexport const GPT_APP_URI_KEY = 'gptapp:uri';\nexport const GPT_APP_OPTIONS_KEY = 'gptapp:options';\n\n/**\n * Options for @GPTApp decorator\n */\nexport interface GPTAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n\n /** Custom resource URI (auto-generated ui://... if not provided) */\n uri?: string;\n\n /** HTML document title */\n title?: string;\n\n /** Additional CSS styles */\n styles?: string;\n\n // ========== GPT-specific options ==========\n\n /** \n * Allow widget to call tools via window.openai.callTool.\n * Maps to _meta[\"openai/widgetAccessible\"]\n */\n widgetAccessible?: boolean;\n\n /** \n * Tool visibility: 'public' (default) or 'private'.\n * 'private' hides the tool from the model but keeps it callable by the widget.\n * Maps to _meta[\"openai/visibility\"]\n */\n visibility?: 'public' | 'private';\n\n /** \n * Widget prefers border around iframe.\n * Maps to _meta[\"openai/widgetPrefersBorder\"]\n */\n prefersBorder?: boolean;\n\n /** \n * Widget domain for API allowlists.\n * Maps to _meta[\"openai/widgetDomain\"]\n */\n widgetDomain?: string;\n\n /** \n * Widget description for the model.\n * Maps to _meta[\"openai/widgetDescription\"]\n */\n widgetDescription?: string;\n\n /** \n * Content Security Policy configuration.\n * Maps to _meta[\"openai/widgetCSP\"]\n */\n csp?: {\n connect_domains?: string[];\n resource_domains?: string[];\n redirect_domains?: string[];\n frame_domains?: string[];\n };\n\n /** \n * File parameters (fields treated as file uploads).\n * Maps to _meta[\"openai/fileParams\"]\n */\n fileParams?: string[];\n\n /** \n * Invocation messages shown during tool call.\n * Maps to _meta[\"openai/toolInvocation\"]\n */\n invocation?: {\n invoking?: string; // e.g., \"Preparing the board…\"\n invoked?: string; // e.g., \"Board ready.\"\n };\n}\n\n\n/**\n * Decorator that links a tool to a UI component for ChatGPT Apps.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML with 'text/html+skybridge'.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { GPTApp } from '@leanmcp/ui';\n * import { KanbanBoard } from './KanbanBoard';\n * \n * class KanbanService {\n * @Tool({ description: 'Show Kanban Board' })\n * @GPTApp({ \n * component: KanbanBoard,\n * widgetAccessible: true,\n * invocation: { invoking: 'Loading board...' }\n * })\n * async showBoard() { ... }\n * }\n * ```\n */\nexport function GPTApp(options: GPTAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(GPT_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI and OpenAI-specific metadata for the @Tool decorator to pick up\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n\n // Build nested ui metadata (preferred by ext-apps 0.2.2)\n const uiMeta: Record<string, any> = {\n resourceUri: uri,\n };\n\n // Map visibility to ext-apps format\n if (options.visibility) {\n uiMeta.visibility = options.visibility === 'private' ? ['app'] : ['model', 'app'];\n }\n\n const openAiMeta: Record<string, any> = {\n // New nested format (preferred by ext-apps 0.2.2)\n ui: uiMeta,\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n 'openai/outputTemplate': uri,\n };\n\n if (options.widgetAccessible !== undefined) {\n openAiMeta['openai/widgetAccessible'] = options.widgetAccessible;\n }\n\n if (options.visibility) {\n openAiMeta['openai/visibility'] = options.visibility;\n }\n\n if (options.prefersBorder !== undefined) {\n openAiMeta['openai/widgetPrefersBorder'] = options.prefersBorder;\n }\n\n if (options.widgetDomain) {\n openAiMeta['openai/widgetDomain'] = options.widgetDomain;\n }\n\n if (options.widgetDescription) {\n openAiMeta['openai/widgetDescription'] = options.widgetDescription;\n }\n\n if (options.csp) {\n openAiMeta['openai/widgetCSP'] = options.csp;\n }\n\n if (options.fileParams) {\n openAiMeta['openai/fileParams'] = options.fileParams;\n }\n\n if (options.invocation) {\n if (options.invocation.invoking) openAiMeta['openai/toolInvocation/invoking'] = options.invocation.invoking;\n if (options.invocation.invoked) openAiMeta['openai/toolInvocation/invoked'] = options.invocation.invoked;\n }\n\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n ...openAiMeta,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get GPTApp metadata from a method\n */\nexport function getGPTAppMetadata(target: Function): GPTAppOptions | undefined {\n const component = Reflect.getMetadata(GPT_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(GPT_APP_URI_KEY, target),\n ...Reflect.getMetadata(GPT_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getGPTAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(GPT_APP_URI_KEY, target);\n}\n"]}
@@ -0,0 +1,138 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /**
6
+ * MCP-Native UI SDK - Global Styles
7
+ *
8
+ * CSS variables that integrate with MCP Host theming.
9
+ * MCP hosts provide variables like --color-background-primary, --color-text-primary, etc.
10
+ * We map these to shadcn-compatible variables for seamless theming.
11
+ */
12
+
13
+ @layer base {
14
+ :root {
15
+ /* === Background colors === */
16
+ /* Map MCP host variables to shadcn variables, with fallbacks */
17
+ --background: var(--color-background-primary, 0 0% 100%);
18
+ --foreground: var(--color-text-primary, 222.2 84% 4.9%);
19
+
20
+ --card: var(--color-background-secondary, 0 0% 100%);
21
+ --card-foreground: var(--color-text-primary, 222.2 84% 4.9%);
22
+
23
+ --popover: 0 0% 100%;
24
+ --popover-foreground: 222.2 84% 4.9%;
25
+
26
+ /* === Brand colors === */
27
+ --primary: 222.2 47.4% 11.2%;
28
+ --primary-foreground: 210 40% 98%;
29
+
30
+ --secondary: var(--color-background-tertiary, 210 40% 96.1%);
31
+ --secondary-foreground: 222.2 47.4% 11.2%;
32
+
33
+ --muted: 210 40% 96.1%;
34
+ --muted-foreground: var(--color-text-secondary, 215.4 16.3% 46.9%);
35
+
36
+ --accent: 210 40% 96.1%;
37
+ --accent-foreground: 222.2 47.4% 11.2%;
38
+
39
+ /* === Semantic colors === */
40
+ --destructive: var(--color-background-danger, 0 84.2% 60.2%);
41
+ --destructive-foreground: 210 40% 98%;
42
+
43
+ --success: var(--color-background-success, 142 76% 36%);
44
+ --success-foreground: 0 0% 100%;
45
+
46
+ --warning: var(--color-background-warning, 38 92% 50%);
47
+ --warning-foreground: 0 0% 0%;
48
+
49
+ --info: var(--color-background-info, 199 89% 48%);
50
+ --info-foreground: 0 0% 100%;
51
+
52
+ /* === Border and focus === */
53
+ --border: var(--color-border-primary, 214.3 31.8% 91.4%);
54
+ --input: 214.3 31.8% 91.4%;
55
+ --ring: var(--color-ring-primary, 222.2 84% 4.9%);
56
+
57
+ /* === Geometry === */
58
+ --radius: var(--border-radius-md, 0.5rem);
59
+
60
+ /* === Typography === */
61
+ --font-sans: var(--font-sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif);
62
+ --font-mono: var(--font-mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace);
63
+ }
64
+
65
+ /* Dark mode - activated by class or data attribute */
66
+ .dark,
67
+ [data-theme="dark"] {
68
+ --background: var(--color-background-primary, 222.2 84% 4.9%);
69
+ --foreground: var(--color-text-primary, 210 40% 98%);
70
+
71
+ --card: var(--color-background-secondary, 222.2 84% 4.9%);
72
+ --card-foreground: var(--color-text-primary, 210 40% 98%);
73
+
74
+ --popover: 222.2 84% 4.9%;
75
+ --popover-foreground: 210 40% 98%;
76
+
77
+ --primary: 210 40% 98%;
78
+ --primary-foreground: 222.2 47.4% 11.2%;
79
+
80
+ --secondary: var(--color-background-tertiary, 217.2 32.6% 17.5%);
81
+ --secondary-foreground: 210 40% 98%;
82
+
83
+ --muted: 217.2 32.6% 17.5%;
84
+ --muted-foreground: var(--color-text-secondary, 215 20.2% 65.1%);
85
+
86
+ --accent: 217.2 32.6% 17.5%;
87
+ --accent-foreground: 210 40% 98%;
88
+
89
+ --destructive: var(--color-background-danger, 0 62.8% 30.6%);
90
+ --destructive-foreground: 210 40% 98%;
91
+
92
+ --success: var(--color-background-success, 142 69% 58%);
93
+ --success-foreground: 0 0% 0%;
94
+
95
+ --warning: var(--color-background-warning, 38 92% 50%);
96
+ --warning-foreground: 0 0% 0%;
97
+
98
+ --info: var(--color-background-info, 199 89% 48%);
99
+ --info-foreground: 0 0% 0%;
100
+
101
+ --border: var(--color-border-primary, 217.2 32.6% 17.5%);
102
+ --input: 217.2 32.6% 17.5%;
103
+ --ring: var(--color-ring-primary, 212.7 26.8% 83.9%);
104
+ }
105
+ }
106
+
107
+ @layer base {
108
+ * {
109
+ @apply border-border;
110
+ }
111
+
112
+ body {
113
+ @apply bg-background text-foreground;
114
+ font-feature-settings: "rlig" 1, "calt" 1;
115
+ }
116
+
117
+ /* Focus visible styles for accessibility */
118
+ :focus-visible {
119
+ @apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
120
+ }
121
+ }
122
+
123
+ /* === MCP App Root Styles === */
124
+ .lui-root {
125
+ @apply bg-background text-foreground min-h-full;
126
+ font-family: var(--font-sans);
127
+ }
128
+
129
+ /* === Loading spinner animation === */
130
+ @keyframes lui-spinner {
131
+ to {
132
+ transform: rotate(360deg);
133
+ }
134
+ }
135
+
136
+ .lui-spinner {
137
+ animation: lui-spinner 1s linear infinite;
138
+ }