egovframe-scaffold-mcp 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -23,6 +23,7 @@ Claude, VS Code(Copilot), Cursor 등 MCP를 지원하는 AI 도구에서 **대
23
23
  | `remove_egovframe_components` | 설치 매니페스트 기반 컴포넌트 제거 — 의존 컴포넌트 보호, `dryRun` 미리보기 |
24
24
  | `validate_egovframe_project` | 조립 프로젝트 무결성 진단 — 파일 존재·DbType↔DB 스크립트 일치 |
25
25
  | `get_egovframe_guide` | 컴포넌트의 공식 가이드 문서 조회 (egovframe-docs, 45종 매핑) |
26
+ | `add_ai_components` | **(신규, M1 미리보기)** 공식 [egovframe-ai-rag](https://github.com/eGovFramework/egovframe-ai-rag) 샘플 기반 AI RAG 챗봇 조립 — Spring AI(Redis Stack)·LangChain4j(PGVector) 스택 선택, 파일 복사 계획·pom 의존성 diff·호환성 진단 (설계: [docs/design-ai-components.md](docs/design-ai-components.md)) |
26
27
 
27
28
  ### create_egovframe_project 파라미터
28
29
 
@@ -106,11 +107,13 @@ Claude Desktop / Claude Code 설정 예 (`mcpServers`):
106
107
 
107
108
  1. 공통컴포넌트 선택 설치 — **M3 완료(v0.4.0)**: 저장소 스캔 기반 카탈로그 자동 생성(`npm run generate:catalog`, 68종) + 조립 파이프라인. 남은 항목: 컴포넌트별 테이블 선별 DDL, 이름·의존성 큐레이션 확대(`catalog/overrides.json` 기여 환영) (설계: [docs/design-components-parameter.md](docs/design-components-parameter.md))
108
109
  2. 실행환경 보일러플레이트(egovframe-msa 등) 템플릿 추가
109
- 3. 검증 도구(`validate_egovframe_config`) 등 후속 도구
110
+ 3. AI 컴포넌트 조립(`add_ai_components`) — **M1 완료(v0.8.0)**: AI 카탈로그(egovframe-ai-rag 스캔 자동 생성) + dryRun 미리보기(의존성 diff·호환성 게이트). M2(v0.9.0): 실조립(복사+pom 병합+설정 프로필화) (설계: [docs/design-ai-components.md](docs/design-ai-components.md))
110
111
  4. 검증 후 eGovFramework 조직 공식 저장소 편입 제안
111
112
 
112
113
  ## 변경 이력
113
114
 
115
+ - **0.8.0** — AI 컴포넌트 조립 M1: `add_ai_components` dryRun 미리보기(파일 복사 계획·pom 의존성 diff·부모 POM 호환성 게이트·스택 상호 배타 검사), AI 카탈로그(`catalog/ai-components.json`, egovframe-ai-rag 모듈 스캔 자동 생성 `npm run generate:ai-catalog`), `list_egovframe_components`에 AI 컴포넌트 노출, 오프라인 테스트(`npm run test:ai`) 추가.
116
+
114
117
  - **0.7.0** — `get_egovframe_guide`: 컴포넌트 id로 표준프레임워크 공식 가이드 문서(egovframe-docs)를 조회. 카탈로그에 문서 매핑 자동 생성(`--docs`, 지배적 패키지 참조 기준 45종) 추가. 한글명 큐레이션 12→27종.
115
118
 
116
119
  - **0.6.0** — 컴포넌트별 테이블 선별 DDL(M4): 카탈로그에 매퍼 기반 참조 테이블 자동 추출(48/68종), `database` 지정 시 통합 스크립트에서 해당 컴포넌트 구문만 추출해 `ddl|dml/<컴포넌트id>.sql` 생성(테이블 미상 컴포넌트는 통합본 폴백), 매니페스트에 컴포넌트별 스크립트 귀속(제거 시 함께 정리).
@@ -0,0 +1,369 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "source": {
4
+ "repo": "eGovFramework/egovframe-ai-rag",
5
+ "branch": "main",
6
+ "surveyedAt": "2026-07-12"
7
+ },
8
+ "note": "add_ai_components용 카탈로그 — 조립 대상은 egovframe-boot-starter-parent 기반 Boot 프로젝트. 두 스택은 같은 패키지(com.example.chat)·UI 경로를 사용하므로 상호 배타(conflictsWith).",
9
+ "components": [
10
+ {
11
+ "id": "ai-rag-spring-ai",
12
+ "stack": "spring-ai",
13
+ "kind": "ai",
14
+ "name": "AI RAG 챗봇 (Spring AI + Redis Stack)",
15
+ "description": "Spring AI 기반 RAG 질의응답 — 문서 업로드(PDF·DOCX·HWP·HWPX·MD)→임베딩→Redis Stack 하이브리드 검색→Ollama LLM 응답, 채팅 UI 포함",
16
+ "modulePath": "spring-ai-rag-redis-stack",
17
+ "vectorStore": "Redis Stack",
18
+ "conflictsWith": [
19
+ "ai-rag-langchain4j"
20
+ ],
21
+ "requires": {
22
+ "java": "17",
23
+ "parent": "egovframe-boot-starter-parent:5.0.0"
24
+ },
25
+ "copyGroups": {
26
+ "source": {
27
+ "paths": [
28
+ "src/main/java/com/example/chat/"
29
+ ],
30
+ "files": 45
31
+ },
32
+ "config": {
33
+ "paths": [
34
+ "src/main/resources/application.yml -> src/main/resources/application-ai.yml",
35
+ "src/main/resources/log4j2-spring.xml -> src/main/resources/log4j2-ai.xml"
36
+ ],
37
+ "files": 2
38
+ },
39
+ "ui": {
40
+ "paths": [
41
+ "src/main/resources/templates/",
42
+ "src/main/resources/static/"
43
+ ],
44
+ "files": 3
45
+ },
46
+ "infra": {
47
+ "paths": [
48
+ "docker-compose.yml -> docker-compose.ai.yml",
49
+ "Dockerfile -> Dockerfile.ai",
50
+ "k8s/ -> k8s/ai/"
51
+ ],
52
+ "files": 6
53
+ },
54
+ "tests": {
55
+ "paths": [
56
+ "src/test/"
57
+ ],
58
+ "files": 15
59
+ }
60
+ },
61
+ "approxFiles": 56,
62
+ "mavenDependencies": [
63
+ {
64
+ "groupId": "org.springframework.boot",
65
+ "artifactId": "spring-boot-starter-actuator",
66
+ "exclusions": [
67
+ {
68
+ "groupId": "org.springframework.boot",
69
+ "artifactId": "spring-boot-starter-logging"
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "groupId": "org.springframework.ai",
75
+ "artifactId": "spring-ai-client-chat"
76
+ },
77
+ {
78
+ "groupId": "org.springframework.ai",
79
+ "artifactId": "spring-ai-rag"
80
+ },
81
+ {
82
+ "groupId": "org.springframework.ai",
83
+ "artifactId": "spring-ai-starter-vector-store-redis"
84
+ },
85
+ {
86
+ "groupId": "org.springframework.ai",
87
+ "artifactId": "spring-ai-starter-model-ollama"
88
+ },
89
+ {
90
+ "groupId": "org.springframework.ai",
91
+ "artifactId": "spring-ai-advisors-vector-store"
92
+ },
93
+ {
94
+ "groupId": "org.springframework.ai",
95
+ "artifactId": "spring-ai-starter-model-transformers"
96
+ },
97
+ {
98
+ "groupId": "org.springframework.ai",
99
+ "artifactId": "spring-ai-pdf-document-reader"
100
+ },
101
+ {
102
+ "groupId": "org.springframework.ai",
103
+ "artifactId": "spring-ai-markdown-document-reader"
104
+ },
105
+ {
106
+ "groupId": "org.egovframe.rte",
107
+ "artifactId": "egovframe-rte-fdl-excel"
108
+ },
109
+ {
110
+ "groupId": "org.springframework.boot",
111
+ "artifactId": "spring-boot-starter-thymeleaf",
112
+ "exclusions": [
113
+ {
114
+ "groupId": "org.springframework.boot",
115
+ "artifactId": "spring-boot-starter-logging"
116
+ }
117
+ ]
118
+ },
119
+ {
120
+ "groupId": "org.springframework.boot",
121
+ "artifactId": "spring-boot-starter-web",
122
+ "exclusions": [
123
+ {
124
+ "groupId": "org.springframework.boot",
125
+ "artifactId": "spring-boot-starter-logging"
126
+ }
127
+ ]
128
+ },
129
+ {
130
+ "groupId": "org.egovframe.rte",
131
+ "artifactId": "egovframe-rte-ptl-mvc",
132
+ "exclusions": [
133
+ {
134
+ "groupId": "org.apache.commons",
135
+ "artifactId": "commons-lang3"
136
+ }
137
+ ]
138
+ },
139
+ {
140
+ "groupId": "kr.dogfoot",
141
+ "artifactId": "hwplib",
142
+ "version": "1.1.9"
143
+ },
144
+ {
145
+ "groupId": "org.projectlombok",
146
+ "artifactId": "lombok",
147
+ "optional": true
148
+ },
149
+ {
150
+ "groupId": "kr.dogfoot",
151
+ "artifactId": "hwpxlib",
152
+ "version": "1.0.8"
153
+ },
154
+ {
155
+ "groupId": "org.springframework.boot",
156
+ "artifactId": "spring-boot-starter-test",
157
+ "scope": "test"
158
+ }
159
+ ],
160
+ "mavenProperties": {},
161
+ "prerequisites": [
162
+ "ollama>=0.17.1",
163
+ "onnx-embedding-model",
164
+ "docker"
165
+ ]
166
+ },
167
+ {
168
+ "id": "ai-rag-langchain4j",
169
+ "stack": "langchain4j",
170
+ "kind": "ai",
171
+ "name": "AI RAG 챗봇 (LangChain4j + PGVector)",
172
+ "description": "LangChain4j 기반 RAG 질의응답 — 문서 업로드(PDF·DOCX·HWP·HWPX·MD)→임베딩→PostgreSQL(PGVector) 하이브리드 검색→Ollama LLM 응답, 채팅 UI 포함",
173
+ "modulePath": "langchain4j-ai-rag-postgre",
174
+ "vectorStore": "PostgreSQL (PGVector)",
175
+ "conflictsWith": [
176
+ "ai-rag-spring-ai"
177
+ ],
178
+ "requires": {
179
+ "java": "17",
180
+ "parent": "egovframe-boot-starter-parent:5.0.0"
181
+ },
182
+ "copyGroups": {
183
+ "source": {
184
+ "paths": [
185
+ "src/main/java/com/example/chat/"
186
+ ],
187
+ "files": 49
188
+ },
189
+ "config": {
190
+ "paths": [
191
+ "src/main/resources/application.yml -> src/main/resources/application-ai.yml",
192
+ "src/main/resources/log4j2-spring.xml -> src/main/resources/log4j2-ai.xml"
193
+ ],
194
+ "files": 2
195
+ },
196
+ "ui": {
197
+ "paths": [
198
+ "src/main/resources/templates/",
199
+ "src/main/resources/static/"
200
+ ],
201
+ "files": 3
202
+ },
203
+ "infra": {
204
+ "paths": [
205
+ "docker-compose.yml -> docker-compose.ai.yml",
206
+ "Dockerfile -> Dockerfile.ai",
207
+ "k8s/ -> k8s/ai/",
208
+ "init-scripts/ -> init-scripts/ai/"
209
+ ],
210
+ "files": 9
211
+ },
212
+ "tests": {
213
+ "paths": [
214
+ "src/test/"
215
+ ],
216
+ "files": 14
217
+ }
218
+ },
219
+ "approxFiles": 63,
220
+ "mavenDependencies": [
221
+ {
222
+ "groupId": "org.springframework.boot",
223
+ "artifactId": "spring-boot-starter-actuator",
224
+ "exclusions": [
225
+ {
226
+ "groupId": "org.springframework.boot",
227
+ "artifactId": "spring-boot-starter-logging"
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "groupId": "dev.langchain4j",
233
+ "artifactId": "langchain4j"
234
+ },
235
+ {
236
+ "groupId": "dev.langchain4j",
237
+ "artifactId": "langchain4j-ollama"
238
+ },
239
+ {
240
+ "groupId": "dev.langchain4j",
241
+ "artifactId": "langchain4j-pgvector"
242
+ },
243
+ {
244
+ "groupId": "dev.langchain4j",
245
+ "artifactId": "langchain4j-embeddings"
246
+ },
247
+ {
248
+ "groupId": "dev.langchain4j",
249
+ "artifactId": "langchain4j-document-parser-apache-pdfbox"
250
+ },
251
+ {
252
+ "groupId": "org.egovframe.rte",
253
+ "artifactId": "egovframe-rte-fdl-excel",
254
+ "exclusions": [
255
+ {
256
+ "groupId": "org.apache.commons",
257
+ "artifactId": "commons-lang3"
258
+ }
259
+ ]
260
+ },
261
+ {
262
+ "groupId": "dev.langchain4j",
263
+ "artifactId": "langchain4j-reactor"
264
+ },
265
+ {
266
+ "groupId": "com.h2database",
267
+ "artifactId": "h2",
268
+ "version": "2.2.224",
269
+ "scope": "test"
270
+ },
271
+ {
272
+ "groupId": "org.postgresql",
273
+ "artifactId": "postgresql",
274
+ "scope": "runtime"
275
+ },
276
+ {
277
+ "groupId": "org.testcontainers",
278
+ "artifactId": "postgresql",
279
+ "scope": "test"
280
+ },
281
+ {
282
+ "groupId": "org.testcontainers",
283
+ "artifactId": "junit-jupiter",
284
+ "scope": "test"
285
+ },
286
+ {
287
+ "groupId": "org.springframework.boot",
288
+ "artifactId": "spring-boot-starter-thymeleaf",
289
+ "exclusions": [
290
+ {
291
+ "groupId": "org.springframework.boot",
292
+ "artifactId": "spring-boot-starter-logging"
293
+ }
294
+ ]
295
+ },
296
+ {
297
+ "groupId": "org.springframework.boot",
298
+ "artifactId": "spring-boot-starter-web",
299
+ "exclusions": [
300
+ {
301
+ "groupId": "org.springframework.boot",
302
+ "artifactId": "spring-boot-starter-logging"
303
+ }
304
+ ]
305
+ },
306
+ {
307
+ "groupId": "org.springframework.boot",
308
+ "artifactId": "spring-boot-starter-webflux",
309
+ "exclusions": [
310
+ {
311
+ "groupId": "org.springframework.boot",
312
+ "artifactId": "spring-boot-starter-logging"
313
+ }
314
+ ]
315
+ },
316
+ {
317
+ "groupId": "org.springframework.boot",
318
+ "artifactId": "spring-boot-starter-data-jpa",
319
+ "exclusions": [
320
+ {
321
+ "groupId": "org.springframework.boot",
322
+ "artifactId": "spring-boot-starter-logging"
323
+ }
324
+ ]
325
+ },
326
+ {
327
+ "groupId": "org.egovframe.rte",
328
+ "artifactId": "egovframe-rte-ptl-mvc",
329
+ "exclusions": [
330
+ {
331
+ "groupId": "org.apache.commons",
332
+ "artifactId": "commons-lang3"
333
+ }
334
+ ]
335
+ },
336
+ {
337
+ "groupId": "commons-codec",
338
+ "artifactId": "commons-codec"
339
+ },
340
+ {
341
+ "groupId": "kr.dogfoot",
342
+ "artifactId": "hwplib",
343
+ "version": "1.1.9"
344
+ },
345
+ {
346
+ "groupId": "kr.dogfoot",
347
+ "artifactId": "hwpxlib",
348
+ "version": "1.0.5"
349
+ },
350
+ {
351
+ "groupId": "org.projectlombok",
352
+ "artifactId": "lombok",
353
+ "optional": true
354
+ },
355
+ {
356
+ "groupId": "org.springframework.boot",
357
+ "artifactId": "spring-boot-starter-test",
358
+ "scope": "test"
359
+ }
360
+ ],
361
+ "mavenProperties": {},
362
+ "prerequisites": [
363
+ "ollama>=0.17.1",
364
+ "onnx-embedding-model",
365
+ "docker"
366
+ ]
367
+ }
368
+ ]
369
+ }
package/dist/index.js CHANGED
@@ -584,10 +584,117 @@ export async function getGuide(componentId, docIndex = 0) {
584
584
  return { componentId, docs, selected: sel, content, truncated };
585
585
  }
586
586
  /* ------------------------------------------------------------------ */
587
+ /* AI 컴포넌트 조립 (M1: dryRun 미리보기) — 설계: docs/design-ai-components.md */
588
+ /* 소스: eGovFramework/egovframe-ai-rag (Spring AI·LangChain4j RAG 샘플) */
589
+ /* ------------------------------------------------------------------ */
590
+ export const AI_STACKS = ["spring-ai", "langchain4j"];
591
+ const AI_CATALOG_URL = new URL("../catalog/ai-components.json", import.meta.url);
592
+ /** AI 카탈로그 로드 + 무결성 검증 (id 중복, conflictsWith 대상 존재, stack 유일) */
593
+ export function loadAiCatalog() {
594
+ const catalog = JSON.parse(fs.readFileSync(AI_CATALOG_URL, "utf-8"));
595
+ const ids = new Set();
596
+ const stacks = new Set();
597
+ for (const c of catalog.components) {
598
+ if (ids.has(c.id))
599
+ throw new Error(`AI 카탈로그 오류: 중복 id '${c.id}'`);
600
+ ids.add(c.id);
601
+ if (stacks.has(c.stack))
602
+ throw new Error(`AI 카탈로그 오류: 중복 stack '${c.stack}'`);
603
+ stacks.add(c.stack);
604
+ }
605
+ for (const c of catalog.components)
606
+ for (const x of c.conflictsWith)
607
+ if (!ids.has(x))
608
+ throw new Error(`AI 카탈로그 오류: '${c.id}'의 conflictsWith '${x}'가 카탈로그에 없습니다`);
609
+ return catalog;
610
+ }
611
+ /**
612
+ * AI 컴포넌트 조립 계획 (M1) — dryRun 미리보기 전용.
613
+ * 파일 복사 계획·pom 의존성 diff·호환성 게이트 결과를 네트워크 없이 반환한다.
614
+ * 실제 조립(파일 복사 + pom 병합 + 설정 프로필화)은 M2(v0.9.0)에서 제공한다.
615
+ */
616
+ export async function planAiComponents(opts) {
617
+ if (opts.dryRun === false)
618
+ throw new Error("add_ai_components는 현재 dryRun 미리보기만 지원합니다 — 실제 조립은 M2(v0.9.0)에서 제공됩니다 (설계: docs/design-ai-components.md)");
619
+ if (!AI_STACKS.includes(opts.stack))
620
+ throw new Error(`stack은 ${AI_STACKS.join("|")} 중 하나여야 합니다: ${String(opts.stack)}`);
621
+ const catalog = loadAiCatalog();
622
+ const comp = catalog.components.find((c) => c.stack === opts.stack);
623
+ const projectDir = path.resolve(opts.projectDir);
624
+ // 매니페스트 게이트: 동일/배타 스택 설치 여부
625
+ const manifest = readManifest(projectDir);
626
+ if (manifest) {
627
+ if (manifest.components[comp.id])
628
+ throw new Error(`'${comp.id}'가 이미 설치되어 있습니다 (매니페스트 기준)`);
629
+ for (const x of comp.conflictsWith)
630
+ if (manifest.components[x])
631
+ throw new Error(`상호 배타 컴포넌트 '${x}'가 이미 설치되어 있습니다 — 두 AI 스택은 같은 패키지(com.example.chat)·UI 경로를 사용합니다. 먼저 remove_egovframe_components로 제거하세요`);
632
+ }
633
+ // 호환성 게이트: 부모 POM 좌표 확인 + 의존성 diff
634
+ const warnings = [];
635
+ const pomPath = path.join(projectDir, "pom.xml");
636
+ const pomFound = fs.existsSync(pomPath);
637
+ let parentFound = null;
638
+ let parentOk = null;
639
+ const toAdd = [];
640
+ const alreadyPresent = [];
641
+ if (pomFound) {
642
+ const pom = fs.readFileSync(pomPath, "utf-8");
643
+ const pm = pom.match(/<parent>[\s\S]*?<\/parent>/);
644
+ if (pm) {
645
+ const a = pm[0].match(/<artifactId>([^<]*)<\/artifactId>/)?.[1]?.trim();
646
+ const v = pm[0].match(/<version>([^<]*)<\/version>/)?.[1]?.trim();
647
+ parentFound = a ? `${a}:${v ?? "?"}` : null;
648
+ }
649
+ const [reqA, reqV] = comp.requires.parent.split(":");
650
+ parentOk = parentFound !== null && parentFound.startsWith(`${reqA}:`);
651
+ if (!parentOk)
652
+ warnings.push(`부모 POM이 '${comp.requires.parent}'가 아닙니다(발견: ${parentFound ?? "없음"}) — Boot 기반 템플릿(simple-backend)에서 지원합니다`);
653
+ else if (parentFound !== comp.requires.parent)
654
+ warnings.push(`부모 POM 버전이 다릅니다(요구 ${reqV}, 발견 ${parentFound}) — BOM 관리 버전 차이를 확인하세요`);
655
+ for (const d of comp.mavenDependencies) {
656
+ const coord = `${d.groupId}:${d.artifactId}${d.version ? ":" + d.version : ""}${d.scope ? " (" + d.scope + ")" : ""}`;
657
+ if (pom.includes(`<artifactId>${d.artifactId}</artifactId>`))
658
+ alreadyPresent.push(coord);
659
+ else
660
+ toAdd.push(coord);
661
+ }
662
+ }
663
+ else {
664
+ warnings.push("pom.xml이 없습니다 — Boot 백엔드 프로젝트 루트 경로인지 확인하세요");
665
+ }
666
+ // 복사 계획: source·config 필수, ui/infra/tests는 옵션
667
+ const groups = ["source", "config"];
668
+ if (opts.includeUi !== false)
669
+ groups.push("ui");
670
+ if (opts.includeInfra !== false)
671
+ groups.push("infra");
672
+ if (opts.includeTests === true)
673
+ groups.push("tests");
674
+ const copyPlan = groups.map((g) => ({ group: g, files: comp.copyGroups[g].files, paths: comp.copyGroups[g].paths }));
675
+ const totalFiles = copyPlan.reduce((n, g) => n + g.files, 0);
676
+ return {
677
+ projectDir,
678
+ component: { id: comp.id, name: comp.name, vectorStore: comp.vectorStore },
679
+ compatibility: { pomFound, required: comp.requires.parent, parentFound, parentOk, warnings },
680
+ dependencyChanges: { toAdd, alreadyPresent },
681
+ copyPlan,
682
+ totalFiles,
683
+ prerequisites: comp.prerequisites,
684
+ nextSteps: [
685
+ "M1은 미리보기 전용입니다 — 실제 조립은 M2(v0.9.0)에서 제공됩니다",
686
+ "Ollama(>=0.17.1) 설치 및 LLM 모델 준비 (폐쇄망 절차는 egovframe-ai-rag README 참조)",
687
+ "ONNX 임베딩 모델 익스포트·배치",
688
+ "docker compose로 벡터 저장소(Redis Stack 또는 PGVector) 기동",
689
+ ],
690
+ dryRun: true,
691
+ };
692
+ }
693
+ /* ------------------------------------------------------------------ */
587
694
  /* MCP 서버 */
588
695
  /* ------------------------------------------------------------------ */
589
696
  export function buildServer() {
590
- const server = new McpServer({ name: "egovframe-scaffold-mcp", version: "0.7.0" });
697
+ const server = new McpServer({ name: "egovframe-scaffold-mcp", version: "0.8.0" });
591
698
  server.tool("list_egovframe_templates", "사용 가능한 전자정부 표준프레임워크 프로젝트 템플릿 목록을 반환합니다.", {}, async () => ({
592
699
  content: [
593
700
  {
@@ -625,6 +732,17 @@ export function buildServer() {
625
732
  });
626
733
  server.tool("list_egovframe_components", "선택 설치를 지원하는 공통컴포넌트 카탈로그를 반환합니다 (저장소 스캔으로 자동 생성, scripts/generate-catalog.mjs).", {}, async () => {
627
734
  const catalog = loadCatalog();
735
+ let ai;
736
+ try {
737
+ const aiCat = loadAiCatalog();
738
+ ai = {
739
+ source: aiCat.source,
740
+ components: aiCat.components.map((c) => ({
741
+ id: c.id, stack: c.stack, name: c.name, description: c.description, approxFiles: c.approxFiles,
742
+ })),
743
+ };
744
+ }
745
+ catch { /* AI 카탈로그가 없어도 기본 목록은 동작 */ }
628
746
  return {
629
747
  content: [
630
748
  {
@@ -632,6 +750,7 @@ export function buildServer() {
632
750
  text: JSON.stringify({
633
751
  source: catalog.source,
634
752
  sqlNote: catalog.sqlNote,
753
+ aiComponents: ai,
635
754
  components: catalog.components.map((c) => ({
636
755
  id: c.id, name: c.name, category: c.category,
637
756
  description: c.description, dependsOn: c.dependsOn, approxFiles: c.approxFiles,
@@ -726,6 +845,36 @@ export function buildServer() {
726
845
  ].filter((l, i) => l !== "" || i >= 4).join("\n");
727
846
  return { content: [{ type: "text", text: head + r.content }] };
728
847
  });
848
+ server.tool("add_ai_components", "공식 egovframe-ai-rag 샘플 기반 AI RAG 챗봇(문서 업로드→임베딩→하이브리드 검색→LLM 응답)을 기존 Boot 프로젝트에 조립합니다. " +
849
+ "현재 M1은 dryRun 미리보기 전용 — 파일 복사 계획·pom 의존성 diff·호환성 진단을 반환합니다 (설계: docs/design-ai-components.md).", {
850
+ projectDir: z.string().describe("대상 프로젝트 디렉터리(절대경로 권장). egovframe-boot-starter-parent 기반 Boot 프로젝트"),
851
+ stack: z.enum(AI_STACKS).describe("AI 스택: spring-ai(Redis Stack) | langchain4j(PGVector). 상호 배타"),
852
+ includeInfra: z.boolean().default(true).describe("docker-compose·Dockerfile·k8s 복사 계획 포함"),
853
+ includeUi: z.boolean().default(true).describe("채팅 UI(chat.html·static) 복사 계획 포함"),
854
+ includeTests: z.boolean().default(false).describe("샘플 테스트 복사 계획 포함"),
855
+ dryRun: z.boolean().default(true).describe("M1에서는 true만 지원 (실조립은 M2/v0.9.0)"),
856
+ }, async (args) => {
857
+ const r = await planAiComponents(args);
858
+ const c = r.compatibility;
859
+ const text = [
860
+ `🔍 AI 컴포넌트 조립 미리보기(dryRun): ${r.projectDir}`,
861
+ `- 컴포넌트: ${r.component.id} — ${r.component.name}`,
862
+ `- 호환성: 부모 POM ${c.parentOk === true ? "일치" : c.parentOk === false ? "불일치" : "미확인"}` +
863
+ ` (요구 ${c.required}${c.parentFound ? ", 발견 " + c.parentFound : ""})`,
864
+ ...(c.warnings.length ? c.warnings.map((w) => ` ! ${w}`) : []),
865
+ `- pom 의존성: 추가 예정 ${r.dependencyChanges.toAdd.length}건` +
866
+ (r.dependencyChanges.alreadyPresent.length ? `, 이미 존재 ${r.dependencyChanges.alreadyPresent.length}건` : ""),
867
+ ...r.dependencyChanges.toAdd.slice(0, 8).map((d) => ` + ${d}`),
868
+ ...(r.dependencyChanges.toAdd.length > 8 ? [` + … 외 ${r.dependencyChanges.toAdd.length - 8}건`] : []),
869
+ `- 복사 계획 (총 ${r.totalFiles}개 파일):`,
870
+ ...r.copyPlan.map((g) => ` · ${g.group}: ${g.files}개 — ${g.paths.join(", ")}`),
871
+ `- 실행 전제: ${r.prerequisites.join(", ")}`,
872
+ ``,
873
+ `다음 단계:`,
874
+ ...r.nextSteps.map((s, i) => ` ${i + 1}. ${s}`),
875
+ ].join("\n");
876
+ return { content: [{ type: "text", text }] };
877
+ });
729
878
  return server;
730
879
  }
731
880
  const isMain = process.argv[1] && import.meta.url.endsWith(path.basename(process.argv[1]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egovframe-scaffold-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "전자정부 표준프레임워크(eGovFrame) 프로젝트 스캐폴딩·공통컴포넌트 조립 MCP 서버 — create/search/add/validate/remove",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -15,7 +15,9 @@
15
15
  "test:catalog": "node test/catalog.mjs",
16
16
  "test:components": "node test/components.mjs",
17
17
  "generate:catalog": "node scripts/generate-catalog.mjs",
18
- "prepublishOnly": "npm run build && npm run test:catalog"
18
+ "prepublishOnly": "npm run build && npm run test:catalog && npm run test:ai",
19
+ "test:ai": "node test/ai.mjs",
20
+ "generate:ai-catalog": "node scripts/generate-ai-catalog.mjs"
19
21
  },
20
22
  "engines": {
21
23
  "node": ">=18"