@rog0x/mcp-perf-tools 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # mcp-perf-tools
2
+
3
+ Performance analysis tools for AI agents, exposed via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
4
+
5
+ ## Tools
6
+
7
+ ### benchmark
8
+
9
+ Benchmark JavaScript code execution. Run a snippet N times and measure min/max/avg/median/p95/p99 latency and operations per second. Optionally compare two implementations side by side.
10
+
11
+ **Parameters:**
12
+ - `code` (string) — JavaScript code to benchmark
13
+ - `iterations` (number, default 1000) — Number of iterations
14
+ - `compareCode` (string, optional) — Second implementation to compare
15
+ - `labelA` / `labelB` (string) — Labels for comparison output
16
+
17
+ ### memory_analyze
18
+
19
+ Analyze Node.js memory usage: heap used/total, RSS, external memory, and array buffers. Takes snapshots over time and uses linear regression to detect trends and potential memory leaks.
20
+
21
+ **Parameters:**
22
+ - `action` — `"snapshot"` | `"analyze"` | `"clear"`
23
+
24
+ ### big_o_estimate
25
+
26
+ Estimate Big O complexity from empirical timing data. Fits measurements against O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), and O(2^n) using R-squared scoring. Includes an ASCII growth curve visualization.
27
+
28
+ **Parameters:**
29
+ - `inputSizes` (number[]) — Array of input sizes
30
+ - `executionTimesMs` (number[]) — Corresponding execution times in ms
31
+
32
+ ### bundle_analyze
33
+
34
+ Analyze a JavaScript bundle file: raw size, gzip compressed estimate, detected module count, largest modules, and tree-shaking opportunities (side effects, duplicates).
35
+
36
+ **Parameters:**
37
+ - `filePath` (string) — Absolute path to the bundle file
38
+
39
+ ### load_test
40
+
41
+ Simple HTTP load tester. Sends N requests at a given concurrency level and reports response time percentiles, error rate, throughput (req/sec), and status code distribution.
42
+
43
+ **Parameters:**
44
+ - `url` (string) — Target URL
45
+ - `totalRequests` (number, default 100) — Total requests to send
46
+ - `concurrency` (number, default 10) — Concurrent request count
47
+ - `method` (string, default "GET") — HTTP method
48
+ - `headers` (object, optional) — HTTP headers
49
+ - `body` (string, optional) — Request body
50
+ - `timeoutMs` (number, default 30000) — Request timeout
51
+
52
+ ## Setup
53
+
54
+ ```bash
55
+ npm install
56
+ npm run build
57
+ ```
58
+
59
+ ## Usage with Claude Desktop
60
+
61
+ Add to your `claude_desktop_config.json`:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "perf-tools": {
67
+ "command": "node",
68
+ "args": ["D:/products/mcp-servers/mcp-perf-tools/dist/index.js"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const zod_1 = require("zod");
7
+ const benchmark_js_1 = require("./tools/benchmark.js");
8
+ const memory_analyzer_js_1 = require("./tools/memory-analyzer.js");
9
+ const big_o_estimator_js_1 = require("./tools/big-o-estimator.js");
10
+ const bundle_analyzer_js_1 = require("./tools/bundle-analyzer.js");
11
+ const load_tester_js_1 = require("./tools/load-tester.js");
12
+ const server = new mcp_js_1.McpServer({
13
+ name: "mcp-perf-tools",
14
+ version: "1.0.0",
15
+ });
16
+ // -------------------------------------------------------------------
17
+ // Tool 1: benchmark
18
+ // -------------------------------------------------------------------
19
+ server.tool("benchmark", "Benchmark JavaScript code execution. Run a snippet N times and measure min/max/avg/p95/p99 time and ops/sec. Optionally compare two implementations.", {
20
+ code: zod_1.z.string().describe("JavaScript code to benchmark"),
21
+ iterations: zod_1.z
22
+ .number()
23
+ .int()
24
+ .min(1)
25
+ .default(1000)
26
+ .describe("Number of iterations to run"),
27
+ compareCode: zod_1.z
28
+ .string()
29
+ .optional()
30
+ .describe("Optional second implementation to compare against"),
31
+ labelA: zod_1.z
32
+ .string()
33
+ .default("A")
34
+ .describe("Label for the first implementation"),
35
+ labelB: zod_1.z
36
+ .string()
37
+ .default("B")
38
+ .describe("Label for the second implementation"),
39
+ }, async ({ code, iterations, compareCode, labelA, labelB }) => {
40
+ try {
41
+ if (compareCode) {
42
+ const result = (0, benchmark_js_1.compareBenchmarks)(code, labelA, compareCode, labelB, iterations);
43
+ return { content: [{ type: "text", text: result.summary }] };
44
+ }
45
+ const result = (0, benchmark_js_1.runBenchmark)(code, iterations);
46
+ return {
47
+ content: [{ type: "text", text: (0, benchmark_js_1.formatBenchmarkResult)(result) }],
48
+ };
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ return {
53
+ content: [{ type: "text", text: `Benchmark error: ${msg}` }],
54
+ isError: true,
55
+ };
56
+ }
57
+ });
58
+ // -------------------------------------------------------------------
59
+ // Tool 2: memory_analyze
60
+ // -------------------------------------------------------------------
61
+ server.tool("memory_analyze", "Analyze Node.js memory usage: heap used/total, RSS, external, array buffers. Tracks snapshots over time and detects potential memory leaks via growth trend analysis.", {
62
+ action: zod_1.z
63
+ .enum(["snapshot", "analyze", "clear"])
64
+ .default("analyze")
65
+ .describe("snapshot: take a single reading; analyze: full analysis with trend detection; clear: reset history"),
66
+ }, async ({ action }) => {
67
+ try {
68
+ if (action === "clear") {
69
+ (0, memory_analyzer_js_1.clearSnapshotHistory)();
70
+ return {
71
+ content: [{ type: "text", text: "Memory snapshot history cleared." }],
72
+ };
73
+ }
74
+ if (action === "snapshot") {
75
+ const snap = (0, memory_analyzer_js_1.takeMemorySnapshot)();
76
+ return {
77
+ content: [{ type: "text", text: (0, memory_analyzer_js_1.formatSnapshot)(snap) }],
78
+ };
79
+ }
80
+ // analyze
81
+ const analysis = (0, memory_analyzer_js_1.analyzeMemory)();
82
+ return {
83
+ content: [{ type: "text", text: analysis.summary }],
84
+ };
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ return {
89
+ content: [{ type: "text", text: `Memory analysis error: ${msg}` }],
90
+ isError: true,
91
+ };
92
+ }
93
+ });
94
+ // -------------------------------------------------------------------
95
+ // Tool 3: big_o_estimate
96
+ // -------------------------------------------------------------------
97
+ server.tool("big_o_estimate", "Estimate Big O complexity from empirical data. Provide input sizes and corresponding execution times to get the best-fit complexity class (O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), O(2^n)) with confidence scores and growth curve.", {
98
+ inputSizes: zod_1.z
99
+ .array(zod_1.z.number().positive())
100
+ .min(3)
101
+ .describe("Array of input sizes (e.g., [100, 500, 1000, 5000])"),
102
+ executionTimesMs: zod_1.z
103
+ .array(zod_1.z.number().nonnegative())
104
+ .min(3)
105
+ .describe("Array of execution times in milliseconds, corresponding to each input size"),
106
+ }, async ({ inputSizes, executionTimesMs }) => {
107
+ try {
108
+ const result = (0, big_o_estimator_js_1.estimateBigO)(inputSizes, executionTimesMs);
109
+ return {
110
+ content: [{ type: "text", text: result.summary }],
111
+ };
112
+ }
113
+ catch (err) {
114
+ const msg = err instanceof Error ? err.message : String(err);
115
+ return {
116
+ content: [{ type: "text", text: `Big O estimation error: ${msg}` }],
117
+ isError: true,
118
+ };
119
+ }
120
+ });
121
+ // -------------------------------------------------------------------
122
+ // Tool 4: bundle_analyze
123
+ // -------------------------------------------------------------------
124
+ server.tool("bundle_analyze", "Analyze a JavaScript bundle file: raw size, gzip estimate, detected modules, largest modules, and tree-shaking opportunities.", {
125
+ filePath: zod_1.z
126
+ .string()
127
+ .describe("Absolute path to the JavaScript bundle file to analyze"),
128
+ }, async ({ filePath }) => {
129
+ try {
130
+ const result = (0, bundle_analyzer_js_1.analyzeBundle)(filePath);
131
+ return {
132
+ content: [{ type: "text", text: result.summary }],
133
+ };
134
+ }
135
+ catch (err) {
136
+ const msg = err instanceof Error ? err.message : String(err);
137
+ return {
138
+ content: [{ type: "text", text: `Bundle analysis error: ${msg}` }],
139
+ isError: true,
140
+ };
141
+ }
142
+ });
143
+ // -------------------------------------------------------------------
144
+ // Tool 5: load_test
145
+ // -------------------------------------------------------------------
146
+ server.tool("load_test", "Run a simple HTTP load test: send N concurrent requests to a URL, measure response times, error rate, throughput (req/sec), and percentile latencies.", {
147
+ url: zod_1.z.string().url().describe("Target URL to test"),
148
+ totalRequests: zod_1.z
149
+ .number()
150
+ .int()
151
+ .min(1)
152
+ .default(100)
153
+ .describe("Total number of requests to send"),
154
+ concurrency: zod_1.z
155
+ .number()
156
+ .int()
157
+ .min(1)
158
+ .default(10)
159
+ .describe("Number of concurrent requests"),
160
+ method: zod_1.z
161
+ .string()
162
+ .default("GET")
163
+ .describe("HTTP method (GET, POST, PUT, DELETE, etc.)"),
164
+ headers: zod_1.z
165
+ .record(zod_1.z.string(), zod_1.z.string())
166
+ .optional()
167
+ .describe("Optional HTTP headers as key-value pairs"),
168
+ body: zod_1.z.string().optional().describe("Optional request body"),
169
+ timeoutMs: zod_1.z
170
+ .number()
171
+ .int()
172
+ .min(1000)
173
+ .default(30000)
174
+ .describe("Request timeout in milliseconds"),
175
+ }, async ({ url, totalRequests, concurrency, method, headers, body, timeoutMs }) => {
176
+ try {
177
+ const result = await (0, load_tester_js_1.runLoadTest)({
178
+ url,
179
+ totalRequests,
180
+ concurrency,
181
+ method,
182
+ headers,
183
+ body,
184
+ timeoutMs,
185
+ });
186
+ return {
187
+ content: [{ type: "text", text: result.summary }],
188
+ };
189
+ }
190
+ catch (err) {
191
+ const msg = err instanceof Error ? err.message : String(err);
192
+ return {
193
+ content: [{ type: "text", text: `Load test error: ${msg}` }],
194
+ isError: true,
195
+ };
196
+ }
197
+ });
198
+ // -------------------------------------------------------------------
199
+ // Start server
200
+ // -------------------------------------------------------------------
201
+ async function main() {
202
+ const transport = new stdio_js_1.StdioServerTransport();
203
+ await server.connect(transport);
204
+ console.error("mcp-perf-tools server running on stdio");
205
+ }
206
+ main().catch((err) => {
207
+ console.error("Fatal error:", err);
208
+ process.exit(1);
209
+ });
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ export interface BenchmarkResult {
2
+ iterations: number;
3
+ minMs: number;
4
+ maxMs: number;
5
+ avgMs: number;
6
+ medianMs: number;
7
+ p95Ms: number;
8
+ p99Ms: number;
9
+ opsPerSecond: number;
10
+ totalMs: number;
11
+ timings: number[];
12
+ }
13
+ export interface ComparisonResult {
14
+ a: BenchmarkResult & {
15
+ label: string;
16
+ };
17
+ b: BenchmarkResult & {
18
+ label: string;
19
+ };
20
+ fasterLabel: string;
21
+ speedupFactor: number;
22
+ summary: string;
23
+ }
24
+ export declare function runBenchmark(code: string, iterations: number): BenchmarkResult;
25
+ export declare function compareBenchmarks(codeA: string, labelA: string, codeB: string, labelB: string, iterations: number): ComparisonResult;
26
+ export declare function formatBenchmarkResult(result: BenchmarkResult): string;
27
+ //# sourceMappingURL=benchmark.d.ts.map
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runBenchmark = runBenchmark;
4
+ exports.compareBenchmarks = compareBenchmarks;
5
+ exports.formatBenchmarkResult = formatBenchmarkResult;
6
+ const node_perf_hooks_1 = require("node:perf_hooks");
7
+ function percentile(sorted, p) {
8
+ if (sorted.length === 0)
9
+ return 0;
10
+ const idx = (p / 100) * (sorted.length - 1);
11
+ const lower = Math.floor(idx);
12
+ const upper = Math.ceil(idx);
13
+ if (lower === upper)
14
+ return sorted[lower];
15
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
16
+ }
17
+ function runBenchmark(code, iterations) {
18
+ const timings = [];
19
+ // Wrap the code in a function so it can be executed repeatedly
20
+ const fn = new Function(code);
21
+ // Warmup: 10% of iterations or at least 3
22
+ const warmupCount = Math.max(3, Math.floor(iterations * 0.1));
23
+ for (let i = 0; i < warmupCount; i++) {
24
+ fn();
25
+ }
26
+ for (let i = 0; i < iterations; i++) {
27
+ const start = node_perf_hooks_1.performance.now();
28
+ fn();
29
+ const end = node_perf_hooks_1.performance.now();
30
+ timings.push(end - start);
31
+ }
32
+ const sorted = [...timings].sort((a, b) => a - b);
33
+ const totalMs = timings.reduce((s, t) => s + t, 0);
34
+ const avgMs = totalMs / timings.length;
35
+ return {
36
+ iterations,
37
+ minMs: sorted[0],
38
+ maxMs: sorted[sorted.length - 1],
39
+ avgMs,
40
+ medianMs: percentile(sorted, 50),
41
+ p95Ms: percentile(sorted, 95),
42
+ p99Ms: percentile(sorted, 99),
43
+ opsPerSecond: totalMs > 0 ? (iterations / totalMs) * 1000 : Infinity,
44
+ totalMs,
45
+ timings: sorted,
46
+ };
47
+ }
48
+ function compareBenchmarks(codeA, labelA, codeB, labelB, iterations) {
49
+ const resultA = runBenchmark(codeA, iterations);
50
+ const resultB = runBenchmark(codeB, iterations);
51
+ const fasterIsA = resultA.avgMs < resultB.avgMs;
52
+ const fasterLabel = fasterIsA ? labelA : labelB;
53
+ const speedupFactor = fasterIsA
54
+ ? resultB.avgMs / resultA.avgMs
55
+ : resultA.avgMs / resultB.avgMs;
56
+ const summary = [
57
+ `"${fasterLabel}" is ${speedupFactor.toFixed(2)}x faster.`,
58
+ ` ${labelA}: avg ${resultA.avgMs.toFixed(4)}ms, p95 ${resultA.p95Ms.toFixed(4)}ms, ${resultA.opsPerSecond.toFixed(0)} ops/s`,
59
+ ` ${labelB}: avg ${resultB.avgMs.toFixed(4)}ms, p95 ${resultB.p95Ms.toFixed(4)}ms, ${resultB.opsPerSecond.toFixed(0)} ops/s`,
60
+ ].join("\n");
61
+ return {
62
+ a: { ...resultA, label: labelA },
63
+ b: { ...resultB, label: labelB },
64
+ fasterLabel,
65
+ speedupFactor,
66
+ summary,
67
+ };
68
+ }
69
+ function formatBenchmarkResult(result) {
70
+ return [
71
+ `Benchmark Results (${result.iterations} iterations):`,
72
+ ` Min: ${result.minMs.toFixed(4)} ms`,
73
+ ` Max: ${result.maxMs.toFixed(4)} ms`,
74
+ ` Avg: ${result.avgMs.toFixed(4)} ms`,
75
+ ` Median: ${result.medianMs.toFixed(4)} ms`,
76
+ ` P95: ${result.p95Ms.toFixed(4)} ms`,
77
+ ` P99: ${result.p99Ms.toFixed(4)} ms`,
78
+ ` Ops/s: ${result.opsPerSecond.toFixed(0)}`,
79
+ ` Total: ${result.totalMs.toFixed(2)} ms`,
80
+ ].join("\n");
81
+ }
82
+ //# sourceMappingURL=benchmark.js.map
@@ -0,0 +1,13 @@
1
+ export type ComplexityClass = "O(1)" | "O(log n)" | "O(n)" | "O(n log n)" | "O(n^2)" | "O(n^3)" | "O(2^n)" | "unknown";
2
+ export interface BigOEstimate {
3
+ bestFit: ComplexityClass;
4
+ confidence: number;
5
+ allFits: Array<{
6
+ complexity: ComplexityClass;
7
+ r2: number;
8
+ }>;
9
+ growthCurve: string;
10
+ summary: string;
11
+ }
12
+ export declare function estimateBigO(inputSizes: number[], executionTimesMs: number[]): BigOEstimate;
13
+ //# sourceMappingURL=big-o-estimator.d.ts.map
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.estimateBigO = estimateBigO;
4
+ function linearRegression(xs, ys) {
5
+ const n = xs.length;
6
+ if (n < 2)
7
+ return { slope: 0, intercept: ys[0] ?? 0, r2: 0 };
8
+ const sumX = xs.reduce((a, b) => a + b, 0);
9
+ const sumY = ys.reduce((a, b) => a + b, 0);
10
+ const sumXY = xs.reduce((a, x, i) => a + x * ys[i], 0);
11
+ const sumX2 = xs.reduce((a, x) => a + x * x, 0);
12
+ const denom = n * sumX2 - sumX * sumX;
13
+ if (denom === 0)
14
+ return { slope: 0, intercept: sumY / n, r2: 0 };
15
+ const slope = (n * sumXY - sumX * sumY) / denom;
16
+ const intercept = (sumY - slope * sumX) / n;
17
+ const meanY = sumY / n;
18
+ const ssTot = ys.reduce((a, y) => a + (y - meanY) ** 2, 0);
19
+ const ssRes = ys.reduce((a, y, i) => a + (y - (slope * xs[i] + intercept)) ** 2, 0);
20
+ const r2 = ssTot === 0 ? 1 : 1 - ssRes / ssTot;
21
+ return { slope, intercept, r2 };
22
+ }
23
+ function tryFit(sizes, times, transform, label, description) {
24
+ const transformed = sizes.map(transform);
25
+ // Check for non-finite values
26
+ if (transformed.some((v) => !isFinite(v))) {
27
+ return { complexity: label, r2: -Infinity, description };
28
+ }
29
+ const { r2 } = linearRegression(transformed, times);
30
+ return { complexity: label, r2, description };
31
+ }
32
+ function estimateBigO(inputSizes, executionTimesMs) {
33
+ if (inputSizes.length !== executionTimesMs.length) {
34
+ throw new Error("inputSizes and executionTimesMs must have the same length");
35
+ }
36
+ if (inputSizes.length < 3) {
37
+ throw new Error("Need at least 3 data points for a meaningful estimate");
38
+ }
39
+ const fits = [
40
+ tryFit(inputSizes, executionTimesMs, (_n) => 1, "O(1)", "Constant time: performance does not change with input size."),
41
+ tryFit(inputSizes, executionTimesMs, (n) => Math.log2(n), "O(log n)", "Logarithmic: performance grows slowly, typical of binary search or balanced trees."),
42
+ tryFit(inputSizes, executionTimesMs, (n) => n, "O(n)", "Linear: performance scales proportionally with input size."),
43
+ tryFit(inputSizes, executionTimesMs, (n) => n * Math.log2(n), "O(n log n)", "Linearithmic: typical of efficient sorting algorithms (mergesort, heapsort)."),
44
+ tryFit(inputSizes, executionTimesMs, (n) => n * n, "O(n^2)", "Quadratic: nested iteration over input, common in naive sorting or pairwise comparisons."),
45
+ tryFit(inputSizes, executionTimesMs, (n) => n * n * n, "O(n^3)", "Cubic: triple-nested loops, e.g., naive matrix multiplication."),
46
+ tryFit(inputSizes, executionTimesMs, (n) => Math.pow(2, n), "O(2^n)", "Exponential: brute-force recursive algorithms, rapidly becomes infeasible."),
47
+ ];
48
+ // Sort by R^2 descending (best fit first)
49
+ fits.sort((a, b) => b.r2 - a.r2);
50
+ const best = fits[0];
51
+ const confidence = Math.max(0, Math.min(1, best.r2));
52
+ // Build a simple ASCII growth curve description
53
+ const growthCurve = buildGrowthCurve(inputSizes, executionTimesMs);
54
+ const allFits = fits.map((f) => ({ complexity: f.complexity, r2: f.r2 }));
55
+ const summary = [
56
+ `Big O Estimate: ${best.complexity} (confidence: ${(confidence * 100).toFixed(1)}%)`,
57
+ `${best.description}`,
58
+ "",
59
+ "All fits (by R² descending):",
60
+ ...allFits.map((f) => ` ${f.complexity.padEnd(10)} R² = ${f.r2.toFixed(4)}`),
61
+ "",
62
+ "Growth Curve:",
63
+ growthCurve,
64
+ ].join("\n");
65
+ return { bestFit: best.complexity, confidence, allFits, growthCurve, summary };
66
+ }
67
+ function buildGrowthCurve(sizes, times) {
68
+ const maxTime = Math.max(...times);
69
+ const maxBarWidth = 40;
70
+ const lines = [];
71
+ for (let i = 0; i < sizes.length; i++) {
72
+ const barLen = maxTime > 0 ? Math.round((times[i] / maxTime) * maxBarWidth) : 0;
73
+ const bar = "\u2588".repeat(barLen);
74
+ const sizeStr = String(sizes[i]).padStart(10);
75
+ const timeStr = times[i].toFixed(2).padStart(10);
76
+ lines.push(` n=${sizeStr} ${timeStr}ms ${bar}`);
77
+ }
78
+ return lines.join("\n");
79
+ }
80
+ //# sourceMappingURL=big-o-estimator.js.map
@@ -0,0 +1,25 @@
1
+ export interface ModuleInfo {
2
+ filePath: string;
3
+ sizeBytes: number;
4
+ sizeKB: number;
5
+ /** Percentage of total bundle size */
6
+ percentage: number;
7
+ }
8
+ export interface TreeShakingOpportunity {
9
+ filePath: string;
10
+ reason: string;
11
+ }
12
+ export interface BundleAnalysis {
13
+ filePath: string;
14
+ rawSizeBytes: number;
15
+ rawSizeKB: number;
16
+ gzipEstimateBytes: number;
17
+ gzipEstimateKB: number;
18
+ compressionRatio: number;
19
+ moduleCount: number;
20
+ largestModules: ModuleInfo[];
21
+ treeShakingOpportunities: TreeShakingOpportunity[];
22
+ summary: string;
23
+ }
24
+ export declare function analyzeBundle(filePath: string): BundleAnalysis;
25
+ //# sourceMappingURL=bundle-analyzer.d.ts.map