1- import {
1+ /**
2+ * @file App that does NOT depend on Apps SDK runtime.
3+ *
4+ * The Raw UI example has no runtime dependency to the Apps SDK
5+ * but still imports its types for static type safety.
6+ * Types can be just stripped, e.g. w/ the command line:
7+ *
8+ * <code>
9+ * npx esbuild src/ui-raw.ts --bundle --outfile=dist/ui-raw.js --minify --sourcemap --platform=browser
10+ * </code>
11+ *
12+ * We implement a barebones JSON-RPC message sender/receiver (see `app` object below),
13+ * but without timeouts or runtime type validation of any kind
14+ * (for that, use the Apps SDK / see ui-vanilla.ts or ui-react.ts).
15+ */
16+
17+ import type {
218 McpUiInitializeRequest ,
319 McpUiInitializeResult ,
420 McpUiInitializedNotification ,
@@ -11,26 +27,30 @@ import {
1127 McpUiOpenLinkRequest ,
1228 McpUiOpenLinkResult ,
1329} from "@modelcontextprotocol/ext-apps" ;
14- import {
30+
31+ import type {
1532 CallToolRequest ,
1633 CallToolResult ,
1734 JSONRPCMessage ,
1835 LoggingMessageNotification ,
1936} from "@modelcontextprotocol/sdk/types.js" ;
2037
2138const app = ( ( ) => {
39+ type Sendable = { method : string ; params : any } ;
40+
2241 let nextId = 1 ;
42+
2343 return {
24- sendRequest ( { method, params } : { method : string ; params : any } ) {
44+ sendRequest < T extends Sendable , Result > ( { method, params } : T ) {
2545 const id = nextId ++ ;
2646 window . parent . postMessage ( { jsonrpc : "2.0" , id, method, params } , "*" ) ;
27- return new Promise ( ( resolve , reject ) => {
47+ return new Promise < Result > ( ( resolve , reject ) => {
2848 window . addEventListener ( "message" , function listener ( event ) {
2949 const data : JSONRPCMessage = event . data ;
3050 if ( event . data ?. id === id ) {
3151 window . removeEventListener ( "message" , listener ) ;
3252 if ( event . data ?. result ) {
33- resolve ( true ) ;
53+ resolve ( event . data . result as Result ) ;
3454 } else if ( event . data ?. error ) {
3555 reject ( new Error ( event . data . error ) ) ;
3656 }
@@ -40,10 +60,13 @@ const app = (() => {
4060 } ) ;
4161 } ) ;
4262 } ,
43- sendNotification ( { method, params } : { method : string ; params : any } ) {
63+ sendNotification < T extends Sendable > ( { method, params } : T ) {
4464 window . parent . postMessage ( { jsonrpc : "2.0" , method, params } , "*" ) ;
4565 } ,
46- onNotification ( method : string , handler : ( params : any ) => void ) {
66+ onNotification < T extends Sendable > (
67+ method : T [ "method" ] ,
68+ handler : ( params : T [ "params" ] ) => void ,
69+ ) {
4770 window . addEventListener ( "message" , function listener ( event ) {
4871 if ( event . data ?. method === method ) {
4972 handler ( event . data . params ) ;
@@ -69,35 +92,40 @@ window.addEventListener("load", async () => {
6992 { style : "color: red;" } ,
7093 ) ;
7194
72- app . onNotification (
73- "ui/notifications/tool-result" as McpUiToolResultNotification [ "method" ] ,
74- async ( params : McpUiToolResultNotification [ "params" ] ) => {
75- appendText ( `Tool call result : ${ JSON . stringify ( params ) } ` ) ;
95+ app . onNotification < McpUiToolInputNotification > (
96+ "ui/notifications/tool-input" ,
97+ async ( params ) => {
98+ appendText ( `Tool call input : ${ JSON . stringify ( params ) } ` ) ;
7699 } ,
77100 ) ;
78- app . onNotification (
79- "ui/notifications/host-context-changed" as McpUiHostContextChangedNotification [ "method" ] ,
80- async ( params : McpUiHostContextChangedNotification [ "params" ] ) => {
81- appendText ( `Host context changed : ${ JSON . stringify ( params ) } ` ) ;
101+ app . onNotification < McpUiToolResultNotification > (
102+ "ui/notifications/tool-result" ,
103+ async ( params ) => {
104+ appendText ( `Tool call result : ${ JSON . stringify ( params ) } ` ) ;
82105 } ,
83106 ) ;
84- app . onNotification (
85- "ui/notifications/tool-input" as McpUiToolInputNotification [ "method" ] ,
86- async ( params : McpUiToolInputNotification [ "params" ] ) => {
87- appendText ( `Tool call input : ${ JSON . stringify ( params ) } ` ) ;
107+ app . onNotification < McpUiHostContextChangedNotification > (
108+ "ui/notifications/host-context-changed" ,
109+ async ( params ) => {
110+ appendText ( `Host context changed : ${ JSON . stringify ( params ) } ` ) ;
88111 } ,
89112 ) ;
90113
91- const initializeResult = ( await app . sendRequest ( < McpUiInitializeRequest > {
114+ const initializeResult = await app . sendRequest <
115+ McpUiInitializeRequest ,
116+ McpUiInitializeResult
117+ > ( {
92118 method : "ui/initialize" ,
93119 params : {
94120 appCapabilities : { } ,
95121 appInfo : { name : "My UI" , version : "1.0.0" } ,
96122 protocolVersion : "2025-06-18" ,
97123 } ,
98- } ) ) as McpUiInitializeResult ;
124+ } ) ;
125+
126+ appendText ( `Initialize result: ${ JSON . stringify ( initializeResult ) } ` ) ;
99127
100- app . sendNotification ( < McpUiInitializedNotification > {
128+ app . sendNotification < McpUiInitializedNotification > ( {
101129 method : "ui/notifications/initialized" ,
102130 params : { } ,
103131 } ) ;
@@ -116,7 +144,7 @@ window.addEventListener("load", async () => {
116144 ( parseFloat ( htmlStyle . borderTop ) || 0 ) +
117145 ( parseFloat ( htmlStyle . borderBottom ) || 0 ) ;
118146
119- app . sendNotification ( < McpUiSizeChangeNotification > {
147+ app . sendNotification < McpUiSizeChangeNotification > ( {
120148 method : "ui/notifications/size-change" ,
121149 params : { width, height } ,
122150 } ) ;
@@ -127,13 +155,15 @@ window.addEventListener("load", async () => {
127155 textContent : "Get Weather (Tool)" ,
128156 onclick : async ( ) => {
129157 try {
130- const result = ( await app . sendRequest ( < CallToolRequest > {
131- method : "tools/call" ,
132- params : {
133- name : "get-weather" ,
134- arguments : { location : "Tokyo" } ,
158+ const result = await app . sendRequest < CallToolRequest , CallToolResult > (
159+ {
160+ method : "tools/call" ,
161+ params : {
162+ name : "get-weather" ,
163+ arguments : { location : "Tokyo" } ,
164+ } ,
135165 } ,
136- } ) ) as CallToolResult ;
166+ ) ;
137167
138168 appendText ( `Weather tool result: ${ JSON . stringify ( result ) } ` ) ;
139169 } catch ( e ) {
@@ -147,7 +177,7 @@ window.addEventListener("load", async () => {
147177 Object . assign ( document . createElement ( "button" ) , {
148178 textContent : "Notify Cart Updated" ,
149179 onclick : async ( ) => {
150- app . sendNotification ( < LoggingMessageNotification > {
180+ app . sendNotification < LoggingMessageNotification > ( {
151181 method : "notifications/message" ,
152182 params : {
153183 level : "info" ,
@@ -163,7 +193,10 @@ window.addEventListener("load", async () => {
163193 textContent : "Prompt Weather in Tokyo" ,
164194 onclick : async ( ) => {
165195 try {
166- const { isError } = ( await app . sendRequest ( < McpUiMessageRequest > {
196+ const { isError } = await app . sendRequest <
197+ McpUiMessageRequest ,
198+ McpUiMessageResult
199+ > ( {
167200 method : "ui/message" ,
168201 params : {
169202 role : "user" ,
@@ -174,7 +207,7 @@ window.addEventListener("load", async () => {
174207 } ,
175208 ] ,
176209 } ,
177- } ) ) as McpUiMessageResult ;
210+ } ) ;
178211
179212 appendText ( `Message result: ${ isError ? "error" : "success" } ` ) ;
180213 } catch ( e ) {
@@ -189,12 +222,15 @@ window.addEventListener("load", async () => {
189222 textContent : "Open Link to Google" ,
190223 onclick : async ( ) => {
191224 try {
192- const { isError } = ( await app . sendRequest ( < McpUiOpenLinkRequest > {
225+ const { isError } = await app . sendRequest <
226+ McpUiOpenLinkRequest ,
227+ McpUiOpenLinkResult
228+ > ( {
193229 method : "ui/open-link" ,
194230 params : {
195231 url : "https://www.google.com" ,
196232 } ,
197- } ) ) as McpUiOpenLinkResult ;
233+ } ) ;
198234 appendText ( `Link result: ${ isError ? "error" : "success" } ` ) ;
199235 } catch ( e ) {
200236 appendError ( e ) ;
0 commit comments