worsoft-frontend-codegen-local-mcp 0.1.0 → 0.1.2

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
@@ -1,36 +1,38 @@
1
1
  # Worsoft Frontend Codegen Local MCP
2
2
 
3
- This plugin is a parallel local-template MVP for frontend code generation.
4
- It does not call `/admin/generator/mcp/execute`.
3
+ This MCP generates Worsoft frontend files from a local Markdown design document.
5
4
 
6
- ## MVP Scope
5
+ ## Scope
7
6
 
8
7
  - local Markdown design file input
9
8
  - style-based template selection
10
9
  - local template rendering
11
10
  - single-table runtime generation
12
- - master-child runtime generation with relation inference from the design file
13
- - render plan preview
11
+ - master-child runtime generation with caller-provided relation input
14
12
  - optional write-to-disk
15
13
 
16
14
  ## Current Inputs
17
15
 
18
- - `designFile` : optional, defaults to `../sql/SQL 设计说明.md`
16
+ - `designFile`: optional, defaults to `../sql/SQL 设计说明.md`
19
17
  - `tableName`
20
18
  - `style`
21
19
  - `frontendPath`
22
20
  - `moduleName`
23
21
  - `writeToDisk`
24
- - `childTableName` when `style=master_child_jump` : optional, only needed when one main table has multiple child-table relations in the design file
22
+ - `overwrite`
23
+ - `childTableName` when `style=master_child_jump`: required
24
+ - `mainField` when `style=master_child_jump`: required
25
+ - `childField` when `style=master_child_jump`: required
26
+ - `relationType` when `style=master_child_jump`: optional
25
27
 
26
28
  ## Recommended MCP Arguments
27
29
 
28
- Use `designFile` as the source argument.
30
+ Single table:
29
31
 
30
32
  ```json
31
33
  {
32
34
  "tableName": "iwm_sys_trade",
33
- "style": "master_child_jump",
35
+ "style": "single_table_dialog",
34
36
  "designFile": "plugins/sql/SQL 设计说明.md",
35
37
  "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
36
38
  "moduleName": "admin/test",
@@ -38,22 +40,41 @@ Use `designFile` as the source argument.
38
40
  }
39
41
  ```
40
42
 
41
- ## Smoke Test Template
43
+ Master-child:
42
44
 
43
- Use `smoke_test_mcp.js` for quick local MCP verification.
45
+ ```json
46
+ {
47
+ "tableName": "iwm_sys_trade",
48
+ "style": "master_child_jump",
49
+ "childTableName": "iwm_sys_trade_level",
50
+ "mainField": "id",
51
+ "childField": "trade_id",
52
+ "relationType": "1:N",
53
+ "designFile": "plugins/sql/SQL 设计说明.md",
54
+ "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
55
+ "moduleName": "admin/test",
56
+ "writeToDisk": false
57
+ }
58
+ ```
44
59
 
45
- PowerShell is the recommended option on Windows because it does not depend on Node spawning a child Node process.
60
+ ## Smoke Test
46
61
 
47
- ```bash
48
- node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
49
- node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
50
- node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
51
- ```
62
+ PowerShell:
52
63
 
53
64
  ```powershell
54
65
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario single
55
66
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario master
56
67
  powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario child
68
+ powershell -ExecutionPolicy Bypass -File plugins/worsoft-codegen-local/smoke_test_mcp.ps1 -Scenario missing_relation
69
+ ```
70
+
71
+ Node:
72
+
73
+ ```bash
74
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario single
75
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario master
76
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario child
77
+ node plugins/worsoft-codegen-local/smoke_test_mcp.js --scenario missing_relation
57
78
  ```
58
79
 
59
80
  Optional overrides:
@@ -62,21 +83,30 @@ Optional overrides:
62
83
  - `--table-name`
63
84
  - `--style`
64
85
  - `--child-table-name`
86
+ - `--main-field`
87
+ - `--child-field`
88
+ - `--relation-type`
65
89
  - `--frontend-path`
66
90
  - `--write-to-disk`
67
91
 
68
- For `master_child_jump`, the execution result will return the detected relation. If the relation is ambiguous or not the one you want, the MCP error/result includes a `correctionEntry` with `childTableName`, `example`, and `retryArguments`, so the caller can resubmit directly.
92
+ ## Master-Child Contract
93
+
94
+ For `master_child_jump`, MCP does not infer relations from the design file anymore.
69
95
 
70
- When one main table has multiple child relations in the design file, add `childTableName` to disambiguate:
96
+ The caller must provide:
97
+
98
+ - `childTableName`
99
+ - `mainField`
100
+ - `childField`
101
+
102
+ If these fields are missing, MCP returns `relation_input_required`.
103
+
104
+ Example:
71
105
 
72
106
  ```json
73
107
  {
74
- "tableName": "iwm_sys_trade_level",
75
- "style": "master_child_jump",
76
- "designFile": "plugins/sql/SQL 设计说明.md",
77
- "childTableName": "iwm_sys_trade_level_standard",
78
- "frontendPath": "E:/own-worker-platform/trunk/worsoft-ui",
79
- "writeToDisk": false
108
+ "type": "relation_input_required",
109
+ "requiredFields": ["childTableName", "mainField", "childField"]
80
110
  }
81
111
  ```
82
112
 
@@ -94,28 +124,20 @@ When one main table has multiple child relations in the design file, add `childT
94
124
  - Machine source: `assets/style-catalog.json`
95
125
 
96
126
  Declared styles:
127
+
97
128
  - `single_table_jump`
98
129
  - `single_table_dialog`
99
130
  - `master_child_jump`
100
131
 
101
- Runtime generation support:
102
- - `single_table_jump`: supported
103
- - `single_table_dialog`: supported
104
- - `master_child_jump`: supported
105
-
106
- ## Non-goals for v0.1
107
-
108
- - backend API forwarding
109
- - generic Velocity rendering
110
- - dict lookup from backend
111
- - full compatibility with the old generator contract
112
-
113
132
  ## Notes
114
133
 
115
134
  - Shared Worsoft template references live under `../template`.
116
135
  - Plugin-local runtime assets live under `assets/templates`.
117
136
  - The preferred source is `plugins/sql/SQL 设计说明.md`.
118
- - Markdown mode parses both the field-definition tables and the "主从表关联说明" section.
119
- - When a main table has multiple child relations, provide `childTableName` to disambiguate.
120
- - Menu SQL is currently emitted to `frontendPath/menu/` because local mode does not accept `backendPath` yet.
121
- - The renderer uses a simple placeholder engine instead of the old backend template engine.
137
+ - Markdown parsing is used for field-definition tables.
138
+ - Master-child relation parsing is handled outside MCP and passed in through tool arguments.
139
+ - Menu SQL is emitted to `frontendPath/menu/`.
140
+
141
+ ## IDE Guide
142
+
143
+ - `IDE_MCP_SETUP.md`
@@ -2,7 +2,7 @@
2
2
  <div class="layout-padding-auto layout-padding-view">
3
3
  <el-card shadow="never">
4
4
  <template #header>
5
- <span>{{ form.{{PK_ATTR}} ? (detail ? 'Detail' : 'Edit') : 'Add' }}</span>
5
+ <span>{{ form.{{PK_ATTR}} ? (detail ? '查看' : '编辑') : '新增' }}</span>
6
6
  </template>
7
7
  <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
8
8
  <el-row :gutter="24">
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <el-dialog v-model="visible" :title="form.{{PK_ATTR}} ? 'Edit' : 'Add'" :close-on-click-modal="false" draggable>
2
+ <el-dialog v-model="visible" :title="form.{{PK_ATTR}} ? '编辑' : '新增'" :close-on-click-modal="false" draggable>
3
3
  <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
4
4
  <el-row :gutter="24">
5
5
  {{FORM_FIELDS}}
@@ -7,8 +7,8 @@
7
7
  </el-form>
8
8
  <template #footer>
9
9
  <span class="dialog-footer">
10
- <el-button @click="visible = false">Cancel</el-button>
11
- <el-button type="primary" @click="onSubmit" :disabled="loading">Confirm</el-button>
10
+ <el-button @click="visible = false">取消</el-button>
11
+ <el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
12
12
  </span>
13
13
  </template>
14
14
  </el-dialog>
@@ -39,7 +39,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
39
39
  const { data } = await getObj({ {{PK_ATTR}}: id });
40
40
  Object.assign(form, data[0] || {});
41
41
  } catch (error) {
42
- useMessage().error('Failed to fetch data');
42
+ useMessage().error('获取数据失败');
43
43
  } finally {
44
44
  loading.value = false;
45
45
  }
@@ -75,11 +75,11 @@ const onSubmit = async () => {
75
75
 
76
76
  try {
77
77
  form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
78
- useMessage().success(form.{{PK_ATTR}} ? 'Updated successfully' : 'Created successfully');
78
+ useMessage().success(form.{{PK_ATTR}} ? '修改成功' : '添加成功');
79
79
  visible.value = false;
80
80
  emit('refresh');
81
81
  } catch (err: any) {
82
- useMessage().error(err.msg || 'Submit failed');
82
+ useMessage().error(err.msg || '提交失败');
83
83
  } finally {
84
84
  loading.value = false;
85
85
  }
@@ -2,7 +2,7 @@
2
2
  <div class="layout-padding-auto layout-padding-view">
3
3
  <el-card shadow="never">
4
4
  <template #header>
5
- <span>{{ form.{{PK_ATTR}} ? (detail ? 'Detail' : 'Edit') : 'Add' }}</span>
5
+ <span>{{ form.{{PK_ATTR}} ? (detail ? '查看' : '编辑') : '新增' }}</span>
6
6
  </template>
7
7
  <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
8
8
  <el-row :gutter="24">
@@ -10,8 +10,8 @@
10
10
  </el-row>
11
11
  </el-form>
12
12
  <div class="dialog-footer" style="text-align: right; margin-top: 18px;">
13
- <el-button @click="handleBack">Cancel</el-button>
14
- <el-button type="primary" @click="onSubmit" :disabled="loading">Confirm</el-button>
13
+ <el-button @click="handleBack">取消</el-button>
14
+ <el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
15
15
  </div>
16
16
  </el-card>
17
17
  </div>
@@ -43,7 +43,7 @@ const get{{CLASS_NAME}}Data = async (id: string) => {
43
43
  const { data } = await getObj({ {{PK_ATTR}}: id });
44
44
  Object.assign(form, data[0] || {});
45
45
  } catch (error) {
46
- useMessage().error('Failed to fetch data');
46
+ useMessage().error('获取数据失败');
47
47
  } finally {
48
48
  loading.value = false;
49
49
  }
@@ -90,10 +90,10 @@ const onSubmit = async () => {
90
90
 
91
91
  try {
92
92
  form.{{PK_ATTR}} ? await putObj(form) : await addObj(form);
93
- useMessage().success(form.{{PK_ATTR}} ? 'Updated successfully' : 'Created successfully');
93
+ useMessage().success(form.{{PK_ATTR}} ? '修改成功' : '添加成功');
94
94
  closeCurrentPage();
95
95
  } catch (err: any) {
96
- useMessage().error(err.msg || 'Submit failed');
96
+ useMessage().error(err.msg || '提交失败');
97
97
  } finally {
98
98
  loading.value = false;
99
99
  }
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.0';
8
+ const SERVER_VERSION = '0.1.2';
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,7 +19,10 @@ 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
- childTableName: { type: 'string', description: 'Optional child table name. In master_child_jump mode, this is the correction entry used to choose one child relation from the design file.' },
22
+ childTableName: { type: 'string', description: 'Child table name. Required when style=master_child_jump.' },
23
+ mainField: { type: 'string', description: 'Main table relation field. Required when style=master_child_jump.' },
24
+ childField: { type: 'string', description: 'Child table relation field. Required when style=master_child_jump.' },
25
+ relationType: { type: 'string', description: 'Optional relation type label, for example 1:N.' },
23
26
  frontendPath: { type: 'string', description: 'Absolute frontend output root path.' },
24
27
  moduleName: { type: 'string', description: 'Relative frontend module path, for example admin/test.' },
25
28
  writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
@@ -375,7 +378,7 @@ function parseMarkdownRelations(markdownText) {
375
378
  function parseMarkdownDesignFile(markdownText) {
376
379
  return {
377
380
  tables: parseMarkdownDesignTables(markdownText),
378
- relations: parseMarkdownRelations(markdownText),
381
+ relations: [],
379
382
  };
380
383
  }
381
384
 
@@ -475,6 +478,18 @@ function buildRetryArguments(safeArgs, sourceFile, childTableName) {
475
478
  retryArguments.childTableName = childTableName;
476
479
  }
477
480
 
481
+ if (safeArgs.mainField) {
482
+ retryArguments.mainField = safeArgs.mainField;
483
+ }
484
+
485
+ if (safeArgs.childField) {
486
+ retryArguments.childField = safeArgs.childField;
487
+ }
488
+
489
+ if (safeArgs.relationType) {
490
+ retryArguments.relationType = safeArgs.relationType;
491
+ }
492
+
478
493
  return retryArguments;
479
494
  }
480
495
 
@@ -522,26 +537,39 @@ function loadSourceDocument(safeArgs) {
522
537
  }
523
538
 
524
539
  function resolveMarkdownChildRelation(sourceDocument, safeArgs) {
525
- let candidates = sourceDocument.designDoc.relations.filter((relation) => relation.mainTableName === safeArgs.tableName);
526
- if (safeArgs.childTableName) {
527
- candidates = candidates.filter((relation) => relation.childTableName === safeArgs.childTableName);
528
- }
529
-
530
- if (!candidates.length) {
531
- const relationCandidates = getRelationCandidates(sourceDocument.designDoc, safeArgs.tableName);
532
- const message = relationCandidates.length
533
- ? 'Could not resolve child relation for main table ' + safeArgs.tableName + ' from Markdown design file.'
534
- : 'No child relation was found for main table ' + safeArgs.tableName + ' in Markdown design file.';
535
- throw createRelationResolutionError(message, safeArgs, sourceDocument.path, relationCandidates, safeArgs.childTableName, 'unresolved');
536
- }
537
-
538
- if (candidates.length > 1) {
539
- const relationCandidates = candidates.map(formatRelationCandidate);
540
- const message = 'Multiple child relations matched main table ' + safeArgs.tableName + '. Provide childTableName to disambiguate.';
541
- throw createRelationResolutionError(message, safeArgs, sourceDocument.path, relationCandidates, safeArgs.childTableName, 'ambiguous');
540
+ const missingFields = ['childTableName', 'mainField', 'childField'].filter((field) => !safeArgs[field]);
541
+ if (missingFields.length) {
542
+ const message =
543
+ 'master_child_jump requires externally provided relation fields. Missing: ' +
544
+ missingFields.join(', ') +
545
+ '.';
546
+ const error = new Error(message);
547
+ error.details = {
548
+ type: 'relation_input_required',
549
+ status: 'missing_required_relation_input',
550
+ tableName: safeArgs.tableName,
551
+ designFile: sourceDocument.path,
552
+ message,
553
+ requiredFields: ['childTableName', 'mainField', 'childField'],
554
+ correctionEntry: {
555
+ fields: ['childTableName', 'mainField', 'childField'],
556
+ example: {
557
+ ...buildRetryArguments(safeArgs, sourceDocument.path, safeArgs.childTableName || '<child_table_name>'),
558
+ mainField: safeArgs.mainField || '<main_field>',
559
+ childField: safeArgs.childField || '<child_field>',
560
+ },
561
+ },
562
+ };
563
+ throw error;
542
564
  }
543
565
 
544
- return candidates[0];
566
+ return {
567
+ mainTableName: safeArgs.tableName,
568
+ childTableName: safeArgs.childTableName,
569
+ mainField: safeArgs.mainField,
570
+ childField: safeArgs.childField,
571
+ relationType: safeArgs.relationType || '',
572
+ };
545
573
  }
546
574
 
547
575
  function getStylePreset(styleId) {
@@ -644,7 +672,7 @@ function renderFormField(field) {
644
672
  return [
645
673
  ' <el-col :span="12" class="mb20">',
646
674
  ` <el-form-item label="${label}" prop="${prop}">`,
647
- ` <el-select v-model="form.${prop}" placeholder="Please select ${label}" style="width: 100%">`,
675
+ ` <el-select v-model="form.${prop}" placeholder="请选择${label}" style="width: 100%">`,
648
676
  ` <el-option v-for="item in ${field.dictType}" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
649
677
  ' </el-select>',
650
678
  ' </el-form-item>',
@@ -658,7 +686,7 @@ function renderFormField(field) {
658
686
  return [
659
687
  ' <el-col :span="12" class="mb20">',
660
688
  ` <el-form-item label="${label}" prop="${prop}">`,
661
- ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} placeholder="Please input ${label}" style="width: 100%" />`,
689
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} placeholder="请输入${label}" style="width: 100%" />`,
662
690
  ' </el-form-item>',
663
691
  ' </el-col>',
664
692
  ].join('\n');
@@ -670,7 +698,7 @@ function renderFormField(field) {
670
698
  return [
671
699
  ' <el-col :span="12" class="mb20">',
672
700
  ` <el-form-item label="${label}" prop="${prop}">`,
673
- ` <el-date-picker type="${pickerType}" placeholder="Please select ${label}" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"></el-date-picker>`,
701
+ ` <el-date-picker type="${pickerType}" placeholder="请选择${label}" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"></el-date-picker>`,
674
702
  ' </el-form-item>',
675
703
  ' </el-col>',
676
704
  ].join('\n');
@@ -680,7 +708,7 @@ function renderFormField(field) {
680
708
  return [
681
709
  ' <el-col :span="24" class="mb20">',
682
710
  ` <el-form-item label="${label}" prop="${prop}">`,
683
- ` <el-input type="textarea" v-model="form.${prop}" placeholder="Please input ${label}" />`,
711
+ ` <el-input type="textarea" v-model="form.${prop}" placeholder="请输入${label}" />`,
684
712
  ' </el-form-item>',
685
713
  ' </el-col>',
686
714
  ].join('\n');
@@ -689,7 +717,7 @@ function renderFormField(field) {
689
717
  return [
690
718
  ' <el-col :span="12" class="mb20">',
691
719
  ` <el-form-item label="${label}" prop="${prop}">`,
692
- ` <el-input v-model="form.${prop}" placeholder="Please input ${label}" />`,
720
+ ` <el-input v-model="form.${prop}" placeholder="请输入${label}" />`,
693
721
  ' </el-form-item>',
694
722
  ' </el-col>',
695
723
  ].join('\n');
@@ -854,6 +882,9 @@ function ensureArguments(input) {
854
882
  tableName: String(input.tableName),
855
883
  style,
856
884
  childTableName: input.childTableName ? String(input.childTableName) : null,
885
+ mainField: input.mainField ? String(input.mainField) : null,
886
+ childField: input.childField ? String(input.childField) : null,
887
+ relationType: input.relationType ? String(input.relationType) : '',
857
888
  frontendPath: String(input.frontendPath),
858
889
  moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
859
890
  writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
@@ -875,7 +906,6 @@ function maybeWriteFiles(files, writeToDisk, overwrite) {
875
906
  }
876
907
 
877
908
  function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, note) {
878
- const relationCandidates = getRelationCandidates(loadSourceDocument(safeArgs).designDoc, safeArgs.tableName);
879
909
  return {
880
910
  mode: 'local-template',
881
911
  style: safeArgs.style,
@@ -901,17 +931,21 @@ function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, not
901
931
  : null,
902
932
  relationResolution: model.child
903
933
  ? {
904
- status: 'resolved',
934
+ status: 'provided',
905
935
  tableName: safeArgs.tableName,
906
936
  designFile: model.designFile,
907
- message: 'The relation was resolved from the design file. If you need another child table, use correctionEntry.retryArguments to resubmit.',
937
+ source: 'arguments',
938
+ message: 'The relation was provided by the caller. MCP skipped design-doc relation inference and generated files directly.',
908
939
  selected: formatRelationCandidate({
909
940
  childTableName: model.child.tableName,
910
941
  mainField: model.child.mainField.fieldName,
911
942
  childField: model.child.childField.fieldName,
912
943
  relationType: model.child.relationType || '',
913
944
  }),
914
- correctionEntry: buildRelationCorrectionEntry(safeArgs, model.designFile, relationCandidates, model.child.tableName),
945
+ correctionEntry: {
946
+ fields: ['childTableName', 'mainField', 'childField'],
947
+ example: buildRetryArguments(safeArgs, model.designFile, model.child.tableName),
948
+ },
915
949
  }
916
950
  : null,
917
951
  summary: {
@@ -962,7 +996,7 @@ async function onMessage(message) {
962
996
  }
963
997
 
964
998
  if (method === 'tools/list') {
965
- 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, relations are inferred from the Markdown design file.', inputSchema: TOOL_SCHEMA }] }));
999
+ 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 }] }));
966
1000
  return;
967
1001
  }
968
1002
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",