feat: Agent Builder 커스텀 JSON-RPC 스펙 및 에러코드 적용, McpBridge 문자열 ID 지원
This commit is contained in:
BIN
Agent Builder MCP Integration Blueprint.pptx
Normal file
BIN
Agent Builder MCP Integration Blueprint.pptx
Normal file
Binary file not shown.
@@ -96,13 +96,26 @@ public class McpBridge {
|
|||||||
int idx = line.indexOf("\"id\":");
|
int idx = line.indexOf("\"id\":");
|
||||||
if (idx == -1) return "null";
|
if (idx == -1) return "null";
|
||||||
int start = idx + 5;
|
int start = idx + 5;
|
||||||
while (start < line.length() && (line.charAt(start) == ' ' || line.charAt(start) == '\"')) {
|
while (start < line.length() && line.charAt(start) == ' ') {
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
|
if (start >= line.length()) return "null";
|
||||||
|
|
||||||
int end = start;
|
int end = start;
|
||||||
while (end < line.length() && Character.isDigit(line.charAt(end))) {
|
if (line.charAt(start) == '\"') {
|
||||||
end++;
|
end = start + 1;
|
||||||
|
while (end < line.length() && line.charAt(end) != '\"') {
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
if (end < line.length()) {
|
||||||
|
end++; // include closing quote
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (end < line.length() && Character.isDigit(line.charAt(end))) {
|
||||||
|
end++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start == end) return "null";
|
if (start == end) return "null";
|
||||||
return line.substring(start, end);
|
return line.substring(start, end);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,9 +123,16 @@ public class BusinessToolController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.error("[Tool] 실행할 함수(Method)를 찾을 수 없습니다: {}. 현재 스캔된 툴 메서드 목록: {}", functionName, availableFunctions);
|
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);
|
||||||
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
Map<String, Object> error = new HashMap<>();
|
||||||
error.put("jsonrpc", "2.0");
|
error.put("jsonrpc", "2.0");
|
||||||
error.put("error", Map.of("code", -32601, "message", "실행할 함수를 찾을 수 없습니다: " + functionName));
|
error.put("error", errorBody);
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
error.put("id", payload != null ? payload.get("id") : null);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -145,13 +152,20 @@ public class BusinessToolController {
|
|||||||
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(arguments));
|
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(arguments));
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
|
log.error("[Tool] 파라미터 유효성 검증 실패: {}", errors);
|
||||||
Map<String, Object> error = new HashMap<>();
|
|
||||||
error.put("jsonrpc", "2.0");
|
|
||||||
List<String> errorMessages = new ArrayList<>();
|
List<String> errorMessages = new ArrayList<>();
|
||||||
for (ValidationMessage vm : errors) {
|
for (ValidationMessage vm : errors) {
|
||||||
errorMessages.add(vm.getMessage());
|
errorMessages.add(vm.getMessage());
|
||||||
}
|
}
|
||||||
error.put("error", Map.of("code", -32602, "message", "파라미터 유효성 검증 실패: " + String.join(", ", errorMessages)));
|
Map<String, Object> errorDetails = new HashMap<>();
|
||||||
|
errorDetails.put("status", "422");
|
||||||
|
Map<String, Object> errorBody = new HashMap<>();
|
||||||
|
errorBody.put("code", "INVALID_PARAM");
|
||||||
|
errorBody.put("message", "파라미터 유효성 검증 실패: " + String.join(", ", errorMessages));
|
||||||
|
errorBody.put("details", errorDetails);
|
||||||
|
|
||||||
|
Map<String, Object> error = new HashMap<>();
|
||||||
|
error.put("jsonrpc", "2.0");
|
||||||
|
error.put("error", errorBody);
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
error.put("id", payload != null ? payload.get("id") : null);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -177,15 +191,23 @@ public class BusinessToolController {
|
|||||||
// 4. 메서드 실행
|
// 4. 메서드 실행
|
||||||
Object methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
Object methodResult = targetMethod.invoke(targetBean, invokeArgument);
|
||||||
|
|
||||||
// 5. 결과 조립 (JSON-RPC 응답)
|
// 5. 결과 조립 (JSON-RPC 응답 - Agent Builder 규격 적용)
|
||||||
Map<String, Object> resultPayload = new HashMap<>();
|
Map<String, Object> innerResult = new HashMap<>();
|
||||||
if (methodResult instanceof Map) {
|
if (methodResult instanceof Map) {
|
||||||
resultPayload = (Map<String, Object>) methodResult;
|
innerResult.putAll((Map<String, Object>) methodResult);
|
||||||
} else {
|
|
||||||
resultPayload.put("status", "SUCCESS");
|
|
||||||
resultPayload.put("legacy_response", methodResult);
|
|
||||||
}
|
}
|
||||||
resultPayload.put("ai_insight", "다이렉트 메서드(" + targetMethod.getName() + ") 통신이 성공적으로 수행되었습니다.");
|
if (!innerResult.containsKey("contracts")) {
|
||||||
|
List<Object> contracts = new ArrayList<>();
|
||||||
|
if (methodResult != null) {
|
||||||
|
contracts.add(methodResult);
|
||||||
|
}
|
||||||
|
innerResult.put("contracts", contracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultPayload = new HashMap<>();
|
||||||
|
resultPayload.put("status", "ok");
|
||||||
|
resultPayload.put("result", innerResult);
|
||||||
|
resultPayload.put("error code", null);
|
||||||
|
|
||||||
Map<String, Object> rpcResponse = new java.util.LinkedHashMap<>(); // 순서 보장을 위해 LinkedHashMap 사용
|
Map<String, Object> rpcResponse = new java.util.LinkedHashMap<>(); // 순서 보장을 위해 LinkedHashMap 사용
|
||||||
rpcResponse.put("jsonrpc", "2.0");
|
rpcResponse.put("jsonrpc", "2.0");
|
||||||
@@ -201,9 +223,16 @@ public class BusinessToolController {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[Tool] 리플렉션 실행 중 예외 발생: {}", e.getMessage());
|
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", "메서드 실행 중 예외 발생: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
|
||||||
|
errorBody.put("details", errorDetails);
|
||||||
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
Map<String, Object> error = new HashMap<>();
|
||||||
error.put("jsonrpc", "2.0");
|
error.put("jsonrpc", "2.0");
|
||||||
error.put("error", Map.of("code", -32000, "message", "메서드 실행 중 예외 발생: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())));
|
error.put("error", errorBody);
|
||||||
error.put("id", payload != null ? payload.get("id") : null);
|
error.put("id", payload != null ? payload.get("id") : null);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
FROM eclipse-temurin:21-jdk-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY build/libs/axhub-tool-hr-0.0.1-SNAPSHOT.jar app.jar
|
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'org.springframework.boot'
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
implementation project(':axhub-tool-core')
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package io.shinhanlife.axhub.biz.mcp.tool.hr;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.axhub.biz.mcp.tool.hr
|
|
||||||
* @className HrToolApplication
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author
|
|
||||||
* @create
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.axhub.biz.mcp.tool", "io.shinhanlife.axhub.biz.mcp.adapter", "io.shinhanlife.axhub.common.mcp", "io.shinhanlife.axhub.common.config"})
|
|
||||||
@org.springframework.boot.context.properties.ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.axhub.biz.mcp.tool", "io.shinhanlife.axhub.biz.mcp.adapter", "io.shinhanlife.axhub.common.mcp", "io.shinhanlife.axhub.common.config"})
|
|
||||||
@EnableCaching
|
|
||||||
public class HrToolApplication {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(HrToolApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
|
||||||
spring.datasource.url=jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
|
|
||||||
spring.datasource.driverClassName=com.p6spy.engine.spy.P6SpyDriver
|
|
||||||
spring.datasource.username=sa
|
|
||||||
spring.datasource.password=password
|
|
||||||
|
|
||||||
spring.h2.console.enabled=true
|
|
||||||
# EIMS 동적 라우팅 접속 정보
|
|
||||||
eims.http.url=http://localhost:${server.port}/api/gateway
|
|
||||||
eims.tcp.host=127.0.0.1
|
|
||||||
eims.tcp.port=8090
|
|
||||||
eims.tcp.timeout=5000
|
|
||||||
eims.jsp.form.url=http://localhost:${server.port}/mock/jsp-form
|
|
||||||
eims.jsp.json.url=http://localhost:${server.port}/mock/jsp-json
|
|
||||||
eims.mci.url=http://localhost:${server.port}/api/mock/esb/api
|
|
||||||
eims.mcistring.url=http://localhost:${server.port}/api/mock/esb/string
|
|
||||||
|
|
||||||
# API 보안 키 설정
|
|
||||||
mcp.security.api-keys.SHINHAN_MCP_SECRET_KEY_2026=mcp-client-1
|
|
||||||
mcp.security.api-keys.SHINHAN_MCP_TEST_KEY_9999=mcp-client-2
|
|
||||||
mcp.security.tenant-domains.mcp-client-1=CUSTOMER,COMMON
|
|
||||||
mcp.security.tenant-domains.mcp-client-2=ALL
|
|
||||||
|
|
||||||
# Gateway/Tool URLs
|
|
||||||
axhub.gateway.url=http://localhost:8081
|
|
||||||
axhub.tool.url=http://localhost:${server.port}
|
|
||||||
|
|
||||||
# Disable Kafka
|
|
||||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
|
||||||
|
|
||||||
# Suppress Kafka Connection Logs
|
|
||||||
logging.level.org.apache.kafka=ERROR
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
spring:
|
|
||||||
application:
|
|
||||||
name: axhub-tool-hr
|
|
||||||
config:
|
|
||||||
import: "classpath:config/application-glow-local.yml"
|
|
||||||
|
|
||||||
server:
|
|
||||||
port: 8086
|
|
||||||
|
|
||||||
axhub:
|
|
||||||
tool:
|
|
||||||
url: "http://tool-hr:8086"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
spring.profiles.active=local
|
|
||||||
mcp.namespace=hr
|
|
||||||
@@ -30,7 +30,7 @@ public class BondIssueService extends AbstractMcpToolService {
|
|||||||
return executeLegacy("EAI", "BOND_001", data);
|
return executeLegacy("EAI", "BOND_001", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@McpFunction(name = "issue", register = true, description = "증권 발행 테스트", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
1 @McpFunction(name = "issue", register = true, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
|
||||||
public Object issue(BondIssueReq data) {
|
public Object issue(BondIssueReq data) {
|
||||||
return executeLegacy("EAI", "BOND_002", data);
|
return executeLegacy("EAI", "BOND_002", data);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user