@@ -2,6 +2,7 @@ package main
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "sort"
78 "strings"
@@ -11,8 +12,10 @@ import (
1112 "github.com/github/github-mcp-server/pkg/github"
1213 "github.com/github/github-mcp-server/pkg/raw"
1314 gogithub "github.com/google/go-github/v74/github"
15+ "github.com/mark3labs/mcp-go/server"
1416 "github.com/shurcooL/githubv4"
1517 "github.com/spf13/cobra"
18+ "github.com/tiktoken-go/tokenizer"
1619)
1720
1821var configureCmd = & cobra.Command {
7174 toolsetBadgeStyle = lipgloss .NewStyle ().
7275 Foreground (lipgloss .Color ("#84a5ecff" ))
7376
77+ tokenBadgeStyle = lipgloss .NewStyle ().
78+ Foreground (lipgloss .Color ("#e8b75aff" ))
79+
7480 successStyle = lipgloss .NewStyle ().
7581 Foreground (lipgloss .Color ("#00FF00" )).
7682 Bold (true )
@@ -100,6 +106,7 @@ type toolInfo struct {
100106 description string
101107 toolsetName string
102108 isReadOnly bool
109+ tokenCount int // Estimated token count for this tool's definition
103110}
104111
105112type toolsetInfo struct {
@@ -122,9 +129,17 @@ type configureModel struct {
122129 confirmed bool
123130 viewportOffset int
124131 showWelcome bool
132+ encoder tokenizer.Codec // Tokenizer encoder for counting tokens
125133}
126134
127135func initialConfigureModel (toolsets []toolsetInfo ) configureModel {
136+ // Initialize tokenizer
137+ enc , err := tokenizer .Get (tokenizer .Cl100kBase )
138+ if err != nil {
139+ // If tokenizer fails to initialize, continue without it
140+ enc = nil
141+ }
142+
128143 // Flatten all tools
129144 var allTools []toolInfo
130145 for _ , ts := range toolsets {
@@ -144,6 +159,7 @@ func initialConfigureModel(toolsets []toolsetInfo) configureModel {
144159 width : 80 ,
145160 height : 24 ,
146161 showWelcome : true , // Start with welcome screen
162+ encoder : enc ,
147163 }
148164}
149165
@@ -352,10 +368,15 @@ func (m configureModel) View() string {
352368 visibleEnd = len (m .filteredTools )
353369 }
354370
371+ // Calculate selected count and total tokens
355372 selectedCount := 0
356- for _ , selected := range m .selected {
373+ totalTokens := 0
374+ for i , selected := range m .selected {
357375 if selected {
358376 selectedCount ++
377+ if i < len (m .filteredTools ) {
378+ totalTokens += m .filteredTools [i ].tokenCount
379+ }
359380 }
360381 }
361382
@@ -392,10 +413,17 @@ func (m configureModel) View() string {
392413 category := toolsetBadgeStyle .Render (fmt .Sprintf ("[%s]" , tool .toolsetName ))
393414 line += category
394415
416+ // Add token count if available
417+ if tool .tokenCount > 0 {
418+ tokenBadge := tokenBadgeStyle .Render (fmt .Sprintf (" ~%s tokens" , formatTokenCount (tool .tokenCount )))
419+ line += tokenBadge
420+ }
421+
395422 // Add description (truncated if needed)
396423 desc := getFirstSentence (tool .description )
397- if len (desc ) > 60 {
398- desc = desc [:57 ] + "..."
424+ maxDescLen := 45 // Reduced to make room for token count
425+ if len (desc ) > maxDescLen {
426+ desc = desc [:maxDescLen - 3 ] + "..."
399427 }
400428 line += " " + dimStyle .Render (desc )
401429
@@ -415,7 +443,11 @@ func (m configureModel) View() string {
415443 s .WriteString ("\n " )
416444
417445 // Footer with help
418- s .WriteString (helpStyle .Render (fmt .Sprintf ("Selected: %d tools" , selectedCount )))
446+ footerInfo := fmt .Sprintf ("Selected: %d tools" , selectedCount )
447+ if m .encoder != nil && totalTokens > 0 {
448+ footerInfo += fmt .Sprintf (" • Estimated tokens: ~%s" , formatTokenCount (totalTokens ))
449+ }
450+ s .WriteString (helpStyle .Render (footerInfo ))
419451 s .WriteString ("\n " )
420452 s .WriteString (helpStyle .Render ("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" ))
421453 s .WriteString ("\n " )
@@ -520,9 +552,11 @@ func (m configureModel) renderConfirmation() string {
520552
521553 // Get selected tools
522554 var selectedTools []string
555+ totalTokens := 0
523556 for i , selected := range m .selected {
524557 if selected && i < len (m .filteredTools ) {
525558 selectedTools = append (selectedTools , m .filteredTools [i ].name )
559+ totalTokens += m .filteredTools [i ].tokenCount
526560 }
527561 }
528562
@@ -532,8 +566,16 @@ func (m configureModel) renderConfirmation() string {
532566 s .WriteString ("\n " )
533567 s .WriteString (successStyle .Render ("✅ Configuration Complete!" ))
534568 s .WriteString ("\n \n " )
569+
570+ // Add token summary
571+ if m .encoder != nil && totalTokens > 0 {
572+ s .WriteString (itemStyle .Render (fmt .Sprintf ("📊 Total Estimated Tokens: ~%s" , formatTokenCount (totalTokens ))))
573+ s .WriteString ("\n " )
574+ s .WriteString (dimStyle .Render (" This is an approximation" ))
575+ s .WriteString ("\n \n " )
576+ }
577+
535578 s .WriteString (titleStyle .Render ("Selected Tools:" ))
536- s .WriteString ("\n " )
537579
538580 if len (selectedTools ) == 0 {
539581 s .WriteString (dimStyle .Render (" (none - all tools will be enabled by default)" ))
@@ -616,6 +658,12 @@ func getAvailableToolsets() []toolsetInfo {
616658 return defaultValue
617659 }
618660
661+ // Initialize tokenizer for token counting
662+ enc , err := tokenizer .Get (tokenizer .Cl100kBase )
663+ if err != nil {
664+ enc = nil // Continue without tokenizer if it fails
665+ }
666+
619667 // Create a dummy toolset group to extract the structure
620668 // We use read-only false to get all tools
621669 tsg := github .DefaultToolsetGroup (false , getClient , getGQLClient , getRawClient , translator , 5000 )
@@ -632,12 +680,19 @@ func getAvailableToolsets() []toolsetInfo {
632680 // Get all available tools (both read and write)
633681 allTools := toolset .GetAvailableTools ()
634682 for _ , tool := range allTools {
635- ts . tools = append ( ts . tools , toolInfo {
683+ toolInfo := toolInfo {
636684 name : tool .Tool .Name ,
637685 description : tool .Tool .Description ,
638686 toolsetName : toolsetName ,
639687 isReadOnly : tool .Tool .Annotations .ReadOnlyHint != nil && * tool .Tool .Annotations .ReadOnlyHint ,
640- })
688+ }
689+
690+ // Estimate token count for this tool using the actual MCP tool
691+ if enc != nil {
692+ toolInfo .tokenCount = estimateToolTokens (enc , tool )
693+ }
694+
695+ ts .tools = append (ts .tools , toolInfo )
641696 }
642697
643698 // Sort tools by name
@@ -670,6 +725,34 @@ func getFirstSentence(description string) string {
670725 return description
671726}
672727
728+ // estimateToolTokens estimates the token count for a tool's MCP definition
729+ // This serializes the complete tool definition (name, description, annotations, inputSchema)
730+ // to approximate the token count that will be sent to the LLM in the tools/list response
731+ func estimateToolTokens (enc tokenizer.Codec , mcpTool server.ServerTool ) int {
732+ if enc == nil {
733+ return 0
734+ }
735+
736+ // Serialize the full MCP tool definition to JSON
737+ // This is what gets sent to the LLM in the tools/list response
738+ jsonBytes , err := json .Marshal (mcpTool .Tool )
739+ if err != nil {
740+ return 0
741+ }
742+
743+ // Encode and count tokens
744+ tokens , _ , _ := enc .Encode (string (jsonBytes ))
745+ return len (tokens )
746+ }
747+
748+ // formatTokenCount formats token count with K suffix for thousands
749+ func formatTokenCount (count int ) string {
750+ if count >= 1000 {
751+ return fmt .Sprintf ("%.1fK" , float64 (count )/ 1000 )
752+ }
753+ return fmt .Sprintf ("%d" , count )
754+ }
755+
673756
674757func runConfigure (cmd * cobra.Command , args []string ) error {
675758 // Dynamically get available toolsets
0 commit comments