@skyramp/mcp 0.0.44 → 0.0.45

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.
Files changed (40) hide show
  1. package/build/index.js +15 -0
  2. package/build/prompts/code-reuse.js +1 -1
  3. package/build/prompts/driftAnalysisPrompt.js +159 -0
  4. package/build/prompts/modularization/ui-test-modularization.js +2 -0
  5. package/build/prompts/testGenerationPrompt.js +2 -2
  6. package/build/prompts/testHealthPrompt.js +82 -0
  7. package/build/services/DriftAnalysisService.js +924 -0
  8. package/build/services/ModularizationService.js +16 -1
  9. package/build/services/TestDiscoveryService.js +237 -0
  10. package/build/services/TestExecutionService.js +311 -0
  11. package/build/services/TestGenerationService.js +16 -2
  12. package/build/services/TestHealthService.js +653 -0
  13. package/build/tools/auth/loginTool.js +1 -1
  14. package/build/tools/auth/logoutTool.js +1 -1
  15. package/build/tools/code-refactor/codeReuseTool.js +5 -3
  16. package/build/tools/code-refactor/modularizationTool.js +8 -2
  17. package/build/tools/executeSkyrampTestTool.js +12 -122
  18. package/build/tools/fixErrorTool.js +1 -1
  19. package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
  20. package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
  21. package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
  22. package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
  23. package/build/tools/generate-tests/generateUIRestTool.js +1 -1
  24. package/build/tools/test-maintenance/actionsTool.js +202 -0
  25. package/build/tools/test-maintenance/analyzeTestDriftTool.js +188 -0
  26. package/build/tools/test-maintenance/calculateHealthScoresTool.js +248 -0
  27. package/build/tools/test-maintenance/discoverTestsTool.js +135 -0
  28. package/build/tools/test-maintenance/executeBatchTestsTool.js +188 -0
  29. package/build/tools/test-maintenance/stateCleanupTool.js +145 -0
  30. package/build/tools/test-recommendation/analyzeRepositoryTool.js +16 -1
  31. package/build/tools/test-recommendation/recommendTestsTool.js +6 -2
  32. package/build/tools/trace/startTraceCollectionTool.js +1 -1
  33. package/build/tools/trace/stopTraceCollectionTool.js +1 -1
  34. package/build/types/TestAnalysis.js +1 -0
  35. package/build/types/TestDriftAnalysis.js +1 -0
  36. package/build/types/TestExecution.js +6 -0
  37. package/build/types/TestHealth.js +4 -0
  38. package/build/utils/AnalysisStateManager.js +238 -0
  39. package/build/utils/utils.test.js +25 -9
  40. package/package.json +6 -3
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ import { AnalysisStateManager } from "../../utils/AnalysisStateManager.js";
3
+ import { logger } from "../../utils/logger.js";
4
+ /**
5
+ * Register the state file cleanup tool with the MCP server
6
+ *
7
+ * This tool helps manage state files created during test analysis workflows.
8
+ * It can list existing state files and clean up old ones to save disk space.
9
+ */
10
+ export function registerStateCleanupTool(server) {
11
+ server.registerTool("skyramp_state_cleanup", {
12
+ description: `Manage and cleanup test analysis state files.
13
+
14
+ **WHAT IT DOES:**
15
+ - List all existing state files with details (size, age, session ID)
16
+ - Delete state files older than specified age
17
+ - Show disk space usage by state files
18
+
19
+ **WHEN TO USE:**
20
+ - Periodically to clean up old analysis sessions
21
+ - To check current state files in the system
22
+ - To free up disk space from temporary analysis data
23
+
24
+ **OPTIONS:**
25
+ - action: "list" - Show all state files
26
+ - action: "cleanup" - Delete old state files
27
+ - maxAgeHours: How old files must be before deletion (default: 24 hours)
28
+
29
+ **Output:**
30
+ Information about state files and cleanup results.`,
31
+ inputSchema: {
32
+ action: z
33
+ .enum(["list", "cleanup"])
34
+ .describe('Action to perform: "list" shows all state files, "cleanup" deletes old files'),
35
+ maxAgeHours: z
36
+ .number()
37
+ .optional()
38
+ .default(24)
39
+ .describe("For cleanup action: delete files older than this many hours (default: 24)"),
40
+ },
41
+ }, async (args) => {
42
+ try {
43
+ logger.info(`State file ${args.action} requested`);
44
+ if (args.action === "list") {
45
+ // List all state files
46
+ const stateFiles = await AnalysisStateManager.listStateFiles();
47
+ if (stateFiles.length === 0) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: JSON.stringify({
53
+ message: "No state files found",
54
+ totalFiles: 0,
55
+ totalSize: 0,
56
+ }, null, 2),
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ const totalSize = stateFiles.reduce((sum, file) => sum + file.size, 0);
62
+ const formatSize = (bytes) => {
63
+ if (bytes === 0)
64
+ return "0 B";
65
+ const k = 1024;
66
+ const sizes = ["B", "KB", "MB", "GB"];
67
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
68
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
69
+ };
70
+ const fileDetails = stateFiles.map((file) => ({
71
+ sessionId: file.sessionId,
72
+ path: file.path,
73
+ size: formatSize(file.size),
74
+ sizeBytes: file.size,
75
+ createdAt: file.createdAt.toISOString(),
76
+ modifiedAt: file.modifiedAt.toISOString(),
77
+ ageHours: ((Date.now() - file.modifiedAt.getTime()) /
78
+ (1000 * 60 * 60)).toFixed(1),
79
+ }));
80
+ logger.info(`Found ${stateFiles.length} state files, total size: ${formatSize(totalSize)}`);
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: JSON.stringify({
86
+ totalFiles: stateFiles.length,
87
+ totalSize: formatSize(totalSize),
88
+ totalSizeBytes: totalSize,
89
+ files: fileDetails,
90
+ }, null, 2),
91
+ },
92
+ ],
93
+ };
94
+ }
95
+ else if (args.action === "cleanup") {
96
+ // Cleanup old state files
97
+ const maxAgeHours = args.maxAgeHours || 24;
98
+ const deletedCount = await AnalysisStateManager.cleanupOldStateFiles(maxAgeHours);
99
+ logger.info(`Cleaned up ${deletedCount} state files older than ${maxAgeHours} hours`);
100
+ // Get remaining files
101
+ const remainingFiles = await AnalysisStateManager.listStateFiles();
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: JSON.stringify({
107
+ deletedCount,
108
+ maxAgeHours,
109
+ remainingFiles: remainingFiles.length,
110
+ message: deletedCount > 0
111
+ ? `Successfully deleted ${deletedCount} state file(s) older than ${maxAgeHours} hours`
112
+ : `No state files found older than ${maxAgeHours} hours`,
113
+ }, null, 2),
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ return {
119
+ content: [
120
+ {
121
+ type: "text",
122
+ text: JSON.stringify({
123
+ error: "Invalid action",
124
+ }, null, 2),
125
+ },
126
+ ],
127
+ isError: true,
128
+ };
129
+ }
130
+ catch (error) {
131
+ logger.error(`State cleanup failed: ${error.message}`, error);
132
+ return {
133
+ content: [
134
+ {
135
+ type: "text",
136
+ text: JSON.stringify({
137
+ error: error.message,
138
+ }, null, 2),
139
+ },
140
+ ],
141
+ isError: true,
142
+ };
143
+ }
144
+ });
145
+ }
@@ -20,7 +20,22 @@ const analyzeRepositorySchema = z.object({
20
20
  });
21
21
  export function registerAnalyzeRepositoryTool(server) {
22
22
  server.registerTool("skyramp_analyze_repository", {
23
- description: `Analyze a code repository to understand its structure, technology stack, and testing readiness.
23
+ description: `
24
+ ⚠️ MANDATORY FIRST STEP - DISPLAY THIS WARNING:
25
+ Before ANY other response text, you MUST output this exact message:
26
+ "** This tool is currently in Early Preview stage. Please verify the results. **"
27
+
28
+ DO NOT proceed with tool calls until you have displayed this warning in your response.
29
+
30
+ ❌ WRONG: Calling this tool without first displaying the warning message to the user
31
+ ✅ CORRECT: First display the warning message in plain text, THEN call this tool
32
+
33
+ EXPECTED FLOW:
34
+ 1. System displays: "** This tool is currently in Early Preview stage..."
35
+ 2. System calls: skyramp_analyze_repository(...)
36
+ 3. System processes results
37
+
38
+ Analyze a code repository to understand its structure, technology stack, and testing readiness.
24
39
 
25
40
  This tool performs comprehensive repository analysis including:
26
41
  - Project type classification (REST API, Frontend, Full-stack, Microservices, etc.)
@@ -56,7 +56,9 @@ For each recommended test type, you'll get:
56
56
  - DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
57
57
  - DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
58
58
 
59
- Output: TestRecommendation JSON with prioritized, actionable test recommendations.`,
59
+ Output: TestRecommendation JSON with prioritized, actionable test recommendations.
60
+ **CRITICAL:** At the end of the tool execution, MUST display the below message:
61
+ ** This tool is currently in Early Preview stage. Please verify the results. **`,
60
62
  inputSchema: recommendTestsSchema.shape,
61
63
  }, async (params) => {
62
64
  try {
@@ -108,7 +110,9 @@ ${prompt}
108
110
  - DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
109
111
  - DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
110
112
 
111
- After analyzing the data above, return the complete JSON response following the structure defined in the prompt.`,
113
+ After analyzing the data above, return the complete JSON response following the structure defined in the prompt.
114
+ **CRITICAL:** At the end of the tool execution, MUST display the below message:
115
+ ** This tool is currently in Early Preview stage. Please verify the results. **`,
112
116
  },
113
117
  ],
114
118
  isError: false,
@@ -57,7 +57,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
57
57
  .string()
58
58
  .describe("The prompt user provided to start trace collection"),
59
59
  },
60
- annotations: {
60
+ _meta: {
61
61
  keywords: ["start trace", "trace collection", "trace generation"],
62
62
  },
63
63
  }, async (params) => {
@@ -36,7 +36,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/load-test/advance
36
36
  .string()
37
37
  .describe("The prompt user provided to stop trace collection"),
38
38
  },
39
- annotations: {
39
+ _meta: {
40
40
  keywords: ["stop trace", "collect generated trace"],
41
41
  },
42
42
  }, async (params) => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Types for Test Execution
3
+ *
4
+ * These types define the structure for test execution results
5
+ */
6
+ export {};
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Types for Test Health Analysis and Recommendations
3
+ */
4
+ export {};
@@ -0,0 +1,238 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { logger } from "./logger.js";
5
+ /**
6
+ * Manages persistent state for test analysis workflow
7
+ * Reduces token usage by storing intermediate results in filesystem
8
+ */
9
+ export class AnalysisStateManager {
10
+ stateFile;
11
+ sessionId;
12
+ /**
13
+ * Create a new state manager
14
+ * @param sessionId Unique session identifier (defaults to timestamp)
15
+ * @param stateDir Directory to store state files (defaults to /tmp)
16
+ */
17
+ constructor(sessionId, stateDir) {
18
+ this.sessionId = sessionId || Date.now().toString();
19
+ const baseDir = stateDir || os.tmpdir();
20
+ this.stateFile = path.join(baseDir, `skyramp-analysis-${this.sessionId}.json`);
21
+ }
22
+ /**
23
+ * Create state manager from existing state file path
24
+ */
25
+ static fromStatePath(stateFilePath) {
26
+ const basename = path.basename(stateFilePath, ".json");
27
+ const sessionId = basename.replace("skyramp-analysis-", "");
28
+ const stateDir = path.dirname(stateFilePath);
29
+ return new AnalysisStateManager(sessionId, stateDir);
30
+ }
31
+ /**
32
+ * Read current state from file
33
+ * @returns Test analysis results, or empty array if file doesn't exist
34
+ */
35
+ async readState() {
36
+ if (!this.exists()) {
37
+ logger.debug(`State file does not exist: ${this.stateFile}`);
38
+ return [];
39
+ }
40
+ try {
41
+ const content = await fs.promises.readFile(this.stateFile, "utf-8");
42
+ const state = JSON.parse(content);
43
+ logger.debug(`Read ${state.tests.length} tests from state file: ${this.stateFile}`);
44
+ return state.tests;
45
+ }
46
+ catch (error) {
47
+ logger.error(`Failed to read state file: ${error.message}`);
48
+ throw new Error(`Failed to read state file: ${error.message}`);
49
+ }
50
+ }
51
+ /**
52
+ * Read full state including metadata
53
+ */
54
+ async readFullState() {
55
+ if (!this.exists()) {
56
+ return null;
57
+ }
58
+ try {
59
+ const content = await fs.promises.readFile(this.stateFile, "utf-8");
60
+ return JSON.parse(content);
61
+ }
62
+ catch (error) {
63
+ logger.error(`Failed to read state file: ${error.message}`);
64
+ throw new Error(`Failed to read state file: ${error.message}`);
65
+ }
66
+ }
67
+ /**
68
+ * Write test results to state file
69
+ * @param tests Test analysis results
70
+ * @param options Additional metadata options
71
+ */
72
+ async writeState(tests, options) {
73
+ try {
74
+ // Read existing metadata if file exists
75
+ let existingMetadata;
76
+ if (this.exists()) {
77
+ const existing = await this.readFullState();
78
+ existingMetadata = existing?.metadata;
79
+ }
80
+ const state = {
81
+ tests,
82
+ metadata: {
83
+ sessionId: this.sessionId,
84
+ repositoryPath: options?.repositoryPath || existingMetadata?.repositoryPath,
85
+ createdAt: existingMetadata?.createdAt || new Date().toISOString(),
86
+ updatedAt: new Date().toISOString(),
87
+ step: options?.step,
88
+ },
89
+ };
90
+ await fs.promises.writeFile(this.stateFile, JSON.stringify(state, null, 2), "utf-8");
91
+ logger.debug(`Wrote ${tests.length} tests to state file: ${this.stateFile}`);
92
+ }
93
+ catch (error) {
94
+ logger.error(`Failed to write state file: ${error.message}`);
95
+ throw new Error(`Failed to write state file: ${error.message}`);
96
+ }
97
+ }
98
+ // /**
99
+ // * Read state with filtering
100
+ // * @param filter Function to filter tests
101
+ // */
102
+ // async readStateFiltered(
103
+ // filter: (test: TestAnalysisResult) => boolean,
104
+ // ): Promise<TestAnalysisResult[]> {
105
+ // const allTests = await this.readState();
106
+ // return allTests.filter(filter);
107
+ // }
108
+ // /**
109
+ // * Read state with pagination
110
+ // * @param page Page number (0-indexed)
111
+ // * @param pageSize Number of tests per page
112
+ // */
113
+ // async readStatePaginated(
114
+ // page: number,
115
+ // pageSize: number = 100,
116
+ // ): Promise<{
117
+ // tests: TestAnalysisResult[];
118
+ // hasMore: boolean;
119
+ // total: number;
120
+ // page: number;
121
+ // pageSize: number;
122
+ // }> {
123
+ // const allTests = await this.readState();
124
+ // const start = page * pageSize;
125
+ // const end = start + pageSize;
126
+ // return {
127
+ // tests: allTests.slice(start, end),
128
+ // hasMore: end < allTests.length,
129
+ // total: allTests.length,
130
+ // page,
131
+ // pageSize,
132
+ // };
133
+ // }
134
+ /**
135
+ * Get the state file path
136
+ */
137
+ getStatePath() {
138
+ return this.stateFile;
139
+ }
140
+ /**
141
+ * Get the session ID
142
+ */
143
+ getSessionId() {
144
+ return this.sessionId;
145
+ }
146
+ /**
147
+ * Check if state file exists
148
+ */
149
+ exists() {
150
+ return fs.existsSync(this.stateFile);
151
+ }
152
+ /**
153
+ * Delete the state file
154
+ */
155
+ async delete() {
156
+ if (this.exists()) {
157
+ await fs.promises.unlink(this.stateFile);
158
+ logger.debug(`Deleted state file: ${this.stateFile}`);
159
+ }
160
+ }
161
+ /**
162
+ * Get state file size in bytes
163
+ */
164
+ async getSize() {
165
+ if (!this.exists()) {
166
+ return 0;
167
+ }
168
+ const stats = await fs.promises.stat(this.stateFile);
169
+ return stats.size;
170
+ }
171
+ /**
172
+ * Get human-readable state file size
173
+ */
174
+ async getSizeFormatted() {
175
+ const bytes = await this.getSize();
176
+ if (bytes === 0)
177
+ return "0 B";
178
+ const k = 1024;
179
+ const sizes = ["B", "KB", "MB", "GB"];
180
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
181
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
182
+ }
183
+ /**
184
+ * Cleanup old state files
185
+ * @param maxAgeHours Maximum age in hours
186
+ * @param stateDir Directory to clean (defaults to /tmp)
187
+ * @returns Number of files deleted
188
+ */
189
+ static async cleanupOldStateFiles(maxAgeHours = 24, stateDir) {
190
+ const baseDir = stateDir || os.tmpdir();
191
+ const files = await fs.promises.readdir(baseDir);
192
+ const stateFiles = files.filter((f) => f.startsWith("skyramp-analysis-"));
193
+ let deletedCount = 0;
194
+ const now = Date.now();
195
+ const maxAge = maxAgeHours * 60 * 60 * 1000;
196
+ for (const file of stateFiles) {
197
+ const filePath = path.join(baseDir, file);
198
+ try {
199
+ const stats = await fs.promises.stat(filePath);
200
+ const age = now - stats.mtimeMs;
201
+ if (age > maxAge) {
202
+ await fs.promises.unlink(filePath);
203
+ deletedCount++;
204
+ logger.debug(`Deleted old state file: ${filePath}`);
205
+ }
206
+ }
207
+ catch (error) {
208
+ logger.error(`Failed to delete state file ${filePath}: ${error.message}`);
209
+ }
210
+ }
211
+ if (deletedCount > 0) {
212
+ logger.info(`Cleaned up ${deletedCount} old state files`);
213
+ }
214
+ return deletedCount;
215
+ }
216
+ /**
217
+ * List all state files in directory
218
+ * @param stateDir Directory to search (defaults to /tmp)
219
+ */
220
+ static async listStateFiles(stateDir) {
221
+ const baseDir = stateDir || os.tmpdir();
222
+ const files = await fs.promises.readdir(baseDir);
223
+ const stateFiles = files.filter((f) => f.startsWith("skyramp-analysis-"));
224
+ const fileInfos = await Promise.all(stateFiles.map(async (file) => {
225
+ const filePath = path.join(baseDir, file);
226
+ const stats = await fs.promises.stat(filePath);
227
+ const sessionId = file.replace("skyramp-analysis-", "").replace(".json", "");
228
+ return {
229
+ sessionId,
230
+ path: filePath,
231
+ size: stats.size,
232
+ createdAt: stats.birthtime,
233
+ modifiedAt: stats.mtime,
234
+ };
235
+ }));
236
+ return fileInfos.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
237
+ }
238
+ }
@@ -12,48 +12,64 @@ describe("validateParams", () => {
12
12
  it("should return error for JSON-like input", () => {
13
13
  const result = validateParams('{"foo":"bar"}', "testField");
14
14
  expect(result?.isError).toBe(true);
15
- expect(result?.content?.[0].text).toMatch(/not a JSON object/);
15
+ const firstContent = result?.content?.[0];
16
+ expect(firstContent?.type).toBe("text");
17
+ if (firstContent?.type === "text") {
18
+ expect(firstContent.text).toMatch(/key=value/);
19
+ }
16
20
  });
17
21
  it("should return error for missing value", () => {
18
22
  const result = validateParams("foo=", "testField");
19
23
  expect(result?.isError).toBe(true);
20
- expect(result?.content?.[0].text).toMatch(/key=value/);
24
+ const firstContent = result?.content?.[0];
25
+ expect(firstContent?.type).toBe("text");
26
+ if (firstContent?.type === "text") {
27
+ expect(firstContent.text).toMatch(/key=value/);
28
+ }
21
29
  });
22
30
  it("should return error for missing key", () => {
23
31
  const result = validateParams("=bar", "testField");
24
32
  expect(result?.isError).toBe(true);
25
- expect(result?.content?.[0].text).toMatch(/key=value/);
33
+ const firstContent = result?.content?.[0];
34
+ expect(firstContent?.type).toBe("text");
35
+ if (firstContent?.type === "text") {
36
+ expect(firstContent.text).toMatch(/key=value/);
37
+ }
26
38
  });
27
39
  it("should return error for missing key and value", () => {
28
40
  const result = validateParams("=", "testField");
29
41
  expect(result?.isError).toBe(true);
30
- expect(result?.content?.[0].text).toMatch(/key=value/);
42
+ const firstContent = result?.content?.[0];
43
+ expect(firstContent?.type).toBe("text");
44
+ if (firstContent?.type === "text") {
45
+ expect(firstContent.text).toMatch(/key=value/);
46
+ }
31
47
  });
32
48
  });
33
49
  describe("generateSkyrampHeader", () => {
34
50
  it("should generate correct header for Python", () => {
35
51
  const header = generateSkyrampHeader("python");
36
- expect(header).toMatch(/^# Generated by Skyramp v1\.2\.23 on/);
52
+ expect(header).toMatch(/^# Generated by Skyramp on/);
37
53
  expect(header).toContain("\n\n");
38
54
  });
39
55
  it("should generate correct header for JavaScript", () => {
40
56
  const header = generateSkyrampHeader("javascript");
41
- expect(header).toMatch(/^\/\/ Generated by Skyramp v1\.2\.23 on/);
57
+ expect(header).toMatch(/^\/\/ Generated by Skyramp on/);
42
58
  expect(header).toContain("\n\n");
43
59
  });
44
60
  it("should generate correct header for TypeScript", () => {
45
61
  const header = generateSkyrampHeader("typescript");
46
- expect(header).toMatch(/^\/\/ Generated by Skyramp v1\.2\.23 on/);
62
+ expect(header).toMatch(/^\/\/ Generated by Skyramp on/);
47
63
  expect(header).toContain("\n\n");
48
64
  });
49
65
  it("should generate correct header for Java", () => {
50
66
  const header = generateSkyrampHeader("java");
51
- expect(header).toMatch(/^\/\/ Generated by Skyramp v1\.2\.23 on/);
67
+ expect(header).toMatch(/^\/\/ Generated by Skyramp on/);
52
68
  expect(header).toContain("\n\n");
53
69
  });
54
70
  it("should generate correct header for unknown language", () => {
55
71
  const header = generateSkyrampHeader("unknown");
56
- expect(header).toMatch(/^# Generated by Skyramp v1\.2\.23 on/);
72
+ expect(header).toMatch(/^# Generated by Skyramp on/);
57
73
  expect(header).toContain("\n\n");
58
74
  });
59
75
  it("should include timestamp in header", () => {
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@skyramp/mcp",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "main": "build/index.js",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mcp": "./build/index.js"
8
8
  },
9
9
  "scripts": {
10
+ "clean-build": "rm -rf build && npm run build",
10
11
  "build": "tsc && chmod 755 build/index.js",
11
12
  "build:prod": "tsc --sourceMap false && chmod 755 build/index.js",
12
13
  "pack": "npm run build:prod && npm pack",
@@ -41,10 +42,12 @@
41
42
  "url": "https://github.com/skyramp/mcp/issues"
42
43
  },
43
44
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.11.4",
45
- "@skyramp/skyramp": "^1.2.37",
45
+ "@modelcontextprotocol/sdk": "^1.24.3",
46
+ "@skyramp/skyramp": "^1.2.38",
46
47
  "@playwright/test": "^1.55.0",
47
48
  "dockerode": "^4.0.6",
49
+ "fast-glob": "^3.3.3",
50
+ "simple-git": "^3.30.0",
48
51
  "zod": "^3.25.3"
49
52
  },
50
53
  "devDependencies": {