forked from kimhyungsik/ax_hub_mcp_tool
feat: Separate Gateway and Tool server architectures, implement Tool Auto-Registration, and add mock response mapping
This commit is contained in:
@@ -77,7 +77,6 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs = [
|
||||
'-Amapstruct.defaultComponentModel=spring'
|
||||
]
|
||||
options.compilerArgs << '-parameters'
|
||||
options.compilerArgs << '-Amapstruct.defaultComponentModel=spring'
|
||||
}
|
||||
|
||||
22
src/main/java/io/shinhanlife/AxHubGatewayApplication.java
Normal file
22
src/main/java/io/shinhanlife/AxHubGatewayApplication.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package io.shinhanlife;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {
|
||||
"io.shinhanlife.axhub.biz.mcp.gateway",
|
||||
"io.shinhanlife.axhub.biz.mcp.adapter",
|
||||
"io.shinhanlife.axhub.common.mcp"
|
||||
})
|
||||
public class AxHubGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(AxHubGatewayApplication.class);
|
||||
// Gateway는 8081 포트에서 실행
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8081"));
|
||||
app.run(args);
|
||||
}
|
||||
}
|
||||
31
src/main/java/io/shinhanlife/AxHubToolApplication.java
Normal file
31
src/main/java/io/shinhanlife/AxHubToolApplication.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package io.shinhanlife;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@ComponentScan(basePackages = {
|
||||
"io.shinhanlife.axhub.biz.mcp.tool",
|
||||
"io.shinhanlife.axhub.biz.mcp.adapter",
|
||||
"io.shinhanlife.axhub.common.mcp"
|
||||
})
|
||||
public class AxHubToolApplication {
|
||||
public static void main(String[] args) {
|
||||
// 기본 포트는 8082로 설정하되, 파라미터(--server.port=808x)가 있으면 해당 포트로 설정
|
||||
String port = "8082";
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("--server.port=")) {
|
||||
port = arg.substring("--server.port=".length());
|
||||
}
|
||||
}
|
||||
System.setProperty("server.port", port);
|
||||
|
||||
SpringApplication app = new SpringApplication(AxHubToolApplication.class);
|
||||
app.run(args);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,11 @@ public class McpRouterController {
|
||||
|
||||
@PostMapping("/registry/heartbeat")
|
||||
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
|
||||
redisRegistryService.refreshHeartbeat(toolName);
|
||||
boolean success = redisRegistryService.refreshHeartbeat(toolName);
|
||||
if (success) {
|
||||
return ResponseEntity.ok("Heartbeat updated");
|
||||
} else {
|
||||
return ResponseEntity.status(404).body("Tool not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,15 @@ public class RedisRegistryService {
|
||||
/**
|
||||
* 하트비트 갱신 (TTL 초기화)
|
||||
*/
|
||||
public void refreshHeartbeat(String toolName) {
|
||||
public boolean refreshHeartbeat(String toolName) {
|
||||
String key = KEY_PREFIX + toolName;
|
||||
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
||||
if (Boolean.TRUE.equals(exists)) {
|
||||
log.debug(" [RedisRegistry] 하트비트 갱신: {}", toolName);
|
||||
return true;
|
||||
} else {
|
||||
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public List<String> getAvailablePods(String toolName) {
|
||||
|
||||
@@ -212,6 +212,7 @@ public class MockToolAutoRegistrar {
|
||||
HttpHeaders headers = createHeaders();
|
||||
headers.setContentType(MediaType.TEXT_PLAIN);
|
||||
|
||||
boolean needsReRegistration = false;
|
||||
for (ToolConfig tool : TOOLS) {
|
||||
try {
|
||||
String heartbeatUrl = gatewayUrl + "/registry/heartbeat";
|
||||
@@ -224,8 +225,14 @@ public class MockToolAutoRegistrar {
|
||||
log.info(" [MockTool] {} Heartbeat 서버 전송 완료", tool.getName());
|
||||
} catch (Exception e) {
|
||||
log.error(" [MockTool] {} Heartbeat 전송 실패: {}", tool.getName(), e.getMessage());
|
||||
needsReRegistration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsReRegistration) {
|
||||
log.info(" [MockTool] 하트비트 실패로 인해 전체 툴 재등록을 시도합니다.");
|
||||
registerOnStartup();
|
||||
}
|
||||
}
|
||||
|
||||
@EventListener(ContextClosedEvent.class)
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
||||
# 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:8081/api/gateway
|
||||
eims.tcp.host=127.0.0.1
|
||||
eims.tcp.port=8090
|
||||
eims.tcp.timeout=5000
|
||||
eims.jsp.form.url=http://localhost:8081/mock/jsp-form
|
||||
eims.jsp.json.url=http://localhost:8081/mock/jsp-json
|
||||
eims.mci.url=http://localhost:8081/mock/esb/api
|
||||
|
||||
# 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
|
||||
mcp.adapter.url=http://localhost:8081/rpc/v1/execute
|
||||
mcp.gateway.url=http://localhost:8081/mcp/api/v1
|
||||
mcp.tool.host=localhost
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
spring.application.name=axhub-admin
|
||||
server.port=8080
|
||||
server.port=8081
|
||||
|
||||
# 기본적으로 local 환경을 실행
|
||||
spring.profiles.active=local
|
||||
|
||||
99
src/main/resources/mock-responses.json
Normal file
99
src/main/resources/mock-responses.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"BILL_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "청구 심사 상태 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"billingStatus": "PROCESSING",
|
||||
"expectedCompletionDate": "2026-07-05"
|
||||
}
|
||||
},
|
||||
"BILL_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "청구 처리가 완료되었습니다.",
|
||||
"data": {
|
||||
"processId": "PRC-998811",
|
||||
"result": "APPROVED"
|
||||
}
|
||||
},
|
||||
"BOND_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "디지털 증권 발행 가능 한도 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"availableLimit": 500000000,
|
||||
"currency": "KRW"
|
||||
}
|
||||
},
|
||||
"BOND_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "디지털 증권 발행이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"bondId": "BND-2026-07-04-1234",
|
||||
"issuedAmount": 10000000
|
||||
}
|
||||
},
|
||||
"HR_VAC_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "연차 휴가 등록이 완료되었습니다.",
|
||||
"data": {
|
||||
"vacationId": "VAC-8877",
|
||||
"status": "APPROVED"
|
||||
}
|
||||
},
|
||||
"HR_VAC_02": {
|
||||
"status": "SUCCESS",
|
||||
"message": "잔여 연차 일수 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"totalDays": 15,
|
||||
"usedDays": 3,
|
||||
"remainingDays": 12
|
||||
}
|
||||
},
|
||||
"COM_SMS_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "SMS 발송이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"messageId": "SMS-11223344",
|
||||
"sentTime": "2026-07-04 10:00:00"
|
||||
}
|
||||
},
|
||||
"COM_EML_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "이메일 발송이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"emailId": "EML-998877",
|
||||
"sentTime": "2026-07-04 10:00:00"
|
||||
}
|
||||
},
|
||||
"CNTR_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "계약 상태 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"contractStatus": "ACTIVE",
|
||||
"startDate": "2024-01-01"
|
||||
}
|
||||
},
|
||||
"CNTR_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "계약 상세 내역 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"productName": "신한 라이프 스마트 연금보험",
|
||||
"monthlyPremium": 500000
|
||||
}
|
||||
},
|
||||
"CRM_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "고객 등급 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"customerGrade": "VIP",
|
||||
"loyaltyPoints": 15000
|
||||
}
|
||||
},
|
||||
"CRM_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "고객 상세 정보 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"address": "서울특별시 중구 세종대로 9",
|
||||
"phoneNumber": "010-XXXX-XXXX"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user