Skip to content

Commit 3cb6458

Browse files
feat(mcp): scaffold mcp-telemetry server with metrics query tool (#262)
1 parent b72038e commit 3cb6458

File tree

18 files changed

+1226
-2
lines changed

18 files changed

+1226
-2
lines changed

cmd/mcp-telemetry/main.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
"github.com/modelcontextprotocol/go-sdk/mcp"
10+
11+
"observability-hub/internal/env"
12+
"observability-hub/internal/mcp/providers"
13+
"observability-hub/internal/telemetry"
14+
)
15+
16+
const serviceName = "mcp.telemetry"
17+
18+
func main() {
19+
ctx, cancel := context.WithCancel(context.Background())
20+
defer cancel()
21+
22+
env.Load()
23+
24+
if shutdown, err := telemetry.Init(ctx, serviceName); err != nil {
25+
telemetry.Error("failed to init telemetry", "error", err)
26+
} else {
27+
defer shutdown()
28+
}
29+
30+
thanosURL := os.Getenv("THANOS_URL")
31+
lokiURL := os.Getenv("LOKI_URL")
32+
tempoURL := os.Getenv("TEMPO_URL")
33+
34+
if thanosURL == "" {
35+
telemetry.Error("THANOS_URL not set")
36+
os.Exit(1)
37+
}
38+
39+
provider := providers.NewTelemetryProvider(thanosURL)
40+
telemetry.Info("MCP Telemetry server initialized", "thanos_url", thanosURL, "loki_url", lokiURL, "tempo_url", tempoURL)
41+
42+
server := mcp.NewServer(&mcp.Implementation{
43+
Name: "mcp-telemetry",
44+
Version: "1.0.0",
45+
}, nil)
46+
47+
registerTools(server, provider)
48+
49+
sigChan := make(chan os.Signal, 1)
50+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
51+
52+
go func() {
53+
t := &mcp.StdioTransport{}
54+
if err := server.Run(ctx, t); err != nil {
55+
telemetry.Error("MCP server stopped", "error", err)
56+
}
57+
}()
58+
59+
telemetry.Info("mcp-telemetry ready", "tools", []string{"query_metrics", "query_logs", "query_traces"})
60+
61+
sig := <-sigChan
62+
telemetry.Info("received signal, shutting down", "signal", sig.String())
63+
cancel()
64+
provider.Close()
65+
}

cmd/mcp-telemetry/tools.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/modelcontextprotocol/go-sdk/mcp"
8+
9+
"observability-hub/internal/mcp/providers"
10+
"observability-hub/internal/mcp/tools"
11+
"observability-hub/internal/telemetry"
12+
)
13+
14+
func registerTools(server *mcp.Server, provider *providers.TelemetryProvider) {
15+
mcp.AddTool(server, &mcp.Tool{
16+
Name: "query_metrics",
17+
Description: "Execute PromQL queries against Thanos/Prometheus for metrics analysis",
18+
}, handleQueryMetrics(provider))
19+
20+
mcp.AddTool(server, &mcp.Tool{
21+
Name: "query_logs",
22+
Description: "Execute LogQL queries against Loki for log analysis",
23+
}, handleQueryLogs(provider))
24+
25+
mcp.AddTool(server, &mcp.Tool{
26+
Name: "query_traces",
27+
Description: "Retrieve distributed traces from Tempo by trace ID",
28+
}, handleQueryTraces(provider))
29+
30+
telemetry.Info("registered tools", "count", 3)
31+
}
32+
33+
func handleQueryMetrics(provider *providers.TelemetryProvider) mcp.ToolHandlerFor[tools.QueryMetricsInput, any] {
34+
handler := tools.NewQueryMetricsHandler(provider.QueryMetrics)
35+
return func(ctx context.Context, _ *mcp.CallToolRequest, input tools.QueryMetricsInput) (*mcp.CallToolResult, any, error) {
36+
result, err := handler.Execute(ctx, input)
37+
if err != nil {
38+
return nil, nil, err
39+
}
40+
text, _ := json.Marshal(result)
41+
return &mcp.CallToolResult{
42+
Content: []mcp.Content{&mcp.TextContent{Text: string(text)}},
43+
}, nil, nil
44+
}
45+
}
46+
47+
func handleQueryLogs(provider *providers.TelemetryProvider) mcp.ToolHandlerFor[tools.QueryLogsInput, any] {
48+
handler := tools.NewQueryLogsHandler(provider.QueryLogs)
49+
return func(ctx context.Context, _ *mcp.CallToolRequest, input tools.QueryLogsInput) (*mcp.CallToolResult, any, error) {
50+
result, err := handler.Execute(ctx, input)
51+
if err != nil {
52+
return nil, nil, err
53+
}
54+
text, _ := json.Marshal(result)
55+
return &mcp.CallToolResult{
56+
Content: []mcp.Content{&mcp.TextContent{Text: string(text)}},
57+
}, nil, nil
58+
}
59+
}
60+
61+
func handleQueryTraces(provider *providers.TelemetryProvider) mcp.ToolHandlerFor[tools.QueryTracesInput, any] {
62+
handler := tools.NewQueryTracesHandler(provider.QueryTraces)
63+
return func(ctx context.Context, _ *mcp.CallToolRequest, input tools.QueryTracesInput) (*mcp.CallToolResult, any, error) {
64+
result, err := handler.Execute(ctx, input)
65+
if err != nil {
66+
return nil, nil, err
67+
}
68+
text, _ := json.Marshal(result)
69+
return &mcp.CallToolResult{
70+
Content: []mcp.Content{&mcp.TextContent{Text: string(text)}},
71+
}, nil, nil
72+
}
73+
}

0 commit comments

Comments
 (0)