From 22f4fab58d70fd03f28ee5a1c51c0c8992140dda Mon Sep 17 00:00:00 2001 From: jade Date: Fri, 14 Aug 2026 18:16:14 +0900 Subject: [PATCH] Initial commit --- .dockerignore | 10 + .gitattributes | 25 + .gitignore | 124 + AddJavadoc.java | 0 Dockerfile | 50 + HELP.md | 0 McpBridge.java | 0 README.md | 268 +++ build.gradle | 113 + dap-was-cus/Dockerfile | 8 + dap-was-cus/build.gradle | 10 + .../dap/mcc/biz/crm/dto/CrmToolRequest.java | 34 + .../dap/mcc/biz/crm/dto/CrmToolResponse.java | 15 + .../mcc/biz/crm/usecase/CrmToolUseCase.java | 60 + .../crm/usecase/impl/CrmToolUseCaseImpl.java | 95 + .../dap/mcc/biz/voc/dto/VocToolRequest.java | 34 + .../dap/mcc/biz/voc/dto/VocToolResponse.java | 15 + .../mcc/biz/voc/usecase/VocToolUseCase.java | 60 + .../voc/usecase/impl/VocToolUseCaseImpl.java | 95 + .../dap/mcc/biz/wcm/dto/WcmToolRequest.java | 34 + .../dap/mcc/biz/wcm/dto/WcmToolResponse.java | 15 + .../mcc/biz/wcm/usecase/WcmToolUseCase.java | 60 + .../wcm/usecase/impl/WcmToolUseCaseImpl.java | 95 + .../dap/mcc/cus/DapWasCusApplication.java | 33 + .../DtoExcelDownloadController.java | 433 ++++ .../src/main/resources/application-dev.yml | 15 + .../src/main/resources/application-local.yml | 32 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 19 + .../src/main/resources/logback-spring.xml | 41 + .../mock-responses/cmm_customer_tool.json | 4 + .../ins_insurance_processor.json | 4 + .../crm/crm_activity_status.yml | 30 + .../crm/crm_consent_change.yml | 30 + .../crm/crm_consent_detail.yml | 30 + .../crm/crm_consultation_history.yml | 30 + .../crm/crm_consultation_register.yml | 30 + .../crm/crm_contact_history.yml | 30 + .../crm/crm_contact_register.yml | 30 + .../crm/crm_customer_create.yml | 30 + .../crm/crm_customer_detail.yml | 30 + .../crm/crm_customer_duplicate_check.yml | 30 + .../crm/crm_customer_search.yml | 30 + .../crm/crm_customer_update.yml | 30 + .../tool-definitions/crm/crm_grade_change.yml | 30 + .../tool-definitions/crm/crm_grade_detail.yml | 30 + .../tool-definitions/crm/crm_owner_assign.yml | 30 + .../crm/crm_segment_detail.yml | 30 + .../tool-definitions/crm/crm_tag_manage.yml | 30 + .../tool-definitions/voc/voc_assign.yml | 30 + .../tool-definitions/voc/voc_attachments.yml | 30 + .../voc/voc_change_status.yml | 30 + .../voc/voc_classify_type.yml | 30 + .../voc/voc_customer_history.yml | 30 + .../tool-definitions/voc/voc_detail.yml | 30 + .../voc/voc_detect_duplicate.yml | 30 + .../voc/voc_extend_due_date.yml | 30 + .../tool-definitions/voc/voc_register.yml | 30 + .../voc/voc_register_result.yml | 30 + .../tool-definitions/voc/voc_search.yml | 30 + .../tool-definitions/voc/voc_send_reply.yml | 30 + .../tool-definitions/voc/voc_set_priority.yml | 30 + .../tool-definitions/voc/voc_statistics.yml | 30 + .../tool-definitions/voc/voc_transfer.yml | 30 + .../tool-definitions/voc/voc_update.yml | 30 + .../tool-definitions/voc/voc_urgent_list.yml | 30 + .../tool-definitions/wcm/wcm_content_copy.yml | 30 + .../wcm/wcm_content_create.yml | 30 + .../wcm/wcm_content_delete.yml | 30 + .../wcm/wcm_content_detail.yml | 30 + .../tool-definitions/wcm/wcm_content_list.yml | 30 + .../wcm/wcm_content_preview.yml | 30 + .../wcm/wcm_content_publish.yml | 30 + .../wcm/wcm_content_request_approval.yml | 30 + .../wcm/wcm_content_restore_version.yml | 30 + .../wcm/wcm_content_review_approval.yml | 30 + .../wcm/wcm_content_schedule_publish.yml | 30 + .../wcm/wcm_content_unpublish.yml | 30 + .../wcm/wcm_content_update.yml | 30 + .../wcm/wcm_content_version_history.yml | 30 + .../wcm/wcm_metadata_detail.yml | 30 + .../wcm/wcm_metadata_upsert.yml | 30 + .../wcm/wcm_taxonomy_manage.yml | 30 + .../DtoExcelDownloadControllerTest.java | 32 + dap-was-lib/build.gradle | 60 + .../dap/lib/adapter/dto/ErrorDetail.java | 32 + .../dap/lib/adapter/dto/JsonRpcRequest.java | 27 + .../dap/lib/adapter/dto/JsonRpcResponse.java | 30 + .../dap/lib/adapter/dto/Params.java | 38 + .../lib/adapter/test/MockEimsHttpServer.java | 61 + .../util/PiiMaskingLogbackConverter.java | 35 + .../dap/lib/adapter/util/PiiMaskingUtils.java | 77 + .../dap/lib/annotation/GrowToolHint.java | 45 + .../dap/lib/annotation/McpOutputSchema.java | 14 + .../dap/lib/aop/ToolSlaMonitoringAspect.java | 83 + .../lib/config/AxhubHttpConfiguration.java | 29 + .../dap/lib/config/CorsConfig.java | 34 + .../config/GlowCommunicationProperties.java | 85 + .../dap/lib/config/McpProperties.java | 37 + .../dap/lib/config/MybatisConfig.java | 37 + .../dap/lib/config/P6SpySqlFormatter.java | 54 + .../lib/config/ToolSchemaConfiguration.java | 24 + .../dap/lib/dto/OperationType.java | 20 + .../lib/integration/GlowIntegrationCall.java | 105 + .../integration/dto/SampleGlowMessage.java | 84 + .../eai/component/AxhubEaiComponent.java | 99 + .../http/component/AxhubHttpComponent.java | 140 ++ .../http/component/AxhubHttpProperties.java | 34 + .../mci/component/AxhubMciComponent.java | 168 ++ .../mci/config/GlowMockConfig.java | 40 + .../config/ShinhanIntegrationProperties.java | 40 + .../mci/dto/MciRequestWrapper.java | 34 + .../mci/dto/MciResponseWrapper.java | 32 + .../mci/dto/OlCommonHeaderDto.java | 90 + .../mci/dto/ShinhanCommonHeaderDto.java | 72 + .../mci/dto/ShinhanMessageDto.java | 51 + .../mci/dto/ShinhanTelegramWrapper.java | 38 + .../mci/dto/SlCommonHeaderDto.java | 84 + .../mci/enums/IndvCtinRoleTyp.java | 27 + .../lib/manifest/ToolManifestAnnotations.java | 10 + .../dap/lib/manifest/ToolManifestItem.java | 16 + .../dap/lib/manifest/ToolManifestMeta.java | 15 + .../lib/manifest/ToolManifestResponse.java | 7 + .../dap/lib/manifest/ToolManifestService.java | 137 ++ .../dap/lib/mcp/McpRequestHeaderContext.java | 21 + .../dap/lib/mcp/McpRequestHeaderFilter.java | 34 + .../dap/lib/mcp/McpRequestHeaders.java | 9 + .../dap/lib/mcp/McpToolExecutionService.java | 101 + .../dap/lib/mcp/McpToolMethodRegistry.java | 87 + .../dap/lib/mcp/ToolExecutionResult.java | 15 + .../lib/mcp/ToolMcpServerConfiguration.java | 50 + .../dap/lib/mcp/ToolMetadataMcpMapper.java | 73 + .../lib/mcp/ToolPodMcpToolSynchronizer.java | 82 + .../lib/mcp/ToolRegistryHeartbeatSender.java | 291 +++ .../dap/lib/mcp/config/CacheConfig.java | 32 + .../dap/lib/mcp/config/JacksonConfig.java | 41 + .../dap/lib/mcp/config/KafkaLocalConfig.java | 58 + .../dap/lib/mcp/config/SwaggerConfig.java | 47 + .../dap/lib/mcp/config/WebConfig.java | 54 + .../mcp/exception/GlobalExceptionHandler.java | 66 + .../dap/lib/mcp/filter/MdcLoggingFilter.java | 56 + .../lib/mcp/security/ApiKeyInterceptor.java | 80 + .../lib/mcp/security/SecurityProperties.java | 40 + .../dap/lib/metadata/ToolDefinition.java | 37 + .../metadata/ToolDefinitionRepository.java | 62 + .../lib/metadata/ToolDefinitionValidator.java | 84 + .../dap/lib/metadata/ToolDescription.java | 11 + .../dap/lib/session/dto/SessionDto.java | 90 + .../dap/lib/util/JsonSchemaGenerator.java | 205 ++ .../dap/lib/util/PodScaffolder.java | 372 +++ .../shinhanlife/dap/lib/util/SessionUtil.java | 45 + .../lib/util/ToolAnnotationSyncRunner.java | 66 + .../dap/lib/util/ToolScaffolder.java | 2029 +++++++++++++++++ .../dap/lib/util/ToolSchemaResolver.java | 78 + .../dap/lib/util/ToolSourceUpdater.java | 181 ++ .../McpToolNameValidationRunner.java | 21 + .../lib/validation/McpToolNameValidator.java | 173 ++ .../ToolArgumentSchemaValidator.java | 30 + .../ToolSchemaV17ValidationRunner.java | 78 + .../shinhanlife/dap/mcc/dto/ToolMetadata.java | 146 ++ .../presentation/BusinessToolController.java | 50 + .../presentation/ToolManifestController.java | 32 + .../io/shinhanlife/glow/BaseException.java | 29 + .../io/shinhanlife/glow/BaseResponse.java | 25 + .../io/shinhanlife/glow/BizException.java | 61 + .../io/shinhanlife/glow/GlowAppServiceId.java | 13 + .../io/shinhanlife/glow/GlowControllerId.java | 5 + .../io/shinhanlife/glow/GlowIndexPaging.java | 9 + .../io/shinhanlife/glow/GlowLogTarget.java | 15 + .../java/io/shinhanlife/glow/GlowLogger.java | 19 + .../io/shinhanlife/glow/GlowMciFieldInfo.java | 44 + .../shinhanlife/glow/GlowMybatisMapper.java | 18 + .../shinhanlife/glow/GlowServiceGroupId.java | 7 + .../io/shinhanlife/glow/GlowTrgmField.java | 25 + .../java/io/shinhanlife/glow/PageInfo.java | 68 + .../io/shinhanlife/glow/ResponseCode.java | 31 + .../io/shinhanlife/glow/ResponseUtil.java | 111 + .../glow/communication/ICommunication.java | 6 + .../annotation/GlowTrgmField.java | 26 + .../glow/communication/dto/CommonHeader.java | 8 + .../communication/dto/HeaderDefaults.java | 6 + .../glow/communication/dto/Transfer.java | 20 + .../exception/ItrfException.java | 10 + .../eai/component/GlowEaiComponent.java | 26 + .../http/component/GlowHttpComponent.java | 98 + .../module/http/dto/HttpBody.java | 5 + .../module/http/dto/HttpHeader.java | 18 + .../module/http/dto/HttpTransfer.java | 19 + .../mci/component/GlowMciComponent.java | 28 + .../util/CommonHeaderFactory.java | 18 + .../io/shinhanlife/glow/db/dto/AuditInfo.java | 21 + .../glow/db/typehandler/CodeEnum.java | 11 + .../db/typehandler/CodeEnumTypeHandler.java | 59 + .../shinhanlife/glow/util/GlowMciParser.java | 105 + .../shinhanlife/glow/util/GlowTrgmParser.java | 92 + .../resources/glow/application-glow-dev.yml | 19 + .../resources/glow/application-glow-local.yml | 41 + .../resources/glow/application-glow-prod.yml | 19 + .../resources/glow/application-glow-test.yml | 19 + .../main/resources/glow/application-glow.yml | 41 + .../src/main/resources/mock-responses.json | 106 + .../resources/static/tool-test-console.html | 356 +++ .../adapter/test/MockEimsHttpServerTest.java | 19 + .../adapter/sender/ShinhanMciSenderTest.java | 60 + .../config/AxhubHttpConfigurationTest.java | 31 + .../config/ToolSchemaConfigurationTest.java | 22 + .../component/AxhubHttpComponentTest.java | 67 + .../dap/lib/mcp/McpSdk2CompatibilityTest.java | 29 + .../lib/mcp/McpToolMethodRegistryTest.java | 57 + .../dap/lib/mcp/ToolExecutionServiceTest.java | 62 + .../mcp/ToolRegistryHeartbeatSenderTest.java | 56 + .../ToolDefinitionRepositoryTest.java | 73 + .../dap/lib/util/JsonSchemaGeneratorTest.java | 130 ++ .../dap/lib/util/PodScaffolderTest.java | 66 + .../dap/lib/util/ToolScaffolderTest.java | 487 ++++ .../dap/lib/util/ToolSchemaResolverTest.java | 83 + .../dap/lib/util/ToolSourceUpdaterTest.java | 38 + .../validation/McpToolNameValidatorTest.java | 112 + .../mcc/manifest/ToolManifestServiceTest.java | 108 + .../mcc/mcp/McpRequestHeaderFilterTest.java | 60 + .../mcp/ToolMcpServerConfigurationTest.java | 34 + ...inessToolControllerHeaderContractTest.java | 32 + .../ToolArgumentSchemaValidatorTest.java | 26 + .../ToolTestConsoleResourceTest.java | 24 + .../annotation/GlowTrgmFieldContractTest.java | 27 + .../glow/util/GlowMciParserTest.java | 65 + .../mock-responses/cmm_memo_retriever.json | 3 + .../tool-definitions/cmm/cmm_claim_search.yml | 30 + dap-was-pro/Dockerfile | 7 + dap-was-pro/build.gradle | 9 + .../dap/mcc/biz/agt/dto/AgentToolRequest.java | 16 + .../mcc/biz/agt/dto/AgentToolResponse.java | 15 + .../biz/agt/usecase/AgentInquiryUseCase.java | 40 + .../agt/usecase/AgentPerformanceUseCase.java | 44 + .../agt/usecase/GaOrganizationUseCase.java | 40 + .../usecase/impl/AgentInquiryUseCaseImpl.java | 67 + .../impl/AgentPerformanceUseCaseImpl.java | 72 + .../impl/GaOrganizationUseCaseImpl.java | 67 + .../mcc/biz/pro/dto/ProductToolRequest.java | 16 + .../mcc/biz/pro/dto/ProductToolResponse.java | 15 + .../biz/pro/usecase/ClaimInquiryUseCase.java | 40 + .../pro/usecase/ContractInquiryUseCase.java | 44 + .../pro/usecase/CustomerInquiryUseCase.java | 40 + .../usecase/impl/ClaimInquiryUseCaseImpl.java | 66 + .../impl/ContractInquiryUseCaseImpl.java | 71 + .../impl/CustomerInquiryUseCaseImpl.java | 66 + .../dap/mcc/pro/DapWasProApplication.java | 32 + .../src/main/resources/application-dev.yml | 46 + .../src/main/resources/application-local.yml | 45 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 18 + .../src/main/resources/logback-spring.xml | 24 + .../mock-responses/agt_activity_status.json | 5 + .../agt_affiliation_history.json | 5 + .../mock-responses/agt_agent_compliance.json | 5 + .../mock-responses/agt_agent_education.json | 5 + .../mock-responses/agt_agent_license.json | 5 + .../mock-responses/agt_agent_profile.json | 5 + .../agt_appointment_status.json | 5 + .../mock-responses/agt_branch_list.json | 5 + .../mock-responses/agt_campaign.json | 5 + .../mock-responses/agt_claim_support.json | 5 + .../mock-responses/agt_commission_grade.json | 5 + .../agt_commission_summary.json | 5 + .../mock-responses/agt_contract_count.json | 5 + .../agt_customer_assignment.json | 5 + .../mock-responses/agt_ga_profile.json | 5 + .../mock-responses/agt_notice_list.json | 5 + .../agt_organization_agent_list.json | 5 + .../agt_organization_manager.json | 5 + .../mock-responses/agt_organization_tree.json | 5 + .../mock-responses/agt_persistency_rate.json | 5 + .../mock-responses/agt_premium_summary.json | 5 + .../agt_production_summary.json | 5 + .../mock-responses/agt_sales_area.json | 5 + .../mock-responses/agt_support_contact.json | 5 + .../mock-responses/agt_task_list.json | 5 + .../mock-responses/pro_claim_accident.json | 5 + .../mock-responses/pro_claim_beneficiary.json | 5 + .../mock-responses/pro_claim_contact.json | 5 + .../mock-responses/pro_claim_documents.json | 5 + .../mock-responses/pro_claim_eligibility.json | 5 + .../mock-responses/pro_claim_history.json | 5 + .../mock-responses/pro_claim_payment.json | 5 + .../mock-responses/pro_claim_status.json | 5 + .../pro_contract_beneficiary.json | 5 + .../mock-responses/pro_contract_coverage.json | 5 + .../mock-responses/pro_contract_detail.json | 5 + .../mock-responses/pro_contract_list.json | 5 + .../pro_contract_loan_status.json | 5 + .../pro_contract_payment_status.json | 5 + .../mock-responses/pro_contract_premium.json | 5 + .../pro_contract_rider_list.json | 5 + .../pro_contract_surrender_value.json | 5 + .../mock-responses/pro_customer_address.json | 5 + .../mock-responses/pro_customer_consent.json | 5 + .../mock-responses/pro_customer_contact.json | 5 + .../mock-responses/pro_customer_family.json | 5 + .../mock-responses/pro_customer_identity.json | 5 + .../pro_customer_occupation.json | 5 + .../mock-responses/pro_customer_profile.json | 5 + .../pro_customer_risk_grade.json | 5 + .../agt/agt_activity_status.yml | 24 + .../agt/agt_affiliation_history.yml | 24 + .../agt/agt_agent_compliance.yml | 24 + .../agt/agt_agent_education.yml | 24 + .../agt/agt_agent_license.yml | 24 + .../agt/agt_agent_profile.yml | 24 + .../agt/agt_appointment_status.yml | 24 + .../tool-definitions/agt/agt_branch_list.yml | 24 + .../tool-definitions/agt/agt_campaign.yml | 24 + .../agt/agt_claim_support.yml | 24 + .../agt/agt_commission_grade.yml | 24 + .../agt/agt_commission_summary.yml | 24 + .../agt/agt_contract_count.yml | 24 + .../agt/agt_customer_assignment.yml | 24 + .../tool-definitions/agt/agt_ga_profile.yml | 24 + .../tool-definitions/agt/agt_notice_list.yml | 24 + .../agt/agt_organization_agent_list.yml | 24 + .../agt/agt_organization_manager.yml | 24 + .../agt/agt_organization_tree.yml | 24 + .../agt/agt_persistency_rate.yml | 24 + .../agt/agt_premium_summary.yml | 24 + .../agt/agt_production_summary.yml | 24 + .../tool-definitions/agt/agt_sales_area.yml | 24 + .../agt/agt_support_contact.yml | 24 + .../tool-definitions/agt/agt_task_list.yml | 24 + .../pro/pro_claim_accident.yml | 24 + .../pro/pro_claim_beneficiary.yml | 24 + .../pro/pro_claim_contact.yml | 24 + .../pro/pro_claim_documents.yml | 24 + .../pro/pro_claim_eligibility.yml | 24 + .../pro/pro_claim_history.yml | 24 + .../pro/pro_claim_payment.yml | 24 + .../tool-definitions/pro/pro_claim_status.yml | 24 + .../pro/pro_contract_beneficiary.yml | 24 + .../pro/pro_contract_coverage.yml | 24 + .../pro/pro_contract_detail.yml | 24 + .../pro/pro_contract_list.yml | 24 + .../pro/pro_contract_loan_status.yml | 24 + .../pro/pro_contract_payment_status.yml | 24 + .../pro/pro_contract_premium.yml | 24 + .../pro/pro_contract_rider_list.yml | 24 + .../pro/pro_contract_surrender_value.yml | 24 + .../pro/pro_customer_address.yml | 24 + .../pro/pro_customer_consent.yml | 24 + .../pro/pro_customer_contact.yml | 24 + .../pro/pro_customer_family.yml | 24 + .../pro/pro_customer_identity.yml | 24 + .../pro/pro_customer_occupation.yml | 24 + .../pro/pro_customer_profile.yml | 24 + .../pro/pro_customer_risk_grade.yml | 24 + .../impl/AgentInquiryUseCaseImplTest.java | 24 + .../impl/ProductInquiryUseCaseImplTest.java | 24 + .../dap/mcc/pro/ProMockToolResourcesTest.java | 83 + dap-was-sal/Dockerfile | 8 + dap-was-sal/build.gradle | 9 + .../cmm/converter/ClaimSearchConverter.java | 14 + .../converter/MemoListRetrieverConverter.java | 17 + .../mcc/biz/cmm/dto/ClaimSearchRequest.java | 16 + .../mcc/biz/cmm/dto/ClaimSearchResponse.java | 22 + .../biz/cmm/dto/MemoListRetrieverRequest.java | 16 + .../cmm/dto/MemoListRetrieverResponse.java | 13 + .../biz/cmm/usecase/ClaimSearchUseCase.java | 29 + .../cmm/usecase/MemoListRetrieverUseCase.java | 27 + .../usecase/impl/ClaimSearchUseCaseImpl.java | 68 + .../impl/MemoListRetrieverUseCaseImpl.java | 30 + .../dap/mcc/biz/lic/dto/LicToolRequest.java | 20 + .../dap/mcc/biz/lic/dto/LicToolResponse.java | 15 + .../lic/usecase/LicUnderwritingUseCase.java | 58 + .../impl/LicUnderwritingUseCaseImpl.java | 77 + .../dap/mcc/biz/sof/dto/SofToolRequest.java | 20 + .../dap/mcc/biz/sof/dto/SofToolResponse.java | 15 + .../sof/usecase/SofFieldActivityUseCase.java | 58 + .../impl/SofFieldActivityUseCaseImpl.java | 77 + .../dap/mcc/biz/sog/dto/SogToolRequest.java | 20 + .../dap/mcc/biz/sog/dto/SogToolResponse.java | 15 + .../usecase/SogSalesPerformanceUseCase.java | 58 + .../impl/SogSalesPerformanceUseCaseImpl.java | 77 + .../dap/mcc/biz/sot/dto/SotToolRequest.java | 20 + .../dap/mcc/biz/sot/dto/SotToolResponse.java | 15 + .../biz/sot/usecase/SotConsentUseCase.java | 58 + .../usecase/impl/SotConsentUseCaseImpl.java | 77 + .../dap/mcc/biz/spr/dto/SprToolRequest.java | 20 + .../dap/mcc/biz/spr/dto/SprToolResponse.java | 15 + .../spr/usecase/SprFieldInquiryUseCase.java | 58 + .../impl/SprFieldInquiryUseCaseImpl.java | 77 + .../mcc/infra/itrf/http/memo/MemoClient.java | 17 + .../memo/io/MemoListRetrieverHttpRequest.java | 16 + .../io/MemoListRetrieverHttpResponse.java | 13 + .../infra/itrf/mci/ncla/MciNclaClient.java | 30 + .../infra/itrf/mci/ncla/io/CLCNNB00001_I.java | 14 + .../infra/itrf/mci/ncla/io/CLCNNB00001_O.java | 17 + .../dap/mcc/sal/DapWasSalApplication.java | 33 + .../src/main/resources/application-dev.yml | 15 + .../src/main/resources/application-local.yml | 28 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 19 + .../src/main/resources/logback-spring.xml | 41 + .../mock-responses/cmm_memo_retriever.json | 3 + .../lic_contract_review_document.json | 4 + .../lic_contract_review_history.json | 4 + .../lic_contract_review_result.json | 4 + .../lic_contract_review_status.json | 4 + .../mock-responses/lic_product_coverage.json | 4 + .../mock-responses/lic_product_info.json | 4 + .../lic_product_premium_table.json | 4 + .../lic_underwriting_additional_request.json | 4 + .../lic_underwriting_result.json | 4 + .../lic_underwriting_risk_grade.json | 4 + .../mock-responses/sms_cmm_claim_search.json | 5 + .../mock-responses/sof_activity_calendar.json | 4 + .../mock-responses/sof_activity_daily.json | 4 + .../sof_activity_report_status.json | 4 + .../mock-responses/sof_activity_summary.json | 4 + .../mock-responses/sof_call_activity.json | 4 + .../sof_customer_consulting.json | 4 + .../sof_field_support_request.json | 4 + .../mock-responses/sof_prospect_list.json | 4 + .../sof_training_completion.json | 4 + .../mock-responses/sof_visit_count.json | 4 + .../mock-responses/sog_incentive_summary.json | 4 + .../sog_monthly_performance.json | 4 + .../sog_new_contract_count.json | 4 + .../sog_performance_comparison.json | 4 + .../sog_premium_performance.json | 4 + .../sog_product_performance.json | 4 + .../mock-responses/sog_sales_ranking.json | 4 + .../mock-responses/sog_target_history.json | 4 + .../mock-responses/sog_target_progress.json | 4 + .../mock-responses/sog_target_summary.json | 4 + .../mock-responses/sot_consent_expiry.json | 4 + .../sot_consent_withdrawal.json | 4 + .../sot_customer_info_consent.json | 4 + .../sot_customer_info_consent_scope.json | 4 + .../sot_marketing_consent_channel.json | 4 + .../sot_marketing_consent_history.json | 4 + .../sot_marketing_consent_status.json | 4 + .../sot_subscription_design_consent.json | 4 + .../sot_subscription_design_detail.json | 4 + .../sot_tm_call_eligibility.json | 4 + .../spr_field_inquiry_answer.json | 4 + .../spr_field_inquiry_category.json | 4 + .../spr_field_inquiry_detail.json | 4 + .../spr_field_inquiry_escalation.json | 4 + .../mock-responses/spr_field_inquiry_faq.json | 4 + .../spr_field_inquiry_list.json | 4 + .../spr_field_inquiry_manager.json | 4 + .../spr_field_inquiry_satisfaction.json | 4 + .../mock-responses/spr_field_inquiry_sla.json | 4 + .../spr_field_inquiry_status.json | 4 + .../tool-definitions/cmm/cmm_claim_search.yml | 24 + .../cmm/cmm_memo_retriever.yml | 24 + .../lic/lic_contract_review_document.yml | 24 + .../lic/lic_contract_review_history.yml | 24 + .../lic/lic_contract_review_result.yml | 24 + .../lic/lic_contract_review_status.yml | 24 + .../lic/lic_product_coverage.yml | 24 + .../tool-definitions/lic/lic_product_info.yml | 24 + .../lic/lic_product_premium_table.yml | 24 + .../lic_underwriting_additional_request.yml | 24 + .../lic/lic_underwriting_result.yml | 24 + .../lic/lic_underwriting_risk_grade.yml | 24 + .../sof/sof_activity_calendar.yml | 24 + .../sof/sof_activity_daily.yml | 24 + .../sof/sof_activity_report_status.yml | 24 + .../sof/sof_activity_summary.yml | 24 + .../sof/sof_call_activity.yml | 24 + .../sof/sof_customer_consulting.yml | 24 + .../sof/sof_field_support_request.yml | 24 + .../sof/sof_prospect_list.yml | 24 + .../sof/sof_training_completion.yml | 24 + .../tool-definitions/sof/sof_visit_count.yml | 24 + .../sog/sog_incentive_summary.yml | 24 + .../sog/sog_monthly_performance.yml | 24 + .../sog/sog_new_contract_count.yml | 24 + .../sog/sog_performance_comparison.yml | 24 + .../sog/sog_premium_performance.yml | 24 + .../sog/sog_product_performance.yml | 24 + .../sog/sog_sales_ranking.yml | 24 + .../sog/sog_target_history.yml | 24 + .../sog/sog_target_progress.yml | 24 + .../sog/sog_target_summary.yml | 24 + .../sot/sot_consent_expiry.yml | 24 + .../sot/sot_consent_withdrawal.yml | 24 + .../sot/sot_customer_info_consent.yml | 24 + .../sot/sot_customer_info_consent_scope.yml | 24 + .../sot/sot_marketing_consent_channel.yml | 24 + .../sot/sot_marketing_consent_history.yml | 24 + .../sot/sot_marketing_consent_status.yml | 24 + .../sot/sot_subscription_design_consent.yml | 24 + .../sot/sot_subscription_design_detail.yml | 24 + .../sot/sot_tm_call_eligibility.yml | 24 + .../spr/spr_field_inquiry_answer.yml | 24 + .../spr/spr_field_inquiry_category.yml | 24 + .../spr/spr_field_inquiry_detail.yml | 24 + .../spr/spr_field_inquiry_escalation.yml | 24 + .../spr/spr_field_inquiry_faq.yml | 24 + .../spr/spr_field_inquiry_list.yml | 24 + .../spr/spr_field_inquiry_manager.yml | 24 + .../spr/spr_field_inquiry_satisfaction.yml | 24 + .../spr/spr_field_inquiry_sla.yml | 24 + .../spr/spr_field_inquiry_status.yml | 24 + .../claim-search-resource-input-schema.json | 11 + .../claim-search-resource-output-schema.json | 16 + .../cmm/usecase/ClaimSearchUseCaseTest.java | 16 + .../usecase/MemoListRetrieverUseCaseTest.java | 16 + .../impl/LicUnderwritingUseCaseImplTest.java | 24 + .../impl/SofFieldActivityUseCaseImplTest.java | 24 + .../SogSalesPerformanceUseCaseImplTest.java | 24 + .../impl/SotConsentUseCaseImplTest.java | 24 + .../impl/SprFieldInquiryUseCaseImplTest.java | 24 + .../sal/SalCategoryMockToolResourcesTest.java | 102 + dap-was-sys/Dockerfile | 7 + dap-was-sys/build.gradle | 9 + .../mcc/biz/iam/dto/SystemStatusRequest.java | 10 + .../mcc/biz/iam/dto/SystemStatusResponse.java | 11 + .../iam/usecase/AccountInquiryUseCase.java | 32 + .../usecase/AuthenticationInquiryUseCase.java | 32 + .../usecase/AuthorizationInquiryUseCase.java | 32 + .../biz/iam/usecase/SystemStatusUseCase.java | 45 + .../impl/AccountInquiryUseCaseImpl.java | 48 + .../AuthenticationInquiryUseCaseImpl.java | 48 + .../impl/AuthorizationInquiryUseCaseImpl.java | 48 + .../usecase/impl/SystemStatusUseCaseImpl.java | 30 + .../mcc/biz/pct/dto/SystemNoticeRequest.java | 10 + .../mcc/biz/pct/dto/SystemNoticeResponse.java | 16 + .../usecase/MaintenanceInquiryUseCase.java | 32 + .../pct/usecase/ReleaseInquiryUseCase.java | 32 + .../biz/pct/usecase/SystemNoticeUseCase.java | 48 + .../impl/MaintenanceInquiryUseCaseImpl.java | 49 + .../impl/ReleaseInquiryUseCaseImpl.java | 49 + .../usecase/impl/SystemNoticeUseCaseImpl.java | 31 + .../dap/mcc/sys/DapWasSysApplication.java | 32 + .../src/main/resources/application-dev.yml | 46 + .../src/main/resources/application-local.yml | 45 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 18 + .../src/main/resources/logback-spring.xml | 24 + .../mock-responses/iam_access_policy.json | 4 + .../iam_access_token_status.json | 4 + .../mock-responses/iam_account_list.json | 4 + .../iam_account_lock_status.json | 4 + .../iam_account_password_expiry.json | 4 + .../mock-responses/iam_account_profile.json | 4 + .../mock-responses/iam_account_role.json | 4 + .../mock-responses/iam_account_status.json | 4 + .../iam_approval_authority.json | 4 + .../iam_authentication_method.json | 4 + .../mock-responses/iam_delegation_status.json | 4 + .../iam_deployment_version.json | 4 + .../mock-responses/iam_environment_info.json | 4 + .../iam_identity_provider_list.json | 1 + .../mock-responses/iam_login_history.json | 4 + .../mock-responses/iam_menu_access.json | 4 + .../mock-responses/iam_mfa_status.json | 4 + .../mock-responses/iam_permission_list.json | 4 + .../iam_privilege_change_history.json | 1 + .../mock-responses/iam_resource_access.json | 4 + .../mock-responses/iam_role_list.json | 4 + .../iam_security_event_list.json | 1 + .../mock-responses/iam_service_health.json | 4 + .../mock-responses/iam_session_list.json | 4 + .../mock-responses/iam_session_status.json | 4 + .../mock-responses/iam_system_status.json | 4 + .../iam_trusted_device_list.json | 4 + .../mock-responses/iam_user_group_list.json | 1 + .../mock-responses/pct_alert_list.json | 4 + .../pct_change_request_list.json | 1 + .../mock-responses/pct_incident_list.json | 1 + .../pct_maintenance_contact.json | 4 + .../pct_maintenance_history.json | 4 + .../pct_maintenance_result.json | 4 + .../pct_maintenance_schedule.json | 4 + .../mock-responses/pct_maintenance_scope.json | 4 + .../pct_maintenance_status.json | 4 + .../mock-responses/pct_notice_category.json | 4 + .../mock-responses/pct_notice_detail.json | 4 + .../mock-responses/pct_notice_list.json | 4 + .../mock-responses/pct_notice_recent.json | 1 + .../mock-responses/pct_notice_search.json | 1 + .../pct_operation_calendar.json | 1 + .../mock-responses/pct_release_history.json | 4 + .../mock-responses/pct_release_notice.json | 4 + .../mock-responses/pct_release_schedule.json | 4 + .../mock-responses/pct_release_scope.json | 4 + .../mock-responses/pct_release_status.json | 4 + .../mock-responses/pct_release_version.json | 4 + .../pct_service_request_status.json | 1 + .../iam/iam_access_policy.yml | 22 + .../iam/iam_access_token_status.yml | 22 + .../tool-definitions/iam/iam_account_list.yml | 22 + .../iam/iam_account_lock_status.yml | 22 + .../iam/iam_account_password_expiry.yml | 22 + .../iam/iam_account_profile.yml | 22 + .../tool-definitions/iam/iam_account_role.yml | 22 + .../iam/iam_account_status.yml | 22 + .../iam/iam_approval_authority.yml | 22 + .../iam/iam_authentication_method.yml | 22 + .../iam/iam_delegation_status.yml | 22 + .../iam/iam_deployment_version.yml | 14 + .../iam/iam_environment_info.yml | 14 + .../iam/iam_identity_provider_list.yml | 22 + .../iam/iam_login_history.yml | 22 + .../tool-definitions/iam/iam_menu_access.yml | 22 + .../tool-definitions/iam/iam_mfa_status.yml | 22 + .../iam/iam_permission_list.yml | 22 + .../iam/iam_privilege_change_history.yml | 22 + .../iam/iam_resource_access.yml | 22 + .../tool-definitions/iam/iam_role_list.yml | 22 + .../iam/iam_security_event_list.yml | 22 + .../iam/iam_service_health.yml | 14 + .../tool-definitions/iam/iam_session_list.yml | 22 + .../iam/iam_session_status.yml | 22 + .../iam/iam_system_status.yml | 14 + .../iam/iam_trusted_device_list.yml | 22 + .../iam/iam_user_group_list.yml | 22 + .../tool-definitions/pct/pct_alert_list.yml | 14 + .../pct/pct_change_request_list.yml | 22 + .../pct/pct_incident_list.yml | 22 + .../pct/pct_maintenance_contact.yml | 22 + .../pct/pct_maintenance_history.yml | 22 + .../pct/pct_maintenance_result.yml | 22 + .../pct/pct_maintenance_schedule.yml | 14 + .../pct/pct_maintenance_scope.yml | 22 + .../pct/pct_maintenance_status.yml | 22 + .../pct/pct_notice_category.yml | 22 + .../pct/pct_notice_detail.yml | 22 + .../tool-definitions/pct/pct_notice_list.yml | 14 + .../pct/pct_notice_recent.yml | 14 + .../pct/pct_notice_search.yml | 14 + .../pct/pct_operation_calendar.yml | 22 + .../pct/pct_release_history.yml | 22 + .../pct/pct_release_notice.yml | 14 + .../pct/pct_release_schedule.yml | 22 + .../pct/pct_release_scope.yml | 22 + .../pct/pct_release_status.yml | 22 + .../pct/pct_release_version.yml | 22 + .../pct/pct_service_request_status.yml | 22 + .../impl/SystemStatusUseCaseImplTest.java | 22 + .../impl/SystemNoticeUseCaseImplTest.java | 21 + .../mcc/sys/SystemMockToolResourcesTest.java | 82 + docker-compose.yml | 76 + .../2026-08-06-mcp-sdk-validator-updater.md | 61 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++ gradlew.bat | 94 + mci_sample_test.http | 35 + settings.gradle | 5 + sql.txt | 283 +++ sse_out.txt | 2 + 656 files changed, 22614 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 AddJavadoc.java create mode 100644 Dockerfile create mode 100644 HELP.md create mode 100644 McpBridge.java create mode 100644 README.md create mode 100644 build.gradle create mode 100644 dap-was-cus/Dockerfile create mode 100644 dap-was-cus/build.gradle create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolRequest.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolResponse.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/CrmToolUseCase.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/impl/CrmToolUseCaseImpl.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolRequest.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolResponse.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/VocToolUseCase.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/impl/VocToolUseCaseImpl.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolRequest.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolResponse.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/WcmToolUseCase.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/impl/WcmToolUseCaseImpl.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/cus/DapWasCusApplication.java create mode 100644 dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java create mode 100644 dap-was-cus/src/main/resources/application-dev.yml create mode 100644 dap-was-cus/src/main/resources/application-local.yml create mode 100644 dap-was-cus/src/main/resources/application-prod.yml create mode 100644 dap-was-cus/src/main/resources/application-test.yml create mode 100644 dap-was-cus/src/main/resources/application.yml create mode 100644 dap-was-cus/src/main/resources/logback-spring.xml create mode 100644 dap-was-cus/src/main/resources/mock-responses/cmm_customer_tool.json create mode 100644 dap-was-cus/src/main/resources/mock-responses/ins_insurance_processor.json create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_activity_status.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_change.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_history.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_register.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_history.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_register.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_create.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_duplicate_check.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_search.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_update.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_change.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_owner_assign.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_segment_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/crm/crm_tag_manage.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_assign.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_attachments.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_change_status.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_classify_type.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_customer_history.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_detect_duplicate.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_extend_due_date.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_register.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_register_result.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_search.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_send_reply.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_set_priority.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_statistics.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_transfer.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_update.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/voc/voc_urgent_list.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_copy.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_create.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_delete.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_list.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_preview.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_publish.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_request_approval.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_restore_version.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_review_approval.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_schedule_publish.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_unpublish.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_update.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_version_history.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_detail.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_upsert.yml create mode 100644 dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_taxonomy_manage.yml create mode 100644 dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadControllerTest.java create mode 100644 dap-was-lib/build.gradle create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/GrowToolHint.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMetadataMcpMapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinition.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepository.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionValidator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDescription.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolAnnotationSyncRunner.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolSchemaV17ValidationRunner.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnum.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnumTypeHandler.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-dev.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-local.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-prod.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-test.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow.yml create mode 100644 dap-was-lib/src/main/resources/mock-responses.json create mode 100644 dap-was-lib/src/main/resources/static/tool-test-console.html create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpSdk2CompatibilityTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepositoryTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java create mode 100644 dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json create mode 100644 dap-was-lib/src/test/resources/tool-definitions/cmm/cmm_claim_search.yml create mode 100644 dap-was-pro/Dockerfile create mode 100644 dap-was-pro/build.gradle create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolRequest.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolResponse.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentInquiryUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentPerformanceUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/GaOrganizationUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentPerformanceUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/GaOrganizationUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolRequest.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolResponse.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ClaimInquiryUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ContractInquiryUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/CustomerInquiryUseCase.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ClaimInquiryUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ContractInquiryUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/CustomerInquiryUseCaseImpl.java create mode 100644 dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/pro/DapWasProApplication.java create mode 100644 dap-was-pro/src/main/resources/application-dev.yml create mode 100644 dap-was-pro/src/main/resources/application-local.yml create mode 100644 dap-was-pro/src/main/resources/application-prod.yml create mode 100644 dap-was-pro/src/main/resources/application-test.yml create mode 100644 dap-was-pro/src/main/resources/application.yml create mode 100644 dap-was-pro/src/main/resources/logback-spring.xml create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_activity_status.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_affiliation_history.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_agent_compliance.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_agent_education.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_agent_license.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_agent_profile.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_appointment_status.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_branch_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_campaign.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_claim_support.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_commission_grade.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_commission_summary.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_contract_count.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_customer_assignment.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_ga_profile.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_notice_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_organization_agent_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_organization_manager.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_organization_tree.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_persistency_rate.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_premium_summary.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_production_summary.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_sales_area.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_support_contact.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/agt_task_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_accident.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_beneficiary.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_contact.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_documents.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_eligibility.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_history.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_payment.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_claim_status.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_beneficiary.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_coverage.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_detail.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_loan_status.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_payment_status.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_premium.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_rider_list.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_contract_surrender_value.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_address.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_consent.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_contact.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_family.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_identity.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_occupation.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_profile.json create mode 100644 dap-was-pro/src/main/resources/mock-responses/pro_customer_risk_grade.json create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_activity_status.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_affiliation_history.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_compliance.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_education.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_license.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_profile.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_appointment_status.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_branch_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_campaign.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_claim_support.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_grade.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_summary.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_contract_count.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_customer_assignment.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_ga_profile.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_notice_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_agent_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_manager.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_tree.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_persistency_rate.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_premium_summary.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_production_summary.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_sales_area.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_support_contact.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/agt/agt_task_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_accident.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_beneficiary.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_contact.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_documents.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_eligibility.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_history.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_payment.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_status.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_beneficiary.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_coverage.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_detail.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_loan_status.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_payment_status.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_premium.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_rider_list.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_surrender_value.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_address.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_consent.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_contact.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_family.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_identity.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_occupation.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_profile.yml create mode 100644 dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_risk_grade.yml create mode 100644 dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImplTest.java create mode 100644 dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ProductInquiryUseCaseImplTest.java create mode 100644 dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/pro/ProMockToolResourcesTest.java create mode 100644 dap-was-sal/Dockerfile create mode 100644 dap-was-sal/build.gradle create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/LicUnderwritingUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/SofFieldActivityUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/SogSalesPerformanceUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/SotConsentUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/SprFieldInquiryUseCase.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImpl.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java create mode 100644 dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/sal/DapWasSalApplication.java create mode 100644 dap-was-sal/src/main/resources/application-dev.yml create mode 100644 dap-was-sal/src/main/resources/application-local.yml create mode 100644 dap-was-sal/src/main/resources/application-prod.yml create mode 100644 dap-was-sal/src/main/resources/application-test.yml create mode 100644 dap-was-sal/src/main/resources/application.yml create mode 100644 dap-was-sal/src/main/resources/logback-spring.xml create mode 100644 dap-was-sal/src/main/resources/mock-responses/cmm_memo_retriever.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_contract_review_document.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_contract_review_history.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_contract_review_result.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_contract_review_status.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_product_coverage.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_product_info.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_product_premium_table.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_underwriting_additional_request.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_underwriting_result.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/lic_underwriting_risk_grade.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sms_cmm_claim_search.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_activity_calendar.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_activity_daily.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_activity_report_status.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_activity_summary.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_call_activity.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_customer_consulting.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_field_support_request.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_prospect_list.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_training_completion.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sof_visit_count.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_incentive_summary.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_monthly_performance.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_new_contract_count.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_performance_comparison.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_premium_performance.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_product_performance.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_sales_ranking.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_target_history.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_target_progress.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sog_target_summary.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_consent_expiry.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_consent_withdrawal.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent_scope.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_channel.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_history.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_status.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_consent.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_detail.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/sot_tm_call_eligibility.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_answer.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_category.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_detail.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_escalation.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_faq.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_list.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_manager.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_satisfaction.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_sla.json create mode 100644 dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_status.json create mode 100644 dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_memo_retriever.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_document.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_history.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_result.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_status.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_coverage.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_info.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_premium_table.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_additional_request.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_result.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_risk_grade.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_calendar.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_daily.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_report_status.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_summary.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_call_activity.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_customer_consulting.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_field_support_request.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_prospect_list.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_training_completion.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sof/sof_visit_count.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_incentive_summary.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_monthly_performance.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_new_contract_count.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_performance_comparison.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_premium_performance.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_product_performance.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_sales_ranking.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_history.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_progress.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_summary.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_expiry.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_withdrawal.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent_scope.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_channel.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_history.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_status.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_consent.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_detail.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/sot/sot_tm_call_eligibility.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_answer.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_category.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_detail.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_escalation.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_faq.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_list.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_manager.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_satisfaction.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_sla.yml create mode 100644 dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_status.yml create mode 100644 dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json create mode 100644 dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImplTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImplTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImplTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImplTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImplTest.java create mode 100644 dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/sal/SalCategoryMockToolResourcesTest.java create mode 100644 dap-was-sys/Dockerfile create mode 100644 dap-was-sys/build.gradle create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusRequest.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusResponse.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AccountInquiryUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthenticationInquiryUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthorizationInquiryUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/SystemStatusUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AccountInquiryUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthenticationInquiryUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthorizationInquiryUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeRequest.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeResponse.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/MaintenanceInquiryUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/ReleaseInquiryUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/SystemNoticeUseCase.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/MaintenanceInquiryUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/ReleaseInquiryUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImpl.java create mode 100644 dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/sys/DapWasSysApplication.java create mode 100644 dap-was-sys/src/main/resources/application-dev.yml create mode 100644 dap-was-sys/src/main/resources/application-local.yml create mode 100644 dap-was-sys/src/main/resources/application-prod.yml create mode 100644 dap-was-sys/src/main/resources/application-test.yml create mode 100644 dap-was-sys/src/main/resources/application.yml create mode 100644 dap-was-sys/src/main/resources/logback-spring.xml create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_access_policy.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_access_token_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_lock_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_password_expiry.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_profile.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_role.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_account_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_approval_authority.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_authentication_method.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_delegation_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_deployment_version.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_environment_info.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_identity_provider_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_login_history.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_menu_access.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_mfa_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_permission_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_privilege_change_history.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_resource_access.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_role_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_security_event_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_service_health.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_session_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_session_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_system_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_trusted_device_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/iam_user_group_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_alert_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_change_request_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_incident_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_contact.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_history.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_result.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_schedule.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_scope.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_maintenance_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_notice_category.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_notice_detail.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_notice_list.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_notice_recent.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_notice_search.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_operation_calendar.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_history.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_notice.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_schedule.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_scope.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_status.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_release_version.json create mode 100644 dap-was-sys/src/main/resources/mock-responses/pct_service_request_status.json create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_policy.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_token_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_lock_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_password_expiry.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_profile.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_role.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_approval_authority.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_authentication_method.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_delegation_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_deployment_version.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_environment_info.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_identity_provider_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_login_history.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_menu_access.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_mfa_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_permission_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_privilege_change_history.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_resource_access.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_role_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_security_event_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_service_health.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_system_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_trusted_device_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/iam/iam_user_group_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_alert_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_change_request_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_incident_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_contact.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_history.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_result.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_schedule.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_scope.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_category.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_detail.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_list.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_recent.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_search.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_operation_calendar.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_history.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_notice.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_schedule.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_scope.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_status.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_version.yml create mode 100644 dap-was-sys/src/main/resources/tool-definitions/pct/pct_service_request_status.yml create mode 100644 dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImplTest.java create mode 100644 dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImplTest.java create mode 100644 dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/sys/SystemMockToolResourcesTest.java create mode 100644 docker-compose.yml create mode 100644 docs/superpowers/plans/2026-08-06-mcp-sdk-validator-updater.md create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 mci_sample_test.http create mode 100644 settings.gradle create mode 100644 sql.txt create mode 100644 sse_out.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..a351ee1bf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.gradle +.idea +/build/ +target/ +*/target/ +bin/ +*/bin/ +out/ +*/out/ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..3ef0642dc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# 湲곕낯媛? 紐⑤뱺 ?띿뒪???뚯씪 CRLF (Windows 媛쒕컻 ?섍꼍) +* text=auto eol=crlf + +# Shell ?ㅽ겕由쏀듃??LF 怨좎젙 (Linux CI/CD ?ㅽ뻾 ?섍꼍) +*.sh text eol=lf +mvnw text eol=lf +gradlew text eol=lf + +# 諛붿씠?덈━ ?뚯씪 (以꾨컮轅?蹂€???쒖쇅) +*.jar binary +*.war binary +*.ear binary +*.class binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.svg binary +*.ttf binary +*.woff binary +*.woff2binary +*.pdf binary +*.zip binary +*.tar.gz binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6f25a3424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ +# ===== 鍮뚮뱶 寃곌낵臾?===== +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ +*.class +*.jar +!gradle/wrapper/gradle-wrapper.jar +*.war +*.ear +*.nar + +# ===== 濡쒓렇 ===== +*.log +logs/ +spring-shell.log + +# ===== Maven ===== +.mvn/wrapper/maven-wrapper.jar +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# ===== IntelliJ IDEA ===== +.idea/ +*.iws +*.iml +*.ipr +out/ + +# ===== Eclipse / STS ===== +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ + +# ===== NetBeans ===== +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +.gradle/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +# ===== VS Code ===== +.vscode/ + +# ===== 鍮뚮뱶 寃곌낵臾?===== +target/ +!**/src/main/**/target/ +!**/src/test/**/target/ +*.class +*.jar +!gradle/wrapper/gradle-wrapper.jar +*.war +*.ear +*.nar + +# ===== 濡쒓렇 ===== +*.log +logs/ +spring-shell.log + +# ===== Maven ===== +.mvn/wrapper/maven-wrapper.jar +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# ===== IntelliJ IDEA ===== +.idea/ +*.iws +*.iml +*.ipr +out/ + +# ===== Eclipse / STS ===== +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ + +# ===== NetBeans ===== +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +.gradle/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +# ===== VS Code ===== +.vscode/ + +# ===== OS ===== +.DS_Store +Thumbs.db +ehthumbs.db + +# ===== ?섍꼍?ㅼ젙 (誘쇨컧?뺣낫 遺꾨━ ?? ===== +# application-local.yml +# application-secret.yml diff --git a/AddJavadoc.java b/AddJavadoc.java new file mode 100644 index 000000000..e69de29bb diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..c731b709e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +# 1. 빌드 환경 (JDK 21) +# ----------------------------------------------------------------------------- +# 외부 인터넷이 차단된 내부망에서는 Docker Hub 대신 사내 Container Registry의 +# Java 21 이미지를 사용합니다. 인프라 담당자에게 이미지의 전체 경로와 태그를 +# 받은 후 아래 FROM 행만 교체합니다. +# +# 예: FROM registry.shinhanlife.co.kr/base/openjdk:21 AS builder +# +# 이 단계는 Gradle 빌드와 Java 컴파일을 수행하므로 반드시 JDK 21 이미지여야 합니다. +# 사내 이미지가 실제로 JDK 21인지 다음 명령으로 확인합니다. +# docker run --rm <사내-JDK-이미지> java -version +FROM eclipse-temurin:21-jdk-alpine AS builder +WORKDIR /app + +# Gradle Wrapper와 소스 파일을 복사합니다. +COPY gradlew . +COPY gradle gradle +COPY build.gradle settings.gradle ./ +COPY src src + +# Wrapper 실행 권한을 부여하고 테스트를 제외한 빌드를 수행합니다. +RUN chmod +x gradlew +RUN ./gradlew clean build -x test + +# 2. 실행 환경 (JRE 21) +# ----------------------------------------------------------------------------- +# 실행 단계는 JRE 21 이미지가 가장 가볍지만, 사내에서 JDK 21 이미지만 제공하는 +# 경우에는 동일한 JDK 21 이미지를 사용해도 정상 동작합니다. +# +# 예: FROM registry.shinhanlife.co.kr/base/openjre:21 +# 예: FROM registry.shinhanlife.co.kr/base/openjdk:21 +# +# 사내 제공 이미지의 기반 OS를 확인합니다. +# - Alpine 기반: 아래 apk 명령을 그대로 사용합니다. +# - Ubuntu/Debian 기반: apk 대신 apt-get update && apt-get install -y tzdata를 사용합니다. +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app + +# 서울 시간대를 설정합니다. +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul + +# 빌드 단계에서 생성한 애플리케이션 JAR를 복사합니다. +COPY --from=builder /app/build/libs/*.jar app.jar + +# 애플리케이션 포트를 노출합니다. +EXPOSE 8081 + +# 애플리케이션을 실행합니다. +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/HELP.md b/HELP.md new file mode 100644 index 000000000..e69de29bb diff --git a/McpBridge.java b/McpBridge.java new file mode 100644 index 000000000..e69de29bb diff --git a/README.md b/README.md new file mode 100644 index 000000000..5df6d7a7c --- /dev/null +++ b/README.md @@ -0,0 +1,268 @@ +# DAP WAS Tool Pods + +신한라이프 업무 시스템과 MCP(Model Context Protocol) 클라이언트를 연결하는 독립형 Tool WAS 프로젝트입니다. 이 저장소에는 Gateway가 포함되어 있지 않습니다. 각 Tool Pod가 직접 MCP Streamable HTTP와 REST 실행 API를 제공하고, 업무 요청은 `UseCase → Converter → MCI/EAI 연동`으로 처리합니다. + +> 이 문서는 현재 `main`의 구현과 설정을 기준으로 합니다. 과거 `dap-gateway`, Chat API, SSE, 외부 Tool Registry/Heartbeat 관련 문서는 현재 저장소의 동작 범위가 아니므로 포함하지 않습니다. + +## 구성 + +```text +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 모듈 + +| 모듈 | 역할 | 기본 포트 | +|---|---|---:| +| `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.0.5, Gradle Wrapper 8.14.3, Spring AI MCP Server WebMVC, Redis, MapStruct, MyBatis, Resilience4j입니다. + +## 현재 제공 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 서버에 추가합니다. + +| 속성 | 현재 구현에서의 의미 | +|---|---| +| `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에 반영됩니다. | + +`POST /mcp/{name}`의 동적 실행은 `visible` 및 `register` 값으로 차단하지 않습니다. 따라서 직접 호출을 막아야 하는 Tool은 네트워크 경계와 별도 인증·인가 정책으로 보호해야 합니다. + +### 현재 Tool 선언 현황 + +`dap-was-oth`에는 실제 `@McpFunction` 선언이 17개 있습니다. 도메인은 다음과 같습니다. + +| 도메인 | 예시 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 요청 조회 | + +`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 그룹은 인터페이스에 `@McpTool`, Agent가 호출하는 메서드는 `@McpFunction`을 선언합니다. `@McpTool`은 Spring `@Component` 별칭이므로 Tool 인터페이스와 구현체는 Spring Bean으로 구성되어야 합니다. + +```java +@McpTool(routingType = "MCI", categoryKey = "claim") +public interface ClaimInquiryUseCase { + + @McpFunction( + name = "oth.claim.inquiry.detail", + displayName = "청구 상세 조회", + description = "청구 번호로 청구 상세를 조회합니다.", + mappingId = "CLM00000001", + readOnlyHint = true + ) + ClaimInquiryResponse inquire(ClaimInquiryRequest request); +} +``` + +구현체에는 업무 흐름만 두고, Tool DTO와 레거시 인터페이스 DTO의 변환은 Converter에 둡니다. + +```java +@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); + } +} +``` + +`@McpFunction.name`은 소문자 점 표기 형식으로 작성합니다. 현재 선언은 `oth.cmm.claim.search`, `oth.smp.weather.inquiry`처럼 `{pod}.{domain}.{service}.{action}` 패턴을 사용합니다. `validateMcpToolNames` Gradle 작업은 모든 Tool 모듈을 대상으로 이름 형식과 중복을 검사하며, 패키징 빌드 전에 실행됩니다. + +## 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 +dap-was-oth/src/main/resources/tool-schemas/cmm/ +├─ claim-search-resource-input-schema.json +└─ claim-search-resource-output-schema.json +``` + +입력 검증에는 JSON Schema Draft 7이 사용됩니다. 출력 Schema 검증에 실패하면 `500 INVALID_TOOL_RESPONSE`을 반환합니다. + +## 로컬 실행 + +### 사전 조건 + +- JDK 21 +- Docker (Redis 또는 MCI mock을 사용할 경우) +- Gradle Wrapper 사용 권장 + +로컬 프로필은 기본값이며, 두 Pod 모두 H2 메모리 DB와 P6Spy를 설정합니다. Pod URL은 `AXHUB_TOOL_URL` 환경 변수로 설정하며, 지정하지 않으면 해당 `server.port`의 localhost 주소를 사용합니다. + +```powershell +# 필수: Redis 기동 (캐시 및 세션 처리용) +$env:ACTIVE_PROFILE = 'local' +docker compose up -d redis + +# OTH Tool Pod 실행 +$env:SPRING_PROFILES_ACTIVE = 'local' +$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 +``` + +실행 후 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 +``` + +`tool-test-console.html`은 공통 라이브러리의 정적 리소스입니다. `/tool-manifest`에서 Tool과 입력 Schema를 읽어 요청 JSON을 만들고, 현재 Pod의 `/mcp/{toolName}`으로 호출합니다. 저장한 테스트 케이스는 브라우저 `localStorage`에 보관됩니다. + +## 테스트와 빌드 + +```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 | +| `was-sms` | 8082 | 8282 | +| `was-oth` | 8084 | 8284 | + +Compose의 Pod URL은 컨테이너 DNS 이름을 사용합니다. + +```text +was-sms: http://was-sms:8082 +was-oth: http://was-oth:8084 +``` + +### Docker 컨테이너 기동 + +별도의 CI/CD 러너나 외부 의존성(MCI Mock 등) 없이 독립적으로 실행 가능하도록 구성되어 있습니다. `docker-compose.yml`을 통해 Redis 및 각 Pod 컨테이너를 구동할 수 있습니다. + + +## 설정 + +| 설정 | 위치/환경 변수 | 설명 | +|---|---|---| +| 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/lib/presentation/BusinessToolController.java` | +| Manifest API | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/ToolManifestController.java` | +| MCP Streamable HTTP | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java` | +| MCP Tool 동기화 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java` | +| Tool 스캔 | `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/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/build.gradle b/build.gradle new file mode 100644 index 000000000..c2758d335 --- /dev/null +++ b/build.gradle @@ -0,0 +1,113 @@ +// 루트 Gradle 설정: 모든 Gateway/Tool Pod 모듈이 공유하는 빌드 기준을 정의합니다. +plugins { + // Java 컴파일, 테스트, JAR 생성 기능을 제공합니다. + id 'java' + + // 하위 실행 모듈에서 bootRun/bootJar를 사용하기 위한 Spring Boot 플러그인입니다. + // 루트 프로젝트에는 적용하지 않으므로 apply false를 사용합니다. + id 'org.springframework.boot' version '3.5.11' apply false + + // Spring Boot / Spring AI BOM에 정의된 라이브러리 버전을 일관되게 적용합니다. + id 'io.spring.dependency-management' version '1.1.6' apply false +} + +// 모든 모듈이 공유하는 Maven 식별자입니다. +allprojects { + group = 'io.shinhanlife' + version = '0.0.1-SNAPSHOT' +} + +// dap-gateway, dap-was-lib, dap-was-oth, dap-was-sms에 공통 적용합니다. +subprojects { + apply plugin: 'java' + apply plugin: 'io.spring.dependency-management' + + java { + // 프로젝트 표준 Java 버전입니다. + sourceCompatibility = '21' + } + + repositories { + // 현재 공개 정식 라이브러리 저장소입니다. + // 폐쇄망 적용 시 사내 Nexus Proxy URL로 교체합니다. + mavenCentral() + } + + dependencyManagement { + imports { + // Spring Boot 3.5.11과 호환되는 Spring/Jackson/Tomcat 등의 버전을 관리합니다. + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES + + // Boot 3.5.11과 호환되는 Spring AI 계층은 1.1.8로 유지합니다. + mavenBom 'org.springframework.ai:spring-ai-bom:1.1.8' + + // MCP Java SDK만 2.0.0으로 올립니다. Spring AI 2.x 전체 BOM은 Boot 4 기반이므로 사용하지 않습니다. + mavenBom 'io.modelcontextprotocol.sdk:mcp-bom:2.0.0' + + // MCP SDK 2.0의 Jackson 2 전송 모듈이 요구하는 호환 버전입니다. + mavenBom 'com.fasterxml.jackson:jackson-bom:2.20.1' + } + } + + dependencies { + // Lombok은 컴파일 시 getter/builder 등 반복 코드를 생성하며 실행 JAR에는 포함하지 않습니다. + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' + testCompileOnly 'org.projectlombok:lombok:1.18.32' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' + + // Tool DTO와 MCI 요청/응답 객체 간 Converter 구현체를 컴파일 시 자동 생성합니다. + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + + // 모든 모듈의 JUnit 5 기반 테스트 공통 의존성입니다. + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + } + + tasks.withType(JavaCompile) { + // 리플렉션/MCP Schema 생성 시 메서드 파라미터명을 사용할 수 있도록 보존합니다. + options.compilerArgs << '-parameters' + + // MapStruct 구현체를 Spring Bean으로 생성해 생성자 주입으로 사용할 수 있게 합니다. + options.compilerArgs << '-Amapstruct.defaultComponentModel=spring' + } + + tasks.withType(Test) { + // JUnit 5 테스트 플랫폼을 사용합니다. + useJUnitPlatform() + } +} + +// Tool 이름 중복 검사 프로그램이 포함된 공통 라이브러리 모듈입니다. +def toolCoreProject = project(':dap-was-lib') + +// 전체 Tool Pod의 @McpTool(name) 중복을 배포 산출물 생성 전에 차단합니다. +tasks.register('validateMcpToolNames', JavaExec) { + group = 'verification' + description = 'Checks duplicate @McpTool names across all Tool modules before packaging.' + + // 검사 Runner를 실행하기 전에 dap-was-lib 클래스를 먼저 컴파일합니다. + dependsOn toolCoreProject.tasks.named('classes') + classpath = toolCoreProject.sourceSets.main.runtimeClasspath + mainClass.set('io.shinhanlife.dap.lib.validation.McpToolNameValidationRunner') + args rootProject.projectDir.absolutePath +} + +tasks.register('validateToolSchemaV17', JavaExec) { + group = 'verification' + description = 'Validates BC-DAB-STD-003 V17 definitions for every @McpTool.' + dependsOn toolCoreProject.tasks.named('classes') + classpath = toolCoreProject.sourceSets.main.runtimeClasspath + mainClass.set('io.shinhanlife.dap.lib.validation.ToolSchemaV17ValidationRunner') + args rootProject.projectDir.absolutePath +} + +subprojects { + // 배포용 Spring Boot JAR 생성 전에 Tool 이름 중복 검증을 강제합니다. + tasks.matching { it.name == 'bootJar' }.configureEach { + dependsOn rootProject.tasks.named('validateMcpToolNames') + dependsOn rootProject.tasks.named('validateToolSchemaV17') + } +} diff --git a/dap-was-cus/Dockerfile b/dap-was-cus/Dockerfile new file mode 100644 index 000000000..5666a1775 --- /dev/null +++ b/dap-was-cus/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-cus/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8084 +ENTRYPOINT ["java", "-jar", "app.jar"] + diff --git a/dap-was-cus/build.gradle b/dap-was-cus/build.gradle new file mode 100644 index 000000000..85b964196 --- /dev/null +++ b/dap-was-cus/build.gradle @@ -0,0 +1,10 @@ +plugins { + // OTH Tool Pod를 독립 실행 가능한 Spring Boot JAR로 생성합니다. + id 'org.springframework.boot' +} + +dependencies { + // MCP Server, Tool 공통 처리, MCI/EAI 연동 기반은 dap-was-lib에서 상속합니다. + implementation project(':dap-was-lib') + implementation 'org.apache.poi:poi-ooxml:5.3.0' +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolRequest.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolRequest.java new file mode 100644 index 000000000..bf0022f5b --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolRequest.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.mcc.biz.crm.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class CrmToolRequest { + @McpToolParam(description = "업무 대상 식별자", required = false) + private String subjectId; + + @McpToolParam(description = "고객 식별자", required = false) + private String customerId; + + @McpToolParam(description = "검색어 또는 처리 내용", required = false) + private String content; + + @McpToolParam(description = "상태, 유형, 등급 또는 처리 값", required = false) + private String value; + + @McpToolParam(description = "담당자 식별자", required = false) + private String assigneeId; + + @McpToolParam(description = "조회 시작일(YYYY-MM-DD)", required = false) + private String startDate; + + @McpToolParam(description = "조회 종료일 또는 처리 예정일(YYYY-MM-DD)", required = false) + private String endDate; + + @McpToolParam(description = "페이지 번호", required = false) + private Integer page; + + @McpToolParam(description = "페이지 크기", required = false) + private Integer size; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolResponse.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolResponse.java new file mode 100644 index 000000000..5880deb4c --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/dto/CrmToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.crm.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class CrmToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String processedAt; + private List details; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/CrmToolUseCase.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/CrmToolUseCase.java new file mode 100644 index 000000000..507c25194 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/CrmToolUseCase.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.mcc.biz.crm.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.crm.dto.CrmToolRequest; +import io.shinhanlife.dap.mcc.biz.crm.dto.CrmToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface CrmToolUseCase { + @McpTool(name = "crm_customer_search", title = "고객 통합 조회", description = "고객 통합 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CUSTOMER_SEARCH") + CrmToolResponse searchCustomers(CrmToolRequest request); + @McpTool(name = "crm_customer_detail", title = "고객 상세 정보 조회", description = "고객 상세 정보 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CUSTOMER_DETAIL") + CrmToolResponse getCustomerDetail(CrmToolRequest request); + @McpTool(name = "crm_customer_create", title = "고객 정보 등록", description = "고객 정보 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_CUSTOMER_CREATE") + CrmToolResponse createCustomer(CrmToolRequest request); + @McpTool(name = "crm_customer_update", title = "고객 정보 수정", description = "고객 정보 수정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_CUSTOMER_UPDATE") + CrmToolResponse updateCustomer(CrmToolRequest request); + @McpTool(name = "crm_customer_duplicate_check", title = "고객 중복 여부 확인", description = "고객 중복 여부 확인 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CUSTOMER_DUPLICATE_CHECK") + CrmToolResponse checkDuplicateCustomer(CrmToolRequest request); + @McpTool(name = "crm_consultation_history", title = "고객 상담 이력 조회", description = "고객 상담 이력 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CONSULTATION_HISTORY") + CrmToolResponse getConsultationHistory(CrmToolRequest request); + @McpTool(name = "crm_consultation_register", title = "고객 상담 이력 등록", description = "고객 상담 이력 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_CONSULTATION_REGISTER") + CrmToolResponse registerConsultation(CrmToolRequest request); + @McpTool(name = "crm_contact_history", title = "고객 접촉 이력 조회", description = "고객 접촉 이력 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CONTACT_HISTORY") + CrmToolResponse getContactHistory(CrmToolRequest request); + @McpTool(name = "crm_contact_register", title = "고객 접촉 이력 등록", description = "고객 접촉 이력 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_CONTACT_REGISTER") + CrmToolResponse registerContact(CrmToolRequest request); + @McpTool(name = "crm_grade_detail", title = "고객 등급 조회", description = "고객 등급 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_GRADE_DETAIL") + CrmToolResponse getCustomerGrade(CrmToolRequest request); + @McpTool(name = "crm_grade_change", title = "고객 등급 변경", description = "고객 등급 변경 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_GRADE_CHANGE") + CrmToolResponse changeCustomerGrade(CrmToolRequest request); + @McpTool(name = "crm_segment_detail", title = "고객 세그먼트 조회", description = "고객 세그먼트 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_SEGMENT_DETAIL") + CrmToolResponse getCustomerSegment(CrmToolRequest request); + @McpTool(name = "crm_tag_manage", title = "고객 태그 관리", description = "고객 태그 관리 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_TAG_MANAGE") + CrmToolResponse manageCustomerTags(CrmToolRequest request); + @McpTool(name = "crm_consent_detail", title = "고객 동의 정보 조회", description = "고객 동의 정보 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_CONSENT_DETAIL") + CrmToolResponse getCustomerConsent(CrmToolRequest request); + @McpTool(name = "crm_consent_change", title = "고객 동의 정보 변경", description = "고객 동의 정보 변경 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_CONSENT_CHANGE") + CrmToolResponse changeCustomerConsent(CrmToolRequest request); + @McpTool(name = "crm_owner_assign", title = "고객 담당자 배정", description = "고객 담당자 배정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "crm", mappingId = "DIRECT_CRM_OWNER_ASSIGN") + CrmToolResponse assignCustomerOwner(CrmToolRequest request); + @McpTool(name = "crm_activity_status", title = "고객 활동 현황 조회", description = "고객 활동 현황 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "crm", mappingId = "DIRECT_CRM_ACTIVITY_STATUS") + CrmToolResponse getCustomerActivityStatus(CrmToolRequest request); +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/impl/CrmToolUseCaseImpl.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/impl/CrmToolUseCaseImpl.java new file mode 100644 index 000000000..ed6e01ed7 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/crm/usecase/impl/CrmToolUseCaseImpl.java @@ -0,0 +1,95 @@ +package io.shinhanlife.dap.mcc.biz.crm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.crm.dto.CrmToolRequest; +import io.shinhanlife.dap.mcc.biz.crm.dto.CrmToolResponse; +import io.shinhanlife.dap.mcc.biz.crm.usecase.CrmToolUseCase; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class CrmToolUseCaseImpl implements CrmToolUseCase { + @Override + public CrmToolResponse searchCustomers(CrmToolRequest request) { + return mockResponse(request, "crm_customer_search", "고객 통합 조회"); + } + @Override + public CrmToolResponse getCustomerDetail(CrmToolRequest request) { + return mockResponse(request, "crm_customer_detail", "고객 상세 정보 조회"); + } + @Override + public CrmToolResponse createCustomer(CrmToolRequest request) { + return mockResponse(request, "crm_customer_create", "고객 정보 등록"); + } + @Override + public CrmToolResponse updateCustomer(CrmToolRequest request) { + return mockResponse(request, "crm_customer_update", "고객 정보 수정"); + } + @Override + public CrmToolResponse checkDuplicateCustomer(CrmToolRequest request) { + return mockResponse(request, "crm_customer_duplicate_check", "고객 중복 여부 확인"); + } + @Override + public CrmToolResponse getConsultationHistory(CrmToolRequest request) { + return mockResponse(request, "crm_consultation_history", "고객 상담 이력 조회"); + } + @Override + public CrmToolResponse registerConsultation(CrmToolRequest request) { + return mockResponse(request, "crm_consultation_register", "고객 상담 이력 등록"); + } + @Override + public CrmToolResponse getContactHistory(CrmToolRequest request) { + return mockResponse(request, "crm_contact_history", "고객 접촉 이력 조회"); + } + @Override + public CrmToolResponse registerContact(CrmToolRequest request) { + return mockResponse(request, "crm_contact_register", "고객 접촉 이력 등록"); + } + @Override + public CrmToolResponse getCustomerGrade(CrmToolRequest request) { + return mockResponse(request, "crm_grade_detail", "고객 등급 조회"); + } + @Override + public CrmToolResponse changeCustomerGrade(CrmToolRequest request) { + return mockResponse(request, "crm_grade_change", "고객 등급 변경"); + } + @Override + public CrmToolResponse getCustomerSegment(CrmToolRequest request) { + return mockResponse(request, "crm_segment_detail", "고객 세그먼트 조회"); + } + @Override + public CrmToolResponse manageCustomerTags(CrmToolRequest request) { + return mockResponse(request, "crm_tag_manage", "고객 태그 관리"); + } + @Override + public CrmToolResponse getCustomerConsent(CrmToolRequest request) { + return mockResponse(request, "crm_consent_detail", "고객 동의 정보 조회"); + } + @Override + public CrmToolResponse changeCustomerConsent(CrmToolRequest request) { + return mockResponse(request, "crm_consent_change", "고객 동의 정보 변경"); + } + @Override + public CrmToolResponse assignCustomerOwner(CrmToolRequest request) { + return mockResponse(request, "crm_owner_assign", "고객 담당자 배정"); + } + @Override + public CrmToolResponse getCustomerActivityStatus(CrmToolRequest request) { + return mockResponse(request, "crm_activity_status", "고객 활동 현황 조회"); + } + private CrmToolResponse mockResponse(CrmToolRequest request, String toolName, String title) { + CrmToolResponse response = new CrmToolResponse(); + response.setToolName(toolName); + response.setCategory("CRM"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "CRM-000001"); + response.setStatus("정상"); + response.setSummary(title + " 처리 결과"); + response.setProcessedAt(LocalDateTime.now().toString()); + response.setDetails(List.of(title + " 모의 상세 정보", "외부 시스템을 변경하지 않는 샘플 응답")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolRequest.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolRequest.java new file mode 100644 index 000000000..85d852be1 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolRequest.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.mcc.biz.voc.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class VocToolRequest { + @McpToolParam(description = "업무 대상 식별자", required = false) + private String subjectId; + + @McpToolParam(description = "고객 식별자", required = false) + private String customerId; + + @McpToolParam(description = "검색어 또는 처리 내용", required = false) + private String content; + + @McpToolParam(description = "상태, 유형, 등급 또는 처리 값", required = false) + private String value; + + @McpToolParam(description = "담당자 식별자", required = false) + private String assigneeId; + + @McpToolParam(description = "조회 시작일(YYYY-MM-DD)", required = false) + private String startDate; + + @McpToolParam(description = "조회 종료일 또는 처리 예정일(YYYY-MM-DD)", required = false) + private String endDate; + + @McpToolParam(description = "페이지 번호", required = false) + private Integer page; + + @McpToolParam(description = "페이지 크기", required = false) + private Integer size; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolResponse.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolResponse.java new file mode 100644 index 000000000..1b9b28df1 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/dto/VocToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.voc.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class VocToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String processedAt; + private List details; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/VocToolUseCase.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/VocToolUseCase.java new file mode 100644 index 000000000..b9aa6e799 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/VocToolUseCase.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.mcc.biz.voc.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.voc.dto.VocToolRequest; +import io.shinhanlife.dap.mcc.biz.voc.dto.VocToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface VocToolUseCase { + @McpTool(name = "voc_register", title = "VOC 접수 등록", description = "VOC 접수 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_REGISTER") + VocToolResponse registerVoc(VocToolRequest request); + @McpTool(name = "voc_detail", title = "VOC 상세 조회", description = "VOC 상세 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_DETAIL") + VocToolResponse getVocDetail(VocToolRequest request); + @McpTool(name = "voc_search", title = "VOC 목록 검색", description = "VOC 목록 검색 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_SEARCH") + VocToolResponse searchVocs(VocToolRequest request); + @McpTool(name = "voc_update", title = "VOC 내용 수정", description = "VOC 내용 수정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_UPDATE") + VocToolResponse updateVoc(VocToolRequest request); + @McpTool(name = "voc_assign", title = "VOC 담당자 배정", description = "VOC 담당자 배정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_ASSIGN") + VocToolResponse assignVocOwner(VocToolRequest request); + @McpTool(name = "voc_change_status", title = "VOC 처리 상태 변경", description = "VOC 처리 상태 변경 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_CHANGE_STATUS") + VocToolResponse changeVocStatus(VocToolRequest request); + @McpTool(name = "voc_register_result", title = "VOC 처리 결과 등록", description = "VOC 처리 결과 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_REGISTER_RESULT") + VocToolResponse registerVocResult(VocToolRequest request); + @McpTool(name = "voc_send_reply", title = "VOC 답변 발송", description = "VOC 답변 발송 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_SEND_REPLY") + VocToolResponse sendVocReply(VocToolRequest request); + @McpTool(name = "voc_transfer", title = "VOC 이관 처리", description = "VOC 이관 처리 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_TRANSFER") + VocToolResponse transferVoc(VocToolRequest request); + @McpTool(name = "voc_set_priority", title = "VOC 우선순위 설정", description = "VOC 우선순위 설정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_SET_PRIORITY") + VocToolResponse setVocPriority(VocToolRequest request); + @McpTool(name = "voc_classify_type", title = "VOC 유형 분류", description = "VOC 유형 분류 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_CLASSIFY_TYPE") + VocToolResponse classifyVocType(VocToolRequest request); + @McpTool(name = "voc_attachments", title = "VOC 첨부파일 조회", description = "VOC 첨부파일 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_ATTACHMENTS") + VocToolResponse getVocAttachments(VocToolRequest request); + @McpTool(name = "voc_customer_history", title = "고객별 VOC 이력 조회", description = "고객별 VOC 이력 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_CUSTOMER_HISTORY") + VocToolResponse getCustomerVocHistory(VocToolRequest request); + @McpTool(name = "voc_detect_duplicate", title = "중복 VOC 탐지", description = "중복 VOC 탐지 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_DETECT_DUPLICATE") + VocToolResponse detectDuplicateVoc(VocToolRequest request); + @McpTool(name = "voc_urgent_list", title = "긴급 VOC 목록 조회", description = "긴급 VOC 목록 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_URGENT_LIST") + VocToolResponse getUrgentVocs(VocToolRequest request); + @McpTool(name = "voc_extend_due_date", title = "VOC 처리 기한 연장", description = "VOC 처리 기한 연장 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "voc", mappingId = "DIRECT_VOC_EXTEND_DUE_DATE") + VocToolResponse extendVocDueDate(VocToolRequest request); + @McpTool(name = "voc_statistics", title = "VOC 통계 조회", description = "VOC 통계 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "voc", mappingId = "DIRECT_VOC_STATISTICS") + VocToolResponse getVocStatistics(VocToolRequest request); +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/impl/VocToolUseCaseImpl.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/impl/VocToolUseCaseImpl.java new file mode 100644 index 000000000..314d10cc4 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/voc/usecase/impl/VocToolUseCaseImpl.java @@ -0,0 +1,95 @@ +package io.shinhanlife.dap.mcc.biz.voc.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.voc.dto.VocToolRequest; +import io.shinhanlife.dap.mcc.biz.voc.dto.VocToolResponse; +import io.shinhanlife.dap.mcc.biz.voc.usecase.VocToolUseCase; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class VocToolUseCaseImpl implements VocToolUseCase { + @Override + public VocToolResponse registerVoc(VocToolRequest request) { + return mockResponse(request, "voc_register", "VOC 접수 등록"); + } + @Override + public VocToolResponse getVocDetail(VocToolRequest request) { + return mockResponse(request, "voc_detail", "VOC 상세 조회"); + } + @Override + public VocToolResponse searchVocs(VocToolRequest request) { + return mockResponse(request, "voc_search", "VOC 목록 검색"); + } + @Override + public VocToolResponse updateVoc(VocToolRequest request) { + return mockResponse(request, "voc_update", "VOC 내용 수정"); + } + @Override + public VocToolResponse assignVocOwner(VocToolRequest request) { + return mockResponse(request, "voc_assign", "VOC 담당자 배정"); + } + @Override + public VocToolResponse changeVocStatus(VocToolRequest request) { + return mockResponse(request, "voc_change_status", "VOC 처리 상태 변경"); + } + @Override + public VocToolResponse registerVocResult(VocToolRequest request) { + return mockResponse(request, "voc_register_result", "VOC 처리 결과 등록"); + } + @Override + public VocToolResponse sendVocReply(VocToolRequest request) { + return mockResponse(request, "voc_send_reply", "VOC 답변 발송"); + } + @Override + public VocToolResponse transferVoc(VocToolRequest request) { + return mockResponse(request, "voc_transfer", "VOC 이관 처리"); + } + @Override + public VocToolResponse setVocPriority(VocToolRequest request) { + return mockResponse(request, "voc_set_priority", "VOC 우선순위 설정"); + } + @Override + public VocToolResponse classifyVocType(VocToolRequest request) { + return mockResponse(request, "voc_classify_type", "VOC 유형 분류"); + } + @Override + public VocToolResponse getVocAttachments(VocToolRequest request) { + return mockResponse(request, "voc_attachments", "VOC 첨부파일 조회"); + } + @Override + public VocToolResponse getCustomerVocHistory(VocToolRequest request) { + return mockResponse(request, "voc_customer_history", "고객별 VOC 이력 조회"); + } + @Override + public VocToolResponse detectDuplicateVoc(VocToolRequest request) { + return mockResponse(request, "voc_detect_duplicate", "중복 VOC 탐지"); + } + @Override + public VocToolResponse getUrgentVocs(VocToolRequest request) { + return mockResponse(request, "voc_urgent_list", "긴급 VOC 목록 조회"); + } + @Override + public VocToolResponse extendVocDueDate(VocToolRequest request) { + return mockResponse(request, "voc_extend_due_date", "VOC 처리 기한 연장"); + } + @Override + public VocToolResponse getVocStatistics(VocToolRequest request) { + return mockResponse(request, "voc_statistics", "VOC 통계 조회"); + } + private VocToolResponse mockResponse(VocToolRequest request, String toolName, String title) { + VocToolResponse response = new VocToolResponse(); + response.setToolName(toolName); + response.setCategory("VOC"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "VOC-000001"); + response.setStatus("정상"); + response.setSummary(title + " 처리 결과"); + response.setProcessedAt(LocalDateTime.now().toString()); + response.setDetails(List.of(title + " 모의 상세 정보", "외부 시스템을 변경하지 않는 샘플 응답")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolRequest.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolRequest.java new file mode 100644 index 000000000..e78723bd0 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolRequest.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.mcc.biz.wcm.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class WcmToolRequest { + @McpToolParam(description = "업무 대상 식별자", required = false) + private String subjectId; + + @McpToolParam(description = "고객 식별자", required = false) + private String customerId; + + @McpToolParam(description = "검색어 또는 처리 내용", required = false) + private String content; + + @McpToolParam(description = "상태, 유형, 등급 또는 처리 값", required = false) + private String value; + + @McpToolParam(description = "담당자 식별자", required = false) + private String assigneeId; + + @McpToolParam(description = "조회 시작일(YYYY-MM-DD)", required = false) + private String startDate; + + @McpToolParam(description = "조회 종료일 또는 처리 예정일(YYYY-MM-DD)", required = false) + private String endDate; + + @McpToolParam(description = "페이지 번호", required = false) + private Integer page; + + @McpToolParam(description = "페이지 크기", required = false) + private Integer size; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolResponse.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolResponse.java new file mode 100644 index 000000000..2111d9763 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/dto/WcmToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.wcm.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class WcmToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String processedAt; + private List details; +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/WcmToolUseCase.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/WcmToolUseCase.java new file mode 100644 index 000000000..bd4d78a2e --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/WcmToolUseCase.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.mcc.biz.wcm.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.wcm.dto.WcmToolRequest; +import io.shinhanlife.dap.mcc.biz.wcm.dto.WcmToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface WcmToolUseCase { + @McpTool(name = "wcm_content_list", title = "웹 콘텐츠 목록 조회", description = "웹 콘텐츠 목록 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_LIST") + WcmToolResponse getContentList(WcmToolRequest request); + @McpTool(name = "wcm_content_detail", title = "웹 콘텐츠 상세 조회", description = "웹 콘텐츠 상세 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_DETAIL") + WcmToolResponse getContentDetail(WcmToolRequest request); + @McpTool(name = "wcm_content_create", title = "웹 콘텐츠 등록", description = "웹 콘텐츠 등록 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_CREATE") + WcmToolResponse createContent(WcmToolRequest request); + @McpTool(name = "wcm_content_update", title = "웹 콘텐츠 수정", description = "웹 콘텐츠 수정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_UPDATE") + WcmToolResponse updateContent(WcmToolRequest request); + @McpTool(name = "wcm_content_delete", title = "웹 콘텐츠 삭제", description = "웹 콘텐츠 삭제 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_DELETE") + WcmToolResponse deleteContent(WcmToolRequest request); + @McpTool(name = "wcm_content_copy", title = "웹 콘텐츠 복사", description = "웹 콘텐츠 복사 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_COPY") + WcmToolResponse copyContent(WcmToolRequest request); + @McpTool(name = "wcm_content_preview", title = "웹 콘텐츠 미리보기", description = "웹 콘텐츠 미리보기 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_PREVIEW") + WcmToolResponse previewContent(WcmToolRequest request); + @McpTool(name = "wcm_content_publish", title = "웹 콘텐츠 게시", description = "웹 콘텐츠 게시 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_PUBLISH") + WcmToolResponse publishContent(WcmToolRequest request); + @McpTool(name = "wcm_content_unpublish", title = "웹 콘텐츠 게시 중지", description = "웹 콘텐츠 게시 중지 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_UNPUBLISH") + WcmToolResponse unpublishContent(WcmToolRequest request); + @McpTool(name = "wcm_content_schedule_publish", title = "웹 콘텐츠 예약 게시", description = "웹 콘텐츠 예약 게시 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_SCHEDULE_PUBLISH") + WcmToolResponse scheduleContentPublish(WcmToolRequest request); + @McpTool(name = "wcm_content_request_approval", title = "웹 콘텐츠 승인 요청", description = "웹 콘텐츠 승인 요청 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_REQUEST_APPROVAL") + WcmToolResponse requestContentApproval(WcmToolRequest request); + @McpTool(name = "wcm_content_review_approval", title = "웹 콘텐츠 승인·반려", description = "웹 콘텐츠 승인·반려 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_REVIEW_APPROVAL") + WcmToolResponse reviewContentApproval(WcmToolRequest request); + @McpTool(name = "wcm_content_version_history", title = "웹 콘텐츠 버전 이력 조회", description = "웹 콘텐츠 버전 이력 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_VERSION_HISTORY") + WcmToolResponse getContentVersionHistory(WcmToolRequest request); + @McpTool(name = "wcm_content_restore_version", title = "웹 콘텐츠 이전 버전 복원", description = "웹 콘텐츠 이전 버전 복원 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_CONTENT_RESTORE_VERSION") + WcmToolResponse restoreContentVersion(WcmToolRequest request); + @McpTool(name = "wcm_metadata_detail", title = "콘텐츠 메타데이터 조회", description = "콘텐츠 메타데이터 조회 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "wcm", mappingId = "DIRECT_WCM_METADATA_DETAIL") + WcmToolResponse getContentMetadata(WcmToolRequest request); + @McpTool(name = "wcm_metadata_upsert", title = "콘텐츠 메타데이터 등록·수정", description = "콘텐츠 메타데이터 등록·수정 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_METADATA_UPSERT") + WcmToolResponse upsertContentMetadata(WcmToolRequest request); + @McpTool(name = "wcm_taxonomy_manage", title = "콘텐츠 카테고리·태그 관리", description = "콘텐츠 카테고리·태그 관리 기능을 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = true, categoryKey = "wcm", mappingId = "DIRECT_WCM_TAXONOMY_MANAGE") + WcmToolResponse manageContentTaxonomy(WcmToolRequest request); +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/impl/WcmToolUseCaseImpl.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/impl/WcmToolUseCaseImpl.java new file mode 100644 index 000000000..30f42052a --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/biz/wcm/usecase/impl/WcmToolUseCaseImpl.java @@ -0,0 +1,95 @@ +package io.shinhanlife.dap.mcc.biz.wcm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.wcm.dto.WcmToolRequest; +import io.shinhanlife.dap.mcc.biz.wcm.dto.WcmToolResponse; +import io.shinhanlife.dap.mcc.biz.wcm.usecase.WcmToolUseCase; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class WcmToolUseCaseImpl implements WcmToolUseCase { + @Override + public WcmToolResponse getContentList(WcmToolRequest request) { + return mockResponse(request, "wcm_content_list", "웹 콘텐츠 목록 조회"); + } + @Override + public WcmToolResponse getContentDetail(WcmToolRequest request) { + return mockResponse(request, "wcm_content_detail", "웹 콘텐츠 상세 조회"); + } + @Override + public WcmToolResponse createContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_create", "웹 콘텐츠 등록"); + } + @Override + public WcmToolResponse updateContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_update", "웹 콘텐츠 수정"); + } + @Override + public WcmToolResponse deleteContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_delete", "웹 콘텐츠 삭제"); + } + @Override + public WcmToolResponse copyContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_copy", "웹 콘텐츠 복사"); + } + @Override + public WcmToolResponse previewContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_preview", "웹 콘텐츠 미리보기"); + } + @Override + public WcmToolResponse publishContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_publish", "웹 콘텐츠 게시"); + } + @Override + public WcmToolResponse unpublishContent(WcmToolRequest request) { + return mockResponse(request, "wcm_content_unpublish", "웹 콘텐츠 게시 중지"); + } + @Override + public WcmToolResponse scheduleContentPublish(WcmToolRequest request) { + return mockResponse(request, "wcm_content_schedule_publish", "웹 콘텐츠 예약 게시"); + } + @Override + public WcmToolResponse requestContentApproval(WcmToolRequest request) { + return mockResponse(request, "wcm_content_request_approval", "웹 콘텐츠 승인 요청"); + } + @Override + public WcmToolResponse reviewContentApproval(WcmToolRequest request) { + return mockResponse(request, "wcm_content_review_approval", "웹 콘텐츠 승인·반려"); + } + @Override + public WcmToolResponse getContentVersionHistory(WcmToolRequest request) { + return mockResponse(request, "wcm_content_version_history", "웹 콘텐츠 버전 이력 조회"); + } + @Override + public WcmToolResponse restoreContentVersion(WcmToolRequest request) { + return mockResponse(request, "wcm_content_restore_version", "웹 콘텐츠 이전 버전 복원"); + } + @Override + public WcmToolResponse getContentMetadata(WcmToolRequest request) { + return mockResponse(request, "wcm_metadata_detail", "콘텐츠 메타데이터 조회"); + } + @Override + public WcmToolResponse upsertContentMetadata(WcmToolRequest request) { + return mockResponse(request, "wcm_metadata_upsert", "콘텐츠 메타데이터 등록·수정"); + } + @Override + public WcmToolResponse manageContentTaxonomy(WcmToolRequest request) { + return mockResponse(request, "wcm_taxonomy_manage", "콘텐츠 카테고리·태그 관리"); + } + private WcmToolResponse mockResponse(WcmToolRequest request, String toolName, String title) { + WcmToolResponse response = new WcmToolResponse(); + response.setToolName(toolName); + response.setCategory("WCMS"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "WCMS-000001"); + response.setStatus("정상"); + response.setSummary(title + " 처리 결과"); + response.setProcessedAt(LocalDateTime.now().toString()); + response.setDetails(List.of(title + " 모의 상세 정보", "외부 시스템을 변경하지 않는 샘플 응답")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/cus/DapWasCusApplication.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/cus/DapWasCusApplication.java new file mode 100644 index 000000000..2d170ed8d --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/cus/DapWasCusApplication.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.cus; + + +/** + * @package io.shinhanlife.dap.mcc.cus + * @className DapWasCusApplication + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; + +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +@Import(ToolMcpServerConfiguration.class) +public class DapWasCusApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasCusApplication.class, args); + } +} diff --git a/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java new file mode 100644 index 000000000..7088729f7 --- /dev/null +++ b/dap-was-cus/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java @@ -0,0 +1,433 @@ +package io.shinhanlife.dap.mcc.presentation; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * MCI DTO 클래스를 조회하고 인터페이스 설계서 형식의 엑셀 파일을 생성한다. + * 외부 템플릿 파일에 의존하지 않고 Apache POI로 양식과 데이터를 모두 만든다. + */ +@RestController +public class DtoExcelDownloadController { + + private static final int FIRST_FIELD_ROW = 12; + private static final int TEMPLATE_LAST_ROW = 35; + private static final int COLUMN_COUNT = 20; + private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + private static final String MCI_BASE_PACKAGE = "io.shinhanlife.dap.mcc.infra.itrf.mci"; + private static final String INVALID_DTO_MESSAGE = + "dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요"; + private static final Pattern VARIANT_SUFFIX = Pattern.compile("_[IO]$"); + + private final Map dtoClasses; + + public DtoExcelDownloadController() { + this.dtoClasses = scanDtoClasses(); + } + + @GetMapping("/dto-download/options") + public List options() { + // DTO는 항상 _I/_O 한 쌍으로 존재하므로 접미사를 제거한 이름 단위로 묶어 화면에 제공한다. + Set baseNames = new TreeSet<>(); + for (String simpleName : dtoClasses.keySet()) { + baseNames.add(VARIANT_SUFFIX.matcher(simpleName).replaceFirst("")); + } + return List.copyOf(baseNames); + } + + @GetMapping("/dto-download/{dtoName}") + public ResponseEntity download(@PathVariable String dtoName) throws IOException { + // 스캔되지 않은 이름을 받아 임의 클래스를 조회하지 못하도록 제한한다. + String className = dtoClasses.get(dtoName); + if (className == null) { + return ResponseEntity.notFound().build(); + } + byte[] workbook = createWorkbook(dtoName, className); + String fileName = dtoName + ".xlsx"; + return ResponseEntity.ok() + .contentType(XLSX_MEDIA_TYPE) + .contentLength(workbook.length) + .header(HttpHeaders.CONTENT_DISPOSITION, + ContentDisposition.attachment().filename(fileName).build().toString()) + .body(workbook); + } + + @ExceptionHandler(DtoFormatException.class) + public ResponseEntity handleInvalidDto() { + return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY) + .contentType(MediaType.parseMediaType("text/plain;charset=UTF-8")) + .body(INVALID_DTO_MESSAGE); + } + + private byte[] createWorkbook(String dtoName, String className) throws IOException { + // 요청마다 새 워크북을 생성하므로 여러 사용자의 다운로드가 서로 영향을 주지 않는다. + try (XSSFWorkbook workbook = createTemplateWorkbook(); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { + Sheet sheet = workbook.getSheetAt(0); + setText(sheet, 2, 2, dtoName); + setText(sheet, 3, 2, dtoName); + + List fields; + try { + fields = describeFields(Class.forName(className)); + } catch (ReflectiveOperationException error) { + throw new IOException("DTO class could not be inspected: " + className, error); + } + writeFields(sheet, fields); + workbook.write(output); + return output.toByteArray(); + } + } + + private XSSFWorkbook createTemplateWorkbook() { + // 기준 문서의 시트명, 열 너비, 병합, 색상과 테두리를 코드로 재현한다. + XSSFWorkbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("대내"); + sheet.setDisplayGridlines(false); + sheet.createFreezePane(0, 12); + sheet.getPrintSetup().setLandscape(true); + sheet.setRepeatingRows(new CellRangeAddress(11, 11, -1, -1)); + + double[] widths = {4.44, 8, 23.22, 23.22, 10, 14, 18, 11, 9, 7.44, + 8, 9.55, 9, 10.55, 11.44, 9, 13, 10.55, 14, 30}; + for (int column = 0; column < widths.length; column++) { + sheet.setColumnWidth(column, (int) (widths[column] * 256)); + } + + CellStyle titleStyle = style(workbook, "000000", "FFFFFF", true, (short) 14, + HorizontalAlignment.CENTER, false); + CellStyle sectionStyle = style(workbook, "F2F2F2", "000000", true, (short) 10, + HorizontalAlignment.CENTER, false); + CellStyle labelStyle = borderedStyle(workbook, "F2F2F2", true, HorizontalAlignment.CENTER); + CellStyle inputStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT); + CellStyle requiredStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT); + CellStyle autoStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.LEFT); + CellStyle userStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.LEFT); + CellStyle headerStyle = borderedStyle(workbook, "D9D9D9", true, HorizontalAlignment.CENTER); + headerStyle.setWrapText(true); + CellStyle whiteDataStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.CENTER); + CellStyle blueDataStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.CENTER); + CellStyle pinkDataStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.CENTER); + + createStyledRow(sheet, 0, 25.5f, titleStyle); + merge(sheet, "A1:T1"); + setText(sheet, 0, 0, "인터페이스 설계서(대내)"); + + createStyledRow(sheet, 1, 18f, sectionStyle); + merge(sheet, "A2:T2"); + setText(sheet, 1, 0, "기본정보"); + + String[] labels = {"코드", "한글명", "영문명", "암호화", "유형", "레코드구분자", "필드구분자"}; + for (int index = 0; index < labels.length; index++) { + int rowIndex = index + 2; + Row row = sheet.createRow(rowIndex); + cell(row, 1, labelStyle).setCellValue(labels[index]); + cell(row, 2, inputStyle); + cell(row, 3, inputStyle); + merge(sheet, "C" + (rowIndex + 1) + ":D" + (rowIndex + 1)); + } + setText(sheet, 6, 2, "json"); + sheet.getRow(7).setHeightInPoints(24); + sheet.getRow(8).setHeightInPoints(24); + + for (int rowIndex = 3; rowIndex <= 5; rowIndex++) { + Row row = sheet.getRow(rowIndex); + CellStyle legendStyle = rowIndex == 3 ? requiredStyle : rowIndex == 4 ? autoStyle : userStyle; + cell(row, 5, legendStyle); + cell(row, 6, legendStyle); + merge(sheet, "F" + (rowIndex + 1) + ":G" + (rowIndex + 1)); + } + setText(sheet, 3, 7, "필수입력"); + setText(sheet, 4, 7, "필드자동채우기(메타시스템 연동시)"); + setText(sheet, 5, 7, "사용자입력(필요시)"); + + createStyledRow(sheet, 10, 18f, sectionStyle); + merge(sheet, "A11:T11"); + setText(sheet, 10, 0, "필드정보"); + + String[] headers = {"NO", "Level", "한글명", "부모식별자(한글명)", "끝수여부", "영문명", + "부모식별자(영문명)", "데이터유형", "필드길이", "SCALE", "기본값", "정렬기준", + "채움문자", "암호화방식", "메타체크여부", "한글여부", "소수점포함여부", + "마스킹여부", "마스킹패턴코드", "비고"}; + Row header = sheet.createRow(11); + header.setHeightInPoints(30); + for (int column = 0; column < headers.length; column++) { + cell(header, column, headerStyle).setCellValue(headers[column]); + } + + for (int rowIndex = FIRST_FIELD_ROW; rowIndex <= TEMPLATE_LAST_ROW; rowIndex++) { + Row row = sheet.createRow(rowIndex); + row.setHeightInPoints(15.75f); + for (int column = 0; column < COLUMN_COUNT; column++) { + CellStyle dataStyle; + if (column == 0 || column == 4 || (column >= 14 && column <= 16) || column == 19) { + dataStyle = whiteDataStyle; + } else if (column >= 1 && column <= 3) { + dataStyle = blueDataStyle; + } else { + dataStyle = pinkDataStyle; + } + cell(row, column, dataStyle); + } + } + return workbook; + } + + private CellStyle style(XSSFWorkbook workbook, String fillColor, String fontColor, + boolean bold, short fontSize, HorizontalAlignment alignment, + boolean bordered) { + CellStyle style = workbook.createCellStyle(); + style.setAlignment(alignment); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(new XSSFColor(java.awt.Color.decode("#" + fillColor), null)); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font font = workbook.createFont(); + font.setFontName("맑은 고딕"); + font.setFontHeightInPoints(fontSize); + font.setBold(bold); + font.setColor("FFFFFF".equals(fontColor) ? IndexedColors.WHITE.getIndex() : IndexedColors.BLACK.getIndex()); + style.setFont(font); + if (bordered) setBorders(style); + return style; + } + + private CellStyle borderedStyle(XSSFWorkbook workbook, String fillColor, + boolean bold, HorizontalAlignment alignment) { + return style(workbook, fillColor, "000000", bold, (short) 9, alignment, true); + } + + private void setBorders(CellStyle style) { + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + } + + private void createStyledRow(Sheet sheet, int rowIndex, float height, CellStyle style) { + Row row = sheet.createRow(rowIndex); + row.setHeightInPoints(height); + for (int column = 0; column < COLUMN_COUNT; column++) cell(row, column, style); + } + + private Cell cell(Row row, int column, CellStyle style) { + Cell cell = row.createCell(column); + cell.setCellStyle(style); + return cell; + } + + private void merge(Sheet sheet, String range) { + sheet.addMergedRegion(CellRangeAddress.valueOf(range)); + } + + private void writeFields(Sheet sheet, List fields) { + // 기본 24행을 유지하고 필드가 더 많으면 마지막 행의 서식을 복제해 확장한다. + int requiredRows = Math.max(fields.size(), TEMPLATE_LAST_ROW - FIRST_FIELD_ROW + 1); + for (int offset = 0; offset < requiredRows; offset++) { + int rowIndex = FIRST_FIELD_ROW + offset; + Row row = sheet.getRow(rowIndex); + if (row == null) { + row = cloneTemplateRow(sheet, rowIndex); + } + clearRowValues(row); + setNumber(row, 0, offset + 1); + if (offset < fields.size()) { + FieldRow field = fields.get(offset); + setNumber(row, 1, field.level()); + setText(row, 2, field.description()); + setText(row, 3, field.parentDescription()); + setText(row, 5, field.name()); + setText(row, 6, field.parentName()); + setText(row, 7, field.dataType()); + if (field.length() > 0) { + setNumber(row, 8, field.length()); + } + } + } + } + + private Row cloneTemplateRow(Sheet sheet, int rowIndex) { + Row source = sheet.getRow(TEMPLATE_LAST_ROW); + Row target = sheet.createRow(rowIndex); + target.setHeight(source.getHeight()); + for (int column = 0; column < COLUMN_COUNT; column++) { + Cell sourceCell = source.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + Cell targetCell = target.createCell(column); + CellStyle style = sourceCell.getCellStyle(); + targetCell.setCellStyle(style); + } + return target; + } + + private void clearRowValues(Row row) { + for (int column = 0; column < COLUMN_COUNT; column++) { + Cell cell = row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + cell.setBlank(); + } + } + + private List describeFields(Class rootClass) { + List output = new ArrayList<>(); + appendFields(rootClass, 1, "", "", output); + return output; + } + + private void appendFields(Class type, int level, String parentName, + String parentDescription, List output) { + // 중첩 DTO와 List 요소 타입을 재귀적으로 펼쳐 Level 및 부모 식별자를 계산한다. + List fields = new ArrayList<>(List.of(type.getDeclaredFields())); + fields.removeIf(field -> field.isSynthetic()); + fields.sort(Comparator.comparingInt(this::fieldOrder)); + for (Field field : fields) { + Annotation metadata = telegramMetadata(field); + // 한글명, 순서, 길이를 알 수 없는 DTO는 설계서 양식으로 변환할 수 없다. + if (metadata == null) { + throw new DtoFormatException(); + } + String description = annotationString(metadata, "description", field.getName()); + int length = annotationInt(metadata, "length", 0); + Class nestedType = nestedType(field); + String dataType = annotationString(metadata, "type", simpleDataType(field)); + output.add(new FieldRow(level, description, parentDescription, field.getName(), + parentName, dataType, length)); + if (nestedType != null && nestedType != type) { + appendFields(nestedType, level + 1, field.getName(), description, output); + } + } + } + + private int fieldOrder(Field field) { + return annotationInt(telegramMetadata(field), "order", Integer.MAX_VALUE); + } + + private Annotation telegramMetadata(Field field) { + for (Annotation annotation : field.getDeclaredAnnotations()) { + String annotationName = annotation.annotationType().getSimpleName(); + if (annotationName.equals("GlowTrgmField") + || annotationName.equals("GlowMciFieldInfo")) { + return annotation; + } + } + return null; + } + + private String annotationString(Annotation annotation, String methodName, String fallback) { + Object value = annotationValue(annotation, methodName); + return value instanceof String text && !text.isBlank() ? text : fallback; + } + + private int annotationInt(Annotation annotation, String methodName, int fallback) { + Object value = annotationValue(annotation, methodName); + return value instanceof Number number ? number.intValue() : fallback; + } + + private Object annotationValue(Annotation annotation, String methodName) { + if (annotation == null) { + return null; + } + try { + Method method = annotation.annotationType().getMethod(methodName); + return method.invoke(annotation); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private Class nestedType(Field field) { + Class type = field.getType(); + if (List.class.isAssignableFrom(type) && field.getGenericType() instanceof ParameterizedType generic) { + Type argument = generic.getActualTypeArguments()[0]; + if (argument instanceof Class itemType && isDtoType(itemType)) { + return itemType; + } + } + return isDtoType(type) ? type : null; + } + + private boolean isDtoType(Class type) { + return !type.isPrimitive() + && !type.getName().startsWith("java.") + && !type.isEnum(); + } + + private String simpleDataType(Field field) { + if (List.class.isAssignableFrom(field.getType())) { + return "List"; + } + return field.getType().getSimpleName(); + } + + private void setText(Sheet sheet, int row, int column, String value) { + setText(sheet.getRow(row), column, value); + } + + private void setText(Row row, int column, String value) { + row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value == null ? "" : value); + } + + private void setNumber(Row row, int column, int value) { + row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value); + } + + private Map scanDtoClasses() { + // itrf.mci 하위의 모든 io 패키지를 검색하므로 신규 DTO 추가 시 하드코딩이 필요 없다. + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new RegexPatternTypeFilter( + Pattern.compile(".*\\.itrf\\.mci\\..*\\.io\\.[^.]+$"))); + + Map classes = new TreeMap<>(); + scanner.findCandidateComponents(MCI_BASE_PACKAGE).forEach(candidate -> { + String className = candidate.getBeanClassName(); + if (className == null || className.contains("$")) { + return; + } + String simpleName = className.substring(className.lastIndexOf('.') + 1); + String previous = classes.putIfAbsent(simpleName, className); + if (previous != null) { + throw new IllegalStateException("Duplicate DTO class name: " + simpleName); + } + }); + return Map.copyOf(classes); + } + + private record FieldRow(int level, String description, String parentDescription, + String name, String parentName, String dataType, int length) { + } + + private static final class DtoFormatException extends RuntimeException { + } +} diff --git a/dap-was-cus/src/main/resources/application-dev.yml b/dap-was-cus/src/main/resources/application-dev.yml new file mode 100644 index 000000000..b3e646360 --- /dev/null +++ b/dap-was-cus/src/main/resources/application-dev.yml @@ -0,0 +1,15 @@ +# OCI 클라우드 개발 환경 전용 설정 +server: + port: ${PORT:8084} + +axhub: + gateway: + url: https://axhubmcp.devjun.net + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml diff --git a/dap-was-cus/src/main/resources/application-local.yml b/dap-was-cus/src/main/resources/application-local.yml new file mode 100644 index 000000000..202566779 --- /dev/null +++ b/dap-was-cus/src/main/resources/application-local.yml @@ -0,0 +1,32 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} + +sol: + req-detail: + mock-enabled: true diff --git a/dap-was-cus/src/main/resources/application-prod.yml b/dap-was-cus/src/main/resources/application-prod.yml new file mode 100644 index 000000000..b5f08f21b --- /dev/null +++ b/dap-was-cus/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8084} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-cus/src/main/resources/application-test.yml b/dap-was-cus/src/main/resources/application-test.yml new file mode 100644 index 000000000..ab1c69084 --- /dev/null +++ b/dap-was-cus/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8084} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-cus/src/main/resources/application.yml b/dap-was-cus/src/main/resources/application.yml new file mode 100644 index 000000000..dfc56afed --- /dev/null +++ b/dap-was-cus/src/main/resources/application.yml @@ -0,0 +1,19 @@ +server: + port: 8084 +spring: + application: + name: dap-was-cus + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: was-cus + # Set the AA-assigned prefix before MCP pull activation (for example: cus.). + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL diff --git a/dap-was-cus/src/main/resources/logback-spring.xml b/dap-was-cus/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..c0a9d1d4c --- /dev/null +++ b/dap-was-cus/src/main/resources/logback-spring.xml @@ -0,0 +1,41 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + + + + /swlog/dap-was-cus/A01/${HOSTNAME}_A01.log + + + /swlog/dap-was-cus/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.%i.log + + 100MB + 30 + 1GB + + + ${LOG_PATTERN} + + + + + + + + + + + + diff --git a/dap-was-cus/src/main/resources/mock-responses/cmm_customer_tool.json b/dap-was-cus/src/main/resources/mock-responses/cmm_customer_tool.json new file mode 100644 index 000000000..07ad1840d --- /dev/null +++ b/dap-was-cus/src/main/resources/mock-responses/cmm_customer_tool.json @@ -0,0 +1,4 @@ +{ + "resultCode" : "SUCCESS", + "data" : "[{\"date\":\"2024-01-05\",\"message\":\"안내 내용\"}]" +} diff --git a/dap-was-cus/src/main/resources/mock-responses/ins_insurance_processor.json b/dap-was-cus/src/main/resources/mock-responses/ins_insurance_processor.json new file mode 100644 index 000000000..d856cc5b5 --- /dev/null +++ b/dap-was-cus/src/main/resources/mock-responses/ins_insurance_processor.json @@ -0,0 +1,4 @@ +{ + "resultCode" : "SUCCESS", + "claimId" : "CLM20230001" +} diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_activity_status.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_activity_status.yml new file mode 100644 index 000000000..1e01bd8cf --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_activity_status.yml @@ -0,0 +1,30 @@ +name: crm_activity_status +display_name: 고객 활동 현황 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 활동 현황 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 활동 현황 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 활동 현황 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 활동 현황 조회 해줘", "고객 활동 현황 조회 결과를 알려줘", "고객 활동 현황 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_change.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_change.yml new file mode 100644 index 000000000..4993d839d --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_change.yml @@ -0,0 +1,30 @@ +name: crm_consent_change +display_name: 고객 동의 정보 변경 +version: 1.0.0 +category_key: crm +description: + function: 고객 동의 정보 변경 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 동의 정보 변경 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 동의 정보 변경 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 동의 정보 변경 해줘", "고객 동의 정보 변경 결과를 알려줘", "고객 동의 정보 변경 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_detail.yml new file mode 100644 index 000000000..85b0f3062 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consent_detail.yml @@ -0,0 +1,30 @@ +name: crm_consent_detail +display_name: 고객 동의 정보 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 동의 정보 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 동의 정보 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 동의 정보 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 동의 정보 조회 해줘", "고객 동의 정보 조회 결과를 알려줘", "고객 동의 정보 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_history.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_history.yml new file mode 100644 index 000000000..5ff7d7bb2 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_history.yml @@ -0,0 +1,30 @@ +name: crm_consultation_history +display_name: 고객 상담 이력 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 상담 이력 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 상담 이력 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 상담 이력 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 상담 이력 조회 해줘", "고객 상담 이력 조회 결과를 알려줘", "고객 상담 이력 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_register.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_register.yml new file mode 100644 index 000000000..b2e3f5783 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_consultation_register.yml @@ -0,0 +1,30 @@ +name: crm_consultation_register +display_name: 고객 상담 이력 등록 +version: 1.0.0 +category_key: crm +description: + function: 고객 상담 이력 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 상담 이력 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 상담 이력 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 상담 이력 등록 해줘", "고객 상담 이력 등록 결과를 알려줘", "고객 상담 이력 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_history.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_history.yml new file mode 100644 index 000000000..4979bcaea --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_history.yml @@ -0,0 +1,30 @@ +name: crm_contact_history +display_name: 고객 접촉 이력 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 접촉 이력 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 접촉 이력 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 접촉 이력 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 접촉 이력 조회 해줘", "고객 접촉 이력 조회 결과를 알려줘", "고객 접촉 이력 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_register.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_register.yml new file mode 100644 index 000000000..4d52eb9c7 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_contact_register.yml @@ -0,0 +1,30 @@ +name: crm_contact_register +display_name: 고객 접촉 이력 등록 +version: 1.0.0 +category_key: crm +description: + function: 고객 접촉 이력 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 접촉 이력 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 접촉 이력 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 접촉 이력 등록 해줘", "고객 접촉 이력 등록 결과를 알려줘", "고객 접촉 이력 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_create.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_create.yml new file mode 100644 index 000000000..f43f01116 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_create.yml @@ -0,0 +1,30 @@ +name: crm_customer_create +display_name: 고객 정보 등록 +version: 1.0.0 +category_key: crm +description: + function: 고객 정보 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 정보 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 정보 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 정보 등록 해줘", "고객 정보 등록 결과를 알려줘", "고객 정보 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_detail.yml new file mode 100644 index 000000000..303255e20 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_detail.yml @@ -0,0 +1,30 @@ +name: crm_customer_detail +display_name: 고객 상세 정보 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 상세 정보 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 상세 정보 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 상세 정보 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 상세 정보 조회 해줘", "고객 상세 정보 조회 결과를 알려줘", "고객 상세 정보 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_duplicate_check.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_duplicate_check.yml new file mode 100644 index 000000000..c582f74e4 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_duplicate_check.yml @@ -0,0 +1,30 @@ +name: crm_customer_duplicate_check +display_name: 고객 중복 여부 확인 +version: 1.0.0 +category_key: crm +description: + function: 고객 중복 여부 확인 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 중복 여부 확인 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 중복 여부 확인 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 중복 여부 확인 해줘", "고객 중복 여부 확인 결과를 알려줘", "고객 중복 여부 확인 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_search.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_search.yml new file mode 100644 index 000000000..dcf5fe7eb --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_search.yml @@ -0,0 +1,30 @@ +name: crm_customer_search +display_name: 고객 통합 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 통합 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 통합 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 통합 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 통합 조회 해줘", "고객 통합 조회 결과를 알려줘", "고객 통합 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_update.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_update.yml new file mode 100644 index 000000000..8081dd819 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_customer_update.yml @@ -0,0 +1,30 @@ +name: crm_customer_update +display_name: 고객 정보 수정 +version: 1.0.0 +category_key: crm +description: + function: 고객 정보 수정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 정보 수정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 정보 수정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 정보 수정 해줘", "고객 정보 수정 결과를 알려줘", "고객 정보 수정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_change.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_change.yml new file mode 100644 index 000000000..cb06445b4 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_change.yml @@ -0,0 +1,30 @@ +name: crm_grade_change +display_name: 고객 등급 변경 +version: 1.0.0 +category_key: crm +description: + function: 고객 등급 변경 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 등급 변경 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 등급 변경 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 등급 변경 해줘", "고객 등급 변경 결과를 알려줘", "고객 등급 변경 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_detail.yml new file mode 100644 index 000000000..5560dd84a --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_grade_detail.yml @@ -0,0 +1,30 @@ +name: crm_grade_detail +display_name: 고객 등급 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 등급 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 등급 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 등급 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 등급 조회 해줘", "고객 등급 조회 결과를 알려줘", "고객 등급 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_owner_assign.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_owner_assign.yml new file mode 100644 index 000000000..b4f8694aa --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_owner_assign.yml @@ -0,0 +1,30 @@ +name: crm_owner_assign +display_name: 고객 담당자 배정 +version: 1.0.0 +category_key: crm +description: + function: 고객 담당자 배정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 담당자 배정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 담당자 배정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 담당자 배정 해줘", "고객 담당자 배정 결과를 알려줘", "고객 담당자 배정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_segment_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_segment_detail.yml new file mode 100644 index 000000000..551605eef --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_segment_detail.yml @@ -0,0 +1,30 @@ +name: crm_segment_detail +display_name: 고객 세그먼트 조회 +version: 1.0.0 +category_key: crm +description: + function: 고객 세그먼트 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 세그먼트 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 세그먼트 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 세그먼트 조회 해줘", "고객 세그먼트 조회 결과를 알려줘", "고객 세그먼트 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/crm/crm_tag_manage.yml b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_tag_manage.yml new file mode 100644 index 000000000..9dbc797ed --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/crm/crm_tag_manage.yml @@ -0,0 +1,30 @@ +name: crm_tag_manage +display_name: 고객 태그 관리 +version: 1.0.0 +category_key: crm +description: + function: 고객 태그 관리 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객 태그 관리 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객 태그 관리 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 태그 관리 해줘", "고객 태그 관리 결과를 알려줘", "고객 태그 관리 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [crm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_assign.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_assign.yml new file mode 100644 index 000000000..df41cdcb3 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_assign.yml @@ -0,0 +1,30 @@ +name: voc_assign +display_name: VOC 담당자 배정 +version: 1.0.0 +category_key: voc +description: + function: VOC 담당자 배정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 담당자 배정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 담당자 배정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 담당자 배정 해줘", "VOC 담당자 배정 결과를 알려줘", "VOC 담당자 배정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_attachments.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_attachments.yml new file mode 100644 index 000000000..121ee990a --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_attachments.yml @@ -0,0 +1,30 @@ +name: voc_attachments +display_name: VOC 첨부파일 조회 +version: 1.0.0 +category_key: voc +description: + function: VOC 첨부파일 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 첨부파일 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 첨부파일 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 첨부파일 조회 해줘", "VOC 첨부파일 조회 결과를 알려줘", "VOC 첨부파일 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_change_status.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_change_status.yml new file mode 100644 index 000000000..8b64a2222 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_change_status.yml @@ -0,0 +1,30 @@ +name: voc_change_status +display_name: VOC 처리 상태 변경 +version: 1.0.0 +category_key: voc +description: + function: VOC 처리 상태 변경 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 처리 상태 변경 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 처리 상태 변경 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 처리 상태 변경 해줘", "VOC 처리 상태 변경 결과를 알려줘", "VOC 처리 상태 변경 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_classify_type.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_classify_type.yml new file mode 100644 index 000000000..46526bef9 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_classify_type.yml @@ -0,0 +1,30 @@ +name: voc_classify_type +display_name: VOC 유형 분류 +version: 1.0.0 +category_key: voc +description: + function: VOC 유형 분류 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 유형 분류 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 유형 분류 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 유형 분류 해줘", "VOC 유형 분류 결과를 알려줘", "VOC 유형 분류 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_customer_history.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_customer_history.yml new file mode 100644 index 000000000..484f1ffb5 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_customer_history.yml @@ -0,0 +1,30 @@ +name: voc_customer_history +display_name: 고객별 VOC 이력 조회 +version: 1.0.0 +category_key: voc +description: + function: 고객별 VOC 이력 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 고객별 VOC 이력 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 고객별 VOC 이력 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["고객별 VOC 이력 조회 해줘", "고객별 VOC 이력 조회 결과를 알려줘", "고객별 VOC 이력 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detail.yml new file mode 100644 index 000000000..73963e512 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detail.yml @@ -0,0 +1,30 @@ +name: voc_detail +display_name: VOC 상세 조회 +version: 1.0.0 +category_key: voc +description: + function: VOC 상세 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 상세 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 상세 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 상세 조회 해줘", "VOC 상세 조회 결과를 알려줘", "VOC 상세 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detect_duplicate.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detect_duplicate.yml new file mode 100644 index 000000000..1b4ba4fdd --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_detect_duplicate.yml @@ -0,0 +1,30 @@ +name: voc_detect_duplicate +display_name: 중복 VOC 탐지 +version: 1.0.0 +category_key: voc +description: + function: 중복 VOC 탐지 기능을 수행합니다. + when_to_use: 고객채널 업무에서 중복 VOC 탐지 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 중복 VOC 탐지 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["중복 VOC 탐지 해줘", "중복 VOC 탐지 결과를 알려줘", "중복 VOC 탐지 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_extend_due_date.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_extend_due_date.yml new file mode 100644 index 000000000..b6d517860 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_extend_due_date.yml @@ -0,0 +1,30 @@ +name: voc_extend_due_date +display_name: VOC 처리 기한 연장 +version: 1.0.0 +category_key: voc +description: + function: VOC 처리 기한 연장 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 처리 기한 연장 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 처리 기한 연장 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 처리 기한 연장 해줘", "VOC 처리 기한 연장 결과를 알려줘", "VOC 처리 기한 연장 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register.yml new file mode 100644 index 000000000..b4aa12cd1 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register.yml @@ -0,0 +1,30 @@ +name: voc_register +display_name: VOC 접수 등록 +version: 1.0.0 +category_key: voc +description: + function: VOC 접수 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 접수 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 접수 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 접수 등록 해줘", "VOC 접수 등록 결과를 알려줘", "VOC 접수 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register_result.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register_result.yml new file mode 100644 index 000000000..6e2cc9065 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_register_result.yml @@ -0,0 +1,30 @@ +name: voc_register_result +display_name: VOC 처리 결과 등록 +version: 1.0.0 +category_key: voc +description: + function: VOC 처리 결과 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 처리 결과 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 처리 결과 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 처리 결과 등록 해줘", "VOC 처리 결과 등록 결과를 알려줘", "VOC 처리 결과 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_search.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_search.yml new file mode 100644 index 000000000..58ef2bf97 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_search.yml @@ -0,0 +1,30 @@ +name: voc_search +display_name: VOC 목록 검색 +version: 1.0.0 +category_key: voc +description: + function: VOC 목록 검색 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 목록 검색 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 목록 검색 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 목록 검색 해줘", "VOC 목록 검색 결과를 알려줘", "VOC 목록 검색 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_send_reply.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_send_reply.yml new file mode 100644 index 000000000..eb20c917d --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_send_reply.yml @@ -0,0 +1,30 @@ +name: voc_send_reply +display_name: VOC 답변 발송 +version: 1.0.0 +category_key: voc +description: + function: VOC 답변 발송 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 답변 발송 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 답변 발송 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 답변 발송 해줘", "VOC 답변 발송 결과를 알려줘", "VOC 답변 발송 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_set_priority.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_set_priority.yml new file mode 100644 index 000000000..389680072 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_set_priority.yml @@ -0,0 +1,30 @@ +name: voc_set_priority +display_name: VOC 우선순위 설정 +version: 1.0.0 +category_key: voc +description: + function: VOC 우선순위 설정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 우선순위 설정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 우선순위 설정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 우선순위 설정 해줘", "VOC 우선순위 설정 결과를 알려줘", "VOC 우선순위 설정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_statistics.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_statistics.yml new file mode 100644 index 000000000..cabb49dfa --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_statistics.yml @@ -0,0 +1,30 @@ +name: voc_statistics +display_name: VOC 통계 조회 +version: 1.0.0 +category_key: voc +description: + function: VOC 통계 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 통계 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 통계 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 통계 조회 해줘", "VOC 통계 조회 결과를 알려줘", "VOC 통계 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_transfer.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_transfer.yml new file mode 100644 index 000000000..9cce390b7 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_transfer.yml @@ -0,0 +1,30 @@ +name: voc_transfer +display_name: VOC 이관 처리 +version: 1.0.0 +category_key: voc +description: + function: VOC 이관 처리 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 이관 처리 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 이관 처리 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 이관 처리 해줘", "VOC 이관 처리 결과를 알려줘", "VOC 이관 처리 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_update.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_update.yml new file mode 100644 index 000000000..0da02b0f0 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_update.yml @@ -0,0 +1,30 @@ +name: voc_update +display_name: VOC 내용 수정 +version: 1.0.0 +category_key: voc +description: + function: VOC 내용 수정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 VOC 내용 수정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: VOC 내용 수정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["VOC 내용 수정 해줘", "VOC 내용 수정 결과를 알려줘", "VOC 내용 수정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/voc/voc_urgent_list.yml b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_urgent_list.yml new file mode 100644 index 000000000..2787c21b7 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/voc/voc_urgent_list.yml @@ -0,0 +1,30 @@ +name: voc_urgent_list +display_name: 긴급 VOC 목록 조회 +version: 1.0.0 +category_key: voc +description: + function: 긴급 VOC 목록 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 긴급 VOC 목록 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 긴급 VOC 목록 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["긴급 VOC 목록 조회 해줘", "긴급 VOC 목록 조회 결과를 알려줘", "긴급 VOC 목록 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [voc, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_copy.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_copy.yml new file mode 100644 index 000000000..8c895008f --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_copy.yml @@ -0,0 +1,30 @@ +name: wcm_content_copy +display_name: 웹 콘텐츠 복사 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 복사 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 복사 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 복사 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 복사 해줘", "웹 콘텐츠 복사 결과를 알려줘", "웹 콘텐츠 복사 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_create.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_create.yml new file mode 100644 index 000000000..bdd834a95 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_create.yml @@ -0,0 +1,30 @@ +name: wcm_content_create +display_name: 웹 콘텐츠 등록 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 등록 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 등록 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 등록 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 등록 해줘", "웹 콘텐츠 등록 결과를 알려줘", "웹 콘텐츠 등록 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: false +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_delete.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_delete.yml new file mode 100644 index 000000000..705cea321 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_delete.yml @@ -0,0 +1,30 @@ +name: wcm_content_delete +display_name: 웹 콘텐츠 삭제 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 삭제 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 삭제 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 삭제 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 삭제 해줘", "웹 콘텐츠 삭제 결과를 알려줘", "웹 콘텐츠 삭제 기능을 실행해줘"] +read_only: false +destructive: true +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_detail.yml new file mode 100644 index 000000000..1beb9566d --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_detail.yml @@ -0,0 +1,30 @@ +name: wcm_content_detail +display_name: 웹 콘텐츠 상세 조회 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 상세 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 상세 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 상세 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 상세 조회 해줘", "웹 콘텐츠 상세 조회 결과를 알려줘", "웹 콘텐츠 상세 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_list.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_list.yml new file mode 100644 index 000000000..9bdeeb011 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_list.yml @@ -0,0 +1,30 @@ +name: wcm_content_list +display_name: 웹 콘텐츠 목록 조회 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 목록 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 목록 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 목록 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 목록 조회 해줘", "웹 콘텐츠 목록 조회 결과를 알려줘", "웹 콘텐츠 목록 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_preview.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_preview.yml new file mode 100644 index 000000000..5687185cc --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_preview.yml @@ -0,0 +1,30 @@ +name: wcm_content_preview +display_name: 웹 콘텐츠 미리보기 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 미리보기 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 미리보기 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 미리보기 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 미리보기 해줘", "웹 콘텐츠 미리보기 결과를 알려줘", "웹 콘텐츠 미리보기 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_publish.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_publish.yml new file mode 100644 index 000000000..bffe3a21e --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_publish.yml @@ -0,0 +1,30 @@ +name: wcm_content_publish +display_name: 웹 콘텐츠 게시 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 게시 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 게시 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 게시 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 게시 해줘", "웹 콘텐츠 게시 결과를 알려줘", "웹 콘텐츠 게시 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_request_approval.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_request_approval.yml new file mode 100644 index 000000000..f25882e07 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_request_approval.yml @@ -0,0 +1,30 @@ +name: wcm_content_request_approval +display_name: 웹 콘텐츠 승인 요청 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 승인 요청 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 승인 요청 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 승인 요청 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 승인 요청 해줘", "웹 콘텐츠 승인 요청 결과를 알려줘", "웹 콘텐츠 승인 요청 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_restore_version.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_restore_version.yml new file mode 100644 index 000000000..07603a966 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_restore_version.yml @@ -0,0 +1,30 @@ +name: wcm_content_restore_version +display_name: 웹 콘텐츠 이전 버전 복원 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 이전 버전 복원 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 이전 버전 복원 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 이전 버전 복원 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 이전 버전 복원 해줘", "웹 콘텐츠 이전 버전 복원 결과를 알려줘", "웹 콘텐츠 이전 버전 복원 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_review_approval.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_review_approval.yml new file mode 100644 index 000000000..4841a1f1b --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_review_approval.yml @@ -0,0 +1,30 @@ +name: wcm_content_review_approval +display_name: 웹 콘텐츠 승인·반려 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 승인·반려 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 승인·반려 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 승인·반려 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 승인·반려 해줘", "웹 콘텐츠 승인·반려 결과를 알려줘", "웹 콘텐츠 승인·반려 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_schedule_publish.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_schedule_publish.yml new file mode 100644 index 000000000..32cb31052 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_schedule_publish.yml @@ -0,0 +1,30 @@ +name: wcm_content_schedule_publish +display_name: 웹 콘텐츠 예약 게시 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 예약 게시 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 예약 게시 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 예약 게시 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 예약 게시 해줘", "웹 콘텐츠 예약 게시 결과를 알려줘", "웹 콘텐츠 예약 게시 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_unpublish.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_unpublish.yml new file mode 100644 index 000000000..ff0bebaa3 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_unpublish.yml @@ -0,0 +1,30 @@ +name: wcm_content_unpublish +display_name: 웹 콘텐츠 게시 중지 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 게시 중지 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 게시 중지 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 게시 중지 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 게시 중지 해줘", "웹 콘텐츠 게시 중지 결과를 알려줘", "웹 콘텐츠 게시 중지 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_update.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_update.yml new file mode 100644 index 000000000..aeabc7573 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_update.yml @@ -0,0 +1,30 @@ +name: wcm_content_update +display_name: 웹 콘텐츠 수정 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 수정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 수정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 수정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 수정 해줘", "웹 콘텐츠 수정 결과를 알려줘", "웹 콘텐츠 수정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_version_history.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_version_history.yml new file mode 100644 index 000000000..613eade8e --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_content_version_history.yml @@ -0,0 +1,30 @@ +name: wcm_content_version_history +display_name: 웹 콘텐츠 버전 이력 조회 +version: 1.0.0 +category_key: wcm +description: + function: 웹 콘텐츠 버전 이력 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 웹 콘텐츠 버전 이력 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 웹 콘텐츠 버전 이력 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["웹 콘텐츠 버전 이력 조회 해줘", "웹 콘텐츠 버전 이력 조회 결과를 알려줘", "웹 콘텐츠 버전 이력 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_detail.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_detail.yml new file mode 100644 index 000000000..636547054 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_detail.yml @@ -0,0 +1,30 @@ +name: wcm_metadata_detail +display_name: 콘텐츠 메타데이터 조회 +version: 1.0.0 +category_key: wcm +description: + function: 콘텐츠 메타데이터 조회 기능을 수행합니다. + when_to_use: 고객채널 업무에서 콘텐츠 메타데이터 조회 기능이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 승인 처리가 필요한 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 조회 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 콘텐츠 메타데이터 조회 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["콘텐츠 메타데이터 조회 해줘", "콘텐츠 메타데이터 조회 결과를 알려줘", "콘텐츠 메타데이터 조회 기능을 실행해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_upsert.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_upsert.yml new file mode 100644 index 000000000..552e4b9f1 --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_metadata_upsert.yml @@ -0,0 +1,30 @@ +name: wcm_metadata_upsert +display_name: 콘텐츠 메타데이터 등록·수정 +version: 1.0.0 +category_key: wcm +description: + function: 콘텐츠 메타데이터 등록·수정 기능을 수행합니다. + when_to_use: 고객채널 업무에서 콘텐츠 메타데이터 등록·수정 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 콘텐츠 메타데이터 등록·수정 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["콘텐츠 메타데이터 등록·수정 해줘", "콘텐츠 메타데이터 등록·수정 결과를 알려줘", "콘텐츠 메타데이터 등록·수정 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_taxonomy_manage.yml b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_taxonomy_manage.yml new file mode 100644 index 000000000..59897a95a --- /dev/null +++ b/dap-was-cus/src/main/resources/tool-definitions/wcm/wcm_taxonomy_manage.yml @@ -0,0 +1,30 @@ +name: wcm_taxonomy_manage +display_name: 콘텐츠 카테고리·태그 관리 +version: 1.0.0 +category_key: wcm +description: + function: 콘텐츠 카테고리·태그 관리 기능을 수행합니다. + when_to_use: 고객채널 업무에서 콘텐츠 카테고리·태그 관리 기능이 필요할 때 사용합니다. + when_not_to_use: 필수 식별자와 변경 내용이 확인되지 않은 경우에는 사용하지 않습니다. + io_limits: 입력 범위에 대한 모의 처리 결과만 반환하며 외부 시스템을 직접 변경하지 않습니다. +display_description: 콘텐츠 카테고리·태그 관리 기능을 안전한 모의 데이터로 제공합니다. +example_queries: ["콘텐츠 카테고리·태그 관리 해줘", "콘텐츠 카테고리·태그 관리 결과를 알려줘", "콘텐츠 카테고리·태그 관리 기능을 실행해줘"] +read_only: false +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 업무 대상 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + customerId: {type: string, description: 고객 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + content: {type: string, description: 검색어 또는 처리 내용, maxLength: 2000} + value: {type: string, description: 상태·유형·등급 또는 처리 값, maxLength: 100} + assigneeId: {type: string, description: 담당자 식별자, pattern: "^[A-Za-z0-9_-]{3,40}$"} + startDate: {type: string, description: 조회 시작일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + endDate: {type: string, description: 조회 종료일 또는 처리 예정일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + page: {type: integer, description: 페이지 번호, minimum: 0} + size: {type: integer, description: 페이지 크기, minimum: 1, maximum: 100} + additionalProperties: false +tags: [wcm, 고객채널, 처리, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadControllerTest.java b/dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadControllerTest.java new file mode 100644 index 000000000..be9f97ee6 --- /dev/null +++ b/dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadControllerTest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; + +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +class DtoExcelDownloadControllerTest { + + private final DtoExcelDownloadController controller = new DtoExcelDownloadController(); + + @Test + void downloadsBothOnild0320Variants() throws Exception { + assertWorkbook("ONILD0320_I", "csNo"); + assertWorkbook("ONILD0320_O", "notiDt"); + } + + private void assertWorkbook(String dtoName, String expectedField) throws Exception { + ResponseEntity response = controller.download(dtoName); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + assertThat(response.getBody()).isNotNull(); + try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(response.getBody()))) { + assertThat(workbook.getSheetAt(0)) + .anySatisfy(row -> assertThat(row) + .anySatisfy(cell -> assertThat(cell.toString()).isEqualTo(expectedField))); + } + } +} diff --git a/dap-was-lib/build.gradle b/dap-was-lib/build.gradle new file mode 100644 index 000000000..78d6bff22 --- /dev/null +++ b/dap-was-lib/build.gradle @@ -0,0 +1,60 @@ +plugins { + // Gateway와 모든 Tool Pod가 의존하는 공통 라이브러리 모듈입니다. + id 'java-library' +} + +dependencies { + // 공통 REST Controller, HTTP Client, 예외 처리 기반입니다. + api 'org.springframework.boot:spring-boot-starter-web' + + // Tool Request DTO의 Bean Validation을 지원합니다. + api 'org.springframework.boot:spring-boot-starter-validation' + + // Tool Manifest/Registry 캐시 및 Redis 기반 공통 기능을 제공합니다. + api 'org.springframework.boot:spring-boot-starter-data-redis' + + // Tool SLA, 로깅, 공통 Aspect를 적용합니다. + api 'org.springframework.boot:spring-boot-starter-aop' + + // MCI/DB 연동에 필요한 JDBC 공통 기능입니다. + api 'org.springframework.boot:spring-boot-starter-jdbc' + + // Tool 호출의 Circuit Breaker, Rate Limit, Retry 등 복원력 기능입니다. + api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' + api 'io.github.resilience4j:resilience4j-core:2.2.0' + api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0' + api 'io.github.resilience4j:resilience4j-ratelimiter' + api 'io.github.resilience4j:resilience4j-retry:2.2.0' + + // MCI/업무 데이터 접근을 위한 MyBatis 지원입니다. + api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' + + // 현재 로컬 테스트 DB 및 SQL 로그 처리에 사용합니다. + api 'com.h2database:h2' + api 'p6spy:p6spy:3.9.1' + + // Spring Data Redis가 사용하는 Redis Client를 공통 모듈에서 직접 참조합니다. + api 'io.lettuce:lettuce-core:6.6.0.RELEASE' + + // MCI XML 전문, JSON Tool Schema/Manifest 처리에 사용합니다. + api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' + api 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' + api 'com.fasterxml.jackson.core:jackson-databind' + // MCP Java SDK 2.0.0의 mcp-json-jackson2가 호출하는 Schema API와 반드시 맞춘다. + // 3.x는 Schema.validate(JsonNode) 반환 규격이 달라 런타임 NoSuchMethodError가 발생한다. + api 'com.networknt:json-schema-validator:2.0.0' + + // Boot 3.5.11 위에서 MCP Java SDK 2.0.0의 Server/Client와 Servlet 전송 계층을 사용합니다. + // Jackson 2 공급자를 명시해 Boot 3의 Jackson 계열과 맞춥니다. + api 'io.modelcontextprotocol.sdk:mcp-core' + api 'io.modelcontextprotocol.sdk:mcp-json-jackson2' + + // Boot 3.5 호환 Spring AI 어노테이션 API를 유지합니다. + api 'org.springaicommunity:mcp-annotations:0.9.0' + + // 이벤트 기반 확장이 필요한 Tool의 공통 Kafka 연동 기능입니다. + api 'org.springframework.kafka:spring-kafka:3.2.0' + + // Tool Pod REST API 문서 및 Swagger UI를 제공합니다. + api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java new file mode 100644 index 000000000..e85d3169d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Builder; +import lombok.NoArgsConstructor; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className ErrorDetail + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +@AllArgsConstructor +@Builder +@NoArgsConstructor +public class ErrorDetail { + private int code; + private String message; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java new file mode 100644 index 000000000..3f4f105e7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className JsonRpcRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +public class JsonRpcRequest { + private String jsonrpc; + private String method; + private Params params; + private String id; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java new file mode 100644 index 000000000..cc79e10d1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +// 2. 응답 DTO +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className JsonRpcResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +@JsonPropertyOrder({"jsonrpc", "result", "error", "id"}) +public class JsonRpcResponse { + public String jsonrpc = "2.0"; + public Object result; + public Object error; + public String id; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java new file mode 100644 index 000000000..e4931eb49 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; +import java.util.Map; + +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className Params + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Params { + private String routingType; + private String name; + private String interfaceId; + private Map data; + private List> spec; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java new file mode 100644 index 000000000..a5634cb86 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java @@ -0,0 +1,61 @@ +package io.shinhanlife.dap.lib.adapter.test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Local HTTP mock server for scaffolded HTTP Tools. + * + *

Each Tool Pod returns the JSON generated under + * {@code src/main/resources/mock-responses/{toolName}.json}. It is enabled only + * when {@code axhub.mock.http.enabled=true}, which the HTTP Scaffold adds to local configuration.

+ */ +@Slf4j +@RestController +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "axhub.mock.http", name = "enabled", havingValue = "true") +@RequestMapping("/api") +public class MockEimsHttpServer { + + private final ObjectMapper objectMapper; + + @PostMapping("/mock/http/{toolName:[a-z0-9_-]+}") + public ResponseEntity mockToolHttpResponse( + @PathVariable String toolName, + @RequestBody(required = false) JsonNode request) { + ClassPathResource resource = new ClassPathResource("mock-responses/" + toolName + ".json"); + if (!resource.exists()) { + return ResponseEntity.notFound().build(); + } + try { + log.info("[MockEimsHttpServer] HTTP mock request. toolName={}, body={}", toolName, request); + return ResponseEntity.ok(objectMapper.readTree(resource.getInputStream())); + } catch (Exception e) { + log.warn("[MockEimsHttpServer] Unable to read mock response. toolName={}", toolName, e); + return ResponseEntity.internalServerError().build(); + } + } + + @PostMapping("/gateway") + public ResponseEntity mockEimsReceiver( + @RequestHeader(value = "X-Trace-Id", required = false) String traceId, + @RequestBody Map request) { + String interfaceId = String.valueOf(request.getOrDefault("interfaceId", "")); + log.info("[MockEimsHttpServer] Legacy gateway mock request. traceId={}, interfaceId={}", traceId, interfaceId); + return ResponseEntity.ok(Map.of( + "status", "404", + "message", "MOCK data is not defined for interfaceId: " + interfaceId)); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java new file mode 100644 index 000000000..50bb7d7e8 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java @@ -0,0 +1,35 @@ +package io.shinhanlife.dap.lib.adapter.util; + +import ch.qos.logback.classic.pattern.MessageConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; + +/** + * Logback 커스텀 컨버터 + * 모든 로그 메시지(%msg)가 파일이나 콘솔에 찍히기 직전에 이 클래스를 거쳐가게 됩니다. + * 여기서 PiiMaskingUtils.mask()를 호출하여 PII(주민번호, 계좌번호 등)를 안전하게 별표(*) 처리합니다. + */ +/** + * @package io.shinhanlife.dap.lib.adapter.util + * @className PiiMaskingLogbackConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class PiiMaskingLogbackConverter extends MessageConverter { + + @Override + public String convert(ILoggingEvent event) { + // 원본 로그 메시지를 가져옵니다. + String originalMessage = super.convert(event); + + // 정규식을 이용하여 개인정보가 포함되어 있으면 마스킹 처리하여 반환합니다. + return PiiMaskingUtils.mask(originalMessage); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java new file mode 100644 index 000000000..d98c57046 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.lib.adapter.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @package io.shinhanlife.dap.lib.adapter.util + * @className PiiMaskingUtils + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class PiiMaskingUtils { + + // 1. 주민등록번호 패턴 (ex: 900101-1234567 또는 9001011234567) + private static final Pattern RRN_PATTERN = Pattern.compile("(\\d{6})[-]?([1-4]\\d{6})"); + + // 2. 휴대전화번호 패턴 (ex: 010-1234-5678) + private static final Pattern PHONE_PATTERN = Pattern.compile("(01[016789])[-]?(\\d{3,4})[-]?(\\d{4})"); + + // 3. 신한라이프 계좌/증권번호 패턴 (단순 예시용 계좌번호 11~14자리) + private static final Pattern ACCOUNT_PATTERN = Pattern.compile("(\\d{3})-?(\\d{3})-?(\\d{5,8})"); + + public static String mask(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + String masked = input; + + // [1] 주민번호 뒷자리 마스킹 (첫자리 성별 식별자는 남기고 마스킹: 900101-1******) + Matcher rrnMatcher = RRN_PATTERN.matcher(masked); + StringBuffer rrnBuffer = new StringBuffer(); + while (rrnMatcher.find()) { + String firstPart = rrnMatcher.group(1); + String secondPart = rrnMatcher.group(2); + rrnMatcher.appendReplacement(rrnBuffer, firstPart + "-" + secondPart.charAt(0) + "******"); + } + rrnMatcher.appendTail(rrnBuffer); + masked = rrnBuffer.toString(); + + // [2] 전화번호 중간자리 마스킹 (010-****-5678) + Matcher phoneMatcher = PHONE_PATTERN.matcher(masked); + StringBuffer phoneBuffer = new StringBuffer(); + while (phoneMatcher.find()) { + String p1 = phoneMatcher.group(1); + String p2 = phoneMatcher.group(2); + String p3 = phoneMatcher.group(3); + String maskedP2 = p2.replaceAll(".", "*"); + phoneMatcher.appendReplacement(phoneBuffer, p1 + "-" + maskedP2 + "-" + p3); + } + phoneMatcher.appendTail(phoneBuffer); + masked = phoneBuffer.toString(); + + // [3] 계좌번호 뒷자리 마스킹 (110-123-********) + Matcher accMatcher = ACCOUNT_PATTERN.matcher(masked); + StringBuffer accBuffer = new StringBuffer(); + while (accMatcher.find()) { + String a1 = accMatcher.group(1); + String a2 = accMatcher.group(2); + String a3 = accMatcher.group(3); + String maskedA3 = a3.replaceAll(".", "*"); + accMatcher.appendReplacement(accBuffer, a1 + "-" + a2 + "-" + maskedA3); + } + accMatcher.appendTail(accBuffer); + masked = accBuffer.toString(); + + return masked; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/GrowToolHint.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/GrowToolHint.java new file mode 100644 index 000000000..75453bf25 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/GrowToolHint.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.lib.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Spring AI @Tool 어노테이션을 보완하여 MCP 시스템 메타데이터를 추가 제공하는 힌트 어노테이션 + * @package io.shinhanlife.dap.lib.annotation + * @className GrowToolHint + * @description 비즈니스 로직(Tool)과 시스템 제어 메타데이터 분리 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface GrowToolHint { + boolean register() default true; + boolean requiresApproval() default false; + String categoryKey() default "com"; + String mappingId() default ""; + String inputSchemaResource() default ""; + String outputSchemaResource() default ""; + + // Meta 정보 추가 (보고용 샘플) + String displayDescription() default ""; + String functionDescription() default ""; + String whenToUse() default ""; + String whenNotToUse() default ""; + String ioLimits() default ""; + String[] exampleQueries() default {}; + boolean destructive() default false; + boolean idempotent() default false; + String[] tags() default {}; + String[] requiredEnvKeys() default {}; + String ownerOrg() default ""; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java new file mode 100644 index 000000000..6d8f83709 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.lib.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a response DTO whose generated JSON Schema must be exposed and validated for a Tool response. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface McpOutputSchema { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java new file mode 100644 index 000000000..40be12b57 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java @@ -0,0 +1,83 @@ +package io.shinhanlife.dap.lib.aop; + + +/** + * @package io.shinhanlife.dap.lib.aop + * @className ToolSlaMonitoringAspect + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import io.shinhanlife.dap.lib.config.McpProperties; +import java.lang.reflect.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class ToolSlaMonitoringAspect { + + private final McpProperties mcpProperties; + + // @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다. + @Around("@annotation(org.springaicommunity.mcp.annotation.McpTool)") + public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + McpTool functionAnnotation = method.getAnnotation(McpTool.class); + + // 네임스페이스 자동 주입 로직을 반영하여 최종 툴 이름을 산출합니다. + String baseName = functionAnnotation.name(); + String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() + ? mcpProperties.getNamespace() + "_" + baseName + : baseName; + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + try { + // 실제 비즈니스 로직(툴) 실행 + Object result = joinPoint.proceed(); + + stopWatch.stop(); + long timeMillis = stopWatch.getTotalTimeMillis(); + + // SLA 기준을 초과하면 (예: 2초 이상) 경고 로깅 처리 가능 + if (timeMillis > 2000) { + log.warn(" [SLA 경고] Tool: {} | 소요시간: {}ms | 상태: SLOW_RESPONSE", finalName, timeMillis); + } else { + log.info(" [SLA 추적] Tool: {} | 소요시간: {}ms | 상태: SUCCESS", finalName, timeMillis); + } + + return result; + + } catch (Throwable e) { + if (stopWatch.isRunning()) { + stopWatch.stop(); + } + long timeMillis = stopWatch.getTotalTimeMillis(); + + // 에러 발생 시 명확하게 실패 로그 기록 + log.error(" [SLA 장애] Tool: {} | 소요시간: {}ms | 상태: FAILED | 사유: {}", finalName, timeMillis, e.getMessage()); + + // 원래 흐름대로 예외를 던져서 게이트웨이나 상위 로직이 에러를 처리하게 함 + throw e; + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java new file mode 100644 index 000000000..2566ed5f5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.lib.config; + +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import java.net.http.HttpClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * Registers the temporary Glow HTTP compatibility component from the DAP library scan scope. + * The bean is only created when an official GlowHttpComponent has not already been supplied. + */ +@Configuration(proxyBeanMethods = false) +public class AxhubHttpConfiguration { + + @Bean + @ConditionalOnMissingBean(GlowHttpComponent.class) + public GlowHttpComponent glowHttpComponent(RestClient.Builder restClientBuilder) { + // WireMock and legacy internal endpoints can only support HTTP/1.1. + // Avoid JDK HTTP/2 negotiation that may cause RST_STREAM responses. + HttpClient http11Client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) + .build(); + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(http11Client); + return new GlowHttpComponent(restClientBuilder.requestFactory(requestFactory)); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java new file mode 100644 index 000000000..8fadfa7ce --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @package io.shinhanlife.dap.lib.config + * @className CorsConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // 모든 엔드포인트에 대해 CORS 허용 + .allowedOriginPatterns("*") // 외부 Agent Builder 등 모든 오리진 허용 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH") // 허용할 HTTP 메서드 + .allowedHeaders("*") // 모든 헤더 허용 + .exposedHeaders("Mcp-Session-Id") // MCP-HTTP 세션 아이디 노출 허용 + .allowCredentials(true) // 쿠키/인증 정보 허용 + .maxAge(3600); // preflight 요청 캐시 시간 (초 단위) + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java new file mode 100644 index 000000000..eacf69e3c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java @@ -0,0 +1,85 @@ +package io.shinhanlife.dap.lib.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * [Glow Framework 통신 환경 설정 클래스] + * application-glow-local.yml 의 'glow.communication' 하위 설정값들을 + * 자바 객체(Bean)로 매핑하여 제공합니다. + */ +/** + * @package io.shinhanlife.dap.lib.config + * @className GlowCommunicationProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Component +@Getter +@Setter +@ConfigurationProperties(prefix = "glow.communication") +public class GlowCommunicationProperties { + + private Common common = new Common(); + private Http http = new Http(); + private Mci mci = new Mci(); + private ExtMci extmci = new ExtMci(); + private Eai eai = new Eai(); + private Websocket websocket = new Websocket(); + + @Getter @Setter + public static class Common { + private String envType; // 대내표준 헤더의 환경 타입정보 (D, T, P) + } + + @Getter @Setter + public static class Http { + private int connectionTimeout; // 연결 타임아웃 시간 (초 단위) + private int readTimeout; // 읽기 타임아웃 시간 (초 단위) + } + + @Getter @Setter + public static class Mci { + private String host; + private int port; + private String uri; + private String receiveUri; + private int connectionTimeout; + private int readTimeout; + private String encoding; + } + + @Getter @Setter + public static class ExtMci { + private String host; + private int port; + private String uri; + private String jsonUri; + private String receiveUri; + private int connectionTimeout; + private int readTimeout; + private String encoding; + } + + @Getter @Setter + public static class Eai { + private String host; + private int port; + } + + @Getter @Setter + public static class Websocket { + private String endpoint; + private String allowedOrigins; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java new file mode 100644 index 000000000..1ff72e5ce --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.config + * @className McpProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Configuration +@ConfigurationProperties(prefix = "mcp") +public class McpProperties { + + private String namespace; + private Manifest manifest = new Manifest(); + + @Data + public static class Manifest { + private String bundleId; + private String namePrefix; + } + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java new file mode 100644 index 000000000..494f8a33f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.config + * @className MybatisConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import org.mybatis.spring.annotation.MapperScan; +import io.shinhanlife.glow.GlowMybatisMapper; + +@Configuration +@MapperScan(basePackages = "io.shinhanlife.dap", annotationClass = GlowMybatisMapper.class) +public class MybatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + return sessionFactory.getObject(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java new file mode 100644 index 000000000..06cc4c69f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java @@ -0,0 +1,54 @@ +package io.shinhanlife.dap.lib.config; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; + +/** + * @package io.shinhanlife.dap.lib.config + * @className P6SpySqlFormatter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class P6SpySqlFormatter implements MessageFormattingStrategy { + + @Override + public String formatMessage(int connectionId, String now, long elapsed, + String category, String prepared, String sql, String url) { + + if (sql == null || sql.isBlank()) return ""; + if (Category.STATEMENT.getName().equals(category)) { + + String prettySQL = sql + .replaceAll("(?i)\\bSELECT\\b", "\nSELECT") + .replaceAll("(?i)\\bFROM\\b", "\n FROM") + .replaceAll("(?i)\\bWHERE\\b", "\n WHERE") + .replaceAll("(?i)\\bAND\\b", "\n AND") + .replaceAll("(?i)\\bOR\\b", "\n OR") + .replaceAll("(?i)\\bINNER JOIN\\b", "\n INNER JOIN") + .replaceAll("(?i)\\bLEFT JOIN\\b", "\n LEFT JOIN") + .replaceAll("(?i)\\bORDER BY\\b", "\n ORDER BY") + .replaceAll("(?i)\\bGROUP BY\\b", "\n GROUP BY") + .replaceAll("(?i)\\bINSERT INTO\\b", "\nINSERT INTO") + .replaceAll("(?i)\\bVALUES\\b", "\n VALUES") + .replaceAll("(?i)\\bUPDATE\\b", "\nUPDATE") + .replaceAll("(?i)\\bSET\\b", "\n SET") + .replaceAll("(?i)\\bDELETE FROM\\b", "\nDELETE FROM"); + + return String.format(""" + \n┌───────────────────────────────────────── + │ SQL [%dms] + │%s + └───────────────────────────────────────── + """, elapsed, prettySQL.indent(2).stripTrailing()); + } + return ""; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java new file mode 100644 index 000000000..d0f8e2ac9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.lib.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Common MCP Tool Schema Bean configuration. + */ +@Configuration +public class ToolSchemaConfiguration { + + @Bean + public ToolSchemaResolver toolSchemaResolver(ObjectMapper objectMapper) { + return new ToolSchemaResolver(objectMapper); + } + + @Bean + public ToolArgumentSchemaValidator toolArgumentSchemaValidator(ObjectMapper objectMapper) { + return new ToolArgumentSchemaValidator(objectMapper); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java new file mode 100644 index 000000000..6d8684007 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.lib.dto; + +/** + * @package io.shinhanlife.dap.mcg.dto + * @className OperationType + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public enum OperationType { + READ, + WRITE +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java new file mode 100644 index 000000000..ce38c7853 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java @@ -0,0 +1,105 @@ +package io.shinhanlife.dap.lib.integration; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석들을 풀고 사용하세요! +// import io.shinhanlife.glow.communication.dto.CommonHeader; +// import io.shinhanlife.glow.communication.dto.Transfer; +// import io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent; +// import io.shinhanlife.glow.communication.module.mci.component.GlowExtMciComponent; +// import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +// import io.shinhanlife.glow.communication.util.CommonHeaderFactory; + +/** + * [MCI / EAI 공통 연동 래퍼(Wrapper) 템플릿] + * 신한라이프 Glow Framework 개발표준정의서를 바탕으로 대내/대외망/EAI 통신을 + * MCP 툴에서 손쉽게 호출할 수 있도록 일원화한 컴포넌트입니다. + */ +/** + * @package io.shinhanlife.dap.lib.integration + * @className GlowIntegrationCall + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Component +@RequiredArgsConstructor +public class GlowIntegrationCall { + + // 1. 실제 의존성이 주입될 프레임워크 컴포넌트들 (임시 주석 처리) + /* + private final GlowMciComponent mci; + private final GlowEaiComponent eai; + private final GlowExtMciComponent extMci; + */ + + /** + * 1. 대외 MCI 호출 (타행, 금융결제원 등 외부 기관) + * 가이드 2.2.2에 명시된 필수 파라미터(기관코드, 종별코드, 업무코드, 거래코드)를 모두 포함합니다. + */ + /* + public Transfer callExtMci(String itrfId, String frbuCd, String cmouDutjCd, String cmouCssfCd, String cmouTraCd, S body, Class resBody) { + + // 1. 공통 헤더 생성 + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + // 2. 대외 전용 필수 코드 세팅 로직 (프레임워크 내부 스펙에 맞게 가공) + // (예: 헤더에 해당 속성들을 주입하거나 Transfer 객체에 싣는 과정 추가) + + // 3. Transfer 객체 빌드 + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + // 4. 대외 MCI 컴포넌트를 통해 최종 전송 + return extMci.call(req, resBody); + } + */ + + /** + * 2. EAI 호출 (대내망 중계기) + * 가이드 2.3에 명시된 대로 수신서비스 ID 없이 인터페이스 ID(itrfId)만 필수로 받습니다. + */ + /* + public Transfer callEai(String itrfId, S body, Class resBody) { + + // 1. EAI는 인터페이스 ID만으로 심플하게 헤더 생성 + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + // 2. Transfer 객체 빌드 + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + // 3. EAI 컴포넌트를 통해 최종 전송 + return eai.call(req, resBody); + } + */ + + /** + * 3. 대내 MCI 호출 (사내 시스템 간 통신) + */ + /* + public Transfer callMci(String itrfId, S body, Class resBody) { + + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + return mci.call(req, resBody); + } + */ +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java new file mode 100644 index 000000000..e9961c050 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java @@ -0,0 +1,84 @@ +package io.shinhanlife.dap.lib.integration.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.List; + +// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석을 풀고 사용하세요! +// import io.shinhanlife.glow.communication.annotation.GlowMciFieldInfo; + +/** + * [대외 MCI 연동용 DTO 표준 템플릿] + * Glow Framework 개발표준정의서(2.2.1 IO 작성) 규칙을 100% 준수한 샘플입니다. + * 새로운 대외 통신 전문을 만들 때 이 파일을 복사해서 필드명과 길이만 수정하여 사용하세요. + */ +/** + * @package io.shinhanlife.dap.lib.integration.dto + * @className SampleGlowMessage + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PUBLIC) // [규칙 1] Reflection을 위한 기본 생성자 필수 (public 유지) +public class SampleGlowMessage { + + // [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서) + // @GlowMciFieldInfo(order = 1) + private MessageHeader header; + + // [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서) + // @GlowMciFieldInfo(order = 2) + private List msgDtdvValu; // 다건(List) 본문 데이터 + + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PUBLIC) + public static class MessageHeader { + + // [규칙 2] 단건 필드의 경우 length 필수 입력 (EIMS 길이와 일치해야 함) + // @GlowMciFieldInfo(order = 1, length = 1) + private String msgTnsmTypeCd; + + // @GlowMciFieldInfo(order = 2, length = 8) + private int msdvLen; + + // [규칙 3] 다건(List) 건수 필드의 경우, target 속성에 대상 변수명("msgDtdvValu") 필수 기입! + // @GlowMciFieldInfo(order = 3, length = 2, target = "msgDtdvValu") + private int msgRpttCc; + } + + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PUBLIC) + public static class MessageBody { + + // @GlowMciFieldInfo(order = 1, length = 8) + private String msgCd; + + // @GlowMciFieldInfo(order = 2, length = 1) + private String msgPrnAttrCd; + + // @GlowMciFieldInfo(order = 3, length = 200) + private String msgCt; + + // @GlowMciFieldInfo(order = 4, length = 200) + private String anxMsgCt; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java new file mode 100644 index 000000000..8cf2b26e8 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java @@ -0,0 +1,99 @@ +package io.shinhanlife.dap.lib.integration.eai.component; + +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.Transfer; +import io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent; +import io.shinhanlife.glow.communication.util.CommonHeaderFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 신한라이프 내부 Glow 표준 EAI 컴포넌트 어댑터 (AXHUB) + */ +@Slf4j +@Component +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.eai.component + * @className AxhubEaiComponent + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class AxhubEaiComponent { + + @SuppressWarnings("rawtypes") + private final GlowEaiComponent eai; + private final GlowCommunicationProperties communicationProperties; + + @SuppressWarnings("unchecked") + private Transfer syncEai(Transfer request) { + // LOG 저장 (AXHUB 방식 로깅) + CommonHeader reqHeader = (CommonHeader) request.getHeader(); + log.info("[AxhubEaiComponent] {} EAI 호출시작 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + Transfer response = (Transfer) eai.sync(request); + + log.info("[AxhubEaiComponent] {} EAI 호출종료 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + return response; + } + + /** + * EAI 호출 + */ + public Transfer call(String itrfId, String rcvSvcId, I inputDto) throws Exception { + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId, rcvSvcId); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .build(); + + return syncEai(request); + } + + /** + * EAI 호출 (응답 타입 명시) + */ + @SuppressWarnings("unchecked") + public Transfer call(String itrfId, String rcvSvcId, I inputDto, Class resBodyClass) throws Exception { + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId, rcvSvcId); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .resBodyClass((Class) (Class) resBodyClass) + .build(); + + return syncEai(request); + } + + /** + * EAI 호출 (rcvSvcId 없는 경우) + */ + public Transfer call(String itrfId, I inputDTO, Class resBodyClass) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return call(itrfId, rcvSvcId, inputDTO, resBodyClass); + } + + /** + * EAI 호출 (Response body class와 rcvSvcId 없는 경우) + */ + public Transfer call(String itrfId, I inputDTO) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return call(itrfId, rcvSvcId, inputDTO); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java new file mode 100644 index 000000000..dccf1fe5c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java @@ -0,0 +1,140 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.dap.lib.mcp.McpRequestHeaderContext; +import io.shinhanlife.dap.lib.mcp.McpRequestHeaders; +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import io.shinhanlife.glow.communication.module.http.dto.HttpBody; +import io.shinhanlife.glow.communication.module.http.dto.HttpHeader; +import io.shinhanlife.glow.communication.module.http.dto.HttpTransfer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +/** + * Tool Pod outbound HTTP component using the Glow HTTP client. + * Target URL, HTTP method, content type and Pod-to-Pod behaviour are resolved from + * {@code glow.communication.http.api-list}; business source code does not own endpoint values. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class AxhubHttpComponent { + + private static final String ANONYMOUS_REQUEST = "AXHUB-TOOL"; + + private final GlowHttpComponent http; + private final ObjectMapper json; + private final GlowCommunicationProperties communicationProperties; + private final AxhubHttpProperties properties; + + /** Calls the exact URL configured for the API name. */ + public R call(String apiName, T inputDto, Class responseBodyClass) { + return call(apiName, "", inputDto, responseBodyClass, 0); + } + + /** Calls the configured URL with an optional resource suffix. */ + public R call(String apiName, String uri, T inputDto, Class responseBodyClass) { + return call(apiName, uri, inputDto, responseBodyClass, 0); + } + + public R call(String apiName, String uri, T inputDto, Class responseBodyClass, int timeout) { + AxhubHttpProperties.ApiDefinition api = resolveApi(apiName); + return execute(api, uri, inputDto, responseBodyClass, timeout); + } + + /** Calls the configured URL only when the target is marked as a business Pod. */ + public R callBizPod(String apiName, T inputDto, Class responseBodyClass) { + AxhubHttpProperties.ApiDefinition api = resolveApi(apiName); + if (!api.bizPod()) { + throw new IllegalArgumentException("Configured API is not a business Pod: " + apiName); + } + return execute(api, "", inputDto, responseBodyClass, 0); + } + + private R execute(AxhubHttpProperties.ApiDefinition api, String uri, T inputDto, + Class responseBodyClass, int timeout) { + HttpHeader header = createHeader(api, timeout); + String requestUri = joinPath(api.url(), uri); + HttpTransfer request = HttpTransfer.http() + .header(header) + .domain(api.domain()) + .uri(requestUri) + .method(api.method()) + .contentType(contentType(api)) + .responseEntity(responseBodyClass) + .body(inputDto) + .build(); + + log.info("[AxhubHttpComponent] Glow HTTP call. apiName={}, method={}, uri={}", + api.name(), api.method(), requestUri); + ResponseEntity response = http.sync(request); + return convertResponse(response.getBody(), responseBodyClass); + } + + private AxhubHttpProperties.ApiDefinition resolveApi(String apiName) { + return properties.getApiList().stream() + .filter(api -> apiName != null && apiName.equals(api.name())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No HTTP API configuration for name: " + apiName)); + } + + private HttpHeader createHeader(AxhubHttpProperties.ApiDefinition api, int timeout) { + HttpHeader header = new HttpHeader(); + if (api.bizPod()) { + header.set("X-POD-TO-POD", "true"); + } + McpRequestHeaders inbound = McpRequestHeaderContext.current(); + if (inbound == null) { + header.set("X-ANONYMOUS-REQ", ANONYMOUS_REQUEST); + } else { + putIfPresent(header, "trace-id", inbound.traceId()); + putIfPresent(header, "request-id", inbound.requestId()); + putIfPresent(header, "X-USER-ID", inbound.encryptedEmployeeId()); + } + header.setReadTimeout(timeout == 0 ? defaultReadTimeout() : timeout); + return header; + } + + private int defaultReadTimeout() { + return communicationProperties == null || communicationProperties.getHttp() == null + ? 0 : communicationProperties.getHttp().getReadTimeout(); + } + + private MediaType contentType(AxhubHttpProperties.ApiDefinition api) { + return api.contentType() == null || api.contentType().isBlank() + ? MediaType.APPLICATION_JSON + : MediaType.parseMediaType(api.contentType()); + } + + private R convertResponse(HttpBody responseBody, Class responseBodyClass) { + String content = responseBody == null ? null : responseBody.content(); + if (responseBodyClass == String.class) { + return responseBodyClass.cast(content); + } + try { + return json.readValue(content, responseBodyClass); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to convert HTTP response to " + responseBodyClass.getSimpleName(), e); + } + } + + private void putIfPresent(HttpHeader header, String name, String value) { + if (value != null && !value.isBlank()) { + header.set(name, value); + } + } + + private String joinPath(String configuredUrl, String suffix) { + String left = configuredUrl == null ? "" : configuredUrl.replaceAll("/+$", ""); + if (suffix == null || suffix.isBlank()) { + return left.isEmpty() ? "/" : left; + } + String right = suffix.startsWith("/") ? suffix : "/" + suffix; + return left + right; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java new file mode 100644 index 000000000..b07835edc --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +/** + * Glow HTTP target catalog. + * + *

Each target follows the ShinhanLife standard: name, domain, url, method, + * content-type, and biz-pod. Target-specific values belong in application-glow*.yml.

+ */ +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "glow.communication.http") +public class AxhubHttpProperties { + + private List apiList = new ArrayList<>(); + + public record ApiDefinition( + String name, + String domain, + String url, + HttpMethod method, + String contentType, + boolean bizPod + ) { + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java new file mode 100644 index 000000000..746d70fb0 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java @@ -0,0 +1,168 @@ +package io.shinhanlife.dap.lib.integration.mci.component; + +import io.shinhanlife.dap.lib.session.dto.SessionDto; +import io.shinhanlife.dap.lib.util.SessionUtil; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.HeaderDefaults; +import io.shinhanlife.glow.communication.dto.Transfer; +import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +import io.shinhanlife.glow.communication.util.CommonHeaderFactory; +import io.shinhanlife.dap.lib.integration.mci.enums.IndvCtinRoleTyp; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 신한라이프 내부 Glow 표준 컴포넌트 어댑터 (AXHUB) + */ +@Slf4j +@Component +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.mci.component + * @className AxhubMciComponent + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class AxhubMciComponent { + + @SuppressWarnings("rawtypes") + private final GlowMciComponent mci; + private final GlowCommunicationProperties communicationProperties; + + /** 전문생성채널유형코드 : 1 (채널계) */ + private final static String TGRM_CREA_CHNN_TYPE_CD_1 = "1"; + private static final String SUCO_UNBL_CODE = "NNB00147"; // 청약불가 + + /** + * 전문 Common Header 생성 + * @param itrfName 인터페이스Id + * @param rcvSvcId 수신서비스Id + * @return Map + */ + private Map createCommonHeaderMap(String itrfName, String rcvSvcId) { + SessionDto sessionDto = SessionUtil.getSession(); + Map commonHeaderMap = new HashMap<>(); + + commonHeaderMap.put(HeaderDefaults.ITRF_ID, itrfName); + commonHeaderMap.put(HeaderDefaults.RCV_SVC_ID, rcvSvcId); + + if (sessionDto != null) { + commonHeaderMap.put(HeaderDefaults.STR_YMD, sessionDto.getStrYmd()); + commonHeaderMap.put(HeaderDefaults.ACNT_OGNZ_NO, sessionDto.getBrafNo()); + commonHeaderMap.put(HeaderDefaults.PSMR_ASRT_CD, sessionDto.getPsmrAsrtCd()); + commonHeaderMap.put(HeaderDefaults.SBSN_RULP_ASRT_CD, sessionDto.getSbsnRulpAsrtCd()); + commonHeaderMap.put(HeaderDefaults.BSDU_CD, sessionDto.getBsduCd()); + commonHeaderMap.put(HeaderDefaults.BSQU_CD, sessionDto.getBsquCd()); + commonHeaderMap.put(HeaderDefaults.OGNZ_ASRT_CD, sessionDto.getOgnzAsrtCd()); + commonHeaderMap.put(HeaderDefaults.OGNZ_LEVE_CD, sessionDto.getOgnzLeveCd()); + commonHeaderMap.put(HeaderDefaults.SCRN_ID, sessionDto.getPrgrId()); + } + + commonHeaderMap.put(HeaderDefaults.INDV_CTIN_ROLE_CD, IndvCtinRoleTyp.CD_Z99.getCode()); + commonHeaderMap.put(HeaderDefaults.TGRM_CREA_CHNN_TYPE_CD, TGRM_CREA_CHNN_TYPE_CD_1); + + if (communicationProperties != null && communicationProperties.getCommon() != null) { + commonHeaderMap.put(HeaderDefaults.ENVR_TYPE_CD, communicationProperties.getCommon().getEnvType()); + } + + return commonHeaderMap; + } + + @SuppressWarnings("unchecked") + private Transfer syncMci(Transfer request) { + // LOG 저장 (AXHUB 방식 로깅) + CommonHeader reqHeader = (CommonHeader) request.getHeader(); + log.info("[AxhubMciComponent] {} MCI 호출시작 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + Transfer response = (Transfer) mci.sync(request); + log.info("[AxhubMciComponent] {} MCI 호출종료 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + if (response != null && response.getHeader() != null) { + CommonHeader resHeader = (CommonHeader) response.getHeader(); + String tgrmDalRsltCd = resHeader.getTgrmDalRsltCd(); + // TODO 추가 메시지 처리 및 오류 코드 제어 로직 + } + + return response; + } + + public Transfer callTo(String itrfName, String rcvSvcId, I inputDto) throws Exception { + Map commonHeaderMap = createCommonHeaderMap(itrfName, rcvSvcId); + CommonHeader header = CommonHeaderFactory.createRequestHeader(commonHeaderMap); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .build(); + + return syncMci(request); + } + + /** + * 대내 mci 호출 + * @param itrfName 인터페이스Id + * @param rcvSvcId 수신서비스Id + * @param inputDto inputDto + * @param resBodyClass resBodyClass + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + */ + @SuppressWarnings("unchecked") + public Transfer callTo(String itrfName, String rcvSvcId, I inputDto, Class resBodyClass) throws Exception { + Map commonHeaderMap = createCommonHeaderMap(itrfName, rcvSvcId); + CommonHeader header = CommonHeaderFactory.createRequestHeader(commonHeaderMap); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .resBodyClass((Class) (Class) resBodyClass) + .build(); + + return syncMci(request); + } + + /** + * 대내 mci 호출 (rcvSvcId 없는 경우) + * @param itrfName 인터페이스Id + * @param inputDTO 수신서비스Id (클래스명 대체) + * @param resBodyClass resBodyClass + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + * @throws Exception Exception + */ + public Transfer callTo(String itrfName, I inputDTO, Class resBodyClass) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return callTo(itrfName, rcvSvcId, inputDTO, resBodyClass); + } + + /** + * 대내 mci 호출 (Response body class와 rcvSvcId 없는 경우) + * @param itrfName 인터페이스Id + * @param inputDTO 수신서비스Id (클래스명 대체) + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + * @throws Exception Exception + */ + public Transfer callTo(String itrfName, I inputDTO) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return callTo(itrfName, rcvSvcId, inputDTO); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java new file mode 100644 index 000000000..a46fcd538 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.integration.mci.config; + +import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * TODO: 실제 Glow Framework 의존성이 추가되어 io.shinhanlife.glow 패키지가 + * ComponentScan에 잡히게 되면 이 설정 클래스는 삭제하세요. + */ +@Configuration + +/** + * @package io.shinhanlife.dap.lib.integration.mci.config + * @className GlowMockConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class GlowMockConfig { + + @Bean + @SuppressWarnings("rawtypes") + public GlowMciComponent glowMciComponent() { + return new GlowMciComponent(); + } + + @Bean + @SuppressWarnings("rawtypes") + public io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent glowEaiComponent() { + return new io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java new file mode 100644 index 000000000..d13027460 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.integration.mci.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.config + * @className ShinhanIntegrationProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +@ConfigurationProperties(prefix = "shinhan.integration") +public class ShinhanIntegrationProperties { + + /** + * 환경유형코드: 운영(R), 테스트(T), 개발(D) + */ + private String envrTypeCd = "D"; + + private ServerInfo eai = new ServerInfo(); + private ServerInfo internalMci = new ServerInfo(); + + @Getter + @Setter + public static class ServerInfo { + private String url; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java new file mode 100644 index 000000000..23c3bc66b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className MciRequestWrapper + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MciRequestWrapper { + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + @JsonUnwrapped + private T body; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java new file mode 100644 index 000000000..4e2b94753 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className MciResponseWrapper + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MciResponseWrapper { + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + @JsonUnwrapped + private T body; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java new file mode 100644 index 000000000..9eff40b6e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java @@ -0,0 +1,90 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className OlCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 - OL(구 오렌지라이프) 공통 헤더 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OlCommonHeaderDto { + private String custNm; // 고객명 + private String custRrn; // 고객 주민등록번호 + private String custNo; // 고객번호 + private String rcevNo; // 접수번호 + private String pono; // 증권번호 + private String scrNm; // 화면명 + private String scrId; // 화면ID + private String lginDttm; // 사용자가 로그인한 접속일시 + private String lginIpAddr; // 사용자가 접속한 IP 주소 + private String userNm; // 사용자 이름(한글) + private String userEngNm; // 사용자 영문이름 + private String userId; // 사용자 ID(AD ID) + private String userNo; // 사용자번호 + private String deptCd; // 사용자조직 코드 + private String salsDvCd; // 영업본부코드 + private String salsBoCd; // 영업지점코드 + private String uppDeptCd; // 상위조직코드 + private String prcsrUserId; // 처리자 ID(AD ID) + private String prcsrUserNo; // 처리지번호 + private String prcsrDeptCd; // 처리지조직 코드 + private String prcsrDvCd; // 처리지 영업본부코드 + private String prcsrBoCd; // 처리지 영업지점코드 + private String prcsrUppDeptCd; // 부서코드 + private String sysCd; // 요청이 들어온 시스템을 표시 + private String reqtSvcNm; // 요청하는 서비스 모듈명 + private String reqtMthdNm; // 요청하는 메소드명 + private String reqtVoNm; // 요청메소드에 전달할 값을 담는 VO명 + private String scrButnFuncClssCd; // 화면에서 버튼 별 이벤트 구분을 위한 구분코드 + private String scrGriCnt; // 화면 그리드 개수 + private List pageList; // 페이징 리스트 (L2 반복) + private String reqtDttm; // 요청일시 + private String crdtInfoIcluFlg; // 신용정보포함여부(Y,N) + private String crdtInfoDataChgTypCd; // 업무내역별 식별코드 부여 + private String crdtInfoIdfInEngAbbrNm; // 신용정보식별영문약어명 + private String crdtInfoIdfnSysCd; // 신용정보식별시스템코드 + private String scrButnNm; // 화면버튼명 + private String msgCnt; // 메시지 개수 + private List msgList; // 메시지 리스트 (L2 반복) + private String respDttm; // 응답일시 + private String svcRunNm; // 거래별로 유일한 ServiceExecutionID + private String stdate; // 기준일자 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OlPageDto { + private String pageSrno; // 페이지 인덱스값 (L3) + private String pageInqCnt; // 한페이지에 조회될 건수 (L3) + private String nxtButnNm; // 다음버튼ID (L3) + private String nxtButnEnbFlg; // 다음버튼 활성여부 (L3) + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OlMsgDto { + private String msgNo; // 서버 측에서 세팅한 정상/에러 메시지코드 (L3) + private String msgTypCd; // 메시지유형코드 (L3) + private String msgNm; // 메시지코드의 내용 (L3) + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java new file mode 100644 index 000000000..43e107577 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java @@ -0,0 +1,72 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanCommonHeaderDto { + + private String tgrmLencn; // 전문길이 + private String glbId; // 글로벌ID (전사공통키) + private String pgrsSriaNo; // 진행일련번호 + private String tgrmVrsnInfoValu; // 전문버전정보값 + private String tgrmEncrYn; // 전문암호화여부 + private String gpcpCd; // 그룹사코드 + private String appliDutjCd; // 어플리케이션업무코드 + private String appliDtptDutjCd; // 어플리케이션상세업무코드 + private String frbuCd; // 대외기관코드 + private String cmouDutjCd; // 대외업무코드 + private String cmouCssfCd; // 대외종별코드 + private String cmouTraCd; // 대외거래코드 + private String rcvSvcId; // 수신서비스ID + private String rsltRcvSvcId; // 결과수신서비스ID + private String tgrmCreaChnnTypeCd; // 전문생성채널유형코드 + + private String lnggDvsnCd; // 언어구분코드 + private String simulTraYn; // 시뮬레이션거래여부 + private String itrIfId; // 인터페이스ID + private String reqRspnScCd; // 요청응답구분코드 + private String tnsmTypeCd; // 전송유형코드 + private String envrTypeCd; // 환경유형코드 + private String inqrTraTypeCd; // 조회거래유형코드 + private String reqTgrmTnsmDtptDt; // 요청전문전송상세일시 + private String strYmd; // 기준일자 + private String scrnId; // 화면ID + private String scrnBtnId; // 화면버튼ID + + private String userIpAddr; // 사용자IP주소 + private String drtmCd; // 부서코드 + private String userId; // 사용자ID + private String indvCtinRoleCd; // 개인신용정보역할코드 + private String acntOgnzNo; // 경리조직번호 + private String rspnTgrmTnsmDtptDt; // 응답전문전송상세일시 + private String tgrmDalRsltCd; // 전문처리결과코드 + private String ognzAsrtCd; // 조직분류코드 + private String ognzLeveCd; // 조직레벨코드 + private String psmrAsrtCd; // 인사조직분류코드 + private String sbsnRulpAsrtCd; // 영업규정분류코드 + private String bsduCd; // 영업지국코드 + private String bsquCd; // 영업자격코드 + private String linkPrafDutyCd; // 연계인사직책코드 + private String indvInfoLogWritYn; // 개인정보로그작성여부 + private String prepImhdNm; // 예비항목명 +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java new file mode 100644 index 000000000..5c02fd310 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java @@ -0,0 +1,51 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanMessageDto + * @description AX HUB 시스템 처리 클래스 - MCI 전문 메시지부 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanMessageDto { + + private MsgHddvValu msgHddvValu; // 메시지헤더부값 + private MsgDtdvValu msgDtdvValu; // 메시지데이터부값 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MsgHddvValu { + private String msgTnsmTypeCd; // 메시지전송유형코드 + private Integer msdvLencn; // 메시지부길이 + private Integer msgRpttCc; // 메시지반복건수 + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MsgDtdvValu { + private String msgCd; // 메시지코드 + private String msgPrnAttrCd; // 메시지출력속성코드 + private String msgCt; // 메시지내용 + private String anxMsgCt; // 부가메시지내용 + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java new file mode 100644 index 000000000..47133822a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanTelegramWrapper + * @description AX HUB 시스템 처리 클래스 - MCI 전문 전체 래퍼 (공통헤더부 + 메시지부 + 데이터부) + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanTelegramWrapper { + + // 1. 공통 헤더부 + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + // 2. 메시지부 + private ShinhanMessageDto tgrmMsdvValu; + + // 3. 데이터부 (비즈니스마다 다름, JsonUnwrapped로 평탄화하거나 객체 자체로 유지 가능. 여기서는 객체 유지) + private T tgrmDtdvValu; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java new file mode 100644 index 000000000..73d3514c7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java @@ -0,0 +1,84 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className SlCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 - SL(신한라이프) 표준 헤더 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SlCommonHeaderDto { + private String length; // 전문길이 + private SlGlobalId globalId; // 글로벌ID + private String headerVer; // 전문헤더버전 + private String encodeFlag; // 전문암호화여부 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SlGlobalId { + private String writeDate; // 전문작성일 (8) + private String sysCd; // 생성시스템명 (8) + private String typeCd; // 구분코드 (2) + private String detailCd; // 세부업무코드 (4) + private String seqNo; // 채번번호 (8) + private String step; // 진행상황 (2) + } + + private String groupCoCd; // 그룹사코드 + private String instCd; // 기관코드 + private String applCd; // 업무코드 + private String kindCd; // 종별코드 + private String txCd; // 거래코드 + private String pfmAppName; // 어플리케이션 명 + private String pfmSvcName; // 서비스 명 + private String pfmFnName; // 오퍼레이션 명 + private String systemCd; // 생성시스템구분 + private String trFlag; // 요청응답구분 + private String syncFlag; // 동기구분 + private String envrFlag; // 환경구분 + private String crudFlag; // 조회거래구분 + private String sendTime; // 전문전송일시 + private String screenId; // 화면ID + private String clntIp; // Client IP + private String orgCd; // 부서(지점)코드 + private String userId; // 사용자 사번(아이디) + private String indvCrdtInfo; // 개인신용정보역할코드 + private String acntOgnzNo; // 경리조직번호 + private String ttiFlag; // TimeOut사용 + private String ttiStartTm; // 최초시작시간 + private String ttiKeepTm; // 유지시간초수 + private String outMsgTm; // 응답전문작성일시 + private String resType; // 처리결과 + private String resCode; // 응답코드 + private String resBascMsg; // 응답기본내역 + private String msgType; // 메시지 유형 + private String rcvSvcCd; // 수신 서비스 Code + private String rsltRcvSvcCd; // 결과수신 서비스 Code + private String realSvcCd; // Real 서비스 Code + private String ognzAsrtCd; // 조직분류코드 + private String ognzLeveCd; // 조직레벨구분코드 + private String psmrAsrtCd; // 인사조직분류코드 + private String sbsnRulpAsrtCd; // 영업규정분류코드 + private String bsduCd; // 영업지국코드 + private String bsquCd; // 영업자격코드 + private String linkPrafDutyCd; // 직책코드 + private String temp; // 예비 필드 +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java new file mode 100644 index 000000000..5ed178870 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.lib.integration.mci.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.mci.enums + * @className IndvCtinRoleTyp + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public enum IndvCtinRoleTyp { + CD_Z99("Z99"); + + private final String code; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java new file mode 100644 index 000000000..f793904ec --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java @@ -0,0 +1,10 @@ +package io.shinhanlife.dap.lib.manifest; + +/** Behaviour hints exposed by the Tool Service manifest. */ +public record ToolManifestAnnotations( + String title, + boolean readOnlyHint, + boolean destructiveHint, + boolean idempotentHint, + boolean openWorldHint) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java new file mode 100644 index 000000000..9f07eb9f5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.lib.manifest; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** One MCP Tool declaration published by a Tool Service. */ +public record ToolManifestItem( + String name, + String endpoint, + String title, + String description, + Map inputSchema, + Map outputSchema, + ToolManifestAnnotations annotations, + @JsonProperty("_meta") ToolManifestMeta meta) { +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java new file mode 100644 index 000000000..f21ba07ee --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.lib.manifest; + +import java.util.List; + +/** Operational metadata exposed by the Tool Service manifest. */ +public record ToolManifestMeta( + String version, + long timeoutMillis, + boolean enabled, + List exampleQueries, + List tags, + String legacyInterfaceId, + List requiredEnvKeys, + String ownerOrg) { +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java new file mode 100644 index 000000000..293a9e01b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java @@ -0,0 +1,7 @@ +package io.shinhanlife.dap.lib.manifest; + +import java.util.List; + +/** Top-level response for GET /tool-manifest. */ +public record ToolManifestResponse(String bundleId, String revision, List tools) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java new file mode 100644 index 000000000..c76a47264 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java @@ -0,0 +1,137 @@ +package io.shinhanlife.dap.lib.manifest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** Builds the Tool Service owned manifest consumed by the MCP server. */ +@Service +public class ToolManifestService { + + private static final long DEFAULT_TIMEOUT_MILLIS = 300000L; + private static final AtomicLong LAST_ISSUED_REVISION = new AtomicLong(); + private final Supplier> toolSupplier; + private final ObjectMapper objectMapper; + private final McpProperties properties; + private String lastFingerprint; + private String lastRevision; + + @Autowired + public ToolManifestService(ToolRegistryHeartbeatSender heartbeatSender, ObjectMapper objectMapper, + McpProperties properties) { + this(heartbeatSender::getAllScannedTools, objectMapper, properties); + } + + ToolManifestService(Supplier> toolSupplier, ObjectMapper objectMapper, + McpProperties properties) { + this.toolSupplier = toolSupplier; + this.objectMapper = objectMapper; + this.properties = properties; + } + + public ToolManifestResponse currentManifest() { + String bundleId = properties.getManifest() == null ? null : properties.getManifest().getBundleId(); + if (bundleId == null || bundleId.isBlank()) { + throw new IllegalStateException("mcp.manifest.bundle-id must be configured"); + } + + List tools = toolSupplier.get().stream() + .map(this::toManifestItem) + .sorted(Comparator.comparing(ToolManifestItem::name)) + .toList(); + validate(tools); + return new ToolManifestResponse(bundleId, revision(bundleId, tools), tools); + } + + private ToolManifestItem toManifestItem(ToolMetadata tool) { + String title = tool.getDisplayName() == null || tool.getDisplayName().isBlank() + ? tool.getName() : tool.getDisplayName(); + Map schema = tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema(); + return new ToolManifestItem( + tool.getName(), endpoint(tool), title, tool.getDescription(), schema, tool.getOutputSchema(), + new ToolManifestAnnotations(title, isTrue(tool.getReadOnlyHint()), isTrue(tool.getDestructiveHint()), + isTrue(tool.getIdempotentHint()), isTrue(tool.getOpenWorldHint())), + new ToolManifestMeta(defaultString(tool.getSemver(), "1.0.0"), + tool.getTimeoutMillis() == null ? DEFAULT_TIMEOUT_MILLIS : tool.getTimeoutMillis(), + tool.getEnabled() == null || tool.getEnabled(), + defaultList(tool.getExampleQueries()), defaultList(tool.getTags()), + tool.getMciServiceId(), defaultList(tool.getRequiredEnvKeys()), tool.getOwnerOrg())); + } + + private void validate(List tools) { + String namePrefix = properties.getManifest() == null ? null : properties.getManifest().getNamePrefix(); + Set names = new LinkedHashSet<>(); + for (ToolManifestItem tool : tools) { + if (tool.name() == null || tool.name().isBlank()) { + throw new IllegalStateException("Tool manifest contains a blank tool name"); + } + if (!names.add(tool.name())) { + throw new IllegalStateException("Tool manifest contains duplicate tool name: " + tool.name()); + } + if (namePrefix != null && !namePrefix.isBlank() && !tool.name().startsWith(namePrefix)) { + throw new IllegalStateException("Tool name does not match mcp.manifest.name-prefix: " + tool.name()); + } + if (!"object".equals(tool.inputSchema().get("type"))) { + throw new IllegalStateException("Tool inputSchema root type must be object: " + tool.name()); + } + } + } + + private synchronized String revision(String bundleId, List tools) { + String fingerprint = fingerprint(bundleId, tools); + if (!fingerprint.equals(lastFingerprint)) { + long localMinimum = lastRevision == null ? Long.MIN_VALUE : Long.parseLong(lastRevision) + 1; + long nextTimestamp = LAST_ISSUED_REVISION.updateAndGet(previous -> + Math.max(Math.max(System.currentTimeMillis(), localMinimum), previous + 1)); + lastFingerprint = fingerprint; + lastRevision = Long.toString(nextTimestamp); + } + return lastRevision; + } + + private String fingerprint(String bundleId, List tools) { + try { + return objectMapper.writeValueAsString(Map.of("bundleId", bundleId, "tools", tools)); + } catch (Exception exception) { + throw new IllegalStateException("Failed to build Tool manifest revision source", exception); + } + } + + private String endpoint(ToolMetadata tool) { + if (tool.getEndpoint() != null && !tool.getEndpoint().isBlank()) { + return tool.getEndpoint(); + } + if (tool.getPodUrl() == null || tool.getPodUrl().isBlank()) { + return "/mcp/" + tool.getName(); + } + return tool.getPodUrl().replaceAll("/+$", "") + "/mcp/" + tool.getName(); + } + private Map emptySchema() { + return Map.of("type", "object", "properties", Map.of(), "additionalProperties", false); + } + + private boolean isTrue(Boolean value) { + return Boolean.TRUE.equals(value); + } + + private String defaultString(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private List defaultList(List value) { + return value == null ? List.of() : List.copyOf(value); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java new file mode 100644 index 000000000..0104394df --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java @@ -0,0 +1,21 @@ +package io.shinhanlife.dap.lib.mcp; + +/** Holds optional MCP headers for the lifetime of one HTTP request thread. */ +public final class McpRequestHeaderContext { + private static final ThreadLocal CURRENT_HEADERS = new ThreadLocal<>(); + + private McpRequestHeaderContext() { + } + + public static McpRequestHeaders current() { + return CURRENT_HEADERS.get(); + } + + static void set(McpRequestHeaders headers) { + CURRENT_HEADERS.set(headers); + } + + static void clear() { + CURRENT_HEADERS.remove(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java new file mode 100644 index 000000000..aae2654f4 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.mcp; + +import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +/** Captures optional correlation and employee headers for an MCP HTTP call. */ +@Component +public class McpRequestHeaderFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return !request.getRequestURI().endsWith("/mcp"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + McpRequestHeaderContext.set(new McpRequestHeaders( + request.getHeader("X-Request-Id"), + request.getHeader("trace-id"), + request.getHeader("request-id"), + request.getHeader("employee-id"))); + try { + filterChain.doFilter(request, response); + } finally { + McpRequestHeaderContext.clear(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java new file mode 100644 index 000000000..3eb87c766 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java @@ -0,0 +1,9 @@ +package io.shinhanlife.dap.lib.mcp; + +/** Optional request headers propagated from an MCP HTTP request to a Tool invocation. */ +public record McpRequestHeaders( + String headerRequestId, + String traceId, + String requestId, + String encryptedEmployeeId) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java new file mode 100644 index 000000000..6b0c6cfbb --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java @@ -0,0 +1,101 @@ +package io.shinhanlife.dap.lib.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** Executes a cached Tool independently from its HTTP or MCP transport. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class McpToolExecutionService { + + private final McpToolMethodRegistry toolMethodRegistry; + private final ObjectMapper objectMapper; + private final ToolArgumentSchemaValidator toolArgumentSchemaValidator; + private final ToolSchemaResolver toolSchemaResolver; + + public ToolExecutionResult execute(String functionName, McpRequestHeaders requestHeaders, + Map arguments) { + String headerRequestId = requestHeaders == null ? null : requestHeaders.headerRequestId(); + String traceId = requestHeaders == null ? null : requestHeaders.traceId(); + String requestId = requestHeaders == null ? null : requestHeaders.requestId(); + log.info("[Tool] IN - trace-id: {}, request-id: {}, tool: {}", traceId, requestId, functionName); + + McpToolMethodRegistry.RegisteredTool resolvedTool = toolMethodRegistry.find(functionName); + if (resolvedTool == null) { + return error(404, "TOOL_NOT_FOUND", "Tool not found: " + functionName, headerRequestId); + } + ToolExecutionResult validationFailure = validateInput(resolvedTool, arguments, headerRequestId); + if (validationFailure != null) { + return validationFailure; + } + try { + Object methodResult = invoke(resolvedTool, convertArgument(resolvedTool.method(), arguments)); + ToolExecutionResult outputFailure = validateOutput(resolvedTool, methodResult, headerRequestId); + if (outputFailure != null) { + return outputFailure; + } + Map headers = new HashMap<>(); + if (traceId != null) headers.put("trace-id", traceId); + if (requestId != null) headers.put("request-id", requestId); + log.info("[Tool] OUT - trace-id: {}, request-id: {}, tool: {}", traceId, requestId, functionName); + return new ToolExecutionResult(200, methodResult, headers); + } catch (Exception error) { + log.error("[Tool] Tool execution failed. tool={}", functionName, error); + return error(502, "TOOL_ERROR", "Tool execution failed", headerRequestId); + } + } + + private ToolExecutionResult validateInput(McpToolMethodRegistry.RegisteredTool tool, + Map arguments, String requestId) { + if (tool.method().getParameterCount() == 0 || Map.class.isAssignableFrom(tool.method().getParameterTypes()[0])) return null; + try { + Map schema = toolSchemaResolver.resolve(tool.annotation(), tool.hint(), tool.method().getParameterTypes()[0]); + List errors = toolArgumentSchemaValidator.validate(schema, arguments); + return errors.isEmpty() ? null : error(422, "INVALID_PARAM", "Tool arguments do not match the input schema", requestId); + } catch (Exception error) { + log.error("[Tool] Input schema validation failed unexpectedly. tool={}", tool.annotation().name(), error); + return null; + } + } + + private Object convertArgument(Method method, Map arguments) { + if (method.getParameterCount() == 0 || arguments == null || Map.class.isAssignableFrom(method.getParameterTypes()[0])) return arguments; + return objectMapper.convertValue(arguments, method.getParameterTypes()[0]); + } + + private Object invoke(McpToolMethodRegistry.RegisteredTool tool, Object argument) throws Exception { + return tool.method().getParameterCount() == 0 ? tool.method().invoke(tool.bean()) : tool.method().invoke(tool.bean(), argument); + } + + private ToolExecutionResult validateOutput(McpToolMethodRegistry.RegisteredTool tool, + Object methodResult, String requestId) { + try { + Map outputSchema = toolSchemaResolver.resolveOutput(tool.annotation(), tool.method().getReturnType(), tool.hint()); + if (!outputSchema.isEmpty() && !toolArgumentSchemaValidator.validateValue(outputSchema, methodResult).isEmpty()) { + return error(500, "INVALID_TOOL_RESPONSE", "Tool response does not match its output schema", requestId); + } + } catch (Exception error) { + log.error("[Tool] Output schema validation failed unexpectedly. tool={}", tool.annotation().name(), error); + } + return null; + } + + private ToolExecutionResult error(int statusCode, String code, String message, String requestId) { + Map body = new HashMap<>(); + body.put("code", code); + body.put("message", message); + body.put("details", Map.of("status", Integer.toString(statusCode))); + if (requestId != null) body.put("request_id", requestId); + return new ToolExecutionResult(statusCode, body, Map.of()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java new file mode 100644 index 000000000..86d99947f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java @@ -0,0 +1,87 @@ +package io.shinhanlife.dap.lib.mcp; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.aop.support.AopUtils; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * Caches executable {@link McpTool} methods once when a Tool Pod starts. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class McpToolMethodRegistry { + + private final ApplicationContext applicationContext; + private final McpProperties mcpProperties; + + private volatile Map tools = Map.of(); + + @EventListener(ApplicationReadyEvent.class) + public void initialize() { + Map discovered = new LinkedHashMap<>(); + + for (Object bean : applicationContext.getBeansOfType(Object.class).values()) { + Class targetClass = AopUtils.getTargetClass(bean); + for (Method declaredMethod : targetClass.getDeclaredMethods()) { + McpTool annotation = AnnotationUtils.findAnnotation(declaredMethod, McpTool.class); + if (annotation == null) { + continue; + } + + RegisteredTool tool = new RegisteredTool( + bean, + findInvocableMethod(bean, declaredMethod), + annotation, + AnnotationUtils.findAnnotation(declaredMethod, GrowToolHint.class)); + register(discovered, annotation.name(), tool); + registerNamespaceAlias(discovered, annotation.name(), tool); + } + } + + tools = Map.copyOf(discovered); + log.info("[Tool Registry] {} executable tool names cached", tools.size()); + } + + public RegisteredTool find(String toolName) { + return tools.get(toolName); + } + + private void registerNamespaceAlias(Map discovered, String toolName, + RegisteredTool tool) { + String namespace = mcpProperties.getNamespace(); + if (StringUtils.hasText(namespace)) { + register(discovered, namespace + "_" + toolName, tool); + } + } + + private void register(Map discovered, String toolName, RegisteredTool tool) { + RegisteredTool existing = discovered.putIfAbsent(toolName, tool); + if (existing != null && existing != tool) { + throw new IllegalStateException("Duplicate @McpTool name: " + toolName); + } + } + + private Method findInvocableMethod(Object bean, Method declaredMethod) { + try { + return bean.getClass().getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes()); + } catch (NoSuchMethodException ignored) { + return declaredMethod; + } + } + + public record RegisteredTool(Object bean, Method method, McpTool annotation, GrowToolHint hint) { + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java new file mode 100644 index 000000000..461c3f423 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.lib.mcp; + +import java.util.Map; + +/** Protocol-neutral result of invoking one Tool Pod business tool. */ +public record ToolExecutionResult(int statusCode, Object body, Map headers) { + + public ToolExecutionResult { + headers = headers == null ? Map.of() : Map.copyOf(headers); + } + + public boolean isSuccess() { + return statusCode >= 200 && statusCode < 300; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java new file mode 100644 index 000000000..47fe285fd --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java @@ -0,0 +1,50 @@ +package io.shinhanlife.dap.lib.mcp; + +import io.modelcontextprotocol.server.McpServer; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import io.modelcontextprotocol.spec.McpSchema; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Slf4j +@Configuration +public class ToolMcpServerConfiguration { + + @PostConstruct + public void init() { + log.warn("================================================="); + log.warn("ToolMcpServerConfiguration IS LOADED BY SPRING!!!"); + log.warn("================================================="); + } + + @Bean + @Primary + public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { + return HttpServletStreamableServerTransportProvider.builder() + .mcpEndpoint("/mcp") + .build(); + } + + /** Creates the Tool Pod MCP server directly with MCP Java SDK 2.0.0. */ + @Bean(destroyMethod = "close") + public McpSyncServer toolMcpServer(HttpServletStreamableServerTransportProvider transportProvider) { + return McpServer.sync(transportProvider) + .serverInfo("dap-tool-pod", "2.0.0") + .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) + .build(); + } + + @Bean + public ServletRegistrationBean toolMcpServlet( + HttpServletStreamableServerTransportProvider transportProvider) { + // "/mcp/*"로 매핑하면 BusinessToolController의 "/mcp/api/v1/tools/local" 까지 가로채게 되므로, + // 정확히 MCP 통신에 사용되는 "/mcp" 와 "/mcp/message" 두 개만 매핑합니다. + return new ServletRegistrationBean<>(transportProvider, "/mcp", "/mcp/message"); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMetadataMcpMapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMetadataMcpMapper.java new file mode 100644 index 000000000..8624e60eb --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMetadataMcpMapper.java @@ -0,0 +1,73 @@ +package io.shinhanlife.dap.lib.mcp; + +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** Registry 메타데이터를 MCP SDK Tool 명세로 일관되게 변환합니다. */ +public final class ToolMetadataMcpMapper { + private ToolMetadataMcpMapper() { + } + + public static McpSchema.Tool toTool(ToolMetadata metadata) { + McpSchema.Tool.Builder builder = McpSchema.Tool.builder() + .name(metadata.getName()) + .title(defaultText(metadata.getDisplayName(), metadata.getName())) + .description(defaultText(metadata.getDescription(), metadata.getName() + " Tool")) + .inputSchema(toJsonSchema(metadata.getParametersSchema())) + .annotations(new McpSchema.ToolAnnotations( + metadata.getDisplayName(), metadata.getReadOnlyHint(), metadata.getDestructiveHint(), + metadata.getIdempotentHint(), metadata.getOpenWorldHint(), null)) + .meta(meta(metadata)); + if (metadata.getOutputSchema() != null && !metadata.getOutputSchema().isEmpty()) { + builder.outputSchema(metadata.getOutputSchema()); + } + return builder.build(); + } + + public static Map meta(ToolMetadata metadata) { + Map meta = new LinkedHashMap<>(); + put(meta, "version", metadata.getSemver()); + put(meta, "category_key", metadata.getCategoryKey()); + put(meta, "display_description", metadata.getDisplayDescription()); + put(meta, "example_queries", metadata.getExampleQueries()); + put(meta, "tags", metadata.getTags()); + put(meta, "legacy_interface_id", metadata.getMciServiceId()); + put(meta, "required_env_keys", metadata.getRequiredEnvKeys()); + put(meta, "owner_org", metadata.getOwnerOrg()); + return Map.copyOf(meta); + } + + private static void put(Map target, String key, Object value) { + if (value instanceof String text && !text.isBlank()) { + target.put(key, text); + } else if (value instanceof List list && !list.isEmpty()) { + target.put(key, List.copyOf(list)); + } + } + + private static String defaultText(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + @SuppressWarnings("unchecked") + private static McpSchema.JsonSchema toJsonSchema(Map source) { + Map schema = source == null ? emptySchema() : source; + return new McpSchema.JsonSchema( + String.valueOf(schema.getOrDefault("type", "object")), + schema.get("properties") instanceof Map properties + ? (Map) properties : Map.of(), + schema.get("required") instanceof List required ? (List) required : List.of(), + schema.get("additionalProperties") instanceof Boolean additionalProperties + ? additionalProperties : Boolean.TRUE, + schema.get("$defs") instanceof Map defs ? (Map) defs : Map.of(), + schema.get("definitions") instanceof Map definitions + ? (Map) definitions : Map.of()); + } + + private static Map emptySchema() { + return Map.of("type", "object", "properties", Map.of(), "additionalProperties", false); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java new file mode 100644 index 000000000..18d25b4d1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java @@ -0,0 +1,82 @@ +package io.shinhanlife.dap.lib.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */ +@Component +@ConditionalOnBean(McpToolExecutionService.class) +public class ToolPodMcpToolSynchronizer { + private final McpSyncServer mcpServer; + private final ToolRegistryHeartbeatSender heartbeatSender; + private final McpToolExecutionService toolExecutionService; + private final ObjectMapper objectMapper; + + public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender, + McpToolExecutionService toolExecutionService, ObjectMapper objectMapper) { + this.mcpServer = mcpServer; + this.heartbeatSender = heartbeatSender; + this.toolExecutionService = toolExecutionService; + this.objectMapper = objectMapper; + } + + @EventListener(ApplicationReadyEvent.class) + public void registerLocalTools() { + heartbeatSender.getAllScannedTools().stream() + .filter(tool -> Boolean.TRUE.equals(tool.getVisible())) + .forEach(tool -> mcpServer.addTool(specification(tool))); + } + + private McpServerFeatures.SyncToolSpecification specification(ToolMetadata tool) { + McpSchema.Tool mcpTool = ToolMetadataMcpMapper.toTool(tool); + return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool) + .callHandler((context, request) -> invoke(tool.getName(), McpRequestHeaderContext.current(), request.arguments())).build(); + } + + private McpSchema.CallToolResult invoke(String toolName, McpRequestHeaders requestHeaders, + Map arguments) { + ToolExecutionResult result = toolExecutionService.execute(toolName, requestHeaders, arguments); + boolean failed = !result.isSuccess(); + Object body = result.body(); + try { + return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body)) + .structuredContent(body).isError(failed).build(); + } catch (Exception error) { + return McpSchema.CallToolResult.builder().addTextContent(String.valueOf(body)).isError(failed).build(); + } + } + + private Map emptySchema() { + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", Map.of()); + schema.put("additionalProperties", false); + return schema; + } + @SuppressWarnings("unchecked") + private McpSchema.JsonSchema toJsonSchema(Map source) { + Map schema = source == null ? emptySchema() : source; + return new McpSchema.JsonSchema( + String.valueOf(schema.getOrDefault("type", "object")), + schema.get("properties") instanceof Map properties + ? (Map) properties : Map.of(), + schema.get("required") instanceof List required + ? (List) required : List.of(), + schema.get("additionalProperties") instanceof Boolean additionalProperties + ? additionalProperties : Boolean.TRUE, + schema.get("$defs") instanceof Map defs ? (Map) defs : Map.of(), + schema.get("definitions") instanceof Map definitions + ? (Map) definitions : Map.of()); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java new file mode 100644 index 000000000..1e79f37a4 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java @@ -0,0 +1,291 @@ +package io.shinhanlife.dap.lib.mcp; + + +/** + * @package io.shinhanlife.dap.mcc.service + * @className ToolRegistryHeartbeatSender + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionRepository; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; +import org.springframework.web.client.RestClient; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +@Slf4j +@Component +@Configuration +@EnableScheduling +@ConditionalOnBean(McpToolExecutionService.class) +public class ToolRegistryHeartbeatSender { + + private final ApplicationContext applicationContext; + private final ObjectMapper objectMapper; + private final McpProperties mcpProperties; + private final RestClient restClient = RestClient.create(); + private final ToolSchemaResolver toolSchemaResolver; + private final ToolDefinitionRepository toolDefinitionRepository; + + @Autowired + public ToolRegistryHeartbeatSender(ApplicationContext applicationContext, ObjectMapper objectMapper, + McpProperties mcpProperties, ToolSchemaResolver toolSchemaResolver, + @Nullable ToolDefinitionRepository toolDefinitionRepository) { + this.applicationContext = applicationContext; + this.objectMapper = objectMapper; + this.mcpProperties = mcpProperties; + this.toolSchemaResolver = toolSchemaResolver; + this.toolDefinitionRepository = toolDefinitionRepository; + } + + public ToolRegistryHeartbeatSender(ApplicationContext applicationContext, ObjectMapper objectMapper, + McpProperties mcpProperties, ToolSchemaResolver toolSchemaResolver) { + this(applicationContext, objectMapper, mcpProperties, toolSchemaResolver, null); + } + + @Value("${axhub.gateway.url:http://localhost:8081}") + private String gatewayUrl; + + @Value("${axhub.tool.url:http://localhost:8080}") + private String podUrl; + + @Value("${spring.application.name:}") + private String applicationName; + + private List registeredTools = new ArrayList<>(); + + @Getter + private List allScannedTools = new ArrayList<>(); + + @PostConstruct + public void init() { + log.info(" [HeartbeatSender] 초기화 시작. Gateway URL: {}, Pod URL: {}", gatewayUrl, podUrl); + scanAndBuildMetadata(); + } + + private void scanAndBuildMetadata() { + Map allBeans = applicationContext.getBeansOfType(Object.class); + for (Object bean : allBeans.values()) { + Class targetClass = AopUtils.getTargetClass(bean); + + for (Method method : targetClass.getDeclaredMethods()) { + McpTool functionAnnotation = AnnotationUtils.findAnnotation(method, McpTool.class); + GrowToolHint hintAnnotation = AnnotationUtils.findAnnotation(method, GrowToolHint.class); + + if (functionAnnotation != null) { + String baseName = functionAnnotation.name(); + String rawSubToolName = functionAnnotation.name(); + String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() + ? mcpProperties.getNamespace() + "_" + rawSubToolName + : rawSubToolName; + // @GrowToolHint인 Tool은 메타데이터 조회에는 남기되, + // Gateway 등록 및 heartbeat 전송 대상에서는 제외합니다. + // ToolHint가 없는 기존 Tool은 이전 동작과 동일하게 등록합니다. + boolean isRegister = hintAnnotation == null || hintAnnotation.register(); + if (!isRegister) { + log.info(" [HeartbeatSender] '{}' Tool is excluded from Gateway registration because . (tool name: {})", + baseName, subToolName); + } + + ToolMetadata meta = new ToolMetadata(); + meta.setUid(UUID.nameUUIDFromBytes(subToolName.getBytes()).toString()); + String displayName = functionAnnotation.title().isEmpty() ? functionAnnotation.name() : functionAnnotation.title(); + meta.setDisplayName(displayName); + meta.setName(subToolName); + meta.setSemver("1.0.0"); + meta.setTimeoutMillis(5000L); + meta.setEnabled(true); + meta.setDescription(functionAnnotation.description()); + meta.setCategoryKey(hintAnnotation == null || hintAnnotation.categoryKey().isBlank() + ? "common" : hintAnnotation.categoryKey()); + meta.setIntegrationType("REST"); + meta.setMciServiceId(hintAnnotation != null ? hintAnnotation.mappingId() : ""); + meta.setPodUrl(podUrl); + meta.setModuleName(applicationName); + meta.setEndpoint(podUrl.replaceAll("/+$", "") + "/mcp/" + subToolName); + + meta.setVisible(true); + meta.setIsRegistered(isRegister); + meta.setRequiresApproval(hintAnnotation != null && hintAnnotation.requiresApproval()); + + // extract standard hints from @McpTool.annotations() + McpTool.McpAnnotations ann = functionAnnotation.annotations(); + if (ann != null) { + meta.setReadOnlyHint(ann.readOnlyHint()); + meta.setDestructiveHint(ann.destructiveHint()); + meta.setIdempotentHint(ann.idempotentHint()); + meta.setOpenWorldHint(ann.openWorldHint()); + } else { + meta.setReadOnlyHint(false); + meta.setDestructiveHint(true); + meta.setIdempotentHint(false); + meta.setOpenWorldHint(true); + } + + Map prompts = new HashMap<>(); + meta.setActionPrompts(prompts); + + if (method.getParameterCount() > 0) { + try { + Class paramType = method.getParameterTypes()[0]; + // TODO: ToolSchemaResolver may need to be updated to take McpTool instead of McpFunction + Map finalSchema = toolSchemaResolver.resolve(functionAnnotation, hintAnnotation, paramType); + meta.setParametersSchema(finalSchema); + Map outputSchema = toolSchemaResolver.resolveOutput( + functionAnnotation, method.getReturnType(), hintAnnotation); + if (!outputSchema.isEmpty()) { + meta.setOutputSchema(outputSchema); + } + } catch (Exception e) { + log.error("Failed to generate schema for {}", subToolName, e); + } + } + + enrichWithDefinition(meta, rawSubToolName, hintAnnotation); + + if (isRegister) { + registeredTools.add(meta); + } + allScannedTools.add(meta); + log.info(" [HeartbeatSender] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getUid(), isRegister); + } + } + } + } + + private void enrichWithDefinition(ToolMetadata meta, String rawToolName, GrowToolHint hintAnnotation) { + if (toolDefinitionRepository == null) { + return; + } + toolDefinitionRepository.findByName(rawToolName) + .ifPresent(definition -> applyDefinition(meta, definition, hintAnnotation)); + } + + private void applyDefinition(ToolMetadata meta, ToolDefinition definition, GrowToolHint hintAnnotation) { + meta.setDisplayName(definition.displayName()); + meta.setSemver(definition.version()); + meta.setCategoryKey(definition.categoryKey()); + meta.setFunctionDescription(definition.description().function()); + meta.setWhenToUse(definition.description().whenToUse()); + meta.setWhenNotToUse(definition.description().whenNotToUse()); + meta.setIoLimits(definition.description().ioLimits()); + meta.setDescription(String.join("\n", definition.description().function(), + "사용 시점: " + definition.description().whenToUse(), + "사용 제외: " + definition.description().whenNotToUse(), + "입출력 제한: " + definition.description().ioLimits())); + meta.setDisplayDescription(definition.displayDescription()); + meta.setExampleQueries(definition.exampleQueries()); + meta.setReadOnlyHint(definition.readOnly()); + meta.setDestructiveHint(definition.destructive()); + meta.setIdempotentHint(definition.idempotent()); + boolean explicitInputResource = hintAnnotation != null && !hintAnnotation.inputSchemaResource().isBlank(); + boolean explicitOutputResource = hintAnnotation != null && !hintAnnotation.outputSchemaResource().isBlank(); + if (!explicitInputResource) { + meta.setParametersSchema(definition.parametersSchema()); + } + if (!explicitOutputResource && definition.outputSchema() != null && !definition.outputSchema().isEmpty()) { + meta.setOutputSchema(definition.outputSchema()); + } + meta.setTags(definition.tags()); + meta.setMciServiceId(definition.legacyInterfaceId()); + meta.setRequiredEnvKeys(definition.requiredEnvKeys()); + meta.setOwnerOrg(definition.ownerOrg()); + } + + @EventListener(ApplicationReadyEvent.class) + public void registerAllTools() { + if (registeredTools.isEmpty()) return; + + new Thread(() -> { + for (ToolMetadata tool : registeredTools) { + registerTool(tool); + } + }, "McpToolRegistrationThread").start(); + } + + @PreDestroy + public void deregisterAllTools() { + if (registeredTools.isEmpty()) return; + + for (ToolMetadata tool : registeredTools) { + try { + restClient.post() + .uri(gatewayUrl + "/mcp/api/v1/registry/deregister") + .header("Content-Type", "application/json") + .body(tool.getUid()) + .retrieve() + .toBodilessEntity(); + log.info(" [HeartbeatSender] 툴 삭제(Deregister) 성공: {}", tool.getUid()); + } catch (Exception ex) { + log.warn(" [HeartbeatSender] 툴 삭제 실패: {}", ex.getMessage()); + } + } + } + + private void registerTool(ToolMetadata tool) { + int maxRetries = 12; + int delayMs = 10000; + for (int i = 0; i < maxRetries; i++) { + try { + restClient.post() + .uri(gatewayUrl + "/mcp/api/v1/registry/register") + .header("Content-Type", "application/json") + .body(tool) + .retrieve() + .toBodilessEntity(); + log.info(" [HeartbeatSender] 툴 등록 성공: {}", tool.getUid()); + return; + } catch (Exception ex) { + if (i == maxRetries - 1) { + log.error(" [HeartbeatSender] 툴 등록 최종 실패 ({}회 재시도): {}", maxRetries, ex.getMessage()); + } else { + log.warn(" [HeartbeatSender] 툴 등록 실패, {}초 후 재시도... ({}/{}): {}", delayMs/1000, i+1, maxRetries, ex.getMessage()); + try { + Thread.sleep(delayMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java new file mode 100644 index 000000000..9a313bf70 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className CacheConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +@EnableCaching +public class CacheConfig { + + // 스프링이 캐시를 관리할 기본 저장소를 빈(Bean)으로 등록합니다. + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("eimsData"); // 아까 설정한 캐시 이름 등록 + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java new file mode 100644 index 000000000..1df9b21f1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java @@ -0,0 +1,41 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className JacksonConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +public class JacksonConfig { + + // 1. JSON 변환기(ObjectMapper)를 스프링 Bean으로 등록 + @Bean + @Primary + public ObjectMapper jsonMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + // 2. XML 변환기(XmlMapper)를 스프링 Bean으로 등록 + @Bean + public XmlMapper xmlMapper() { + return new XmlMapper(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java new file mode 100644 index 000000000..f9bda4e0e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +import org.springframework.beans.factory.annotation.Value; +import java.util.HashMap; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className KafkaLocalConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +public class KafkaLocalConfig { + + @Value("${spring.kafka.bootstrap-servers:localhost:9092}") + private String bootstrapServers; + + // 1. 카프카 전송 공장(Factory) 세팅 + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap<>(); + // 가짜 로컬 주소 혹은 환경변수 세팅 + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + // 데이터를 카프카로 보낼 때 문자열(String) 형태로 변환하겠다는 규칙 + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + + // 3초 만에 빠른 실패 처리 (로컬 무한 대기 방지) + configProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 3000); + // 재접속 주기를 10초로 설정 (콘솔 로그 도배 방지) + configProps.put(ProducerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG, 10000); + + return new DefaultKafkaProducerFactory<>(configProps); + } + + // 2. EaiEimsSender가 애타게 찾던 KafkaTemplate을 스프링 Bean으로 등록! + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java new file mode 100644 index 000000000..5c2e22c96 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java @@ -0,0 +1,47 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className SwaggerConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Shinhan MCP Gateway API 명세서") + .version("v1.0") + .description("AI Agent와 신한라이프 내부망(EIMS/EAI)을 연결하는 Adapter Gateway API 문서입니다.")) + .addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8080").description("Adapter Pod (8080)")) + .addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8081").description("Gateway Pod (8081)")) + // 전역적으로 X-API-KEY 보안 설정을 Swagger UI에 추가합니다. + .addSecurityItem(new SecurityRequirement().addList("X-API-KEY")) + .components(new Components() + .addSecuritySchemes("X-API-KEY", + new SecurityScheme() + .name("X-API-KEY") + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .description("헤더에 API Key를 입력해주세요. "))); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java new file mode 100644 index 000000000..9e3cfc8b4 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java @@ -0,0 +1,54 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.CorsRegistry; + +import io.shinhanlife.dap.lib.mcp.security.ApiKeyInterceptor; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className WebConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + // 1. 우리가 만든 인터셉터를 주입받습니다. + private final ApiKeyInterceptor apiKeyInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 2. 인터셉터 등록 및 검사할 URL 패턴 지정 + registry.addInterceptor(apiKeyInterceptor) + .addPathPatterns("/rpc/**", "/mcp/api/v1/**") // /rpc/, /mcp/api/v1/ 로 시작하는 모든 API는 API Key 검사 수행! + .excludePathPatterns( + "/test/**", "/health", "/error", "/mcp/api/v1/admin/**", + "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", // Swagger UI 경로는 인증 제외 + "/mcp/api/v1/tools/docs/markdown", "/favicon.ico", "/mcp/api/v1/tools/list" + ); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + // Swagger UI(8080)에서 Gateway(8081)로 API 호출 시 발생하는 CORS 에러 해결 + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("Mcp-Session-Id") + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..6523c6331 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java @@ -0,0 +1,66 @@ +package io.shinhanlife.dap.lib.mcp.exception; + + +/** + * @package io.shinhanlife.dap.lib.mcp.exception + * @className GlobalExceptionHandler + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import io.shinhanlife.dap.lib.adapter.dto.ErrorDetail; +import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@Slf4j +@RestControllerAdvice // 이 어노테이션이 전역 적용의 핵심입니다! +public class GlobalExceptionHandler { + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFound(NoResourceFoundException e) { + log.warn(" [Gateway Not Found] 요청하신 리소스를 찾을 수 없습니다: {}", e.getResourcePath()); + return ResponseEntity.notFound().build(); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + log.warn(" [Gateway Bad Request] 잘못된 요청: {}", e.getMessage()); + return buildErrorResponse(-32602, "Invalid params: " + e.getMessage()); + } + + @ExceptionHandler(org.springframework.web.context.request.async.AsyncRequestNotUsableException.class) + public ResponseEntity handleAsyncRequestNotUsable(org.springframework.web.context.request.async.AsyncRequestNotUsableException e) { + log.debug(" [Gateway Info] 클라이언트가 비동기 연결을 종료했습니다 (정상): {}", e.getMessage()); + return ResponseEntity.ok().build(); + } + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntime(RuntimeException e) { + log.error(" [Gateway Internal Error] 시스템 장애: {}", e.getMessage(), e); + return buildErrorResponse(-32603, "Internal error: " + e.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllException(Exception e) { + log.error(" [Gateway Fatal Error] 치명적 오류 발생", e); + return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요."); + } + + private ResponseEntity buildErrorResponse(int code, String message) { + JsonRpcResponse response = new JsonRpcResponse(); + response.setError(new ErrorDetail(code, message)); + + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java new file mode 100644 index 000000000..69a76ff18 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.lib.mcp.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.UUID; + +/** + * @package io.shinhanlife.dap.lib.mcp.filter + * @className MdcLoggingFilter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Component +public class MdcLoggingFilter extends OncePerRequestFilter { + + private static final String TRACE_ID_HEADER = "X-Trace-Id"; + private static final String MDC_KEY = "traceId"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // 클라이언트가 보낸 Trace ID가 있으면 쓰고, 없으면 새로 생성 + String traceId = request.getHeader(TRACE_ID_HEADER); + if (traceId == null || traceId.isEmpty()) { + // 간결하게 8자리 UUID만 사용 + traceId = UUID.randomUUID().toString().substring(0, 8); + } + + // 로깅 컨텍스트에 고유 ID 저장 + MDC.put(MDC_KEY, traceId); + + try { + // 이 요청이 처리되는 동안 찍히는 모든 log.info, log.error에 traceId가 자동으로 붙습니다. + filterChain.doFilter(request, response); + } finally { + // 메모리 누수 방지를 위해 요청이 끝나면 반드시 비워줍니다. + MDC.clear(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java new file mode 100644 index 000000000..89fb3db58 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java @@ -0,0 +1,80 @@ +package io.shinhanlife.dap.lib.mcp.security; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.mcp.security + * @className ApiKeyInterceptor + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ApiKeyInterceptor implements HandlerInterceptor { + + // 1. 다중 테넌트 API Key 목록이 담긴 프로퍼티 객체를 주입받습니다. + private final SecurityProperties securityProperties; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + + String apiKey = request.getHeader("X-API-KEY"); + Map validApiKeys = securityProperties.getApiKeys(); + + // 2. 만약 프로퍼티에 API Key가 하나도 설정되어 있지 않다면 (개발/로컬 환경 등) 인증 없이 통과시킵니다. + if (validApiKeys == null || validApiKeys.isEmpty()) { + MDC.put("tenantId", "anonymous"); + request.setAttribute("tenantId", "anonymous"); + log.debug(" [보안 패스] 등록된 API Key 없음 - 익명 사용자(anonymous)로 통과"); + return true; + } + + // 3. 헤더로 들어온 API Key가 우리가 발급해준 목록(Map)에 존재하는지 확인합니다. + if (apiKey == null || !validApiKeys.containsKey(apiKey)) { + log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key"); + return false; // 컨트롤러로 넘어가지 않음 + } + + // 4. 유효하다면 해당 키에 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1) + String tenantId = validApiKeys.get(apiKey); + + // 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다. + // 이렇게 하면 이 요청이 끝날 때까지 찍히는 모든 로그에 어떤 테넌트가 호출했는지 자동으로 기록됩니다. + MDC.put("tenantId", tenantId); + + // 5. 필요시 컨트롤러 로직에서 사용할 수 있도록 Request 속성에도 담아줍니다. + request.setAttribute("tenantId", tenantId); + + log.debug(" [보안 통과] API Key 인증 성공 - 접속 테넌트: {}", tenantId); + + return true; // 인증 통과! 컨트롤러로 진행 + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + // 6. 메모리 누수를 방지하기 위해 요청 처리가 완전히 끝나면 MDC에서 테넌트 정보를 지워줍니다. + MDC.remove("tenantId"); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java new file mode 100644 index 000000000..381164e61 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.mcp.security; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * [다중 테넌트 설정 매핑 클래스] + * application-local.properties 파일에 정의된 mcp.security.api-keys.* 설정들을 + * Map 자료구조로 자동 바인딩(주입) 받기 위한 설정 클래스입니다. + */ +/** + * @package io.shinhanlife.dap.lib.mcp.security + * @className SecurityProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Data +@Component +@ConfigurationProperties(prefix = "mcp.security") +public class SecurityProperties { + // API Key를 Key로, Tenant ID를 Value로 가지는 맵 + private Map apiKeys = new HashMap<>(); + + // Tenant ID를 Key로, 허용된 도메인 그룹 목록을 Value로 가지는 맵 (ex. mcp-client-1 -> [CUSTOMER, COMMON]) + // 만약 "ALL" 이 포함되어 있다면 모든 도메인에 접근 허용 + private Map> tenantDomains = new HashMap<>(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinition.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinition.java new file mode 100644 index 000000000..f5233e1fe --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinition.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.metadata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +/** BC-DAB-STD-003 V17 Tool 정의 파일의 불변 모델입니다. */ +public record ToolDefinition( + String name, + @JsonProperty("display_name") String displayName, + String version, + @JsonProperty("category_key") String categoryKey, + ToolDescription description, + @JsonProperty("display_description") String displayDescription, + @JsonProperty("example_queries") List exampleQueries, + @JsonProperty("read_only") Boolean readOnly, + Boolean destructive, + Boolean idempotent, + @JsonProperty("parameters_schema") Map parametersSchema, + @JsonProperty("output_schema") Map outputSchema, + List tags, + @JsonProperty("legacy_interface_id") String legacyInterfaceId, + @JsonProperty("required_env_keys") List requiredEnvKeys, + @JsonProperty("owner_org") String ownerOrg) { + + public ToolDefinition withExampleQueries(List queries) { + return new ToolDefinition(name, displayName, version, categoryKey, description, displayDescription, + queries, readOnly, destructive, idempotent, parametersSchema, outputSchema, tags, legacyInterfaceId, + requiredEnvKeys, ownerOrg); + } + + public ToolDefinition withName(String value) { + return new ToolDefinition(value, displayName, version, categoryKey, description, displayDescription, + exampleQueries, readOnly, destructive, idempotent, parametersSchema, outputSchema, tags, legacyInterfaceId, + requiredEnvKeys, ownerOrg); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepository.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepository.java new file mode 100644 index 000000000..d7b42d3fa --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepository.java @@ -0,0 +1,62 @@ +package io.shinhanlife.dap.lib.metadata; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.stereotype.Component; + +/** classpath의 Tool 정의를 기동 시 한 번 읽어 name 기준으로 캐시합니다. */ +@Component +public class ToolDefinitionRepository { + public static final String DEFAULT_LOCATION = "classpath*:tool-definitions/**/*.yml"; + + private final Map definitions; + + @Autowired + public ToolDefinitionRepository(ResourceLoader resourceLoader) { + this(new ObjectMapper(new YAMLFactory()), resourceLoader, DEFAULT_LOCATION); + } + + public ToolDefinitionRepository(ObjectMapper yamlMapper, ResourceLoader resourceLoader, String location) { + this(yamlMapper, new PathMatchingResourcePatternResolver(resourceLoader), location); + } + + private ToolDefinitionRepository(ObjectMapper yamlMapper, ResourcePatternResolver resolver, String location) { + this.definitions = Collections.unmodifiableMap(load(yamlMapper, resolver, location)); + } + + public Optional findByName(String name) { + return Optional.ofNullable(definitions.get(name)); + } + + public Map findAll() { + return definitions; + } + + private Map load(ObjectMapper mapper, ResourcePatternResolver resolver, String location) { + Map loaded = new LinkedHashMap<>(); + try { + for (Resource resource : resolver.getResources(location)) { + ToolDefinition definition = mapper.readValue(resource.getInputStream(), ToolDefinition.class); + String source = resource.getDescription(); + ToolDefinitionValidator.validate(definition, source); + ToolDefinition previous = loaded.putIfAbsent(definition.name(), definition); + if (previous != null) { + throw new IllegalStateException("Duplicate Tool definition name: " + definition.name()); + } + } + return loaded; + } catch (IOException exception) { + throw new IllegalStateException("Failed to load Tool definitions from " + location, exception); + } + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionValidator.java new file mode 100644 index 000000000..6137ac1ea --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionValidator.java @@ -0,0 +1,84 @@ +package io.shinhanlife.dap.lib.metadata; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** Tool 정의가 BC-DAB-STD-003 V17 필수 규칙을 만족하는지 검증합니다. */ +public final class ToolDefinitionValidator { + private static final Pattern TOOL_NAME = Pattern.compile("^[a-z][a-z0-9_]{2,63}$"); + + private ToolDefinitionValidator() { + } + + public static void validate(ToolDefinition definition, String source) { + if (definition == null) { + fail(source, "definition", "문서가 비어 있습니다"); + } + if (isBlank(definition.name()) || !TOOL_NAME.matcher(definition.name()).matches()) { + fail(source, "name", "^[a-z][a-z0-9_]{2,63}$ 형식이어야 합니다"); + } + requireText(source, "display_name", definition.displayName()); + requireText(source, "version", definition.version()); + requireText(source, "category_key", definition.categoryKey()); + requireText(source, "display_description", definition.displayDescription()); + if (definition.description() == null) { + fail(source, "description", "필수입니다"); + } + requireText(source, "description.function", definition.description().function()); + requireText(source, "description.when_to_use", definition.description().whenToUse()); + requireText(source, "description.when_not_to_use", definition.description().whenNotToUse()); + requireText(source, "description.io_limits", definition.description().ioLimits()); + List examples = definition.exampleQueries(); + if (examples == null || examples.size() < 3 || examples.size() > 10 + || examples.stream().anyMatch(ToolDefinitionValidator::isBlank)) { + fail(source, "example_queries", "비어 있지 않은 자연어 질의가 3~10개 필요합니다"); + } + if (examples.stream().anyMatch(query -> query.contains(definition.name()))) { + fail(source, "example_queries", "Tool name을 직접 포함할 수 없습니다"); + } + if (definition.readOnly() == null || definition.destructive() == null || definition.idempotent() == null) { + fail(source, "annotations", "read_only, destructive, idempotent는 필수입니다"); + } + validateSchema(definition.parametersSchema(), source); + if (definition.outputSchema() != null && !definition.outputSchema().isEmpty()) { + validateSchema(definition.outputSchema(), source + " output_schema"); + } + } + + @SuppressWarnings("unchecked") + private static void validateSchema(Map schema, String source) { + if (schema == null || !"object".equals(schema.get("type"))) { + fail(source, "parameters_schema.type", "object여야 합니다"); + } + if (!Boolean.FALSE.equals(schema.get("additionalProperties"))) { + fail(source, "parameters_schema.additionalProperties", "false여야 합니다"); + } + Object propertiesValue = schema.get("properties"); + if (!(propertiesValue instanceof Map)) { + fail(source, "parameters_schema.properties", "object여야 합니다"); + } + Map properties = (Map) propertiesValue; + for (Map.Entry entry : properties.entrySet()) { + if (!(entry.getValue() instanceof Map property) + || isBlank(String.valueOf(property.containsKey("description") + ? property.get("description") : ""))) { + fail(source, "parameters_schema.properties." + entry.getKey() + ".description", "필수입니다"); + } + } + } + + private static void requireText(String source, String field, String value) { + if (isBlank(value)) { + fail(source, field, "필수입니다"); + } + } + + private static boolean isBlank(String value) { + return value == null || value.isBlank(); + } + + private static void fail(String source, String field, String message) { + throw new IllegalStateException("Invalid Tool definition [" + source + "] " + field + ": " + message); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDescription.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDescription.java new file mode 100644 index 000000000..5fb841832 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/metadata/ToolDescription.java @@ -0,0 +1,11 @@ +package io.shinhanlife.dap.lib.metadata; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** LLM이 Tool 선택 여부를 판단할 때 사용하는 V17 목적 설명입니다. */ +public record ToolDescription( + String function, + @JsonProperty("when_to_use") String whenToUse, + @JsonProperty("when_not_to_use") String whenNotToUse, + @JsonProperty("io_limits") String ioLimits) { +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java new file mode 100644 index 000000000..f65211746 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java @@ -0,0 +1,90 @@ +package io.shinhanlife.dap.lib.session.dto; + +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.session.dto + * @className SessionDto + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SessionDto { + + /* 인사번호 */ + private String prafNo; + /* 인사명 */ + private String prafNm; + /* 조직번호 */ + private String ognzNo; + /* 조직번호 */ + private String ognzNm; + /* 이메일주소 */ + private String addre; + /* 인사직무코드 */ + private String prafOfduCd; + /* 인사직무명 */ + private String prafOfduNm; + /* 인사직급코드 */ + private String prafOfleCd; + /* 인사직급명 */ + private String prafOfleNm; + /* 인사직책코드 */ + private String prafDutyCd; + /* 인사직책명 */ + private String prafDutyNm; + + private List roleNoList; + private List roleNmList; + private List tgtrPrafNoList; + private List tgtrOgnzNoList; + + // 추가된 LICO 연동 공통 헤더 필수 필드들 + private String strYmd; + private String brafNo; + private String psmrAsrtCd; + private String sbsnRulpAsrtCd; + private String bsduCd; + private String bsquCd; + private String ognzAsrtCd; + private String ognzLeveCd; + private String prgrId; + + + // 유틸성 + private String loginDtm; // 로그인일시 + private String isManager; // 관리자여부 + + public void setLoginDtm() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + this.loginDtm = LocalDateTime.now().format(formatter); + } + + public void setIsManager(String isManager) { + // TODO 역할 필터링 후 관리자 여부 체크 + this.isManager = "Y"; + } + + + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java new file mode 100644 index 000000000..626bbdf2a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java @@ -0,0 +1,205 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import org.springaicommunity.mcp.annotation.McpToolParam; +import io.swagger.v3.oas.annotations.media.Schema; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * @package io.shinhanlife.dap.lib.util + * @className JsonSchemaGenerator + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class JsonSchemaGenerator { + + /** + * Java DTO 클래스를 분석하여 MCP 규격의 완전한 JSON Schema를 생성합니다. + */ + public static Map generateSchema(Class clazz) { + return generateSchema(clazz, new HashSet<>()); + } + + private static Map generateSchema(Class clazz, Set> visiting) { + Map schema = new HashMap<>(); + schema.put("type", "object"); + schema.put("additionalProperties", false); + if (!visiting.add(clazz)) { + return schema; + } + + Map properties = new HashMap<>(); + List requiredList = new ArrayList<>(); + + for (Field field : clazz.getDeclaredFields()) { + Map fieldSchema = createFieldSchema(field, visiting); + + // 1. 타입 매핑 + + // 2. 어노테이션 기반 설명 추출 + McpToolParam paramAnnotation = field.getAnnotation(McpToolParam.class); + JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class); + if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) { + fieldSchema.put("description", paramAnnotation.description()); + } else if (descAnnotation != null && !descAnnotation.value().isEmpty()) { + fieldSchema.put("description", descAnnotation.value()); + } else { + fieldSchema.put("description", field.getName()); // 기본값 + } + + // 3. 필수 여부 판단 + JsonProperty jsonProp = field.getAnnotation(JsonProperty.class); + if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) { + requiredList.add(field.getName()); + } + + Schema schemaAnnotation = field.getAnnotation(Schema.class); + if (schemaAnnotation != null) { + if (!schemaAnnotation.description().isEmpty() && !fieldSchema.containsKey("description")) { + fieldSchema.put("description", schemaAnnotation.description()); + } + if ((schemaAnnotation.required() || schemaAnnotation.requiredMode() == Schema.RequiredMode.REQUIRED) + && !requiredList.contains(field.getName())) { + requiredList.add(field.getName()); + } + if (!schemaAnnotation.pattern().isEmpty()) { + fieldSchema.put("pattern", schemaAnnotation.pattern()); + } + if (!schemaAnnotation.minimum().isEmpty()) { + try { + fieldSchema.put("minimum", Long.valueOf(schemaAnnotation.minimum())); + } catch (NumberFormatException ignored) {} + } + if (!schemaAnnotation.maximum().isEmpty()) { + try { + fieldSchema.put("maximum", Long.valueOf(schemaAnnotation.maximum())); + } catch (NumberFormatException ignored) {} + } + if (schemaAnnotation.minLength() > 0) { + fieldSchema.put("minLength", schemaAnnotation.minLength()); + } + if (schemaAnnotation.maxLength() > 0 && schemaAnnotation.maxLength() != Integer.MAX_VALUE) { + fieldSchema.put("maxLength", schemaAnnotation.maxLength()); + } + if (schemaAnnotation.allowableValues().length > 0 && !schemaAnnotation.allowableValues()[0].isEmpty()) { + fieldSchema.put("enum", List.of(schemaAnnotation.allowableValues())); + } + if (!schemaAnnotation.format().isEmpty()) { + fieldSchema.put("format", schemaAnnotation.format()); + } + if (!schemaAnnotation.defaultValue().isEmpty()) { + fieldSchema.put("default", coerceDefaultValue(schemaAnnotation.defaultValue(), field.getType())); + } + if (!schemaAnnotation.example().isEmpty()) { + fieldSchema.put("examples", List.of(schemaAnnotation.example())); + } + if (schemaAnnotation.nullable()) { + Map nonNullSchema = new HashMap<>(fieldSchema); + fieldSchema = new HashMap<>(); + fieldSchema.put("anyOf", List.of( + nonNullSchema, + Map.of("type", "null") + )); + } + } + properties.put(field.getName(), fieldSchema); + } + + schema.put("properties", properties); + if (!requiredList.isEmpty()) { + schema.put("required", requiredList); + } + + // anyOf removed + + visiting.remove(clazz); + return schema; + } + + + private static Object coerceDefaultValue(String value, Class fieldType) { + try { + if (fieldType == Integer.class || fieldType == int.class + || fieldType == Long.class || fieldType == long.class + || fieldType == Short.class || fieldType == short.class + || fieldType == Byte.class || fieldType == byte.class) { + return Long.valueOf(value); + } + if (fieldType == Double.class || fieldType == double.class + || fieldType == Float.class || fieldType == float.class) { + return Double.valueOf(value); + } + if (fieldType == Boolean.class || fieldType == boolean.class) { + return Boolean.valueOf(value); + } + return value; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid MCP default value: " + value, e); + } + } + private static Map createFieldSchema(Field field, Set> visiting) { + Class fieldType = field.getType(); + if (isSimpleType(fieldType)) { + return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(fieldType))); + } + if (List.class.isAssignableFrom(fieldType)) { + Map fieldSchema = new HashMap<>(); + fieldSchema.put("type", "array"); + fieldSchema.put("items", generateItemsSchema(field, visiting)); + return fieldSchema; + } + return generateSchema(fieldType, visiting); + } + + private static Map generateItemsSchema(Field field, Set> visiting) { + Type genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType parameterizedType) { + Type itemType = parameterizedType.getActualTypeArguments()[0]; + if (itemType instanceof Class itemClass) { + if (isSimpleType(itemClass)) { + return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(itemClass))); + } + return generateSchema(itemClass, visiting); + } + } + return new HashMap<>(Map.of("type", "object")); + } + + private static boolean isSimpleType(Class clazz) { + return clazz == String.class + || clazz == Integer.class || clazz == int.class + || clazz == Long.class || clazz == long.class + || clazz == Double.class || clazz == double.class + || clazz == Float.class || clazz == float.class + || clazz == Boolean.class || clazz == boolean.class; + } + + private static String mapJavaTypeToJsonType(Class clazz) { + if (clazz == String.class) return "string"; + if (clazz == Integer.class || clazz == int.class) return "integer"; + if (clazz == Long.class || clazz == long.class) return "integer"; + if (clazz == Double.class || clazz == double.class) return "number"; + if (clazz == Float.class || clazz == float.class) return "number"; + if (clazz == Boolean.class || clazz == boolean.class) return "boolean"; + if (List.class.isAssignableFrom(clazz)) return "array"; + return "object"; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java new file mode 100644 index 000000000..7974277d2 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java @@ -0,0 +1,372 @@ +package io.shinhanlife.dap.lib.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Scanner; + +public class PodScaffolder { + + public static void main(String[] args) throws IOException { + Scanner scanner = new Scanner(System.in); + + System.out.println("========================================="); + System.out.println(" MCP Tool Pod Scaffolder (Java CLI) "); + System.out.println("=========================================\n"); + + String rawModuleName = getOrAsk(args, 0, scanner, "1. 생성할 모듈(Pod) 이름 (예: payment 또는 dap-was-payment): "); + String moduleName = rawModuleName.startsWith("dap-was-") ? rawModuleName : "dap-was-" + rawModuleName; + String portStr = getOrAsk(args, 1, scanner, "2. 사용할 포트 번호 (예: 8085): "); + String shortName = moduleName.replace("dap-was-", "").replace("-", ""); + + String defaultAuthor = System.getProperty("user.name"); + String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + + String author = getOrAsk(args, 2, scanner, "3. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): "); + if (author.trim().isEmpty()) author = defaultAuthor; + String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): "); + if (createDate.trim().isEmpty()) createDate = defaultDate; + + String result = scaffoldPod(moduleName, portStr, shortName, author, createDate); + System.out.println(result); + } + + private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) { + if (args.length > index) { + return args[index]; + } + System.out.print(prompt); + return scanner.nextLine().trim(); + } + + public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException { + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); + return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate); + } + + static String scaffoldPod(Path rootDir, String moduleName, String portStr, String shortName, + String author, String createDate) throws IOException { + Path modulePath = rootDir.resolve(Paths.get(moduleName)); + if (Files.exists(modulePath)) { + return "[오류] 이미 존재하는 모듈입니다: " + moduleName; + } + + StringBuilder log = new StringBuilder(); + log.append("[1/6] 모듈 디렉터리 생성 중...\n"); + Files.createDirectories(modulePath); + + log.append("[2/6] build.gradle 생성 중...\n"); + String buildGradle = """ + plugins { + // Spring Boot 3.5.11 version is managed by the root build.gradle. + id 'org.springframework.boot' + } + + dependencies { + // Shared MCP, Glow integration, validation, logging, Lombok and MapStruct configuration. + implementation project(':dap-was-lib') + } + """; + writeUtf8(modulePath.resolve("build.gradle"), buildGradle); + + log.append("[3/6] Dockerfile 생성 중...\n"); + String dockerfile = """ + FROM eclipse-temurin:21-jre-alpine + WORKDIR /app + RUN apk add --no-cache tzdata + ENV TZ=Asia/Seoul + COPY %s/build/libs/*-SNAPSHOT.jar app.jar + EXPOSE %s + ENTRYPOINT ["java", "-jar", "app.jar"] + """.formatted(moduleName, portStr); + writeUtf8(modulePath.resolve("Dockerfile"), dockerfile); + + log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n"); + Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName); + Files.createDirectories(srcPath); + + String appClass = """ + package io.shinhanlife.dap.mcc.%s; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + import org.springframework.cache.annotation.EnableCaching; + + /** + * @package io.shinhanlife.dap.mcc.%s + * @className DapWas%sApplication + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+             * ---------- 개정이력 ----------
+             * 수정일      수정자    수정내용
+             * ---------- -------- ---------------------------
+             * %s  %s    최초생성
+             * 
+             * 
+ */ + @SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) + @ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) + @EnableCaching + public class DapWas%sApplication { + public static void main(String[] args) { + SpringApplication.run(DapWas%sApplication.class, args); + } + } + """.formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName)); + writeUtf8(srcPath.resolve("DapWas" + capitalize(shortName) + "Application.java"), appClass); + + Path resPath = modulePath.resolve("src/main/resources"); + Files.createDirectories(resPath); + String applicationYml = """ + server: + port: %s + spring: + application: + name: %s + profiles: + active: local + logging: + level: + org.apache.kafka: ERROR + mcp: + namespace: "" + manifest: + bundle-id: %s + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL + """.formatted(portStr, moduleName, moduleName.replace("dap-", "")); + writeUtf8(resPath.resolve("application.yml"), applicationYml); + + String applicationLocalYml = """ + # Local 환경 전용 설정 (H2 메모리 DB 등) + spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + + eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + + mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + + axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} + """; + writeUtf8(resPath.resolve("application-local.yml"), applicationLocalYml); + + String applicationDevYml = """ + # OCI 클라우드 환경 전용 설정 + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml + + axhub: + gateway: + url: https://axhubmcp.devjun.net + tool: + url: http://144.24.70.100:%s + + eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + + shinhan: + integration: + envrTypeCd: D + eai: + url: http://10.176.32.181 + internalMci: + url: http://10.176.32.173 + bancaMci: + url: http://10.176.32.117 + externalMci: + url: http://10.176.32.176 + """.formatted(portStr, portStr); + writeUtf8(resPath.resolve("application-dev.yml"), applicationDevYml); + + String applicationTestYml = """ + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + + axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} + """.formatted(portStr); + writeUtf8(resPath.resolve("application-test.yml"), applicationTestYml); + + String applicationProdYml = """ + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + + axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} + """.formatted(portStr); + writeUtf8(resPath.resolve("application-prod.yml"), applicationProdYml); + + String logbackXml = """ + + + + + + ${LOG_PATTERN} + + + + logs/%s.log + + logs/%s-%%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN} + + + + + + + + + """.formatted(moduleName, moduleName); + writeUtf8(resPath.resolve("logback-spring.xml"), logbackXml); + + log.append("[5/6] settings.gradle 에 모듈 등록 중...\n"); + Path settingsPath = rootDir.resolve(Paths.get("settings.gradle")); + if (Files.exists(settingsPath)) { + String settings = Files.readString(settingsPath, StandardCharsets.UTF_8); + if (!settings.contains("include '" + moduleName + "'")) { + Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.APPEND); + } + } + + log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n"); + Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml")); + if (Files.exists(dockerComposePath)) { + String compose = Files.readString(dockerComposePath, StandardCharsets.UTF_8); + String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment + if (!compose.contains(" " + serviceName + ":")) { + String newService = """ + %s: + build: + context: . + dockerfile: %s/Dockerfile + ports: + - "%s:%s" + environment: + - TZ=Asia/Seoul + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_TOOL_URL=http://%s:%s + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + """.formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr); + Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardCharsets.UTF_8, StandardOpenOption.APPEND); + } + } + + log.append("\n=========================================\n"); + log.append(" Pod Scaffolding Complete! \n"); + log.append("=========================================\n"); + log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n"); + log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n"); + log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n"); + return log.toString(); + } + + private static void writeUtf8(Path path, String content) throws IOException { + Files.writeString(path, content, StandardCharsets.UTF_8); + } + + private static String capitalize(String str) { + if (str == null || str.isEmpty()) return str; + return str.substring(0, 1).toUpperCase() + str.substring(1); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java new file mode 100644 index 000000000..e155b9811 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.lib.util; + +import io.shinhanlife.dap.lib.session.dto.SessionDto; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * @package io.shinhanlife.dap.lib.util + * @className SessionUtil + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class SessionUtil { + + private static final String SESSION_KEY = "userInfo"; + + private SessionUtil() {} + + public static SessionDto getSession() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) return null; + HttpSession session = attributes.getRequest().getSession(false); + if (session == null) return null; + return (SessionDto) session.getAttribute(SESSION_KEY); + } + + public static String getPrafNo() { + SessionDto session = getSession(); + return session != null ? session.getPrafNo() : null; + } + + public static String getOgnzNo() { + SessionDto session = getSession(); + return session != null ? session.getOgnzNo() : null; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolAnnotationSyncRunner.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolAnnotationSyncRunner.java new file mode 100644 index 000000000..7f1e6f015 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolAnnotationSyncRunner.java @@ -0,0 +1,66 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import java.util.List; + +/** + * CI/CD 및 로컬 개발 환경에서 V17 Tool YAML 정의 파일을 읽어 Java @McpTool 어노테이션을 동기화합니다. + */ +public final class ToolAnnotationSyncRunner { + + private ToolAnnotationSyncRunner() { + } + + public static void main(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: ToolAnnotationSyncRunner "); + } + sync(Path.of(args[0])); + } + + static void sync(Path projectRoot) { + ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + int syncCount = 0; + try (Stream files = Files.walk(projectRoot)) { + List yamlFiles = files.filter(Files::isRegularFile) + .filter(path -> path.toString().replace('\\', '/').contains("/src/main/resources/tool-definitions/")) + .filter(path -> path.toString().endsWith(".yml") || path.toString().endsWith(".yaml")) + .toList(); + + for (Path file : yamlFiles) { + ToolDefinition definition = yamlMapper.readValue(file.toFile(), ToolDefinition.class); + + String toolName = definition.name(); + String title = definition.displayName(); + String description = definition.displayDescription(); + boolean readOnlyHint = Boolean.TRUE.equals(definition.readOnly()); + boolean destructiveHint = Boolean.TRUE.equals(definition.destructive()); + boolean idempotentHint = Boolean.TRUE.equals(definition.idempotent()); + String categoryKey = definition.categoryKey(); + + try { + boolean updated = ToolSourceUpdater.syncToolSource(projectRoot, toolName, title, description, + readOnlyHint, destructiveHint, idempotentHint, categoryKey); + if (updated) { + syncCount++; + System.out.println("Synced (Updated): " + toolName); + } else { + System.out.println("Synced (Unchanged): " + toolName); + } + } catch (IllegalArgumentException e) { + System.err.println("Skipped (Not Found): " + toolName); + } + } + } catch (IOException exception) { + throw new IllegalStateException("Failed to run tool annotation sync", exception); + } + System.out.println("Tool annotation sync completed: " + syncCount + " tools updated."); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java new file mode 100644 index 000000000..a8e97b41a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -0,0 +1,2029 @@ +package io.shinhanlife.dap.lib.util; + + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.Scanner; + +/** + * MCP Tool 코드를 자동 생성(Scaffolding)하는 유틸리티 클래스입니다. + * + * [실행 방법] + * 방법 1. IDE(IntelliJ 등)에서 직접 실행 + * - ToolScaffolder.java의 main 메서드를 실행합니다. + * - 콘솔 질문에 차례대로 값을 입력하면 파일이 생성됩니다. + * + * 방법 2. 명령줄에서 실행 + * - 컴파일: javac -encoding UTF-8 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java + * - 실행: java -cp dap-was-lib/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [이름] [ID] "[설명]" "[그룹]" "[통신방식]" "[모듈명]" + */ +/** + * @package io.shinhanlife.dap.lib.util + * @className ToolScaffolder + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일       수정자     수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +public class ToolScaffolder { + + private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc"; + private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc"; + + public record FieldDefinition(String name, String type, String description, List examples, String pattern, boolean required, + List enumValues, String itemType, List itemFields) { + public FieldDefinition(String name, String type, String description, List examples, String pattern, boolean required) { + this(name, type, description, examples, pattern, required, List.of(), null, List.of()); + } + } + + public record ToolMethodDefinition(String baseName, String methodName, String interfaceId, + String title, String description, String group, String routingType, + boolean register, String clientSystemCode, String httpApiName, + List inputFields, List outputFields, + ToolDefinitionOptions definitionOptions) { + } + + public record ToolDefinitionOptions( + String functionDescription, + String whenToUse, + String whenNotToUse, + String ioLimits, + String displayDescription, + List exampleQueries, + List tags, + String ownerOrg) { + } + + public static String scaffoldUseCase(String useCaseName, String moduleName, String author, + String createDate, List tools) throws IOException { + if (tools == null || tools.isEmpty()) { + throw new IllegalArgumentException("At least one Tool method is required."); + } + String useCaseBaseName = toPascalCase(useCaseName); + String group = tools.getFirst().group().toLowerCase(Locale.ROOT); + validateToolMethods(tools, group); + + String sourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = sourceDir == null ? Paths.get(".") : Paths.get(sourceDir); + Path configuredModule = Paths.get(moduleName); + Path moduleRoot = configuredModule.isAbsolute() ? configuredModule : rootDir.resolve(configuredModule); + Path sourceRoot = moduleRoot.resolve(BASE_PACKAGE_PATH); + Path useCaseDir = sourceRoot.resolve(Paths.get("biz", group, "usecase")); + Path implDir = useCaseDir.resolve("impl"); + Path dtoDir = sourceRoot.resolve(Paths.get("biz", group, "dto")); + Path converterDir = sourceRoot.resolve(Paths.get("biz", group, "converter")); + Path definitionDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "tool-definitions", group)); + Path mockDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "mock-responses")); + Files.createDirectories(useCaseDir); + Files.createDirectories(implDir); + Files.createDirectories(dtoDir); + Files.createDirectories(converterDir); + Files.createDirectories(definitionDir); + Files.createDirectories(mockDir); + + String bizPackage = BASE_PACKAGE + ".biz." + group; + Path useCaseFile = useCaseDir.resolve(useCaseBaseName + "UseCase.java"); + Path useCaseImplFile = implDir.resolve(useCaseBaseName + "UseCaseImpl.java"); + Path converterFile = converterDir.resolve(useCaseBaseName + "Converter.java"); + boolean existingUseCase = Files.exists(useCaseFile); + if (existingUseCase) { + appendGroupedUseCaseSources(useCaseFile, useCaseImplFile, converterFile, bizPackage, useCaseBaseName, + moduleName, tools); + } else { + writeUtf8(useCaseFile, groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools)); + writeUtf8(useCaseImplFile, groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools)); + writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools)); + } + + StringBuilder log = new StringBuilder("\n=========================================\n") + .append(" Multi Tool Scaffolding Complete\n") + .append("=========================================\n") + .append(existingUseCase ? " Existing UseCase Extended\n" : " New UseCase Created\n") + .append("[Usecase Interface] ").append(useCaseFile).append("\n") + .append("[Usecase Impl] ").append(useCaseImplFile).append("\n"); + for (ToolMethodDefinition tool : tools) { + writeGroupedToolFiles(moduleRoot, sourceRoot, dtoDir, definitionDir, mockDir, bizPackage, tool, moduleName, log); + if ("HTTP".equalsIgnoreCase(tool.routingType())) { + ensureLocalHttpApiConfiguration(moduleRoot, tool.httpApiName(), + toToolName(moduleName, tool.group(), toPascalCase(tool.baseName()))); + } + } + log.append("[Converter] ").append(converterFile).append("\n"); + return log.toString(); + } + + private static void validateToolMethods(List tools, String expectedGroup) { + Set methods = new LinkedHashSet<>(); + Set toolNames = new LinkedHashSet<>(); + for (ToolMethodDefinition tool : tools) { + if (tool == null || tool.baseName() == null || tool.baseName().isBlank() + || tool.methodName() == null || !tool.methodName().matches("^[a-zA-Z_$][a-zA-Z0-9_$]*$")) { + throw new IllegalArgumentException("Every Tool needs a valid base name and Java method name."); + } + if (!expectedGroup.equalsIgnoreCase(tool.group())) { + throw new IllegalArgumentException("All Tool methods in one UseCase must use the same category."); + } + boolean mci = "MCI".equalsIgnoreCase(tool.routingType()); + boolean http = "HTTP".equalsIgnoreCase(tool.routingType()); + if (!mci && !http) { + throw new IllegalArgumentException("Grouped Tool supports only MCI or HTTP routing."); + } + if (mci && (tool.interfaceId() == null || tool.interfaceId().isBlank() + || tool.clientSystemCode() == null || tool.clientSystemCode().isBlank())) { + throw new IllegalArgumentException("MCI Tool needs an interface ID and Client system code."); + } + if (http && (tool.httpApiName() == null || tool.httpApiName().isBlank())) { + throw new IllegalArgumentException("HTTP Tool needs an HTTP API name."); + } + String toolName = toToolName("", tool.group(), toPascalCase(tool.baseName())); + if (!methods.add(tool.methodName()) || !toolNames.add(toolName)) { + throw new IllegalArgumentException("Tool method names and MCP Tool names must be unique."); + } + } + } + + private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir, + Path mockDir, String bizPackage, ToolMethodDefinition tool, + String moduleName, StringBuilder log) throws IOException { + String baseName = toPascalCase(tool.baseName()); + boolean mci = "MCI".equalsIgnoreCase(tool.routingType()); + String code = mci ? tool.clientSystemCode().toLowerCase(Locale.ROOT) : toPackageSegment(tool.httpApiName()); + String ioPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." : ".infra.itrf.http.") + code; + Path clientDir = sourceRoot.resolve(Paths.get("infra", "itrf", mci ? "mci" : "http", code)); + Path ioDir = clientDir.resolve("io"); + Files.createDirectories(ioDir); + writeUtf8(dtoDir.resolve(baseName + "Request.java"), + dtoContent(bizPackage + ".dto", baseName + "Request", tool.inputFields(), "", "", true)); + writeUtf8(dtoDir.resolve(baseName + "Response.java"), + dtoContent(bizPackage + ".dto", baseName + "Response", tool.outputFields(), "", "", false)); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", tool.inputFields()); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", tool.outputFields()); + if (mci) { + writeUtf8(ioDir.resolve(baseName + "_I.java"), + mciIoContent("infra.itrf.mci." + code, baseName + "_I", tool.inputFields(), "", "")); + writeUtf8(ioDir.resolve(baseName + "_O.java"), + mciIoContent("infra.itrf.mci." + code, baseName + "_O", tool.outputFields(), "", "")); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_I", tool.inputFields()); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_O", tool.outputFields()); + writeUtf8(clientDir.resolve(baseName + "Client.java"), + groupedMciClientContent(ioPackage, baseName, tool.interfaceId())); + writeUtf8(sourceRoot.resolve(Paths.get("biz", tool.group().toLowerCase(Locale.ROOT), "converter", baseName + "Converter.java")), + groupedMciConverterContent(bizPackage, baseName, ioPackage)); + } else { + writeUtf8(ioDir.resolve(baseName + "HttpRequest.java"), + dtoContent(ioPackage + ".io", baseName + "HttpRequest", tool.inputFields(), "", "", true)); + writeUtf8(ioDir.resolve(baseName + "HttpResponse.java"), + dtoContent(ioPackage + ".io", baseName + "HttpResponse", tool.outputFields(), "", "", false)); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "HttpRequest", tool.inputFields()); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "HttpResponse", tool.outputFields()); + writeUtf8(clientDir.resolve(baseName + "Client.java"), + httpClientContent(ioPackage, baseName + "Client", tool.httpApiName())); + writeUtf8(sourceRoot.resolve(Paths.get("biz", tool.group().toLowerCase(Locale.ROOT), "converter", baseName + "Converter.java")), + groupedHttpConverterContent(bizPackage, baseName, ioPackage)); + } + + String toolName = toToolName(moduleName, tool.group(), baseName); + writeUtf8(definitionDir.resolve(toolName + ".yml"), toolDefinitionContentV17(toolName, + option(tool.title(), baseName), tool.description(), tool.group(), tool.interfaceId(), + tool.inputFields(), isMutationTool(baseName), tool.definitionOptions())); + writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields())); + log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n"); + } + + private static String groupedUseCaseContent(String bizPackage, String useCaseBaseName, String moduleName, + List tools) { + StringBuilder imports = new StringBuilder(); + StringBuilder methods = new StringBuilder(); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") + .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n"); + methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName)) + .append("\", title = \"").append(javaText(option(tool.title(), baseName))) + .append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n") + .append(" @GrowToolHint(register = ").append(tool.register()).append(", categoryKey = \"") + .append(tool.group().toLowerCase(Locale.ROOT)).append("\", mappingId = \"") + .append(javaText(tool.interfaceId())).append("\")\n") + .append(" ").append(baseName).append("Response ").append(tool.methodName()).append("(") + .append(baseName).append("Request req);\n\n"); + } + return "package " + bizPackage + ".usecase;\n\n" + + "import org.springaicommunity.mcp.annotation.McpTool;\n" + + "import io.shinhanlife.dap.lib.annotation.GrowToolHint;\n" + + imports + "\npublic interface " + useCaseBaseName + "UseCase {\n\n" + methods + "}\n"; + } + + private static String groupedUseCaseImplContent(String bizPackage, String useCaseBaseName, + List tools) { + StringBuilder imports = new StringBuilder(); + StringBuilder fields = new StringBuilder(); + StringBuilder methods = new StringBuilder(); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + String clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client"; + String converterVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Converter"; + boolean mci = "MCI".equalsIgnoreCase(tool.routingType()); + String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + tool.clientSystemCode().toLowerCase(Locale.ROOT) + : ".infra.itrf.http." + toPackageSegment(tool.httpApiName())); + imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") + .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n") + .append("import ").append(bizPackage).append(".converter.").append(baseName).append("Converter;\n") + .append("import ").append(integrationPackage).append(".").append(baseName).append("Client;\n") + .append("import ").append(integrationPackage).append(".io.").append(baseName) + .append(mci ? "_I;\n" : "HttpRequest;\n") + .append("import ").append(integrationPackage).append(".io.").append(baseName) + .append(mci ? "_O;\n" : "HttpResponse;\n"); + fields.append(" private final ").append(baseName).append("Client ").append(clientVariable).append(";\n"); + fields.append(" private final ").append(baseName).append("Converter ").append(converterVariable).append(";\n"); + methods.append(" @Override\n public ").append(baseName).append("Response ").append(tool.methodName()) + .append("(").append(baseName).append("Request req) {\n") + .append(" ").append(baseName).append(mci ? "_I" : "HttpRequest").append(" request = ").append(converterVariable).append(".toRequest(req);\n") + .append(" ").append(baseName).append(mci ? "_O" : "HttpResponse").append(" response = ").append(clientVariable).append(mci ? ".call" + baseName + "(request);\n" : ".call(request, " + baseName + "HttpResponse.class);\n") + .append(" ").append(baseName).append("Response toolResponse = ").append(converterVariable).append(".toResponse(response);\n") + .append(" if (toolResponse == null) toolResponse = new ").append(baseName).append("Response();\n") + .append(" toolResponse.setResultCode(\"SUCCESS\");\n") + .append(" return toolResponse;\n }\n\n"); + } + return "package " + bizPackage + ".usecase.impl;\n\n" + + "import " + bizPackage + ".usecase." + useCaseBaseName + "UseCase;\n" + + "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n" + imports + + "\n@Service\n@RequiredArgsConstructor\npublic class " + useCaseBaseName + "UseCaseImpl implements " + useCaseBaseName + "UseCase {\n\n" + + fields + "\n" + methods + "}\n"; + } + + private static String groupedConverterContent(String bizPackage, String useCaseBaseName, + List tools) { + return "package " + bizPackage + ".converter;\n\n/** Per-Tool converters are generated beside this compatibility marker. */\n" + + "public interface " + useCaseBaseName + "Converter {\n}\n"; + } + + private static String groupedMciConverterContent(String bizPackage, String baseName, String ioPackage) { + return "package " + bizPackage + ".converter;\n\n" + + "import " + bizPackage + ".dto." + baseName + "Request;\n" + + "import " + bizPackage + ".dto." + baseName + "Response;\n" + + "import " + ioPackage + ".io." + baseName + "_I;\n" + + "import " + ioPackage + ".io." + baseName + "_O;\n" + + "import org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n" + + "@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n" + + "public interface " + baseName + "Converter {\n" + + " " + baseName + "_I toRequest(" + baseName + "Request request);\n" + + " " + baseName + "Response toResponse(" + baseName + "_O response);\n}\n"; + } + + private static String groupedHttpConverterContent(String bizPackage, String baseName, String ioPackage) { + return "package " + bizPackage + ".converter;\n\n" + + "import " + bizPackage + ".dto." + baseName + "Request;\n" + + "import " + bizPackage + ".dto." + baseName + "Response;\n" + + "import " + ioPackage + ".io." + baseName + "HttpRequest;\n" + + "import " + ioPackage + ".io." + baseName + "HttpResponse;\n" + + "import org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n\n" + + "@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n" + + "public interface " + baseName + "Converter {\n" + + " " + baseName + "HttpRequest toRequest(" + baseName + "Request request);\n" + + " " + baseName + "Response toResponse(" + baseName + "HttpResponse response);\n}\n"; + } + + private static void appendGroupedUseCaseSources(Path useCaseFile, Path useCaseImplFile, Path converterFile, + String bizPackage, String useCaseBaseName, String moduleName, + List tools) throws IOException { + if (!Files.exists(useCaseImplFile)) { + throw new IllegalArgumentException("UseCase implementation not found: " + useCaseImplFile); + } + String useCase = Files.readString(useCaseFile, StandardCharsets.UTF_8); + String implementation = Files.readString(useCaseImplFile, StandardCharsets.UTF_8); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + String methodName = tool.methodName(); + String toolName = toToolName(moduleName, tool.group(), baseName); + if (useCase.matches("(?s).*\\b" + java.util.regex.Pattern.quote(methodName) + "\\s*\\(.*") + || useCase.contains("name = \"" + toolName + "\"")) { + throw new IllegalArgumentException("Tool method or MCP Tool name already exists: " + methodName); + } + boolean mci = "MCI".equalsIgnoreCase(tool.routingType()); + String integrationPackage = BASE_PACKAGE + (mci ? ".infra.itrf.mci." + tool.clientSystemCode().toLowerCase(Locale.ROOT) + : ".infra.itrf.http." + toPackageSegment(tool.httpApiName())); + String requestType = baseName + "Request"; + String responseType = baseName + "Response"; + String requestIo = baseName + (mci ? "_I" : "HttpRequest"); + String responseIo = baseName + (mci ? "_O" : "HttpResponse"); + String clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client"; + String converterVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Converter"; + + useCase = addImport(useCase, "import " + bizPackage + ".dto." + requestType + ";") ; + useCase = addImport(useCase, "import " + bizPackage + ".dto." + responseType + ";") ; + String declaration = "\n @McpTool(name = \"" + toolName + "\", title = \"" + javaText(option(tool.title(), baseName)) + + "\", description = \"" + javaText(option(tool.description(), "")) + "\")\n" + + " @GrowToolHint(register = " + tool.register() + ", categoryKey = \"" + tool.group().toLowerCase(Locale.ROOT) + + "\", mappingId = \"" + javaText(option(tool.interfaceId(), tool.httpApiName())) + "\")\n" + + " " + responseType + " " + methodName + "(" + requestType + " req);\n"; + useCase = insertBeforeLastBrace(useCase, declaration); + + implementation = addImport(implementation, "import " + bizPackage + ".dto." + requestType + ";"); + implementation = addImport(implementation, "import " + bizPackage + ".dto." + responseType + ";"); + implementation = addImport(implementation, "import " + bizPackage + ".converter." + baseName + "Converter;"); + implementation = addImport(implementation, "import " + integrationPackage + "." + baseName + "Client;"); + implementation = addImport(implementation, "import " + integrationPackage + ".io." + requestIo + ";"); + implementation = addImport(implementation, "import " + integrationPackage + ".io." + responseIo + ";"); + implementation = addImport(implementation, "import lombok.RequiredArgsConstructor;"); + if (!implementation.contains("@RequiredArgsConstructor")) { + implementation = implementation.replaceFirst("public class ", "@RequiredArgsConstructor\npublic class "); + } + implementation = insertConstructorField(implementation, " private final " + baseName + "Client " + clientVariable + ";"); + implementation = insertConstructorField(implementation, " private final " + baseName + "Converter " + converterVariable + ";"); + String call = mci + ? clientVariable + ".call" + baseName + "(request)" + : clientVariable + ".call(request, " + responseIo + ".class)"; + String method = "\n @Override\n public " + responseType + " " + methodName + "(" + requestType + " req) {\n" + + " " + requestIo + " request = " + converterVariable + ".toRequest(req);\n" + + " " + responseIo + " response = " + call + ";\n" + + " " + responseType + " toolResponse = " + converterVariable + ".toResponse(response);\n" + + " if (toolResponse == null) toolResponse = new " + responseType + "();\n" + + " toolResponse.setResultCode(\"SUCCESS\");\n" + + " return toolResponse;\n }\n"; + implementation = insertBeforeLastBrace(implementation, method); + } + writeUtf8(useCaseFile, useCase); + writeUtf8(useCaseImplFile, implementation); + if (!Files.exists(converterFile)) { + writeUtf8(converterFile, groupedConverterContent(bizPackage, useCaseBaseName, tools)); + } + } + + private static String addImport(String content, String importLine) { + if (content.contains(importLine)) return content; + int lastImport = content.lastIndexOf("import "); + if (lastImport < 0) { + int packageEnd = content.indexOf(';'); + return content.substring(0, packageEnd + 1) + "\n\n" + importLine + content.substring(packageEnd + 1); + } + int lineEnd = content.indexOf('\n', lastImport); + return content.substring(0, lineEnd + 1) + importLine + "\n" + content.substring(lineEnd + 1); + } + + private static String insertConstructorField(String content, String field) { + if (content.contains(field)) return content; + int constructorField = content.lastIndexOf("private final "); + if (constructorField < 0) { + int classDecl = content.indexOf("public class "); + if (classDecl >= 0) { + int brace = content.indexOf('{', classDecl); + if (brace >= 0) return content.substring(0, brace + 1) + "\n" + field + content.substring(brace + 1); + } + return insertBeforeLastBrace(content, "\n" + field + "\n"); + } + int lineEnd = content.indexOf('\n', constructorField); + if (lineEnd < 0) return content + "\n" + field; + return content.substring(0, lineEnd + 1) + field + "\n" + content.substring(lineEnd + 1); + } + + private static String insertBeforeLastBrace(String content, String addition) { + int brace = content.lastIndexOf('}'); + if (brace < 0) throw new IllegalArgumentException("Java source closing brace not found."); + return content.substring(0, brace) + addition + content.substring(brace); + } + + private static String groupedMciClientContent(String ioPackage, String baseName, String interfaceId) { + return "package " + ioPackage + ";\n\n" + + "import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;\n" + + "import io.shinhanlife.glow.communication.dto.Transfer;\n" + + "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n" + + "import " + ioPackage + ".io." + baseName + "_I;\n" + + "import " + ioPackage + ".io." + baseName + "_O;\n\n" + + "@Component\n@RequiredArgsConstructor\npublic class " + baseName + "Client {\n" + + " private final AxhubMciComponent mci;\n\n" + + " public " + baseName + "_O call" + baseName + "(" + baseName + "_I request) {\n" + + " try {\n" + + " Transfer<" + baseName + "_O> transfer = mci.callTo(\"" + javaText(interfaceId) + "\", null, request, " + baseName + "_O.class);\n" + + " return transfer.getBody();\n" + + " } catch (Exception e) {\n" + + " throw new IllegalStateException(\"MCI call failed: " + javaText(interfaceId) + "\", e);\n" + + " }\n }\n}\n"; + } + + private static String javaText(String value) { + return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", " ").replace("\n", " "); + } + + public static void main(String[] args) throws IOException { + Scanner scanner = new Scanner(System.in); + + System.out.println("========================================="); + System.out.println(" MCP Tool Scaffolder (Java CLI) "); + System.out.println("=========================================\n"); + + String baseName = getOrAsk(args, 0, scanner, "1. 생성할 Tool의 기본 이름 (예: ExchangeRate) [영문 PascalCase]: "); + String interfaceId = getOrAsk(args, 1, scanner, "2. 레거시 API 인터페이스 ID (예: EXCH_001): "); + String title = getOrAsk(args, 2, scanner, "3. Tool title: "); + String description = getOrAsk(args, 3, scanner, "4. Tool description for LLM: "); + String group = getOrAsk(args, 4, scanner, "5. Tool category: "); + if (group.isEmpty()) group = "COMMON"; + String routingType = getOrAsk(args, 5, scanner, "6. Routing type (HTTP, TCP, MCI, EAI): "); + if (routingType.trim().isEmpty()) { + routingType = "HTTP"; + } + String moduleName = getOrAsk(args, 6, scanner, "7. Target module (default dap-was-cus): "); + if (moduleName.trim().isEmpty()) { + moduleName = "dap-was-cus"; + } + + String defaultAuthor = System.getProperty("user.name"); + String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + + String author = getOrAsk(args, 7, scanner, "8. 작성자(Enter 입력 시 '" + defaultAuthor + "'): "); + if (author.trim().isEmpty()) author = defaultAuthor; + String createDate = getOrAsk(args, 8, scanner, "9. 작성일(Enter 입력 시 '" + defaultDate + "'): "); + if (createDate.trim().isEmpty()) createDate = defaultDate; + + String useSchemaResourceStr = getOrAsk(args, 9, scanner, "10. input/output JSON Schema 파일 자동 생성 여부 (y/N): "); + boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim()); + + String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/"; + String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null; + String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null; + + String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of()); + System.out.println(result); + } + + private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) { + if (args.length > index) { + return args[index]; + } + System.out.print(prompt); + return scanner.nextLine().trim(); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode) throws IOException { + return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, register, clientSystemCode, null, null); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource) throws IOException { + return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, + List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of()); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields) throws IOException { + return scaffold(baseName, interfaceId, description, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, inputFields, outputFields); + } + + /** + * Generates a Tool with a human-facing title and an LLM-facing description. + * Existing overloads keep their previous behavior by using the description as the title. + */ + public static String scaffold(String baseName, String interfaceId, String title, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields) throws IOException { + return scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, inputFields, outputFields, + toKebabCase(toPascalCase(baseName))); + } + + /** + * Generates a Tool using an HTTP API name that is resolved from glow.communication.http.api-list. + */ + public static String scaffold(String baseName, String interfaceId, String title, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields, String httpApiName) throws IOException { + return scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, inputFields, outputFields, + httpApiName, null); + } + + public static String scaffold(String baseName, String interfaceId, String title, String description, String group, + String routingType, String moduleName, String author, String createDate, + boolean register, String clientSystemCode, String inputSchemaResource, + String outputSchemaResource, List inputFields, + List outputFields, String httpApiName, + ToolDefinitionOptions definitionOptions) throws IOException { + baseName = toPascalCase(baseName); + title = title == null || title.isBlank() ? baseName : title.trim(); + description = description == null ? "" : description.trim(); + httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(baseName) : httpApiName.trim(); + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); + + Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase")); + Path usecaseImplDir = usecaseDir.resolve("impl"); + Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto")); + + Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy")); + Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter")); + + // Schema Resource 파일 경로(useSchemaResource=true일 때만 생성) + boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty()); + String schemaBaseName = toKebabCase(baseName); + String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json"; + String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json"; + Path schemaDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/tool-schemas", group.toLowerCase())); + Path definitionDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/tool-definitions", group.toLowerCase())); + String inputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + inputSchemaFileName; + String outputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + outputSchemaFileName; + + String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase(); + boolean isMci = "MCI".equalsIgnoreCase(routingType); + boolean isHttp = "HTTP".equalsIgnoreCase(routingType); + if (!isMci && !isHttp) { + throw new IllegalArgumentException("Unsupported routing type: " + routingType + ". Only MCI and HTTP are supported."); + } + String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase(); + String clientPrefixCap = ""; + Path mciClientDir = null; + + if (isMci && clientSystemCode != null && clientSystemCode.length() == 4) { + String clientPrefix = clientSystemCode.toLowerCase(); + clientPrefixCap = toPascalCase(clientSystemCode); + mciGroupPath = "infra/itrf/mci/" + clientPrefix; + mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath)); + } + + Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io")); + String httpApiPackage = toPackageSegment(httpApiName); + String httpApiClass = toPascalCase(httpApiName); + String httpGroupPath = "infra/itrf/http/" + httpApiPackage; + Path httpClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, httpGroupPath)); + Path httpIoDir = httpClientDir.resolve("io"); + + Files.createDirectories(usecaseDir); + Files.createDirectories(usecaseImplDir); + Files.createDirectories(dtoDir); + if (isMci) { + Files.createDirectories(mciIoDir); + if (mciClientDir != null) { + Files.createDirectories(mciClientDir); + } + } else if (isHttp) { + Files.createDirectories(httpIoDir); + } else { + Files.createDirectories(legacyDtoDir); + } + Files.createDirectories(converterDir); + + StringBuilder log = new StringBuilder(); + + // Generate Request DTO + String reqContent = """ + package %s.dto; + + import com.fasterxml.jackson.annotation.JsonInclude; + import lombok.Data; + + /** + * @package %s.dto + * @className %sRequest + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
+             * ---------- -------- ---------------------------
+             * %s  %s    최초생성
+             * 
+             * 
+ */ + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %sRequest { + @McpToolParam(description = "수신자 전화번호", required = true) + -(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"}) + private String phoneNumber; + + @McpToolParam(description = "전송할 메시지 내용", required = true) + private String message; + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + reqContent = reqContent + .replace("import com.fasterxml.jackson.annotation.JsonInclude;", + "import com.fasterxml.jackson.annotation.JsonInclude;\nimport io.swagger.v3.oas.annotations.media.Schema;") + .replaceAll("(?m)^\\s*@McpToolParam\\([^\\r\\n]*\\)\\R", "") + .replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "") + .replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;") + .replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;"); + reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true); + writeUtf8(dtoDir.resolve(baseName + "Request.java"), reqContent); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", inputFields); + + // Generate Response DTO + String resContent = """ + package %s.dto; + + import com.fasterxml.jackson.annotation.JsonInclude; + import lombok.Data; + + /** + * @package %s.dto + * @className %sResponse + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
+             * ---------- -------- ---------------------------
+             * %s  %s    최초생성
+             * 
+             * 
+ */ + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %sResponse { + private String resultCode; + + private String resultMessage; + + // TODO: Add response fields here. Do not include PII in the Tool response. + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false); + writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", outputFields); + + String toolName = toToolName(moduleName, group, baseName); + + String toolHintLine; + if (useSchemaResource) { + toolHintLine = (" @GrowToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\",\n" + + " inputSchemaResource = \"%s\",\n" + + " outputSchemaResource = \"%s\")").formatted(register, group.toLowerCase(Locale.ROOT), interfaceId, inputSchemaClasspath, outputSchemaClasspath); + } else { + toolHintLine = " @GrowToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\")".formatted(register, group.toLowerCase(Locale.ROOT), interfaceId); + } + + String serviceInterfaceContent = """ + package %s.usecase; + + import org.springaicommunity.mcp.annotation.McpTool; + import io.shinhanlife.dap.lib.annotation.GrowToolHint; + import %s.dto.%sRequest; + import %s.dto.%sResponse; + + /** + * @package %s.usecase + * @className %sUseCase + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+             * ---------- 개정이력 ----------
+             * 수정일       수정자     수정내용
+             * ---------- -------- ---------------------------
+             * %s  %s    최초생성
+             *
+             * 
+ */ + public interface %sUseCase { + + @McpTool(name = "%s", title = "%s", description = "%s") + %s + %sResponse execute(%sRequest req); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, author, createDate, createDate, author, + baseName, + toolName, title, description, + toolHintLine, + baseName, baseName + ); + + writeUtf8(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent); + + String serviceImplContent; + + if (isMci) { + serviceImplContent = """ + package %s.usecase.impl; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.usecase.%sUseCase; + import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; + import io.shinhanlife.glow.communication.dto.Transfer; + import org.springframework.stereotype.Service; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + import %s.converter.%sConverter; + import %s.%s.io.%s_I; + %s + + /** + * @package %s.usecase.impl + * @className %sUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Slf4j + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + %s + private final %sConverter converter; + + @Override + public %sResponse execute(%sRequest req) { + log.info("[MCI Tool] {} 요청 수신.", "%s"); + try { + // MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO) + %s_I mciReq = converter.toLegacyRequest(req); + + Transfer resTransfer = mci.callTo( + "%s", + null, + mciReq, + Object.class + ); + %sResponse response = new %sResponse(); + response.setResultCode("SUCCESS"); + response.setResultMessage(resTransfer.getBody() != null + ? "MCI call completed." + : "MCI call completed without a response body."); + return response; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + %sResponse response = new %sResponse(); + response.setResultCode("ERROR"); + response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error"); + return response; + } + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + (clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"), + bizPackage, + baseName, + author, + createDate, + createDate, author, + baseName, + baseName, + (clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"), + baseName, + baseName, + baseName, + toolName, + interfaceId, + interfaceId, + baseName, + baseName, + baseName, + baseName + ); + String mciIoPackage = BASE_PACKAGE + "." + mciGroupPath.replace("/", "."); + serviceImplContent = serviceImplContent + .replace("import " + mciIoPackage + ".io." + interfaceId + "_I;", + "import " + mciIoPackage + ".io." + interfaceId + "_I;\nimport " + mciIoPackage + ".io." + interfaceId + "_O;") + .replace("Transfer resTransfer", "Transfer<" + interfaceId + "_O> resTransfer") + .replace("Object.class", interfaceId + "_O.class") + .replace("response.setResultCode(\"SUCCESS\");", + "if (resTransfer.getBody() != null) {\n response = converter.toResponse(resTransfer.getBody());\n }\n response.setResultCode(\"SUCCESS\");"); + } else { + serviceImplContent = isHttp + ? httpUseCaseImplContent(bizPackage, baseName, httpGroupPath.replace("/", "."), httpApiClass, author, createDate) + : """ + package %s.usecase.impl; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.usecase.%sUseCase; + import %s.converter.%sConverter; + import org.springframework.stereotype.Service; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + + /** + * @package %s.usecase.impl + * @className %sUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Slf4j + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + private final %sConverter converter; + + @Override + public %sResponse execute(%sRequest req) { + // %sLegacyRequest legacyRequest = converter.toLegacyRequest(req); + Object legacyResponse = null; + if (legacyResponse instanceof %sResponse response) { + return response; + } + %sResponse response = new %sResponse(); + response.setResultCode("SUCCESS"); + response.setResultMessage("Legacy call completed."); + return response; + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + author, + createDate, + createDate, author, + baseName, baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + routingType, interfaceId + ); + } + + writeUtf8(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent); + + if (isMci) { + String mciReqContent = """ + package %s.%s.io; + + import lombok.Data; + + /** + * @package %s.%s.io + * @className %s_I + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Data + public class %s_I { + /** + * EAI 시스템이 요구하는 수신자 번호 파라미터명 + */ + private String phone; + + /** + * EAI 시스템이 요구하는 메시지 내용 파라미터명 + */ + private String content; + } + """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); + mciReqContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_I", inputFields, author, createDate); + writeUtf8(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent); + writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_I", inputFields); + + String mciResContent = """ + package %s.%s.io; + + import lombok.Data; + + /** + * @package %s.%s.io + * @className %s_O + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Data + public class %s_O { + // TODO: Add response fields here + } + """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); + mciResContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_O", outputFields, author, createDate); + writeUtf8(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent); + writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_O", outputFields); + + String converterContent = """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.io.%s_I; + import %s.%s.io.%s_O; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.factory.Mappers; + + /** + * @package %s.converter + * @className %sConverter + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Mapper(componentModel = "spring") + public interface %sConverter { + + @Mapping(source = "phoneNumber", target = "phone") + @Mapping(source = "message", target = "content") + %s_I toLegacyRequest(%sRequest req); + + @Mapping(source = "phone", target = "phoneNumber") + @Mapping(source = "content", target = "message") + %sRequest toRequest(%s_I mciReq); + + // %sResponse toResponse(%s_O mciRes); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + bizPackage, baseName, author, createDate, createDate, author, + baseName, interfaceId, baseName, + baseName, interfaceId, + baseName, interfaceId + ); + converterContent = mciConverterContent(bizPackage, baseName, mciGroupPath.replace("/", "."), interfaceId); + writeUtf8(converterDir.resolve(baseName + "Converter.java"), converterContent); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n"); + log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n"); + log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + + if (!clientPrefixCap.isEmpty()) { + String mciClientContent = """ + package %s.%s; + + import org.springframework.stereotype.Component; + import lombok.RequiredArgsConstructor; + import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; + import io.shinhanlife.glow.communication.dto.Transfer; + + /** + * @package %s.%s + * @className Mci%sClient + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                     * ---------- 개정이력 ----------
+                     * 수정일       수정자     수정내용
+                     * ---------- -------- ---------------------------
+                     * %s  %s    최초생성
+                     * 
+                     * 
+ */ + @Component + @RequiredArgsConstructor + public class Mci%sClient { + private final AxhubMciComponent mci; + + public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception { + return mci.callTo(interfaceId, dummy, mciReq, resType); + } + } + """.formatted( + BASE_PACKAGE, mciGroupPath.replace("/", "."), + BASE_PACKAGE, mciGroupPath.replace("/", "."), clientPrefixCap, author, createDate, createDate, author, clientPrefixCap + ); + writeUtf8(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent); + log.append("[MCI Client] ").append(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java")).append("\n"); + } + + } else if (isHttp) { + String httpPackage = BASE_PACKAGE + "." + httpGroupPath.replace("/", "."); + String httpRequestClass = baseName + "HttpRequest"; + String httpResponseClass = baseName + "HttpResponse"; + String httpClientClass = httpApiClass + "Client"; + + writeUtf8(httpIoDir.resolve(httpRequestClass + ".java"), + dtoContent(httpPackage + ".io", httpRequestClass, inputFields, author, createDate, true)); + writeUtf8(httpIoDir.resolve(httpResponseClass + ".java"), + dtoContent(httpPackage + ".io", httpResponseClass, outputFields, author, createDate, false)); + writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpRequestClass, inputFields); + writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpResponseClass, outputFields); + writeUtf8(httpClientDir.resolve(httpClientClass + ".java"), + httpClientContent(httpPackage, httpClientClass, httpApiName)); + writeUtf8(converterDir.resolve(baseName + "Converter.java"), + httpConverterContent(bizPackage, baseName, httpPackage)); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: HTTP)\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[HTTP Request IO] ").append(httpIoDir.resolve(httpRequestClass + ".java")).append("\n"); + log.append("[HTTP Response IO] ").append(httpIoDir.resolve(httpResponseClass + ".java")).append("\n"); + log.append("[HTTP Client] ").append(httpClientDir.resolve(httpClientClass + ".java")).append("\n"); + log.append("[HTTP Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + } else { + String legacyReqContent = """ + package %s.legacy; + + import lombok.Data; + + /** + * @package %s.legacy + * @className %sLegacyRequest + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Data + public class %sLegacyRequest { + /** + * EAI 시스템이 요구하는 수신자 번호 파라미터명 + */ + private String phone; + + /** + * EAI 시스템이 요구하는 메시지 내용 파라미터명 + */ + private String content; + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyReqContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyRequest", inputFields, author, createDate, true); + writeUtf8(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent); + + String legacyResContent = """ + package %s.legacy; + + import lombok.Data; + + /** + * @package %s.legacy + * @className %sLegacyResponse + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Data + public class %sLegacyResponse { + // TODO: Add legacy response fields here + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyResContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyResponse", outputFields, author, createDate, true); + writeUtf8(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent); + + String converterContent = """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.factory.Mappers; + + /** + * @package %s.converter + * @className %sConverter + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
+                 * ---------- 개정이력 ----------
+                 * 수정일       수정자     수정내용
+                 * ---------- -------- ---------------------------
+                 * %s  %s    최초생성
+                 * 
+                 * 
+ */ + @Mapper(componentModel = "spring") + public interface %sConverter { + + @Mapping(source = "phoneNumber", target = "phone") + @Mapping(source = "message", target = "content") + %sLegacyRequest toLegacyRequest(%sRequest req); + + @Mapping(source = "phone", target = "phoneNumber") + @Mapping(source = "content", target = "message") + %sRequest toRequest(%sLegacyRequest legacyRequest); + + // %sResponse toResponse(%sLegacyResponse legacyResponse); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, author, createDate, createDate, author, + baseName, baseName, baseName, baseName, baseName, baseName, baseName + ); + converterContent = legacyConverterContent(bizPackage, baseName); + writeUtf8(converterDir.resolve(baseName + "Converter.java"), converterContent); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[Legacy Request DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRequest.java")).append("\n"); + log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n"); + log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + } + // Schema Resource 파일 생성(useSchemaResource=true일 때) + if (useSchemaResource) { + Files.createDirectories(schemaDir); + String inputSchema = """ + { + "type": "object", + "additionalProperties": false, + "properties": { + "TODO_FIELD": { + "type": "string", + "description": "TODO: 파라미터 설명을 입력하세요." + } + }, + "required": [] + } + """; + String outputSchema = """ + { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "처리 결과 상태 (SUCCESS / FAILURE)", + "enum": ["SUCCESS", "FAILURE"] + }, + "message": { + "type": "string", + "description": "처리 결과 메시지" + } + }, + "required": ["status"] + } + """; + writeUtf8(schemaDir.resolve(inputSchemaFileName), inputSchema); + writeUtf8(schemaDir.resolve(outputSchemaFileName), outputSchema); + log.append("[Input Schema] ").append(schemaDir.resolve(inputSchemaFileName)).append("\n"); + log.append("[Output Schema] ").append(schemaDir.resolve(outputSchemaFileName)).append("\n"); + } + + String mockResponse = mockResponseContent(outputFields); + if (isHttp) { + Path moduleRoot = rootDir.resolve(moduleName).toAbsolutePath().normalize(); + Path projectRoot = moduleRoot.getParent(); + Path wireMockBodyPath = projectRoot.resolve(Paths.get("mci-mock", "__files", toolName + ".json")); + Path wireMockMappingPath = projectRoot.resolve(Paths.get("mci-mock", "mappings", toolName + ".json")); + Path podMockResponsePath = moduleRoot.resolve(Paths.get("src/main/resources/mock-responses", toolName + ".json")); + Files.createDirectories(wireMockBodyPath.getParent()); + Files.createDirectories(wireMockMappingPath.getParent()); + Files.createDirectories(podMockResponsePath.getParent()); + writeUtf8(wireMockBodyPath, mockResponse); + writeUtf8(wireMockMappingPath, wireMockMappingContent(interfaceId, wireMockBodyPath.getFileName().toString())); + writeUtf8(podMockResponsePath, mockResponse); + ensureLocalHttpApiConfiguration(projectRoot, httpApiName, toolName); + log.append("[WireMock Response] ").append(wireMockBodyPath).append("\n"); + log.append("[WireMock Mapping] ").append(wireMockMappingPath).append("\n"); + log.append("[Pod Mock Response] ").append(podMockResponsePath).append("\n"); + } else { + Path mockResponsePath = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mock-responses", toolName + ".json")); + Files.createDirectories(mockResponsePath.getParent()); + writeUtf8(mockResponsePath, mockResponse); + log.append("[Mock Response] ").append(mockResponsePath).append("\n"); + } + + Path generatedTestDir = rootDir.resolve(Paths.get(moduleName, "src/test/java/io/shinhanlife/dap/mcc/biz", group.toLowerCase(), "usecase")); + Files.createDirectories(generatedTestDir); + Path generatedTestPath = generatedTestDir.resolve(baseName + "UseCaseTest.java"); + writeUtf8(generatedTestPath, useCaseTestContent(bizPackage, baseName)); + log.append("[Unit Test] ").append(generatedTestPath).append("\\n"); + log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n"); + Files.createDirectories(definitionDir); + Path definitionPath = definitionDir.resolve(toolName + ".yml"); + writeUtf8(definitionPath, toolDefinitionContentV17(toolName, title, description, group, + interfaceId, inputFields, isMutationTool(baseName), definitionOptions)); + log.append("[V17 Tool Definition] ").append(definitionPath).append("\n"); + log.append("\n Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n"); + + return log.toString(); + } + + private static String toolDefinitionContent(String toolName, String title, String description, + String categoryKey, String interfaceId, + List inputFields, boolean mutation) { + String safeDescription = description == null || description.isBlank() + ? title + " 기능을 수행한다." : description; + StringBuilder properties = new StringBuilder(); + StringBuilder required = new StringBuilder(); + Set generatedNames = new LinkedHashSet<>(); + for (FieldDefinition field : inputFields == null ? List.of() : inputFields) { + if (field == null || field.name() == null || field.name().isBlank() + || !generatedNames.add(field.name().trim())) { + continue; + } + appendSchemaProperty(properties, field); + if (field.required()) { + required.append(" - ").append(field.name()).append("\n"); + } + } + if (properties.isEmpty()) { + properties.append(" {}\n"); + } + String requiredBlock = required.isEmpty() ? "" : " required:\n" + required; + String legacyLine = interfaceId == null || interfaceId.isBlank() + ? "" : "legacy_interface_id: " + yamlText(interfaceId) + "\n"; + return """ + name: %s + display_name: %s + version: 1.0.0 + category_key: %s + description: + function: %s + when_to_use: 사용자가 이 업무 기능의 실행 또는 조회를 명확히 요청한 경우 사용한다. + when_not_to_use: 입력값이 확인되지 않았거나 다른 업무 기능이 더 적합한 경우에는 사용하지 않는다. + io_limits: 정의된 입력 항목만 허용하며 응답 DTO에 정의된 업무 결과만 반환한다. + display_description: %s + example_queries: + - %s 처리해줘 + - %s 정보를 확인해줘 + - %s 업무 결과를 알려줘 + read_only: %s + destructive: %s + idempotent: %s + parameters_schema: + type: object + properties: + %s%s additionalProperties: false + tags: [%s] + %srequired_env_keys: [] + owner_org: MCP_TOOL + """.formatted(toolName, yamlText(title), categoryKey.toLowerCase(Locale.ROOT), + yamlText(safeDescription), yamlText(title), yamlText(title), yamlText(title), yamlText(title), + !mutation, mutation, !mutation, properties, requiredBlock, + categoryKey.toLowerCase(Locale.ROOT), legacyLine); + } + + private static String toolDefinitionContentV17(String toolName, String title, String description, + String categoryKey, String interfaceId, + List inputFields, boolean mutation, + ToolDefinitionOptions options) { + String function = option(options == null ? null : options.functionDescription(), + option(description, title + " 기능을 수행한다.")); + String whenToUse = option(options == null ? null : options.whenToUse(), + "사용자가 해당 업무 기능의 실행 또는 조회를 명확히 요청한 경우 사용한다."); + String whenNotToUse = option(options == null ? null : options.whenNotToUse(), + "필수 입력값이 확인되지 않았거나 다른 업무 기능이 더 적합한 경우에는 사용하지 않는다."); + String ioLimits = option(options == null ? null : options.ioLimits(), + "정의된 입력 항목만 허용하며 응답 DTO에 정의된 업무 결과만 반환한다."); + String displayDescription = option(options == null ? null : options.displayDescription(), title); + List examples = normalizedList(options == null ? null : options.exampleQueries(), List.of( + title + " 처리해줘", title + " 정보를 확인해줘", title + " 업무 결과를 알려줘")); + List tags = normalizedList(options == null ? null : options.tags(), + List.of(categoryKey.toLowerCase(Locale.ROOT))); + String ownerOrg = option(options == null ? null : options.ownerOrg(), "MCP_TOOL"); + + StringBuilder properties = new StringBuilder(); + StringBuilder required = new StringBuilder(); + Set generatedNames = new LinkedHashSet<>(); + for (FieldDefinition field : inputFields == null ? List.of() : inputFields) { + if (field == null || field.name() == null || field.name().isBlank() + || !generatedNames.add(field.name().trim())) { + continue; + } + appendSchemaProperty(properties, field); + if (field.required()) { + required.append(" - ").append(field.name().trim()).append("\n"); + } + } + if (properties.isEmpty()) { + properties.append(" {}\n"); + } + String requiredBlock = required.isEmpty() ? "" : " required:\n" + required; + String legacyLine = interfaceId == null || interfaceId.isBlank() + ? "" : "legacy_interface_id: " + yamlText(interfaceId) + "\n"; + String exampleBlock = examples.stream().map(value -> " - " + yamlText(value)) + .collect(java.util.stream.Collectors.joining("\n")); + String tagBlock = tags.stream().map(ToolScaffolder::yamlText) + .collect(java.util.stream.Collectors.joining(", ")); + + return """ + name: %s + display_name: %s + version: 1.0.0 + category_key: %s + description: + function: %s + when_to_use: %s + when_not_to_use: %s + io_limits: %s + display_description: %s + example_queries: + %s + read_only: %s + destructive: %s + idempotent: %s + parameters_schema: + type: object + properties: + %s%s additionalProperties: false + tags: [%s] + %srequired_env_keys: [] + owner_org: %s + """.formatted(toolName, yamlText(title), categoryKey.toLowerCase(Locale.ROOT), + yamlText(function), yamlText(whenToUse), yamlText(whenNotToUse), yamlText(ioLimits), + yamlText(displayDescription), exampleBlock, !mutation, mutation, !mutation, + properties, requiredBlock, tagBlock, legacyLine, yamlText(ownerOrg)); + } + + private static String option(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value.trim(); + } + + private static List normalizedList(List values, List fallback) { + if (values == null) { + return fallback; + } + List normalized = values.stream() + .filter(java.util.Objects::nonNull) + .map(String::trim) + .filter(value -> !value.isBlank()) + .distinct() + .toList(); + return normalized.isEmpty() ? fallback : normalized; + } + + private static boolean isMutationTool(String baseName) { + String value = baseName.toLowerCase(Locale.ROOT); + return value.matches(".*(create|add|update|delete|remove|send|process|approve|reject|register|issue).*"); + } + + private static String jsonSchemaType(String javaType) { + return switch (javaType == null ? "String" : javaType) { + case "Integer", "Long" -> "integer"; + case "Double", "BigDecimal" -> "number"; + case "Boolean" -> "boolean"; + case "List" -> "array"; + default -> "string"; + }; + } + + private static void appendSchemaProperty(StringBuilder properties, FieldDefinition field) { + properties.append(" ").append(field.name().trim()).append(":\n") + .append(" type: ").append(jsonSchemaType(field.type())).append("\n") + .append(" description: ").append(yamlText(richDescription(field))).append("\n"); + if ("Enum".equals(field.type()) && field.enumValues() != null && !field.enumValues().isEmpty()) { + properties.append(" enum: [").append(field.enumValues().stream() + .filter(value -> value != null && !value.isBlank()).map(String::trim) + .collect(java.util.stream.Collectors.joining(", "))).append("]\n"); + } + if ("List".equals(field.type())) { + properties.append(" items:\n") + .append(" type: ").append("Object".equals(field.itemType()) ? "object" : jsonSchemaType(field.itemType())).append("\n"); + if ("Object".equals(field.itemType()) && field.itemFields() != null && !field.itemFields().isEmpty()) { + properties.append(" properties:\n"); + for (FieldDefinition itemField : field.itemFields()) { + properties.append(" ").append(itemField.name()).append(":\n") + .append(" type: ").append(jsonSchemaType(itemField.type())).append("\n") + .append(" description: ").append(yamlText(richDescription(itemField))).append("\n"); + } + } + } + } + + private static String yamlText(String value) { + String safe = value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"") + .replace("\r", " ").replace("\n", " "); + return "\"" + safe + "\""; + } + + private static String toKebabCase(String pascalCase) { + if (pascalCase == null || pascalCase.isEmpty()) return pascalCase; + return pascalCase + .replaceAll("([a-z0-9])([A-Z])", "$1-$2") + .toLowerCase(Locale.ROOT); + } + + private static void ensureLocalHttpApiConfiguration(Path projectRoot, String httpApiName, String toolName) throws IOException { + Path localConfigPath = projectRoot.resolve("src/main/resources/glow/application-glow-local.yml"); + Files.createDirectories(localConfigPath.getParent()); + String existing = Files.exists(localConfigPath) ? Files.readString(localConfigPath, StandardCharsets.UTF_8) : ""; + if (java.util.regex.Pattern.compile("(?m)^\\s*-\\s+name:\\s*" + + java.util.regex.Pattern.quote(httpApiName) + "\\s*$").matcher(existing).find()) { + return; + } + String environmentKey = toPackageSegment(httpApiName).toUpperCase(Locale.ROOT).replace('-', '_'); + String apiEntry = """ + - name: %s + domain: ${AXHUB_%s_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_%s_HTTP_URL:/api/mock/http/%s} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + """.formatted(httpApiName, environmentKey, environmentKey, toolName).stripTrailing() + "\n"; + if (existing.isBlank()) { + existing = """ + spring: + config: + activate: + on-profile: local + + glow: + communication: + http: + api-list: + """ + apiEntry; + } else if (existing.contains("\n mci:")) { + existing = existing.replace("\n mci:", "\n" + apiEntry + " mci:"); + } else if (existing.contains("\naxhub:")) { + existing = existing.replace("\naxhub:", apiEntry + "axhub:"); + } else if (existing.contains("api-list:")) { + existing += apiEntry; + } else { + throw new IllegalStateException("application-glow-local.yml must define glow.communication.http.api-list"); + } + if (!existing.contains("axhub:\n mock:\n http:\n enabled: true")) { + existing += """ + + axhub: + mock: + http: + enabled: true + """; + } + writeUtf8(localConfigPath, existing); + } + + private static void writeUtf8(Path path, String content) throws IOException { + Files.writeString(path, content, StandardCharsets.UTF_8); + } + + private static String dtoContent(String packageName, String className, List fields, + String author, String createDate, boolean request) { + String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"), className); + if (!request) { + body = " private String resultCode;\n\n private String resultMessage;\n" + body; + } + String listImport = hasListField(fields) ? "import java.util.List;\n" : ""; + String patternImport = hasPatternField(fields) ? "import jakarta.validation.constraints.Pattern;\n" : ""; + return """ + package %s; + + import com.fasterxml.jackson.annotation.JsonInclude; + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; + %s%s + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %s { + %s%s} + """.formatted(packageName, listImport, patternImport, className, body, innerObjectListClasses(fields)); + } + + private static String mciIoContent(String packageSuffix, String className, List fields, + String author, String createDate) { + String listImport = hasListField(fields) ? "import java.util.List;\n" : ""; + String patternImport = hasPatternField(fields) ? "import jakarta.validation.constraints.Pattern;\n" : ""; + return """ + package %s.%s.io; + + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; + %s%s + + @Data + public class %s { + %s%s} + """.formatted(BASE_PACKAGE, packageSuffix, listImport, patternImport, className, fieldLines(fields, Set.of(), className), + innerObjectListClasses(fields)); + } + + private static boolean hasListField(List fields) { + return fields != null && fields.stream().anyMatch(field -> field != null && "List".equals(field.type())); + } + + private static boolean hasPatternField(List fields) { + if (fields == null) return false; + return fields.stream().anyMatch(field -> { + if (field == null) return false; + if (field.pattern() != null && !field.pattern().isBlank()) return true; + if ("List".equals(field.type()) && "Object".equals(field.itemType())) { + return hasPatternField(field.itemFields()); + } + return false; + }); + } + + private static void writeStructuredFieldTypes(Path directory, String packageName, String ownerClass, + List fields) throws IOException { + for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || field.name() == null || field.name().isBlank()) { + continue; + } + if ("Enum".equals(field.type())) { + String enumName = toPascalCase(field.name()); + List values = field.enumValues() == null ? List.of() : field.enumValues().stream() + .filter(value -> value != null && !value.isBlank()).map(String::trim).distinct().toList(); + if (values.isEmpty()) { + throw new IllegalArgumentException("Enum field needs at least one allowed value: " + field.name()); + } + String constants = values.stream().map(value -> " " + enumConstant(value) + "(\"" + javaText(value) + "\")") + .collect(java.util.stream.Collectors.joining(",\n")); + writeUtf8(directory.resolve(enumName + ".java"), """ + package %s; + + import com.fasterxml.jackson.annotation.JsonCreator; + import com.fasterxml.jackson.annotation.JsonValue; + + public enum %s { + %s; + + private final String value; + + %s(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static %s fromValue(String value) { + for (%s candidate : values()) { + if (candidate.value.equals(value)) return candidate; + } + throw new IllegalArgumentException("Unsupported value: " + value); + } + } + """.formatted(packageName, enumName, constants, enumName, enumName, enumName)); + } + if ("List".equals(field.type()) && "Object".equals(field.itemType()) + && (field.itemFields() == null || field.itemFields().isEmpty())) { + throw new IllegalArgumentException("Object List field needs item fields: " + field.name()); + } + } + } + + private static String enumConstant(String value) { + String constant = value.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]+", "_").replaceAll("^_+|_+$", ""); + return constant.isBlank() ? "VALUE" : (Character.isDigit(constant.charAt(0)) ? "VALUE_" + constant : constant); + } + + private static String listItemClassName(String ownerClass, FieldDefinition field) { + return toPascalCase(field.name()) + "Item"; + } + + private static String innerObjectListClasses(List fields) { + StringBuilder source = new StringBuilder(); + Set generated = new LinkedHashSet<>(); + for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || !"List".equals(field.type()) || !"Object".equals(field.itemType()) + || field.name() == null || field.name().isBlank()) { + continue; + } + String itemName = listItemClassName("", field); + if (!generated.add(itemName)) continue; + List itemFields = field.itemFields() == null ? List.of() : field.itemFields(); + if (itemFields.isEmpty()) { + throw new IllegalArgumentException("Object List field needs item fields: " + field.name()); + } + source.append("\n @Data\n public static class ").append(itemName).append(" {\n") + .append(fieldLines(itemFields, Set.of(), itemName)) + .append(innerObjectListClasses(itemFields)) + .append(" }\n"); + } + return source.toString(); + } + + private static String httpUseCaseImplContent(String bizPackage, String baseName, String httpPackage, + String httpApiClass, String author, String createDate) { + String httpRequestClass = baseName + "HttpRequest"; + String httpResponseClass = baseName + "HttpResponse"; + String httpClientClass = httpApiClass + "Client"; + String clientVariable = Character.toLowerCase(httpClientClass.charAt(0)) + httpClientClass.substring(1); + return """ + package %s.usecase.impl; + + import %s.converter.%sConverter; + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.%s; + import %s.%s.io.%s; + import %s.%s.io.%s; + import %s.usecase.%sUseCase; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; + + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + private final %sConverter converter; + private final %s %s; + + @Override + public %sResponse execute(%sRequest req) { + %s httpRequest = converter.toHttpRequest(req); + %s httpResponse = %s.call(httpRequest, %s.class); + + %sResponse response = converter.toResponse(httpResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, httpPackage, httpClientClass, + BASE_PACKAGE, httpPackage, httpRequestClass, + BASE_PACKAGE, httpPackage, httpResponseClass, + bizPackage, baseName, + baseName, baseName, + baseName, httpClientClass, clientVariable, + baseName, baseName, + httpRequestClass, httpResponseClass, clientVariable, httpResponseClass, + baseName); + } + + private static String httpClientContent(String httpPackage, String clientClass, String apiName) { + return """ + package %s; + + import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Component; + + @Component + @RequiredArgsConstructor + public class %s { + private static final String API_NAME = "%s"; + + private final AxhubHttpComponent http; + + public O call(I request, Class responseType) { + return http.call(API_NAME, request, responseType); + } + } + """.formatted(httpPackage, clientClass, apiName); + } + + private static String httpConverterContent(String bizPackage, String baseName, String httpPackage) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.io.%sHttpRequest; + import %s.io.%sHttpResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %sHttpRequest toHttpRequest(%sRequest request); + %sResponse toResponse(%sHttpResponse httpResponse); + } + """.formatted(bizPackage, + bizPackage, baseName, + bizPackage, baseName, + httpPackage, baseName, + httpPackage, baseName, + baseName, baseName, baseName, baseName, baseName); + } + + private static String toPackageSegment(String value) { + String normalized = value == null ? "" : value.toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]+", "_") + .replaceAll("^_+|_+$", ""); + return normalized.isBlank() ? "http_api" : normalized; + } + private static String mciConverterContent(String bizPackage, String baseName, String mciPackage, String interfaceId) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.io.%s_I; + import %s.%s.io.%s_O; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %s_I toLegacyRequest(%sRequest request); + %sRequest toRequest(%s_I mciRequest); + %sResponse toResponse(%s_O mciRes); + } + """.formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, + BASE_PACKAGE, mciPackage, interfaceId, BASE_PACKAGE, mciPackage, interfaceId, + baseName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId); + } + + private static String legacyConverterContent(String bizPackage, String baseName) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %sLegacyRequest toLegacyRequest(%sRequest request); + %sRequest toRequest(%sLegacyRequest legacyRequest); + %sResponse toResponse(%sLegacyResponse legacyResponse); + } + """.formatted( + bizPackage, bizPackage, baseName, bizPackage, baseName, + bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName, baseName, baseName, baseName, baseName); + } + private static String fieldLines(List fields) { + return fieldLines(fields, Set.of(), ""); + } + + private static String fieldLines(List fields, Set excludedNames) { + return fieldLines(fields, excludedNames, ""); + } + + private static String fieldLines(List fields, Set excludedNames, String ownerClass) { + StringBuilder source = new StringBuilder(); + Set generatedNames = new LinkedHashSet<>(); + for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || field.name() == null || field.name().isBlank()) { + continue; + } + String fieldName = field.name().trim(); + if (excludedNames.contains(fieldName) || !generatedNames.add(fieldName)) { + continue; + } + String type = javaFieldType(field, ownerClass); + String description = richDescription(field).replace("\"", "\\\""); + String example = ""; + if (field.examples() != null && !field.examples().isEmpty()) { + example = field.examples().get(0).replace("\"", "\\\""); + } + source.append(" @Schema(description = \"").append(description).append("\""); + if (!example.isEmpty()) { + source.append(", example = \"").append(example).append("\""); + } + if (field.required()) { + source.append(", requiredMode = Schema.RequiredMode.REQUIRED"); + } + source.append(")\n"); + if (field.pattern() != null && !field.pattern().isBlank()) { + source.append(" @Pattern(regexp = \"").append(field.pattern().replace("\"", "\\\"")).append("\")\n"); + } + source.append(" private ").append(type).append(' ').append(fieldName).append(";\n\n"); + } + return source.toString(); + } + + private static String richDescription(FieldDefinition field) { + String desc = field.description() == null ? "" : field.description().trim(); + if (field.pattern() != null && !field.pattern().isBlank()) { + desc += " (형식: " + field.pattern() + ")"; + } + if (field.examples() != null && !field.examples().isEmpty()) { + desc += " (예시: " + String.join(", ", field.examples()) + ")"; + } + return desc.trim(); + } + + private static String javaFieldType(FieldDefinition field, String ownerClass) { + return switch (field.type() == null ? "String" : field.type()) { + case "Enum" -> toPascalCase(field.name()); + case "List" -> "List<" + listItemJavaType(field, ownerClass) + ">"; + default -> supportedType(field.type()); + }; + } + + private static String listItemJavaType(FieldDefinition field, String ownerClass) { + String itemType = field.itemType() == null ? "" : field.itemType(); + if ("Object".equals(itemType)) { + return listItemClassName(ownerClass, field); + } + return supportedType(itemType); + } + private static String supportedType(String type) { + return switch (type == null ? "String" : type) { + case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type; + default -> throw new IllegalArgumentException("Unsupported field type: " + type); + }; + } + + + static String wireMockMappingContent(String interfaceId, String bodyFileName) { + return """ + { + "request" : { + "method" : "POST", + "urlPath" : "/%s" + }, + "response" : { + "status" : 200, + "headers" : { + "Content-Type" : "application/json;charset=UTF-8" + }, + "bodyFileName" : "%s" + } + } + """.formatted(jsonEscape(interfaceId), jsonEscape(bodyFileName)); + } + private static String mockResponseContent(List outputFields) { + StringBuilder json = new StringBuilder("{\n"); + List fields = outputFields == null ? List.of() : outputFields; + boolean first = true; + for (FieldDefinition field : fields) { + if (field.name() == null || field.name().isBlank()) { + continue; + } + if (!first) { + json.append(",\n"); + } + json.append(" \"").append(jsonEscape(field.name())).append("\" : ") + .append(mockValue(field)); + first = false; + } + return json.append("\n}\n").toString(); + } + + private static String mockValue(FieldDefinition field) { + if ("List".equals(field.type())) { + if ("Object".equals(field.itemType())) { + StringBuilder object = new StringBuilder("{"); + boolean first = true; + for (FieldDefinition itemField : field.itemFields() == null ? List.of() : field.itemFields()) { + if (!first) object.append(", "); + object.append("\"").append(jsonEscape(itemField.name())).append("\" : ").append(mockValue(itemField)); + first = false; + } + return "[" + object + "]"; + } + FieldDefinition item = new FieldDefinition("item", field.itemType(), "", field.examples(), field.pattern(), false); + return "[" + mockValue(item) + "]"; + } + + String exampleStr = (field.examples() != null && !field.examples().isEmpty()) ? field.examples().get(0) : null; + if (exampleStr == null || exampleStr.isBlank()) { + return "null"; + } + if ("Enum".equals(field.type())) { + return "\"" + jsonEscape(exampleStr) + "\""; + } + return switch (supportedType(field.type())) { + case "Integer", "Long", "Double", "BigDecimal" -> exampleStr; + case "Boolean" -> Boolean.parseBoolean(exampleStr) ? "true" : "false"; + default -> "\"" + jsonEscape(exampleStr) + "\""; + }; + } + + private static String jsonEscape(String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } + + private static String useCaseTestContent(String bizPackage, String baseName) { + return """ + package %s.usecase; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import org.junit.jupiter.api.Test; + + class %sUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new %sRequest()); + assertNotNull(new %sResponse()); + } + } + """.formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName); + } + private static String toToolName(String moduleName, String group, String baseName) { + String normalizedName = baseName.replaceAll("([a-z0-9])([A-Z])", "$1 $2") + .toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]+", " ") + .trim(); + String[] words = normalizedName.split("\\s+"); + String service = words[0]; + String action = words.length == 1 ? "execute" : words[words.length - 1]; + return "%s_%s_%s".formatted( + group.toLowerCase(Locale.ROOT), + service, + action); + } + + private static String toPascalCase(String str) { + if (str == null || str.isEmpty()) { + return str; + } + StringBuilder result = new StringBuilder(); + boolean capitalizeNext = true; + for (char c : str.toCharArray()) { + if (c == '_' || c == '-' || c == ' ') { + capitalizeNext = true; + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(c); + } + } + if (result.length() > 0) { + result.setCharAt(0, Character.toUpperCase(result.charAt(0))); + } + return result.toString(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java new file mode 100644 index 000000000..7915e6e92 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java @@ -0,0 +1,78 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.McpOutputSchema; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import java.io.InputStream; +import java.util.Map; +import org.springframework.core.io.ClassPathResource; + +/** Resolves MCP Tool schemas from resources or DTO metadata. */ +public class ToolSchemaResolver { + + private final ObjectMapper objectMapper; + + public ToolSchemaResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public Map resolve(org.springaicommunity.mcp.annotation.McpTool function, + GrowToolHint hint, Class requestType) { + if (hint != null && !hint.inputSchemaResource().isBlank()) { + return loadResource(hint.inputSchemaResource()); + } + return JsonSchemaGenerator.generateSchema(requestType); + } + + /** + * Resolves a response schema only when it is explicitly declared. + * A JSON resource has precedence over a DTO marker annotation. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, + Class responseType, GrowToolHint hint) { + if (hint != null && !hint.outputSchemaResource().isBlank()) { + return loadResource(hint.outputSchemaResource()); + } + return resolveOutput(function, responseType); + } + + /** + * Generates a response schema only for DTOs marked with {@link McpOutputSchema}. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, + Class responseType) { + if (responseType == null + || responseType == Object.class + || Map.class.isAssignableFrom(responseType) + || responseType == Void.class + || responseType == void.class + || !responseType.isAnnotationPresent(McpOutputSchema.class)) { + return Map.of(); + } + return JsonSchemaGenerator.generateSchema(responseType); + } + + /** + * Retained for callers that use only explicit output schemas. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function) { + return Map.of(); + } + + private Map loadResource(String location) { + String path = location.startsWith("classpath:") + ? location.substring("classpath:".length()) + : location; + ClassPathResource resource = new ClassPathResource(path); + if (!resource.exists()) { + throw new IllegalStateException("MCP schema resource not found: " + location); + } + + try (InputStream inputStream = resource.getInputStream()) { + return objectMapper.readValue(inputStream, new TypeReference<>() { }); + } catch (Exception e) { + throw new IllegalStateException("Failed to load MCP schema resource: " + location, e); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java new file mode 100644 index 000000000..a5c1bc218 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java @@ -0,0 +1,181 @@ +package io.shinhanlife.dap.lib.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; + +/** Updates MCP SDK and project-owned metadata in a generated tool source file. */ +public final class ToolSourceUpdater { + + private ToolSourceUpdater() { + } + + public static void updateToolSource(String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + String configuredSourceDirectory = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDirectory = configuredSourceDirectory == null || configuredSourceDirectory.isBlank() + ? Paths.get(".") : Paths.get(configuredSourceDirectory); + updateToolSource(rootDirectory, toolName, categoryKey, description, register, requiresApproval); + } + + static void updateToolSource(Path rootDirectory, String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + Path targetFile = findToolSource(rootDirectory, toolName); + if (targetFile == null) { + throw new IllegalArgumentException("Tool source not found: " + toolName); + } + + String content = Files.readString(targetFile, StandardCharsets.UTF_8); + content = updateMcpTool(content, toolName, description); + content = updateToolHint(content, categoryKey, register, requiresApproval); + Files.writeString(targetFile, content, StandardCharsets.UTF_8); + } + + public static boolean syncToolSource(Path rootDirectory, String toolName, String title, String description, + boolean readOnlyHint, boolean destructiveHint, boolean idempotentHint, + String categoryKey) throws IOException { + Path targetFile = findToolSource(rootDirectory, toolName); + if (targetFile == null) { + throw new IllegalArgumentException("Tool source not found: " + toolName); + } + + String content = Files.readString(targetFile, StandardCharsets.UTF_8); + String originalContent = content; + + AnnotationRange mcpRange = annotationArguments(content, "McpTool", toolName); + if (mcpRange != null) { + if (title != null) { + content = replaceAttribute(content, mcpRange, "title", quote(title)); + mcpRange = annotationArguments(content, "McpTool", toolName); + } + if (description != null) { + content = replaceAttribute(content, mcpRange, "description", quote(description)); + } + } + + AnnotationRange hintRange = annotationArguments(content, "GrowToolHint", null); + if (hintRange != null) { + if (categoryKey != null && !categoryKey.isBlank()) { + content = replaceAttribute(content, hintRange, "categoryKey", quote(categoryKey)); + hintRange = annotationArguments(content, "GrowToolHint", null); + } + content = replaceAttribute(content, hintRange, "destructive", Boolean.toString(destructiveHint)); + hintRange = annotationArguments(content, "GrowToolHint", null); + content = replaceAttribute(content, hintRange, "idempotent", Boolean.toString(idempotentHint)); + } + + if (!content.equals(originalContent)) { + Files.writeString(targetFile, content, StandardCharsets.UTF_8); + return true; + } + return false; + } + + private static Path findToolSource(Path rootDirectory, String toolName) throws IOException { + try (var paths = Files.walk(rootDirectory)) { + return paths.filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith("UseCase.java")) + .filter(path -> path.toString().contains("dap-was-") || path.toString().contains("axhub-tool-")) + .sorted(Comparator.naturalOrder()) + .filter(path -> containsMcpTool(path, toolName)) + .findFirst() + .orElse(null); + } + } + + private static boolean containsMcpTool(Path path, String toolName) { + try { + return annotationArguments(Files.readString(path, StandardCharsets.UTF_8), "McpTool", toolName) != null; + } catch (IOException exception) { + throw new IllegalStateException("Failed to read tool source: " + path, exception); + } + } + + private static String updateMcpTool(String content, String toolName, String description) { + AnnotationRange range = annotationArguments(content, "McpTool", toolName); + if (range == null) { + throw new IllegalArgumentException("McpTool declaration not found: " + toolName); + } + return description == null ? content : replaceAttribute(content, range, "description", quote(description)); + } + + private static String updateToolHint(String content, String categoryKey, boolean register, Boolean requiresApproval) { + AnnotationRange range = annotationArguments(content, "GrowToolHint", null); + if (range == null) { + throw new IllegalArgumentException("GrowToolHint declaration not found next to McpTool"); + } + String updated = replaceAttribute(content, range, "register", Boolean.toString(register)); + range = annotationArguments(updated, "GrowToolHint", null); + if (requiresApproval != null) { + updated = replaceAttribute(updated, range, "requiresApproval", Boolean.toString(requiresApproval)); + range = annotationArguments(updated, "GrowToolHint", null); + } + if (categoryKey != null && !categoryKey.isBlank()) { + updated = replaceAttribute(updated, range, "categoryKey", quote(categoryKey)); + } + return updated; + } + + private static String replaceAttribute(String content, AnnotationRange range, String attribute, String value) { + String arguments = content.substring(range.argumentsStart(), range.argumentsEnd()); + String pattern = "\\b" + attribute + "\\s*=\\s*(?:true|false|\\\"(?:\\\\.|[^\\\"\\\\])*\\\")"; + String replacement = arguments.replaceFirst(pattern, attribute + " = " + value); + if (replacement.equals(arguments)) { + replacement = arguments.isBlank() ? attribute + " = " + value : arguments + ", " + attribute + " = " + value; + } + return content.substring(0, range.argumentsStart()) + replacement + content.substring(range.argumentsEnd()); + } + + private static String quote(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + + private static AnnotationRange annotationArguments(String content, String annotationName, String toolName) { + int offset = content.indexOf("@" + annotationName); + while (offset >= 0) { + int openingParenthesis = content.indexOf('(', offset); + int closingParenthesis = findAnnotationEnd(content, openingParenthesis); + if (openingParenthesis < 0 || closingParenthesis < 0) { + return null; + } + AnnotationRange range = new AnnotationRange(openingParenthesis + 1, closingParenthesis); + if (toolName == null || content.substring(range.argumentsStart(), range.argumentsEnd()) + .matches("(?s).*\\bname\\s*=\\s*\\\"" + java.util.regex.Pattern.quote(toolName) + "\\\".*")) { + return range; + } + offset = content.indexOf("@" + annotationName, closingParenthesis + 1); + } + return null; + } + + private static int findAnnotationEnd(String content, int openingParenthesis) { + int depth = 0; + boolean inString = false; + boolean escaped = false; + for (int index = openingParenthesis; index < content.length(); index++) { + char character = content.charAt(index); + if (inString) { + if (escaped) { + escaped = false; + } else if (character == '\\') { + escaped = true; + } else if (character == '\"') { + inString = false; + } + } else if (character == '\"') { + inString = true; + } else if (character == '(') { + depth++; + } else if (character == ')' && --depth == 0) { + return index; + } + } + return -1; + } + + private record AnnotationRange(int argumentsStart, int argumentsEnd) { + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java new file mode 100644 index 000000000..414843e38 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java @@ -0,0 +1,21 @@ +package io.shinhanlife.dap.lib.validation; + +import java.nio.file.Path; + +/** Gradle entry point for validating unique MCP Tool names before packaging. */ +public final class McpToolNameValidationRunner { + + private McpToolNameValidationRunner() { + } + + public static void main(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: McpToolNameValidationRunner "); + } + validate(Path.of(args[0])); + } + + static void validate(Path projectRoot) { + McpToolNameValidator.assertUnique(projectRoot); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java new file mode 100644 index 000000000..d662c925c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java @@ -0,0 +1,173 @@ +package io.shinhanlife.dap.lib.validation; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @package io.shinhanlife.dap.lib.validation + * @className McpToolNameValidator + * @description Validates unique MCP SDK tool names across tool modules + * @author 0986406 + * @create 2026.07.27 + *
+ * ---------- revision history ----------
+ * date       author    description
+ * ---------- --------- ---------------------------
+ * 2026.07.27 0986406    initial creation
+ * 
+ */ +public final class McpToolNameValidator { + + private static final Pattern TOOL_NAME_PATTERN = Pattern.compile("\\bname\\s*=\\s*\\\"([^\\\"]+)\\\""); + private static final Pattern TOOL_NAME_CONVENTION = Pattern.compile("^[a-zA-Z0-9_-]{1,128}$"); + + private McpToolNameValidator() { + } + + public static void assertUnique(Path projectRoot) { + Map> declarationsByName = new LinkedHashMap<>(); + + try (var modules = Files.list(projectRoot)) { + modules.filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().startsWith("dap-was-")) + .filter(path -> !path.getFileName().toString().equals("dap-was-lib")) + .sorted() + .forEach(module -> collectDeclarations(module, declarationsByName)); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to scan MCP tool modules", exception); + } + + List>> invalidNames = declarationsByName.entrySet().stream() + .filter(entry -> !TOOL_NAME_CONVENTION.matcher(entry.getKey()).matches()) + .sorted(Map.Entry.comparingByKey()) + .toList(); + + if (!invalidNames.isEmpty()) { + throw new IllegalStateException(buildInvalidNameMessage(invalidNames)); + } + List>> duplicates = declarationsByName.entrySet().stream() + .filter(entry -> entry.getValue().size() > 1) + .sorted(Map.Entry.comparingByKey()) + .toList(); + + if (!duplicates.isEmpty()) { + throw new IllegalStateException(buildDuplicateMessage(duplicates)); + } + } + + private static void collectDeclarations(Path module, Map> declarationsByName) { + Path sourceDirectory = module.resolve("src/main/java"); + if (!Files.isDirectory(sourceDirectory)) { + return; + } + + try (var sources = Files.walk(sourceDirectory)) { + sources.filter(path -> path.toString().endsWith(".java")) + .sorted() + .forEach(source -> collectDeclarations(module.getFileName().toString(), source, declarationsByName)); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to scan module " + module.getFileName(), exception); + } + } + + private static void collectDeclarations(String moduleName, Path source, + Map> declarationsByName) { + String content; + try { + content = Files.readString(source); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to read " + source, exception); + } + + int annotationOffset = content.indexOf("@McpTool"); + while (annotationOffset >= 0) { + int openingParenthesis = content.indexOf('(', annotationOffset); + int closingParenthesis = findAnnotationEnd(content, openingParenthesis); + if (openingParenthesis < 0 || closingParenthesis < 0) { + break; + } + + Matcher matcher = TOOL_NAME_PATTERN.matcher(content.substring(openingParenthesis + 1, closingParenthesis)); + if (matcher.find()) { + String toolName = matcher.group(1); + int line = 1 + (int) content.substring(0, annotationOffset).chars().filter(character -> character == '\n').count(); + declarationsByName.computeIfAbsent(toolName, ignored -> new ArrayList<>()) + .add(new ToolDeclaration(moduleName, source, line)); + } + annotationOffset = content.indexOf("@McpTool", closingParenthesis + 1); + } + } + + private static int findAnnotationEnd(String content, int openingParenthesis) { + if (openingParenthesis < 0) { + return -1; + } + + int depth = 0; + boolean inString = false; + boolean escaped = false; + for (int index = openingParenthesis; index < content.length(); index++) { + char character = content.charAt(index); + if (inString) { + if (escaped) { + escaped = false; + } else if (character == '\\') { + escaped = true; + } else if (character == '\"') { + inString = false; + } + continue; + } + if (character == '\"') { + inString = true; + } else if (character == '(') { + depth++; + } else if (character == ')' && --depth == 0) { + return index; + } + } + return -1; + } + + private static String buildInvalidNameMessage(List>> invalidNames) { + StringBuilder message = new StringBuilder("Invalid MCP tool name(s): expected 1-128 characters using letters, digits, underscores, or hyphens."); + for (Map.Entry> invalidName : invalidNames) { + message.append("\n\n").append(invalidName.getKey()); + invalidName.getValue().stream() + .sorted(Comparator.comparing(ToolDeclaration::moduleName).thenComparing(declaration -> declaration.source().toString())) + .forEach(declaration -> message.append("\n- ") + .append(declaration.moduleName()) + .append(": ") + .append(declaration.source()) + .append(':').append(declaration.line())); + } + return message.toString(); + } + + private static String buildDuplicateMessage(List>> duplicates) { + StringBuilder message = new StringBuilder("Duplicate MCP tool name(s):"); + for (Map.Entry> duplicate : duplicates) { + message.append("\n\n").append(duplicate.getKey()); + duplicate.getValue().stream() + .sorted(Comparator.comparing(ToolDeclaration::moduleName).thenComparing(declaration -> declaration.source().toString())) + .forEach(declaration -> message.append("\n- ") + .append(declaration.moduleName()) + .append(": ") + .append(declaration.source()) + .append(':').append(declaration.line())); + } + return message.toString(); + } + + private record ToolDeclaration(String moduleName, Path source, int line) { + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java new file mode 100644 index 000000000..46e54ea64 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.lib.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; + +/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */ +public class ToolArgumentSchemaValidator { + + private final ObjectMapper objectMapper; + + public ToolArgumentSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public List validate(Map schemaDefinition, Map arguments) throws Exception { + return validateValue(schemaDefinition, arguments); + } + + public List validateValue(Map schemaDefinition, Object value) throws Exception { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = schemaRegistry.getSchema(objectMapper.writeValueAsString(schemaDefinition)); + return schema.validate(objectMapper.writeValueAsString(value), InputFormat.JSON); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolSchemaV17ValidationRunner.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolSchemaV17ValidationRunner.java new file mode 100644 index 000000000..a4492a563 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolSchemaV17ValidationRunner.java @@ -0,0 +1,78 @@ +package io.shinhanlife.dap.lib.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionValidator; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** CI/CD에서 모든 Java MCP Tool과 V17 정의 파일의 1:1 대응을 검증합니다. */ +public final class ToolSchemaV17ValidationRunner { + private static final Pattern MCP_TOOL_NAME = Pattern.compile( + "@McpTool\\s*\\(\\s*name\\s*=\\s*\"([^\"]+)\""); + + private ToolSchemaV17ValidationRunner() { + } + + public static void main(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: ToolSchemaV17ValidationRunner "); + } + validate(Path.of(args[0])); + } + + static void validate(Path projectRoot) { + ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + Map definitions = new LinkedHashMap<>(); + Set toolNames = new LinkedHashSet<>(); + try (Stream files = Files.walk(projectRoot)) { + for (Path file : files.filter(Files::isRegularFile).toList()) { + String normalized = file.toString().replace('\\', '/'); + if (normalized.contains("/build/") || normalized.contains("/.gradle/") + || normalized.contains("/tmp_") || normalized.contains("/org/")) { + continue; + } + if (normalized.endsWith(".java") && normalized.contains("/dap-was-") + && !normalized.contains("/dap-was-lib/")) { + Matcher matcher = MCP_TOOL_NAME.matcher(Files.readString(file, StandardCharsets.UTF_8)); + while (matcher.find()) { + toolNames.add(matcher.group(1)); + } + } + if (normalized.contains("/src/main/resources/tool-definitions/") + && (normalized.endsWith(".yml") || normalized.endsWith(".yaml"))) { + ToolDefinition definition = yamlMapper.readValue(file.toFile(), ToolDefinition.class); + ToolDefinitionValidator.validate(definition, file.toString()); + Path previous = definitions.putIfAbsent(definition.name(), file); + if (previous != null) { + throw new IllegalStateException("Duplicate V17 Tool definition: " + definition.name() + + " [" + previous + ", " + file + "]"); + } + } + } + } catch (IOException exception) { + throw new IllegalStateException("Failed to scan Tool schema V17 files", exception); + } + Set missing = new LinkedHashSet<>(toolNames); + missing.removeAll(definitions.keySet()); + if (!missing.isEmpty()) { + throw new IllegalStateException("Missing V17 Tool definitions: " + missing); + } + Set orphan = new LinkedHashSet<>(definitions.keySet()); + orphan.removeAll(toolNames); + if (!orphan.isEmpty()) { + throw new IllegalStateException("V17 definitions without matching @McpTool: " + orphan); + } + System.out.println("Tool schema V17 validation passed: " + toolNames.size() + " tools"); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java new file mode 100644 index 000000000..d329d77b5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java @@ -0,0 +1,146 @@ +package io.shinhanlife.dap.mcc.dto; + +import lombok.Getter; +import lombok.Setter; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; +import java.util.Set; +import java.util.List; +import java.util.HashSet; +import io.shinhanlife.dap.lib.dto.OperationType; +/** + * Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스 + * Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다. + */ +/** + * @package io.shinhanlife.dap.mcg.dto + * @className ToolMetadata + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ToolMetadata { + + // 1. Tool 기본 정보 + private String uid; // UUID 형식의 고유 식별자 + private String semver; // 버전 (예: 1.0.0) + private String displayName; // 사람이 읽는 라벨 (1-128자) + private String name; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool) + private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능) + private String functionDescription; + private String whenToUse; + private String whenNotToUse; + private String ioLimits; + private String displayDescription; + private List exampleQueries; + private List tags; + private String ownerOrg; + private List requiredEnvKeys; + + // 2. 파라미터 스키마 (JSON Schema 형태의 Map) + private Map parametersSchema; + private Map outputSchema; + + // 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨) + private Map actionPrompts; + + // 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식) + private String categoryKey; + + // 2-1-1. 어플리케이션(모듈) 이름 (예: dap-was-cus) + private String moduleName; + + // 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info) + private String endpoint; + + // 2-3. Pod 실행 URL (독립적인 Microservice 라우팅용, 예: http://localhost:8082) + private String podUrl; + + // 2-4. 가시성 여부 + @Builder.Default + private Boolean visible = true; + + // 활성화 여부 + @Builder.Default + private Boolean enabled = true; + + // 2-5. Redis 등록 여부 (UI 표출용) + @Builder.Default + private Boolean isRegistered = true; + + // 2-6. HITL 승인 필요 여부 + @Builder.Default + private Boolean requiresApproval = false; + + @Builder.Default + private Boolean readOnlyHint = false; + + @Builder.Default + private Boolean destructiveHint = false; + + @Builder.Default + private Boolean idempotentHint = false; + + @Builder.Default + private Boolean openWorldHint = false; + + + + // 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI) + private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI" + + // 4. 레거시(MCI/EAI) 연동 시 필수 정보 (integrationType이 "MCI_EAI"일 때 사용) + private String mciServiceId; // MCI/EAI 호출을 위한 서비스 ID (예: CRM_001, LICO_992) + + // 5. 인프라 상태 정보 (DIRECT 연동 시 사용) + private Long lastHeartbeat; // Redis TTL 갱신용 마지막 하트비트 타임스탬프 + + // 6. 동적 서킷 브레이커 & 속도 제어 설정 (Registry 기반) + private Integer failureRateThreshold; // 서킷 브레이커 동작 기준 실패율 (%) + private Integer slidingWindowSize; // 서킷 브레이커 에러율 계산 표본 요청 수 + private Integer rateLimitForPeriod; // 속도 제어: 1초당 허용 최대 요청 수 + + // 7. Gateway 코어 제어용 설정 필드 추가 (재시도, 타임아웃, 오퍼레이션 타입) + @Builder.Default + private OperationType operationType = OperationType.READ; + + @Builder.Default + private Boolean retryEnabled = true; + + @Builder.Default + private Integer circuitBreakerFailureThreshold = 0; + + @Builder.Default + private Long circuitBreakerOpenMillis = 0L; + + @Builder.Default + private Long timeoutMillis = 0L; + + // --- Guardrail 호환성을 위한 메서드 추가 --- + public Set allowedArguments() { + if (parametersSchema == null || !parametersSchema.containsKey("properties")) return Set.of(); + return ((Map) parametersSchema.get("properties")).keySet(); + } + + public Set requiredArguments() { + if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of(); + return new HashSet<>((List) parametersSchema.get("required")); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java new file mode 100644 index 000000000..3926a8795 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java @@ -0,0 +1,50 @@ +package io.shinhanlife.dap.mcc.presentation; + +import io.shinhanlife.dap.lib.mcp.McpRequestHeaders; +import io.shinhanlife.dap.lib.mcp.ToolExecutionResult; +import io.shinhanlife.dap.lib.mcp.McpToolExecutionService; +import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** Legacy REST adapter for Tool Pod execution. */ +@RestController +@RequestMapping("/") +@RequiredArgsConstructor +public class BusinessToolController { + + private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender; + private final McpToolExecutionService toolExecutionService; + + @GetMapping("/mcp/api/v1/tools/local") + public List getLocalTools() { + return toolRegistryHeartbeatSender.getAllScannedTools(); + } + + @PostMapping("/mcp/{name}") + public ResponseEntity executeDynamicTool( + @PathVariable("name") String functionName, + @RequestHeader(value = "X-Request-Id", required = false) String headerRequestId, + @RequestHeader(value = "trace-id", required = false) String traceId, + @RequestHeader(value = "request-id", required = false) String requestId, + @RequestHeader(value = "employee-id", required = false) String encryptedEmployeeId, + @RequestBody(required = false) Map arguments) { + ToolExecutionResult result = toolExecutionService.execute( + functionName, + new McpRequestHeaders(headerRequestId, traceId, requestId, encryptedEmployeeId), + arguments); + ResponseEntity.BodyBuilder response = ResponseEntity.status(result.statusCode()); + result.headers().forEach(response::header); + return response.body(result.body()); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java new file mode 100644 index 000000000..a8eeea7fc --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.presentation; + +import io.shinhanlife.dap.lib.manifest.ToolManifestResponse; +import io.shinhanlife.dap.lib.manifest.ToolManifestService; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +/** Read-only Tool Service manifest endpoint for MCP background discovery. */ +@RestController +public class ToolManifestController { + + private final ToolManifestService toolManifestService; + + public ToolManifestController(ToolManifestService toolManifestService) { + this.toolManifestService = toolManifestService; + } + + @GetMapping(value = "/tool-manifest", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getManifest( + @RequestHeader(value = HttpHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { + ToolManifestResponse manifest = toolManifestService.currentManifest(); + String eTag = '"' + manifest.revision() + '"'; + if (eTag.equals(ifNoneMatch)) { + return ResponseEntity.status(304).eTag(eTag).build(); + } + return ResponseEntity.ok().eTag(eTag).body(manifest); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java new file mode 100644 index 000000000..ae63fd968 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java @@ -0,0 +1,29 @@ +package io.shinhanlife.glow; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +//@Schema(description = "응답 에러 객체. 성공 케이스일 경우 null. 실제 에러가 발생할 경우에만 예외명, 예외 메시지 필드 세팅 예정.") +public class BaseException { + +// @Schema(description = "Error 코드 Meta 참조 운영. (예) 20001, 50001 등", shinhanlife = "20001") + String code; + +// @Schema(description = "메시지") + String message; + +// @Schema(description = "예외명.", shinhanlife = "NullPointerException") + @Builder.Default + String exceptionName = ""; + +// @Schema(description = "예외상세", shinhanlife = "StackTrace") + @Builder.Default + String exceptionDetail = ""; + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java new file mode 100644 index 000000000..e0d88e139 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java @@ -0,0 +1,25 @@ +package io.shinhanlife.glow; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@Builder +@AllArgsConstructor +public class BaseResponse { + +// @Schema(description = "응답 코드 HTTP STATUS 오류 - 실제 Header 는 200 으로 내려감", shinhanlife = "200") + private int code; + +// @Schema(description = "응답 메시지. 응답 코드와 매핑된 메시지.", shinhanlife = "데이터 생성 성공") + private String message; + +// @Schema(description = "응답 본문 데이터. 실제 비즈니스 데이터.") + private T data; + + private BaseException error; + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java new file mode 100644 index 000000000..5d3665e94 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java @@ -0,0 +1,61 @@ +package io.shinhanlife.glow; + +/** + * 업무 예외. + * + *

두 가지 방식으로 쓸 수 있다.

+ *
    + *
  1. 메시지코드 방식(권장) — {@code throw new BizException("DAH00004", "사번")}
    + * 통합메시지(ZT_UNFC_MSG)에서 문구를 찾아 {0},{1}.. 을 인자로 치환해 응답한다. + * 문구가 화면·서버 한곳(관리 화면)에서 관리되고, 다국어 확장도 여기서 처리된다.
  2. + *
  3. 문구 직접 방식(기존 호환) — {@code throw new BizException("사번은 필수입니다.")}
    + * 메시지코드로 해석되지 않으면 문구 그대로 응답한다.
  4. + *
+ * + *

변환은 {@code common/config/GlobalExceptionHandler} 가 수행한다. + * 메시지코드 여부는 코드 형식(영문 대문자+숫자 8자리)으로 판별한다.

+ */ +public class BizException extends RuntimeException { + + /** 통합메시지코드 (문구 직접 방식이면 null) */ + private final String msgCd; + + /** 메시지 치환 인자 */ + private final Object[] msgArgs; + + /** + * 문구를 직접 지정하거나, 메시지코드만 던진다. + * + * @param messageOrCode 메시지 문구 또는 통합메시지코드 + */ + public BizException(String messageOrCode) { + super(messageOrCode); + this.msgCd = isMessageCode(messageOrCode) ? messageOrCode : null; + this.msgArgs = new Object[0]; + } + + /** + * 메시지코드 + 치환 인자. + * + * @param msgCd 통합메시지코드 (예: DAH00004) + * @param msgArgs {0},{1}.. 에 순서대로 치환될 인자 + */ + public BizException(String msgCd, Object... msgArgs) { + super(msgCd); + this.msgCd = msgCd; + this.msgArgs = msgArgs == null ? new Object[0] : msgArgs; + } + + public String getMsgCd() { + return msgCd; + } + + public Object[] getMsgArgs() { + return msgArgs; + } + + /** 통합메시지코드 형식인지 — 영문 대문자 3자리 + 숫자 5자리 (예: DAH00001) */ + private static boolean isMessageCode(String value) { + return value != null && value.matches("^[A-Z]{3}\\d{5}$"); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java new file mode 100644 index 000000000..1772ba09a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java @@ -0,0 +1,13 @@ +package io.shinhanlife.glow; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowAppServiceId { + + String value(); + + String description() default ""; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java new file mode 100644 index 000000000..078ac61f5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java @@ -0,0 +1,5 @@ +package io.shinhanlife.glow; + +public @interface GlowControllerId { + String value(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java new file mode 100644 index 000000000..884d75f8f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java @@ -0,0 +1,9 @@ +package io.shinhanlife.glow; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowIndexPaging { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java new file mode 100644 index 000000000..c16937eaf --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java @@ -0,0 +1,15 @@ +package io.shinhanlife.glow; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowLogTarget { + + Target[] value() default {}; + + enum Target { + FILE, CONSOLE + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java new file mode 100644 index 000000000..3da0e4932 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java @@ -0,0 +1,19 @@ +package io.shinhanlife.glow; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class GlowLogger { + + private Logger log = LoggerFactory.getLogger(GlowLogger.class); + + public void debug(String message, Object... args) { log.debug(message, args); } + public void info(String message, Object... args) { log.info(message, args); } + public void warn(String message, Object... args) { log.warn(message, args); } + public void error(String message, Object... args) { log.error(message, args); } + public void error(String message, Throwable t) { log.error(message, t); } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java new file mode 100644 index 000000000..bce1efef9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java @@ -0,0 +1,44 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowMciFieldInfo + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowMciFieldInfo { + + /** + * 필드 순서 + */ + int order(); + + /** + * 필드 길이 (List인 경우 전체 길이로 활용될 수 있음) + */ + int length(); + + /** + * 필드 설명 + */ + String description() default ""; + + /** + * List 매핑 시 대상 DTO 클래스 + */ + Class target() default void.class; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java new file mode 100644 index 000000000..ca50c4420 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow; + +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +/** + * MyBatis Mapper 인터페이스를 나타내는 어노테이션 + * MyBatis Mapper와 동일한 기능을 제공합니다. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Mapper +@Component +public @interface GlowMybatisMapper { +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java new file mode 100644 index 000000000..375a3b6bd --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java @@ -0,0 +1,7 @@ +package io.shinhanlife.glow; + +public @interface GlowServiceGroupId { + String value(); + + String description(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java new file mode 100644 index 000000000..883d7dda1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java @@ -0,0 +1,25 @@ +package io.shinhanlife.glow; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowTrgmField { + + /** + * 필드 순서 + */ + int order(); + + /** + * 필드 길이 + */ + int length(); + + /** + * 필드 설명 + */ + String description() default ""; + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java new file mode 100644 index 000000000..a83c5a223 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java @@ -0,0 +1,68 @@ +package io.shinhanlife.glow; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.ibatis.session.RowBounds; + +import java.io.Serial; +import java.io.Serializable; + +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode(callSuper = true) +public class PageInfo extends RowBounds implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 페이지번호 (입력값) + */ + @GlowTrgmField(order = 1, length = 5, description = "페이지번호") + private int pageNo; + + /** + * 페이지 데이터 건수 (열 건수, 입력값) + */ + @GlowTrgmField(order = 2, length = 5, description = "페이지데이터건수") + private int pageDataCc; + + /** + * 총페이지 수 (리턴값) + */ + @GlowTrgmField(order = 3, length = 10, description = "총페이지수") + private int totaPageCn; + + /** + * 총 페이지 데이터 건수 (리턴값) + */ + @GlowTrgmField(order = 4, length = 10, description = "총페이지데이터건수") + private int totaPageDataCc; + + public PageInfo(int pageNo, int pageDataCc) { + super(((pageNo <= 0 ? 1 : pageNo) - 1) * pageDataCc, pageDataCc); + this.pageNo = pageNo; + this.pageDataCc = pageDataCc; + } + + public PageInfo() { + + } + + @JsonIgnore + @Override + public int getOffset() { + return super.getOffset(); + } + + @JsonIgnore + @Override + public int getLimit() { + return super.getLimit(); + } + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java new file mode 100644 index 000000000..63ffb6939 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java @@ -0,0 +1,31 @@ +package io.shinhanlife.glow; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ResponseCode { + + /* ===================== 성공 ===================== */ + SUCCESS(HttpStatus.OK, "정상 처리되었습니다."), + CREATED(HttpStatus.CREATED, "데이터 생성 성공"), + + /* ===================== 클라이언트 오류 (4xx) ===================== */ + BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "유효하지 않은 파라미터입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 리소스를 찾을 수 없습니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 HTTP 메서드입니다."), + CONFLICT(HttpStatus.CONFLICT, "이미 존재하는 데이터입니다."), + + /* ===================== 서버 오류 (5xx) ===================== */ + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), + SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스를 사용할 수 없습니다."); + + private final HttpStatus status; + private final String message; + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java new file mode 100644 index 000000000..9ebe6b97b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java @@ -0,0 +1,111 @@ +package io.shinhanlife.glow; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public final class ResponseUtil { + + private ResponseUtil() { + + } + + public static ResponseEntity> ok() { + return ResponseEntity.ok( + BaseResponse.builder() + .code(ResponseCode.SUCCESS.getStatus().value()) + .message(ResponseCode.SUCCESS.getMessage()) + .build() + ); + } + + public static ResponseEntity> ok(T data) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(ResponseCode.SUCCESS.getStatus().value()) + .message(ResponseCode.SUCCESS.getMessage()) + .data(data) + .build() + ); + } + + public static ResponseEntity> ok(T data, ResponseCode responseCode) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(responseCode.getStatus().value()) + .message(responseCode.getMessage()) + .data(data) + .build() + ); + } + + public static ResponseEntity> error( + HttpStatus status, String code, String message + ) { + return ResponseEntity.status(status) + .body( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .build() + ) + .build() + ); + } + + public static ResponseEntity> error( + HttpStatus status, String code, String message, String exceptionName, String stackTrace + ) { + return ResponseEntity.status(status) + .body( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .exceptionName(exceptionName) + .exceptionDetail(stackTrace) + .build() + ) + .build() + ); + } + + public static ResponseEntity> okError( + HttpStatus status, String code, String message + ) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .build() + ) + .build() + ); + } + + public static ResponseEntity> okError( + HttpStatus status, String code, String message, String exceptionName, String stackTrace + ) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .exceptionName(exceptionName) + .exceptionDetail(stackTrace) + .build() + ) + .build() + ); + } + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java new file mode 100644 index 000000000..f93b1ea2d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java @@ -0,0 +1,6 @@ +package io.shinhanlife.glow.communication; + +/** Minimal Glow communication contract used by the temporary compatibility layer. */ +public interface ICommunication { + O sync(I request); +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java new file mode 100644 index 000000000..9463acf2b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java @@ -0,0 +1,26 @@ +package io.shinhanlife.glow.communication.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Declares fixed-width Glow transaction-message metadata for a DTO field. */ +@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowTrgmField { + + int order(); + + int length() default 0; + + int decimal() default 0; + + String description() default ""; + + String target() default ""; + + String type() default ""; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java new file mode 100644 index 000000000..7c991d52f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java @@ -0,0 +1,8 @@ +package io.shinhanlife.glow.communication.dto; +import lombok.Data; +@Data +public class CommonHeader { + private String itrfId; + private String rcvSvcId; + private String tgrmDalRsltCd; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java new file mode 100644 index 000000000..7df3c0a7f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java @@ -0,0 +1,6 @@ +package io.shinhanlife.glow.communication.dto; + +public enum HeaderDefaults { + ITRF_ID, RCV_SVC_ID, STR_YMD, ACNT_OGNZ_NO, PSMR_ASRT_CD, SBSN_RULP_ASRT_CD, BSDU_CD, BSQU_CD, + INDV_CTIN_ROLE_CD, SCRN_ID, OGNZ_ASRT_CD, OGNZ_LEVE_CD, TGRM_CREA_CHNN_TYPE_CD, ENVR_TYPE_CD +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java new file mode 100644 index 000000000..31383d775 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java @@ -0,0 +1,20 @@ +package io.shinhanlife.glow.communication.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Transfer { + private Object header; + private T body; + private Class resBodyClass; + private Object message; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java new file mode 100644 index 000000000..464cc52e3 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java @@ -0,0 +1,10 @@ +package io.shinhanlife.glow.communication.exception; + +public class ItrfException extends Exception { + public ItrfException(String msg) { + super(msg); + } + public ItrfException(String msgCd, String msgPrnAttrCd, String msgCt, String anxMsgCt) { + super(msgCd + ": " + msgCt); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java new file mode 100644 index 000000000..99bc00be7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java @@ -0,0 +1,26 @@ +package io.shinhanlife.glow.communication.module.eai.component; + +import io.shinhanlife.glow.communication.dto.Transfer; + +import org.springframework.stereotype.Component; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Component +public class GlowEaiComponent { + public Transfer sync(Transfer request) { + // Mock 구현 + Transfer response = new Transfer<>(); + response.setHeader(request.getHeader()); + response.setResBodyClass((Class) request.getResBodyClass()); + return response; + } + + public Transfer call(Transfer request, Class resBody) { + Transfer response = new Transfer<>(); + response.setHeader(request.getHeader()); + response.setResBodyClass(resBody); + return response; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java new file mode 100644 index 000000000..34265031e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java @@ -0,0 +1,98 @@ +package io.shinhanlife.glow.communication.module.http.component; + +import io.shinhanlife.glow.communication.ICommunication; +import io.shinhanlife.glow.communication.module.http.dto.HttpBody; +import io.shinhanlife.glow.communication.module.http.dto.HttpHeader; +import io.shinhanlife.glow.communication.module.http.dto.HttpTransfer; +import java.util.List; +import java.util.Map; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Temporary compatibility implementation of the internal Glow HTTP component. + * Replace this class with the official Glow HTTP JAR when it is supplied. + */ +@Component +public class GlowHttpComponent implements ICommunication, ResponseEntity> { + + private final RestClient restClient; + + public GlowHttpComponent(RestClient.Builder restClientBuilder) { + this.restClient = restClientBuilder.build(); + } + + @Override + public ResponseEntity sync(HttpTransfer request) { + if (request == null || request.getHeader() == null || request.getMethod() == null + || !StringUtils.hasText(request.getDomain())) { + throw new IllegalArgumentException("Glow HTTP request header, domain, and method are required."); + } + if (HttpMethod.GET.equals(request.getMethod())) { + return get(request); + } + if (HttpMethod.POST.equals(request.getMethod())) { + return post(request); + } + if (HttpMethod.PUT.equals(request.getMethod())) { + return put(request); + } + if (HttpMethod.DELETE.equals(request.getMethod())) { + return delete(request); + } + throw new IllegalArgumentException("Unsupported HTTP method: " + request.getMethod()); + } + + private ResponseEntity get(HttpTransfer request) { + return toHttpBody(restClient.get().uri(buildUri(request, true)) + .headers(headers -> applyHeaders(headers, request.getHeader())) + .retrieve().toEntity(String.class)); + } + + private ResponseEntity post(HttpTransfer request) { + return toHttpBody(restClient.post().uri(buildUri(request, false)) + .contentType(contentType(request)).headers(headers -> applyHeaders(headers, request.getHeader())) + .body(request.getBody()).retrieve().toEntity(String.class)); + } + + private ResponseEntity put(HttpTransfer request) { + return toHttpBody(restClient.put().uri(buildUri(request, false)) + .contentType(contentType(request)).headers(headers -> applyHeaders(headers, request.getHeader())) + .body(request.getBody()).retrieve().toEntity(String.class)); + } + + private ResponseEntity delete(HttpTransfer request) { + return toHttpBody(restClient.delete().uri(buildUri(request, true)) + .headers(headers -> applyHeaders(headers, request.getHeader())) + .retrieve().toEntity(String.class)); + } + + private String buildUri(HttpTransfer request, boolean includeQueryParameters) { + String uri = request.getDomain() + (request.getUri() == null ? "" : request.getUri()); + if (!includeQueryParameters || !(request.getBody() instanceof Map parameters)) { + return uri; + } + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uri); + parameters.forEach((key, value) -> { if (key != null && value != null) builder.queryParam(String.valueOf(key), value); }); + return builder.build().encode().toUriString(); + } + + private MediaType contentType(HttpTransfer request) { + return request.getContentType() == null ? MediaType.APPLICATION_JSON : request.getContentType(); + } + + private void applyHeaders(HttpHeaders target, HttpHeader source) { + target.setAccept(List.of(MediaType.APPLICATION_JSON)); + source.getValues().forEach((name, value) -> { if (StringUtils.hasText(name) && StringUtils.hasText(value)) target.set(name, value); }); + } + + private ResponseEntity toHttpBody(ResponseEntity response) { + return new ResponseEntity<>(new HttpBody(response.getBody()), response.getHeaders(), response.getStatusCode()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java new file mode 100644 index 000000000..2406e7b52 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java @@ -0,0 +1,5 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +/** Raw body returned by the Glow HTTP transport. */ +public record HttpBody(String content) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java new file mode 100644 index 000000000..396f4a7d1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; + +/** Glow HTTP request headers and timeout metadata. */ +@Getter +@Setter +public class HttpHeader { + private Map values = new LinkedHashMap<>(); + private int readTimeout; + + public void set(String name, String value) { + values.put(name, value); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java new file mode 100644 index 000000000..30fb28e9f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java @@ -0,0 +1,19 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; + +/** Glow HTTP request envelope. Use HttpTransfer.http() to build a request. */ +@Getter +@Builder(builderMethodName = "http") +public class HttpTransfer { + private final HttpHeader header; + private final String domain; + private final String uri; + private final HttpMethod method; + private final MediaType contentType; + private final Class responseEntity; + private final T body; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java new file mode 100644 index 000000000..3cdf3179d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java @@ -0,0 +1,28 @@ +package io.shinhanlife.glow.communication.module.mci.component; + +import io.shinhanlife.glow.communication.dto.Transfer; +import org.springframework.stereotype.Component; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Component +public class GlowMciComponent { + + public Transfer sync(Transfer request) { + // 실제 Glow HTTP 통신 (GlowHttpHeaderUtil, HttpClient 등) 수행 시뮬레이션 + Object responseBody = new Object(); + if (request != null && request.getResBodyClass() != null) { + try { + responseBody = request.getResBodyClass().getDeclaredConstructor().newInstance(); + } catch (Exception e) { + // Ignore and fallback to Object + } + } + + return (Transfer) Transfer.builder() + .header(request != null ? request.getHeader() : null) + .body(responseBody) + .build(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java new file mode 100644 index 000000000..fbb154cd6 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow.communication.util; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.HeaderDefaults; +import java.util.Map; +public class CommonHeaderFactory { + public static CommonHeader createRequestHeader(Map commonHeaderMap) { + CommonHeader header = new CommonHeader(); + header.setItrfId(commonHeaderMap.get(HeaderDefaults.ITRF_ID)); + header.setRcvSvcId(commonHeaderMap.get(HeaderDefaults.RCV_SVC_ID)); + return header; + } + public static CommonHeader createRequestHeader(String itrfId, String rcvSvcId) { + CommonHeader header = new CommonHeader(); + header.setItrfId(itrfId); + header.setRcvSvcId(rcvSvcId); + return header; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java new file mode 100644 index 000000000..605362c19 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java @@ -0,0 +1,21 @@ +package io.shinhanlife.glow.db.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +public class AuditInfo { + private Date systRgiDt; // 시스템등록일시 + private String systRgiPrafNo; // 시스템등록인사번호 + private String systRgiOgnzNo; // 시스템등록조직번호 + private String systRgiSystCd; // 시스템등록시스템코드 + private String systRgiPrgrId; // 시스템등록프로그램ID + private Date systChgDt; // 시스템변경일시 + private String systChgPrafNo; // 시스템변경인사번호 + private String systChgOgnzNo; // 시스템변경조직번호 + private String systChgSystCd; // 시스템변경시스템코드 + private String systChgPrgrId; // 시스템변경프로그램ID +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnum.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnum.java new file mode 100644 index 000000000..3e7b94d64 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnum.java @@ -0,0 +1,11 @@ +package io.shinhanlife.glow.db.typehandler; + +/** + * glow 프레임워크 스텁 (실제 라이브러리가 없는 로컬 개발 환경용 더미). + * 코드값을 갖는 enum이 공통으로 구현하는 인터페이스 — {@link CodeEnumTypeHandler}가 이 getCode()로 + * DB 컬럼(String)과 enum 상수를 상호 변환한다. + */ +public interface CodeEnum { + + String getCode(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnumTypeHandler.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnumTypeHandler.java new file mode 100644 index 000000000..b0dc8a8ea --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/typehandler/CodeEnumTypeHandler.java @@ -0,0 +1,59 @@ +package io.shinhanlife.glow.db.typehandler; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * glow 프레임워크 스텁 (실제 라이브러리가 없는 로컬 개발 환경용 더미). + * {@link CodeEnum}을 구현하는 코드 enum과 DB 문자열 컬럼(코드값)을 상호 변환하는 MyBatis TypeHandler + * 공통 베이스. common/enums/type의 {@code {ClassName}TypeHandler}는 모두 이 클래스를 상속하고, + * 생성자에서 자신의 enum 타입을 super(...)로 넘기기만 한다. + */ +public abstract class CodeEnumTypeHandler & CodeEnum> extends BaseTypeHandler { + + private final Class type; + + protected CodeEnumTypeHandler(Class type) { + if (type == null) { + throw new IllegalArgumentException("Type argument cannot be null"); + } + this.type = type; + } + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, parameter.getCode()); + } + + @Override + public E getNullableResult(ResultSet rs, String columnName) throws SQLException { + return toEnum(rs.getString(columnName)); + } + + @Override + public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return toEnum(rs.getString(columnIndex)); + } + + @Override + public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return toEnum(cs.getString(columnIndex)); + } + + private E toEnum(String code) { + if (code == null) { + return null; + } + for (E constant : type.getEnumConstants()) { + if (constant.getCode().equals(code)) { + return constant; + } + } + throw new IllegalArgumentException("알 수 없는 코드 [" + code + "] - " + type.getName()); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java new file mode 100644 index 000000000..5216ae1e8 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java @@ -0,0 +1,105 @@ +package io.shinhanlife.glow.util; + +import io.shinhanlife.glow.GlowMciFieldInfo; +import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @package io.shinhanlife.glow.util + * @className GlowMciParser + * @description MCI 고정 길이 전문 파싱 유틸리티 (GlowMciFieldInfo 기반) + * @author 0986406 + * @create 2026.09.01 + */ +@Slf4j +public class GlowMciParser { + + public static T parse(String mciString, Class clazz) { + if (mciString == null || mciString.isEmpty()) { + return null; + } + + try { + T instance = clazz.getDeclaredConstructor().newInstance(); + + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowMciFieldInfo.class)) { + fields.add(field); + } + } + + fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowMciFieldInfo.class).order())); + + int currentIndex = 0; + for (Field field : fields) { + GlowMciFieldInfo annotation = field.getAnnotation(GlowMciFieldInfo.class); + int length = annotation.length(); + + if (currentIndex >= mciString.length()) { + break; + } + + int endIndex = Math.min(currentIndex + length, mciString.length()); + String value = mciString.substring(currentIndex, endIndex); + + field.setAccessible(true); + setFieldValue(instance, field, value, annotation); + + currentIndex += length; + } + + return instance; + } catch (Exception e) { + log.error("GlowMciFieldInfo 파싱 중 오류 발생: {}", e.getMessage(), e); + throw new RuntimeException("MCI 전문 파싱 오류", e); + } + } + + private static void setFieldValue(Object instance, Field field, String value, GlowMciFieldInfo annotation) throws IllegalAccessException { + Class fieldType = field.getType(); + String trimmedValue = value.trim(); + + if (fieldType == String.class) { + field.set(instance, trimmedValue); + } else if (fieldType == int.class || fieldType == Integer.class) { + field.set(instance, trimmedValue.isEmpty() ? 0 : Integer.parseInt(trimmedValue)); + } else if (fieldType == long.class || fieldType == Long.class) { + field.set(instance, trimmedValue.isEmpty() ? 0L : Long.parseLong(trimmedValue)); + } else if (fieldType == boolean.class || fieldType == Boolean.class) { + field.set(instance, Boolean.parseBoolean(trimmedValue)); + } else if (fieldType == double.class || fieldType == Double.class) { + field.set(instance, trimmedValue.isEmpty() ? 0.0 : Double.parseDouble(trimmedValue)); + } else if (List.class.isAssignableFrom(fieldType)) { + Class targetClass = annotation.target(); + if (targetClass != void.class) { + List list = new ArrayList<>(); + int itemLength = calculateTotalLength(targetClass); + if (itemLength > 0) { + for (int i = 0; i < value.length(); i += itemLength) { + int end = Math.min(i + itemLength, value.length()); + String itemStr = value.substring(i, end); + if (itemStr.trim().isEmpty()) continue; + list.add(parse(itemStr, targetClass)); + } + } + field.set(instance, list); + } + } else { + field.set(instance, trimmedValue); + } + } + + private static int calculateTotalLength(Class clazz) { + int total = 0; + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowMciFieldInfo.class)) { + total += field.getAnnotation(GlowMciFieldInfo.class).length(); + } + } + return total; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java new file mode 100644 index 000000000..1b89ede9a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java @@ -0,0 +1,92 @@ +package io.shinhanlife.glow.util; + +import io.shinhanlife.glow.communication.annotation.GlowTrgmField; +import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @package io.shinhanlife.glow.util + * @className GlowTrgmParser + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +@Slf4j +public class GlowTrgmParser { + + /** + * 고정 길이 문자열 전문을 @GlowTrgmField 어노테이션 정보에 따라 DTO 객체로 파싱합니다. + * @param trgmString 원본 전문 문자열 + * @param clazz 매핑할 DTO 클래스 + * @return 파싱된 DTO 인스턴스 + */ + public static T parse(String trgmString, Class clazz) { + if (trgmString == null || trgmString.isEmpty()) { + return null; + } + + try { + T instance = clazz.getDeclaredConstructor().newInstance(); + + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowTrgmField.class)) { + fields.add(field); + } + } + + fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowTrgmField.class).order())); + + int currentIndex = 0; + for (Field field : fields) { + GlowTrgmField annotation = field.getAnnotation(GlowTrgmField.class); + int length = annotation.length(); + + if (currentIndex >= trgmString.length()) { + break; + } + + int endIndex = Math.min(currentIndex + length, trgmString.length()); + String value = trgmString.substring(currentIndex, endIndex).trim(); + + field.setAccessible(true); + setFieldValue(instance, field, value); + + currentIndex += length; + } + + return instance; + } catch (Exception e) { + log.error("GlowTrgmField 파싱 중 오류 발생: {}", e.getMessage(), e); + throw new RuntimeException("전문 파싱 오류", e); + } + } + + private static void setFieldValue(Object instance, Field field, String value) throws IllegalAccessException { + Class fieldType = field.getType(); + + if (fieldType == String.class) { + field.set(instance, value); + } else if (fieldType == int.class || fieldType == Integer.class) { + field.set(instance, value.isEmpty() ? 0 : Integer.parseInt(value)); + } else if (fieldType == long.class || fieldType == Long.class) { + field.set(instance, value.isEmpty() ? 0L : Long.parseLong(value)); + } else if (fieldType == boolean.class || fieldType == Boolean.class) { + field.set(instance, Boolean.parseBoolean(value)); + } else if (fieldType == double.class || fieldType == Double.class) { + field.set(instance, value.isEmpty() ? 0.0 : Double.parseDouble(value)); + } else { + field.set(instance, value); + } + } +} diff --git a/dap-was-lib/src/main/resources/glow/application-glow-dev.yml b/dap-was-lib/src/main/resources/glow/application-glow-dev.yml new file mode 100644 index 000000000..e8b864cf6 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-dev.yml @@ -0,0 +1,19 @@ +# Glow 개발 환경 설정 +spring: + config: + activate: + on-profile: dev + +glow: + communication: + common: + env-type: D + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST:https://dev-ichmci.shinhanlife.co.kr} + port: ${GLOW_COMMUNICATION_MCI_PORT:26160} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST:http://host.docker.internal} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT:8080} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST:tcp://host.docker.internal} + port: ${GLOW_COMMUNICATION_EAI_PORT:9999} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow-local.yml b/dap-was-lib/src/main/resources/glow/application-glow-local.yml new file mode 100644 index 000000000..0a9b014e9 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-local.yml @@ -0,0 +1,41 @@ +# Glow 로컬 환경 설정 +spring: + config: + activate: + on-profile: local + +glow: + communication: + common: + env-type: D + # Direct local process uses WireMock host port mapped by docker-compose. + http: + connection-timeout: 5 + read-timeout: 5 + api-list: + - name: memo + domain: ${AXHUB_MEMO_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_MEMO_HTTP_URL:/api/mock/http/cmm_memo_retriever} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + - name: insurance + domain: ${AXHUB_INSURANCE_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_INSURANCE_HTTP_URL:/api/mock/http/ins_insurance_processor} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_MCI_PORT:8080} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT:8080} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_EAI_PORT:8080} + +axhub: + mock: + http: + enabled: true diff --git a/dap-was-lib/src/main/resources/glow/application-glow-prod.yml b/dap-was-lib/src/main/resources/glow/application-glow-prod.yml new file mode 100644 index 000000000..cf51ecb25 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-prod.yml @@ -0,0 +1,19 @@ +# Glow 운영 환경 설정 +spring: + config: + activate: + on-profile: prod + +glow: + communication: + common: + env-type: P + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST} + port: ${GLOW_COMMUNICATION_MCI_PORT} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST} + port: ${GLOW_COMMUNICATION_EAI_PORT} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow-test.yml b/dap-was-lib/src/main/resources/glow/application-glow-test.yml new file mode 100644 index 000000000..600840906 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-test.yml @@ -0,0 +1,19 @@ +# Glow 테스트 환경 설정 +spring: + config: + activate: + on-profile: test + +glow: + communication: + common: + env-type: T + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST} + port: ${GLOW_COMMUNICATION_MCI_PORT} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST} + port: ${GLOW_COMMUNICATION_EAI_PORT} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow.yml b/dap-was-lib/src/main/resources/glow/application-glow.yml new file mode 100644 index 000000000..9beff3ed5 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow.yml @@ -0,0 +1,41 @@ +# AX HUB Tool Pod 공통 Glow Framework 설정입니다. +# Tool Pod의 application-{profile}.yml에서 이 파일과 환경별 Glow 설정을 함께 import합니다. +logging: + level: + root: INFO + io.shinhanlife.axhub: DEBUG + io.shinhanlife.glow: INFO + org.springframework.web: INFO + pattern: + console: "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger{36} - %msg%n" + +spring: + data: + redis: + host: ${SPRING_DATA_REDIS_HOST:127.0.0.1} + port: ${SPRING_DATA_REDIS_PORT:6379} + password: ${SPRING_DATA_REDIS_PASSWORD:} + +glow: + communication: + http: + connection-timeout: 5 + read-timeout: 5 + # HTTP Tool target catalog. Scaffold adds local mock entries after a Tool is created. + api-list: [] + mci: + uri: /ntl_mci/dap_rcv + receive-uri: /itrf/mciReceive + connection-timeout: 300 + read-timeout: 300 + encoding: UTF-8 + extmci: + uri: /extmci + json-uri: /extmciJson + receive-uri: /app/extMciReceive + connection-timeout: 5 + read-timeout: 30 + encoding: EUC-KR + websocket: + endpoint: /ws-glow + allowed-origins: "*" diff --git a/dap-was-lib/src/main/resources/mock-responses.json b/dap-was-lib/src/main/resources/mock-responses.json new file mode 100644 index 000000000..7d6abfed8 --- /dev/null +++ b/dap-was-lib/src/main/resources/mock-responses.json @@ -0,0 +1,106 @@ +{ + "BILL_001": { + "status": "SUCCESS", + "message": "청구 심사 상태 조회가 완료되었습니다.", + "data": { + "billingStatus": "PROCESSING", + "expectedCompletionDate": "2026-07-05" + } + }, + "BILL_002": { + "status": "SUCCESS", + "message": "청구 처리가 완료되었습니다.", + "data": { + "processId": "PRC-998811", + "result": "APPROVED" + } + }, + "BOND_001": { + "status": "SUCCESS", + "message": "디지털 증권 발행 가능 한도 조회가 완료되었습니다.", + "data": { + "availableLimit": 500000000, + "currency": "KRW" + } + }, + "BOND_002": { + "status": "SUCCESS", + "message": "디지털 증권 발행이 성공적으로 완료되었습니다.", + "data": { + "bondId": "BND-2026-07-04-1234", + "issuedAmount": 10000000 + } + }, + "HR_VAC_01": { + "status": "SUCCESS", + "message": "연차 휴가 등록이 완료되었습니다.", + "data": { + "vacationId": "VAC-8877", + "status": "APPROVED" + } + }, + "HR_VAC_02": { + "status": "SUCCESS", + "message": "잔여 연차 일수 조회가 완료되었습니다.", + "data": { + "totalDays": 15, + "usedDays": 3, + "remainingDays": 12 + } + }, + "COM_SMS_01": { + "status": "SUCCESS", + "message": "SMS 발송이 성공적으로 완료되었습니다.", + "data": { + "messageId": "SMS-11223344", + "sentTime": "2026-07-04 10:00:00" + } + }, + "COM_EML_01": { + "status": "SUCCESS", + "message": "이메일 발송이 성공적으로 완료되었습니다.", + "data": { + "emailId": "EML-998877", + "sentTime": "2026-07-04 10:00:00" + } + }, + "CNTR_001": { + "status": "SUCCESS", + "message": "계약 상태 조회가 완료되었습니다.", + "data": { + "contractStatus": "ACTIVE", + "startDate": "2024-01-01" + } + }, + "CNTR_002": { + "status": "SUCCESS", + "message": "계약 상세 내역 조회가 완료되었습니다.", + "data": { + "productName": "신한 라이프 스마트 연금보험", + "monthlyPremium": 500000 + } + }, + "CRM_001": { + "status": "SUCCESS", + "message": "고객 등급 조회가 완료되었습니다.", + "data": { + "customerGrade": "VIP", + "loyaltyPoints": 15000 + } + }, + "CRM_002": { + "status": "SUCCESS", + "message": "고객 상세 정보 조회가 완료되었습니다.", + "data": { + "address": "서울특별시 중구 세종대로 9", + "phoneNumber": "010-XXXX-XXXX" + } + }, + "PAY_001": { + "status": "SUCCESS", + "message": "결제가 성공적으로 승인되었습니다.", + "data": { + "processStatus": "COMPLETED" + } + } +} diff --git a/dap-was-lib/src/main/resources/static/tool-test-console.html b/dap-was-lib/src/main/resources/static/tool-test-console.html new file mode 100644 index 000000000..85644db9e --- /dev/null +++ b/dap-was-lib/src/main/resources/static/tool-test-console.html @@ -0,0 +1,356 @@ + + + + + + AX HUB Tool Test Console + + + + + +
+
+ +
+ + Manifest loading + v0.0.1 +
+
+
+
+

Schema 기반 Tool 테스트

Tool을 선택하고 요청 JSON을 확인한 뒤 실행하세요. 검증한 요청은 브라우저에 저장되며, 저장된 케이스 전체를 한 번에 다시 실행할 수 있습니다.

+
+ +
+

2. 요청 JSON

필수값과 형식은 Tool의 inputSchema 기준입니다. MCI·외부 연동 Tool은 업무에 맞는 테스트 데이터를 입력한 후 저장하세요.
+

3. 실행 결과

대기-trace-id: -request-id: -
Tool을 선택하고 실행하세요.
+
+
+ +
+ + + + diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java new file mode 100644 index 000000000..773659d40 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.lib.adapter.test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MockEimsHttpServerTest { + + @Test + void returnsTheScaffoldGeneratedJsonResponse() { + MockEimsHttpServer server = new MockEimsHttpServer(new ObjectMapper()); + + var response = server.mockToolHttpResponse("cmm_memo_retriever", null); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + assertThat(response.getBody().path("resultCode").asText()).isEqualTo("SUCCESS"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java new file mode 100644 index 000000000..9ee767a27 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.lib.common.adapter.sender; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.integration.mci.dto.MciRequestWrapper; +import io.shinhanlife.dap.lib.integration.mci.dto.ShinhanCommonHeaderDto; +import lombok.Data; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @package io.shinhanlife.dap.lib.adapter.sender + * @className ShinhanMciSenderTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +class ShinhanMciSenderTest { + + @Data + static class SampleBody { + private String msgCd; + private String anxMsgCt; + } + + @Test + void testJsonUnwrapped() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + ShinhanCommonHeaderDto header = new ShinhanCommonHeaderDto(); + header.setGlbId("20211115150135679009808815NCS17007887"); + header.setPgrsSriaNo("002"); + header.setItrIfId("NCSBACO00001"); + + SampleBody body = new SampleBody(); + body.setMsgCd("12345"); + body.setAnxMsgCt("Test Message"); + + MciRequestWrapper wrapper = new MciRequestWrapper<>(); + wrapper.setTgrmCmnnhddValu(header); + wrapper.setBody(body); + + String json = mapper.writeValueAsString(wrapper); + + System.out.println(json); + + // 검증: body 필드가 json root 레벨에 평탄화되어 있는지 확인 + assertThat(json).contains("\"tgrmCmnnhddValu\":{"); + assertThat(json).contains("\"msgCd\":\"12345\""); + assertThat(json).contains("\"anxMsgCt\":\"Test Message\""); + assertThat(json).doesNotContain("\"body\":"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java new file mode 100644 index 000000000..4e7f10f59 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.lib.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.client.RestClient; + +class AxhubHttpConfigurationTest { + + @Test + void registersGlowHttpComponentFromDapLibConfiguration() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class)) { + assertThat(context.getBean(GlowHttpComponent.class)).isNotNull(); + } + } + + @Configuration + @Import(AxhubHttpConfiguration.class) + static class TestConfiguration { + + @Bean + RestClient.Builder restClientBuilder() { + return RestClient.builder(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java new file mode 100644 index 000000000..c33789f25 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.lib.config; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +class ToolSchemaConfigurationTest { + + @Test + void providesToolArgumentSchemaValidatorWithoutComponentScanning() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.registerBean(ObjectMapper.class); + context.register(ToolSchemaConfiguration.class); + context.refresh(); + + assertNotNull(context.getBean(ToolArgumentSchemaValidator.class)); + } + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java new file mode 100644 index 000000000..074a0b292 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java @@ -0,0 +1,67 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +class AxhubHttpComponentTest { + + @Test + void callByApiNameBuildsGlowTransferAndDeserializesJsonResponse() { + RestClient.Builder builder = RestClient.builder(); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + GlowHttpComponent glowHttpComponent = new GlowHttpComponent(builder); + AxhubHttpProperties properties = new AxhubHttpProperties(); + properties.setApiList(List.of(new AxhubHttpProperties.ApiDefinition( + "status", "https://api.example.test", "/v1", HttpMethod.GET, "application/json", false))); + AxhubHttpComponent component = new AxhubHttpComponent( + glowHttpComponent, new ObjectMapper(), new GlowCommunicationProperties(), properties); + + server.expect(requestTo("https://api.example.test/v1/status")) + .andExpect(header("X-ANONYMOUS-REQ", "AXHUB-TOOL")) + .andRespond(withSuccess("{\"status\":\"OK\"}", APPLICATION_JSON)); + + SampleResponse response = component.call("status", "/status", null, SampleResponse.class); + + assertThat(response.status()).isEqualTo("OK"); + server.verify(); + } + + @Test + void callByApiNameUsesConfiguredUrlMethodContentTypeAndBizPodHeader() { + RestClient.Builder builder = RestClient.builder(); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + GlowHttpComponent glowHttpComponent = new GlowHttpComponent(builder); + AxhubHttpProperties properties = new AxhubHttpProperties(); + properties.setApiList(List.of(new AxhubHttpProperties.ApiDefinition( + "employee", "https://employee.example.test", "/itrf/employee", HttpMethod.POST, + "application/json;charset=UTF-8", true))); + AxhubHttpComponent component = new AxhubHttpComponent( + glowHttpComponent, new ObjectMapper(), new GlowCommunicationProperties(), properties); + + server.expect(requestTo("https://employee.example.test/itrf/employee")) + .andExpect(method(HttpMethod.POST)) + .andExpect(header("Content-Type", "application/json;charset=UTF-8")) + .andExpect(header("X-POD-TO-POD", "true")) + .andRespond(withSuccess("{\"status\":\"OK\"}", APPLICATION_JSON)); + + SampleResponse response = component.call("employee", "{\"employeeId\":\"EMP10001\"}", SampleResponse.class); + + assertThat(response.status()).isEqualTo("OK"); + server.verify(); + } + record SampleResponse(String status) { + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpSdk2CompatibilityTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpSdk2CompatibilityTest.java new file mode 100644 index 000000000..9cb357f7b --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpSdk2CompatibilityTest.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.modelcontextprotocol.json.schema.jackson2.DefaultJsonSchemaValidator; +import io.modelcontextprotocol.spec.McpSchema; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class McpSdk2CompatibilityTest { + + @Test + void usesMcpJavaSdkTwo() { + assertEquals("2.0.0", McpSchema.class.getPackage().getImplementationVersion()); + } + + @Test + void validatesJsonSchemaWithTheNetworkntVersionRequiredByMcpSdkTwo() { + DefaultJsonSchemaValidator validator = new DefaultJsonSchemaValidator(); + + var response = validator.validateSchema(Map.of( + "$schema", McpSchema.JSON_SCHEMA_DIALECT_2020_12, + "type", "object", + "properties", Map.of("employeeId", Map.of("type", "string")))); + + assertTrue(response.valid(), response.errorMessage()); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java new file mode 100644 index 000000000..647d4229c --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java @@ -0,0 +1,57 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.shinhanlife.dap.lib.config.McpProperties; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.context.support.StaticApplicationContext; + +class McpToolMethodRegistryTest { + + @Test + void indexesAnnotatedToolDuringInitialization() { + StaticApplicationContext context = contextWith(new EchoTool()); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + + registry.initialize(); + + McpToolMethodRegistry.RegisteredTool tool = registry.find("cmm_echo_search"); + assertNotNull(tool); + assertEquals("execute", tool.method().getName()); + assertEquals("cmm_echo_search", tool.annotation().name()); + } + + @Test + void rejectsDuplicateToolNamesDuringInitialization() { + StaticApplicationContext context = contextWith(new EchoTool(), new DuplicateEchoTool()); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + + assertThrows(IllegalStateException.class, registry::initialize); + } + + private StaticApplicationContext contextWith(Object... tools) { + StaticApplicationContext context = new StaticApplicationContext(); + for (int index = 0; index < tools.length; index++) { + context.getBeanFactory().registerSingleton("tool" + index, tools[index]); + } + context.refresh(); + return context; + } + + static class EchoTool { + @McpTool(name = "cmm_echo_search") + public String execute(String request) { + return request; + } + } + + static class DuplicateEchoTool { + @McpTool(name = "cmm_echo_search") + public String execute(String request) { + return request; + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java new file mode 100644 index 000000000..7b3fd5779 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java @@ -0,0 +1,62 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springaicommunity.mcp.annotation.McpToolParam; +import org.springframework.context.support.StaticApplicationContext; + +class McpToolExecutionServiceTest { + + @Test + void returnsNotFoundWhenNoToolMatchesTheRequestedName() { + McpToolExecutionService service = serviceWith(new Object()); + ToolExecutionResult result = service.execute("missing.cmm.tool.inquiry", null, Map.of()); + assertEquals(404, result.statusCode()); + assertEquals("TOOL_NOT_FOUND", ((Map) result.body()).get("code")); + } + + @Test + void convertsArgumentsToDtoAndExecutesTheMatchedTool() { + McpToolExecutionService service = serviceWith(new EchoTool()); + ToolExecutionResult result = service.execute("sample.cmm.value.echo", + new McpRequestHeaders("gateway-1", "trace-1", "request-1", "employee-1"), Map.of("value", "hello")); + assertEquals(200, result.statusCode()); + assertEquals("hello", ((Map) result.body()).get("value")); + assertEquals("trace-1", result.headers().get("trace-id")); + assertEquals("request-1", result.headers().get("request-id")); + } + + private McpToolExecutionService serviceWith(Object toolBean) { + ObjectMapper objectMapper = new ObjectMapper(); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("toolBean", toolBean); + context.refresh(); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + registry.initialize(); + return new McpToolExecutionService(registry, objectMapper, + new ToolArgumentSchemaValidator(objectMapper), new ToolSchemaResolver(objectMapper)); + } + + static class EchoTool { + @McpTool(name = "sample.cmm.value.echo", description = "Echoes a value") + @GrowToolHint + public Map execute(EchoRequest request) { + return Map.of("value", request.value); + } + } + + static class EchoRequest { + @McpToolParam(description = "Value", required = true) + private String value; + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java new file mode 100644 index 000000000..f91639d08 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.context.ApplicationContext; + +class ToolRegistryHeartbeatSenderTest { + + @Test + void excludesRegisterFalseToolFromGatewayRegistrationTargets() throws Exception { + ApplicationContext applicationContext = mock(ApplicationContext.class); + when(applicationContext.getBeansOfType(Object.class)).thenReturn(Map.of("disabledTool", new DisabledTool())); + ToolRegistryHeartbeatSender sender = new ToolRegistryHeartbeatSender( + applicationContext, new ObjectMapper(), new McpProperties(), mock(ToolSchemaResolver.class)); + + setField(sender, "podUrl", "http://localhost:8084"); + + sender.init(); + + assertThat(sender.getAllScannedTools()).singleElement() + .extracting(tool -> tool.getIsRegistered()) + .isEqualTo(false); + assertThat(registeredTools(sender)).isEmpty(); + } + + private void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } + @SuppressWarnings("unchecked") + private List registeredTools(ToolRegistryHeartbeatSender sender) throws Exception { + Field field = ToolRegistryHeartbeatSender.class.getDeclaredField("registeredTools"); + field.setAccessible(true); + return (List) field.get(sender); + } + + static class DisabledTool { + + @McpTool(name = "test_disabled_tool") + @GrowToolHint + void execute() { + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepositoryTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepositoryTest.java new file mode 100644 index 000000000..f423e1bb8 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/metadata/ToolDefinitionRepositoryTest.java @@ -0,0 +1,73 @@ +package io.shinhanlife.dap.lib.metadata; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.io.DefaultResourceLoader; + +class ToolDefinitionRepositoryTest { + + @Test + void springCanCreateRepositoryWithoutDefaultConstructor() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.register(ToolDefinitionRepository.class); + context.refresh(); + + assertTrue(context.getBean(ToolDefinitionRepository.class) + .findByName("cmm_claim_search").isPresent()); + } + } + + @Test + void loadsAndCachesAValidV17DefinitionByToolName() { + ToolDefinitionRepository repository = new ToolDefinitionRepository( + new ObjectMapper(new YAMLFactory()), new DefaultResourceLoader(), + "classpath*:tool-definitions/**/*.yml"); + + ToolDefinition definition = repository.findByName("cmm_claim_search").orElseThrow(); + + assertEquals("보험금 청구 상태 조회", definition.displayName()); + assertEquals("cmm", definition.categoryKey()); + assertEquals(3, definition.exampleQueries().size()); + assertEquals(false, definition.parametersSchema().get("additionalProperties")); + } + + @Test + void rejectsDefinitionWithFewerThanThreeExampleQueries() { + ToolDefinition invalid = validDefinition().withExampleQueries(java.util.List.of("청구 상태 알려줘")); + + IllegalStateException error = assertThrows(IllegalStateException.class, + () -> ToolDefinitionValidator.validate(invalid, "memory:invalid")); + + assertTrue(error.getMessage().contains("example_queries")); + } + + @Test + void rejectsNonStandardToolName() { + ToolDefinition invalid = validDefinition().withName("cmm_claim.Search"); + + IllegalStateException error = assertThrows(IllegalStateException.class, + () -> ToolDefinitionValidator.validate(invalid, "memory:invalid")); + + assertTrue(error.getMessage().contains("name")); + } + + private ToolDefinition validDefinition() { + return new ToolDefinition( + "cmm_claim_search", "보험금 청구 상태 조회", "1.0.0", "cmm", + new ToolDescription("청구 상태를 조회한다.", "상태 확인 시 사용한다.", + "청구 접수 시 사용하지 않는다.", "청구번호가 필요하다."), + "보험금 청구 상태를 조회합니다.", + java.util.List.of("청구 상태 알려줘", "심사 결과 조회해줘", "계약번호로 청구를 찾아줘"), + true, false, true, + java.util.Map.of("type", "object", "properties", java.util.Map.of(), + "additionalProperties", false), + null, + java.util.List.of("보험금"), null, java.util.List.of(), "MCP_TOOL"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java new file mode 100644 index 000000000..2381031a2 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java @@ -0,0 +1,130 @@ +package io.shinhanlife.dap.lib.util; + + +import org.springaicommunity.mcp.annotation.McpToolParam; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class JsonSchemaGeneratorTest { + + @Test + void includesMcpParameterConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class); + Map> properties = properties(schema); + + assertTrue(((List) schema.get("required")).contains("phoneNumber")); + assertTrue(((List) schema.get("required")).contains("approvalStatus")); + assertEquals("^01[0-9]{8,9}$", properties.get("phoneNumber").get("pattern")); + assertEquals(1L, properties.get("amount").get("minimum")); + assertEquals(List.of("APPROVE", "REJECT"), properties.get("approvalStatus").get("enum")); + } + + @Test + void includesExtendedMcpValidationConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class); + + assertEquals(50L, property(schema, "pageSize").get("maximum")); + assertEquals(20L, property(schema, "pageSize").get("default")); + assertEquals(1, property(schema, "reference").get("minLength")); + assertEquals(30, property(schema, "reference").get("maxLength")); + } + + @Test + void includesNestedDtoConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(NestedRequest.class); + Map childSchema = property(schema, "child"); + + assertEquals("object", childSchema.get("type")); + assertTrue(required(childSchema).contains("businessDate")); + assertEquals("^\\d{8}$", property(childSchema, "businessDate").get("pattern")); + } + + @Test + void includesNestedDtoSchemaForListItems() { + Map schema = JsonSchemaGenerator.generateSchema(ListRequest.class); + Map itemSchema = map(property(schema, "items").get("items")); + + assertEquals("object", itemSchema.get("type")); + assertTrue(required(itemSchema).contains("businessDate")); + } + + @Test + void validatorRejectsInvalidNestedValue() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) + .getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class))); + List errors = schema.validate(objectMapper.writeValueAsString(Map.of( + "child", Map.of("businessDate", "2026-07-28"))), InputFormat.JSON); + + assertFalse(errors.isEmpty()); + } + + @SuppressWarnings("unchecked") + private Map> properties(Map schema) { + return (Map>) schema.get("properties"); + } + + private Map property(Map schema, String name) { + return properties(schema).get(name); + } + + @SuppressWarnings("unchecked") + private Map map(Object value) { + return (Map) value; + } + + @SuppressWarnings("unchecked") + private List required(Map schema) { + return (List) schema.get("required"); + } + + private static class ValidatedRequest { + @McpToolParam(description = "recipient phone number", required = true) + @io.swagger.v3.oas.annotations.media.Schema(pattern = "^01[0-9]{8,9}$") + private String phoneNumber; + + @McpToolParam(description = "issue amount", required = true) + @io.swagger.v3.oas.annotations.media.Schema(minimum = "1") + private Long amount; + + @McpToolParam(description = "approval result") + @io.swagger.v3.oas.annotations.media.Schema( + requiredMode = io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED, + allowableValues = {"APPROVE", "REJECT"}) + private String approvalStatus; + + @McpToolParam(description = "page size") + @io.swagger.v3.oas.annotations.media.Schema(maximum = "50", defaultValue = "20") + private Integer pageSize; + + @McpToolParam(description = "reference") + @io.swagger.v3.oas.annotations.media.Schema(minLength = 1, maxLength = 30) + private String reference; + } + + private static class NestedRequest { + private NestedChild child; + } + + private static class ListRequest { + private List items; + } + + private static class NestedChild { + @io.swagger.v3.oas.annotations.media.Schema( + requiredMode = io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED, + pattern = "^\\d{8}$") + private String businessDate; + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java new file mode 100644 index 000000000..2fd79d9c6 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java @@ -0,0 +1,66 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class PodScaffolderTest { + + @TempDir + Path root; + + @Test + void generatesBuildGradleUsingRepositoryStandard() throws Exception { + PodScaffolder.scaffoldPod(root, "dap-was-sample", "8099", "sample", "tester", "2026.08.07"); + + String buildGradle = Files.readString(root.resolve("dap-was-sample/build.gradle")); + assertTrue(buildGradle.contains("id 'org.springframework.boot'")); + assertTrue(buildGradle.contains("Spring Boot 3.5.11")); + assertTrue(buildGradle.contains("implementation project(':dap-was-lib')")); + assertFalse(buildGradle.contains("compileOnly 'org.projectlombok:lombok")); + } + + @Test + void generatesOthStandardRuntimeResources() throws Exception { + PodScaffolder.scaffoldPod(root, "dap-was-sample", "8099", "sample", "tester", "2026.08.07"); + + Path resources = root.resolve("dap-was-sample/src/main/resources"); + String dockerfile = Files.readString(root.resolve("dap-was-sample/Dockerfile")); + + assertTrue(dockerfile.contains("RUN apk add --no-cache tzdata")); + assertTrue(dockerfile.contains("ENV TZ=Asia/Seoul")); + assertTrue(dockerfile.contains("COPY dap-was-sample/build/libs/*-SNAPSHOT.jar app.jar")); + assertTrue(dockerfile.contains("EXPOSE 8099")); + assertTrue(Files.exists(resources.resolve("application-test.yml"))); + assertTrue(Files.exists(resources.resolve("application-prod.yml"))); + assertTrue(Files.readString(resources.resolve("application-test.yml")) + .contains("${AXHUB_GATEWAY_URL}")); + assertTrue(Files.readString(resources.resolve("application-prod.yml")) + .contains("${AXHUB_TOOL_URL}")); + assertTrue(Files.readString(resources.resolve("application-local.yml")) + .contains("classpath:glow/application-glow-local.yml")); + assertTrue(Files.readString(resources.resolve("application-dev.yml")) + .contains("classpath:glow/application-glow-dev.yml")); + assertTrue(Files.readString(resources.resolve("application-test.yml")) + .contains("classpath:glow/application-glow-test.yml")); + assertTrue(Files.readString(resources.resolve("application-prod.yml")) + .contains("classpath:glow/application-glow-prod.yml")); + } + + @Test + void generatesRedisOptionalPodComposeConfiguration() throws Exception { + Files.writeString(root.resolve("settings.gradle"), "rootProject.name = 'test'\n"); + Files.writeString(root.resolve("docker-compose.yml"), "services:\n"); + + PodScaffolder.scaffoldPod(root, "dap-was-pro", "8085", "pro", "tester", "2026.08.13"); + + String compose = Files.readString(root.resolve("docker-compose.yml")); + assertTrue(compose.contains("was-pro:")); + assertFalse(compose.contains("depends_on:\n - redis")); + assertFalse(compose.contains("SPRING_REDIS_HOST=redis")); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java new file mode 100644 index 000000000..db22407f7 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java @@ -0,0 +1,487 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionValidator; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ToolScaffolderTest { + + @Test + void exposesGroupedUseCaseScaffoldApi() { + assertDoesNotThrow(() -> ToolScaffolder.class.getMethod( + "scaffoldUseCase", String.class, String.class, String.class, String.class, List.class)); + } + + @Test + void generatesOneUseCaseWithTwoMcpToolMethodsAndTypedClients() throws Exception { + String moduleName = root.resolve("dap-was-customer").toString(); + ToolScaffolder.scaffoldUseCase("Customer", moduleName, "tester", "2026.08.12", List.of( + new ToolScaffolder.ToolMethodDefinition( + "CustomerGuidance", "searchGuidance", "CTMNILO00007", "Customer guidance", "Search guidance", "cmm", "MCI", + false, "NILD", null, + List.of(new ToolScaffolder.FieldDefinition("customerId", "String", "Customer ID", "C001", true)), + List.of(new ToolScaffolder.FieldDefinition("guidanceStatus", "String", "Guidance status", "OPEN", false)), null), + new ToolScaffolder.ToolMethodDefinition( + "CustomerContract", "searchContract", "CTMCNT00001", "Customer contract", "Search contract", "cmm", "MCI", + false, "CNTD", null, + List.of(new ToolScaffolder.FieldDefinition("customerId", "String", "Customer ID", "C001", true)), + List.of(new ToolScaffolder.FieldDefinition("contractStatus", "String", "Contract status", "ACTIVE", false)), null))); + + Path sourceRoot = root.resolve("dap-was-customer/src/main/java/io/shinhanlife/dap/mcc"); + String useCase = Files.readString(sourceRoot.resolve("biz/cmm/usecase/CustomerUseCase.java")); + String implementation = Files.readString(sourceRoot.resolve("biz/cmm/usecase/impl/CustomerUseCaseImpl.java")); + String guidanceClient = Files.readString(sourceRoot.resolve("infra/itrf/mci/nild/CustomerGuidanceClient.java")); + + assertTrue(useCase.contains("CustomerGuidanceResponse searchGuidance(CustomerGuidanceRequest req)"), useCase); + assertTrue(useCase.contains("CustomerContractResponse searchContract(CustomerContractRequest req)"), useCase); + assertTrue(implementation.contains("private final CustomerGuidanceClient customerGuidanceClient;"), implementation); + assertTrue(implementation.contains("customerGuidanceClient.callCustomerGuidance(request)"), implementation); + assertTrue(guidanceClient.contains("CustomerGuidance_O callCustomerGuidance(CustomerGuidance_I request)"), guidanceClient); + } + + @Test + void groupedHttpToolRegistersItsGlowApiCatalogEntry() throws Exception { + String moduleName = root.resolve("dap-was-http").toString(); + + ToolScaffolder.scaffoldUseCase("Employee", moduleName, "tester", "2026.08.13", List.of( + new ToolScaffolder.ToolMethodDefinition( + "EmployeeSearch", "searchEmployee", null, "Employee search", "Search employee", "smp", "HTTP", + false, null, "employee-search", + List.of(new ToolScaffolder.FieldDefinition("employeeNo", "String", "Employee number", "10001", true)), + List.of(new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong", false)), null))); + + Path glowConfig = root.resolve("dap-was-http/src/main/resources/glow/application-glow-local.yml"); + assertTrue(Files.exists(glowConfig)); + assertTrue(Files.readString(glowConfig).contains("- name: employee-search")); + } + + @Test + void generatesEnumAndListFieldsInDtoSchemaAndMockResponse() throws Exception { + String moduleName = root.resolve("dap-was-claim").toString(); + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("claimStatus", "Enum", "Claim status", "OPEN", true, + List.of("OPEN", "CLOSED"), null, List.of()), + new ToolScaffolder.FieldDefinition("customerIds", "List", "Customer IDs", "C001", false, + List.of(), "String", List.of())); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("guidanceItems", "List", "Guidance items", "", false, + List.of(), "Object", List.of(new ToolScaffolder.FieldDefinition("status", "String", "Status", "OPEN", true)))); + + ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName, + "tester", "2026.08.12", false, "CLM1", null, null, inputFields, outputFields); + + Path dtoRoot = root.resolve("dap-was-claim/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto"); + String request = Files.readString(dtoRoot.resolve("ClaimSearchRequest.java")); + String response = Files.readString(dtoRoot.resolve("ClaimSearchResponse.java")); + String definition = Files.readString(root.resolve("dap-was-claim/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml")); + String mock = Files.readString(root.resolve("dap-was-claim/src/main/resources/mock-responses/cmm_claim_search.json")); + + assertTrue(request.contains("private ClaimStatus claimStatus;"), request); + assertTrue(request.contains("private List customerIds;"), request); + assertTrue(Files.exists(dtoRoot.resolve("ClaimStatus.java"))); + assertTrue(response.contains("private List guidanceItems;"), response); + assertTrue(response.contains("public static class GuidanceItemsItem"), response); + assertFalse(Files.exists(dtoRoot.resolve("ClaimSearchResponseGuidanceItemsItem.java"))); + assertTrue(definition.contains("enum: [OPEN, CLOSED]"), definition); + assertTrue(definition.contains("type: array"), definition); + assertTrue(mock.contains("\"guidanceItems\" : [{"), mock); + } + + @Test + void generatesEveryToolSourceAsUtf8WithoutBrokenKoreanOrBom() throws Exception { + String moduleName = root.resolve("dap-was-korean").toString(); + + ToolScaffolder.scaffold("analysis data query", "CLYMCI00001", "분석 데이터 조회", + "분석 데이터를 조회하기 위한 도구로 다양한 분석 결과를 제공합니다.", + "cmm", "MCI", moduleName, "테스터", "2026.08.12", + false, null, null, null, + List.of(new ToolScaffolder.FieldDefinition("query", "String", "조회 조건", "계약 분석", true)), + List.of(new ToolScaffolder.FieldDefinition("analysisResult", "String", "분석 결과", "정상", false))); + + Path sourceRoot = root.resolve("dap-was-korean/src/main/java/io/shinhanlife/dap/mcc"); + Path useCase = sourceRoot.resolve("biz/cmm/usecase/AnalysisDataQueryUseCase.java"); + Path implementation = sourceRoot.resolve("biz/cmm/usecase/impl/AnalysisDataQueryUseCaseImpl.java"); + + String useCaseSource = Files.readString(useCase, StandardCharsets.UTF_8); + String implementationSource = Files.readString(implementation, StandardCharsets.UTF_8); + assertTrue(useCaseSource.contains("title = \"분석 데이터 조회\""), useCaseSource); + assertTrue(useCaseSource.contains("description = \"분석 데이터를 조회하기 위한 도구로 다양한 분석 결과를 제공합니다.\""), useCaseSource); + assertTrue(useCaseSource.contains("AX HUB 시스템 처리 클래스"), useCaseSource); + assertTrue(useCaseSource.contains("개정이력"), useCaseSource); + assertTrue(useCaseSource.contains("최초생성"), useCaseSource); + assertTrue(implementationSource.contains("요청 수신"), implementationSource); + assertTrue(implementationSource.contains("MapStruct를 이용한 자동 매핑"), implementationSource); + assertTrue(implementationSource.contains("연동 중 오류 발생"), implementationSource); + + try (var files = Files.walk(root.resolve("dap-was-korean"))) { + for (Path file : files.filter(Files::isRegularFile).toList()) { + byte[] bytes = Files.readAllBytes(file); + assertFalse(bytes.length >= 3 + && (bytes[0] & 0xff) == 0xef + && (bytes[1] & 0xff) == 0xbb + && (bytes[2] & 0xff) == 0xbf, + "UTF-8 BOM must not be generated: " + file); + String content = Files.readString(file, StandardCharsets.UTF_8); + assertFalse(content.contains("�"), "Invalid replacement character: " + file); + } + } + } + + @Test + void generatesV17ToolDefinitionTogetherWithToolSources() throws Exception { + String moduleName = root.resolve("dap-was-v17-definition").toString(); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 조회", + "사번으로 재직 중인 직원을 조회한다.", "smp", "HTTP", moduleName, + "tester", "2026.08.12", false, null, null, null, + List.of(new ToolScaffolder.FieldDefinition("employeeNo", "String", "조회할 사번", "09860000", true)), + List.of(), "employee"); + + Path definition = root.resolve("dap-was-v17-definition/src/main/resources/tool-definitions/smp/smp_employee_search.yml"); + String yaml = Files.readString(definition); + + assertTrue(yaml.contains("name: smp_employee_search"), yaml); + assertTrue(yaml.contains("when_to_use:"), yaml); + assertTrue(yaml.contains("example_queries:"), yaml); + assertTrue(yaml.contains("additionalProperties: false"), yaml); + assertTrue(yaml.contains("owner_org: \"MCP_TOOL\""), yaml); + ToolDefinition parsed = new ObjectMapper(new YAMLFactory()).readValue(yaml, ToolDefinition.class); + ToolDefinitionValidator.validate(parsed, definition.toString()); + } + + @Test + void appliesV17MetadataEnteredByScaffoldUser() throws Exception { + String moduleName = root.resolve("dap-was-v17-options").toString(); + ToolScaffolder.ToolDefinitionOptions options = new ToolScaffolder.ToolDefinitionOptions( + "사번으로 직원을 조회한다.", + "직원 정보 조회 요청에 사용한다.", + "사번이 없으면 사용하지 않는다.", + "최대 1건만 반환한다.", + "직원 기본 정보 조회", + List.of("사번 10001을 조회해줘", "직원 10001 소속을 알려줘", "10001 직원을 찾아줘"), + List.of("employee", "search"), "HR_TEAM"); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 조회", "직원을 조회한다.", + "smp", "HTTP", moduleName, "tester", "2026.08.12", false, null, null, null, + List.of(new ToolScaffolder.FieldDefinition("employeeNo", "String", "조회할 사번", "10001", true)), + List.of(), "employee", options); + + Path definition = root.resolve("dap-was-v17-options/src/main/resources/tool-definitions/smp/smp_employee_search.yml"); + ToolDefinition parsed = new ObjectMapper(new YAMLFactory()).readValue(Files.readString(definition), ToolDefinition.class); + assertEquals("HR_TEAM", parsed.ownerOrg()); + assertEquals("10001 직원을 찾아줘", parsed.exampleQueries().get(2)); + assertEquals(2, parsed.tags().size()); + ToolDefinitionValidator.validate(parsed, definition.toString()); + } + + @TempDir + Path root; + + @Test + void generatesSpringAiToolAndResponseDto() throws Exception { + String moduleName = "build/scaffold-manifest-test"; + + ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName, + "tester", "2026.08.04", true, null); + + Path root = Path.of(moduleName, "src/main/java/io/shinhanlife/dap/mcc/biz/cmm"); + String useCase = Files.readString(root.resolve("usecase/ClaimSearchUseCase.java")); + String response = Files.readString(root.resolve("dto/ClaimSearchResponse.java")); + + assertTrue(useCase.contains("name = \"cmm_claim_search\"")); + assertTrue(useCase.contains("@GrowToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\")")); + assertTrue(response.contains("private String resultCode;")); + assertTrue(response.contains("private String resultMessage;")); + } + @Test + void usesWasModuleNameAsToolPodPrefix() throws Exception { + String moduleName = "build/dap-was-sal"; + + ToolScaffolder.scaffold("notification send", "SMS0001", "SMS 발송", "cmm", "HTTP", moduleName, + "tester", "2026.08.05", true, null); + + Path useCasePath = Path.of(moduleName, + "src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/NotificationSendUseCase.java"); + String useCase = Files.readString(useCasePath); + + assertTrue(useCase.contains("name = \"cmm_notification_send\"")); + } + + @Test + void createsSchemaResourcesInToolSchemaCategoryDirectory() throws Exception { + String moduleName = root.resolve("dap-was-sample").toString(); + + ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName, + "tester", "2026.08.09", true, null, + "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", + "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json"); + + Path schemas = root.resolve("dap-was-sample/src/main/resources/tool-schemas/cmm"); + assertTrue(Files.exists(schemas.resolve("claim-search-resource-input-schema.json"))); + assertTrue(Files.exists(schemas.resolve("claim-search-resource-output-schema.json"))); String useCase = Files.readString(root.resolve("dap-was-sample/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java")); + assertTrue(useCase.contains("@GrowToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\",")); + assertTrue(useCase.contains("inputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-input-schema.json\"")); + assertTrue(useCase.contains("outputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-output-schema.json\"")); + } + + @Test + void generatesRequestDtoFromDeclaredFields() throws Exception { + String moduleName = root.resolve("dap-was-pay").toString(); + + ToolScaffolder.scaffold("search hr", "SHEARCH_01", "HR 조회", "pay", "MCI", moduleName, + "tester", "2026.08.09", true, "DFAG"); + + Path requestPath = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc/biz/pay/dto/SearchHrRequest.java"); + String request = Files.readString(requestPath); + + assertTrue(request.contains("import io.swagger.v3.oas.annotations.media.Schema;")); + assertTrue(request.contains("@Schema(description = \"Search query\", example = \"example\")")); + assertTrue(request.contains("private String query;")); + assertFalse(request.contains("McpToolParam")); + + Path implementationPath = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc/biz/pay/usecase/impl/SearchHrUseCaseImpl.java"); + String implementation = Files.readString(implementationPath); + assertTrue(implementation.contains("public SearchHrResponse execute(SearchHrRequest req)")); + assertTrue(implementation.contains("response.setResultCode(\"SUCCESS\")")); + } + + @Test + void generatesMciToolFromDeclaredInputAndOutputFields() throws Exception { + String moduleName = root.resolve("dap-was-pay").toString(); + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeId", "String", "Employee identifier", "EMP10001", true), + new ToolScaffolder.FieldDefinition("page", "Integer", "Page number", "1", false)); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong Gildong", true)); + + ToolScaffolder.scaffold("search hr", "SHEARCH_01", "HR search", "pay", "MCI", moduleName, + "tester", "2026.08.09", true, "DFAG", null, null, inputFields, outputFields); + + Path sourceRoot = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc"); + String request = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrRequest.java")); + String response = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrResponse.java")); + String mciRequest = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_I.java")); + String mciResponse = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_O.java")); + String converter = Files.readString(sourceRoot.resolve("biz/pay/converter/SearchHrConverter.java")); String useCase = Files.readString(sourceRoot.resolve("biz/pay/usecase/SearchHrUseCase.java")); + String implementation = Files.readString(sourceRoot.resolve("biz/pay/usecase/impl/SearchHrUseCaseImpl.java")); + assertTrue(mciRequest.contains("package io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io;"), mciRequest); assertTrue(useCase.contains("@GrowToolHint(register = true, categoryKey = \"pay\", mappingId = \"SHEARCH_01\")")); + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io.SHEARCH_01_O;")); + + assertTrue(request.contains("private String employeeId;")); + assertTrue(request.contains("private Integer page;")); + assertFalse(request.contains("phoneNumber")); + assertTrue(response.contains("private String employeeName;")); + assertTrue(mciRequest.contains("private String employeeId;")); + assertTrue(mciResponse.contains("private String employeeName;")); + assertTrue(converter.contains("SearchHrResponse toResponse(SHEARCH_01_O mciRes);")); + } + @Test + void generatesMockResponseAndUnitTestSkeletonFromOutputFields() throws Exception { + String moduleName = root.resolve("dap-was-cus").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("status", "String", "Claim status", "RECEIVED", true)); + + ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName, + "tester", "2026.08.10", true, null, null, null, List.of(), outputFields); + + Path mockResponse = root.resolve("dap-was-cus/src/main/resources/mock-responses/cmm_claim_search.json"); + Path useCaseTest = root.resolve("dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java"); + + assertTrue(Files.exists(mockResponse)); + assertTrue(Files.readString(mockResponse).contains("\"status\" : \"RECEIVED\"")); + assertTrue(Files.exists(useCaseTest)); + assertTrue(Files.readString(useCaseTest).contains("class ClaimSearchUseCaseTest")); + } + @Test + void createsWireMockMappingForHttpTool() { + String mapping = ToolScaffolder.wireMockMappingContent("HR_EMPLOYEE_SEARCH", "smp_employee_search.json"); + + assertTrue(mapping.contains("\"method\" : \"POST\""), mapping); + assertTrue(mapping.contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\""), mapping); + assertTrue(mapping.contains("\"bodyFileName\" : \"smp_employee_search.json\""), mapping); + } + + + @Test + void generatesDtoPackageAndRemovesDuplicateResponseFields() throws Exception { + String moduleName = root.resolve("dap-was-http").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("resultCode", "String", "API result", "SUCCESS", true), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong Gildong", false), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Duplicate name", "Duplicate", false)); + + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeId", "String", "Employee identifier", "EMP10001", true)); + String resultLog = ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "Employee search", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, inputFields, outputFields); + + Path dtoRoot = root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto"); + String request = Files.readString(dtoRoot.resolve("EmployeeSearchRequest.java")); + String response = Files.readString(dtoRoot.resolve("EmployeeSearchResponse.java")); + + assertTrue(request.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), request); + assertTrue(response.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), response); + assertTrue(response.indexOf("private String resultCode;") == response.lastIndexOf("private String resultCode;"), response); + assertTrue(response.indexOf("private String employeeName;") == response.lastIndexOf("private String employeeName;"), response); + String converter = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/converter/EmployeeSearchConverter.java")); + String httpRequest = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/io/EmployeeSearchHttpRequest.java")); + String httpResponse = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/io/EmployeeSearchHttpResponse.java")); + String httpClient = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/EmployeeSearchClient.java")); + assertFalse(converter.contains("phoneNumber"), converter); + assertTrue(converter.contains("infra.itrf.http.employee_search.io.EmployeeSearchHttpRequest"), converter); + assertFalse(converter.contains("io.shinhanlife.dap.mcc.io.shinhanlife.dap.mcc"), converter); + assertTrue(converter.contains("// @Mapping(source = \"sourceField\", target = \"targetField\")"), converter); + assertTrue(httpRequest.contains("private String employeeId;"), httpRequest); + assertTrue(httpResponse.contains("private String employeeName;"), httpResponse); + assertTrue(httpClient.contains("http.call(API_NAME, request, responseType)"), httpClient); + assertFalse(Files.exists(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/legacy"))); + String implementation = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/EmployeeSearchUseCaseImpl.java")); + assertTrue(implementation.contains("public EmployeeSearchResponse execute(EmployeeSearchRequest req)"), implementation); + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.http.employee_search.EmployeeSearchClient;"), implementation); + assertTrue(implementation.contains("private final EmployeeSearchClient employeeSearchClient;"), implementation); + assertTrue(implementation.contains("employeeSearchClient.call(httpRequest, EmployeeSearchHttpResponse.class)"), implementation); + assertFalse(implementation.contains("AxhubHttpComponent"), implementation); + assertFalse(implementation.contains("executeLegacy(\"HTTP\""), implementation); + Path wireMockResponse = root.resolve("mci-mock/__files/smp_employee_search.json"); + Path wireMockMapping = root.resolve("mci-mock/mappings/smp_employee_search.json"); + assertTrue(Files.exists(wireMockResponse), wireMockResponse.toString()); + assertTrue(Files.exists(wireMockMapping), wireMockMapping.toString()); + assertTrue(Files.readString(wireMockMapping).contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\"")); + Path localConfig = root.resolve("src/main/resources/glow/application-glow-local.yml"); + assertTrue(Files.exists(localConfig), localConfig.toString()); + assertTrue(Files.readString(localConfig).contains("name: employee-search")); + assertTrue(Files.readString(localConfig).contains("url: ${AXHUB_EMPLOYEE_SEARCH_HTTP_URL:/api/mock/http/smp_employee_search}")); + Path podMockResponse = root.resolve("dap-was-http/src/main/resources/mock-responses/smp_employee_search.json"); + assertTrue(Files.exists(podMockResponse), podMockResponse.toString()); + assertTrue(resultLog.contains("Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요."), resultLog); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "Employee search", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, inputFields, outputFields); + long apiNameCount = Files.readAllLines(localConfig).stream() + .filter(line -> line.trim().equals("- name: employee-search")) + .count(); + assertEquals(1, apiNameCount); + } + @Test + void generatesSeparateToolTitleAndDescription() throws Exception { + String moduleName = root.resolve("dap-was-title").toString(); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 정보 조회", + "사번을 입력받아 재직 중인 직원의 기본 정보를 조회한다.", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, List.of(), List.of()); + + Path useCasePath = root.resolve("dap-was-title/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/EmployeeSearchUseCase.java"); + String useCase = Files.readString(useCasePath); + + assertTrue(useCase.contains("title = \"직원 정보 조회\""), useCase); + assertTrue(useCase.contains("description = \"사번을 입력받아 재직 중인 직원의 기본 정보를 조회한다.\""), useCase); + } + @Test + void generatesHttpToolUsingConfiguredApiNameAndConfiguredUrlOnly() throws Exception { + String moduleName = root.resolve("dap-was-http-api-name").toString(); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 정보 조회", + "사번으로 직원을 조회한다.", "smp", "HTTP", moduleName, "tester", "2026.08.11", + false, null, null, null, List.of(), List.of(), "employee"); + + Path implementationPath = root.resolve("dap-was-http-api-name/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/EmployeeSearchUseCaseImpl.java"); + String implementation = Files.readString(implementationPath); + + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.http.employee.EmployeeClient;"), implementation); + assertTrue(implementation.contains("employeeClient.call(httpRequest, EmployeeSearchHttpResponse.class)"), implementation); + assertFalse(implementation.contains("AxhubHttpDomain"), implementation); + assertFalse(implementation.contains("\"/HR_EMPLOYEE_SEARCH\""), implementation); + } + + @Test + void addsHttpApiEntryOnItsOwnYamlLineBeforeMciConfiguration() throws Exception { + String moduleName = root.resolve("dap-was-http-yaml").toString(); + Path localConfig = root.resolve("src/main/resources/glow/application-glow-local.yml"); + Files.createDirectories(localConfig.getParent()); + Files.writeString(localConfig, """ + glow: + communication: + http: + api-list: + - name: memo + biz-pod: false + mci: + host: localhost + """); + + ToolScaffolder.scaffold("insurance claim processor", "CLAIM0000001", "Insurance claim", "Insurance claim", "ins", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, List.of(), List.of(), "insurance"); + + String yaml = Files.readString(localConfig); + assertFalse(yaml.contains("biz-pod: false - name"), yaml); + assertTrue(yaml.contains(" - name: insurance"), yaml); + assertTrue(yaml.indexOf(" - name: insurance") < yaml.indexOf(" mci:"), yaml); + } + + @Test + void generatesObjectListAsNestedInnerClassWithoutSeparateItemSource() throws Exception { + String moduleName = root.resolve("dap-was-inner-list").toString(); + List fields = List.of( + new ToolScaffolder.FieldDefinition("data", "List", "activity data", "", false, + List.of(), "Object", List.of( + new ToolScaffolder.FieldDefinition("date", "String", "date", "2026-08-13", true), + new ToolScaffolder.FieldDefinition("users", "Integer", "users", "10", false)))); + + ToolScaffolder.scaffold("ga activity status", "GA001", "GA status", "GA status", "ana", "HTTP", + moduleName, "tester", "2026.08.13", false, null, null, null, List.of(), fields); + + Path dtoDir = root.resolve("dap-was-inner-list/src/main/java/io/shinhanlife/dap/mcc/biz/ana/dto"); + String response = Files.readString(dtoDir.resolve("GaActivityStatusResponse.java")); + assertTrue(response.contains("private List data;"), response); + assertTrue(response.contains("public static class DataItem"), response); + assertTrue(response.contains("private String date;"), response); + assertFalse(Files.exists(dtoDir.resolve("GaActivityStatusResponseDataItem.java"))); + } + + @Test + void groupedUseCaseSupportsHttpAndMciTools() throws Exception { + String moduleName = root.resolve("dap-was-mixed").toString(); + ToolScaffolder.scaffoldUseCase("Customer", moduleName, "tester", "2026.08.13", List.of( + new ToolScaffolder.ToolMethodDefinition("CustomerProfile", "getProfile", "CUST001", "Profile", "profile", "cmm", "MCI", + false, "CSTM", null, List.of(), List.of(), null), + new ToolScaffolder.ToolMethodDefinition("CustomerNotice", "getNotice", "NOTICE001", "Notice", "notice", "cmm", "HTTP", + false, null, "customer-notice", List.of(), List.of(), null))); + + Path sourceRoot = root.resolve("dap-was-mixed/src/main/java/io/shinhanlife/dap/mcc"); + String impl = Files.readString(sourceRoot.resolve("biz/cmm/usecase/impl/CustomerUseCaseImpl.java")); + assertTrue(impl.contains("getProfile(CustomerProfileRequest req)"), impl); + assertTrue(impl.contains("getNotice(CustomerNoticeRequest req)"), impl); + assertTrue(Files.exists(sourceRoot.resolve("infra/itrf/http/customer_notice/CustomerNoticeClient.java"))); + } + + @Test + void addsToolMethodToExistingUseCaseInsteadOfOverwritingIt() throws Exception { + String moduleName = root.resolve("dap-was-existing").toString(); + ToolScaffolder.scaffoldUseCase("Customer", moduleName, "tester", "2026.08.13", List.of( + new ToolScaffolder.ToolMethodDefinition("CustomerProfile", "getProfile", "CUST001", "Profile", "profile", "cmm", "MCI", + false, "CSTM", null, List.of(), List.of(), null))); + ToolScaffolder.scaffoldUseCase("Customer", moduleName, "tester", "2026.08.13", List.of( + new ToolScaffolder.ToolMethodDefinition("CustomerNotice", "getNotice", "NOTICE001", "Notice", "notice", "cmm", "HTTP", + false, null, "customer-notice", List.of(), List.of(), null))); + + Path sourceRoot = root.resolve("dap-was-existing/src/main/java/io/shinhanlife/dap/mcc"); + String useCase = Files.readString(sourceRoot.resolve("biz/cmm/usecase/CustomerUseCase.java")); + String impl = Files.readString(sourceRoot.resolve("biz/cmm/usecase/impl/CustomerUseCaseImpl.java")); + assertTrue(useCase.contains("getProfile(CustomerProfileRequest req)"), useCase); + assertTrue(useCase.contains("getNotice(CustomerNoticeRequest req)"), useCase); + assertTrue(impl.contains("private final CustomerProfileClient customerProfileClient;"), impl); + assertTrue(impl.contains("private final CustomerNoticeClient customerNoticeClient;"), impl); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java new file mode 100644 index 000000000..9e0cab833 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java @@ -0,0 +1,83 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.McpOutputSchema; +import io.swagger.v3.oas.annotations.media.Schema; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; + +class ToolSchemaResolverTest { + + private final ToolSchemaResolver resolver = new ToolSchemaResolver(new ObjectMapper()); + + @Test + void generatesSchemaFromRequestDtoWhenNoExplicitSchemaIsConfigured() throws Exception { + Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolve(method.getAnnotation(McpTool.class), null, AutomaticRequest.class); + + assertTrue(properties(schema).containsKey("differentField")); + } + + @Test + void generatesOutputSchemaFromMarkedResponseDto() throws Exception { + Method method = AutomaticOutputSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolveOutput( + method.getAnnotation(McpTool.class), SimpleResponse.class); + + assertEquals(List.of("resultCode"), schema.get("required")); + assertEquals(List.of("SUCCESS", "FAILURE"), property(schema, "resultCode").get("enum")); + } + + @Test + void doesNotEnableOutputValidationWhenOutputSchemaIsNotDeclared() throws Exception { + Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolveOutput( + method.getAnnotation(McpTool.class), AutomaticRequest.class); + + assertTrue(schema.isEmpty()); + } + + @SuppressWarnings("unchecked") + private Map properties(Map schema) { + return (Map) schema.get("properties"); + } + + @SuppressWarnings("unchecked") + private Map property(Map schema, String name) { + return (Map) properties(schema).get(name); + } + + static class AutomaticSchemaTool { + @McpTool(name = "oth_test_automatic_search") + void search(AutomaticRequest request) { + } + } + + static class AutomaticOutputSchemaTool { + @McpTool(name = "oth_test_output_search") + SimpleResponse search(AutomaticRequest request) { + return null; + } + } + + @McpOutputSchema + static class SimpleResponse { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"SUCCESS", "FAILURE"}) + private String resultCode; + + private String message; + } + + static class AutomaticRequest { + private String differentField; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java new file mode 100644 index 000000000..205ad604b --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ToolSourceUpdaterTest { + + @TempDir + Path temporaryRoot; + + @Test + void updatesSdkAndProjectOwnedMetadataOnTheSameToolMethod() throws Exception { + Path source = temporaryRoot.resolve("dap-was-cus/src/main/java/example/SampleUseCase.java"); + Files.createDirectories(source.getParent()); + Files.writeString(source, """ + package example; + import org.springaicommunity.mcp.annotation.McpTool; + import io.shinhanlife.dap.lib.annotation.GrowToolHint; + interface SampleUseCase { + @McpTool(name = "cmm_sample_search", description = "old") + @GrowToolHint(requiresApproval = false) + void search(); + } + """); + + ToolSourceUpdater.updateToolSource(temporaryRoot, "cmm_sample_search", "customer", "new", true, true); + + String updated = Files.readString(source); + assertTrue(updated.contains("@McpTool(name = \"cmm_sample_search\", description = \"new\")")); + assertTrue(updated.contains("register = true"), updated); + assertTrue(updated.contains("requiresApproval = true"), updated); + assertTrue(updated.contains("categoryKey = \"customer\""), updated); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java new file mode 100644 index 000000000..8435a81b2 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java @@ -0,0 +1,112 @@ +package io.shinhanlife.dap.lib.validation; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * @package io.shinhanlife.dap.lib.validation + * @className McpToolNameValidatorTest + * @description MCP tool name uniqueness validation test + * @author 0986406 + * @create 2026.07.27 + *
+ * ---------- revision history ----------
+ * date       author    description
+ * ---------- --------- ---------------------------
+ * 2026.07.27 0986406    initial creation
+ * 
+ */ +class McpToolNameValidatorTest { + + @TempDir + Path temporaryRoot; + + @Test + void rejectsDuplicateMcpToolNamesAcrossToolModules() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "oth_sms_notification_send"); + writeToolSource("dap-was-second", "SecondTool.java", "second", "oth_sms_notification_send"); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains("oth_sms_notification_send")); + assertTrue(exception.getMessage().contains("dap-was-first")); + assertTrue(exception.getMessage().contains("dap-was-second")); + } + + @Test + void validationRunnerRejectsDuplicateMcpToolNamesBeforePackaging() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "oth_sms_notification_send"); + writeToolSource("dap-was-second", "SecondTool.java", "second", "oth_sms_notification_send"); + + assertThrows(IllegalStateException.class, + () -> McpToolNameValidationRunner.validate(temporaryRoot)); + } + + @Test + void rejectsToolNameOutsideConfiguredPattern() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "bond.issue"); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains("Invalid MCP tool name(s)")); + assertTrue(exception.getMessage().contains("bond.issue")); + } + @Test + void acceptsLettersDigitsUnderscoresAndDashesWithin128Characters() throws IOException { + String validName = "Tool_Name-" + "a".repeat(118); + writeToolSource("dap-was-first", "FirstTool.java", "first", validName); + + assertDoesNotThrow(() -> McpToolNameValidator.assertUnique(temporaryRoot)); + } + + @Test + void rejectsToolNameLongerThan128Characters() throws IOException { + String invalidName = "a".repeat(129); + writeToolSource("dap-was-first", "FirstTool.java", "first", invalidName); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains(invalidName)); + } + @Test + void acceptsCurrentProjectToolNames() { + assertDoesNotThrow(() -> McpToolNameValidator.assertUnique(findProjectRoot())); + } + + private void writeToolSource(String moduleName, String fileName, String className, String toolName) throws IOException { + Path source = temporaryRoot.resolve(moduleName).resolve("src/main/java/example").resolve(fileName); + Files.createDirectories(source.getParent()); + Files.writeString(source, """ + package example; + + import org.springaicommunity.mcp.annotation.McpTool; + + class %s { + @McpTool(name = "%s") + void execute() { } + } + """.formatted(className, toolName)); + } + + private Path findProjectRoot() { + Path current = Path.of("").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("settings.gradle"))) { + current = current.getParent(); + } + if (current == null) { + throw new IllegalStateException("Could not locate Gradle project root"); + } + return current; + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java new file mode 100644 index 000000000..fec3156fb --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java @@ -0,0 +1,108 @@ +package io.shinhanlife.dap.mcc.manifest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.manifest.ToolManifestItem; +import io.shinhanlife.dap.lib.manifest.ToolManifestResponse; +import io.shinhanlife.dap.lib.manifest.ToolManifestService; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolManifestServiceTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void buildsStandardManifestAndDerivesRevisionFromToolDefinition() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 3000)), objectMapper, properties); + + ToolManifestResponse manifest = service.currentManifest(); + + assertEquals("insurance-processing", manifest.bundleId()); + assertTrue(manifest.revision().matches("\\d+")); + assertEquals(1, manifest.tools().size()); + ToolManifestItem item = manifest.tools().getFirst(); + assertEquals("processing.contract.inquiry", item.name()); + assertEquals("http://tool-processing.ax-hub.svc.cluster.local:8080/mcp/processing.contract.inquiry", + item.endpoint()); + assertEquals("계약 조회", item.title()); + assertEquals("object", item.inputSchema().get("type")); + assertTrue(item.annotations().readOnlyHint()); + assertEquals("1.2.0", item.meta().version()); + assertEquals(3000, item.meta().timeoutMillis()); + assertEquals(List.of("계약 상태를 알려줘", "내 계약을 조회해줘", "계약번호로 찾아줘"), + item.meta().exampleQueries()); + } + + @Test + void changesRevisionWhenToolDefinitionChanges() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService before = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 3000)), objectMapper, properties); + ToolManifestService after = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 5000)), objectMapper, properties); + + assertTrue(!before.currentManifest().revision().equals(after.currentManifest().revision())); + } + + @Test + void rejectsEntireManifestWhenToolNameDoesNotMatchConfiguredPrefix() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("notification_sms_send", "1.0.0", 3000)), objectMapper, properties); + + IllegalStateException error = assertThrows(IllegalStateException.class, service::currentManifest); + + assertTrue(error.getMessage().contains("name-prefix")); + } + + @Test + void rejectsEntireManifestWhenToolNamesAreDuplicated() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.0.0", 3000), + tool("processing.contract.inquiry", "1.0.1", 3000)), objectMapper, properties); + + IllegalStateException error = assertThrows(IllegalStateException.class, service::currentManifest); + + assertTrue(error.getMessage().contains("duplicate")); + } + + private McpProperties manifestProperties(String bundleId, String namePrefix) { + McpProperties properties = new McpProperties(); + McpProperties.Manifest manifest = new McpProperties.Manifest(); + manifest.setBundleId(bundleId); + manifest.setNamePrefix(namePrefix); + properties.setManifest(manifest); + return properties; + } + + private ToolMetadata tool(String name, String version, long timeoutMillis) { + return ToolMetadata.builder() + .name(name) + .podUrl("http://tool-processing.ax-hub.svc.cluster.local:8080") + .displayName("계약 조회") + .description("계약번호로 계약 정보를 조회합니다.") + .exampleQueries(List.of("계약 상태를 알려줘", "내 계약을 조회해줘", "계약번호로 찾아줘")) + .tags(List.of("계약")) + .ownerOrg("MCP_TOOL") + .parametersSchema(Map.of("type", "object", "properties", Map.of("contractNo", Map.of("type", "string")), + "required", List.of("contractNo"), "additionalProperties", false)) + .semver(version) + .timeoutMillis(timeoutMillis) + .enabled(true) + .readOnlyHint(true) + .destructiveHint(false) + .idempotentHint(true) + .openWorldHint(false) + .build(); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java new file mode 100644 index 000000000..159c27ff7 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpSyncServer; +import io.shinhanlife.dap.lib.mcp.*; +import java.lang.reflect.Method; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class McpRequestHeaderFilterTest { + + @Test + void capturesOptionalMcpHeadersOnlyForTheCurrentRequest() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/mcp"); + request.addHeader("X-Request-Id", "gateway-request-id"); + request.addHeader("trace-id", "trace-001"); + request.addHeader("request-id", "tool-request-001"); + request.addHeader("employee-id", "encrypted-employee-id"); + + new McpRequestHeaderFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> { + McpRequestHeaders headers = McpRequestHeaderContext.current(); + assertEquals("gateway-request-id", headers.headerRequestId()); + assertEquals("trace-001", headers.traceId()); + assertEquals("tool-request-001", headers.requestId()); + assertEquals("encrypted-employee-id", headers.encryptedEmployeeId()); + }); + + assertNull(McpRequestHeaderContext.current()); + } + + @Test + void forwardsCapturedHeadersToSharedToolExecutionService() throws Exception { + McpToolExecutionService service = mock(McpToolExecutionService.class); + McpRequestHeaders headers = new McpRequestHeaders( + "gateway-request-id", "trace-001", "tool-request-001", "encrypted-employee-id"); + doReturn(new ToolExecutionResult(200, Map.of("result", "ok"), Map.of())) + .when(service).execute(eq("sampleTool"), eq(headers), eq(Map.of("key", "value"))); + ToolPodMcpToolSynchronizer synchronizer = new ToolPodMcpToolSynchronizer( + mock(McpSyncServer.class), mock(ToolRegistryHeartbeatSender.class), service, new ObjectMapper()); + + Method invoke = ToolPodMcpToolSynchronizer.class.getDeclaredMethod( + "invoke", String.class, McpRequestHeaders.class, Map.class); + invoke.setAccessible(true); + invoke.invoke(synchronizer, "sampleTool", + headers, + Map.of("key", "value")); + + verify(service).execute("sampleTool", headers, Map.of("key", "value")); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java new file mode 100644 index 000000000..dfd5b70dd --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import io.modelcontextprotocol.server.McpSyncServer; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +class ToolMcpServerConfigurationTest { + + private final ToolMcpServerConfiguration configuration = new ToolMcpServerConfiguration(); + + @Test + void exposesOnlyExactMcpEndpointSoLegacyMcpApiPathsRemainAvailable() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + ServletRegistrationBean registration = configuration.toolMcpServlet(transport); + + assertThat(registration.getUrlMappings()).containsExactlyInAnyOrder("/mcp", "/mcp/message"); + } + + @Test + void createsMcpSdkTwoSyncServerWithoutSpringAiAutoConfiguration() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + + McpSyncServer server = configuration.toolMcpServer(transport); + + assertEquals("dap-tool-pod", server.getServerInfo().name()); + server.close(); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java new file mode 100644 index 000000000..165b6aebc --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.web.bind.annotation.RequestHeader; + +class BusinessToolControllerHeaderContractTest { + + @Test + void encryptedEmployeeIdHeaderIsOptional() throws Exception { + Method method = BusinessToolController.class.getDeclaredMethod( + "executeDynamicTool", + String.class, + String.class, + String.class, + String.class, + String.class, + Map.class); + + Parameter employeeIdParameter = method.getParameters()[4]; + RequestHeader requestHeader = employeeIdParameter.getAnnotation(RequestHeader.class); + + assertEquals("employee-id", requestHeader.value()); + assertFalse(requestHeader.required()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java new file mode 100644 index 000000000..c62bf5190 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolArgumentSchemaValidatorTest { + + private final ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + @Test + void validatesDraft7SchemaWithTheRuntimeNetworkntVersion() throws Exception { + Map schema = Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name")); + + assertTrue(validator.validate(schema, Map.of("name", "Hong")).isEmpty()); + assertFalse(validator.validate(schema, Map.of()).isEmpty()); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java new file mode 100644 index 000000000..6c21272c1 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class ToolTestConsoleResourceTest { + + @Test + void publishesManifestDrivenToolTestConsole() throws Exception { + try (InputStream resource = getClass().getResourceAsStream("/static/tool-test-console.html")) { + assertNotNull(resource); + String html = new String(resource.readAllBytes(), StandardCharsets.UTF_8); + + assertTrue(html.contains("/tool-manifest")); + assertTrue(html.contains("Run saved cases")); + assertTrue(html.contains("localStorage")); + assertTrue(html.contains("request-id")); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java b/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java new file mode 100644 index 000000000..3740a24ae --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.glow.communication.annotation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; + +class GlowTrgmFieldContractTest { + + private static class StandardTrgmDto { + @GlowTrgmField(order = 1, length = 8, decimal = 2, description = "금액", target = "body", type = "gs") + private String amount; + } + + @Test + void exposesTheStandardGlowTrgmFieldMetadata() throws Exception { + Field field = StandardTrgmDto.class.getDeclaredField("amount"); + GlowTrgmField metadata = field.getAnnotation(GlowTrgmField.class); + + assertEquals(1, metadata.order()); + assertEquals(8, metadata.length()); + assertEquals(2, metadata.decimal()); + assertEquals("금액", metadata.description()); + assertEquals("body", metadata.target()); + assertEquals("gs", metadata.type()); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java b/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java new file mode 100644 index 000000000..283a129a9 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java @@ -0,0 +1,65 @@ +package io.shinhanlife.glow.util; + + +/** + * @package io.shinhanlife.glow.util + * @className GlowMciParserTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import io.shinhanlife.glow.GlowMciFieldInfo; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class GlowMciParserTest { + + public static class DummyMciDto { + @GlowMciFieldInfo(order = 1, length = 10) + private String customerId; + + @GlowMciFieldInfo(order = 2, length = 15) + private String name; + + @GlowMciFieldInfo(order = 3, length = 3) + private int age; + + public String getCustomerId() { return customerId; } + public String getName() { return name; } + public int getAge() { return age; } + } + + @Test + @DisplayName("고정 길이 MCI 문자열을 DTO로 파싱하는 테스트") + public void testParseFixedLengthString() { + // given: 고정 길이 텍스트 (총 28자리) + // ID(10) + Name(15) + Age(3) + String rawMciString = "CUST000001KIM SHINHAN 035"; + + // when + DummyMciDto result = GlowMciParser.parse(rawMciString, DummyMciDto.class); + + // then + System.out.println("=================================================="); + System.out.println(" [원본 MCI 전문] : [" + rawMciString + "]"); + System.out.println(" [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]"); + System.out.println(" [파싱된 Name (15자리)] : [" + result.getName() + "]"); + System.out.println(" [파싱된 Age (3자리)] : [" + result.getAge() + "]"); + System.out.println("=================================================="); + + assertNotNull(result); + assertEquals("CUST000001", result.getCustomerId()); + assertEquals("KIM SHINHAN", result.getName()); + assertEquals(35, result.getAge()); + } +} diff --git a/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json b/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json new file mode 100644 index 000000000..a949502b1 --- /dev/null +++ b/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json @@ -0,0 +1,3 @@ +{ + "resultCode": "SUCCESS" +} diff --git a/dap-was-lib/src/test/resources/tool-definitions/cmm/cmm_claim_search.yml b/dap-was-lib/src/test/resources/tool-definitions/cmm/cmm_claim_search.yml new file mode 100644 index 000000000..e3193c639 --- /dev/null +++ b/dap-was-lib/src/test/resources/tool-definitions/cmm/cmm_claim_search.yml @@ -0,0 +1,30 @@ +name: cmm_claim_search +display_name: 보험금 청구 상태 조회 +version: 1.0.0 +category_key: cmm +description: + function: 청구번호 또는 계약번호로 보험금 청구 상태를 조회한다. + when_to_use: 사용자가 보험금 청구 진행 상태나 심사 결과를 확인하려는 경우 사용한다. + when_not_to_use: 보험금 청구를 새로 접수하거나 기존 청구를 변경하려는 경우에는 사용하지 않는다. + io_limits: 청구번호 또는 계약번호 중 하나 이상이 필요하며 조회 결과만 반환한다. +display_description: 보험금 청구 상태와 심사 결과를 조회합니다. +example_queries: + - 내 보험금 청구가 어디까지 진행됐는지 알려줘 + - 계약번호로 최근 청구 상태를 확인해줘 + - 청구 심사 결과가 나왔는지 조회해줘 +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + claimNo: + type: string + description: 조회할 보험금 청구번호 + required: + - claimNo + additionalProperties: false +tags: + - 보험금 + - 청구조회 +owner_org: MCP_TOOL diff --git a/dap-was-pro/Dockerfile b/dap-was-pro/Dockerfile new file mode 100644 index 000000000..171eebde7 --- /dev/null +++ b/dap-was-pro/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-pro/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8085 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/dap-was-pro/build.gradle b/dap-was-pro/build.gradle new file mode 100644 index 000000000..629ebb458 --- /dev/null +++ b/dap-was-pro/build.gradle @@ -0,0 +1,9 @@ +plugins { + // Spring Boot 3.5.11 version is managed by the root build.gradle. + id 'org.springframework.boot' +} + +dependencies { + // Shared MCP, Glow integration, validation, logging, Lombok and MapStruct configuration. + implementation project(':dap-was-lib') +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolRequest.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolRequest.java new file mode 100644 index 000000000..b6a855790 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.agt.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class AgentToolRequest { + @McpToolParam(description = "설계사번호", required = false) + private String agentId; + + @McpToolParam(description = "GA 조직번호", required = false) + private String organizationId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + private String queryDate; +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolResponse.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolResponse.java new file mode 100644 index 000000000..f3edb38f9 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/dto/AgentToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.agt.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class AgentToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentInquiryUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentInquiryUseCase.java new file mode 100644 index 000000000..635f830fc --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentInquiryUseCase.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface AgentInquiryUseCase { + @McpTool(name = "agt_agent_profile", title = "GA 설계사 기본정보 조회", description = "GA 설계사 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_AGENT_PROFILE") + AgentToolResponse getAgentProfile(AgentToolRequest request); + + @McpTool(name = "agt_appointment_status", title = "GA 설계사 위촉상태 조회", description = "GA 설계사 위촉상태 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_APPOINTMENT_STATUS") + AgentToolResponse getAppointmentStatus(AgentToolRequest request); + + @McpTool(name = "agt_agent_license", title = "GA 설계사 자격정보 조회", description = "GA 설계사 자격정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_AGENT_LICENSE") + AgentToolResponse getAgentLicense(AgentToolRequest request); + + @McpTool(name = "agt_commission_grade", title = "GA 설계사 수수료등급 조회", description = "GA 설계사 수수료등급 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_COMMISSION_GRADE") + AgentToolResponse getCommissionGrade(AgentToolRequest request); + + @McpTool(name = "agt_agent_education", title = "GA 설계사 교육이력 조회", description = "GA 설계사 교육이력 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_AGENT_EDUCATION") + AgentToolResponse getAgentEducation(AgentToolRequest request); + + @McpTool(name = "agt_agent_compliance", title = "GA 설계사 준법정보 조회", description = "GA 설계사 준법정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_AGENT_COMPLIANCE") + AgentToolResponse getAgentCompliance(AgentToolRequest request); + + @McpTool(name = "agt_customer_assignment", title = "GA 설계사 고객배정 조회", description = "GA 설계사 고객배정 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_CUSTOMER_ASSIGNMENT") + AgentToolResponse getCustomerAssignment(AgentToolRequest request); + + @McpTool(name = "agt_activity_status", title = "GA 설계사 활동상태 조회", description = "GA 설계사 활동상태 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_ACTIVITY_STATUS") + AgentToolResponse getActivityStatus(AgentToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentPerformanceUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentPerformanceUseCase.java new file mode 100644 index 000000000..2c0c11bff --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/AgentPerformanceUseCase.java @@ -0,0 +1,44 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface AgentPerformanceUseCase { + @McpTool(name = "agt_production_summary", title = "GA 설계사 신계약 실적 조회", description = "GA 설계사 신계약 실적 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_PRODUCTION_SUMMARY") + AgentToolResponse getProductionSummary(AgentToolRequest request); + + @McpTool(name = "agt_contract_count", title = "GA 설계사 계약건수 조회", description = "GA 설계사 계약건수 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_CONTRACT_COUNT") + AgentToolResponse getContractCount(AgentToolRequest request); + + @McpTool(name = "agt_premium_summary", title = "GA 설계사 보험료 실적 조회", description = "GA 설계사 보험료 실적 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_PREMIUM_SUMMARY") + AgentToolResponse getPremiumSummary(AgentToolRequest request); + + @McpTool(name = "agt_persistency_rate", title = "GA 설계사 계약유지율 조회", description = "GA 설계사 계약유지율 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_PERSISTENCY_RATE") + AgentToolResponse getPersistencyRate(AgentToolRequest request); + + @McpTool(name = "agt_claim_support", title = "GA 보험금 청구지원 현황 조회", description = "GA 보험금 청구지원 현황 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_CLAIM_SUPPORT") + AgentToolResponse getClaimSupport(AgentToolRequest request); + + @McpTool(name = "agt_commission_summary", title = "GA 설계사 수수료 요약 조회", description = "GA 설계사 수수료 요약 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_COMMISSION_SUMMARY") + AgentToolResponse getCommissionSummary(AgentToolRequest request); + + @McpTool(name = "agt_campaign", title = "GA 영업 캠페인 조회", description = "GA 영업 캠페인 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_CAMPAIGN") + AgentToolResponse getCampaign(AgentToolRequest request); + + @McpTool(name = "agt_task_list", title = "GA 설계사 업무목록 조회", description = "GA 설계사 업무목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_TASK_LIST") + AgentToolResponse getTaskList(AgentToolRequest request); + + @McpTool(name = "agt_notice_list", title = "GA 설계사 공지목록 조회", description = "GA 설계사 공지목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_NOTICE_LIST") + AgentToolResponse getNoticeList(AgentToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/GaOrganizationUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/GaOrganizationUseCase.java new file mode 100644 index 000000000..29278205f --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/GaOrganizationUseCase.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface GaOrganizationUseCase { + @McpTool(name = "agt_ga_profile", title = "GA 조직 기본정보 조회", description = "GA 조직 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_GA_PROFILE") + AgentToolResponse getGaProfile(AgentToolRequest request); + + @McpTool(name = "agt_branch_list", title = "GA 지점목록 조회", description = "GA 지점목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_BRANCH_LIST") + AgentToolResponse getBranchList(AgentToolRequest request); + + @McpTool(name = "agt_organization_tree", title = "GA 조직도 조회", description = "GA 조직도 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_ORGANIZATION_TREE") + AgentToolResponse getOrganizationTree(AgentToolRequest request); + + @McpTool(name = "agt_organization_manager", title = "GA 조직 책임자 조회", description = "GA 조직 책임자 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_ORGANIZATION_MANAGER") + AgentToolResponse getOrganizationManager(AgentToolRequest request); + + @McpTool(name = "agt_organization_agent_list", title = "GA 소속 설계사 목록 조회", description = "GA 소속 설계사 목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_ORGANIZATION_AGENT_LIST") + AgentToolResponse getOrganizationAgentList(AgentToolRequest request); + + @McpTool(name = "agt_sales_area", title = "GA 영업지역 조회", description = "GA 영업지역 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_SALES_AREA") + AgentToolResponse getSalesArea(AgentToolRequest request); + + @McpTool(name = "agt_affiliation_history", title = "GA 소속변경 이력 조회", description = "GA 소속변경 이력 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_AFFILIATION_HISTORY") + AgentToolResponse getAffiliationHistory(AgentToolRequest request); + + @McpTool(name = "agt_support_contact", title = "GA 업무지원 연락처 조회", description = "GA 업무지원 연락처 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "agt", mappingId = "DIRECT_AGT_SUPPORT_CONTACT") + AgentToolResponse getSupportContact(AgentToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImpl.java new file mode 100644 index 000000000..2f3e63490 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImpl.java @@ -0,0 +1,67 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import io.shinhanlife.dap.mcc.biz.agt.usecase.AgentInquiryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class AgentInquiryUseCaseImpl implements AgentInquiryUseCase { + @Override + public AgentToolResponse getAgentProfile(AgentToolRequest request) { + return mockResponse(request, "agt_agent_profile", "GA 설계사 기본정보 조회"); + } + + @Override + public AgentToolResponse getAppointmentStatus(AgentToolRequest request) { + return mockResponse(request, "agt_appointment_status", "GA 설계사 위촉상태 조회"); + } + + @Override + public AgentToolResponse getAgentLicense(AgentToolRequest request) { + return mockResponse(request, "agt_agent_license", "GA 설계사 자격정보 조회"); + } + + @Override + public AgentToolResponse getCommissionGrade(AgentToolRequest request) { + return mockResponse(request, "agt_commission_grade", "GA 설계사 수수료등급 조회"); + } + + @Override + public AgentToolResponse getAgentEducation(AgentToolRequest request) { + return mockResponse(request, "agt_agent_education", "GA 설계사 교육이력 조회"); + } + + @Override + public AgentToolResponse getAgentCompliance(AgentToolRequest request) { + return mockResponse(request, "agt_agent_compliance", "GA 설계사 준법정보 조회"); + } + + @Override + public AgentToolResponse getCustomerAssignment(AgentToolRequest request) { + return mockResponse(request, "agt_customer_assignment", "GA 설계사 고객배정 조회"); + } + + @Override + public AgentToolResponse getActivityStatus(AgentToolRequest request) { + return mockResponse(request, "agt_activity_status", "GA 설계사 활동상태 조회"); + } + + private AgentToolResponse mockResponse(AgentToolRequest request, String toolName, String title) { + AgentToolResponse response = new AgentToolResponse(); + response.setToolName(toolName); + response.setCategory("AGT"); + response.setReferenceId(request != null && hasText(request.getAgentId()) ? request.getAgentId() + : request != null && hasText(request.getOrganizationId()) ? request.getOrganizationId() : "AGT-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentPerformanceUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentPerformanceUseCaseImpl.java new file mode 100644 index 000000000..78332570c --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentPerformanceUseCaseImpl.java @@ -0,0 +1,72 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import io.shinhanlife.dap.mcc.biz.agt.usecase.AgentPerformanceUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class AgentPerformanceUseCaseImpl implements AgentPerformanceUseCase { + @Override + public AgentToolResponse getProductionSummary(AgentToolRequest request) { + return mockResponse(request, "agt_production_summary", "GA 설계사 신계약 실적 조회"); + } + + @Override + public AgentToolResponse getContractCount(AgentToolRequest request) { + return mockResponse(request, "agt_contract_count", "GA 설계사 계약건수 조회"); + } + + @Override + public AgentToolResponse getPremiumSummary(AgentToolRequest request) { + return mockResponse(request, "agt_premium_summary", "GA 설계사 보험료 실적 조회"); + } + + @Override + public AgentToolResponse getPersistencyRate(AgentToolRequest request) { + return mockResponse(request, "agt_persistency_rate", "GA 설계사 계약유지율 조회"); + } + + @Override + public AgentToolResponse getClaimSupport(AgentToolRequest request) { + return mockResponse(request, "agt_claim_support", "GA 보험금 청구지원 현황 조회"); + } + + @Override + public AgentToolResponse getCommissionSummary(AgentToolRequest request) { + return mockResponse(request, "agt_commission_summary", "GA 설계사 수수료 요약 조회"); + } + + @Override + public AgentToolResponse getCampaign(AgentToolRequest request) { + return mockResponse(request, "agt_campaign", "GA 영업 캠페인 조회"); + } + + @Override + public AgentToolResponse getTaskList(AgentToolRequest request) { + return mockResponse(request, "agt_task_list", "GA 설계사 업무목록 조회"); + } + + @Override + public AgentToolResponse getNoticeList(AgentToolRequest request) { + return mockResponse(request, "agt_notice_list", "GA 설계사 공지목록 조회"); + } + + private AgentToolResponse mockResponse(AgentToolRequest request, String toolName, String title) { + AgentToolResponse response = new AgentToolResponse(); + response.setToolName(toolName); + response.setCategory("AGT"); + response.setReferenceId(request != null && hasText(request.getAgentId()) ? request.getAgentId() + : request != null && hasText(request.getOrganizationId()) ? request.getOrganizationId() : "AGT-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/GaOrganizationUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/GaOrganizationUseCaseImpl.java new file mode 100644 index 000000000..fe3f19268 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/GaOrganizationUseCaseImpl.java @@ -0,0 +1,67 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolResponse; +import io.shinhanlife.dap.mcc.biz.agt.usecase.GaOrganizationUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class GaOrganizationUseCaseImpl implements GaOrganizationUseCase { + @Override + public AgentToolResponse getGaProfile(AgentToolRequest request) { + return mockResponse(request, "agt_ga_profile", "GA 조직 기본정보 조회"); + } + + @Override + public AgentToolResponse getBranchList(AgentToolRequest request) { + return mockResponse(request, "agt_branch_list", "GA 지점목록 조회"); + } + + @Override + public AgentToolResponse getOrganizationTree(AgentToolRequest request) { + return mockResponse(request, "agt_organization_tree", "GA 조직도 조회"); + } + + @Override + public AgentToolResponse getOrganizationManager(AgentToolRequest request) { + return mockResponse(request, "agt_organization_manager", "GA 조직 책임자 조회"); + } + + @Override + public AgentToolResponse getOrganizationAgentList(AgentToolRequest request) { + return mockResponse(request, "agt_organization_agent_list", "GA 소속 설계사 목록 조회"); + } + + @Override + public AgentToolResponse getSalesArea(AgentToolRequest request) { + return mockResponse(request, "agt_sales_area", "GA 영업지역 조회"); + } + + @Override + public AgentToolResponse getAffiliationHistory(AgentToolRequest request) { + return mockResponse(request, "agt_affiliation_history", "GA 소속변경 이력 조회"); + } + + @Override + public AgentToolResponse getSupportContact(AgentToolRequest request) { + return mockResponse(request, "agt_support_contact", "GA 업무지원 연락처 조회"); + } + + private AgentToolResponse mockResponse(AgentToolRequest request, String toolName, String title) { + AgentToolResponse response = new AgentToolResponse(); + response.setToolName(toolName); + response.setCategory("AGT"); + response.setReferenceId(request != null && hasText(request.getAgentId()) ? request.getAgentId() + : request != null && hasText(request.getOrganizationId()) ? request.getOrganizationId() : "AGT-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolRequest.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolRequest.java new file mode 100644 index 000000000..12dbeb820 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.pro.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class ProductToolRequest { + @McpToolParam(description = "고객번호 또는 계약번호", required = false) + private String subjectId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + private String queryDate; + + @McpToolParam(description = "조회 상세 수준", required = false) + private String detailLevel; +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolResponse.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolResponse.java new file mode 100644 index 000000000..06b4e358e --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/dto/ProductToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.pro.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class ProductToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ClaimInquiryUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ClaimInquiryUseCase.java new file mode 100644 index 000000000..380603b9f --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ClaimInquiryUseCase.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface ClaimInquiryUseCase { + @McpTool(name = "pro_claim_eligibility", title = "보험금 청구 가능여부 조회", description = "보험금 청구 가능여부 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_ELIGIBILITY") + ProductToolResponse getClaimEligibility(ProductToolRequest request); + + @McpTool(name = "pro_claim_history", title = "보험금 청구이력 조회", description = "보험금 청구이력 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_HISTORY") + ProductToolResponse getClaimHistory(ProductToolRequest request); + + @McpTool(name = "pro_claim_status", title = "보험금 청구상태 조회", description = "보험금 청구상태 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_STATUS") + ProductToolResponse getClaimStatus(ProductToolRequest request); + + @McpTool(name = "pro_claim_documents", title = "보험금 청구서류 조회", description = "보험금 청구서류 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_DOCUMENTS") + ProductToolResponse getClaimDocuments(ProductToolRequest request); + + @McpTool(name = "pro_claim_payment", title = "보험금 지급내역 조회", description = "보험금 지급내역 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_PAYMENT") + ProductToolResponse getClaimPayment(ProductToolRequest request); + + @McpTool(name = "pro_claim_accident", title = "보험사고 정보 조회", description = "보험사고 정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_ACCIDENT") + ProductToolResponse getClaimAccident(ProductToolRequest request); + + @McpTool(name = "pro_claim_beneficiary", title = "보험금 수익자 조회", description = "보험금 수익자 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_BENEFICIARY") + ProductToolResponse getClaimBeneficiary(ProductToolRequest request); + + @McpTool(name = "pro_claim_contact", title = "보험금 청구 담당자 조회", description = "보험금 청구 담당자 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CLAIM_CONTACT") + ProductToolResponse getClaimContact(ProductToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ContractInquiryUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ContractInquiryUseCase.java new file mode 100644 index 000000000..d2d3b0dfd --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/ContractInquiryUseCase.java @@ -0,0 +1,44 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface ContractInquiryUseCase { + @McpTool(name = "pro_contract_list", title = "보험계약 목록 조회", description = "보험계약 목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_LIST") + ProductToolResponse getContractList(ProductToolRequest request); + + @McpTool(name = "pro_contract_detail", title = "보험계약 상세 조회", description = "보험계약 상세 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_DETAIL") + ProductToolResponse getContractDetail(ProductToolRequest request); + + @McpTool(name = "pro_contract_coverage", title = "보험계약 보장내역 조회", description = "보험계약 보장내역 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_COVERAGE") + ProductToolResponse getContractCoverage(ProductToolRequest request); + + @McpTool(name = "pro_contract_premium", title = "보험계약 보험료 조회", description = "보험계약 보험료 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_PREMIUM") + ProductToolResponse getContractPremium(ProductToolRequest request); + + @McpTool(name = "pro_contract_payment_status", title = "보험료 납입상태 조회", description = "보험료 납입상태 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_PAYMENT_STATUS") + ProductToolResponse getContractPaymentStatus(ProductToolRequest request); + + @McpTool(name = "pro_contract_beneficiary", title = "보험계약 수익자 조회", description = "보험계약 수익자 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_BENEFICIARY") + ProductToolResponse getContractBeneficiary(ProductToolRequest request); + + @McpTool(name = "pro_contract_surrender_value", title = "보험계약 해지환급금 조회", description = "보험계약 해지환급금 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_SURRENDER_VALUE") + ProductToolResponse getContractSurrenderValue(ProductToolRequest request); + + @McpTool(name = "pro_contract_loan_status", title = "보험계약 대출현황 조회", description = "보험계약 대출현황 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_LOAN_STATUS") + ProductToolResponse getContractLoanStatus(ProductToolRequest request); + + @McpTool(name = "pro_contract_rider_list", title = "보험계약 특약목록 조회", description = "보험계약 특약목록 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CONTRACT_RIDER_LIST") + ProductToolResponse getContractRiderList(ProductToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/CustomerInquiryUseCase.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/CustomerInquiryUseCase.java new file mode 100644 index 000000000..9b9bd2e91 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/CustomerInquiryUseCase.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface CustomerInquiryUseCase { + @McpTool(name = "pro_customer_profile", title = "고객 기본정보 조회", description = "고객 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_PROFILE") + ProductToolResponse getCustomerProfile(ProductToolRequest request); + + @McpTool(name = "pro_customer_contact", title = "고객 연락처 조회", description = "고객 연락처 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_CONTACT") + ProductToolResponse getCustomerContact(ProductToolRequest request); + + @McpTool(name = "pro_customer_consent", title = "고객 동의정보 조회", description = "고객 동의정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_CONSENT") + ProductToolResponse getCustomerConsent(ProductToolRequest request); + + @McpTool(name = "pro_customer_identity", title = "고객 본인확인정보 조회", description = "고객 본인확인정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_IDENTITY") + ProductToolResponse getCustomerIdentity(ProductToolRequest request); + + @McpTool(name = "pro_customer_address", title = "고객 주소정보 조회", description = "고객 주소정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_ADDRESS") + ProductToolResponse getCustomerAddress(ProductToolRequest request); + + @McpTool(name = "pro_customer_occupation", title = "고객 직업정보 조회", description = "고객 직업정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_OCCUPATION") + ProductToolResponse getCustomerOccupation(ProductToolRequest request); + + @McpTool(name = "pro_customer_family", title = "고객 가족정보 조회", description = "고객 가족정보 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_FAMILY") + ProductToolResponse getCustomerFamily(ProductToolRequest request); + + @McpTool(name = "pro_customer_risk_grade", title = "고객 위험등급 조회", description = "고객 위험등급 조회 정보를 안전한 모의 데이터로 제공합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pro", mappingId = "DIRECT_PRO_CUSTOMER_RISK_GRADE") + ProductToolResponse getCustomerRiskGrade(ProductToolRequest request); +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ClaimInquiryUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ClaimInquiryUseCaseImpl.java new file mode 100644 index 000000000..84436ffd1 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ClaimInquiryUseCaseImpl.java @@ -0,0 +1,66 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import io.shinhanlife.dap.mcc.biz.pro.usecase.ClaimInquiryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class ClaimInquiryUseCaseImpl implements ClaimInquiryUseCase { + @Override + public ProductToolResponse getClaimEligibility(ProductToolRequest request) { + return mockResponse(request, "pro_claim_eligibility", "보험금 청구 가능여부 조회"); + } + + @Override + public ProductToolResponse getClaimHistory(ProductToolRequest request) { + return mockResponse(request, "pro_claim_history", "보험금 청구이력 조회"); + } + + @Override + public ProductToolResponse getClaimStatus(ProductToolRequest request) { + return mockResponse(request, "pro_claim_status", "보험금 청구상태 조회"); + } + + @Override + public ProductToolResponse getClaimDocuments(ProductToolRequest request) { + return mockResponse(request, "pro_claim_documents", "보험금 청구서류 조회"); + } + + @Override + public ProductToolResponse getClaimPayment(ProductToolRequest request) { + return mockResponse(request, "pro_claim_payment", "보험금 지급내역 조회"); + } + + @Override + public ProductToolResponse getClaimAccident(ProductToolRequest request) { + return mockResponse(request, "pro_claim_accident", "보험사고 정보 조회"); + } + + @Override + public ProductToolResponse getClaimBeneficiary(ProductToolRequest request) { + return mockResponse(request, "pro_claim_beneficiary", "보험금 수익자 조회"); + } + + @Override + public ProductToolResponse getClaimContact(ProductToolRequest request) { + return mockResponse(request, "pro_claim_contact", "보험금 청구 담당자 조회"); + } + + private ProductToolResponse mockResponse(ProductToolRequest request, String toolName, String title) { + ProductToolResponse response = new ProductToolResponse(); + response.setToolName(toolName); + response.setCategory("PRO"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "CUST-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ContractInquiryUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ContractInquiryUseCaseImpl.java new file mode 100644 index 000000000..ee6b4bc5c --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ContractInquiryUseCaseImpl.java @@ -0,0 +1,71 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import io.shinhanlife.dap.mcc.biz.pro.usecase.ContractInquiryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class ContractInquiryUseCaseImpl implements ContractInquiryUseCase { + @Override + public ProductToolResponse getContractList(ProductToolRequest request) { + return mockResponse(request, "pro_contract_list", "보험계약 목록 조회"); + } + + @Override + public ProductToolResponse getContractDetail(ProductToolRequest request) { + return mockResponse(request, "pro_contract_detail", "보험계약 상세 조회"); + } + + @Override + public ProductToolResponse getContractCoverage(ProductToolRequest request) { + return mockResponse(request, "pro_contract_coverage", "보험계약 보장내역 조회"); + } + + @Override + public ProductToolResponse getContractPremium(ProductToolRequest request) { + return mockResponse(request, "pro_contract_premium", "보험계약 보험료 조회"); + } + + @Override + public ProductToolResponse getContractPaymentStatus(ProductToolRequest request) { + return mockResponse(request, "pro_contract_payment_status", "보험료 납입상태 조회"); + } + + @Override + public ProductToolResponse getContractBeneficiary(ProductToolRequest request) { + return mockResponse(request, "pro_contract_beneficiary", "보험계약 수익자 조회"); + } + + @Override + public ProductToolResponse getContractSurrenderValue(ProductToolRequest request) { + return mockResponse(request, "pro_contract_surrender_value", "보험계약 해지환급금 조회"); + } + + @Override + public ProductToolResponse getContractLoanStatus(ProductToolRequest request) { + return mockResponse(request, "pro_contract_loan_status", "보험계약 대출현황 조회"); + } + + @Override + public ProductToolResponse getContractRiderList(ProductToolRequest request) { + return mockResponse(request, "pro_contract_rider_list", "보험계약 특약목록 조회"); + } + + private ProductToolResponse mockResponse(ProductToolRequest request, String toolName, String title) { + ProductToolResponse response = new ProductToolResponse(); + response.setToolName(toolName); + response.setCategory("PRO"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "CUST-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/CustomerInquiryUseCaseImpl.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/CustomerInquiryUseCaseImpl.java new file mode 100644 index 000000000..15feb4a84 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/CustomerInquiryUseCaseImpl.java @@ -0,0 +1,66 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolResponse; +import io.shinhanlife.dap.mcc.biz.pro.usecase.CustomerInquiryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class CustomerInquiryUseCaseImpl implements CustomerInquiryUseCase { + @Override + public ProductToolResponse getCustomerProfile(ProductToolRequest request) { + return mockResponse(request, "pro_customer_profile", "고객 기본정보 조회"); + } + + @Override + public ProductToolResponse getCustomerContact(ProductToolRequest request) { + return mockResponse(request, "pro_customer_contact", "고객 연락처 조회"); + } + + @Override + public ProductToolResponse getCustomerConsent(ProductToolRequest request) { + return mockResponse(request, "pro_customer_consent", "고객 동의정보 조회"); + } + + @Override + public ProductToolResponse getCustomerIdentity(ProductToolRequest request) { + return mockResponse(request, "pro_customer_identity", "고객 본인확인정보 조회"); + } + + @Override + public ProductToolResponse getCustomerAddress(ProductToolRequest request) { + return mockResponse(request, "pro_customer_address", "고객 주소정보 조회"); + } + + @Override + public ProductToolResponse getCustomerOccupation(ProductToolRequest request) { + return mockResponse(request, "pro_customer_occupation", "고객 직업정보 조회"); + } + + @Override + public ProductToolResponse getCustomerFamily(ProductToolRequest request) { + return mockResponse(request, "pro_customer_family", "고객 가족정보 조회"); + } + + @Override + public ProductToolResponse getCustomerRiskGrade(ProductToolRequest request) { + return mockResponse(request, "pro_customer_risk_grade", "고객 위험등급 조회"); + } + + private ProductToolResponse mockResponse(ProductToolRequest request, String toolName, String title) { + ProductToolResponse response = new ProductToolResponse(); + response.setToolName(toolName); + response.setCategory("PRO"); + response.setReferenceId(request != null && hasText(request.getSubjectId()) ? request.getSubjectId() : "CUST-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/pro/DapWasProApplication.java b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/pro/DapWasProApplication.java new file mode 100644 index 000000000..2b1921f34 --- /dev/null +++ b/dap-was-pro/src/main/java/io/shinhanlife/dap/mcc/pro/DapWasProApplication.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.pro; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; + +/** + * @package io.shinhanlife.dap.mcc.pro + * @className DapWasProApplication + * @description AX HUB 시스템 처리 클래스 + * @author system + * @create 2026.08.13 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.13  system    최초생성
+ *
+ * 
+ */ +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +@Import(ToolMcpServerConfiguration.class) +public class DapWasProApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasProApplication.class, args); + } +} diff --git a/dap-was-pro/src/main/resources/application-dev.yml b/dap-was-pro/src/main/resources/application-dev.yml new file mode 100644 index 000000000..44d61de2b --- /dev/null +++ b/dap-was-pro/src/main/resources/application-dev.yml @@ -0,0 +1,46 @@ +# OCI 클라우드 환경 전용 설정 +server: + port: ${PORT:8085} + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml + +axhub: + gateway: + url: https://axhubmcp.devjun.net + tool: + url: http://144.24.70.100:8085 + +eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + +shinhan: + integration: + envrTypeCd: D + eai: + url: http://10.176.32.181 + internalMci: + url: http://10.176.32.173 + bancaMci: + url: http://10.176.32.117 + externalMci: + url: http://10.176.32.176 diff --git a/dap-was-pro/src/main/resources/application-local.yml b/dap-was-pro/src/main/resources/application-local.yml new file mode 100644 index 000000000..caa93c7fd --- /dev/null +++ b/dap-was-pro/src/main/resources/application-local.yml @@ -0,0 +1,45 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} diff --git a/dap-was-pro/src/main/resources/application-prod.yml b/dap-was-pro/src/main/resources/application-prod.yml new file mode 100644 index 000000000..682467fd7 --- /dev/null +++ b/dap-was-pro/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8085} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-pro/src/main/resources/application-test.yml b/dap-was-pro/src/main/resources/application-test.yml new file mode 100644 index 000000000..e2bc327dd --- /dev/null +++ b/dap-was-pro/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8085} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-pro/src/main/resources/application.yml b/dap-was-pro/src/main/resources/application.yml new file mode 100644 index 000000000..8838ee170 --- /dev/null +++ b/dap-was-pro/src/main/resources/application.yml @@ -0,0 +1,18 @@ +server: + port: 8085 +spring: + application: + name: dap-was-pro + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: was-pro + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL diff --git a/dap-was-pro/src/main/resources/logback-spring.xml b/dap-was-pro/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..93c7c9635 --- /dev/null +++ b/dap-was-pro/src/main/resources/logback-spring.xml @@ -0,0 +1,24 @@ + + + + + + ${LOG_PATTERN} + + + + logs/dap-was-pro.log + + logs/dap-was-pro-%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN} + + + + + + + + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_activity_status.json b/dap-was-pro/src/main/resources/mock-responses/agt_activity_status.json new file mode 100644 index 000000000..7b8ce2854 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_activity_status.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_activity_status\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 활동상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 활동상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_affiliation_history.json b/dap-was-pro/src/main/resources/mock-responses/agt_affiliation_history.json new file mode 100644 index 000000000..4b573fb5c --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_affiliation_history.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_affiliation_history\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 소속변경 이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 소속변경 이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_agent_compliance.json b/dap-was-pro/src/main/resources/mock-responses/agt_agent_compliance.json new file mode 100644 index 000000000..a0d8b3c52 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_agent_compliance.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_agent_compliance\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 준법정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 준법정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_agent_education.json b/dap-was-pro/src/main/resources/mock-responses/agt_agent_education.json new file mode 100644 index 000000000..394e47a98 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_agent_education.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_agent_education\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 교육이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 교육이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_agent_license.json b/dap-was-pro/src/main/resources/mock-responses/agt_agent_license.json new file mode 100644 index 000000000..64c55e405 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_agent_license.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_agent_license\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 자격정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 자격정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_agent_profile.json b/dap-was-pro/src/main/resources/mock-responses/agt_agent_profile.json new file mode 100644 index 000000000..904f04fc8 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_agent_profile.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_agent_profile\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 기본정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 기본정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_appointment_status.json b/dap-was-pro/src/main/resources/mock-responses/agt_appointment_status.json new file mode 100644 index 000000000..df1d06cee --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_appointment_status.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_appointment_status\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 위촉상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 위촉상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_branch_list.json b/dap-was-pro/src/main/resources/mock-responses/agt_branch_list.json new file mode 100644 index 000000000..1dc63fc4f --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_branch_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_branch_list\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 지점목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 지점목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_campaign.json b/dap-was-pro/src/main/resources/mock-responses/agt_campaign.json new file mode 100644 index 000000000..b147a7536 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_campaign.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_campaign\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 영업 캠페인 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 영업 캠페인 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_claim_support.json b/dap-was-pro/src/main/resources/mock-responses/agt_claim_support.json new file mode 100644 index 000000000..ae1472c6a --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_claim_support.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_claim_support\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 보험금 청구지원 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 보험금 청구지원 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_commission_grade.json b/dap-was-pro/src/main/resources/mock-responses/agt_commission_grade.json new file mode 100644 index 000000000..7c92bc791 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_commission_grade.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_commission_grade\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 수수료등급 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 수수료등급 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_commission_summary.json b/dap-was-pro/src/main/resources/mock-responses/agt_commission_summary.json new file mode 100644 index 000000000..62b58a4e2 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_commission_summary.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_commission_summary\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 수수료 요약 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 수수료 요약 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_contract_count.json b/dap-was-pro/src/main/resources/mock-responses/agt_contract_count.json new file mode 100644 index 000000000..fddf80dc5 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_contract_count.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_contract_count\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 계약건수 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 계약건수 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_customer_assignment.json b/dap-was-pro/src/main/resources/mock-responses/agt_customer_assignment.json new file mode 100644 index 000000000..2c1fb0554 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_customer_assignment.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_customer_assignment\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 고객배정 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 고객배정 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_ga_profile.json b/dap-was-pro/src/main/resources/mock-responses/agt_ga_profile.json new file mode 100644 index 000000000..a5ba867b5 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_ga_profile.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_ga_profile\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 조직 기본정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 조직 기본정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_notice_list.json b/dap-was-pro/src/main/resources/mock-responses/agt_notice_list.json new file mode 100644 index 000000000..dfc71983b --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_notice_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_notice_list\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 공지목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 공지목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_organization_agent_list.json b/dap-was-pro/src/main/resources/mock-responses/agt_organization_agent_list.json new file mode 100644 index 000000000..e14bf9a81 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_organization_agent_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_organization_agent_list\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 소속 설계사 목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 소속 설계사 목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_organization_manager.json b/dap-was-pro/src/main/resources/mock-responses/agt_organization_manager.json new file mode 100644 index 000000000..fc55fc793 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_organization_manager.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_organization_manager\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 조직 책임자 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 조직 책임자 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_organization_tree.json b/dap-was-pro/src/main/resources/mock-responses/agt_organization_tree.json new file mode 100644 index 000000000..304b2f4bc --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_organization_tree.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_organization_tree\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 조직도 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 조직도 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_persistency_rate.json b/dap-was-pro/src/main/resources/mock-responses/agt_persistency_rate.json new file mode 100644 index 000000000..9938d7a5e --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_persistency_rate.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_persistency_rate\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 계약유지율 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 계약유지율 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_premium_summary.json b/dap-was-pro/src/main/resources/mock-responses/agt_premium_summary.json new file mode 100644 index 000000000..d5d25beee --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_premium_summary.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_premium_summary\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 보험료 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 보험료 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_production_summary.json b/dap-was-pro/src/main/resources/mock-responses/agt_production_summary.json new file mode 100644 index 000000000..61fe3063b --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_production_summary.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_production_summary\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 신계약 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 신계약 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_sales_area.json b/dap-was-pro/src/main/resources/mock-responses/agt_sales_area.json new file mode 100644 index 000000000..9c68e4f67 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_sales_area.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_sales_area\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 영업지역 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 영업지역 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_support_contact.json b/dap-was-pro/src/main/resources/mock-responses/agt_support_contact.json new file mode 100644 index 000000000..d9c8f4bbd --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_support_contact.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_support_contact\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 업무지원 연락처 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 업무지원 연락처 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/agt_task_list.json b/dap-was-pro/src/main/resources/mock-responses/agt_task_list.json new file mode 100644 index 000000000..244646a74 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/agt_task_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"agt_task_list\",\"category\":\"AGT\",\"referenceId\":\"AGT-000001\",\"status\":\"정상\",\"summary\":\"GA 설계사 업무목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 설계사 업무목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_accident.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_accident.json new file mode 100644 index 000000000..9d4c089b0 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_accident.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_accident\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험사고 정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험사고 정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_beneficiary.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_beneficiary.json new file mode 100644 index 000000000..e53e25011 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_beneficiary.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_beneficiary\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 수익자 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 수익자 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_contact.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_contact.json new file mode 100644 index 000000000..6eee428c5 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_contact.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_contact\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 청구 담당자 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 청구 담당자 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_documents.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_documents.json new file mode 100644 index 000000000..6c09b4dfe --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_documents.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_documents\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 청구서류 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 청구서류 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_eligibility.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_eligibility.json new file mode 100644 index 000000000..996bd8d68 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_eligibility.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_eligibility\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 청구 가능여부 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 청구 가능여부 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_history.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_history.json new file mode 100644 index 000000000..9a2d5d9bd --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_history.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_history\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 청구이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 청구이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_payment.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_payment.json new file mode 100644 index 000000000..e794dced0 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_payment.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_payment\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 지급내역 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 지급내역 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_claim_status.json b/dap-was-pro/src/main/resources/mock-responses/pro_claim_status.json new file mode 100644 index 000000000..515c1c0a4 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_claim_status.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_claim_status\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험금 청구상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험금 청구상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_beneficiary.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_beneficiary.json new file mode 100644 index 000000000..c0cb8437c --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_beneficiary.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_beneficiary\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 수익자 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 수익자 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_coverage.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_coverage.json new file mode 100644 index 000000000..f287f0eb4 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_coverage.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_coverage\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 보장내역 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 보장내역 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_detail.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_detail.json new file mode 100644 index 000000000..1ccd171f8 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_detail.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_detail\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 상세 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 상세 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_list.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_list.json new file mode 100644 index 000000000..d945c38a0 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_list\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_loan_status.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_loan_status.json new file mode 100644 index 000000000..7157fa8bf --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_loan_status.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_loan_status\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 대출현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 대출현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_payment_status.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_payment_status.json new file mode 100644 index 000000000..66396b4a6 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_payment_status.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_payment_status\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험료 납입상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험료 납입상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_premium.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_premium.json new file mode 100644 index 000000000..0feacdaf6 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_premium.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_premium\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 보험료 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 보험료 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_rider_list.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_rider_list.json new file mode 100644 index 000000000..26020cba2 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_rider_list.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_rider_list\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 특약목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 특약목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_contract_surrender_value.json b/dap-was-pro/src/main/resources/mock-responses/pro_contract_surrender_value.json new file mode 100644 index 000000000..a9045beb1 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_contract_surrender_value.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_contract_surrender_value\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"보험계약 해지환급금 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험계약 해지환급금 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_address.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_address.json new file mode 100644 index 000000000..2ecf34bbb --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_address.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_address\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 주소정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 주소정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_consent.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_consent.json new file mode 100644 index 000000000..11c7191c6 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_consent.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_consent\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 동의정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 동의정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_contact.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_contact.json new file mode 100644 index 000000000..3c5d4963c --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_contact.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_contact\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 연락처 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 연락처 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_family.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_family.json new file mode 100644 index 000000000..e50625e86 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_family.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_family\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 가족정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 가족정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_identity.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_identity.json new file mode 100644 index 000000000..d14d7ada5 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_identity.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_identity\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 본인확인정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 본인확인정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_occupation.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_occupation.json new file mode 100644 index 000000000..24839754a --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_occupation.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_occupation\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 직업정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 직업정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_profile.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_profile.json new file mode 100644 index 000000000..de5be84b2 --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_profile.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_profile\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 기본정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 기본정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/mock-responses/pro_customer_risk_grade.json b/dap-was-pro/src/main/resources/mock-responses/pro_customer_risk_grade.json new file mode 100644 index 000000000..d0b3ebe8a --- /dev/null +++ b/dap-was-pro/src/main/resources/mock-responses/pro_customer_risk_grade.json @@ -0,0 +1,5 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pro_customer_risk_grade\",\"category\":\"PRO\",\"referenceId\":\"CUST-000001\",\"status\":\"정상\",\"summary\":\"고객 위험등급 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 위험등급 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} + diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_activity_status.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_activity_status.yml new file mode 100644 index 000000000..38c003d39 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_activity_status.yml @@ -0,0 +1,24 @@ +name: agt_activity_status +display_name: GA 설계사 활동상태 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 활동상태 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 활동상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 활동상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 활동상태 조회 정보를 보여줘", "GA 설계사 활동상태 조회 결과를 확인해줘", "현재 GA 설계사 활동상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_affiliation_history.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_affiliation_history.yml new file mode 100644 index 000000000..78fd62a51 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_affiliation_history.yml @@ -0,0 +1,24 @@ +name: agt_affiliation_history +display_name: GA 소속변경 이력 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 소속변경 이력 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 소속변경 이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 소속변경 이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 소속변경 이력 조회 정보를 보여줘", "GA 소속변경 이력 조회 결과를 확인해줘", "현재 GA 소속변경 이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_compliance.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_compliance.yml new file mode 100644 index 000000000..c641bab0d --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_compliance.yml @@ -0,0 +1,24 @@ +name: agt_agent_compliance +display_name: GA 설계사 준법정보 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 준법정보 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 준법정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 준법정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 준법정보 조회 정보를 보여줘", "GA 설계사 준법정보 조회 결과를 확인해줘", "현재 GA 설계사 준법정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_education.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_education.yml new file mode 100644 index 000000000..cb475b29c --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_education.yml @@ -0,0 +1,24 @@ +name: agt_agent_education +display_name: GA 설계사 교육이력 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 교육이력 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 교육이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 교육이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 교육이력 조회 정보를 보여줘", "GA 설계사 교육이력 조회 결과를 확인해줘", "현재 GA 설계사 교육이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_license.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_license.yml new file mode 100644 index 000000000..63113bac5 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_license.yml @@ -0,0 +1,24 @@ +name: agt_agent_license +display_name: GA 설계사 자격정보 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 자격정보 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 자격정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 자격정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 자격정보 조회 정보를 보여줘", "GA 설계사 자격정보 조회 결과를 확인해줘", "현재 GA 설계사 자격정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_profile.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_profile.yml new file mode 100644 index 000000000..28b50051a --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_agent_profile.yml @@ -0,0 +1,24 @@ +name: agt_agent_profile +display_name: GA 설계사 기본정보 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 기본정보 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 기본정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 기본정보 조회 정보를 보여줘", "GA 설계사 기본정보 조회 결과를 확인해줘", "현재 GA 설계사 기본정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_appointment_status.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_appointment_status.yml new file mode 100644 index 000000000..a90da83a4 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_appointment_status.yml @@ -0,0 +1,24 @@ +name: agt_appointment_status +display_name: GA 설계사 위촉상태 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 위촉상태 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 위촉상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 위촉상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 위촉상태 조회 정보를 보여줘", "GA 설계사 위촉상태 조회 결과를 확인해줘", "현재 GA 설계사 위촉상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_branch_list.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_branch_list.yml new file mode 100644 index 000000000..a274e4635 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_branch_list.yml @@ -0,0 +1,24 @@ +name: agt_branch_list +display_name: GA 지점목록 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 지점목록 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 지점목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 지점목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 지점목록 조회 정보를 보여줘", "GA 지점목록 조회 결과를 확인해줘", "현재 GA 지점목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_campaign.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_campaign.yml new file mode 100644 index 000000000..3b17223eb --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_campaign.yml @@ -0,0 +1,24 @@ +name: agt_campaign +display_name: GA 영업 캠페인 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 영업 캠페인 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 영업 캠페인 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 영업 캠페인 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 영업 캠페인 조회 정보를 보여줘", "GA 영업 캠페인 조회 결과를 확인해줘", "현재 GA 영업 캠페인 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_claim_support.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_claim_support.yml new file mode 100644 index 000000000..c12a37ff7 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_claim_support.yml @@ -0,0 +1,24 @@ +name: agt_claim_support +display_name: GA 보험금 청구지원 현황 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 보험금 청구지원 현황 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 보험금 청구지원 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 보험금 청구지원 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 보험금 청구지원 현황 조회 정보를 보여줘", "GA 보험금 청구지원 현황 조회 결과를 확인해줘", "현재 GA 보험금 청구지원 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_grade.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_grade.yml new file mode 100644 index 000000000..d459ab9cf --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_grade.yml @@ -0,0 +1,24 @@ +name: agt_commission_grade +display_name: GA 설계사 수수료등급 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 수수료등급 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 수수료등급 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 수수료등급 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 수수료등급 조회 정보를 보여줘", "GA 설계사 수수료등급 조회 결과를 확인해줘", "현재 GA 설계사 수수료등급 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_summary.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_summary.yml new file mode 100644 index 000000000..3ab151484 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_commission_summary.yml @@ -0,0 +1,24 @@ +name: agt_commission_summary +display_name: GA 설계사 수수료 요약 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 수수료 요약 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 수수료 요약 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 수수료 요약 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 수수료 요약 조회 정보를 보여줘", "GA 설계사 수수료 요약 조회 결과를 확인해줘", "현재 GA 설계사 수수료 요약 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_contract_count.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_contract_count.yml new file mode 100644 index 000000000..a9c1b0c73 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_contract_count.yml @@ -0,0 +1,24 @@ +name: agt_contract_count +display_name: GA 설계사 계약건수 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 계약건수 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 계약건수 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 계약건수 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 계약건수 조회 정보를 보여줘", "GA 설계사 계약건수 조회 결과를 확인해줘", "현재 GA 설계사 계약건수 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_customer_assignment.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_customer_assignment.yml new file mode 100644 index 000000000..94ec4fc61 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_customer_assignment.yml @@ -0,0 +1,24 @@ +name: agt_customer_assignment +display_name: GA 설계사 고객배정 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 고객배정 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 고객배정 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 고객배정 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 고객배정 조회 정보를 보여줘", "GA 설계사 고객배정 조회 결과를 확인해줘", "현재 GA 설계사 고객배정 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_ga_profile.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_ga_profile.yml new file mode 100644 index 000000000..b16e221a9 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_ga_profile.yml @@ -0,0 +1,24 @@ +name: agt_ga_profile +display_name: GA 조직 기본정보 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 조직 기본정보 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 조직 기본정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 조직 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 조직 기본정보 조회 정보를 보여줘", "GA 조직 기본정보 조회 결과를 확인해줘", "현재 GA 조직 기본정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_notice_list.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_notice_list.yml new file mode 100644 index 000000000..1e06ed263 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_notice_list.yml @@ -0,0 +1,24 @@ +name: agt_notice_list +display_name: GA 설계사 공지목록 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 공지목록 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 공지목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 공지목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 공지목록 조회 정보를 보여줘", "GA 설계사 공지목록 조회 결과를 확인해줘", "현재 GA 설계사 공지목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_agent_list.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_agent_list.yml new file mode 100644 index 000000000..77960379b --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_agent_list.yml @@ -0,0 +1,24 @@ +name: agt_organization_agent_list +display_name: GA 소속 설계사 목록 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 소속 설계사 목록 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 소속 설계사 목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 소속 설계사 목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 소속 설계사 목록 조회 정보를 보여줘", "GA 소속 설계사 목록 조회 결과를 확인해줘", "현재 GA 소속 설계사 목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_manager.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_manager.yml new file mode 100644 index 000000000..99b552975 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_manager.yml @@ -0,0 +1,24 @@ +name: agt_organization_manager +display_name: GA 조직 책임자 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 조직 책임자 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 조직 책임자 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 조직 책임자 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 조직 책임자 조회 정보를 보여줘", "GA 조직 책임자 조회 결과를 확인해줘", "현재 GA 조직 책임자 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_tree.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_tree.yml new file mode 100644 index 000000000..f790f3208 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_organization_tree.yml @@ -0,0 +1,24 @@ +name: agt_organization_tree +display_name: GA 조직도 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 조직도 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 조직도 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 조직도 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 조직도 조회 정보를 보여줘", "GA 조직도 조회 결과를 확인해줘", "현재 GA 조직도 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_persistency_rate.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_persistency_rate.yml new file mode 100644 index 000000000..5b54f4b94 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_persistency_rate.yml @@ -0,0 +1,24 @@ +name: agt_persistency_rate +display_name: GA 설계사 계약유지율 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 계약유지율 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 계약유지율 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 계약유지율 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 계약유지율 조회 정보를 보여줘", "GA 설계사 계약유지율 조회 결과를 확인해줘", "현재 GA 설계사 계약유지율 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_premium_summary.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_premium_summary.yml new file mode 100644 index 000000000..f53062668 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_premium_summary.yml @@ -0,0 +1,24 @@ +name: agt_premium_summary +display_name: GA 설계사 보험료 실적 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 보험료 실적 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 보험료 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 보험료 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 보험료 실적 조회 정보를 보여줘", "GA 설계사 보험료 실적 조회 결과를 확인해줘", "현재 GA 설계사 보험료 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_production_summary.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_production_summary.yml new file mode 100644 index 000000000..c564daa65 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_production_summary.yml @@ -0,0 +1,24 @@ +name: agt_production_summary +display_name: GA 설계사 신계약 실적 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 신계약 실적 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 신계약 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 신계약 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 신계약 실적 조회 정보를 보여줘", "GA 설계사 신계약 실적 조회 결과를 확인해줘", "현재 GA 설계사 신계약 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_sales_area.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_sales_area.yml new file mode 100644 index 000000000..991c0fd90 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_sales_area.yml @@ -0,0 +1,24 @@ +name: agt_sales_area +display_name: GA 영업지역 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 영업지역 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 영업지역 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 영업지역 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 영업지역 조회 정보를 보여줘", "GA 영업지역 조회 결과를 확인해줘", "현재 GA 영업지역 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_support_contact.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_support_contact.yml new file mode 100644 index 000000000..f01cad4c6 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_support_contact.yml @@ -0,0 +1,24 @@ +name: agt_support_contact +display_name: GA 업무지원 연락처 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 업무지원 연락처 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 업무지원 연락처 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 업무지원 연락처 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 업무지원 연락처 조회 정보를 보여줘", "GA 업무지원 연락처 조회 결과를 확인해줘", "현재 GA 업무지원 연락처 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/agt/agt_task_list.yml b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_task_list.yml new file mode 100644 index 000000000..534619147 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/agt/agt_task_list.yml @@ -0,0 +1,24 @@ +name: agt_task_list +display_name: GA 설계사 업무목록 조회 +version: 1.0.0 +category_key: agt +description: + function: GA 설계사 업무목록 조회 정보를 조회합니다. + when_to_use: GA·설계사 업무에서 GA 설계사 업무목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 설계사 업무목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 설계사 업무목록 조회 정보를 보여줘", "GA 설계사 업무목록 조회 결과를 확인해줘", "현재 GA 설계사 업무목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [agt, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_accident.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_accident.yml new file mode 100644 index 000000000..b3194a9d0 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_accident.yml @@ -0,0 +1,24 @@ +name: pro_claim_accident +display_name: 보험사고 정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험사고 정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험사고 정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험사고 정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험사고 정보 조회 정보를 보여줘", "보험사고 정보 조회 결과를 확인해줘", "현재 보험사고 정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_beneficiary.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_beneficiary.yml new file mode 100644 index 000000000..6297ece86 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_beneficiary.yml @@ -0,0 +1,24 @@ +name: pro_claim_beneficiary +display_name: 보험금 수익자 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 수익자 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 수익자 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 수익자 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 수익자 조회 정보를 보여줘", "보험금 수익자 조회 결과를 확인해줘", "현재 보험금 수익자 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_contact.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_contact.yml new file mode 100644 index 000000000..c0e17a80e --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_contact.yml @@ -0,0 +1,24 @@ +name: pro_claim_contact +display_name: 보험금 청구 담당자 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 청구 담당자 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 청구 담당자 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 청구 담당자 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 청구 담당자 조회 정보를 보여줘", "보험금 청구 담당자 조회 결과를 확인해줘", "현재 보험금 청구 담당자 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_documents.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_documents.yml new file mode 100644 index 000000000..574844222 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_documents.yml @@ -0,0 +1,24 @@ +name: pro_claim_documents +display_name: 보험금 청구서류 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 청구서류 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 청구서류 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 청구서류 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 청구서류 조회 정보를 보여줘", "보험금 청구서류 조회 결과를 확인해줘", "현재 보험금 청구서류 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_eligibility.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_eligibility.yml new file mode 100644 index 000000000..1056bc5ee --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_eligibility.yml @@ -0,0 +1,24 @@ +name: pro_claim_eligibility +display_name: 보험금 청구 가능여부 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 청구 가능여부 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 청구 가능여부 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 청구 가능여부 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 청구 가능여부 조회 정보를 보여줘", "보험금 청구 가능여부 조회 결과를 확인해줘", "현재 보험금 청구 가능여부 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_history.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_history.yml new file mode 100644 index 000000000..3759ef47f --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_history.yml @@ -0,0 +1,24 @@ +name: pro_claim_history +display_name: 보험금 청구이력 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 청구이력 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 청구이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 청구이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 청구이력 조회 정보를 보여줘", "보험금 청구이력 조회 결과를 확인해줘", "현재 보험금 청구이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_payment.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_payment.yml new file mode 100644 index 000000000..8072bdaac --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_payment.yml @@ -0,0 +1,24 @@ +name: pro_claim_payment +display_name: 보험금 지급내역 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 지급내역 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 지급내역 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 지급내역 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 지급내역 조회 정보를 보여줘", "보험금 지급내역 조회 결과를 확인해줘", "현재 보험금 지급내역 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_status.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_status.yml new file mode 100644 index 000000000..df68efb7b --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_claim_status.yml @@ -0,0 +1,24 @@ +name: pro_claim_status +display_name: 보험금 청구상태 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험금 청구상태 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험금 청구상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험금 청구상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험금 청구상태 조회 정보를 보여줘", "보험금 청구상태 조회 결과를 확인해줘", "현재 보험금 청구상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_beneficiary.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_beneficiary.yml new file mode 100644 index 000000000..bc6e8979c --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_beneficiary.yml @@ -0,0 +1,24 @@ +name: pro_contract_beneficiary +display_name: 보험계약 수익자 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 수익자 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 수익자 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 수익자 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 수익자 조회 정보를 보여줘", "보험계약 수익자 조회 결과를 확인해줘", "현재 보험계약 수익자 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_coverage.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_coverage.yml new file mode 100644 index 000000000..50fa078e1 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_coverage.yml @@ -0,0 +1,24 @@ +name: pro_contract_coverage +display_name: 보험계약 보장내역 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 보장내역 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 보장내역 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 보장내역 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 보장내역 조회 정보를 보여줘", "보험계약 보장내역 조회 결과를 확인해줘", "현재 보험계약 보장내역 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_detail.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_detail.yml new file mode 100644 index 000000000..6eb9483c2 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_detail.yml @@ -0,0 +1,24 @@ +name: pro_contract_detail +display_name: 보험계약 상세 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 상세 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 상세 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 상세 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 상세 조회 정보를 보여줘", "보험계약 상세 조회 결과를 확인해줘", "현재 보험계약 상세 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_list.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_list.yml new file mode 100644 index 000000000..7dd56eade --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_list.yml @@ -0,0 +1,24 @@ +name: pro_contract_list +display_name: 보험계약 목록 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 목록 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 목록 조회 정보를 보여줘", "보험계약 목록 조회 결과를 확인해줘", "현재 보험계약 목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_loan_status.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_loan_status.yml new file mode 100644 index 000000000..fc695cd6e --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_loan_status.yml @@ -0,0 +1,24 @@ +name: pro_contract_loan_status +display_name: 보험계약 대출현황 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 대출현황 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 대출현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 대출현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 대출현황 조회 정보를 보여줘", "보험계약 대출현황 조회 결과를 확인해줘", "현재 보험계약 대출현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_payment_status.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_payment_status.yml new file mode 100644 index 000000000..486498be1 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_payment_status.yml @@ -0,0 +1,24 @@ +name: pro_contract_payment_status +display_name: 보험료 납입상태 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험료 납입상태 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험료 납입상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험료 납입상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험료 납입상태 조회 정보를 보여줘", "보험료 납입상태 조회 결과를 확인해줘", "현재 보험료 납입상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_premium.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_premium.yml new file mode 100644 index 000000000..6046161a8 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_premium.yml @@ -0,0 +1,24 @@ +name: pro_contract_premium +display_name: 보험계약 보험료 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 보험료 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 보험료 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 보험료 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 보험료 조회 정보를 보여줘", "보험계약 보험료 조회 결과를 확인해줘", "현재 보험계약 보험료 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_rider_list.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_rider_list.yml new file mode 100644 index 000000000..dea9d6918 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_rider_list.yml @@ -0,0 +1,24 @@ +name: pro_contract_rider_list +display_name: 보험계약 특약목록 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 특약목록 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 특약목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 특약목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 특약목록 조회 정보를 보여줘", "보험계약 특약목록 조회 결과를 확인해줘", "현재 보험계약 특약목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_surrender_value.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_surrender_value.yml new file mode 100644 index 000000000..3a070960a --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_contract_surrender_value.yml @@ -0,0 +1,24 @@ +name: pro_contract_surrender_value +display_name: 보험계약 해지환급금 조회 +version: 1.0.0 +category_key: pro +description: + function: 보험계약 해지환급금 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 보험계약 해지환급금 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험계약 해지환급금 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험계약 해지환급금 조회 정보를 보여줘", "보험계약 해지환급금 조회 결과를 확인해줘", "현재 보험계약 해지환급금 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_address.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_address.yml new file mode 100644 index 000000000..2c9e4efe7 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_address.yml @@ -0,0 +1,24 @@ +name: pro_customer_address +display_name: 고객 주소정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 주소정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 주소정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 주소정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 주소정보 조회 정보를 보여줘", "고객 주소정보 조회 결과를 확인해줘", "현재 고객 주소정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_consent.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_consent.yml new file mode 100644 index 000000000..c0704a292 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_consent.yml @@ -0,0 +1,24 @@ +name: pro_customer_consent +display_name: 고객 동의정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 동의정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 동의정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 동의정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 동의정보 조회 정보를 보여줘", "고객 동의정보 조회 결과를 확인해줘", "현재 고객 동의정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_contact.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_contact.yml new file mode 100644 index 000000000..4c0bb3e87 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_contact.yml @@ -0,0 +1,24 @@ +name: pro_customer_contact +display_name: 고객 연락처 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 연락처 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 연락처 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 연락처 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 연락처 조회 정보를 보여줘", "고객 연락처 조회 결과를 확인해줘", "현재 고객 연락처 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_family.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_family.yml new file mode 100644 index 000000000..715827600 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_family.yml @@ -0,0 +1,24 @@ +name: pro_customer_family +display_name: 고객 가족정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 가족정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 가족정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 가족정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 가족정보 조회 정보를 보여줘", "고객 가족정보 조회 결과를 확인해줘", "현재 고객 가족정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_identity.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_identity.yml new file mode 100644 index 000000000..3e8daf4c9 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_identity.yml @@ -0,0 +1,24 @@ +name: pro_customer_identity +display_name: 고객 본인확인정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 본인확인정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 본인확인정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 본인확인정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 본인확인정보 조회 정보를 보여줘", "고객 본인확인정보 조회 결과를 확인해줘", "현재 고객 본인확인정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_occupation.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_occupation.yml new file mode 100644 index 000000000..48608360c --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_occupation.yml @@ -0,0 +1,24 @@ +name: pro_customer_occupation +display_name: 고객 직업정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 직업정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 직업정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 직업정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 직업정보 조회 정보를 보여줘", "고객 직업정보 조회 결과를 확인해줘", "현재 고객 직업정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_profile.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_profile.yml new file mode 100644 index 000000000..889b1c951 --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_profile.yml @@ -0,0 +1,24 @@ +name: pro_customer_profile +display_name: 고객 기본정보 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 기본정보 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 기본정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 기본정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 기본정보 조회 정보를 보여줘", "고객 기본정보 조회 결과를 확인해줘", "현재 고객 기본정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_risk_grade.yml b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_risk_grade.yml new file mode 100644 index 000000000..3e7df232c --- /dev/null +++ b/dap-was-pro/src/main/resources/tool-definitions/pro/pro_customer_risk_grade.yml @@ -0,0 +1,24 @@ +name: pro_customer_risk_grade +display_name: 고객 위험등급 조회 +version: 1.0.0 +category_key: pro +description: + function: 고객 위험등급 조회 정보를 조회합니다. + when_to_use: 고객·보험계약 업무에서 고객 위험등급 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 위험등급 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 위험등급 조회 정보를 보여줘", "고객 위험등급 조회 결과를 확인해줘", "현재 고객 위험등급 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + subjectId: {type: string, description: 고객번호 또는 계약번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + detailLevel: {type: string, description: 조회 상세 수준, enum: [SUMMARY, DETAIL]} + additionalProperties: false +tags: [pro, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImplTest.java b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImplTest.java new file mode 100644 index 000000000..b9fb09f42 --- /dev/null +++ b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/agt/usecase/impl/AgentInquiryUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.agt.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.agt.dto.AgentToolRequest; +import org.junit.jupiter.api.Test; + +class AgentInquiryUseCaseImplTest { + + @Test + void returnsAgentProductionForRequestedAgent() { + AgentToolRequest request = new AgentToolRequest(); + request.setAgentId("AGT-123456"); + request.setQueryDate("2026-08-14"); + + var response = new AgentPerformanceUseCaseImpl().getProductionSummary(request); + + assertEquals("agt_production_summary", response.getToolName()); + assertEquals("AGT-123456", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ProductInquiryUseCaseImplTest.java b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ProductInquiryUseCaseImplTest.java new file mode 100644 index 000000000..8e9004542 --- /dev/null +++ b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/biz/pro/usecase/impl/ProductInquiryUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.pro.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.pro.dto.ProductToolRequest; +import org.junit.jupiter.api.Test; + +class ProductInquiryUseCaseImplTest { + + @Test + void returnsContractDetailForRequestedCustomer() { + ProductToolRequest request = new ProductToolRequest(); + request.setSubjectId("CUST-123456"); + request.setQueryDate("2026-08-14"); + + var response = new ContractInquiryUseCaseImpl().getContractDetail(request); + + assertEquals("pro_contract_detail", response.getToolName()); + assertEquals("CUST-123456", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/pro/ProMockToolResourcesTest.java b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/pro/ProMockToolResourcesTest.java new file mode 100644 index 000000000..c51fcd3d4 --- /dev/null +++ b/dap-was-pro/src/test/java/io/shinhanlife/dap/mcc/pro/ProMockToolResourcesTest.java @@ -0,0 +1,83 @@ +package io.shinhanlife.dap.mcc.pro; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionRepository; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.core.io.DefaultResourceLoader; + +class ProMockToolResourcesTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void registersTwentyFiveProAndTwentyFiveAgtTools() { + ToolDefinitionRepository repository = repository(); + Map definitions = repository.findAll(); + + assertEquals(50, definitions.size()); + assertEquals(25, definitions.values().stream() + .filter(definition -> "pro".equals(definition.categoryKey())).count()); + assertEquals(25, definitions.values().stream() + .filter(definition -> "agt".equals(definition.categoryKey())).count()); + assertTrue(definitions.values().stream().allMatch(ToolDefinition::readOnly)); + assertTrue(definitions.values().stream().noneMatch(ToolDefinition::destructive)); + assertTrue(definitions.values().stream().allMatch(ToolDefinition::idempotent)); + } + + @Test + void everyDefinitionHasMatchingValidMockResponse() throws Exception { + for (ToolDefinition definition : repository().findAll().values()) { + try (InputStream input = getClass().getResourceAsStream( + "/mock-responses/" + definition.name() + ".json")) { + assertNotNull(input, definition.name()); + JsonNode response = objectMapper.readTree(input); + assertEquals("SUCCESS", response.path("resultCode").asText(), definition.name()); + JsonNode data = objectMapper.readTree(response.path("data").asText()); + assertEquals(definition.name(), data.path("toolName").asText()); + assertFalse(data.path("details").isEmpty(), definition.name()); + } + } + } + + @Test + void useCaseInterfacesExposeFiftyUniqueMcpTools() { + List> useCases = List.of( + io.shinhanlife.dap.mcc.biz.pro.usecase.CustomerInquiryUseCase.class, + io.shinhanlife.dap.mcc.biz.pro.usecase.ClaimInquiryUseCase.class, + io.shinhanlife.dap.mcc.biz.pro.usecase.ContractInquiryUseCase.class, + io.shinhanlife.dap.mcc.biz.agt.usecase.AgentInquiryUseCase.class, + io.shinhanlife.dap.mcc.biz.agt.usecase.GaOrganizationUseCase.class, + io.shinhanlife.dap.mcc.biz.agt.usecase.AgentPerformanceUseCase.class); + + Set names = new HashSet<>(); + for (Class useCase : useCases) { + for (Method method : useCase.getDeclaredMethods()) { + McpTool tool = method.getAnnotation(McpTool.class); + assertNotNull(tool, useCase.getSimpleName() + "." + method.getName()); + assertTrue(names.add(tool.name()), "duplicate tool: " + tool.name()); + } + } + assertEquals(50, names.size()); + assertEquals(repository().findAll().keySet(), names); + } + + private ToolDefinitionRepository repository() { + return new ToolDefinitionRepository(new ObjectMapper(new YAMLFactory()), + new DefaultResourceLoader(), "classpath*:tool-definitions/**/*.yml"); + } +} diff --git a/dap-was-sal/Dockerfile b/dap-was-sal/Dockerfile new file mode 100644 index 000000000..2ea05b078 --- /dev/null +++ b/dap-was-sal/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-sal/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8082 +ENTRYPOINT ["java", "-jar", "app.jar"] + diff --git a/dap-was-sal/build.gradle b/dap-was-sal/build.gradle new file mode 100644 index 000000000..561bc8e3e --- /dev/null +++ b/dap-was-sal/build.gradle @@ -0,0 +1,9 @@ +plugins { + // SMS Tool Pod를 독립 실행 가능한 Spring Boot JAR로 생성합니다. + id 'org.springframework.boot' +} + +dependencies { + // MCP Server, Tool 공통 처리, MCI/EAI 연동 기반은 dap-was-lib에서 상속합니다. + implementation project(':dap-was-lib') +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java new file mode 100644 index 000000000..ed5d79c85 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface ClaimSearchConverter { + CLCNNB00001_I toLegacyRequest(ClaimSearchRequest request); + ClaimSearchRequest toRequest(CLCNNB00001_I mciRequest); + ClaimSearchResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java new file mode 100644 index 000000000..ba759323c --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MemoListRetrieverConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + MemoListRetrieverHttpRequest toHttpRequest(MemoListRetrieverRequest request); + MemoListRetrieverResponse toResponse(MemoListRetrieverHttpResponse httpResponse); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java new file mode 100644 index 000000000..644d3146c --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClaimSearchRequest { + @Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNo; + + @Schema(description = "보험 계약번호", example = "10023456789") + private String contractNo; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java new file mode 100644 index 000000000..301d3f8a6 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClaimSearchResponse { + private String resultCode; + + private String resultMessage; + @Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED) + private String status; + + @Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED) + private String statusLabel; + + @Schema(description = "승인 금액", example = "150000") + private Long approvedAmount; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java new file mode 100644 index 000000000..9cc7c4d32 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverRequest { + @Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED) + private String memoStatus; + + @Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED) + private String searchKeyword; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java new file mode 100644 index 000000000..79f704d8d --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java @@ -0,0 +1,13 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverResponse { + private String resultCode; + + private String resultMessage; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java new file mode 100644 index 000000000..378cdae75 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className ClaimSearchUseCase + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.10  jade    최초생성
+ *
+ * 
+ */ +public interface ClaimSearchUseCase { + + @McpTool(name = "cmm_claim_search", title = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.", description = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.") + @GrowToolHint(categoryKey = "cmm", mappingId = "CLCNNB00001", + inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", + outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json") + ClaimSearchResponse searchClaim(ClaimSearchRequest req); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java new file mode 100644 index 000000000..dd7b39dc2 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MemoListRetrieverUseCase + * @description AX HUB 시스템 처리 클래스 + * @author Admin + * @create 2026.08.11 + *
+ * ---------- 개정이력 ----------
+ * 수정일       수정자     수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.11  Admin    최초생성
+ *
+ * 
+ */ +public interface MemoListRetrieverUseCase { + + @McpTool(name = "cmm_memo_retriever", title = "의뢰서 목록 조회", description = "의뢰서 목록을 조회하여 의뢰 정보를 반환합니다.") + @GrowToolHint(categoryKey = "cmm", mappingId = "MEMO0000001") + MemoListRetrieverResponse retrieveMemoList(MemoListRetrieverRequest req); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java new file mode 100644 index 000000000..675c32e37 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java @@ -0,0 +1,68 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchUseCase; +import io.shinhanlife.glow.communication.dto.Transfer; +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import io.shinhanlife.dap.mcc.biz.cmm.converter.ClaimSearchConverter; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.MciNclaClient; + + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className ClaimSearchUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.10  jade    최초생성
+ *
+ * 
+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ClaimSearchUseCaseImpl implements ClaimSearchUseCase { + + private final MciNclaClient mci; + private final ClaimSearchConverter converter; + + @Override + public ClaimSearchResponse searchClaim(ClaimSearchRequest req) { + log.info("[MCI Tool] {} 요청 수신.", "cmm_claim_search"); + try { + // MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO) + CLCNNB00001_I mciReq = converter.toLegacyRequest(req); + + Transfer resTransfer = mci.callTo( + "CLCNNB00001", + null, + mciReq, + CLCNNB00001_O.class + ); + ClaimSearchResponse response = new ClaimSearchResponse(); + if (resTransfer.getBody() != null) { + response = converter.toResponse(resTransfer.getBody()); + } + response.setResultCode("SUCCESS"); + response.setResultMessage(resTransfer.getBody() != null + ? "MCI call completed." + : "MCI call completed without a response body."); + return response; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + ClaimSearchResponse response = new ClaimSearchResponse(); + response.setResultCode("ERROR"); + response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error"); + return response; + } + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java new file mode 100644 index 000000000..7fa29b46f --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MemoListRetrieverConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.MemoClient; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MemoListRetrieverUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MemoListRetrieverUseCaseImpl implements MemoListRetrieverUseCase { + + private final MemoListRetrieverConverter converter; + private final MemoClient memoClient; + + @Override + public MemoListRetrieverResponse retrieveMemoList(MemoListRetrieverRequest req) { + MemoListRetrieverHttpRequest httpRequest = converter.toHttpRequest(req); + MemoListRetrieverHttpResponse httpResponse = memoClient.call(httpRequest, MemoListRetrieverHttpResponse.class); + + MemoListRetrieverResponse response = converter.toResponse(httpResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolRequest.java new file mode 100644 index 000000000..891659414 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolRequest.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.lic.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class LicToolRequest { + @McpToolParam(description = "상품코드", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String productCode; + + @McpToolParam(description = "계약심사번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String contractId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$") + private String queryDate; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolResponse.java new file mode 100644 index 000000000..d4bd416ad --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/dto/LicToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.lic.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class LicToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/LicUnderwritingUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/LicUnderwritingUseCase.java new file mode 100644 index 000000000..b1f641e6a --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/LicUnderwritingUseCase.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.lic.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.lic.dto.LicToolRequest; +import io.shinhanlife.dap.mcc.biz.lic.dto.LicToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface LicUnderwritingUseCase { + @McpTool(name = "lic_product_info", title = "상품 정보 조회", description = "상품 정보 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_PRODUCT_INFO") + LicToolResponse getProductInfo(LicToolRequest request); + + @McpTool(name = "lic_product_coverage", title = "상품 보장내용 조회", description = "상품 보장내용 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_PRODUCT_COVERAGE") + LicToolResponse getProductCoverage(LicToolRequest request); + + @McpTool(name = "lic_product_premium_table", title = "상품 보험료 요율표 조회", description = "상품 보험료 요율표 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_PRODUCT_PREMIUM_TABLE") + LicToolResponse getProductPremiumTable(LicToolRequest request); + + @McpTool(name = "lic_contract_review_status", title = "계약심사 진행상태 조회", description = "계약심사 진행상태 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_CONTRACT_REVIEW_STATUS") + LicToolResponse getContractReviewStatus(LicToolRequest request); + + @McpTool(name = "lic_contract_review_document", title = "계약심사 제출서류 현황 조회", description = "계약심사 제출서류 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_CONTRACT_REVIEW_DOCUMENT") + LicToolResponse getContractReviewDocument(LicToolRequest request); + + @McpTool(name = "lic_contract_review_result", title = "계약심사 결과 조회", description = "계약심사 결과 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_CONTRACT_REVIEW_RESULT") + LicToolResponse getContractReviewResult(LicToolRequest request); + + @McpTool(name = "lic_contract_review_history", title = "계약심사 이력 조회", description = "계약심사 이력 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_CONTRACT_REVIEW_HISTORY") + LicToolResponse getContractReviewHistory(LicToolRequest request); + + @McpTool(name = "lic_underwriting_result", title = "언더라이팅 인수심사 결과 조회", description = "언더라이팅 인수심사 결과 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_UNDERWRITING_RESULT") + LicToolResponse getUnderwritingResult(LicToolRequest request); + + @McpTool(name = "lic_underwriting_risk_grade", title = "언더라이팅 위험등급 조회", description = "언더라이팅 위험등급 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_UNDERWRITING_RISK_GRADE") + LicToolResponse getUnderwritingRiskGrade(LicToolRequest request); + + @McpTool(name = "lic_underwriting_additional_request", title = "언더라이팅 추가자료 요청 현황 조회", description = "언더라이팅 추가자료 요청 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "lic", mappingId = "DIRECT_LIC_UNDERWRITING_ADDITIONAL_REQUEST") + LicToolResponse getUnderwritingAdditionalRequest(LicToolRequest request); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImpl.java new file mode 100644 index 000000000..80c13c099 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImpl.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.mcc.biz.lic.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.lic.dto.LicToolRequest; +import io.shinhanlife.dap.mcc.biz.lic.dto.LicToolResponse; +import io.shinhanlife.dap.mcc.biz.lic.usecase.LicUnderwritingUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class LicUnderwritingUseCaseImpl implements LicUnderwritingUseCase { + @Override + public LicToolResponse getProductInfo(LicToolRequest request) { + return mockResponse(request, "lic_product_info", "상품 정보 조회"); + } + + @Override + public LicToolResponse getProductCoverage(LicToolRequest request) { + return mockResponse(request, "lic_product_coverage", "상품 보장내용 조회"); + } + + @Override + public LicToolResponse getProductPremiumTable(LicToolRequest request) { + return mockResponse(request, "lic_product_premium_table", "상품 보험료 요율표 조회"); + } + + @Override + public LicToolResponse getContractReviewStatus(LicToolRequest request) { + return mockResponse(request, "lic_contract_review_status", "계약심사 진행상태 조회"); + } + + @Override + public LicToolResponse getContractReviewDocument(LicToolRequest request) { + return mockResponse(request, "lic_contract_review_document", "계약심사 제출서류 현황 조회"); + } + + @Override + public LicToolResponse getContractReviewResult(LicToolRequest request) { + return mockResponse(request, "lic_contract_review_result", "계약심사 결과 조회"); + } + + @Override + public LicToolResponse getContractReviewHistory(LicToolRequest request) { + return mockResponse(request, "lic_contract_review_history", "계약심사 이력 조회"); + } + + @Override + public LicToolResponse getUnderwritingResult(LicToolRequest request) { + return mockResponse(request, "lic_underwriting_result", "언더라이팅 인수심사 결과 조회"); + } + + @Override + public LicToolResponse getUnderwritingRiskGrade(LicToolRequest request) { + return mockResponse(request, "lic_underwriting_risk_grade", "언더라이팅 위험등급 조회"); + } + + @Override + public LicToolResponse getUnderwritingAdditionalRequest(LicToolRequest request) { + return mockResponse(request, "lic_underwriting_additional_request", "언더라이팅 추가자료 요청 현황 조회"); + } + + private LicToolResponse mockResponse(LicToolRequest request, String toolName, String title) { + LicToolResponse response = new LicToolResponse(); + response.setToolName(toolName); + response.setCategory("LIC"); + response.setReferenceId(request != null && hasText(request.getProductCode()) ? request.getProductCode() + : request != null && hasText(request.getContractId()) ? request.getContractId() : "LIC-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolRequest.java new file mode 100644 index 000000000..a2b09e841 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolRequest.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.sof.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SofToolRequest { + @McpToolParam(description = "설계사번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String agentId; + + @McpToolParam(description = "GA 조직번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String organizationId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$") + private String queryDate; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolResponse.java new file mode 100644 index 000000000..d1325134b --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/dto/SofToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.sof.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class SofToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/SofFieldActivityUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/SofFieldActivityUseCase.java new file mode 100644 index 000000000..578694a86 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/SofFieldActivityUseCase.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.sof.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.sof.dto.SofToolRequest; +import io.shinhanlife.dap.mcc.biz.sof.dto.SofToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SofFieldActivityUseCase { + @McpTool(name = "sof_activity_daily", title = "GA 일별 활동 현황 조회", description = "GA 일별 활동 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_ACTIVITY_DAILY") + SofToolResponse getActivityDaily(SofToolRequest request); + + @McpTool(name = "sof_visit_count", title = "고객 방문 건수 조회", description = "고객 방문 건수 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_VISIT_COUNT") + SofToolResponse getVisitCount(SofToolRequest request); + + @McpTool(name = "sof_call_activity", title = "콜 활동 현황 조회", description = "콜 활동 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_CALL_ACTIVITY") + SofToolResponse getCallActivity(SofToolRequest request); + + @McpTool(name = "sof_customer_consulting", title = "고객 상담 현황 조회", description = "고객 상담 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_CUSTOMER_CONSULTING") + SofToolResponse getCustomerConsulting(SofToolRequest request); + + @McpTool(name = "sof_prospect_list", title = "신규 발굴 고객 목록 조회", description = "신규 발굴 고객 목록 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_PROSPECT_LIST") + SofToolResponse getProspectList(SofToolRequest request); + + @McpTool(name = "sof_activity_calendar", title = "활동 일정 조회", description = "활동 일정 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_ACTIVITY_CALENDAR") + SofToolResponse getActivityCalendar(SofToolRequest request); + + @McpTool(name = "sof_activity_report_status", title = "활동일지 제출 현황 조회", description = "활동일지 제출 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_ACTIVITY_REPORT_STATUS") + SofToolResponse getActivityReportStatus(SofToolRequest request); + + @McpTool(name = "sof_training_completion", title = "교육 이수 현황 조회", description = "교육 이수 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_TRAINING_COMPLETION") + SofToolResponse getTrainingCompletion(SofToolRequest request); + + @McpTool(name = "sof_field_support_request", title = "현장 지원 요청 현황 조회", description = "현장 지원 요청 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_FIELD_SUPPORT_REQUEST") + SofToolResponse getFieldSupportRequest(SofToolRequest request); + + @McpTool(name = "sof_activity_summary", title = "GA 활동 종합 현황 조회", description = "GA 활동 종합 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sof", mappingId = "DIRECT_SOF_ACTIVITY_SUMMARY") + SofToolResponse getActivitySummary(SofToolRequest request); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImpl.java new file mode 100644 index 000000000..8d6701677 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImpl.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.mcc.biz.sof.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sof.dto.SofToolRequest; +import io.shinhanlife.dap.mcc.biz.sof.dto.SofToolResponse; +import io.shinhanlife.dap.mcc.biz.sof.usecase.SofFieldActivityUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SofFieldActivityUseCaseImpl implements SofFieldActivityUseCase { + @Override + public SofToolResponse getActivityDaily(SofToolRequest request) { + return mockResponse(request, "sof_activity_daily", "GA 일별 활동 현황 조회"); + } + + @Override + public SofToolResponse getVisitCount(SofToolRequest request) { + return mockResponse(request, "sof_visit_count", "고객 방문 건수 조회"); + } + + @Override + public SofToolResponse getCallActivity(SofToolRequest request) { + return mockResponse(request, "sof_call_activity", "콜 활동 현황 조회"); + } + + @Override + public SofToolResponse getCustomerConsulting(SofToolRequest request) { + return mockResponse(request, "sof_customer_consulting", "고객 상담 현황 조회"); + } + + @Override + public SofToolResponse getProspectList(SofToolRequest request) { + return mockResponse(request, "sof_prospect_list", "신규 발굴 고객 목록 조회"); + } + + @Override + public SofToolResponse getActivityCalendar(SofToolRequest request) { + return mockResponse(request, "sof_activity_calendar", "활동 일정 조회"); + } + + @Override + public SofToolResponse getActivityReportStatus(SofToolRequest request) { + return mockResponse(request, "sof_activity_report_status", "활동일지 제출 현황 조회"); + } + + @Override + public SofToolResponse getTrainingCompletion(SofToolRequest request) { + return mockResponse(request, "sof_training_completion", "교육 이수 현황 조회"); + } + + @Override + public SofToolResponse getFieldSupportRequest(SofToolRequest request) { + return mockResponse(request, "sof_field_support_request", "현장 지원 요청 현황 조회"); + } + + @Override + public SofToolResponse getActivitySummary(SofToolRequest request) { + return mockResponse(request, "sof_activity_summary", "GA 활동 종합 현황 조회"); + } + + private SofToolResponse mockResponse(SofToolRequest request, String toolName, String title) { + SofToolResponse response = new SofToolResponse(); + response.setToolName(toolName); + response.setCategory("SOF"); + response.setReferenceId(request != null && hasText(request.getAgentId()) ? request.getAgentId() + : request != null && hasText(request.getOrganizationId()) ? request.getOrganizationId() : "SOF-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolRequest.java new file mode 100644 index 000000000..8872f40f1 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolRequest.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.sog.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SogToolRequest { + @McpToolParam(description = "설계사번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String agentId; + + @McpToolParam(description = "GA 조직번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String organizationId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$") + private String queryDate; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolResponse.java new file mode 100644 index 000000000..4360b6769 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/dto/SogToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.sog.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class SogToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/SogSalesPerformanceUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/SogSalesPerformanceUseCase.java new file mode 100644 index 000000000..b4bdc4c95 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/SogSalesPerformanceUseCase.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.sog.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.sog.dto.SogToolRequest; +import io.shinhanlife.dap.mcc.biz.sog.dto.SogToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SogSalesPerformanceUseCase { + @McpTool(name = "sog_target_summary", title = "GA 개인별 영업목표 요약 조회", description = "GA 개인별 영업목표 요약 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_TARGET_SUMMARY") + SogToolResponse getTargetSummary(SogToolRequest request); + + @McpTool(name = "sog_target_progress", title = "영업목표 달성률 조회", description = "영업목표 달성률 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_TARGET_PROGRESS") + SogToolResponse getTargetProgress(SogToolRequest request); + + @McpTool(name = "sog_monthly_performance", title = "GA 월별 실적 조회", description = "GA 월별 실적 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_MONTHLY_PERFORMANCE") + SogToolResponse getMonthlyPerformance(SogToolRequest request); + + @McpTool(name = "sog_product_performance", title = "상품별 실적 조회", description = "상품별 실적 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_PRODUCT_PERFORMANCE") + SogToolResponse getProductPerformance(SogToolRequest request); + + @McpTool(name = "sog_new_contract_count", title = "신계약 건수 실적 조회", description = "신계약 건수 실적 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_NEW_CONTRACT_COUNT") + SogToolResponse getNewContractCount(SogToolRequest request); + + @McpTool(name = "sog_premium_performance", title = "보험료 실적 조회", description = "보험료 실적 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_PREMIUM_PERFORMANCE") + SogToolResponse getPremiumPerformance(SogToolRequest request); + + @McpTool(name = "sog_sales_ranking", title = "GA 영업 순위 조회", description = "GA 영업 순위 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_SALES_RANKING") + SogToolResponse getSalesRanking(SogToolRequest request); + + @McpTool(name = "sog_incentive_summary", title = "영업 인센티브 요약 조회", description = "영업 인센티브 요약 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_INCENTIVE_SUMMARY") + SogToolResponse getIncentiveSummary(SogToolRequest request); + + @McpTool(name = "sog_target_history", title = "영업목표 변경 이력 조회", description = "영업목표 변경 이력 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_TARGET_HISTORY") + SogToolResponse getTargetHistory(SogToolRequest request); + + @McpTool(name = "sog_performance_comparison", title = "전년 동기 대비 실적 비교 조회", description = "전년 동기 대비 실적 비교 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sog", mappingId = "DIRECT_SOG_PERFORMANCE_COMPARISON") + SogToolResponse getPerformanceComparison(SogToolRequest request); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImpl.java new file mode 100644 index 000000000..dad900104 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImpl.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.mcc.biz.sog.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sog.dto.SogToolRequest; +import io.shinhanlife.dap.mcc.biz.sog.dto.SogToolResponse; +import io.shinhanlife.dap.mcc.biz.sog.usecase.SogSalesPerformanceUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SogSalesPerformanceUseCaseImpl implements SogSalesPerformanceUseCase { + @Override + public SogToolResponse getTargetSummary(SogToolRequest request) { + return mockResponse(request, "sog_target_summary", "GA 개인별 영업목표 요약 조회"); + } + + @Override + public SogToolResponse getTargetProgress(SogToolRequest request) { + return mockResponse(request, "sog_target_progress", "영업목표 달성률 조회"); + } + + @Override + public SogToolResponse getMonthlyPerformance(SogToolRequest request) { + return mockResponse(request, "sog_monthly_performance", "GA 월별 실적 조회"); + } + + @Override + public SogToolResponse getProductPerformance(SogToolRequest request) { + return mockResponse(request, "sog_product_performance", "상품별 실적 조회"); + } + + @Override + public SogToolResponse getNewContractCount(SogToolRequest request) { + return mockResponse(request, "sog_new_contract_count", "신계약 건수 실적 조회"); + } + + @Override + public SogToolResponse getPremiumPerformance(SogToolRequest request) { + return mockResponse(request, "sog_premium_performance", "보험료 실적 조회"); + } + + @Override + public SogToolResponse getSalesRanking(SogToolRequest request) { + return mockResponse(request, "sog_sales_ranking", "GA 영업 순위 조회"); + } + + @Override + public SogToolResponse getIncentiveSummary(SogToolRequest request) { + return mockResponse(request, "sog_incentive_summary", "영업 인센티브 요약 조회"); + } + + @Override + public SogToolResponse getTargetHistory(SogToolRequest request) { + return mockResponse(request, "sog_target_history", "영업목표 변경 이력 조회"); + } + + @Override + public SogToolResponse getPerformanceComparison(SogToolRequest request) { + return mockResponse(request, "sog_performance_comparison", "전년 동기 대비 실적 비교 조회"); + } + + private SogToolResponse mockResponse(SogToolRequest request, String toolName, String title) { + SogToolResponse response = new SogToolResponse(); + response.setToolName(toolName); + response.setCategory("SOG"); + response.setReferenceId(request != null && hasText(request.getAgentId()) ? request.getAgentId() + : request != null && hasText(request.getOrganizationId()) ? request.getOrganizationId() : "SOG-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolRequest.java new file mode 100644 index 000000000..d998b9f88 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolRequest.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.sot.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SotToolRequest { + @McpToolParam(description = "고객번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String customerId; + + @McpToolParam(description = "가입설계번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String contractId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$") + private String queryDate; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolResponse.java new file mode 100644 index 000000000..515912a09 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/dto/SotToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.sot.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class SotToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/SotConsentUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/SotConsentUseCase.java new file mode 100644 index 000000000..e88e97870 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/SotConsentUseCase.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.sot.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.sot.dto.SotToolRequest; +import io.shinhanlife.dap.mcc.biz.sot.dto.SotToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SotConsentUseCase { + @McpTool(name = "sot_marketing_consent_status", title = "마케팅 수신동의 현황 조회", description = "마케팅 수신동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_MARKETING_CONSENT_STATUS") + SotToolResponse getMarketingConsentStatus(SotToolRequest request); + + @McpTool(name = "sot_marketing_consent_history", title = "마케팅 동의 이력 조회", description = "마케팅 동의 이력 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_MARKETING_CONSENT_HISTORY") + SotToolResponse getMarketingConsentHistory(SotToolRequest request); + + @McpTool(name = "sot_marketing_consent_channel", title = "채널별 마케팅 동의 현황 조회", description = "채널별 마케팅 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_MARKETING_CONSENT_CHANNEL") + SotToolResponse getMarketingConsentChannel(SotToolRequest request); + + @McpTool(name = "sot_subscription_design_consent", title = "가입설계 동의 현황 조회", description = "가입설계 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_SUBSCRIPTION_DESIGN_CONSENT") + SotToolResponse getSubscriptionDesignConsent(SotToolRequest request); + + @McpTool(name = "sot_subscription_design_detail", title = "가입설계 동의 상세 조회", description = "가입설계 동의 상세 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_SUBSCRIPTION_DESIGN_DETAIL") + SotToolResponse getSubscriptionDesignDetail(SotToolRequest request); + + @McpTool(name = "sot_customer_info_consent", title = "고객정보 수집·이용 동의 현황 조회", description = "고객정보 수집·이용 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_CUSTOMER_INFO_CONSENT") + SotToolResponse getCustomerInfoConsent(SotToolRequest request); + + @McpTool(name = "sot_customer_info_consent_scope", title = "고객정보 동의 범위 조회", description = "고객정보 동의 범위 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_CUSTOMER_INFO_CONSENT_SCOPE") + SotToolResponse getCustomerInfoConsentScope(SotToolRequest request); + + @McpTool(name = "sot_consent_expiry", title = "동의 유효기간 만료 현황 조회", description = "동의 유효기간 만료 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_CONSENT_EXPIRY") + SotToolResponse getConsentExpiry(SotToolRequest request); + + @McpTool(name = "sot_consent_withdrawal", title = "동의 철회 이력 조회", description = "동의 철회 이력 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_CONSENT_WITHDRAWAL") + SotToolResponse getConsentWithdrawal(SotToolRequest request); + + @McpTool(name = "sot_tm_call_eligibility", title = "텔레마케팅 콜 가능여부 종합 조회", description = "텔레마케팅 콜 가능여부 종합 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "sot", mappingId = "DIRECT_SOT_TM_CALL_ELIGIBILITY") + SotToolResponse getTmCallEligibility(SotToolRequest request); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImpl.java new file mode 100644 index 000000000..29e85ba93 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImpl.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.mcc.biz.sot.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sot.dto.SotToolRequest; +import io.shinhanlife.dap.mcc.biz.sot.dto.SotToolResponse; +import io.shinhanlife.dap.mcc.biz.sot.usecase.SotConsentUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SotConsentUseCaseImpl implements SotConsentUseCase { + @Override + public SotToolResponse getMarketingConsentStatus(SotToolRequest request) { + return mockResponse(request, "sot_marketing_consent_status", "마케팅 수신동의 현황 조회"); + } + + @Override + public SotToolResponse getMarketingConsentHistory(SotToolRequest request) { + return mockResponse(request, "sot_marketing_consent_history", "마케팅 동의 이력 조회"); + } + + @Override + public SotToolResponse getMarketingConsentChannel(SotToolRequest request) { + return mockResponse(request, "sot_marketing_consent_channel", "채널별 마케팅 동의 현황 조회"); + } + + @Override + public SotToolResponse getSubscriptionDesignConsent(SotToolRequest request) { + return mockResponse(request, "sot_subscription_design_consent", "가입설계 동의 현황 조회"); + } + + @Override + public SotToolResponse getSubscriptionDesignDetail(SotToolRequest request) { + return mockResponse(request, "sot_subscription_design_detail", "가입설계 동의 상세 조회"); + } + + @Override + public SotToolResponse getCustomerInfoConsent(SotToolRequest request) { + return mockResponse(request, "sot_customer_info_consent", "고객정보 수집·이용 동의 현황 조회"); + } + + @Override + public SotToolResponse getCustomerInfoConsentScope(SotToolRequest request) { + return mockResponse(request, "sot_customer_info_consent_scope", "고객정보 동의 범위 조회"); + } + + @Override + public SotToolResponse getConsentExpiry(SotToolRequest request) { + return mockResponse(request, "sot_consent_expiry", "동의 유효기간 만료 현황 조회"); + } + + @Override + public SotToolResponse getConsentWithdrawal(SotToolRequest request) { + return mockResponse(request, "sot_consent_withdrawal", "동의 철회 이력 조회"); + } + + @Override + public SotToolResponse getTmCallEligibility(SotToolRequest request) { + return mockResponse(request, "sot_tm_call_eligibility", "텔레마케팅 콜 가능여부 종합 조회"); + } + + private SotToolResponse mockResponse(SotToolRequest request, String toolName, String title) { + SotToolResponse response = new SotToolResponse(); + response.setToolName(toolName); + response.setCategory("SOT"); + response.setReferenceId(request != null && hasText(request.getCustomerId()) ? request.getCustomerId() + : request != null && hasText(request.getContractId()) ? request.getContractId() : "SOT-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolRequest.java new file mode 100644 index 000000000..a50f2dd42 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolRequest.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.spr.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SprToolRequest { + @McpToolParam(description = "현장문의번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String inquiryId; + + @McpToolParam(description = "설계사번호", required = false) + @Schema(pattern = "^[A-Za-z0-9_-]{3,30}$") + private String agentId; + + @McpToolParam(description = "조회 기준일(YYYY-MM-DD)", required = false) + @Schema(pattern = "^\\d{4}-\\d{2}-\\d{2}$") + private String queryDate; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolResponse.java new file mode 100644 index 000000000..764512722 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/dto/SprToolResponse.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.spr.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class SprToolResponse { + private String toolName; + private String category; + private String referenceId; + private String status; + private String summary; + private String asOfDate; + private List details; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/SprFieldInquiryUseCase.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/SprFieldInquiryUseCase.java new file mode 100644 index 000000000..3338e1650 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/SprFieldInquiryUseCase.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.spr.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.spr.dto.SprToolRequest; +import io.shinhanlife.dap.mcc.biz.spr.dto.SprToolResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SprFieldInquiryUseCase { + @McpTool(name = "spr_field_inquiry_list", title = "GA현장문의 목록 조회", description = "GA현장문의 목록 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_LIST") + SprToolResponse getFieldInquiryList(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_detail", title = "GA현장문의 상세 조회", description = "GA현장문의 상세 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_DETAIL") + SprToolResponse getFieldInquiryDetail(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_status", title = "문의 처리상태 조회", description = "문의 처리상태 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_STATUS") + SprToolResponse getFieldInquiryStatus(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_category", title = "문의 유형별 통계 조회", description = "문의 유형별 통계 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_CATEGORY") + SprToolResponse getFieldInquiryCategory(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_answer", title = "문의 답변 조회", description = "문의 답변 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_ANSWER") + SprToolResponse getFieldInquiryAnswer(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_escalation", title = "에스컬레이션 현황 조회", description = "에스컬레이션 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_ESCALATION") + SprToolResponse getFieldInquiryEscalation(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_faq", title = "자주묻는질문 FAQ 조회", description = "자주묻는질문 FAQ 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_FAQ") + SprToolResponse getFieldInquiryFaq(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_manager", title = "담당자 배정 현황 조회", description = "담당자 배정 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_MANAGER") + SprToolResponse getFieldInquiryManager(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_sla", title = "처리기한 SLA 현황 조회", description = "처리기한 SLA 현황 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_SLA") + SprToolResponse getFieldInquirySla(SprToolRequest request); + + @McpTool(name = "spr_field_inquiry_satisfaction", title = "문의 만족도 조사 결과 조회", description = "문의 만족도 조사 결과 조회 정보를 안전한 모의 데이터로 제공합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "spr", mappingId = "DIRECT_SPR_FIELD_INQUIRY_SATISFACTION") + SprToolResponse getFieldInquirySatisfaction(SprToolRequest request); +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImpl.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImpl.java new file mode 100644 index 000000000..2d37279b1 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImpl.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.mcc.biz.spr.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.spr.dto.SprToolRequest; +import io.shinhanlife.dap.mcc.biz.spr.dto.SprToolResponse; +import io.shinhanlife.dap.mcc.biz.spr.usecase.SprFieldInquiryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SprFieldInquiryUseCaseImpl implements SprFieldInquiryUseCase { + @Override + public SprToolResponse getFieldInquiryList(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_list", "GA현장문의 목록 조회"); + } + + @Override + public SprToolResponse getFieldInquiryDetail(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_detail", "GA현장문의 상세 조회"); + } + + @Override + public SprToolResponse getFieldInquiryStatus(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_status", "문의 처리상태 조회"); + } + + @Override + public SprToolResponse getFieldInquiryCategory(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_category", "문의 유형별 통계 조회"); + } + + @Override + public SprToolResponse getFieldInquiryAnswer(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_answer", "문의 답변 조회"); + } + + @Override + public SprToolResponse getFieldInquiryEscalation(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_escalation", "에스컬레이션 현황 조회"); + } + + @Override + public SprToolResponse getFieldInquiryFaq(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_faq", "자주묻는질문 FAQ 조회"); + } + + @Override + public SprToolResponse getFieldInquiryManager(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_manager", "담당자 배정 현황 조회"); + } + + @Override + public SprToolResponse getFieldInquirySla(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_sla", "처리기한 SLA 현황 조회"); + } + + @Override + public SprToolResponse getFieldInquirySatisfaction(SprToolRequest request) { + return mockResponse(request, "spr_field_inquiry_satisfaction", "문의 만족도 조사 결과 조회"); + } + + private SprToolResponse mockResponse(SprToolRequest request, String toolName, String title) { + SprToolResponse response = new SprToolResponse(); + response.setToolName(toolName); + response.setCategory("SPR"); + response.setReferenceId(request != null && hasText(request.getInquiryId()) ? request.getInquiryId() + : request != null && hasText(request.getAgentId()) ? request.getAgentId() : "SPR-000001"); + response.setStatus("정상"); + response.setSummary(title + " 결과"); + response.setAsOfDate(request != null && hasText(request.getQueryDate()) ? request.getQueryDate() : "2026-08-14"); + response.setDetails(List.of(title + " 모의 상세정보", "외부 시스템을 변경하지 않는 조회 결과")); + return response; + } + + private boolean hasText(String value) { + return value != null && !value.isBlank(); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java new file mode 100644 index 000000000..530266023 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo; + +import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class MemoClient { + private static final String API_NAME = "memo"; + + private final AxhubHttpComponent http; + + public O call(I request, Class responseType) { + return http.call(API_NAME, request, responseType); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java new file mode 100644 index 000000000..0a90902c1 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverHttpRequest { + @Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED) + private String memoStatus; + + @Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED) + private String searchKeyword; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java new file mode 100644 index 000000000..191aeea2c --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java @@ -0,0 +1,13 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverHttpResponse { + private String resultCode; + + private String resultMessage; +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java new file mode 100644 index 000000000..5c81c4faf --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla; + +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; +import io.shinhanlife.glow.communication.dto.Transfer; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla + * @className MciNclaClient + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.10  jade    최초생성
+ *
+ * 
+ */ +@Component +@RequiredArgsConstructor +public class MciNclaClient { + private final AxhubMciComponent mci; + + public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception { + return mci.callTo(interfaceId, dummy, mciReq, resType); + } +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java new file mode 100644 index 000000000..1c5a292fa --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class CLCNNB00001_I { + @Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNo; + + @Schema(description = "보험 계약번호", example = "10023456789") + private String contractNo; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java new file mode 100644 index 000000000..997609976 --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class CLCNNB00001_O { + @Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED) + private String status; + + @Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED) + private String statusLabel; + + @Schema(description = "승인 금액", example = "150000") + private Long approvedAmount; + +} diff --git a/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/sal/DapWasSalApplication.java b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/sal/DapWasSalApplication.java new file mode 100644 index 000000000..e05db816b --- /dev/null +++ b/dap-was-sal/src/main/java/io/shinhanlife/dap/mcc/sal/DapWasSalApplication.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.sal; + + +/** + * @package io.shinhanlife.dap.mcc.sal + * @className DapWasSalApplication + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.09.01  0986406    최초생성
+ * 
+ * 
+ */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; + +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +@Import(ToolMcpServerConfiguration.class) +public class DapWasSalApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasSalApplication.class, args); + } +} diff --git a/dap-was-sal/src/main/resources/application-dev.yml b/dap-was-sal/src/main/resources/application-dev.yml new file mode 100644 index 000000000..6aed956e9 --- /dev/null +++ b/dap-was-sal/src/main/resources/application-dev.yml @@ -0,0 +1,15 @@ +# OCI 클라우드 개발 환경 전용 설정 +server: + port: ${PORT:8082} + +axhub: + gateway: + url: https://axhubmcp.devjun.net + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml diff --git a/dap-was-sal/src/main/resources/application-local.yml b/dap-was-sal/src/main/resources/application-local.yml new file mode 100644 index 000000000..d4add3581 --- /dev/null +++ b/dap-was-sal/src/main/resources/application-local.yml @@ -0,0 +1,28 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} diff --git a/dap-was-sal/src/main/resources/application-prod.yml b/dap-was-sal/src/main/resources/application-prod.yml new file mode 100644 index 000000000..8d6f1ab04 --- /dev/null +++ b/dap-was-sal/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8082} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} \ No newline at end of file diff --git a/dap-was-sal/src/main/resources/application-test.yml b/dap-was-sal/src/main/resources/application-test.yml new file mode 100644 index 000000000..a394e88df --- /dev/null +++ b/dap-was-sal/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8082} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} \ No newline at end of file diff --git a/dap-was-sal/src/main/resources/application.yml b/dap-was-sal/src/main/resources/application.yml new file mode 100644 index 000000000..964b96532 --- /dev/null +++ b/dap-was-sal/src/main/resources/application.yml @@ -0,0 +1,19 @@ +server: + port: 8082 +spring: + application: + name: dap-was-sal + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: was-sal + # Set the AA-assigned prefix before MCP pull activation (for example: sal.). + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL diff --git a/dap-was-sal/src/main/resources/logback-spring.xml b/dap-was-sal/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..52764cd6f --- /dev/null +++ b/dap-was-sal/src/main/resources/logback-spring.xml @@ -0,0 +1,41 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + + + + /swlog/dap-was-sal/A01/${HOSTNAME}_A01.log + + + /swlog/dap-was-sal/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.%i.log + + 100MB + 30 + 1GB + + + ${LOG_PATTERN} + + + + + + + + + + + + diff --git a/dap-was-sal/src/main/resources/mock-responses/cmm_memo_retriever.json b/dap-was-sal/src/main/resources/mock-responses/cmm_memo_retriever.json new file mode 100644 index 000000000..a949502b1 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/cmm_memo_retriever.json @@ -0,0 +1,3 @@ +{ + "resultCode": "SUCCESS" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_document.json b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_document.json new file mode 100644 index 000000000..99650ca3a --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_document.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_contract_review_document\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"계약심사 제출서류 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"계약심사 제출서류 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_history.json b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_history.json new file mode 100644 index 000000000..bf49c60e4 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_contract_review_history\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"계약심사 이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"계약심사 이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_result.json b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_result.json new file mode 100644 index 000000000..2020e2bd6 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_result.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_contract_review_result\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"계약심사 결과 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"계약심사 결과 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_status.json b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_status.json new file mode 100644 index 000000000..f813199df --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_contract_review_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_contract_review_status\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"계약심사 진행상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"계약심사 진행상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_product_coverage.json b/dap-was-sal/src/main/resources/mock-responses/lic_product_coverage.json new file mode 100644 index 000000000..9e1d63c64 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_product_coverage.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_product_coverage\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"상품 보장내용 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"상품 보장내용 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_product_info.json b/dap-was-sal/src/main/resources/mock-responses/lic_product_info.json new file mode 100644 index 000000000..fbb220f73 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_product_info.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_product_info\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"상품 정보 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"상품 정보 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_product_premium_table.json b/dap-was-sal/src/main/resources/mock-responses/lic_product_premium_table.json new file mode 100644 index 000000000..ea30062ec --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_product_premium_table.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_product_premium_table\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"상품 보험료 요율표 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"상품 보험료 요율표 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_additional_request.json b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_additional_request.json new file mode 100644 index 000000000..3bf048d43 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_additional_request.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_underwriting_additional_request\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"언더라이팅 추가자료 요청 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"언더라이팅 추가자료 요청 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_result.json b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_result.json new file mode 100644 index 000000000..13d7d7b6c --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_result.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_underwriting_result\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"언더라이팅 인수심사 결과 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"언더라이팅 인수심사 결과 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_risk_grade.json b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_risk_grade.json new file mode 100644 index 000000000..24d54cf58 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/lic_underwriting_risk_grade.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"lic_underwriting_risk_grade\",\"category\":\"LIC\",\"referenceId\":\"LIC-000001\",\"status\":\"정상\",\"summary\":\"언더라이팅 위험등급 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"언더라이팅 위험등급 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sms_cmm_claim_search.json b/dap-was-sal/src/main/resources/mock-responses/sms_cmm_claim_search.json new file mode 100644 index 000000000..9c4f9c037 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sms_cmm_claim_search.json @@ -0,0 +1,5 @@ +{ + "status" : "RECEIVED", + "statusLabel" : "접수", + "approvedAmount" : 150000 +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_activity_calendar.json b/dap-was-sal/src/main/resources/mock-responses/sof_activity_calendar.json new file mode 100644 index 000000000..8aa12eb8d --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_activity_calendar.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_activity_calendar\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"활동 일정 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"활동 일정 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_activity_daily.json b/dap-was-sal/src/main/resources/mock-responses/sof_activity_daily.json new file mode 100644 index 000000000..e10f8dc3d --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_activity_daily.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_activity_daily\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"GA 일별 활동 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 일별 활동 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_activity_report_status.json b/dap-was-sal/src/main/resources/mock-responses/sof_activity_report_status.json new file mode 100644 index 000000000..533993ac1 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_activity_report_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_activity_report_status\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"활동일지 제출 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"활동일지 제출 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_activity_summary.json b/dap-was-sal/src/main/resources/mock-responses/sof_activity_summary.json new file mode 100644 index 000000000..1f46cc369 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_activity_summary.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_activity_summary\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"GA 활동 종합 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 활동 종합 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_call_activity.json b/dap-was-sal/src/main/resources/mock-responses/sof_call_activity.json new file mode 100644 index 000000000..78d5a136f --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_call_activity.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_call_activity\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"콜 활동 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"콜 활동 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_customer_consulting.json b/dap-was-sal/src/main/resources/mock-responses/sof_customer_consulting.json new file mode 100644 index 000000000..2fa0e9a55 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_customer_consulting.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_customer_consulting\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"고객 상담 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 상담 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_field_support_request.json b/dap-was-sal/src/main/resources/mock-responses/sof_field_support_request.json new file mode 100644 index 000000000..3bec29c52 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_field_support_request.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_field_support_request\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"현장 지원 요청 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"현장 지원 요청 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_prospect_list.json b/dap-was-sal/src/main/resources/mock-responses/sof_prospect_list.json new file mode 100644 index 000000000..205d8dc9a --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_prospect_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_prospect_list\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"신규 발굴 고객 목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"신규 발굴 고객 목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_training_completion.json b/dap-was-sal/src/main/resources/mock-responses/sof_training_completion.json new file mode 100644 index 000000000..f2cabec6a --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_training_completion.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_training_completion\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"교육 이수 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"교육 이수 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sof_visit_count.json b/dap-was-sal/src/main/resources/mock-responses/sof_visit_count.json new file mode 100644 index 000000000..f43017c74 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sof_visit_count.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sof_visit_count\",\"category\":\"SOF\",\"referenceId\":\"SOF-000001\",\"status\":\"정상\",\"summary\":\"고객 방문 건수 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객 방문 건수 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_incentive_summary.json b/dap-was-sal/src/main/resources/mock-responses/sog_incentive_summary.json new file mode 100644 index 000000000..2ab905bbf --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_incentive_summary.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_incentive_summary\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"영업 인센티브 요약 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"영업 인센티브 요약 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_monthly_performance.json b/dap-was-sal/src/main/resources/mock-responses/sog_monthly_performance.json new file mode 100644 index 000000000..79fdc1b78 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_monthly_performance.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_monthly_performance\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"GA 월별 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 월별 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_new_contract_count.json b/dap-was-sal/src/main/resources/mock-responses/sog_new_contract_count.json new file mode 100644 index 000000000..3d8bd3945 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_new_contract_count.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_new_contract_count\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"신계약 건수 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"신계약 건수 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_performance_comparison.json b/dap-was-sal/src/main/resources/mock-responses/sog_performance_comparison.json new file mode 100644 index 000000000..0fa8fd9bb --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_performance_comparison.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_performance_comparison\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"전년 동기 대비 실적 비교 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"전년 동기 대비 실적 비교 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_premium_performance.json b/dap-was-sal/src/main/resources/mock-responses/sog_premium_performance.json new file mode 100644 index 000000000..2b3e876e3 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_premium_performance.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_premium_performance\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"보험료 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"보험료 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_product_performance.json b/dap-was-sal/src/main/resources/mock-responses/sog_product_performance.json new file mode 100644 index 000000000..1d1ba6cbf --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_product_performance.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_product_performance\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"상품별 실적 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"상품별 실적 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_sales_ranking.json b/dap-was-sal/src/main/resources/mock-responses/sog_sales_ranking.json new file mode 100644 index 000000000..c02ed4a18 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_sales_ranking.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_sales_ranking\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"GA 영업 순위 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 영업 순위 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_target_history.json b/dap-was-sal/src/main/resources/mock-responses/sog_target_history.json new file mode 100644 index 000000000..6e1aab445 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_target_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_target_history\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"영업목표 변경 이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"영업목표 변경 이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_target_progress.json b/dap-was-sal/src/main/resources/mock-responses/sog_target_progress.json new file mode 100644 index 000000000..ee5adc78a --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_target_progress.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_target_progress\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"영업목표 달성률 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"영업목표 달성률 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sog_target_summary.json b/dap-was-sal/src/main/resources/mock-responses/sog_target_summary.json new file mode 100644 index 000000000..70053ed32 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sog_target_summary.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sog_target_summary\",\"category\":\"SOG\",\"referenceId\":\"SOG-000001\",\"status\":\"정상\",\"summary\":\"GA 개인별 영업목표 요약 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA 개인별 영업목표 요약 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_consent_expiry.json b/dap-was-sal/src/main/resources/mock-responses/sot_consent_expiry.json new file mode 100644 index 000000000..9683c2002 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_consent_expiry.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_consent_expiry\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"동의 유효기간 만료 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"동의 유효기간 만료 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_consent_withdrawal.json b/dap-was-sal/src/main/resources/mock-responses/sot_consent_withdrawal.json new file mode 100644 index 000000000..1b83f6920 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_consent_withdrawal.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_consent_withdrawal\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"동의 철회 이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"동의 철회 이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent.json b/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent.json new file mode 100644 index 000000000..d000e4977 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_customer_info_consent\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"고객정보 수집·이용 동의 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객정보 수집·이용 동의 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent_scope.json b/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent_scope.json new file mode 100644 index 000000000..bce43d9b7 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_customer_info_consent_scope.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_customer_info_consent_scope\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"고객정보 동의 범위 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"고객정보 동의 범위 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_channel.json b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_channel.json new file mode 100644 index 000000000..2120afb03 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_channel.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_marketing_consent_channel\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"채널별 마케팅 동의 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"채널별 마케팅 동의 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_history.json b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_history.json new file mode 100644 index 000000000..5697fff0d --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_marketing_consent_history\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"마케팅 동의 이력 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"마케팅 동의 이력 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_status.json b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_status.json new file mode 100644 index 000000000..e6d4e0fb0 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_marketing_consent_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_marketing_consent_status\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"마케팅 수신동의 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"마케팅 수신동의 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_consent.json b/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_consent.json new file mode 100644 index 000000000..ae8ee1bbc --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_consent.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_subscription_design_consent\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"가입설계 동의 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"가입설계 동의 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_detail.json b/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_detail.json new file mode 100644 index 000000000..6c2847774 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_subscription_design_detail.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_subscription_design_detail\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"가입설계 동의 상세 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"가입설계 동의 상세 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/sot_tm_call_eligibility.json b/dap-was-sal/src/main/resources/mock-responses/sot_tm_call_eligibility.json new file mode 100644 index 000000000..3c7d45a59 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/sot_tm_call_eligibility.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"sot_tm_call_eligibility\",\"category\":\"SOT\",\"referenceId\":\"SOT-000001\",\"status\":\"정상\",\"summary\":\"텔레마케팅 콜 가능여부 종합 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"텔레마케팅 콜 가능여부 종합 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_answer.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_answer.json new file mode 100644 index 000000000..d861b08a4 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_answer.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_answer\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"문의 답변 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"문의 답변 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_category.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_category.json new file mode 100644 index 000000000..c1b03bbd8 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_category.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_category\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"문의 유형별 통계 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"문의 유형별 통계 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_detail.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_detail.json new file mode 100644 index 000000000..7a8f3b7a2 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_detail.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_detail\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"GA현장문의 상세 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA현장문의 상세 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_escalation.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_escalation.json new file mode 100644 index 000000000..8b6eaebd5 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_escalation.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_escalation\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"에스컬레이션 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"에스컬레이션 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_faq.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_faq.json new file mode 100644 index 000000000..cd43237ec --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_faq.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_faq\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"자주묻는질문 FAQ 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"자주묻는질문 FAQ 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_list.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_list.json new file mode 100644 index 000000000..f7daea8dc --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_list\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"GA현장문의 목록 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"GA현장문의 목록 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_manager.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_manager.json new file mode 100644 index 000000000..5c2811280 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_manager.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_manager\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"담당자 배정 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"담당자 배정 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_satisfaction.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_satisfaction.json new file mode 100644 index 000000000..2c03b22e1 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_satisfaction.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_satisfaction\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"문의 만족도 조사 결과 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"문의 만족도 조사 결과 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_sla.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_sla.json new file mode 100644 index 000000000..5fe9a343f --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_sla.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_sla\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"처리기한 SLA 현황 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"처리기한 SLA 현황 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_status.json b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_status.json new file mode 100644 index 000000000..b89d0d9a2 --- /dev/null +++ b/dap-was-sal/src/main/resources/mock-responses/spr_field_inquiry_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"spr_field_inquiry_status\",\"category\":\"SPR\",\"referenceId\":\"SPR-000001\",\"status\":\"정상\",\"summary\":\"문의 처리상태 조회 결과\",\"asOfDate\":\"2026-08-14\",\"details\":[\"문의 처리상태 조회 모의 상세정보\",\"외부 시스템을 변경하지 않는 조회 결과\"]}" +} diff --git a/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml b/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml new file mode 100644 index 000000000..9af8b88d9 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml @@ -0,0 +1,24 @@ +name: cmm_claim_search +display_name: 보험금 청구 상태 조회 +version: 1.0.0 +category_key: cmm +description: + function: 청구번호 또는 계약번호로 보험금 청구 상태와 심사 결과를 조회한다. + when_to_use: 사용자가 기존 보험금 청구의 진행 상태나 지급 결과를 확인하려는 경우 사용한다. + when_not_to_use: 보험금 청구를 새로 접수하거나 기존 청구 내용을 변경하려는 경우에는 사용하지 않는다. + io_limits: 청구번호 또는 계약번호 중 하나 이상이 필요하며 조회 결과만 반환한다. +display_description: 보험금 청구 상태와 심사 결과를 조회합니다. +example_queries: ["내 보험금 청구가 어디까지 진행됐는지 알려줘", "계약번호로 최근 청구 상태를 확인해줘", "청구 심사 결과가 나왔는지 조회해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + claimNo: {type: string, description: 조회할 보험금 청구번호} + contractNo: {type: string, description: 조회할 보험계약 번호} + additionalProperties: false +tags: [보험금, 청구조회] +legacy_interface_id: CLCNNB00001 +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_memo_retriever.yml b/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_memo_retriever.yml new file mode 100644 index 000000000..7c871a60f --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/cmm/cmm_memo_retriever.yml @@ -0,0 +1,24 @@ +name: cmm_memo_retriever +display_name: 의뢰서 목록 조회 +version: 1.0.0 +category_key: cmm +description: + function: 상태와 검색어 조건으로 의뢰서 목록을 조회한다. + when_to_use: 사용자가 등록된 의뢰서의 목록이나 처리 현황을 찾으려는 경우 사용한다. + when_not_to_use: 의뢰서를 신규 등록하거나 내용을 변경하려는 경우에는 사용하지 않는다. + io_limits: 선택 조건에 해당하는 의뢰서 요약 목록만 반환한다. +display_description: 조건에 맞는 의뢰서 목록을 조회합니다. +example_queries: ["처리 중인 의뢰서를 보여줘", "지난번 계약 관련 의뢰서를 찾아줘", "완료된 의뢰서 목록을 조회해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + memoStatus: {type: string, description: 조회할 의뢰서 처리 상태} + searchKeyword: {type: string, description: 의뢰서 제목 또는 내용 검색어} + additionalProperties: false +tags: [의뢰서, 목록조회] +legacy_interface_id: MEMO0000001 +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_document.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_document.yml new file mode 100644 index 000000000..1846b3999 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_document.yml @@ -0,0 +1,24 @@ +name: lic_contract_review_document +display_name: 계약심사 제출서류 현황 조회 +version: 1.0.0 +category_key: lic +description: + function: 계약심사 제출서류 현황 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 계약심사 제출서류 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계약심사 제출서류 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["계약심사 제출서류 현황 조회 정보를 보여줘", "계약심사 제출서류 현황 조회 결과를 확인해줘", "현재 계약심사 제출서류 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_history.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_history.yml new file mode 100644 index 000000000..371b48268 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_history.yml @@ -0,0 +1,24 @@ +name: lic_contract_review_history +display_name: 계약심사 이력 조회 +version: 1.0.0 +category_key: lic +description: + function: 계약심사 이력 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 계약심사 이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계약심사 이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["계약심사 이력 조회 정보를 보여줘", "계약심사 이력 조회 결과를 확인해줘", "현재 계약심사 이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_result.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_result.yml new file mode 100644 index 000000000..89dacca69 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_result.yml @@ -0,0 +1,24 @@ +name: lic_contract_review_result +display_name: 계약심사 결과 조회 +version: 1.0.0 +category_key: lic +description: + function: 계약심사 결과 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 계약심사 결과 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계약심사 결과 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["계약심사 결과 조회 정보를 보여줘", "계약심사 결과 조회 결과를 확인해줘", "현재 계약심사 결과 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_status.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_status.yml new file mode 100644 index 000000000..24909dd18 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_contract_review_status.yml @@ -0,0 +1,24 @@ +name: lic_contract_review_status +display_name: 계약심사 진행상태 조회 +version: 1.0.0 +category_key: lic +description: + function: 계약심사 진행상태 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 계약심사 진행상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계약심사 진행상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["계약심사 진행상태 조회 정보를 보여줘", "계약심사 진행상태 조회 결과를 확인해줘", "현재 계약심사 진행상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_coverage.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_coverage.yml new file mode 100644 index 000000000..2d97b0da7 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_coverage.yml @@ -0,0 +1,24 @@ +name: lic_product_coverage +display_name: 상품 보장내용 조회 +version: 1.0.0 +category_key: lic +description: + function: 상품 보장내용 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 상품 보장내용 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 상품 보장내용 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["상품 보장내용 조회 정보를 보여줘", "상품 보장내용 조회 결과를 확인해줘", "현재 상품 보장내용 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_info.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_info.yml new file mode 100644 index 000000000..413503786 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_info.yml @@ -0,0 +1,24 @@ +name: lic_product_info +display_name: 상품 정보 조회 +version: 1.0.0 +category_key: lic +description: + function: 상품 정보 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 상품 정보 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 상품 정보 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["상품 정보 조회 정보를 보여줘", "상품 정보 조회 결과를 확인해줘", "현재 상품 정보 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_premium_table.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_premium_table.yml new file mode 100644 index 000000000..c2ef9de4f --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_product_premium_table.yml @@ -0,0 +1,24 @@ +name: lic_product_premium_table +display_name: 상품 보험료 요율표 조회 +version: 1.0.0 +category_key: lic +description: + function: 상품 보험료 요율표 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 상품 보험료 요율표 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 상품 보험료 요율표 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["상품 보험료 요율표 조회 정보를 보여줘", "상품 보험료 요율표 조회 결과를 확인해줘", "현재 상품 보험료 요율표 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_additional_request.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_additional_request.yml new file mode 100644 index 000000000..dd2908675 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_additional_request.yml @@ -0,0 +1,24 @@ +name: lic_underwriting_additional_request +display_name: 언더라이팅 추가자료 요청 현황 조회 +version: 1.0.0 +category_key: lic +description: + function: 언더라이팅 추가자료 요청 현황 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 언더라이팅 추가자료 요청 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 언더라이팅 추가자료 요청 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["언더라이팅 추가자료 요청 현황 조회 정보를 보여줘", "언더라이팅 추가자료 요청 현황 조회 결과를 확인해줘", "현재 언더라이팅 추가자료 요청 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_result.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_result.yml new file mode 100644 index 000000000..243b8652a --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_result.yml @@ -0,0 +1,24 @@ +name: lic_underwriting_result +display_name: 언더라이팅 인수심사 결과 조회 +version: 1.0.0 +category_key: lic +description: + function: 언더라이팅 인수심사 결과 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 언더라이팅 인수심사 결과 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 언더라이팅 인수심사 결과 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["언더라이팅 인수심사 결과 조회 정보를 보여줘", "언더라이팅 인수심사 결과 조회 결과를 확인해줘", "현재 언더라이팅 인수심사 결과 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_risk_grade.yml b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_risk_grade.yml new file mode 100644 index 000000000..8b15acc5b --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/lic/lic_underwriting_risk_grade.yml @@ -0,0 +1,24 @@ +name: lic_underwriting_risk_grade +display_name: 언더라이팅 위험등급 조회 +version: 1.0.0 +category_key: lic +description: + function: 언더라이팅 위험등급 조회 정보를 조회합니다. + when_to_use: 상품·계약심사 업무에서 언더라이팅 위험등급 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 언더라이팅 위험등급 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["언더라이팅 위험등급 조회 정보를 보여줘", "언더라이팅 위험등급 조회 결과를 확인해줘", "현재 언더라이팅 위험등급 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + productCode: {type: string, description: 상품코드, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 계약심사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [lic, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_calendar.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_calendar.yml new file mode 100644 index 000000000..867b867bc --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_calendar.yml @@ -0,0 +1,24 @@ +name: sof_activity_calendar +display_name: 활동 일정 조회 +version: 1.0.0 +category_key: sof +description: + function: 활동 일정 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 활동 일정 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 활동 일정 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["활동 일정 조회 정보를 보여줘", "활동 일정 조회 결과를 확인해줘", "현재 활동 일정 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_daily.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_daily.yml new file mode 100644 index 000000000..84e5b2ec3 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_daily.yml @@ -0,0 +1,24 @@ +name: sof_activity_daily +display_name: GA 일별 활동 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: GA 일별 활동 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 GA 일별 활동 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 일별 활동 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 일별 활동 현황 조회 정보를 보여줘", "GA 일별 활동 현황 조회 결과를 확인해줘", "현재 GA 일별 활동 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_report_status.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_report_status.yml new file mode 100644 index 000000000..23142079c --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_report_status.yml @@ -0,0 +1,24 @@ +name: sof_activity_report_status +display_name: 활동일지 제출 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: 활동일지 제출 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 활동일지 제출 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 활동일지 제출 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["활동일지 제출 현황 조회 정보를 보여줘", "활동일지 제출 현황 조회 결과를 확인해줘", "현재 활동일지 제출 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_summary.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_summary.yml new file mode 100644 index 000000000..225075caa --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_activity_summary.yml @@ -0,0 +1,24 @@ +name: sof_activity_summary +display_name: GA 활동 종합 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: GA 활동 종합 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 GA 활동 종합 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 활동 종합 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 활동 종합 현황 조회 정보를 보여줘", "GA 활동 종합 현황 조회 결과를 확인해줘", "현재 GA 활동 종합 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_call_activity.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_call_activity.yml new file mode 100644 index 000000000..d0691423a --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_call_activity.yml @@ -0,0 +1,24 @@ +name: sof_call_activity +display_name: 콜 활동 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: 콜 활동 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 콜 활동 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 콜 활동 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["콜 활동 현황 조회 정보를 보여줘", "콜 활동 현황 조회 결과를 확인해줘", "현재 콜 활동 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_customer_consulting.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_customer_consulting.yml new file mode 100644 index 000000000..f01e17fe9 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_customer_consulting.yml @@ -0,0 +1,24 @@ +name: sof_customer_consulting +display_name: 고객 상담 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: 고객 상담 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 고객 상담 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 상담 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 상담 현황 조회 정보를 보여줘", "고객 상담 현황 조회 결과를 확인해줘", "현재 고객 상담 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_field_support_request.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_field_support_request.yml new file mode 100644 index 000000000..cb2812c8b --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_field_support_request.yml @@ -0,0 +1,24 @@ +name: sof_field_support_request +display_name: 현장 지원 요청 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: 현장 지원 요청 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 현장 지원 요청 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 현장 지원 요청 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["현장 지원 요청 현황 조회 정보를 보여줘", "현장 지원 요청 현황 조회 결과를 확인해줘", "현재 현장 지원 요청 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_prospect_list.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_prospect_list.yml new file mode 100644 index 000000000..225cbb858 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_prospect_list.yml @@ -0,0 +1,24 @@ +name: sof_prospect_list +display_name: 신규 발굴 고객 목록 조회 +version: 1.0.0 +category_key: sof +description: + function: 신규 발굴 고객 목록 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 신규 발굴 고객 목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 신규 발굴 고객 목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["신규 발굴 고객 목록 조회 정보를 보여줘", "신규 발굴 고객 목록 조회 결과를 확인해줘", "현재 신규 발굴 고객 목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_training_completion.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_training_completion.yml new file mode 100644 index 000000000..514651125 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_training_completion.yml @@ -0,0 +1,24 @@ +name: sof_training_completion +display_name: 교육 이수 현황 조회 +version: 1.0.0 +category_key: sof +description: + function: 교육 이수 현황 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 교육 이수 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 교육 이수 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["교육 이수 현황 조회 정보를 보여줘", "교육 이수 현황 조회 결과를 확인해줘", "현재 교육 이수 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sof/sof_visit_count.yml b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_visit_count.yml new file mode 100644 index 000000000..ba28633ab --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sof/sof_visit_count.yml @@ -0,0 +1,24 @@ +name: sof_visit_count +display_name: 고객 방문 건수 조회 +version: 1.0.0 +category_key: sof +description: + function: 고객 방문 건수 조회 정보를 조회합니다. + when_to_use: GA 활동 업무에서 고객 방문 건수 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객 방문 건수 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객 방문 건수 조회 정보를 보여줘", "고객 방문 건수 조회 결과를 확인해줘", "현재 고객 방문 건수 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sof, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_incentive_summary.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_incentive_summary.yml new file mode 100644 index 000000000..8588db800 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_incentive_summary.yml @@ -0,0 +1,24 @@ +name: sog_incentive_summary +display_name: 영업 인센티브 요약 조회 +version: 1.0.0 +category_key: sog +description: + function: 영업 인센티브 요약 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 영업 인센티브 요약 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 영업 인센티브 요약 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["영업 인센티브 요약 조회 정보를 보여줘", "영업 인센티브 요약 조회 결과를 확인해줘", "현재 영업 인센티브 요약 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_monthly_performance.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_monthly_performance.yml new file mode 100644 index 000000000..8c1a7d9ae --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_monthly_performance.yml @@ -0,0 +1,24 @@ +name: sog_monthly_performance +display_name: GA 월별 실적 조회 +version: 1.0.0 +category_key: sog +description: + function: GA 월별 실적 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 GA 월별 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 월별 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 월별 실적 조회 정보를 보여줘", "GA 월별 실적 조회 결과를 확인해줘", "현재 GA 월별 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_new_contract_count.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_new_contract_count.yml new file mode 100644 index 000000000..eec7e4d93 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_new_contract_count.yml @@ -0,0 +1,24 @@ +name: sog_new_contract_count +display_name: 신계약 건수 실적 조회 +version: 1.0.0 +category_key: sog +description: + function: 신계약 건수 실적 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 신계약 건수 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 신계약 건수 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["신계약 건수 실적 조회 정보를 보여줘", "신계약 건수 실적 조회 결과를 확인해줘", "현재 신계약 건수 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_performance_comparison.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_performance_comparison.yml new file mode 100644 index 000000000..6832cea85 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_performance_comparison.yml @@ -0,0 +1,24 @@ +name: sog_performance_comparison +display_name: 전년 동기 대비 실적 비교 조회 +version: 1.0.0 +category_key: sog +description: + function: 전년 동기 대비 실적 비교 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 전년 동기 대비 실적 비교 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 전년 동기 대비 실적 비교 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["전년 동기 대비 실적 비교 조회 정보를 보여줘", "전년 동기 대비 실적 비교 조회 결과를 확인해줘", "현재 전년 동기 대비 실적 비교 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_premium_performance.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_premium_performance.yml new file mode 100644 index 000000000..805cb561f --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_premium_performance.yml @@ -0,0 +1,24 @@ +name: sog_premium_performance +display_name: 보험료 실적 조회 +version: 1.0.0 +category_key: sog +description: + function: 보험료 실적 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 보험료 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보험료 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["보험료 실적 조회 정보를 보여줘", "보험료 실적 조회 결과를 확인해줘", "현재 보험료 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_product_performance.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_product_performance.yml new file mode 100644 index 000000000..cb165c37e --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_product_performance.yml @@ -0,0 +1,24 @@ +name: sog_product_performance +display_name: 상품별 실적 조회 +version: 1.0.0 +category_key: sog +description: + function: 상품별 실적 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 상품별 실적 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 상품별 실적 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["상품별 실적 조회 정보를 보여줘", "상품별 실적 조회 결과를 확인해줘", "현재 상품별 실적 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_sales_ranking.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_sales_ranking.yml new file mode 100644 index 000000000..d4f8f8dc3 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_sales_ranking.yml @@ -0,0 +1,24 @@ +name: sog_sales_ranking +display_name: GA 영업 순위 조회 +version: 1.0.0 +category_key: sog +description: + function: GA 영업 순위 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 GA 영업 순위 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 영업 순위 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 영업 순위 조회 정보를 보여줘", "GA 영업 순위 조회 결과를 확인해줘", "현재 GA 영업 순위 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_history.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_history.yml new file mode 100644 index 000000000..d6fba7b95 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_history.yml @@ -0,0 +1,24 @@ +name: sog_target_history +display_name: 영업목표 변경 이력 조회 +version: 1.0.0 +category_key: sog +description: + function: 영업목표 변경 이력 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 영업목표 변경 이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 영업목표 변경 이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["영업목표 변경 이력 조회 정보를 보여줘", "영업목표 변경 이력 조회 결과를 확인해줘", "현재 영업목표 변경 이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_progress.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_progress.yml new file mode 100644 index 000000000..2a4a93c5a --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_progress.yml @@ -0,0 +1,24 @@ +name: sog_target_progress +display_name: 영업목표 달성률 조회 +version: 1.0.0 +category_key: sog +description: + function: 영업목표 달성률 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 영업목표 달성률 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 영업목표 달성률 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["영업목표 달성률 조회 정보를 보여줘", "영업목표 달성률 조회 결과를 확인해줘", "현재 영업목표 달성률 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_summary.yml b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_summary.yml new file mode 100644 index 000000000..f6818743f --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sog/sog_target_summary.yml @@ -0,0 +1,24 @@ +name: sog_target_summary +display_name: GA 개인별 영업목표 요약 조회 +version: 1.0.0 +category_key: sog +description: + function: GA 개인별 영업목표 요약 조회 정보를 조회합니다. + when_to_use: GA 영업 업무에서 GA 개인별 영업목표 요약 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA 개인별 영업목표 요약 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA 개인별 영업목표 요약 조회 정보를 보여줘", "GA 개인별 영업목표 요약 조회 결과를 확인해줘", "현재 GA 개인별 영업목표 요약 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + organizationId: {type: string, description: GA 조직번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sog, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_expiry.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_expiry.yml new file mode 100644 index 000000000..9b7bdab2e --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_expiry.yml @@ -0,0 +1,24 @@ +name: sot_consent_expiry +display_name: 동의 유효기간 만료 현황 조회 +version: 1.0.0 +category_key: sot +description: + function: 동의 유효기간 만료 현황 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 동의 유효기간 만료 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 동의 유효기간 만료 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["동의 유효기간 만료 현황 조회 정보를 보여줘", "동의 유효기간 만료 현황 조회 결과를 확인해줘", "현재 동의 유효기간 만료 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_withdrawal.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_withdrawal.yml new file mode 100644 index 000000000..34a095d6f --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_consent_withdrawal.yml @@ -0,0 +1,24 @@ +name: sot_consent_withdrawal +display_name: 동의 철회 이력 조회 +version: 1.0.0 +category_key: sot +description: + function: 동의 철회 이력 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 동의 철회 이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 동의 철회 이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["동의 철회 이력 조회 정보를 보여줘", "동의 철회 이력 조회 결과를 확인해줘", "현재 동의 철회 이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent.yml new file mode 100644 index 000000000..75ec01432 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent.yml @@ -0,0 +1,24 @@ +name: sot_customer_info_consent +display_name: 고객정보 수집·이용 동의 현황 조회 +version: 1.0.0 +category_key: sot +description: + function: 고객정보 수집·이용 동의 현황 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 고객정보 수집·이용 동의 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객정보 수집·이용 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객정보 수집·이용 동의 현황 조회 정보를 보여줘", "고객정보 수집·이용 동의 현황 조회 결과를 확인해줘", "현재 고객정보 수집·이용 동의 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent_scope.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent_scope.yml new file mode 100644 index 000000000..db3d4a94d --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_customer_info_consent_scope.yml @@ -0,0 +1,24 @@ +name: sot_customer_info_consent_scope +display_name: 고객정보 동의 범위 조회 +version: 1.0.0 +category_key: sot +description: + function: 고객정보 동의 범위 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 고객정보 동의 범위 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 고객정보 동의 범위 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["고객정보 동의 범위 조회 정보를 보여줘", "고객정보 동의 범위 조회 결과를 확인해줘", "현재 고객정보 동의 범위 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_channel.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_channel.yml new file mode 100644 index 000000000..266b66acf --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_channel.yml @@ -0,0 +1,24 @@ +name: sot_marketing_consent_channel +display_name: 채널별 마케팅 동의 현황 조회 +version: 1.0.0 +category_key: sot +description: + function: 채널별 마케팅 동의 현황 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 채널별 마케팅 동의 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 채널별 마케팅 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["채널별 마케팅 동의 현황 조회 정보를 보여줘", "채널별 마케팅 동의 현황 조회 결과를 확인해줘", "현재 채널별 마케팅 동의 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_history.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_history.yml new file mode 100644 index 000000000..7369f60e7 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_history.yml @@ -0,0 +1,24 @@ +name: sot_marketing_consent_history +display_name: 마케팅 동의 이력 조회 +version: 1.0.0 +category_key: sot +description: + function: 마케팅 동의 이력 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 마케팅 동의 이력 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 마케팅 동의 이력 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["마케팅 동의 이력 조회 정보를 보여줘", "마케팅 동의 이력 조회 결과를 확인해줘", "현재 마케팅 동의 이력 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_status.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_status.yml new file mode 100644 index 000000000..47d865900 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_marketing_consent_status.yml @@ -0,0 +1,24 @@ +name: sot_marketing_consent_status +display_name: 마케팅 수신동의 현황 조회 +version: 1.0.0 +category_key: sot +description: + function: 마케팅 수신동의 현황 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 마케팅 수신동의 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 마케팅 수신동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["마케팅 수신동의 현황 조회 정보를 보여줘", "마케팅 수신동의 현황 조회 결과를 확인해줘", "현재 마케팅 수신동의 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_consent.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_consent.yml new file mode 100644 index 000000000..db2d8d8e5 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_consent.yml @@ -0,0 +1,24 @@ +name: sot_subscription_design_consent +display_name: 가입설계 동의 현황 조회 +version: 1.0.0 +category_key: sot +description: + function: 가입설계 동의 현황 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 가입설계 동의 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 가입설계 동의 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["가입설계 동의 현황 조회 정보를 보여줘", "가입설계 동의 현황 조회 결과를 확인해줘", "현재 가입설계 동의 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_detail.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_detail.yml new file mode 100644 index 000000000..3ba765af4 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_subscription_design_detail.yml @@ -0,0 +1,24 @@ +name: sot_subscription_design_detail +display_name: 가입설계 동의 상세 조회 +version: 1.0.0 +category_key: sot +description: + function: 가입설계 동의 상세 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 가입설계 동의 상세 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 가입설계 동의 상세 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["가입설계 동의 상세 조회 정보를 보여줘", "가입설계 동의 상세 조회 결과를 확인해줘", "현재 가입설계 동의 상세 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/sot/sot_tm_call_eligibility.yml b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_tm_call_eligibility.yml new file mode 100644 index 000000000..0903d73bc --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/sot/sot_tm_call_eligibility.yml @@ -0,0 +1,24 @@ +name: sot_tm_call_eligibility +display_name: 텔레마케팅 콜 가능여부 종합 조회 +version: 1.0.0 +category_key: sot +description: + function: 텔레마케팅 콜 가능여부 종합 조회 정보를 조회합니다. + when_to_use: 텔레마케팅 동의 업무에서 텔레마케팅 콜 가능여부 종합 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 텔레마케팅 콜 가능여부 종합 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["텔레마케팅 콜 가능여부 종합 조회 정보를 보여줘", "텔레마케팅 콜 가능여부 종합 조회 결과를 확인해줘", "현재 텔레마케팅 콜 가능여부 종합 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + customerId: {type: string, description: 고객번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + contractId: {type: string, description: 가입설계번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [sot, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_answer.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_answer.yml new file mode 100644 index 000000000..e701ef643 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_answer.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_answer +display_name: 문의 답변 조회 +version: 1.0.0 +category_key: spr +description: + function: 문의 답변 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 문의 답변 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 문의 답변 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["문의 답변 조회 정보를 보여줘", "문의 답변 조회 결과를 확인해줘", "현재 문의 답변 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_category.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_category.yml new file mode 100644 index 000000000..514fcce41 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_category.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_category +display_name: 문의 유형별 통계 조회 +version: 1.0.0 +category_key: spr +description: + function: 문의 유형별 통계 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 문의 유형별 통계 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 문의 유형별 통계 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["문의 유형별 통계 조회 정보를 보여줘", "문의 유형별 통계 조회 결과를 확인해줘", "현재 문의 유형별 통계 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_detail.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_detail.yml new file mode 100644 index 000000000..fb78b6120 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_detail.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_detail +display_name: GA현장문의 상세 조회 +version: 1.0.0 +category_key: spr +description: + function: GA현장문의 상세 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 GA현장문의 상세 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA현장문의 상세 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA현장문의 상세 조회 정보를 보여줘", "GA현장문의 상세 조회 결과를 확인해줘", "현재 GA현장문의 상세 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_escalation.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_escalation.yml new file mode 100644 index 000000000..e9725422c --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_escalation.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_escalation +display_name: 에스컬레이션 현황 조회 +version: 1.0.0 +category_key: spr +description: + function: 에스컬레이션 현황 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 에스컬레이션 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 에스컬레이션 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["에스컬레이션 현황 조회 정보를 보여줘", "에스컬레이션 현황 조회 결과를 확인해줘", "현재 에스컬레이션 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_faq.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_faq.yml new file mode 100644 index 000000000..6553b111d --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_faq.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_faq +display_name: 자주묻는질문 FAQ 조회 +version: 1.0.0 +category_key: spr +description: + function: 자주묻는질문 FAQ 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 자주묻는질문 FAQ 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 자주묻는질문 FAQ 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["자주묻는질문 FAQ 조회 정보를 보여줘", "자주묻는질문 FAQ 조회 결과를 확인해줘", "현재 자주묻는질문 FAQ 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_list.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_list.yml new file mode 100644 index 000000000..f9018324c --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_list.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_list +display_name: GA현장문의 목록 조회 +version: 1.0.0 +category_key: spr +description: + function: GA현장문의 목록 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 GA현장문의 목록 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: GA현장문의 목록 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["GA현장문의 목록 조회 정보를 보여줘", "GA현장문의 목록 조회 결과를 확인해줘", "현재 GA현장문의 목록 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_manager.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_manager.yml new file mode 100644 index 000000000..981d2f0bf --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_manager.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_manager +display_name: 담당자 배정 현황 조회 +version: 1.0.0 +category_key: spr +description: + function: 담당자 배정 현황 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 담당자 배정 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 담당자 배정 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["담당자 배정 현황 조회 정보를 보여줘", "담당자 배정 현황 조회 결과를 확인해줘", "현재 담당자 배정 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_satisfaction.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_satisfaction.yml new file mode 100644 index 000000000..3ca93e8cb --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_satisfaction.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_satisfaction +display_name: 문의 만족도 조사 결과 조회 +version: 1.0.0 +category_key: spr +description: + function: 문의 만족도 조사 결과 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 문의 만족도 조사 결과 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 문의 만족도 조사 결과 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["문의 만족도 조사 결과 조회 정보를 보여줘", "문의 만족도 조사 결과 조회 결과를 확인해줘", "현재 문의 만족도 조사 결과 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_sla.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_sla.yml new file mode 100644 index 000000000..16c7feb3c --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_sla.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_sla +display_name: 처리기한 SLA 현황 조회 +version: 1.0.0 +category_key: spr +description: + function: 처리기한 SLA 현황 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 처리기한 SLA 현황 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 처리기한 SLA 현황 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["처리기한 SLA 현황 조회 정보를 보여줘", "처리기한 SLA 현황 조회 결과를 확인해줘", "현재 처리기한 SLA 현황 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_status.yml b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_status.yml new file mode 100644 index 000000000..f97ba3471 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-definitions/spr/spr_field_inquiry_status.yml @@ -0,0 +1,24 @@ +name: spr_field_inquiry_status +display_name: 문의 처리상태 조회 +version: 1.0.0 +category_key: spr +description: + function: 문의 처리상태 조회 정보를 조회합니다. + when_to_use: GA현장지원 업무에서 문의 처리상태 조회 정보가 필요할 때 사용합니다. + when_not_to_use: 정보 변경, 승인 또는 실제 업무처리에는 사용하지 않습니다. + io_limits: 입력 식별자에 대한 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 문의 처리상태 조회 정보를 안전한 모의 데이터로 제공합니다. +example_queries: ["문의 처리상태 조회 정보를 보여줘", "문의 처리상태 조회 결과를 확인해줘", "현재 문의 처리상태 조회 내용을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + inquiryId: {type: string, description: 현장문의번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + agentId: {type: string, description: 설계사번호, pattern: "^[A-Za-z0-9_-]{3,30}$"} + queryDate: {type: string, description: 조회 기준일, pattern: "^\\d{4}-\\d{2}-\\d{2}$"} + additionalProperties: false +tags: [spr, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json b/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json new file mode 100644 index 000000000..e6f63087e --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "TODO_FIELD": { + "type": "string", + "description": "TODO: 파라미터 설명을 입력하세요." + } + }, + "required": [] +} diff --git a/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json b/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json new file mode 100644 index 000000000..9c280db87 --- /dev/null +++ b/dap-was-sal/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "처리 결과 상태 (SUCCESS / FAILURE)", + "enum": ["SUCCESS", "FAILURE"] + }, + "message": { + "type": "string", + "description": "처리 결과 메시지" + } + }, + "required": ["status"] +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java new file mode 100644 index 000000000..37356b3e1 --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import org.junit.jupiter.api.Test; + +class ClaimSearchUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new ClaimSearchRequest()); + assertNotNull(new ClaimSearchResponse()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java new file mode 100644 index 000000000..61d6f7f4a --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import org.junit.jupiter.api.Test; + +class MemoListRetrieverUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new MemoListRetrieverRequest()); + assertNotNull(new MemoListRetrieverResponse()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImplTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImplTest.java new file mode 100644 index 000000000..fc023ff83 --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/lic/usecase/impl/LicUnderwritingUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.lic.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.lic.dto.LicToolRequest; +import org.junit.jupiter.api.Test; + +class LicUnderwritingUseCaseImplTest { + + @Test + void returnsProductInfoForRequestedProductCode() { + LicToolRequest request = new LicToolRequest(); + request.setProductCode("LIC-PRD-000123"); + request.setQueryDate("2026-08-14"); + + var response = new LicUnderwritingUseCaseImpl().getProductInfo(request); + + assertEquals("lic_product_info", response.getToolName()); + assertEquals("LIC-PRD-000123", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImplTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImplTest.java new file mode 100644 index 000000000..f7e8601ed --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sof/usecase/impl/SofFieldActivityUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.sof.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.sof.dto.SofToolRequest; +import org.junit.jupiter.api.Test; + +class SofFieldActivityUseCaseImplTest { + + @Test + void returnsActivityDailyForRequestedAgentId() { + SofToolRequest request = new SofToolRequest(); + request.setAgentId("SOF-AGT-000123"); + request.setQueryDate("2026-08-14"); + + var response = new SofFieldActivityUseCaseImpl().getActivityDaily(request); + + assertEquals("sof_activity_daily", response.getToolName()); + assertEquals("SOF-AGT-000123", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImplTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImplTest.java new file mode 100644 index 000000000..06ce8fbc2 --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sog/usecase/impl/SogSalesPerformanceUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.sog.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.sog.dto.SogToolRequest; +import org.junit.jupiter.api.Test; + +class SogSalesPerformanceUseCaseImplTest { + + @Test + void returnsTargetSummaryForRequestedAgentId() { + SogToolRequest request = new SogToolRequest(); + request.setAgentId("SOG-AGT-000123"); + request.setQueryDate("2026-08-14"); + + var response = new SogSalesPerformanceUseCaseImpl().getTargetSummary(request); + + assertEquals("sog_target_summary", response.getToolName()); + assertEquals("SOG-AGT-000123", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImplTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImplTest.java new file mode 100644 index 000000000..58977b871 --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/sot/usecase/impl/SotConsentUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.sot.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.sot.dto.SotToolRequest; +import org.junit.jupiter.api.Test; + +class SotConsentUseCaseImplTest { + + @Test + void returnsMarketingConsentStatusForRequestedCustomerId() { + SotToolRequest request = new SotToolRequest(); + request.setCustomerId("SOT-CUS-000123"); + request.setQueryDate("2026-08-14"); + + var response = new SotConsentUseCaseImpl().getMarketingConsentStatus(request); + + assertEquals("sot_marketing_consent_status", response.getToolName()); + assertEquals("SOT-CUS-000123", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImplTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImplTest.java new file mode 100644 index 000000000..4439c0527 --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/biz/spr/usecase/impl/SprFieldInquiryUseCaseImplTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.spr.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.spr.dto.SprToolRequest; +import org.junit.jupiter.api.Test; + +class SprFieldInquiryUseCaseImplTest { + + @Test + void returnsFieldInquiryListForRequestedInquiryId() { + SprToolRequest request = new SprToolRequest(); + request.setInquiryId("SPR-INQ-000123"); + request.setQueryDate("2026-08-14"); + + var response = new SprFieldInquiryUseCaseImpl().getFieldInquiryList(request); + + assertEquals("spr_field_inquiry_list", response.getToolName()); + assertEquals("SPR-INQ-000123", response.getReferenceId()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getDetails().isEmpty()); + } +} diff --git a/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/sal/SalCategoryMockToolResourcesTest.java b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/sal/SalCategoryMockToolResourcesTest.java new file mode 100644 index 000000000..c7e7c55ba --- /dev/null +++ b/dap-was-sal/src/test/java/io/shinhanlife/dap/mcc/sal/SalCategoryMockToolResourcesTest.java @@ -0,0 +1,102 @@ +package io.shinhanlife.dap.mcc.sal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionRepository; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.core.io.DefaultResourceLoader; + +class SalCategoryMockToolResourcesTest { + + private static final Set NEW_CATEGORY_KEYS = Set.of("sog", "sof", "sot", "lic", "spr"); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void registersTenToolsPerNewCategory() { + Map definitions = repository().findAll(); + + for (String key : NEW_CATEGORY_KEYS) { + long count = definitions.values().stream().filter(d -> key.equals(d.categoryKey())).count(); + assertEquals(10, count, key); + } + + long newCategoryTotal = definitions.values().stream() + .filter(d -> NEW_CATEGORY_KEYS.contains(d.categoryKey())).count(); + assertEquals(50, newCategoryTotal); + + assertTrue(definitions.values().stream() + .filter(d -> NEW_CATEGORY_KEYS.contains(d.categoryKey())) + .allMatch(ToolDefinition::readOnly)); + assertTrue(definitions.values().stream() + .filter(d -> NEW_CATEGORY_KEYS.contains(d.categoryKey())) + .noneMatch(ToolDefinition::destructive)); + assertTrue(definitions.values().stream() + .filter(d -> NEW_CATEGORY_KEYS.contains(d.categoryKey())) + .allMatch(ToolDefinition::idempotent)); + } + + @Test + void everyNewCategoryDefinitionHasMatchingValidMockResponse() throws Exception { + for (ToolDefinition definition : repository().findAll().values()) { + if (!NEW_CATEGORY_KEYS.contains(definition.categoryKey())) { + continue; + } + try (InputStream input = getClass().getResourceAsStream( + "/mock-responses/" + definition.name() + ".json")) { + assertNotNull(input, definition.name()); + JsonNode response = objectMapper.readTree(input); + assertEquals("SUCCESS", response.path("resultCode").asText(), definition.name()); + JsonNode data = objectMapper.readTree(response.path("data").asText()); + assertEquals(definition.name(), data.path("toolName").asText()); + assertFalse(data.path("details").isEmpty(), definition.name()); + } + } + } + + @Test + void useCaseInterfacesExposeFiftyUniqueMcpToolsMatchingDefinitions() { + List> useCases = List.of( + io.shinhanlife.dap.mcc.biz.sog.usecase.SogSalesPerformanceUseCase.class, + io.shinhanlife.dap.mcc.biz.sof.usecase.SofFieldActivityUseCase.class, + io.shinhanlife.dap.mcc.biz.sot.usecase.SotConsentUseCase.class, + io.shinhanlife.dap.mcc.biz.lic.usecase.LicUnderwritingUseCase.class, + io.shinhanlife.dap.mcc.biz.spr.usecase.SprFieldInquiryUseCase.class); + + Set names = new HashSet<>(); + for (Class useCase : useCases) { + for (Method method : useCase.getDeclaredMethods()) { + McpTool tool = method.getAnnotation(McpTool.class); + assertNotNull(tool, useCase.getSimpleName() + "." + method.getName()); + assertTrue(names.add(tool.name()), "duplicate tool: " + tool.name()); + } + } + assertEquals(50, names.size()); + + Set newCategoryDefinitionNames = repository().findAll().values().stream() + .filter(d -> NEW_CATEGORY_KEYS.contains(d.categoryKey())) + .map(ToolDefinition::name) + .collect(Collectors.toSet()); + assertEquals(newCategoryDefinitionNames, names); + } + + private ToolDefinitionRepository repository() { + return new ToolDefinitionRepository(new ObjectMapper(new YAMLFactory()), + new DefaultResourceLoader(), "classpath*:tool-definitions/**/*.yml"); + } +} diff --git a/dap-was-sys/Dockerfile b/dap-was-sys/Dockerfile new file mode 100644 index 000000000..b6c1c8459 --- /dev/null +++ b/dap-was-sys/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-sys/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8086 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/dap-was-sys/build.gradle b/dap-was-sys/build.gradle new file mode 100644 index 000000000..629ebb458 --- /dev/null +++ b/dap-was-sys/build.gradle @@ -0,0 +1,9 @@ +plugins { + // Spring Boot 3.5.11 version is managed by the root build.gradle. + id 'org.springframework.boot' +} + +dependencies { + // Shared MCP, Glow integration, validation, logging, Lombok and MapStruct configuration. + implementation project(':dap-was-lib') +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusRequest.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusRequest.java new file mode 100644 index 000000000..859789277 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusRequest.java @@ -0,0 +1,10 @@ +package io.shinhanlife.dap.mcc.biz.iam.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SystemStatusRequest { + @McpToolParam(description = "조회할 환경명", required = false) + private String environment; +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusResponse.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusResponse.java new file mode 100644 index 000000000..055c621ac --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/dto/SystemStatusResponse.java @@ -0,0 +1,11 @@ +package io.shinhanlife.dap.mcc.biz.iam.dto; + +import lombok.Data; + +@Data +public class SystemStatusResponse { + private String systemName; + private String environment; + private String status; + private String checkedAt; +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AccountInquiryUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AccountInquiryUseCase.java new file mode 100644 index 000000000..18b8b26f4 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AccountInquiryUseCase.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface AccountInquiryUseCase { + @McpTool(name = "iam_account_profile", title = "계정 프로필 조회", description = "모의 계정 프로필 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_PROFILE") + SystemStatusResponse getPrimary(SystemStatusRequest request); + + @McpTool(name = "iam_account_list", title = "계정 목록 조회", description = "모의 계정 목록 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_LIST") + SystemStatusResponse getList(SystemStatusRequest request); + + @McpTool(name = "iam_account_status", title = "계정 상태 조회", description = "모의 계정 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_STATUS") + SystemStatusResponse getStatus(SystemStatusRequest request); + + @McpTool(name = "iam_account_role", title = "계정 역할 조회", description = "모의 계정 역할 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_ROLE") + SystemStatusResponse getDetail(SystemStatusRequest request); + + @McpTool(name = "iam_account_lock_status", title = "계정 잠금 상태 조회", description = "모의 계정 잠금 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_LOCK_STATUS") + SystemStatusResponse getContact(SystemStatusRequest request); + + @McpTool(name = "iam_account_password_expiry", title = "비밀번호 만료일 조회", description = "모의 비밀번호 만료일 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCOUNT_PASSWORD_EXPIRY") + SystemStatusResponse getResult(SystemStatusRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthenticationInquiryUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthenticationInquiryUseCase.java new file mode 100644 index 000000000..8e90844c6 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthenticationInquiryUseCase.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface AuthenticationInquiryUseCase { + @McpTool(name = "iam_login_history", title = "로그인 이력 조회", description = "모의 로그인 이력 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_LOGIN_HISTORY") + SystemStatusResponse getPrimary(SystemStatusRequest request); + + @McpTool(name = "iam_authentication_method", title = "인증 수단 조회", description = "모의 인증 수단 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_AUTHENTICATION_METHOD") + SystemStatusResponse getList(SystemStatusRequest request); + + @McpTool(name = "iam_mfa_status", title = "다중 인증 상태 조회", description = "모의 다중 인증 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_MFA_STATUS") + SystemStatusResponse getStatus(SystemStatusRequest request); + + @McpTool(name = "iam_access_token_status", title = "접근 토큰 상태 조회", description = "모의 접근 토큰 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCESS_TOKEN_STATUS") + SystemStatusResponse getDetail(SystemStatusRequest request); + + @McpTool(name = "iam_session_list", title = "세션 목록 조회", description = "모의 세션 목록 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_SESSION_LIST") + SystemStatusResponse getContact(SystemStatusRequest request); + + @McpTool(name = "iam_trusted_device_list", title = "신뢰 기기 목록 조회", description = "모의 신뢰 기기 목록 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_TRUSTED_DEVICE_LIST") + SystemStatusResponse getResult(SystemStatusRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthorizationInquiryUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthorizationInquiryUseCase.java new file mode 100644 index 000000000..048afdfd0 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/AuthorizationInquiryUseCase.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface AuthorizationInquiryUseCase { + @McpTool(name = "iam_permission_list", title = "권한 목록 조회", description = "모의 권한 목록 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_PERMISSION_LIST") + SystemStatusResponse getPrimary(SystemStatusRequest request); + + @McpTool(name = "iam_role_list", title = "역할 목록 조회", description = "모의 역할 목록 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ROLE_LIST") + SystemStatusResponse getList(SystemStatusRequest request); + + @McpTool(name = "iam_menu_access", title = "메뉴 접근 권한 조회", description = "모의 메뉴 접근 권한 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_MENU_ACCESS") + SystemStatusResponse getStatus(SystemStatusRequest request); + + @McpTool(name = "iam_resource_access", title = "자원 접근 권한 조회", description = "모의 자원 접근 권한 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_RESOURCE_ACCESS") + SystemStatusResponse getDetail(SystemStatusRequest request); + + @McpTool(name = "iam_approval_authority", title = "결재 권한 조회", description = "모의 결재 권한 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_APPROVAL_AUTHORITY") + SystemStatusResponse getContact(SystemStatusRequest request); + + @McpTool(name = "iam_delegation_status", title = "권한 위임 상태 조회", description = "모의 권한 위임 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_DELEGATION_STATUS") + SystemStatusResponse getResult(SystemStatusRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/SystemStatusUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/SystemStatusUseCase.java new file mode 100644 index 000000000..5fe47f8ad --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/SystemStatusUseCase.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SystemStatusUseCase { + @McpTool(name = "iam_system_status", title = "시스템 상태 조회", + description = "조회 전용 모의 시스템 상태를 반환합니다.", + annotations = @McpTool.McpAnnotations(openWorldHint = false)) + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_STATUS") + SystemStatusResponse getSystemStatus(SystemStatusRequest request); + + @McpTool(name = "iam_service_health", title = "서비스 상태 조회", description = "모의 서비스 상태를 조회합니다.") + SystemStatusResponse getServiceHealth(SystemStatusRequest request); + @McpTool(name = "iam_environment_info", title = "환경 정보 조회", description = "모의 환경 정보를 조회합니다.") + SystemStatusResponse getEnvironmentInfo(SystemStatusRequest request); + @McpTool(name = "iam_deployment_version", title = "배포 버전 조회", description = "모의 배포 버전을 조회합니다.") + SystemStatusResponse getDeploymentVersion(SystemStatusRequest request); + + @McpTool(name = "iam_access_policy", title = "접근 정책 조회", description = "모의 접근 정책을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_ACCESS_POLICY") + SystemStatusResponse getAccessPolicy(SystemStatusRequest request); + + @McpTool(name = "iam_session_status", title = "세션 상태 조회", description = "모의 세션 상태를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_SESSION_STATUS") + SystemStatusResponse getSessionStatus(SystemStatusRequest request); + + @McpTool(name = "iam_identity_provider_list", title = "인증 제공자 목록 조회", description = "모의 인증 제공자 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_IDENTITY_PROVIDER_LIST") + SystemStatusResponse getIdentityProviderList(SystemStatusRequest request); + + @McpTool(name = "iam_security_event_list", title = "보안 이벤트 목록 조회", description = "모의 보안 이벤트 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_SECURITY_EVENT_LIST") + SystemStatusResponse getSecurityEventList(SystemStatusRequest request); + + @McpTool(name = "iam_privilege_change_history", title = "권한 변경 이력 조회", description = "모의 권한 변경 이력을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_PRIVILEGE_CHANGE_HISTORY") + SystemStatusResponse getPrivilegeChangeHistory(SystemStatusRequest request); + + @McpTool(name = "iam_user_group_list", title = "사용자 그룹 목록 조회", description = "모의 사용자 그룹 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "iam", mappingId = "DIRECT_IAM_USER_GROUP_LIST") + SystemStatusResponse getUserGroupList(SystemStatusRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AccountInquiryUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AccountInquiryUseCaseImpl.java new file mode 100644 index 000000000..93967e3c5 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AccountInquiryUseCaseImpl.java @@ -0,0 +1,48 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import io.shinhanlife.dap.mcc.biz.iam.usecase.AccountInquiryUseCase; +import org.springframework.stereotype.Service; + +@Service +public class AccountInquiryUseCaseImpl implements AccountInquiryUseCase { + @Override + public SystemStatusResponse getPrimary(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getList(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getStatus(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getDetail(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getContact(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getResult(SystemStatusRequest request) { + return mockResponse(request); + } + + private SystemStatusResponse mockResponse(SystemStatusRequest request) { + SystemStatusResponse response = new SystemStatusResponse(); + response.setSystemName("AX Hub IAM"); + response.setEnvironment(request != null && request.getEnvironment() != null && !request.getEnvironment().isBlank() ? request.getEnvironment() : "개발"); + response.setStatus("정상"); + response.setCheckedAt(java.time.OffsetDateTime.now().toString()); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthenticationInquiryUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthenticationInquiryUseCaseImpl.java new file mode 100644 index 000000000..fb2d7f38c --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthenticationInquiryUseCaseImpl.java @@ -0,0 +1,48 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import io.shinhanlife.dap.mcc.biz.iam.usecase.AuthenticationInquiryUseCase; +import org.springframework.stereotype.Service; + +@Service +public class AuthenticationInquiryUseCaseImpl implements AuthenticationInquiryUseCase { + @Override + public SystemStatusResponse getPrimary(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getList(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getStatus(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getDetail(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getContact(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getResult(SystemStatusRequest request) { + return mockResponse(request); + } + + private SystemStatusResponse mockResponse(SystemStatusRequest request) { + SystemStatusResponse response = new SystemStatusResponse(); + response.setSystemName("AX Hub IAM"); + response.setEnvironment(request != null && request.getEnvironment() != null && !request.getEnvironment().isBlank() ? request.getEnvironment() : "개발"); + response.setStatus("정상"); + response.setCheckedAt(java.time.OffsetDateTime.now().toString()); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthorizationInquiryUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthorizationInquiryUseCaseImpl.java new file mode 100644 index 000000000..07d8e9c9f --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/AuthorizationInquiryUseCaseImpl.java @@ -0,0 +1,48 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import io.shinhanlife.dap.mcc.biz.iam.usecase.AuthorizationInquiryUseCase; +import org.springframework.stereotype.Service; + +@Service +public class AuthorizationInquiryUseCaseImpl implements AuthorizationInquiryUseCase { + @Override + public SystemStatusResponse getPrimary(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getList(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getStatus(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getDetail(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getContact(SystemStatusRequest request) { + return mockResponse(request); + } + + @Override + public SystemStatusResponse getResult(SystemStatusRequest request) { + return mockResponse(request); + } + + private SystemStatusResponse mockResponse(SystemStatusRequest request) { + SystemStatusResponse response = new SystemStatusResponse(); + response.setSystemName("AX Hub IAM"); + response.setEnvironment(request != null && request.getEnvironment() != null && !request.getEnvironment().isBlank() ? request.getEnvironment() : "개발"); + response.setStatus("정상"); + response.setCheckedAt(java.time.OffsetDateTime.now().toString()); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImpl.java new file mode 100644 index 000000000..59c87706f --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImpl.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import io.shinhanlife.dap.mcc.biz.iam.usecase.SystemStatusUseCase; +import java.time.OffsetDateTime; +import org.springframework.stereotype.Service; + +@Service +public class SystemStatusUseCaseImpl implements SystemStatusUseCase { + @Override public SystemStatusResponse getServiceHealth(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getEnvironmentInfo(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getDeploymentVersion(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getAccessPolicy(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getSessionStatus(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getIdentityProviderList(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getSecurityEventList(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getPrivilegeChangeHistory(SystemStatusRequest request) { return getSystemStatus(request); } + @Override public SystemStatusResponse getUserGroupList(SystemStatusRequest request) { return getSystemStatus(request); } + @Override + public SystemStatusResponse getSystemStatus(SystemStatusRequest request) { + SystemStatusResponse response = new SystemStatusResponse(); + response.setSystemName("AX Hub 시스템"); + response.setEnvironment(request != null && request.getEnvironment() != null && !request.getEnvironment().isBlank() + ? request.getEnvironment() : "개발"); + response.setStatus("정상"); + response.setCheckedAt(OffsetDateTime.now().toString()); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeRequest.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeRequest.java new file mode 100644 index 000000000..08d358fcb --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeRequest.java @@ -0,0 +1,10 @@ +package io.shinhanlife.dap.mcc.biz.pct.dto; + +import lombok.Data; +import org.springaicommunity.mcp.annotation.McpToolParam; + +@Data +public class SystemNoticeRequest { + @McpToolParam(description = "공지 분류", required = false) + private String category; +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeResponse.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeResponse.java new file mode 100644 index 000000000..96491fbbf --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/dto/SystemNoticeResponse.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.pct.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class SystemNoticeResponse { + private List notices; + + @Data + public static class Notice { + private String title; + private String priority; + private String publishedDate; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/MaintenanceInquiryUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/MaintenanceInquiryUseCase.java new file mode 100644 index 000000000..d501edde4 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/MaintenanceInquiryUseCase.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface MaintenanceInquiryUseCase { + @McpTool(name = "pct_maintenance_schedule", title = "점검 일정 조회", description = "모의 점검 일정 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_SCHEDULE") + SystemNoticeResponse getPrimary(SystemNoticeRequest request); + + @McpTool(name = "pct_maintenance_history", title = "점검 이력 조회", description = "모의 점검 이력 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_HISTORY") + SystemNoticeResponse getList(SystemNoticeRequest request); + + @McpTool(name = "pct_maintenance_scope", title = "점검 범위 조회", description = "모의 점검 범위 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_SCOPE") + SystemNoticeResponse getStatus(SystemNoticeRequest request); + + @McpTool(name = "pct_maintenance_status", title = "점검 진행 상태 조회", description = "모의 점검 진행 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_STATUS") + SystemNoticeResponse getDetail(SystemNoticeRequest request); + + @McpTool(name = "pct_maintenance_contact", title = "점검 담당자 조회", description = "모의 점검 담당자 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_CONTACT") + SystemNoticeResponse getContact(SystemNoticeRequest request); + + @McpTool(name = "pct_maintenance_result", title = "점검 결과 조회", description = "모의 점검 결과 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_MAINTENANCE_RESULT") + SystemNoticeResponse getResult(SystemNoticeRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/ReleaseInquiryUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/ReleaseInquiryUseCase.java new file mode 100644 index 000000000..8e0ff5158 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/ReleaseInquiryUseCase.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface ReleaseInquiryUseCase { + @McpTool(name = "pct_release_notice", title = "배포 공지 조회", description = "모의 배포 공지 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_NOTICE") + SystemNoticeResponse getPrimary(SystemNoticeRequest request); + + @McpTool(name = "pct_release_history", title = "배포 이력 조회", description = "모의 배포 이력 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_HISTORY") + SystemNoticeResponse getList(SystemNoticeRequest request); + + @McpTool(name = "pct_release_schedule", title = "배포 일정 조회", description = "모의 배포 일정 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_SCHEDULE") + SystemNoticeResponse getStatus(SystemNoticeRequest request); + + @McpTool(name = "pct_release_version", title = "배포 버전 조회", description = "모의 배포 버전 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_VERSION") + SystemNoticeResponse getDetail(SystemNoticeRequest request); + + @McpTool(name = "pct_release_scope", title = "배포 범위 조회", description = "모의 배포 범위 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_SCOPE") + SystemNoticeResponse getContact(SystemNoticeRequest request); + + @McpTool(name = "pct_release_status", title = "배포 상태 조회", description = "모의 배포 상태 조회 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_RELEASE_STATUS") + SystemNoticeResponse getResult(SystemNoticeRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/SystemNoticeUseCase.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/SystemNoticeUseCase.java new file mode 100644 index 000000000..f8844b2fe --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/SystemNoticeUseCase.java @@ -0,0 +1,48 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase; + +import io.shinhanlife.dap.lib.annotation.GrowToolHint; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import org.springaicommunity.mcp.annotation.McpTool; + +public interface SystemNoticeUseCase { + @McpTool(name = "pct_notice_list", title = "시스템 공지 조회", description = "모의 시스템 공지 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_NOTICE_LIST") + SystemNoticeResponse getSystemNotices(SystemNoticeRequest request); + + @McpTool(name = "pct_notice_search", title = "공지 검색 조회", description = "모의 공지 검색 결과를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_NOTICE_SEARCH") + SystemNoticeResponse getNoticeSearch(SystemNoticeRequest request); + + @McpTool(name = "pct_notice_recent", title = "최신 공지 조회", description = "모의 최신 공지를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_NOTICE_RECENT") + SystemNoticeResponse getRecentNotices(SystemNoticeRequest request); + + @McpTool(name = "pct_alert_list", title = "알림 목록 조회", description = "모의 알림 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_ALERT_LIST") + SystemNoticeResponse getAlertList(SystemNoticeRequest request); + + @McpTool(name = "pct_notice_detail", title = "공지 상세 조회", description = "모의 공지 상세 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_NOTICE_DETAIL") + SystemNoticeResponse getNoticeDetail(SystemNoticeRequest request); + + @McpTool(name = "pct_notice_category", title = "공지 분류 조회", description = "모의 공지 분류 정보를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_NOTICE_CATEGORY") + SystemNoticeResponse getNoticeCategory(SystemNoticeRequest request); + + @McpTool(name = "pct_incident_list", title = "장애 목록 조회", description = "모의 장애 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_INCIDENT_LIST") + SystemNoticeResponse getIncidentList(SystemNoticeRequest request); + + @McpTool(name = "pct_change_request_list", title = "변경 요청 목록 조회", description = "모의 변경 요청 목록을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_CHANGE_REQUEST_LIST") + SystemNoticeResponse getChangeRequestList(SystemNoticeRequest request); + + @McpTool(name = "pct_operation_calendar", title = "운영 일정 조회", description = "모의 운영 일정을 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_OPERATION_CALENDAR") + SystemNoticeResponse getOperationCalendar(SystemNoticeRequest request); + + @McpTool(name = "pct_service_request_status", title = "서비스 요청 상태 조회", description = "모의 서비스 요청 상태를 조회합니다.") + @GrowToolHint(requiresApproval = false, categoryKey = "pct", mappingId = "DIRECT_PCT_SERVICE_REQUEST_STATUS") + SystemNoticeResponse getServiceRequestStatus(SystemNoticeRequest request); +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/MaintenanceInquiryUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/MaintenanceInquiryUseCaseImpl.java new file mode 100644 index 000000000..a1f52c4e8 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/MaintenanceInquiryUseCaseImpl.java @@ -0,0 +1,49 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import io.shinhanlife.dap.mcc.biz.pct.usecase.MaintenanceInquiryUseCase; +import org.springframework.stereotype.Service; + +@Service +public class MaintenanceInquiryUseCaseImpl implements MaintenanceInquiryUseCase { + @Override + public SystemNoticeResponse getPrimary(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getList(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getStatus(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getDetail(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getContact(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getResult(SystemNoticeRequest request) { + return mockResponse(request); + } + + private SystemNoticeResponse mockResponse(SystemNoticeRequest request) { + SystemNoticeResponse.Notice notice = new SystemNoticeResponse.Notice(); + notice.setTitle("점검 정보 조회"); + notice.setPriority("보통"); + notice.setPublishedDate("2026-08-13"); + SystemNoticeResponse response = new SystemNoticeResponse(); + response.setNotices(java.util.List.of(notice)); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/ReleaseInquiryUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/ReleaseInquiryUseCaseImpl.java new file mode 100644 index 000000000..8d585d268 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/ReleaseInquiryUseCaseImpl.java @@ -0,0 +1,49 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import io.shinhanlife.dap.mcc.biz.pct.usecase.ReleaseInquiryUseCase; +import org.springframework.stereotype.Service; + +@Service +public class ReleaseInquiryUseCaseImpl implements ReleaseInquiryUseCase { + @Override + public SystemNoticeResponse getPrimary(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getList(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getStatus(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getDetail(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getContact(SystemNoticeRequest request) { + return mockResponse(request); + } + + @Override + public SystemNoticeResponse getResult(SystemNoticeRequest request) { + return mockResponse(request); + } + + private SystemNoticeResponse mockResponse(SystemNoticeRequest request) { + SystemNoticeResponse.Notice notice = new SystemNoticeResponse.Notice(); + notice.setTitle("배포 정보 조회"); + notice.setPriority("보통"); + notice.setPublishedDate("2026-08-13"); + SystemNoticeResponse response = new SystemNoticeResponse(); + response.setNotices(java.util.List.of(notice)); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImpl.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImpl.java new file mode 100644 index 000000000..c07135bcc --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImpl.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import io.shinhanlife.dap.mcc.biz.pct.usecase.SystemNoticeUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SystemNoticeUseCaseImpl implements SystemNoticeUseCase { + @Override public SystemNoticeResponse getSystemNotices(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getNoticeSearch(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getRecentNotices(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getAlertList(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getNoticeDetail(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getNoticeCategory(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getIncidentList(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getChangeRequestList(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getOperationCalendar(SystemNoticeRequest request) { return mockResponse(); } + @Override public SystemNoticeResponse getServiceRequestStatus(SystemNoticeRequest request) { return mockResponse(); } + + private SystemNoticeResponse mockResponse() { + SystemNoticeResponse.Notice notice = new SystemNoticeResponse.Notice(); + notice.setTitle("정기 점검 안내"); + notice.setPriority("보통"); + notice.setPublishedDate("2026-08-13"); + SystemNoticeResponse response = new SystemNoticeResponse(); + response.setNotices(List.of(notice)); + return response; + } +} diff --git a/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/sys/DapWasSysApplication.java b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/sys/DapWasSysApplication.java new file mode 100644 index 000000000..b34cf0ee9 --- /dev/null +++ b/dap-was-sys/src/main/java/io/shinhanlife/dap/mcc/sys/DapWasSysApplication.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.sys; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; + +/** + * @package io.shinhanlife.dap.mcc.sys + * @className DapWasSysApplication + * @description AX HUB 시스템 처리 클래스 + * @author system + * @create 2026.08.13 + *
+ * ---------- 개정이력 ----------
+ * 수정일      수정자    수정내용
+ * ---------- -------- ---------------------------
+ * 2026.08.13  system    최초생성
+ *
+ * 
+ */ +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +@Import(ToolMcpServerConfiguration.class) +public class DapWasSysApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasSysApplication.class, args); + } +} diff --git a/dap-was-sys/src/main/resources/application-dev.yml b/dap-was-sys/src/main/resources/application-dev.yml new file mode 100644 index 000000000..727939dda --- /dev/null +++ b/dap-was-sys/src/main/resources/application-dev.yml @@ -0,0 +1,46 @@ +# OCI 클라우드 환경 전용 설정 +server: + port: ${PORT:8086} + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml + +axhub: + gateway: + url: https://axhubmcp.devjun.net + tool: + url: http://144.24.70.100:8086 + +eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + +shinhan: + integration: + envrTypeCd: D + eai: + url: http://10.176.32.181 + internalMci: + url: http://10.176.32.173 + bancaMci: + url: http://10.176.32.117 + externalMci: + url: http://10.176.32.176 diff --git a/dap-was-sys/src/main/resources/application-local.yml b/dap-was-sys/src/main/resources/application-local.yml new file mode 100644 index 000000000..caa93c7fd --- /dev/null +++ b/dap-was-sys/src/main/resources/application-local.yml @@ -0,0 +1,45 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} diff --git a/dap-was-sys/src/main/resources/application-prod.yml b/dap-was-sys/src/main/resources/application-prod.yml new file mode 100644 index 000000000..0351d3f86 --- /dev/null +++ b/dap-was-sys/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8086} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-sys/src/main/resources/application-test.yml b/dap-was-sys/src/main/resources/application-test.yml new file mode 100644 index 000000000..4771d148f --- /dev/null +++ b/dap-was-sys/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8086} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-sys/src/main/resources/application.yml b/dap-was-sys/src/main/resources/application.yml new file mode 100644 index 000000000..5c05991ba --- /dev/null +++ b/dap-was-sys/src/main/resources/application.yml @@ -0,0 +1,18 @@ +server: + port: 8086 +spring: + application: + name: dap-was-sys + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: was-sys + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL diff --git a/dap-was-sys/src/main/resources/logback-spring.xml b/dap-was-sys/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..5f946d79b --- /dev/null +++ b/dap-was-sys/src/main/resources/logback-spring.xml @@ -0,0 +1,24 @@ + + + + + + ${LOG_PATTERN} + + + + logs/dap-was-sys.log + + logs/dap-was-sys-%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN} + + + + + + + + diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_access_policy.json b/dap-was-sys/src/main/resources/mock-responses/iam_access_policy.json new file mode 100644 index 000000000..3e009f81d --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_access_policy.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_access_policy\",\"title\":\"접근 정책 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_access_token_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_access_token_status.json new file mode 100644 index 000000000..5753d6595 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_access_token_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_access_token_status\",\"title\":\"접근 토큰 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_list.json new file mode 100644 index 000000000..5f8bdf8ac --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_list\",\"title\":\"계정 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_lock_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_lock_status.json new file mode 100644 index 000000000..37d3d2f22 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_lock_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_lock_status\",\"title\":\"계정 잠금 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_password_expiry.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_password_expiry.json new file mode 100644 index 000000000..c62fbf514 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_password_expiry.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_password_expiry\",\"title\":\"비밀번호 만료일 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_profile.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_profile.json new file mode 100644 index 000000000..b23615419 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_profile.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_profile\",\"title\":\"계정 프로필 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_role.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_role.json new file mode 100644 index 000000000..33b954153 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_role.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_role\",\"title\":\"계정 역할 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_account_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_account_status.json new file mode 100644 index 000000000..fd1111b64 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_account_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_account_status\",\"title\":\"계정 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_approval_authority.json b/dap-was-sys/src/main/resources/mock-responses/iam_approval_authority.json new file mode 100644 index 000000000..40a65525a --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_approval_authority.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_approval_authority\",\"title\":\"결재 권한 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_authentication_method.json b/dap-was-sys/src/main/resources/mock-responses/iam_authentication_method.json new file mode 100644 index 000000000..d5776fd06 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_authentication_method.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_authentication_method\",\"title\":\"인증 수단 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_delegation_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_delegation_status.json new file mode 100644 index 000000000..9d1c4a954 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_delegation_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_delegation_status\",\"title\":\"권한 위임 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_deployment_version.json b/dap-was-sys/src/main/resources/mock-responses/iam_deployment_version.json new file mode 100644 index 000000000..34d7c0e1c --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_deployment_version.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_deployment_version\",\"title\":\"배포 버전 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_environment_info.json b/dap-was-sys/src/main/resources/mock-responses/iam_environment_info.json new file mode 100644 index 000000000..ae85ada20 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_environment_info.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_environment_info\",\"title\":\"환경 정보 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_identity_provider_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_identity_provider_list.json new file mode 100644 index 000000000..473dd8b05 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_identity_provider_list.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"iam_identity_provider_list\",\"title\":\"인증 제공자 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\",\"checkedAt\":\"2026-08-13T09:00:00+09:00\"}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_login_history.json b/dap-was-sys/src/main/resources/mock-responses/iam_login_history.json new file mode 100644 index 000000000..96c2d2220 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_login_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_login_history\",\"title\":\"로그인 이력 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_menu_access.json b/dap-was-sys/src/main/resources/mock-responses/iam_menu_access.json new file mode 100644 index 000000000..1398e4d69 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_menu_access.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_menu_access\",\"title\":\"메뉴 접근 권한 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_mfa_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_mfa_status.json new file mode 100644 index 000000000..d3d75c830 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_mfa_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_mfa_status\",\"title\":\"다중 인증 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_permission_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_permission_list.json new file mode 100644 index 000000000..56165b55c --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_permission_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_permission_list\",\"title\":\"권한 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_privilege_change_history.json b/dap-was-sys/src/main/resources/mock-responses/iam_privilege_change_history.json new file mode 100644 index 000000000..b1d6939b5 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_privilege_change_history.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"iam_privilege_change_history\",\"title\":\"권한 변경 이력 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\",\"checkedAt\":\"2026-08-13T09:00:00+09:00\"}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_resource_access.json b/dap-was-sys/src/main/resources/mock-responses/iam_resource_access.json new file mode 100644 index 000000000..87562bd9c --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_resource_access.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_resource_access\",\"title\":\"자원 접근 권한 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_role_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_role_list.json new file mode 100644 index 000000000..85337f392 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_role_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_role_list\",\"title\":\"역할 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_security_event_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_security_event_list.json new file mode 100644 index 000000000..f93f87afb --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_security_event_list.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"iam_security_event_list\",\"title\":\"보안 이벤트 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\",\"checkedAt\":\"2026-08-13T09:00:00+09:00\"}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_service_health.json b/dap-was-sys/src/main/resources/mock-responses/iam_service_health.json new file mode 100644 index 000000000..05935c3fc --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_service_health.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_service_health\",\"title\":\"서비스 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_session_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_session_list.json new file mode 100644 index 000000000..0c4f715bd --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_session_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_session_list\",\"title\":\"세션 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_session_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_session_status.json new file mode 100644 index 000000000..681530e3a --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_session_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_session_status\",\"title\":\"세션 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_system_status.json b/dap-was-sys/src/main/resources/mock-responses/iam_system_status.json new file mode 100644 index 000000000..9bca1b167 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_system_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_system_status\",\"title\":\"시스템 상태 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\",\"checkedAt\":\"2026-08-13T09:00:00+09:00\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_trusted_device_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_trusted_device_list.json new file mode 100644 index 000000000..0529dc92f --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_trusted_device_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"iam_trusted_device_list\",\"title\":\"신뢰 기기 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\"}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/iam_user_group_list.json b/dap-was-sys/src/main/resources/mock-responses/iam_user_group_list.json new file mode 100644 index 000000000..9431f4c41 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/iam_user_group_list.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"iam_user_group_list\",\"title\":\"사용자 그룹 목록 조회\",\"systemName\":\"AX Hub IAM\",\"environment\":\"개발\",\"status\":\"정상\",\"checkedAt\":\"2026-08-13T09:00:00+09:00\"}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_alert_list.json b/dap-was-sys/src/main/resources/mock-responses/pct_alert_list.json new file mode 100644 index 000000000..1d73af2ed --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_alert_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_alert_list\",\"title\":\"알림 목록 조회\",\"notices\":[{\"title\":\"알림 목록 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_change_request_list.json b/dap-was-sys/src/main/resources/mock-responses/pct_change_request_list.json new file mode 100644 index 000000000..d57c72210 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_change_request_list.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_change_request_list\",\"title\":\"변경 요청 목록 조회\",\"notices\":[{\"title\":\"변경 요청 목록 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_incident_list.json b/dap-was-sys/src/main/resources/mock-responses/pct_incident_list.json new file mode 100644 index 000000000..0da2be9b6 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_incident_list.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_incident_list\",\"title\":\"장애 목록 조회\",\"notices\":[{\"title\":\"장애 목록 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_contact.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_contact.json new file mode 100644 index 000000000..22b507647 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_contact.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_contact\",\"title\":\"점검 담당자 조회\",\"notices\":[{\"title\":\"점검 담당자 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_history.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_history.json new file mode 100644 index 000000000..faefc07f8 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_history\",\"title\":\"점검 이력 조회\",\"notices\":[{\"title\":\"점검 이력 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_result.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_result.json new file mode 100644 index 000000000..f46d7e986 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_result.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_result\",\"title\":\"점검 결과 조회\",\"notices\":[{\"title\":\"점검 결과 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_schedule.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_schedule.json new file mode 100644 index 000000000..195634dd9 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_schedule.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_schedule\",\"title\":\"점검 일정 조회\",\"notices\":[{\"title\":\"점검 일정 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_scope.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_scope.json new file mode 100644 index 000000000..789c01084 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_scope.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_scope\",\"title\":\"점검 범위 조회\",\"notices\":[{\"title\":\"점검 범위 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_status.json b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_status.json new file mode 100644 index 000000000..fadcccb99 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_maintenance_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_maintenance_status\",\"title\":\"점검 진행 상태 조회\",\"notices\":[{\"title\":\"점검 진행 상태 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_notice_category.json b/dap-was-sys/src/main/resources/mock-responses/pct_notice_category.json new file mode 100644 index 000000000..db153e85e --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_notice_category.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_notice_category\",\"title\":\"공지 분류 조회\",\"notices\":[{\"title\":\"공지 분류 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_notice_detail.json b/dap-was-sys/src/main/resources/mock-responses/pct_notice_detail.json new file mode 100644 index 000000000..ed9ee7ffa --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_notice_detail.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_notice_detail\",\"title\":\"공지 상세 조회\",\"notices\":[{\"title\":\"공지 상세 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_notice_list.json b/dap-was-sys/src/main/resources/mock-responses/pct_notice_list.json new file mode 100644 index 000000000..9b40b04dd --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_notice_list.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_notice_list\",\"title\":\"시스템 공지 조회\",\"notices\":[{\"title\":\"시스템 공지 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_notice_recent.json b/dap-was-sys/src/main/resources/mock-responses/pct_notice_recent.json new file mode 100644 index 000000000..da1e0cfba --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_notice_recent.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_notice_recent\",\"title\":\"최신 공지 조회\",\"notices\":[{\"title\":\"최신 시스템 공지\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_notice_search.json b/dap-was-sys/src/main/resources/mock-responses/pct_notice_search.json new file mode 100644 index 000000000..8cc156539 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_notice_search.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_notice_search\",\"title\":\"공지 검색 조회\",\"notices\":[{\"title\":\"점검 공지\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_operation_calendar.json b/dap-was-sys/src/main/resources/mock-responses/pct_operation_calendar.json new file mode 100644 index 000000000..6788550a3 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_operation_calendar.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_operation_calendar\",\"title\":\"운영 일정 조회\",\"notices\":[{\"title\":\"운영 일정 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_history.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_history.json new file mode 100644 index 000000000..3f3f312d9 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_history.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_history\",\"title\":\"배포 이력 조회\",\"notices\":[{\"title\":\"배포 이력 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_notice.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_notice.json new file mode 100644 index 000000000..6d359ae7d --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_notice.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_notice\",\"title\":\"배포 공지 조회\",\"notices\":[{\"title\":\"배포 공지 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_schedule.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_schedule.json new file mode 100644 index 000000000..80c28c75c --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_schedule.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_schedule\",\"title\":\"배포 일정 조회\",\"notices\":[{\"title\":\"배포 일정 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_scope.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_scope.json new file mode 100644 index 000000000..d66429a1e --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_scope.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_scope\",\"title\":\"배포 범위 조회\",\"notices\":[{\"title\":\"배포 범위 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_status.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_status.json new file mode 100644 index 000000000..79ec95a81 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_status.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_status\",\"title\":\"배포 상태 조회\",\"notices\":[{\"title\":\"배포 상태 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_release_version.json b/dap-was-sys/src/main/resources/mock-responses/pct_release_version.json new file mode 100644 index 000000000..45d30aef2 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_release_version.json @@ -0,0 +1,4 @@ +{ + "resultCode": "SUCCESS", + "data": "{\"toolName\":\"pct_release_version\",\"title\":\"배포 버전 조회\",\"notices\":[{\"title\":\"배포 버전 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}" +} diff --git a/dap-was-sys/src/main/resources/mock-responses/pct_service_request_status.json b/dap-was-sys/src/main/resources/mock-responses/pct_service_request_status.json new file mode 100644 index 000000000..fb7252711 --- /dev/null +++ b/dap-was-sys/src/main/resources/mock-responses/pct_service_request_status.json @@ -0,0 +1 @@ +{"resultCode":"SUCCESS","data":"{\"toolName\":\"pct_service_request_status\",\"title\":\"서비스 요청 상태 조회\",\"notices\":[{\"title\":\"서비스 요청 상태 조회\",\"priority\":\"보통\",\"publishedDate\":\"2026-08-13\"}]}"} diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_policy.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_policy.yml new file mode 100644 index 000000000..5d37ca1f5 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_policy.yml @@ -0,0 +1,22 @@ +name: iam_access_policy +display_name: 접근 정책 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 접근 정책 조회 정보를 조회합니다. + when_to_use: 접근 정책 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 접근 정책 조회 정보를 조회합니다. +example_queries: ["접근 정책 조회 정보를 보여줘", "접근 정책 조회을 확인해줘", "현재 접근 정책 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_token_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_token_status.yml new file mode 100644 index 000000000..0166f1f60 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_access_token_status.yml @@ -0,0 +1,22 @@ +name: iam_access_token_status +display_name: 접근 토큰 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 접근 토큰 상태 조회 정보를 조회합니다. + when_to_use: 접근 토큰 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 접근 토큰 상태 조회 정보를 조회합니다. +example_queries: ["접근 토큰 상태 조회 정보를 보여줘", "접근 토큰 상태 조회을 확인해줘", "현재 접근 토큰 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_list.yml new file mode 100644 index 000000000..4859faed9 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_list.yml @@ -0,0 +1,22 @@ +name: iam_account_list +display_name: 계정 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 계정 목록 조회 정보를 조회합니다. + when_to_use: 계정 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계정 목록 조회 정보를 조회합니다. +example_queries: ["계정 목록 조회 정보를 보여줘", "계정 목록 조회을 확인해줘", "현재 계정 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_lock_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_lock_status.yml new file mode 100644 index 000000000..813264b38 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_lock_status.yml @@ -0,0 +1,22 @@ +name: iam_account_lock_status +display_name: 계정 잠금 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 계정 잠금 상태 조회 정보를 조회합니다. + when_to_use: 계정 잠금 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계정 잠금 상태 조회 정보를 조회합니다. +example_queries: ["계정 잠금 상태 조회 정보를 보여줘", "계정 잠금 상태 조회을 확인해줘", "현재 계정 잠금 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_password_expiry.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_password_expiry.yml new file mode 100644 index 000000000..35cef0472 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_password_expiry.yml @@ -0,0 +1,22 @@ +name: iam_account_password_expiry +display_name: 비밀번호 만료일 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 비밀번호 만료일 조회 정보를 조회합니다. + when_to_use: 비밀번호 만료일 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 비밀번호 만료일 조회 정보를 조회합니다. +example_queries: ["비밀번호 만료일 조회 정보를 보여줘", "비밀번호 만료일 조회을 확인해줘", "현재 비밀번호 만료일 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_profile.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_profile.yml new file mode 100644 index 000000000..0f29b6234 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_profile.yml @@ -0,0 +1,22 @@ +name: iam_account_profile +display_name: 계정 프로필 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 계정 프로필 조회 정보를 조회합니다. + when_to_use: 계정 프로필 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계정 프로필 조회 정보를 조회합니다. +example_queries: ["계정 프로필 조회 정보를 보여줘", "계정 프로필 조회을 확인해줘", "현재 계정 프로필 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_role.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_role.yml new file mode 100644 index 000000000..7de105eb9 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_role.yml @@ -0,0 +1,22 @@ +name: iam_account_role +display_name: 계정 역할 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 계정 역할 조회 정보를 조회합니다. + when_to_use: 계정 역할 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계정 역할 조회 정보를 조회합니다. +example_queries: ["계정 역할 조회 정보를 보여줘", "계정 역할 조회을 확인해줘", "현재 계정 역할 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_status.yml new file mode 100644 index 000000000..d91b7a2ad --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_account_status.yml @@ -0,0 +1,22 @@ +name: iam_account_status +display_name: 계정 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 계정 상태 조회 정보를 조회합니다. + when_to_use: 계정 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 계정 상태 조회 정보를 조회합니다. +example_queries: ["계정 상태 조회 정보를 보여줘", "계정 상태 조회을 확인해줘", "현재 계정 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_approval_authority.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_approval_authority.yml new file mode 100644 index 000000000..2ff2c3569 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_approval_authority.yml @@ -0,0 +1,22 @@ +name: iam_approval_authority +display_name: 결재 권한 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 결재 권한 조회 정보를 조회합니다. + when_to_use: 결재 권한 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 결재 권한 조회 정보를 조회합니다. +example_queries: ["결재 권한 조회 정보를 보여줘", "결재 권한 조회을 확인해줘", "현재 결재 권한 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_authentication_method.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_authentication_method.yml new file mode 100644 index 000000000..c2e5f85df --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_authentication_method.yml @@ -0,0 +1,22 @@ +name: iam_authentication_method +display_name: 인증 수단 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 인증 수단 조회 정보를 조회합니다. + when_to_use: 인증 수단 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 인증 수단 조회 정보를 조회합니다. +example_queries: ["인증 수단 조회 정보를 보여줘", "인증 수단 조회을 확인해줘", "현재 인증 수단 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_delegation_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_delegation_status.yml new file mode 100644 index 000000000..8716d9d48 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_delegation_status.yml @@ -0,0 +1,22 @@ +name: iam_delegation_status +display_name: 권한 위임 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 권한 위임 상태 조회 정보를 조회합니다. + when_to_use: 권한 위임 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 권한 위임 상태 조회 정보를 조회합니다. +example_queries: ["권한 위임 상태 조회 정보를 보여줘", "권한 위임 상태 조회을 확인해줘", "현재 권한 위임 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_deployment_version.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_deployment_version.yml new file mode 100644 index 000000000..0e43ee83a --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_deployment_version.yml @@ -0,0 +1,14 @@ +name: iam_deployment_version +display_name: 배포 버전 조회 +version: 1.0.0 +category_key: iam +description: {function: 모의 배포 버전을 조회합니다., when_to_use: 배포 버전 확인이 필요할 때 사용합니다., when_not_to_use: 배포 실행에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 배포 버전을 조회합니다. +example_queries: ["배포 버전을 보여줘", "현재 버전을 확인해줘", "개발 환경 버전을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"}}, additionalProperties: false} +tags: [iam, 배포, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_environment_info.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_environment_info.yml new file mode 100644 index 000000000..b963de672 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_environment_info.yml @@ -0,0 +1,14 @@ +name: iam_environment_info +display_name: 환경 정보 조회 +version: 1.0.0 +category_key: iam +description: {function: 모의 환경 정보를 조회합니다., when_to_use: 환경 정보 확인이 필요할 때 사용합니다., when_not_to_use: 환경 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 환경 정보를 조회합니다. +example_queries: ["환경 정보를 보여줘", "개발 환경을 확인해줘", "현재 환경 정보를 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"}}, additionalProperties: false} +tags: [iam, 환경, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_identity_provider_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_identity_provider_list.yml new file mode 100644 index 000000000..d76f13662 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_identity_provider_list.yml @@ -0,0 +1,22 @@ +name: iam_identity_provider_list +display_name: 인증 제공자 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 인증 제공자 목록 조회 정보를 조회합니다. + when_to_use: 인증 제공자 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 인증 제공자 목록 조회 정보를 조회합니다. +example_queries: ["인증 제공자 목록 조회 정보를 보여줘", "인증 제공자 목록 조회을 확인해줘", "현재 인증 제공자 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_login_history.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_login_history.yml new file mode 100644 index 000000000..95a0d6de3 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_login_history.yml @@ -0,0 +1,22 @@ +name: iam_login_history +display_name: 로그인 이력 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 로그인 이력 조회 정보를 조회합니다. + when_to_use: 로그인 이력 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 로그인 이력 조회 정보를 조회합니다. +example_queries: ["로그인 이력 조회 정보를 보여줘", "로그인 이력 조회을 확인해줘", "현재 로그인 이력 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_menu_access.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_menu_access.yml new file mode 100644 index 000000000..711cbeb89 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_menu_access.yml @@ -0,0 +1,22 @@ +name: iam_menu_access +display_name: 메뉴 접근 권한 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 메뉴 접근 권한 조회 정보를 조회합니다. + when_to_use: 메뉴 접근 권한 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 메뉴 접근 권한 조회 정보를 조회합니다. +example_queries: ["메뉴 접근 권한 조회 정보를 보여줘", "메뉴 접근 권한 조회을 확인해줘", "현재 메뉴 접근 권한 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_mfa_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_mfa_status.yml new file mode 100644 index 000000000..bf08a2a6e --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_mfa_status.yml @@ -0,0 +1,22 @@ +name: iam_mfa_status +display_name: 다중 인증 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 다중 인증 상태 조회 정보를 조회합니다. + when_to_use: 다중 인증 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 다중 인증 상태 조회 정보를 조회합니다. +example_queries: ["다중 인증 상태 조회 정보를 보여줘", "다중 인증 상태 조회을 확인해줘", "현재 다중 인증 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_permission_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_permission_list.yml new file mode 100644 index 000000000..e7f4603a9 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_permission_list.yml @@ -0,0 +1,22 @@ +name: iam_permission_list +display_name: 권한 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 권한 목록 조회 정보를 조회합니다. + when_to_use: 권한 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 권한 목록 조회 정보를 조회합니다. +example_queries: ["권한 목록 조회 정보를 보여줘", "권한 목록 조회을 확인해줘", "현재 권한 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_privilege_change_history.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_privilege_change_history.yml new file mode 100644 index 000000000..e59c5b5b1 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_privilege_change_history.yml @@ -0,0 +1,22 @@ +name: iam_privilege_change_history +display_name: 권한 변경 이력 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 권한 변경 이력 조회 정보를 조회합니다. + when_to_use: 권한 변경 이력 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 권한 변경 이력 조회 정보를 조회합니다. +example_queries: ["권한 변경 이력 조회 정보를 보여줘", "권한 변경 이력 조회을 확인해줘", "현재 권한 변경 이력 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_resource_access.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_resource_access.yml new file mode 100644 index 000000000..33f9b7eb2 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_resource_access.yml @@ -0,0 +1,22 @@ +name: iam_resource_access +display_name: 자원 접근 권한 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 자원 접근 권한 조회 정보를 조회합니다. + when_to_use: 자원 접근 권한 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 자원 접근 권한 조회 정보를 조회합니다. +example_queries: ["자원 접근 권한 조회 정보를 보여줘", "자원 접근 권한 조회을 확인해줘", "현재 자원 접근 권한 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_role_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_role_list.yml new file mode 100644 index 000000000..18875ede8 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_role_list.yml @@ -0,0 +1,22 @@ +name: iam_role_list +display_name: 역할 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 역할 목록 조회 정보를 조회합니다. + when_to_use: 역할 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 역할 목록 조회 정보를 조회합니다. +example_queries: ["역할 목록 조회 정보를 보여줘", "역할 목록 조회을 확인해줘", "현재 역할 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_security_event_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_security_event_list.yml new file mode 100644 index 000000000..e10755160 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_security_event_list.yml @@ -0,0 +1,22 @@ +name: iam_security_event_list +display_name: 보안 이벤트 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 보안 이벤트 목록 조회 정보를 조회합니다. + when_to_use: 보안 이벤트 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 보안 이벤트 목록 조회 정보를 조회합니다. +example_queries: ["보안 이벤트 목록 조회 정보를 보여줘", "보안 이벤트 목록 조회을 확인해줘", "현재 보안 이벤트 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_service_health.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_service_health.yml new file mode 100644 index 000000000..22a7ea43e --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_service_health.yml @@ -0,0 +1,14 @@ +name: iam_service_health +display_name: 서비스 상태 조회 +version: 1.0.0 +category_key: iam +description: {function: 모의 서비스 상태를 조회합니다., when_to_use: 서비스 상태 확인이 필요할 때 사용합니다., when_not_to_use: 서비스 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 서비스 상태를 조회합니다. +example_queries: ["서비스 상태를 보여줘", "서비스가 정상인가요?", "개발 서비스 상태를 확인해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"}}, additionalProperties: false} +tags: [iam, 서비스, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_list.yml new file mode 100644 index 000000000..6403b607f --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_list.yml @@ -0,0 +1,22 @@ +name: iam_session_list +display_name: 세션 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 세션 목록 조회 정보를 조회합니다. + when_to_use: 세션 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 세션 목록 조회 정보를 조회합니다. +example_queries: ["세션 목록 조회 정보를 보여줘", "세션 목록 조회을 확인해줘", "현재 세션 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_status.yml new file mode 100644 index 000000000..5b86ed6ba --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_session_status.yml @@ -0,0 +1,22 @@ +name: iam_session_status +display_name: 세션 상태 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 세션 상태 조회 정보를 조회합니다. + when_to_use: 세션 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 세션 상태 조회 정보를 조회합니다. +example_queries: ["세션 상태 조회 정보를 보여줘", "세션 상태 조회을 확인해줘", "현재 세션 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_system_status.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_system_status.yml new file mode 100644 index 000000000..eab6385af --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_system_status.yml @@ -0,0 +1,14 @@ +name: iam_system_status +display_name: 시스템 상태 조회 +version: 1.0.0 +category_key: iam +description: {function: 모의 시스템 상태를 조회합니다., when_to_use: 시스템 상태 확인이 필요할 때 사용합니다., when_not_to_use: 시스템 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 시스템 상태를 조회합니다. +example_queries: ["시스템 상태를 보여줘", "개발 환경 상태를 확인해줘", "현재 서비스가 정상인가요?"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"}}, additionalProperties: false} +tags: [iam, 상태, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_trusted_device_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_trusted_device_list.yml new file mode 100644 index 000000000..dd1892219 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_trusted_device_list.yml @@ -0,0 +1,22 @@ +name: iam_trusted_device_list +display_name: 신뢰 기기 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 신뢰 기기 목록 조회 정보를 조회합니다. + when_to_use: 신뢰 기기 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 신뢰 기기 목록 조회 정보를 조회합니다. +example_queries: ["신뢰 기기 목록 조회 정보를 보여줘", "신뢰 기기 목록 조회을 확인해줘", "현재 신뢰 기기 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회할 환경명, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/iam/iam_user_group_list.yml b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_user_group_list.yml new file mode 100644 index 000000000..0af36309f --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/iam/iam_user_group_list.yml @@ -0,0 +1,22 @@ +name: iam_user_group_list +display_name: 사용자 그룹 목록 조회 +version: 1.0.0 +category_key: iam +description: + function: 모의 사용자 그룹 목록 조회 정보를 조회합니다. + when_to_use: 사용자 그룹 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 사용자 그룹 목록 조회 정보를 조회합니다. +example_queries: ["사용자 그룹 목록 조회 정보를 보여줘", "사용자 그룹 목록 조회을 확인해줘", "현재 사용자 그룹 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + environment: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9_-]{2,20}$"} + additionalProperties: false +tags: [iam, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_alert_list.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_alert_list.yml new file mode 100644 index 000000000..2ffbe2baf --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_alert_list.yml @@ -0,0 +1,14 @@ +name: pct_alert_list +display_name: 알림 목록 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 알림 목록을 조회합니다., when_to_use: 알림 확인이 필요할 때 사용합니다., when_not_to_use: 알림 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 알림 목록을 조회합니다. +example_queries: ["알림 목록을 보여줘", "현재 알림을 알려줘", "점검 알림이 있나요?"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 알림, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_change_request_list.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_change_request_list.yml new file mode 100644 index 000000000..9626fc62f --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_change_request_list.yml @@ -0,0 +1,22 @@ +name: pct_change_request_list +display_name: 변경 요청 목록 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 변경 요청 목록 조회 정보를 조회합니다. + when_to_use: 변경 요청 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 변경 요청 목록 조회 정보를 조회합니다. +example_queries: ["변경 요청 목록 조회 정보를 보여줘", "변경 요청 목록 조회을 확인해줘", "현재 변경 요청 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_incident_list.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_incident_list.yml new file mode 100644 index 000000000..96d09f670 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_incident_list.yml @@ -0,0 +1,22 @@ +name: pct_incident_list +display_name: 장애 목록 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 장애 목록 조회 정보를 조회합니다. + when_to_use: 장애 목록 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 장애 목록 조회 정보를 조회합니다. +example_queries: ["장애 목록 조회 정보를 보여줘", "장애 목록 조회을 확인해줘", "현재 장애 목록 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_contact.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_contact.yml new file mode 100644 index 000000000..b2b2e729d --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_contact.yml @@ -0,0 +1,22 @@ +name: pct_maintenance_contact +display_name: 점검 담당자 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 점검 담당자 조회 정보를 조회합니다. + when_to_use: 점검 담당자 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 점검 담당자 조회 정보를 조회합니다. +example_queries: ["점검 담당자 조회 정보를 보여줘", "점검 담당자 조회을 확인해줘", "현재 점검 담당자 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_history.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_history.yml new file mode 100644 index 000000000..44665a6a7 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_history.yml @@ -0,0 +1,22 @@ +name: pct_maintenance_history +display_name: 점검 이력 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 점검 이력 조회 정보를 조회합니다. + when_to_use: 점검 이력 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 점검 이력 조회 정보를 조회합니다. +example_queries: ["점검 이력 조회 정보를 보여줘", "점검 이력 조회을 확인해줘", "현재 점검 이력 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_result.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_result.yml new file mode 100644 index 000000000..cf01265ce --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_result.yml @@ -0,0 +1,22 @@ +name: pct_maintenance_result +display_name: 점검 결과 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 점검 결과 조회 정보를 조회합니다. + when_to_use: 점검 결과 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 점검 결과 조회 정보를 조회합니다. +example_queries: ["점검 결과 조회 정보를 보여줘", "점검 결과 조회을 확인해줘", "현재 점검 결과 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_schedule.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_schedule.yml new file mode 100644 index 000000000..013864644 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_schedule.yml @@ -0,0 +1,14 @@ +name: pct_maintenance_schedule +display_name: 점검 일정 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 점검 일정을 조회합니다., when_to_use: 점검 일정 확인이 필요할 때 사용합니다., when_not_to_use: 점검 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 점검 일정을 조회합니다. +example_queries: ["점검 일정을 보여줘", "정기 점검이 언제야?", "이번 주 점검을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 점검, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_scope.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_scope.yml new file mode 100644 index 000000000..04c75c591 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_scope.yml @@ -0,0 +1,22 @@ +name: pct_maintenance_scope +display_name: 점검 범위 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 점검 범위 조회 정보를 조회합니다. + when_to_use: 점검 범위 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 점검 범위 조회 정보를 조회합니다. +example_queries: ["점검 범위 조회 정보를 보여줘", "점검 범위 조회을 확인해줘", "현재 점검 범위 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_status.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_status.yml new file mode 100644 index 000000000..f6ef5bd65 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_maintenance_status.yml @@ -0,0 +1,22 @@ +name: pct_maintenance_status +display_name: 점검 진행 상태 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 점검 진행 상태 조회 정보를 조회합니다. + when_to_use: 점검 진행 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 점검 진행 상태 조회 정보를 조회합니다. +example_queries: ["점검 진행 상태 조회 정보를 보여줘", "점검 진행 상태 조회을 확인해줘", "현재 점검 진행 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_category.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_category.yml new file mode 100644 index 000000000..6e99fde20 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_category.yml @@ -0,0 +1,22 @@ +name: pct_notice_category +display_name: 공지 분류 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 공지 분류 조회 정보를 조회합니다. + when_to_use: 공지 분류 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 공지 분류 조회 정보를 조회합니다. +example_queries: ["공지 분류 조회 정보를 보여줘", "공지 분류 조회을 확인해줘", "현재 공지 분류 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_detail.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_detail.yml new file mode 100644 index 000000000..c1b29d6aa --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_detail.yml @@ -0,0 +1,22 @@ +name: pct_notice_detail +display_name: 공지 상세 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 공지 상세 조회 정보를 조회합니다. + when_to_use: 공지 상세 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 공지 상세 조회 정보를 조회합니다. +example_queries: ["공지 상세 조회 정보를 보여줘", "공지 상세 조회을 확인해줘", "현재 공지 상세 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_list.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_list.yml new file mode 100644 index 000000000..29e51cb32 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_list.yml @@ -0,0 +1,14 @@ +name: pct_notice_list +display_name: 시스템 공지 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 시스템 공지를 조회합니다., when_to_use: 공지 확인이 필요할 때 사용합니다., when_not_to_use: 공지 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 시스템 공지를 조회합니다. +example_queries: ["시스템 공지를 보여줘", "현재 공지를 알려줘", "개발 공지가 있나요?"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 공지, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_recent.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_recent.yml new file mode 100644 index 000000000..043be1d41 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_recent.yml @@ -0,0 +1,14 @@ +name: pct_notice_recent +display_name: 최신 공지 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 최신 공지를 조회합니다., when_to_use: 최신 공지 확인이 필요할 때 사용합니다., when_not_to_use: 공지 변경에는 사용하지 않습니다., io_limits: 모의 조회 데이터만 반환합니다.} +display_description: 최신 공지를 조회합니다. +example_queries: ["최신 공지를 보여줘", "오늘 공지가 있나요?", "최근 시스템 공지를 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 공지, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_search.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_search.yml new file mode 100644 index 000000000..eacdb7041 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_notice_search.yml @@ -0,0 +1,14 @@ +name: pct_notice_search +display_name: 공지 검색 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 공지 검색 결과를 조회합니다., when_to_use: 공지를 검색할 때 사용합니다., when_not_to_use: 공지 변경에는 사용하지 않습니다., io_limits: 모의 조회 데이터만 반환합니다.} +display_description: 공지 검색 결과를 조회합니다. +example_queries: ["점검 공지를 검색해줘", "배포 공지를 찾아줘", "공지 내용을 조회해줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 공지, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_operation_calendar.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_operation_calendar.yml new file mode 100644 index 000000000..306b9753a --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_operation_calendar.yml @@ -0,0 +1,22 @@ +name: pct_operation_calendar +display_name: 운영 일정 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 운영 일정 조회 정보를 조회합니다. + when_to_use: 운영 일정 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 운영 일정 조회 정보를 조회합니다. +example_queries: ["운영 일정 조회 정보를 보여줘", "운영 일정 조회을 확인해줘", "현재 운영 일정 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_history.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_history.yml new file mode 100644 index 000000000..29d44903e --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_history.yml @@ -0,0 +1,22 @@ +name: pct_release_history +display_name: 배포 이력 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 배포 이력 조회 정보를 조회합니다. + when_to_use: 배포 이력 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 배포 이력 조회 정보를 조회합니다. +example_queries: ["배포 이력 조회 정보를 보여줘", "배포 이력 조회을 확인해줘", "현재 배포 이력 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_notice.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_notice.yml new file mode 100644 index 000000000..f37a2619a --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_notice.yml @@ -0,0 +1,14 @@ +name: pct_release_notice +display_name: 배포 공지 조회 +version: 1.0.0 +category_key: pct +description: {function: 모의 배포 공지를 조회합니다., when_to_use: 배포 공지 확인이 필요할 때 사용합니다., when_not_to_use: 배포 변경에는 사용하지 않습니다., io_limits: 모의 데이터만 반환합니다.} +display_description: 배포 공지를 조회합니다. +example_queries: ["배포 공지를 보여줘", "최근 배포 내용을 알려줘", "개발 배포 공지가 있나요?"] +read_only: true +destructive: false +idempotent: true +parameters_schema: {type: object, properties: {category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"}}, additionalProperties: false} +tags: [pct, 배포, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_schedule.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_schedule.yml new file mode 100644 index 000000000..feeba356f --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_schedule.yml @@ -0,0 +1,22 @@ +name: pct_release_schedule +display_name: 배포 일정 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 배포 일정 조회 정보를 조회합니다. + when_to_use: 배포 일정 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 배포 일정 조회 정보를 조회합니다. +example_queries: ["배포 일정 조회 정보를 보여줘", "배포 일정 조회을 확인해줘", "현재 배포 일정 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_scope.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_scope.yml new file mode 100644 index 000000000..64a18c255 --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_scope.yml @@ -0,0 +1,22 @@ +name: pct_release_scope +display_name: 배포 범위 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 배포 범위 조회 정보를 조회합니다. + when_to_use: 배포 범위 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 배포 범위 조회 정보를 조회합니다. +example_queries: ["배포 범위 조회 정보를 보여줘", "배포 범위 조회을 확인해줘", "현재 배포 범위 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_status.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_status.yml new file mode 100644 index 000000000..bd0cc2fff --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_status.yml @@ -0,0 +1,22 @@ +name: pct_release_status +display_name: 배포 상태 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 배포 상태 조회 정보를 조회합니다. + when_to_use: 배포 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 배포 상태 조회 정보를 조회합니다. +example_queries: ["배포 상태 조회 정보를 보여줘", "배포 상태 조회을 확인해줘", "현재 배포 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_version.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_version.yml new file mode 100644 index 000000000..6fda7c7bf --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_release_version.yml @@ -0,0 +1,22 @@ +name: pct_release_version +display_name: 배포 버전 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 배포 버전 조회 정보를 조회합니다. + when_to_use: 배포 버전 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 배포 버전 조회 정보를 조회합니다. +example_queries: ["배포 버전 조회 정보를 보여줘", "배포 버전 조회을 확인해줘", "현재 배포 버전 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회할 공지 분류, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/main/resources/tool-definitions/pct/pct_service_request_status.yml b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_service_request_status.yml new file mode 100644 index 000000000..86e37b0ae --- /dev/null +++ b/dap-was-sys/src/main/resources/tool-definitions/pct/pct_service_request_status.yml @@ -0,0 +1,22 @@ +name: pct_service_request_status +display_name: 서비스 요청 상태 조회 +version: 1.0.0 +category_key: pct +description: + function: 모의 서비스 요청 상태 조회 정보를 조회합니다. + when_to_use: 서비스 요청 상태 조회 확인이 필요할 때 사용합니다. + when_not_to_use: 정보 변경이나 실행 작업에는 사용하지 않습니다. + io_limits: 모의 조회 데이터만 반환하며 외부 시스템을 변경하지 않습니다. +display_description: 서비스 요청 상태 조회 정보를 조회합니다. +example_queries: ["서비스 요청 상태 조회 정보를 보여줘", "서비스 요청 상태 조회을 확인해줘", "현재 서비스 요청 상태 조회을 알려줘"] +read_only: true +destructive: false +idempotent: true +parameters_schema: + type: object + properties: + category: {type: string, description: 조회 조건, pattern: "^[a-zA-Z0-9가-힣_\\s-]{2,30}$"} + additionalProperties: false +tags: [pct, 조회, mock] +required_env_keys: [] +owner_org: MCP_TOOL diff --git a/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImplTest.java b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImplTest.java new file mode 100644 index 000000000..ad881d1fa --- /dev/null +++ b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/iam/usecase/impl/SystemStatusUseCaseImplTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.biz.iam.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusRequest; +import io.shinhanlife.dap.mcc.biz.iam.dto.SystemStatusResponse; +import org.junit.jupiter.api.Test; + +class SystemStatusUseCaseImplTest { + + @Test + void returnsHealthyDefaultDevelopmentStatus() { + SystemStatusResponse response = new SystemStatusUseCaseImpl() + .getSystemStatus(new SystemStatusRequest()); + + assertEquals("AX Hub 시스템", response.getSystemName()); + assertEquals("개발", response.getEnvironment()); + assertEquals("정상", response.getStatus()); + assertFalse(response.getCheckedAt().isEmpty()); + } +} diff --git a/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImplTest.java b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImplTest.java new file mode 100644 index 000000000..2ffa114fa --- /dev/null +++ b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/biz/pct/usecase/impl/SystemNoticeUseCaseImplTest.java @@ -0,0 +1,21 @@ +package io.shinhanlife.dap.mcc.biz.pct.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeRequest; +import io.shinhanlife.dap.mcc.biz.pct.dto.SystemNoticeResponse; +import org.junit.jupiter.api.Test; + +class SystemNoticeUseCaseImplTest { + + @Test + void returnsSyntheticNotices() { + SystemNoticeResponse response = new SystemNoticeUseCaseImpl() + .getSystemNotices(new SystemNoticeRequest()); + + assertFalse(response.getNotices().isEmpty()); + assertEquals("정기 점검 안내", response.getNotices().get(0).getTitle()); + assertEquals("보통", response.getNotices().get(0).getPriority()); + } +} diff --git a/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/sys/SystemMockToolResourcesTest.java b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/sys/SystemMockToolResourcesTest.java new file mode 100644 index 000000000..7674fb028 --- /dev/null +++ b/dap-was-sys/src/test/java/io/shinhanlife/dap/mcc/sys/SystemMockToolResourcesTest.java @@ -0,0 +1,82 @@ +package io.shinhanlife.dap.mcc.sys; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.shinhanlife.dap.lib.metadata.ToolDefinition; +import io.shinhanlife.dap.lib.metadata.ToolDefinitionRepository; +import java.io.InputStream; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.DefaultResourceLoader; + +class SystemMockToolResourcesTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void systemStatusDefinitionIsReadOnlyAndHasValidMockResponse() throws Exception { + ToolDefinition definition = definition("iam_system_status"); + JsonNode response = mockResponse("iam_system_status"); + + assertTrue(definition.readOnly()); + assertFalse(definition.destructive()); + assertTrue(definition.idempotent()); + assertTrue(properties(definition).containsKey("environment")); + assertEquals("SUCCESS", response.path("resultCode").asText()); + JsonNode data = objectMapper.readTree(response.path("data").asText()); + assertTrue(data.hasNonNull("systemName")); + assertTrue(data.hasNonNull("environment")); + assertTrue(data.hasNonNull("status")); + assertTrue(data.hasNonNull("checkedAt")); + } + + @Test + void noticeListDefinitionIsReadOnlyAndHasValidMockResponse() throws Exception { + ToolDefinition definition = definition("pct_notice_list"); + JsonNode response = mockResponse("pct_notice_list"); + + assertTrue(definition.readOnly()); + assertFalse(definition.destructive()); + assertTrue(definition.idempotent()); + assertTrue(properties(definition).containsKey("category")); + assertEquals("SUCCESS", response.path("resultCode").asText()); + JsonNode notices = objectMapper.readTree(response.path("data").asText()).path("notices"); + assertTrue(notices.isArray()); + assertFalse(notices.isEmpty()); + assertTrue(notices.get(0).hasNonNull("title")); + assertTrue(notices.get(0).hasNonNull("priority")); + assertTrue(notices.get(0).hasNonNull("publishedDate")); + } + + @Test + void loadsAllFortyTwoIamAndPctToolDefinitions() { + ToolDefinitionRepository repository = new ToolDefinitionRepository(new ObjectMapper(new YAMLFactory()), + new DefaultResourceLoader(), "classpath*:tool-definitions/**/*.yml"); + + assertEquals(50, repository.findAll().size()); + assertTrue(repository.findAll().values().stream().allMatch(definition -> + "iam".equals(definition.categoryKey()) || "pct".equals(definition.categoryKey()))); + } + + private ToolDefinition definition(String toolName) { + return new ToolDefinitionRepository(new ObjectMapper(new YAMLFactory()), new DefaultResourceLoader(), + "classpath*:tool-definitions/**/*.yml") + .findByName(toolName) + .orElseThrow(); + } + + private JsonNode mockResponse(String toolName) throws Exception { + try (InputStream input = getClass().getResourceAsStream("/mock-responses/" + toolName + ".json")) { + return objectMapper.readTree(input); + } + } + + @SuppressWarnings("unchecked") + private java.util.Map properties(ToolDefinition definition) { + return (java.util.Map) definition.parametersSchema().get("properties"); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..27de5d5e9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,76 @@ +name: ax_hub_mcp_tool + +services: + + was-sal: + build: + context: . + dockerfile: dap-was-sal/Dockerfile + ports: + - "8282:8082" + environment: + - TZ=Asia/Seoul + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_SOURCE_DIR=/src + - AXHUB_TOOL_URL=http://was-sal:8082 + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + - SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local} + + was-cus: + build: + context: . + dockerfile: dap-was-cus/Dockerfile + ports: + - "8284:8084" + environment: + - TZ=Asia/Seoul + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_TOOL_URL=http://was-cus:8084 + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + - SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local} + + + was-pro: + build: + context: . + dockerfile: dap-was-pro/Dockerfile + ports: + - "8285:8085" + environment: + - TZ=Asia/Seoul + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_TOOL_URL=http://was-pro:8085 + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + + was-sys: + build: + context: . + dockerfile: dap-was-sys/Dockerfile + ports: + - "8286:8086" + environment: + - TZ=Asia/Seoul + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_TOOL_URL=http://was-sys:8086 + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + diff --git a/docs/superpowers/plans/2026-08-06-mcp-sdk-validator-updater.md b/docs/superpowers/plans/2026-08-06-mcp-sdk-validator-updater.md new file mode 100644 index 000000000..30e9988a0 --- /dev/null +++ b/docs/superpowers/plans/2026-08-06-mcp-sdk-validator-updater.md @@ -0,0 +1,61 @@ +# MCP SDK Validator and Source Updater Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Keep tool-name validation and source metadata updates functional after replacing the custom `@McpFunction` annotation with Spring AI's `@McpTool`. + +**Architecture:** `McpToolNameValidator` will scan `@McpTool(name = "...")` declarations for naming and duplication rules. `ToolSourceUpdater` will update SDK-owned fields (`name`, `description`) on `@McpTool` and project-owned fields (`group`, `register`, `requiresApproval`) on adjacent `@ToolHint`, without trying to write unsupported fields to the SDK annotation. + +**Tech Stack:** Java 21, JUnit 5, Spring AI MCP annotations, Gradle. + +## Global Constraints + +- Preserve the existing tool-name convention and duplicate-name build validation. +- Do not reintroduce the removed custom `McpFunction` annotation. +- Keep project-specific metadata in `io.shinhanlife.dap.lib.annotation.ToolHint`. +- Verify with focused tests and `gradlew.bat test`. + +--- + +### Task 1: Migrate tool-name validation + +**Files:** +- Modify: `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java` +- Modify: `dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java` + +**Interfaces:** +- Consumes: Java source files containing `@McpTool(name = "...")`. +- Produces: `McpToolNameValidator.assertUnique(Path)` that rejects duplicate or invalid SDK tool names. + +- [ ] Write tests using `@McpTool` source snippets for duplicate, invalid, and valid names. +- [ ] Run `:dap-was-lib:test --tests *McpToolNameValidatorTest` and confirm current implementation does not detect those annotations. +- [ ] Replace the annotation scan target from `@McpFunction` to `@McpTool`. +- [ ] Re-run the focused test and confirm it passes. + +### Task 2: Migrate source metadata update behavior + +**Files:** +- Modify: `dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java` +- Create: `dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java` + +**Interfaces:** +- Consumes: a tool interface source containing `@McpTool` and `@ToolHint`. +- Produces: `ToolSourceUpdater.updateToolSource(String, String, String, boolean, Boolean)` that updates SDK name/description and project metadata without emitting invalid `@McpTool` attributes. + +- [ ] Write a temporary-source test defining one SDK annotation and one `ToolHint` annotation. +- [ ] Run the focused test and confirm the legacy updater cannot locate `@McpTool` declarations. +- [ ] Update annotation matching and replacements: `description` belongs to `@McpTool`; `group`, `register`, and `requiresApproval` belong to `@ToolHint`. +- [ ] Re-run the focused test and confirm it passes. + +### Task 3: Restore test-suite compilation after SDK migration + +**Files:** +- Modify: stale tests referencing `McpFunction` or old package locations. + +**Interfaces:** +- Consumes: `McpTool`, `ToolHint`, current `ToolSchemaResolver`, and current package names. +- Produces: compilation and behavioral coverage aligned with the SDK-based production code. + +- [ ] Replace stale annotation and resolver signatures in tests. +- [ ] Fix package relocation and missing assertion imports without widening production visibility. +- [ ] Run `gradlew.bat test`. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 zcma&OWmKeVvL#I6?i3D%6z=Zs?ofE*?rw#G$eqJB ziT4y8-Y@s9rkH0Tz>ll(^xkcTl)CY?rS&9VNd66Yc)g^6)JcWaY(5$5gt z8gr3SBXUTN;~cBgz&})qX%#!Fxom2Yau_`&8)+6aSN7YY+pS410rRUU*>J}qL0TnJ zRxt*7QeUqTh8j)Q&iavh<}L+$Jqz))<`IfKussVk%%Ah-Ti?Eo0hQH!rK%K=#EAw0 zwq@@~XNUXRnv8$;zv<6rCRJ6fPD^hfrh;0K?n z=p!u^3xOgWZ%f3+?+>H)9+w^$Tn1e;?UpVMJb!!;f)`6f&4|8mr+g)^@x>_rvnL0< zvD0Hu_N>$(Li7|Jgu0mRh&MV+<}`~Wi*+avM01E)Jtg=)-vViQKax!GeDc!xv$^mL z{#OVBA$U{(Zr8~Xm|cP@odkHC*1R8z6hcLY#N@3E-A8XEvpt066+3t9L_6Zg6j@9Q zj$$%~yO-OS6PUVrM2s)(T4#6=JpI_@Uz+!6=GdyVU?`!F=d;8#ZB@(5g7$A0(`eqY z8_i@3w$0*es5mrSjhW*qzrl!_LQWs4?VfLmo1Sd@Ztt53+etwzAT^8ow_*7Jp`Y|l z*UgSEwvxq+FYO!O*aLf-PinZYne7Ib6ny3u>MjQz=((r3NTEeU4=-i0LBq3H-VJH< z^>1RE3_JwrclUn9vb7HcGUaFRA0QHcnE;6)hnkp%lY1UII#WPAv?-;c?YH}LWB8Nl z{sx-@Z;QxWh9fX8SxLZk8;kMFlGD3Jc^QZVL4nO)1I$zQwvwM&_!kW+LMf&lApv#< zur|EyC|U@5OQuph$TC_ZU`{!vJp`13e9alaR0Dbn5ikLFH7>eIz4QbV|C=%7)F=qo z_>M&5N)d)7G(A%c>}UCrW!Ql_6_A{?R7&CL`;!KOb3 z8Z=$YkV-IF;c7zs{3-WDEFJzuakFbd*4LWd<_kBE8~BFcv}js_2OowRNzWCtCQ6&k z{&~Me92$m*@e0ANcWKuz)?YjB*VoSTx??-3Cc0l2U!X^;Bv@m87eKHukAljrD54R+ zE;@_w4NPe1>3`i5Qy*3^E9x#VB6?}v=~qIprrrd5|DFkg;v5ixo0IsBmik8=Y;zv2 z%Bcf%NE$a44bk^`i4VwDLTbX=q@j9;JWT9JncQ!+Y%2&HHk@1~*L8-{ZpY?(-a9J-1~<1ltr9i~D9`P{XTIFWA6IG8c4;6bFw*lzU-{+?b&%OcIoCiw00n>A1ra zFPE$y@>ebbZlf(sN_iWBzQKDV zmmaLX#zK!@ZdvCANfwV}9@2O&w)!5gSgQzHdk2Q`jG6KD7S+1R5&F)j6QTD^=hq&7 zHUW+r^da^%V(h(wonR(j?BOiC!;y=%nJvz?*aW&5E87qq;2z`EI(f zBJNNSMFF9U{sR-af5{IY&AtoGcoG)Iq-S^v{7+t0>7N(KRoPj;+2N5;9o_nxIGjJ@ z7bYQK)bX)vEhy~VL%N6g^NE@D5VtV+Q8U2%{ji_=6+i^G%xeskEhH>Sqr194PJ$fB zu1y^){?9Vkg(FY2h)3ZHrw0Z<@;(gd_dtF#6y_;Iwi{yX$?asr?0N0_B*CifEi7<6 zq`?OdQjCYbhVcg+7MSgIM|pJRu~`g?g3x?Tl+V}#$It`iD1j+!x+!;wS0+2e>#g?Z z*EA^k7W{jO1r^K~cD#5pamp+o@8&yw6;%b|uiT?{Wa=4+9<}aXWUuL#ZwN1a;lQod zW{pxWCYGXdEq9qAmvAB904}?97=re$>!I%wxPV#|f#@A*Y=qa%zHlDv^yWbR03%V0 zprLP+b(#fBqxI%FiF*-n8HtH6$8f(P6!H3V^ysgd8de-N(@|K!A< z^qP}jp(RaM9kQ(^K(U8O84?D)aU(g?1S8iWwe)gqpHCaFlJxb*ilr{KTnu4_@5{K- z)n=CCeCrPHO0WHz)dDtkbZfUfVBd?53}K>C5*-wC4hpDN8cGk3lu-ypq+EYpb_2H; z%vP4@&+c2p;thaTs$dc^1CDGlPG@A;yGR5@$UEqk6p58qpw#7lc<+W(WR;(vr(D>W z#(K$vE#uBkT=*q&uaZwzz=P5mjiee6>!lV?c}QIX%ZdkO1dHg>Fa#xcGT6~}1*2m9 zkc7l3ItD6Ie~o_aFjI$Ri=C!8uF4!Ky7iG9QTrxVbsQroi|r)SAon#*B*{}TB-?=@ z8~jJs;_R2iDd!$+n$%X6FO&PYS{YhDAS+U2o4su9x~1+U3z7YN5o0qUK&|g^klZ6X zj_vrM5SUTnz5`*}Hyts9ADwLu#x_L=nv$Z0`HqN`Zo=V>OQI)fh01n~*a%01%cx%0 z4LTFVjmW+ipVQv5rYcn3;d2o4qunWUY!p+?s~X~(ost@WR@r@EuDOSs8*MT4fiP>! zkfo^!PWJJ1MHgKS2D_hc?Bs?isSDO61>ebl$U*9*QY(b=i&rp3@3GV@z>KzcZOxip z^dzA~44;R~cnhWz7s$$v?_8y-k!DZys}Q?4IkSyR!)C0j$(Gm|t#e3|QAOFaV2}36 z?dPNY;@I=FaCwylc_;~kXlZsk$_eLkNb~TIl8QQ`mmH&$*zwwR8zHU*sId)rxHu*K z;yZWa8UmCwju%aSNLwD5fBl^b0Ux1%q8YR*uG`53Mi<`5uA^Dc6Ync)J3N7;zQ*75)hf%a@{$H+%S?SGT)ks60)?6j$ zspl|4Ad6@%-r1t*$tT(en!gIXTUDcsj?28ZEzz)dH)SV3bZ+pjMaW0oc~rOPZP@g! zb9E+ndeVO_Ib9c_>{)`01^`ZS198 z)(t=+{Azi11$eu%aU7jbwuQrO`vLOixuh~%4z@mKr_Oc;F%Uq01fA)^W&y+g16e?rkLhTxV!EqC%2}sx_1u7IBq|}Be&7WI z4I<;1-9tJsI&pQIhj>FPkQV9{(m!wYYV@i5h?A0#BN2wqlEwNDIq06|^2oYVa7<~h zI_OLan0Do*4R5P=a3H9`s5*>xU}_PSztg`+2mv)|3nIy=5#Z$%+@tZnr> zLcTI!Mxa`PY7%{;KW~!=;*t)R_sl<^b>eNO@w#fEt(tPMg_jpJpW$q_DoUlkY|uo> z0-1{ouA#;t%spf*7VjkK&$QrvwUERKt^Sdo)5@?qAP)>}Y!h4(JQ!7{wIdkA+|)bv z&8hBwoX4v|+fie}iTslaBX^i*TjwO}f{V)8*!dMmRPi%XAWc8<_IqK1jUsApk)+~R zNFTCD-h>M5Y{qTQ&0#j@I@tmXGj%rzhTW5%Bkh&sSc=$Fv;M@1y!zvYG5P2(2|(&W zlcbR1{--rJ&s!rB{G-sX5^PaM@3EqWVz_y9cwLR9xMig&9gq(voeI)W&{d6j1jh&< zARXi&APWE1FQWh7eoZjuP z;vdgX>zep^{{2%hem;e*gDJhK1Hj12nBLIJoL<=0+8SVEBx7!4Ea+hBY;A1gBwvY<)tj~T=H`^?3>zeWWm|LAwo*S4Z%bDVUe z6r)CH1H!(>OH#MXFJ2V(U(qxD{4Px2`8qfFLG+=a;B^~Te_Z!r3RO%Oc#ZAHKQxV5 zRYXxZ9T2A%NVJIu5Pu7!Mj>t%YDO$T@M=RR(~mi%sv(YXVl`yMLD;+WZ{vG9(@P#e zMo}ZiK^7^h6TV%cG+;jhJ0s>h&VERs=tuZz^Tlu~%d{ZHtq6hX$V9h)Bw|jVCMudd zwZ5l7In8NT)qEPGF$VSKg&fb0%R2RnUnqa){)V(X(s0U zkCdVZe6wy{+_WhZh3qLp245Y2RR$@g-!9PjJ&4~0cFSHMUn=>dapv)hy}|y91ZWTV zCh=z*!S3_?`$&-eZ6xIXUq8RGl9oK0BJw*TdU6A`LJqX9eS3X@F)g$jLkBWFscPhR zpCv8#KeAc^y>>Y$k^=r|K(DTC}T$0#jQBOwB#@`P6~*IuW_8JxCG}J4va{ zsZzt}tt+cv7=l&CEuVtjD6G2~_Meh%p4RGuY?hSt?(sreO_F}8r7Kp$qQdvCdZnDQ zxzc*qchE*E2=WK)^oRNa>Ttj`fpvF-JZ5tu5>X1xw)J@1!IqWjq)ESBG?J|ez`-Tc zi5a}GZx|w-h%5lNDE_3ho0hEXMoaofo#Z;$8|2;EDF&*L+e$u}K=u?pb;dv$SXeQM zD-~7P0i_`Wk$#YP$=hw3UVU+=^@Kuy$>6?~gIXx636jh{PHly_a2xNYe1l60`|y!7 z(u%;ILuW0DDJ)2%y`Zc~hOALnj1~txJtcdD#o4BCT68+8gZe`=^te6H_egxY#nZH&P*)hgYaoJ^qtmpeea`35Fw)cy!w@c#v6E29co8&D9CTCl%^GV|X;SpneSXzV~LXyRn-@K0Df z{tK-nDWA!q38M1~`xUIt_(MO^R(yNY#9@es9RQbY@Ia*xHhD&=k^T+ zJi@j2I|WcgW=PuAc>hs`(&CvgjL2a9Rx zCbZyUpi8NWUOi@S%t+Su4|r&UoU|ze9SVe7p@f1GBkrjkkq)T}X%Qo1g!SQ{O{P?m z-OfGyyWta+UCXH+-+(D^%kw#A1-U;?9129at7MeCCzC{DNgO zeSqsV>W^NIfTO~4({c}KUiuoH8A*J!Cb0*sp*w-Bg@YfBIPZFH!M}C=S=S7PLLcIG zs7K77g~W)~^|+mx9onzMm0qh(f~OsDTzVmRtz=aZTllgR zGUn~_5hw_k&rll<4G=G+`^Xlnw;jNYDJz@bE?|r866F2hA9v0-8=JO3g}IHB#b`hy zA42a0>{0L7CcabSD+F7?pGbS1KMvT{@1_@k!_+Ki|5~EMGt7T%u=79F)8xEiL5!EJ zzuxQ`NBliCoJMJdwu|);zRCD<5Sf?Y>U$trQ-;xj6!s5&w=9E7)%pZ+1Nh&8nCCwM zv5>Ket%I?cxr3vVva`YeR?dGxbG@pi{H#8@kFEf0Jq6~K4>kt26*bxv=P&jyE#e$| zDJB_~imk^-z|o!2njF2hL*|7sHCnzluhJjwLQGDmC)Y9 zr9ZN`s)uCd^XDvn)VirMgW~qfn1~SaN^7vcX#K1G`==UGaDVVx$0BQnubhX|{e z^i0}>k-;BP#Szk{cFjO{2x~LjK{^Upqd&<+03_iMLp0$!6_$@TbX>8U-f*-w-ew1?`CtD_0y_Lo|PfKi52p?`5$Jzx0E8`M0 zNIb?#!K$mM4X%`Ry_yhG5k@*+n4||2!~*+&pYLh~{`~o(W|o64^NrjP?-1Lgu?iK^ zTX6u3?#$?R?N!{599vg>G8RGHw)Hx&=|g4599y}mXNpM{EPKKXB&+m?==R3GsIq?G zL5fH={=zawB(sMlDBJ+{dgb)Vx3pu>L=mDV0{r1Qs{0Pn%TpopH{m(By4;{FBvi{I z$}x!Iw~MJOL~&)p93SDIfP3x%ROjg}X{Sme#hiJ&Yk&a;iR}V|n%PriZBY8SX2*;6 z4hdb^&h;Xz%)BDACY5AUsV!($lib4>11UmcgXKWpzRL8r2Srl*9Y(1uBQsY&hO&uv znDNff0tpHlLISam?o(lOp#CmFdH<6HmA0{UwfU#Y{8M+7od8b8|B|7ZYR9f<#+V|ZSaCQvI$~es~g(Pv{2&m_rKSB2QQ zMvT}$?Ll>V+!9Xh5^iy3?UG;dF-zh~RL#++roOCsW^cZ&({6q|?Jt6`?S8=16Y{oH zp50I7r1AC1(#{b`Aq5cw>ypNggHKM9vBx!W$eYIzD!4KbLsZGr2o8>g<@inmS3*>J zx8oG((8f!ei|M@JZB`p7+n<Q}?>h249<`7xJ?u}_n;Gq(&km#1ULN87CeTO~FY zS_Ty}0TgQhV zOh3T7{{x&LSYGQfKR1PDIkP!WnfC1$l+fs@Di+d4O=eVKeF~2fq#1<8hEvpwuqcaH z4A8u~r^gnY3u6}zj*RHjk{AHhrrDqaj?|6GaVJbV%o-nATw}ASFr!f`Oz|u_QPkR# z0mDudY1dZRlk@TyQ?%Eti=$_WNFtLpSx9=S^be{wXINp%MU?a`F66LNU<c;0&ngifmP9i;bj6&hdGMW^Kf8e6ZDXbQD&$QAAMo;OQ)G zW(qlHh;}!ZP)JKEjm$VZjTs@hk&4{?@+NADuYrr!R^cJzU{kGc1yB?;7mIyAWwhbeA_l_lw-iDVi7wcFurf5 z#Uw)A@a9fOf{D}AWE%<`s1L_AwpZ?F!Vac$LYkp<#A!!`XKaDC{A%)~K#5z6>Hv@V zBEqF(D5?@6r3Pwj$^krpPDCjB+UOszqUS;b2n>&iAFcw<*im2(b3|5u6SK!n9Sg4I z0KLcwA6{Mq?p%t>aW0W!PQ>iUeYvNjdKYqII!CE7SsS&Rj)eIw-K4jtI?II+0IdGq z2WT|L3RL?;GtGgt1LWfI4Ka`9dbZXc$TMJ~8#Juv@K^1RJN@yzdLS8$AJ(>g!U9`# zx}qr7JWlU+&m)VG*Se;rGisutS%!6yybi%B`bv|9rjS(xOUIvbNz5qtvC$_JYY+c& za*3*2$RUH8p%pSq>48xR)4qsp!Q7BEiJ*`^>^6INRbC@>+2q9?x(h0bpc>GaNFi$K zPH$6!#(~{8@0QZk=)QnM#I=bDx5vTvjm$f4K}%*s+((H2>tUTf==$wqyoI`oxI7>C z&>5fe)Yg)SmT)eA(|j@JYR1M%KixxC-Eceknf-;N=jJTwKvk#@|J^&5H0c+%KxHUI z6dQbwwVx3p?X<_VRVb2fStH?HH zFR@Mp=qX%#L3XL)+$PXKV|o|#DpHAoqvj6uQKe@M-mnhCSou7Dj4YuO6^*V`m)1lf z;)@e%1!Qg$10w8uEmz{ENb$^%u}B;J7sDd zump}onoD#!l=agcBR)iG!3AF0-63%@`K9G(CzKrm$VJ{v7^O9Ps7Zej|3m= zVXlR&yW6=Y%mD30G@|tf=yC7-#L!16Q=dq&@beWgaIL40k0n% z)QHrp2Jck#evLMM1RGt3WvQ936ZC9vEje0nFMfvmOHVI+&okB_K|l-;|4vW;qk>n~ z+|kk8#`K?x`q>`(f6A${wfw9Cx(^)~tX7<#TpxR#zYG2P+FY~mG{tnEkv~d6oUQA+ z&hNTL=~Y@rF`v-RZlts$nb$3(OL1&@Y11hhL9+zUb6)SP!;CD)^GUtUpCHBE`j1te zAGud@miCVFLk$fjsrcpjsadP__yj9iEZUW{Ll7PPi<$R;m1o!&Xdl~R_v0;oDX2z^!&8}zNGA}iYG|k zmehMd1%?R)u6R#<)B)1oe9TgYH5-CqUT8N7K-A-dm3hbm_W21p%8)H{O)xUlBVb+iUR}-v5dFaCyfSd zC6Bd7=N4A@+Bna=!-l|*_(nWGDpoyU>nH=}IOrLfS+-d40&(Wo*dDB9nQiA2Tse$R z;uq{`X7LLzP)%Y9aHa4YQ%H?htkWd3Owv&UYbr5NUDAH^<l@Z0Cx%`N+B*i!!1u>D8%;Qt1$ zE5O0{-`9gdDxZ!`0m}ywH!;c{oBfL-(BH<&SQ~smbcobU!j49O^f4&IIYh~f+hK*M zZwTp%{ZSAhMFj1qFaOA+3)p^gnXH^=)`NTYgTu!CLpEV2NF=~-`(}7p^Eof=@VUbd z_9U|8qF7Rueg&$qpSSkN%%%DpbV?8E8ivu@ensI0toJ7Eas^jyFReQ1JeY9plb^{m z&eQO)qPLZQ6O;FTr*aJq=$cMN)QlQO@G&%z?BKUs1&I^`lq>=QLODwa`(mFGC`0H< zOlc*|N?B5&!U6BuJvkL?s1&nsi$*5cCv7^j_*l&$-sBmRS85UIrE--7eD8Gr3^+o? zqG-Yl4S&E;>H>k^a0GdUI(|n1`ws@)1%sq2XBdK`mqrNq_b4N{#VpouCXLzNvjoFv zo9wMQ6l0+FT+?%N(ka*;%m~(?338bu32v26!{r)|w8J`EL|t$}TA4q_FJRX5 zCPa{hc_I(7TGE#@rO-(!$1H3N-C0{R$J=yPCXCtGk{4>=*B56JdXU9cQVwB`6~cQZ zf^qK21x_d>X%dT!!)CJQ3mlHA@ z{Prkgfs6=Tz%63$6Zr8CO0Ak3A)Cv#@BVKr&aiKG7RYxY$Yx>Bj#3gJk*~Ps-jc1l z;4nltQwwT4@Z)}Pb!3xM?+EW0qEKA)sqzw~!C6wd^{03-9aGf3Jmt=}w-*!yXupLf z;)>-7uvWN4Unn8b4kfIza-X=x*e4n5pU`HtgpFFd))s$C@#d>aUl3helLom+RYb&g zI7A9GXLRZPl}iQS*d$Azxg-VgcUr*lpLnbPKUV{QI|bsG{8bLG<%CF( zMoS4pRDtLVYOWG^@ox^h8xL~afW_9DcE#^1eEC1SVSb1BfDi^@g?#f6e%v~Aw>@w- zIY0k+2lGWNV|aA*e#`U3=+oBDmGeInfcL)>*!w|*;mWiKNG6wP6AW4-4imN!W)!hE zA02~S1*@Q`fD*+qX@f3!2yJX&6FsEfPditB%TWo3=HA;T3o2IrjS@9SSxv%{{7&4_ zdS#r4OU41~GYMiib#z#O;zohNbhJknrPPZS6sN$%HB=jUnlCO_w5Gw5EeE@KV>soy z2EZ?Y|4RQDDjt5y!WBlZ(8M)|HP<0YyG|D%RqD+K#e7-##o3IZxS^wQ5{Kbzb6h(i z#(wZ|^ei>8`%ta*!2tJzwMv+IFHLF`zTU8E^Mu!R*45_=ccqI};Zbyxw@U%a#2}%f zF>q?SrUa_a4H9l+uW8JHh2Oob>NyUwG=QH~-^ZebU*R@67DcXdz2{HVB4#@edz?B< z5!rQH3O0>A&ylROO%G^fimV*LX7>!%re{_Sm6N>S{+GW1LCnGImHRoF@csnFzn@P0 zM=jld0z%oz;j=>c7mMwzq$B^2mae7NiG}%>(wtmsDXkWk{?BeMpTrIt3Mizq?vRsf zi_WjNp+61uV(%gEU-Vf0;>~vcDhe(dzWdaf#4mH3o^v{0EWhj?E?$5v02sV@xL0l4 zX0_IMFtQ44PfWBbPYN#}qxa%=J%dlR{O!KyZvk^g5s?sTNycWYPJ^FK(nl3k?z-5t z39#hKrdO7V(@!TU)LAPY&ngnZ1MzLEeEiZznn7e-jLCy8LO zu^7_#z*%I-BjS#Pg-;zKWWqX-+Ly$T!4`vTe5ZOV0j?TJVA*2?*=82^GVlZIuH%9s zXiV&(T(QGHHah=s&7e|6y?g+XxZGmK55`wGV>@1U)Th&=JTgJq>4mI&Av2C z)w+kRoj_dA!;SfTfkgMPO>7Dw6&1*Hi1q?54Yng`JO&q->^CX21^PrU^JU#CJ_qhV zSG>afB%>2fx<~g8p=P8Yzxqc}s@>>{g7}F!;lCXvF#RV)^fyYb_)iKVCz1xEq=fJ| z0a7DMCK*FuP=NM*5h;*D`R4y$6cpW-E&-i{v`x=Jbk_xSn@2T3q!3HoAOB`@5Vg6) z{PW|@9o!e;v1jZ2{=Uw6S6o{g82x6g=k!)cFSC*oemHaVjg?VpEmtUuD2_J^A~$4* z3O7HsbA6wxw{TP5Kk)(Vm?gKo+_}11vbo{Tp_5x79P~#F)ahQXT)tSH5;;14?s)On zel1J>1x>+7;g1Iz2FRpnYz;sD0wG9Q!vuzE9yKi3@4a9Nh1!GGN?hA)!mZEnnHh&i zf?#ZEN2sFbf~kV;>K3UNj1&vFhc^sxgj8FCL4v>EOYL?2uuT`0eDH}R zmtUJMxVrV5H{L53hu3#qaWLUa#5zY?f5ozIn|PkMWNP%n zWB5!B0LZB0kLw$k39=!akkE9Q>F4j+q434jB4VmslQ;$ zKiO#FZ`p|dKS716jpcvR{QJkSNfDVhr2%~eHrW;fU45>>snr*S8Vik-5eN5k*c2Mp zyxvX&_cFbB6lODXznHHT|rsURe2!swomtrqc~w5 zymTM8!w`1{04CBprR!_F{5LB+2_SOuZN{b*!J~1ZiPpP-M;);!ce!rOPDLtgR@Ie1 zPreuqm4!H)hYePcW1WZ0Fyaqe%l}F~Orr)~+;mkS&pOhP5Ebb`cnUt!X_QhP4_4p( z8YKQCDKGIy>?WIFm3-}Br2-N`T&FOi?t)$hjphB9wOhBXU#Hb+zm&We_-O)s(wc`2 z8?VsvU;J>Ju7n}uUb3s1yPx_F*|FlAi=Ge=-kN?1;`~6szP%$3B0|8Sqp%ebM)F8v zADFrbeT0cgE>M0DMV@_Ze*GHM>q}wWMzt|GYC%}r{OXRG3Ij&<+nx9;4jE${Fj_r* z`{z1AW_6Myd)i6e0E-h&m{{CvzH=Xg!&(bLYgRMO_YVd8JU7W+7MuGWNE=4@OvP9+ zxi^vqS@5%+#gf*Z@RVyU9N1sO-(rY$24LGsg1>w>s6ST^@)|D9>cT50maXLUD{Fzf zt~tp{OSTEKg3ZSQyQQ5r51){%=?xlZ54*t1;Ow)zLe3i?8tD8YyY^k%M)e`V*r+vL zPqUf&m)U+zxps+NprxMHF{QSxv}>lE{JZETNk1&F+R~bp{_T$dbXL2UGnB|hgh*p4h$clt#6;NO~>zuyY@C-MD@)JCc5XrYOt`wW7! z_ti2hhZBMJNbn0O-uTxl_b6Hm313^fG@e;RrhIUK9@# z+DHGv_Ow$%S8D%RB}`doJjJy*aOa5mGHVHz0e0>>O_%+^56?IkA5eN+L1BVCp4~m=1eeL zb;#G!#^5G%6Mw}r1KnaKsLvJB%HZL)!3OxT{k$Yo-XrJ?|7{s4!H+S2o?N|^Z z)+?IE9H7h~Vxn5hTis^3wHYuOU84+bWd)cUKuHapq=&}WV#OxHpLab`NpwHm8LmOo zjri+!k;7j_?FP##CpM+pOVx*0wExEex z@`#)K<-ZrGyArK;a%Km`^+We|eT+#MygHOT6lXBmz`8|lyZOwL1+b+?Z$0OhMEp3R z&J=iRERpv~TC=p2-BYLC*?4 zxvPs9V@g=JT0>zky5Poj=fW_M!c)Xxz1<=&_ZcL=LMZJqlnO1P^xwGGW*Z+yTBvbV z-IFe6;(k1@$1;tS>{%pXZ_7w+i?N4A2=TXnGf=YhePg8bH8M|Lk-->+w8Y+FjZ;L=wSGwxfA`gqSn)f(XNuSm>6Y z@|#e-)I(PQ^G@N`%|_DZSb4_pkaEF0!-nqY+t#pyA>{9^*I-zw4SYA1_z2Bs$XGUZbGA;VeMo%CezHK0lO={L%G)dI-+8w?r9iexdoB{?l zbJ}C?huIhWXBVs7oo{!$lOTlvCLZ_KN1N+XJGuG$rh<^eUQIqcI7^pmqhBSaOKNRq zrx~w^?9C?*&rNwP_SPYmo;J-#!G|{`$JZK7DxsM3N^8iR4vvn>E4MU&Oe1DKJvLc~ zCT>KLZ1;t@My zRj_2hI^61T&LIz)S!+AQIV23n1>ng+LUvzv;xu!4;wpqb#EZz;F)BLUzT;8UA1x*6vJ zicB!3Mj03s*kGV{g`fpC?V^s(=JG-k1EMHbkdP4P*1^8p_TqO|;!Zr%GuP$8KLxuf z=pv*H;kzd;P|2`JmBt~h6|GxdU~@weK5O=X&5~w$HpfO}@l-T7@vTCxVOwCkoPQv8 z@aV_)I5HQtfs7^X=C03zYmH4m0S!V@JINm6#(JmZRHBD?T!m^DdiZJrhKpBcur2u1 zf9e4%k$$vcFopK5!CC`;ww(CKL~}mlxK_Pv!cOsFgVkNIghA2Au@)t6;Y3*2gK=5d z?|@1a)-(sQ%uFOmJ7v2iG&l&m^u&^6DJM#XzCrF%r>{2XKyxLD2rgWBD;i(!e4InDQBDg==^z;AzT2z~OmV0!?Z z0S9pX$+E;w3WN;v&NYT=+G8hf=6w0E1$0AOr61}eOvE8W1jX%>&Mjo7&!ulawgzLH zbcb+IF(s^3aj12WSi#pzIpijJJzkP?JzRawnxmNDSUR#7!29vHULCE<3Aa#be}ie~d|!V+ z%l~s9Odo$G&fH!t!+`rUT0T9DulF!Yq&BfQWFZV1L9D($r4H(}Gnf6k3^wa7g5|Ws zj7%d`!3(0bb55yhC6@Q{?H|2os{_F%o=;-h{@Yyyn*V7?{s%Grvpe!H^kl6tF4Zf5 z{Jv1~yZ*iIWL_9C*8pBMQArfJJ0d9Df6Kl#wa}7Xa#Ef_5B7=X}DzbQXVPfCwTO@9+@;A^Ti6il_C>g?A-GFwA0#U;t4;wOm-4oS})h z5&on>NAu67O?YCQr%7XIzY%LS4bha9*e*4bU4{lGCUmO2UQ2U)QOqClLo61Kx~3dI zmV3*(P6F_Tr-oP%x!0kTnnT?Ep5j;_IQ^pTRp=e8dmJtI4YgWd0}+b2=ATkOhgpXe z;jmw+FBLE}UIs4!&HflFr4)vMFOJ19W4f2^W(=2)F%TAL)+=F>IE$=e=@j-*bFLSg z)wf|uFQu+!=N-UzSef62u0-C8Zc7 zo6@F)c+nZA{H|+~7i$DCU0pL{0Ye|fKLuV^w!0Y^tT$isu%i1Iw&N|tX3kwFKJN(M zXS`k9js66o$r)x?TWL}Kxl`wUDUpwFx(w4Yk%49;$sgVvT~n8AgfG~HUcDt1TRo^s zdla@6heJB@JV z!vK;BUMznhzGK6PVtj0)GB=zTv6)Q9Yt@l#fv7>wKovLobMV-+(8)NJmyF8R zcB|_K7=FJGGn^X@JdFaat0uhKjp3>k#^&xE_}6NYNG?kgTp>2Iu?ElUjt4~E-?`Du z?mDCS9wbuS%fU?5BU@Ijx>1HG*N?gIP+<~xE4u=>H`8o((cS5M6@_OK%jSjFHirQK zN9@~NXFx*jS{<|bgSpC|SAnA@I)+GB=2W|JJChLI_mx+-J(mSJ!b)uUom6nH0#2^(L@JBlV#t zLl?j54s`Y3vE^c_3^Hl0TGu*tw_n?@HyO@ZrENxA+^!)OvUX28gDSF*xFtQzM$A+O zCG=n#6~r|3zt=8%GuG} z<#VCZ%2?3Q(Ad#Y7GMJ~{U3>E{5e@z6+rgZLX{Cxk^p-7dip^d29;2N1_mm4QkASo z-L`GWWPCq$uCo;X_BmGIpJFBlhl<8~EG{vOD1o|X$aB9KPhWO_cKiU*$HWEgtf=fn zsO%9bp~D2c@?*K9jVN@_vhR03>M_8h!_~%aN!Cnr?s-!;U3SVfmhRwk11A^8Ns`@KeE}+ zN$H}a1U6E;*j5&~Og!xHdfK5M<~xka)x-0N)K_&e7AjMz`toDzasH+^1bZlC!n()crk9kg@$(Y{wdKvbuUd04N^8}t1iOgsKF zGa%%XWx@WoVaNC1!|&{5ZbkopFre-Lu(LCE5HWZBoE#W@er9W<>R=^oYxBvypN#x3 zq#LC8&q)GFP=5^-bpHj?LW=)-g+3_)Ylps!3^YQ{9~O9&K)xgy zMkCWaApU-MI~e^cV{Je75Qr7eF%&_H)BvfyKL=gIA>;OSq(y z052BFz3E(Prg~09>|_Z@!qj}@;8yxnw+#Ej0?Rk<y}4ghbD569B{9hSFr*^ygZ zr6j7P#gtZh6tMk6?4V$*Jgz+#&ug;yOr>=qdI#9U&^am2qoh4Jy}H2%a|#Fs{E(5r z%!ijh;VuGA6)W)cJZx+;9Bp1LMUzN~x_8lQ#D3+sL{be-Jyeo@@dv7XguJ&S5vrH` z>QxOMWn7N-T!D@1(@4>ZlL^y5>m#0!HKovs12GRav4z!>p(1~xok8+_{| z#Ae4{9#NLh#Vj2&JuIn5$d6t@__`o}umFo(n0QxUtd2GKCyE+erwXY?`cm*h&^9*8 zJ+8x6fRZI-e$CRygofIQN^dWysCxgkyr{(_oBwwSRxZora1(%(aC!5BTtj^+YuevI zx?)H#(xlALUp6QJ!=l9N__$cxBZ5p&7;qD3PsXRFVd<({Kh+mShFWJNpy`N@ab7?9 zv5=klvCJ4bx|-pvOO2-+G)6O?$&)ncA#Urze2rlBfp#htudhx-NeRnJ@u%^_bfw4o z4|{b8SkPV3b>Wera1W(+N@p9H>dc6{cnkh-sgr?e%(YkWvK+0YXVwk0=d`)}*47*B z5JGkEdVix!w7-<%r0JF~`ZMMPe;f0EQHuYHxya`puazyph*ZSb1mJAt^k4549BfS; zK7~T&lRb=W{s&t`DJ$B}s-eH1&&-wEOH1KWsKn0a(ZI+G!v&W4A*cl>qAvUv6pbUR z#(f#EKV8~hk&8oayBz4vaswc(?qw1vn`yC zZQDl2PCB-&Uu@g9ZQHhO+v(W0bNig{-k0;;`+wM@#@J)8r?qOYs#&vUna8ILxN7S{ zp1s41KnR8miQJtJtOr|+qk}wrLt+N*z#5o`TmD1)E&QD(Vh&pjZJ_J*0!8dy_ z>^=@v=J)C`x&gjqAYu`}t^S=DFCtc0MkBU2zf|69?xW`Ck~(6zLD)gSE{7n~6w8j_ zoH&~$ED2k5-yRa0!r8fMRy z;QjBYUaUnpd}mf%iVFPR%Dg9!d>g`01m~>2s))`W|5!kc+_&Y>wD@@C9%>-lE`WB0 zOIf%FVD^cj#2hCkFgi-fgzIfOi+ya)MZK@IZhHT5FVEaSbv-oDDs0W)pA0&^nM0TW zmgJmd7b1R7b0a`UwWJYZXp4AJPteYLH>@M|xZFKwm!t3D3&q~av?i)WvAKHE{RqpD{{%OhYkK?47}+}` zrR2(Iv9bhVa;cDzJ%6ntcSbx7v7J@Y4x&+eWSKZ*eR7_=CVIUSB$^lfYe@g+p|LD{ zPSpQmxx@b$%d!05|H}WzBT4_cq?@~dvy<7s&QWtieJ9)hd4)$SZz}#H2UTi$CkFWW|I)v_-NjuH!VypONC=1`A=rm_jfzQ8Fu~1r8i{q-+S_j$ z#u^t&Xnfi5tZtl@^!fUJhx@~Cg0*vXMK}D{>|$#T*+mj(J_@c{jXBF|rm4-8%Z2o! z2z0o(4%8KljCm^>6HDK!{jI7p+RAPcty_~GZ~R_+=+UzZ0qzOwD=;YeZt*?3%UGdr z`c|BPE;yUbnyARUl&XWSNJ<+uRt%!xPF&K;(l$^JcA_CMH6)FZt{>6ah$|(9$2fc~ z=CD00uHM{qv;{Zk9FR0~u|3|Eiqv9?z2#^GqylT5>6JNZwKqKBzzQpKU2_pmtD;CT zi%Ktau!Y2Tldfu&b0UgmF(SSBID)15*r08eoUe#bT_K-G4VecJL2Pa=6D1K6({zj6 za(2Z{r!FY5W^y{qZ}08+h9f>EKd&PN90f}Sc0ejf%kB4+f#T8Q1=Pj=~#pi$U zp#5rMR%W25>k?<$;$x72pkLibu1N|jX4cWjD3q^Pk3js!uK6h7!dlvw24crL|MZs_ zb%Y%?Fyp0bY0HkG^XyS76Ts*|Giw{31LR~+WU5NejqfPr73Rp!xQ1mLgq@mdWncLy z%8}|nzS4P&`^;zAR-&nm5f;D-%yNQPwq4N7&yULM8bkttkD)hVU>h>t47`{8?n2&4 zjEfL}UEagLUYwdx0sB2QXGeRmL?sZ%J!XM`$@ODc2!y|2#7hys=b$LrGbvvjx`Iqi z&RDDm3YBrlKhl`O@%%&rhLWZ*ABFz2nHu7k~3@e4)kO3%$=?GEFUcCF=6-1n!x^vmu+Ai*amgXH+Rknl6U>#9w;A} zn2xanZSDu`4%%x}+~FG{Wbi1jo@wqBc5(5Xl~d0KW(^Iu(U3>WB@-(&vn_PJt9{1`e9Iic@+{VPc`vP776L*viP{wYB2Iff8hB%E3|o zGMOu)tJX!`qJ}ZPzq7>=`*9TmETN7xwU;^AmFZ-ckZjV5B2T09pYliaqGFY|X#E-8 z20b>y?(r-Fn5*WZ-GsK}4WM>@TTqsxvSYWL6>18q8Q`~JO1{vLND2wg@58OaU!EvT z1|o+f1mVXz2EKAbL!Q=QWQKDZpV|jznuJ}@-)1&cdo z^&~b4Mx{*1gurlH;Vhk5g_cM&6LOHS2 zRkLfO#HabR1JD4Vc2t828dCUG#DL}f5QDSBg?o)IYYi@_xVwR2w_ntlpAW0NWk$F1 z$If?*lP&Ka1oWfl!)1c3fl`g*lMW3JOn#)R1+tfwrs`aiFUgz3;XIJ>{QFxLCkK30 zNS-)#DON3yb!7LBHQJ$)4y%TN82DC2-9tOIqzhZ27@WY^<6}vXCWcR5iN{LN8{0u9 zNXayqD=G|e?O^*ms*4P?G%o@J1tN9_76e}E#66mr89%W_&w4n66~R;X_vWD(oArwj z4CpY`)_mH2FvDuxgT+akffhX0b_slJJ*?Jn3O3~moqu2Fs1oL*>7m=oVek2bnprnW zixkaIFU%+3XhNA@@9hyhFwqsH2bM|`P?G>i<-gy>NflhrN{$9?LZ1ynSE_Mj0rADF zhOz4FnK}wpLmQuV zgO4_Oz9GBu_NN>cPLA=`SP^$gxAnj;WjJnBi%Q1zg`*^cG;Q)#3Gv@c^j6L{arv>- zAW%8WrSAVY1sj$=umcAf#ZgC8UGZGoamK}hR7j6}i8#np8ruUlvgQ$j+AQglFsQQq zOjyHf22pxh9+h#n$21&$h?2uq0>C9P?P=Juw0|;oE~c$H{#RGfa>| zj)Iv&uOnaf@foiBJ}_;zyPHcZt1U~nOcNB{)og8Btv+;f@PIT*xz$x!G?u0Di$lo7 zOugtQ$Wx|C($fyJTZE1JvR~i7LP{ zbdIwqYghQAJi9p}V&$=*2Azev$6K@pyblphgpv8^9bN!?V}{BkC!o#bl&AP!3DAjM zmWFsvn2fKWCfjcAQmE+=c3Y7j@#7|{;;0f~PIodmq*;W9Fiak|gil6$w3%b_Pr6K_ zJEG@&!J%DgBZJDCMn^7mk`JV0&l07Bt`1ymM|;a)MOWz*bh2#d{i?SDe9IcHs7 zjCrnyQ*Y5GzIt}>`bD91o#~5H?4_nckAgotN{2%!?wsSl|LVmJht$uhGa+HiH>;av z8c?mcMYM7;mvWr6noUR{)gE!=i7cZUY7e;HXa221KkRoc2UB>s$Y(k%NzTSEr>W(u z<(4mcc)4rB_&bPzX*1?*ra%VF}P1nwiP5cykJ&W{!OTlz&Td0pOkVp+wc z@k=-Hg=()hNg=Q!Ub%`BONH{ z_=ZFgetj@)NvppAK2>8r!KAgi>#%*7;O-o9MOOfQjV-n@BX6;Xw;I`%HBkk20v`qoVd0)}L6_49y1IhR z_OS}+eto}OPVRn*?UHC{eGyFU7JkPz!+gX4P>?h3QOwGS63fv4D1*no^6PveUeE5% zlehjv_3_^j^C({a2&RSoVlOn71D8WwMu9@Nb@=E_>1R*ve3`#TF(NA0?d9IR_tm=P zOP-x;gS*vtyE1Cm zG0L?2nRUFj#aLr-R1fX*$sXhad)~xdA*=hF3zPZhha<2O$Ps+F07w*3#MTe?)T8|A!P!v+a|ot{|^$q(TX`35O{WI0RbU zCj?hgOv=Z)xV?F`@HKI11IKtT^ocP78cqHU!YS@cHI@{fPD?YXL)?sD~9thOAv4JM|K8OlQhPXgnevF=F7GKD2#sZW*d za}ma31wLm81IZxX(W#A9mBvLZr|PoLnP>S4BhpK8{YV_}C|p<)4#yO{#ISbco92^3 zv&kCE(q9Wi;9%7>>PQ!zSkM%qqqLZW7O`VXvcj;WcJ`2~v?ZTYB@$Q&^CTfvy?1r^ z;Cdi+PTtmQwHX_7Kz?r#1>D zS5lWU(Mw_$B&`ZPmqxpIvK<~fbXq?x20k1~9az-Q!uR78mCgRj*eQ>zh3c$W}>^+w^dIr-u{@s30J=)1zF8?Wn|H`GS<=>Om|DjzC{}Jt?{!fSJe*@$H zg>wFnlT)k#T?LslW zu$^7Uy~$SQ21cE?3Ijl+bLfuH^U5P^$@~*UY#|_`uvAIe(+wD2eF}z_y!pvomuVO; zS^9fbdv)pcm-B@CW|Upm<7s|0+$@@<&*>$a{aW+oJ%f+VMO<#wa)7n|JL5egEgoBv zl$BY(NQjE0#*nv=!kMnp&{2Le#30b)Ql2e!VkPLK*+{jv77H7)xG7&=aPHL7LK9ER z5lfHxBI5O{-3S?GU4X6$yVk>lFn;ApnwZybdC-GAvaznGW-lScIls-P?Km2mF>%B2 zkcrXTk+__hj-3f48U%|jX9*|Ps41U_cd>2QW81Lz9}%`mTDIhE)jYI$q$ma7Y-`>% z8=u+Oftgcj%~TU}3nP8&h7k+}$D-CCgS~wtWvM|UU77r^pUw3YCV80Ou*+bH0!mf0 zxzUq4ed6y>oYFz7+l18PGGzhB^pqSt)si=9M>~0(Bx9*5r~W7sa#w+_1TSj3Jn9mW zMuG9BxN=}4645Cpa#SVKjFst;9UUY@O<|wpnZk$kE+to^4!?0@?Cwr3(>!NjYbu?x z1!U-?0_O?k!NdM^-rIQ8p)%?M+2xkhltt*|l=%z2WFJhme7*2xD~@zk#`dQR$6Lmd zb3LOD4fdt$Cq>?1<%&Y^wTWX=eHQ49Xl_lFUA(YQYHGHhd}@!VpYHHm=(1-O=yfK#kKe|2Xc*9}?BDFN zD7FJM-AjVi)T~OG)hpSWqH>vlb41V#^G2B_EvYlWhDB{Z;Q9-0)ja(O+By`31=biA zG&Fs#5!%_mHi|E4Nm$;vVQ!*>=_F;ZC=1DTPB#CICS5fL2T3XmzyHu?bI;m7D4@#; ztr~;dGYwb?m^VebuULtS4lkC_7>KCS)F@)0OdxZIFZp@FM_pHnJes8YOvwB|++#G( z&dm*OP^cz95Wi15vh`Q+yB>R{8zqEhz5of>Po$9LNE{xS<)lg2*roP*sQ}3r3t<}; zPbDl{lk{pox~2(XY5=qg0z!W-x^PJ`VVtz$git7?)!h>`91&&hESZy1KCJ2nS^yMH z!=Q$eTyRi68rKxdDsdt+%J_&lapa{ds^HV9Ngp^YDvtq&-Xp}60B_w@Ma>_1TTC;^ zpbe!#gH}#fFLkNo#|`jcn?5LeUYto%==XBk6Ik0kc4$6Z+L3x^4=M6OI1=z5u#M%0 z0E`kevJEpJjvvN>+g`?gtnbo$@p4VumliZV3Z%CfXXB&wPS^5C+7of2tyVkMwNWBiTE2 z8CdPu3i{*vR-I(NY5syRR}I1TJOV@DJy-Xmvxn^IInF>Tx2e)eE9jVSz69$6T`M9-&om!T+I znia!ZWJRB28o_srWlAxtz4VVft8)cYloIoVF=pL zugnk@vFLXQ_^7;%hn9x;Vq?lzg7%CQR^c#S)Oc-8d=q_!2ZVH764V z!wDKSgP}BrVV6SfCLZnYe-7f;igDs9t+K*rbMAKsp9L$Kh<6Z;e7;xxced zn=FGY<}CUz31a2G}$Q(`_r~75PzM4l_({Hg&b@d8&jC}B?2<+ed`f#qMEWi z`gm!STV9E4sLaQX+sp5Nu9*;9g12naf5?=P9p@H@f}dxYprH+3ju)uDFt^V{G0APn zS;16Dk{*fm6&BCg#2vo?7cbkkI4R`S9SSEJ=#KBk3rl69SxnCnS#{*$!^T9UUmO#&XXKjHKBqLdt^3yVvu8yn|{ zZ#%1CP)8t-PAz(+_g?xyq;C2<9<5Yy<~C74Iw(y>uUL$+$mp(DRcCWbCKiGCZw@?_ zdomfp+C5xt;j5L@VfhF*xvZdXwA5pcdsG>G<8II-|1dhAgzS&KArcb0BD4ZZ#WfiEY{hkCq5%z9@f|!EwTm;UEjKJsUo696V>h zy##eXYX}GUu%t{Gql8vVZKkNhQeQ4C%n|RmxL4ee5$cgwlU+?V7a?(jI#&3wid+Kz5+x^G!bb#$q>QpR#BZ}Xo5UW^ zD&I`;?(a}Oys7-`I^|AkN?{XLZNa{@27Dv^s4pGowuyhHuXc zuctKG2x0{WCvg_sGN^n9myJ}&FXyGmUQnW7fR$=bj$AHR88-q$D!*8MNB{YvTTEyS zn22f@WMdvg5~o_2wkjItJN@?mDZ9UUlat2zCh(zVE=dGi$rjXF7&}*sxac^%HFD`Y zTM5D3u5x**{bW!68DL1A!s&$2XG@ytB~dX-?BF9U@XZABO`a|LM1X3HWCllgl0+uL z04S*PX$%|^WAq%jkzp~%9HyYIF{Ym?k)j3nMwPZ=hlCg9!G+t>tf0o|J2%t1 ztC+`((dUplgm3`+0JN~}&FRRJ3?l*>Y&TfjS>!ShS`*MwO{WIbAZR#<%M|4c4^dY8 z{Rh;-!qhY=dz5JthbWoovLY~jNaw>%tS4gHVlt5epV8ekXm#==Po$)}mh^u*cE>q7*kvX&gq)(AHoItMYH6^s6f(deNw%}1=7O~bTHSj1rm2|Cq+3M z93djjdomWCTCYu!3Slx2bZVy#CWDozNedIHbqa|otsUl+ut?>a;}OqPfQA05Yim_2 zs@^BjPoFHOYNc6VbNaR5QZfSMh2S*`BGwcHMM(1@w{-4jVqE8Eu0Bi%d!E*^Rj?cR z7qgxkINXZR)K^=fh{pc0DCKtrydVbVILI>@Y0!Jm>x-xM!gu%dehm?cC6ok_msDVA*J#{75%4IZt}X|tIVPReZS#aCvuHkZxc zHVMtUhT(wp09+w9j9eRqz~LtuSNi2rQx_QgQ(}jBt7NqyT&ma61ldD(s9x%@q~PQl zp6N*?=N$BtvjQ_xIT{+vhb1>{pM0Arde0!X-y))A4znDrVx8yrP3B1(7bKPE5jR@5 zwpzwT4cu~_qUG#zYMZ_!2Tkl9zP>M%cy>9Y(@&VoB84#%>amTAH{(hL4cDYt!^{8L z645F>BWO6QaFJ-{C-i|-d%j7#&7)$X7pv#%9J6da#9FB5KyDhkA+~)G0^87!^}AP>XaCSScr;kL;Z%RSPD2CgoJ;gpYT5&6NUK$86$T?jRH=w8nI9Z534O?5fk{kd z`(-t$8W|#$3>xoMfXvV^-A(Q~$8SKDE^!T;J+rQXP71XZ(kCCbP%bAQ1|%$%Ov9_a zyC`QP3uPvFoBqr_+$HenHklqyIr>PU_Fk5$2C+0eYy^~7U&(!B&&P2%7#mBUhM!z> z_B$Ko?{Pf6?)gpYs~N*y%-3!1>o-4;@1Zz9VQHh)j5U1aL-Hyu@1d?X;jtDBNk*vMXPn@ z+u@wxHN*{uHR!*g*4Xo&w;5A+=Pf9w#PeZ^x@UD?iQ&${K2c}UQgLRik-rKM#Y5rdDphdcNTF~cCX&9ViRP}`>L)QA4zNXeG)KXFzSDa6 zd^St;inY6J_i=5mcGTx4_^Ys`M3l%Q==f>{8S1LEHn{y(kbxn5g1ezt4CELqy)~TV6{;VW>O9?5^ ztcoxHRa0jQY7>wwHWcxA-BCwzsP>63Kt&3fy*n#Cha687CQurXaRQnf5wc9o8v7Rw zNwGr2fac;Wr-Ldehn7tF^(-gPJwPt@VR1f;AmKgxN&YPL;j=0^xKM{!wuU|^mh3NE zy35quf}MeL!PU;|{OW_x$TBothLylT-J>_x6p}B_jW1L>k)ps6n%7Rh z96mPkJIM0QFNYUM2H}YF5bs%@Chs6#pEnloQhEl?J-)es!(SoJpEPoMTdgA14-#mC zghayD-DJWtUu`TD8?4mR)w5E`^EHbsz2EjH5aQLYRcF{l7_Q5?CEEvzDo(zjh|BKg z3aJl_n#j&eFHsUw4~lxqnr!6NL*se)6H=A+T1e3xUJGQrd}oSPwSy5+$tt{2t5J5@(lFxl43amsARG74iyNC}uuS zd2$=(r6RdamdGx^eatX@F2D8?U23tDpR+Os?0Gq2&^dF+$9wiWf?=mDWfjo4LfRwL zI#SRV9iSz>XCSgEj!cW&9H-njJopYiYuq|2w<5R2!nZ27DyvU4UDrHpoNQZiGPkp@ z1$h4H46Zn~eqdj$pWrv;*t!rTYTfZ1_bdkZmVVIRC21YeU$iS-*XMNK`#p8Z_DJx| zk3Jssf^XP7v0X?MWFO{rACltn$^~q(M9rMYoVxG$15N;nP)A98k^m3CJx8>6}NrUd@wp-E#$Q0uUDQT5GoiK_R{ z<{`g;8s>UFLpbga#DAf%qbfi`WN1J@6IA~R!YBT}qp%V-j!ybkR{uY0X|x)gmzE0J z&)=eHPjBxJvrZSOmt|)hC+kIMI;qgOnuL3mbNR0g^<%|>9x7>{}>a2qYSZAGPt4it?8 zNcLc!Gy0>$jaU?}ZWxK78hbhzE+etM`67*-*x4DN>1_&{@5t7_c*n(qz>&K{Y?10s zXsw2&nQev#SUSd|D8w7ZD2>E<%g^; zV{yE_O}gq?Q|zL|jdqB^zcx7vo(^})QW?QKacx$yR zhG|XH|8$vDZNIfuxr-sYFR{^csEI*IM#_gd;9*C+SysUFejP0{{z7@P?1+&_o6=7V|EJLQun^XEMS)w(=@eMi5&bbH*a0f;iC~2J74V2DZIlLUHD&>mlug5+v z6xBN~8-ovZylyH&gG#ptYsNlT?-tzOh%V#Y33zlsJ{AIju`CjIgf$@gr8}JugRq^c zAVQ3;&uGaVlVw}SUSWnTkH_6DISN&k2QLMBe9YU=sA+WiX@z)FoSYX`^k@B!j;ZeC zf&**P?HQG6Rk98hZ*ozn6iS-dG}V>jQhb3?4NJB*2F?6N7Nd;EOOo;xR7acylLaLy z9)^lykX39d@8@I~iEVar4jmjjLWhR0d=EB@%I;FZM$rykBNN~jf>#WbH4U{MqhhF6 zU??@fSO~4EbU4MaeQ_UXQcFyO*Rae|VAPLYMJEU`Q_Q_%s2*>$#S^)&7er+&`9L=1 z4q4ao07Z2Vsa%(nP!kJ590YmvrWg+YrgXYs_lv&B5EcoD`%uL79WyYA$0>>qi6ov7 z%`ia~J^_l{p39EY zv>>b}Qs8vxsu&WcXEt8B#FD%L%ZpcVtY!rqVTHe;$p9rbb5O{^rFMB>auLn-^;s+-&P1#h~mf~YLg$8M9 zZ4#87;e-Y6x6QO<{McUzhy(%*6| z)`D~A(TJ$>+0H+mct(jfgL4x%^oC^T#u(bL)`E2tBI#V1kSikAWmOOYrO~#-cc_8! zCe|@1&mN2{*ceeiBldHCdrURk4>V}79_*TVP3aCyV*5n@jiNbOm+~EQ_}1#->_tI@ zqXv+jj2#8xJtW508rzFrYcJxoek@iW6SR@1%a%Bux&;>25%`j3UI`0DaUr7l79`B1 zqqUARhW1^h6=)6?;@v>xrZNM;t}{yY3P@|L}ey@gG( z9r{}WoYN(9TW&dE2dEJIXkyHA4&pU6ki=rx&l2{DLGbVmg4%3Dlfvn!GB>EVaY_%3+Df{fBiqJV>~Xf8A0aqUjgpa} zoF8YXO&^_x*Ej}nw-$-F@(ddB>%RWoPUj?p8U{t0=n>gAI83y<9Ce@Q#3&(soJ{64 z37@Vij1}5fmzAuIUnXX`EYe;!H-yTVTmhAy;y8VZeB#vD{vw9~P#DiFiKQ|kWwGFZ z=jK;JX*A;Jr{#x?n8XUOLS;C%f|zj-7vXtlf_DtP7bpurBeX%Hjwr z4lI-2TdFpzkjgiv!8Vfv`=SP+s=^i3+N~1ELNWUbH|ytVu>EyPN_3(4TM^QE1swRo zoV7Y_g)a>28+hZG0e7g%@2^s>pzR4^fzR-El}ARTmtu!zjZLuX%>#OoU3}|rFjJg} zQ2TmaygxJ#sbHVyiA5KE+yH0LREWr%^C*yR|@gM$nK2P zo}M}PV0v))uJh&33N>#aU376@ZH79u(Yw`EQ2hM3SJs9f99+cO6_pNW$j$L-CtAfe zYfM)ccwD!P%LiBk!eCD?fHCGvgMQ%Q2oT_gmf?OY=A>&PaZQOq4eT=lwbaf}33LCH zFD|)lu{K7$8n9gX#w4~URjZxWm@wlH%oL#G|I~Fb-v^0L0TWu+`B+ZG!yII)w05DU z>GO?n(TN+B=>HdxVDSlIH76pta$_LhbBg;eZ`M7OGcqt||qi zogS72W1IN%=)5JCyOHWoFP7pOFK0L*OAh=i%&VW&4^LF@R;+K)t^S!96?}^+5QBIs zjJNTCh)?)4k^H^g1&jc>gysM`y^8Rm3qsvkr$9AeWwYpa$b22=yAd1t<*{ zaowSEFP+{y?Ob}8&cwfqoy4Pb9IA~VnM3u!trIK$&&0Op#Ql4j>(EW?UNUv#*iH1$ z^j>+W{afcd`{e&`-A{g}{JnIzYib)!T56IT@YEs{4|`sMpW3c8@UCoIJv`XsAw!XC z34|Il$LpW}CIHFC5e*)}00I5{%OL*WZRGzC0?_}-9{#ue?-ug^ zLE|uv-~6xnSs_2_&CN9{9vyc!Xgtn36_g^wI0C4s0s^;8+p?|mm;Odt3`2ZjwtK;l zfd6j)*Fr#53>C6Y8(N5?$H0ma;BCF3HCjUs7rpb2Kf*x3Xcj#O8mvs#&33i+McX zQpBxD8!O{5Y8D&0*QjD=Yhl9%M0)&_vk}bmN_Ud^BPN;H=U^bn&(csl-pkA+GyY0Z zKV7sU_4n;}uR78ouo8O%g*V;79KY?3d>k6%gpcmQsKk&@Vkw9yna_3asGt`0Hmj59 z%0yiF*`jXhByBI9QsD=+>big5{)BGe&+U2gAARGe3ID)xrid~QN_{I>k}@tzL!Md_ z&=7>TWciblF@EMC3t4-WX{?!m!G6$M$1S?NzF*2KHMP3Go4=#ZHkeIv{eEd;s-yD# z_jU^Ba06TZqvV|Yd;Z_sN%$X=!T+&?#p+OQIHS%!LO`Hx0q_Y0MyGYFNoM{W;&@0@ zLM^!X4KhdtsET5G<0+|q0oqVXMW~-7LW9Bg}=E$YtNh1#1D^6Mz(V9?2g~I1( zoz9Cz=8Hw98zVLwC2AQvp@pBeKyidn6Xu0-1SY1((^Hu*-!HxFUPs)yJ+i`^BC>PC zjwd0mygOVK#d2pRC9LxqGc6;Ui>f{YW9Bvb>33bp^NcnZoH~w9(lM5@JiIlfa-6|k ziy31UoMN%fvQfhi8^T+=yrP{QEyb-jK~>$A4SZT-N56NYEbpvO&yUme&pWKs3^94D zH{oXnUTb3T@H+RgzML*lejx`WAyw*?K7B-I(VJx($2!NXYm%3`=F~TbLv3H<{>D?A zJo-FDYdSA-(Y%;4KUP2SpHKAIcv9-ld(UEJE7=TKp|Gryn;72?0LHqAN^fk6%8PCW z{g_-t)G5uCIf0I`*F0ZNl)Z>))MaLMpXgqWgj-y;R+@A+AzDjsTqw2Mo9ULKA3c70 z!7SOkMtZb+MStH>9MnvNV0G;pwSW9HgP+`tg}e{ij0H6Zt5zJ7iw`hEnvye!XbA@!~#%vIkzowCOvq5I5@$3wtc*w2R$7!$*?}vg4;eDyJ_1=ixJuEp3pUS27W?qq(P^8$_lU!mRChT}ctvZz4p!X^ zOSp|JOAi~f?UkwH#9k{0smZ7-#=lK6X3OFEMl7%)WIcHb=#ZN$L=aD`#DZKOG4p4r zwlQ~XDZ`R-RbF&hZZhu3(67kggsM-F4Y_tI^PH8PMJRcs7NS9ogF+?bZB*fcpJ z=LTM4W=N9yepVvTj&Hu~0?*vR1HgtEvf8w%Q;U0^`2@e8{SwgX5d(cQ|1(!|i$km! zvY03MK}j`sff;*-%mN~ST>xU$6Bu?*Hm%l@0dk;j@%>}jsgDcQ)Hn*UfuThz9(ww_ zasV`rSrp_^bp-0sx>i35FzJwA!d6cZ5#5#nr@GcPEjNnFHIrtUYm1^Z$;{d&{hQV9 z6EfFHaIS}46p^5I-D_EcwwzUUuO}mqRh&T7r9sfw`)G^Q%oHxEs~+XoM?8e*{-&!7 z7$m$lg9t9KP9282eke608^Q2E%H-xm|oJ8=*SyEo} z@&;TQ3K)jgspgKHyGiKVMCz>xmC=H5Fy3!=TP)-R3|&1S-B)!6q50wfLHKM@7Bq6E z44CY%G;GY>tC`~yh!qv~YdXw! zSkquvYNs6k1r7>Eza?Vkkxo6XRS$W7EzL&A`o>=$HXgBp{L(i^$}t`NcnAxzbH8Ht z2!;`bhKIh`f1hIFcI5bHI=ueKdzmB9)!z$s-BT4ItyY|NaA_+o=jO%MU5as9 zc2)aLP>N%u>wlaXTK!p)r?+~)L+0eCGb5{8WIk7K52$nufnQ+m8YF+GQc&{^(zh-$ z#wyWV*Zh@d!b(WwXqvfhQX)^aoHTBkc;4ossV3&Ut*k>AI|m+{#kh4B!`3*<)EJVj zwrxK>99v^k4&Y&`Awm>|exo}NvewV%E+@vOc>5>%H#BK9uaE2$vje zWYM5fKuOTtn96B_2~~!xJPIcXF>E_;yO8AwpJ4)V`Hht#wbO3Ung~@c%%=FX4)q+9 z99#>VC2!4l`~0WHs9FI$Nz+abUq# zz`Of97})Su=^rGp2S$)7N3rQCj#0%2YO<R&p>$<#lgXcUj=4H_{oAYiT3 z44*xDn-$wEzRw7#@6aD)EGO$0{!C5Z^7#yl1o;k0PhN=aVUQu~eTQ^Xy{z8Ow6tk83 z4{5xe%(hx)%nD&|e*6sTWH`4W&U!Jae#U4TnICheJmsw{l|CH?UA{a6?2GNgpZLyzU2UlFu1ZVwlALmh_DOs03J^Cjh1im`E3?9&zvNmg(MuMw&0^Lu$(#CJ*q6DjlKsY-RMJ^8yIY|{SQZ*9~CH|u9L z`R78^r=EbbR*_>5?-)I+$6i}G)%mN(`!X72KaV(MNUP7Nv3MS9S|Pe!%N2AeOt5zG zVJ;jI4HZ$W->Ai_4X+`9c(~m=@ek*m`ZQbv3ryI-AD#AH=`x$~WeW~M{Js57(K7(v ze5`};LG|%C_tmd>bkufMWmAo&B+DT9ZV~h(4jg0>^aeAqL`PEUzJJtI8W1M!bQWpv zvN(d}E1@nlYa!L!!A*RN!(Q3F%J?5PvQ0udu?q-T)j3JKV~NL>KRb~w-lWc685uS6 z=S#aR&B8Sc8>cGJ!!--?kwsJTUUm`Jk?7`H z7PrO~xgBrSW2_tTlCq1LH8*!o?pj?qxy8}(=r_;G18POrFh#;buWR0qU24+XUaVZ0 z?(sXcr@-YqvkCmHr{U2oPogHL{r#3r49TeR<{SJX1pcUqyWPrkYz^X8#QW~?F)R5i z>p^!i<;qM8Nf{-fd6!_&V*e_9qP6q(s<--&1Ttj01j0w>bXY7y1W*%Auu&p|XSOH=)V7Bd4fUKh&T1)@cvqhuD-d=?w}O zjI%i(f|thk0Go*!d7D%0^ztBfE*V=(ZIN84f5HU}T9?ulmEYzT5usi=DeuI*d|;M~ zp_=Cx^!4k#=m_qSPBr5EK~E?3J{dWWPH&oCcNepYVqL?nh4D5ynfWip$m*YlZ8r^Z zuFEUL-nW!3qjRCLIWPT0x)FDL7>Yt7@8dA?R2kF@WE>ysMY+)lTsgNM#3VbXVGL}F z1O(>q>2a+_`6r5Xv$NZAnp=Kgnr3)cL(^=8ypEeOf3q8(HGe@7Tt59;yFl||w|mnO zHDxg2G3z8=(6wjj9kbcEY@Z0iOd7Gq5GiPS5% z*sF1J<#daxDV2Z8H>wxOF<;yKzMeTaSOp_|XkS9Sfn6Mpe9UBi1cSTieGG5$O;ZLIIJ60Y>SN4vC?=yE_CWlo(EEE$e4j?z&^FM%kNmRtlbEL^dPPgvs9sbK5fGw*r@ z+!EU@u$T8!nZh?Fdf_qk$VuHk^yVw`h`_#KoS*N%epIIOfQUy_&V}VWDGp3tplMbf z5Se1sJUC$7N0F1-9jdV2mmGK{-}fu|Nv;12jDy0<-kf^AmkDnu6j~TPWOgy1MT68|D z=4=50jVbUKdKaQgD`eWGr3I&^<6uhkjz$YwItY8%Yp9{z4-{6g{73<_b*@XJ4Nm3-3z z?BW3{aY_ccRjb@W1)i5nLg|7BnWS!B`_Uo9CWaE`Ij327QH?i)9A}4Ug4wmxVVa^b z-4+m%-wwOl7cKH7+=x&nrCrbEC)Q$fpg&V83#uEH;C=GNMz`ps@^RxK%T*8%OPnC` z{WO~J%nxYJ`x|N%?&i7?;{_8t^jM&=50HlaOQj8fS}_`moH$c;vI<|cruPFnpT8yU zS%rPOCUSd5Zdb(zwk`hqwTQn)*&n)uYsP*F_(~xEWq}C= zv30kFmZFwJZ@ELVX3?$dXQh|icO7UrL*_5G=I^xXjImz`ZPp>?g#tf(ej~KaIU0algsG!IS09;>?MvqGg#c{i+}qY|{P8W~O%#>|gFd z<1dr$-oxyRGN17yZo1OwLnzwYs0|;IS_nymNB0IlSzPQ%-r`?T=;_XQ^~&#}b|AB} zkNbN5uB?-sUB-T5QLlg%Uk3)uHB;>VIzGe9_J9 zaeISkQm!v(9d(0ML^b9fR^sfHFlH?7Mvddt37OuR{|O0{uv)(&-6<87W4 zyO>s!=cPgP3O&7xxU5DlIPw_o3O>6o6Qb?JWs3qw#p3sBc3g$?Dx zi(6D+DYgV;GrUis-CL%Qe{nvZnwaVXmbhH(|GFh|Q)k=1uvA$I@1DXI7bKlQ@8D6P zS?(*?><>)G49q0wr;NajpxP4W2G)kHl6^=Z>hrNEI4Mwd_$O6$1dXF;Q#hE(-eeW6 zz03GJF%Wl?HO=_ztv5*zRlcU~{+{k%#N59mgm~eK>P!QZ6E?#Cu^2)+K8m@ySvZ*5 z|HDT}BkF@3!l(0%75G=1u2hETXEj!^1Z$!)!lyGXlWD!_vqGE$Z)#cUVBqlORW>0^ zDjyVTxwKHKG|0}j-`;!R-p>}qQfBl(?($7pP<+Y8QE#M8SCDq~k<+>Q^Zf@cT_WdX3~BSe z+|KK|7OL5Hm5(NFP~j>Ct3*$wi0n0!xl=(C61`q&cec@mFlH(sy%+RH<=s)8aAPN`SfJdkAQjdv82G5iRdv8 zh{9wHUZaniSEpslXl^_ODh}mypC?b*9FzLjb~H@3DFSe;D(A-K3t3eOTB(m~I6C;(-lKAvit(70k`%@+O*Ztdz;}|_TS~B?Tpmi=QKC^m_ z2YpEaT3iiz*;T~ap1yiA)a`dKMwu`^UhIUeltNQ1Yjo=q@bI@&3zH?rVUg=IxLy-ni zyxDu%-Fr{H6owTjZU2O5>nDb=q&Jz_TjeSq%!2m40x&U6w~GQ({quPL73IsJS;f`$ zsuhioqCBj(gJ>2hoo)Gou7(WP*pX)f=Y=!=k!&1K?EYY%jJ~X&DnK{^saPQK<1BJ z_A`_{%ZozcB(3w$z^To^6d|XuT@=X~wtW!+{4ID@N{AB~J6AL5vuY>JwvWCNFKsKh zd}@>q@_WV#QZ&UJ0#?X(pXR!oyXOEG3rqzHbCzGLONDb042i$})fM@XF)uSP(DHUc z^&{|$*xe{cs?Gp8=B%RY3L7#$ve$?TWh>MZdxF1zH1v}1z+$Ov#G7?%D)bBCyDe*% zSeKSpETC2V1){II>@UwJi>4uBN+iAx+82E~gb|Cr&8E^i&)A!uv-g?jzH99wU}8+# z$nh>yvb;TwZmS@7LrvuCu_d0-WxFNI&C7%sWuTL%YU!l|I1{|->=dlOeHOCtUO#zkS3ESO8LHV4hTdQL5EdV zuWD33fFPH}HPrW^s$Qn1Xgp&AT6<-He{{4%eIu3rN=iK|9mURdKXfB&Q?qGok%!cs ze53UP{Z!TO-Y@q2;;k2avA3`lm4OoN4@S*k=UA)7H;qZ`d8`XaYFCv?Ba+uGW@r5v z&&{nf(24WSBOhc7!qF^@0cz;XcUynNaj6w2349;s!K{KVqs5yS{ z7VubS`2OzT^5#1~6Tt^RTvt9-J|D2F>y~>2;jeF>g`hx5l%B3H=aLExQihuYngzlnBTYOTHJQMzl>kwqN5JYs)Ej zblA@ntkUS~xi+}y6|(81helS}Q~&VB37qyV|S3Y=><^1wh%msQM?fz z<58MX(=|PSUKCF#)dbhR%D&xgCD?$aR0qen+wpp6 zst}vX18!Be96TD??j1HsHTUx(a&@F?=gT`Q$oJFFyrh^;zgz!(NlAHGn0cJy@us=w zNhC#l5G;H}+>49Nsh12=ZPO2r*2OBQe5kpb&1?*PIBFitK8}FUfb~S-#hKfF0o#&d z#3aPkB$9scYku&kA6{0xHnBV#&Wei5J>5T-XX-gUXEPo+9b7WL=*XESc(3BshL`aj zXp}QIp*40}oWJt*l043e8_5;H5PI5c)U&IEw5dF(4zjX0y_lk9 zAp@!mK>WUqHo)-jop=DoK>&no>kAD=^qIE7qis&_*4~ z6q^EF$D@R~3_xseCG>Ikb6Gfofb$g|75PPyyZN&tiRxqovo_k zO|HA|sgy#B<32gyU9x^&)H$1jvw@qp+1b(eGAb)O%O!&pyX@^nQd^9BQ4{(F8<}|A zhF&)xusQhtoXOOhic=8#Xtt5&slLia3c*a?dIeczyTbC#>FTfiLST57nc3@Y#v_Eg#VUv zT8cKH#f3=1PNj!Oroz_MAR*pow%Y0*6YCYmUy^7`^r|j23Q~^*TW#cU7CHf0eAD_0 zEWEVddxFgQ7=!nEBQ|ibaScslvhuUk^*%b#QUNrEB{3PG@uTxNwW}Bs4$nS9wc(~O zG7Iq>aMsYkcr!9#A;HNsJrwTDYkK8ikdj{M;N$sN6BqJ<8~z>T20{J8Z2rRUuH7~3 z=tgS`AgxbBOMg87UT4Lwge`*Y=01Dvk>)^{Iu+n6fuVX4%}>?3czOGR$0 zpp*wp>bsFFSV`V;r_m+TZns$ZprIi`OUMhe^cLE$2O+pP3nP!YB$ry}2THx2QJs3< za1;>d-AggCarrQ>&Z!d@;mW+!q6eXhb&`GbzUDSxpl8AJ#Cm#tuc)_xh(2NV=5XMs zrf_ozRYO$NkC=pKFX5OH8v1>0i9Z$ec`~Mf+_jQ68spn(CJwclDhEEkH2Qw;${J$clv__nUjn5jA0wCLEnu1j;v!0vB>Ri6m9`;R{JMS%^)4FC zU0Z44+u$I$w=Bj|iu4DT5h~sS`C*zbmX?@-crY}E+hy>}2~C0Nn(EKk@5^qO4@l@! z6O0lr%tzGC`D^)8xU3FnMZVm0kX1sBWhaQyzVoXFWwr%Ny?=2M{5s#5i7fTu3gEkG zc{(Pr$v=;`Y#&`y*J}#M9ux>0?xu!`$9cUKm#Bdd_&S#LPTS?ZPV6zN6>W6JTS~-LfjL{mB=b(KMk3 z2HjBSlJeyUVqDd=Mt!=hpYsvby2GL&3~zm;0{^nZJq+4vb?5HH4wufvr}IX42sHeK zm@x?HN$8TsTavXs)tLDFJtY9b)y~Tl@7z4^I8oUQq4JckH@~CVQ;FoK(+e0XAM>1O z(ei}h?)JQp>)d=6ng-BZF1Z5hsAKW@mXq+hU?r8I(*%`tnIIOXw7V6ZK(T9RFJJe@ zZS!aC+p)Gf2Ujc=a6hx4!A1Th%YH!Lb^xpI!Eu` zmJO{9rw){B1Ql18d%F%da+Tbu1()?o(zT7StYqK6_w`e+fjXq5L^y(0 z09QA6H4oFj59c2wR~{~>jUoDzDdKz}5#onYPJRwa`SUO)Pd4)?(ENBaFVLJr6Kvz= zhTtXqbx09C1z~~iZt;g^9_2nCZ{};-b4dQJbv8HsWHXPVg^@(*!@xycp#R?a|L!+` zY5w))JWV`Gls(=}shH0#r*;~>_+-P5Qc978+QUd>J%`fyn{*TsiG-dWMiJXNgwBaT zJ=wgYFt+1ACW)XwtNx)Q9tA2LPoB&DkL16P)ERWQlY4%Y`-5aM9mZ{eKPUgI!~J3Z zkMd5A_p&v?V-o-6TUa8BndiX?ooviev(DKw=*bBVOW|=zps9=Yl|-R5@yJe*BPzN}a0mUsLn{4LfjB_oxpv(mwq# zSY*%E{iB)sNvWfzg-B!R!|+x(Q|b@>{-~cFvdDHA{F2sFGA5QGiIWy#3?P2JIpPKg6ncI^)dvqe`_|N=8 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..8de1053a1 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mci_sample_test.http b/mci_sample_test.http new file mode 100644 index 000000000..fd34ef20e --- /dev/null +++ b/mci_sample_test.http @@ -0,0 +1,35 @@ +### 대내 MCI 샘플 툴(send_mci_sample) 테스트 +# 이 스크립트는 IntelliJ IDEA의 HTTP Client 플러그인을 위한 테스트 파일입니다. +# 1. 서버(Gateway 및 Tool 모듈)를 먼저 실행해 주세요. +# 2. 아래 초록색 플레이(▶) 버튼을 누르면 툴 함수가 호출됩니다! + +POST http://localhost:8081/mcp/api/v1/tools/call +Content-Type: application/json +Accept: application/json + +{ + "jsonrpc": "2.0", + "id": "test-1234", + "params": { + "name": "other_send_mci_sample", + "arguments": { + "tgrmCmnnhddValu": { + "envrTypeCd": "D", + "itrIfId": "IF_TEST_001" + }, + "tgrmMsdvValu": { + "msgHddvValu": { + "msgTnsmTypeCd": "0" + }, + "msgDtdvValu": { + "msgCd": "0000" + } + }, + "tgrmDtdvValu": { + "customerName": "홍길동", + "targetDate": "20260901", + "remarks": "MCI 연동 테스트" + } + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..5c2197505 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'dap-was-dapmt' + +include 'dap-was-lib' +include 'dap-was-sms' +include 'dap-was-oth' diff --git a/sql.txt b/sql.txt new file mode 100644 index 000000000..959f76b92 --- /dev/null +++ b/sql.txt @@ -0,0 +1,283 @@ +-------------------------------------------------------------------------------- +-- ?곸슜?쒖뒪?쒕챸 : ?댁쁺怨?ORACLE) +-- ?뚯씠釉?: ZT_?듯빀肄붾뱶 +-- ?뚯씠釉봊D : ZT_UNFC_CD +-------------------------------------------------------------------------------- +CREATE TABLE S_NNZ.ZT_UNFC_CD +( + UNFC_CD_ID VARCHAR2(50) NOT NULL, + UNFC_CD_NM VARCHAR2(100) NULL, + UNFC_CD_HAN_NM VARCHAR2(100) NULL, + UNFC_CD_ENG_NM VARCHAR2(100) NULL, + UNFC_CD_DS VARCHAR2(4000) NULL, + META_SYST_CD VARCHAR2(3) NOT NULL, + META_DUTJ_TYPE_CD VARCHAR2(5) NOT NULL, + PUSE_YN VARCHAR2(1) NOT NULL, + SPPO_UNFC_CD_ID VARCHAR2(50) NULL, + DATA_LOAD_DT DATE NULL, + SYST_RGI_DT DATE NOT NULL, + SYST_RGI_PRAF_NO VARCHAR2(8) NOT NULL, + SYST_RGI_OGNZ_NO VARCHAR2(7) NOT NULL, + SYST_RGI_SYST_CD VARCHAR2(3) NOT NULL, + SYST_RGI_PRGR_ID VARCHAR2(100) NOT NULL, + SYST_CHG_DT DATE NOT NULL, + SYST_CHG_PRAF_NO VARCHAR2(8) NOT NULL, + SYST_CHG_OGNZ_NO VARCHAR2(7) NOT NULL, + SYST_CHG_SYST_CD VARCHAR2(3) NOT NULL, + SYST_CHG_PRGR_ID VARCHAR2(100) NOT NULL +) +TABLESPACE TS_ZT_D01; + + + +COMMENT ON TABLE S_NNZ.ZT_UNFC_CD IS 'ZT_?듯빀肄붾뱶'; + +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.UNFC_CD_ID IS '?듯빀肄붾뱶ID'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.UNFC_CD_NM IS '?듯빀肄붾뱶紐?; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.UNFC_CD_HAN_NM IS '?듯빀肄붾뱶?쒓?紐?; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.UNFC_CD_ENG_NM IS '?듯빀肄붾뱶?곷Ц紐?; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.UNFC_CD_DS IS '?듯빀肄붾뱶?ㅻ챸'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.META_SYST_CD IS '硫뷀??쒖뒪?쒖퐫??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.META_DUTJ_TYPE_CD IS '硫뷀??낅Т?좏삎肄붾뱶'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.PUSE_YN IS '?ъ슜?щ?'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SPPO_UNFC_CD_ID IS '?곸쐞?듯빀肄붾뱶ID'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.DATA_LOAD_DT IS '?곗씠?곗쟻?ъ씪??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_RGI_DT IS '?쒖뒪?쒕벑濡앹씪??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_RGI_PRAF_NO IS '?쒖뒪?쒕벑濡앹씤?щ쾲??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_RGI_OGNZ_NO IS '?쒖뒪?쒕벑濡앹“吏곷쾲??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_RGI_SYST_CD IS '?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_RGI_PRGR_ID IS '?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_CHG_DT IS '?쒖뒪?쒕?寃쎌씪??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_CHG_PRAF_NO IS '?쒖뒪?쒕?寃쎌씤?щ쾲??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_CHG_OGNZ_NO IS '?쒖뒪?쒕?寃쎌“吏곷쾲??; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_CHG_SYST_CD IS '?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN S_NNZ.ZT_UNFC_CD.SYST_CHG_PRGR_ID IS '?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D'; + + + +-- 媛쒕퀎沅뚰븳 Role +GRANT SELECT ON S_NNZ.ZT_UNFC_CD TO RL_A_EAI_NNZ_SEL; + +GRANT INSERT,UPDATE,DELETE ON S_NNZ.ZT_UNFC_CD TO RL_A_EAI_NNZ_IUD; + +-- Default Role +GRANT SELECT ON S_NNZ.ZT_UNFC_CD TO RL_NNZ_SEL; + +GRANT INSERT,UPDATE,DELETE ON S_NNZ.ZT_UNFC_CD TO RL_NNZ_IUD; + + + +CREATE INDEX S_NNZ.IX_ZT_UNFC_CD_01 ON S_NNZ.ZT_UNFC_CD +(UNFC_CD_NM) + TABLESPACE TS_ZT_I01; + +CREATE INDEX S_NNZ.IX_ZT_UNFC_CD_02 ON S_NNZ.ZT_UNFC_CD +(UNFC_CD_HAN_NM) + TABLESPACE TS_ZT_I01; + +CREATE INDEX S_NNZ.IX_ZT_UNFC_CD_03 ON S_NNZ.ZT_UNFC_CD +(UNFC_CD_ENG_NM) + TABLESPACE TS_ZT_I01; + + +CREATE UNIQUE INDEX S_NNZ.PK_ZT_UNFC_CD ON S_NNZ.ZT_UNFC_CD + (UNFC_CD_ID) + TABLESPACE TS_ZT_I01; + +ALTER TABLE S_NNZ.ZT_UNFC_CD ADD ( + CONSTRAINT PK_ZT_UNFC_CD + PRIMARY KEY (UNFC_CD_ID) +USING INDEX S_NNZ.PK_ZT_UNFC_CD); + +-------------------------------------------------------------------------------- +-- ?곸슜?쒖뒪?쒕챸 : 媛쒕컻怨?ORACLE) +-- ?뚯씠釉?: ZT_?듯빀肄붾뱶?곸꽭 +-- ?뚯씠釉봊D : ZT_UNFC_CD_DTPT +-------------------------------------------------------------------------------- +CREATE TABLE S_TFG.ZT_UNFC_CD_DTPT +( + UNFC_CD_ID VARCHAR2(50) NOT NULL, + CD_VLDT_VALU VARCHAR2(50) NOT NULL, + CD_VLDT_VALU_INQR_SEQ NUMBER(5) NULL, + CD_VLDT_VALU_NM VARCHAR2(400) NULL, + CD_VLDT_VALU_HNGL_ABR_NM VARCHAR2(200) NULL, + CD_VLDT_VALU_ENGC_ABR_NM VARCHAR2(200) NULL, + CD_VLDT_VALU_HAN_NM VARCHAR2(400) NULL, + CD_VLDT_VALU_ENG_NM VARCHAR2(400) NULL, + CD_VLDT_VALU_DS VARCHAR2(4000) NULL, + CD_ASRT_TYPE_BIT_CD VARCHAR2(20) NOT NULL, + SPPO_CD_VLDT_VALU VARCHAR2(50) NULL, + SPPO_UNFC_CD_ID VARCHAR2(50) NULL, + VLDT_STRT_YMD DATE NOT NULL, + VLDT_END_YMD DATE NOT NULL, + USER_DEF_VALU_1 VARCHAR2(100) NULL, + USER_DEF_VALU_2 VARCHAR2(100) NULL, + SYST_RGI_DT DATE NOT NULL, + SYST_RGI_PRAF_NO VARCHAR2(8) NOT NULL, + SYST_RGI_OGNZ_NO VARCHAR2(7) NOT NULL, + SYST_RGI_SYST_CD VARCHAR2(3) NOT NULL, + SYST_RGI_PRGR_ID VARCHAR2(100) NOT NULL, + SYST_CHG_DT DATE NOT NULL, + SYST_CHG_PRAF_NO VARCHAR2(8) NOT NULL, + SYST_CHG_OGNZ_NO VARCHAR2(7) NOT NULL, + SYST_CHG_SYST_CD VARCHAR2(3) NOT NULL, + SYST_CHG_PRGR_ID VARCHAR2(100) NOT NULL, + DATA_LOAD_DT DATE NOT NULL +) +TABLESPACE TS_TFG_D01; + + + +COMMENT ON TABLE S_TFG.ZT_UNFC_CD_DTPT IS 'ZT_?듯빀肄붾뱶?곸꽭'; + +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.UNFC_CD_ID IS '?듯빀肄붾뱶ID'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU IS '肄붾뱶?좏슚媛?; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_INQR_SEQ IS '肄붾뱶?좏슚媛믪“?뚯닚??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_NM IS '肄붾뱶?좏슚媛믩챸'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_HNGL_ABR_NM IS '肄붾뱶?좏슚媛믫븳湲€?쎌뼱紐?; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_ENGC_ABR_NM IS '肄붾뱶?좏슚媛믪쁺臾몄빟?대챸'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_HAN_NM IS '肄붾뱶?좏슚媛믫븳湲€紐?; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_ENG_NM IS '肄붾뱶?좏슚媛믪쁺臾몃챸'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_VLDT_VALU_DS IS '肄붾뱶?좏슚媛믪꽕紐?; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.CD_ASRT_TYPE_BIT_CD IS '肄붾뱶遺꾨쪟?좏삎鍮꾪듃肄붾뱶'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SPPO_CD_VLDT_VALU IS '?곸쐞肄붾뱶?좏슚媛?; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SPPO_UNFC_CD_ID IS '?곸쐞?듯빀肄붾뱶ID'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.VLDT_STRT_YMD IS '?좏슚?쒖옉?쇱옄'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.VLDT_END_YMD IS '?좏슚醫낅즺?쇱옄'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.USER_DEF_VALU_1 IS '?ъ슜?먯젙?섍컪1'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.USER_DEF_VALU_2 IS '?ъ슜?먯젙?섍컪2'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_RGI_DT IS '?쒖뒪?쒕벑濡앹씪??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_RGI_PRAF_NO IS '?쒖뒪?쒕벑濡앹씤?щ쾲??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_RGI_OGNZ_NO IS '?쒖뒪?쒕벑濡앹“吏곷쾲??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_RGI_SYST_CD IS '?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_RGI_PRGR_ID IS '?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_CHG_DT IS '?쒖뒪?쒕?寃쎌씪??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_CHG_PRAF_NO IS '?쒖뒪?쒕?寃쎌씤?щ쾲??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_CHG_OGNZ_NO IS '?쒖뒪?쒕?寃쎌“吏곷쾲??; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_CHG_SYST_CD IS '?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.SYST_CHG_PRGR_ID IS '?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D'; +COMMENT ON COLUMN S_TFG.ZT_UNFC_CD_DTPT.DATA_LOAD_DT IS '?곗씠?곗쟻?ъ씪??; + + + +-- Default Role +GRANT INSERT,UPDATE,DELETE ON S_TFG.ZT_UNFC_CD_DTPT TO RL_TFG_IUD; + +GRANT SELECT ON S_TFG.ZT_UNFC_CD_DTPT TO RL_TFG_SEL; + + + +CREATE UNIQUE INDEX S_TFG.PK_ZT_UNFC_CD_DTPT ON S_TFG.ZT_UNFC_CD_DTPT + (UNFC_CD_ID,CD_VLDT_VALU) + TABLESPACE TS_TFG_I01; + +ALTER TABLE S_TFG.ZT_UNFC_CD_DTPT ADD ( + CONSTRAINT PK_ZT_UNFC_CD_DTPT + PRIMARY KEY (UNFC_CD_ID,CD_VLDT_VALU) +USING INDEX S_TFG.PK_ZT_UNFC_CD_DTPT); + +------------------------------------------------------------------------------------------- +-- ZT_MENU ?뚯씠釉??앹꽦 (硫붾돱 湲곕낯 ?뺣낫 ?€?? +CREATE TABLE ZT_MENU ( + MENU_ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- ?먮룞 利앷? ID (PK) + PARENT_MENU_ID INTEGER, -- 遺€紐?硫붾돱 ID (FK, NULL 媛€??for 猷⑦듃 硫붾돱) + NAME VARCHAR(100), -- 而댄룷?뚰듃 ?대쫫 (?듭뀛?? + PATH VARCHAR(255) NOT NULL, -- ?쇱슦??寃쎈줈 (?꾩닔) + TITLE VARCHAR(100) NOT NULL, -- 硫붾돱 ?대쫫 (meta.title, ?꾩닔) + ICON VARCHAR(50), -- 硫붾돱 ?꾩씠肄?(?듭뀛?? + SHOW_BADGE VARCHAR(1) DEFAULT 'N' CHECK (SHOW_BADGE IN ('Y', 'N')), -- 諛곗? ?쒖떆 ?щ? (Y/N, ?듭뀛?? + SHOW_TEXT_BADGE VARCHAR(50), -- ?띿뒪??諛곗? ?쒖떆 (?듭뀛?? + IS_HIDE VARCHAR(1) DEFAULT 'N' CHECK (IS_HIDE IN ('Y', 'N')), -- 硫붾돱 ?④? ?щ? (Y/N, ?듭뀛?? + IS_HIDE_TAB VARCHAR(1) DEFAULT 'N' CHECK (IS_HIDE_TAB IN ('Y', 'N')),-- ???④? ?щ? (Y/N, ?듭뀛?? + LINK VARCHAR(255), -- 留곹겕 (?듭뀛?? + IS_IFRAME VARCHAR(1) DEFAULT 'N' CHECK (IS_IFRAME IN ('Y', 'N')), -- iframe ?щ? (Y/N, ?듭뀛?? + KEEP_ALIVE VARCHAR(1) DEFAULT 'N' CHECK (KEEP_ALIVE IN ('Y', 'N')), -- 罹먯떆 ?щ? (Y/N, ?듭뀛?? + ORDER_SEQ INTEGER DEFAULT 0 NOT NULL, -- 硫붾돱 ?쒖떆 ?쒖꽌 (?뺣젹?? ?꾩닔) + IS_ACTIVE VARCHAR(1) DEFAULT 'Y' CHECK (IS_ACTIVE IN ('Y', 'N')), -- ?쒖꽦 ?щ? (Y/N, 湲곕낯 Y) + SYST_RGI_DT DATE, -- ?쒖뒪?쒕벑濡앹씪?? + SYST_RGI_PRAF_NO VARCHAR(50), -- ?쒖뒪?쒕벑濡앹씤?щ쾲?? + SYST_RGI_OGNZ_NO VARCHAR(50), -- ?쒖뒪?쒕벑濡앹“吏곷쾲?? + SYST_RGI_SYST_CD VARCHAR(50), -- ?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶 + SYST_RGI_PRGR_ID VARCHAR(50), -- ?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D + SYST_CHG_DT DATE, -- ?쒖뒪?쒕?寃쎌씪?? + SYST_CHG_PRAF_NO VARCHAR(50), -- ?쒖뒪?쒕?寃쎌씤?щ쾲?? + SYST_CHG_OGNZ_NO VARCHAR(50), -- ?쒖뒪?쒕?寃쎌“吏곷쾲?? + SYST_CHG_SYST_CD VARCHAR(50), -- ?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶 + SYST_CHG_PRGR_ID VARCHAR(50) -- ?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D +); + +-- ?쒖빟 議곌굔 異붽? +ALTER TABLE ZT_MENU ADD CONSTRAINT FK_ZT_MENU_PARENT FOREIGN KEY (PARENT_MENU_ID) REFERENCES ZT_MENU (MENU_ID) ON DELETE CASCADE; -- 遺€紐???젣 ???먯떇????젣 (怨꾩링 ??젣 吏€?? + +-- ?몃뜳???앹꽦 (議고쉶 理쒖쟻?? +CREATE INDEX IDX_ZT_MENU_PARENT ON ZT_MENU (PARENT_MENU_ID); +CREATE INDEX IDX_ZT_MENU_PATH ON ZT_MENU (PATH); + +-- 而щ읆 肄붾찘??異붽? +COMMENT ON COLUMN ZT_MENU.MENU_ID IS '硫붾돱 ID (PK)'; +COMMENT ON COLUMN ZT_MENU.PARENT_MENU_ID IS '遺€紐?硫붾돱 ID (FK)'; +COMMENT ON COLUMN ZT_MENU.NAME IS '而댄룷?뚰듃 ?대쫫'; +COMMENT ON COLUMN ZT_MENU.PATH IS '?쇱슦??寃쎈줈'; +COMMENT ON COLUMN ZT_MENU.TITLE IS '硫붾돱 ?대쫫'; +COMMENT ON COLUMN ZT_MENU.ICON IS '硫붾돱 ?꾩씠肄?; +COMMENT ON COLUMN ZT_MENU.SHOW_BADGE IS '諛곗? ?쒖떆 ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.SHOW_TEXT_BADGE IS '?띿뒪??諛곗? ?쒖떆'; +COMMENT ON COLUMN ZT_MENU.IS_HIDE IS '硫붾돱 ?④? ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.IS_HIDE_TAB IS '???④? ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.LINK IS '留곹겕'; +COMMENT ON COLUMN ZT_MENU.IS_IFRAME IS 'iframe ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.KEEP_ALIVE IS '罹먯떆 ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.ORDER_SEQ IS '硫붾돱 ?쒖떆 ?쒖꽌'; +COMMENT ON COLUMN ZT_MENU.IS_ACTIVE IS '?쒖꽦 ?щ? (Y/N)'; +COMMENT ON COLUMN ZT_MENU.SYST_RGI_DT IS '?쒖뒪?쒕벑濡앹씪??; +COMMENT ON COLUMN ZT_MENU.SYST_RGI_PRAF_NO IS '?쒖뒪?쒕벑濡앹씤?щ쾲??; +COMMENT ON COLUMN ZT_MENU.SYST_RGI_OGNZ_NO IS '?쒖뒪?쒕벑濡앹“吏곷쾲??; +COMMENT ON COLUMN ZT_MENU.SYST_RGI_SYST_CD IS '?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN ZT_MENU.SYST_RGI_PRGR_ID IS '?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D'; +COMMENT ON COLUMN ZT_MENU.SYST_CHG_DT IS '?쒖뒪?쒕?寃쎌씪??; +COMMENT ON COLUMN ZT_MENU.SYST_CHG_PRAF_NO IS '?쒖뒪?쒕?寃쎌씤?щ쾲??; +COMMENT ON COLUMN ZT_MENU.SYST_CHG_OGNZ_NO IS '?쒖뒪?쒕?寃쎌“吏곷쾲??; +COMMENT ON COLUMN ZT_MENU.SYST_CHG_SYST_CD IS '?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN ZT_MENU.SYST_CHG_PRGR_ID IS '?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D'; + +-- ZT_MENU_AUTH ?뚯씠釉??앹꽦 (硫붾돱 沅뚰븳 紐⑸줉 ?€?? +CREATE TABLE ZT_MENU_AUTH ( + AUTH_ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- ?먮룞 利앷? ID (PK) + MENU_ID INTEGER NOT NULL, -- 硫붾돱 ID (FK) + TITLE VARCHAR(100) NOT NULL, -- 沅뚰븳 ?쒕ぉ (e.g., ''異붽?'', ''?몄쭛'') + AUTH_MARK VARCHAR(100) NOT NULL, -- 沅뚰븳 留덊겕 (e.g., ''add'', ''edit'') + ORDER_SEQ INTEGER DEFAULT 0 NOT NULL, -- 沅뚰븳 ?쒖떆 ?쒖꽌 (?뺣젹?? + SYST_RGI_DT DATE, -- ?쒖뒪?쒕벑濡앹씪?? + SYST_RGI_PRAF_NO VARCHAR(50), -- ?쒖뒪?쒕벑濡앹씤?щ쾲?? + SYST_RGI_OGNZ_NO VARCHAR(50), -- ?쒖뒪?쒕벑濡앹“吏곷쾲?? + SYST_RGI_SYST_CD VARCHAR(50), -- ?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶 + SYST_RGI_PRGR_ID VARCHAR(50), -- ?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D + SYST_CHG_DT DATE, -- ?쒖뒪?쒕?寃쎌씪?? + SYST_CHG_PRAF_NO VARCHAR(50), -- ?쒖뒪?쒕?寃쎌씤?щ쾲?? + SYST_CHG_OGNZ_NO VARCHAR(50), -- ?쒖뒪?쒕?寃쎌“吏곷쾲?? + SYST_CHG_SYST_CD VARCHAR(50), -- ?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶 + SYST_CHG_PRGR_ID VARCHAR(50) -- ?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D +); + +-- ?쒖빟 議곌굔 異붽? +ALTER TABLE ZT_MENU_AUTH ADD CONSTRAINT FK_ZT_MENU_AUTH_MENU FOREIGN KEY (MENU_ID) REFERENCES ZT_MENU (MENU_ID) ON DELETE CASCADE; -- 硫붾돱 ??젣 ??沅뚰븳????젣 + +-- ?몃뜳???앹꽦 +CREATE INDEX IDX_ZT_MENU_AUTH_MENU ON ZT_MENU_AUTH (MENU_ID); + +-- 而щ읆 肄붾찘??異붽? +COMMENT ON COLUMN ZT_MENU_AUTH.AUTH_ID IS '沅뚰븳 ID (PK)'; +COMMENT ON COLUMN ZT_MENU_AUTH.MENU_ID IS '硫붾돱 ID (FK)'; +COMMENT ON COLUMN ZT_MENU_AUTH.TITLE IS '沅뚰븳 ?쒕ぉ'; +COMMENT ON COLUMN ZT_MENU_AUTH.AUTH_MARK IS '沅뚰븳 留덊겕'; +COMMENT ON COLUMN ZT_MENU_AUTH.ORDER_SEQ IS '沅뚰븳 ?쒖떆 ?쒖꽌'; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_RGI_DT IS '?쒖뒪?쒕벑濡앹씪??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_RGI_PRAF_NO IS '?쒖뒪?쒕벑濡앹씤?щ쾲??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_RGI_OGNZ_NO IS '?쒖뒪?쒕벑濡앹“吏곷쾲??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_RGI_SYST_CD IS '?쒖뒪?쒕벑濡앹떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_RGI_PRGR_ID IS '?쒖뒪?쒕벑濡앺봽濡쒓렇?쭵D'; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_CHG_DT IS '?쒖뒪?쒕?寃쎌씪??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_CHG_PRAF_NO IS '?쒖뒪?쒕?寃쎌씤?щ쾲??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_CHG_OGNZ_NO IS '?쒖뒪?쒕?寃쎌“吏곷쾲??; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_CHG_SYST_CD IS '?쒖뒪?쒕?寃쎌떆?ㅽ뀥肄붾뱶'; +COMMENT ON COLUMN ZT_MENU_AUTH.SYST_CHG_PRGR_ID IS '?쒖뒪?쒕?寃쏀봽濡쒓렇?쭵D'; diff --git a/sse_out.txt b/sse_out.txt new file mode 100644 index 000000000..085415956 --- /dev/null +++ b/sse_out.txt @@ -0,0 +1,2 @@ +event:endpoint +data:/mcp/message/common?sessionId=b21c1837-e1ef-43d6-890d-5a57ca7428e7