forked from kimhyungsik/ax_hub_mcp_tool
refactor: Adopt JSON-RPC 2.0 standard for MCP pipeline routing
This commit is contained in:
@@ -22,14 +22,32 @@ public class RequestValidator {
|
||||
throw new IllegalArgumentException("요청 페이로드가 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
// 2. 필수 파라미터 누락 및 빈 값 검증
|
||||
// JSON-RPC 2.0 껍데기 검증
|
||||
if (!"2.0".equals(payload.get("jsonrpc"))) {
|
||||
throw new IllegalArgumentException("지원하지 않는 규격입니다. 'jsonrpc': '2.0' 이 필요합니다.");
|
||||
}
|
||||
if (!"tools/call".equals(payload.get("method"))) {
|
||||
throw new IllegalArgumentException("지원하지 않는 method 입니다. 'tools/call' 이 필요합니다.");
|
||||
}
|
||||
|
||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||
if (params == null || params.isEmpty()) {
|
||||
throw new IllegalArgumentException("params 객체가 누락되었습니다.");
|
||||
}
|
||||
|
||||
Map<String, Object> arguments = (Map<String, Object>) params.get("arguments");
|
||||
if (arguments == null) {
|
||||
throw new IllegalArgumentException("params.arguments 객체가 누락되었습니다.");
|
||||
}
|
||||
|
||||
// 2. 필수 파라미터 누락 및 빈 값 검증 (arguments 내에서 검증)
|
||||
for (String field : REQUIRED_FIELDS) {
|
||||
if (!payload.containsKey(field)) {
|
||||
if (!arguments.containsKey(field)) {
|
||||
log.error(" [Validator] 검증 실패: 필수 키 누락 [{}]", field);
|
||||
throw new IllegalArgumentException("필수 파라미터가 누락되었습니다: " + field);
|
||||
}
|
||||
|
||||
Object value = payload.get(field);
|
||||
Object value = arguments.get(field);
|
||||
if (value == null || value.toString().trim().isEmpty()) {
|
||||
log.error(" [Validator] 검증 실패: 필수 키의 값이 비어 있음 [{}]", field);
|
||||
throw new IllegalArgumentException("필수 파라미터의 값이 비어있을 수 없습니다: " + field);
|
||||
@@ -37,8 +55,8 @@ public class RequestValidator {
|
||||
}
|
||||
|
||||
// 3. 비즈니스 로직에 따른 추가 데이터 길이 또는 타입 검증 (예: 프롬프트 길이)
|
||||
String traceId = payload.get("traceId").toString();
|
||||
String userPrompt = payload.get("userPrompt").toString();
|
||||
String traceId = arguments.get("traceId").toString();
|
||||
String userPrompt = arguments.get("userPrompt").toString();
|
||||
|
||||
if (userPrompt.length() > 2000) {
|
||||
log.warn(" [Validator] 프롬프트 길이 초과 (Trace ID: {})", traceId);
|
||||
|
||||
@@ -23,8 +23,9 @@ public class ToolPlanner {
|
||||
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
||||
log.info("📝 [Planner] 요청 분석 및 실행 계획 수립 시작");
|
||||
|
||||
// 1. 요청에서 호출하려는 툴 이름 추출
|
||||
String toolName = (String) payload.get("toolName");
|
||||
// 1. 요청에서 호출하려는 툴 이름 추출 (JSON-RPC params.name)
|
||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||
String toolName = params != null ? (String) params.get("name") : null;
|
||||
|
||||
if (toolName == null || toolName.isEmpty()) {
|
||||
throw new IllegalArgumentException("요청에 toolName이 포함되어 있지 않습니다.");
|
||||
|
||||
@@ -60,13 +60,16 @@ public class BusinessToolController {
|
||||
if (config == null) {
|
||||
log.error(" [Tool] 등록되지 않은 툴 호출: {}", toolName);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("status", "ERROR");
|
||||
error.put("message", "등록되지 않은 툴입니다: " + toolName);
|
||||
error.put("jsonrpc", "2.0");
|
||||
error.put("error", Map.of("code", -32601, "message", "등록되지 않은 툴입니다: " + toolName));
|
||||
error.put("id", payload != null ? payload.get("id") : null);
|
||||
return error;
|
||||
}
|
||||
|
||||
// 2. 파라미터에서 실행할 함수(action) 추출 및 라우팅 결정
|
||||
String action = payload != null && payload.containsKey("action") ? payload.get("action").toString() : null;
|
||||
// 2. 파라미터에서 실행할 함수(action) 추출 및 라우팅 결정 (JSON-RPC)
|
||||
Map<String, Object> params = payload != null ? (Map<String, Object>) payload.get("params") : null;
|
||||
Map<String, Object> arguments = params != null ? (Map<String, Object>) params.get("arguments") : null;
|
||||
String action = arguments != null && arguments.containsKey("action") ? arguments.get("action").toString() : null;
|
||||
|
||||
ToolFunction targetFunction = null;
|
||||
if (action != null) {
|
||||
@@ -93,16 +96,17 @@ public class BusinessToolController {
|
||||
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
|
||||
JsonSchema schema = factory.getSchema(fullSchemaJson);
|
||||
|
||||
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(payload));
|
||||
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(arguments));
|
||||
if (!errors.isEmpty()) {
|
||||
log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("status", "ERROR");
|
||||
error.put("jsonrpc", "2.0");
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
for (ValidationMessage vm : errors) {
|
||||
errorMessages.add(vm.getMessage());
|
||||
}
|
||||
error.put("message", "파라미터 유효성 검증 실패: " + String.join(", ", errorMessages));
|
||||
error.put("error", Map.of("code", -32602, "message", "파라미터 유효성 검증 실패: " + String.join(", ", errorMessages)));
|
||||
error.put("id", payload != null ? payload.get("id") : null);
|
||||
return error;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -124,8 +128,9 @@ public class BusinessToolController {
|
||||
if (targetBean == null) {
|
||||
log.error("[Tool] 실행할 Tool Bean을 찾을 수 없습니다: {}", toolName);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("status", "ERROR");
|
||||
error.put("message", "실행할 Tool Bean을 찾을 수 없습니다: " + toolName);
|
||||
error.put("jsonrpc", "2.0");
|
||||
error.put("error", Map.of("code", -32601, "message", "실행할 Tool Bean을 찾을 수 없습니다: " + toolName));
|
||||
error.put("id", payload != null ? payload.get("id") : null);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -143,8 +148,9 @@ public class BusinessToolController {
|
||||
if (targetMethod == null) {
|
||||
log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}", actionName);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("status", "ERROR");
|
||||
error.put("message", "실행할 함수(Method)를 찾을 수 없습니다: " + actionName);
|
||||
error.put("jsonrpc", "2.0");
|
||||
error.put("error", Map.of("code", -32601, "message", "실행할 함수(Method)를 찾을 수 없습니다: " + actionName));
|
||||
error.put("id", payload != null ? payload.get("id") : null);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -152,11 +158,11 @@ public class BusinessToolController {
|
||||
|
||||
try {
|
||||
// 5. DTO 파라미터 자동 매핑 (Map -> DTO)
|
||||
Object invokeArgument = payload;
|
||||
if (targetMethod.getParameterCount() > 0) {
|
||||
Object invokeArgument = arguments;
|
||||
if (targetMethod.getParameterCount() > 0 && arguments != null) {
|
||||
Class<?> paramType = targetMethod.getParameterTypes()[0];
|
||||
if (!Map.class.isAssignableFrom(paramType)) {
|
||||
invokeArgument = objectMapper.convertValue(payload, paramType);
|
||||
invokeArgument = objectMapper.convertValue(arguments, paramType);
|
||||
log.info("[Tool] DTO 자동 매핑 성공: {}", paramType.getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -164,24 +170,28 @@ public class BusinessToolController {
|
||||
// 6. 메서드 실행
|
||||
Object methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
||||
|
||||
// 6. 결과 반환 (Map 타입으로 캐스팅 보장)
|
||||
// 7. 결과 조립 (JSON-RPC 응답)
|
||||
Map<String, Object> resultPayload = new HashMap<>();
|
||||
if (methodResult instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> resultMap = (Map<String, Object>) methodResult;
|
||||
resultMap.put("ai_insight", "다이렉트 메서드(" + targetMethod.getName() + ") 통신이 성공적으로 수행되었습니다.");
|
||||
return resultMap;
|
||||
resultPayload = (Map<String, Object>) methodResult;
|
||||
} else {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("status", "SUCCESS");
|
||||
resultMap.put("legacy_response", methodResult);
|
||||
resultMap.put("ai_insight", "다이렉트 메서드(" + targetMethod.getName() + ") 통신이 성공적으로 수행되었습니다.");
|
||||
return resultMap;
|
||||
resultPayload.put("status", "SUCCESS");
|
||||
resultPayload.put("legacy_response", methodResult);
|
||||
}
|
||||
resultPayload.put("ai_insight", "다이렉트 메서드(" + targetMethod.getName() + ") 통신이 성공적으로 수행되었습니다.");
|
||||
|
||||
Map<String, Object> rpcResponse = new HashMap<>();
|
||||
rpcResponse.put("jsonrpc", "2.0");
|
||||
rpcResponse.put("result", resultPayload);
|
||||
rpcResponse.put("id", payload != null ? payload.get("id") : null);
|
||||
return rpcResponse;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage());
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
error.put("status", "ERROR");
|
||||
error.put("message", "메서드 실행 중 예외 발생: " + e.getCause().getMessage());
|
||||
error.put("jsonrpc", "2.0");
|
||||
error.put("error", Map.of("code", -32000, "message", "메서드 실행 중 예외 발생: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())));
|
||||
error.put("id", payload != null ? payload.get("id") : null);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user