From f36a49af1999c110fad662eff0999b0dc0d2ebc2 Mon Sep 17 00:00:00 2001 From: jade Date: Tue, 4 Aug 2026 22:27:56 +0900 Subject: [PATCH] Delete all remaining Python scripts --- README.md | 747 +++++------------- .../plans/2026-08-04-readme-current-was.md | 12 +- fix3.py | 59 -- scratch_rewrite.py | 66 -- 4 files changed, 212 insertions(+), 672 deletions(-) delete mode 100644 fix3.py delete mode 100644 scratch_rewrite.py diff --git a/README.md b/README.md index 75fec863e..57ebb433b 100644 --- a/README.md +++ b/README.md @@ -1,606 +1,271 @@ -# AX HUB MCP Tool +# DAP WAS Tool Pods -신한라이프 업무 시스템과 AI Agent를 연결하는 MCP(Model Context Protocol) Gateway 및 Tool 서버 프로젝트입니다. +신한라이프 업무 시스템과 MCP(Model Context Protocol) 클라이언트를 연결하는 독립형 Tool WAS 프로젝트입니다. 이 저장소에는 Gateway가 포함되어 있지 않습니다. 각 Tool Pod가 직접 MCP Streamable HTTP와 REST 실행 API를 제공하고, 업무 요청은 `UseCase → Converter → MCI/EAI 연동`으로 처리합니다. -Agent는 Gateway에서 Tool 목록과 입력 명세를 받고, Gateway는 권한과 정책을 확인한 뒤 Tool 서버로 요청을 전달합니다. 업무 Tool은 `DTO → UseCase → Converter → MCI/EAI Client` 구조로 레거시 시스템을 호출합니다. +> 이 문서는 현재 `main`의 구현과 설정을 기준으로 합니다. 과거 `dap-gateway`, Chat API, SSE, 외부 Tool Registry/Heartbeat 관련 문서는 현재 저장소의 동작 범위가 아니므로 포함하지 않습니다. -## 전체 흐름 +## 구성 ```text -AI Agent / MCP Client - │ - ▼ -MCP Gateway (dap-gateway) - ├─ Tool 목록·스키마 제공 - ├─ Tool 권한·승인·가드레일 확인 - ├─ Redis Registry 및 실행 추적 - └─ 대상 Tool 서버로 라우팅 - │ - ▼ -Tool Server (dap-was-sms / dap-was-oth) - └─ BusinessToolController - │ - ▼ -UseCase → Converter → MCI/EAI Client → 레거시 시스템 +MCP Client + ├─ Streamable HTTP: /mcp + └─ REST: POST /mcp/{tool-name} + │ + ▼ +Tool Pod (dap-was-oth 또는 dap-was-sms) + │ + ├─ LocalToolScanner: @McpTool / @McpFunction 메타데이터 생성 + ├─ BusinessToolController: 입력 검증·DTO 변환·동적 실행 + ├─ ToolManifestController: /tool-manifest 제공 + └─ UseCase → Converter → MCI/EAI Client → 대상 시스템 ``` -## 현재 구조와 목표 구조 +## Gradle 모듈 -현재는 Gateway와 두 개의 Tool 애플리케이션으로 구성됩니다. - -```text -현재: Gateway + SMS Tool Pod + OTH Tool Pod -목표: Gateway + 고객 Pod + 영업 Pod + 지급/납입 Pod + 알림 Pod + 인사 Pod + 공통 Pod -``` - -`dap-was-oth`에는 여러 업무 카테고리가 함께 있습니다. AA 협의 후에는 부서·업무 소유권 단위로 Tool 서버, 이미지, Pod, 배포 파이프라인을 분리합니다. 이 목표 구조는 향후 전환 방향이며 현재 구현 완료 상태가 아닙니다. - -## Gradle 멀티모듈 - -| 모듈 | 역할 | 실행 포트 | +| 모듈 | 역할 | 기본 포트 | |---|---|---:| -| `dap-gateway` | MCP 진입점, Tool Registry, 라우팅, 권한·가드레일, Chat API | 8081 | -| `dap-was-lib` | 공통 어노테이션, Controller, JSON Schema, MCI/EAI 지원, 보안·로깅 공통 기능 | 라이브러리 | -| `dap-was-sms` | SMS/알림 Tool 서버 | 8082 | -| `dap-was-oth` | 공통·업무·샘플·MCI Tool 서버 | 8084 | +| `dap-was-lib` | MCP 어노테이션, 스캐너, 실행 Controller, Manifest, Schema, MCI/EAI/로깅/보안 공통 기능 | - | +| `dap-was-oth` | 공통·기타·샘플·SOL 업무 Tool Pod | 8084 | +| `dap-was-sms` | SMS/알림 업무 Pod | 8082 | -기술 기준은 Java 21, Spring Boot 4, Gradle, Spring AI MCP Server, Redis, MapStruct, MyBatis, Resilience4j입니다. +기술 기준은 Java 21, Spring Boot 4.0.5, Gradle Wrapper 8.14.3, Spring AI MCP Server WebMVC, Redis, MapStruct, MyBatis, Resilience4j입니다. -## 환경 (Environment) +## 현재 제공 API -| 항목 | 버전 / 기준 | +아래 API는 각 Tool Pod가 직접 제공합니다. OTH Pod의 로컬 주소는 `http://localhost:8084`, SMS Pod는 `http://localhost:8082`입니다. + +| 목적 | 메서드 | 경로 | 구현 | +|---|---|---|---| +| MCP Streamable HTTP 전송 | MCP 프로토콜 | `/mcp` | `ToolMcpServerConfiguration` | +| Pod에서 스캔한 Tool 메타데이터 조회 | `GET` | `/mcp/api/v1/tools/local` | `BusinessToolController` | +| 이름으로 Tool 직접 실행 | `POST` | `/mcp/{name}` | `BusinessToolController` | +| Pod 소유 Manifest 조회 | `GET` | `/tool-manifest` | `ToolManifestController` | + +`GET /tool-manifest`는 `If-None-Match` 요청 헤더를 지원하며, 내용이 바뀌지 않으면 `304 Not Modified`를 반환합니다. 응답에는 bundle ID, revision, Tool 목록, 입력 Schema, 실행 endpoint, annotation/meta 정보가 포함됩니다. Manifest는 `LocalToolScanner`의 전체 스캔 목록을 사용하므로 `visible = false` 또는 `register = false`인 항목도 포함될 수 있습니다. + +`GET /mcp/api/v1/tools/local`은 Manifest 검증이나 MCP 세션을 열지 않고, 현재 Pod에서 스캔한 `ToolMetadata` 목록을 반환합니다. + +### REST 실행 예시 + +Tool 이름은 `@McpFunction.name` 값입니다. 예를 들어 OTH Pod의 `oth.smp.weather.inquiry`는 다음처럼 호출합니다. + +```powershell +$headers = @{ + 'trace-id' = 'trace-local-001' + 'request-id' = 'request-local-001' +} + +Invoke-RestMethod ` + -Method Post ` + -Uri 'http://localhost:8084/mcp/oth.smp.weather.inquiry' ` + -Headers $headers ` + -ContentType 'application/json' ` + -Body '{"city":"Seoul"}' +``` + +Controller는 이름을 찾은 뒤 요청 JSON을 첫 번째 DTO 매개변수로 변환합니다. 입력 Schema 검증 실패는 `422 INVALID_PARAM`, 존재하지 않는 Tool은 `404 TOOL_NOT_FOUND`, 실행 예외는 `502 TOOL_ERROR` 응답입니다. `trace-id`와 `request-id`는 성공 응답 헤더로 다시 전달됩니다. + +## Tool 검색과 MCP 노출 규칙 + +애플리케이션 기동 시 `LocalToolScanner`는 Spring Bean에서 `@McpTool`과 `@McpFunction` 메타데이터를 읽어 로컬 Tool 목록을 만듭니다. 기동 완료 후 `ToolPodMcpToolSynchronizer`는 이 목록 중 `visible = true`인 Tool만 MCP SDK 서버에 추가합니다. + +| 속성 | 현재 구현에서의 의미 | |---|---| -| Java | 21 | -| Spring Boot | 4.0.5 | -| Gradle Wrapper | 8.14.3 | -| Spring AI BOM | 2.0.0 | -| Spring AI MCP Server | `spring-ai-starter-mcp-server-webmvc` | -| Redis Client | Lettuce 6.6.0.RELEASE | -| Resilience | Resilience4j 2.2.0 | -| MyBatis Spring Boot Starter | 3.0.3 | -| MapStruct | 1.5.5.Final | -| Lombok | 1.18.32 | -| JSON Schema Validator | networknt 1.4.0 | -| OpenAPI UI | springdoc 2.5.0 | -| 컨테이너 실행 | Docker Compose | +| `visible` | `false`이면 MCP SDK의 Tool 등록에서 제외됩니다. | +| `register` | 스캐너 메타데이터의 `isRegistered` 값과 내부 `registeredTools` 목록에만 반영됩니다. 현재 저장소에는 외부 Registry 전송 구현이 없습니다. | +| `namespace` | 비어 있지 않으면 Tool 이름 앞에 `{namespace}_`가 붙습니다. 기본 설정은 빈 문자열입니다. | +| `enabled` | Manifest의 `_meta.enabled` 값으로 노출됩니다. 현재 synchronizer는 이 값으로 별도 필터링하지 않습니다. | +| `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` | MCP Tool annotation과 Manifest annotation에 반영됩니다. | -프로젝트는 JDK 21을 기준으로 컴파일됩니다. IntelliJ에서는 Project SDK, Gradle JVM, Run Configuration JRE를 모두 JDK 21로 맞춰야 합니다. -## 5분 빠른 시작 +`POST /mcp/{name}`의 동적 실행은 `visible` 및 `register` 값으로 차단하지 않습니다. 따라서 직접 호출을 막아야 하는 Tool은 네트워크 경계와 별도 인증·인가 정책으로 보호해야 합니다. -Docker와 JDK 21이 준비된 로컬 개발 환경 기준입니다. +### 현재 Tool 선언 현황 -```powershell -# 1. Redis와 MCI Mock만 먼저 실행 -$env:ACTIVE_PROFILE = 'local' -docker compose up -d redis mci-mock +`dap-was-oth`에는 실제 `@McpFunction` 선언이 17개 있습니다. 도메인은 다음과 같습니다. -# 2. Gateway 실행 (새 PowerShell) -$env:SPRING_PROFILES_ACTIVE = 'local' -$env:OPENROUTER_API_KEY = '<개발용 비밀 저장소의 키>' -.\gradlew.bat :dap-gateway:bootRun - -# 3. OTH Tool 실행 (또 다른 PowerShell) -$env:SPRING_PROFILES_ACTIVE = 'local' -$env:AXHUB_GATEWAY_URL = 'http://localhost:8081' -$env:AXHUB_TOOL_URL = 'http://localhost:8084' -.\gradlew.bat :dap-was-oth:bootRun -``` - -Tool 서버가 기동된 뒤 아래 URL로 등록된 Tool 목록을 확인합니다. - -```text -http://localhost:8081/mcp/api/v1/tools/list -``` - -SMS Tool도 함께 확인하려면 별도 PowerShell에서 아래 명령을 실행합니다. - -```powershell -$env:SPRING_PROFILES_ACTIVE = 'local' -$env:AXHUB_GATEWAY_URL = 'http://localhost:8081' -$env:AXHUB_TOOL_URL = 'http://localhost:8082' -.\gradlew.bat :dap-was-sms:bootRun -``` - -전체 컨테이너 환경이 필요하면 개별 실행 대신 다음 한 줄을 사용합니다. - -```powershell -$env:ACTIVE_PROFILE = 'local' -docker compose up -d --build -``` -## Tool 등록과 실행 - -### 등록과 목록 제공 - -1. Tool 서버 기동 시 `ToolRegistryHeartbeatSender`가 `@McpTool`, `@McpFunction`을 스캔합니다. -2. Tool 이름, 설명, 입력 JSON Schema, `categoryKey`, 연동 방식, 실행 URL을 메타데이터로 생성합니다. -3. Gateway Redis Registry에 등록·Heartbeat 정보를 전송합니다. -4. Agent와 관리 화면은 Gateway에서 Tool 목록과 명세를 조회합니다. - -### 실행 - -1. Agent가 Gateway에 Tool 이름과 입력값을 보냅니다. -2. Gateway가 Tool 존재 여부, 허용 Tool, 쓰기 승인, 가드레일을 확인합니다. -3. Gateway가 Tool 서버의 `/mcp/{toolName}`으로 요청을 전달합니다. -4. `BusinessToolController`가 Tool 메서드를 찾아 DTO로 변환하고 JSON Schema를 검증합니다. -5. UseCase가 업무 흐름을 수행합니다. -6. Converter가 업무 DTO를 인터페이스 ID 기반 MCI 요청 DTO로 변환합니다. -7. MCI/EAI Client가 레거시를 호출하고 결과를 Tool 응답으로 반환합니다. - -## 주요 URL - -로컬에서 Gateway를 직접 실행할 때의 기준입니다. Docker Compose를 사용하면 Gateway 호스트 포트는 `8281`입니다. - -| 용도 | 메서드 | URL | +| 도메인 | 예시 Tool 이름 | 내용 | |---|---|---| -| Tool 목록 | `GET` | `http://localhost:8081/mcp/api/v1/tools/list` | -| Tool 실행 | `POST` | `http://localhost:8081/mcp/api/v1/tools/call` | -| Tool Markdown 문서 | `GET` | `http://localhost:8081/mcp/api/v1/tools/docs/markdown` | -| 카테고리별 SSE MCP 연결 | `GET` | `http://localhost:8081/mcp/sse/{categoryKey}` | -| 카테고리별 MCP 호출 채널 | `POST` | `http://localhost:8081/mcp/custom/{categoryKey}` | -| Chat 스트리밍 API | `POST` | `http://localhost:8081/api/chat` | -| Scaffold API | `POST` | `http://localhost:8081/api/v1/scaffold/pod` 또는 `/tool` | +| `cmm` | `oth.cmm.claim.search`, `oth.cmm.customer.detail`, `oth.cmm.meta.table` | 공통·고객·계약·청구·메타 기능 | +| `smp` | `oth.smp.weather.inquiry`, `oth.smp.exchange-rate.inquiry` | 샘플·조회 기능 | +| `sol` | `oth.sol.request.list`, `oth.sol.request.detail` | SOL 요청 조회 | -`/mcp/sse/{categoryKey}`는 SSE 연결을 여는 전송 경로이고, `/mcp/custom/{categoryKey}`는 같은 카테고리의 MCP 요청을 처리하는 호출 경로입니다. 두 URL은 역할이 다릅니다. +`dap-was-oth`에는 `categoryKey = "oth"`인 `Onnba3011UseCase`도 있으나, 현재 `@McpFunction` 선언은 없습니다. `dap-was-sms`도 `@McpTool(routingType = "EAI", categoryKey = "notification")`은 선언되어 있지만, `SmsToolUseCase`/구현체에 `@McpFunction`이 없습니다. 따라서 현 상태에서 두 영역은 스캐너, `/tool-manifest`, MCP SDK에 노출되는 호출 가능 Tool을 만들지 않습니다. MCP Tool로 제공하려면 각 계약 메서드에 `@McpFunction`을 선언해야 합니다. -## Tool 개발 표준 +## Tool 개발 방식 -| 구성 요소 | 책임 | -|---|---| -| `XxxRequest`, `XxxResponse` | Agent/Tool 관점의 입력·응답 DTO | -| `XxxUseCase` | Tool 계약과 MCP 메타데이터 선언 | -| `XxxUseCaseImpl` | 업무 흐름 조합과 Client 호출 | -| `XxxConverter` | 업무 DTO와 레거시 인터페이스 DTO 사이 변환 | -| `MciXxxClient` | Glow/MCI 또는 EAI 통신 호출 | -| `INTERFACE_ID_I`, `INTERFACE_ID_O` | 인터페이스 ID 기준 MCI 요청·응답 DTO | - -### Tool 선언 - -Tool 그룹에는 `@McpTool`, Agent가 호출하는 함수에는 `@McpFunction`을 사용합니다. +Tool 그룹은 인터페이스에 `@McpTool`, Agent가 호출하는 메서드는 `@McpFunction`을 선언합니다. `@McpTool`은 Spring `@Component` 별칭이므로 Tool 인터페이스와 구현체는 Spring Bean으로 구성되어야 합니다. ```java @McpTool(routingType = "MCI", categoryKey = "claim") public interface ClaimInquiryUseCase { @McpFunction( - name = "claim_inquiry", - displayName = "보험금 청구 조회", - description = "청구 번호로 보험금 청구 상태를 조회합니다.", - mappingId = "CLM00000001" + name = "oth.claim.inquiry.detail", + displayName = "청구 상세 조회", + description = "청구 번호로 청구 상세를 조회합니다.", + mappingId = "CLM00000001", + readOnlyHint = true ) ClaimInquiryResponse inquire(ClaimInquiryRequest request); } ``` -`categoryKey`는 Tool의 업무 그룹입니다. Gateway의 목록 필터링, Agent 권한, 동적 MCP 서버 구분에 사용하므로 합의된 업무 키를 사용합니다. - -- `MCI`: 사내 MCI 인터페이스 호출 -- `EAI`: EAI 연동 -- `DIRECT`: 외부 HTTP 또는 내부 직접 연동 - -### 변환 원칙 - -UseCase 구현체는 Tool 요청을 레거시 요청과 섞어 쓰지 않습니다. Converter에서 변환한 뒤 Client에 전달합니다. +구현체에는 업무 흐름만 두고, Tool DTO와 레거시 인터페이스 DTO의 변환은 Converter에 둡니다. ```java -@Override -public ClaimInquiryResponse inquire(ClaimInquiryRequest request) { - CLM00000001_I mciRequest = converter.toMciRequest(request); - CLM00000001_O mciResponse = mciClmClient.callClm00000001(mciRequest); - return converter.toResponse(mciResponse); +@Service +@RequiredArgsConstructor +class ClaimInquiryUseCaseImpl implements ClaimInquiryUseCase { + private final ClaimInquiryConverter converter; + private final MciClaimClient client; + + @Override + public ClaimInquiryResponse inquire(ClaimInquiryRequest request) { + ClaimMciRequest legacyRequest = converter.toMciRequest(request); + ClaimMciResponse legacyResponse = client.call(legacyRequest); + return converter.toResponse(legacyResponse); + } } ``` -MCI 입출력 객체는 업무 이름이 아니라 인터페이스 ID를 기준으로 둡니다. +`@McpFunction.name`은 소문자 점 표기 형식으로 작성합니다. 현재 선언은 `oth.cmm.claim.search`, `oth.smp.weather.inquiry`처럼 `{pod}.{domain}.{service}.{action}` 패턴을 사용합니다. `validateMcpToolNames` Gradle 작업은 모든 Tool 모듈을 대상으로 이름 형식과 중복을 검사하며, 각 `bootJar` 전에 실행됩니다. + +## Input/Output Schema + +입력 Schema는 다음 우선순위로 결정됩니다. + +1. `inputSchemaResource`에 지정한 classpath JSON Schema +2. `inputSchema`에 인라인으로 지정한 JSON Schema +3. 요청 DTO의 `@McpValidation`을 이용한 자동 생성 Schema + +출력 검증은 선택 사항입니다. 다음 중 하나가 있을 때만 반환값을 검증합니다. + +1. `outputSchemaResource` +2. `outputSchema` +3. 반환 DTO의 `@McpOutputSchema`와 `@McpValidation` + +복잡한 Schema 리소스는 Tool 모듈에 둡니다. 현재 OTH의 청구 검색 예제는 다음 리소스를 사용합니다. ```text -CLCNNB00001_I : CLCNNB00001 요청 DTO -CLCNNB00001_O : CLCNNB00001 응답 DTO +dap-was-oth/src/main/resources/tool-schemas/cmm/ +├─ claim-search-resource-input-schema.json +└─ claim-search-resource-output-schema.json ``` -예를 들어 `Onnba3011Request`와 `CLCNNB00001_I`는 같은 업무 데이터를 담을 수 있지만 같은 객체가 아닙니다. 둘 사이의 변환 책임은 `Onnba3011Converter`에 둡니다. +입력 검증에는 JSON Schema Draft 7이 사용됩니다. 출력 Schema 검증에 실패하면 `500 INVALID_TOOL_RESPONSE`을 반환합니다. -### 새 Tool 추가 체크리스트 - -새 업무 Tool을 추가할 때는 아래 순서로 확인합니다. - -- [ ] 소속 모듈과 `categoryKey`를 업무 소유 조직 기준으로 결정한다. -- [ ] Agent 입력·응답 DTO인 `XxxRequest`, `XxxResponse`를 만든다. -- [ ] `XxxUseCase`에 `@McpTool`을 선언하고, 호출 메서드에 `@McpFunction`의 이름·설명·연동 ID를 선언한다. -- [ ] `XxxUseCaseImpl`에서 업무 흐름만 조합한다. -- [ ] `XxxConverter`에 업무 DTO ↔ 인터페이스 ID DTO 변환을 둔다. -- [ ] `MciXxxClient`와 `INTERFACE_ID_I`, `INTERFACE_ID_O`를 인터페이스 ID 기준으로 만든다. -- [ ] DTO에 Bean Validation을 선언하고, 중첩 DTO가 있으면 입력 Schema와 검증 대상에 포함되는지 확인한다. -- [ ] 조회·변경 작업 특성에 따라 `readOnlyHint`, `requiresApproval`, `idempotentHint`를 설정한다. -- [ ] 단위 테스트를 작성하고 `:dap-was-lib:test` 또는 대상 모듈 테스트를 실행한다. -- [ ] Tool 서버 기동 후 `/mcp/api/v1/tools/list`에서 이름, 설명, category, JSON Schema가 맞는지 확인한다. -- [ ] 요청·응답 로그에 개인정보나 인증값이 남지 않는지 확인한다. -### 등록·노출 제어 - -| 속성 | 의미 | -|---|---| -| `register` | Redis Registry와 Gateway 카탈로그에 등록할지 여부 | -| `visible` | Agent/클라이언트 목록에 표시할지 여부 | -| `requiresApproval` | 쓰기·고위험 작업의 승인 요구 여부 | -| `readOnlyHint`, `destructiveHint`, `idempotentHint` | Agent 호출 특성 힌트 | - -`register = false`인 Tool은 자동 카탈로그 등록 대상이 아닙니다. 별도 실행 목적이 있는 경우에만 사용하고, 필요한 fallback 경로를 운영 설정으로 확인합니다. - -## 빌드·테스트·로컬 실행 +## 로컬 실행 ### 사전 조건 - JDK 21 +- Docker (Redis 또는 MCI mock을 사용할 경우) - Gradle Wrapper 사용 권장 -- 로컬 Redis 또는 Docker Compose 환경 -- 필요 시 MCI Mock 또는 사내 MCI/EAI 접근 환경 -### 전체 빌드와 대표 검증 +로컬 프로필은 기본값이며, 두 Pod 모두 H2 메모리 DB와 P6Spy를 설정합니다. Pod URL은 `AXHUB_TOOL_URL` 환경 변수로 설정하며, 지정하지 않으면 해당 `server.port`의 localhost 주소를 사용합니다. ```powershell -.\gradlew.bat clean build -.\gradlew.bat :dap-was-lib:test -.\gradlew.bat :dap-was-lib:compileJava -``` - -### 애플리케이션 실행 - -각 애플리케이션은 별도 PowerShell에서 실행합니다. - -```powershell -# Gateway -.\gradlew.bat :dap-gateway:bootRun - -# SMS Tool -.\gradlew.bat :dap-was-sms:bootRun - -# 기타 업무 Tool -.\gradlew.bat :dap-was-oth:bootRun -``` - -기본 프로필은 `local`입니다. 개발 서버 설정이 필요하면 실행 환경에 프로필을 지정합니다. - -```powershell -$env:SPRING_PROFILES_ACTIVE = 'dev' -.\gradlew.bat :dap-was-oth:bootRun -``` - -## Docker Compose 실행 - -Docker Compose는 Redis, Gateway, MCI Mock, SMS Tool, OTH Tool을 함께 기동합니다. - -```powershell -$env:ACTIVE_PROFILE = 'local' -docker compose up -d --build -docker compose ps -``` - -| 서비스 | 컨테이너 포트 | 호스트 포트 | -|---|---:|---:| -| Redis | 6379 | 6379 | -| Gateway | 8081 | 8281 | -| MCI Mock | 8080 | 8089 | -| SMS Tool | 8082 | 8282 | -| OTH Tool | 8084 | 8284 | - -컨테이너 내부와 PC 브라우저의 접속 주소는 다릅니다. - -```text -컨테이너 내부: http://gateway:8081, http://tool-oth:8084 -PC 브라우저: http://localhost:8281, http://localhost:8284 -``` - -## 환경 설정 - -### 프로필 - -기본 프로필은 `local`입니다. Tool 서버와 Gateway 모두 `local`, `dev` 프로필 파일을 사용합니다. - -| 프로필 | 목적 | 주요 설정 파일 | -|---|---|---| -| `local` | PC 개발·MCI Mock·H2 메모리 DB 기반 실행 | `application-local.yml` | -| `dev` | 개발 서버 배포 실행 | `application-dev.yml` | - -프로필은 환경 변수로 지정합니다. - -```powershell -# 로컬 개발 -$env:SPRING_PROFILES_ACTIVE = 'local' - -# 개발 서버 설정으로 실행 -$env:SPRING_PROFILES_ACTIVE = 'dev' -``` - -### 환경별 연결 주소 비교 - -| 항목 | `local` 프로세스 실행 | Docker Compose | `dev` 프로필 | -|---|---|---|---| -| Gateway 접근 주소 | `http://localhost:8081` | 컨테이너 내부 `http://gateway:8081` | 배포 환경 Gateway URL | -| SMS Tool 주소 | `http://localhost:8082` | `http://tool-sms:8082` | `PORT` 기본 8082 | -| OTH Tool 주소 | `http://localhost:8084` | `http://tool-oth:8084` | `PORT` 기본 8084 | -| Redis 주소 | 로컬 Redis 또는 `localhost:6379` | `redis:6379` | 운영/개발 Redis 설정 | -| MCI/EAI 대상 | MCI Mock 또는 로컬 설정 | `mci-mock:8080` | 개발망 연동 설정 | -| 브라우저 Gateway 접속 | `http://localhost:8081` | `http://localhost:8281` | 운영·개발 도메인 | - -`local`에서 프로세스를 직접 실행하면 Tool의 `AXHUB_GATEWAY_URL`은 `localhost`를 사용합니다. Docker에서는 각 컨테이너가 서로 다른 네트워크 공간에 있으므로 반드시 Compose 서비스 이름을 사용합니다. -### Gateway 환경 변수 - -| 변수 | 적용 대상 | 설명 | 로컬 기본값/예시 | -|---|---|---|---| -| `SPRING_PROFILES_ACTIVE` | Gateway, 모든 Tool | 활성 Spring 프로필 | `local` | -| `OPENROUTER_API_KEY` | Gateway | Chat/LLM 호출 API 키 | 운영·개발 환경의 비밀 저장소에서 주입 | -| `SPRING_DATA_REDIS_HOST` | Gateway, 모든 Tool | Redis 호스트 | Docker: `redis` | -| `SPRING_DATA_REDIS_PORT` | Gateway, 모든 Tool | Redis 포트 | `6379` | -| `MCP_GATEWAY_FALLBACK_DEFAULT_URL` | Gateway | Registry에 없는 Tool의 기본 fallback URL | Docker: `http://tool-oth:8084` | -| `MCP_GATEWAY_FALLBACK_ROUTES_SMS` | Gateway | SMS 계열 fallback URL | Docker: `http://tool-sms:8082` | -| `ACTIVE_PROFILE` | Docker Compose | Compose가 `SPRING_PROFILES_ACTIVE`에 전달할 프로필 | `local` | - -`OPENROUTER_API_KEY` 같은 인증값은 `application.yml`, README, Git 커밋에 직접 넣지 않습니다. 개발·운영 환경의 Secret, CI/CD 변수 또는 안전한 환경 변수로 주입합니다. - -### Tool 서버 환경 변수 - -| 변수 | 적용 대상 | 설명 | 로컬 기본값/예시 | -|---|---|---|---| -| `PORT` | `dap-was-sms`, `dap-was-oth` | 개발 프로필에서 Tool 서버 포트 변경 | SMS `8082`, OTH `8084` | -| `AXHUB_GATEWAY_URL` | 모든 Tool | Tool 등록·Heartbeat 대상 Gateway 주소 | 로컬 `http://localhost:8081`, Docker `http://gateway:8081` | -| `AXHUB_TOOL_URL` | 모든 Tool | Gateway가 해당 Tool Pod를 호출할 주소 | 로컬 `http://localhost:{server.port}` | -| `GLOW_COMMUNICATION_MCI_HOMT` | Docker Tool 컨테이너 | MCI 대상 호스트 | 로컬 Compose는 `mci-mock` | -| `GLOW_COMMUNICATION_MCI_PORT` | Docker Tool 컨테이너 | MCI 대상 포트 | 로컬 Compose는 `8080` | -| `GLOW_COMMUNICATION_EAI_HOMT` | Docker Tool 컨테이너 | EAI 대상 호스트 | 로컬 Compose는 `mci-mock` | -| `GLOW_COMMUNICATION_EAI_PORT` | Docker Tool 컨테이너 | EAI 대상 포트 | 로컬 Compose는 `8080` | - -`AXHUB_TOOL_URL`은 반드시 Gateway가 실제로 접근 가능한 주소여야 합니다. PC에서 각각 실행할 때는 `localhost`를 사용하고, Docker 컨테이너 안에서는 `tool-sms`, `tool-oth` 같은 Compose 서비스 이름을 사용합니다. - -### 로컬 실행용 권장 설정 - -아래는 키 값 없이 로컬 프로세스를 실행하는 예시입니다. Redis를 Docker로 먼저 기동하거나 전체 Docker Compose를 사용합니다. - -```powershell -# 선택 1: Redis만 기동 +# 선택: Redis와 WireMock 기반 MCI mock 기동 $env:ACTIVE_PROFILE = 'local' docker compose up -d redis mci-mock -# 선택 2: 각 프로세스를 로컬에서 기동 +# OTH Tool Pod 실행 $env:SPRING_PROFILES_ACTIVE = 'local' -$env:OPENROUTER_API_KEY = '<개인 또는 개발용 비밀 저장소의 키>' -.\gradlew.bat :dap-gateway:bootRun -``` - -다른 PowerShell에서 Tool을 실행합니다. - -```powershell -$env:SPRING_PROFILES_ACTIVE = 'local' -$env:AXHUB_GATEWAY_URL = 'http://localhost:8081' $env:AXHUB_TOOL_URL = 'http://localhost:8084' .\gradlew.bat :dap-was-oth:bootRun + +# SMS Tool Pod 실행 (별도 PowerShell) +$env:SPRING_PROFILES_ACTIVE = 'local' +$env:AXHUB_TOOL_URL = 'http://localhost:8082' +.\gradlew.bat :dap-was-sms:bootRun ``` -### 권한 도메인 설정 - -`mcp.security.tenant-domains`는 테넌트 또는 호출 주체가 접근할 수 있는 `categoryKey`를 정의합니다. 운영 환경에서는 `ALL`을 무분별하게 사용하지 않고, Agent·조직별 허용 도메인을 최소 권한으로 설정합니다. - -```yaml -mcp: - security: - tenant-domains: - claims-agent: claim, customer - notification-agent: notification -``` - -## 공통 기능과 운영 기준 - -### Redis - -Redis는 Tool Registry의 등록 상태와 Heartbeat, Gateway 실행 추적 정보에 사용됩니다. 현재 Redis는 PII 원문 보관소가 아닙니다. - -### 권한과 승인 - -Gateway는 Agent가 전달한 허용 Tool 목록, 서버 정책, 신뢰된 Claim 여부, 쓰기 승인 여부를 확인합니다. Tool마다 권한 로직을 중복 구현하지 말고 Gateway 공통 정책과 `@McpFunction` 메타데이터를 사용합니다. - -### Trace ID와 Request ID - -```text -trace-id : 사용자 요청 전체에서 유지되는 상관관계 ID -request-id : Gateway → Tool, Tool → MCI 등 HTTP 호출마다 새로 생성되는 ID -``` - -Gateway와 Tool에는 관련 헤더 및 MDC 기반 로그 처리가 있습니다. 신규 HTTP Client도 공통 전파 정책을 따르며, Tool별로 임의의 헤더 이름을 추가하지 않습니다. - -### 로그와 개인정보 - -Gateway에는 민감 키와 일부 형식을 마스킹하는 공통 기능이 있습니다. 마스킹은 원문을 Agent에서 분리하는 PII 토큰화와는 다릅니다. - -- 요청·응답 전문을 로그에 남길 때는 반드시 마스킹합니다. -- 운영 로그에 주민번호, 계좌번호, 전화번호, 이메일, 인증값을 남기지 않습니다. -- Tool 서버의 신규 로그도 같은 마스킹 정책을 적용합니다. -- 민감정보 복원 필요 여부는 Tool 개발자가 임의로 결정하지 않고 보안·AA 정책을 따릅니다. - -## AA 협의 기반 향후 전환 과제 - -아래 항목은 현재 구현 완료 기능이 아니라 회의에서 합의한 목표 구조입니다. - -### 부서별 Tool Pod와 저장소 경계 - -현재 `dap-was-oth`에 함께 있는 업무 Tool을 부서·업무 소유권 단위로 분리합니다. 각 Pod는 독립 이미지, 독립 배포, 독립 장애 범위를 갖도록 구성합니다. 실제 분리는 AA가 확정한 Tool 소유 부서와 운영 책임 매핑을 기준으로 수행합니다. - -### Agent별 Tool 노출 수 제한 - -Agent에게 모든 Tool을 한 번에 제공하지 않고, 업무 도메인과 권한에 따라 약 10~20개 수준의 Tool 그룹을 제공합니다. `categoryKey`는 이를 위한 기초 메타데이터이며, 향후 Agent-Tool Group 정책으로 확장합니다. - -### PII 토큰화 - -```text -원문 개인정보 - → PII Gateway가 Redis에 짧은 TTL로 보관 - → Agent에는 PII 토큰 또는 안전한 식별자만 전달 - → 인가된 Tool이 MCI 호출 직전에 필요한 항목만 복원 -``` - -이 전환 전까지는 현행 마스킹 기능을 PII 분리 구현으로 오해하지 않아야 합니다. - -### MCP SDK와 Glow Framework - -Gateway에는 Spring AI MCP Server 의존성이 포함되어 있습니다. MCP SDK/Glow 표준 적용 시에는 업무 Tool을 재작성하지 않고, 기존 `@McpTool`·`@McpFunction`과 UseCase를 표준 MCP Tool 명세·호출 콜백으로 연결하는 Adapter 계층을 공통 Core에 추가합니다. - -```text -MCP SDK 표준 tools/list, tools/call - ↓ -공통 Adapter - ↓ -기존 UseCase → Converter → MCI Client -``` - -따라서 업무 DTO, Converter, MCI Client의 책임은 유지됩니다. - -## 참고 소스 - -| 주제 | 대표 위치 | -|---|---| -| Gateway Tool API | `dap-gateway/.../presentation/McpRouterController.java` | -| 동적 MCP SSE/호출 경로 | `dap-gateway/.../sync/DynamicMcpController.java` | -| Tool 실행 Controller | `dap-was-lib/.../presentation/BusinessToolController.java` | -| Tool 자동 등록 | `dap-was-lib/.../usecase/ToolRegistryHeartbeatSender.java` | -| Tool 어노테이션 | `dap-was-lib/.../annotation/McpTool.java`, `McpFunction.java` | -| Tool 예시 | `dap-was-oth/.../biz/oth`, `biz/sol`, `biz/smp` | -| SMS Tool 예시 | `dap-was-sms/.../biz/sms` | -| Docker 환경 | `docker-compose.yml` | - ---- - -문서에 없는 업무·보안·배포 기준은 임의로 추가하지 말고 AA 및 플랫폼 운영 기준과 먼저 합의합니다. -## Input/Output Schema 작성 가이드 - -Tool Schema는 Agent가 Tool을 정확히 호출하고, 반환값의 의미를 일관되게 해석하도록 하는 계약입니다. 인증 정보·사번·주민번호 등 민감정보(PII)는 Input/Output Schema와 Tool 응답에 포함하지 않습니다. - -### Input Schema - -Input Schema는 Agent가 Tool에 전달하는 파라미터의 이름, 타입, 필수 여부, 허용값, 형식 등을 정의합니다. - -적용 우선순위는 다음과 같습니다. - -1. `inputSchemaResource` — 복잡한 규칙을 담은 JSON Schema 리소스 -2. `inputSchema` — 어노테이션에 직접 선언한 JSON Schema -3. 요청 DTO 필드의 `@McpValidation` — 자동 JSON Schema 생성 - -단순한 요청 DTO는 `@McpValidation`만으로 관리합니다. - -```java -public class ClaimSearchRequest { - - @McpValidation(required = true, pattern = "^CLM[0-9]{13}$") - private String claimNo; - - @McpValidation(minimum = 1, maximum = 100) - private Integer size; -} -``` - -### Output Schema - -Output Schema는 Tool이 반환하는 결과의 타입과 의미를 정의합니다. `BusinessToolController`는 Tool 실행 후 반환값을 Output Schema 기준으로 검증합니다. - -적용 우선순위는 다음과 같습니다. - -1. `outputSchemaResource` — 조건부 필드·중첩 배열 등 복잡한 규칙을 담은 JSON Schema 리소스 -2. `outputSchema` — 어노테이션에 직접 선언한 JSON Schema -3. 반환 DTO의 `@McpOutputSchema`와 필드 `@McpValidation` — 자동 JSON Schema 생성 -4. 위 설정이 모두 없으면 Output Schema 검증을 수행하지 않음 - -따라서 단순한 응답은 별도 `outputSchemaResource` 없이 반환 DTO에 `@McpOutputSchema`를 선언하면 됩니다. `null`이 정상 값일 수 있는 필드는 `nullable = true`를 반드시 지정합니다. - -```java -@McpOutputSchema -public class ClaimSearchResponse { - - @McpValidation(required = true, allowedValues = {"SUCCESS", "FAILURE"}) - private String resultCode; - - @McpValidation(nullable = true, minimum = 0) - private Long approvedAmount; -} -``` - -### 복잡한 Schema는 Tool 모듈별 리소스로 관리 - -조건부 응답, 중첩 DTO, 배열 정렬 기준처럼 어노테이션만으로 표현하기 어려운 규칙은 Tool Core가 아니라 각 Tool 모듈의 리소스에 JSON Schema로 둡니다. - -```text -src/main/resources/ -└─ tool-schemas/ - └─ {categoryKey}/ - ├─ claim-search-resource-input-schema.json - └─ claim-search-resource-output-schema.json -``` - -예를 들어 `categoryKey`가 `cmm`이면 아래와 같이 선언합니다. - -```java -@McpFunction( - name = "oth.cmm.claim.search", - inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", - outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json" -) -public ClaimSearchResponse search(ClaimSearchRequest request) { - // ... -} -``` - -`inputSchemaResource`와 `outputSchemaResource`는 복잡한 경우에만 선언합니다. 단순한 Tool까지 JSON 파일을 별도 생성할 필요는 없습니다. - -### Output 설계 규칙 - -- 코드와 표시용 라벨을 함께 반환합니다. 예: `status` + `statusLabel` -- `null`이 정상인 값은 의미를 설명에 명시하고 DTO에는 `nullable = true`를 설정합니다. -- 조건부 필드는 어떤 조건에서 값이 존재하는지 JSON Schema에 명시합니다. -- 배열은 정렬 기준을 설명에 명시합니다. 예: `접수일 내림차순` -- 목록 응답에는 추가 조회 여부를 나타내는 `hasMore`를 포함합니다. -- 민감정보는 마스킹보다 **응답에서 제외**하는 것을 우선합니다. - -### 실행 로그 및 확인 - -Tool 실행이 끝나면 아래 로그는 Schema 정의가 아니라 **검증을 통과한 실제 최종 응답값**을 출력합니다. - -```text -[Tool -> MCP Gateway] Output Schema Result: { ... } -``` - -따라서 로그에도 실제 응답이 남으므로, 응답 DTO와 Output Schema에 민감정보가 포함되지 않도록 설계해야 합니다. - -스키마 리소스와 DTO 기반 자동 Schema는 아래 테스트로 함께 검증할 수 있습니다. - -```powershell -.\gradlew.bat :dap-was-oth:test --tests "io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequestSchemaTest" -``` - -### Tool Naming Convention - -All Tool names use the four-level lowercase format `pod.domain.service.action`. Do not use underscores or CamelCase; use a hyphen (`-`) only when a single level has multiple words. - -- `pod`: deployment Tool Pod/module (`dap-was-oth` → `oth`, `dap-was-sms` → `sms`) -- `domain`: business-domain package (`cmm`, `smp`, `sol`, etc.) -- `service`: business service or resource -- `action`: the requested operation (`search`, `list`, `detail`, `issue`, `inquiry`, etc.) - -```text -oth.cmm.bond.issue -oth.cmm.claim.search -oth.sol.request.list -oth.smp.weather.inquiry -``` - -When Scaffold receives `dap-was-oth`, `cmm`, and `ClaimSearch`, it generates `oth.cmm.claim.search`. The `validateMcpToolNames` Gradle task rejects both a duplicate name and any name outside this format before packaging, including its source file and line number. - -### Tool Test Console - -각 Tool Pod는 공통 테스트 화면을 제공합니다. +실행 후 OTH Pod에서 다음 URL로 현재 스캔된 메타데이터와 Manifest를 확인할 수 있습니다. ```text +http://localhost:8084/mcp/api/v1/tools/local +http://localhost:8084/tool-manifest http://localhost:8084/tool-test-console.html ``` -화면은 현재 Pod의 `/tool-manifest`에서 Tool 목록과 `inputSchema`를 읽습니다. Tool을 선택한 뒤 `Schema 샘플 채우기`로 요청 JSON을 만들고 실행할 수 있습니다. 업무에 맞게 보정한 요청은 `현재 요청 저장`으로 브라우저의 `localStorage`에 보관합니다. +`tool-test-console.html`은 공통 라이브러리의 정적 리소스입니다. `/tool-manifest`에서 Tool과 입력 Schema를 읽어 요청 JSON을 만들고, 현재 Pod의 `/mcp/{toolName}`으로 호출합니다. 저장한 테스트 케이스는 브라우저 `localStorage`에 보관됩니다. -`Run saved cases`는 저장된 테스트 케이스를 순차 실행해 성공/실패, HTTP 상태, 소요 시간을 보여줍니다. 따라서 Tool이 수백 개여도 각 Tool마다 테스트 화면을 만들 필요 없이, 유효한 업무 테스트 데이터만 한 번 저장하면 이후에는 몇 번의 클릭으로 회귀 테스트할 수 있습니다. +## 테스트와 빌드 -- Tool 호출은 현재 Pod의 `/mcp/{toolName}`로 수행합니다. -- 매 실행마다 `trace-id`, `request-id`를 새로 생성하여 응답과 함께 표시합니다. -- 외부 MCI/EAI Tool은 샘플값 대신 개발계에서 허용된 테스트 데이터를 저장해서 사용해야 합니다. +```powershell +# 전체 테스트 +.\gradlew.bat test + +# 공통 라이브러리 테스트 +.\gradlew.bat :dap-was-lib:test + +# OTH Tool 테스트 +.\gradlew.bat :dap-was-oth:test + +# Tool 이름 규칙 및 중복 검증 +.\gradlew.bat validateMcpToolNames + +# 패키징 전 전체 빌드 +.\gradlew.bat clean build +``` + +테스트는 공통 MCP Schema/Manifest/Header 처리, Glow MCI 파서, Tool 이름 검증과 OTH의 청구·SOL·MCI 변환을 다룹니다. SMS 모듈에는 현재 별도 테스트 소스가 없습니다. + +## Docker Compose + +현재 Compose 서비스와 호스트 포트는 다음과 같습니다. + +| 서비스 | 컨테이너 포트 | 호스트 포트 | +|---|---:|---:| +| `redis` | 6379 | 6379 | +| `mci-mock` (WireMock) | 8080 | 8089 | +| `was-sms` | 8082 | 8282 | +| `was-oth` | 8084 | 8284 | +| `dozzle` | 8080 | 8288 | + +Compose의 Pod URL은 컨테이너 DNS 이름을 사용합니다. + +```text +was-sms: http://was-sms:8082 +was-oth: http://was-oth:8084 +``` + +### 현재 Docker 이미지 빌드 제약 + +`docker-compose.yml`은 새 모듈 경로의 Dockerfile을 사용하지만, 두 Dockerfile 내부의 `COPY` 대상은 여전히 이전 경로인 `dap-tool-oth/build/libs`와 `dap-tool-sms/build/libs`입니다. 따라서 현재 모듈명으로 빌드한 jar를 이미지에 복사하지 못할 수 있습니다. 이 불일치를 수정하기 전에는 `docker compose up --build`를 정상 배포 절차로 간주하면 안 됩니다. + +또한 Compose의 Gitea Runner 등록 토큰은 저장소에 직접 두지 말고 배포 환경의 Secret 또는 환경 변수로 주입해야 합니다. + +## 설정 + +| 설정 | 위치/환경 변수 | 설명 | +|---|---|---| +| Pod 포트 | `server.port` 또는 `PORT` | SMS 8082, OTH 8084 | +| Pod 외부 URL | `AXHUB_TOOL_URL` | 스캐너가 Tool endpoint를 만들 때 사용 | +| MCP namespace | `mcp.namespace` | Tool 이름 앞에 `{namespace}_`를 붙임 | +| Manifest bundle | `mcp.manifest.bundle-id` | SMS는 `tool-sms`, OTH는 `tool-oth` | +| Manifest 이름 접두사 | `mcp.manifest.name-prefix` | 지정 시 모든 Manifest Tool 이름이 이 접두사로 시작해야 함 | +| 활성 프로필 | `SPRING_PROFILES_ACTIVE` | 기본값 `local`, 선택값 `dev` | + +`mcp.security.tenant-domains` 설정은 각 Pod의 YAML에 존재하지만, 현재 `McpProperties`와 `BusinessToolController`에는 이를 이용해 호출을 차단하는 로직이 없습니다. 문서상 권한 기능으로 간주하지 말고, 운영 노출 시 별도 인증·인가 계층을 적용해야 합니다. + +## 보안과 운영 주의사항 + +- `BusinessToolController`는 요청 파라미터와 결과를 로그로 남깁니다. Tool 입력·응답에는 주민번호, 계좌번호, 전화번호, 인증값 등 민감정보를 포함하지 않도록 설계하고 공통 마스킹 적용 여부를 검토해야 합니다. +- `employee-id` 헤더는 Controller가 수신하지만 현재 실행 로직에서 사용하지 않습니다. 이 헤더만으로 인증·인가가 수행된다고 가정하면 안 됩니다. +- `mcp.security.tenant-domains`, `requiresApproval`, `register`는 현재 독립 WAS에서 실행 차단 정책을 구현하지 않습니다. +- 외부 MCI/EAI 대상은 local/dev 설정과 실제 네트워크 정책을 별도로 점검해야 합니다. + +## 참고 소스 + +| 주제 | 위치 | +|---|---| +| REST 실행 및 로컬 목록 | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java` | +| Manifest API | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java` | +| MCP Streamable HTTP | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfiguration.java` | +| MCP Tool 동기화 | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/mcp/ToolPodMcpToolSynchronizer.java` | +| Tool 스캔 | `dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/usecase/LocalToolScanner.java` | +| Tool 어노테이션 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpTool.java`, `McpFunction.java` | +| OTH 업무 Tool | `dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/` | +| SMS Tool | `dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/sms/` | +| 컨테이너 구성 | `docker-compose.yml`, `dap-was-oth/Dockerfile`, `dap-was-sms/Dockerfile` | diff --git a/docs/superpowers/plans/2026-08-04-readme-current-was.md b/docs/superpowers/plans/2026-08-04-readme-current-was.md index a7ab1c3f9..5c34a7896 100644 --- a/docs/superpowers/plans/2026-08-04-readme-current-was.md +++ b/docs/superpowers/plans/2026-08-04-readme-current-was.md @@ -4,7 +4,7 @@ **Goal:** Replace the README's removed Gateway-era documentation with an accurate guide to the current independent Tool WAS modules. -**Architecture:** The README becomes the single user-facing reference for the Gradle modules, the two Tool Pods, direct REST and Streamable HTTP MCP access, schema resolution, local configuration, and Docker Compose. Every statement must be traceable to the current `HEAD` source or current checked-in configuration; the working tree's zero-byte Java files are documented as an explicit limitation. +**Architecture:** The README becomes the single user-facing reference for the Gradle modules, the two Tool Pods, direct REST and Streamable HTTP MCP access, schema resolution, local configuration, and Docker Compose. Every statement must be traceable to the current checked-in source or configuration; stale Dockerfile jar paths and the missing SMS `@McpFunction` are documented as explicit limitations. **Tech Stack:** Java 21, Spring Boot 4.0.5, Gradle 8.14.3, Spring AI MCP Server WebMVC, Redis, Docker Compose. @@ -13,7 +13,7 @@ - Modify only `README.md` for the requested deliverable; do not restore or alter Java source, Gradle, Docker, or CI files. - Describe only current Tool WAS behavior; exclude the removed `dap-gateway`, Chat API, SSE transport, and external registry/heartbeat workflow. - Use exact module names `dap-was-lib`, `dap-was-oth`, and `dap-was-sms`. -- Label the working tree's 238 zero-byte Java files and stale Dockerfile jar paths as known execution blockers, not supported behavior. +- Label stale Dockerfile jar paths and the missing SMS `@McpFunction` as known implementation limitations, not supported behavior. --- @@ -26,7 +26,7 @@ - Consumes: Gradle module declarations in `settings.gradle`, runtime configuration in both Tool Pods, `BusinessToolController`, `ToolManifestController`, `ToolMcpServerConfiguration`, `LocalToolScanner`, and `ToolPodMcpToolSynchronizer` from `HEAD`. - Produces: A self-contained Korean README for developers operating or extending the current Tool WAS deployment. -- [ ] **Step 1: Create a fact inventory before editing** +- [x] **Step 1: Create a fact inventory before editing** Record the following source-backed details for use in the README: @@ -38,11 +38,11 @@ MCP transport: Streamable HTTP at /mcp Compose host ports: SMS 8282, OTH 8284, Redis 6379, WireMock 8089, Dozzle 8288 ``` -- [ ] **Step 2: Rewrite README sections** +- [x] **Step 2: Rewrite README sections** Replace Gateway-centric architecture, commands, URLs, environment variables, and future Gateway design material with sections for architecture, modules, API behavior, tool development, schema behavior, local/Docker execution, configuration, testing, and known limitations. -- [ ] **Step 3: Verify README facts mechanically** +- [x] **Step 3: Verify README facts mechanically** Run: @@ -53,7 +53,7 @@ rg -n "dap-was-(lib|sms|oth)|/mcp/\{name\}|/tool-manifest|/mcp/api/v1/tools/loca Expected: the first command produces no matches; the second shows the retained current implementation references. -- [ ] **Step 4: Cross-check every endpoint and command** +- [x] **Step 4: Cross-check every endpoint and command** Compare README endpoint statements with the Java controller/configuration classes and compare module names, ports, profiles, and Docker service names with `settings.gradle`, `application*.yml`, and `docker-compose.yml`. Confirm that the README explicitly distinguishes source-backed behavior from known blockers. diff --git a/fix3.py b/fix3.py deleted file mode 100644 index 0333019cb..000000000 --- a/fix3.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import glob -import re - -def replace_in_file(path, replacements): - with open(path, 'r', encoding='utf-8') as f: - content = f.read() - original = content - for old, new in replacements: - content = content.replace(old, new) - if original != content: - with open(path, 'w', encoding='utf-8') as f: - f.write(content) - -# 1. Rename Main Applications -base_oth = 'c:/egov/workspace/dap-tool/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/' -if os.path.exists(base_oth + 'DapToolOthApplication.java'): - os.rename(base_oth + 'DapToolOthApplication.java', base_oth + 'DapWasOthApplication.java') - -base_sms = 'c:/egov/workspace/dap-tool/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/sms/' -if os.path.exists(base_sms + 'DapToolSmsApplication.java'): - os.rename(base_sms + 'DapToolSmsApplication.java', base_sms + 'DapWasSmsApplication.java') - -# Replace DapTool...Application in code -java_files = glob.glob('c:/egov/workspace/dap-tool/dap-was-*/**/*.java', recursive=True) -for f in java_files: - replace_in_file(f, [ - ('DapToolOthApplication', 'DapWasOthApplication'), - ('DapToolSmsApplication', 'DapWasSmsApplication'), - ('DapTool%sApplication', 'DapWas%sApplication'), - ('dap-tool-', 'dap-was-'), - ('dap-tool-core', 'dap-was-lib'), - ('[Gateway Not Found]', '[WAS Not Found]'), - ('[Gateway Bad Request]', '[WAS Bad Request]'), - ('[Gateway Internal Error]', '[WAS Internal Error]'), - ('[Gateway Fatal Error]', '[WAS Fatal Error]'), - ('Shinhan MCP Gateway API 명세서', 'Shinhan MCP WAS API 명세서'), - ('Gateway Pod (8081)', 'WAS Pod') - ]) - -# Remove gateway from PodScaffolder.java -pod = 'c:/egov/workspace/dap-tool/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java' -replace_in_file(pod, [ - (' gateway:\n url: http://localhost:${server.port}/api/gateway\n', '') -]) - -# 2. Update logback-spring.xml -xml_files = glob.glob('c:/egov/workspace/dap-tool/**/logback-spring.xml', recursive=True) -for f in xml_files: - replace_in_file(f, [('dap-tool-', 'dap-was-')]) - -# 3. Update application.yml (Remove gateway blocks) -yml_files = glob.glob('c:/egov/workspace/dap-tool/**/*.yml', recursive=True) -for f in yml_files: - replace_in_file(f, [ - (' gateway:\n url: http://localhost:${server.port}/api/gateway\n', '') - ]) - -print("Fix applied.") diff --git a/scratch_rewrite.py b/scratch_rewrite.py deleted file mode 100644 index 98a69d9f4..000000000 --- a/scratch_rewrite.py +++ /dev/null @@ -1,66 +0,0 @@ -import yaml -import copy - -with open('docker-compose.yml', 'r') as f: - compose = yaml.safe_load(f) - -services = compose['services'] -new_services = {} - -# Services to duplicate -app_services = ['gateway', 'tool-sms', 'tool-email', 'tool-oth', 'tool-payment'] - -for name, svc in services.items(): - if name in app_services: - # Create blue - blue_svc = copy.deepcopy(svc) - if 'ports' in blue_svc: - del blue_svc['ports'] # Nginx will handle ports - - # Update AXHUB_GATEWAY_URL and AXHUB_TOOL_URL to point to blue if they refer to the base name - if 'environment' in blue_svc: - env = blue_svc['environment'] - for i, e in enumerate(env): - if isinstance(e, str): - env[i] = e.replace('http://gateway:8081', 'http://gateway-blue:8081') - env[i] = env[i].replace(f'http://{name}:', f'http://{name}-blue:') - - new_services[f'{name}-blue'] = blue_svc - - # Create green - green_svc = copy.deepcopy(svc) - if 'ports' in green_svc: - del green_svc['ports'] - - if 'environment' in green_svc: - env = green_svc['environment'] - for i, e in enumerate(env): - if isinstance(e, str): - env[i] = e.replace('http://gateway-blue:8081', 'http://gateway-green:8081') # replace from previous - env[i] = e.replace('http://gateway:8081', 'http://gateway-green:8081') - env[i] = env[i].replace(f'http://{name}:', f'http://{name}-green:') - - new_services[f'{name}-green'] = green_svc - else: - new_services[name] = svc - -# Add nginx -new_services['nginx'] = { - 'image': 'nginx:latest', - 'ports': [ - '8281:8281', - '8282:8282', - '8283:8283', - '8284:8284', - '8285:8285' - ], - 'volumes': [ - './nginx/conf.d:/etc/nginx/conf.d' - ], - 'depends_on': ['redis'] -} - -compose['services'] = new_services - -with open('docker-compose.yml', 'w') as f: - yaml.dump(compose, f, sort_keys=False, default_flow_style=False)