Skip to content
Open
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
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,26 @@ test-python-e2e: ## Run Python E2E tests (requires docker-compose services and S
run: ## Run the MCP server in stdio mode.
go run ./cmd/mcp-grafana

.PHONY: run-local
run-local: ## Run the MCP server in stdio mode without grafana config
go run ./cmd/mcp-grafana -log-level=debug -use-grafana-config=false

.PHONY: run-sse
run-sse: ## Run the MCP server in SSE mode.
go run ./cmd/mcp-grafana --transport sse --log-level debug --debug

.PHONY: run-sse-local
run-sse-local: ## Run the MCP server in SSE mode without grafana config.
go run ./cmd/mcp-grafana -transport sse --log-level debug -use-grafana-config=false

PHONY: run-streamable-http
run-streamable-http: ## Run the MCP server in StreamableHTTP mode.
go run ./cmd/mcp-grafana --transport streamable-http --log-level debug --debug

PHONY: run-streamable-http-local
run-streamable-http-local: ## Run the MCP server in StreamableHTTP mode without grafana config.
go run ./cmd/mcp-grafana --transport streamable-http --log-level debug -use-grafana-config=false

.PHONY: run-test-services
run-test-services: ## Run the docker-compose services required for the unit and integration tests.
docker compose up -d --build
61 changes: 44 additions & 17 deletions cmd/mcp-grafana/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,30 +102,53 @@ func newServer(dt disabledTools) *server.MCPServer {
return s
}

func run(transport, addr, basePath, endpointPath string, logLevel slog.Level, dt disabledTools, gc mcpgrafana.GrafanaConfig) error {
func run(transport, addr, basePath, endpointPath string, logLevel slog.Level, dt disabledTools, gc *mcpgrafana.GrafanaConfig) error {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel})))
s := newServer(dt)

if gc == nil {
slog.Info("Skipping grafana config")
}

switch transport {
case "stdio":
srv := server.NewStdioServer(s)
srv.SetContextFunc(mcpgrafana.ComposedStdioContextFunc(gc))
if gc != nil {
srv.SetContextFunc(mcpgrafana.ComposedStdioContextFunc(gc))
} else {
srv.SetContextFunc(mcpgrafana.ComposeStdioContextFuncs(mcpgrafana.ExtractStdioLocalInfoFromEnv))
}
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(gc)),
server.WithStaticBasePath(basePath),
)
opts := []server.SSEOption{server.WithStaticBasePath(basePath)}
if gc != nil {
opts = append(opts, server.WithSSEContextFunc(mcpgrafana.ComposedSSEContextFunc(gc)))
} else {
opts = append(opts, server.WithSSEContextFunc(mcpgrafana.ComposeSSEContextFuncs(
mcpgrafana.ExtractHttpLocalInfoFromEnv,
mcpgrafana.ExtractAuthorizationFromHeaders,
)))
}
srv := server.NewSSEServer(s, opts...)
slog.Info("Starting Grafana MCP server using SSE transport", "address", addr, "basePath", basePath)
if err := srv.Start(addr); err != nil {
return fmt.Errorf("Server error: %v", err)
}
case "streamable-http":
srv := server.NewStreamableHTTPServer(s, server.WithHTTPContextFunc(mcpgrafana.ComposedHTTPContextFunc(gc)),
opts := []server.StreamableHTTPOption{
server.WithStateLess(true),
server.WithEndpointPath(endpointPath),
)
}
if gc != nil {
opts = append(opts, server.WithHTTPContextFunc(mcpgrafana.ComposedHTTPContextFunc(gc)))
} else {
opts = append(opts, server.WithHTTPContextFunc(mcpgrafana.ComposeHTTPContextFuncs(
mcpgrafana.ExtractHttpLocalInfoFromEnv,
mcpgrafana.ExtractAuthorizationFromHeaders,
)))
}
srv := server.NewStreamableHTTPServer(s, opts...)
slog.Info("Starting Grafana MCP server using StreamableHTTP transport", "address", addr, "endpointPath", endpointPath)
if err := srv.Start(addr); err != nil {
return fmt.Errorf("Server error: %v", err)
Expand All @@ -152,24 +175,28 @@ func main() {
basePath := flag.String("base-path", "", "Base path for the sse server")
endpointPath := flag.String("endpoint-path", "/mcp", "Endpoint path for the streamable-http server")
logLevel := flag.String("log-level", "info", "Log level (debug, info, warn, error)")
useGrafanaConfig := flag.Bool("use-grafana-config", true, "Use grafana config")
var dt disabledTools
dt.addFlags()
var gc grafanaConfig
gc.addFlags()
flag.Parse()

// Convert local grafanaConfig to mcpgrafana.GrafanaConfig
grafanaConfig := mcpgrafana.GrafanaConfig{Debug: gc.debug}
if gc.tlsCertFile != "" || gc.tlsKeyFile != "" || gc.tlsCAFile != "" || gc.tlsSkipVerify {
grafanaConfig.TLSConfig = &mcpgrafana.TLSConfig{
CertFile: gc.tlsCertFile,
KeyFile: gc.tlsKeyFile,
CAFile: gc.tlsCAFile,
SkipVerify: gc.tlsSkipVerify,
var config *mcpgrafana.GrafanaConfig
if *useGrafanaConfig {
// Convert local grafanaConfig to mcpgrafana.GrafanaConfig
config = &mcpgrafana.GrafanaConfig{Debug: gc.debug}
if gc.tlsCertFile != "" || gc.tlsKeyFile != "" || gc.tlsCAFile != "" || gc.tlsSkipVerify {
config.TLSConfig = &mcpgrafana.TLSConfig{
CertFile: gc.tlsCertFile,
KeyFile: gc.tlsKeyFile,
CAFile: gc.tlsCAFile,
SkipVerify: gc.tlsSkipVerify,
}
}
}

if err := run(transport, *addr, *basePath, *endpointPath, parseLevel(*logLevel), dt, grafanaConfig); err != nil {
if err := run(transport, *addr, *basePath, *endpointPath, parseLevel(*logLevel), dt, config); err != nil {
panic(err)
}
}
Expand Down
42 changes: 42 additions & 0 deletions examples/k8s/k8s-mcp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
kind: Pod
apiVersion: v1
metadata:
name: k8s-mcp
namespace: my-app
labels:
app: k8s-mcp
spec:
restartPolicy: Always
containers:
- name: mcp-grafana
image: 'quay.io/grafana/mcp-grafana:dev'
command: ["/app/mcp-grafana"]
args:
- '-transport=streamable-http'
- '-address=localhost:8000'
- '-endpoint-path=/mcp'
- '-use-grafana-config=false'
- '-enabled-tools=prometheus,loki'
- '-log-level=debug'
env:
- name: LOKI_URL
value: http://loki.my-app.svc:3100
- name: PROM_URL
value: http://prom.my-app.svc:9091
---
kind: Service
apiVersion: v1
metadata:
name: k8s-mcp
namespace: my-app
labels:
app: k8s-mcp
spec:
ports:
- protocol: TCP
port: 8000
targetPort: 8000
internalTrafficPolicy: Cluster
type: ClusterIP
selector:
app: k8s-mcp
10 changes: 5 additions & 5 deletions examples/tls_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func basicTLSExample() {
}

// Create a context function that includes TLS configuration
contextFunc := mcpgrafana.ComposedStdioContextFunc(grafanaConfig)
contextFunc := mcpgrafana.ComposedStdioContextFunc(&grafanaConfig)

// Test the context function
ctx := contextFunc(context.Background())
Expand Down Expand Up @@ -85,9 +85,9 @@ func fullTLSExample() {
fmt.Printf(" - Debug mode: %v\n", grafanaConfig.Debug)

// Create context functions for different transport types
stdioFunc := mcpgrafana.ComposedStdioContextFunc(grafanaConfig)
sseFunc := mcpgrafana.ComposedSSEContextFunc(grafanaConfig)
httpFunc := mcpgrafana.ComposedHTTPContextFunc(grafanaConfig)
stdioFunc := mcpgrafana.ComposedStdioContextFunc(&grafanaConfig)
sseFunc := mcpgrafana.ComposedSSEContextFunc(&grafanaConfig)
httpFunc := mcpgrafana.ComposedHTTPContextFunc(&grafanaConfig)

fmt.Printf("✓ Context functions created for all transport types\n")

Expand Down Expand Up @@ -154,7 +154,7 @@ func runServerWithTLS() {

// Create stdio server with TLS-enabled context function
srv := server.NewStdioServer(s)
srv.SetContextFunc(mcpgrafana.ComposedStdioContextFunc(grafanaConfig))
srv.SetContextFunc(mcpgrafana.ComposedStdioContextFunc(&grafanaConfig))

fmt.Printf("Starting MCP Grafana server with TLS support...\n")
fmt.Printf("Grafana URL: %s\n", os.Getenv("GRAFANA_URL"))
Expand Down
Loading