@ninebone/mcp 0.1.26

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 (41) hide show
  1. package/README.kr.md +71 -0
  2. package/README.md +72 -0
  3. package/bak/generate-query.md +28 -0
  4. package/bak/generator-source-mapper.md +123 -0
  5. package/bak/mcp-server.js +498 -0
  6. package/bak/nomenu-navigator.md +16 -0
  7. package/bak/system-brain.md +62 -0
  8. package/bak/table-filter.md +21 -0
  9. package/package.json +33 -0
  10. package/prompts/menu/generate-menu.md +89 -0
  11. package/prompts/source/generate-source-controller.md +120 -0
  12. package/prompts/source/generate-source-mapper.mysql.md +97 -0
  13. package/prompts/source/generate-source-mapper.oracle.md +90 -0
  14. package/prompts/source/generate-source-mapper.postgre.md +89 -0
  15. package/prompts/source/generate-source-service.md +116 -0
  16. package/prompts/source/generate-source-ui-react.md +174 -0
  17. package/prompts/system/generate-source-brain.md +57 -0
  18. package/prompts/system/modify-source-brain.md +50 -0
  19. package/prompts/system/system-brain.md +88 -0
  20. package/src/ai/AIProcessor.js +85 -0
  21. package/src/ai/AIService.js +24 -0
  22. package/src/core/init.js +116 -0
  23. package/src/database/config/database.js +42 -0
  24. package/src/database/core/DatabaseManager.js +115 -0
  25. package/src/database/core/Dialects.js +66 -0
  26. package/src/database/core/PoolManager.js +92 -0
  27. package/src/drivers/mysql.js +0 -0
  28. package/src/index.js +38 -0
  29. package/src/mcp/loaders/promptLoader.js +62 -0
  30. package/src/mcp/mcp-server.js +129 -0
  31. package/src/mcp/tools/generateSourceBrainTool.js +179 -0
  32. package/src/mcp/tools/modifySourceBrainTool.js +283 -0
  33. package/src/mcp/tools/staticTools.js +29 -0
  34. package/src/mcp/tools/systemBrain.js +182 -0
  35. package/src/mcp/utils/mcp-utils.js +30 -0
  36. package/src/mcp-handler.js +131 -0
  37. package/src/services/NoMenuService.js +43 -0
  38. package/src/services/QueryService.js +26 -0
  39. package/src/services/SourceService.js +32 -0
  40. package/src/utils/CustomWsTransport.js +52 -0
  41. package/src/utils/asyncHandler.js +13 -0
@@ -0,0 +1,283 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * ๐Ÿ’ก [์ดˆ์ •๋ฐ€ ๋ฌธ๋งฅ ๋งค์นญ] ๊ณต๋ฐฑ, ๋“ค์—ฌ์“ฐ๊ธฐ, ๋”ฐ์˜ดํ‘œ ๊ฒฉ์ฐจ๋ฅผ ์™„์ „ํžˆ ์ดˆ์›”ํ•œ ์ตœ์ข… ์ง„ํ™”ํ˜• ๋จธ์ง€ ์—”์ง„
5
+ */
6
+ const applyPureDiff = (asisSource, sourceChanges) => {
7
+ if (!asisSource) return "";
8
+ if (!sourceChanges || sourceChanges.length === 0) return asisSource;
9
+
10
+ let finalSource = asisSource.replace(/\r/g, "");
11
+
12
+ for (const change of sourceChanges) {
13
+ let searchText = change.search_block || change.search_string || "";
14
+ let replaceText = change.replace_block || change.replace_string || "";
15
+
16
+ if (!searchText) continue;
17
+
18
+ // ๐ŸŽฏ 1. [๋งˆํฌ์—… ์ฒญ์†Œ]: ๋ผ์ธ ๋ฒˆํ˜ธ ํ”์  ์ง€์šฐ๊ธฐ
19
+ searchText = searchText.split("\n").map(line => line.replace(/^\[LN\d+\]\s*/, "")).join("\n");
20
+ replaceText = replaceText.split("\n").map(line => line.replace(/^\[LN\d+\]\s*/, "")).join("\n");
21
+
22
+ // ๐ŸŽฏ 2. [๊ณต๋ฐฑ/๋”ฐ์˜ดํ‘œ ์ •๊ทœํ™” ํ—ฌํผ]: ๋ชจ๋“  ๊ณต๋ฐฑ๊ณผ ๋”ฐ์˜ดํ‘œ๋ฅผ ์••์ถ•ํ•˜์—ฌ ์ˆœ์ˆ˜ '์•Œ๋งน์ด'๋งŒ ๋น„๊ต
23
+ const normalizeLine = (line) => {
24
+ return (line || "").replace(/['"]/g, '').replace(/\s+/g, '').trim();
25
+ };
26
+
27
+ // ๊ฒ€์ƒ‰ ๋Œ€์ƒ ๋ธ”๋ก์„ ๋ผ์ธ๋ณ„ ์•Œ๋งน์ด ๋ฐฐ์—ด๋กœ ๋ถ„๋ฆฌ
28
+ const searchLinesNormalized = searchText.split("\n")
29
+ .map(l => normalizeLine(l))
30
+ .filter(Boolean);
31
+
32
+ if (searchLinesNormalized.length === 0) continue;
33
+
34
+ // ์›๋ณธ ์†Œ์Šค์ฝ”๋“œ ๋ผ์ธ ๋ฐฐ์—ด
35
+ const sourceLines = finalSource.split("\n");
36
+ let startIdx = -1;
37
+ let matchedLineCount = searchLinesNormalized.length;
38
+
39
+ // ๐ŸŽฏ 3. [์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ ์ •๋ฐ€ ํƒ์ƒ‰]: ์›๋ณธ ์†Œ์Šค๋ฅผ ๋Œ๋ฉฐ ์•Œ๋งน์ด ์ˆœ์„œ๊ฐ€ ์™„๋ฒฝํžˆ ์ผ์น˜ํ•˜๋Š” ๊ตฌ๊ฐ„ ํฌ์ฐฉ
40
+ for (let i = 0; i < sourceLines.length; i++) {
41
+ let matchCount = 0;
42
+ let sourceOffset = 0;
43
+
44
+ for (let j = 0; j < searchLinesNormalized.length; j++) {
45
+ // ์›๋ณธ์˜ ๋นˆ ์ค„์€ ๊ฑด๋„ˆ๋›ฐ๋ฉฐ ๋น„๊ต ์œ ์—ฐ์„ฑ ํ™•๋ณด
46
+ while (sourceLines[i + sourceOffset] !== undefined && normalizeLine(sourceLines[i + sourceOffset]) === "") {
47
+ sourceOffset++;
48
+ }
49
+
50
+ if (sourceLines[i + sourceOffset] !== undefined &&
51
+ normalizeLine(sourceLines[i + sourceOffset]) === searchLinesNormalized[j]) {
52
+ matchCount++;
53
+ sourceOffset++;
54
+ } else {
55
+ break;
56
+ }
57
+ }
58
+
59
+ if (matchCount === searchLinesNormalized.length) {
60
+ startIdx = i;
61
+ matchedLineCount = sourceOffset; // ์‹ค์ œ ์›๋ณธ์—์„œ ๋จน์–ด์น˜์šธ ์ค„ ์ˆ˜ (๊ณต๋ฐฑ ํฌํ•จ)
62
+ break;
63
+ }
64
+ }
65
+
66
+ // ๐ŸŽฏ 4. [์น˜ํ™˜ ์‹คํ–‰]: ๋งค์นญ ๊ตฌ๊ฐ„์„ ์ฐพ์•˜๋‹ค๋ฉด ํ•ด๋‹น ์˜์—ญ์„ ๊น”๋”ํ•˜๊ฒŒ ์Šค์™‘
67
+ if (startIdx !== -1) {
68
+ console.log(`๐ŸŽฏ [์ดˆ์ •๋ฐ€ ๋ฌธ๋งฅ ๋งค์นญ ์„ฑ๊ณต] ์›๋ณธ ${startIdx + 1}ํ–‰๋ถ€ํ„ฐ ${matchedLineCount}๊ฐœ ํ–‰ ์น˜ํ™˜ ์™„๋ฃŒ`);
69
+ const replaceLines = replaceText ? replaceText.split("\n") : [];
70
+ sourceLines.splice(startIdx, matchedLineCount, ...replaceLines);
71
+ finalSource = sourceLines.join("\n");
72
+ } else {
73
+ console.warn("[๋งค์นญ ์ตœ์ข… ์‹คํŒจ] ์•Œ๋งน์ด ๋น„๊ต ๋ถ„์„์œผ๋กœ๋„ ์›๋ณธ์—์„œ ๋Œ€์ƒ์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", searchLinesNormalized);
74
+ }
75
+ }
76
+
77
+ return finalSource;
78
+ };
79
+
80
+ /**
81
+ * ๐Ÿ’ก [ํ™•์žฅ์ž ๊ธฐ๋ฐ˜ ์†Œ์Šค ์ถ”์ถœ ๋ฐ ๊ฒฝ๋กœ ๋ฐ˜ํ™˜ ํ—ฌํผ]
82
+ */
83
+ const extractFileByExtension = (fileArray, allowedExtensions) => {
84
+ if (!Array.isArray(fileArray) || fileArray.length === 0) return { contents: "", full_path: "" };
85
+
86
+ const matchedFile = fileArray.find(file => {
87
+ const fullPath = (file?.full_path || "").toLowerCase();
88
+ return allowedExtensions.some(ext => fullPath.endsWith(ext));
89
+ });
90
+
91
+ return matchedFile
92
+ ? { contents: matchedFile.contents || "", full_path: matchedFile.full_path || "" }
93
+ : { contents: fileArray[0]?.contents || "", full_path: fileArray[0]?.full_path || "" };
94
+ };
95
+
96
+
97
+ export const modifySourceBrainTool = (db, ai) => ({
98
+ name: "modify-source-brain",
99
+ description: "์‚ฌ์šฉ์ž์˜ ๊ตฌ์ฒด์ ์ธ ์ˆ˜์ • ์š”์ฒญ์„ ๋ถ„์„ํ•˜์—ฌ, ๊ธฐ์กด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ˆœ์ˆ˜ ํ…์ŠคํŠธ ๋งค์นญ ๊ธฐ๋ฐ˜์œผ๋กœ ์ •๋ฐ€ ๊ฐฑ์‹ ํ•˜๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.",
100
+ schema: {
101
+ user_input: z.string(),
102
+ base_package: z.string(),
103
+ current_path: z.string(),
104
+ current_source: z.any(),
105
+ },
106
+ handler: async (params, context) => {
107
+ const schemaSummary = await db.getTableSchemaSummary();
108
+ const enrichedParams = { ...params, schema_summary: schemaSummary };
109
+
110
+ const rawToolResult = await ai.runChain("modify-source-brain", enrichedParams);
111
+ let decision = typeof rawToolResult === 'string' ? JSON.parse(rawToolResult) : rawToolResult;
112
+
113
+ if (decision.intent === "EXECUTE_MODIFY") {
114
+ await context.sendNotification({
115
+ method: "notifications/logging/message",
116
+ params: { level: "info", logger: "modify-source-brain", message: decision.message }
117
+ });
118
+
119
+ const modifyList = decision.action?.modifyList || [];
120
+ const schema = await db.getTableSchema();
121
+ const basePackage = params.base_package;
122
+ const resultType = "com.ninelab.ai.util.CamelCaseMap";
123
+ const asisSources = params.current_source || {};
124
+
125
+ for (let target of modifyList) {
126
+ const filteredSchema = schema.filter(table => target.tableIds.includes(table.tableName));
127
+ const formattedPath = (target?.path || params.current_path || "").replaceAll("/", ".").replaceAll("-", "_");
128
+ const cleanPath = formattedPath.startsWith(".") ? formattedPath.substring(1) : formattedPath;
129
+
130
+ const pathParts = (target?.path || params.current_path || "").split("/");
131
+ const rawClassName = pathParts[pathParts.length - 1] || "Sample";
132
+ const baseClassName = rawClassName.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join("");
133
+
134
+ const mybatisFile = extractFileByExtension(asisSources.mybatis, [".xml"]);
135
+ const serviceFile = extractFileByExtension(asisSources.service, [".java"]);
136
+ const controllerFile = extractFileByExtension(asisSources.controller, [".java"]);
137
+ const javascriptFile = extractFileByExtension(asisSources.javascript, [".js", ".jsx", ".ts", ".tsx"]);
138
+
139
+ const updatedOutputs = {
140
+ mapperSource: mybatisFile.contents,
141
+ serviceSource: serviceFile.contents,
142
+ controllerSource: controllerFile.contents,
143
+ uiSource: javascriptFile.contents
144
+ };
145
+
146
+ const targetChains = [
147
+ {
148
+ id: "generate-source-mapper",
149
+ layerKey: "MyBatis",
150
+ label: "MyBatis ๋งตํผ ์ˆ˜์ •",
151
+ fullPath: mybatisFile.full_path,
152
+ getOriginal: () => updatedOutputs.mapperSource,
153
+ buildParams: () => ({
154
+ ...params,
155
+ menu_description: target.description,
156
+ schema_detail: filteredSchema,
157
+ result_type: resultType,
158
+ namespace: `${basePackage}.${cleanPath}.mapper`,
159
+ asis_source: updatedOutputs.mapperSource,
160
+ modification_task: target.modificationTask
161
+ }),
162
+ saveOutput: (source) => { updatedOutputs.mapperSource = source; }
163
+ },
164
+ {
165
+ id: "generate-source-service",
166
+ layerKey: "Service",
167
+ label: "๋น„์ฆˆ๋‹ˆ์Šค ์„œ๋น„์Šค ์ˆ˜์ •",
168
+ fullPath: serviceFile.full_path,
169
+ getOriginal: () => updatedOutputs.serviceSource,
170
+ buildParams: () => ({
171
+ ...params,
172
+ menu_description: target.description,
173
+ service_package: `${basePackage}.${cleanPath}.service`,
174
+ mapper_package: `${basePackage}.${cleanPath}.mapper`,
175
+ asis_source: updatedOutputs.serviceSource,
176
+ modification_task: target.modificationTask,
177
+ mapper_source: updatedOutputs.mapperSource
178
+ }),
179
+ saveOutput: (source) => { updatedOutputs.serviceSource = source; }
180
+ },
181
+ {
182
+ id: "generate-source-controller",
183
+ layerKey: "Controller",
184
+ label: "์ปจํŠธ๋กค๋Ÿฌ API ์ˆ˜์ •",
185
+ fullPath: controllerFile.full_path,
186
+ getOriginal: () => updatedOutputs.controllerSource,
187
+ buildParams: () => ({
188
+ ...params,
189
+ menu_description: target.description,
190
+ api_base_url: target.path || params.current_path,
191
+ controller_package: `${basePackage}.${cleanPath}.controller`,
192
+ service_package: `${basePackage}.${cleanPath}.service`,
193
+ asis_source: updatedOutputs.controllerSource,
194
+ modification_task: target.modificationTask,
195
+ service_source: updatedOutputs.serviceSource
196
+ }),
197
+ saveOutput: (source) => { updatedOutputs.controllerSource = source; }
198
+ },
199
+ {
200
+ id: "generate-source-ui-react",
201
+ layerKey: "JavaScript",
202
+ label: "๋ฆฌ์•กํŠธ UI ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •",
203
+ fullPath: javascriptFile.full_path,
204
+ getOriginal: () => updatedOutputs.uiSource,
205
+ buildParams: () => ({
206
+ ...params,
207
+ menu_description: target.description,
208
+ api_base_url: target.path || params.current_path,
209
+ menu_name: target.description,
210
+ base_class: baseClassName,
211
+ schema_detail: filteredSchema,
212
+ asis_source: updatedOutputs.uiSource,
213
+ modification_task: target.modificationTask,
214
+ mapper_source: updatedOutputs.mapperSource,
215
+ controller_source: updatedOutputs.controllerSource
216
+ }),
217
+ saveOutput: (source) => { updatedOutputs.uiSource = source; }
218
+ }
219
+ ];
220
+
221
+ for (const chain of targetChains) {
222
+ if (!target.targetLayers.includes(chain.layerKey)) {
223
+ continue;
224
+ }
225
+
226
+ // ๐Ÿ’ก [ํ•ต์‹ฌ] AI ๊ฐ€ ๊ณ ์น˜๊ธฐ ์ „์˜ ์ˆœ์ˆ˜ ์˜ค๋ฆฌ์ง€๋„ ์†Œ์Šค ์Šค๋ƒ…์ƒท์„ ๋ฏธ๋ฆฌ ์บก์ฒ˜
227
+ const originalSource = chain.getOriginal();
228
+
229
+ const specificToolParams = chain.buildParams();
230
+ console.log(`๐Ÿ› ๏ธ [${target.componentName || "Component"}] ${chain.label} ์ˆ˜์ • ์ค‘...`);
231
+
232
+ const rawSourceResult = await ai.runChain(chain.id, specificToolParams);
233
+ let resultObj = typeof rawSourceResult === 'string' ? JSON.parse(rawSourceResult) : rawSourceResult;
234
+
235
+ let finalSource = "";
236
+ if (resultObj.mode === "CREATE" || resultObj.full_source) {
237
+ finalSource = resultObj.full_source;
238
+ } else if (resultObj.mode === "UPDATE" || resultObj.source_changes) {
239
+ finalSource = applyPureDiff(originalSource, resultObj.source_changes);
240
+ } else {
241
+ finalSource = originalSource;
242
+ }
243
+
244
+ // ์—ฐ์‡„ ๋ฐ˜์‘์šฉ ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„ํผ ์ตœ์‹ ํ™”
245
+ chain.saveOutput(finalSource);
246
+
247
+ // ๐Ÿ’ก [ํด๋ผ์ด์–ธํŠธ ๊ฐ€์‹œ์„ฑ ๊ฐœ์„ ] ์˜ค๋ฆฌ์ง€๋„ ์†Œ์Šค์™€ ์ตœ์ข… ์ˆ˜์ • ์†Œ์Šค๋ฅผ ํ•จ๊ป˜ ์ŠคํŠธ๋ฆฌ๋ฐ
248
+ await context.sendNotification({
249
+ method: "notifications/logging/message",
250
+ params: {
251
+ level: "info",
252
+ logger: "modify-source-brain",
253
+ message: `${chain.label} ์ˆ˜์ • ์™„๋ฃŒ`,
254
+ data: {
255
+ layer: chain.layerKey,
256
+ full_path: chain.fullPath,
257
+ asis_source: originalSource, // ๐Ÿ‘ˆ ํด๋ผ์ด์–ธํŠธ์—์„œ Diff ํŒ์—…์šฉ์œผ๋กœ ๋งคํ•‘ํ•  AS-IS ์›๋ณธ ์†Œ์Šค
258
+ source: finalSource // ๐Ÿ‘ˆ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฎ์–ด์”Œ์šฐ๊ฑฐ๋‚˜ ์šฐ์ธก ์ฐฝ์— ๋„์šธ TO-BE ๊ฒฐ๊ณผ ์†Œ์Šค
259
+ }
260
+ }
261
+ });
262
+ }
263
+ }
264
+
265
+ await context.sendNotification({
266
+ method: "notifications/logging/message",
267
+ params: {
268
+ level: "info",
269
+ logger: "modify-source-brain-completed", // ๐Ÿ‘ˆ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋œ ์ปดํ”Œ๋ฆฌํŠธ ์ „์šฉ ๋กœ๊ฑฐ
270
+ message: "์š”์ฒญํ•˜์‹  ์ž‘์—…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ตœ์ข… Diff ๋ช…์„ธ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.",
271
+ data: {
272
+ status: "SUCCESS"
273
+ }
274
+ }
275
+ });
276
+
277
+ return { success: true, intent: "EXECUTE_MODIFY", message: null };
278
+ }
279
+ else {
280
+ return { success: false, intent: "NONE", message: decision.message };
281
+ }
282
+ }
283
+ });
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+
3
+ export const getStaticToolsConfig = () => [
4
+ {
5
+ name: "generate-menu",
6
+ description: "์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ถ„์„ํ•˜์—ฌ ์‹œ์Šคํ…œ ๋‚ด์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” '์‹ ๊ทœ ๋ฉ”๋‰ด ๋ผ์šฐํŠธ ํŠธ๋ฆฌ'๋ฅผ ๋„์ถœํ•˜๊ณ  ์„ค๊ณ„ ์ •๋ณด(๋ฉ”๋‰ด๋ช…, URL Path, ์—ฐ๊ด€ ํ…Œ์ด๋ธ” ๋งคํ•‘ ์ •๋ณด)๋ฅผ ํ™”๋ฉด์— ๊ฐฑ์‹  ๋ฐ ์žฌ๋ฐฐ์น˜ํ•˜๋Š” ๋ผ์šฐํŠธ ๊ตฌ์กฐ ์„ค๊ณ„ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์†Œ์Šค์ฝ”๋“œ ํŒŒ์ผ(MyBatis, Service, Controller, React UI)์„ ์ƒ์„ฑ ๋ฐ ์ž‘์„ฑํ•˜๋Š” ํ–‰์œ„๋Š” ์ ˆ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
7
+ schema: { user_input: z.string(), routes: z.any(), schema_summary: z.any() }
8
+ },
9
+ {
10
+ name: "generate-source",
11
+ description: "ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
12
+ schema: { user_input: z.string(), routes: z.any() }
13
+ },
14
+ {
15
+ name: "generate-source-controller",
16
+ description: "ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
17
+ schema: { user_input: z.string(), routes: z.any() }
18
+ },
19
+ {
20
+ name: "generate-source-service",
21
+ description: "ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
22
+ schema: { user_input: z.string(), routes: z.any() }
23
+ },
24
+ {
25
+ name: "generate-source-mapper", // ๋ฒค๋” ์ข…์† ๋ช…์นญ ์ œ๊ฑฐ ์™„๋ฃŒ
26
+ description: "ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค ๋งตํผ XML ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
27
+ schema: { user_input: z.string(), routes: z.any() }
28
+ }
29
+ ];
@@ -0,0 +1,182 @@
1
+ import { z } from "zod";
2
+
3
+ export const systemBrainTool = (db, ai, nineTools = [], generateSourceBrain = null, modifySourceBrain = null) => ({
4
+ name: "system-brain",
5
+ description: "์˜๋„ ๋ถ„์„ ๋ฐ ์ž๋™ ์‹คํ–‰ ๊ด€์ œ",
6
+ schema: {
7
+ base_package: z.string(),
8
+ chat_history: z.string(),
9
+ user_input: z.string(),
10
+ current_path: z.string(),
11
+ current_source: z.any(),
12
+ routes: z.any(), // ๋ฉ”๋‰ด ์ง€๋„๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ์ „๋‹ฌ ๋ฐ›์Œ
13
+ unmapped_routes: z.any(),
14
+ },
15
+ handler: async (params, context) => {
16
+ const schemaSummary = await db.getTableSchemaSummary();
17
+ //const { selected_tables } = await this.ai.filterTables(question, schemaSummary);
18
+
19
+ const allToolsConfig = [
20
+ ...nineTools.map(t => ({ name: t.name, description: t.description, schema: t.schema })),
21
+ ];
22
+
23
+ // ๐Ÿ’ก ์ฃผ์ž…๋ฐ›์€ generateSourceBrain ๋ณธ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํ•ด๋‹น ๋ช…์„ธ๋„ ํˆด ์ปจํ…์ŠคํŠธ์— ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.
24
+ if (generateSourceBrain) {
25
+ allToolsConfig.push({
26
+ name: generateSourceBrain.name,
27
+ description: generateSourceBrain.description,
28
+ schema: generateSourceBrain.schema
29
+ });
30
+ }
31
+
32
+ if (modifySourceBrain) {
33
+ allToolsConfig.push({
34
+ name: modifySourceBrain.name,
35
+ description: modifySourceBrain.description,
36
+ schema: modifySourceBrain.schema
37
+ });
38
+ }
39
+
40
+ const enrichedParams = {
41
+ ...params,
42
+ schema_summary: schemaSummary,
43
+ tools: allToolsConfig
44
+ };
45
+
46
+ // 1. ๊ด€์ œํƒ‘(system-brain)์—๊ฒŒ ๋จผ์ € ๋ฌผ์–ด๋ด„
47
+ const brainResult = await ai.runChain("system-brain", enrichedParams);
48
+ const decision = typeof brainResult === 'string' ? JSON.parse(brainResult) : brainResult;
49
+
50
+ console.log(decision);
51
+ // ๐Ÿ’ก ์ „์†ก ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์—, ์„œ๋ฒ„ ๊ฐ์ฒด๊ฐ€ ์ด ์„ธ์…˜์„ ์ง„์งœ ์—ด์–ด๋‘” ๊ฒŒ ๋งž๋Š”์ง€ ๋ˆˆ์œผ๋กœ ํ™•์ธ
52
+
53
+ if (decision.action?.selected_tool === "generate-source-brain") {
54
+ if (!generateSourceBrain) throw new Error("generate-source-brain ์ธ์Šคํ„ด์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
55
+
56
+ await context.sendNotification({
57
+ method: "notifications/logging/message",
58
+ params: {
59
+ level: "info",
60
+ logger: "system-brain",
61
+ message: decision.message,//`์†Œ์Šค ์ƒ์„ฑ ์˜๋„๊ฐ€ ํ™•์ •๋˜์–ด [generate-source-brain] ๊ด€์ œ๋กœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.`
62
+ }
63
+ });
64
+
65
+ // ๐Ÿ’ก ๊ทœ๊ฒฉ์— ๋งž์ถฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋นŒ๋“œ ํ›„ ํˆด ๋ณธ์ฒด ํ•ธ๋“ค๋Ÿฌ ์ง์ ‘ ๊ตฌ๋™
66
+ const generateSourceBrainParams = {
67
+ user_input: params.user_input,
68
+ base_package: params.base_package,
69
+ unmapped_routes: (params.unmapped_routes || []).filter(route => route["is-nine"] !== true),
70
+ current_source: "",
71
+ };
72
+
73
+ // ๐ŸŽฏ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ source-brain ๋‚ด๋ถ€์˜ await db.getTableSchemaSummary()๊ฐ€
74
+ // ์™„๋ฒฝํ•˜๊ฒŒ ์ •์ƒ ๊ธฐ๋™๋˜์–ด LangChain ํ…œํ”Œ๋ฆฟ ์ฃผ์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
75
+ return await generateSourceBrain.handler(generateSourceBrainParams, context);
76
+ } else if (decision.action?.selected_tool === "modify-source-brain") {
77
+ if (!modifySourceBrain) throw new Error("modify-source-brain ์ธ์Šคํ„ด์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
78
+
79
+ await context.sendNotification({
80
+ method: "notifications/logging/message",
81
+ params: {
82
+ level: "info",
83
+ logger: "system-brain",
84
+ message: decision.message,//`๐Ÿง  ์†Œ์Šค ์ˆ˜์ • ์˜๋„๊ฐ€ ํ™•์ •๋˜์–ด [modify-source-brain] ๊ด€์ œ๋กœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.`
85
+ }
86
+ });
87
+
88
+ // ๐Ÿ’ก ๊ทœ๊ฒฉ์— ๋งž์ถฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋นŒ๋“œ ํ›„ ํˆด ๋ณธ์ฒด ํ•ธ๋“ค๋Ÿฌ ์ง์ ‘ ๊ตฌ๋™
89
+ const modifySourceBrainParams = {
90
+ user_input: params.user_input,
91
+ current_path: params.current_path,
92
+ base_package: params.base_package,
93
+ //unmapped_routes: params.unmapped_routes || [],
94
+ current_source: params.current_source,
95
+ };
96
+
97
+ // ๐ŸŽฏ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ source-brain ๋‚ด๋ถ€์˜ await db.getTableSchemaSummary()๊ฐ€
98
+ // ์™„๋ฒฝํ•˜๊ฒŒ ์ •์ƒ ๊ธฐ๋™๋˜์–ด LangChain ํ…œํ”Œ๋ฆฟ ์ฃผ์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
99
+ return await modifySourceBrain.handler(modifySourceBrainParams, context);
100
+ }
101
+
102
+ // 2. [๋‚ด๋ถ€ ๋ฃจํ”„] ์ •๋ณด๊ฐ€ ์ถฉ๋ถ„ํ•˜๊ณ  ๋‹ค๋ฅธ ํˆด์„ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ
103
+ if (decision.intent === "EXECUTE_TOOL" && decision.action?.selected_tool) {
104
+
105
+ await context.sendNotification({
106
+ method: "notifications/logging/message",
107
+ params: {
108
+ level: "info",
109
+ logger: "system-brain",
110
+ message: decision.message,
111
+ }
112
+ });
113
+
114
+ console.log(`๐Ÿ”„ [Auto-Chain]: ${decision.action.selected_tool} ์‹คํ–‰`);
115
+
116
+ // ๐Ÿ› ๏ธ ์•ˆ์ „ํ•œ ํ•˜์œ„ ํˆด ์ฒด์ด๋‹ ํŒŒ๋ผ๋ฏธํ„ฐ ์ „๋‹ฌ ๋ฐฉ์‹
117
+ const toolParams = {
118
+ ...enrichedParams, // ์›๋ณธ ์ปจํ…์ŠคํŠธ(routes, schema_summary ๋“ฑ)๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ๊น”๊ณ 
119
+ ...decision.action.params // AI๊ฐ€ ๊ฐ€๊ณตํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ
120
+ };
121
+
122
+ const rawToolResult = await ai.runChain(decision.action.selected_tool, toolParams);
123
+
124
+ console.log(rawToolResult);
125
+
126
+ let processedResult = rawToolResult;
127
+ if (typeof rawToolResult === 'string') {
128
+ try {
129
+ processedResult = JSON.parse(rawToolResult);
130
+ } catch (e) {
131
+ processedResult = { message: rawToolResult, data: null };
132
+ }
133
+ }
134
+
135
+ //console.log(decision.action.selected_tool);
136
+
137
+ switch (decision.action.selected_tool) {
138
+ case "generate-menu":
139
+ await context.sendNotification({
140
+ method: "notifications/logging/message",
141
+ params: {
142
+ level: "info",
143
+ logger: "generate-menu-completed",
144
+ message: processedResult.message,
145
+ data: processedResult.data,
146
+ }
147
+ });
148
+ break;
149
+ }
150
+
151
+ switch (decision.action.selected_tool) {
152
+ case "generate-menu":
153
+ return {
154
+ intent: "EXECUTE_TOOL",
155
+ selected_tool: decision.action.selected_tool,
156
+ action: {
157
+ selected_tool: decision.action.selected_tool,
158
+ params: decision.action.params
159
+ },
160
+ message: null, // ๐Ÿ‘ˆ AI ์•„ํ‚คํ…ํŠธ๊ฐ€ ์ž‘์„ฑํ•œ ์ง„์งœ ๋ถ„์„ ๊ทผ๊ฑฐ ๋ฉ˜ํŠธ๊ฐ€ ๋“ค์–ด๊ฐ
161
+ data: processedResult.data // ๐Ÿ‘ˆ ์‹ ๊ทœ ํ†ตํ•ฉ ๋ผ์šฐํŠธ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ
162
+ };
163
+ }
164
+
165
+
166
+ // ๐Ÿ’ก [ํ•ต์‹ฌ] ํ•˜์œ„ ํˆด์ด ์ •์„ฑ๊ป ์ž‘์„ฑํ•œ ์ง„์งœ ๋ถ„์„ ์ด์œ (message)์™€ ๊ฒฐ๊ณผ(data)๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ด๋ ค์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค!
167
+ return {
168
+ intent: "EXECUTE_TOOL",
169
+ selected_tool: decision.action.selected_tool,
170
+ action: {
171
+ selected_tool: decision.action.selected_tool,
172
+ params: decision.action.params
173
+ },
174
+ message: processedResult.message, // ๐Ÿ‘ˆ AI ์•„ํ‚คํ…ํŠธ๊ฐ€ ์ž‘์„ฑํ•œ ์ง„์งœ ๋ถ„์„ ๊ทผ๊ฑฐ ๋ฉ˜ํŠธ๊ฐ€ ๋“ค์–ด๊ฐ
175
+ data: processedResult.data // ๐Ÿ‘ˆ ์‹ ๊ทœ ํ†ตํ•ฉ ๋ผ์šฐํŠธ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ
176
+ };
177
+ }
178
+ else {
179
+ return decision;
180
+ }
181
+ }
182
+ });
@@ -0,0 +1,30 @@
1
+ export const safeExecute = async (fn) => {
2
+ try {
3
+ const result = await fn();
4
+
5
+ // [์›๋ณต] ์–ด์„คํ”ˆ ๋ถ„๊ธฐ๋‚˜ ํŒŒ์‹ฑ ์—†์ด, ํˆด์ด ๋ฑ‰์€ ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ์ง๋ ฌํ™”ํ•˜์—ฌ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
6
+ const textResult = typeof result === 'string'
7
+ ? result
8
+ : JSON.stringify(result, null, 2);
9
+
10
+ return {
11
+ content: [
12
+ {
13
+ type: "text",
14
+ text: textResult
15
+ }
16
+ ]
17
+ };
18
+ } catch (error) {
19
+ console.error("Tool Error:", error);
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: JSON.stringify({ success: false, error: error.message })
25
+ }
26
+ ],
27
+ isError: true
28
+ };
29
+ }
30
+ };
@@ -0,0 +1,131 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import {
3
+ ListToolsRequestSchema,
4
+ CallToolRequestSchema
5
+ } from "@modelcontextprotocol/sdk/types.js";
6
+
7
+ // ๊ธฐ์กด ์„œ๋น„์Šค ์ž„ํฌํŠธ
8
+ // import { AIService } from './ai/AIService.js';
9
+ // import { SourceService } from './services/SourceService.js';
10
+
11
+ export async function createMCPServer(aiService, sourceService, queryService) {
12
+ const server = new Server({
13
+ name: "nine-mcp",
14
+ version: "0.1.25",
15
+ }, {
16
+ capabilities: { tools: {} }
17
+ });
18
+
19
+ // 1. ๋„๊ตฌ ๋ชฉ๋ก ์ •์˜
20
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
21
+ tools: [
22
+ {
23
+ name: "navigate_menu",
24
+ description: "ํ˜„์žฌ ํ™”๋ฉด์˜ ๋ฉ”๋‰ด ๊ตฌ์กฐ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ตœ์ ์˜ ์ด๋™ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ menuName: { type: "string", description: "์ด๋™ํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฉ”๋‰ด์˜ ์ž์—ฐ์–ด ๋ช…์นญ" },
29
+ currentRoutes: {
30
+ type: "array",
31
+ items: { type: "object" },
32
+ description: "ํ˜„์žฌ React ์•ฑ์— ์ •์˜๋œ ์ตœ์‹  ๋ฉ”๋‰ด ๊ตฌ์กฐ ๋ฐฐ์—ด"
33
+ }
34
+ },
35
+ required: ["menuName", "currentRoutes"]
36
+ }
37
+ },
38
+ {
39
+ name: "ask_query",
40
+ description: "์ž์—ฐ์–ด๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋ฅผ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ prompt: { type: "string", description: "์งˆ์˜ ๋‚ด์šฉ" }
45
+ },
46
+ required: ["prompt"]
47
+ }
48
+ },
49
+ {
50
+ name: "analyze_missing",
51
+ description: "ํ˜„์žฌ ๋ฉ”๋‰ด์™€ DB ์Šคํ‚ค๋งˆ๋ฅผ ๋น„๊ตํ•˜์—ฌ ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ prompt: { type: "string" },
56
+ currentRoutes: { type: "array", items: { type: "object" } }
57
+ },
58
+ required: ["prompt", "currentRoutes"]
59
+ }
60
+ },
61
+ {
62
+ name: "generate_source",
63
+ description: "ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ ํƒ๋œ ๋ ˆ์ด์–ด(MyBatis/Service/Controller/JS) ์†Œ์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ tableName: { type: "string" },
68
+ // UI ์ฒดํฌ๋ฐ•์Šค ์ƒํƒœ๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ๊ฐ์ฒด ์ถ”๊ฐ€
69
+ options: {
70
+ type: "object",
71
+ properties: {
72
+ mybatis: { type: "boolean" },
73
+ service: { type: "boolean" },
74
+ controller: { type: "boolean" },
75
+ javascript: { type: "boolean" }
76
+ }
77
+ }
78
+ },
79
+ required: ["tableName"]
80
+ }
81
+ }
82
+ ]
83
+ }));
84
+
85
+ // 2. ๋„๊ตฌ ์‹คํ–‰ ๋กœ์ง
86
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
87
+ const { name, arguments: args } = request.params;
88
+
89
+ try {
90
+ switch (name) {
91
+ case "navigate_menu":
92
+ // ์„œ๋ฒ„๊ฐ€ ์•„๋‹Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „๋‹ฌํ•œ args.currentRoutes๋ฅผ ์‚ฌ์šฉํ•จ
93
+ const navResult = await aiService.processNavigation(
94
+ args.menuName,
95
+ args.currentRoutes
96
+ );
97
+ return {
98
+ content: [{
99
+ type: "text",
100
+ text: JSON.stringify({
101
+ targetPath: navResult.path,
102
+ explanation: navResult.explanation,
103
+ success: navResult.success
104
+ })
105
+ }]
106
+ };
107
+
108
+ case "ask_query":
109
+ const queryResult = await queryService.ask(args.prompt);
110
+ return {
111
+ content: [{ type: "text", text: JSON.stringify(queryResult) }]
112
+ };
113
+
114
+ case "generate_source":
115
+ // args.options๋ฅผ sourceService์— ํ•จ๊ป˜ ์ „๋‹ฌ
116
+ const sourceResult = await sourceService.generate(args.tableName, args.options);
117
+ return { content: [{ type: "text", text: JSON.stringify(sourceResult) }] };
118
+
119
+ default:
120
+ throw new Error(`์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค: ${name}`);
121
+ }
122
+ } catch (error) {
123
+ return {
124
+ isError: true,
125
+ content: [{ type: "text", text: error.message }]
126
+ };
127
+ }
128
+ });
129
+
130
+ return server;
131
+ }