@@ -12,6 +12,7 @@ import (
1212 "strconv"
1313 "strings"
1414
15+ "github.com/charmbracelet/bubbles/key"
1516 "github.com/charmbracelet/bubbles/list"
1617 "github.com/charmbracelet/bubbles/textinput"
1718 "github.com/charmbracelet/bubbles/viewport"
@@ -190,29 +191,39 @@ type viewState int
190191const (
191192 toolSelectionView viewState = iota
192193 argumentInputView
194+ resourceListView
195+ resourceDetailView
196+ promptListView
193197)
194198
195199type AppModel struct {
196- state viewState
197- ctx context.Context
198- session * mcp.ClientSession
199- toolList list.Model
200- argInputs []textinput.Model
201- argOrder []string
202- argFocus int
203- selectedTool * mcp.Tool
204- tools []* mcp.Tool
205- result string
206- err error
207- log []string
208- width int
209- height int
210- debugViewport viewport.Model
200+ state viewState
201+ ctx context.Context
202+ session * mcp.ClientSession
203+ toolList list.Model
204+ resourceList list.Model
205+ promptList list.Model
206+ argInputs []textinput.Model
207+ argOrder []string
208+ argFocus int
209+ selectedTool * mcp.Tool
210+ tools []* mcp.Tool
211+ resources []* mcp.Resource
212+ prompts []* mcp.Prompt
213+ selectedResource * mcp.Resource
214+ result string
215+ resourceResult string
216+ err error
217+ log []string
218+ width int
219+ height int
220+ debugViewport viewport.Model
211221}
212222
213223func initialModel (ctx context.Context , session * mcp.ClientSession ) * AppModel {
214224 var err error
215225 var tools []* mcp.Tool
226+ var resources []* mcp.Resource
216227
217228 // Iterate over the tools using range
218229 for tool , iterErr := range session .Tools (ctx , nil ) {
@@ -227,13 +238,72 @@ func initialModel(ctx context.Context, session *mcp.ClientSession) *AppModel {
227238 return & AppModel {err : err }
228239 }
229240
230- items := []list.Item {}
241+ var prompts []* mcp.Prompt
242+ for prompt , iterErr := range session .Prompts (ctx , nil ) {
243+ if iterErr != nil {
244+ err = iterErr
245+ break
246+ }
247+ prompts = append (prompts , prompt )
248+ }
249+
250+ if err != nil {
251+ return & AppModel {err : err }
252+ }
253+
254+ for resource , iterErr := range session .Resources (ctx , nil ) {
255+ if iterErr != nil {
256+ err = iterErr
257+ break
258+ }
259+ resources = append (resources , resource )
260+ }
261+
262+ if err != nil {
263+ return & AppModel {err : err }
264+ }
265+
266+ toolItems := []list.Item {}
231267 for _ , tool := range tools {
232- items = append (items , item {title : tool .Name , desc : tool .Description , tool : tool })
268+ toolItems = append (toolItems , item {title : tool .Name , desc : tool .Description , tool : tool })
269+ }
270+
271+ resourceItems := []list.Item {}
272+ for _ , resource := range resources {
273+ resourceItems = append (resourceItems , resourceItem {title : resource .Name , desc : resource .Description , resource : resource })
274+ }
275+
276+ promptItems := []list.Item {}
277+ for _ , prompt := range prompts {
278+ promptItems = append (promptItems , promptItem {title : prompt .Name , desc : prompt .Description , prompt : prompt })
279+ }
280+
281+ toolList := list .New (toolItems , list .NewDefaultDelegate (), 0 , 0 )
282+ toolList .Title = "Select a tool to execute"
283+ toolList .AdditionalShortHelpKeys = func () []key.Binding {
284+ return []key.Binding {
285+ key .NewBinding (key .WithKeys ("r" ), key .WithHelp ("r" , "resources" )),
286+ key .NewBinding (key .WithKeys ("p" ), key .WithHelp ("p" , "prompts" )),
287+ }
233288 }
234289
235- l := list .New (items , list .NewDefaultDelegate (), 0 , 0 )
236- l .Title = "Select a tool to execute"
290+ resourceList := list .New (resourceItems , list .NewDefaultDelegate (), 0 , 0 )
291+ resourceList .Title = "Select a resource"
292+ resourceList .AdditionalShortHelpKeys = func () []key.Binding {
293+ return []key.Binding {
294+ key .NewBinding (key .WithKeys ("t" ), key .WithHelp ("t" , "tools" )),
295+ key .NewBinding (key .WithKeys ("p" ), key .WithHelp ("p" , "prompts" )),
296+ }
297+ }
298+
299+ promptList := list .New (promptItems , list .NewDefaultDelegate (), 0 , 0 )
300+ promptList .Title = "Select a prompt"
301+ promptList .AdditionalShortHelpKeys = func () []key.Binding {
302+ return []key.Binding {
303+ key .NewBinding (key .WithKeys ("t" ), key .WithHelp ("t" , "tools" )),
304+ key .NewBinding (key .WithKeys ("r" ), key .WithHelp ("r" , "resources" )),
305+ }
306+ }
237307
238308 vp := viewport .New (1 , 1 ) // Initial size, will be updated on WindowSizeMsg
239309 vp .SetContent ("Debug log will appear here..." )
@@ -242,8 +312,12 @@ func initialModel(ctx context.Context, session *mcp.ClientSession) *AppModel {
242312 state : toolSelectionView ,
243313 ctx : ctx ,
244314 session : session ,
245- toolList : l ,
315+ toolList : toolList ,
316+ resourceList : resourceList ,
317+ promptList : promptList ,
246318 tools : tools ,
319+ resources : resources ,
320+ prompts : prompts ,
247321 debugViewport : vp ,
248322 }
249323}
@@ -257,6 +331,24 @@ func (i item) Title() string { return i.title }
257331func (i item ) Description () string { return i .desc }
258332func (i item ) FilterValue () string { return i .title }
259333
334+ type resourceItem struct {
335+ title , desc string
336+ resource * mcp.Resource
337+ }
338+
339+ func (i resourceItem ) Title () string { return i .title }
340+ func (i resourceItem ) Description () string { return i .desc }
341+ func (i resourceItem ) FilterValue () string { return i .title }
342+
343+ type promptItem struct {
344+ title , desc string
345+ prompt * mcp.Prompt
346+ }
347+
348+ func (i promptItem ) Title () string { return i .title }
349+ func (i promptItem ) Description () string { return i .desc }
350+ func (i promptItem ) FilterValue () string { return i .title }
351+
260352func (m * AppModel ) logf (format string , a ... any ) {
261353 m .log = append (m .log , fmt .Sprintf (format , a ... ))
262354 m .debugViewport .SetContent (strings .Join (m .log , "\n " ))
@@ -282,13 +374,27 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
282374 }
283375 m .logf ("Result:\n ========\n %s" , msg .result )
284376 m .result = msg .result
377+ case resourceResult :
378+ if msg .err != nil {
379+ m .err = msg .err
380+ return m , nil
381+ }
382+ if verbose {
383+ m .logf ("Resource result received" )
384+ }
385+ m .logf ("Result:\n ========\n %s" , msg .result )
386+ m .resourceResult = msg .result
285387 case tea.KeyMsg :
286388 if verbose {
287389 m .logf ("Key pressed: %s" , msg .String ())
288390 }
289391 switch msg .Type {
290392 case tea .KeyEsc :
291- m .state = toolSelectionView
393+ if m .state == resourceDetailView {
394+ m .state = resourceListView
395+ } else {
396+ m .state = toolSelectionView
397+ }
292398 return m , nil
293399 case tea .KeyCtrlC :
294400 return m , tea .Quit
@@ -302,13 +408,31 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
302408 m .debugViewport , cmd = m .debugViewport .Update (msg )
303409 cmds = append (cmds , cmd )
304410 return model , tea .Batch (cmds ... )
411+ case resourceListView :
412+ var model tea.Model
413+ model , cmd = m .updateResourceListView (msg )
414+ cmds = append (cmds , cmd )
415+ m .debugViewport , cmd = m .debugViewport .Update (msg )
416+ cmds = append (cmds , cmd )
417+ return model , tea .Batch (cmds ... )
418+ case promptListView :
419+ var model tea.Model
420+ model , cmd = m .updatePromptListView (msg )
421+ cmds = append (cmds , cmd )
422+ m .debugViewport , cmd = m .debugViewport .Update (msg )
423+ cmds = append (cmds , cmd )
424+ return model , tea .Batch (cmds ... )
305425 case argumentInputView :
306426 var model tea.Model
307427 model , cmd = m .updateArgumentInputView (msg )
308428 cmds = append (cmds , cmd )
309429 m .debugViewport , cmd = m .debugViewport .Update (msg )
310430 cmds = append (cmds , cmd )
311431 return model , tea .Batch (cmds ... )
432+ case resourceDetailView :
433+ m .debugViewport , cmd = m .debugViewport .Update (msg )
434+ cmds = append (cmds , cmd )
435+ return m , tea .Batch (cmds ... )
312436 }
313437
314438 case tea.WindowSizeMsg :
@@ -330,7 +454,14 @@ func (m *AppModel) updateToolSelectionView(msg tea.Msg) (tea.Model, tea.Cmd) {
330454 m .toolList , cmd = m .toolList .Update (msg )
331455
332456 if keyMsg , ok := msg .(tea.KeyMsg ); ok {
333- if keyMsg .Type == tea .KeyEnter {
457+ switch keyMsg .String () {
458+ case "r" :
459+ m .state = resourceListView
460+ return m , nil
461+ case "p" :
462+ m .state = promptListView
463+ return m , nil
464+ case "enter" :
334465 selectedItem := m .toolList .SelectedItem ().(item )
335466 m .selectedTool = selectedItem .tool
336467
@@ -368,6 +499,47 @@ func (m *AppModel) updateToolSelectionView(msg tea.Msg) (tea.Model, tea.Cmd) {
368499 return m , cmd
369500}
370501
502+ func (m * AppModel ) updatePromptListView (msg tea.Msg ) (tea.Model , tea.Cmd ) {
503+ var cmd tea.Cmd
504+ m .promptList , cmd = m .promptList .Update (msg )
505+
506+ if keyMsg , ok := msg .(tea.KeyMsg ); ok {
507+ switch keyMsg .String () {
508+ case "t" :
509+ m .state = toolSelectionView
510+ return m , nil
511+ case "r" :
512+ m .state = resourceListView
513+ return m , nil
514+ }
515+ }
516+
517+ return m , cmd
518+ }
519+
520+ func (m * AppModel ) updateResourceListView (msg tea.Msg ) (tea.Model , tea.Cmd ) {
521+ var cmd tea.Cmd
522+ m .resourceList , cmd = m .resourceList .Update (msg )
523+
524+ if keyMsg , ok := msg .(tea.KeyMsg ); ok {
525+ switch keyMsg .String () {
526+ case "t" :
527+ m .state = toolSelectionView
528+ return m , nil
529+ case "p" :
530+ m .state = promptListView
531+ return m , nil
532+ case "enter" :
533+ selectedItem := m .resourceList .SelectedItem ().(resourceItem )
534+ m .selectedResource = selectedItem .resource
535+ m .state = resourceDetailView
536+ return m , m .readResourceCmd ()
537+ }
538+ }
539+
540+ return m , cmd
541+ }
542+
371543func (m * AppModel ) updateArgumentInputView (msg tea.Msg ) (tea.Model , tea.Cmd ) {
372544 keyMsg , ok := msg .(tea.KeyMsg )
373545 if ! ok {
@@ -422,6 +594,18 @@ func (m AppModel) View() string {
422594 case toolSelectionView :
423595 m .toolList .SetSize (mainWidth - 2 , m .height - 2 )
424596 mainContent .WriteString (m .toolList .View ())
597+ case resourceListView :
598+ m .resourceList .SetSize (mainWidth - 2 , m .height - 2 )
599+ mainContent .WriteString (m .resourceList .View ())
600+ case promptListView :
601+ m .promptList .SetSize (mainWidth - 2 , m .height - 2 )
602+ mainContent .WriteString (m .promptList .View ())
603+ case resourceDetailView :
604+ var b strings.Builder
605+ b .WriteString (fmt .Sprintf ("Details for %s:\n \n " , m .selectedResource .Name ))
606+ b .WriteString (m .resourceResult )
607+ b .WriteString ("\n \n Press Esc to go back to resource list." )
608+ mainContent .WriteString (b .String ())
425609 case argumentInputView :
426610 var b strings.Builder
427611 b .WriteString (fmt .Sprintf ("Enter arguments for %s:\n \n " , m .selectedTool .Name ))
@@ -456,6 +640,12 @@ type toolResult struct {
456640 err error
457641}
458642
643+ // resourceResult represents the result of a resource read
644+ type resourceResult struct {
645+ result string
646+ err error
647+ }
648+
459649// callToolCmd returns a tea.Cmd that calls the tool
460650func (m * AppModel ) callToolCmd () tea.Cmd {
461651 return func () tea.Msg {
@@ -554,6 +744,30 @@ func (m *AppModel) callTool() (tea.Model, tea.Cmd) {
554744 return m , m .callToolCmd ()
555745}
556746
747+ func (m * AppModel ) readResourceCmd () tea.Cmd {
748+ return func () tea.Msg {
749+ params := & mcp.ReadResourceParams {
750+ URI : m .selectedResource .URI ,
751+ }
752+ result , err := m .session .ReadResource (m .ctx , params )
753+ if err != nil {
754+ return resourceResult {err : err }
755+ }
756+
757+ var resultStr strings.Builder
758+ for _ , content := range result .Contents {
759+ prettyJSON , err := json .MarshalIndent (content , "" , " " )
760+ if err != nil {
761+ resultStr .WriteString (fmt .Sprintf ("Error marshalling content: %v\n " , err ))
762+ } else {
763+ resultStr .WriteString (string (prettyJSON ))
764+ }
765+ }
766+
767+ return resourceResult {result : resultStr .String ()}
768+ }
769+ }
770+
557771func handleSession (ctx context.Context , session * mcp.ClientSession ) {
558772 if verbose {
559773 f , err := tea .LogToFile ("debug.log" , "debug" )
0 commit comments