@performance-agent/mcp-server 1.0.1 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @performance-agent/mcp-server might be problematic. Click here for more details.
- package/README.md +400 -25
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +783 -47
- package/dist/index.js.map +1 -1
- package/dist/prompts/jmx_prompt.txt +262 -0
- package/dist/prompts/prompts/jmx_prompt.txt +262 -0
- package/dist/tools/azure-infra/config-parser.d.ts +14 -0
- package/dist/tools/azure-infra/config-parser.d.ts.map +1 -0
- package/dist/tools/azure-infra/config-parser.js +361 -0
- package/dist/tools/azure-infra/config-parser.js.map +1 -0
- package/dist/tools/azure-infra/index.d.ts +52 -0
- package/dist/tools/azure-infra/index.d.ts.map +1 -0
- package/dist/tools/azure-infra/index.js +448 -0
- package/dist/tools/azure-infra/index.js.map +1 -0
- package/dist/tools/azure-infra/terraform-generator.d.ts +19 -0
- package/dist/tools/azure-infra/terraform-generator.d.ts.map +1 -0
- package/dist/tools/azure-infra/terraform-generator.js +458 -0
- package/dist/tools/azure-infra/terraform-generator.js.map +1 -0
- package/dist/tools/azure-infra/types.d.ts +123 -0
- package/dist/tools/azure-infra/types.d.ts.map +1 -0
- package/dist/tools/azure-infra/types.js +95 -0
- package/dist/tools/azure-infra/types.js.map +1 -0
- package/dist/tools/cloud-executor/index.d.ts +78 -0
- package/dist/tools/cloud-executor/index.d.ts.map +1 -0
- package/dist/tools/cloud-executor/index.js +528 -0
- package/dist/tools/cloud-executor/index.js.map +1 -0
- package/dist/tools/cloud-executor/result-analyzer.d.ts +65 -0
- package/dist/tools/cloud-executor/result-analyzer.d.ts.map +1 -0
- package/dist/tools/cloud-executor/result-analyzer.js +367 -0
- package/dist/tools/cloud-executor/result-analyzer.js.map +1 -0
- package/dist/tools/cloud-executor/vm-metrics.d.ts +87 -0
- package/dist/tools/cloud-executor/vm-metrics.d.ts.map +1 -0
- package/dist/tools/cloud-executor/vm-metrics.js +506 -0
- package/dist/tools/cloud-executor/vm-metrics.js.map +1 -0
- package/dist/tools/jmx-generator/index.d.ts +20 -0
- package/dist/tools/jmx-generator/index.d.ts.map +1 -0
- package/dist/tools/jmx-generator/index.js +115 -0
- package/dist/tools/jmx-generator/index.js.map +1 -0
- package/dist/tools/jmx-generator/sanitizer.d.ts +19 -0
- package/dist/tools/jmx-generator/sanitizer.d.ts.map +1 -0
- package/dist/tools/jmx-generator/sanitizer.js +166 -0
- package/dist/tools/jmx-generator/sanitizer.js.map +1 -0
- package/package.json +5 -2
- package/scripts/copy-prompts.js +46 -0
package/dist/index.js
CHANGED
|
@@ -2,61 +2,43 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Performance Agent MCP Server
|
|
4
4
|
*
|
|
5
|
-
* This MCP server provides
|
|
5
|
+
* This MCP server provides 12 core tools:
|
|
6
6
|
* 1. Generate JMX scripts from Swagger/OpenAPI specifications
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
7
|
+
* 2. Prepare JMX for testing (workflow tool)
|
|
8
|
+
* 3. Analyze code complexity in local git repositories
|
|
9
|
+
* 4. Profile function execution time and identify slow functions
|
|
10
|
+
* 5. Provision Azure infrastructure (VMs) from natural language descriptions
|
|
11
|
+
* 6. List Azure subscriptions for user selection
|
|
12
|
+
* 7. Set active Azure subscription
|
|
13
|
+
* 8. List Azure resource groups for reuse
|
|
14
|
+
* 9. Run end-to-end cloud performance tests
|
|
15
|
+
* 10. Get VM size recommendations for JMX scripts
|
|
16
|
+
* 11. Cleanup Azure test infrastructure (Terraform destroy)
|
|
17
|
+
* 12. Analyze test results with VM metrics
|
|
18
|
+
* 13. Get Azure VM performance metrics (CPU, Memory, Network, Disk)
|
|
9
19
|
*
|
|
10
|
-
* Focused on local development and
|
|
20
|
+
* Focused on local development, code quality, cloud performance testing, and infrastructure monitoring.
|
|
11
21
|
*/
|
|
12
22
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
23
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
24
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
-
import axios from 'axios';
|
|
16
25
|
import * as dotenv from 'dotenv';
|
|
26
|
+
import * as fs from 'fs';
|
|
27
|
+
import * as path from 'path';
|
|
17
28
|
import { analyzeCodeComplexity } from './tools/code-analyzer/index.js';
|
|
18
29
|
import { profileFunctionPerformance } from './tools/function-profiler/index.js';
|
|
30
|
+
import { provisionAzureVM, checkPrerequisites, getVMSizePresets, listAzureSubscriptions, setAzureSubscription, listResourceGroups, } from './tools/azure-infra/index.js';
|
|
31
|
+
import { runCloudPerformanceTest, getVMRecommendation, cleanupAzureResources, analyzeTestResults, getVMMetrics, } from './tools/cloud-executor/index.js';
|
|
32
|
+
import { generateJMX } from './tools/jmx-generator/index.js';
|
|
19
33
|
// Load environment variables
|
|
20
34
|
dotenv.config();
|
|
21
|
-
// Configuration
|
|
22
|
-
const API_BASE_URL = process.env.PERFORMANCE_AGENT_API_URL || 'http://localhost:8000';
|
|
23
|
-
const API_KEY = process.env.PERFORMANCE_AGENT_API_KEY || '';
|
|
24
|
-
/**
|
|
25
|
-
* Performance Agent API Client - Minimal client for JMX generation
|
|
26
|
-
*/
|
|
27
|
-
class PerformanceAgentClient {
|
|
28
|
-
client;
|
|
29
|
-
constructor(baseURL, apiKey) {
|
|
30
|
-
this.client = axios.create({
|
|
31
|
-
baseURL,
|
|
32
|
-
headers: {
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
...(apiKey && { 'Authorization': `Bearer ${apiKey}` }),
|
|
35
|
-
},
|
|
36
|
-
timeout: 300000, // 5 minutes
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Generate JMX script from Swagger content
|
|
41
|
-
*/
|
|
42
|
-
async generateFromSwagger(repoName, swaggerContent) {
|
|
43
|
-
const response = await this.client.post('/api/orchestrator/execute', {
|
|
44
|
-
action: 'generate_from_swagger',
|
|
45
|
-
payload: {
|
|
46
|
-
repo_name: repoName,
|
|
47
|
-
swagger_content: swaggerContent,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
return response.data;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
35
|
/**
|
|
54
36
|
* Define MCP Tools - Simplified to 2 core features
|
|
55
37
|
*/
|
|
56
38
|
const TOOLS = [
|
|
57
39
|
{
|
|
58
40
|
name: 'generateJMXFromSwagger',
|
|
59
|
-
description: 'Generate JMeter (JMX) performance test script from Swagger/OpenAPI specification. Analyzes API endpoints and creates comprehensive load test scenarios.',
|
|
41
|
+
description: 'Generate JMeter (JMX) performance test script from Swagger/OpenAPI specification. Analyzes API endpoints and creates comprehensive load test scenarios. **IMPORTANT**: Always use this to regenerate JMX if you have updated the Swagger/OpenAPI spec, changed load parameters, or modified API definitions.',
|
|
60
42
|
inputSchema: {
|
|
61
43
|
type: 'object',
|
|
62
44
|
properties: {
|
|
@@ -73,6 +55,38 @@ const TOOLS = [
|
|
|
73
55
|
required: ['swagger_content'],
|
|
74
56
|
},
|
|
75
57
|
},
|
|
58
|
+
{
|
|
59
|
+
name: 'prepareJMXForTest',
|
|
60
|
+
description: 'WORKFLOW TOOL: Prepare JMX script for performance testing. This tool guides you through checking if JMX exists, optionally regenerating it from Swagger, analyzing load requirements, and getting VM size recommendations. **USE THIS FIRST** before running cloud performance tests to ensure JMX is current and properly analyzed.',
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
jmx_file_path: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Path to existing JMX file to analyze',
|
|
67
|
+
},
|
|
68
|
+
swagger_file_path: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Path to Swagger/OpenAPI file if user wants to regenerate JMX',
|
|
71
|
+
},
|
|
72
|
+
regenerate_jmx: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
description: 'Set to true if user wants to regenerate JMX from Swagger (e.g., after API changes, load changes)',
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
swagger_content: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
description: 'Swagger/OpenAPI JSON content if regenerating JMX',
|
|
80
|
+
additionalProperties: true,
|
|
81
|
+
},
|
|
82
|
+
repo_name: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Repository name for JMX generation',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['jmx_file_path'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
76
90
|
{
|
|
77
91
|
name: 'analyzeCodeComplexity',
|
|
78
92
|
description: 'Analyze cyclomatic complexity of code changes in local git repository. Helps identify overly complex functions that may have performance or maintainability issues. Supports Python, JavaScript, and TypeScript.',
|
|
@@ -146,6 +160,282 @@ const TOOLS = [
|
|
|
146
160
|
required: ['repo_path'],
|
|
147
161
|
},
|
|
148
162
|
},
|
|
163
|
+
{
|
|
164
|
+
name: 'provisionAzureVM',
|
|
165
|
+
description: 'Provision Azure Virtual Machines from natural language descriptions or structured configuration. Supports VM size presets (small/medium/large/gpu), OS selection (Ubuntu, Windows, RHEL), networking, and generates Terraform or Azure CLI commands.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
description: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'Natural language description of the VM you want. Example: "Create a large Ubuntu VM in East US with 4 cores, 16GB RAM, public IP, and ports 22, 80, 443 open for a web server"',
|
|
172
|
+
},
|
|
173
|
+
config: {
|
|
174
|
+
type: 'object',
|
|
175
|
+
description: 'Structured VM configuration (optional, can be used instead of or with description)',
|
|
176
|
+
properties: {
|
|
177
|
+
name: { type: 'string', description: 'VM name' },
|
|
178
|
+
resourceGroup: { type: 'string', description: 'Azure resource group name' },
|
|
179
|
+
subscriptionId: { type: 'string', description: 'Azure subscription ID (if user selected a specific subscription)' },
|
|
180
|
+
useExistingResourceGroup: { type: 'boolean', description: 'Whether to use an existing resource group instead of creating new (default: false)' },
|
|
181
|
+
sizePreset: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
enum: ['small', 'medium', 'large', 'xlarge', 'compute-optimized', 'memory-optimized', 'gpu'],
|
|
184
|
+
description: 'VM size preset',
|
|
185
|
+
},
|
|
186
|
+
customSize: { type: 'string', description: 'Custom Azure VM size (e.g., Standard_D4s_v3)' },
|
|
187
|
+
os: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
enum: ['ubuntu-22.04', 'ubuntu-20.04', 'debian-11', 'rhel-8', 'windows-2022', 'windows-2019'],
|
|
190
|
+
description: 'Operating system',
|
|
191
|
+
},
|
|
192
|
+
region: { type: 'string', description: 'Azure region (e.g., eastus, westeurope)' },
|
|
193
|
+
cpuCores: { type: 'number', description: 'Number of CPU cores' },
|
|
194
|
+
memoryGB: { type: 'number', description: 'Memory in GB' },
|
|
195
|
+
diskSizeGB: { type: 'number', description: 'OS disk size in GB' },
|
|
196
|
+
diskType: { type: 'string', enum: ['standard', 'premium', 'ultra'], description: 'Disk type' },
|
|
197
|
+
publicIP: { type: 'boolean', description: 'Whether to assign a public IP' },
|
|
198
|
+
openPorts: { type: 'array', items: { type: 'number' }, description: 'Ports to open in NSG' },
|
|
199
|
+
spotInstance: { type: 'boolean', description: 'Use spot instance for cost savings' },
|
|
200
|
+
autoShutdown: { type: 'string', description: 'Auto-shutdown time (e.g., 19:00)' },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
method: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
enum: ['dry-run', 'terraform', 'azure-cli'],
|
|
206
|
+
description: 'Provisioning method: dry-run (generate config only), terraform (apply with Terraform), azure-cli (apply with Azure CLI)',
|
|
207
|
+
default: 'dry-run',
|
|
208
|
+
},
|
|
209
|
+
generateTerraform: {
|
|
210
|
+
type: 'boolean',
|
|
211
|
+
description: 'Generate Terraform files (default: true for dry-run)',
|
|
212
|
+
default: true,
|
|
213
|
+
},
|
|
214
|
+
outputPath: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
description: 'Directory to save generated files (defaults to current directory)',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
required: [],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'listAzureSubscriptions',
|
|
224
|
+
description: 'List all Azure subscriptions the user has access to. Use this BEFORE provisioning VMs when user might have multiple subscriptions. Returns list of subscriptions with their IDs, names, and which one is currently active.',
|
|
225
|
+
inputSchema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {},
|
|
228
|
+
required: [],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'setAzureSubscription',
|
|
233
|
+
description: 'Set the active Azure subscription for subsequent operations. Use this when user selects a different subscription from the list.',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
subscriptionId: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'The subscription ID to set as active',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
required: ['subscriptionId'],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'listAzureResourceGroups',
|
|
247
|
+
description: 'List all resource groups in the current (or specified) Azure subscription. Use this to offer users the option to reuse existing resource groups instead of creating new ones.',
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: 'object',
|
|
250
|
+
properties: {
|
|
251
|
+
subscriptionId: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'Optional subscription ID to list resource groups from. If not provided, uses the current active subscription.',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
required: [],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'runCloudPerformanceTest',
|
|
261
|
+
description: 'Run end-to-end cloud performance test. Provisions an Azure VM using Terraform, installs JMeter, uploads JMX script, executes test, downloads results, and optionally cleans up resources. This is the fully automated workflow for cloud-based performance testing.',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
jmx_file_path: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
description: 'Absolute path to the JMX test script file',
|
|
268
|
+
},
|
|
269
|
+
subscription_id: {
|
|
270
|
+
type: 'string',
|
|
271
|
+
description: 'Azure subscription ID (required)',
|
|
272
|
+
},
|
|
273
|
+
resource_group: {
|
|
274
|
+
type: 'string',
|
|
275
|
+
description: 'Azure resource group name (required)',
|
|
276
|
+
},
|
|
277
|
+
vm_name: {
|
|
278
|
+
type: 'string',
|
|
279
|
+
description: 'Name for the VM (optional, auto-generated if not provided)',
|
|
280
|
+
},
|
|
281
|
+
region: {
|
|
282
|
+
type: 'string',
|
|
283
|
+
description: 'Azure region (default: eastus)',
|
|
284
|
+
default: 'eastus',
|
|
285
|
+
},
|
|
286
|
+
vm_size: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
enum: ['small', 'medium', 'large', 'xlarge'],
|
|
289
|
+
description: 'VM size preset. If not provided, will be auto-recommended based on JMX analysis.',
|
|
290
|
+
},
|
|
291
|
+
reuse_existing_vm: {
|
|
292
|
+
type: 'boolean',
|
|
293
|
+
description: 'Whether to reuse an existing VM instead of creating a new one',
|
|
294
|
+
default: false,
|
|
295
|
+
},
|
|
296
|
+
existing_vm_ip: {
|
|
297
|
+
type: 'string',
|
|
298
|
+
description: 'IP address of existing VM to reuse (required if reuse_existing_vm is true)',
|
|
299
|
+
},
|
|
300
|
+
ssh_key_path: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: 'Path to SSH private key (required if reusing existing VM)',
|
|
303
|
+
},
|
|
304
|
+
cleanup_after_test: {
|
|
305
|
+
type: 'boolean',
|
|
306
|
+
description: 'Whether to delete Azure resources after test completion using Terraform destroy (default: false)',
|
|
307
|
+
default: false,
|
|
308
|
+
},
|
|
309
|
+
test_duration_minutes: {
|
|
310
|
+
type: 'number',
|
|
311
|
+
description: 'Maximum test duration in minutes for timeout calculation (default: 30)',
|
|
312
|
+
default: 30,
|
|
313
|
+
},
|
|
314
|
+
output_path: {
|
|
315
|
+
type: 'string',
|
|
316
|
+
description: 'Local directory to save test results (default: ./.perf-output/test-results)',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
required: ['jmx_file_path', 'subscription_id', 'resource_group'],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'getCloudTestRecommendation',
|
|
324
|
+
description: 'Analyze a JMX file and get VM size recommendations for cloud execution. Use this before runCloudPerformanceTest to show users the recommended configuration.',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
jmx_file_path: {
|
|
329
|
+
type: 'string',
|
|
330
|
+
description: 'Absolute path to the JMX test script file to analyze',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ['jmx_file_path'],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'cleanupAzureResources',
|
|
338
|
+
description: 'Delete Azure resource group and all associated resources (VM, network, disks, etc.) after performance test completion. Uses Terraform destroy as primary method. Requires user confirmation before destroying resources. Use this to stop billing and clean up test infrastructure.',
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: 'object',
|
|
341
|
+
properties: {
|
|
342
|
+
resource_group: {
|
|
343
|
+
type: 'string',
|
|
344
|
+
description: 'Name of the Azure resource group to delete (e.g., rg-perf-test-agent)',
|
|
345
|
+
},
|
|
346
|
+
terraform_dir: {
|
|
347
|
+
type: 'string',
|
|
348
|
+
description: 'Path to Terraform directory (optional, will auto-detect from .perf-output if not provided)',
|
|
349
|
+
},
|
|
350
|
+
force: {
|
|
351
|
+
type: 'boolean',
|
|
352
|
+
description: 'If true, deletion runs asynchronously in background (faster). If false, waits for completion (default: true)',
|
|
353
|
+
default: true,
|
|
354
|
+
},
|
|
355
|
+
confirmed: {
|
|
356
|
+
type: 'boolean',
|
|
357
|
+
description: 'User confirmation to proceed with resource destruction. First call returns resources to be deleted, second call with confirmed=true performs the deletion.',
|
|
358
|
+
default: false,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
required: ['resource_group'],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'analyzeTestResults',
|
|
366
|
+
description: 'Analyze performance test results and generate detailed report with insights. Provides comprehensive statistics including response times (avg, min, max, percentiles), error rates, throughput, per-endpoint breakdown, and Azure VM metrics (CPU, memory, network usage during test). Use this after test completion to get actionable insights and recommendations.',
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: 'object',
|
|
369
|
+
properties: {
|
|
370
|
+
result_file_path: {
|
|
371
|
+
type: 'string',
|
|
372
|
+
description: 'Absolute path to the JTL results file (e.g., .perf-output/test-results/test-xxx/results.jtl)',
|
|
373
|
+
},
|
|
374
|
+
resource_group: {
|
|
375
|
+
type: 'string',
|
|
376
|
+
description: 'Azure resource group name (optional, for VM metrics)',
|
|
377
|
+
},
|
|
378
|
+
vm_name: {
|
|
379
|
+
type: 'string',
|
|
380
|
+
description: 'Azure VM name (optional, for VM metrics)',
|
|
381
|
+
},
|
|
382
|
+
test_start_time: {
|
|
383
|
+
type: 'string',
|
|
384
|
+
description: 'Test start time in ISO format (optional, for VM metrics)',
|
|
385
|
+
},
|
|
386
|
+
test_end_time: {
|
|
387
|
+
type: 'string',
|
|
388
|
+
description: 'Test end time in ISO format (optional, for VM metrics)',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
required: ['result_file_path'],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'getVMMetrics',
|
|
396
|
+
description: 'Fetch Azure VM performance metrics (CPU, Memory, Network, Disk) from Azure Monitor. Used to verify VM had sufficient capacity during performance tests and identify infrastructure bottlenecks. Automatically detects VM from Terraform state or accepts explicit resource group/VM name.',
|
|
397
|
+
inputSchema: {
|
|
398
|
+
type: 'object',
|
|
399
|
+
properties: {
|
|
400
|
+
resource_group: {
|
|
401
|
+
type: 'string',
|
|
402
|
+
description: 'Azure resource group name containing the VM',
|
|
403
|
+
},
|
|
404
|
+
vm_name: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
description: 'VM name (optional, will auto-detect from Terraform state if not provided)',
|
|
407
|
+
},
|
|
408
|
+
time_range: {
|
|
409
|
+
type: 'string',
|
|
410
|
+
enum: ['1h', '6h', '24h', 'custom'],
|
|
411
|
+
description: 'Time range for metrics (default: 1h)',
|
|
412
|
+
default: '1h',
|
|
413
|
+
},
|
|
414
|
+
start_time: {
|
|
415
|
+
type: 'string',
|
|
416
|
+
description: 'Start time in ISO 8601 format (required if time_range=custom)',
|
|
417
|
+
},
|
|
418
|
+
end_time: {
|
|
419
|
+
type: 'string',
|
|
420
|
+
description: 'End time in ISO 8601 format (required if time_range=custom)',
|
|
421
|
+
},
|
|
422
|
+
metrics: {
|
|
423
|
+
type: 'array',
|
|
424
|
+
items: {
|
|
425
|
+
type: 'string',
|
|
426
|
+
enum: ['cpu', 'memory', 'network', 'disk', 'all'],
|
|
427
|
+
},
|
|
428
|
+
description: 'Specific metrics to fetch (default: ["all"])',
|
|
429
|
+
default: ['all'],
|
|
430
|
+
},
|
|
431
|
+
terraform_dir: {
|
|
432
|
+
type: 'string',
|
|
433
|
+
description: 'Path to Terraform directory (optional, for auto-detection)',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
required: ['resource_group'],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
149
439
|
];
|
|
150
440
|
/**
|
|
151
441
|
* Create and configure MCP Server
|
|
@@ -158,8 +448,6 @@ const server = new Server({
|
|
|
158
448
|
tools: {},
|
|
159
449
|
},
|
|
160
450
|
});
|
|
161
|
-
// Initialize API client
|
|
162
|
-
const apiClient = new PerformanceAgentClient(API_BASE_URL, API_KEY);
|
|
163
451
|
/**
|
|
164
452
|
* List available tools
|
|
165
453
|
*/
|
|
@@ -183,12 +471,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
183
471
|
case 'generateJMXFromSwagger':
|
|
184
472
|
result = await handleGenerateJMX(args);
|
|
185
473
|
break;
|
|
474
|
+
case 'prepareJMXForTest':
|
|
475
|
+
result = await handlePrepareJMXForTest(args);
|
|
476
|
+
break;
|
|
186
477
|
case 'analyzeCodeComplexity':
|
|
187
478
|
result = await handleCodeComplexity(args);
|
|
188
479
|
break;
|
|
189
480
|
case 'profileFunctionPerformance':
|
|
190
481
|
result = await handleFunctionProfiling(args);
|
|
191
482
|
break;
|
|
483
|
+
case 'provisionAzureVM':
|
|
484
|
+
result = await handleAzureProvisioning(args);
|
|
485
|
+
break;
|
|
486
|
+
case 'listAzureSubscriptions':
|
|
487
|
+
result = await handleListSubscriptions();
|
|
488
|
+
break;
|
|
489
|
+
case 'setAzureSubscription':
|
|
490
|
+
result = await handleSetSubscription(args);
|
|
491
|
+
break;
|
|
492
|
+
case 'listAzureResourceGroups':
|
|
493
|
+
result = await handleListResourceGroups(args);
|
|
494
|
+
break;
|
|
495
|
+
case 'runCloudPerformanceTest':
|
|
496
|
+
result = await handleCloudPerformanceTest(args);
|
|
497
|
+
break;
|
|
498
|
+
case 'getCloudTestRecommendation':
|
|
499
|
+
result = await handleCloudTestRecommendation(args);
|
|
500
|
+
break;
|
|
501
|
+
case 'cleanupAzureResources':
|
|
502
|
+
result = await handleCleanupAzureResources(args);
|
|
503
|
+
break;
|
|
504
|
+
case 'analyzeTestResults':
|
|
505
|
+
result = await handleAnalyzeTestResults(args);
|
|
506
|
+
break;
|
|
507
|
+
case 'getVMMetrics':
|
|
508
|
+
result = await handleGetVMMetrics(args);
|
|
509
|
+
break;
|
|
192
510
|
default:
|
|
193
511
|
throw new Error(`Unknown tool: ${name}`);
|
|
194
512
|
}
|
|
@@ -219,21 +537,147 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
219
537
|
}
|
|
220
538
|
});
|
|
221
539
|
/**
|
|
222
|
-
* Handler for generateJMXFromSwagger tool
|
|
540
|
+
* Handler for generateJMXFromSwagger tool - Now uses local OpenAI integration
|
|
223
541
|
*/
|
|
224
542
|
async function handleGenerateJMX(args) {
|
|
225
543
|
const { repo_name = 'api-test', swagger_content } = args;
|
|
226
544
|
if (!swagger_content) {
|
|
227
545
|
throw new Error('Missing required field: swagger_content');
|
|
228
546
|
}
|
|
229
|
-
|
|
547
|
+
console.error(`[generateJMXFromSwagger] Generating JMX for repo: ${repo_name}...`);
|
|
548
|
+
const result = await generateJMX(swagger_content, repo_name);
|
|
549
|
+
if (!result.success) {
|
|
550
|
+
throw new Error(result.error || 'JMX generation failed');
|
|
551
|
+
}
|
|
552
|
+
// Save JMX script to local file
|
|
553
|
+
const outputDir = path.join(process.cwd(), '.perf-output', 'jmx-scripts');
|
|
554
|
+
if (!fs.existsSync(outputDir)) {
|
|
555
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
556
|
+
}
|
|
230
557
|
return {
|
|
231
558
|
success: true,
|
|
232
559
|
tool: 'generateJMXFromSwagger',
|
|
233
560
|
repo_name,
|
|
234
561
|
jmx_script: result.jmx_script,
|
|
235
|
-
|
|
236
|
-
|
|
562
|
+
message: `JMX script generated successfully for repo: ${repo_name}`,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Handler for prepareJMXForTest tool - Workflow tool for JMX preparation
|
|
567
|
+
* This tool ALWAYS reads the latest file content and provides fresh analysis
|
|
568
|
+
*/
|
|
569
|
+
async function handlePrepareJMXForTest(args) {
|
|
570
|
+
const { jmx_file_path, swagger_file_path, regenerate_jmx = false, swagger_content, repo_name = 'api-test' } = args;
|
|
571
|
+
if (!jmx_file_path) {
|
|
572
|
+
throw new Error('Missing required field: jmx_file_path');
|
|
573
|
+
}
|
|
574
|
+
const jmxExists = fs.existsSync(jmx_file_path);
|
|
575
|
+
let regenerated = false;
|
|
576
|
+
// Step 1: Check if JMX exists and handle regeneration
|
|
577
|
+
if (!jmxExists) {
|
|
578
|
+
return {
|
|
579
|
+
success: false,
|
|
580
|
+
tool: 'prepareJMXForTest',
|
|
581
|
+
jmx_exists: false,
|
|
582
|
+
jmx_file_path,
|
|
583
|
+
message: `JMX file not found at: ${jmx_file_path}. Please provide swagger_content to generate a new JMX script.`,
|
|
584
|
+
next_step: 'Provide swagger_content and set regenerate_jmx=true to generate JMX',
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
// Step 2: Regenerate JMX if requested
|
|
588
|
+
if (regenerate_jmx) {
|
|
589
|
+
if (!swagger_content) {
|
|
590
|
+
return {
|
|
591
|
+
success: false,
|
|
592
|
+
tool: 'prepareJMXForTest',
|
|
593
|
+
message: 'swagger_content is required when regenerate_jmx=true',
|
|
594
|
+
next_step: 'Provide swagger_content to regenerate JMX',
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
console.error('Regenerating JMX from Swagger...');
|
|
598
|
+
const genResult = await generateJMX(swagger_content, repo_name);
|
|
599
|
+
if (!genResult.success || !genResult.jmx_script) {
|
|
600
|
+
return {
|
|
601
|
+
success: false,
|
|
602
|
+
tool: 'prepareJMXForTest',
|
|
603
|
+
message: `JMX regeneration failed: ${genResult.error}`,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
// Save the new JMX file
|
|
607
|
+
fs.writeFileSync(jmx_file_path, genResult.jmx_script);
|
|
608
|
+
regenerated = true;
|
|
609
|
+
console.error(`JMX regenerated and saved to: ${jmx_file_path}`);
|
|
610
|
+
}
|
|
611
|
+
// Step 3: Read current JMX file content (fresh read every time)
|
|
612
|
+
console.error('Reading latest JMX file content...');
|
|
613
|
+
let jmxContent;
|
|
614
|
+
try {
|
|
615
|
+
jmxContent = fs.readFileSync(jmx_file_path, 'utf-8');
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
return {
|
|
619
|
+
success: false,
|
|
620
|
+
tool: 'prepareJMXForTest',
|
|
621
|
+
message: `Failed to read JMX file: ${error}`,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
// Step 4: Analyze the JMX file content
|
|
625
|
+
console.error('Analyzing current JMX configuration for resource requirements...');
|
|
626
|
+
const recommendation = getVMRecommendation(jmx_file_path);
|
|
627
|
+
// Extract actual thread counts from the file for display
|
|
628
|
+
const threadGroupMatches = jmxContent.match(/<stringProp name="ThreadGroup\.num_threads">([^<]+)<\/stringProp>/g) || [];
|
|
629
|
+
const threadCounts = threadGroupMatches.map(match => {
|
|
630
|
+
const valueMatch = match.match(/>([^<]+)</);
|
|
631
|
+
return valueMatch ? valueMatch[1] : '0';
|
|
632
|
+
});
|
|
633
|
+
const loopCountMatches = jmxContent.match(/<stringProp name="LoopController\.loops">([^<]+)<\/stringProp>/g) || [];
|
|
634
|
+
const loopCounts = loopCountMatches.map(match => {
|
|
635
|
+
const valueMatch = match.match(/>([^<]+)</);
|
|
636
|
+
return valueMatch ? valueMatch[1] : '0';
|
|
637
|
+
});
|
|
638
|
+
// Step 5: Return comprehensive analysis and recommendations with "where to run" prompt
|
|
639
|
+
return {
|
|
640
|
+
success: true,
|
|
641
|
+
tool: 'prepareJMXForTest',
|
|
642
|
+
jmx_exists: true,
|
|
643
|
+
jmx_file_path,
|
|
644
|
+
regenerated,
|
|
645
|
+
current_config: {
|
|
646
|
+
thread_groups: threadCounts.length,
|
|
647
|
+
thread_counts: threadCounts,
|
|
648
|
+
loop_counts: loopCounts,
|
|
649
|
+
detected_variables: jmxContent.includes('${ThreadCount_') ? 'Parameterized (using variables)' : 'Hardcoded',
|
|
650
|
+
},
|
|
651
|
+
analysis: {
|
|
652
|
+
threadCount: recommendation.threadCount,
|
|
653
|
+
testDurationSeconds: recommendation.duration,
|
|
654
|
+
totalVirtualUsers: recommendation.threadCount,
|
|
655
|
+
file_size_kb: Math.round(Buffer.from(jmxContent).length / 1024),
|
|
656
|
+
},
|
|
657
|
+
recommendation: {
|
|
658
|
+
vmSize: recommendation.recommendedSize,
|
|
659
|
+
estimatedCost: recommendation.estimatedCost,
|
|
660
|
+
description: recommendation.description,
|
|
661
|
+
},
|
|
662
|
+
message: regenerated
|
|
663
|
+
? `✅ JMX regenerated and analyzed. Current config: ${recommendation.threadCount} total threads, ${recommendation.duration}s duration. Recommended VM: ${recommendation.recommendedSize} (${recommendation.estimatedCost})`
|
|
664
|
+
: `✅ JMX analyzed from latest file content. Current config: ${recommendation.threadCount} total threads, ${recommendation.duration}s duration. Recommended VM: ${recommendation.recommendedSize} (${recommendation.estimatedCost})`,
|
|
665
|
+
next_steps: {
|
|
666
|
+
option_1: {
|
|
667
|
+
name: 'Run on Azure VM (Automated)',
|
|
668
|
+
action: 'Use runCloudPerformanceTest tool',
|
|
669
|
+
requires: ['subscription_id', 'resource_group'],
|
|
670
|
+
recommended_vm: recommendation.recommendedSize,
|
|
671
|
+
will_provision: 'VM, install JMeter, execute test, download results, optional cleanup',
|
|
672
|
+
},
|
|
673
|
+
option_2: {
|
|
674
|
+
name: 'Run Locally',
|
|
675
|
+
action: 'Open JMX file in JMeter GUI',
|
|
676
|
+
requires: 'JMeter installed locally',
|
|
677
|
+
note: 'Manual execution',
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
prompt_user: '📍 Where would you like to run this performance test? (1) Azure VM (automated) or (2) Local JMeter?',
|
|
237
681
|
};
|
|
238
682
|
}
|
|
239
683
|
/**
|
|
@@ -279,6 +723,290 @@ async function handleFunctionProfiling(args) {
|
|
|
279
723
|
...report,
|
|
280
724
|
};
|
|
281
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Handler for provisionAzureVM tool
|
|
728
|
+
*/
|
|
729
|
+
async function handleAzureProvisioning(args) {
|
|
730
|
+
const { description, config, method = 'dry-run', generateTerraform = true, outputPath, } = args;
|
|
731
|
+
if (!description && !config) {
|
|
732
|
+
// Return available presets and prerequisites
|
|
733
|
+
const prerequisites = checkPrerequisites();
|
|
734
|
+
const presets = getVMSizePresets();
|
|
735
|
+
return {
|
|
736
|
+
success: true,
|
|
737
|
+
tool: 'provisionAzureVM',
|
|
738
|
+
message: 'No configuration provided. Here are the available options:',
|
|
739
|
+
prerequisites,
|
|
740
|
+
availablePresets: presets,
|
|
741
|
+
exampleDescriptions: [
|
|
742
|
+
'Create a small Ubuntu VM in East US for testing',
|
|
743
|
+
'Create a large Windows 2022 VM with 4 cores, 16GB RAM, and public IP',
|
|
744
|
+
'Create a GPU VM for machine learning with 100GB disk',
|
|
745
|
+
'Create a medium web server with ports 80, 443, and auto-shutdown at 7pm',
|
|
746
|
+
],
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const result = await provisionAzureVM({
|
|
750
|
+
description,
|
|
751
|
+
config,
|
|
752
|
+
method,
|
|
753
|
+
generateTerraform,
|
|
754
|
+
outputPath,
|
|
755
|
+
});
|
|
756
|
+
return {
|
|
757
|
+
tool: 'provisionAzureVM',
|
|
758
|
+
...result,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Handler for listAzureSubscriptions tool
|
|
763
|
+
*/
|
|
764
|
+
async function handleListSubscriptions() {
|
|
765
|
+
const result = await listAzureSubscriptions();
|
|
766
|
+
if (!result.success) {
|
|
767
|
+
throw new Error(result.error || 'Failed to list subscriptions');
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
tool: 'listAzureSubscriptions',
|
|
771
|
+
success: true,
|
|
772
|
+
subscriptions: result.subscriptions,
|
|
773
|
+
currentSubscription: result.currentSubscription,
|
|
774
|
+
count: result.subscriptions?.length || 0,
|
|
775
|
+
message: result.subscriptions && result.subscriptions.length > 1
|
|
776
|
+
? `Found ${result.subscriptions.length} subscriptions. Please select one.`
|
|
777
|
+
: 'Using current subscription.',
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Handler for setAzureSubscription tool
|
|
782
|
+
*/
|
|
783
|
+
async function handleSetSubscription(args) {
|
|
784
|
+
const { subscriptionId } = args;
|
|
785
|
+
if (!subscriptionId) {
|
|
786
|
+
throw new Error('Missing required field: subscriptionId');
|
|
787
|
+
}
|
|
788
|
+
const result = await setAzureSubscription(subscriptionId);
|
|
789
|
+
if (!result.success) {
|
|
790
|
+
throw new Error(result.error || 'Failed to set subscription');
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
tool: 'setAzureSubscription',
|
|
794
|
+
success: true,
|
|
795
|
+
subscriptionId,
|
|
796
|
+
message: result.message,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Handler for listAzureResourceGroups tool
|
|
801
|
+
*/
|
|
802
|
+
async function handleListResourceGroups(args) {
|
|
803
|
+
const { subscriptionId } = args;
|
|
804
|
+
const result = await listResourceGroups(subscriptionId);
|
|
805
|
+
if (!result.success) {
|
|
806
|
+
throw new Error(result.error || 'Failed to list resource groups');
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
tool: 'listAzureResourceGroups',
|
|
810
|
+
success: true,
|
|
811
|
+
resourceGroups: result.resourceGroups,
|
|
812
|
+
count: result.resourceGroups?.length || 0,
|
|
813
|
+
message: result.resourceGroups && result.resourceGroups.length > 0
|
|
814
|
+
? `Found ${result.resourceGroups.length} existing resource groups.`
|
|
815
|
+
: 'No existing resource groups found. A new one will be created.',
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Handler for runCloudPerformanceTest tool
|
|
820
|
+
*/
|
|
821
|
+
async function handleCloudPerformanceTest(args) {
|
|
822
|
+
const { jmx_file_path, vm_name, resource_group, subscription_id, region = 'eastus', vm_size, reuse_existing_vm = false, existing_vm_ip, ssh_key_path, cleanup_after_test = false, test_duration_minutes = 30, output_path, } = args;
|
|
823
|
+
if (!jmx_file_path) {
|
|
824
|
+
throw new Error('Missing required field: jmx_file_path');
|
|
825
|
+
}
|
|
826
|
+
const config = {
|
|
827
|
+
jmxFilePath: jmx_file_path,
|
|
828
|
+
vmName: vm_name,
|
|
829
|
+
resourceGroup: resource_group,
|
|
830
|
+
subscriptionId: subscription_id,
|
|
831
|
+
region,
|
|
832
|
+
vmSize: vm_size,
|
|
833
|
+
reuseExistingVM: reuse_existing_vm,
|
|
834
|
+
existingVMIp: existing_vm_ip,
|
|
835
|
+
sshKeyPath: ssh_key_path,
|
|
836
|
+
cleanupAfterTest: cleanup_after_test,
|
|
837
|
+
testDurationMinutes: test_duration_minutes,
|
|
838
|
+
outputPath: output_path,
|
|
839
|
+
};
|
|
840
|
+
const result = await runCloudPerformanceTest(config);
|
|
841
|
+
return {
|
|
842
|
+
tool: 'runCloudPerformanceTest',
|
|
843
|
+
...result,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Handler for getCloudTestRecommendation tool
|
|
848
|
+
*/
|
|
849
|
+
async function handleCloudTestRecommendation(args) {
|
|
850
|
+
const { jmx_file_path } = args;
|
|
851
|
+
if (!jmx_file_path) {
|
|
852
|
+
throw new Error('Missing required field: jmx_file_path');
|
|
853
|
+
}
|
|
854
|
+
const recommendation = getVMRecommendation(jmx_file_path);
|
|
855
|
+
return {
|
|
856
|
+
tool: 'getCloudTestRecommendation',
|
|
857
|
+
success: true,
|
|
858
|
+
jmxFile: jmx_file_path,
|
|
859
|
+
analysis: {
|
|
860
|
+
threadCount: recommendation.threadCount,
|
|
861
|
+
testDurationSeconds: recommendation.duration,
|
|
862
|
+
},
|
|
863
|
+
recommendation: {
|
|
864
|
+
vmSize: recommendation.recommendedSize,
|
|
865
|
+
estimatedCost: recommendation.estimatedCost,
|
|
866
|
+
description: recommendation.description,
|
|
867
|
+
},
|
|
868
|
+
message: `Analyzed JMX: ${recommendation.threadCount} threads, ${recommendation.duration}s duration. Recommended VM: ${recommendation.recommendedSize} (${recommendation.estimatedCost})`,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Handler for cleanupAzureResources tool
|
|
873
|
+
*/
|
|
874
|
+
async function handleCleanupAzureResources(args) {
|
|
875
|
+
const { resource_group, terraform_dir, force = true, confirmed = false } = args;
|
|
876
|
+
if (!resource_group) {
|
|
877
|
+
throw new Error('resource_group is required');
|
|
878
|
+
}
|
|
879
|
+
// Auto-detect Terraform directory by searching .perf-output/terraform/
|
|
880
|
+
let terraformDirPath = terraform_dir;
|
|
881
|
+
let detectedVmName;
|
|
882
|
+
if (!terraformDirPath) {
|
|
883
|
+
const terraformBaseDir = `${process.cwd()}/.perf-output/terraform`;
|
|
884
|
+
if (fs.existsSync(terraformBaseDir)) {
|
|
885
|
+
// Search all subdirectories for terraform state that matches the resource group
|
|
886
|
+
const subdirs = fs.readdirSync(terraformBaseDir).filter((d) => {
|
|
887
|
+
const fullPath = path.join(terraformBaseDir, d);
|
|
888
|
+
return fs.statSync(fullPath).isDirectory() && !d.startsWith('.');
|
|
889
|
+
});
|
|
890
|
+
console.error(`Searching for Terraform config matching resource group: ${resource_group}`);
|
|
891
|
+
console.error(`Found ${subdirs.length} terraform directories: ${subdirs.join(', ')}`);
|
|
892
|
+
for (const subdir of subdirs) {
|
|
893
|
+
const mainTfPath = path.join(terraformBaseDir, subdir, 'main.tf');
|
|
894
|
+
const tfStatePath = path.join(terraformBaseDir, subdir, 'terraform.tfstate');
|
|
895
|
+
// Check main.tf for resource_group (primary method)
|
|
896
|
+
if (fs.existsSync(mainTfPath)) {
|
|
897
|
+
const content = fs.readFileSync(mainTfPath, 'utf-8');
|
|
898
|
+
// Match patterns like: name = "rg-perf-test-agent" or name = "rg-perf-test-agent"
|
|
899
|
+
if (content.includes(`"${resource_group}"`)) {
|
|
900
|
+
terraformDirPath = path.join(terraformBaseDir, subdir);
|
|
901
|
+
detectedVmName = subdir;
|
|
902
|
+
console.error(`✅ Auto-detected Terraform directory from main.tf: ${terraformDirPath}`);
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
// Check terraform.tfstate for resource_group (backup method)
|
|
907
|
+
if (!terraformDirPath && fs.existsSync(tfStatePath)) {
|
|
908
|
+
try {
|
|
909
|
+
const stateContent = fs.readFileSync(tfStatePath, 'utf-8');
|
|
910
|
+
if (stateContent.includes(`"${resource_group}"`)) {
|
|
911
|
+
terraformDirPath = path.join(terraformBaseDir, subdir);
|
|
912
|
+
detectedVmName = subdir;
|
|
913
|
+
console.error(`✅ Auto-detected Terraform directory from state file: ${terraformDirPath}`);
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
catch (e) {
|
|
918
|
+
// State file might be corrupted or unreadable
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// If still not found, use the most recent terraform directory as fallback
|
|
923
|
+
if (!terraformDirPath && subdirs.length > 0) {
|
|
924
|
+
// Sort by modification time and use the most recent
|
|
925
|
+
const sortedDirs = subdirs.map((d) => {
|
|
926
|
+
const fullPath = path.join(terraformBaseDir, d);
|
|
927
|
+
return { name: d, mtime: fs.statSync(fullPath).mtime };
|
|
928
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
929
|
+
terraformDirPath = path.join(terraformBaseDir, sortedDirs[0].name);
|
|
930
|
+
detectedVmName = sortedDirs[0].name;
|
|
931
|
+
console.error(`⚠️ Using most recent Terraform directory as fallback: ${terraformDirPath}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
console.error(`❌ Terraform base directory not found: ${terraformBaseDir}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
// List resources that will be deleted
|
|
939
|
+
const resourcesToDelete = [
|
|
940
|
+
'Virtual Machine',
|
|
941
|
+
'Network Interface',
|
|
942
|
+
'Public IP Address',
|
|
943
|
+
'Virtual Network',
|
|
944
|
+
'Network Security Group',
|
|
945
|
+
'OS Disk',
|
|
946
|
+
'SSH Key (local file)',
|
|
947
|
+
];
|
|
948
|
+
// If not confirmed, return preview of what will be deleted
|
|
949
|
+
if (!confirmed) {
|
|
950
|
+
return {
|
|
951
|
+
tool: 'cleanupAzureResources',
|
|
952
|
+
success: true,
|
|
953
|
+
requiresConfirmation: true,
|
|
954
|
+
message: `⚠️ CONFIRMATION REQUIRED: The following resources will be permanently deleted from resource group '${resource_group}':`,
|
|
955
|
+
resourceGroup: resource_group,
|
|
956
|
+
terraformDir: terraformDirPath,
|
|
957
|
+
detectedVmName: detectedVmName,
|
|
958
|
+
resourcesToDelete: resourcesToDelete,
|
|
959
|
+
deletionMethod: terraformDirPath ? 'Terraform destroy' : 'Azure CLI',
|
|
960
|
+
instructions: 'To proceed with deletion, call this tool again with confirmed=true',
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
// User confirmed, proceed with deletion
|
|
964
|
+
const result = await cleanupAzureResources(resource_group, terraformDirPath, force);
|
|
965
|
+
return {
|
|
966
|
+
tool: 'cleanupAzureResources',
|
|
967
|
+
...result,
|
|
968
|
+
resourceGroup: resource_group,
|
|
969
|
+
terraformDir: terraformDirPath,
|
|
970
|
+
deletionMethod: terraformDirPath ? 'Terraform destroy' : 'Azure CLI',
|
|
971
|
+
mode: force ? 'async' : 'sync',
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Handler for analyzeTestResults tool
|
|
976
|
+
*/
|
|
977
|
+
async function handleAnalyzeTestResults(args) {
|
|
978
|
+
const { result_file_path, resource_group, vm_name, test_start_time, test_end_time } = args;
|
|
979
|
+
if (!result_file_path) {
|
|
980
|
+
throw new Error('result_file_path is required');
|
|
981
|
+
}
|
|
982
|
+
const result = await analyzeTestResults(result_file_path, resource_group, vm_name, test_start_time, test_end_time);
|
|
983
|
+
return {
|
|
984
|
+
tool: 'analyzeTestResults',
|
|
985
|
+
...result,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Handler for getVMMetrics tool
|
|
990
|
+
*/
|
|
991
|
+
async function handleGetVMMetrics(args) {
|
|
992
|
+
const { resource_group, vm_name, time_range = '1h', start_time, end_time, metrics = ['all'], terraform_dir } = args;
|
|
993
|
+
if (!resource_group) {
|
|
994
|
+
throw new Error('resource_group is required');
|
|
995
|
+
}
|
|
996
|
+
const result = await getVMMetrics({
|
|
997
|
+
resourceGroup: resource_group,
|
|
998
|
+
vmName: vm_name,
|
|
999
|
+
timeRange: time_range,
|
|
1000
|
+
startTime: start_time,
|
|
1001
|
+
endTime: end_time,
|
|
1002
|
+
metrics,
|
|
1003
|
+
terraformDir: terraform_dir
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
tool: 'getVMMetrics',
|
|
1007
|
+
...result,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
282
1010
|
/**
|
|
283
1011
|
* Start the MCP server
|
|
284
1012
|
*/
|
|
@@ -286,9 +1014,17 @@ async function main() {
|
|
|
286
1014
|
console.error('Starting Performance Agent MCP Server...');
|
|
287
1015
|
console.error('Available tools:');
|
|
288
1016
|
console.error(' 1. generateJMXFromSwagger - Generate JMeter scripts from Swagger');
|
|
289
|
-
console.error(' 2.
|
|
290
|
-
console.error(' 3.
|
|
291
|
-
console.error(
|
|
1017
|
+
console.error(' 2. prepareJMXForTest - Workflow tool: Prepare and analyze JMX for testing');
|
|
1018
|
+
console.error(' 3. analyzeCodeComplexity - Analyze code complexity in local repos');
|
|
1019
|
+
console.error(' 4. profileFunctionPerformance - Profile function execution time');
|
|
1020
|
+
console.error(' 5. provisionAzureVM - Provision Azure VMs from natural language');
|
|
1021
|
+
console.error(' 6. listAzureSubscriptions - List Azure subscriptions');
|
|
1022
|
+
console.error(' 7. listAzureResourceGroups - List Azure resource groups');
|
|
1023
|
+
console.error(' 8. runCloudPerformanceTest - End-to-end cloud performance testing');
|
|
1024
|
+
console.error(' 9. getCloudTestRecommendation - Get VM recommendations for JMX');
|
|
1025
|
+
console.error(' 10. cleanupAzureResources - Cleanup Azure test infrastructure');
|
|
1026
|
+
console.error(' 11. analyzeTestResults - Detailed analysis of test results with VM metrics');
|
|
1027
|
+
console.error(' 12. getVMMetrics - Fetch Azure VM performance metrics from Azure Monitor');
|
|
292
1028
|
const transport = new StdioServerTransport();
|
|
293
1029
|
await server.connect(transport);
|
|
294
1030
|
console.error('Performance Agent MCP Server running on stdio');
|