Compare commits
10 Commits
05388566ba
...
d867551248
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d867551248 | ||
|
|
7edda9aa9e | ||
|
|
b431988ee6 | ||
|
|
d87af0cc82 | ||
|
|
3131440171 | ||
|
|
f8f17e7ea7 | ||
|
|
fce3df35c9 | ||
|
|
4b45acbded | ||
|
|
aceb1a9799 | ||
|
|
145ca90ed1 |
@@ -10,6 +10,7 @@
|
|||||||
## 명심해야 할 규칙 추가란
|
## 명심해야 할 규칙 추가란
|
||||||
* 이모지는 무조건 넣지 않는다
|
* 이모지는 무조건 넣지 않는다
|
||||||
* import 할것 무조건 한다
|
* import 할것 무조건 한다
|
||||||
|
* Git commit과 push는 사용자의 명시적인 허락(지시) 없이는 절대 수행하지 않는다.
|
||||||
* 자바 만들때는 무조건 아래 내용을 넣는다
|
* 자바 만들때는 무조건 아래 내용을 넣는다
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.axhub.biz.mcp.tool.sms
|
* @package io.shinhanlife.axhub.biz.mcp.tool.sms
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ public class PodScaffolder {
|
|||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=%s
|
mcp.namespace=%s
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
""".formatted(portStr, moduleName, shortName);
|
""".formatted(portStr, moduleName, shortName);
|
||||||
Files.writeString(resPath.resolve("application.properties"), applicationProperties);
|
Files.writeString(resPath.resolve("application.properties"), applicationProperties);
|
||||||
|
|
||||||
@@ -170,6 +173,23 @@ public class PodScaffolder {
|
|||||||
""";
|
""";
|
||||||
Files.writeString(resPath.resolve("application-local.properties"), applicationLocalProperties);
|
Files.writeString(resPath.resolve("application-local.properties"), applicationLocalProperties);
|
||||||
|
|
||||||
|
String applicationDevProperties = """
|
||||||
|
# Render 클라우드 환경 전용 설정
|
||||||
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
|
axhub.tool.url=https://%s.onrender.com
|
||||||
|
|
||||||
|
# EIMS 동적 라우팅 접속 정보 (Mock)
|
||||||
|
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
|
||||||
|
""".formatted(moduleName);
|
||||||
|
Files.writeString(resPath.resolve("application-dev.properties"), applicationDevProperties);
|
||||||
|
|
||||||
String logbackXml = """
|
String logbackXml = """
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
|||||||
@@ -62,8 +62,11 @@ public class McpRouterController {
|
|||||||
|
|
||||||
@PostMapping("/tools/call")
|
@PostMapping("/tools/call")
|
||||||
public ResponseEntity<?> callTool(@RequestBody Map<String, Object> payload,
|
public ResponseEntity<?> callTool(@RequestBody Map<String, Object> payload,
|
||||||
|
@RequestHeader(value = "X-Agent-Id", required = false) String agentId,
|
||||||
@RequestHeader(value = "X-Tenant-Id", required = false, defaultValue = "system") String tenantId) {
|
@RequestHeader(value = "X-Tenant-Id", required = false, defaultValue = "system") String tenantId) {
|
||||||
|
|
||||||
|
String effectiveTenantId = (agentId != null && !agentId.trim().isEmpty()) ? agentId : tenantId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("[Admin UI -> MCP Gateway] 동적 툴 실행 요청 수신: {}", objectMapper.writeValueAsString(payload));
|
log.info("[Admin UI -> MCP Gateway] 동적 툴 실행 요청 수신: {}", objectMapper.writeValueAsString(payload));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -71,7 +74,7 @@ public class McpRouterController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object result = executeService.execute(payload, tenantId);
|
Object result = executeService.execute(payload, effectiveTenantId);
|
||||||
|
|
||||||
io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcResponse response = new io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcResponse();
|
io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcResponse response = new io.shinhanlife.axhub.biz.mcp.adapter.dto.JsonRpcResponse();
|
||||||
response.setJsonrpc("2.0");
|
response.setJsonrpc("2.0");
|
||||||
@@ -89,7 +92,7 @@ public class McpRouterController {
|
|||||||
log.warn(" [보안 차단] 권한 오류: {}", se.getMessage());
|
log.warn(" [보안 차단] 권한 오류: {}", se.getMessage());
|
||||||
return ResponseEntity.status(403).body(Map.of("error", se.getMessage()));
|
return ResponseEntity.status(403).body(Map.of("error", se.getMessage()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(" 파이프라인 실행 중 오류: {}", e.getMessage());
|
log.error(" 파이프라인 실행 중 오류:", e);
|
||||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,9 +166,17 @@ public class ExecuteService {
|
|||||||
|
|
||||||
private Object executeWithResilience(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, Map<String, Object> payload) {
|
private Object executeWithResilience(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, Map<String, Object> payload) {
|
||||||
RetryPolicy retryPolicy = retryPolicy(metadata, arguments);
|
RetryPolicy retryPolicy = retryPolicy(metadata, arguments);
|
||||||
|
|
||||||
|
int failureThreshold = (metadata.getCircuitBreakerFailureThreshold() != null && metadata.getCircuitBreakerFailureThreshold() > 0)
|
||||||
|
? metadata.getCircuitBreakerFailureThreshold()
|
||||||
|
: properties.circuitBreakerFailureThreshold();
|
||||||
|
|
||||||
|
long openMillis = (metadata.getCircuitBreakerOpenMillis() != null && metadata.getCircuitBreakerOpenMillis() > 0)
|
||||||
|
? metadata.getCircuitBreakerOpenMillis()
|
||||||
|
: properties.circuitBreakerOpenMillis();
|
||||||
|
|
||||||
CircuitBreaker circuitBreaker = circuitBreakerService.breaker("tool:" + metadata.getName(),
|
CircuitBreaker circuitBreaker = circuitBreakerService.breaker("tool:" + metadata.getName(),
|
||||||
metadata.getCircuitBreakerFailureThreshold(),
|
failureThreshold, openMillis);
|
||||||
metadata.getCircuitBreakerOpenMillis());
|
|
||||||
|
|
||||||
ToolExecutionException lastError = null;
|
ToolExecutionException lastError = null;
|
||||||
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
||||||
|
|||||||
@@ -82,7 +82,14 @@ public class ToolPlanner {
|
|||||||
|
|
||||||
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
||||||
if (tenantId != null && toolMetadata.getCategoryKey() != null) {
|
if (tenantId != null && toolMetadata.getCategoryKey() != null) {
|
||||||
List<String> allowedDomains = securityProperties.getTenantDomains().get(tenantId);
|
String normalizedTenantId = tenantId.toLowerCase();
|
||||||
|
List<String> allowedDomains = securityProperties.getTenantDomains().get(normalizedTenantId);
|
||||||
|
|
||||||
|
// 만약 대소문자 변환 후에도 없으면 원래 값으로 한 번 더 시도 (하위 호환성)
|
||||||
|
if (allowedDomains == null) {
|
||||||
|
allowedDomains = securityProperties.getTenantDomains().get(tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
if (allowedDomains == null ||
|
if (allowedDomains == null ||
|
||||||
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getCategoryKey()))) {
|
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getCategoryKey()))) {
|
||||||
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey());
|
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey());
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ mcp.max-stream-bytes=1048576
|
|||||||
mcp.max-response-bytes-from-tool=5242880
|
mcp.max-response-bytes-from-tool=5242880
|
||||||
mcp.tool-registry-sync-interval-millis=5000
|
mcp.tool-registry-sync-interval-millis=5000
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
# Spring AI OpenAI / Gemini API Config
|
# Spring AI OpenAI / Gemini API Config
|
||||||
spring.ai.openai.api-key=AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg
|
spring.ai.openai.api-key=AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg
|
||||||
spring.ai.openai.base-url=https://generativelanguage.googleapis.com/v1beta/openai/
|
spring.ai.openai.base-url=https://generativelanguage.googleapis.com/v1beta/openai/
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Render 클라우드 환경 전용 설정
|
||||||
|
server.port=${PORT:8083}
|
||||||
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
|
axhub.tool.url=https://axhub-tool-email.onrender.com
|
||||||
|
|
||||||
|
# EIMS 동적 라우팅 접속 정보 (Mock)
|
||||||
|
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
|
||||||
@@ -8,3 +8,6 @@ logging.level.org.apache.kafka=ERROR
|
|||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=email
|
mcp.namespace=email
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Render 클라우드 환경 전용 설정
|
# Render 클라우드 환경 전용 설정
|
||||||
|
server.port=${PORT:8084}
|
||||||
axhub.gateway.url=https://axhub-gateway.onrender.com
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
axhub.tool.url=https://axhub-tool-other.onrender.com
|
axhub.tool.url=https://axhub-tool-other.onrender.com
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ logging.level.org.apache.kafka=ERROR
|
|||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=other
|
mcp.namespace=other
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Render 클라우드 환경 전용 설정
|
||||||
|
server.port=${PORT:8085}
|
||||||
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
|
axhub.tool.url=https://axhub-tool-payment.onrender.com
|
||||||
|
|
||||||
|
# EIMS 동적 라우팅 접속 정보 (Mock)
|
||||||
|
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
|
||||||
@@ -2,3 +2,6 @@ spring.profiles.active=local
|
|||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=payment
|
mcp.namespace=payment
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Render 클라우드 환경 전용 설정
|
# Render 클라우드 환경 전용 설정
|
||||||
|
server.port=${PORT:8082}
|
||||||
axhub.gateway.url=https://axhub-gateway.onrender.com
|
axhub.gateway.url=https://axhub-gateway.onrender.com
|
||||||
axhub.tool.url=https://axhub-tool-sms.onrender.com
|
axhub.tool.url=https://axhub-tool-sms.onrender.com
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ logging.level.org.apache.kafka=ERROR
|
|||||||
|
|
||||||
# Auto Prefix Namespace
|
# Auto Prefix Namespace
|
||||||
mcp.namespace=sms
|
mcp.namespace=sms
|
||||||
|
|
||||||
|
# API 보안 키 설정
|
||||||
|
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||||
|
|||||||
Reference in New Issue
Block a user