@bimatrix-aud-platform/aud_mcp_server 1.0.0 → 1.1.1
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/fixer.d.ts +10 -0
- package/dist/generators/fixer.js +346 -0
- package/dist/index.js +38 -1
- package/package.json +1 -1
- package/schemas/mtsd.interface.ts +2430 -0
- package/schemas/mtsd.schema.json +366 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
+
/**
|
|
3
|
+
* MTSD 파일을 읽고, 자동 보정 규칙을 적용한 뒤, 파일을 덮어씁니다.
|
|
4
|
+
*/
|
|
5
|
+
export function fixMtsd(filePath) {
|
|
6
|
+
const fixes = [];
|
|
7
|
+
const errors = [];
|
|
8
|
+
// 1. 파일 읽기
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = readFileSync(filePath, "utf-8");
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return {
|
|
15
|
+
fixed: false,
|
|
16
|
+
fixCount: 0,
|
|
17
|
+
fixes: [],
|
|
18
|
+
errors: [`파일을 읽을 수 없습니다: ${e instanceof Error ? e.message : String(e)}`],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
let doc;
|
|
22
|
+
try {
|
|
23
|
+
doc = JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return {
|
|
27
|
+
fixed: false,
|
|
28
|
+
fixCount: 0,
|
|
29
|
+
fixes: [],
|
|
30
|
+
errors: ["JSON 파싱 오류: 유효한 JSON 형식이 아닙니다."],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// 2. DataSource Name→Id 매핑 구성
|
|
34
|
+
const dsNameToId = new Map();
|
|
35
|
+
const dsIdSet = new Set();
|
|
36
|
+
const datas = doc.DataSources?.Datas || [];
|
|
37
|
+
for (const ds of datas) {
|
|
38
|
+
if (ds.Id && ds.Name) {
|
|
39
|
+
dsNameToId.set(ds.Name, ds.Id);
|
|
40
|
+
dsIdSet.add(ds.Id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 3. 보정 규칙 적용
|
|
44
|
+
// Rule 1: DataSource Name→Id 참조 보정
|
|
45
|
+
fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes);
|
|
46
|
+
// Rule 2: OlapGrid DataSource 기반 Fields 자동 생성
|
|
47
|
+
fixOlapGridFields(doc, datas, fixes);
|
|
48
|
+
// Rule 3: DataSource Params에 Value 누락 보정
|
|
49
|
+
//fixParamsMissingValue(datas, fixes);
|
|
50
|
+
// Rule 4: DataSource Columns에 Type 누락 보정
|
|
51
|
+
//fixColumnsMissingType(datas, fixes);
|
|
52
|
+
// Rule 5: Element Position.Docking 필수 속성 보정
|
|
53
|
+
//fixDockingDefaults(doc, fixes);
|
|
54
|
+
// Rule 6: Element Style.Border 필수 속성 보정
|
|
55
|
+
//fixBorderDefaults(doc, fixes);
|
|
56
|
+
// 4. 파일 저장
|
|
57
|
+
if (fixes.length > 0) {
|
|
58
|
+
try {
|
|
59
|
+
writeFileSync(filePath, JSON.stringify(doc, null, 2), "utf-8");
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
errors.push(`파일 저장 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
63
|
+
return { fixed: false, fixCount: fixes.length, fixes, errors };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
fixed: fixes.length > 0,
|
|
68
|
+
fixCount: fixes.length,
|
|
69
|
+
fixes,
|
|
70
|
+
errors,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ---- Rule 1: DataSource 참조 보정 ----
|
|
74
|
+
function fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes) {
|
|
75
|
+
const forms = doc.Forms || [];
|
|
76
|
+
for (const form of forms) {
|
|
77
|
+
const elements = form.Elements || [];
|
|
78
|
+
walkElements(elements, (el, path) => {
|
|
79
|
+
if (el.DataSource && typeof el.DataSource === "string") {
|
|
80
|
+
const val = el.DataSource;
|
|
81
|
+
// 이미 올바른 Id인 경우 스킵
|
|
82
|
+
if (dsIdSet.has(val))
|
|
83
|
+
return;
|
|
84
|
+
// Name으로 참조한 경우 Id로 보정
|
|
85
|
+
const correctId = dsNameToId.get(val);
|
|
86
|
+
if (correctId) {
|
|
87
|
+
el.DataSource = correctId;
|
|
88
|
+
fixes.push(`[Rule1] ${path}.DataSource: Name "${val}" → Id "${correctId}" 보정`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
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 누락 보정 ----
|
|
254
|
+
// function fixParamsMissingValue(datas: any[], fixes: string[]) {
|
|
255
|
+
// for (const ds of datas) {
|
|
256
|
+
// const params: any[] = ds.Params || [];
|
|
257
|
+
// for (const param of params) {
|
|
258
|
+
// if (!("Value" in param)) {
|
|
259
|
+
// param.Value = "";
|
|
260
|
+
// fixes.push(
|
|
261
|
+
// `[Rule2] DataSource "${ds.Name}".Params "${param.Name}": Value 속성 추가 ("")`
|
|
262
|
+
// );
|
|
263
|
+
// }
|
|
264
|
+
// }
|
|
265
|
+
// }
|
|
266
|
+
// }
|
|
267
|
+
// ---- Rule 3: Columns Type 누락 보정 ----
|
|
268
|
+
// function fixColumnsMissingType(datas: any[], fixes: string[]) {
|
|
269
|
+
// for (const ds of datas) {
|
|
270
|
+
// const columns: any[] = ds.Columns || [];
|
|
271
|
+
// for (const col of columns) {
|
|
272
|
+
// if (!("Type" in col)) {
|
|
273
|
+
// col.Type = "string";
|
|
274
|
+
// fixes.push(
|
|
275
|
+
// `[Rule3] DataSource "${ds.Name}".Columns "${col.Name}": Type 속성 추가 ("string")`
|
|
276
|
+
// );
|
|
277
|
+
// }
|
|
278
|
+
// }
|
|
279
|
+
// }
|
|
280
|
+
// }
|
|
281
|
+
// ---- Rule 4: Docking 필수 속성 보정 ----
|
|
282
|
+
const DOCKING_DEFAULTS = {
|
|
283
|
+
Left: false,
|
|
284
|
+
Right: false,
|
|
285
|
+
Top: false,
|
|
286
|
+
Bottom: false,
|
|
287
|
+
Margin: "0,0,0,0",
|
|
288
|
+
HoldSize: false,
|
|
289
|
+
MinWidth: 0,
|
|
290
|
+
MinHeight: 0,
|
|
291
|
+
};
|
|
292
|
+
// function fixDockingDefaults(doc: any, fixes: string[]) {
|
|
293
|
+
// const forms: any[] = doc.Forms || [];
|
|
294
|
+
// for (const form of forms) {
|
|
295
|
+
// const elements: any[] = form.Elements || [];
|
|
296
|
+
// walkElements(elements, (el, path) => {
|
|
297
|
+
// const docking = el.Position?.Docking;
|
|
298
|
+
// if (!docking) return;
|
|
299
|
+
// for (const [key, defaultVal] of Object.entries(DOCKING_DEFAULTS)) {
|
|
300
|
+
// if (!(key in docking)) {
|
|
301
|
+
// docking[key] = defaultVal;
|
|
302
|
+
// fixes.push(
|
|
303
|
+
// `[Rule4] ${path}.Position.Docking.${key}: 누락 속성 추가 (${JSON.stringify(defaultVal)})`
|
|
304
|
+
// );
|
|
305
|
+
// }
|
|
306
|
+
// }
|
|
307
|
+
// });
|
|
308
|
+
// }
|
|
309
|
+
// }
|
|
310
|
+
// ---- Rule 5: Border 필수 속성 보정 ----
|
|
311
|
+
const BORDER_DEFAULTS = {
|
|
312
|
+
CornerRadius: "0,0,0,0",
|
|
313
|
+
LineType: "none",
|
|
314
|
+
Thickness: "0,0,0,0",
|
|
315
|
+
};
|
|
316
|
+
// function fixBorderDefaults(doc: any, fixes: string[]) {
|
|
317
|
+
// const forms: any[] = doc.Forms || [];
|
|
318
|
+
// for (const form of forms) {
|
|
319
|
+
// const elements: any[] = form.Elements || [];
|
|
320
|
+
// walkElements(elements, (el, path) => {
|
|
321
|
+
// const border = el.Style?.Border;
|
|
322
|
+
// if (!border) return;
|
|
323
|
+
// for (const [key, defaultVal] of Object.entries(BORDER_DEFAULTS)) {
|
|
324
|
+
// if (!(key in border)) {
|
|
325
|
+
// border[key] = defaultVal;
|
|
326
|
+
// fixes.push(
|
|
327
|
+
// `[Rule5] ${path}.Style.Border.${key}: 누락 속성 추가 ("${defaultVal}")`
|
|
328
|
+
// );
|
|
329
|
+
// }
|
|
330
|
+
// }
|
|
331
|
+
// });
|
|
332
|
+
// }
|
|
333
|
+
// }
|
|
334
|
+
// ---- 유틸: Element 트리 순회 ----
|
|
335
|
+
function walkElements(elements, callback, parentPath = "") {
|
|
336
|
+
for (const el of elements) {
|
|
337
|
+
const path = parentPath
|
|
338
|
+
? `${parentPath} > ${el.Type || "?"}(${el.Name || el.Id || "?"})`
|
|
339
|
+
: `${el.Type || "?"}(${el.Name || el.Id || "?"})`;
|
|
340
|
+
callback(el, path);
|
|
341
|
+
// Group의 ChildElements 재귀 순회
|
|
342
|
+
if (el.ChildElements && Array.isArray(el.ChildElements)) {
|
|
343
|
+
walkElements(el.ChildElements, callback, path);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { dirname, join } from "path";
|
|
|
10
10
|
import { generateElement } from "./generators/element.js";
|
|
11
11
|
import { generateGridColumns } from "./generators/grid-column.js";
|
|
12
12
|
import { generateDataSource } from "./generators/datasource.js";
|
|
13
|
+
import { fixMtsd } from "./generators/fixer.js";
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = dirname(__filename);
|
|
15
16
|
// Load MTSD schema
|
|
@@ -394,6 +395,20 @@ const tools = [
|
|
|
394
395
|
additionalProperties: true,
|
|
395
396
|
},
|
|
396
397
|
},
|
|
398
|
+
{
|
|
399
|
+
name: "fix_mtsd",
|
|
400
|
+
description: "MTSD 파일을 읽어 자동 보정 규칙을 적용하고 파일을 덮어씁니다. DataSource Name→Id 참조 보정, Params Value 누락 보정, Columns Type 누락 보정, Docking/Border 필수 속성 보정을 수행합니다.",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
path: {
|
|
405
|
+
type: "string",
|
|
406
|
+
description: "MTSD 파일의 전체 경로 (예: C:/reports/sample/sample.mtsd)",
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
required: ["path"],
|
|
410
|
+
},
|
|
411
|
+
},
|
|
397
412
|
];
|
|
398
413
|
// Create MCP server
|
|
399
414
|
const server = new Server({
|
|
@@ -677,6 +692,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
677
692
|
],
|
|
678
693
|
};
|
|
679
694
|
}
|
|
695
|
+
case "fix_mtsd": {
|
|
696
|
+
const filePath = args?.path;
|
|
697
|
+
if (!filePath) {
|
|
698
|
+
return {
|
|
699
|
+
content: [
|
|
700
|
+
{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: JSON.stringify({ error: "path 파라미터가 필요합니다." }),
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const result = fixMtsd(filePath);
|
|
708
|
+
return {
|
|
709
|
+
content: [
|
|
710
|
+
{
|
|
711
|
+
type: "text",
|
|
712
|
+
text: JSON.stringify(result, null, 2),
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
};
|
|
716
|
+
}
|
|
680
717
|
default:
|
|
681
718
|
return {
|
|
682
719
|
content: [
|
|
@@ -705,7 +742,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
705
742
|
async function main() {
|
|
706
743
|
const transport = new StdioServerTransport();
|
|
707
744
|
await server.connect(transport);
|
|
708
|
-
console.error("MCP
|
|
745
|
+
console.error("i-AUD MCP server started");
|
|
709
746
|
}
|
|
710
747
|
main().catch((error) => {
|
|
711
748
|
console.error("Server error:", error);
|