mcp-migration-advisor 0.2.10 → 0.2.12
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 +1 -1
- package/build/analyzers/data-loss.js +6 -2
- package/build/generators/rollback.js +2 -1
- package/build/index.js +18 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Unlike MigrationPilot (PG-only, raw SQL analysis), this tool **parses Liquibase
|
|
|
31
31
|
- Priority support
|
|
32
32
|
|
|
33
33
|
<!-- TODO: replace placeholder Stripe Payment Link once STRIPE_SECRET_KEY is configured -->
|
|
34
|
-
**$9.
|
|
34
|
+
**$9.00/month** — [Get Pro License](https://buy.stripe.com/PLACEHOLDER)
|
|
35
35
|
|
|
36
36
|
Pro license key activates the `generate_report` MCP tool in mcp-jvm-diagnostics.
|
|
37
37
|
|
|
@@ -101,8 +101,12 @@ export function analyzeDataLoss(migration) {
|
|
|
101
101
|
mitigation: "Add a WHERE clause to limit the delete, or use TRUNCATE if intentional.",
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
// Detect UPDATE without WHERE
|
|
105
|
-
|
|
104
|
+
// Detect UPDATE without WHERE.
|
|
105
|
+
// Match UPDATE at the start of the string or after a statement separator
|
|
106
|
+
// (semicolon or newline), allowing leading whitespace on the line.
|
|
107
|
+
// This ensures detection works in multi-statement <sql> Liquibase blocks
|
|
108
|
+
// where UPDATE is not the very first statement.
|
|
109
|
+
if (upper.match(/(?:^|[;\n])\s*UPDATE\b/) && !upper.includes("WHERE")) {
|
|
106
110
|
const tableMatch = stmt.raw.match(/UPDATE\s+(?:`|"|)?(?:\w+\.)?(\w+)/i);
|
|
107
111
|
issues.push({
|
|
108
112
|
risk: "LIKELY",
|
|
@@ -47,8 +47,9 @@ export function generateRollback(migration) {
|
|
|
47
47
|
}
|
|
48
48
|
// Add Flyway schema_version cleanup
|
|
49
49
|
if (migration.version) {
|
|
50
|
+
const escapedVersion = migration.version.replace(/'/g, "''");
|
|
50
51
|
rollbackLines.push("-- Remove migration record from Flyway history:");
|
|
51
|
-
rollbackLines.push(`DELETE FROM flyway_schema_history WHERE version = '${
|
|
52
|
+
rollbackLines.push(`DELETE FROM flyway_schema_history WHERE version = '${escapedVersion}';`);
|
|
52
53
|
}
|
|
53
54
|
return {
|
|
54
55
|
statements: rollbackStatements,
|
package/build/index.js
CHANGED
|
@@ -33,7 +33,7 @@ function formatParserWarnings(migration) {
|
|
|
33
33
|
}
|
|
34
34
|
// Handle --help
|
|
35
35
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
36
|
-
console.log(`mcp-migration-advisor v0.2.
|
|
36
|
+
console.log(`mcp-migration-advisor v0.2.12 — MCP server for database migration risk analysis
|
|
37
37
|
|
|
38
38
|
Usage:
|
|
39
39
|
mcp-migration-advisor [options]
|
|
@@ -52,7 +52,7 @@ Tools provided:
|
|
|
52
52
|
}
|
|
53
53
|
const server = new McpServer({
|
|
54
54
|
name: "mcp-migration-advisor",
|
|
55
|
-
version: "0.2.
|
|
55
|
+
version: "0.2.12",
|
|
56
56
|
});
|
|
57
57
|
// Tool 1: analyze_migration
|
|
58
58
|
server.tool("analyze_migration", "Analyze a SQL migration file for lock risks, data loss potential, and unsafe patterns. Supports Flyway (V__*.sql) and plain SQL.", {
|
|
@@ -141,7 +141,7 @@ server.tool("analyze_liquibase", "Analyze a Liquibase XML changelog for lock ris
|
|
|
141
141
|
let output = `## Liquibase Changelog Analysis\n\n`;
|
|
142
142
|
output += `**ChangeSets**: ${migration.description}\n`;
|
|
143
143
|
output += `**Statements**: ${migration.statements.length}\n`;
|
|
144
|
-
output += `**Risk Score**: ${riskScore}/100${riskScore >= 60 ? " HIGH RISK" : riskScore >= 30 ? " MODERATE RISK" : " LOW RISK"}\n\n`;
|
|
144
|
+
output += `**Risk Score**: ${riskScore}/100${riskScore >= 60 ? " ⚠️ HIGH RISK" : riskScore >= 30 ? " ⚡ MODERATE RISK" : " ✅ LOW RISK"}\n\n`;
|
|
145
145
|
const typeCounts = {};
|
|
146
146
|
for (const stmt of migration.statements) {
|
|
147
147
|
typeCounts[stmt.type] = (typeCounts[stmt.type] || 0) + 1;
|
|
@@ -154,13 +154,17 @@ server.tool("analyze_liquibase", "Analyze a Liquibase XML changelog for lock ris
|
|
|
154
154
|
if (lockRisks.length > 0) {
|
|
155
155
|
output += "### Lock Risks\n\n";
|
|
156
156
|
for (const risk of lockRisks) {
|
|
157
|
-
output += `**${risk.severity}**: ${risk.risk}\n
|
|
157
|
+
output += `**${risk.severity}**: ${risk.risk}\n`;
|
|
158
|
+
output += `> \`${risk.statement}\`\n`;
|
|
159
|
+
output += `> **Recommendation**: ${risk.recommendation}\n\n`;
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
if (dataLossIssues.length > 0) {
|
|
161
163
|
output += "### Data Loss Analysis\n\n";
|
|
162
164
|
for (const issue of dataLossIssues) {
|
|
163
|
-
output += `**${issue.risk}**: ${issue.description}\n
|
|
165
|
+
output += `**${issue.risk}**: ${issue.description}\n`;
|
|
166
|
+
output += `> \`${issue.statement}\`\n`;
|
|
167
|
+
output += `> **Mitigation**: ${issue.mitigation}\n\n`;
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
if (lockRisks.length === 0 && dataLossIssues.length === 0) {
|
|
@@ -179,7 +183,7 @@ server.tool("analyze_liquibase", "Analyze a Liquibase XML changelog for lock ris
|
|
|
179
183
|
}
|
|
180
184
|
});
|
|
181
185
|
// Tool 3: analyze_liquibase_yaml
|
|
182
|
-
server.tool("analyze_liquibase_yaml", "Analyze a Liquibase YAML changelog for lock risks, data loss potential, and unsafe patterns. Supports createTable, dropTable, addColumn, dropColumn, modifyDataType, createIndex, renameTable, renameColumn, and more.", {
|
|
186
|
+
server.tool("analyze_liquibase_yaml", "Analyze a Liquibase YAML changelog for lock risks, data loss potential, and unsafe patterns. Supports createTable, dropTable, addColumn, dropColumn, modifyDataType, createIndex, addForeignKeyConstraint, renameTable, renameColumn, and more.", {
|
|
183
187
|
yaml: z.string().describe("The Liquibase YAML changelog content"),
|
|
184
188
|
}, async ({ yaml }) => {
|
|
185
189
|
try {
|
|
@@ -194,7 +198,7 @@ server.tool("analyze_liquibase_yaml", "Analyze a Liquibase YAML changelog for lo
|
|
|
194
198
|
let output = `## Liquibase YAML Changelog Analysis\n\n`;
|
|
195
199
|
output += `**ChangeSets**: ${migration.description}\n`;
|
|
196
200
|
output += `**Statements**: ${migration.statements.length}\n`;
|
|
197
|
-
output += `**Risk Score**: ${riskScore}/100${riskScore >= 60 ? " HIGH RISK" : riskScore >= 30 ? " MODERATE RISK" : " LOW RISK"}\n\n`;
|
|
201
|
+
output += `**Risk Score**: ${riskScore}/100${riskScore >= 60 ? " ⚠️ HIGH RISK" : riskScore >= 30 ? " ⚡ MODERATE RISK" : " ✅ LOW RISK"}\n\n`;
|
|
198
202
|
const typeCounts = {};
|
|
199
203
|
for (const stmt of migration.statements) {
|
|
200
204
|
typeCounts[stmt.type] = (typeCounts[stmt.type] || 0) + 1;
|
|
@@ -207,13 +211,17 @@ server.tool("analyze_liquibase_yaml", "Analyze a Liquibase YAML changelog for lo
|
|
|
207
211
|
if (lockRisks.length > 0) {
|
|
208
212
|
output += "### Lock Risks\n\n";
|
|
209
213
|
for (const risk of lockRisks) {
|
|
210
|
-
output += `**${risk.severity}**: ${risk.risk}\n
|
|
214
|
+
output += `**${risk.severity}**: ${risk.risk}\n`;
|
|
215
|
+
output += `> \`${risk.statement}\`\n`;
|
|
216
|
+
output += `> **Recommendation**: ${risk.recommendation}\n\n`;
|
|
211
217
|
}
|
|
212
218
|
}
|
|
213
219
|
if (dataLossIssues.length > 0) {
|
|
214
220
|
output += "### Data Loss Analysis\n\n";
|
|
215
221
|
for (const issue of dataLossIssues) {
|
|
216
|
-
output += `**${issue.risk}**: ${issue.description}\n
|
|
222
|
+
output += `**${issue.risk}**: ${issue.description}\n`;
|
|
223
|
+
output += `> \`${issue.statement}\`\n`;
|
|
224
|
+
output += `> **Mitigation**: ${issue.mitigation}\n\n`;
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
if (lockRisks.length === 0 && dataLossIssues.length === 0) {
|
|
@@ -324,7 +332,7 @@ server.tool("generate_rollback", "Generate reverse DDL to undo a SQL migration.
|
|
|
324
332
|
}
|
|
325
333
|
});
|
|
326
334
|
// Tool 6: detect_conflicts
|
|
327
|
-
server.tool("detect_conflicts", "Detect conflicts between two SQL migration files
|
|
335
|
+
server.tool("detect_conflicts", "Detect structural conflicts between two SQL migration files — same-table modifications, same-column changes, lock contention, and drop dependencies. Use this when two migrations touch the same schema objects and you need to know if ordering or concurrent execution matters. Note: only structural conflicts are detected (same table/column); semantic conflicts such as two migrations adding different indexes on the same column are not reported.", {
|
|
328
336
|
filename_a: z.string().describe("First migration filename (e.g., V3__add_email.sql)"),
|
|
329
337
|
sql_a: z.string().describe("SQL content of the first migration"),
|
|
330
338
|
filename_b: z.string().describe("Second migration filename (e.g., V4__modify_users.sql)"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-migration-advisor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "MCP server for database migration risk analysis — Flyway and Liquibase XML/YAML/SQL support with lock detection and conflict analysis",
|
|
5
5
|
"mcpName": "io.github.dmitriusan/mcp-migration-advisor",
|
|
6
6
|
"main": "build/index.js",
|