@@ -121,6 +121,10 @@ func NewLokiQueryTool() mcp.Tool {
121121 mcp .WithString ("org" ,
122122 mcp .Description (fmt .Sprintf ("Organization ID for the query (default: %s from %s env var)" , orgID , EnvLokiOrgID )),
123123 ),
124+ mcp .WithString ("format" ,
125+ mcp .Description ("Output format: raw, json, or text (default: raw)" ),
126+ mcp .DefaultString ("raw" ),
127+ ),
124128 )
125129}
126130
@@ -195,6 +199,12 @@ func HandleLokiQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
195199 limit = int (limitVal )
196200 }
197201
202+ // Extract format parameter
203+ format := "raw" // default
204+ if formatArg , ok := args ["format" ].(string ); ok && formatArg != "" {
205+ format = formatArg
206+ }
207+
198208 // Build query URL
199209 queryURL , err := buildLokiQueryURL (lokiURL , queryString , start , end , limit )
200210 if err != nil {
@@ -208,7 +218,7 @@ func HandleLokiQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
208218 }
209219
210220 // Format results
211- formattedResult , err := formatLokiResults (result )
221+ formattedResult , err := formatLokiResults (result , format )
212222 if err != nil {
213223 return nil , fmt .Errorf ("failed to format results: %v" , err )
214224 }
@@ -356,50 +366,102 @@ func executeLokiQuery(ctx context.Context, queryURL string, username, password,
356366}
357367
358368// formatLokiResults formats the Loki query results into a readable string
359- func formatLokiResults (result * LokiResult ) (string , error ) {
369+ func formatLokiResults (result * LokiResult , format string ) (string , error ) {
360370 if len (result .Data .Result ) == 0 {
361- return "No logs found matching the query" , nil
371+ switch format {
372+ case "json" :
373+ return "{\" message\" : \" No logs found matching the query\" }" , nil
374+ default :
375+ return "No logs found matching the query" , nil
376+ }
362377 }
363378
364- var output string
365- output = fmt .Sprintf ("Found %d streams:\n \n " , len (result .Data .Result ))
379+ switch format {
380+ case "json" :
381+ // Return raw JSON response
382+ jsonBytes , err := json .MarshalIndent (result , "" , " " )
383+ if err != nil {
384+ return "" , fmt .Errorf ("failed to marshal JSON: %v" , err )
385+ }
386+ return string (jsonBytes ), nil
387+
388+ case "raw" :
389+ // Return raw log lines with timestamps and labels in simple format
390+ var output string
391+ for _ , entry := range result .Data .Result {
392+ // Build labels string
393+ var labels string
394+ if len (entry .Stream ) > 0 {
395+ labelParts := make ([]string , 0 , len (entry .Stream ))
396+ for k , v := range entry .Stream {
397+ labelParts = append (labelParts , fmt .Sprintf ("%s=%s" , k , v ))
398+ }
399+ labels = "{" + strings .Join (labelParts , "," ) + "} "
400+ }
366401
367- for i , entry := range result .Data .Result {
368- // Format stream labels
369- streamInfo := "Stream "
370- if len (entry .Stream ) > 0 {
371- streamInfo += "("
372- first := true
373- for k , v := range entry .Stream {
374- if ! first {
375- streamInfo += ", "
402+ for _ , val := range entry .Values {
403+ if len (val ) >= 2 {
404+ // Parse timestamp and convert to readable format
405+ ts , err := strconv .ParseFloat (val [0 ], 64 )
406+ var timestamp string
407+ if err == nil {
408+ // Convert to time - Loki returns timestamps in nanoseconds
409+ t := time .Unix (0 , int64 (ts ))
410+ timestamp = t .Format (time .RFC3339 )
411+ } else {
412+ timestamp = val [0 ]
413+ }
414+
415+ output += fmt .Sprintf ("%s %s%s\n " , timestamp , labels , val [1 ])
376416 }
377- streamInfo += fmt .Sprintf ("%s=%s" , k , v )
378- first = false
379417 }
380- streamInfo += ")"
381418 }
419+ return output , nil
420+
421+ case "text" :
422+ // Return formatted text with timestamps and stream info (original behavior)
423+ var output string
424+ output = fmt .Sprintf ("Found %d streams:\n \n " , len (result .Data .Result ))
425+
426+ for i , entry := range result .Data .Result {
427+ // Format stream labels
428+ streamInfo := "Stream "
429+ if len (entry .Stream ) > 0 {
430+ streamInfo += "("
431+ first := true
432+ for k , v := range entry .Stream {
433+ if ! first {
434+ streamInfo += ", "
435+ }
436+ streamInfo += fmt .Sprintf ("%s=%s" , k , v )
437+ first = false
438+ }
439+ streamInfo += ")"
440+ }
382441
383- output += fmt .Sprintf ("%s %d:\n " , streamInfo , i + 1 )
384-
385- // Format log entries
386- for _ , val := range entry .Values {
387- if len (val ) >= 2 {
388- // Parse timestamp
389- ts , err := strconv .ParseFloat (val [0 ], 64 )
390- if err == nil {
391- // Convert to time - Loki returns timestamps in nanoseconds already
392- timestamp := time .Unix (0 , int64 (ts ))
393- output += fmt .Sprintf ("[%s] %s\n " , timestamp .Format (time .RFC3339 ), val [1 ])
394- } else {
395- output += fmt .Sprintf ("[%s] %s\n " , val [0 ], val [1 ])
442+ output += fmt .Sprintf ("%s %d:\n " , streamInfo , i + 1 )
443+
444+ // Format log entries
445+ for _ , val := range entry .Values {
446+ if len (val ) >= 2 {
447+ // Parse timestamp
448+ ts , err := strconv .ParseFloat (val [0 ], 64 )
449+ if err == nil {
450+ // Convert to time - Loki returns timestamps in nanoseconds already
451+ timestamp := time .Unix (0 , int64 (ts ))
452+ output += fmt .Sprintf ("[%s] %s\n " , timestamp .Format (time .RFC3339 ), val [1 ])
453+ } else {
454+ output += fmt .Sprintf ("[%s] %s\n " , val [0 ], val [1 ])
455+ }
396456 }
397457 }
458+ output += "\n "
398459 }
399- output += "\n "
400- }
460+ return output , nil
401461
402- return output , nil
462+ default :
463+ return "" , fmt .Errorf ("unsupported format: %s. Supported formats: raw, json, text" , format )
464+ }
403465}
404466
405467// NewLokiLabelNamesTool creates and returns a tool for getting all label names from Grafana Loki
@@ -440,6 +502,10 @@ func NewLokiLabelNamesTool() mcp.Tool {
440502 mcp .WithString ("org" ,
441503 mcp .Description (fmt .Sprintf ("Organization ID for the query (default: %s from %s env var)" , orgID , EnvLokiOrgID )),
442504 ),
505+ mcp .WithString ("format" ,
506+ mcp .Description ("Output format: raw, json, or text (default: raw)" ),
507+ mcp .DefaultString ("raw" ),
508+ ),
443509 )
444510}
445511
@@ -485,6 +551,10 @@ func NewLokiLabelValuesTool() mcp.Tool {
485551 mcp .WithString ("org" ,
486552 mcp .Description (fmt .Sprintf ("Organization ID for the query (default: %s from %s env var)" , orgID , EnvLokiOrgID )),
487553 ),
554+ mcp .WithString ("format" ,
555+ mcp .Description ("Output format: raw, json, or text (default: raw)" ),
556+ mcp .DefaultString ("raw" ),
557+ ),
488558 )
489559}
490560
@@ -549,6 +619,12 @@ func HandleLokiLabelNames(ctx context.Context, request mcp.CallToolRequest) (*mc
549619 end = endTime .Unix ()
550620 }
551621
622+ // Extract format parameter
623+ format := "raw" // default
624+ if formatArg , ok := args ["format" ].(string ); ok && formatArg != "" {
625+ format = formatArg
626+ }
627+
552628 // Build labels URL
553629 labelsURL , err := buildLokiLabelsURL (lokiURL , start , end )
554630 if err != nil {
@@ -562,7 +638,7 @@ func HandleLokiLabelNames(ctx context.Context, request mcp.CallToolRequest) (*mc
562638 }
563639
564640 // Format results
565- formattedResult , err := formatLokiLabelsResults (result )
641+ formattedResult , err := formatLokiLabelsResults (result , format )
566642 if err != nil {
567643 return nil , fmt .Errorf ("failed to format results: %v" , err )
568644 }
@@ -632,6 +708,12 @@ func HandleLokiLabelValues(ctx context.Context, request mcp.CallToolRequest) (*m
632708 end = endTime .Unix ()
633709 }
634710
711+ // Extract format parameter
712+ format := "raw" // default
713+ if formatArg , ok := args ["format" ].(string ); ok && formatArg != "" {
714+ format = formatArg
715+ }
716+
635717 // Build label values URL
636718 labelValuesURL , err := buildLokiLabelValuesURL (lokiURL , labelName , start , end )
637719 if err != nil {
@@ -645,7 +727,7 @@ func HandleLokiLabelValues(ctx context.Context, request mcp.CallToolRequest) (*m
645727 }
646728
647729 // Format results
648- formattedResult , err := formatLokiLabelValuesResults (labelName , result )
730+ formattedResult , err := formatLokiLabelValuesResults (labelName , result , format )
649731 if err != nil {
650732 return nil , fmt .Errorf ("failed to format results: %v" , err )
651733 }
@@ -824,33 +906,87 @@ func executeLokiLabelValuesQuery(ctx context.Context, queryURL string, username,
824906}
825907
826908// formatLokiLabelsResults formats the Loki labels results into a readable string
827- func formatLokiLabelsResults (result * LokiLabelsResult ) (string , error ) {
909+ func formatLokiLabelsResults (result * LokiLabelsResult , format string ) (string , error ) {
828910 if len (result .Data ) == 0 {
829- return "No labels found" , nil
911+ switch format {
912+ case "json" :
913+ return "{\" message\" : \" No labels found\" }" , nil
914+ default :
915+ return "No labels found" , nil
916+ }
830917 }
831918
832- var output string
833- output = fmt .Sprintf ("Found %d labels:\n \n " , len (result .Data ))
919+ switch format {
920+ case "json" :
921+ // Return raw JSON response
922+ jsonBytes , err := json .MarshalIndent (result , "" , " " )
923+ if err != nil {
924+ return "" , fmt .Errorf ("failed to marshal JSON: %v" , err )
925+ }
926+ return string (jsonBytes ), nil
834927
835- for i , label := range result .Data {
836- output += fmt .Sprintf ("%d. %s\n " , i + 1 , label )
837- }
928+ case "raw" :
929+ // Return raw label names only, one per line
930+ var output string
931+ for _ , label := range result .Data {
932+ output += label + "\n "
933+ }
934+ return output , nil
838935
839- return output , nil
936+ case "text" :
937+ // Return formatted text with numbering (original behavior)
938+ var output string
939+ output = fmt .Sprintf ("Found %d labels:\n \n " , len (result .Data ))
940+
941+ for i , label := range result .Data {
942+ output += fmt .Sprintf ("%d. %s\n " , i + 1 , label )
943+ }
944+ return output , nil
945+
946+ default :
947+ return "" , fmt .Errorf ("unsupported format: %s. Supported formats: raw, json, text" , format )
948+ }
840949}
841950
842951// formatLokiLabelValuesResults formats the Loki label values results into a readable string
843- func formatLokiLabelValuesResults (labelName string , result * LokiLabelValuesResult ) (string , error ) {
952+ func formatLokiLabelValuesResults (labelName string , result * LokiLabelValuesResult , format string ) (string , error ) {
844953 if len (result .Data ) == 0 {
845- return fmt .Sprintf ("No values found for label '%s'" , labelName ), nil
954+ switch format {
955+ case "json" :
956+ return fmt .Sprintf ("{\" message\" : \" No values found for label '%s'\" }" , labelName ), nil
957+ default :
958+ return fmt .Sprintf ("No values found for label '%s'" , labelName ), nil
959+ }
846960 }
847961
848- var output string
849- output = fmt .Sprintf ("Found %d values for label '%s':\n \n " , len (result .Data ), labelName )
962+ switch format {
963+ case "json" :
964+ // Return raw JSON response
965+ jsonBytes , err := json .MarshalIndent (result , "" , " " )
966+ if err != nil {
967+ return "" , fmt .Errorf ("failed to marshal JSON: %v" , err )
968+ }
969+ return string (jsonBytes ), nil
850970
851- for i , value := range result .Data {
852- output += fmt .Sprintf ("%d. %s\n " , i + 1 , value )
853- }
971+ case "raw" :
972+ // Return raw label values only, one per line
973+ var output string
974+ for _ , value := range result .Data {
975+ output += value + "\n "
976+ }
977+ return output , nil
854978
855- return output , nil
979+ case "text" :
980+ // Return formatted text with numbering (original behavior)
981+ var output string
982+ output = fmt .Sprintf ("Found %d values for label '%s':\n \n " , len (result .Data ), labelName )
983+
984+ for i , value := range result .Data {
985+ output += fmt .Sprintf ("%d. %s\n " , i + 1 , value )
986+ }
987+ return output , nil
988+
989+ default :
990+ return "" , fmt .Errorf ("unsupported format: %s. Supported formats: raw, json, text" , format )
991+ }
856992}
0 commit comments