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 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.99/month** — [Get Pro License](https://buy.stripe.com/PLACEHOLDER)
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
- if (upper.match(/^UPDATE\b/) && !upper.includes("WHERE")) {
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 = '${migration.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.10 — MCP server for database migration risk analysis
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.10",
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> **Recommendation**: ${risk.recommendation}\n\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> **Mitigation**: ${issue.mitigation}\n\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> **Recommendation**: ${risk.recommendation}\n\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> **Mitigation**: ${issue.mitigation}\n\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. Identifies same-table modifications, same-column changes, lock contention risks, and drop dependencies that could cause failures if applied concurrently or in the wrong order.", {
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.10",
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",