Skip to content

Commit b0d4a4d

Browse files
EPMRPP-109560 || MCP Tools. Fix analyze_items_modes parameter configuration
1 parent df821b3 commit b0d4a4d

File tree

2 files changed

+137
-6
lines changed

2 files changed

+137
-6
lines changed

internal/reportportal/launches.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func (lr *LaunchResources) toolRunAutoAnalysis() (mcp.Tool, server.ToolHandlerFu
283283
),
284284
mcp.WithString(
285285
"analyzer_mode",
286-
mcp.Description("Analyzer mode"),
286+
mcp.Description("Analyzer mode, only one of the values is allowed"),
287287
mcp.Enum(
288288
"all",
289289
"launch_name",
@@ -295,15 +295,19 @@ func (lr *LaunchResources) toolRunAutoAnalysis() (mcp.Tool, server.ToolHandlerFu
295295
mcp.Required(),
296296
),
297297
mcp.WithString("analyzer_type",
298-
mcp.Description("Analyzer type"),
298+
mcp.Description("Analyzer type, only one of the values is allowed"),
299299
mcp.Enum("autoAnalyzer", "patternAnalyzer"),
300300
mcp.DefaultString("autoAnalyzer"),
301301
mcp.Required(),
302302
),
303-
mcp.WithArray("analyzer_item_modes",
304-
mcp.Description("Analyzer item modes"),
305-
mcp.Enum("to_investigate", "auto_analyzed", "manually_analyzed"),
306-
mcp.DefaultArray([]string{"to_investigate"}),
303+
mcp.WithArray(
304+
"analyzer_item_modes",
305+
mcp.Description("Analyze items modes, one or more of the values are allowed"),
306+
mcp.WithStringEnumItems(
307+
[]string{"to_investigate", "auto_analyzed", "manually_analyzed"},
308+
),
309+
mcp.DefaultString("to_investigate"),
310+
mcp.Required(),
307311
),
308312
), lr.analytics.WithAnalytics("run_auto_analysis", func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
309313
project, err := extractProject(ctx, request)

internal/reportportal/launches_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,133 @@ func TestListLaunchesTool(t *testing.T) {
6868
assert.Equal(t, string(launches), text)
6969
}
7070

71+
// TestRunAutoAnalysisTool tests the run_auto_analysis tool to ensure:
72+
// 1. The tool schema correctly includes the "items" property for array parameters
73+
// (critical for GitHub Copilot compatibility - fixes "array type must have items" error)
74+
// 2. The enum values for analyzer_item_modes are correctly defined
75+
// 3. The tool handler correctly processes requests and calls the ReportPortal API
76+
func TestRunAutoAnalysisTool(t *testing.T) {
77+
ctx := context.Background()
78+
testProject := "test-project"
79+
launchID := 123
80+
expectedMessage := "Auto analysis started successfully"
81+
82+
// Track the request payload to verify correct parameters
83+
var capturedRequest *openapi.AnalyzeLaunchRQ
84+
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85+
assert.Equal(t, fmt.Sprintf("/api/v1/%s/launch/analyze", testProject), r.URL.Path)
86+
assert.Equal(t, http.MethodPost, r.Method)
87+
88+
// Parse request body
89+
var reqBody openapi.AnalyzeLaunchRQ
90+
err := json.NewDecoder(r.Body).Decode(&reqBody)
91+
require.NoError(t, err)
92+
capturedRequest = &reqBody
93+
94+
// Return success response - using map to match actual API response structure
95+
response := map[string]interface{}{
96+
"message": expectedMessage,
97+
}
98+
w.Header().Set("Content-Type", "application/json")
99+
w.WriteHeader(http.StatusOK)
100+
_ = json.NewEncoder(w).Encode(response)
101+
}))
102+
defer mockServer.Close()
103+
104+
srv := mcptest.NewUnstartedServer(t)
105+
106+
serverURL, _ := url.Parse(mockServer.URL)
107+
launchTools := NewLaunchResources(gorp.NewClient(serverURL, ""), nil, "")
108+
tool, handler := launchTools.toolRunAutoAnalysis()
109+
srv.AddTool(tool, handler)
110+
111+
// Verify the tool schema includes items property for array parameter
112+
// This is critical for GitHub Copilot compatibility
113+
toolSchema := tool.InputSchema
114+
require.NotNil(t, toolSchema)
115+
require.NotNil(t, toolSchema.Properties)
116+
117+
analyzerItemModesProp, exists := toolSchema.Properties["analyzer_item_modes"]
118+
require.True(t, exists, "analyzer_item_modes parameter should exist in schema")
119+
120+
// Verify it's an array type with items property (critical for GitHub Copilot compatibility)
121+
// Properties are stored as map[string]any, so we need to check the JSON schema structure
122+
propMap, ok := analyzerItemModesProp.(map[string]interface{})
123+
require.True(t, ok, "analyzer_item_modes property should be a map")
124+
require.Equal(t, "array", propMap["type"], "analyzer_item_modes should be an array type")
125+
require.NotNil(
126+
t,
127+
propMap["items"],
128+
"analyzer_item_modes must have items property for GitHub Copilot compatibility",
129+
)
130+
131+
// Verify items have enum values
132+
itemsMap, ok := propMap["items"].(map[string]interface{})
133+
require.True(t, ok, "items should be a map")
134+
require.NotNil(t, itemsMap["enum"], "items should have enum values")
135+
136+
// Enum can be stored as []interface{} or []string, handle both cases
137+
enumValue := itemsMap["enum"]
138+
var enumValues []interface{}
139+
switch v := enumValue.(type) {
140+
case []interface{}:
141+
enumValues = v
142+
case []string:
143+
enumValues = make([]interface{}, len(v))
144+
for i, s := range v {
145+
enumValues[i] = s
146+
}
147+
default:
148+
require.Fail(t, "enum should be an array", "got type: %T", enumValue)
149+
}
150+
151+
expectedEnumValues := []string{"to_investigate", "auto_analyzed", "manually_analyzed"}
152+
actualEnumValues := make([]string, len(enumValues))
153+
for i, v := range enumValues {
154+
actualEnumValues[i] = v.(string)
155+
}
156+
assert.Equal(
157+
t,
158+
expectedEnumValues,
159+
actualEnumValues,
160+
"enum values should match expected values",
161+
)
162+
163+
err := srv.Start(ctx)
164+
require.NoError(t, err)
165+
defer srv.Close()
166+
167+
client := srv.Client()
168+
169+
// Test with valid enum values
170+
var req mcp.CallToolRequest
171+
req.Params.Name = "run_auto_analysis"
172+
req.Params.Arguments = map[string]any{
173+
"project": testProject,
174+
"launch_id": launchID,
175+
"analyzer_mode": "current_launch",
176+
"analyzer_type": "autoAnalyzer",
177+
"analyzer_item_modes": []string{"to_investigate", "auto_analyzed"},
178+
}
179+
180+
result, err := client.CallTool(ctx, req)
181+
require.NoError(t, err)
182+
require.NotNil(t, result)
183+
require.Len(t, result.Content, 1)
184+
185+
var textContent mcp.TextContent
186+
require.IsType(t, textContent, result.Content[0])
187+
text := result.Content[0].(mcp.TextContent).Text
188+
assert.Equal(t, expectedMessage, text)
189+
190+
// Verify the API was called with correct parameters
191+
require.NotNil(t, capturedRequest)
192+
assert.Equal(t, int64(launchID), capturedRequest.LaunchId)
193+
assert.Equal(t, "CURRENT_LAUNCH", capturedRequest.AnalyzerMode)
194+
assert.Equal(t, "AUTOANALYZER", capturedRequest.AnalyzerTypeName)
195+
assert.Equal(t, []string{"to_investigate", "auto_analyzed"}, capturedRequest.AnalyzeItemsMode)
196+
}
197+
71198
func testLaunches() *openapi.PageLaunchResource {
72199
launches := openapi.NewPageLaunchResource()
73200
launches.SetContent([]openapi.LaunchResource{

0 commit comments

Comments
 (0)