worsoft-frontend-codegen-local-mcp 0.1.3 → 0.1.4

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
@@ -24,6 +24,7 @@ This MCP generates Worsoft frontend files from a local Markdown design document.
24
24
  - `mainField` when `style=master_child_jump`: required
25
25
  - `childField` when `style=master_child_jump`: required
26
26
  - `relationType` when `style=master_child_jump`: optional
27
+ - `children` when `style=master_child_jump`: optional array of direct child relations; when provided it overrides the legacy single-child fields
27
28
 
28
29
  ## Recommended MCP Arguments
29
30
 
@@ -57,6 +58,33 @@ Master-child:
57
58
  }
58
59
  ```
59
60
 
61
+ Multi direct children on one main form:
62
+
63
+ ```json
64
+ {
65
+ "tableName": "iwm_sys_trade_level",
66
+ "style": "master_child_jump",
67
+ "children": [
68
+ {
69
+ "childTableName": "iwm_sys_trade_level_standard",
70
+ "mainField": "id",
71
+ "childField": "trade_level_id",
72
+ "relationType": "1:N"
73
+ },
74
+ {
75
+ "childTableName": "iwm_sys_trade_level_competency",
76
+ "mainField": "id",
77
+ "childField": "trade_level_id",
78
+ "relationType": "1:N"
79
+ }
80
+ ],
81
+ "designFile": "plugins/sql/SQL 璁捐璇存槑.md",
82
+ "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
83
+ "moduleName": "admin/test",
84
+ "writeToDisk": false
85
+ }
86
+ ```
87
+
60
88
  ## Smoke Test
61
89
 
62
90
  PowerShell:
@@ -65,6 +93,7 @@ PowerShell:
65
93
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
66
94
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
67
95
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
96
+ powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario multi
68
97
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario missing_relation
69
98
  ```
70
99
 
@@ -74,6 +103,7 @@ Node:
74
103
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
75
104
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
76
105
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
106
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario multi
77
107
  node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario missing_relation
78
108
  ```
79
109
 
@@ -95,9 +125,8 @@ For `master_child_jump`, MCP does not infer relations from the design file anymo
95
125
 
96
126
  The caller must provide:
97
127
 
98
- - `childTableName`
99
- - `mainField`
100
- - `childField`
128
+ - either `children[]`
129
+ - or the legacy single-child fields: `childTableName`, `mainField`, `childField`
101
130
 
102
131
  If these fields are missing, MCP returns `relation_input_required`.
103
132
 
@@ -40,10 +40,10 @@ export function putObj(obj?: object) {
40
40
  });
41
41
  }
42
42
 
43
- export function delChildObj(ids?: object) {
43
+ export function delChildObj(ids?: object, childTableName?: string) {
44
44
  return request({
45
45
  url: '/{{API_PATH}}/child',
46
46
  method: 'delete',
47
- data: ids,
47
+ data: childTableName ? { ids, childTableName } : ids,
48
48
  });
49
49
  }
@@ -9,9 +9,7 @@
9
9
  {{FORM_FIELDS}}
10
10
  </el-row>
11
11
  <el-row :gutter="24">
12
- <sc-form-table v-model="form.{{CHILD_LIST_NAME}}" :addTemplate="childTemp" @delete="deleteChild" placeholder="暂无数据">
13
- {{CHILD_TABLE_COLUMNS}}
14
- </sc-form-table>
12
+ {{CHILD_SECTIONS}}
15
13
  </el-row>
16
14
  </el-form>
17
15
  <div class="dialog-footer" style="text-align: right; margin-top: 18px;">
@@ -38,12 +36,10 @@ const detail = ref(false);
38
36
 
39
37
  const form = reactive({
40
38
  {{FORM_DEFAULTS}}
41
- {{CHILD_LIST_NAME}}: [],
39
+ {{CHILD_FORM_LIST_DEFAULTS}}
42
40
  });
43
41
 
44
- const childTemp = reactive({
45
- {{CHILD_TEMP_DEFAULTS}}
46
- });
42
+ {{CHILD_TEMP_DECLARATIONS}}
47
43
 
48
44
  const dataRules = ref({
49
45
  {{FORM_RULES}}
@@ -64,11 +60,11 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
64
60
  const resetFormState = () => {
65
61
  Object.assign(form, {
66
62
  {{FORM_DEFAULTS}}
67
- {{CHILD_LIST_NAME}}: [],
63
+ {{CHILD_FORM_LIST_DEFAULTS}}
68
64
  });
69
65
  nextTick(() => {
70
66
  dataFormRef.value?.resetFields();
71
- form.{{CHILD_LIST_NAME}} = [];
67
+ {{CHILD_RESET_LISTS}}
72
68
  });
73
69
  };
74
70
 
@@ -113,10 +109,10 @@ const onSubmit = async () => {
113
109
  }
114
110
  };
115
111
 
116
- const deleteChild = async (obj: { {{CHILD_PK_ATTR}}: string }) => {
117
- if (obj.{{CHILD_PK_ATTR}}) {
112
+ const deleteChild = async (obj: Record<string, any>, childPkAttr: string, childTableName?: string) => {
113
+ if (obj[childPkAttr]) {
118
114
  try {
119
- await delChildObj([obj.{{CHILD_PK_ATTR}}]);
115
+ await delChildObj([obj[childPkAttr]], childTableName);
120
116
  useMessage().success('删除成功');
121
117
  } catch (err: any) {
122
118
  useMessage().error(err.msg || '删除失败');
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.3';
8
+ const SERVER_VERSION = '0.1.4';
9
9
  const PROTOCOL_VERSION = '2024-11-05';
10
10
  const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
11
11
  const TEMPLATE_LIBRARY_ROOT = path.resolve(__dirname, '..', 'template');
@@ -19,6 +19,21 @@ const TOOL_SCHEMA = {
19
19
  designFile: { type: 'string', description: 'Absolute or relative Markdown design file path. Defaults to ../sql/SQL 设计说明.md when omitted.' },
20
20
  tableName: { type: 'string', description: 'Target main table name from the design file.' },
21
21
  style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Style id from assets/style-catalog.json.' },
22
+ children: {
23
+ type: 'array',
24
+ description: 'Optional direct child-table relations for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form.',
25
+ items: {
26
+ type: 'object',
27
+ properties: {
28
+ childTableName: { type: 'string', description: 'Child table name.' },
29
+ mainField: { type: 'string', description: 'Main table relation field.' },
30
+ childField: { type: 'string', description: 'Child table relation field.' },
31
+ relationType: { type: 'string', description: 'Optional relation type label, for example 1:N.' },
32
+ },
33
+ required: ['childTableName', 'mainField', 'childField'],
34
+ additionalProperties: false,
35
+ },
36
+ },
22
37
  childTableName: { type: 'string', description: 'Child table name. Required when style=master_child_jump.' },
23
38
  mainField: { type: 'string', description: 'Main table relation field. Required when style=master_child_jump.' },
24
39
  childField: { type: 'string', description: 'Child table relation field. Required when style=master_child_jump.' },
@@ -474,6 +489,15 @@ function buildRetryArguments(safeArgs, sourceFile, childTableName) {
474
489
  retryArguments.designFile = sourceFile;
475
490
  }
476
491
 
492
+ if (safeArgs.children && safeArgs.children.length) {
493
+ retryArguments.children = safeArgs.children.map((relation) => ({
494
+ childTableName: relation.childTableName,
495
+ mainField: relation.mainField,
496
+ childField: relation.childField,
497
+ relationType: relation.relationType || '',
498
+ }));
499
+ }
500
+
477
501
  if (childTableName) {
478
502
  retryArguments.childTableName = childTableName;
479
503
  }
@@ -536,13 +560,43 @@ function loadSourceDocument(safeArgs) {
536
560
  };
537
561
  }
538
562
 
539
- function resolveMarkdownChildRelation(sourceDocument, safeArgs) {
563
+ function normalizeChildrenInput(inputChildren) {
564
+ if (inputChildren === undefined || inputChildren === null) {
565
+ return [];
566
+ }
567
+ if (!Array.isArray(inputChildren)) {
568
+ throw new Error('children must be an array');
569
+ }
570
+ return inputChildren.map((item, index) => {
571
+ if (!item || typeof item !== 'object') {
572
+ throw new Error('children[' + index + '] must be an object');
573
+ }
574
+ const childTableName = item.childTableName ? String(item.childTableName) : '';
575
+ const mainField = item.mainField ? String(item.mainField) : '';
576
+ const childField = item.childField ? String(item.childField) : '';
577
+ const relationType = item.relationType ? String(item.relationType) : '';
578
+ const missingFields = ['childTableName', 'mainField', 'childField'].filter((field) => !({ childTableName, mainField, childField })[field]);
579
+ if (missingFields.length) {
580
+ throw new Error('children[' + index + '] is missing required fields: ' + missingFields.join(', '));
581
+ }
582
+ return { childTableName, mainField, childField, relationType };
583
+ });
584
+ }
585
+
586
+ function resolveMarkdownChildRelations(sourceDocument, safeArgs) {
587
+ if (safeArgs.children && safeArgs.children.length) {
588
+ return safeArgs.children.map((relation) => ({
589
+ mainTableName: safeArgs.tableName,
590
+ childTableName: relation.childTableName,
591
+ mainField: relation.mainField,
592
+ childField: relation.childField,
593
+ relationType: relation.relationType || '',
594
+ }));
595
+ }
596
+
540
597
  const missingFields = ['childTableName', 'mainField', 'childField'].filter((field) => !safeArgs[field]);
541
598
  if (missingFields.length) {
542
- const message =
543
- 'master_child_jump requires externally provided relation fields. Missing: ' +
544
- missingFields.join(', ') +
545
- '.';
599
+ const message = 'master_child_jump requires either children[] or legacy relation fields. Missing: ' + missingFields.join(', ') + '.';
546
600
  const error = new Error(message);
547
601
  error.details = {
548
602
  type: 'relation_input_required',
@@ -550,11 +604,19 @@ function resolveMarkdownChildRelation(sourceDocument, safeArgs) {
550
604
  tableName: safeArgs.tableName,
551
605
  designFile: sourceDocument.path,
552
606
  message,
553
- requiredFields: ['childTableName', 'mainField', 'childField'],
607
+ requiredFields: ['children[] or childTableName/mainField/childField'],
554
608
  correctionEntry: {
555
- fields: ['childTableName', 'mainField', 'childField'],
609
+ fields: ['children', 'childTableName', 'mainField', 'childField'],
556
610
  example: {
557
611
  ...buildRetryArguments(safeArgs, sourceDocument.path, safeArgs.childTableName || '<child_table_name>'),
612
+ children: [
613
+ {
614
+ childTableName: safeArgs.childTableName || '<child_table_name>',
615
+ mainField: safeArgs.mainField || '<main_field>',
616
+ childField: safeArgs.childField || '<child_field>',
617
+ relationType: safeArgs.relationType || '1:N',
618
+ },
619
+ ],
558
620
  mainField: safeArgs.mainField || '<main_field>',
559
621
  childField: safeArgs.childField || '<child_field>',
560
622
  },
@@ -563,13 +625,15 @@ function resolveMarkdownChildRelation(sourceDocument, safeArgs) {
563
625
  throw error;
564
626
  }
565
627
 
566
- return {
567
- mainTableName: safeArgs.tableName,
568
- childTableName: safeArgs.childTableName,
569
- mainField: safeArgs.mainField,
570
- childField: safeArgs.childField,
571
- relationType: safeArgs.relationType || '',
572
- };
628
+ return [
629
+ {
630
+ mainTableName: safeArgs.tableName,
631
+ childTableName: safeArgs.childTableName,
632
+ mainField: safeArgs.mainField,
633
+ childField: safeArgs.childField,
634
+ relationType: safeArgs.relationType || '',
635
+ },
636
+ ];
573
637
  }
574
638
 
575
639
  function getStylePreset(styleId) {
@@ -601,31 +665,33 @@ function ensureFieldExists(fields, fieldName, tableName, role) {
601
665
  return field;
602
666
  }
603
667
 
604
- function buildChildModel(sourceDocument, safeArgs, mainParsed) {
605
- if (safeArgs.style !== 'master_child_jump') return null;
606
-
607
- const relation = resolveMarkdownChildRelation(sourceDocument, safeArgs);
608
- const childParsed = parseTableFromMarkdownDesign(sourceDocument.designDoc, relation.childTableName);
609
- const childFields = normalizeFields(childParsed);
610
- const mainRelationField = ensureFieldExists(mainParsed.fields, relation.mainField, safeArgs.tableName, 'Main relation');
611
- const childRelationField = ensureFieldExists(childParsed.fields, relation.childField, relation.childTableName, 'Child relation');
612
- const childVisibleFields = childFields.filter(
613
- (field) => field.fieldName !== childParsed.pkField.fieldName && !field.isAudit && field.fieldName !== relation.childField
614
- );
615
-
616
- return {
617
- tableName: relation.childTableName,
618
- tableComment: childParsed.tableComment,
619
- className: toPascalCase(relation.childTableName),
620
- functionName: toCamelCase(relation.childTableName),
621
- listName: toCamelCase(relation.childTableName) + 'List',
622
- pk: childParsed.pkField,
623
- fields: childFields,
624
- visibleFields: childVisibleFields,
625
- mainField: mainRelationField,
626
- childField: childRelationField,
627
- relationType: relation.relationType,
628
- };
668
+ function buildChildModels(sourceDocument, safeArgs, mainParsed) {
669
+ if (safeArgs.style !== 'master_child_jump') return [];
670
+
671
+ const relations = resolveMarkdownChildRelations(sourceDocument, safeArgs);
672
+ return relations.map((relation) => {
673
+ const childParsed = parseTableFromMarkdownDesign(sourceDocument.designDoc, relation.childTableName);
674
+ const childFields = normalizeFields(childParsed);
675
+ const mainRelationField = ensureFieldExists(mainParsed.fields, relation.mainField, safeArgs.tableName, 'Main relation');
676
+ const childRelationField = ensureFieldExists(childParsed.fields, relation.childField, relation.childTableName, 'Child relation');
677
+ const childVisibleFields = childFields.filter(
678
+ (field) => field.fieldName !== childParsed.pkField.fieldName && !field.isAudit && field.fieldName !== relation.childField
679
+ );
680
+
681
+ return {
682
+ tableName: relation.childTableName,
683
+ tableComment: childParsed.tableComment,
684
+ className: toPascalCase(relation.childTableName),
685
+ functionName: toCamelCase(relation.childTableName),
686
+ listName: toCamelCase(relation.childTableName) + 'List',
687
+ pk: childParsed.pkField,
688
+ fields: childFields,
689
+ visibleFields: childVisibleFields,
690
+ mainField: mainRelationField,
691
+ childField: childRelationField,
692
+ relationType: relation.relationType,
693
+ };
694
+ });
629
695
  }
630
696
 
631
697
  function buildModel(safeArgs) {
@@ -634,8 +700,8 @@ function buildModel(safeArgs) {
634
700
  const fields = normalizeFields(mainParsed);
635
701
  const visibleFields = fields.filter((field) => field.fieldName !== mainParsed.pkField.fieldName && !field.isAudit);
636
702
  const gridFields = visibleFields.slice(0, 8);
637
- const child = buildChildModel(sourceDocument, safeArgs, mainParsed);
638
- const childDictTypes = child ? child.visibleFields.map((field) => field.dictType).filter(Boolean) : [];
703
+ const children = buildChildModels(sourceDocument, safeArgs, mainParsed);
704
+ const childDictTypes = children.flatMap((child) => child.visibleFields.map((field) => field.dictType).filter(Boolean));
639
705
  const dictTypes = [...new Set([...visibleFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
640
706
 
641
707
  return {
@@ -653,7 +719,7 @@ function buildModel(safeArgs) {
653
719
  dictTypes,
654
720
  frontendPath: path.resolve(safeArgs.frontendPath),
655
721
  style: safeArgs.style,
656
- child,
722
+ children,
657
723
  };
658
724
  }
659
725
 
@@ -792,6 +858,50 @@ function renderChildTempDefaults(childModel) {
792
858
  return childModel.fields.map(renderDefaultLine).join('\n');
793
859
  }
794
860
 
861
+ function renderChildListDefaultLine(childModel) {
862
+ return ` ${childModel.listName}: [],`;
863
+ }
864
+
865
+ function renderChildTempDeclaration(childModel) {
866
+ return [
867
+ `const childTemp${childModel.className} = reactive({`,
868
+ renderChildTempDefaults(childModel),
869
+ '});',
870
+ ].join('\n');
871
+ }
872
+
873
+ function renderChildSection(childModel, childCount) {
874
+ const title = childModel.tableComment.replace(/'/g, "\\'");
875
+ const deleteExpression =
876
+ childCount > 1
877
+ ? `deleteChild(obj, '${childModel.pk.attrName}', '${childModel.tableName}')`
878
+ : `deleteChild(obj, '${childModel.pk.attrName}')`;
879
+
880
+ return [
881
+ ' <el-col :span="24" class="mb20">',
882
+ ` <div class="mb10" style="font-weight: 600;">${title}</div>`,
883
+ ` <sc-form-table v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" @delete="(obj) => ${deleteExpression}" placeholder="暂无数据">`,
884
+ childModel.visibleFields.map((field) => renderChildTableColumn(field, childModel.listName)).join('\n'),
885
+ ' </sc-form-table>',
886
+ ' </el-col>',
887
+ ].join('\n');
888
+ }
889
+
890
+ function renderChildFormListDefaults(children) {
891
+ if (!children.length) return '';
892
+ return children.map(renderChildListDefaultLine).join('\n');
893
+ }
894
+
895
+ function renderChildTempDeclarations(children) {
896
+ if (!children.length) return '';
897
+ return children.map(renderChildTempDeclaration).join('\n\n');
898
+ }
899
+
900
+ function renderChildResetListLines(children) {
901
+ if (!children.length) return '';
902
+ return children.map((childModel) => ` form.${childModel.listName} = [];`).join('\n');
903
+ }
904
+
795
905
  function renderDictImportBlock(dictTypes) {
796
906
  if (!dictTypes.length) return '';
797
907
  return [
@@ -846,12 +956,10 @@ function buildReplacements(model) {
846
956
  OPTIONS_FIELDS: model.visibleFields.map(renderOptionField).join('\n'),
847
957
  FILTER_TYPES: model.visibleFields.map(renderFilterType).filter(Boolean).join('\n'),
848
958
  FORM_RULES: renderFormRules(model.visibleFields),
849
- CHILD_CLASS_NAME: model.child ? model.child.className : '',
850
- CHILD_LIST_NAME: model.child ? model.child.listName : '',
851
- CHILD_PK_ATTR: model.child ? model.child.pk.attrName : '',
852
- CHILD_RELATION_ATTR: model.child ? model.child.childField.attrName : '',
853
- CHILD_TABLE_COLUMNS: model.child ? model.child.visibleFields.map((field) => renderChildTableColumn(field, model.child.listName)).join('\n') : '',
854
- CHILD_TEMP_DEFAULTS: model.child ? renderChildTempDefaults(model.child) : '',
959
+ CHILD_FORM_LIST_DEFAULTS: renderChildFormListDefaults(model.children),
960
+ CHILD_TEMP_DECLARATIONS: renderChildTempDeclarations(model.children),
961
+ CHILD_RESET_LISTS: renderChildResetListLines(model.children),
962
+ CHILD_SECTIONS: model.children.map((childModel) => renderChildSection(childModel, model.children.length)).join('\n'),
855
963
  };
856
964
  }
857
965
 
@@ -897,6 +1005,7 @@ function ensureArguments(input) {
897
1005
  designFile: input.designFile ? String(input.designFile) : null,
898
1006
  tableName: String(input.tableName),
899
1007
  style,
1008
+ children: normalizeChildrenInput(input.children),
900
1009
  childTableName: input.childTableName ? String(input.childTableName) : null,
901
1010
  mainField: input.mainField ? String(input.mainField) : null,
902
1011
  childField: input.childField ? String(input.childField) : null,
@@ -922,6 +1031,16 @@ function maybeWriteFiles(files, writeToDisk, overwrite) {
922
1031
  }
923
1032
 
924
1033
  function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, note) {
1034
+ const relations = model.children.map((childModel) => ({
1035
+ childTableName: childModel.tableName,
1036
+ childTableComment: childModel.tableComment,
1037
+ mainField: childModel.mainField.fieldName,
1038
+ childField: childModel.childField.fieldName,
1039
+ childListName: childModel.listName,
1040
+ relationType: childModel.relationType || '',
1041
+ }));
1042
+ const selectedList = relations.map((relation) => formatRelationCandidate(relation));
1043
+
925
1044
  return {
926
1045
  mode: 'local-template',
927
1046
  style: safeArgs.style,
@@ -935,32 +1054,23 @@ function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, not
935
1054
  writeToDisk: safeArgs.writeToDisk,
936
1055
  selectedTemplates: sharedTemplates,
937
1056
  files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
938
- relation: model.child
939
- ? {
940
- childTableName: model.child.tableName,
941
- childTableComment: model.child.tableComment,
942
- mainField: model.child.mainField.fieldName,
943
- childField: model.child.childField.fieldName,
944
- childListName: model.child.listName,
945
- relationType: model.child.relationType || '',
946
- }
947
- : null,
948
- relationResolution: model.child
1057
+ relation: relations.length === 1 ? relations[0] : null,
1058
+ relations,
1059
+ relationResolution: model.children.length
949
1060
  ? {
950
1061
  status: 'provided',
951
1062
  tableName: safeArgs.tableName,
952
1063
  designFile: model.designFile,
953
1064
  source: 'arguments',
954
- message: 'The relation was provided by the caller. MCP skipped design-doc relation inference and generated files directly.',
955
- selected: formatRelationCandidate({
956
- childTableName: model.child.tableName,
957
- mainField: model.child.mainField.fieldName,
958
- childField: model.child.childField.fieldName,
959
- relationType: model.child.relationType || '',
960
- }),
1065
+ message:
1066
+ model.children.length > 1
1067
+ ? 'Direct child relations were provided by the caller. MCP skipped design-doc relation inference and generated a single main form with multiple child tables.'
1068
+ : 'The relation was provided by the caller. MCP skipped design-doc relation inference and generated files directly.',
1069
+ selected: selectedList.length === 1 ? selectedList[0] : null,
1070
+ selectedList,
961
1071
  correctionEntry: {
962
- fields: ['childTableName', 'mainField', 'childField'],
963
- example: buildRetryArguments(safeArgs, model.designFile, model.child.tableName),
1072
+ fields: ['children', 'childTableName', 'mainField', 'childField'],
1073
+ example: buildRetryArguments(safeArgs, model.designFile, model.children[0].tableName),
964
1074
  },
965
1075
  }
966
1076
  : null,
@@ -969,7 +1079,9 @@ function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, not
969
1079
  visibleFields: model.visibleFields.length,
970
1080
  dictFields: model.visibleFields.filter((field) => field.dictType).map((field) => field.attrName),
971
1081
  skippedAuditFields: model.fields.filter((field) => field.isAudit).map((field) => field.fieldName),
972
- childVisibleFields: model.child ? model.child.visibleFields.length : 0,
1082
+ childCount: model.children.length,
1083
+ childTables: model.children.map((childModel) => childModel.tableName),
1084
+ childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
973
1085
  },
974
1086
  note,
975
1087
  };
@@ -1012,7 +1124,7 @@ async function onMessage(message) {
1012
1124
  }
1013
1125
 
1014
1126
  if (method === 'tools/list') {
1015
- writeMessage(successResponse(id, { tools: [{ name: TOOL_NAME, description: 'Generate Worsoft frontend files and menu SQL from a local Markdown design file with style-based local templates. In master_child_jump mode, the caller must provide childTableName, mainField, and childField.', inputSchema: TOOL_SCHEMA }] }));
1127
+ writeMessage(successResponse(id, { tools: [{ name: TOOL_NAME, description: 'Generate Worsoft frontend files and menu SQL from a local Markdown design file with style-based local templates. In master_child_jump mode, the caller must provide either children[] or childTableName/mainField/childField.', inputSchema: TOOL_SCHEMA }] }));
1016
1128
  return;
1017
1129
  }
1018
1130
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",