@atlashub/smartstack-mcp 1.1.0 → 1.2.1

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/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
 
4
3
  // src/server.ts
5
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -132,11 +131,24 @@ var defaultConfig = {
132
131
  apiEnabled: process.env.SMARTSTACK_API_ENABLED !== "false"
133
132
  },
134
133
  conventions: {
135
- tablePrefix: {
136
- core: "Core_",
137
- client: "Client_"
134
+ schemas: {
135
+ platform: "core",
136
+ extensions: "extensions"
138
137
  },
139
- migrationFormat: "YYYYMMDD_{Prefix}_NNN_{Description}",
138
+ tablePrefixes: [
139
+ "auth_",
140
+ "nav_",
141
+ "usr_",
142
+ "ai_",
143
+ "cfg_",
144
+ "wkf_",
145
+ "support_",
146
+ "entra_",
147
+ "ref_",
148
+ "loc_",
149
+ "lic_"
150
+ ],
151
+ migrationFormat: "{context}_v{version}_{sequence}_{Description}",
140
152
  namespaces: {
141
153
  domain: "SmartStack.Domain",
142
154
  application: "SmartStack.Application",
@@ -210,9 +222,9 @@ function mergeConfig(base, override) {
210
222
  conventions: {
211
223
  ...base.conventions,
212
224
  ...override.conventions,
213
- tablePrefix: {
214
- ...base.conventions.tablePrefix,
215
- ...override.conventions?.tablePrefix
225
+ schemas: {
226
+ ...base.conventions.schemas,
227
+ ...override.conventions?.schemas
216
228
  },
217
229
  namespaces: {
218
230
  ...base.conventions.namespaces,
@@ -520,7 +532,7 @@ async function findControllerFiles(apiPath) {
520
532
  import path5 from "path";
521
533
  var validateConventionsTool = {
522
534
  name: "validate_conventions",
523
- description: "Validate AtlasHub/SmartStack conventions: SQL schemas (core/extensions), domain table prefixes (auth_, nav_, ai_, etc.), migration naming (YYYYMMDD_NNN_*), service interfaces (I*Service), namespace structure",
535
+ description: "Validate AtlasHub/SmartStack conventions: SQL schemas (core/extensions), domain table prefixes (auth_, nav_, ai_, etc.), migration naming ({context}_v{version}_{sequence}_*), service interfaces (I*Service), namespace structure",
524
536
  inputSchema: {
525
537
  type: "object",
526
538
  properties: {
@@ -662,7 +674,7 @@ async function validateMigrationNaming(structure, _config, result) {
662
674
  return;
663
675
  }
664
676
  const migrationFiles = await findFiles("*.cs", { cwd: structure.migrations });
665
- const migrationPattern = /^(\d{8})_(\d{3})_(.+)\.cs$/;
677
+ const migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
666
678
  const designerPattern = /\.Designer\.cs$/;
667
679
  for (const file of migrationFiles) {
668
680
  const fileName = path5.basename(file);
@@ -675,7 +687,7 @@ async function validateMigrationNaming(structure, _config, result) {
675
687
  category: "migrations",
676
688
  message: `Migration "${fileName}" does not follow naming convention`,
677
689
  file: path5.relative(structure.root, file),
678
- suggestion: `Expected format: YYYYMMDD_NNN_Description.cs (e.g., 20260115_001_InitialCreate.cs)`
690
+ suggestion: `Expected format: {context}_v{version}_{sequence}_{Description}.cs (e.g., core_v1.0.0_001_CreateAuthUsers.cs)`
679
691
  });
680
692
  }
681
693
  }
@@ -683,18 +695,22 @@ async function validateMigrationNaming(structure, _config, result) {
683
695
  for (let i = 1; i < orderedMigrations.length; i++) {
684
696
  const prev = orderedMigrations[i - 1];
685
697
  const curr = orderedMigrations[i];
686
- const prevDate = prev.substring(0, 8);
687
- const currDate = curr.substring(0, 8);
688
- if (currDate < prevDate) {
689
- result.warnings.push({
690
- type: "warning",
691
- category: "migrations",
692
- message: `Migration order issue: "${curr}" dated before "${prev}"`
693
- });
698
+ const prevMatch = migrationPattern.exec(prev);
699
+ const currMatch = migrationPattern.exec(curr);
700
+ if (prevMatch && currMatch) {
701
+ const prevVersion = prevMatch[2];
702
+ const currVersion = currMatch[2];
703
+ if (currVersion < prevVersion) {
704
+ result.warnings.push({
705
+ type: "warning",
706
+ category: "migrations",
707
+ message: `Migration order issue: "${curr}" (v${currVersion}) comes before "${prev}" (v${prevVersion})`
708
+ });
709
+ }
694
710
  }
695
711
  }
696
712
  }
697
- async function validateServiceInterfaces(structure, config, result) {
713
+ async function validateServiceInterfaces(structure, _config, result) {
698
714
  if (!structure.application) {
699
715
  result.warnings.push({
700
716
  type: "warning",
@@ -858,7 +874,7 @@ async function handleCheckMigrations(args, config) {
858
874
  async function parseMigrations(migrationsPath, rootPath) {
859
875
  const files = await findFiles("*.cs", { cwd: migrationsPath });
860
876
  const migrations = [];
861
- const pattern = /^(\d{8})_(\d{3})_(.+)\.cs$/;
877
+ const pattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
862
878
  for (const file of files) {
863
879
  const fileName = path6.basename(file);
864
880
  if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
@@ -868,10 +884,13 @@ async function parseMigrations(migrationsPath, rootPath) {
868
884
  if (match) {
869
885
  migrations.push({
870
886
  name: fileName.replace(".cs", ""),
871
- timestamp: match[1],
872
- prefix: match[2],
873
- // Now this is the sequence number (NNN)
874
- description: match[3],
887
+ context: match[1],
888
+ // DbContext (core, extensions, etc.)
889
+ version: match[2],
890
+ // Semver version (1.0.0, 1.2.0, etc.)
891
+ sequence: match[3],
892
+ // Sequence number (001, 002, etc.)
893
+ description: match[4],
875
894
  file: path6.relative(rootPath, file),
876
895
  applied: true
877
896
  // We'd need DB connection to check this
@@ -879,8 +898,9 @@ async function parseMigrations(migrationsPath, rootPath) {
879
898
  } else {
880
899
  migrations.push({
881
900
  name: fileName.replace(".cs", ""),
882
- timestamp: "",
883
- prefix: "Unknown",
901
+ context: "Unknown",
902
+ version: "0.0.0",
903
+ sequence: "000",
884
904
  description: fileName.replace(".cs", ""),
885
905
  file: path6.relative(rootPath, file),
886
906
  applied: true
@@ -888,51 +908,63 @@ async function parseMigrations(migrationsPath, rootPath) {
888
908
  }
889
909
  }
890
910
  return migrations.sort((a, b) => {
891
- const dateCompare = a.timestamp.localeCompare(b.timestamp);
892
- if (dateCompare !== 0) return dateCompare;
893
- return a.prefix.localeCompare(b.prefix);
911
+ const versionCompare = compareVersions(a.version, b.version);
912
+ if (versionCompare !== 0) return versionCompare;
913
+ return a.sequence.localeCompare(b.sequence);
894
914
  });
895
915
  }
916
+ function compareVersions(a, b) {
917
+ const partsA = a.split(".").map(Number);
918
+ const partsB = b.split(".").map(Number);
919
+ for (let i = 0; i < 3; i++) {
920
+ if (partsA[i] > partsB[i]) return 1;
921
+ if (partsA[i] < partsB[i]) return -1;
922
+ }
923
+ return 0;
924
+ }
896
925
  function checkNamingConventions(result, _config) {
897
926
  for (const migration of result.migrations) {
898
- if (!migration.timestamp) {
927
+ if (migration.context === "Unknown") {
899
928
  result.conflicts.push({
900
929
  type: "naming",
901
930
  description: `Migration "${migration.name}" does not follow naming convention`,
902
931
  files: [migration.file],
903
- resolution: `Rename to format: YYYYMMDD_NNN_Description (e.g., 20260115_001_InitialCreate)`
932
+ resolution: `Rename to format: {context}_v{version}_{sequence}_{Description} (e.g., core_v1.0.0_001_CreateAuthUsers)`
904
933
  });
905
934
  }
906
- if (migration.prefix === "Unknown") {
935
+ if (migration.version === "0.0.0") {
907
936
  result.conflicts.push({
908
937
  type: "naming",
909
- description: `Migration "${migration.name}" missing sequence number`,
938
+ description: `Migration "${migration.name}" missing version number`,
910
939
  files: [migration.file],
911
- resolution: `Use format: YYYYMMDD_NNN_Description where NNN is a 3-digit sequence (001, 002, etc.)`
940
+ resolution: `Use format: {context}_v{version}_{sequence}_{Description} where version is semver (1.0.0, 1.2.0, etc.)`
912
941
  });
913
942
  }
914
943
  }
915
944
  }
916
945
  function checkChronologicalOrder(result) {
917
- const migrations = result.migrations.filter((m) => m.timestamp);
946
+ const migrations = result.migrations.filter((m) => m.context !== "Unknown");
918
947
  for (let i = 1; i < migrations.length; i++) {
919
948
  const prev = migrations[i - 1];
920
949
  const curr = migrations[i];
921
- if (curr.timestamp < prev.timestamp) {
922
- result.conflicts.push({
923
- type: "order",
924
- description: `Migration "${curr.name}" (${curr.timestamp}) is dated before "${prev.name}" (${prev.timestamp})`,
925
- files: [curr.file, prev.file],
926
- resolution: "Reorder migrations or update timestamps"
927
- });
928
- }
929
- if (curr.timestamp === prev.timestamp && curr.prefix === prev.prefix) {
930
- result.conflicts.push({
931
- type: "order",
932
- description: `Migrations "${curr.name}" and "${prev.name}" have same timestamp`,
933
- files: [curr.file, prev.file],
934
- resolution: "Use different sequence numbers (NNN) or different dates"
935
- });
950
+ if (curr.context === prev.context) {
951
+ const versionCompare = compareVersions(curr.version, prev.version);
952
+ if (versionCompare < 0) {
953
+ result.conflicts.push({
954
+ type: "order",
955
+ description: `Migration "${curr.name}" (v${curr.version}) is versioned before "${prev.name}" (v${prev.version})`,
956
+ files: [curr.file, prev.file],
957
+ resolution: "Reorder migrations or update version numbers"
958
+ });
959
+ }
960
+ if (curr.version === prev.version && curr.sequence === prev.sequence) {
961
+ result.conflicts.push({
962
+ type: "order",
963
+ description: `Migrations "${curr.name}" and "${prev.name}" have same version and sequence`,
964
+ files: [curr.file, prev.file],
965
+ resolution: "Use different sequence numbers (001, 002, etc.) for migrations in the same version"
966
+ });
967
+ }
936
968
  }
937
969
  }
938
970
  }
@@ -987,7 +1019,7 @@ async function checkModelSnapshot(result, structure) {
987
1019
  }
988
1020
  const snapshotContent = await readText(snapshotFiles[0]);
989
1021
  for (const migration of result.migrations) {
990
- if (migration.timestamp && !snapshotContent.includes(migration.name)) {
1022
+ if (migration.context !== "Unknown" && !snapshotContent.includes(migration.name)) {
991
1023
  result.conflicts.push({
992
1024
  type: "dependency",
993
1025
  description: `Migration "${migration.name}" not referenced in ModelSnapshot`,
@@ -1005,12 +1037,12 @@ function generateSuggestions(result) {
1005
1037
  }
1006
1038
  if (result.conflicts.some((c) => c.type === "naming")) {
1007
1039
  result.suggestions.push(
1008
- "Use convention: YYYYMMDD_NNN_Description for migration naming (e.g., 20260115_001_InitialCreate)"
1040
+ "Use convention: {context}_v{version}_{sequence}_{Description} for migration naming (e.g., core_v1.0.0_001_CreateAuthUsers)"
1009
1041
  );
1010
1042
  }
1011
1043
  if (result.conflicts.some((c) => c.type === "order")) {
1012
1044
  result.suggestions.push(
1013
- "Ensure migrations are created in chronological order to avoid conflicts"
1045
+ "Ensure migrations are created in version order to avoid conflicts"
1014
1046
  );
1015
1047
  }
1016
1048
  if (result.migrations.length > 20) {
@@ -1034,11 +1066,11 @@ function formatResult2(result, currentBranch, compareBranch) {
1034
1066
  lines.push("");
1035
1067
  lines.push("## Migrations");
1036
1068
  lines.push("");
1037
- lines.push("| Name | Timestamp | Prefix | Description |");
1038
- lines.push("|------|-----------|--------|-------------|");
1069
+ lines.push("| Name | Context | Version | Sequence | Description |");
1070
+ lines.push("|------|---------|---------|----------|-------------|");
1039
1071
  for (const migration of result.migrations) {
1040
1072
  lines.push(
1041
- `| ${migration.name} | ${migration.timestamp || "N/A"} | ${migration.prefix} | ${migration.description} |`
1073
+ `| ${migration.name} | ${migration.context} | ${migration.version} | ${migration.sequence} | ${migration.description} |`
1042
1074
  );
1043
1075
  }
1044
1076
  lines.push("");
@@ -1269,7 +1301,7 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{domainNamespace}
1269
1301
  baseEntity,
1270
1302
  infrastructureNamespace: config.conventions.namespaces.infrastructure,
1271
1303
  domainNamespace: config.conventions.namespaces.domain,
1272
- tablePrefix: config.conventions.tablePrefix.core
1304
+ schema: config.conventions.schemas.platform
1273
1305
  };
1274
1306
  const entityContent = Handlebars.compile(entityTemplate)(context);
1275
1307
  const configContent = Handlebars.compile(configTemplate)(context);
@@ -1965,28 +1997,37 @@ Migrations MUST follow this naming pattern:
1965
1997
  ${migrationFormat}
1966
1998
  \`\`\`
1967
1999
 
2000
+ | Part | Description | Example |
2001
+ |------|-------------|---------|
2002
+ | \`{context}\` | DbContext name | \`core\`, \`extensions\` |
2003
+ | \`{version}\` | Semver version | \`v1.0.0\`, \`v1.2.0\` |
2004
+ | \`{sequence}\` | Order in version | \`001\`, \`002\` |
2005
+ | \`{Description}\` | Action (PascalCase) | \`CreateAuthUsers\` |
2006
+
1968
2007
  **Examples:**
1969
- - \`20260115_001_InitialSchema.cs\`
1970
- - \`20260120_002_AddUserProfiles.cs\`
1971
- - \`20260125_003_AddSupportTickets.cs\`
2008
+ - \`core_v1.0.0_001_InitialSchema.cs\`
2009
+ - \`core_v1.0.0_002_CreateAuthUsers.cs\`
2010
+ - \`core_v1.2.0_001_AddUserProfiles.cs\`
2011
+ - \`extensions_v1.0.0_001_AddClientFeatures.cs\`
1972
2012
 
1973
2013
  ### Creating Migrations
1974
2014
 
1975
2015
  \`\`\`bash
1976
2016
  # Create a new migration
1977
- dotnet ef migrations add 20260115_001_InitialSchema
2017
+ dotnet ef migrations add core_v1.0.0_001_InitialSchema
1978
2018
 
1979
2019
  # With context specified
1980
- dotnet ef migrations add 20260115_001_InitialSchema --context ApplicationDbContext
2020
+ dotnet ef migrations add core_v1.2.0_001_AddUserProfiles --context ApplicationDbContext
1981
2021
  \`\`\`
1982
2022
 
1983
2023
  ### Migration Rules
1984
2024
 
1985
2025
  1. **One migration per feature** - Group related changes in a single migration
1986
- 2. **Timestamps ensure order** - Use YYYYMMDD format for chronological sorting
1987
- 3. **Sequence numbers** - Use NNN (001, 002, etc.) for migrations on the same day
1988
- 4. **Descriptive names** - Use clear descriptions (InitialSchema, AddUserProfiles, etc.)
1989
- 5. **Schema must be specified** - All tables must specify their schema in ToTable()
2026
+ 2. **Version-based naming** - Use semver (v1.0.0, v1.2.0) to link migrations to releases
2027
+ 3. **Sequence numbers** - Use NNN (001, 002, etc.) for migrations in the same version
2028
+ 4. **Context prefix** - Use \`core_\` for platform tables, \`extensions_\` for client extensions
2029
+ 5. **Descriptive names** - Use clear PascalCase descriptions (CreateAuthUsers, AddUserProfiles, etc.)
2030
+ 6. **Schema must be specified** - All tables must specify their schema in ToTable()
1990
2031
 
1991
2032
  ---
1992
2033
 
@@ -2153,7 +2194,7 @@ public interface IUserServiceHooks
2153
2194
  | Platform schema | \`${schemas.platform}\` | \`.ToTable("auth_Users", "${schemas.platform}")\` |
2154
2195
  | Extensions schema | \`${schemas.extensions}\` | \`.ToTable("client_Custom", "${schemas.extensions}")\` |
2155
2196
  | Table prefixes | \`${tablePrefixes.slice(0, 5).join(", ")}\`, etc. | \`auth_Users\`, \`nav_Modules\` |
2156
- | Migration | \`YYYYMMDD_NNN_Description\` | \`20260115_001_InitialCreate\` |
2197
+ | Migration | \`{context}_v{version}_{seq}_{Desc}\` | \`core_v1.0.0_001_CreateAuthUsers\` |
2157
2198
  | Interface | \`I<Name>Service\` | \`IUserService\` |
2158
2199
  | Implementation | \`<Name>Service\` | \`UserService\` |
2159
2200
  | Domain namespace | \`${namespaces.domain}\` | - |
@@ -2256,7 +2297,8 @@ async function getProjectInfoResource(config) {
2256
2297
  lines.push(JSON.stringify({
2257
2298
  smartstack: config.smartstack,
2258
2299
  conventions: {
2259
- tablePrefix: config.conventions.tablePrefix,
2300
+ schemas: config.conventions.schemas,
2301
+ tablePrefixes: config.conventions.tablePrefixes,
2260
2302
  migrationFormat: config.conventions.migrationFormat
2261
2303
  }
2262
2304
  }, null, 2));
@@ -2319,7 +2361,7 @@ async function getApiEndpointsResource(config, endpointFilter) {
2319
2361
  ) : allEndpoints;
2320
2362
  return formatEndpoints(endpoints, endpointFilter);
2321
2363
  }
2322
- async function parseController(filePath, rootPath) {
2364
+ async function parseController(filePath, _rootPath) {
2323
2365
  const content = await readText(filePath);
2324
2366
  const fileName = path10.basename(filePath, ".cs");
2325
2367
  const controllerName = fileName.replace("Controller", "");
@@ -2327,10 +2369,8 @@ async function parseController(filePath, rootPath) {
2327
2369
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
2328
2370
  const baseRoute = routeMatch ? routeMatch[1].replace("[controller]", controllerName.toLowerCase()) : `/api/${controllerName.toLowerCase()}`;
2329
2371
  const classAuthorize = /\[Authorize\s*(?:\([^)]*\))?\s*\]\s*(?:\[.*\]\s*)*public\s+class/.test(content);
2330
- const methodPattern = /\/\/\/\s*<summary>\s*\n\s*\/\/\/\s*([^\n]+)\s*\n\s*\/\/\/\s*<\/summary>[\s\S]*?(?=\[Http)|(\[Http(Get|Post|Put|Patch|Delete)(?:\s*\(\s*"([^"]*)"\s*\))?\][\s\S]*?public\s+(?:async\s+)?(?:Task<)?(?:ActionResult<)?(\w+)(?:[<>[\],\s\w]*)?\s+(\w+)\s*\(([^)]*)\))/g;
2331
- let lastSummary = "";
2332
- let match;
2333
2372
  const httpMethods = ["HttpGet", "HttpPost", "HttpPut", "HttpPatch", "HttpDelete"];
2373
+ let match;
2334
2374
  for (const httpMethod of httpMethods) {
2335
2375
  const regex = new RegExp(
2336
2376
  `\\[${httpMethod}(?:\\s*\\(\\s*"([^"]*)"\\s*\\))?\\]\\s*(?:\\[.*?\\]\\s*)*public\\s+(?:async\\s+)?(?:Task<)?(?:ActionResult<)?(\\w+)(?:[<>\\[\\],\\s\\w]*)?\\s+(\\w+)\\s*\\(([^)]*)\\)`,
@@ -2500,13 +2540,11 @@ async function getDbSchemaResource(config, tableFilter) {
2500
2540
  ) : entities;
2501
2541
  return formatSchema(filteredEntities, tableFilter, config);
2502
2542
  }
2503
- async function parseEntity(filePath, rootPath, config) {
2543
+ async function parseEntity(filePath, rootPath, _config) {
2504
2544
  const content = await readText(filePath);
2505
- const fileName = path11.basename(filePath, ".cs");
2506
2545
  const classMatch = content.match(/public\s+(?:class|record)\s+(\w+)(?:\s*:\s*(\w+))?/);
2507
2546
  if (!classMatch) return null;
2508
2547
  const entityName = classMatch[1];
2509
- const baseClass = classMatch[2];
2510
2548
  if (entityName.endsWith("Dto") || entityName.endsWith("Command") || entityName.endsWith("Query") || entityName.endsWith("Handler")) {
2511
2549
  return null;
2512
2550
  }
@@ -2556,7 +2594,7 @@ async function parseEntity(filePath, rootPath, config) {
2556
2594
  isPrimaryKey: propertyName === "Id" || propertyName === `${entityName}Id`
2557
2595
  });
2558
2596
  }
2559
- const tableName = `${config.conventions.tablePrefix.core}${entityName}s`;
2597
+ const tableName = `cfg_${entityName}s`;
2560
2598
  return {
2561
2599
  name: entityName,
2562
2600
  tableName,
@@ -2565,7 +2603,7 @@ async function parseEntity(filePath, rootPath, config) {
2565
2603
  file: path11.relative(rootPath, filePath)
2566
2604
  };
2567
2605
  }
2568
- async function enrichFromConfigurations(entities, infrastructurePath, config) {
2606
+ async function enrichFromConfigurations(entities, infrastructurePath, _config) {
2569
2607
  const configFiles = await findFiles("**/Configurations/**/*.cs", {
2570
2608
  cwd: infrastructurePath
2571
2609
  });
@@ -2619,7 +2657,7 @@ function mapCSharpType(csharpType) {
2619
2657
  const baseType = csharpType.replace("?", "");
2620
2658
  return typeMap[baseType] || "nvarchar";
2621
2659
  }
2622
- function formatSchema(entities, filter, config) {
2660
+ function formatSchema(entities, filter, _config) {
2623
2661
  const lines = [];
2624
2662
  lines.push("# SmartStack Database Schema");
2625
2663
  lines.push("");