worsoft-frontend-codegen-local-mcp 0.1.33 → 0.1.36

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 +83 -48
  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.33';
8
+ const SERVER_VERSION = '0.1.36';
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,19 +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.' },
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
155
  pageType: {
156
156
  type: 'string',
157
157
  enum: ['feature', 'dictionary', 'non_standard', 'business', 'dict'],
158
- description: 'Structured page type from parseResult. Dictionary pages are restricted to dialog-based templates.',
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
159
  },
160
- style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Style id from assets/style-catalog.json.' },
161
- fields: {
162
- type: 'array',
163
- description: 'Structured main-table field metadata provided by the caller.',
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.',
164
164
  items: {
165
165
  type: 'object',
166
166
  properties: {
@@ -177,8 +177,10 @@ const TOOL_SCHEMA = {
177
177
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
178
178
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
179
179
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
180
- description: { type: 'string', description: 'Field description or notes.' },
181
- 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.' },
182
184
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
183
185
  },
184
186
  required: ['fieldName', 'label', 'type'],
@@ -187,7 +189,7 @@ const TOOL_SCHEMA = {
187
189
  },
188
190
  children: {
189
191
  type: 'array',
190
- 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.',
191
193
  items: {
192
194
  type: 'object',
193
195
  properties: {
@@ -216,8 +218,10 @@ const TOOL_SCHEMA = {
216
218
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
217
219
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
218
220
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
219
- description: { type: 'string', description: 'Field description or notes.' },
220
- 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.' },
221
225
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
222
226
  },
223
227
  required: ['fieldName', 'label', 'type'],
@@ -243,8 +247,8 @@ const TOOL_SCHEMA = {
243
247
  items: {
244
248
  type: 'object',
245
249
  properties: {
246
- tableName: { type: 'string', description: 'Module table name.' },
247
- 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.' },
248
252
  apiPath: { type: 'string', description: 'Backend API base path for this module.' },
249
253
  primaryKey: { type: 'string', description: 'Primary key field name. Defaults to id when omitted.' },
250
254
  queryParentField: { type: 'string', description: 'Direct parent foreign key field used for page queries.' },
@@ -271,8 +275,10 @@ const TOOL_SCHEMA = {
271
275
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
272
276
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
273
277
  defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
274
- description: { type: 'string', description: 'Field description or notes.' },
275
- 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.' },
276
282
  primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' }
277
283
  },
278
284
  required: ['fieldName', 'label', 'type'],
@@ -303,11 +309,6 @@ const TOOL_SCHEMA = {
303
309
  type: 'string',
304
310
  description: 'Explicit zh-cn i18n namespace, for example admin.iwmSysTrade. When provided, MCP writes zh-cn content to the matching file path and namespace instead of deriving them from moduleName and functionName.',
305
311
  },
306
- generationMode: {
307
- type: 'string',
308
- enum: ['create_new', 'overwrite_existing', 'patch_existing'],
309
- description: 'Structured generation mode from parseResult. MCP currently uses this as a pass-through runtime hint and result annotation.',
310
- },
311
312
  writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
312
313
  overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
313
314
  writeSupportFiles: {
@@ -850,15 +851,34 @@ function findDictType(comment) {
850
851
  return extractDictType(comment);
851
852
  }
852
853
 
853
- function mapFieldType(field) {
854
- if (field.dictType) return 'select';
855
- if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
856
- if (field.sqlType === 'DATE') return 'date';
857
- if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
858
- if (field.sqlType === 'TEXT') return 'textarea';
859
- if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
860
- return 'text';
861
- }
854
+ function mapFieldType(field) {
855
+ if (field.dictType) return 'select';
856
+ if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
857
+ if (field.sqlType === 'DATE') return 'date';
858
+ if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
859
+ if (field.sqlType === 'TEXT') return 'textarea';
860
+ if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
861
+ return 'text';
862
+ }
863
+
864
+ function normalizeStructuredFormType(value) {
865
+ const normalized = String(value || '').trim().toLowerCase();
866
+ if (!normalized) return '';
867
+ if (normalized === 'date') return 'date';
868
+ if (normalized === 'datetime') return 'datetime';
869
+ if (normalized === 'microme-operator') return 'number';
870
+ if (normalized === 'upload' || normalized === 'picker') {
871
+ throw new Error(
872
+ 'Explicit component/form type "' +
873
+ normalized +
874
+ '" is not yet supported by worsoft-codegen-local templates. Please keep it in parseResult for downstream handling or extend MCP template support first.'
875
+ );
876
+ }
877
+ if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
878
+ return normalized;
879
+ }
880
+ throw new Error('Unsupported explicit component/form type: ' + normalized);
881
+ }
862
882
 
863
883
  function escapeForRegex(value) {
864
884
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -1013,6 +1033,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1013
1033
  }
1014
1034
 
1015
1035
  const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
1036
+ const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1016
1037
  const explicitQueryType =
1017
1038
  inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1018
1039
  ? undefined
@@ -1028,6 +1049,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1028
1049
  comment: label,
1029
1050
  label,
1030
1051
  description: String(inputField.description || '').trim(),
1052
+ formType: explicitFormType,
1031
1053
  dictType: normalizeDictType(inputField.dictType),
1032
1054
  notNull: parseBooleanLike(inputField.required, false),
1033
1055
  defaultValue: normalizeDefaultValue(inputField.defaultValue),
@@ -1221,12 +1243,29 @@ function getStylePreset(styleId) {
1221
1243
  function normalizePageTypeInput(pageType) {
1222
1244
  if (pageType === undefined || pageType === null || pageType === '') return '';
1223
1245
  const normalized = String(pageType).trim();
1224
- if (normalized === 'dict') return 'dictionary';
1225
- if (normalized === 'business') return 'feature';
1246
+ if (normalized === 'dict' || normalized === 'business') {
1247
+ throw new Error(`Legacy pageType alias "${normalized}" is not allowed. Use canonical pageType "dictionary" or "feature" from parseResult.`);
1248
+ }
1226
1249
  if (['feature', 'dictionary', 'non_standard'].includes(normalized)) return normalized;
1227
1250
  throw new Error('Unsupported pageType: ' + normalized);
1228
1251
  }
1229
1252
 
1253
+ function rejectSemanticStageInputs(input) {
1254
+ const forbiddenKeys = [
1255
+ 'parseResult',
1256
+ 'prdFile',
1257
+ 'apiDocFile',
1258
+ 'fieldMappings',
1259
+ 'dictionaryMeta',
1260
+ 'listQueryMeta',
1261
+ 'fieldUiMeta',
1262
+ ];
1263
+ const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
1264
+ if (present.length) {
1265
+ throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
1266
+ }
1267
+ }
1268
+
1230
1269
  function validatePageTypeAndStyle(pageType, style) {
1231
1270
  if (!pageType) return;
1232
1271
  if (pageType === 'non_standard') {
@@ -1242,9 +1281,9 @@ function hasRuntimeSupport(stylePreset) {
1242
1281
  return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
1243
1282
  }
1244
1283
 
1245
- function normalizeFields(parsed) {
1246
- return parsed.fields.map((field) => ({ ...field, formType: mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1247
- }
1284
+ function normalizeFields(parsed) {
1285
+ return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
1286
+ }
1248
1287
 
1249
1288
  function ensureFieldExists(fields, fieldName, tableName, role) {
1250
1289
  const field = fields.find((item) => item.fieldName === fieldName);
@@ -1357,7 +1396,6 @@ function buildMultiLevelDictModel(safeArgs) {
1357
1396
  targetViewDir: resolvedTargets.targetViewDir,
1358
1397
  targetApiModule: resolvedTargets.targetApiModule,
1359
1398
  targetI18nKey: resolvedTargets.targetI18nKey,
1360
- generationMode: safeArgs.generationMode,
1361
1399
  frontendPath: path.resolve(safeArgs.frontendPath),
1362
1400
  style: safeArgs.style,
1363
1401
  levels: builtLevels,
@@ -1452,7 +1490,6 @@ function buildModel(safeArgs) {
1452
1490
  targetViewDir: resolvedTargets.targetViewDir,
1453
1491
  targetApiModule: resolvedTargets.targetApiModule,
1454
1492
  targetI18nKey: resolvedTargets.targetI18nKey,
1455
- generationMode: safeArgs.generationMode,
1456
1493
  pk: pkField,
1457
1494
  fields,
1458
1495
  optionFields,
@@ -2618,11 +2655,12 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
2618
2655
  return files;
2619
2656
  }
2620
2657
 
2621
- function ensureArguments(input) {
2622
- if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
2623
- for (const key of TOOL_SCHEMA.required) {
2624
- if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
2625
- }
2658
+ function ensureArguments(input) {
2659
+ if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
2660
+ rejectSemanticStageInputs(input);
2661
+ for (const key of TOOL_SCHEMA.required) {
2662
+ if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
2663
+ }
2626
2664
 
2627
2665
  const style = String(input.style);
2628
2666
  const pageType = normalizePageTypeInput(input.pageType);
@@ -2655,7 +2693,6 @@ function ensureArguments(input) {
2655
2693
  targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
2656
2694
  targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
2657
2695
  targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
2658
- generationMode: input.generationMode ? String(input.generationMode) : 'create_new',
2659
2696
  writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
2660
2697
  overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
2661
2698
  writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
@@ -2699,7 +2736,6 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
2699
2736
  targetViewDir: model.targetViewDir,
2700
2737
  targetApiModule: model.targetApiModule,
2701
2738
  targetI18nKey: model.targetI18nKey,
2702
- generationMode: model.generationMode || safeArgs.generationMode,
2703
2739
  writeToDisk: safeArgs.writeToDisk,
2704
2740
  sideEffects: {
2705
2741
  writeSupportFiles: safeArgs.writeSupportFiles,
@@ -2756,7 +2792,6 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
2756
2792
  targetViewDir: model.targetViewDir,
2757
2793
  targetApiModule: model.targetApiModule,
2758
2794
  targetI18nKey: model.targetI18nKey,
2759
- generationMode: model.generationMode || safeArgs.generationMode,
2760
2795
  writeToDisk: safeArgs.writeToDisk,
2761
2796
  sideEffects: {
2762
2797
  writeSupportFiles: safeArgs.writeSupportFiles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.36",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",