@stacksfinder/mcp-server 1.5.1 → 1.6.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 +6 -1
- package/dist/data/compatibility_matrix.json +1160 -228
- package/dist/data/index.d.ts +2 -2
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +52 -43
- package/dist/data/index.js.map +1 -1
- package/dist/data/technology_scores.json +2936 -1030
- package/dist/http.js +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +463 -313
- package/dist/server.js.map +1 -1
- package/dist/tools/audit.d.ts +81 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +358 -171
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/list-techs.d.ts +4 -4
- package/dist/tools/list-techs.js +1 -1
- package/dist/tools/list-techs.js.map +1 -1
- package/dist/tools/project-kit/generate.d.ts.map +1 -1
- package/dist/tools/project-kit/generate.js +2 -1
- package/dist/tools/project-kit/generate.js.map +1 -1
- package/dist/tools/project-kit/match-mcps.d.ts +1 -1
- package/dist/tools/project-kit/match-mcps.d.ts.map +1 -1
- package/dist/tools/project-kit/match-mcps.js +213 -199
- package/dist/tools/project-kit/match-mcps.js.map +1 -1
- package/dist/tools/recommend-demo.d.ts.map +1 -1
- package/dist/tools/recommend-demo.js +34 -10
- package/dist/tools/recommend-demo.js.map +1 -1
- package/package.json +75 -75
package/dist/tools/audit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { apiRequest } from
|
|
3
|
-
import { McpError, ErrorCode, checkProAccess } from
|
|
4
|
-
import { debug } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiRequest } from "../utils/api-client.js";
|
|
3
|
+
import { McpError, ErrorCode, checkProAccess } from "../utils/errors.js";
|
|
4
|
+
import { debug } from "../utils/logger.js";
|
|
5
5
|
// ============================================================================
|
|
6
6
|
// SCHEMAS
|
|
7
7
|
// ============================================================================
|
|
@@ -9,36 +9,51 @@ import { debug } from '../utils/logger.js';
|
|
|
9
9
|
* Input schema for create_audit tool.
|
|
10
10
|
*/
|
|
11
11
|
export const CreateAuditInputSchema = z.object({
|
|
12
|
-
name: z.string().min(1).max(200).describe(
|
|
12
|
+
name: z.string().min(1).max(200).describe("Name for the audit report"),
|
|
13
13
|
technologies: z
|
|
14
14
|
.array(z.object({
|
|
15
|
-
name: z
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
name: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe('Technology name (e.g., "react", "express", "lodash")'),
|
|
19
|
+
version: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Version string (e.g., "18.2.0", "4.17.21")'),
|
|
23
|
+
category: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Optional category (e.g., "frontend", "backend")'),
|
|
18
27
|
}))
|
|
19
28
|
.min(1)
|
|
20
29
|
.max(50)
|
|
21
|
-
.describe(
|
|
30
|
+
.describe("List of technologies to audit"),
|
|
22
31
|
});
|
|
23
32
|
/**
|
|
24
33
|
* Input schema for get_audit tool.
|
|
25
34
|
*/
|
|
26
35
|
export const GetAuditInputSchema = z.object({
|
|
27
|
-
auditId: z.string().uuid().describe(
|
|
36
|
+
auditId: z.string().uuid().describe("Audit report UUID"),
|
|
28
37
|
});
|
|
29
38
|
/**
|
|
30
39
|
* Input schema for list_audits tool.
|
|
31
40
|
*/
|
|
32
41
|
export const ListAuditsInputSchema = z.object({
|
|
33
|
-
limit: z
|
|
34
|
-
|
|
42
|
+
limit: z
|
|
43
|
+
.number()
|
|
44
|
+
.min(1)
|
|
45
|
+
.max(50)
|
|
46
|
+
.optional()
|
|
47
|
+
.default(10)
|
|
48
|
+
.describe("Max results to return"),
|
|
49
|
+
offset: z.number().min(0).optional().default(0).describe("Pagination offset"),
|
|
35
50
|
});
|
|
36
51
|
/**
|
|
37
52
|
* Input schema for compare_audits tool.
|
|
38
53
|
*/
|
|
39
54
|
export const CompareAuditsInputSchema = z.object({
|
|
40
|
-
baseAuditId: z.string().uuid().describe(
|
|
41
|
-
compareAuditId: z.string().uuid().describe(
|
|
55
|
+
baseAuditId: z.string().uuid().describe("Base audit ID (older)"),
|
|
56
|
+
compareAuditId: z.string().uuid().describe("Compare audit ID (newer)"),
|
|
42
57
|
});
|
|
43
58
|
/**
|
|
44
59
|
* Input schema for get_audit_quota tool.
|
|
@@ -48,165 +63,168 @@ export const GetAuditQuotaInputSchema = z.object({});
|
|
|
48
63
|
* Input schema for get_migration_recommendation tool.
|
|
49
64
|
*/
|
|
50
65
|
export const GetMigrationRecommendationInputSchema = z.object({
|
|
51
|
-
auditId: z
|
|
66
|
+
auditId: z
|
|
67
|
+
.string()
|
|
68
|
+
.uuid()
|
|
69
|
+
.describe("Audit report UUID to analyze for migration"),
|
|
52
70
|
});
|
|
53
71
|
// ============================================================================
|
|
54
72
|
// TOOL DEFINITIONS
|
|
55
73
|
// ============================================================================
|
|
56
74
|
export const createAuditToolDefinition = {
|
|
57
|
-
name:
|
|
58
|
-
description: `Create a technical debt audit for your tech stack. Analyzes for deprecated packages, security vulnerabilities, EOL versions, and upgrade recommendations.
|
|
59
|
-
|
|
60
|
-
**Tier**: Requires Pro or Team subscription (OR OAuth session)
|
|
61
|
-
|
|
62
|
-
**Prerequisites**:
|
|
63
|
-
- Pro/Team account or authenticated via OAuth
|
|
64
|
-
- List of technologies with versions (use package.json data)
|
|
65
|
-
|
|
66
|
-
**Next Steps**:
|
|
67
|
-
- Get full report: \`get_audit({ auditId: "returned-uuid" })\`
|
|
68
|
-
- Get migration plan: \`get_migration_recommendation({ auditId: "uuid" })\`
|
|
69
|
-
- Compare over time: \`compare_audits({ baseAuditId, compareAuditId })\`
|
|
70
|
-
|
|
71
|
-
**Output includes**:
|
|
72
|
-
- Health score (0-100)
|
|
73
|
-
- Findings by severity (critical/high/medium/low/info)
|
|
74
|
-
- Actionable upgrade recommendations
|
|
75
|
-
- CVE detection for known vulnerabilities
|
|
76
|
-
|
|
77
|
-
**Common Pitfalls**:
|
|
78
|
-
- Include version numbers for accurate vulnerability detection
|
|
79
|
-
- Use technology names as they appear in package managers
|
|
80
|
-
|
|
75
|
+
name: "create_audit",
|
|
76
|
+
description: `Create a technical debt audit for your tech stack. Analyzes for deprecated packages, security vulnerabilities, EOL versions, and upgrade recommendations.
|
|
77
|
+
|
|
78
|
+
**Tier**: Requires Pro or Team subscription (OR OAuth session)
|
|
79
|
+
|
|
80
|
+
**Prerequisites**:
|
|
81
|
+
- Pro/Team account or authenticated via OAuth
|
|
82
|
+
- List of technologies with versions (use package.json data)
|
|
83
|
+
|
|
84
|
+
**Next Steps**:
|
|
85
|
+
- Get full report: \`get_audit({ auditId: "returned-uuid" })\`
|
|
86
|
+
- Get migration plan: \`get_migration_recommendation({ auditId: "uuid" })\`
|
|
87
|
+
- Compare over time: \`compare_audits({ baseAuditId, compareAuditId })\`
|
|
88
|
+
|
|
89
|
+
**Output includes**:
|
|
90
|
+
- Health score (0-100)
|
|
91
|
+
- Findings by severity (critical/high/medium/low/info)
|
|
92
|
+
- Actionable upgrade recommendations
|
|
93
|
+
- CVE detection for known vulnerabilities
|
|
94
|
+
|
|
95
|
+
**Common Pitfalls**:
|
|
96
|
+
- Include version numbers for accurate vulnerability detection
|
|
97
|
+
- Use technology names as they appear in package managers
|
|
98
|
+
|
|
81
99
|
**Example**: \`create_audit({ name: "Q1 2026 Stack Review", technologies: [{ name: "React", version: "17.0.0" }, { name: "Node.js", version: "14.0.0" }] })\``,
|
|
82
100
|
inputSchema: {
|
|
83
|
-
type:
|
|
101
|
+
type: "object",
|
|
84
102
|
properties: {
|
|
85
103
|
name: {
|
|
86
|
-
type:
|
|
87
|
-
description: 'Name for the audit report (e.g., "Q1 2026 Stack Review")'
|
|
104
|
+
type: "string",
|
|
105
|
+
description: 'Name for the audit report (e.g., "Q1 2026 Stack Review")',
|
|
88
106
|
},
|
|
89
107
|
technologies: {
|
|
90
|
-
type:
|
|
108
|
+
type: "array",
|
|
91
109
|
items: {
|
|
92
|
-
type:
|
|
110
|
+
type: "object",
|
|
93
111
|
properties: {
|
|
94
|
-
name: { type:
|
|
95
|
-
version: { type:
|
|
96
|
-
category: { type:
|
|
112
|
+
name: { type: "string", description: "Technology name" },
|
|
113
|
+
version: { type: "string", description: "Version (optional)" },
|
|
114
|
+
category: { type: "string", description: "Category (optional)" },
|
|
97
115
|
},
|
|
98
|
-
required: [
|
|
116
|
+
required: ["name"],
|
|
99
117
|
},
|
|
100
|
-
description:
|
|
101
|
-
}
|
|
118
|
+
description: "Technologies to audit",
|
|
119
|
+
},
|
|
102
120
|
},
|
|
103
|
-
required: [
|
|
104
|
-
}
|
|
121
|
+
required: ["name", "technologies"],
|
|
122
|
+
},
|
|
105
123
|
};
|
|
106
124
|
export const getAuditToolDefinition = {
|
|
107
|
-
name:
|
|
108
|
-
description:
|
|
125
|
+
name: "get_audit",
|
|
126
|
+
description: "Fetch a completed audit report by ID. Returns all findings and health score.",
|
|
109
127
|
inputSchema: {
|
|
110
|
-
type:
|
|
128
|
+
type: "object",
|
|
111
129
|
properties: {
|
|
112
130
|
auditId: {
|
|
113
|
-
type:
|
|
114
|
-
format:
|
|
115
|
-
description:
|
|
116
|
-
}
|
|
131
|
+
type: "string",
|
|
132
|
+
format: "uuid",
|
|
133
|
+
description: "Audit report UUID",
|
|
134
|
+
},
|
|
117
135
|
},
|
|
118
|
-
required: [
|
|
119
|
-
}
|
|
136
|
+
required: ["auditId"],
|
|
137
|
+
},
|
|
120
138
|
};
|
|
121
139
|
export const listAuditsToolDefinition = {
|
|
122
|
-
name:
|
|
123
|
-
description:
|
|
140
|
+
name: "list_audits",
|
|
141
|
+
description: "List your audit reports with pagination. Shows name, status, health score, and creation date.",
|
|
124
142
|
inputSchema: {
|
|
125
|
-
type:
|
|
143
|
+
type: "object",
|
|
126
144
|
properties: {
|
|
127
145
|
limit: {
|
|
128
|
-
type:
|
|
129
|
-
description:
|
|
146
|
+
type: "number",
|
|
147
|
+
description: "Max results (1-50, default 10)",
|
|
130
148
|
},
|
|
131
149
|
offset: {
|
|
132
|
-
type:
|
|
133
|
-
description:
|
|
134
|
-
}
|
|
150
|
+
type: "number",
|
|
151
|
+
description: "Pagination offset (default 0)",
|
|
152
|
+
},
|
|
135
153
|
},
|
|
136
|
-
required: []
|
|
137
|
-
}
|
|
154
|
+
required: [],
|
|
155
|
+
},
|
|
138
156
|
};
|
|
139
157
|
export const compareAuditsToolDefinition = {
|
|
140
|
-
name:
|
|
141
|
-
description: `Compare two audit reports to track technical debt trends over time.
|
|
142
|
-
Shows new issues introduced, issues resolved, and health score change.
|
|
158
|
+
name: "compare_audits",
|
|
159
|
+
description: `Compare two audit reports to track technical debt trends over time.
|
|
160
|
+
Shows new issues introduced, issues resolved, and health score change.
|
|
143
161
|
Useful for measuring progress on debt reduction.`,
|
|
144
162
|
inputSchema: {
|
|
145
|
-
type:
|
|
163
|
+
type: "object",
|
|
146
164
|
properties: {
|
|
147
165
|
baseAuditId: {
|
|
148
|
-
type:
|
|
149
|
-
format:
|
|
150
|
-
description:
|
|
166
|
+
type: "string",
|
|
167
|
+
format: "uuid",
|
|
168
|
+
description: "Base (older) audit ID",
|
|
151
169
|
},
|
|
152
170
|
compareAuditId: {
|
|
153
|
-
type:
|
|
154
|
-
format:
|
|
155
|
-
description:
|
|
156
|
-
}
|
|
171
|
+
type: "string",
|
|
172
|
+
format: "uuid",
|
|
173
|
+
description: "Compare (newer) audit ID",
|
|
174
|
+
},
|
|
157
175
|
},
|
|
158
|
-
required: [
|
|
159
|
-
}
|
|
176
|
+
required: ["baseAuditId", "compareAuditId"],
|
|
177
|
+
},
|
|
160
178
|
};
|
|
161
179
|
export const getAuditQuotaToolDefinition = {
|
|
162
|
-
name:
|
|
163
|
-
description:
|
|
180
|
+
name: "get_audit_quota",
|
|
181
|
+
description: "Check your remaining audit quota for this month.",
|
|
164
182
|
inputSchema: {
|
|
165
|
-
type:
|
|
183
|
+
type: "object",
|
|
166
184
|
properties: {},
|
|
167
|
-
required: []
|
|
168
|
-
}
|
|
185
|
+
required: [],
|
|
186
|
+
},
|
|
169
187
|
};
|
|
170
188
|
export const getMigrationRecommendationToolDefinition = {
|
|
171
|
-
name:
|
|
172
|
-
description: `Analyze an audit report for migration opportunities.
|
|
173
|
-
Returns a detailed migration recommendation including:
|
|
174
|
-
- Technologies that should be replaced
|
|
175
|
-
- Recommended modern alternatives
|
|
176
|
-
- Migration roadmap with phases
|
|
177
|
-
- Risk assessment
|
|
178
|
-
- Builder constraints to pre-fill for generating a migration blueprint
|
|
179
|
-
|
|
189
|
+
name: "get_migration_recommendation",
|
|
190
|
+
description: `Analyze an audit report for migration opportunities.
|
|
191
|
+
Returns a detailed migration recommendation including:
|
|
192
|
+
- Technologies that should be replaced
|
|
193
|
+
- Recommended modern alternatives
|
|
194
|
+
- Migration roadmap with phases
|
|
195
|
+
- Risk assessment
|
|
196
|
+
- Builder constraints to pre-fill for generating a migration blueprint
|
|
197
|
+
|
|
180
198
|
Use this after create_audit to get actionable migration guidance.`,
|
|
181
199
|
inputSchema: {
|
|
182
|
-
type:
|
|
200
|
+
type: "object",
|
|
183
201
|
properties: {
|
|
184
202
|
auditId: {
|
|
185
|
-
type:
|
|
186
|
-
format:
|
|
187
|
-
description:
|
|
188
|
-
}
|
|
203
|
+
type: "string",
|
|
204
|
+
format: "uuid",
|
|
205
|
+
description: "Audit report UUID to analyze",
|
|
206
|
+
},
|
|
189
207
|
},
|
|
190
|
-
required: [
|
|
191
|
-
}
|
|
208
|
+
required: ["auditId"],
|
|
209
|
+
},
|
|
192
210
|
};
|
|
193
211
|
// ============================================================================
|
|
194
212
|
// FORMATTERS
|
|
195
213
|
// ============================================================================
|
|
196
214
|
function formatSeverityMarker(severity) {
|
|
197
215
|
switch (severity) {
|
|
198
|
-
case
|
|
199
|
-
return
|
|
200
|
-
case
|
|
201
|
-
return
|
|
202
|
-
case
|
|
203
|
-
return
|
|
204
|
-
case
|
|
205
|
-
return
|
|
206
|
-
case
|
|
207
|
-
return
|
|
216
|
+
case "critical":
|
|
217
|
+
return "[CRITICAL]";
|
|
218
|
+
case "high":
|
|
219
|
+
return "[HIGH]";
|
|
220
|
+
case "medium":
|
|
221
|
+
return "[MEDIUM]";
|
|
222
|
+
case "low":
|
|
223
|
+
return "[LOW]";
|
|
224
|
+
case "info":
|
|
225
|
+
return "[INFO]";
|
|
208
226
|
default:
|
|
209
|
-
return
|
|
227
|
+
return "[-]";
|
|
210
228
|
}
|
|
211
229
|
}
|
|
212
230
|
function formatHealthScore(score) {
|
|
@@ -255,7 +273,7 @@ function formatAuditReport(audit) {
|
|
|
255
273
|
text += `**Migration Effort**: ${finding.migrationEffort}\n`;
|
|
256
274
|
}
|
|
257
275
|
if (finding.cveIds && finding.cveIds.length > 0) {
|
|
258
|
-
text += `**CVEs**: ${finding.cveIds.join(
|
|
276
|
+
text += `**CVEs**: ${finding.cveIds.join(", ")}\n`;
|
|
259
277
|
}
|
|
260
278
|
text += `\n**Action**: ${finding.suggestedAction}\n\n`;
|
|
261
279
|
if (finding.references && finding.references.length > 0) {
|
|
@@ -267,7 +285,7 @@ function formatAuditReport(audit) {
|
|
|
267
285
|
text += `---\n\n`;
|
|
268
286
|
}
|
|
269
287
|
}
|
|
270
|
-
else if (audit.status ===
|
|
288
|
+
else if (audit.status === "completed") {
|
|
271
289
|
text += `\n### All Clear\n`;
|
|
272
290
|
text += `Your stack passed all checks. Great job maintaining your technical health!\n`;
|
|
273
291
|
}
|
|
@@ -281,13 +299,13 @@ function formatComparison(comparison) {
|
|
|
281
299
|
text += `| ${comparison.baseAudit.name} (base) | ${comparison.baseAudit.healthScore}/100 |\n`;
|
|
282
300
|
text += `| ${comparison.compareAudit.name} (compare) | ${comparison.compareAudit.healthScore}/100 |\n`;
|
|
283
301
|
text += `\n`;
|
|
284
|
-
const trendMarker = comparison.trend ===
|
|
285
|
-
?
|
|
286
|
-
: comparison.trend ===
|
|
287
|
-
?
|
|
288
|
-
:
|
|
302
|
+
const trendMarker = comparison.trend === "improving"
|
|
303
|
+
? "[UP]"
|
|
304
|
+
: comparison.trend === "degrading"
|
|
305
|
+
? "[DOWN]"
|
|
306
|
+
: "[STABLE]";
|
|
289
307
|
text += `### Trend: ${trendMarker} ${comparison.trend.toUpperCase()}\n`;
|
|
290
|
-
text += `**Health Score Change**: ${comparison.healthScoreDelta > 0 ?
|
|
308
|
+
text += `**Health Score Change**: ${comparison.healthScoreDelta > 0 ? "+" : ""}${comparison.healthScoreDelta} points\n\n`;
|
|
291
309
|
if (comparison.resolvedCount > 0) {
|
|
292
310
|
text += `### Resolved Issues (${comparison.resolvedCount})\n`;
|
|
293
311
|
for (const finding of comparison.resolvedFindings) {
|
|
@@ -317,10 +335,10 @@ function formatMigrationRecommendation(response) {
|
|
|
317
335
|
return `## Migration Analysis\n\nUnable to generate recommendation.`;
|
|
318
336
|
}
|
|
319
337
|
const urgencyMarker = {
|
|
320
|
-
critical:
|
|
321
|
-
high:
|
|
322
|
-
medium:
|
|
323
|
-
low:
|
|
338
|
+
critical: "[CRITICAL]",
|
|
339
|
+
high: "[HIGH]",
|
|
340
|
+
medium: "[MEDIUM]",
|
|
341
|
+
low: "[LOW]",
|
|
324
342
|
}[rec.urgency];
|
|
325
343
|
let text = `## ${urgencyMarker} Migration Recommendation\n\n`;
|
|
326
344
|
text += `### ${rec.title}\n\n`;
|
|
@@ -337,7 +355,7 @@ function formatMigrationRecommendation(response) {
|
|
|
337
355
|
text += `| Technology | Version | Reason |\n`;
|
|
338
356
|
text += `|------------|---------|--------|\n`;
|
|
339
357
|
for (const tech of rec.techsToReplace) {
|
|
340
|
-
text += `| ${tech.name} | ${tech.currentVersion ||
|
|
358
|
+
text += `| ${tech.name} | ${tech.currentVersion || "-"} | ${tech.reason} |\n`;
|
|
341
359
|
}
|
|
342
360
|
text += `\n`;
|
|
343
361
|
}
|
|
@@ -346,8 +364,8 @@ function formatMigrationRecommendation(response) {
|
|
|
346
364
|
text += `### Recommended Alternatives\n\n`;
|
|
347
365
|
for (const alt of rec.suggestedAlternatives) {
|
|
348
366
|
text += `**${alt.forTech}** → `;
|
|
349
|
-
const alts = alt.alternatives.map(a => a === alt.preferredChoice ? `**${a}** (recommended)` : a);
|
|
350
|
-
text += alts.join(
|
|
367
|
+
const alts = alt.alternatives.map((a) => a === alt.preferredChoice ? `**${a}** (recommended)` : a);
|
|
368
|
+
text += alts.join(", ");
|
|
351
369
|
text += `\n`;
|
|
352
370
|
text += ` _${alt.reason}_\n\n`;
|
|
353
371
|
}
|
|
@@ -359,7 +377,7 @@ function formatMigrationRecommendation(response) {
|
|
|
359
377
|
text += `**Phase ${step.order}: ${step.phase}** (${step.effort} effort)\n`;
|
|
360
378
|
text += `${step.description}\n`;
|
|
361
379
|
if (step.techsAffected.length > 0) {
|
|
362
|
-
text += `Affects: ${step.techsAffected.join(
|
|
380
|
+
text += `Affects: ${step.techsAffected.join(", ")}\n`;
|
|
363
381
|
}
|
|
364
382
|
text += `\n`;
|
|
365
383
|
}
|
|
@@ -368,7 +386,11 @@ function formatMigrationRecommendation(response) {
|
|
|
368
386
|
if (rec.risks.length > 0) {
|
|
369
387
|
text += `### Risk Assessment\n\n`;
|
|
370
388
|
for (const risk of rec.risks) {
|
|
371
|
-
const riskMarker = risk.level ===
|
|
389
|
+
const riskMarker = risk.level === "high"
|
|
390
|
+
? "[HIGH]"
|
|
391
|
+
: risk.level === "medium"
|
|
392
|
+
? "[MEDIUM]"
|
|
393
|
+
: "[LOW]";
|
|
372
394
|
text += `${riskMarker} **${risk.level.toUpperCase()} RISK**: ${risk.description}\n`;
|
|
373
395
|
text += ` _Mitigation_: ${risk.mitigation}\n\n`;
|
|
374
396
|
}
|
|
@@ -377,7 +399,7 @@ function formatMigrationRecommendation(response) {
|
|
|
377
399
|
if (rec.inferredConstraints.length > 0) {
|
|
378
400
|
text += `### Inferred Builder Constraints\n\n`;
|
|
379
401
|
text += `These constraints will be pre-filled when generating a migration blueprint:\n`;
|
|
380
|
-
text += rec.inferredConstraints.map(c => `\`${c}\``).join(
|
|
402
|
+
text += rec.inferredConstraints.map((c) => `\`${c}\``).join(", ");
|
|
381
403
|
text += `\n\n`;
|
|
382
404
|
}
|
|
383
405
|
// Builder Pre-fill info
|
|
@@ -391,10 +413,10 @@ function formatMigrationRecommendation(response) {
|
|
|
391
413
|
text += `- **Scale**: ${response.builderPreFill.context.scale}\n`;
|
|
392
414
|
}
|
|
393
415
|
if (response.builderPreFill.context.priorities.length > 0) {
|
|
394
|
-
text += `- **Priorities**: ${response.builderPreFill.context.priorities.join(
|
|
416
|
+
text += `- **Priorities**: ${response.builderPreFill.context.priorities.join(", ")}\n`;
|
|
395
417
|
}
|
|
396
418
|
if (response.builderPreFill.hints.techsToAvoid.length > 0) {
|
|
397
|
-
text += `- **Avoid**: ${response.builderPreFill.hints.techsToAvoid.join(
|
|
419
|
+
text += `- **Avoid**: ${response.builderPreFill.hints.techsToAvoid.join(", ")}\n`;
|
|
398
420
|
}
|
|
399
421
|
text += `\nVisit https://stacksfinder.com/builder to generate your migration blueprint.\n`;
|
|
400
422
|
}
|
|
@@ -408,21 +430,24 @@ function formatMigrationRecommendation(response) {
|
|
|
408
430
|
*/
|
|
409
431
|
export async function executeCreateAudit(input) {
|
|
410
432
|
// Check Pro access
|
|
411
|
-
const tierCheck = await checkProAccess(
|
|
433
|
+
const tierCheck = await checkProAccess("create_audit");
|
|
412
434
|
if (tierCheck)
|
|
413
435
|
return tierCheck;
|
|
414
|
-
debug(
|
|
436
|
+
debug("Creating audit", {
|
|
437
|
+
name: input.name,
|
|
438
|
+
techCount: input.technologies.length,
|
|
439
|
+
});
|
|
415
440
|
try {
|
|
416
|
-
const response = await apiRequest(
|
|
417
|
-
method:
|
|
441
|
+
const response = await apiRequest("/api/v1/audits", {
|
|
442
|
+
method: "POST",
|
|
418
443
|
body: {
|
|
419
444
|
name: input.name,
|
|
420
445
|
stackInput: {
|
|
421
|
-
technologies: input.technologies
|
|
446
|
+
technologies: input.technologies,
|
|
422
447
|
},
|
|
423
|
-
source:
|
|
448
|
+
source: "mcp",
|
|
424
449
|
},
|
|
425
|
-
timeoutMs: 30000
|
|
450
|
+
timeoutMs: 30000,
|
|
426
451
|
});
|
|
427
452
|
const text = formatAuditReport(response);
|
|
428
453
|
return { text };
|
|
@@ -431,7 +456,7 @@ export async function executeCreateAudit(input) {
|
|
|
431
456
|
if (err instanceof McpError) {
|
|
432
457
|
return { text: err.toResponseText(), isError: true };
|
|
433
458
|
}
|
|
434
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message :
|
|
459
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message : "Failed to create audit");
|
|
435
460
|
return { text: error.toResponseText(), isError: true };
|
|
436
461
|
}
|
|
437
462
|
}
|
|
@@ -440,10 +465,10 @@ export async function executeCreateAudit(input) {
|
|
|
440
465
|
*/
|
|
441
466
|
export async function executeGetAudit(input) {
|
|
442
467
|
// Check Pro access
|
|
443
|
-
const tierCheck = await checkProAccess(
|
|
468
|
+
const tierCheck = await checkProAccess("get_audit");
|
|
444
469
|
if (tierCheck)
|
|
445
470
|
return tierCheck;
|
|
446
|
-
debug(
|
|
471
|
+
debug("Fetching audit", { auditId: input.auditId });
|
|
447
472
|
try {
|
|
448
473
|
const response = await apiRequest(`/api/v1/audits/${input.auditId}`);
|
|
449
474
|
const text = formatAuditReport(response);
|
|
@@ -453,13 +478,13 @@ export async function executeGetAudit(input) {
|
|
|
453
478
|
if (err instanceof McpError) {
|
|
454
479
|
if (err.code === ErrorCode.NOT_FOUND) {
|
|
455
480
|
err.suggestions = [
|
|
456
|
-
|
|
457
|
-
|
|
481
|
+
"Use list_audits to see your available audit reports.",
|
|
482
|
+
"Create a new audit with create_audit.",
|
|
458
483
|
];
|
|
459
484
|
}
|
|
460
485
|
return { text: err.toResponseText(), isError: true };
|
|
461
486
|
}
|
|
462
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message :
|
|
487
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message : "Failed to fetch audit");
|
|
463
488
|
return { text: error.toResponseText(), isError: true };
|
|
464
489
|
}
|
|
465
490
|
}
|
|
@@ -468,22 +493,22 @@ export async function executeGetAudit(input) {
|
|
|
468
493
|
*/
|
|
469
494
|
export async function executeListAudits(input) {
|
|
470
495
|
// Check Pro access
|
|
471
|
-
const tierCheck = await checkProAccess(
|
|
496
|
+
const tierCheck = await checkProAccess("list_audits");
|
|
472
497
|
if (tierCheck)
|
|
473
498
|
return tierCheck;
|
|
474
|
-
debug(
|
|
499
|
+
debug("Listing audits", { limit: input.limit, offset: input.offset });
|
|
475
500
|
try {
|
|
476
501
|
const response = await apiRequest(`/api/v1/audits?limit=${input.limit}&offset=${input.offset}`);
|
|
477
502
|
if (response.audits.length === 0) {
|
|
478
503
|
return {
|
|
479
|
-
text: `## Your Audits\n\nNo audit reports found. Create one with the \`create_audit\` tool
|
|
504
|
+
text: `## Your Audits\n\nNo audit reports found. Create one with the \`create_audit\` tool.`,
|
|
480
505
|
};
|
|
481
506
|
}
|
|
482
507
|
let text = `## Your Audits (${response.audits.length} of ${response.total})\n\n`;
|
|
483
508
|
text += `| Name | Health Score | Status | Created |\n`;
|
|
484
509
|
text += `|------|-------------|--------|--------|\n`;
|
|
485
510
|
for (const audit of response.audits) {
|
|
486
|
-
const score = audit.summary?.healthScore ??
|
|
511
|
+
const score = audit.summary?.healthScore ?? "-";
|
|
487
512
|
const date = new Date(audit.createdAt).toLocaleDateString();
|
|
488
513
|
text += `| ${audit.name} | ${score}/100 | ${audit.status} | ${date} |\n`;
|
|
489
514
|
}
|
|
@@ -496,7 +521,7 @@ export async function executeListAudits(input) {
|
|
|
496
521
|
if (err instanceof McpError) {
|
|
497
522
|
return { text: err.toResponseText(), isError: true };
|
|
498
523
|
}
|
|
499
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message :
|
|
524
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message : "Failed to list audits");
|
|
500
525
|
return { text: error.toResponseText(), isError: true };
|
|
501
526
|
}
|
|
502
527
|
}
|
|
@@ -505,17 +530,20 @@ export async function executeListAudits(input) {
|
|
|
505
530
|
*/
|
|
506
531
|
export async function executeCompareAudits(input) {
|
|
507
532
|
// Check Pro access
|
|
508
|
-
const tierCheck = await checkProAccess(
|
|
533
|
+
const tierCheck = await checkProAccess("compare_audits");
|
|
509
534
|
if (tierCheck)
|
|
510
535
|
return tierCheck;
|
|
511
|
-
debug(
|
|
536
|
+
debug("Comparing audits", {
|
|
537
|
+
base: input.baseAuditId,
|
|
538
|
+
compare: input.compareAuditId,
|
|
539
|
+
});
|
|
512
540
|
try {
|
|
513
|
-
const response = await apiRequest(
|
|
514
|
-
method:
|
|
541
|
+
const response = await apiRequest("/api/v1/audits/compare", {
|
|
542
|
+
method: "POST",
|
|
515
543
|
body: {
|
|
516
544
|
baseAuditId: input.baseAuditId,
|
|
517
|
-
compareAuditId: input.compareAuditId
|
|
518
|
-
}
|
|
545
|
+
compareAuditId: input.compareAuditId,
|
|
546
|
+
},
|
|
519
547
|
});
|
|
520
548
|
const text = formatComparison(response.comparison);
|
|
521
549
|
return { text };
|
|
@@ -524,7 +552,7 @@ export async function executeCompareAudits(input) {
|
|
|
524
552
|
if (err instanceof McpError) {
|
|
525
553
|
return { text: err.toResponseText(), isError: true };
|
|
526
554
|
}
|
|
527
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message :
|
|
555
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message : "Failed to compare audits");
|
|
528
556
|
return { text: error.toResponseText(), isError: true };
|
|
529
557
|
}
|
|
530
558
|
}
|
|
@@ -533,12 +561,12 @@ export async function executeCompareAudits(input) {
|
|
|
533
561
|
*/
|
|
534
562
|
export async function executeGetAuditQuota() {
|
|
535
563
|
// Check Pro access
|
|
536
|
-
const tierCheck = await checkProAccess(
|
|
564
|
+
const tierCheck = await checkProAccess("get_audit_quota");
|
|
537
565
|
if (tierCheck)
|
|
538
566
|
return tierCheck;
|
|
539
|
-
debug(
|
|
567
|
+
debug("Getting audit quota");
|
|
540
568
|
try {
|
|
541
|
-
const response = await apiRequest(
|
|
569
|
+
const response = await apiRequest("/api/v1/audits/quota");
|
|
542
570
|
const { quota } = response;
|
|
543
571
|
let text = `## Audit Quota\n\n`;
|
|
544
572
|
text += `| Metric | Value |\n`;
|
|
@@ -555,7 +583,7 @@ export async function executeGetAuditQuota() {
|
|
|
555
583
|
if (err instanceof McpError) {
|
|
556
584
|
return { text: err.toResponseText(), isError: true };
|
|
557
585
|
}
|
|
558
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message :
|
|
586
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error ? err.message : "Failed to get audit quota");
|
|
559
587
|
return { text: error.toResponseText(), isError: true };
|
|
560
588
|
}
|
|
561
589
|
}
|
|
@@ -564,10 +592,10 @@ export async function executeGetAuditQuota() {
|
|
|
564
592
|
*/
|
|
565
593
|
export async function executeGetMigrationRecommendation(input) {
|
|
566
594
|
// Check Pro access
|
|
567
|
-
const tierCheck = await checkProAccess(
|
|
595
|
+
const tierCheck = await checkProAccess("get_migration_recommendation");
|
|
568
596
|
if (tierCheck)
|
|
569
597
|
return tierCheck;
|
|
570
|
-
debug(
|
|
598
|
+
debug("Getting migration recommendation", { auditId: input.auditId });
|
|
571
599
|
try {
|
|
572
600
|
const response = await apiRequest(`/api/v1/audits/${input.auditId}/migration`);
|
|
573
601
|
const text = formatMigrationRecommendation(response);
|
|
@@ -577,14 +605,173 @@ export async function executeGetMigrationRecommendation(input) {
|
|
|
577
605
|
if (err instanceof McpError) {
|
|
578
606
|
if (err.code === ErrorCode.NOT_FOUND) {
|
|
579
607
|
err.suggestions = [
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
608
|
+
"Use list_audits to see your available audit reports.",
|
|
609
|
+
"Make sure the audit is completed before requesting migration analysis.",
|
|
610
|
+
"Create a new audit with create_audit.",
|
|
611
|
+
];
|
|
612
|
+
}
|
|
613
|
+
return { text: err.toResponseText(), isError: true };
|
|
614
|
+
}
|
|
615
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error
|
|
616
|
+
? err.message
|
|
617
|
+
: "Failed to get migration recommendation");
|
|
618
|
+
return { text: error.toResponseText(), isError: true };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// BETTER-T-STACK IMPORT
|
|
623
|
+
// ============================================================================
|
|
624
|
+
/**
|
|
625
|
+
* Input schema for import_better_t_stack tool.
|
|
626
|
+
*/
|
|
627
|
+
export const ImportBetterTStackInputSchema = z.discriminatedUnion("type", [
|
|
628
|
+
z.object({
|
|
629
|
+
type: z.literal("github"),
|
|
630
|
+
url: z.string().url().describe("GitHub repository URL"),
|
|
631
|
+
name: z.string().min(1).max(200).optional().describe("Custom audit name"),
|
|
632
|
+
}),
|
|
633
|
+
z.object({
|
|
634
|
+
type: z.literal("package-json"),
|
|
635
|
+
content: z.string().min(2).max(500000).describe("Raw package.json content"),
|
|
636
|
+
name: z.string().min(1).max(200).optional().describe("Custom audit name"),
|
|
637
|
+
}),
|
|
638
|
+
]);
|
|
639
|
+
export const importBetterTStackToolDefinition = {
|
|
640
|
+
name: "import_better_t_stack",
|
|
641
|
+
description: `Import a Better-T-Stack project and create an audit.
|
|
642
|
+
|
|
643
|
+
**Use case**: If a project was scaffolded with Better-T-Stack CLI, you can import it directly for a technical debt audit.
|
|
644
|
+
|
|
645
|
+
**Input options**:
|
|
646
|
+
1. GitHub URL - We'll fetch the package.json automatically
|
|
647
|
+
2. Raw package.json content - Paste the file contents directly
|
|
648
|
+
|
|
649
|
+
**What happens**:
|
|
650
|
+
- Detects technologies from dependencies
|
|
651
|
+
- Calculates a "BTS confidence score" (higher = more likely a Better-T-Stack project)
|
|
652
|
+
- Creates and runs an audit automatically
|
|
653
|
+
- Returns findings and health score
|
|
654
|
+
|
|
655
|
+
**Tier**: Requires Pro or Team subscription (OR OAuth session)
|
|
656
|
+
|
|
657
|
+
**Example (GitHub)**:
|
|
658
|
+
\`\`\`json
|
|
659
|
+
{
|
|
660
|
+
"type": "github",
|
|
661
|
+
"url": "https://github.com/username/my-bts-app",
|
|
662
|
+
"name": "My BTS App Audit"
|
|
663
|
+
}
|
|
664
|
+
\`\`\`
|
|
665
|
+
|
|
666
|
+
**Example (package.json)**:
|
|
667
|
+
\`\`\`json
|
|
668
|
+
{
|
|
669
|
+
"type": "package-json",
|
|
670
|
+
"content": "{\"name\": \"my-app\", \"dependencies\": {...}}"
|
|
671
|
+
}
|
|
672
|
+
\`\`\`
|
|
673
|
+
|
|
674
|
+
**Next Steps after import**:
|
|
675
|
+
- Get migration plan: \`get_migration_recommendation({ auditId: "uuid" })\`
|
|
676
|
+
- Compare with future audits: \`compare_audits({ baseAuditId, compareAuditId })\``,
|
|
677
|
+
inputSchema: {
|
|
678
|
+
type: "object",
|
|
679
|
+
oneOf: [
|
|
680
|
+
{
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: {
|
|
683
|
+
type: { type: "string", const: "github" },
|
|
684
|
+
url: { type: "string", description: "GitHub repository URL" },
|
|
685
|
+
name: { type: "string", description: "Custom audit name (optional)" },
|
|
686
|
+
},
|
|
687
|
+
required: ["type", "url"],
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
type: { type: "string", const: "package-json" },
|
|
693
|
+
content: { type: "string", description: "Raw package.json content" },
|
|
694
|
+
name: { type: "string", description: "Custom audit name (optional)" },
|
|
695
|
+
},
|
|
696
|
+
required: ["type", "content"],
|
|
697
|
+
},
|
|
698
|
+
],
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
function formatImportResult(response) {
|
|
702
|
+
const { audit, import: imp } = response;
|
|
703
|
+
let text = `## Better-T-Stack Import Successful\n\n`;
|
|
704
|
+
// Import metadata
|
|
705
|
+
text += `### Project Analysis\n`;
|
|
706
|
+
text += `| Metric | Value |\n`;
|
|
707
|
+
text += `|--------|-------|\n`;
|
|
708
|
+
text += `| Project Name | ${imp.projectName} |\n`;
|
|
709
|
+
text += `| Import Source | ${imp.source} |\n`;
|
|
710
|
+
text += `| BTS Confidence | ${imp.confidence}% |\n`;
|
|
711
|
+
text += `| Technologies Detected | ${imp.technologiesDetected} |\n`;
|
|
712
|
+
if (imp.betterTStackVersion) {
|
|
713
|
+
text += `| Better-T-Stack Version | ${imp.betterTStackVersion} |\n`;
|
|
714
|
+
}
|
|
715
|
+
text += `\n`;
|
|
716
|
+
// Tech summary by category
|
|
717
|
+
const nonEmptyCategories = Object.entries(imp.techSummary).filter(([, techs]) => techs.length > 0);
|
|
718
|
+
if (nonEmptyCategories.length > 0) {
|
|
719
|
+
text += `### Detected Stack\n`;
|
|
720
|
+
for (const [category, techs] of nonEmptyCategories) {
|
|
721
|
+
text += `- **${category}**: ${techs.join(", ")}\n`;
|
|
722
|
+
}
|
|
723
|
+
text += `\n`;
|
|
724
|
+
}
|
|
725
|
+
// Warnings
|
|
726
|
+
if (imp.warnings.length > 0) {
|
|
727
|
+
text += `### Warnings\n`;
|
|
728
|
+
for (const warning of imp.warnings) {
|
|
729
|
+
text += `- [WARN] ${warning}\n`;
|
|
730
|
+
}
|
|
731
|
+
text += `\n`;
|
|
732
|
+
}
|
|
733
|
+
// Audit results
|
|
734
|
+
text += `---\n\n`;
|
|
735
|
+
text += formatAuditReport(audit);
|
|
736
|
+
return text;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Execute import_better_t_stack tool.
|
|
740
|
+
*/
|
|
741
|
+
export async function executeImportBetterTStack(input) {
|
|
742
|
+
// Check Pro access
|
|
743
|
+
const tierCheck = await checkProAccess("import_better_t_stack");
|
|
744
|
+
if (tierCheck)
|
|
745
|
+
return tierCheck;
|
|
746
|
+
debug("Importing Better-T-Stack project", {
|
|
747
|
+
type: input.type,
|
|
748
|
+
...(input.type === "github"
|
|
749
|
+
? { url: input.url }
|
|
750
|
+
: { contentLength: input.content.length }),
|
|
751
|
+
});
|
|
752
|
+
try {
|
|
753
|
+
const response = await apiRequest("/api/v1/audits/import/better-t-stack", {
|
|
754
|
+
method: "POST",
|
|
755
|
+
body: input,
|
|
756
|
+
timeoutMs: 30000,
|
|
757
|
+
});
|
|
758
|
+
const text = formatImportResult(response);
|
|
759
|
+
return { text };
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
if (err instanceof McpError) {
|
|
763
|
+
if (err.code === ErrorCode.INVALID_INPUT) {
|
|
764
|
+
err.suggestions = [
|
|
765
|
+
"For GitHub: Use format https://github.com/owner/repo",
|
|
766
|
+
"For package.json: Make sure the content is valid JSON",
|
|
767
|
+
"Check that the package.json has dependencies defined",
|
|
583
768
|
];
|
|
584
769
|
}
|
|
585
770
|
return { text: err.toResponseText(), isError: true };
|
|
586
771
|
}
|
|
587
|
-
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error
|
|
772
|
+
const error = new McpError(ErrorCode.API_ERROR, err instanceof Error
|
|
773
|
+
? err.message
|
|
774
|
+
: "Failed to import Better-T-Stack project");
|
|
588
775
|
return { text: error.toResponseText(), isError: true };
|
|
589
776
|
}
|
|
590
777
|
}
|