You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by "CodePrometheus (via GitHub)" <gi...@apache.org> on 2025/07/07 15:35:21 UTC

[PR] add support for MQE [skywalking-mcp]

CodePrometheus opened a new pull request, #9:
URL: https://github.com/apache/skywalking-mcp/pull/9

   Adds MQE (Metrics Query Expression) support with three tools:
   - `execute_mqe_expression` Execute MQE queries
   - `list_mqe_metrics` List available metrics with filtering
   - `get_mqe_metric_type` Get metric type information


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "CodePrometheus (via GitHub)" <gi...@apache.org>.
CodePrometheus commented on PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#issuecomment-3049383619

   > As MQE has its syntax tree, is that possible to make LLM understand it and build mqe from raw text?
   
   I'm considering whether it's possible to make LLMs understand the rules of `MQEParser.g4` and `MQELexer.g4` just by using some prompts, or maybe there are other ways(rag?). Anyway, let me spend some time exploring this.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "CodePrometheus (via GitHub)" <gi...@apache.org>.
CodePrometheus commented on PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#issuecomment-3064795327

   > Is the internal md file for AI prompt? I saw that file in the source folder.
   
   yes, I add a lot of prompts to help build query statements.
   
   <img width="1221" height="733" alt="Warp 2025-07-12 14 57 29" src="https://github.com/user-attachments/assets/e383ab36-3b5f-42d3-b8aa-eb7edb6222d6" />
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "kezhenxu94 (via GitHub)" <gi...@apache.org>.
kezhenxu94 merged PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng commented on PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#issuecomment-3045695772

   As MQE has its syntax tree, is that possible to make LLM understand it and build mqe from raw text?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng commented on PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#issuecomment-3049760338

   Yes, I think g4 files are valid inputs.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#discussion_r2190424939


##########
internal/tools/mqe.go:
##########
@@ -0,0 +1,595 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tools
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+	"github.com/spf13/viper"
+	api "skywalking.apache.org/repo/goapi/query"
+)
+
+// AddMQETools registers MQE-related tools with the MCP server
+func AddMQETools(mcp *server.MCPServer) {
+	MQEExpressionTool.Register(mcp)
+	MQEMetricsListTool.Register(mcp)
+	MQEMetricsTypeTool.Register(mcp)
+}
+
+// GraphQLRequest represents a GraphQL request
+type GraphQLRequest struct {
+	Query     string                 `json:"query"`
+	Variables map[string]interface{} `json:"variables,omitempty"`
+}
+
+// GraphQLResponse represents a GraphQL response
+type GraphQLResponse struct {
+	Data   interface{} `json:"data"`
+	Errors []struct {
+		Message string `json:"message"`
+	} `json:"errors,omitempty"`
+}
+
+// executeGraphQL executes a GraphQL query against SkyWalking OAP
+func executeGraphQL(ctx context.Context, query string, variables map[string]interface{}) (*GraphQLResponse, error) {
+	url := FinalizeURL(viper.GetString("url"))
+
+	reqBody := GraphQLRequest{
+		Query:     query,
+		Variables: variables,
+	}
+
+	jsonData, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal GraphQL request: %w", err)
+	}
+
+	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
+	if err != nil {
+		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+
+	client := &http.Client{Timeout: 30 * time.Second}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, _ := io.ReadAll(resp.Body)
+		return nil, fmt.Errorf("HTTP request failed with status: %d, body: %s", resp.StatusCode, string(bodyBytes))
+	}
+
+	var graphqlResp GraphQLResponse
+	if err := json.NewDecoder(resp.Body).Decode(&graphqlResp); err != nil {
+		return nil, fmt.Errorf("failed to decode GraphQL response: %w", err)
+	}
+
+	if len(graphqlResp.Errors) > 0 {
+		var errorMsgs []string
+		for _, err := range graphqlResp.Errors {
+			errorMsgs = append(errorMsgs, err.Message)
+		}
+		return nil, fmt.Errorf("GraphQL errors: %s", strings.Join(errorMsgs, ", "))
+	}
+
+	return &graphqlResp, nil
+}
+
+// MQEExpressionRequest represents a request to execute MQE expression
+type MQEExpressionRequest struct {
+	Expression              string `json:"expression"`
+	ServiceName             string `json:"service_name,omitempty"`
+	Layer                   string `json:"layer,omitempty"`
+	ServiceInstanceName     string `json:"service_instance_name,omitempty"`
+	EndpointName            string `json:"endpoint_name,omitempty"`
+	ProcessName             string `json:"process_name,omitempty"`
+	Normal                  *bool  `json:"normal,omitempty"`
+	DestServiceName         string `json:"dest_service_name,omitempty"`
+	DestLayer               string `json:"dest_layer,omitempty"`
+	DestServiceInstanceName string `json:"dest_service_instance_name,omitempty"`
+	DestEndpointName        string `json:"dest_endpoint_name,omitempty"`
+	DestProcessName         string `json:"dest_process_name,omitempty"`
+	DestNormal              *bool  `json:"dest_normal,omitempty"`
+	Duration                string `json:"duration,omitempty"`
+	Start                   string `json:"start,omitempty"`
+	End                     string `json:"end,omitempty"`
+	Step                    string `json:"step,omitempty"`
+	Cold                    bool   `json:"cold,omitempty"`
+	Debug                   bool   `json:"debug,omitempty"`
+	DumpDBRsp               bool   `json:"dump_db_rsp,omitempty"`
+}
+
+// MQEMetricsListRequest represents a request to list available metrics
+type MQEMetricsListRequest struct {
+	Regex string `json:"regex,omitempty"`
+}
+
+// MQEMetricsTypeRequest represents a request to get metric type
+type MQEMetricsTypeRequest struct {
+	MetricName string `json:"metric_name"`
+}
+
+// ListServicesRequest represents a request to list services
+type ListServicesRequest struct {
+	Layer string `json:"layer"`
+}
+
+// getServiceInfo queries service information using the specified layer
+func getServiceInfo(ctx context.Context, serviceName, layer string) bool {
+	if serviceName == "" {
+		return false
+	}
+
+	if layer == "" {
+		layer = "GENERAL"
+	}
+
+	normal, err := getServiceByName(ctx, serviceName, layer)
+	if err != nil {
+		return true
+	}
+	if normal != nil {
+		return *normal
+	}
+
+	return true
+}
+
+// getServiceByName tries to get service info directly by name in specified layer
+func getServiceByName(ctx context.Context, serviceName, layer string) (*bool, error) {
+	serviceID, err := findServiceID(ctx, serviceName, layer)
+	if err != nil {
+		return nil, fmt.Errorf("service not found in layer %s: %s", layer, serviceName)
+	}
+	if serviceID == "" {
+		return nil, fmt.Errorf("service not found in layer %s: %s", layer, serviceName)
+	}
+
+	query := `
+		query getService($serviceId: String!) {
+			service: getService(serviceId: $serviceId) {
+				id
+				name
+				normal
+				layers
+			}
+		}
+	`
+
+	variables := map[string]interface{}{
+		"serviceId": serviceID,
+	}
+
+	result, err := executeGraphQL(ctx, query, variables)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get service details: %w", err)
+	}
+
+	if data, ok := result.Data.(map[string]interface{}); ok {
+		if service, ok := data["service"].(map[string]interface{}); ok {
+			if normal, ok := service["normal"].(bool); ok {
+				return &normal, nil
+			}
+		}
+	}
+
+	return nil, fmt.Errorf("invalid service data returned for: %s", serviceName)
+}
+
+// findServiceID finds service ID by name in a specific layer
+func findServiceID(ctx context.Context, serviceName, layer string) (string, error) {
+	query := `
+		query getServices($layer: String!) {
+			services: listServices(layer: $layer) {
+				id
+				name
+			}
+		}
+	`
+
+	variables := map[string]interface{}{
+		"layer": layer,
+	}
+
+	result, err := executeGraphQL(ctx, query, variables)
+	if err != nil {
+		return "", err
+	}
+
+	if data, ok := result.Data.(map[string]interface{}); ok {
+		if services, ok := data["services"].([]interface{}); ok {
+			for _, s := range services {
+				svc, ok := s.(map[string]interface{})
+				if !ok {
+					continue
+				}
+				if svc["name"] == serviceName {
+					if id, ok := svc["id"].(string); ok {
+						return id, nil
+					}
+				}
+			}
+		}
+	}
+
+	return "", nil
+}
+
+// buildMQEEntity builds the entity from request parameters
+func buildMQEEntity(ctx context.Context, req *MQEExpressionRequest) map[string]interface{} {
+	entity := make(map[string]interface{})
+
+	if req.ServiceName != "" {
+		entity["serviceName"] = req.ServiceName
+
+		if req.Normal == nil {
+			normal := getServiceInfo(ctx, req.ServiceName, req.Layer)
+			entity["normal"] = normal
+		} else {
+			entity["normal"] = *req.Normal
+		}
+	}
+
+	if req.ServiceInstanceName != "" {
+		entity["serviceInstanceName"] = req.ServiceInstanceName
+	}
+	if req.EndpointName != "" {
+		entity["endpointName"] = req.EndpointName
+	}
+	if req.ProcessName != "" {
+		entity["processName"] = req.ProcessName
+	}
+	if req.DestServiceName != "" {
+		entity["destServiceName"] = req.DestServiceName
+	}
+	if req.DestServiceInstanceName != "" {
+		entity["destServiceInstanceName"] = req.DestServiceInstanceName
+	}
+	if req.DestEndpointName != "" {
+		entity["destEndpointName"] = req.DestEndpointName
+	}
+	if req.DestProcessName != "" {
+		entity["destProcessName"] = req.DestProcessName
+	}
+
+	if req.ServiceName == "" && req.Normal != nil {
+		entity["normal"] = *req.Normal
+	}

Review Comment:
   [nitpick] buildMQEEntity contains many nearly identical blocks for each request field. Refactor by mapping field names to struct values or using helper functions to reduce duplication and improve readability.
   ```suggestion
   	// Define a mapping of field names to their corresponding values
   	fields := map[string]interface{}{
   		"serviceName":             req.ServiceName,
   		"serviceInstanceName":     req.ServiceInstanceName,
   		"endpointName":            req.EndpointName,
   		"processName":             req.ProcessName,
   		"destServiceName":         req.DestServiceName,
   		"destServiceInstanceName": req.DestServiceInstanceName,
   		"destEndpointName":        req.DestEndpointName,
   		"destProcessName":         req.DestProcessName,
   	}
   
   	// Populate the entity map based on the mapping
   	for key, value := range fields {
   		if strValue, ok := value.(string); ok && strValue != "" {
   			entity[key] = strValue
   		}
   	}
   
   	// Handle special cases
   	if req.ServiceName != "" {
   		if req.Normal == nil {
   			normal := getServiceInfo(ctx, req.ServiceName, req.Layer)
   			entity["normal"] = normal
   		} else {
   			entity["normal"] = *req.Normal
   		}
   	} else if req.Normal != nil {
   		entity["normal"] = *req.Normal
   	}
   ```



##########
internal/tools/mqe.go:
##########
@@ -0,0 +1,595 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tools
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+	"github.com/spf13/viper"
+	api "skywalking.apache.org/repo/goapi/query"
+)
+
+// AddMQETools registers MQE-related tools with the MCP server
+func AddMQETools(mcp *server.MCPServer) {
+	MQEExpressionTool.Register(mcp)
+	MQEMetricsListTool.Register(mcp)
+	MQEMetricsTypeTool.Register(mcp)
+}
+
+// GraphQLRequest represents a GraphQL request
+type GraphQLRequest struct {
+	Query     string                 `json:"query"`
+	Variables map[string]interface{} `json:"variables,omitempty"`
+}
+
+// GraphQLResponse represents a GraphQL response
+type GraphQLResponse struct {
+	Data   interface{} `json:"data"`
+	Errors []struct {
+		Message string `json:"message"`
+	} `json:"errors,omitempty"`
+}
+
+// executeGraphQL executes a GraphQL query against SkyWalking OAP
+func executeGraphQL(ctx context.Context, query string, variables map[string]interface{}) (*GraphQLResponse, error) {
+	url := FinalizeURL(viper.GetString("url"))

Review Comment:
   executeGraphQL always uses viper.GetString("url"), ignoring dynamically passed SkyWalking URL context. Consider passing the URL through the request context or as a function parameter to respect the URL extracted from headers or environment.
   ```suggestion
   func executeGraphQL(ctx context.Context, url string, query string, variables map[string]interface{}) (*GraphQLResponse, error) {
   	url = FinalizeURL(url)
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] add support for MQE [skywalking-mcp]

Posted by "wu-sheng (via GitHub)" <gi...@apache.org>.
wu-sheng commented on PR #9:
URL: https://github.com/apache/skywalking-mcp/pull/9#issuecomment-3064792473

   Is the internal md file for AI prompt? I saw that file in the source folder.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@skywalking.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org