worsoft-frontend-codegen-local-mcp 0.1.32 → 0.1.35

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 (2) hide show
  1. package/mcp_server.js +127 -50
  2. package/package.json +1 -1
package/mcp_server.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const SERVER_NAME = 'worsoft-codegen-local';
8
- const SERVER_VERSION = '0.1.32';
8
+ const SERVER_VERSION = '0.1.35';
9
9
  const PROTOCOL_VERSION = '2024-11-05';
10
10
  const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
11
11
  const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
@@ -148,14 +148,19 @@ export function createCrudSchema(
148
148
  const TOOL_SCHEMA = {
149
149
  type: 'object',
150
150
  properties: {
151
- featureTitle: { type: 'string', description: 'Feature title from structured metadata.' },
152
- tableName: { type: 'string', description: 'Target main table name from the structured feature metadata.' },
153
- tableComment: { type: 'string', description: 'Main table comment or feature label for menu/title generation.' },
154
- apiPath: { type: 'string', description: 'Backend API base path from structured metadata, for example iwmEmpOutsourcePerson.' },
155
- style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Style id from assets/style-catalog.json.' },
156
- fields: {
157
- type: 'array',
158
- description: 'Structured main-table field metadata provided by the caller.',
151
+ featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
152
+ tableName: { type: 'string', description: 'Canonical main table name from PRD-aligned structured metadata.' },
153
+ tableComment: { type: 'string', description: 'Canonical main table comment or feature label from PRD-aligned structured metadata.' },
154
+ apiPath: { type: 'string', description: 'Backend API base path from pre-parsed structured metadata, for example iwmEmpOutsourcePerson.' },
155
+ pageType: {
156
+ type: 'string',
157
+ enum: ['feature', 'dictionary', 'non_standard', 'business', 'dict'],
158
+ description: 'Structured page type from parseResult. MCP consumes this value but does not derive it. Dictionary pages are restricted to dialog-based templates.',
159
+ },
160
+ style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Final style id from parseResult or translated mcpPayload. MCP validates it but does not infer it.' },
161
+ fields: {
162
+ type: 'array',
163
+ description: 'Structured main-table field metadata already translated by the caller. MCP only consumes these low-level generation parameters.',
159
164
  items: {
160
165
  type: 'object',
161
166
  properties: {
@@ -172,8 +177,10 @@ const TOOL_SCHEMA = {
172
177
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
173
178
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
174
179
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
175
- description: { type: 'string', description: 'Field description or notes.' },
176
- sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
180
+ description: { type: 'string', description: 'Field description or notes.' },
181
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number or datetime.' },
182
+ formType: { type: 'string', description: 'Explicit form control type translated by the caller. MCP consumes this value first and only falls back to legacy heuristics when omitted.' },
183
+ sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
177
184
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
178
185
  },
179
186
  required: ['fieldName', 'label', 'type'],
@@ -182,7 +189,7 @@ const TOOL_SCHEMA = {
182
189
  },
183
190
  children: {
184
191
  type: 'array',
185
- description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form.',
192
+ description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form. The caller must determine semantic truth before passing them in.',
186
193
  items: {
187
194
  type: 'object',
188
195
  properties: {
@@ -211,8 +218,10 @@ const TOOL_SCHEMA = {
211
218
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
212
219
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
213
220
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
214
- description: { type: 'string', description: 'Field description or notes.' },
215
- sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
221
+ description: { type: 'string', description: 'Field description or notes.' },
222
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
223
+ formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
224
+ sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
216
225
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
217
226
  },
218
227
  required: ['fieldName', 'label', 'type'],
@@ -238,8 +247,8 @@ const TOOL_SCHEMA = {
238
247
  items: {
239
248
  type: 'object',
240
249
  properties: {
241
- tableName: { type: 'string', description: 'Module table name.' },
242
- tableComment: { type: 'string', description: 'Module display label.' },
250
+ tableName: { type: 'string', description: 'Canonical module table name from PRD-aligned structured metadata.' },
251
+ tableComment: { type: 'string', description: 'Canonical module display label from PRD-aligned structured metadata.' },
243
252
  apiPath: { type: 'string', description: 'Backend API base path for this module.' },
244
253
  primaryKey: { type: 'string', description: 'Primary key field name. Defaults to id when omitted.' },
245
254
  queryParentField: { type: 'string', description: 'Direct parent foreign key field used for page queries.' },
@@ -266,8 +275,10 @@ const TOOL_SCHEMA = {
266
275
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
267
276
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
268
277
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
269
- description: { type: 'string', description: 'Field description or notes.' },
270
- sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
278
+ description: { type: 'string', description: 'Field description or notes.' },
279
+ componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
280
+ formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
281
+ sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
271
282
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' }
272
283
  },
273
284
  required: ['fieldName', 'label', 'type'],
@@ -845,15 +856,34 @@ function findDictType(comment) {
845
856
  return extractDictType(comment);
846
857
  }
847
858
 
848
- function mapFieldType(field) {
849
- if (field.dictType) return 'select';
850
- if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
851
- if (field.sqlType === 'DATE') return 'date';
852
- if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
853
- if (field.sqlType === 'TEXT') return 'textarea';
854
- if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
855
- return 'text';
856
- }
859
+ function mapFieldType(field) {
860
+ if (field.dictType) return 'select';
861
+ if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
862
+ if (field.sqlType === 'DATE') return 'date';
863
+ if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
864
+ if (field.sqlType === 'TEXT') return 'textarea';
865
+ if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
866
+ return 'text';
867
+ }
868
+
869
+ function normalizeStructuredFormType(value) {
870
+ const normalized = String(value || '').trim().toLowerCase();
871
+ if (!normalized) return '';
872
+ if (normalized === 'date') return 'date';
873
+ if (normalized === 'datetime') return 'datetime';
874
+ if (normalized === 'microme-operator') return 'number';
875
+ if (normalized === 'upload' || normalized === 'picker') {
876
+ throw new Error(
877
+ 'Explicit component/form type "' +
878
+ normalized +
879
+ '" is not yet supported by worsoft-codegen-local templates. Please keep it in parseResult for downstream handling or extend MCP template support first.'
880
+ );
881
+ }
882
+ if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
883
+ return normalized;
884
+ }
885
+ throw new Error('Unsupported explicit component/form type: ' + normalized);
886
+ }
857
887
 
858
888
  function escapeForRegex(value) {
859
889
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -1008,6 +1038,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1008
1038
  }
1009
1039
 
1010
1040
  const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
1041
+ const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1011
1042
  const explicitQueryType =
1012
1043
  inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1013
1044
  ? undefined
@@ -1023,6 +1054,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1023
1054
  comment: label,
1024
1055
  label,
1025
1056
  description: String(inputField.description || '').trim(),
1057
+ formType: explicitFormType,
1026
1058
  dictType: normalizeDictType(inputField.dictType),
1027
1059
  notNull: parseBooleanLike(inputField.required, false),
1028
1060
  defaultValue: normalizeDefaultValue(inputField.defaultValue),
@@ -1207,19 +1239,56 @@ function normalizeLevelsInput(inputLevels) {
1207
1239
  return levels;
1208
1240
  }
1209
1241
 
1210
- function getStylePreset(styleId) {
1211
- const preset = STYLE_CATALOG[styleId];
1212
- if (!preset) throw new Error('Unsupported style: ' + styleId);
1213
- return preset;
1214
- }
1215
-
1242
+ function getStylePreset(styleId) {
1243
+ const preset = STYLE_CATALOG[styleId];
1244
+ if (!preset) throw new Error('Unsupported style: ' + styleId);
1245
+ return preset;
1246
+ }
1247
+
1248
+ function normalizePageTypeInput(pageType) {
1249
+ if (pageType === undefined || pageType === null || pageType === '') return '';
1250
+ const normalized = String(pageType).trim();
1251
+ if (normalized === 'dict' || normalized === 'business') {
1252
+ throw new Error(`Legacy pageType alias "${normalized}" is not allowed. Use canonical pageType "dictionary" or "feature" from parseResult.`);
1253
+ }
1254
+ if (['feature', 'dictionary', 'non_standard'].includes(normalized)) return normalized;
1255
+ throw new Error('Unsupported pageType: ' + normalized);
1256
+ }
1257
+
1258
+ function rejectSemanticStageInputs(input) {
1259
+ const forbiddenKeys = [
1260
+ 'parseResult',
1261
+ 'prdFile',
1262
+ 'apiDocFile',
1263
+ 'fieldMappings',
1264
+ 'dictionaryMeta',
1265
+ 'listQueryMeta',
1266
+ 'fieldUiMeta',
1267
+ ];
1268
+ const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
1269
+ if (present.length) {
1270
+ throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
1271
+ }
1272
+ }
1273
+
1274
+ function validatePageTypeAndStyle(pageType, style) {
1275
+ if (!pageType) return;
1276
+ if (pageType === 'non_standard') {
1277
+ throw new Error('non_standard pages are not supported by worsoft_codegen_local_generate_frontend');
1278
+ }
1279
+ if (pageType !== 'dictionary') return;
1280
+ if (style === 'single_table_jump' || style === 'master_child_jump') {
1281
+ throw new Error(`Dictionary pages must use dialog-based styles. pageType=dictionary does not support style=${style}`);
1282
+ }
1283
+ }
1284
+
1216
1285
  function hasRuntimeSupport(stylePreset) {
1217
1286
  return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
1218
1287
  }
1219
1288
 
1220
- function normalizeFields(parsed) {
1221
- return parsed.fields.map((field) => ({ ...field, formType: mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1222
- }
1289
+ function normalizeFields(parsed) {
1290
+ return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1291
+ }
1223
1292
 
1224
1293
  function ensureFieldExists(fields, fieldName, tableName, role) {
1225
1294
  const field = fields.find((item) => item.fieldName === fieldName);
@@ -1322,9 +1391,10 @@ function buildMultiLevelDictModel(safeArgs) {
1322
1391
 
1323
1392
  return {
1324
1393
  featureTitle: safeArgs.featureTitle || safeArgs.tableComment || parentModule.tableComment,
1325
- tableName: safeArgs.tableName,
1394
+ tableName: safeArgs.tableName,
1326
1395
  tableComment: safeArgs.tableComment || parentModule.tableComment,
1327
1396
  apiPath: safeArgs.apiPath || parentModule.apiPath,
1397
+ pageType: safeArgs.pageType || 'dictionary',
1328
1398
  className: toPascalCase(safeArgs.tableName || parentModule.tableName),
1329
1399
  functionName: resolvedTargets.functionName,
1330
1400
  moduleName: resolvedTargets.moduleName,
@@ -1419,6 +1489,7 @@ function buildModel(safeArgs) {
1419
1489
  tableName: safeArgs.tableName,
1420
1490
  tableComment: safeArgs.tableComment || safeArgs.featureTitle || safeArgs.tableName,
1421
1491
  apiPath,
1492
+ pageType: safeArgs.pageType || '',
1422
1493
  className: toPascalCase(safeArgs.tableName),
1423
1494
  functionName: resolvedTargets.functionName,
1424
1495
  moduleName: resolvedTargets.moduleName,
@@ -2591,15 +2662,18 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
2591
2662
  return files;
2592
2663
  }
2593
2664
 
2594
- function ensureArguments(input) {
2595
- if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
2596
- for (const key of TOOL_SCHEMA.required) {
2597
- if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
2598
- }
2665
+ function ensureArguments(input) {
2666
+ if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
2667
+ rejectSemanticStageInputs(input);
2668
+ for (const key of TOOL_SCHEMA.required) {
2669
+ if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
2670
+ }
2599
2671
 
2600
- const style = String(input.style);
2601
- getStylePreset(style);
2602
- const isMultiLevelDict = style === 'multi_level_dict';
2672
+ const style = String(input.style);
2673
+ const pageType = normalizePageTypeInput(input.pageType);
2674
+ getStylePreset(style);
2675
+ validatePageTypeAndStyle(pageType, style);
2676
+ const isMultiLevelDict = style === 'multi_level_dict';
2603
2677
  const fields = isMultiLevelDict ? [] : normalizeStructuredFieldArray(input.fields, 'fields');
2604
2678
  const levels = isMultiLevelDict ? normalizeLevelsInput(input.levels) : [];
2605
2679
 
@@ -2616,7 +2690,8 @@ function ensureArguments(input) {
2616
2690
  tableName: String(input.tableName),
2617
2691
  tableComment: input.tableComment ? String(input.tableComment) : '',
2618
2692
  apiPath: normalizeApiPath(input.apiPath),
2619
- style,
2693
+ pageType,
2694
+ style,
2620
2695
  fields,
2621
2696
  levels,
2622
2697
  children: normalizeChildrenInput(input.children),
@@ -2659,6 +2734,7 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
2659
2734
  return {
2660
2735
  mode: 'local-template',
2661
2736
  style: safeArgs.style,
2737
+ pageType: model.pageType || '',
2662
2738
  styleLabel: stylePreset.label,
2663
2739
  runtimeSupported: hasRuntimeSupport(stylePreset),
2664
2740
  tableName: model.tableName,
@@ -2712,10 +2788,11 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
2712
2788
  }));
2713
2789
  const selectedList = relations.map((relation) => formatRelationCandidate(relation));
2714
2790
 
2715
- return {
2716
- mode: 'local-template',
2717
- style: safeArgs.style,
2718
- styleLabel: stylePreset.label,
2791
+ return {
2792
+ mode: 'local-template',
2793
+ style: safeArgs.style,
2794
+ pageType: model.pageType || '',
2795
+ styleLabel: stylePreset.label,
2719
2796
  runtimeSupported: hasRuntimeSupport(stylePreset),
2720
2797
  tableName: model.tableName,
2721
2798
  tableComment: model.tableComment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.32",
3
+ "version": "0.1.35",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",