@axboot-mcp/mcp-server 1.0.0

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 (78) hide show
  1. package/CLAUDE.md +119 -0
  2. package/MCP_TOOL_PLAN.md +710 -0
  3. package/MCP_USAGE.md +914 -0
  4. package/README.md +168 -0
  5. package/REPOSITORY_CONVENTIONS.md +250 -0
  6. package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
  7. package/SEARCH_PARAMS_PLAN.md +2570 -0
  8. package/STORE_PATTERNS.md +1178 -0
  9. package/debug-dto.js +72 -0
  10. package/generate-banner-store.js +62 -0
  11. package/generation-plan.json +2176 -0
  12. package/generation-results.json +1817 -0
  13. package/package.json +45 -0
  14. package/scripts/batch-generate-all.js +159 -0
  15. package/scripts/batch-generate-mcp.js +329 -0
  16. package/scripts/batch-generate-stores-v2.js +272 -0
  17. package/scripts/batch-generate-stores.js +179 -0
  18. package/scripts/batch-plan.json +3810 -0
  19. package/scripts/batch-process.py +90 -0
  20. package/scripts/batch-regenerate.js +356 -0
  21. package/scripts/direct-generate.js +227 -0
  22. package/scripts/execute-batches.js +1911 -0
  23. package/scripts/generate-all-stores.js +144 -0
  24. package/scripts/generate-stores-mcp.js +161 -0
  25. package/scripts/generate-stores-v2.js +450 -0
  26. package/scripts/generate-stores-v3.js +412 -0
  27. package/scripts/generate-stores-v4.js +521 -0
  28. package/scripts/generate-stores.js +382 -0
  29. package/scripts/repos-to-process.json +1899 -0
  30. package/src/config/nh-layout-patterns.ts +166 -0
  31. package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
  32. package/src/docs/NH_STORE_PATTERNS.md +297 -0
  33. package/src/docs/README.md +216 -0
  34. package/src/docs/index.ts +28 -0
  35. package/src/docs/loader.ts +568 -0
  36. package/src/docs/patterns.json +419 -0
  37. package/src/docs/practical-examples.md +732 -0
  38. package/src/docs/quick-start.md +257 -0
  39. package/src/docs/requirements-analysis-guide.md +364 -0
  40. package/src/docs/rules.json +321 -0
  41. package/src/docs/store-pattern-analysis.md +664 -0
  42. package/src/docs/store-patterns-rules.md +1168 -0
  43. package/src/docs/store-patterns-usage-guide.md +1835 -0
  44. package/src/docs/troubleshooting.md +544 -0
  45. package/src/docs/type-selection-guide.md +572 -0
  46. package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
  47. package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
  48. package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
  49. package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
  50. package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
  51. package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
  52. package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
  53. package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
  54. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
  55. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
  56. package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
  57. package/src/features/store-features.ts +232 -0
  58. package/src/handlers/analyze-requirements.ts +403 -0
  59. package/src/handlers/analyze.ts +1373 -0
  60. package/src/handlers/generate-from-requirements.ts +250 -0
  61. package/src/handlers/generate-hook.ts +950 -0
  62. package/src/handlers/generate-interactive.ts +840 -0
  63. package/src/handlers/generate-listdatagrid.ts +521 -0
  64. package/src/handlers/generate-multi-stores.ts +577 -0
  65. package/src/handlers/generate-requirements-from-layout.ts +160 -0
  66. package/src/handlers/generate-search-params.ts +717 -0
  67. package/src/handlers/generate.ts +911 -0
  68. package/src/handlers/list-templates.ts +104 -0
  69. package/src/handlers/scan-metadata.ts +485 -0
  70. package/src/handlers/suggest-layout.ts +326 -0
  71. package/src/index.ts +959 -0
  72. package/src/prompts/search-params.md +793 -0
  73. package/src/templates/index.ts +107 -0
  74. package/src/templates/unified.ts +462 -0
  75. package/store-generation-error-patterns.md +225 -0
  76. package/test/useAgentStore.ts +136 -0
  77. package/test-server.js +78 -0
  78. package/tsconfig.json +20 -0
package/src/index.ts ADDED
@@ -0,0 +1,959 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ ListPromptsRequestSchema,
8
+ GetPromptRequestSchema,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { analyzeInterface } from './handlers/analyze.js';
11
+ import { generateStore } from './handlers/generate.js';
12
+ import { generateHook } from './handlers/generate-hook.js';
13
+ import * as path from 'path';
14
+ import { analyzeRequirements } from './handlers/analyze-requirements.js';
15
+ import { generateStoreFromRequirements } from './handlers/generate-from-requirements.js';
16
+ import { generateMultiStores, detectMultiListPattern } from './handlers/generate-multi-stores.js';
17
+ import { generateRequirementsFromLayout } from './handlers/generate-requirements-from-layout.js';
18
+ import { suggestLayout } from './handlers/suggest-layout.js';
19
+ import { getNHLayoutTypes } from './config/nh-layout-patterns.js';
20
+ import { generateSearchParams } from './handlers/generate-search-params.js';
21
+ import { generateListDataGrid } from './handlers/generate-listdatagrid.js';
22
+ import { generateStoreAndHookInteractive } from './handlers/generate-interactive.js';
23
+ import { scanMetadata } from './handlers/scan-metadata.js';
24
+ import { existsSync, unlinkSync, readFileSync } from 'fs';
25
+ import * as chokidar from 'chokidar';
26
+
27
+ // ========== 파일 감시 관리자 ==========
28
+
29
+ /**
30
+ * 프로젝트별 파일 감시자 관리
31
+ */
32
+ const projectWatchers = new Map<string, chokidar.FSWatcher>();
33
+
34
+ /**
35
+ * 프로젝트의 파일 감시 시작
36
+ * - DTO, Repository, Code, Modal 파일 변경 시 캐시 삭제
37
+ */
38
+ function startWatching(projectRoot: string): void {
39
+ // 이미 감시 중이면 무시
40
+ if (projectWatchers.has(projectRoot)) {
41
+ return;
42
+ }
43
+
44
+ const cachePath = path.join(projectRoot, '.claude', 'mcp-cache', 'search-params-meta.json');
45
+
46
+ // 감시할 파일 패턴
47
+ const watchPatterns = [
48
+ 'src/services/@interface/dto/**/*.ts',
49
+ 'src/services/**/dto/**/*.ts',
50
+ 'src/types/**/*Dto*.ts',
51
+ 'src/types/**/*.ts', // DefaultDto 등 포함
52
+ 'src/@types/**/*.ts', // DefaultDto 포함
53
+ 'src/services/@interface/types/**/*.ts', // DefaultDto 포함
54
+ 'src/interfaces/**/*Request*.ts',
55
+ 'src/interfaces/**/*Response*.ts',
56
+ 'src/services/**/*Repository.ts',
57
+ 'src/services/**/repository/*.ts',
58
+ 'src/stores/**/*.ts',
59
+ 'src/**/useCodeStore.ts',
60
+ 'src/modals/**/*Modal.ts',
61
+ 'src/components/**/*Modal.ts',
62
+ ];
63
+
64
+ const watcher = chokidar.watch(watchPatterns, {
65
+ cwd: projectRoot,
66
+ ignoreInitial: true,
67
+ persistent: true,
68
+ });
69
+
70
+ // 파일 변경 시 캐시 삭제
71
+ watcher.on('change', () => {
72
+ try {
73
+ if (existsSync(cachePath)) {
74
+ unlinkSync(cachePath);
75
+ console.error(`[MCP] 캐시 삭제 (파일 변경 감지): ${cachePath}`);
76
+ }
77
+ } catch (e) {
78
+ // 무시
79
+ }
80
+ });
81
+
82
+ watcher.on('add', () => {
83
+ try {
84
+ if (existsSync(cachePath)) {
85
+ unlinkSync(cachePath);
86
+ console.error(`[MCP] 캐시 삭제 (파일 추가 감지): ${cachePath}`);
87
+ }
88
+ } catch (e) {
89
+ // 무시
90
+ }
91
+ });
92
+
93
+ watcher.on('unlink', () => {
94
+ try {
95
+ if (existsSync(cachePath)) {
96
+ unlinkSync(cachePath);
97
+ console.error(`[MCP] 캐시 삭제 (파일 삭제 감지): ${cachePath}`);
98
+ }
99
+ } catch (e) {
100
+ // 무시
101
+ }
102
+ });
103
+
104
+ projectWatchers.set(projectRoot, watcher);
105
+ console.error(`[MCP] 파일 감시 시작: ${projectRoot}`);
106
+ }
107
+
108
+ /**
109
+ * 프로젝트의 파일 감시 중지
110
+ */
111
+ function stopWatching(projectRoot: string): void {
112
+ const watcher = projectWatchers.get(projectRoot);
113
+ if (watcher) {
114
+ watcher.close();
115
+ projectWatchers.delete(projectRoot);
116
+ console.error(`[MCP] 파일 감시 중지: ${projectRoot}`);
117
+ }
118
+ }
119
+
120
+ // ========== 자동 스캔 헬퍼 ==========
121
+
122
+ /**
123
+ * 파일 경로에서 프로젝트 루트 추출
124
+ * - src/, package.json, tsconfig.json 등이 있는 경로를 찾음
125
+ */
126
+ function extractProjectRoot(filePath: string): string {
127
+ const normalizedPath = path.normalize(filePath);
128
+
129
+ // 이미 루트 경로인지 확인 (src, package.json 등이 있으면)
130
+ const possibleRoots = [
131
+ path.dirname(normalizedPath), // 파일의 디렉토리
132
+ path.dirname(path.dirname(normalizedPath)), // 그 상위 디렉토리
133
+ path.dirname(path.dirname(path.dirname(normalizedPath))), // 그 상위 디렉토리
134
+ ];
135
+
136
+ for (const root of possibleRoots) {
137
+ if (root === path.normalize('/')) continue; // 루트 디렉토리는 제외
138
+
139
+ // 프로젝트 루트 표식 확인
140
+ const hasProjectMarkers = [
141
+ path.join(root, 'package.json'),
142
+ path.join(root, 'tsconfig.json'),
143
+ path.join(root, 'src'),
144
+ ].some(marker => existsSync(marker));
145
+
146
+ if (hasProjectMarkers) {
147
+ return root;
148
+ }
149
+ }
150
+
151
+ // 기본: 파일의 디렉토리 반환
152
+ return path.dirname(normalizedPath);
153
+ }
154
+
155
+ /**
156
+ * 메타데이터 자동 스캔 보장
157
+ * - 캐시가 없거나 만료된 경우 자동으로 스캔
158
+ * - 파일 감시자 시작
159
+ */
160
+ async function ensureScanned(projectRoot: string): Promise<void> {
161
+ // 파일 감시 시작 (백그라운드)
162
+ startWatching(projectRoot);
163
+
164
+ const cachePath = path.join(projectRoot, '.claude', 'mcp-cache', 'search-params-meta.json');
165
+
166
+ // 캐시 확인
167
+ if (existsSync(cachePath)) {
168
+ try {
169
+ const cacheContent = await import('fs').then(fs => fs.promises.readFile(cachePath, 'utf-8'));
170
+ const cache = JSON.parse(cacheContent);
171
+
172
+ // 캐시 유효성 확인 (파일 감시 모드에서는 1시간 제한 없음)
173
+ // 단순히 캐시가 존재하면 사용
174
+ return; // 캐시 유효함
175
+ } catch {
176
+ // 캐시 손상 → 재스캔
177
+ }
178
+ }
179
+
180
+ // 스캔 실행 (조용히, 에러 무시)
181
+ try {
182
+ await scanMetadata({ projectRoot, forceRescan: false });
183
+ } catch (e) {
184
+ // 스캔 실패해도 툴 실행은 계속
185
+ }
186
+ }
187
+
188
+ // MCP 서버 생성
189
+ const server = new Server(
190
+ {
191
+ name: 'mcp-axboot',
192
+ version: '1.0.0',
193
+ },
194
+ {
195
+ capabilities: {
196
+ tools: {},
197
+ prompts: {},
198
+ },
199
+ }
200
+ );
201
+
202
+ // 프롬프트 목록 등록
203
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
204
+ return {
205
+ prompts: [
206
+ {
207
+ name: 'search-params-guide',
208
+ description: 'NH-FE-B 패턴의 SearchParams 컴포넌트 생성 가이드. 사용자가 "search params 만들어줘: dateRange code:STATUS_CODE search" 같은 요청을 하면 이 가이드를 참고하세요.',
209
+ },
210
+ ],
211
+ };
212
+ });
213
+
214
+ // 프롬프트 가져오기
215
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
216
+ const { name } = request.params;
217
+
218
+ if (name === 'search-params-guide') {
219
+ const promptPath = path.join(__dirname, 'prompts', 'search-params.md');
220
+ const content = readFileSync(promptPath, 'utf-8');
221
+
222
+ return {
223
+ messages: [
224
+ {
225
+ role: 'user',
226
+ content: {
227
+ type: 'text',
228
+ text: content,
229
+ },
230
+ },
231
+ ],
232
+ };
233
+ }
234
+
235
+ throw new Error(`Unknown prompt: ${name}`);
236
+ });
237
+
238
+ // 도구 목록 등록
239
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
240
+ return {
241
+ tools: [
242
+ {
243
+ name: 'analyze_interface',
244
+ description: '인터페이스 Repository 파일을 분석하여 Service 메서드와 타입 정보 추출',
245
+ inputSchema: {
246
+ type: 'object',
247
+ properties: {
248
+ interfacePath: {
249
+ type: 'string',
250
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
251
+ },
252
+ },
253
+ required: ['interfacePath'],
254
+ },
255
+ },
256
+ {
257
+ name: 'generate_store',
258
+ description: '분석된 인터페이스 정보를 기반으로 Zustand Store 파일 생성',
259
+ inputSchema: {
260
+ type: 'object',
261
+ properties: {
262
+ interfacePath: {
263
+ type: 'string',
264
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
265
+ },
266
+ outputPath: {
267
+ type: 'string',
268
+ description: '생성할 Store 파일 경로 (절대 경로)',
269
+ },
270
+ storeType: {
271
+ type: 'number',
272
+ description: 'Store 타입 (1: 기본+상세, 2: 마스터-디테일+모달+삭제, 3: 트리, 4: 대시보드, 5: 단순, 6: 재정렬, 7: 마스터-디테일+모달+삭제+엑셀, 8: 리스트+선택+엑셀, 9: 마스터-디테일+모달+상세조회, 10: 마스터-디테일+엑셀)',
273
+ default: 1,
274
+ enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
275
+ },
276
+ storeName: {
277
+ type: 'string',
278
+ description: 'Store 이름 (camelCase, 예: useMemberListStore)',
279
+ default: 'useGeneratedStore',
280
+ },
281
+ },
282
+ required: ['interfacePath', 'outputPath'],
283
+ },
284
+ },
285
+ {
286
+ name: 'analyze_requirements',
287
+ description: '자연어 요구사항을 분석하여 필요한 Store 피처 추출 및 적합한 타입 추천',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ requirements: {
292
+ type: 'string',
293
+ description: 'Store 요구사항 (자연어)',
294
+ },
295
+ },
296
+ required: ['requirements'],
297
+ },
298
+ },
299
+ {
300
+ name: 'generate_store_from_requirements',
301
+ description: '요구사항 기반으로 Store 파일 자동 생성 (적합한 타입 자동 선택, 다중 리스트 자동 감지)',
302
+ inputSchema: {
303
+ type: 'object',
304
+ properties: {
305
+ interfacePath: {
306
+ type: 'string',
307
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
308
+ },
309
+ outputPath: {
310
+ type: 'string',
311
+ description: '생성할 Store 파일 경로 (절대 경로, 예: /path/to/useOrderStore.ts)',
312
+ },
313
+ requirements: {
314
+ type: 'string',
315
+ description: 'Store 요구사항 (자연어, 예: 왼쪽 목록, 상단 디테일 목록, 하단 디테일 목록, 각 목록은 검색과 엑셀 다운로드, 하단은 체크박스)',
316
+ },
317
+ },
318
+ required: ['interfacePath', 'outputPath', 'requirements'],
319
+ },
320
+ },
321
+ {
322
+ name: 'generate_multi_stores',
323
+ description: '다중 리스트 통합 Store 생성 (왼쪽/상단/하단 목록 등을 하나의 Store로 통합)',
324
+ inputSchema: {
325
+ type: 'object',
326
+ properties: {
327
+ interfacePath: {
328
+ type: 'string',
329
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
330
+ },
331
+ outputPath: {
332
+ type: 'string',
333
+ description: '생성할 Store 파일 경로 (절대 경로, 예: /path/to/useOrderStore.ts)',
334
+ },
335
+ requirements: {
336
+ type: 'string',
337
+ description: 'Store 요구사항 (자연어, 예: 왼쪽 목록, 상단 디테일 목록, 하단 디테일 목록, 각 목록은 검색과 엑셀 다운로드, 하단은 체크박스)',
338
+ },
339
+ baseStoreName: {
340
+ type: 'string',
341
+ description: 'Store 파일명 (예: useOrderStore)',
342
+ },
343
+ },
344
+ required: ['interfacePath', 'outputPath', 'requirements', 'baseStoreName'],
345
+ },
346
+ },
347
+ {
348
+ name: 'detect_multi_list_pattern',
349
+ description: '요구사항에서 다중 리스트 패턴 감지',
350
+ inputSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ requirements: {
354
+ type: 'string',
355
+ description: 'Store 요구사항 (자연어)',
356
+ },
357
+ },
358
+ required: ['requirements'],
359
+ },
360
+ },
361
+ {
362
+ name: 'generate_requirements_from_layout',
363
+ description: 'NH 레이아웃 패턴과 Repository를 기반으로 요구사항 생성',
364
+ inputSchema: {
365
+ type: 'object',
366
+ properties: {
367
+ interfacePath: {
368
+ type: 'string',
369
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
370
+ },
371
+ layoutType: {
372
+ type: 'string',
373
+ description: 'NH 레이아웃 타입 (LEFT_DETAIL_FORM, LEFT_DETAIL_LIST, MULTI_PANEL, LIST_DRAWER, LIST_MODAL, TAB_BASED, LIST_SUMMARY, DASHBOARD, FORM_ONLY)',
374
+ },
375
+ additional: {
376
+ type: 'string',
377
+ description: '추가 요구사항 (선택사항)',
378
+ },
379
+ },
380
+ required: ['interfacePath', 'layoutType'],
381
+ },
382
+ },
383
+ {
384
+ name: 'list_layouts',
385
+ description: '사용 가능한 NH 레이아웃 패턴 목록 조회',
386
+ inputSchema: {
387
+ type: 'object',
388
+ properties: {},
389
+ },
390
+ },
391
+ {
392
+ name: 'suggest_layout',
393
+ description: 'Repository를 분석하여 가장 적합한 NH 레이아웃 패턴 추천',
394
+ inputSchema: {
395
+ type: 'object',
396
+ properties: {
397
+ interfacePath: {
398
+ type: 'string',
399
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
400
+ },
401
+ },
402
+ required: ['interfacePath'],
403
+ },
404
+ },
405
+ {
406
+ name: 'generate_search_params',
407
+ description: 'App.tsx에 추가되는 SearchParams JSX 코드 생성 (NH-FE-B 패턴 기반)',
408
+ inputSchema: {
409
+ type: 'object',
410
+ properties: {
411
+ appPath: {
412
+ type: 'string',
413
+ description: 'App.tsx 파일 경로 (절대 경로)',
414
+ },
415
+ searchParams: {
416
+ type: 'object',
417
+ description: 'SearchParams 설정',
418
+ properties: {
419
+ dateRange: {
420
+ type: 'object',
421
+ description: '날짜 범위 검색 설정',
422
+ properties: {
423
+ withPeriodType: { type: 'boolean', description: '기간구분 포함 여부' },
424
+ periodOptions: {
425
+ type: 'array',
426
+ description: '기간구분 옵션',
427
+ items: {
428
+ type: 'object',
429
+ properties: {
430
+ label: { type: 'string' },
431
+ value: { type: 'string' },
432
+ },
433
+ },
434
+ },
435
+ name: { type: 'string', description: '날짜 필드명 (기본: dateRange)' },
436
+ label: { type: 'string', description: '라벨 (기본: 등록일)' },
437
+ presetType: { type: 'string', enum: ['all', 'default'], description: '프리셋 타입' },
438
+ },
439
+ },
440
+ codes: {
441
+ type: 'array',
442
+ description: '코드 선택 필드 목록',
443
+ items: {
444
+ type: 'object',
445
+ properties: {
446
+ name: { type: 'string', description: '코드 필드명 (예: statusCd)' },
447
+ label: { type: 'string', description: '라벨 (예: 상태)' },
448
+ codeVar: { type: 'string', description: '코드 변수명 (예: STATUS_CODE)' },
449
+ width: { type: 'number', description: '너비 (기본: 150)' },
450
+ multi: { type: 'boolean', description: '다중 선택 여부' },
451
+ },
452
+ required: ['name', 'label'],
453
+ },
454
+ },
455
+ search: {
456
+ type: 'object',
457
+ description: '검색어 설정',
458
+ properties: {
459
+ withSearchType: { type: 'boolean', description: '검색구분 포함 여부' },
460
+ options: {
461
+ type: 'array',
462
+ description: '검색구분 옵션',
463
+ items: {
464
+ type: 'object',
465
+ properties: {
466
+ label: { type: 'string' },
467
+ value: { type: 'string' },
468
+ },
469
+ },
470
+ },
471
+ textName: { type: 'string', description: '검색어 필드명 (기본: searchText)' },
472
+ typeName: { type: 'string', description: '검색구분 필드명 (기본: searchType)' },
473
+ },
474
+ },
475
+ ynFields: {
476
+ type: 'array',
477
+ description: 'Y/N 필드 목록',
478
+ items: {
479
+ type: 'object',
480
+ properties: {
481
+ name: { type: 'string', description: 'Y/N 필드명 (예: useYn)' },
482
+ label: { type: 'string', description: '라벨 (예: 사용여부)' },
483
+ },
484
+ required: ['name', 'label'],
485
+ },
486
+ },
487
+ numberRanges: {
488
+ type: 'array',
489
+ description: '숫자 범위 필드 목록',
490
+ items: {
491
+ type: 'object',
492
+ properties: {
493
+ name: { type: 'string' },
494
+ label: { type: 'string' },
495
+ minPlaceholder: { type: 'string' },
496
+ maxPlaceholder: { type: 'string' },
497
+ },
498
+ required: ['name', 'label'],
499
+ },
500
+ },
501
+ lineBreak: { type: 'boolean', description: '줄바꿈 추가 여부' },
502
+ },
503
+ },
504
+ },
505
+ required: ['appPath', 'searchParams'],
506
+ },
507
+ },
508
+ {
509
+ name: 'generate_listdatagrid',
510
+ description: 'ListDataGrid.tsx 컴포넌트 생성 (NH-FE-B 패턴 기반)',
511
+ inputSchema: {
512
+ type: 'object',
513
+ properties: {
514
+ outputPath: {
515
+ type: 'string',
516
+ description: '생성할 ListDataGrid 파일 경로 (절대 경로)',
517
+ },
518
+ config: {
519
+ type: 'object',
520
+ description: 'ListDataGrid 설정',
521
+ properties: {
522
+ storeName: {
523
+ type: 'string',
524
+ description: 'Store 이름 (예: useProductListStore)',
525
+ },
526
+ dtoType: {
527
+ type: 'string',
528
+ description: 'DTO 타입 (예: ProductRes)',
529
+ },
530
+ rowKey: {
531
+ type: 'string',
532
+ description: '행 키 (예: id, prdCd)',
533
+ },
534
+ selectionMode: {
535
+ type: 'string',
536
+ enum: ['single', 'multi', 'none'],
537
+ description: '선택 모드',
538
+ },
539
+ withFormHeader: {
540
+ type: 'boolean',
541
+ description: 'FormHeader 포함 여부',
542
+ },
543
+ withExcel: {
544
+ type: 'boolean',
545
+ description: '엑셀 다운로드 포함 여부',
546
+ },
547
+ columns: {
548
+ type: 'array',
549
+ description: '컬럼 정의',
550
+ items: {
551
+ type: 'object',
552
+ properties: {
553
+ key: { type: 'string', description: '컬럼 키 (DTO 필드명)' },
554
+ label: { type: 'string', description: '라벨' },
555
+ type: {
556
+ type: 'string',
557
+ enum: ['rowNo', 'title', 'money', 'date', 'dateTime', 'user', 'selectable', 'custom'],
558
+ description: '컬럼 타입',
559
+ },
560
+ align: {
561
+ type: 'string',
562
+ enum: ['left', 'center', 'right'],
563
+ description: '정렬',
564
+ },
565
+ width: { type: 'number', description: '너비' },
566
+ codeVar: { type: 'string', description: '코드 변수명 (코드 변환시 사용)' },
567
+ itemRender: { type: 'string', description: 'itemRender 코드 (사용자 정의 렌더링)' },
568
+ },
569
+ required: ['key', 'label'],
570
+ },
571
+ },
572
+ onClickType: {
573
+ type: 'string',
574
+ enum: ['modal', 'link', 'select', 'none'],
575
+ description: 'onClickItem 동작 타입',
576
+ },
577
+ extraImports: {
578
+ type: 'array',
579
+ description: '추가적인 import',
580
+ items: { type: 'string' },
581
+ },
582
+ },
583
+ required: ['storeName', 'dtoType', 'rowKey', 'columns'],
584
+ },
585
+ },
586
+ required: ['outputPath', 'config'],
587
+ },
588
+ },
589
+ {
590
+ name: 'generate_hook',
591
+ description: '리포지토리 인터페이스를 분석하여 React Hook 파일 생성 (목록/조회 API만 포함)',
592
+ inputSchema: {
593
+ type: 'object',
594
+ properties: {
595
+ interfacePath: {
596
+ type: 'string',
597
+ description: '인터페이스 Repository 파일 경로 (절대 경로)',
598
+ },
599
+ outputPath: {
600
+ type: 'string',
601
+ description: '생성할 Hook 파일 경로 (절대 경로, 기본: src/hooks/use{Entity}Service.ts)',
602
+ },
603
+ },
604
+ required: ['interfacePath'],
605
+ },
606
+ },
607
+ {
608
+ name: 'generate_store_and_hook_interactive',
609
+ description: '[CRITICAL] 대화형 Store와 Hook 생성 - 반드시 3단계로 진행하세요:\n\n1단계: [첫 호출] fileName만 전달 → 파일 후보 목록 반환\n [CRITICAL] 1단계에서는 userSelections를 절대 포함하지 마세요!\n [CRITICAL] 1단계에서는 fileIndex를 선택하지 말고 사용자에게 번호를 입력받으세요!\n\n2단계: fileIndex 전달 → 경로 제안 반환\n - 제안된 경로로 생성: "확인", "진행" 등 → confirmed: true로 전달\n - 경로 변경 후 생성: "store는 src/stores/useBannerStore.ts로" 등 → storePath/hookPath로 전달\n\n3단계: (2단계에서 confirmed 또는 storePath/hookPath를 지정하면 즉시 생성됨)',
610
+ inputSchema: {
611
+ type: 'object',
612
+ properties: {
613
+ fileName: {
614
+ type: 'string',
615
+ description: 'Repository 파일명 또는 자연어 검색어 (예: MemberRepository, "쿠폰", "회원"). 1단계에서만 사용합니다.',
616
+ },
617
+ userSelections: {
618
+ type: 'object',
619
+ description: '[CRITICAL] 2/3단계 전용. [CRITICAL] 1단계(첫 호출)에서는 절대 포함하지 마세요! [CRITICAL] 사용자가 명시적으로 파일 번호를 입력하기 전까지는 fileIndex를 설정하지 마세요!',
620
+ properties: {
621
+ fileIndex: {
622
+ type: 'number',
623
+ description: '[2단계] 선택한 파일 번호 (1부터 시작). 여러 파일 후보 중 선택할 때 사용합니다.',
624
+ },
625
+ storePath: {
626
+ type: 'string',
627
+ description: '[2/3단계] Store 경로 (사용자가 경로를 직접 지정할 때 사용). 예: "src/stores/useBannerStore.ts"',
628
+ nullable: true,
629
+ },
630
+ hookPath: {
631
+ type: 'string',
632
+ description: '[2/3단계] Hook 경로 (사용자가 경로를 직접 지정할 때 사용). 예: "src/hooks/useBannerService.ts"',
633
+ nullable: true,
634
+ },
635
+ confirmed: {
636
+ type: 'boolean',
637
+ description: '[3단계 전용] 경로 확인 완료 플래그. 사용자가 "확인", "생성", "진행" 등 명시적으로 동의했을 때만 true로 설정하세요. [CRITICAL] 1/2단계에서는 절대 true로 설정하지 마세요. 기본값은 false입니다.',
638
+ },
639
+ },
640
+ },
641
+ projectRoot: {
642
+ type: 'string',
643
+ description: '프로젝트 루트 경로 (선택사항, 기본: 현재 작업 디렉토리)',
644
+ },
645
+ },
646
+ required: ['fileName'],
647
+ },
648
+ },
649
+ {
650
+ name: 'scan_metadata',
651
+ description: '프로젝트 메타데이터 스캔 (DTO, Repository, Code, Modal) → .mcp-cache/에 저장',
652
+ inputSchema: {
653
+ type: 'object',
654
+ properties: {
655
+ projectRoot: {
656
+ type: 'string',
657
+ description: '프로젝트 루트 경로 (절대 경로)',
658
+ },
659
+ forceRescan: {
660
+ type: 'boolean',
661
+ description: '강제 재스캔 여부 (기본: false, 캐시 있으면 사용)',
662
+ },
663
+ },
664
+ required: ['projectRoot'],
665
+ },
666
+ },
667
+ ],
668
+ };
669
+ });
670
+
671
+ // 도구 실행 핸들러
672
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
673
+ const { name, arguments: args } = request.params;
674
+
675
+ // args가 undefined인 경우 처리
676
+ const params = args || {};
677
+
678
+ try {
679
+ switch (name) {
680
+ case 'analyze_interface':
681
+ return await analyzeInterface(params as { interfacePath: string });
682
+
683
+ case 'generate_store':
684
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
685
+ return await generateStore(params as {
686
+ interfacePath: string;
687
+ outputPath: string;
688
+ storeType?: number;
689
+ storeName?: string;
690
+ });
691
+
692
+ case 'analyze_requirements':
693
+ return await analyzeRequirements(params as { requirements: string });
694
+
695
+ case 'generate_store_from_requirements':
696
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
697
+ return await generateStoreFromRequirements(params as {
698
+ interfacePath: string;
699
+ outputPath: string;
700
+ requirements: string;
701
+ });
702
+
703
+ case 'generate_multi_stores':
704
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
705
+ return await generateMultiStores({
706
+ interfacePath: (params as { interfacePath: string }).interfacePath,
707
+ outputPath: (params as { outputPath: string }).outputPath,
708
+ requirements: (params as { requirements: string }).requirements,
709
+ baseStoreName: (params as { baseStoreName: string }).baseStoreName,
710
+ });
711
+
712
+ case 'detect_multi_list_pattern':
713
+ const pattern = detectMultiListPattern((params as { requirements: string }).requirements);
714
+ return {
715
+ content: [
716
+ {
717
+ type: 'text',
718
+ text: JSON.stringify({
719
+ success: true,
720
+ isMultiList: pattern?.isMultiList || false,
721
+ pattern,
722
+ }),
723
+ },
724
+ ],
725
+ };
726
+
727
+ case 'generate_requirements_from_layout':
728
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
729
+ return await generateRequirementsFromLayout(params as {
730
+ interfacePath: string;
731
+ layoutType: string;
732
+ additional?: string;
733
+ });
734
+
735
+ case 'list_layouts':
736
+ return {
737
+ content: [
738
+ {
739
+ type: 'text',
740
+ text: JSON.stringify({
741
+ success: true,
742
+ layouts: getNHLayoutTypes(),
743
+ }),
744
+ },
745
+ ],
746
+ };
747
+
748
+ case 'suggest_layout':
749
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
750
+ return await suggestLayout(params as { interfacePath: string });
751
+
752
+ case 'generate_search_params':
753
+ await ensureScanned(extractProjectRoot((params as { appPath: string }).appPath));
754
+ return await generateSearchParams(params as {
755
+ appPath: string;
756
+ searchParams: import('./handlers/generate-search-params.js').SearchParamsConfig;
757
+ });
758
+
759
+ case 'generate_listdatagrid':
760
+ await ensureScanned(extractProjectRoot((params as { outputPath: string }).outputPath));
761
+ return await generateListDataGrid(params as {
762
+ outputPath: string;
763
+ config: import('./handlers/generate-listdatagrid.js').ListDataGridConfig;
764
+ });
765
+
766
+ case 'generate_hook':
767
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
768
+ return await generateHook(params as {
769
+ interfacePath: string;
770
+ outputPath?: string;
771
+ });
772
+
773
+ case 'generate_store_and_hook':
774
+ await ensureScanned(extractProjectRoot((params as { interfacePath: string }).interfacePath));
775
+ return await generateStoreAndHook(params as {
776
+ interfacePath: string;
777
+ storeOutputPath?: string;
778
+ hookOutputPath?: string;
779
+ storeName?: string;
780
+ });
781
+
782
+ case 'generate_store_and_hook_interactive':
783
+ const root = (params as { projectRoot?: string }).projectRoot || process.cwd();
784
+ await ensureScanned(root);
785
+ return await generateStoreAndHookInteractive(params as {
786
+ fileName: string;
787
+ userSelections?: {
788
+ fileIndex?: number;
789
+ storePath?: string | null;
790
+ hookPath?: string | null;
791
+ };
792
+ projectRoot?: string;
793
+ });
794
+
795
+ case 'scan_metadata':
796
+ return await scanMetadata(params as {
797
+ projectRoot: string;
798
+ forceRescan?: boolean;
799
+ });
800
+
801
+ default:
802
+ throw new Error(`알 수 없는 도구입니다: ${name}`);
803
+ }
804
+ } catch (error) {
805
+ return {
806
+ content: [
807
+ {
808
+ type: 'text',
809
+ text: JSON.stringify({
810
+ success: false,
811
+ error: error instanceof Error ? error.message : String(error),
812
+ }),
813
+ },
814
+ ],
815
+ isError: true,
816
+ };
817
+ }
818
+ });
819
+
820
+ /**
821
+ * Store와 Hook 동시 생성 (결과 요약)
822
+ */
823
+ async function generateStoreAndHook(args: {
824
+ interfacePath: string;
825
+ storeOutputPath?: string;
826
+ hookOutputPath?: string;
827
+ storeName?: string;
828
+ }) {
829
+ const { interfacePath, storeOutputPath, hookOutputPath, storeName } = args;
830
+
831
+ // 1. 인터페이스 분석 (entityName 추출)
832
+ const analyzeResult = await analyzeInterface({ interfacePath });
833
+ const analyzeData = JSON.parse(analyzeResult.content[0].text);
834
+
835
+ if (!analyzeData.success) {
836
+ return {
837
+ content: [{
838
+ type: 'text',
839
+ text: JSON.stringify({
840
+ success: false,
841
+ error: analyzeData.error || '인터페이스 분석 실패',
842
+ }),
843
+ }],
844
+ };
845
+ }
846
+
847
+ const entityName = analyzeData.className?.replace('Repository', '') || 'Generated';
848
+
849
+ // 2. 경로 설정 (각각 지정 또는 자동 추정)
850
+ const finalStorePath = storeOutputPath || path.join('src', 'stores', `use${entityName}ListStore.ts`);
851
+ const finalHookPath = hookOutputPath || path.join('src', 'hooks', `use${entityName}Service.ts`);
852
+
853
+ // 3. Store 생성
854
+ const storeResult = await generateStore({
855
+ interfacePath,
856
+ outputPath: finalStorePath,
857
+ storeType: 1, // 통합 템플릿이므로 고정
858
+ storeName: storeName || `use${entityName}ListStore`,
859
+ });
860
+
861
+ const storeData = JSON.parse(storeResult.content[0].text);
862
+
863
+ // 4. Hook 생성
864
+ const hookResult = await generateHook({
865
+ interfacePath,
866
+ outputPath: finalHookPath,
867
+ });
868
+
869
+ const hookData = JSON.parse(hookResult.content[0].text);
870
+
871
+ // 5. 결과 요약
872
+ return {
873
+ content: [{
874
+ type: 'text',
875
+ text: formatSummary(storeData, hookData, finalStorePath, finalHookPath, entityName),
876
+ }],
877
+ };
878
+ }
879
+
880
+ /**
881
+ * 결과 요약 포맷팅
882
+ */
883
+ function formatSummary(
884
+ storeData: any,
885
+ hookData: any,
886
+ storePath: string,
887
+ hookPath: string,
888
+ entityName: string
889
+ ): string {
890
+ const lines: string[] = [];
891
+
892
+ // 헤더
893
+ const hasErrors = storeData.typeErrors || hookData.typeErrors;
894
+ const icon = hasErrors ? '⚠️' : '✅';
895
+ const title = hasErrors ? 'Store 및 Hook 생성 완료 (일부 타입 오류)' : 'Store 및 Hook 생성 완료';
896
+
897
+ lines.push(`${icon} ${title}\n`);
898
+ lines.push(`엔티티: ${entityName}\n`);
899
+
900
+ // 생성된 파일
901
+ lines.push('생성된 파일:');
902
+ lines.push(`- ${storePath} (Store)`);
903
+ lines.push(`${hookPath} (Hook)\n`);
904
+
905
+ // 상태 요약
906
+ const storeStatus = storeData.typeErrors
907
+ ? `Store: 타입 오류 ${storeData.typeErrors.length}건`
908
+ : 'Store: 타입 체크 통과';
909
+ lines.push(storeStatus);
910
+
911
+ const hookStatus = hookData.typeErrors
912
+ ? `Hook: 타입 오류 ${hookData.typeErrors.length}건`
913
+ : 'Hook: 타입 체크 통과';
914
+ lines.push(hookStatus);
915
+
916
+ // 타입 오류가 있으면 추가
917
+ const allErrors = [
918
+ ...(storeData.typeErrors || []).map((e: string) => `[Store] ${e}`),
919
+ ...(hookData.typeErrors || []).map((e: string) => `[Hook] ${e}`),
920
+ ];
921
+
922
+ if (allErrors.length > 0) {
923
+ lines.push('\n타입 오류:');
924
+ allErrors.forEach(error => lines.push(`- ${error}`));
925
+ }
926
+
927
+ return lines.join('\n');
928
+ }
929
+
930
+ // 서버 시작
931
+ async function main() {
932
+ const transport = new StdioServerTransport();
933
+ await server.connect(transport);
934
+ console.error('mcp-axboot server started');
935
+
936
+ // 시작 시 현재 작업 디렉토리 스캔 (백그라운드)
937
+ const cwd = process.cwd();
938
+
939
+ // 프로젝트 루트 확인 (package.json, src 등이 있으면)
940
+ const isProjectRoot = [
941
+ path.join(cwd, 'package.json'),
942
+ path.join(cwd, 'tsconfig.json'),
943
+ path.join(cwd, 'src'),
944
+ ].some(marker => existsSync(marker));
945
+
946
+ if (isProjectRoot) {
947
+ // 백그라운드에서 즉시 스캔 시작 (await 안 함)
948
+ ensureScanned(cwd).then(() => {
949
+ console.error(`[MCP] 백그라운드 스캔 완료: ${cwd}`);
950
+ }).catch(() => {
951
+ // 무시
952
+ });
953
+ }
954
+ }
955
+
956
+ main().catch((error) => {
957
+ console.error('Server error:', error);
958
+ process.exit(1);
959
+ });