mirror of
https://github.com/hanzoai/o11y-mcp.git
synced 2026-10-30 18:26:37 +00:00
The module declared itself github.com/SigNoz/signoz-mcp-server and imported the
schema migrator from github.com/SigNoz/signoz-otel-collector — a fork that
hanzoai/otel-collector superseded, where the same package is
cmd/o11yschemamigrator/schema_migrator. Both now point at the repo that is
actually maintained, at v1.3.0.
Go identifiers came with it: the client type is Client rather than SigNoz, the
import alias is o11yclient, and the env-name constants are EnvURL and
EnvAPIKey.
NOT touched, because each is a contract with something running and renaming it
breaks that thing rather than debranding it:
the 30 MCP tool names (signoz_list_alerts, signoz_get_trace_details, …) and
the signoz://dashboard/* resource URIs, which are this server's public API
and are what an agent calls;
the env vars SIGNOZ_URL and SIGNOZ_API_KEY and the SIGNOZ-API-KEY header,
which the deployment sets;
the table names — signoz_traces.distributed_signoz_index_v3, signoz_logs.*,
signoz_metrics.* — and the metric names signoz_calls_total and
signoz_latency_*, which name rows and series that exist on the server.
Those want a coordinated change on both sides, not a rename here.
go build and go vet both clean.
106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
package mcp_server
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/mark3labs/mcp-go/server"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/hanzoai/o11y-mcp/internal/config"
|
|
"github.com/hanzoai/o11y-mcp/internal/handler/tools"
|
|
"github.com/hanzoai/o11y-mcp/pkg/util"
|
|
)
|
|
|
|
type MCPServer struct {
|
|
logger *zap.Logger
|
|
handler *tools.Handler
|
|
config *config.Config
|
|
}
|
|
|
|
func NewMCPServer(log *zap.Logger, handler *tools.Handler, cfg *config.Config) *MCPServer {
|
|
return &MCPServer{logger: log, handler: handler, config: cfg}
|
|
}
|
|
|
|
func (m *MCPServer) Start() error {
|
|
s := server.NewMCPServer("SigNozMCP", "0.0.1", server.WithLogging(), server.WithToolCapabilities(false))
|
|
|
|
m.logger.Info("Starting SigNoz MCP Server",
|
|
zap.String("server_name", "SigNozMCPServer"),
|
|
zap.String("transport_mode", m.config.TransportMode))
|
|
|
|
// Register all handlers
|
|
m.handler.RegisterMetricsHandlers(s)
|
|
m.handler.RegisterAlertsHandlers(s)
|
|
m.handler.RegisterDashboardHandlers(s)
|
|
m.handler.RegisterServiceHandlers(s)
|
|
m.handler.RegisterQueryBuilderV5Handlers(s)
|
|
m.handler.RegisterLogsHandlers(s)
|
|
m.handler.RegisterTracesHandlers(s)
|
|
|
|
m.logger.Info("All handlers registered successfully")
|
|
|
|
if m.config.TransportMode == "http" {
|
|
return m.startHTTP(s)
|
|
}
|
|
return m.startStdio(s)
|
|
}
|
|
|
|
func (m *MCPServer) startStdio(s *server.MCPServer) error {
|
|
m.logger.Info("MCP Server running in stdio mode")
|
|
return server.ServeStdio(s)
|
|
}
|
|
|
|
func (m *MCPServer) authMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Extract Authorization header
|
|
authHeader := r.Header.Get("Authorization")
|
|
|
|
var apiKey string
|
|
if authHeader != "" {
|
|
// Support both "Bearer <token>" and raw token formats
|
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
|
apiKey = strings.TrimPrefix(authHeader, "Bearer ")
|
|
} else {
|
|
apiKey = authHeader
|
|
}
|
|
|
|
// Store API key in request context
|
|
ctx := util.SetAPIKey(r.Context(), apiKey)
|
|
r = r.WithContext(ctx)
|
|
|
|
m.logger.Debug("API key extracted from Authorization header")
|
|
} else if m.config.APIKey != "" {
|
|
// Fallback to config API key if no Authorization header
|
|
ctx := util.SetAPIKey(r.Context(), m.config.APIKey)
|
|
r = r.WithContext(ctx)
|
|
|
|
m.logger.Debug("Using API key from environment config")
|
|
} else {
|
|
m.logger.Warn("No API key found in Authorization header or environment")
|
|
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (m *MCPServer) startHTTP(s *server.MCPServer) error {
|
|
m.logger.Info("MCP Server running in HTTP mode")
|
|
|
|
addr := fmt.Sprintf(":%s", m.config.Port)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
httpServer := server.NewStreamableHTTPServer(s)
|
|
mux.Handle("/mcp", m.authMiddleware(httpServer))
|
|
|
|
m.logger.Info("Listening for MCP clients",
|
|
zap.String("addr", addr),
|
|
zap.String("mcp_endpoint", "/mcp"))
|
|
|
|
return http.ListenAndServe(addr, mux)
|
|
}
|