feat: Separate Gateway and Tool server architectures, implement Tool Auto-Registration, and add mock response mapping

This commit is contained in:
jade
2026-07-04 00:22:44 +09:00
parent 93b2d6e205
commit a3e4dc7e38
9 changed files with 191 additions and 8 deletions

View 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);
}
}

View 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);
}
}

View File

@@ -153,7 +153,11 @@ public class McpRouterController {
@PostMapping("/registry/heartbeat")
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
redisRegistryService.refreshHeartbeat(toolName);
return ResponseEntity.ok("Heartbeat updated");
boolean success = redisRegistryService.refreshHeartbeat(toolName);
if (success) {
return ResponseEntity.ok("Heartbeat updated");
} else {
return ResponseEntity.status(404).body("Tool not found");
}
}
}

View File

@@ -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) {

View File

@@ -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)