@waniwani/sdk 0.0.4 → 0.0.6
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.
- package/dist/chunk-OVQDB7QX.js +3 -0
- package/dist/chunk-OVQDB7QX.js.map +1 -0
- package/dist/chunk-SCDEAZN4.js +3 -0
- package/dist/chunk-SCDEAZN4.js.map +1 -0
- package/dist/index.d.ts +45 -65
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +367 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/react.d.ts +435 -0
- package/dist/mcp/react.js +68 -0
- package/dist/mcp/react.js.map +1 -0
- package/dist/mcp-apps-client-BU7EN3Z2.js +3 -0
- package/dist/mcp-apps-client-BU7EN3Z2.js.map +1 -0
- package/dist/openai-client-UL6T463M.js +3 -0
- package/dist/openai-client-UL6T463M.js.map +1 -0
- package/dist/platform-4QZXTYT5.js +3 -0
- package/dist/platform-4QZXTYT5.js.map +1 -0
- package/package.json +40 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/hooks/@types.ts"],"sourcesContent":["/**\n * Source: https://github.com/openai/openai-apps-sdk-examples/tree/main/src\n */\n\nexport type OpenAIGlobals<\n\tToolInput = UnknownObject,\n\tToolOutput = UnknownObject,\n\tToolResponseMetadata = UnknownObject,\n\tWidgetState = UnknownObject,\n> = {\n\t// visuals\n\ttheme: Theme;\n\n\tuserAgent: UserAgent;\n\tlocale: string;\n\n\t// layout\n\tmaxHeight: number;\n\tdisplayMode: DisplayMode;\n\tsafeArea: SafeArea;\n\n\t// state\n\ttoolInput: ToolInput;\n\ttoolOutput: ToolOutput | null;\n\ttoolResponseMetadata: ToolResponseMetadata | null;\n\twidgetState: WidgetState | null;\n\tsetWidgetState: (state: WidgetState) => Promise<void>;\n};\n\ntype API = {\n\tcallTool: CallTool;\n\tsendFollowUpMessage: (args: { prompt: string }) => Promise<void>;\n\topenExternal(payload: { href: string }): void;\n\n\t// Layout controls\n\trequestDisplayMode: RequestDisplayMode;\n};\n\nexport type UnknownObject = Record<string, unknown>;\n\nexport type Theme = \"light\" | \"dark\";\n\nexport type SafeAreaInsets = {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n};\n\nexport type SafeArea = {\n\tinsets: SafeAreaInsets;\n};\n\nexport type DeviceType = \"mobile\" | \"tablet\" | \"desktop\" | \"unknown\";\n\nexport type UserAgent = {\n\tdevice: { type: DeviceType };\n\tcapabilities: {\n\t\thover: boolean;\n\t\ttouch: boolean;\n\t};\n};\n\n/** Display mode */\nexport type DisplayMode = \"pip\" | \"inline\" | \"fullscreen\";\nexport type RequestDisplayMode = (args: { mode: DisplayMode }) => Promise<{\n\t/**\n\t * The granted display mode. The host may reject the request.\n\t * For mobile, PiP is always coerced to fullscreen.\n\t */\n\tmode: DisplayMode;\n}>;\n\nexport type CallToolResponse = {\n\tresult: string;\n};\n\n/** Calling APIs */\nexport type CallTool = (\n\tname: string,\n\targs: Record<string, unknown>,\n) => Promise<CallToolResponse>;\n\n/** Extra events */\nexport const SET_GLOBALS_EVENT_TYPE = \"openai:set_globals\";\nexport class SetGlobalsEvent extends CustomEvent<{\n\tglobals: Partial<OpenAIGlobals>;\n}> {\n\tconstructor(detail: { globals: Partial<OpenAIGlobals> }) {\n\t\tsuper(SET_GLOBALS_EVENT_TYPE, { detail });\n\t}\n}\n\n/**\n * Global oai object injected by the web sandbox for communicating with chatgpt host page.\n */\ndeclare global {\n\tinterface Window {\n\t\topenai: API & OpenAIGlobals;\n\t\tinnerBaseUrl: string;\n\t}\n\n\tinterface WindowEventMap {\n\t\t[SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;\n\t}\n}\n"],"mappings":";AAoFO,IAAMA,EAAyB,qBACzBC,EAAN,cAA8B,WAElC,CACF,YAAYC,EAA6C,CACxD,MAAMF,EAAwB,CAAE,OAAAE,CAAO,CAAC,CACzC,CACD","names":["SET_GLOBALS_EVENT_TYPE","SetGlobalsEvent","detail"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/widgets/@utils/platform.ts"],"sourcesContent":["/**\n * Widget platform types\n */\nexport type WidgetPlatform = \"openai\" | \"mcp-apps\";\n\n/**\n * Detects which platform the widget is running on.\n *\n * OpenAI injects a global `window.openai` object.\n * MCP Apps runs in a sandboxed iframe and uses postMessage.\n */\nexport function detectPlatform(): WidgetPlatform {\n\tif (typeof window !== \"undefined\" && \"openai\" in window) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mcp-apps\";\n}\n\n/**\n * Check if running on OpenAI platform\n */\nexport function isOpenAI(): boolean {\n\treturn detectPlatform() === \"openai\";\n}\n\n/**\n * Check if running on MCP Apps platform\n */\nexport function isMCPApps(): boolean {\n\treturn detectPlatform() === \"mcp-apps\";\n}\n"],"mappings":";AAWO,SAASA,GAAiC,CAChD,OAAI,OAAO,OAAW,KAAe,WAAY,OACzC,SAED,UACR,CAKO,SAASC,GAAoB,CACnC,OAAOD,EAAe,IAAM,QAC7B,CAKO,SAASE,GAAqB,CACpC,OAAOF,EAAe,IAAM,UAC7B","names":["detectPlatform","isOpenAI","isMCPApps"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,82 +3,71 @@ declare class WaniWaniError extends Error {
|
|
|
3
3
|
constructor(message: string, status: number);
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"openai/session"?: string;
|
|
11
|
-
"openai/userAgent"?: string;
|
|
12
|
-
"openai/locale"?: string;
|
|
13
|
-
"openai/userLocation"?: {
|
|
14
|
-
city?: string;
|
|
15
|
-
region?: string;
|
|
16
|
-
country?: string;
|
|
17
|
-
timezone?: string;
|
|
18
|
-
latitude?: string;
|
|
19
|
-
longitude?: string;
|
|
20
|
-
};
|
|
21
|
-
timezone_offset_minutes?: number;
|
|
6
|
+
type EventType = "tool.called" | "quote.requested" | "quote.succeeded" | "quote.failed" | "link.clicked" | "purchase.completed";
|
|
7
|
+
interface ToolCalledProperties {
|
|
8
|
+
name?: string;
|
|
9
|
+
type?: "pricing" | "product_info" | "availability" | "support" | "other";
|
|
22
10
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
region?: string;
|
|
27
|
-
country?: string;
|
|
28
|
-
timezone?: string;
|
|
11
|
+
interface QuoteSucceededProperties {
|
|
12
|
+
amount?: number;
|
|
13
|
+
currency?: string;
|
|
29
14
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
locale?: string;
|
|
37
|
-
location?: LocationInfo;
|
|
15
|
+
interface LinkClickedProperties {
|
|
16
|
+
url?: string;
|
|
17
|
+
}
|
|
18
|
+
interface PurchaseCompletedProperties {
|
|
19
|
+
amount?: number;
|
|
20
|
+
currency?: string;
|
|
38
21
|
}
|
|
39
|
-
type EventType = "session.started" | "tool.called" | "quote.requested" | "quote.succeeded" | "quote.failed" | "link.clicked" | "purchase.completed";
|
|
40
|
-
type ToolType = "pricing" | "product_info" | "availability" | "support" | "other";
|
|
41
22
|
interface BaseEvent {
|
|
42
23
|
/**
|
|
43
|
-
*
|
|
44
|
-
* (sessionId, userId, location, etc.). Can also include custom fields.
|
|
24
|
+
* Event type.
|
|
45
25
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* wani.track({
|
|
29
|
+
* event: 'tool.called',
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
event: EventType;
|
|
34
|
+
/**
|
|
35
|
+
* Event properties.
|
|
49
36
|
*
|
|
50
37
|
* @example
|
|
51
38
|
* ```typescript
|
|
52
39
|
* wani.track({
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* meta: extra._meta,
|
|
40
|
+
* event: 'tool.called',
|
|
41
|
+
* properties: { name: 'search' },
|
|
56
42
|
* });
|
|
57
43
|
* ```
|
|
58
44
|
*/
|
|
45
|
+
properties?: Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* MCP request metadata passed through to the API.
|
|
48
|
+
*
|
|
49
|
+
* Location varies by MCP library:
|
|
50
|
+
* - `@vercel/mcp-handler`: `extra._meta`
|
|
51
|
+
* - `@modelcontextprotocol/sdk`: `request.params._meta`
|
|
52
|
+
*/
|
|
59
53
|
meta?: Record<string, unknown>;
|
|
60
54
|
}
|
|
61
55
|
type TrackEvent = ({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
eventType: "tool.called";
|
|
65
|
-
toolName?: string;
|
|
66
|
-
toolType?: ToolType;
|
|
56
|
+
event: "tool.called";
|
|
57
|
+
properties: ToolCalledProperties;
|
|
67
58
|
} & BaseEvent) | ({
|
|
68
|
-
|
|
59
|
+
event: "quote.requested";
|
|
69
60
|
} & BaseEvent) | ({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
quoteCurrency?: string;
|
|
61
|
+
event: "quote.succeeded";
|
|
62
|
+
properties: QuoteSucceededProperties;
|
|
73
63
|
} & BaseEvent) | ({
|
|
74
|
-
|
|
64
|
+
event: "quote.failed";
|
|
75
65
|
} & BaseEvent) | ({
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
event: "link.clicked";
|
|
67
|
+
properties: LinkClickedProperties;
|
|
78
68
|
} & BaseEvent) | ({
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
purchaseCurrency?: string;
|
|
69
|
+
event: "purchase.completed";
|
|
70
|
+
properties: PurchaseCompletedProperties;
|
|
82
71
|
} & BaseEvent);
|
|
83
72
|
/**
|
|
84
73
|
* Tracking module methods for WaniWaniClient
|
|
@@ -120,15 +109,6 @@ interface WaniWaniConfig {
|
|
|
120
109
|
interface WaniWaniClient extends TrackingClient {
|
|
121
110
|
}
|
|
122
111
|
|
|
123
|
-
/**
|
|
124
|
-
* Detect which MCP provider sent the request based on metadata keys
|
|
125
|
-
*/
|
|
126
|
-
declare function detectProvider(meta: Record<string, unknown>): MCPProvider;
|
|
127
|
-
/**
|
|
128
|
-
* Extract normalized metadata from any MCP provider's metadata
|
|
129
|
-
*/
|
|
130
|
-
declare function extractMetadata(meta: Record<string, unknown>): NormalizedMeta;
|
|
131
|
-
|
|
132
112
|
/**
|
|
133
113
|
* Create a WaniWani SDK client
|
|
134
114
|
*
|
|
@@ -150,4 +130,4 @@ declare function extractMetadata(meta: Record<string, unknown>): NormalizedMeta;
|
|
|
150
130
|
*/
|
|
151
131
|
declare function waniwani(config?: WaniWaniConfig): WaniWaniClient;
|
|
152
132
|
|
|
153
|
-
export { type EventType, type
|
|
133
|
+
export { type EventType, type LinkClickedProperties, type PurchaseCompletedProperties, type QuoteSucceededProperties, type ToolCalledProperties, type TrackEvent, type WaniWaniClient, type WaniWaniConfig, WaniWaniError, waniwani };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var n=class extends Error{constructor(e,o){super(e);this.status=o;this.name="WaniWaniError"}};function l(r){let{baseUrl:i,apiKey:e}=r;function o(){if(!e)throw new Error("WANIWANI_API_KEY is not set")}return{async track(p){try{o();let t=await fetch(`${i}/api/mcp/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(p)}),c=await t.json();if(!t.ok)throw new n(c.message??"Request failed",t.status);return{eventId:c.data.eventId}}catch(t){throw console.error("[WaniWani] Track error:",t),t}}}}function f(r){let i=r?.baseUrl??"https://app.waniwani.ai",e=r?.apiKey??process.env.WANIWANI_API_KEY;return{...l({baseUrl:i,apiKey:e})}}export{n as WaniWaniError,f as waniwani};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/tracking/
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/tracking/index.ts","../src/waniwani.ts"],"sourcesContent":["// WaniWani SDK - Errors\n\nexport class WaniWaniError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"WaniWaniError\";\n\t}\n}\n","// Tracking Module\n\nimport { WaniWaniError } from \"../error.js\";\nimport type { InternalConfig } from \"../types.js\";\nimport type { TrackEvent, TrackingClient } from \"./@types.js\";\n\n// Re-export types\nexport type {\n\tEventType,\n\tLinkClickedProperties,\n\tPurchaseCompletedProperties,\n\tQuoteSucceededProperties,\n\tToolCalledProperties,\n\tTrackEvent,\n\tTrackingClient,\n} from \"./@types.js\";\n\nexport function createTrackingClient(config: InternalConfig): TrackingClient {\n\tconst { baseUrl, apiKey } = config;\n\n\tfunction checkIfApiKeyIsSet() {\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"WANIWANI_API_KEY is not set\");\n\t\t}\n\t}\n\n\treturn {\n\t\tasync track(event: TrackEvent): Promise<{ eventId: string }> {\n\t\t\ttry {\n\t\t\t\tcheckIfApiKeyIsSet();\n\n\t\t\t\tconst response = await fetch(`${baseUrl}/api/mcp/events`, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(event),\n\t\t\t\t});\n\n\t\t\t\tconst data = await response.json();\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new WaniWaniError(\n\t\t\t\t\t\tdata.message ?? \"Request failed\",\n\t\t\t\t\t\tresponse.status,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn { eventId: data.data.eventId };\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[WaniWani] Track error:\", error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t};\n}\n","// WaniWani SDK - Main Entry\n\nimport { createTrackingClient } from \"./tracking/index.js\";\nimport type { WaniWaniClient, WaniWaniConfig } from \"./types.js\";\n\n/**\n * Create a WaniWani SDK client\n *\n * @param config - Configuration options\n * @returns A fully typed WaniWani client\n *\n * @example\n * ```typescript\n * import { waniwani } from \"@waniwani\";\n *\n * const client = waniwani({ apiKey: \"...\" });\n *\n * await client.track({\n * eventType: \"tool.called\",\n * sessionId: \"session-123\",\n * toolName: \"pricing\"\n * });\n * ```\n */\nexport function waniwani(config?: WaniWaniConfig): WaniWaniClient {\n\tconst baseUrl = config?.baseUrl ?? \"https://app.waniwani.ai\";\n\tconst apiKey = config?.apiKey ?? process.env.WANIWANI_API_KEY;\n\n\tconst internalConfig = { baseUrl, apiKey };\n\n\t// Compose client from modules\n\tconst tracking = createTrackingClient(internalConfig);\n\n\treturn {\n\t\t...tracking,\n\t\t// Future modules will be spread here\n\t\t// ...tools,\n\t};\n}\n"],"mappings":"AAEO,IAAMA,EAAN,cAA4B,KAAM,CACxC,YACCC,EACOC,EACN,CACD,MAAMD,CAAO,EAFN,YAAAC,EAGP,KAAK,KAAO,eACb,CACD,ECOO,SAASC,EAAqBC,EAAwC,CAC5E,GAAM,CAAE,QAAAC,EAAS,OAAAC,CAAO,EAAIF,EAE5B,SAASG,GAAqB,CAC7B,GAAI,CAACD,EACJ,MAAM,IAAI,MAAM,6BAA6B,CAE/C,CAEA,MAAO,CACN,MAAM,MAAME,EAAiD,CAC5D,GAAI,CACHD,EAAmB,EAEnB,IAAME,EAAW,MAAM,MAAM,GAAGJ,CAAO,kBAAmB,CACzD,OAAQ,OACR,QAAS,CACR,eAAgB,mBAChB,cAAe,UAAUC,CAAM,EAChC,EACA,KAAM,KAAK,UAAUE,CAAK,CAC3B,CAAC,EAEKE,EAAO,MAAMD,EAAS,KAAK,EAEjC,GAAI,CAACA,EAAS,GACb,MAAM,IAAIE,EACTD,EAAK,SAAW,iBAChBD,EAAS,MACV,EAGD,MAAO,CAAE,QAASC,EAAK,KAAK,OAAQ,CACrC,OAASE,EAAO,CACf,cAAQ,MAAM,0BAA2BA,CAAK,EACxCA,CACP,CACD,CACD,CACD,CChCO,SAASC,EAASC,EAAyC,CACjE,IAAMC,EAAUD,GAAQ,SAAW,0BAC7BE,EAASF,GAAQ,QAAU,QAAQ,IAAI,iBAO7C,MAAO,CACN,GAHgBG,EAHM,CAAE,QAAAF,EAAS,OAAAC,CAAO,CAGW,CAMpD,CACD","names":["WaniWaniError","message","status","createTrackingClient","config","baseUrl","apiKey","checkIfApiKeyIsSet","event","response","data","WaniWaniError","error","waniwani","config","baseUrl","apiKey","createTrackingClient"]}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { McpServer, ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { ZodRawShapeCompat, ShapeOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
|
5
|
+
export { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Context passed to widget handlers
|
|
9
|
+
*/
|
|
10
|
+
type WidgetHandlerContext = {
|
|
11
|
+
/** Raw MCP request extra data (includes _meta for session extraction) */
|
|
12
|
+
extra?: {
|
|
13
|
+
_meta?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type WidgetCSP = {
|
|
17
|
+
/** Domains permitted for fetch/XHR network requests */
|
|
18
|
+
connect_domains?: string[];
|
|
19
|
+
/** Domains for static assets (images, fonts, scripts, styles) */
|
|
20
|
+
resource_domains?: string[];
|
|
21
|
+
/** Origins allowed for iframe embeds (triggers stricter app review) */
|
|
22
|
+
frame_domains?: string[];
|
|
23
|
+
/** Origins that can receive openExternal redirects without safe-link modal */
|
|
24
|
+
redirect_domains?: string[];
|
|
25
|
+
};
|
|
26
|
+
type WidgetConfig<TInput extends ZodRawShapeCompat> = {
|
|
27
|
+
/** Unique identifier for the widget/tool */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Display title */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Action-oriented description for the tool (tells the model WHEN to use it) */
|
|
32
|
+
description: string;
|
|
33
|
+
/** UI component description (describes WHAT the widget displays). Falls back to description if not provided. */
|
|
34
|
+
widgetDescription?: string;
|
|
35
|
+
/** Base URL for fetching widget HTML */
|
|
36
|
+
baseUrl: string;
|
|
37
|
+
/** Path to fetch HTML from (relative to baseUrl) */
|
|
38
|
+
htmlPath: string;
|
|
39
|
+
/** Input schema using zod */
|
|
40
|
+
inputSchema: TInput;
|
|
41
|
+
/** Optional loading message (defaults to "Loading...") */
|
|
42
|
+
invoking?: string;
|
|
43
|
+
/** Optional loaded message (defaults to "Loaded") */
|
|
44
|
+
invoked?: string;
|
|
45
|
+
/** Optional widget domain for security context */
|
|
46
|
+
widgetDomain?: string;
|
|
47
|
+
/** Optional: whether widget prefers border (defaults to true) */
|
|
48
|
+
prefersBorder?: boolean;
|
|
49
|
+
/** Content Security Policy configuration (required for app submission) */
|
|
50
|
+
widgetCSP?: WidgetCSP;
|
|
51
|
+
/** Optional: Annotations describe the tool’s potential impact. ChatGPT uses these hints to classify tools and decide when to ask the user for confirmation (elicitation) before using the tool.
|
|
52
|
+
*
|
|
53
|
+
* Note: openWorldHint and destructiveHint are only considered for writes (i.e. when readOnlyHint=false).
|
|
54
|
+
*/
|
|
55
|
+
annotations?: {
|
|
56
|
+
/** Optional: Set to true for tools that do not change state (search, lookups, previews). This won't require elicitation. */
|
|
57
|
+
readOnlyHint?: boolean;
|
|
58
|
+
/** Optional: Set to true for tools where calling multiple times with the same args has no additional effect. */
|
|
59
|
+
idempotentHint?: boolean;
|
|
60
|
+
/** Optional: Set to false for tools that only affect a bounded target (for example, "update a task by id" in your own product). Leave true for tools that can write to arbitrary URLs/files/resources. */
|
|
61
|
+
openWorldHint?: boolean;
|
|
62
|
+
/** Optional: Set to true for tools that can delete, overwrite, or have irreversible side effects. */
|
|
63
|
+
destructiveHint?: boolean;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
type WidgetHandler<TInput extends ZodRawShapeCompat> = (input: ShapeOutput<TInput>, context: WidgetHandlerContext) => Promise<{
|
|
67
|
+
/** Text content to return */
|
|
68
|
+
text: string;
|
|
69
|
+
/** Structured data to pass to the widget */
|
|
70
|
+
data: Record<string, unknown>;
|
|
71
|
+
}>;
|
|
72
|
+
type WidgetToolCallback<TInput extends ZodRawShapeCompat> = ToolCallback<TInput>;
|
|
73
|
+
type RegisteredWidget = {
|
|
74
|
+
id: string;
|
|
75
|
+
title: string;
|
|
76
|
+
description: string;
|
|
77
|
+
register: (server: McpServer) => Promise<void>;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates a widget with minimal boilerplate.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const weatherWidget = createWidget({
|
|
86
|
+
* id: "show_weather",
|
|
87
|
+
* title: "Show Weather",
|
|
88
|
+
* description: "Displays weather information for a city",
|
|
89
|
+
* baseUrl: "https://my-app.com",
|
|
90
|
+
* htmlPath: "/weather",
|
|
91
|
+
* inputSchema: {
|
|
92
|
+
* city: z.string().describe("The city name"),
|
|
93
|
+
* },
|
|
94
|
+
* }, async ({ city }) => ({
|
|
95
|
+
* text: `Weather for ${city}`,
|
|
96
|
+
* data: { city, temperature: 72 },
|
|
97
|
+
* }));
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function createWidget<TInput extends z.ZodRawShape>(config: WidgetConfig<TInput> & {
|
|
101
|
+
widgetDomain: string;
|
|
102
|
+
}, handler: WidgetHandler<TInput>): RegisteredWidget;
|
|
103
|
+
/**
|
|
104
|
+
* Registers multiple widgets on the server
|
|
105
|
+
*/
|
|
106
|
+
declare function registerWidgets(server: McpServer, widgets: RegisteredWidget[]): Promise<void>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Widget platform types
|
|
110
|
+
*/
|
|
111
|
+
type WidgetPlatform = "openai" | "mcp-apps";
|
|
112
|
+
/**
|
|
113
|
+
* Detects which platform the widget is running on.
|
|
114
|
+
*
|
|
115
|
+
* OpenAI injects a global `window.openai` object.
|
|
116
|
+
* MCP Apps runs in a sandboxed iframe and uses postMessage.
|
|
117
|
+
*/
|
|
118
|
+
declare function detectPlatform(): WidgetPlatform;
|
|
119
|
+
/**
|
|
120
|
+
* Check if running on OpenAI platform
|
|
121
|
+
*/
|
|
122
|
+
declare function isOpenAI(): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Check if running on MCP Apps platform
|
|
125
|
+
*/
|
|
126
|
+
declare function isMCPApps(): boolean;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Source: https://github.com/openai/openai-apps-sdk-examples/tree/main/src
|
|
130
|
+
*/
|
|
131
|
+
type OpenAIGlobals<ToolInput = UnknownObject, ToolOutput = UnknownObject, ToolResponseMetadata = UnknownObject, WidgetState = UnknownObject> = {
|
|
132
|
+
theme: Theme;
|
|
133
|
+
userAgent: UserAgent;
|
|
134
|
+
locale: string;
|
|
135
|
+
maxHeight: number;
|
|
136
|
+
displayMode: DisplayMode;
|
|
137
|
+
safeArea: SafeArea;
|
|
138
|
+
toolInput: ToolInput;
|
|
139
|
+
toolOutput: ToolOutput | null;
|
|
140
|
+
toolResponseMetadata: ToolResponseMetadata | null;
|
|
141
|
+
widgetState: WidgetState | null;
|
|
142
|
+
setWidgetState: (state: WidgetState) => Promise<void>;
|
|
143
|
+
};
|
|
144
|
+
type API = {
|
|
145
|
+
callTool: CallTool;
|
|
146
|
+
sendFollowUpMessage: (args: {
|
|
147
|
+
prompt: string;
|
|
148
|
+
}) => Promise<void>;
|
|
149
|
+
openExternal(payload: {
|
|
150
|
+
href: string;
|
|
151
|
+
}): void;
|
|
152
|
+
requestDisplayMode: RequestDisplayMode;
|
|
153
|
+
};
|
|
154
|
+
type UnknownObject = Record<string, unknown>;
|
|
155
|
+
type Theme = "light" | "dark";
|
|
156
|
+
type SafeAreaInsets = {
|
|
157
|
+
top: number;
|
|
158
|
+
bottom: number;
|
|
159
|
+
left: number;
|
|
160
|
+
right: number;
|
|
161
|
+
};
|
|
162
|
+
type SafeArea = {
|
|
163
|
+
insets: SafeAreaInsets;
|
|
164
|
+
};
|
|
165
|
+
type DeviceType = "mobile" | "tablet" | "desktop" | "unknown";
|
|
166
|
+
type UserAgent = {
|
|
167
|
+
device: {
|
|
168
|
+
type: DeviceType;
|
|
169
|
+
};
|
|
170
|
+
capabilities: {
|
|
171
|
+
hover: boolean;
|
|
172
|
+
touch: boolean;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
/** Display mode */
|
|
176
|
+
type DisplayMode = "pip" | "inline" | "fullscreen";
|
|
177
|
+
type RequestDisplayMode = (args: {
|
|
178
|
+
mode: DisplayMode;
|
|
179
|
+
}) => Promise<{
|
|
180
|
+
/**
|
|
181
|
+
* The granted display mode. The host may reject the request.
|
|
182
|
+
* For mobile, PiP is always coerced to fullscreen.
|
|
183
|
+
*/
|
|
184
|
+
mode: DisplayMode;
|
|
185
|
+
}>;
|
|
186
|
+
type CallToolResponse = {
|
|
187
|
+
result: string;
|
|
188
|
+
};
|
|
189
|
+
/** Calling APIs */
|
|
190
|
+
type CallTool = (name: string, args: Record<string, unknown>) => Promise<CallToolResponse>;
|
|
191
|
+
/** Extra events */
|
|
192
|
+
declare const SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
|
|
193
|
+
declare class SetGlobalsEvent extends CustomEvent<{
|
|
194
|
+
globals: Partial<OpenAIGlobals>;
|
|
195
|
+
}> {
|
|
196
|
+
constructor(detail: {
|
|
197
|
+
globals: Partial<OpenAIGlobals>;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Global oai object injected by the web sandbox for communicating with chatgpt host page.
|
|
202
|
+
*/
|
|
203
|
+
declare global {
|
|
204
|
+
interface Window {
|
|
205
|
+
openai: API & OpenAIGlobals;
|
|
206
|
+
innerBaseUrl: string;
|
|
207
|
+
}
|
|
208
|
+
interface WindowEventMap {
|
|
209
|
+
[SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Result from calling a tool
|
|
215
|
+
*/
|
|
216
|
+
type ToolCallResult = {
|
|
217
|
+
content?: Array<{
|
|
218
|
+
type: string;
|
|
219
|
+
text?: string;
|
|
220
|
+
}>;
|
|
221
|
+
structuredContent?: Record<string, unknown>;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Tool result notification (what the host pushes to the widget)
|
|
225
|
+
*/
|
|
226
|
+
type ToolResult = {
|
|
227
|
+
content?: Array<{
|
|
228
|
+
type: string;
|
|
229
|
+
text?: string;
|
|
230
|
+
}>;
|
|
231
|
+
structuredContent?: Record<string, unknown>;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Host context - all values available from the host.
|
|
235
|
+
*/
|
|
236
|
+
type HostContext = {
|
|
237
|
+
theme: Theme;
|
|
238
|
+
locale: string;
|
|
239
|
+
displayMode: DisplayMode;
|
|
240
|
+
maxHeight: number | null;
|
|
241
|
+
safeArea: SafeArea | null;
|
|
242
|
+
toolOutput: UnknownObject | null;
|
|
243
|
+
toolResponseMetadata: UnknownObject | null;
|
|
244
|
+
widgetState: UnknownObject | null;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Unified widget client interface that works on both OpenAI and MCP Apps.
|
|
248
|
+
*
|
|
249
|
+
* Platform-specific behavior:
|
|
250
|
+
* - Display mode: OpenAI-only. MCP Apps returns "inline" and requestDisplayMode is a no-op.
|
|
251
|
+
* - Follow-up messages: Unified API, different underlying implementations.
|
|
252
|
+
*/
|
|
253
|
+
interface UnifiedWidgetClient {
|
|
254
|
+
/**
|
|
255
|
+
* Connect to the host. Must be called before using other methods.
|
|
256
|
+
* On OpenAI, this is a no-op (already connected via window.openai).
|
|
257
|
+
* On MCP Apps, this establishes the postMessage connection.
|
|
258
|
+
*/
|
|
259
|
+
connect(): Promise<void>;
|
|
260
|
+
/**
|
|
261
|
+
* Get the tool output (structured content returned by the tool handler).
|
|
262
|
+
* This is the main data source for widget rendering.
|
|
263
|
+
*/
|
|
264
|
+
getToolOutput<T = Record<string, unknown>>(): T | null;
|
|
265
|
+
/**
|
|
266
|
+
* Register a callback for when tool results are received.
|
|
267
|
+
* On OpenAI, this subscribes to toolOutput changes.
|
|
268
|
+
* On MCP Apps, this sets app.ontoolresult.
|
|
269
|
+
*/
|
|
270
|
+
onToolResult(callback: (result: ToolResult) => void): () => void;
|
|
271
|
+
/**
|
|
272
|
+
* Call another tool on the server.
|
|
273
|
+
*/
|
|
274
|
+
callTool(name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
|
|
275
|
+
/**
|
|
276
|
+
* Open an external URL.
|
|
277
|
+
* On OpenAI: openai.openExternal({ href })
|
|
278
|
+
* On MCP Apps: app.sendOpenLink(url)
|
|
279
|
+
*/
|
|
280
|
+
openExternal(url: string): void;
|
|
281
|
+
/**
|
|
282
|
+
* Send a follow-up message to the AI.
|
|
283
|
+
* On OpenAI: openai.sendFollowUpMessage({ prompt })
|
|
284
|
+
* On MCP Apps: app.sendMessages([{ role: 'user', content: { type: 'text', text: prompt } }])
|
|
285
|
+
*/
|
|
286
|
+
sendFollowUp(prompt: string): void;
|
|
287
|
+
/**
|
|
288
|
+
* Get the current theme.
|
|
289
|
+
*/
|
|
290
|
+
getTheme(): Theme;
|
|
291
|
+
/**
|
|
292
|
+
* Subscribe to theme changes.
|
|
293
|
+
*/
|
|
294
|
+
onThemeChange(callback: (theme: Theme) => void): () => void;
|
|
295
|
+
/**
|
|
296
|
+
* Get the current locale.
|
|
297
|
+
*/
|
|
298
|
+
getLocale(): string;
|
|
299
|
+
/**
|
|
300
|
+
* Get the current display mode.
|
|
301
|
+
* OpenAI-only: returns "pip" | "inline" | "fullscreen"
|
|
302
|
+
* MCP Apps: always returns "inline"
|
|
303
|
+
*/
|
|
304
|
+
getDisplayMode(): DisplayMode;
|
|
305
|
+
/**
|
|
306
|
+
* Request a display mode change.
|
|
307
|
+
* OpenAI-only: requests the mode from the host.
|
|
308
|
+
* MCP Apps: no-op (returns current mode).
|
|
309
|
+
*/
|
|
310
|
+
requestDisplayMode(mode: DisplayMode): Promise<DisplayMode>;
|
|
311
|
+
/**
|
|
312
|
+
* Subscribe to display mode changes.
|
|
313
|
+
* OpenAI-only: subscribes to displayMode changes.
|
|
314
|
+
* MCP Apps: callback is never called.
|
|
315
|
+
*/
|
|
316
|
+
onDisplayModeChange(callback: (mode: DisplayMode) => void): () => void;
|
|
317
|
+
/**
|
|
318
|
+
* Get the safe area insets.
|
|
319
|
+
* OpenAI-only: returns insets from window.openai.safeArea.
|
|
320
|
+
* MCP Apps: returns null.
|
|
321
|
+
*/
|
|
322
|
+
getSafeArea(): SafeArea | null;
|
|
323
|
+
/**
|
|
324
|
+
* Subscribe to safe area changes.
|
|
325
|
+
* OpenAI-only: subscribes to safeArea changes.
|
|
326
|
+
* MCP Apps: callback is never called.
|
|
327
|
+
*/
|
|
328
|
+
onSafeAreaChange(callback: (safeArea: SafeArea | null) => void): () => void;
|
|
329
|
+
/**
|
|
330
|
+
* Get the max height constraint.
|
|
331
|
+
* OpenAI-only: returns maxHeight from window.openai.maxHeight.
|
|
332
|
+
* MCP Apps: returns null.
|
|
333
|
+
*/
|
|
334
|
+
getMaxHeight(): number | null;
|
|
335
|
+
/**
|
|
336
|
+
* Subscribe to max height changes.
|
|
337
|
+
* OpenAI-only: subscribes to maxHeight changes.
|
|
338
|
+
* MCP Apps: callback is never called.
|
|
339
|
+
*/
|
|
340
|
+
onMaxHeightChange(callback: (maxHeight: number | null) => void): () => void;
|
|
341
|
+
/**
|
|
342
|
+
* Get the tool response metadata.
|
|
343
|
+
* OpenAI-only: returns metadata from window.openai.toolResponseMetadata.
|
|
344
|
+
* MCP Apps: returns null.
|
|
345
|
+
*/
|
|
346
|
+
getToolResponseMetadata(): UnknownObject | null;
|
|
347
|
+
/**
|
|
348
|
+
* Subscribe to tool response metadata changes.
|
|
349
|
+
* OpenAI-only: subscribes to toolResponseMetadata changes.
|
|
350
|
+
* MCP Apps: callback is never called.
|
|
351
|
+
*/
|
|
352
|
+
onToolResponseMetadataChange(callback: (metadata: UnknownObject | null) => void): () => void;
|
|
353
|
+
/**
|
|
354
|
+
* Get the widget state.
|
|
355
|
+
* OpenAI-only: returns state from window.openai.widgetState.
|
|
356
|
+
* MCP Apps: returns null.
|
|
357
|
+
*/
|
|
358
|
+
getWidgetState(): UnknownObject | null;
|
|
359
|
+
/**
|
|
360
|
+
* Subscribe to widget state changes.
|
|
361
|
+
* OpenAI-only: subscribes to widgetState changes.
|
|
362
|
+
* MCP Apps: callback is never called.
|
|
363
|
+
*/
|
|
364
|
+
onWidgetStateChange(callback: (state: UnknownObject | null) => void): () => void;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export { type HostContext, type RegisteredWidget, type ToolCallResult, type ToolResult, type UnifiedWidgetClient, type WidgetCSP, type WidgetConfig, type WidgetHandler, type WidgetHandlerContext, type WidgetPlatform, type WidgetToolCallback, createWidget, detectPlatform, isMCPApps, isOpenAI, registerWidgets };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var P="text/html+skybridge",C="text/html;profile=mcp-app",h=async(e,i)=>{let t=e.endsWith("/")?e.slice(0,-1):e;return await(await fetch(`${t}${i}`)).text()};function I(e){return{"openai/widgetDescription":e.description,"openai/widgetPrefersBorder":e.prefersBorder,"openai/widgetDomain":e.widgetDomain,...e.widgetCSP&&{"openai/widgetCSP":e.widgetCSP}}}function b(e){let i=e.widgetCSP?{connectDomains:e.widgetCSP.connect_domains,resourceDomains:e.widgetCSP.resource_domains,frameDomains:e.widgetCSP.frame_domains,redirectDomains:e.widgetCSP.redirect_domains}:void 0;return{ui:{...i&&{csp:i},...e.prefersBorder!==void 0&&{prefersBorder:e.prefersBorder}}}}function A(e){return{"openai/outputTemplate":e.openaiTemplateUri,"openai/toolInvocation/invoking":e.invoking,"openai/toolInvocation/invoked":e.invoked,"openai/widgetAccessible":!0,"openai/resultCanProduceWidget":!0,ui:{resourceUri:e.mcpTemplateUri}}}function B(e,i){let{id:t,title:r,description:a,widgetDescription:y,baseUrl:W,htmlPath:T,inputSchema:k,invoking:M="Loading...",invoked:R="Loaded",widgetDomain:v,prefersBorder:s=!0,widgetCSP:c,annotations:_}=e,o=y??a,m=`ui://widgets/apps-sdk/${t}.html`,g=`ui://widgets/ext-apps/${t}.html`;return{id:t,title:r,description:a,async register(p){let u=await h(W,T),l=A({openaiTemplateUri:m,mcpTemplateUri:g,invoking:M,invoked:R});p.registerResource(`${t}-openai-widget`,m,{title:r,description:o,mimeType:P,_meta:{"openai/widgetDescription":o,"openai/widgetPrefersBorder":s}},async n=>({contents:[{uri:n.href,mimeType:P,text:u,_meta:I({description:o,prefersBorder:s,widgetDomain:v,widgetCSP:c})}]})),p.registerResource(`${t}-mcp-widget`,g,{title:r,description:o,mimeType:C,_meta:{ui:{prefersBorder:s}}},async n=>({contents:[{uri:n.href,mimeType:C,text:u,_meta:b({description:o,prefersBorder:s,widgetCSP:c})}]})),p.registerTool(t,{title:r,description:a,inputSchema:k,annotations:_,_meta:l},(async(n,D)=>{let w=D._meta??{},f=await i(n,{extra:{_meta:w}});return{content:[{type:"text",text:f.text}],structuredContent:f.data,_meta:{...l,...w}}}))}}}async function E(e,i){await Promise.all(i.map(t=>t.register(e)))}function d(){return typeof window<"u"&&"openai"in window?"openai":"mcp-apps"}function H(){return d()==="openai"}function O(){return d()==="mcp-apps"}export{B as createWidget,d as detectPlatform,O as isMCPApps,H as isOpenAI,E as registerWidgets};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/widgets/@utils/create-widget.ts","../../src/mcp/widgets/@utils/platform.ts"],"sourcesContent":["import type { ToolCallback } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ShapeOutput } from \"@modelcontextprotocol/sdk/server/zod-compat.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n\tServerNotification,\n\tServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { z } from \"zod\";\nimport type {\n\tMcpServer,\n\tRegisteredWidget,\n\tWidgetConfig,\n\tWidgetHandler,\n} from \"./types\";\n\n/**\n * MIME types for widget resources.\n * OpenAI Apps SDK uses \"text/html+skybridge\"\n * MCP Apps uses \"text/html;profile=mcp-app\"\n */\nconst MIME_TYPE_OPENAI = \"text/html+skybridge\";\nconst MIME_TYPE_MCP = \"text/html;profile=mcp-app\";\n\ninterface WidgetCSP {\n\tconnect_domains?: string[];\n\tresource_domains?: string[];\n\tframe_domains?: string[];\n\tredirect_domains?: string[];\n}\n\ninterface OpenAIResourceMeta {\n\t[key: string]: unknown;\n\t\"openai/widgetDescription\"?: string;\n\t\"openai/widgetPrefersBorder\"?: boolean;\n\t\"openai/widgetDomain\"?: string;\n\t\"openai/widgetCSP\"?: WidgetCSP;\n}\n\ninterface McpAppsResourceMeta {\n\t[key: string]: unknown;\n\tui?: {\n\t\tcsp?: {\n\t\t\tconnectDomains?: string[];\n\t\t\tresourceDomains?: string[];\n\t\t\tframeDomains?: string[];\n\t\t\tredirectDomains?: string[];\n\t\t};\n\t\tdomain?: string;\n\t\tprefersBorder?: boolean;\n\t};\n}\n\nconst fetchHtml = async (baseUrl: string, path: string): Promise<string> => {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\tconst result = await fetch(`${normalizedBase}${path}`);\n\treturn await result.text();\n};\n\n/**\n * Build OpenAI-specific resource metadata\n */\nfunction buildOpenAIResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetDomain: string;\n\twidgetCSP?: WidgetCSP;\n}): OpenAIResourceMeta {\n\treturn {\n\t\t\"openai/widgetDescription\": config.description,\n\t\t\"openai/widgetPrefersBorder\": config.prefersBorder,\n\t\t\"openai/widgetDomain\": config.widgetDomain,\n\t\t...(config.widgetCSP && { \"openai/widgetCSP\": config.widgetCSP }),\n\t};\n}\n\n/**\n * Build MCP Apps-specific resource metadata\n * Note: MCP Apps (Claude) doesn't use the domain field in the same way as OpenAI.\n * Claude computes it dynamically at request time in the format: {hash}.claudemcpcontent.com\n */\nfunction buildMcpAppsResourceMeta(config: {\n\tdescription?: string;\n\tprefersBorder?: boolean;\n\twidgetCSP?: WidgetCSP;\n}): McpAppsResourceMeta {\n\tconst csp = config.widgetCSP\n\t\t? {\n\t\t\t\tconnectDomains: config.widgetCSP.connect_domains,\n\t\t\t\tresourceDomains: config.widgetCSP.resource_domains,\n\t\t\t\tframeDomains: config.widgetCSP.frame_domains,\n\t\t\t\tredirectDomains: config.widgetCSP.redirect_domains,\n\t\t\t}\n\t\t: undefined;\n\n\treturn {\n\t\tui: {\n\t\t\t...(csp && { csp }),\n\t\t\t...(config.prefersBorder !== undefined && {\n\t\t\t\tprefersBorder: config.prefersBorder,\n\t\t\t}),\n\t\t},\n\t};\n}\n\n/**\n * Build tool metadata that references both OpenAI and MCP widget URIs\n */\nfunction buildToolMeta(config: {\n\topenaiTemplateUri: string;\n\tmcpTemplateUri: string;\n\tinvoking: string;\n\tinvoked: string;\n}) {\n\treturn {\n\t\t// OpenAI metadata\n\t\t\"openai/outputTemplate\": config.openaiTemplateUri,\n\t\t\"openai/toolInvocation/invoking\": config.invoking,\n\t\t\"openai/toolInvocation/invoked\": config.invoked,\n\t\t\"openai/widgetAccessible\": true,\n\t\t\"openai/resultCanProduceWidget\": true,\n\t\t// MCP Apps metadata\n\t\tui: {\n\t\t\tresourceUri: config.mcpTemplateUri,\n\t\t},\n\t} as const;\n}\n\n/**\n * Creates a widget with minimal boilerplate.\n *\n * @example\n * ```ts\n * const weatherWidget = createWidget({\n * id: \"show_weather\",\n * title: \"Show Weather\",\n * description: \"Displays weather information for a city\",\n * baseUrl: \"https://my-app.com\",\n * htmlPath: \"/weather\",\n * inputSchema: {\n * city: z.string().describe(\"The city name\"),\n * },\n * }, async ({ city }) => ({\n * text: `Weather for ${city}`,\n * data: { city, temperature: 72 },\n * }));\n * ```\n */\nexport function createWidget<TInput extends z.ZodRawShape>(\n\tconfig: WidgetConfig<TInput> & { widgetDomain: string },\n\thandler: WidgetHandler<TInput>,\n): RegisteredWidget {\n\tconst {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\t\twidgetDescription,\n\t\tbaseUrl,\n\t\thtmlPath,\n\t\tinputSchema,\n\t\tinvoking = \"Loading...\",\n\t\tinvoked = \"Loaded\",\n\t\twidgetDomain,\n\t\tprefersBorder = true,\n\t\twidgetCSP,\n\t\tannotations,\n\t} = config;\n\n\t// Use widgetDescription for UI metadata, fall back to description\n\tconst uiDescription = widgetDescription ?? description;\n\n\t// Create URIs for both platforms\n\tconst openaiTemplateUri = `ui://widgets/apps-sdk/${id}.html`;\n\tconst mcpTemplateUri = `ui://widgets/ext-apps/${id}.html`;\n\n\treturn {\n\t\tid,\n\t\ttitle,\n\t\tdescription,\n\n\t\tasync register(server: McpServer): Promise<void> {\n\t\t\tconst html = await fetchHtml(baseUrl, htmlPath);\n\n\t\t\t// Build tool metadata that references both widget URIs\n\t\t\tconst toolMeta = buildToolMeta({\n\t\t\t\topenaiTemplateUri,\n\t\t\t\tmcpTemplateUri,\n\t\t\t\tinvoking,\n\t\t\t\tinvoked,\n\t\t\t});\n\n\t\t\t// Register OpenAI Apps SDK resource\n\t\t\tserver.registerResource(\n\t\t\t\t`${id}-openai-widget`,\n\t\t\t\topenaiTemplateUri,\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\"openai/widgetDescription\": uiDescription,\n\t\t\t\t\t\t\"openai/widgetPrefersBorder\": prefersBorder,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (uri) => ({\n\t\t\t\t\tcontents: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\t\tmimeType: MIME_TYPE_OPENAI,\n\t\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t\t_meta: buildOpenAIResourceMeta({\n\t\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\t\twidgetDomain,\n\t\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\t// Register MCP Apps resource\n\t\t\tserver.registerResource(\n\t\t\t\t`${id}-mcp-widget`,\n\t\t\t\tmcpTemplateUri,\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t\t_meta: {\n\t\t\t\t\t\tui: {\n\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (uri) => ({\n\t\t\t\t\tcontents: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\turi: uri.href,\n\t\t\t\t\t\t\tmimeType: MIME_TYPE_MCP,\n\t\t\t\t\t\t\ttext: html,\n\t\t\t\t\t\t\t_meta: buildMcpAppsResourceMeta({\n\t\t\t\t\t\t\t\tdescription: uiDescription,\n\t\t\t\t\t\t\t\tprefersBorder,\n\t\t\t\t\t\t\t\twidgetCSP,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\t// Register the tool\n\t\t\tserver.registerTool(\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tannotations,\n\t\t\t\t\t_meta: toolMeta,\n\t\t\t\t},\n\t\t\t\t(async (args: ShapeOutput<TInput>, extra: unknown) => {\n\t\t\t\t\tconst requestExtra = extra as RequestHandlerExtra<\n\t\t\t\t\t\tServerRequest,\n\t\t\t\t\t\tServerNotification\n\t\t\t\t\t>;\n\t\t\t\t\tconst _meta: Record<string, unknown> = requestExtra._meta ?? {};\n\n\t\t\t\t\tconst result = await handler(args, { extra: { _meta } });\n\n\t\t\t\t\t/**\n\t\t\t\t\t * This is a workaround to type the tool callback correctly.\n\t\t\t\t\t *\n\t\t\t\t\t * The types are correct but TS is not able to infer the type correctly.\n\t\t\t\t\t */\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: result.text }],\n\t\t\t\t\t\tstructuredContent: result.data,\n\t\t\t\t\t\t_meta: {\n\t\t\t\t\t\t\t...toolMeta,\n\t\t\t\t\t\t\t..._meta,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}) as unknown as ToolCallback<TInput>,\n\t\t\t);\n\t\t},\n\t};\n}\n\n/**\n * Registers multiple widgets on the server\n */\nexport async function registerWidgets(\n\tserver: McpServer,\n\twidgets: RegisteredWidget[],\n): Promise<void> {\n\tawait Promise.all(widgets.map((w) => w.register(server)));\n}\n","/**\n * Widget platform types\n */\nexport type WidgetPlatform = \"openai\" | \"mcp-apps\";\n\n/**\n * Detects which platform the widget is running on.\n *\n * OpenAI injects a global `window.openai` object.\n * MCP Apps runs in a sandboxed iframe and uses postMessage.\n */\nexport function detectPlatform(): WidgetPlatform {\n\tif (typeof window !== \"undefined\" && \"openai\" in window) {\n\t\treturn \"openai\";\n\t}\n\treturn \"mcp-apps\";\n}\n\n/**\n * Check if running on OpenAI platform\n */\nexport function isOpenAI(): boolean {\n\treturn detectPlatform() === \"openai\";\n}\n\n/**\n * Check if running on MCP Apps platform\n */\nexport function isMCPApps(): boolean {\n\treturn detectPlatform() === \"mcp-apps\";\n}\n"],"mappings":"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","names":["MIME_TYPE_OPENAI","MIME_TYPE_MCP","fetchHtml","baseUrl","path","normalizedBase","buildOpenAIResourceMeta","config","buildMcpAppsResourceMeta","csp","buildToolMeta","createWidget","handler","id","title","description","widgetDescription","htmlPath","inputSchema","invoking","invoked","widgetDomain","prefersBorder","widgetCSP","annotations","uiDescription","openaiTemplateUri","mcpTemplateUri","server","html","toolMeta","uri","args","extra","_meta","result","registerWidgets","widgets","w","detectPlatform","isOpenAI","isMCPApps"]}
|