@bimatrix-aud-platform/aud_mcp_server 1.1.0 → 1.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/dist/generators/control-info.d.ts +17 -0
- package/dist/generators/control-info.js +89 -0
- package/dist/generators/fixer.js +166 -5
- package/dist/index.js +43 -1
- package/package.json +1 -1
- package/schemas/mtsd.interface.ts +2430 -0
- package/schemas/mtsd.schema.json +366 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ControlEntry {
|
|
2
|
+
Name: string;
|
|
3
|
+
Type: string;
|
|
4
|
+
InGroup: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface ControlInfoResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
controls: ControlEntry[];
|
|
9
|
+
imports: string[];
|
|
10
|
+
errors: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* MTSD 파일에서 컨트롤 Name → Type 매핑을 추출합니다.
|
|
14
|
+
* @param filePath MTSD 파일 경로
|
|
15
|
+
* @param filterName 특정 컨트롤만 조회 (생략 시 전체)
|
|
16
|
+
*/
|
|
17
|
+
export declare function getControlInfo(filePath: string, filterName?: string): ControlInfoResult;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
// MTSD Type → TypeScript import 매핑 (예외 케이스만 정의)
|
|
3
|
+
const TYPE_TO_IMPORT = {
|
|
4
|
+
MXGrid: { importName: "iGrid", importPath: "@AUD_CLIENT/control/iGrid" },
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* MTSD 파일에서 컨트롤 Name → Type 매핑을 추출합니다.
|
|
8
|
+
* @param filePath MTSD 파일 경로
|
|
9
|
+
* @param filterName 특정 컨트롤만 조회 (생략 시 전체)
|
|
10
|
+
*/
|
|
11
|
+
export function getControlInfo(filePath, filterName) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
// 1. 파일 읽기
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = readFileSync(filePath, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
controls: [],
|
|
22
|
+
imports: [],
|
|
23
|
+
errors: [
|
|
24
|
+
`파일을 읽을 수 없습니다: ${e instanceof Error ? e.message : String(e)}`,
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
let doc;
|
|
29
|
+
try {
|
|
30
|
+
doc = JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
controls: [],
|
|
36
|
+
imports: [],
|
|
37
|
+
errors: ["JSON 파싱 오류: 유효한 JSON 형식이 아닙니다."],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// 2. Elements 트리 순회하여 컨트롤 수집
|
|
41
|
+
const controls = [];
|
|
42
|
+
const typeSet = new Set();
|
|
43
|
+
function walkElements(elements, parentGroup) {
|
|
44
|
+
for (const el of elements) {
|
|
45
|
+
const name = el.Name || "";
|
|
46
|
+
const type = el.Type || "";
|
|
47
|
+
const inGroup = el.InGroup || parentGroup;
|
|
48
|
+
if (name && type) {
|
|
49
|
+
// filterName이 지정된 경우 해당 이름만 수집
|
|
50
|
+
if (!filterName || name === filterName) {
|
|
51
|
+
controls.push({ Name: name, Type: type, InGroup: inGroup });
|
|
52
|
+
typeSet.add(type);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ChildElements 재귀 순회
|
|
56
|
+
if (el.ChildElements && Array.isArray(el.ChildElements)) {
|
|
57
|
+
walkElements(el.ChildElements, type === "Group" ? name : parentGroup);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Forms > Elements 순회
|
|
62
|
+
const forms = doc.Forms || [];
|
|
63
|
+
for (const form of forms) {
|
|
64
|
+
const elements = form.Elements || [];
|
|
65
|
+
walkElements(elements, null);
|
|
66
|
+
}
|
|
67
|
+
// 최상위 Elements (Forms 없는 구조)
|
|
68
|
+
if (doc.Elements && Array.isArray(doc.Elements)) {
|
|
69
|
+
walkElements(doc.Elements, null);
|
|
70
|
+
}
|
|
71
|
+
// 3. import 문 생성
|
|
72
|
+
const imports = [];
|
|
73
|
+
const sortedTypes = Array.from(typeSet).sort();
|
|
74
|
+
for (const type of sortedTypes) {
|
|
75
|
+
const mapping = TYPE_TO_IMPORT[type];
|
|
76
|
+
if (mapping) {
|
|
77
|
+
imports.push(`import { ${mapping.importName} } from "${mapping.importPath}";`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
imports.push(`import { ${type} } from "@AUD_CLIENT/control/${type}";`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
controls,
|
|
86
|
+
imports,
|
|
87
|
+
errors,
|
|
88
|
+
};
|
|
89
|
+
}
|
package/dist/generators/fixer.js
CHANGED
|
@@ -43,13 +43,15 @@ export function fixMtsd(filePath) {
|
|
|
43
43
|
// 3. 보정 규칙 적용
|
|
44
44
|
// Rule 1: DataSource Name→Id 참조 보정
|
|
45
45
|
fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes);
|
|
46
|
-
// Rule 2: DataSource
|
|
46
|
+
// Rule 2: OlapGrid DataSource 기반 Fields 자동 생성
|
|
47
|
+
fixOlapGridFields(doc, datas, fixes);
|
|
48
|
+
// Rule 3: DataSource Params에 Value 누락 보정
|
|
47
49
|
//fixParamsMissingValue(datas, fixes);
|
|
48
|
-
// Rule
|
|
50
|
+
// Rule 4: DataSource Columns에 Type 누락 보정
|
|
49
51
|
//fixColumnsMissingType(datas, fixes);
|
|
50
|
-
// Rule
|
|
52
|
+
// Rule 5: Element Position.Docking 필수 속성 보정
|
|
51
53
|
//fixDockingDefaults(doc, fixes);
|
|
52
|
-
// Rule
|
|
54
|
+
// Rule 6: Element Style.Border 필수 속성 보정
|
|
53
55
|
//fixBorderDefaults(doc, fixes);
|
|
54
56
|
// 4. 파일 저장
|
|
55
57
|
if (fixes.length > 0) {
|
|
@@ -89,7 +91,166 @@ function fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes) {
|
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
// ---- Rule 2:
|
|
94
|
+
// ---- Rule 2: OlapGrid DataSource 기반 Fields 자동 생성 ----
|
|
95
|
+
// DataSource Column.Type이 "Numeric"이면 숫자형으로 판단
|
|
96
|
+
const NUMERIC_COLUMN_TYPES = new Set([
|
|
97
|
+
"Numeric",
|
|
98
|
+
// SQL 타입 호환
|
|
99
|
+
"INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT",
|
|
100
|
+
"NUMERIC", "DECIMAL", "FLOAT", "DOUBLE", "REAL", "NUMBER", "MONEY",
|
|
101
|
+
]);
|
|
102
|
+
function isNumericColumnType(type) {
|
|
103
|
+
return NUMERIC_COLUMN_TYPES.has(type) || NUMERIC_COLUMN_TYPES.has(type.toUpperCase());
|
|
104
|
+
}
|
|
105
|
+
/** OlapField 기본값 생성 (OlapField constructor + Serialize 기준) */
|
|
106
|
+
function createDefaultOlapField(key, caption) {
|
|
107
|
+
return {
|
|
108
|
+
Key: key,
|
|
109
|
+
Caption: caption,
|
|
110
|
+
ToolTipField: "",
|
|
111
|
+
ToolTipText: "",
|
|
112
|
+
Category: 1, // enCategory.Dimension
|
|
113
|
+
Area: 0, // enArea.Hidden
|
|
114
|
+
SummaryType: 0, // enSummaryType.None
|
|
115
|
+
TotalSummaryType: 0,
|
|
116
|
+
SummaryVariation: 0,
|
|
117
|
+
GroupByType: 0, // enGroupByType.Auto
|
|
118
|
+
Format: "",
|
|
119
|
+
Formula: "",
|
|
120
|
+
Formula2: "",
|
|
121
|
+
RefFormula: "",
|
|
122
|
+
Width: 100,
|
|
123
|
+
Unit: 1,
|
|
124
|
+
CreateType: 0, // enOlapFieldCreateType.Default
|
|
125
|
+
SortType: 0, // enSortType.None
|
|
126
|
+
MeasureSortField: "",
|
|
127
|
+
SortBaseField: "",
|
|
128
|
+
MoveAble: true,
|
|
129
|
+
SortAble: true,
|
|
130
|
+
FilterAble: true,
|
|
131
|
+
AllowFilter: true,
|
|
132
|
+
AllowRow: true,
|
|
133
|
+
AllowColumn: true,
|
|
134
|
+
AllowData: true,
|
|
135
|
+
KeyType: 0, // enKeyType.None
|
|
136
|
+
DataType: 0, // enDataType.Numeric
|
|
137
|
+
SaveMode: 0, // enSaveMode.All
|
|
138
|
+
LanguageCode: "",
|
|
139
|
+
UseChartSource: true,
|
|
140
|
+
VisibleSubTotal: true,
|
|
141
|
+
SummaryBaseFieldKey: "",
|
|
142
|
+
TextAlignment: 1, // enHorizonAlign.Right
|
|
143
|
+
HeaderAlignment: 0, // enHorizonAlign.Left
|
|
144
|
+
InDimensions: "",
|
|
145
|
+
Visible: true,
|
|
146
|
+
Expanded: true,
|
|
147
|
+
MetaItemCode: "",
|
|
148
|
+
MetaItemName: "",
|
|
149
|
+
MetaRollupType: 0,
|
|
150
|
+
MetaCalculatorField: false,
|
|
151
|
+
MetaSummaryTypeIsDistinct: false,
|
|
152
|
+
FilterInfo: {
|
|
153
|
+
FilterType: 0, // enOlapFilterType.In
|
|
154
|
+
FilterKind: 0, // enOlapFilterKind.Dimension
|
|
155
|
+
HasMeasureFilter: false,
|
|
156
|
+
MeasureFilterTypeA: 0, // enOlapMeasureFilterType.Equals
|
|
157
|
+
MeasureFilterTypeB: 0,
|
|
158
|
+
MeasureAndOrOperator: 2, // enAndOrOperator.And
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function fixOlapGridFields(doc, datas, fixes) {
|
|
163
|
+
// DataSource Id → DataSource 객체 매핑
|
|
164
|
+
const dsById = new Map();
|
|
165
|
+
for (const ds of datas) {
|
|
166
|
+
if (ds.Id)
|
|
167
|
+
dsById.set(ds.Id, ds);
|
|
168
|
+
}
|
|
169
|
+
const forms = doc.Forms || [];
|
|
170
|
+
for (const form of forms) {
|
|
171
|
+
const elements = form.Elements || [];
|
|
172
|
+
walkElements(elements, (el, path) => {
|
|
173
|
+
// OlapGrid 타입만 대상
|
|
174
|
+
if (el.Type !== "OlapGrid")
|
|
175
|
+
return;
|
|
176
|
+
// DataSource가 설정되어 있어야 함
|
|
177
|
+
if (!el.DataSource)
|
|
178
|
+
return;
|
|
179
|
+
// iOLAPView.Fields가 이미 존재하고 비어있지 않으면 스킵
|
|
180
|
+
const fields = el.iOLAPView?.Fields;
|
|
181
|
+
if (fields && Array.isArray(fields) && fields.length > 0)
|
|
182
|
+
return;
|
|
183
|
+
// 해당 DataSource 찾기
|
|
184
|
+
const ds = dsById.get(el.DataSource);
|
|
185
|
+
if (!ds)
|
|
186
|
+
return;
|
|
187
|
+
const columns = ds.Columns || [];
|
|
188
|
+
if (columns.length === 0)
|
|
189
|
+
return;
|
|
190
|
+
// Fields 생성
|
|
191
|
+
const newFields = [];
|
|
192
|
+
for (const col of columns) {
|
|
193
|
+
const colName = col.Name || "";
|
|
194
|
+
const caption = col.Caption || colName;
|
|
195
|
+
const field = createDefaultOlapField(colName, caption);
|
|
196
|
+
if (isNumericColumnType(col.Type || "")) {
|
|
197
|
+
// Measure 필드
|
|
198
|
+
field.Category = 2; // enCategory.Measure
|
|
199
|
+
field.Format = "{0:N0}";
|
|
200
|
+
field.DataType = 0; // enDataType.Numeric
|
|
201
|
+
field.SummaryType = 1; // enSummaryType.Sum
|
|
202
|
+
field.TextAlignment = 1; // enHorizonAlign.Right
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Dimension 필드
|
|
206
|
+
field.Category = 1; // enCategory.Dimension
|
|
207
|
+
field.DataType = 1; // enDataType.String
|
|
208
|
+
field.SortType = 1; // enSortType.Asc
|
|
209
|
+
field.SummaryType = 5; // enSummaryType.Count
|
|
210
|
+
field.TextAlignment = 1; // enHorizonAlign.Right
|
|
211
|
+
}
|
|
212
|
+
newFields.push(field);
|
|
213
|
+
}
|
|
214
|
+
// Area 자동 배치 (setDataSource 로직 준용)
|
|
215
|
+
let rowCnt = 0;
|
|
216
|
+
let colCnt = 0;
|
|
217
|
+
let dataCnt = 0;
|
|
218
|
+
for (const field of newFields) {
|
|
219
|
+
if (field.DataType === 0) {
|
|
220
|
+
// Numeric → Data 영역 (최대 3개), 나머지 Filter
|
|
221
|
+
if (dataCnt < 3) {
|
|
222
|
+
field.Area = 4; // enArea.Data
|
|
223
|
+
dataCnt++;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
field.Area = 3; // enArea.Filter
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// String → Row(최대 2개) → Column(최대 2개) → Filter
|
|
231
|
+
if (rowCnt < 2) {
|
|
232
|
+
field.Area = 1; // enArea.Row
|
|
233
|
+
rowCnt++;
|
|
234
|
+
}
|
|
235
|
+
else if (colCnt < 2) {
|
|
236
|
+
field.Area = 2; // enArea.Column
|
|
237
|
+
colCnt++;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
field.Area = 3; // enArea.Filter
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// iOLAPView 구조 생성/보정
|
|
245
|
+
if (!el.iOLAPView) {
|
|
246
|
+
el.iOLAPView = {};
|
|
247
|
+
}
|
|
248
|
+
el.iOLAPView.Fields = newFields;
|
|
249
|
+
fixes.push(`[Rule2] ${path}: DataSource "${ds.Name || ds.Id}" 컬럼 기준 OlapGrid Fields ${newFields.length}개 자동 생성`);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ---- Rule 3: Params Value 누락 보정 ----
|
|
93
254
|
// function fixParamsMissingValue(datas: any[], fixes: string[]) {
|
|
94
255
|
// for (const ds of datas) {
|
|
95
256
|
// const params: any[] = ds.Params || [];
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { generateElement } from "./generators/element.js";
|
|
|
11
11
|
import { generateGridColumns } from "./generators/grid-column.js";
|
|
12
12
|
import { generateDataSource } from "./generators/datasource.js";
|
|
13
13
|
import { fixMtsd } from "./generators/fixer.js";
|
|
14
|
+
import { getControlInfo } from "./generators/control-info.js";
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = dirname(__filename);
|
|
16
17
|
// Load MTSD schema
|
|
@@ -409,6 +410,24 @@ const tools = [
|
|
|
409
410
|
required: ["path"],
|
|
410
411
|
},
|
|
411
412
|
},
|
|
413
|
+
{
|
|
414
|
+
name: "get_control_info",
|
|
415
|
+
description: "MTSD 파일에서 컨트롤의 Name과 Type 매핑 정보를 추출합니다. TypeScript 스크립트 작성 시 컨트롤 변수의 타입을 확인하거나, import 문을 생성할 때 사용합니다.",
|
|
416
|
+
inputSchema: {
|
|
417
|
+
type: "object",
|
|
418
|
+
properties: {
|
|
419
|
+
path: {
|
|
420
|
+
type: "string",
|
|
421
|
+
description: "MTSD 파일의 전체 경로 (예: C:/reports/sample/sample.mtsd)",
|
|
422
|
+
},
|
|
423
|
+
name: {
|
|
424
|
+
type: "string",
|
|
425
|
+
description: "특정 컨트롤 이름 (생략 시 전체 컨트롤 목록 반환)",
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
required: ["path"],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
412
431
|
];
|
|
413
432
|
// Create MCP server
|
|
414
433
|
const server = new Server({
|
|
@@ -714,6 +733,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
714
733
|
],
|
|
715
734
|
};
|
|
716
735
|
}
|
|
736
|
+
case "get_control_info": {
|
|
737
|
+
const filePath = args?.path;
|
|
738
|
+
if (!filePath) {
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: "text",
|
|
743
|
+
text: JSON.stringify({ error: "path 파라미터가 필요합니다." }),
|
|
744
|
+
},
|
|
745
|
+
],
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const filterName = args?.name;
|
|
749
|
+
const result = getControlInfo(filePath, filterName);
|
|
750
|
+
return {
|
|
751
|
+
content: [
|
|
752
|
+
{
|
|
753
|
+
type: "text",
|
|
754
|
+
text: JSON.stringify(result, null, 2),
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
717
759
|
default:
|
|
718
760
|
return {
|
|
719
761
|
content: [
|
|
@@ -742,7 +784,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
742
784
|
async function main() {
|
|
743
785
|
const transport = new StdioServerTransport();
|
|
744
786
|
await server.connect(transport);
|
|
745
|
-
console.error("MCP
|
|
787
|
+
console.error("i-AUD MCP server started");
|
|
746
788
|
}
|
|
747
789
|
main().catch((error) => {
|
|
748
790
|
console.error("Server error:", error);
|