@@ -143,6 +143,85 @@ <h1>Statekit Visualizer</h1>
143143 < script >
144144 const { createApp, ref, computed, watch, onMounted, nextTick } = Vue ;
145145
146+ // --- MCP App Bridge ---
147+ const _mcpPending = { } ;
148+ let _mcpNextId = 1 ;
149+ let _mcpMachineId = null ;
150+ let _mcpAppMode = false ;
151+
152+ function _mcpSend ( msg ) {
153+ if ( window . parent && window . parent !== window ) {
154+ window . parent . postMessage ( msg , '*' ) ;
155+ }
156+ }
157+
158+ function callServerTool ( name , args ) {
159+ const id = _mcpNextId ++ ;
160+ const msg = { jsonrpc : '2.0' , id, method : 'tools/call' , params : { name, arguments : args } } ;
161+ return new Promise ( ( resolve , reject ) => {
162+ _mcpPending [ id ] = { resolve, reject } ;
163+ _mcpSend ( msg ) ;
164+ } ) ;
165+ }
166+
167+ window . addEventListener ( 'message' , ( ev ) => {
168+ const msg = ev . data ;
169+ if ( ! msg || ! msg . jsonrpc ) return ;
170+
171+ // JSON-RPC response (has id, no method)
172+ if ( msg . id != null && ! msg . method ) {
173+ const pending = _mcpPending [ msg . id ] ;
174+ if ( pending ) {
175+ delete _mcpPending [ msg . id ] ;
176+ if ( msg . error ) pending . reject ( msg . error ) ;
177+ else pending . resolve ( msg . result ) ;
178+ }
179+ return ;
180+ }
181+
182+ // JSON-RPC requests/notifications from host
183+ if ( msg . method === 'ui/initialize' ) {
184+ _mcpAppMode = true ;
185+ _mcpSend ( { jsonrpc : '2.0' , id : msg . id , result : { capabilities : { } } } ) ;
186+ return ;
187+ }
188+
189+ if ( msg . method === 'ui/notifications/tool-input' ) {
190+ if ( msg . params && msg . params . arguments && msg . params . arguments . machine_id ) {
191+ _mcpMachineId = msg . params . arguments . machine_id ;
192+ }
193+ return ;
194+ }
195+
196+ if ( msg . method === 'ui/notifications/tool-result' ) {
197+ try {
198+ const content = msg . params && msg . params . content ;
199+ if ( Array . isArray ( content ) && content . length > 0 && content [ 0 ] . text ) {
200+ const data = JSON . parse ( content [ 0 ] . text ) ;
201+ // Check if this looks like a VizMachine
202+ if ( data . id && data . states ) {
203+ if ( window . _statekitApp ) {
204+ window . _statekitApp . loadMachine ( data ) ;
205+ }
206+ }
207+ }
208+ } catch ( e ) { /* ignore parse errors */ }
209+ return ;
210+ }
211+
212+ if ( msg . method === 'ui/notifications/theme-changed' ) {
213+ if ( msg . params && msg . params . colorScheme ) {
214+ const isLight = msg . params . colorScheme === 'light' ;
215+ document . documentElement . classList . toggle ( 'light' , isLight ) ;
216+ if ( window . _statekitApp ) {
217+ window . _statekitApp . setDarkMode ( ! isLight ) ;
218+ }
219+ }
220+ return ;
221+ }
222+ } ) ;
223+ // --- End MCP App Bridge ---
224+
146225 createApp ( {
147226 setup ( ) {
148227 const machine = ref ( null ) ;
@@ -155,6 +234,21 @@ <h1>Statekit Visualizer</h1>
155234 const historyList = ref ( null ) ;
156235 let cy = null ;
157236
237+ // Expose helpers for MCP bridge
238+ window . _statekitApp = {
239+ loadMachine ( data ) {
240+ machine . value = data ;
241+ if ( data . id ) _mcpMachineId = data . id ;
242+ currentState . value = resolveInitial ( data . initial ) ;
243+ nextTick ( ( ) => initGraph ( ) ) ;
244+ } ,
245+ setDarkMode ( val ) {
246+ darkMode . value = val ;
247+ localStorage . setItem ( 'statekit-theme' , val ? 'dark' : 'light' ) ;
248+ if ( cy ) updateCyStyles ( ) ;
249+ }
250+ } ;
251+
158252 // Theme
159253 const savedTheme = localStorage . getItem ( 'statekit-theme' ) ;
160254 if ( savedTheme === 'light' ) {
@@ -351,24 +445,31 @@ <h1>Statekit Visualizer</h1>
351445 }
352446
353447 function sendEvent ( eventType ) {
354- // Try MCP tool call if available
355- if ( window . parent !== window ) {
356- try {
357- window . parent . postMessage ( {
358- jsonrpc : '2.0' ,
359- method : 'tools/call' ,
360- params : {
361- name : 'send_event' ,
362- arguments : {
363- machine_id : machine . value . id ,
364- event : eventType
448+ // Use MCP App bridge when running inside an MCP host
449+ if ( _mcpAppMode && _mcpMachineId ) {
450+ const machineId = _mcpMachineId ;
451+ callServerTool ( 'send_event' , { machine_id : machineId , event : eventType } )
452+ . then ( result => {
453+ try {
454+ const content = result && result . content ;
455+ if ( Array . isArray ( content ) && content . length > 0 && content [ 0 ] . text ) {
456+ const output = JSON . parse ( content [ 0 ] . text ) ;
457+ const from = currentState . value ;
458+ if ( output . currentState ) {
459+ previousState . value = from ;
460+ currentState . value = output . currentState ;
461+ isDone . value = ! ! output . done ;
462+ if ( output . transitioned && from !== output . currentState ) {
463+ addHistoryEntry ( eventType , from , output . currentState ) ;
464+ animateTransition ( from , output . currentState , eventType ) ;
465+ }
466+ highlightCurrent ( ) ;
467+ }
365468 }
366- }
367- } , '*' ) ;
368- return ;
369- } catch ( e ) {
370- // Fall through to client-side simulation
371- }
469+ } catch ( e ) { /* ignore parse errors */ }
470+ } )
471+ . catch ( ( ) => { /* ignore errors */ } ) ;
472+ return ;
372473 }
373474
374475 // Client-side simulation fallback
0 commit comments