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 index 105337870..3695e6af0 100644 --- 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 @@ -21,7 +21,7 @@ import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.server.McpSyncServer; import io.modelcontextprotocol.spec.McpSchema; import io.shinhanlife.dap.mcc.dto.ToolMetadata; -import io.shinhanlife.dap.lib.presentation.BusinessToolController; +import io.shinhanlife.dap.mcc.presentation.BusinessToolController; import io.shinhanlife.dap.lib.mcp.LocalToolScanner; import java.util.LinkedHashMap; import java.util.Map; diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/BusinessToolController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/BusinessToolController.java deleted file mode 100644 index 4ca3f6d63..000000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/BusinessToolController.java +++ /dev/null @@ -1,244 +0,0 @@ -package io.shinhanlife.dap.lib.presentation; - - -/** - * @package io.shinhanlife.dap.mcc.presentation - * @className BusinessToolController - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.Error; -import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; -import io.shinhanlife.dap.lib.annotation.McpFunction; -import io.shinhanlife.dap.lib.annotation.McpTool; -import io.shinhanlife.dap.lib.config.McpProperties; -import io.shinhanlife.dap.mcc.dto.ToolMetadata; -import io.shinhanlife.dap.lib.mcp.LocalToolScanner; -import io.shinhanlife.dap.lib.util.ToolSchemaResolver; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.aop.support.AopUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.http.ResponseEntity; -import org.springframework.util.ClassUtils; -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; - -@Slf4j -@RestController -@RequestMapping("/") -@RequiredArgsConstructor -public class BusinessToolController { - - private final ApplicationContext applicationContext; - private final ObjectMapper objectMapper; - private final McpProperties mcpProperties; - private final LocalToolScanner localToolScanner; - private final ToolArgumentSchemaValidator toolArgumentSchemaValidator; - - private final ToolSchemaResolver toolSchemaResolver; - // 내부 조회용 로컬 Tool 목록 엔드포인트 - @GetMapping("/mcp/api/v1/tools/local") - public List getLocalTools() { - return localToolScanner.getAllScannedTools(); - } - - // 순수 REST 기반 동적 라우팅 엔드포인트 - @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) { - - String finalRequestId = headerRequestId; - - log.info(" [Tool] IN - trace-id: {}, request-id: {}", traceId, requestId); - log.info(" [Tool] 동적 툴 실행 요청 수신 (함수명): {}", functionName); - if (arguments != null) { - try { - log.info(" [Tool] 호출 파라미터: {}", objectMapper.writeValueAsString(arguments)); - } catch (Exception e) { - log.info(" [Tool] 호출 파라미터: {}", arguments); - } - } - Object targetBean = null; - Method targetMethod = null; - McpFunction targetFunctionAnnotation = null; - - // McpTool 어노테이션 기반 조회가 프록시 문제로 누락될 수 있으므로, 전체 빈을 순회하며 @McpFunction을 찾습니다. - Map allBeans = applicationContext.getBeansOfType(Object.class); - outerLoop: - for (Object bean : allBeans.values()) { - Class targetClass = AopUtils.getTargetClass(bean); - for (Method targetMethodOfClass : targetClass.getDeclaredMethods()) { - McpFunction mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpFunction.class); - if (mcpFunc != null) { - String baseName = mcpFunc.name(); - String expectedName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() - ? mcpProperties.getNamespace() + "_" + baseName - : baseName; - - if (expectedName.equals(functionName) || baseName.equals(functionName)) { - targetBean = bean; - try { - targetMethod = bean.getClass().getMethod(targetMethodOfClass.getName(), targetMethodOfClass.getParameterTypes()); - } catch (NoSuchMethodException e) { - targetMethod = targetMethodOfClass; - } - targetFunctionAnnotation = mcpFunc; - break outerLoop; - } - } - } - } - - if (targetBean == null || targetMethod == null) { - List availableFunctions = new ArrayList<>(); - for (Object bean : allBeans.values()) { - Class targetCls = AopUtils.getTargetClass(bean); - for (Method m : targetCls.getDeclaredMethods()) { - McpFunction func = AnnotationUtils.findAnnotation(m, McpFunction.class); - if (func != null) { - String baseName = func.name(); - String expName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() - ? mcpProperties.getNamespace() + "_" + baseName : baseName; - availableFunctions.add(expName + " (in " + targetCls.getSimpleName() + ")"); - } - } - } - log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}. 현재 스캔된 툴 메서드 목록: {}", functionName, availableFunctions); - Map errorDetails = new HashMap<>(); - errorDetails.put("status", "404"); - Map errorBody = new HashMap<>(); - errorBody.put("code", "TOOL_NOT_FOUND"); - errorBody.put("message", "실행할 함수를 찾을 수 없습니다: " + functionName); - errorBody.put("details", errorDetails); - if (finalRequestId != null) errorBody.put("request_id", finalRequestId); - - return ResponseEntity.status(404).body(errorBody); - } - // (기존 차단 로직 제거됨) - - // 2. 파라미터 유효성 검증 (JSON Schema) - if (targetMethod.getParameterCount() > 0) { - Class paramType = targetMethod.getParameterTypes()[0]; - if (!Map.class.isAssignableFrom(paramType)) { - try { - Map inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, paramType); - List errors = toolArgumentSchemaValidator.validate(inputSchema, arguments); - if (!errors.isEmpty()) { - log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors); - List errorMessages = new ArrayList<>(); - for (Error validationError : errors) { - errorMessages.add(validationError.getMessage()); - } - Map errorDetails = new HashMap<>(); - errorDetails.put("status", "422"); - Map errorBody = new HashMap<>(); - errorBody.put("code", "INVALID_PARAM"); - errorBody.put("message", "파라미터 유효성 검증 실패"); - errorBody.put("details", errorDetails); - if (finalRequestId != null) errorBody.put("request_id", finalRequestId); - - return ResponseEntity.status(422).body(errorBody); - } - } catch (Exception e) { - log.error("[Tool] 스키마 검증 중 오류 발생: {}", e.getMessage()); - } - } - } - - log.info("[Tool] 리플렉션 직접 실행 -> Method: {}", targetMethod.getName()); - - try { - // 3. DTO 파라미터 자동 매핑 (Map -> DTO) - Object invokeArgument = arguments; - if (targetMethod.getParameterCount() > 0 && arguments != null) { - Class paramType = targetMethod.getParameterTypes()[0]; - if (!Map.class.isAssignableFrom(paramType)) { - invokeArgument = objectMapper.convertValue(arguments, paramType); - log.info("[Tool] DTO 자동 매핑 성공: {}", paramType.getSimpleName()); - } - } - - long startTime = System.currentTimeMillis(); - Object methodResult = null; - if (targetMethod.getParameterCount() == 0) { - methodResult = targetMethod.invoke(targetBean); - } else { - methodResult = targetMethod.invoke(targetBean, invokeArgument); - } - - Map outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType()); - if (!outputSchema.isEmpty()) { - List outputErrors = toolArgumentSchemaValidator.validateValue(outputSchema, methodResult); - if (!outputErrors.isEmpty()) { - log.error("[Tool] Output schema validation failed. tool={}, errors={}", - functionName, outputErrors); - Map errorBody = new HashMap<>(); - errorBody.put("code", "INVALID_TOOL_RESPONSE"); - errorBody.put("message", "Tool response does not match its output schema"); - if (finalRequestId != null) { - errorBody.put("request_id", finalRequestId); - } - return ResponseEntity.internalServerError().body(errorBody); - } - } - - long elapsed = System.currentTimeMillis() - startTime; - - // 5. 결과 반환 (순수 REST 응답) - try { - log.info("[Tool Execution] Output Schema Result: {}", objectMapper.writeValueAsString(methodResult)); - } catch (Exception e) { - log.info("[Tool Execution] Output Schema Result: {}", methodResult); - } - - log.info(" [Tool] OUT - trace-id: {}, request-id: {}", traceId, requestId); - - ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok(); - if (traceId != null) responseBuilder.header("trace-id", traceId); - if (requestId != null) responseBuilder.header("request-id", requestId); - - return responseBuilder.body(methodResult); - - } catch (Exception e) { - log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage()); - Map errorDetails = new HashMap<>(); - errorDetails.put("status", "500"); - Map errorBody = new HashMap<>(); - errorBody.put("code", "TOOL_ERROR"); - errorBody.put("message", "Tool execution failed"); - errorDetails.clear(); // Hide details for upstream errors - errorBody.put("details", errorDetails); - if (finalRequestId != null) errorBody.put("request_id", finalRequestId); - - return ResponseEntity.status(502).body(errorBody); - } - } -} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/ToolManifestController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/ToolManifestController.java deleted file mode 100644 index 057a68acb..000000000 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/presentation/ToolManifestController.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.shinhanlife.dap.lib.presentation; - -/** - * @package io.shinhanlife.dap.lib.presentation - * @className ToolManifestController - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ - - -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/test/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilterTest.java index dffd2d178..7479ea3b8 100644 --- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilterTest.java +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilterTest.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.McpSyncServer; -import io.shinhanlife.dap.lib.presentation.BusinessToolController; +import io.shinhanlife.dap.mcc.presentation.BusinessToolController; import io.shinhanlife.dap.lib.mcp.LocalToolScanner; import java.lang.reflect.Method; import java.util.Map; 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 index 243283d36..f1f5b21d0 100644 --- 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 @@ -1,7 +1,7 @@ -package io.shinhanlife.dap.lib.presentation; +package io.shinhanlife.dap.mcc.presentation; /** - * @package io.shinhanlife.dap.lib.presentation + * @package io.shinhanlife.dap.mcc.presentation * @className BusinessToolControllerHeaderContractTest * @description AX HUB 시스템 처리 클래스 * @author 0986406 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 index d43fb9747..bb08a254c 100644 --- 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 @@ -1,7 +1,7 @@ -package io.shinhanlife.dap.lib.presentation; +package io.shinhanlife.dap.mcc.presentation; /** - * @package io.shinhanlife.dap.lib.presentation + * @package io.shinhanlife.dap.mcc.presentation * @className ToolTestConsoleResourceTest * @description AX HUB 시스템 처리 클래스 * @author 0986406