forked from kimhyungsik/ax_hub_mcp_tool
소스 수정
This commit is contained in:
@@ -23,7 +23,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
|
|||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StopWatch;
|
import org.springframework.util.StopWatch;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ public class ToolSlaMonitoringAspect {
|
|||||||
private final McpProperties mcpProperties;
|
private final McpProperties mcpProperties;
|
||||||
|
|
||||||
// @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다.
|
// @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다.
|
||||||
@Around("@annotation(org.springframework.ai.mcp.annotation.McpTool)")
|
@Around("@annotation(org.springaicommunity.mcp.annotation.McpTool)")
|
||||||
public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable {
|
public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method method = signature.getMethod();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.shinhanlife.dap.lib.config;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@@ -15,4 +16,9 @@ public class ToolSchemaConfiguration {
|
|||||||
public ToolSchemaResolver toolSchemaResolver(ObjectMapper objectMapper) {
|
public ToolSchemaResolver toolSchemaResolver(ObjectMapper objectMapper) {
|
||||||
return new ToolSchemaResolver(objectMapper);
|
return new ToolSchemaResolver(objectMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ToolArgumentSchemaValidator toolArgumentSchemaValidator(ObjectMapper objectMapper) {
|
||||||
|
return new ToolArgumentSchemaValidator(objectMapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.networknt.schema.Error;
|
||||||
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
|
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.springaicommunity.mcp.annotation.McpTool;
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/** Executes a discovered Tool independently from its HTTP or MCP transport. */
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class McpToolExecutionService {
|
||||||
|
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final McpProperties mcpProperties;
|
||||||
|
private final ToolArgumentSchemaValidator toolArgumentSchemaValidator;
|
||||||
|
private final ToolSchemaResolver toolSchemaResolver;
|
||||||
|
|
||||||
|
public ToolExecutionResult execute(String functionName, McpRequestHeaders requestHeaders,
|
||||||
|
Map<String, Object> 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);
|
||||||
|
|
||||||
|
ResolvedTool resolvedTool = findTool(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<String, String> 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 ResolvedTool findTool(String functionName) {
|
||||||
|
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 && matches(functionName, annotation.name())) {
|
||||||
|
return new ResolvedTool(bean, findInvocableMethod(bean, declaredMethod), annotation,
|
||||||
|
AnnotationUtils.findAnnotation(declaredMethod, ToolHint.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(String requestedName, String baseName) {
|
||||||
|
String namespace = mcpProperties.getNamespace();
|
||||||
|
String expectedName = namespace != null && !namespace.isEmpty() ? namespace + "_" + baseName : baseName;
|
||||||
|
return expectedName.equals(requestedName) || baseName.equals(requestedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findInvocableMethod(Object bean, Method declaredMethod) {
|
||||||
|
try {
|
||||||
|
return bean.getClass().getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes());
|
||||||
|
} catch (NoSuchMethodException ignored) {
|
||||||
|
return declaredMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolExecutionResult validateInput(ResolvedTool tool, Map<String, Object> arguments, String requestId) {
|
||||||
|
if (tool.method().getParameterCount() == 0 || Map.class.isAssignableFrom(tool.method().getParameterTypes()[0])) return null;
|
||||||
|
try {
|
||||||
|
Map<String, Object> schema = toolSchemaResolver.resolve(tool.annotation(), tool.hint(), tool.method().getParameterTypes()[0]);
|
||||||
|
List<Error> 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<String, Object> 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(ResolvedTool tool, Object argument) throws Exception {
|
||||||
|
return tool.method().getParameterCount() == 0 ? tool.method().invoke(tool.bean()) : tool.method().invoke(tool.bean(), argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolExecutionResult validateOutput(ResolvedTool tool, Object methodResult, String requestId) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> 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<String, Object> 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ResolvedTool(Object bean, Method method, McpTool annotation, ToolHint hint) { }
|
||||||
|
}
|
||||||
@@ -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<String, String> headers) {
|
||||||
|
|
||||||
|
public ToolExecutionResult {
|
||||||
|
headers = headers == null ? Map.of() : Map.copyOf(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return statusCode >= 200 && statusCode < 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,33 +5,29 @@ import io.modelcontextprotocol.server.McpServerFeatures;
|
|||||||
import io.modelcontextprotocol.server.McpSyncServer;
|
import io.modelcontextprotocol.server.McpSyncServer;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
|
||||||
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
|
||||||
import io.shinhanlife.dap.lib.mcp.McpRequestHeaderContext;
|
|
||||||
import io.shinhanlife.dap.lib.mcp.McpRequestHeaders;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
|
||||||
/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */
|
/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnBean(BusinessToolController.class)
|
@ConditionalOnBean(McpToolExecutionService.class)
|
||||||
public class ToolPodMcpToolSynchronizer {
|
public class ToolPodMcpToolSynchronizer {
|
||||||
private final McpSyncServer mcpServer;
|
private final McpSyncServer mcpServer;
|
||||||
private final ToolRegistryHeartbeatSender heartbeatSender;
|
private final ToolRegistryHeartbeatSender heartbeatSender;
|
||||||
private final BusinessToolController businessToolController;
|
private final McpToolExecutionService toolExecutionService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender,
|
public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender,
|
||||||
BusinessToolController businessToolController, ObjectMapper objectMapper) {
|
McpToolExecutionService toolExecutionService, ObjectMapper objectMapper) {
|
||||||
this.mcpServer = mcpServer;
|
this.mcpServer = mcpServer;
|
||||||
this.heartbeatSender = heartbeatSender;
|
this.heartbeatSender = heartbeatSender;
|
||||||
this.businessToolController = businessToolController;
|
this.toolExecutionService = toolExecutionService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +42,14 @@ public class ToolPodMcpToolSynchronizer {
|
|||||||
McpSchema.Tool mcpTool = McpSchema.Tool.builder()
|
McpSchema.Tool mcpTool = McpSchema.Tool.builder()
|
||||||
.name(tool.getName())
|
.name(tool.getName())
|
||||||
.description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription())
|
.description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription())
|
||||||
.inputSchema(tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema())
|
.inputSchema(toJsonSchema(tool.getParametersSchema()))
|
||||||
.annotations(McpSchema.ToolAnnotations.builder()
|
.annotations(new McpSchema.ToolAnnotations(
|
||||||
.readOnlyHint(Boolean.TRUE.equals(tool.getReadOnlyHint()))
|
tool.getDisplayName(),
|
||||||
.destructiveHint(Boolean.TRUE.equals(tool.getDestructiveHint()))
|
tool.getReadOnlyHint(),
|
||||||
.idempotentHint(Boolean.TRUE.equals(tool.getIdempotentHint()))
|
tool.getDestructiveHint(),
|
||||||
.openWorldHint(Boolean.TRUE.equals(tool.getOpenWorldHint())).build())
|
tool.getIdempotentHint(),
|
||||||
|
tool.getOpenWorldHint(),
|
||||||
|
null))
|
||||||
.build();
|
.build();
|
||||||
return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool)
|
return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool)
|
||||||
.callHandler((context, request) -> invoke(tool.getName(), McpRequestHeaderContext.current(), request.arguments())).build();
|
.callHandler((context, request) -> invoke(tool.getName(), McpRequestHeaderContext.current(), request.arguments())).build();
|
||||||
@@ -59,15 +57,9 @@ public class ToolPodMcpToolSynchronizer {
|
|||||||
|
|
||||||
private McpSchema.CallToolResult invoke(String toolName, McpRequestHeaders requestHeaders,
|
private McpSchema.CallToolResult invoke(String toolName, McpRequestHeaders requestHeaders,
|
||||||
Map<String, Object> arguments) {
|
Map<String, Object> arguments) {
|
||||||
ResponseEntity<?> response = businessToolController.executeDynamicTool(
|
ToolExecutionResult result = toolExecutionService.execute(toolName, requestHeaders, arguments);
|
||||||
toolName,
|
boolean failed = !result.isSuccess();
|
||||||
requestHeaders == null ? null : requestHeaders.headerRequestId(),
|
Object body = result.body();
|
||||||
requestHeaders == null ? null : requestHeaders.traceId(),
|
|
||||||
requestHeaders == null ? null : requestHeaders.requestId(),
|
|
||||||
requestHeaders == null ? null : requestHeaders.encryptedEmployeeId(),
|
|
||||||
arguments);
|
|
||||||
boolean failed = !response.getStatusCode().is2xxSuccessful();
|
|
||||||
Object body = response.getBody();
|
|
||||||
try {
|
try {
|
||||||
return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body))
|
return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body))
|
||||||
.structuredContent(body).isError(failed).build();
|
.structuredContent(body).isError(failed).build();
|
||||||
@@ -83,4 +75,19 @@ public class ToolPodMcpToolSynchronizer {
|
|||||||
schema.put("additionalProperties", false);
|
schema.put("additionalProperties", false);
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private McpSchema.JsonSchema toJsonSchema(Map<String, Object> source) {
|
||||||
|
Map<String, Object> schema = source == null ? emptySchema() : source;
|
||||||
|
return new McpSchema.JsonSchema(
|
||||||
|
String.valueOf(schema.getOrDefault("type", "object")),
|
||||||
|
schema.get("properties") instanceof Map<?, ?> properties
|
||||||
|
? (Map<String, Object>) properties : Map.of(),
|
||||||
|
schema.get("required") instanceof List<?> required
|
||||||
|
? (List<String>) required : List.of(),
|
||||||
|
schema.get("additionalProperties") instanceof Boolean additionalProperties
|
||||||
|
? additionalProperties : Boolean.TRUE,
|
||||||
|
schema.get("$defs") instanceof Map<?, ?> defs ? (Map<String, Object>) defs : Map.of(),
|
||||||
|
schema.get("definitions") instanceof Map<?, ?> definitions
|
||||||
|
? (Map<String, Object>) definitions : Map.of());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ package io.shinhanlife.dap.lib.mcp;
|
|||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
import io.shinhanlife.dap.lib.config.McpProperties;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
@@ -44,14 +44,13 @@ import org.springframework.util.ClassUtils;
|
|||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import io.shinhanlife.dap.mcc.presentation.BusinessToolController;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ConditionalOnBean(BusinessToolController.class)
|
@ConditionalOnBean(McpToolExecutionService.class)
|
||||||
public class ToolRegistryHeartbeatSender {
|
public class ToolRegistryHeartbeatSender {
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package io.shinhanlife.dap.lib.util;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ public class PodScaffolder {
|
|||||||
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException {
|
||||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
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));
|
Path modulePath = rootDir.resolve(Paths.get(moduleName));
|
||||||
if (Files.exists(modulePath)) {
|
if (Files.exists(modulePath)) {
|
||||||
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
||||||
@@ -59,15 +63,14 @@ public class PodScaffolder {
|
|||||||
log.append("[2/6] build.gradle 생성 중...\n");
|
log.append("[2/6] build.gradle 생성 중...\n");
|
||||||
String buildGradle = """
|
String buildGradle = """
|
||||||
plugins {
|
plugins {
|
||||||
|
// Spring Boot 3.5.11 version is managed by the root build.gradle.
|
||||||
id 'org.springframework.boot'
|
id 'org.springframework.boot'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Shared MCP, Glow integration, validation, logging, Lombok and MapStruct configuration.
|
||||||
implementation project(':dap-was-lib')
|
implementation project(':dap-was-lib')
|
||||||
}
|
}
|
||||||
dependencies {
|
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
|
||||||
}
|
|
||||||
""";
|
""";
|
||||||
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
|
Files.writeString(modulePath.resolve("build.gradle"), buildGradle);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.lib.util;
|
package io.shinhanlife.dap.lib.util;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -74,7 +74,10 @@ public class ToolScaffolder {
|
|||||||
String useSchemaResourceStr = getOrAsk(args, 8, scanner, "9. input/output JSON Schema 파일 자동 생성 여부 (y/N): ");
|
String useSchemaResourceStr = getOrAsk(args, 8, scanner, "9. input/output JSON Schema 파일 자동 생성 여부 (y/N): ");
|
||||||
boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim());
|
boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim());
|
||||||
|
|
||||||
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null, useSchemaResource);
|
String inputSchemaResource = useSchemaResource ? "classpath:mcp/schema/" + toKebabCase(baseName) + "-resource-input-schema.json" : null;
|
||||||
|
String outputSchemaResource = useSchemaResource ? "classpath:mcp/schema/" + toKebabCase(baseName) + "-resource-output-schema.json" : null;
|
||||||
|
|
||||||
|
String result = scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, true, null, inputSchemaResource, outputSchemaResource);
|
||||||
System.out.println(result);
|
System.out.println(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +90,10 @@ public class ToolScaffolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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, false);
|
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, boolean useSchemaResource) throws IOException {
|
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 {
|
||||||
baseName = toPascalCase(baseName);
|
baseName = toPascalCase(baseName);
|
||||||
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
String envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||||
@@ -103,6 +106,7 @@ public class ToolScaffolder {
|
|||||||
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
|
Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter"));
|
||||||
|
|
||||||
// schema resource 파일 경로 (useSchemaResource=true 일 때만 생성)
|
// schema resource 파일 경로 (useSchemaResource=true 일 때만 생성)
|
||||||
|
boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty());
|
||||||
String schemaBaseName = toKebabCase(baseName);
|
String schemaBaseName = toKebabCase(baseName);
|
||||||
String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json";
|
String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json";
|
||||||
String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json";
|
String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json";
|
||||||
@@ -223,7 +227,7 @@ public class ToolScaffolder {
|
|||||||
String serviceInterfaceContent = """
|
String serviceInterfaceContent = """
|
||||||
package %s.usecase;
|
package %s.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import %s.dto.%sRequest;
|
import %s.dto.%sRequest;
|
||||||
import %s.dto.%sResponse;
|
import %s.dto.%sResponse;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class ToolSchemaResolver {
|
|||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> resolve(org.springframework.ai.mcp.annotation.McpTool function, ToolHint hint, Class<?> requestType) {
|
public Map<String, Object> resolve(org.springaicommunity.mcp.annotation.McpTool function, ToolHint hint, Class<?> requestType) {
|
||||||
if (hint != null && !hint.inputSchemaResource().isBlank()) {
|
if (hint != null && !hint.inputSchemaResource().isBlank()) {
|
||||||
return loadResource(hint.inputSchemaResource());
|
return loadResource(hint.inputSchemaResource());
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ public class ToolSchemaResolver {
|
|||||||
* ToolHint.outputSchemaResource()가 있으면 classpath JSON 파일에서 로드하고,
|
* ToolHint.outputSchemaResource()가 있으면 classpath JSON 파일에서 로드하고,
|
||||||
* 없으면 responseType DTO를 분석하여 자동 생성합니다.
|
* 없으면 responseType DTO를 분석하여 자동 생성합니다.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function, Class<?> responseType, ToolHint hint) {
|
public Map<String, Object> resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, Class<?> responseType, ToolHint hint) {
|
||||||
if (hint != null && !hint.outputSchemaResource().isBlank()) {
|
if (hint != null && !hint.outputSchemaResource().isBlank()) {
|
||||||
return loadResource(hint.outputSchemaResource());
|
return loadResource(hint.outputSchemaResource());
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ public class ToolSchemaResolver {
|
|||||||
* Resolves an explicitly declared response schema.
|
* Resolves an explicitly declared response schema.
|
||||||
* Response schemas are opt-in so existing tools keep their current response behavior.
|
* Response schemas are opt-in so existing tools keep their current response behavior.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function, Class<?> responseType) {
|
public Map<String, Object> resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, Class<?> responseType) {
|
||||||
// Object, Map 등 구체적인 DTO가 아닌 경우 검증 스킵
|
// Object, Map 등 구체적인 DTO가 아닌 경우 검증 스킵
|
||||||
if (responseType == null
|
if (responseType == null
|
||||||
|| responseType == Object.class
|
|| responseType == Object.class
|
||||||
@@ -57,7 +57,7 @@ public class ToolSchemaResolver {
|
|||||||
/**
|
/**
|
||||||
* Retained for callers that use only explicit output schemas.
|
* Retained for callers that use only explicit output schemas.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> resolveOutput(org.springframework.ai.mcp.annotation.McpTool function) {
|
public Map<String, Object> resolveOutput(org.springaicommunity.mcp.annotation.McpTool function) {
|
||||||
return resolveOutput(function, null);
|
return resolveOutput(function, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import com.networknt.schema.SchemaRegistry;
|
|||||||
import com.networknt.schema.SpecificationVersion;
|
import com.networknt.schema.SpecificationVersion;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */
|
/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */
|
||||||
@Component
|
|
||||||
public class ToolArgumentSchemaValidator {
|
public class ToolArgumentSchemaValidator {
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|||||||
@@ -1,42 +1,14 @@
|
|||||||
package io.shinhanlife.dap.mcc.presentation;
|
package io.shinhanlife.dap.mcc.presentation;
|
||||||
|
|
||||||
|
import io.shinhanlife.dap.lib.mcp.McpRequestHeaders;
|
||||||
/**
|
import io.shinhanlife.dap.lib.mcp.ToolExecutionResult;
|
||||||
* @package io.shinhanlife.dap.mcc.presentation
|
import io.shinhanlife.dap.lib.mcp.McpToolExecutionService;
|
||||||
* @className BusinessToolController
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.networknt.schema.Error;
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
|
||||||
import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator;
|
|
||||||
import io.shinhanlife.dap.lib.config.McpProperties;
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
|
||||||
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender;
|
||||||
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
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.http.ResponseEntity;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -45,26 +17,20 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@Slf4j
|
/** Legacy REST adapter for Tool Pod execution. */
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/")
|
@RequestMapping("/")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BusinessToolController {
|
public class BusinessToolController {
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
private final McpProperties mcpProperties;
|
|
||||||
private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender;
|
private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender;
|
||||||
private final ToolArgumentSchemaValidator toolArgumentSchemaValidator;
|
private final McpToolExecutionService toolExecutionService;
|
||||||
|
|
||||||
private final ToolSchemaResolver toolSchemaResolver;
|
|
||||||
// 내부 조회용 로컬 Tool 목록 엔드포인트
|
|
||||||
@GetMapping("/mcp/api/v1/tools/local")
|
@GetMapping("/mcp/api/v1/tools/local")
|
||||||
public List<ToolMetadata> getLocalTools() {
|
public List<ToolMetadata> getLocalTools() {
|
||||||
return toolRegistryHeartbeatSender.getAllScannedTools();
|
return toolRegistryHeartbeatSender.getAllScannedTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 순수 REST 기반 동적 라우팅 엔드포인트
|
|
||||||
@PostMapping("/mcp/{name}")
|
@PostMapping("/mcp/{name}")
|
||||||
public ResponseEntity<?> executeDynamicTool(
|
public ResponseEntity<?> executeDynamicTool(
|
||||||
@PathVariable("name") String functionName,
|
@PathVariable("name") String functionName,
|
||||||
@@ -73,173 +39,12 @@ public class BusinessToolController {
|
|||||||
@RequestHeader(value = "request-id", required = false) String requestId,
|
@RequestHeader(value = "request-id", required = false) String requestId,
|
||||||
@RequestHeader(value = "employee-id", required = false) String encryptedEmployeeId,
|
@RequestHeader(value = "employee-id", required = false) String encryptedEmployeeId,
|
||||||
@RequestBody(required = false) Map<String, Object> arguments) {
|
@RequestBody(required = false) Map<String, Object> arguments) {
|
||||||
|
ToolExecutionResult result = toolExecutionService.execute(
|
||||||
String finalRequestId = headerRequestId;
|
functionName,
|
||||||
|
new McpRequestHeaders(headerRequestId, traceId, requestId, encryptedEmployeeId),
|
||||||
log.info(" [Tool] IN - trace-id: {}, request-id: {}", traceId, requestId);
|
arguments);
|
||||||
log.info(" [Tool] 동적 툴 실행 요청 수신 (함수명): {}", functionName);
|
ResponseEntity.BodyBuilder response = ResponseEntity.status(result.statusCode());
|
||||||
if (arguments != null) {
|
result.headers().forEach(response::header);
|
||||||
try {
|
return response.body(result.body());
|
||||||
log.info(" [Tool] 호출 파라미터: {}", objectMapper.writeValueAsString(arguments));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info(" [Tool] 호출 파라미터: {}", arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Object targetBean = null;
|
|
||||||
Method targetMethod = null;
|
|
||||||
McpTool targetFunctionAnnotation = null;
|
|
||||||
|
|
||||||
// McpTool 어노테이션 기반 조회가 프록시 문제로 누락될 수 있으므로, 전체 빈을 순회하며 @McpTool을 찾습니다.
|
|
||||||
Map<String, Object> allBeans = applicationContext.getBeansOfType(Object.class);
|
|
||||||
outerLoop:
|
|
||||||
for (Object bean : allBeans.values()) {
|
|
||||||
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
|
||||||
for (Method targetMethodOfClass : targetClass.getDeclaredMethods()) {
|
|
||||||
McpTool mcpFunc = AnnotationUtils.findAnnotation(targetMethodOfClass, McpTool.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<String> availableFunctions = new ArrayList<>();
|
|
||||||
for (Object bean : allBeans.values()) {
|
|
||||||
Class<?> targetCls = AopUtils.getTargetClass(bean);
|
|
||||||
for (Method m : targetCls.getDeclaredMethods()) {
|
|
||||||
McpTool func = AnnotationUtils.findAnnotation(m, McpTool.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<String, Object> errorDetails = new HashMap<>();
|
|
||||||
errorDetails.put("status", "404");
|
|
||||||
Map<String, Object> 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 {
|
|
||||||
io.shinhanlife.dap.lib.annotation.ToolHint hint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
|
|
||||||
Map<String, Object> inputSchema = toolSchemaResolver.resolve(targetFunctionAnnotation, hint, paramType);
|
|
||||||
List<Error> errors = toolArgumentSchemaValidator.validate(inputSchema, arguments);
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
|
|
||||||
List<String> errorMessages = new ArrayList<>();
|
|
||||||
for (Error validationError : errors) {
|
|
||||||
errorMessages.add(validationError.getMessage());
|
|
||||||
}
|
|
||||||
Map<String, Object> errorDetails = new HashMap<>();
|
|
||||||
errorDetails.put("status", "422");
|
|
||||||
Map<String, Object> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
io.shinhanlife.dap.lib.annotation.ToolHint outputHint = AnnotationUtils.findAnnotation(targetMethod, io.shinhanlife.dap.lib.annotation.ToolHint.class);
|
|
||||||
Map<String, Object> outputSchema = toolSchemaResolver.resolveOutput(targetFunctionAnnotation, targetMethod.getReturnType(), outputHint);
|
|
||||||
if (!outputSchema.isEmpty()) {
|
|
||||||
List<Error> outputErrors = toolArgumentSchemaValidator.validateValue(outputSchema, methodResult);
|
|
||||||
if (!outputErrors.isEmpty()) {
|
|
||||||
log.error("[Tool] Output schema validation failed. tool={}, errors={}",
|
|
||||||
functionName, outputErrors);
|
|
||||||
Map<String, Object> 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 -> MCP Gateway] Output Schema Result: {}", objectMapper.writeValueAsString(methodResult));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info("[Tool -> MCP Gateway] 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<String, Object> errorDetails = new HashMap<>();
|
|
||||||
errorDetails.put("status", "500");
|
|
||||||
Map<String, Object> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcc.dto
|
* @package io.shinhanlife.dap.mcc.dto
|
||||||
* @className BalanceRequest
|
* @className BalanceRequest
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.cmm.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.oth.usecase;
|
package io.shinhanlife.dap.mcc.biz.oth.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
|
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
|
||||||
|
|
||||||
public interface Onnba3011UseCase {
|
public interface Onnba3011UseCase {
|
||||||
@McpTool(name = "oth.sol.request.detail", title = "보종By가입설계한도계산조회 툴")
|
@McpTool(name = "oth.oth.onnba3011.call", description = "Onnba3011 호출 툴")
|
||||||
@ToolHint(register = false, categoryKey = "oth")
|
@ToolHint(categoryKey = "oth")
|
||||||
Object callOnnba3011(Onnba3011Request req);
|
Object execute(Onnba3011Request req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class Onnba3011UseCaseImpl implements Onnba3011UseCase {
|
|||||||
* AI Agent가 호출하게 될 메서드입니다.
|
* AI Agent가 호출하게 될 메서드입니다.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object callOnnba3011(Onnba3011Request req) {
|
public Object execute(Onnba3011Request req) {
|
||||||
log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신.");
|
log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package io.shinhanlife.dap.mcc.biz.smp.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package io.shinhanlife.dap.mcc.biz.smp.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.smp.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package io.shinhanlife.dap.mcc.biz.smp.dto;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
package io.shinhanlife.dap.mcc.biz.smp.usecase;
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.dto.*;
|
import io.shinhanlife.dap.mcc.biz.smp.dto.*;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.sol.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.sol.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.sol.usecase;
|
package io.shinhanlife.dap.mcc.biz.sol.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest;
|
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.sol.usecase;
|
package io.shinhanlife.dap.mcc.biz.sol.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package io.shinhanlife.dap.mcc.biz.sms.dto;
|
|||||||
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package io.shinhanlife.dap.mcc.biz.sms.usecase;
|
package io.shinhanlife.dap.mcc.biz.sms.usecase;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.annotation.McpTool;
|
import org.springaicommunity.mcp.annotation.McpTool;
|
||||||
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
import io.shinhanlife.dap.lib.annotation.ToolHint;
|
||||||
import io.shinhanlife.dap.mcc.biz.sms.dto.*;
|
import io.shinhanlife.dap.mcc.biz.sms.dto.*;
|
||||||
|
|
||||||
public interface SmsToolUseCase {
|
public interface SmsToolUseCase {
|
||||||
@McpTool(name = "sms.send", title = "SMS 발송 툴", description = "SMS 발송 기능을 제공합니다.")
|
@McpTool(name = "sms.sms.msg.send", title = "SMS 발송 툴", description = "SMS 발송 기능을 제공합니다.")
|
||||||
@ToolHint(register = false, categoryKey = "notification", mappingId = "SMS_SEND")
|
@ToolHint(register = false, categoryKey = "sms", mappingId = "SMS_SEND")
|
||||||
Object sendSms(SmsSendRequest req);
|
Object sendSms(SmsSendRequest req);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user