11/**
2- * MCP-UI Adapter
2+ * MCP-UI Adapter Utilities
3+ *
4+ * Pure functions to convert mcp-use high-level UIResource definitions
5+ * into @mcp-ui/server compatible resource objects.
36 *
4- * Provides an adapter between mcp-use high-level UIResource definitions
5- * and the low-level @mcp-ui/server resource format.
67 * Ref: https://mcpui.dev/guide/server/typescript/usage-examples
78 */
89
910import { createUIResource } from '@mcp-ui/server'
10- import type { UIResourceContent , UIResourceDefinition } from '../types/resource.js'
11+ import type {
12+ UIResourceContent ,
13+ UIResourceDefinition ,
14+ UIEncoding
15+ } from '../types/resource.js'
1116
1217/**
13- * Content type options for UI resources
18+ * Configuration for building widget URLs
1419 */
15- export type UIContentType =
16- | 'externalUrl' // Default: iframe URL for serving widgets
17- | 'rawHtml' // Direct HTML content
18- | 'remoteDom' // Remote DOM scripting
20+ export interface UrlConfig {
21+ baseUrl : string
22+ port : number | string
23+ }
1924
2025/**
21- * Encoding options for UI resources
26+ * Build the full URL for a widget including query parameters
27+ *
28+ * @param widget - Widget identifier
29+ * @param props - Parameters to pass as query params
30+ * @param config - URL configuration (baseUrl and port)
31+ * @returns Complete widget URL with encoded parameters
2232 */
23- export type UIEncoding = 'text' | 'blob'
33+ export function buildWidgetUrl (
34+ widget : string ,
35+ props : Record < string , any > | undefined ,
36+ config : UrlConfig
37+ ) : string {
38+ const url = new URL (
39+ `/mcp-use/widgets/${ widget } ` ,
40+ `${ config . baseUrl } :${ config . port } `
41+ )
42+
43+ if ( props ) {
44+ Object . entries ( props ) . forEach ( ( [ key , value ] ) => {
45+ if ( value !== undefined && value !== null ) {
46+ const stringValue = typeof value === 'object'
47+ ? JSON . stringify ( value )
48+ : String ( value )
49+ url . searchParams . set ( key , stringValue )
50+ }
51+ } )
52+ }
53+
54+ return url . toString ( )
55+ }
2456
2557/**
26- * Framework options for Remote DOM resources
58+ * Create a UIResource for an external URL (iframe)
59+ *
60+ * @param uri - Resource URI (must start with ui://)
61+ * @param iframeUrl - URL to load in iframe
62+ * @param encoding - Encoding type ('text' or 'blob')
63+ * @returns UIResourceContent object
2764 */
28- export type RemoteDomFramework = 'react' | 'webcomponents'
65+ export function createExternalUrlResource (
66+ uri : string ,
67+ iframeUrl : string ,
68+ encoding : UIEncoding = 'text'
69+ ) : UIResourceContent {
70+ return createUIResource ( {
71+ uri : uri as `ui://${string } `,
72+ content : { type : 'externalUrl' , iframeUrl } ,
73+ encoding
74+ } )
75+ }
2976
3077/**
31- * Extended UI resource definition with content type support
78+ * Create a UIResource for raw HTML content
79+ *
80+ * @param uri - Resource URI (must start with ui://)
81+ * @param htmlString - HTML content to render
82+ * @param encoding - Encoding type ('text' or 'blob')
83+ * @returns UIResourceContent object
3284 */
33- export interface ExtendedUIResourceDefinition extends UIResourceDefinition {
34- contentType ?: UIContentType
35- encoding ?: UIEncoding
36- htmlContent ?: string
37- remoteDomScript ?: string
38- remoteDomFramework ?: RemoteDomFramework
85+ export function createRawHtmlResource (
86+ uri : string ,
87+ htmlString : string ,
88+ encoding : UIEncoding = 'text'
89+ ) : UIResourceContent {
90+ return createUIResource ( {
91+ uri : uri as `ui://${string } `,
92+ content : { type : 'rawHtml' , htmlString } ,
93+ encoding
94+ } )
3995}
4096
4197/**
42- * Configuration for the adapter
98+ * Create a UIResource for Remote DOM scripting
99+ *
100+ * @param uri - Resource URI (must start with ui://)
101+ * @param script - JavaScript code for remote DOM manipulation
102+ * @param framework - Framework for remote DOM ('react' or 'webcomponents')
103+ * @param encoding - Encoding type ('text' or 'blob')
104+ * @returns UIResourceContent object
43105 */
44- export interface AdapterConfig {
45- baseUrl : string
46- port : number | string
106+ export function createRemoteDomResource (
107+ uri : string ,
108+ script : string ,
109+ framework : 'react' | 'webcomponents' = 'react' ,
110+ encoding : UIEncoding = 'text'
111+ ) : UIResourceContent {
112+ return createUIResource ( {
113+ uri : uri as `ui://${string } `,
114+ content : { type : 'remoteDom' , script, framework } ,
115+ encoding
116+ } )
47117}
48118
49119/**
50- * MCP-UI Adapter class
120+ * Create a UIResource from a high-level definition
121+ *
122+ * This is the main function that routes to the appropriate resource creator
123+ * based on the discriminated union type.
124+ *
125+ * @param definition - UIResource definition (discriminated union)
126+ * @param params - Runtime parameters for the widget (for externalUrl type)
127+ * @param config - URL configuration for building widget URLs
128+ * @returns UIResourceContent object
51129 */
52- export class McpUiAdapter {
53- private config : AdapterConfig
54-
55- constructor ( config : AdapterConfig ) {
56- this . config = config
57- }
58-
59- /**
60- * Build the full URL for a widget
61- */
62- private buildWidgetUrl ( widget : string , props ?: Record < string , any > ) : string {
63- const url = new URL (
64- `/mcp-use/widgets/${ widget } ` ,
65- `http://localhost:${ this . config . port } `
66- )
67-
68- if ( props ) {
69- Object . entries ( props ) . forEach ( ( [ key , value ] ) => {
70- if ( value !== undefined && value !== null ) {
71- const stringValue = typeof value === 'object'
72- ? JSON . stringify ( value )
73- : String ( value )
74- url . searchParams . set ( key , stringValue )
75- }
76- } )
130+ export function createUIResourceFromDefinition (
131+ definition : UIResourceDefinition ,
132+ params : Record < string , any > ,
133+ config : UrlConfig
134+ ) : UIResourceContent {
135+ const uri = `ui://widget/${ definition . name } ` as `ui://${string } `
136+ const encoding = definition . encoding || 'text'
137+
138+ switch ( definition . type ) {
139+ case 'externalUrl' : {
140+ const widgetUrl = buildWidgetUrl ( definition . widget , params , config )
141+ return createExternalUrlResource ( uri , widgetUrl , encoding )
77142 }
78143
79- return url . toString ( )
80- }
81-
82- /**
83- * Create a UIResource for an external URL (default for widgets)
84- * @param uri - URI of the resource
85- * @param iframeUrl - URL of the iframe
86- * @param encoding - Encoding of the resource (text or blob (URL is Base64 encoded))
87- * @returns UIResourceContent
88- */
89- createExternalUrlResource (
90- uri : string ,
91- iframeUrl : string ,
92- encoding : UIEncoding = 'text'
93- ) : UIResourceContent {
94- return createUIResource ( {
95- uri : uri as `ui://${string } `,
96- content : { type : 'externalUrl' , iframeUrl } ,
97- encoding
98- } )
99- }
100-
101- /**
102- * Create a UIResource for raw HTML content
103- *
104- * @param uri - URI of the resource
105- * @param htmlString - HTML string to embed
106- * @param encoding - Encoding of the resource
107- * @returns UIResourceContent
108- */
109- createRawHtmlResource (
110- uri : string ,
111- htmlString : string ,
112- encoding : UIEncoding = 'text'
113- ) : UIResourceContent {
114- return createUIResource ( {
115- uri : uri as `ui://${string } `,
116- content : { type : 'rawHtml' , htmlString } ,
117- encoding
118- } )
119- }
120-
121- /**
122- * Create a UIResource for Remote DOM scripting
123- */
124- createRemoteDomResource (
125- uri : string ,
126- script : string ,
127- framework : RemoteDomFramework = 'react' ,
128- encoding : UIEncoding = 'text'
129- ) : UIResourceContent {
130- return createUIResource ( {
131- uri : uri as `ui://${string } `,
132- content : { type : 'remoteDom' , script, framework } ,
133- encoding
134- } )
135- }
136-
137- /**
138- * Create a UIResource from our high-level definition
139- */
140- createWidgetUIResource (
141- definition : ExtendedUIResourceDefinition ,
142- props ?: Record < string , any >
143- ) : UIResourceContent {
144- const uri = `ui://widget/${ definition . name } `
145- const contentType = definition . contentType || 'externalUrl'
146- const encoding = definition . encoding || 'text'
147-
148- switch ( contentType ) {
149- case 'externalUrl' : {
150- const widgetUrl = this . buildWidgetUrl ( definition . widget , props )
151- return this . createExternalUrlResource ( uri , widgetUrl , encoding )
152- }
153-
154- case 'rawHtml' : {
155- if ( ! definition . htmlContent ) {
156- throw new Error ( `HTML content required for rawHtml type in widget ${ definition . name } ` )
157- }
158- return this . createRawHtmlResource ( uri , definition . htmlContent , encoding )
159- }
144+ case 'rawHtml' : {
145+ return createRawHtmlResource ( uri , definition . htmlContent , encoding )
146+ }
160147
161- case 'remoteDom' : {
162- if ( ! definition . remoteDomScript ) {
163- throw new Error ( `Remote DOM script required for remoteDom type in widget ${ definition . name } ` )
164- }
165- const framework = definition . remoteDomFramework || 'react'
166- return this . createRemoteDomResource (
167- uri ,
168- definition . remoteDomScript ,
169- framework ,
170- encoding
171- )
172- }
148+ case 'remoteDom' : {
149+ const framework = definition . framework || 'react'
150+ return createRemoteDomResource ( uri , definition . script , framework , encoding )
151+ }
173152
174- default :
175- throw new Error ( `Unknown content type: ${ contentType } ` )
153+ default : {
154+ // TypeScript exhaustiveness check
155+ const _exhaustive : never = definition
156+ throw new Error ( `Unknown UI resource type: ${ ( _exhaustive as any ) . type } ` )
176157 }
177158 }
159+ }
178160
179- /**
180- * Generate HTML content for a widget (for rawHtml type)
181- */
182- generateWidgetHtml (
183- definition : UIResourceDefinition ,
184- props ?: Record < string , any >
185- ) : string {
186- const [ width = '100%' , height = '400px' ] = definition . size || [ ]
187- const propsJson = props ? JSON . stringify ( props ) : '{}'
188-
189- return `
190- <!DOCTYPE html>
161+ /**
162+ * Generate HTML content for a widget (utility function)
163+ *
164+ * @param definition - Base UI resource definition
165+ * @param props - Widget properties to inject
166+ * @returns Generated HTML string
167+ */
168+ export function generateWidgetHtml (
169+ definition : Pick < UIResourceDefinition , 'name' | 'title' | 'description' | 'size' > ,
170+ props ?: Record < string , any >
171+ ) : string {
172+ const [ width = '100%' , height = '400px' ] = definition . size || [ ]
173+ const propsJson = props ? JSON . stringify ( props ) : '{}'
174+
175+ return `<!DOCTYPE html>
191176<html>
192177<head>
193178 <meta charset="UTF-8">
@@ -246,16 +231,20 @@ export class McpUiAdapter {
246231 </script>
247232</body>
248233</html>`
249- }
234+ }
250235
251- /**
252- * Generate a Remote DOM script for a widget
253- */
254- generateRemoteDomScript (
255- definition : UIResourceDefinition ,
256- props ?: Record < string , any >
257- ) : string {
258- return `
236+ /**
237+ * Generate a Remote DOM script for a widget (utility function)
238+ *
239+ * @param definition - Base UI resource definition
240+ * @param props - Widget properties to inject
241+ * @returns Generated JavaScript string
242+ */
243+ export function generateRemoteDomScript (
244+ definition : Pick < UIResourceDefinition , 'name' | 'title' | 'description' > ,
245+ props ?: Record < string , any >
246+ ) : string {
247+ return `
259248// Remote DOM script for ${ definition . name }
260249const container = document.createElement('div');
261250container.style.padding = '20px';
@@ -295,12 +284,4 @@ console.log('Remote DOM widget ${definition.name} initialized with props:', prop
295284
296285// Append to root
297286root.appendChild(container);`
298- }
299287}
300-
301- /**
302- * Factory function to create an adapter instance
303- */
304- export function createMcpUiAdapter ( config : AdapterConfig ) : McpUiAdapter {
305- return new McpUiAdapter ( config )
306- }
0 commit comments