Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions cmd/mcp-grafana/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"flag"
"fmt"
"log"
"log/slog"
"os"

"github.com/mark3labs/mcp-go/server"
Expand All @@ -29,19 +29,21 @@ func newServer() *server.MCPServer {
return s
}

func run(transport, addr string) error {
func run(transport, addr string, logLevel slog.Level) error {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel})))
s := newServer()

switch transport {
case "stdio":
srv := server.NewStdioServer(s)
srv.SetContextFunc(mcpgrafana.ComposedStdioContextFunc)
slog.Info("Starting Grafana MCP server using stdio transport")
return srv.Listen(context.Background(), os.Stdin, os.Stdout)
case "sse":
srv := server.NewSSEServer(s,
server.WithSSEContextFunc(mcpgrafana.ComposedSSEContextFunc),
)
log.Printf("SSE server listening on %s", addr)
slog.Info("Starting Grafana MCP server using SSE transport", "address", addr)
if err := srv.Start(addr); err != nil {
return fmt.Errorf("Server error: %v", err)
}
Expand All @@ -64,9 +66,18 @@ func main() {
"Transport type (stdio or sse)",
)
addr := flag.String("sse-address", "localhost:8000", "The host and port to start the sse server on")
logLevel := flag.String("log-level", "info", "Log level (debug, info, warn, error)")
flag.Parse()

if err := run(transport, *addr); err != nil {
if err := run(transport, *addr, parseLevel(*logLevel)); err != nil {
panic(err)
}
}

func parseLevel(level string) slog.Level {
var l slog.Level
if err := l.UnmarshalText([]byte(level)); err != nil {
return slog.LevelInfo
}
return l
}
33 changes: 28 additions & 5 deletions mcpgrafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mcpgrafana
import (
"context"
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -47,6 +48,11 @@ var ExtractGrafanaInfoFromEnv server.StdioContextFunc = func(ctx context.Context
if u == "" {
u = defaultGrafanaURL
}
parsedURL, err := url.Parse(u)
if err != nil {
panic(fmt.Errorf("invalid Grafana URL %s: %w", u, err))
}
slog.Info("Using Grafana configuration", "url", parsedURL.Redacted(), "api_key_set", apiKey != "")
return WithGrafanaURL(WithGrafanaAPIKey(ctx, apiKey), u)
}

Expand Down Expand Up @@ -100,22 +106,31 @@ type grafanaClientKey struct{}
var ExtractGrafanaClientFromEnv server.StdioContextFunc = func(ctx context.Context) context.Context {
cfg := client.DefaultTransportConfig()
// Extract transport config from env vars, and set it on the context.
if u, ok := os.LookupEnv(grafanaURLEnvVar); ok {
url, err := url.Parse(u)
var grafanaURL string
var parsedURL *url.URL
var ok bool
var err error
if grafanaURL, ok = os.LookupEnv(grafanaURLEnvVar); ok {
parsedURL, err = url.Parse(grafanaURL)
if err != nil {
panic(fmt.Errorf("invalid %s: %w", grafanaURLEnvVar, err))
}
cfg.Host = url.Host
cfg.Host = parsedURL.Host
// The Grafana client will always prefer HTTPS even if the URL is HTTP,
// so we need to limit the schemes to HTTP if the URL is HTTP.
if url.Scheme == "http" {
if parsedURL.Scheme == "http" {
cfg.Schemes = []string{"http"}
}
} else {
parsedURL, _ = url.Parse(defaultGrafanaURL)
}
if apiKey := os.Getenv(grafanaAPIEnvVar); apiKey != "" {

apiKey := os.Getenv(grafanaAPIEnvVar)
if apiKey != "" {
cfg.APIKey = apiKey
}

slog.Debug("Creating Grafana client", "url", parsedURL.Redacted(), "api_key_set", apiKey != "")
client := client.NewHTTPClientWithConfig(strfmt.Default, cfg)
return context.WithValue(ctx, grafanaClientKey{}, client)
}
Expand Down Expand Up @@ -161,7 +176,15 @@ type incidentClientKey struct{}

var ExtractIncidentClientFromEnv server.StdioContextFunc = func(ctx context.Context) context.Context {
grafanaURL, apiKey := urlAndAPIKeyFromEnv()
if grafanaURL == "" {
grafanaURL = defaultGrafanaURL
}
incidentURL := fmt.Sprintf("%s/api/plugins/grafana-incident-app/resources/api/v1/", grafanaURL)
parsedURL, err := url.Parse(incidentURL)
if err != nil {
panic(fmt.Errorf("invalid incident URL %s: %w", incidentURL, err))
}
slog.Debug("Creating Incident client", "url", parsedURL.Redacted(), "api_key_set", apiKey != "")
client := incident.NewClient(incidentURL, apiKey)
return context.WithValue(ctx, incidentClientKey{}, client)
}
Expand Down
Loading