refactor: remove redis and switch to in-memory, rollback incomplete Spring AI 2.0 upgrade
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 4m31s
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 4m31s
This commit is contained in:
@@ -59,7 +59,7 @@ jobs:
|
|||||||
# 4. 마운트된 /app 디렉토리로 이동하여 호스트의 도커 컴포즈 제어!
|
# 4. 마운트된 /app 디렉토리로 이동하여 호스트의 도커 컴포즈 제어!
|
||||||
cd /app
|
cd /app
|
||||||
docker system prune -f
|
docker system prune -f
|
||||||
ACTIVE_PROFILE=dev docker compose up -d --build --pull always --remove-orphans gateway redis mci-mock dozzle was-sms was-oth
|
ACTIVE_PROFILE=dev docker compose up -d --build --pull always --remove-orphans gateway mci-mock dozzle was-sms was-oth
|
||||||
|
|
||||||
# 5. 배포 후 대롱대롱 매달려 있는 가비지 이미지 및 빌드 캐시 자동 소거 청소!
|
# 5. 배포 후 대롱대롱 매달려 있는 가비지 이미지 및 빌드 캐시 자동 소거 청소!
|
||||||
docker image prune -a -f
|
docker image prune -a -f
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ dependencies {
|
|||||||
// 등록된 Excel 양식을 보존하면서 Tool 문서를 생성합니다.
|
// 등록된 Excel 양식을 보존하면서 Tool 문서를 생성합니다.
|
||||||
implementation 'org.apache.poi:poi-ooxml:5.5.1'
|
implementation 'org.apache.poi:poi-ooxml:5.5.1'
|
||||||
|
|
||||||
// Tool Registry 및 분산 캐시 연동에 사용합니다.
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
|
||||||
|
|
||||||
// Tool/Registry 관련 DB 조회와 MyBatis Mapper 실행에 사용합니다.
|
// Tool/Registry 관련 DB 조회와 MyBatis Mapper 실행에 사용합니다.
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||||
|
|
||||||
// Gateway가 /mcp Endpoint를 MCP Server로 노출하도록 지원합니다.
|
// Gateway가 /mcp Endpoint를 MCP Server로 노출하도록 지원합니다.
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public class McpGatewayProperties {
|
|||||||
private Boolean agentClaimsRequired;
|
private Boolean agentClaimsRequired;
|
||||||
private Boolean trustedClaimsRequired;
|
private Boolean trustedClaimsRequired;
|
||||||
private Boolean writeApprovalRequired;
|
private Boolean writeApprovalRequired;
|
||||||
private Boolean redisTraceEnabled;
|
private Boolean traceEnabled;
|
||||||
private Long redisTraceTtlSeconds;
|
private Long traceRetentionSize;
|
||||||
private Long toolTimeoutMillis;
|
private Long toolTimeoutMillis;
|
||||||
private Integer retryMaxAttempts;
|
private Integer retryMaxAttempts;
|
||||||
private Long retryInitialBackoffMillis;
|
private Long retryInitialBackoffMillis;
|
||||||
@@ -70,8 +70,8 @@ public class McpGatewayProperties {
|
|||||||
public void setAgentClaimsRequired(Boolean agentClaimsRequired) { this.agentClaimsRequired = agentClaimsRequired; }
|
public void setAgentClaimsRequired(Boolean agentClaimsRequired) { this.agentClaimsRequired = agentClaimsRequired; }
|
||||||
public void setTrustedClaimsRequired(Boolean trustedClaimsRequired) { this.trustedClaimsRequired = trustedClaimsRequired; }
|
public void setTrustedClaimsRequired(Boolean trustedClaimsRequired) { this.trustedClaimsRequired = trustedClaimsRequired; }
|
||||||
public void setWriteApprovalRequired(Boolean writeApprovalRequired) { this.writeApprovalRequired = writeApprovalRequired; }
|
public void setWriteApprovalRequired(Boolean writeApprovalRequired) { this.writeApprovalRequired = writeApprovalRequired; }
|
||||||
public void setRedisTraceEnabled(Boolean redisTraceEnabled) { this.redisTraceEnabled = redisTraceEnabled; }
|
public void setTraceEnabled(Boolean traceEnabled) { this.traceEnabled = traceEnabled; }
|
||||||
public void setRedisTraceTtlSeconds(Long redisTraceTtlSeconds) { this.redisTraceTtlSeconds = redisTraceTtlSeconds; }
|
public void setTraceRetentionSize(Long traceRetentionSize) { this.traceRetentionSize = traceRetentionSize; }
|
||||||
public void setToolTimeoutMillis(Long toolTimeoutMillis) { this.toolTimeoutMillis = toolTimeoutMillis; }
|
public void setToolTimeoutMillis(Long toolTimeoutMillis) { this.toolTimeoutMillis = toolTimeoutMillis; }
|
||||||
public void setRetryMaxAttempts(Integer retryMaxAttempts) { this.retryMaxAttempts = retryMaxAttempts; }
|
public void setRetryMaxAttempts(Integer retryMaxAttempts) { this.retryMaxAttempts = retryMaxAttempts; }
|
||||||
public void setRetryInitialBackoffMillis(Long retryInitialBackoffMillis) { this.retryInitialBackoffMillis = retryInitialBackoffMillis; }
|
public void setRetryInitialBackoffMillis(Long retryInitialBackoffMillis) { this.retryInitialBackoffMillis = retryInitialBackoffMillis; }
|
||||||
@@ -103,8 +103,8 @@ public class McpGatewayProperties {
|
|||||||
public boolean agentClaimsRequired() { return agentClaimsRequired == null || agentClaimsRequired; }
|
public boolean agentClaimsRequired() { return agentClaimsRequired == null || agentClaimsRequired; }
|
||||||
public boolean trustedClaimsRequired() { return trustedClaimsRequired == null || trustedClaimsRequired; }
|
public boolean trustedClaimsRequired() { return trustedClaimsRequired == null || trustedClaimsRequired; }
|
||||||
public boolean writeApprovalRequired() { return writeApprovalRequired == null || writeApprovalRequired; }
|
public boolean writeApprovalRequired() { return writeApprovalRequired == null || writeApprovalRequired; }
|
||||||
public boolean redisTraceEnabled() { return redisTraceEnabled == null || redisTraceEnabled; }
|
public boolean traceEnabled() { return traceEnabled == null || traceEnabled; }
|
||||||
public long redisTraceTtlSeconds() { return redisTraceTtlSeconds == null || redisTraceTtlSeconds < 1 ? 3_600 : redisTraceTtlSeconds; }
|
public long traceRetentionSize() { return traceRetentionSize == null || traceRetentionSize < 1 ? 1000 : traceRetentionSize; }
|
||||||
public long toolTimeoutMillis() { return toolTimeoutMillis == null || toolTimeoutMillis < 1 ? 5_000 : toolTimeoutMillis; }
|
public long toolTimeoutMillis() { return toolTimeoutMillis == null || toolTimeoutMillis < 1 ? 5_000 : toolTimeoutMillis; }
|
||||||
public int retryMaxAttempts() { return retryMaxAttempts == null || retryMaxAttempts < 1 ? 3 : retryMaxAttempts; }
|
public int retryMaxAttempts() { return retryMaxAttempts == null || retryMaxAttempts < 1 ? 3 : retryMaxAttempts; }
|
||||||
public long retryInitialBackoffMillis() { return retryInitialBackoffMillis == null || retryInitialBackoffMillis < 1 ? 1_000 : retryInitialBackoffMillis; }
|
public long retryInitialBackoffMillis() { return retryInitialBackoffMillis == null || retryInitialBackoffMillis < 1 ? 1_000 : retryInitialBackoffMillis; }
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package io.shinhanlife.dap.mcg.config;
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
|
|
||||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcg.config
|
|
||||||
* @className RedisConfig
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class RedisConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisTemplate<String, ToolMetadata> redisTemplate(RedisConnectionFactory connectionFactory) {
|
|
||||||
RedisTemplate<String, ToolMetadata> template = new RedisTemplate<>();
|
|
||||||
template.setConnectionFactory(connectionFactory);
|
|
||||||
|
|
||||||
// 1. Key는 무조건 String
|
|
||||||
template.setKeySerializer(new StringRedisSerializer());
|
|
||||||
template.setHashKeySerializer(new StringRedisSerializer());
|
|
||||||
|
|
||||||
// 2. Value는 Generic 직렬화기 사용 (생성자 인자 없음)
|
|
||||||
Jackson2JsonRedisSerializer<ToolMetadata> serializer = new Jackson2JsonRedisSerializer<>(ToolMetadata.class);
|
|
||||||
template.setValueSerializer(serializer);
|
|
||||||
template.setHashValueSerializer(serializer);
|
|
||||||
|
|
||||||
template.afterPropertiesSet();
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ package io.shinhanlife.dap.mcg.presentation;
|
|||||||
*/
|
*/
|
||||||
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
import io.shinhanlife.dap.mcg.registry.InMemoryRegistryService;
|
||||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -41,7 +41,7 @@ public class ChatController {
|
|||||||
|
|
||||||
private final ExecuteService executeService;
|
private final ExecuteService executeService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final RedisRegistryService registryService;
|
private final InMemoryRegistryService registryService;
|
||||||
private final ChatClient.Builder chatClientBuilder;
|
private final ChatClient.Builder chatClientBuilder;
|
||||||
private final McpRouterController mcpRouterController;
|
private final McpRouterController mcpRouterController;
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse;
|
|||||||
import io.shinhanlife.dap.lib.adapter.dto.Params;
|
import io.shinhanlife.dap.lib.adapter.dto.Params;
|
||||||
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
import io.shinhanlife.dap.mcg.registry.InMemoryRegistryService;
|
||||||
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
import io.shinhanlife.dap.mcg.service.ExecuteService;
|
||||||
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -49,19 +49,19 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
|||||||
@Tag(name = "MCP Router API", description = "AI Agent의 요청을 받아 Adapter 시스템으로 라우팅하는 게이트웨이 API")
|
@Tag(name = "MCP Router API", description = "AI Agent의 요청을 받아 Adapter 시스템으로 라우팅하는 게이트웨이 API")
|
||||||
public class McpRouterController {
|
public class McpRouterController {
|
||||||
|
|
||||||
private final RedisRegistryService redisRegistryService;
|
private final InMemoryRegistryService registryService;
|
||||||
private final ExecuteService executeService;
|
private final ExecuteService executeService;
|
||||||
private final SecurityProperties securityProperties;
|
private final SecurityProperties securityProperties;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final GatewayFallbackProperties gatewayFallbackProperties;
|
private final GatewayFallbackProperties gatewayFallbackProperties;
|
||||||
private final RestClient restClient;
|
private final RestClient restClient;
|
||||||
|
|
||||||
public McpRouterController(RedisRegistryService redisRegistryService,
|
public McpRouterController(InMemoryRegistryService registryService,
|
||||||
ExecuteService executeService,
|
ExecuteService executeService,
|
||||||
SecurityProperties securityProperties,
|
SecurityProperties securityProperties,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
GatewayFallbackProperties gatewayFallbackProperties) {
|
GatewayFallbackProperties gatewayFallbackProperties) {
|
||||||
this.redisRegistryService = redisRegistryService;
|
this.registryService = registryService;
|
||||||
this.executeService = executeService;
|
this.executeService = executeService;
|
||||||
this.securityProperties = securityProperties;
|
this.securityProperties = securityProperties;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
@@ -115,12 +115,12 @@ public class McpRouterController {
|
|||||||
private List<ToolMetadata> fetchAllActiveTools() {
|
private List<ToolMetadata> fetchAllActiveTools() {
|
||||||
List<ToolMetadata> activeTools = new ArrayList<>();
|
List<ToolMetadata> activeTools = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
activeTools.addAll(redisRegistryService.getAllTools()
|
activeTools.addAll(registryService.getAllTools()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(ToolMetadata::getVisible)
|
.filter(ToolMetadata::getVisible)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
} catch (org.springframework.data.redis.RedisConnectionFailureException exception) {
|
} catch (Exception exception) {
|
||||||
log.warn("Redis is unavailable. Fetching tools from configured fallback Tool Pods instead.");
|
log.warn("Registry is unavailable. Fetching tools from configured fallback Tool Pods instead.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> knownTools = activeTools.stream()
|
Set<String> knownTools = activeTools.stream()
|
||||||
@@ -240,19 +240,19 @@ public class McpRouterController {
|
|||||||
|
|
||||||
@PostMapping("/registry/register")
|
@PostMapping("/registry/register")
|
||||||
public ResponseEntity<String> registerTool(@RequestBody ToolMetadata meta) {
|
public ResponseEntity<String> registerTool(@RequestBody ToolMetadata meta) {
|
||||||
redisRegistryService.saveTool(meta);
|
registryService.saveTool(meta);
|
||||||
return ResponseEntity.ok("Registered");
|
return ResponseEntity.ok("Registered");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/registry/deregister")
|
@PostMapping("/registry/deregister")
|
||||||
public ResponseEntity<String> deregisterTool(@RequestBody String uid) {
|
public ResponseEntity<String> deregisterTool(@RequestBody String uid) {
|
||||||
redisRegistryService.removeTool(uid);
|
registryService.removeTool(uid);
|
||||||
return ResponseEntity.ok("Deregistered");
|
return ResponseEntity.ok("Deregistered");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/registry/heartbeat")
|
@PostMapping("/registry/heartbeat")
|
||||||
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
|
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
|
||||||
boolean success = redisRegistryService.refreshHeartbeat(uid);
|
boolean success = registryService.refreshHeartbeat(uid);
|
||||||
if (success) {
|
if (success) {
|
||||||
return ResponseEntity.ok("Heartbeat updated");
|
return ResponseEntity.ok("Heartbeat updated");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
package io.shinhanlife.dap.mcg.redis;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcg.redis
|
|
||||||
* @className RedisToolTraceService
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
|
||||||
import io.shinhanlife.dap.mcg.security.McpRequestContext;
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.HexFormat;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RedisToolTraceService {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RedisToolTraceService.class);
|
|
||||||
private static final String KEY_PREFIX = "mcp:request:";
|
|
||||||
private static final String RECENT_KEY = "mcp:request:recent";
|
|
||||||
|
|
||||||
private final McpGatewayProperties properties;
|
|
||||||
private final ObjectProvider<StringRedisTemplate> redisProvider;
|
|
||||||
private final ObjectMapper json;
|
|
||||||
private final McpMonitorEventService monitorEvents;
|
|
||||||
private final Map<String, AttemptState> attemptStates = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public RedisToolTraceService(McpGatewayProperties properties,
|
|
||||||
ObjectProvider<StringRedisTemplate> redisProvider,
|
|
||||||
ObjectMapper json,
|
|
||||||
McpMonitorEventService monitorEvents) {
|
|
||||||
this.properties = properties;
|
|
||||||
this.redisProvider = redisProvider;
|
|
||||||
this.json = json;
|
|
||||||
this.monitorEvents = monitorEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent request has entered the MCP tool gateway.
|
|
||||||
*/
|
|
||||||
public void started(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments) {
|
|
||||||
save(context, metadata, arguments, "STARTED", 0, "", 0, 0, 0,
|
|
||||||
"Agent request entered MCP", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCP is attempting to call the target Tool server.
|
|
||||||
*/
|
|
||||||
public void attemptStarted(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
|
||||||
int attempt, int maxAttempts) {
|
|
||||||
save(context, metadata, arguments, "ATTEMPTING", 0, "", attempt, maxAttempts, 0,
|
|
||||||
"Calling tool server", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCP will retry the Tool call after backoff.
|
|
||||||
*/
|
|
||||||
public void retryWaiting(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
|
||||||
int failedAttempt, int maxAttempts, long backoffMillis, String failureType) {
|
|
||||||
save(context, metadata, arguments, "RETRY_WAITING", 0, failureType, failedAttempt, maxAttempts, backoffMillis,
|
|
||||||
"Retry will be attempted after backoff", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tool execution has finished.
|
|
||||||
*/
|
|
||||||
public void finished(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
|
||||||
long elapsedMillis, boolean success, String failureType, String responseText) {
|
|
||||||
save(context, metadata, arguments, success ? "SUCCESS" : "FAILED", elapsedMillis, failureType, 0, 0, 0,
|
|
||||||
success ? "Tool call completed" : "Tool call failed", responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recent live trace entries. One current entry is kept per request id.
|
|
||||||
*/
|
|
||||||
public List<String> recent() {
|
|
||||||
if (!properties.redisTraceEnabled()) {
|
|
||||||
return List.of("Redis trace is disabled. Set MCP_REDIS_TRACE_ENABLED=true.");
|
|
||||||
}
|
|
||||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
|
||||||
if (redis == null) {
|
|
||||||
return List.of("RedisTemplate is not available.");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
List<String> requestIds = redis.opsForList().range(RECENT_KEY, 0, 49);
|
|
||||||
if (requestIds == null || requestIds.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return requestIds.stream()
|
|
||||||
.map(requestId -> redis.opsForValue().get(KEY_PREFIX + requestId))
|
|
||||||
.filter(value -> value != null && !value.isBlank())
|
|
||||||
.toList();
|
|
||||||
} catch (Exception error) {
|
|
||||||
log.warn("Redis trace read failed. message={}", error.getMessage());
|
|
||||||
return List.of("Redis trace read failed: " + error.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Historical accumulation is intentionally disabled.
|
|
||||||
*
|
|
||||||
* The MCP monitor is used to observe the current Agent request flow only,
|
|
||||||
* so this method returns the same live entries as recent().
|
|
||||||
*/
|
|
||||||
public List<String> history() {
|
|
||||||
return recent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void save(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
|
||||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
|
||||||
long backoffMillis, String message, String responseText) {
|
|
||||||
String stateKey = stateKey(context, metadata);
|
|
||||||
if (attempt > 0 && maxAttempts > 0) {
|
|
||||||
attemptStates.put(stateKey, new AttemptState(attempt, maxAttempts));
|
|
||||||
}
|
|
||||||
String payload;
|
|
||||||
try {
|
|
||||||
payload = tracePayload(context, metadata, arguments, status, elapsedMillis, failureType, attempt,
|
|
||||||
maxAttempts, backoffMillis, message, responseText);
|
|
||||||
monitorEvents.publish(payload);
|
|
||||||
if ("SUCCESS".equals(status) || "FAILED".equals(status)) {
|
|
||||||
attemptStates.remove(stateKey);
|
|
||||||
}
|
|
||||||
} catch (Exception error) {
|
|
||||||
log.warn("MCP monitor event publish failed. requestId={} tool={} message={}",
|
|
||||||
context.requestId(), metadata.getName(), error.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!properties.redisTraceEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
|
||||||
if (redis == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
String requestId = context.requestId();
|
|
||||||
Duration ttl = Duration.ofSeconds(properties.redisTraceTtlSeconds());
|
|
||||||
redis.opsForValue().set(KEY_PREFIX + requestId, payload, ttl);
|
|
||||||
redis.opsForList().remove(RECENT_KEY, 0, requestId);
|
|
||||||
redis.opsForList().leftPush(RECENT_KEY, requestId);
|
|
||||||
redis.opsForList().trim(RECENT_KEY, 0, 49);
|
|
||||||
redis.expire(RECENT_KEY, ttl);
|
|
||||||
} catch (Exception error) {
|
|
||||||
log.warn("Redis trace save failed. requestId={} tool={} message={}",
|
|
||||||
context.requestId(), metadata.getName(), error.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String tracePayload(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
|
||||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
|
||||||
long backoffMillis, String message, String responseText) throws Exception {
|
|
||||||
AttemptState state = attemptStates.get(stateKey(context, metadata));
|
|
||||||
int displayAttempt = attempt > 0 ? attempt : state == null ? 0 : state.attempt();
|
|
||||||
int displayMaxAttempts = maxAttempts > 0 ? maxAttempts : state == null ? 0 : state.maxAttempts();
|
|
||||||
int retryCount = Math.max(0, displayAttempt - 1);
|
|
||||||
Map<String, Object> trace = new LinkedHashMap<>();
|
|
||||||
trace.put("requestId", context.requestId());
|
|
||||||
trace.put("traceGroupId", context.traceGroupId());
|
|
||||||
trace.put("agentId", context.agentId());
|
|
||||||
trace.put("userId", context.userId());
|
|
||||||
trace.put("clientAddress", context.clientAddress());
|
|
||||||
trace.put("toolName", metadata.getName());
|
|
||||||
trace.put("operationType", metadata.getOperationType() != null ? metadata.getOperationType().name() : "READ");
|
|
||||||
trace.put("status", status);
|
|
||||||
trace.put("message", message == null ? "" : message);
|
|
||||||
trace.put("failureType", failureType == null ? "" : failureType);
|
|
||||||
trace.put("attempt", displayAttempt);
|
|
||||||
trace.put("maxAttempts", displayMaxAttempts);
|
|
||||||
trace.put("retryCount", retryCount);
|
|
||||||
trace.put("backoffMillis", backoffMillis);
|
|
||||||
trace.put("elapsedMillis", elapsedMillis);
|
|
||||||
trace.put("idempotencyKeyHash", hashText(arguments.path("idempotencyKey").asText("")));
|
|
||||||
|
|
||||||
List<String> argNames = new ArrayList<>();
|
|
||||||
arguments.fieldNames().forEachRemaining(argNames::add);
|
|
||||||
trace.put("argumentNames", argNames);
|
|
||||||
|
|
||||||
trace.put("arguments", arguments.toString());
|
|
||||||
trace.put("responseSummary", responseSummary(responseText));
|
|
||||||
trace.put("timestamp", Instant.now().toString());
|
|
||||||
return json.writeValueAsString(trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redis Trace에는 Tool 응답 원문을 저장하지 않고 size/hash/상태/카운트만 저장합니다.
|
|
||||||
*/
|
|
||||||
Map<String, Object> responseSummary(String responseText) {
|
|
||||||
Map<String, Object> summary = new LinkedHashMap<>();
|
|
||||||
if (responseText == null || responseText.isBlank()) {
|
|
||||||
summary.put("bytes", 0);
|
|
||||||
summary.put("sha256", "");
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
byte[] bytes = responseText.getBytes(StandardCharsets.UTF_8);
|
|
||||||
summary.put("bytes", bytes.length);
|
|
||||||
summary.put("sha256", sha256(bytes));
|
|
||||||
try {
|
|
||||||
JsonNode root = json.readTree(responseText);
|
|
||||||
summary.put("success", root.path("success").asBoolean(false));
|
|
||||||
summary.put("status", root.path("status").asText(""));
|
|
||||||
summary.put("toolName", root.path("toolName").asText(""));
|
|
||||||
summary.put("pageSize", root.path("pageSize").asInt(0));
|
|
||||||
summary.put("pageCount", root.path("pageCount").asInt(0));
|
|
||||||
summary.put("returnedCount", root.path("returnedCount").asInt(0));
|
|
||||||
summary.put("totalCount", root.path("totalCount").asLong(0));
|
|
||||||
summary.put("hasMore", root.path("hasMore").asBoolean(false));
|
|
||||||
summary.put("nextCursor", root.path("nextCursor").asText(""));
|
|
||||||
summary.put("message", root.path("message").asText(""));
|
|
||||||
summary.put("resultRef", root.path("resultRef").asText(""));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
summary.put("parseable", false);
|
|
||||||
}
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String sha256(byte[] bytes) {
|
|
||||||
try {
|
|
||||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes));
|
|
||||||
} catch (Exception error) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String hashText(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return sha256(value.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String stateKey(McpRequestContext context, ToolMetadata metadata) {
|
|
||||||
return context.requestId() + ":" + metadata.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private record AttemptState(int attempt, int maxAttempts) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package io.shinhanlife.dap.mcg.registry;
|
||||||
|
|
||||||
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcg.registry
|
||||||
|
* @className InMemoryRegistryService
|
||||||
|
* @description AX HUB 인메모리 툴 레지스트리
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class InMemoryRegistryService {
|
||||||
|
|
||||||
|
private final Map<String, ToolMetadata> toolCache = new ConcurrentHashMap<>();
|
||||||
|
private static final long DEFAULT_TTL_MILLIS = 45000; // 45초
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴 등록 및 갱신
|
||||||
|
*/
|
||||||
|
public void saveTool(ToolMetadata meta) {
|
||||||
|
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||||
|
toolCache.put(meta.getUid(), meta);
|
||||||
|
log.info(" [InMemoryRegistry] 툴 등록 완료: {}", meta.getUid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 하트비트 갱신 (TTL 초기화)
|
||||||
|
*/
|
||||||
|
public boolean refreshHeartbeat(String uid) {
|
||||||
|
ToolMetadata meta = toolCache.get(uid);
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||||
|
log.debug(" [InMemoryRegistry] 하트비트 갱신: {}", uid);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.warn(" [InMemoryRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAvailablePods(String uid) {
|
||||||
|
ToolMetadata tool = getTool(uid);
|
||||||
|
if (tool != null && tool.getPodUrl() != null) {
|
||||||
|
return List.of(tool.getPodUrl());
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 실행 시 툴 정보 조회
|
||||||
|
*/
|
||||||
|
public ToolMetadata getTool(String uid) {
|
||||||
|
return toolCache.get(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴 이름으로 정보 조회
|
||||||
|
*/
|
||||||
|
public ToolMetadata getToolByName(String name) {
|
||||||
|
for (ToolMetadata tool : toolCache.values()) {
|
||||||
|
if (name.equals(tool.getName())) {
|
||||||
|
return tool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 등록된 모든 활성 툴 목록 조회
|
||||||
|
*/
|
||||||
|
public List<ToolMetadata> getAllTools() {
|
||||||
|
return List.copyOf(toolCache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴 명시적 제거 (Deregister)
|
||||||
|
*/
|
||||||
|
public void removeTool(String uid) {
|
||||||
|
toolCache.remove(uid);
|
||||||
|
log.info(" [InMemoryRegistry] 툴 삭제 완료: {}", uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 만료된 툴을 스케줄러로 삭제합니다. (10초마다 실행)
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 10000)
|
||||||
|
public void evictExpiredTools() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
List<String> expiredKeys = toolCache.entrySet().stream()
|
||||||
|
.filter(entry -> (now - entry.getValue().getLastHeartbeat()) > DEFAULT_TTL_MILLIS)
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (String key : expiredKeys) {
|
||||||
|
toolCache.remove(key);
|
||||||
|
log.info(" [InMemoryRegistry] TTL 초과로 툴 자동 삭제: {}", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package io.shinhanlife.dap.mcg.registry;
|
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcg.registry
|
|
||||||
* @className RedisRegistryService
|
|
||||||
* @description AX HUB 시스템 처리 클래스
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class RedisRegistryService {
|
|
||||||
|
|
||||||
private final RedisTemplate<String, ToolMetadata> redisTemplate;
|
|
||||||
private static final String KEY_PREFIX = "mcp:tool:";
|
|
||||||
private static final Duration DEFAULT_TTL = Duration.ofSeconds(45); // 실무 하트비트 주기 반영
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료)
|
|
||||||
*/
|
|
||||||
public void saveTool(ToolMetadata meta) {
|
|
||||||
String key = KEY_PREFIX + meta.getUid();
|
|
||||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
|
||||||
redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL);
|
|
||||||
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getUid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 하트비트 갱신 (TTL 초기화)
|
|
||||||
*/
|
|
||||||
public boolean refreshHeartbeat(String uid) {
|
|
||||||
String key = KEY_PREFIX + uid;
|
|
||||||
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
|
||||||
if (Boolean.TRUE.equals(exists)) {
|
|
||||||
log.debug(" [RedisRegistry] 하트비트 갱신: {}", uid);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public List<String> getAvailablePods(String uid) {
|
|
||||||
ToolMetadata tool = getTool(uid);
|
|
||||||
|
|
||||||
if (tool != null && tool.getPodUrl() != null) {
|
|
||||||
return List.of(tool.getPodUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 실행 시 툴 정보 조회 (Tool Execution 시 참조)
|
|
||||||
*/
|
|
||||||
public ToolMetadata getTool(String uid) {
|
|
||||||
return redisTemplate.opsForValue().get(KEY_PREFIX + uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 툴 이름으로 정보 조회
|
|
||||||
*/
|
|
||||||
public ToolMetadata getToolByName(String name) {
|
|
||||||
List<ToolMetadata> allTools = getAllTools();
|
|
||||||
for (ToolMetadata tool : allTools) {
|
|
||||||
if (name.equals(tool.getName())) {
|
|
||||||
return tool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 등록된 모든 활성 툴 목록 조회
|
|
||||||
*/
|
|
||||||
public List<ToolMetadata> getAllTools() {
|
|
||||||
Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*");
|
|
||||||
if (keys == null || keys.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<ToolMetadata> tools = redisTemplate.opsForValue().multiGet(keys);
|
|
||||||
return tools != null ? tools.stream().filter(Objects::nonNull).collect(Collectors.toList()) : List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 툴 명시적 제거 (Deregister)
|
|
||||||
*/
|
|
||||||
public void removeTool(String uid) {
|
|
||||||
redisTemplate.delete(KEY_PREFIX + uid);
|
|
||||||
log.info(" [RedisRegistry] 툴 삭제 완료: {}", uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ import io.shinhanlife.dap.mcg.audit.AuditLogService;
|
|||||||
import io.shinhanlife.dap.mcg.resilience.CircuitBreaker;
|
import io.shinhanlife.dap.mcg.resilience.CircuitBreaker;
|
||||||
import io.shinhanlife.dap.mcg.resilience.CircuitBreakerService;
|
import io.shinhanlife.dap.mcg.resilience.CircuitBreakerService;
|
||||||
import io.shinhanlife.dap.mcg.security.ToolAuthorizationService;
|
import io.shinhanlife.dap.mcg.security.ToolAuthorizationService;
|
||||||
import io.shinhanlife.dap.mcg.redis.RedisToolTraceService;
|
import io.shinhanlife.dap.mcg.trace.InMemoryToolTraceService;
|
||||||
|
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -62,7 +62,7 @@ public class ExecuteService {
|
|||||||
private final AuditLogService auditLogService;
|
private final AuditLogService auditLogService;
|
||||||
private final CircuitBreakerService circuitBreakerService;
|
private final CircuitBreakerService circuitBreakerService;
|
||||||
private final ToolAuthorizationService authorizationService;
|
private final ToolAuthorizationService authorizationService;
|
||||||
private final RedisToolTraceService redisTrace;
|
private final InMemoryToolTraceService traceService;
|
||||||
private final McpGatewayProperties properties;
|
private final McpGatewayProperties properties;
|
||||||
private final LargeToolResponseService largeResponses;
|
private final LargeToolResponseService largeResponses;
|
||||||
private final PaginationRequestValidator paginationValidator;
|
private final PaginationRequestValidator paginationValidator;
|
||||||
@@ -79,7 +79,7 @@ public class ExecuteService {
|
|||||||
AuditLogService auditLogService,
|
AuditLogService auditLogService,
|
||||||
CircuitBreakerService circuitBreakerService,
|
CircuitBreakerService circuitBreakerService,
|
||||||
ToolAuthorizationService authorizationService,
|
ToolAuthorizationService authorizationService,
|
||||||
RedisToolTraceService redisTrace,
|
InMemoryToolTraceService traceService,
|
||||||
McpGatewayProperties properties,
|
McpGatewayProperties properties,
|
||||||
LargeToolResponseService largeResponses,
|
LargeToolResponseService largeResponses,
|
||||||
PaginationRequestValidator paginationValidator,
|
PaginationRequestValidator paginationValidator,
|
||||||
@@ -94,7 +94,7 @@ public class ExecuteService {
|
|||||||
this.auditLogService = auditLogService;
|
this.auditLogService = auditLogService;
|
||||||
this.circuitBreakerService = circuitBreakerService;
|
this.circuitBreakerService = circuitBreakerService;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.redisTrace = redisTrace;
|
this.traceService = traceService;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.largeResponses = largeResponses;
|
this.largeResponses = largeResponses;
|
||||||
this.paginationValidator = paginationValidator;
|
this.paginationValidator = paginationValidator;
|
||||||
@@ -133,7 +133,7 @@ public class ExecuteService {
|
|||||||
|
|
||||||
long startedAt = System.nanoTime();
|
long startedAt = System.nanoTime();
|
||||||
auditLogService.toolStarted(context, toolName, argumentsNode);
|
auditLogService.toolStarted(context, toolName, argumentsNode);
|
||||||
redisTrace.started(context, metadata, argumentsNode);
|
traceService.started(context, metadata, argumentsNode);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authorizationService.authorize(context, metadata);
|
authorizationService.authorize(context, metadata);
|
||||||
@@ -174,13 +174,13 @@ public class ExecuteService {
|
|||||||
finalResult.put("original_size", originalSize);
|
finalResult.put("original_size", originalSize);
|
||||||
|
|
||||||
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
traceService.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
||||||
|
|
||||||
return finalResult;
|
return finalResult;
|
||||||
} catch (ToolExecutionException error) {
|
} catch (ToolExecutionException error) {
|
||||||
long elapsedMillis = elapsedMillis(startedAt);
|
long elapsedMillis = elapsedMillis(startedAt);
|
||||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
traceService.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
||||||
|
|
||||||
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||||
errorResult.put("status", "error");
|
errorResult.put("status", "error");
|
||||||
@@ -194,7 +194,7 @@ public class ExecuteService {
|
|||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
long elapsedMillis = elapsedMillis(startedAt);
|
long elapsedMillis = elapsedMillis(startedAt);
|
||||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
||||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
traceService.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
||||||
|
|
||||||
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
|
||||||
errorResult.put("status", "error");
|
errorResult.put("status", "error");
|
||||||
@@ -225,7 +225,7 @@ public class ExecuteService {
|
|||||||
ToolExecutionException lastError = null;
|
ToolExecutionException lastError = null;
|
||||||
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
||||||
try {
|
try {
|
||||||
redisTrace.attemptStarted(context, metadata, arguments, attempt, retryPolicy.maxAttempts());
|
traceService.attemptStarted(context, metadata, arguments, attempt, retryPolicy.maxAttempts());
|
||||||
circuitBreaker.beforeCall();
|
circuitBreaker.beforeCall();
|
||||||
Object result = executeOnce(context, metadata, arguments, payload);
|
Object result = executeOnce(context, metadata, arguments, payload);
|
||||||
circuitBreaker.recordSuccess();
|
circuitBreaker.recordSuccess();
|
||||||
@@ -237,7 +237,7 @@ public class ExecuteService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
long backoffMillis = retryPolicy.backoffMillis(attempt);
|
long backoffMillis = retryPolicy.backoffMillis(attempt);
|
||||||
redisTrace.retryWaiting(context, metadata, arguments, attempt, retryPolicy.maxAttempts(), backoffMillis,
|
traceService.retryWaiting(context, metadata, arguments, attempt, retryPolicy.maxAttempts(), backoffMillis,
|
||||||
error.failureType().name());
|
error.failureType().name());
|
||||||
sleepBeforeRetry(metadata.getName(), attempt, backoffMillis, error.failureType());
|
sleepBeforeRetry(metadata.getName(), attempt, backoffMillis, error.failureType());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,34 +2,29 @@ package io.shinhanlife.dap.mcg.service;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcg.service
|
* @package io.shinhanlife.dap.mcg.service
|
||||||
* @className KillSwitchService
|
* @className KillSwitchService
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description AX HUB 시스템 킬 스위치 서비스 (인메모리)
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
* @create 2026.09.01
|
* @create 2026.09.01
|
||||||
* <pre>
|
|
||||||
* ---------- 개정이력 ----------
|
|
||||||
* 수정일 수정자 수정내용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초생성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class KillSwitchService {
|
public class KillSwitchService {
|
||||||
|
|
||||||
// 기본 제공되는 StringRedisTemplate 사용
|
// 인메모리 맵으로 변경
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final Map<String, String> killSwitchMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public void checkAgent(String tenantId) {
|
public void checkAgent(String tenantId) {
|
||||||
String key = "mcp:kill:agent:" + tenantId;
|
String key = "mcp:kill:agent:" + tenantId;
|
||||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||||
log.warn(" [KillSwitch] 차단된 에이전트 접근 시도: {}", tenantId);
|
log.warn(" [KillSwitch] 차단된 에이전트 접근 시도: {}", tenantId);
|
||||||
throw new SecurityException(" 비상 차단: 해당 테넌트(" + tenantId + ")의 접근이 관리자에 의해 차단되었습니다.");
|
throw new SecurityException(" 비상 차단: 해당 테넌트(" + tenantId + ")의 접근이 관리자에 의해 차단되었습니다.");
|
||||||
}
|
}
|
||||||
@@ -39,7 +34,7 @@ public class KillSwitchService {
|
|||||||
if (toolName == null || toolName.isBlank()) return;
|
if (toolName == null || toolName.isBlank()) return;
|
||||||
|
|
||||||
String key = "mcp:kill:tool:" + toolName;
|
String key = "mcp:kill:tool:" + toolName;
|
||||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||||
log.warn(" [KillSwitch] 차단된 툴 실행 시도: {}", toolName);
|
log.warn(" [KillSwitch] 차단된 툴 실행 시도: {}", toolName);
|
||||||
throw new SecurityException(" 비상 차단: 해당 툴(" + toolName + ")의 실행이 관리자에 의해 차단되었습니다.");
|
throw new SecurityException(" 비상 차단: 해당 툴(" + toolName + ")의 실행이 관리자에 의해 차단되었습니다.");
|
||||||
}
|
}
|
||||||
@@ -49,7 +44,7 @@ public class KillSwitchService {
|
|||||||
if (integrationType == null || integrationType.isBlank()) return;
|
if (integrationType == null || integrationType.isBlank()) return;
|
||||||
|
|
||||||
String key = "mcp:kill:route:" + integrationType;
|
String key = "mcp:kill:route:" + integrationType;
|
||||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
if ("true".equalsIgnoreCase(killSwitchMap.get(key))) {
|
||||||
log.warn(" [KillSwitch] 차단된 라우트 통신 시도: {}", integrationType);
|
log.warn(" [KillSwitch] 차단된 라우트 통신 시도: {}", integrationType);
|
||||||
throw new SecurityException(" 비상 차단: 해당 레거시 라우트(" + integrationType + ")로의 통신이 관리자에 의해 차단되었습니다.");
|
throw new SecurityException(" 비상 차단: 해당 레거시 라우트(" + integrationType + ")로의 통신이 관리자에 의해 차단되었습니다.");
|
||||||
}
|
}
|
||||||
@@ -58,14 +53,14 @@ public class KillSwitchService {
|
|||||||
// 관리자 API용 토글 메서드
|
// 관리자 API용 토글 메서드
|
||||||
public void toggleKillSwitch(String type, String target, boolean state) {
|
public void toggleKillSwitch(String type, String target, boolean state) {
|
||||||
String key = "mcp:kill:" + type + ":" + target;
|
String key = "mcp:kill:" + type + ":" + target;
|
||||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(state));
|
killSwitchMap.put(key, String.valueOf(state));
|
||||||
log.info(" [KillSwitch] 상태 변경: {} -> {} (차단 상태: {})", type, target, state);
|
log.info(" [KillSwitch] 상태 변경: {} -> {} (차단 상태: {})", type, target, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 관리자 API용 삭제 메서드 (Redis 키 자체를 완전 삭제)
|
// 관리자 API용 삭제 메서드 (맵 항목 삭제)
|
||||||
public void removeKillSwitch(String type, String target) {
|
public void removeKillSwitch(String type, String target) {
|
||||||
String key = "mcp:kill:" + type + ":" + target;
|
String key = "mcp:kill:" + type + ":" + target;
|
||||||
stringRedisTemplate.delete(key);
|
killSwitchMap.remove(key);
|
||||||
log.info(" [KillSwitch] 차단 키 완전 삭제: {} -> {}", type, target);
|
log.info(" [KillSwitch] 차단 키 완전 삭제: {} -> {}", type, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcg.service;
|
package io.shinhanlife.dap.mcg.service;
|
||||||
|
|
||||||
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
import io.shinhanlife.dap.lib.mcp.security.SecurityProperties;
|
||||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
import io.shinhanlife.dap.mcg.registry.InMemoryRegistryService;
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -14,14 +14,14 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcg.service
|
* @package io.shinhanlife.dap.mcg.service
|
||||||
* @className ToolPlanner
|
* @className ToolPlanner
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description AX HUB ?<3F>스??처리 ?<3F>래??
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
* @create 2026.09.01
|
* @create 2026.09.01
|
||||||
* <pre>
|
* <pre>
|
||||||
* ---------- 개정이력 ----------
|
* ---------- 개정?<3F>력 ----------
|
||||||
* 수정일 수정자 수정내용
|
* ?<3F>정?? ?<3F>정?? ?<3F>정?<3F>용
|
||||||
* ---------- -------- ---------------------------
|
* ---------- -------- ---------------------------
|
||||||
* 2026.09.01 0986406 최초생성
|
* 2026.09.01 0986406 최초?<3F>성
|
||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@@ -30,30 +30,30 @@ import java.util.Map;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ToolPlanner {
|
public class ToolPlanner {
|
||||||
|
|
||||||
private final RedisRegistryService redisRegistryService;
|
private final InMemoryRegistryService InMemoryRegistryService;
|
||||||
private final SecurityProperties securityProperties;
|
private final SecurityProperties securityProperties;
|
||||||
private final GatewayFallbackProperties fallbackProperties;
|
private final GatewayFallbackProperties fallbackProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 요청(payload)을 분석하여 실행해야 할 Tool의 계획을 생성합니다.
|
* ?<3F>청(payload)??분석?<3F>여 ?<3F>행?<3F>야 ??Tool??계획???<3F>성?<3F>니??
|
||||||
*/
|
*/
|
||||||
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
||||||
log.info(" [Planner] 요청 분석 및 실행 계획 수립 시작");
|
log.info(" [Planner] ?<3F>청 분석 <EFBFBD>??<3F>행 계획 ?<3F>립 ?<3F>작");
|
||||||
|
|
||||||
// 1. 요청에서 호출하려는 툴 이름 추출 (JSON-RPC params.name)
|
// 1. ?<3F>청?<3F>서 ?<3F>출?<3F>려?????<3F>름 추출 (JSON-RPC params.name)
|
||||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||||
String toolName = params != null ? (String) params.get("name") : null;
|
String toolName = params != null ? (String) params.get("name") : null;
|
||||||
|
|
||||||
if (toolName == null || toolName.isEmpty()) {
|
if (toolName == null || toolName.isEmpty()) {
|
||||||
throw new IllegalArgumentException("요청에 toolName이 포함되어 있지 않습니다.");
|
throw new IllegalArgumentException("?<3F>청??toolName???<3F>함?<3F>어 ?<3F><>? ?<3F>습?<3F>다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. RedisRegistry에서 해당 툴의 메타데이터 조회
|
// 2. RedisRegistry?<3F>서 ?<3F>당 ?<3F>의 메<>??<3F>이??조회
|
||||||
// (실제로는 이 메타데이터가 실행 계획의 핵심이 됩니다)
|
// (?<3F>제로는 ??메<>??<3F>이?<3F><>? ?<3F>행 계획???<3F>심???<3F>니??
|
||||||
var toolMetadata = redisRegistryService.getToolByName(toolName);
|
var toolMetadata = InMemoryRegistryService.getToolByName(toolName);
|
||||||
|
|
||||||
if (toolMetadata == null) {
|
if (toolMetadata == null) {
|
||||||
log.warn(" [Planner] 등록되지 않은 툴 요청: {}. Fallback 라우팅 규칙을 확인합니다.", toolName);
|
log.warn(" [Planner] ?<3F>록?<3F><>? ?<3F><>? ???<3F>청: {}. Fallback ?<3F>우??규칙???<3F>인?<3F>니??", toolName);
|
||||||
|
|
||||||
String fallbackPodUrl = fallbackProperties.getDefaultUrl();
|
String fallbackPodUrl = fallbackProperties.getDefaultUrl();
|
||||||
if (fallbackProperties.getRoutes() != null) {
|
if (fallbackProperties.getRoutes() != null) {
|
||||||
@@ -66,11 +66,11 @@ public class ToolPlanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fallbackPodUrl == null || fallbackPodUrl.isEmpty()) {
|
if (fallbackPodUrl == null || fallbackPodUrl.isEmpty()) {
|
||||||
log.error(" [Planner] Fallback 라우팅 대상이 아닙니다. 툴: {}", toolName);
|
log.error(" [Planner] Fallback ?<3F>우???<3F>?<3F>이 ?<3F>닙?<3F>다. ?? {}", toolName);
|
||||||
throw new RuntimeException("해당 툴(" + toolName + ")이 레지스트리에 존재하지 않습니다.");
|
throw new RuntimeException("?<3F>당 ??" + toolName + ")???<3F><>??<3F>트리에 존재?<3F><>? ?<3F>습?<3F>다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(" [Planner] Fallback 라우팅 매칭됨: {} -> {}", toolName, fallbackPodUrl);
|
log.info(" [Planner] Fallback ?<3F>우??매칭?? {} -> {}", toolName, fallbackPodUrl);
|
||||||
|
|
||||||
toolMetadata = ToolMetadata.builder()
|
toolMetadata = ToolMetadata.builder()
|
||||||
.uid(toolName)
|
.uid(toolName)
|
||||||
@@ -80,26 +80,26 @@ public class ToolPlanner {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
// 2-1. [?<3F>규] ?<3F>메??그룹??기반 권한 검<EFBFBD>?
|
||||||
if (tenantId != null && !tenantId.equalsIgnoreCase("system") && toolMetadata.getCategoryKey() != null) {
|
if (tenantId != null && !tenantId.equalsIgnoreCase("system") && toolMetadata.getCategoryKey() != null) {
|
||||||
String normalizedTenantId = tenantId.toLowerCase();
|
String normalizedTenantId = tenantId.toLowerCase();
|
||||||
List<String> allowedDomains = securityProperties.getTenantDomains().get(normalizedTenantId);
|
List<String> allowedDomains = securityProperties.getTenantDomains().get(normalizedTenantId);
|
||||||
|
|
||||||
// 만약 대소문자 변환 후에도 없으면 원래 값으로 한 번 더 시도 (하위 호환성)
|
// 만약 ?<3F>?<3F>문??변???<3F>에???<3F>으<EFBFBD>??<3F>래 값으<EAB092>???<3F>????<3F>도 (?<3F>위 ?<3F>환??
|
||||||
if (allowedDomains == null) {
|
if (allowedDomains == null) {
|
||||||
allowedDomains = securityProperties.getTenantDomains().get(tenantId);
|
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] 권한 거<EFBFBD>? - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey());
|
||||||
throw new SecurityException("해당 도메인(" + toolMetadata.getCategoryKey() + ")의 툴을 실행할 권한이 없습니다.");
|
throw new SecurityException("?<3F>당 ?<3F>메??" + toolMetadata.getCategoryKey() + ")???<3F>을 ?<3F>행??권한???<3F>습?<3F>다.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(" [Planner] 툴 '{}'에 대한 실행 계획 수립 완료", toolName);
|
log.info(" [Planner] ??'{}'???<3F>???<3F>행 계획 ?<3F>립 ?<3F>료", toolName);
|
||||||
|
|
||||||
// 3. 수립된 계획(툴 메타데이터) 반환
|
// 3. ?<3F>립??계획(??메<>??<3F>이?? 반환
|
||||||
return toolMetadata;
|
return toolMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.shinhanlife.dap.mcg.sync;
|
package io.shinhanlife.dap.mcg.sync;
|
||||||
|
|
||||||
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
|
import io.shinhanlife.dap.mcg.registry.InMemoryRegistryService;
|
||||||
import io.modelcontextprotocol.server.McpSyncServer;
|
import io.modelcontextprotocol.server.McpSyncServer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -38,18 +38,18 @@ public class RegistryMcpToolSynchronizer {
|
|||||||
|
|
||||||
private final ObjectProvider<McpSyncServer> mcpServerProvider;
|
private final ObjectProvider<McpSyncServer> mcpServerProvider;
|
||||||
private final DynamicMcpServerManager dynamicMcpServerManager;
|
private final DynamicMcpServerManager dynamicMcpServerManager;
|
||||||
private final RedisRegistryService redisRegistryService;
|
private final InMemoryRegistryService InMemoryRegistryService;
|
||||||
private final RegistryMcpToolSpecificationFactory specificationFactory;
|
private final RegistryMcpToolSpecificationFactory specificationFactory;
|
||||||
private final Set<String> managedToolNames = new LinkedHashSet<>();
|
private final Set<String> managedToolNames = new LinkedHashSet<>();
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
public RegistryMcpToolSynchronizer(ObjectProvider<McpSyncServer> mcpServerProvider,
|
public RegistryMcpToolSynchronizer(ObjectProvider<McpSyncServer> mcpServerProvider,
|
||||||
DynamicMcpServerManager dynamicMcpServerManager,
|
DynamicMcpServerManager dynamicMcpServerManager,
|
||||||
RedisRegistryService redisRegistryService,
|
InMemoryRegistryService InMemoryRegistryService,
|
||||||
RegistryMcpToolSpecificationFactory specificationFactory) {
|
RegistryMcpToolSpecificationFactory specificationFactory) {
|
||||||
this.mcpServerProvider = mcpServerProvider;
|
this.mcpServerProvider = mcpServerProvider;
|
||||||
this.dynamicMcpServerManager = dynamicMcpServerManager;
|
this.dynamicMcpServerManager = dynamicMcpServerManager;
|
||||||
this.redisRegistryService = redisRegistryService;
|
this.InMemoryRegistryService = InMemoryRegistryService;
|
||||||
this.specificationFactory = specificationFactory;
|
this.specificationFactory = specificationFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ public class RegistryMcpToolSynchronizer {
|
|||||||
public void synchronize() {
|
public void synchronize() {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
List<ToolMetadata> activeEntries = redisRegistryService.getAllTools()
|
List<ToolMetadata> activeEntries = InMemoryRegistryService.getAllTools()
|
||||||
.stream().filter(ToolMetadata::getVisible).collect(Collectors.toList());
|
.stream().filter(ToolMetadata::getVisible).collect(Collectors.toList());
|
||||||
|
|
||||||
// 1. Dynamic MCP Server 동기화 (카테고리별)
|
// 1. Dynamic MCP Server 동기화 (카테고리별)
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package io.shinhanlife.dap.mcg.trace;
|
||||||
|
|
||||||
|
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
|
||||||
|
import io.shinhanlife.dap.mcg.security.McpRequestContext;
|
||||||
|
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package io.shinhanlife.dap.mcg.trace
|
||||||
|
* @className InMemoryToolTraceService
|
||||||
|
* @description AX HUB 시스템 인메모리 툴 트레이스 서비스
|
||||||
|
* @author 0986406
|
||||||
|
* @create 2026.09.01
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class InMemoryToolTraceService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(InMemoryToolTraceService.class);
|
||||||
|
|
||||||
|
private final McpGatewayProperties properties;
|
||||||
|
private final ObjectMapper json;
|
||||||
|
private final McpMonitorEventService monitorEvents;
|
||||||
|
private final Map<String, AttemptState> attemptStates = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// LRU Cache for recent traces
|
||||||
|
private final Map<String, String> recentTraces;
|
||||||
|
|
||||||
|
public InMemoryToolTraceService(McpGatewayProperties properties,
|
||||||
|
ObjectMapper json,
|
||||||
|
McpMonitorEventService monitorEvents) {
|
||||||
|
this.properties = properties;
|
||||||
|
this.json = json;
|
||||||
|
this.monitorEvents = monitorEvents;
|
||||||
|
|
||||||
|
final int maxSize = (int) properties.traceRetentionSize();
|
||||||
|
this.recentTraces = new LinkedHashMap<String, String>(maxSize, 0.75f, true) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
|
||||||
|
return size() > maxSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void started(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments) {
|
||||||
|
save(context, metadata, arguments, "STARTED", 0, "", 0, 0, 0,
|
||||||
|
"Agent request entered MCP", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attemptStarted(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||||
|
int attempt, int maxAttempts) {
|
||||||
|
save(context, metadata, arguments, "ATTEMPTING", 0, "", attempt, maxAttempts, 0,
|
||||||
|
"Calling tool server", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void retryWaiting(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||||
|
int failedAttempt, int maxAttempts, long backoffMillis, String failureType) {
|
||||||
|
save(context, metadata, arguments, "RETRY_WAITING", 0, failureType, failedAttempt, maxAttempts, backoffMillis,
|
||||||
|
"Retry will be attempted after backoff", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finished(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||||
|
long elapsedMillis, boolean success, String failureType, String responseText) {
|
||||||
|
save(context, metadata, arguments, success ? "SUCCESS" : "FAILED", elapsedMillis, failureType, 0, 0, 0,
|
||||||
|
success ? "Tool call completed" : "Tool call failed", responseText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRecentTraces(int limit) {
|
||||||
|
if (!properties.traceEnabled()) {
|
||||||
|
return List.of("Trace is disabled. Set traceEnabled=true.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
synchronized (recentTraces) {
|
||||||
|
List<String> values = new ArrayList<>(recentTraces.values());
|
||||||
|
// reverse to get latest first
|
||||||
|
for (int i = values.size() - 1; i >= 0 && result.size() < limit; i--) {
|
||||||
|
result.add(values.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTraceDetail(String requestId) {
|
||||||
|
if (!properties.traceEnabled()) {
|
||||||
|
return "Trace is disabled.";
|
||||||
|
}
|
||||||
|
synchronized (recentTraces) {
|
||||||
|
return recentTraces.get(requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||||
|
String state, long elapsedMillis, String failureType, int attempt, int maxAttempts, long backoffMillis,
|
||||||
|
String message, String responseText) {
|
||||||
|
|
||||||
|
if (!properties.traceEnabled()) {
|
||||||
|
return; // trace is off
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AttemptState ast = attemptStates.computeIfAbsent(context.requestId(), k -> new AttemptState(0));
|
||||||
|
if (attempt > 0) ast.currentAttempt = attempt;
|
||||||
|
|
||||||
|
ObjectNode root = json.createObjectNode();
|
||||||
|
root.put("requestId", context.requestId());
|
||||||
|
root.put("agentId", context.agentId());
|
||||||
|
root.put("tenantId", context.traceGroupId());
|
||||||
|
root.put("toolName", metadata.getName());
|
||||||
|
root.put("targetPod", metadata.getPodUrl());
|
||||||
|
root.put("state", state);
|
||||||
|
root.put("timestamp", Instant.now().toString());
|
||||||
|
root.put("elapsedMillis", elapsedMillis);
|
||||||
|
root.put("attempt", ast.currentAttempt);
|
||||||
|
root.put("maxAttempts", maxAttempts > 0 ? maxAttempts : properties.retryMaxAttempts());
|
||||||
|
root.put("backoffMillis", backoffMillis);
|
||||||
|
|
||||||
|
if (failureType != null && !failureType.isEmpty()) {
|
||||||
|
root.put("failureType", failureType);
|
||||||
|
}
|
||||||
|
if (message != null && !message.isEmpty()) {
|
||||||
|
root.put("message", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask arguments if needed, for simplicity we just put them
|
||||||
|
root.set("arguments", arguments);
|
||||||
|
|
||||||
|
if (responseText != null && !responseText.isEmpty()) {
|
||||||
|
if (responseText.length() > 500) {
|
||||||
|
root.put("responsePreview", responseText.substring(0, 500) + "...");
|
||||||
|
} else {
|
||||||
|
root.put("responsePreview", responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String payload = json.writeValueAsString(root);
|
||||||
|
|
||||||
|
synchronized (recentTraces) {
|
||||||
|
recentTraces.put(context.requestId(), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorEvents.publish(payload);
|
||||||
|
|
||||||
|
if ("SUCCESS".equals(state) || "FAILED".equals(state)) {
|
||||||
|
attemptStates.remove(context.requestId());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to save trace: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AttemptState {
|
||||||
|
int currentAttempt;
|
||||||
|
AttemptState(int currentAttempt) {
|
||||||
|
this.currentAttempt = currentAttempt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package io.shinhanlife.dap.mcg.redis;
|
package io.shinhanlife.dap.mcg.trace;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcg.redis
|
* @package io.shinhanlife.dap.mcg.trace
|
||||||
* @className McpMonitorEventService
|
* @className McpMonitorEventService
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description AX HUB 시스템 처리 클래스
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
package io.shinhanlife.dap.mcg;
|
package io.shinhanlife.dap.mcg;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package io.shinhanlife.dap.mcg
|
* @package io.shinhanlife.dap.mcg
|
||||||
* @className DapGatewayApplicationTests
|
* @className DapGatewayApplicationTests
|
||||||
* @description AX HUB 시스템 처리 클래스
|
* @description AX HUB ?<3F>스??처리 ?<3F>래??
|
||||||
* @author 0986406
|
* @author 0986406
|
||||||
* @create 2026.09.01
|
* @create 2026.09.01
|
||||||
* <pre>
|
* <pre>
|
||||||
* ---------- 개정이력 ----------
|
* ---------- 개정?<3F>력 ----------
|
||||||
* 수정일 수정자 수정내용
|
* ?<3F>정?? ?<3F>정?? ?<3F>정?<3F>용
|
||||||
* ---------- -------- ---------------------------
|
* ---------- -------- ---------------------------
|
||||||
* 2026.09.01 0986406 최초생성
|
* 2026.09.01 0986406 최초?<3F>성
|
||||||
*
|
*
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@@ -20,7 +20,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import io.shinhanlife.dap.mcg.audit.AuditLogService;
|
import io.shinhanlife.dap.mcg.audit.AuditLogService;
|
||||||
import io.shinhanlife.dap.mcg.redis.RedisToolTraceService;
|
import io.shinhanlife.dap.mcg.trace.InMemoryToolTraceService;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
@@ -28,7 +28,7 @@ class DapGatewayApplicationTests {
|
|||||||
|
|
||||||
// Mock components that might require external dependencies (like Redis/DB) to pass the context load
|
// Mock components that might require external dependencies (like Redis/DB) to pass the context load
|
||||||
@MockitoBean
|
@MockitoBean
|
||||||
private RedisToolTraceService redisToolTraceService;
|
private InMemoryToolTraceService InMemoryToolTraceService;
|
||||||
|
|
||||||
@MockitoBean
|
@MockitoBean
|
||||||
private AuditLogService auditLogService;
|
private AuditLogService auditLogService;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.shinhanlife.dap.lib.mcp;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.ai.mcp.annotation.McpTool;
|
||||||
|
|
||||||
|
class McpSdk2CompatibilityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesOfficialSpringAiMcpAnnotations() {
|
||||||
|
assertEquals("org.springframework.ai.mcp.annotation", McpTool.class.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesMcpJavaSdkTwo() {
|
||||||
|
assertEquals("2.0.0", McpSchema.class.getPackage().getImplementationVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
name: ax_hub_mcp_tool
|
name: ax_hub_mcp_tool
|
||||||
|
|
||||||
services:
|
services:
|
||||||
redis:
|
|
||||||
image: redis:latest
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
build:
|
build:
|
||||||
@@ -20,16 +12,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./:/src
|
- ./:/src
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
tool-report:
|
tool-report:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Seoul
|
- TZ=Asia/Seoul
|
||||||
- SPRING_REDIS_HOST=redis
|
|
||||||
- SPRING_REDIS_PORT=6379
|
|
||||||
- SPRING_DATA_REDIS_HOST=redis
|
|
||||||
- SPRING_DATA_REDIS_PORT=6379
|
|
||||||
- AXHUB_SOURCE_DIR=/src
|
- AXHUB_SOURCE_DIR=/src
|
||||||
- JAVA_TOOL_OPTIONS=-Xms64m -Xmx384m
|
- JAVA_TOOL_OPTIONS=-Xms64m -Xmx384m
|
||||||
- SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local}
|
- SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local}
|
||||||
@@ -67,13 +53,8 @@ services:
|
|||||||
dockerfile: dap-was-sms/Dockerfile
|
dockerfile: dap-was-sms/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "8282:8082"
|
- "8282:8082"
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Seoul
|
- TZ=Asia/Seoul
|
||||||
- SPRING_REDIS_HOST=redis
|
|
||||||
- SPRING_REDIS_PORT=6379
|
|
||||||
- SPRING_DATA_REDIS_PORT=6379
|
|
||||||
- AXHUB_GATEWAY_URL=http://gateway:8081
|
- AXHUB_GATEWAY_URL=http://gateway:8081
|
||||||
- AXHUB_SOURCE_DIR=/src
|
- AXHUB_SOURCE_DIR=/src
|
||||||
- AXHUB_TOOL_URL=http://was-sms:8082
|
- AXHUB_TOOL_URL=http://was-sms:8082
|
||||||
@@ -91,13 +72,8 @@ services:
|
|||||||
dockerfile: dap-was-oth/Dockerfile
|
dockerfile: dap-was-oth/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "8284:8084"
|
- "8284:8084"
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Seoul
|
- TZ=Asia/Seoul
|
||||||
- SPRING_REDIS_HOST=redis
|
|
||||||
- SPRING_REDIS_PORT=6379
|
|
||||||
- SPRING_DATA_REDIS_PORT=6379
|
|
||||||
- AXHUB_GATEWAY_URL=http://gateway:8081
|
- AXHUB_GATEWAY_URL=http://gateway:8081
|
||||||
- AXHUB_TOOL_URL=http://was-oth:8084
|
- AXHUB_TOOL_URL=http://was-oth:8084
|
||||||
- GLOW_COMMUNICATION_MCI_HOMT=http://mci-mock
|
- GLOW_COMMUNICATION_MCI_HOMT=http://mci-mock
|
||||||
|
|||||||
859
smp_tools.json
Normal file
859
smp_tools.json
Normal file
@@ -0,0 +1,859 @@
|
|||||||
|
{
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"uid": "080f2207-f98a-31b4-9a46-5bc7a8e41f90",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ê³ ê° íµí© ìë´ì´ë ¥ ì¡°í",
|
||||||
|
"name": "cmm_customer_tool",
|
||||||
|
"description": "ê³ ê°ì íµí© ìë´ ì´ë ¥ì ì¡°ííë ë구ì\u0085ëë¤.\nì¬ì© ìì : ê³ ê° IDì ì¡°í 기ê°ì 기ë°ì¼ë¡ í´ë¹ ê¸°ê° ëìì ìë´ ì´ë ¥ì íì¸í ë ì¬ì©í©ëë¤.\nì¬ì© ì ì¸: ìë´ ì´ë ¥ì ìì±íê±°ë ìì í ëë ì¬ì©íì§ ììµëë¤.\nì\u0085ì¶ë ¥ ì í: ìë´ ì´ë ¥ ì¡°íë§ ìííë©° ë°ì´í°ë¥¼ ë³ê²½íì§ ììµëë¤.",
|
||||||
|
"functionDescription": "ê³ ê°ì íµí© ìë´ ì´ë ¥ì ì¡°ííë ë구ì\u0085ëë¤.",
|
||||||
|
"whenToUse": "ê³ ê° IDì ì¡°í 기ê°ì 기ë°ì¼ë¡ í´ë¹ ê¸°ê° ëìì ìë´ ì´ë ¥ì íì¸í ë ì¬ì©í©ëë¤.",
|
||||||
|
"whenNotToUse": "ìë´ ì´ë ¥ì ìì±íê±°ë ìì í ëë ì¬ì©íì§ ììµëë¤.",
|
||||||
|
"ioLimits": "ìë´ ì´ë ¥ ì¡°íë§ ìííë©° ë°ì´í°ë¥¼ ë³ê²½íì§ ììµëë¤.",
|
||||||
|
"displayDescription": "ê³ ê°ì íµí© ìë´ ì´ë ¥ì ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ê³ ê° íµí© ìë´ì´ë ¥ ì¡°íí´ì¤",
|
||||||
|
"í¹ì 기ê°ì ìë´ ì´ë ¥ì íì¸í´ì¤",
|
||||||
|
"ê³ ê° ë²í¸ë¡ ìë´ ì´ë ¥ì ì°¾ìì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ê³ ê°",
|
||||||
|
"íµí©ìë´ì´ë ¥"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"csNo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê³ ê°ë²í¸"
|
||||||
|
},
|
||||||
|
"ntleCd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ìë´ì¥ì½ë"
|
||||||
|
},
|
||||||
|
"notiPmlMdCd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ìë´ë°ì¡ë°©ë²ì½ë"
|
||||||
|
},
|
||||||
|
"inqrStrYmd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íììì¼ì"
|
||||||
|
},
|
||||||
|
"inqrEndYmd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íì¢\u0085ë£ì¼ì"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"csNo"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "cmm",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/cmm_customer_tool",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "ONILD0320",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "26c5b305-a75d-3092-9252-16849187090d",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ë©í ê³µíµì½ë ì¡°í",
|
||||||
|
"name": "cmm_comcode_lookup",
|
||||||
|
"description": "íµí©ì½ë 그룹과 ì½ëëª\u0085 ì¡°ê±´ì¼ë¡ ë©í ê³µíµì½ë 목ë¡ì ì¡°ííë¤.\nì¬ì© ìì : ì\u0085무 ì½ëì ê°ê³¼ íìëª\u0085ì íì¸íê±°ë ì í¨í ì½ë 목ë¡ì´ íìí ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ê³µíµì½ë를 ì ê· ë±ë¡íê±°ë ë³ê²½ ëë ìì íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ê²ì ì¡°ê±´ì ë§ë ì½ëì ì½ëëª\u0085ë§ ë°ííë©° ì½ë ë°ì´í°ë ë³ê²½íì§ ìëë¤.",
|
||||||
|
"functionDescription": "íµí©ì½ë 그룹과 ì½ëëª\u0085 ì¡°ê±´ì¼ë¡ ë©í ê³µíµì½ë 목ë¡ì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì\u0085무 ì½ëì ê°ê³¼ íìëª\u0085ì íì¸íê±°ë ì í¨í ì½ë 목ë¡ì´ íìí ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ê³µíµì½ë를 ì ê· ë±ë¡íê±°ë ë³ê²½ ëë ìì íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ê²ì ì¡°ê±´ì ë§ë ì½ëì ì½ëëª\u0085ë§ ë°ííë©° ì½ë ë°ì´í°ë ë³ê²½íì§ ìëë¤.",
|
||||||
|
"displayDescription": "ë©í ìì¤í\u0085ì ê³µíµì½ë 목ë¡ì ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì¬ì© ìí ì½ë 목ë¡ì ìë ¤ì¤",
|
||||||
|
"ê³ ê° êµ¬ë¶ ê³µíµì½ë를 ì°¾ìì¤",
|
||||||
|
"ì¬ì© ì¤ì¸ íµí©ì½ë를 ì¡°íí´ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ë©í",
|
||||||
|
"ê³µíµì½ë"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"groupCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí íµí©ì½ë 그룹 ID"
|
||||||
|
},
|
||||||
|
"codeName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì½ëëª\u0085 ê²ì í¤ìë"
|
||||||
|
},
|
||||||
|
"useYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¬ì© ì¬ë¶ Y ëë N",
|
||||||
|
"enum": [
|
||||||
|
"Y",
|
||||||
|
"N"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "cmm",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/cmm_comcode_lookup",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "CLCNNB00001",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "bf437712-8e47-3db6-9e56-2d1995dc0609",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ë©í í\u0085ì´ë¸ ì¡°í",
|
||||||
|
"name": "cmm_meta_table",
|
||||||
|
"description": "물리ëª\u0085, ë\u0085¼ë¦¬ëª\u0085 ëë ìì ì ì¡°ê±´ì¼ë¡ ë©í í\u0085ì´ë¸ ì 보를 ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° ì\u0085무 ë°ì´í°ì í\u0085ì´ë¸ëª\u0085ì´ë ìì ì¤í¤ë§ë¥¼ íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: í\u0085ì´ë¸ì ìì±íê±°ë 구조를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ë©íì ë±ë¡ë í\u0085ì´ë¸ ì¤ëª\u0085 ì ë³´ë§ ë°ííë©° ì¤ì í\u0085ì´ë¸ ë°ì´í°ë ì¡°ííì§ ìëë¤.",
|
||||||
|
"functionDescription": "물리ëª\u0085, ë\u0085¼ë¦¬ëª\u0085 ëë ìì ì ì¡°ê±´ì¼ë¡ ë©í í\u0085ì´ë¸ ì 보를 ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° ì\u0085무 ë°ì´í°ì í\u0085ì´ë¸ëª\u0085ì´ë ìì ì¤í¤ë§ë¥¼ íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "í\u0085ì´ë¸ì ìì±íê±°ë 구조를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ë©íì ë±ë¡ë í\u0085ì´ë¸ ì¤ëª\u0085 ì ë³´ë§ ë°ííë©° ì¤ì í\u0085ì´ë¸ ë°ì´í°ë ì¡°ííì§ ìëë¤.",
|
||||||
|
"displayDescription": "ë©í ìì¤í\u0085ì ë±ë¡ë í\u0085ì´ë¸ ì 보를 ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ê³ ê° ê¸°ë³¸ í\u0085ì´ë¸ì ì°¾ìì¤",
|
||||||
|
"ê³ì½ ê´ë ¨ í\u0085ì´ë¸ 목ë¡ì ë³´ì¬ì¤",
|
||||||
|
"í¹ì ì¤í¤ë§ì í\u0085ì´ë¸ì ì¡°íí´ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ë©í",
|
||||||
|
"í\u0085ì´ë¸"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "í\u0085ì´ë¸ 물리ëª\u0085 ê²ìì´"
|
||||||
|
},
|
||||||
|
"tableLogicalName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "í\u0085ì´ë¸ ë\u0085¼ë¦¬ëª\u0085 ê²ìì´"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "í\u0085ì´ë¸ ìì ì¤í¤ë§ëª\u0085"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "cmm",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/cmm_meta_table",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "4914953b-ea25-389b-a6a9-c3c407f4d5bb",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ì\u0085무 í\u0085í릿 ë¤ì´ë¡ë URL ì¡°í",
|
||||||
|
"name": "cmm_template_url",
|
||||||
|
"description": "ìì²í ì\u0085무 í\u0085í릿 íì¼ì ë´ë ¤ë°ì ì ìë URLì ë°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° ìì\u0085ì´ë ìë ì\u0085무 ììì ë¤ì´ë¡ë ìì¹ë¥¼ ìì²í ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: í\u0085í릿 ë´ì©ì ìì±íê±°ë ì\u0085ë¡ë ëë ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ë±ë¡ë í\u0085í릿 IDì ëí ë¤ì´ë¡ë URLë§ ë°ííë©° íì¼ ìì²´ë ë°ííì§ ìëë¤.",
|
||||||
|
"functionDescription": "ìì²í ì\u0085무 í\u0085í릿 íì¼ì ë´ë ¤ë°ì ì ìë URLì ë°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° ìì\u0085ì´ë ìë ì\u0085무 ììì ë¤ì´ë¡ë ìì¹ë¥¼ ìì²í ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "í\u0085í릿 ë´ì©ì ìì±íê±°ë ì\u0085ë¡ë ëë ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ë±ë¡ë í\u0085í릿 IDì ëí ë¤ì´ë¡ë URLë§ ë°ííë©° íì¼ ìì²´ë ë°ííì§ ìëë¤.",
|
||||||
|
"displayDescription": "ì\u0085무 í\u0085í릿ì ë¤ì´ë¡ëí ì ìë URLì ì ê³µí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì²êµ¬ ìì ë¤ì´ë¡ë ë§í¬ë¥¼ ìë ¤ì¤",
|
||||||
|
"ì\u0085ë¬´ì© ìì\u0085 í\u0085í릿ì ë°ê³ ì¶ì´",
|
||||||
|
"ë±ë¡ë 문ì ìì ìì¹ë¥¼ ì°¾ìì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"í\u0085í릿",
|
||||||
|
"ë¤ì´ë¡ë"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"templateId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ë¤ì´ë¡ëí í\u0085í릿 ìë³ì"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"templateId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "cmm",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/cmm_template_url",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "e14f5dc9-38c5-3424-bc73-69052c28f660",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ë³´íê¸ ì²êµ¬ ì²ë¦¬",
|
||||||
|
"name": "ins_insurance_processor",
|
||||||
|
"description": "ë³´íê¸ ì²êµ¬ë²í¸, ì²êµ¬ê¸ì¡ê³¼ ì²êµ¬ì¼ì ë°ì ì²êµ¬ ì²ë¦¬ ìì²ì ìííë¤.\nì¬ì© ìì : ì¬ì©ìê° íì¸ë ë³´íê¸ ì²êµ¬ ì 보를 ì¤ì ì²ë¦¬ê³ì ì ìíë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì²êµ¬ ìíë§ ì¡°ííê±°ë íì ì ë³´ê° íì¸ëì§ ìì ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì¤í ì ì\u0085무 ìíê° ë³ê²½ë ì ìì¼ë¯ë¡ í¸ì¶ ì ì ì\u0085ë ¥ê°ê³¼ ì¬ì©ì ìì¬ë¥¼ íì¸í´ì¼ íë¤.",
|
||||||
|
"functionDescription": "ë³´íê¸ ì²êµ¬ë²í¸, ì²êµ¬ê¸ì¡ê³¼ ì²êµ¬ì¼ì ë°ì ì²êµ¬ ì²ë¦¬ ìì²ì ìííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° íì¸ë ë³´íê¸ ì²êµ¬ ì 보를 ì¤ì ì²ë¦¬ê³ì ì ìíë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì²êµ¬ ìíë§ ì¡°ííê±°ë íì ì ë³´ê° íì¸ëì§ ìì ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì¤í ì ì\u0085무 ìíê° ë³ê²½ë ì ìì¼ë¯ë¡ í¸ì¶ ì ì ì\u0085ë ¥ê°ê³¼ ì¬ì©ì ìì¬ë¥¼ íì¸í´ì¼ íë¤.",
|
||||||
|
"displayDescription": "íì¸ë ë³´íê¸ ì²êµ¬ ìì²ì ì²ë¦¬ê³ì ì ë¬í©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"íì¸í ë´ì©ì¼ë¡ ë³´íê¸ ì²êµ¬ë¥¼ ì ìí´ì¤",
|
||||||
|
"ì´ ì²êµ¬ë²í¸ì ë³´íê¸ ì²ë¦¬ë¥¼ ì§íí´ì¤",
|
||||||
|
"ì¤ë ë ì§ë¡ ë³´íê¸ ì²êµ¬ ìì²ì ë³´ë´ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ë³´íê¸",
|
||||||
|
"ì²êµ¬ì²ë¦¬"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"claimNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì²ë¦¬í ë³´íê¸ ì²êµ¬ë²í¸"
|
||||||
|
},
|
||||||
|
"claimAmount": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "ì²ë¦¬í ë³´íê¸ ì²êµ¬ê¸ì¡"
|
||||||
|
},
|
||||||
|
"claimDate": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì²êµ¬ì¼ì YYYYMMDD",
|
||||||
|
"pattern": "^[0-9]{8}$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"claimNumber",
|
||||||
|
"claimAmount",
|
||||||
|
"claimDate"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "ins",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/ins_insurance_processor",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": false,
|
||||||
|
"destructiveHint": true,
|
||||||
|
"idempotentHint": false,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "CLAIM0000001",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "a503bf94-adc5-3bdc-b9b0-9c6fcb59838d",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ONNBA3011 ë³´í ì\u0085무 ì¡°í",
|
||||||
|
"name": "oth_onnba3011_call",
|
||||||
|
"description": "ONNBA3011 ì\u0085ë ¥ì 보를 MCI ì 문ì¼ë¡ ë³íí´ ë³´í ì\u0085무 결과를 ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° ONNBA3011 ì¸í°íì´ì¤ 기ì¤ì ë³´í ê³ì½ ëë ì§ê¸ ì 보를 ì¡°ííë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì¸í°íì´ì¤ ì\u0085ë ¥ê°ì íì¸í ì ìê±°ë ë³´í ì 보를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: CLCNNB00001 ì¸í°íì´ì¤ ê·ê²©ì ë§ë ê°ë§ ì ë¬íë©° ì¡°í 결과를 ì\u0085무 ìëµì¼ë¡ ë³ííë¤.",
|
||||||
|
"functionDescription": "ONNBA3011 ì\u0085ë ¥ì 보를 MCI ì 문ì¼ë¡ ë³íí´ ë³´í ì\u0085무 결과를 ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° ONNBA3011 ì¸í°íì´ì¤ 기ì¤ì ë³´í ê³ì½ ëë ì§ê¸ ì 보를 ì¡°ííë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì¸í°íì´ì¤ ì\u0085ë ¥ê°ì íì¸í ì ìê±°ë ë³´í ì 보를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "CLCNNB00001 ì¸í°íì´ì¤ ê·ê²©ì ë§ë ê°ë§ ì ë¬íë©° ì¡°í 결과를 ì\u0085무 ìëµì¼ë¡ ë³ííë¤.",
|
||||||
|
"displayDescription": "ONNBA3011 ì\u0085무 ì 보를 MCIë¡ ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ê³ ê°ì ë³´í ì\u0085무 ì 보를 ì¡°íí´ì¤",
|
||||||
|
"ONNBA3011 기ì¤ì¼ë¡ ê³ì½ ì 보를 íì¸í´ì¤",
|
||||||
|
"ì\u0085ë ¥í ê³ ê°ë²í¸ì ë³´í 결과를 ìë ¤ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ë³´í",
|
||||||
|
"MCI"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
"GLOW_COMMUNICATION_MCI_HOST",
|
||||||
|
"GLOW_COMMUNICATION_MCI_PORT"
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"dalScCd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê±°ë êµ¬ë¶ ì½ë"
|
||||||
|
},
|
||||||
|
"cstSucoRltyCd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê³ ê° ì±ê³µ ê´ê³ ì½ë"
|
||||||
|
},
|
||||||
|
"csNo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê³ ê° ë²í¸"
|
||||||
|
},
|
||||||
|
"rdreNo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¤ê³ì¬ ë²í¸"
|
||||||
|
},
|
||||||
|
"unfcPvsCalReqYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "미íì ì§ê¸ ê³ì° ìì² ì¬ë¶"
|
||||||
|
},
|
||||||
|
"kcisPymmTnnrRequest": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "KCIS ë©ì\u0085 ê¸°ê° ìì²ê°"
|
||||||
|
},
|
||||||
|
"lmovYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê³ì½ ì´ë ì¬ë¶"
|
||||||
|
},
|
||||||
|
"genPsthApvTrgtYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¼ë° ì¬í ì¹ì¸ ëì ì¬ë¶"
|
||||||
|
},
|
||||||
|
"ircoLmovEcpbTrgtYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ê³ì½ ì´ë ìì¸ ëì ì¬ë¶"
|
||||||
|
},
|
||||||
|
"digCalYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ëì§í¸ ê³ì° ì¬ë¶"
|
||||||
|
},
|
||||||
|
"prbuIciDigCalYn": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ìíë³ ëì§í¸ ê³ì° ì¬ë¶"
|
||||||
|
},
|
||||||
|
"unfcPrbuIrcoAddu": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "미íì ìí ì¶ê° ì ë³´",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"sucoIspaBasDto": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "ì±ê³µ ì¬ì¬ 기본 ì ë³´",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "oth",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/oth_onnba3011_call",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "CLCNNB00001",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "850ea7c1-f54f-38d9-8b4f-958602271fb2",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ì¤ëì ëª\u0085ì¸ ì¡°í",
|
||||||
|
"name": "smp_quote_daily",
|
||||||
|
"description": "ì íí ì¹´í\u0085ê³ ë¦¬ì ë§ë ì¤ëì ëª\u0085ì¸ í ê±´ì ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° ëª\u0085ì¸ì´ë ì§§ì ë기ë¶ì¬ 문구를 ìì²í ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì\u0085무 ë°ì´í° ì¡°íë ì¬ì¤ ê²ì¦ì´ íìí ì§ë¬¸ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ë±ë¡ë ëª\u0085ì¸ ë°ì´í° ì¤ í ê±´ë§ ë°ííë©° ì¶ì² ì ë³´ê° ìì ì ìë¤.",
|
||||||
|
"functionDescription": "ì íí ì¹´í\u0085ê³ ë¦¬ì ë§ë ì¤ëì ëª\u0085ì¸ í ê±´ì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° ëª\u0085ì¸ì´ë ì§§ì ë기ë¶ì¬ 문구를 ìì²í ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì\u0085무 ë°ì´í° ì¡°íë ì¬ì¤ ê²ì¦ì´ íìí ì§ë¬¸ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ë±ë¡ë ëª\u0085ì¸ ë°ì´í° ì¤ í ê±´ë§ ë°ííë©° ì¶ì² ì ë³´ê° ìì ì ìë¤.",
|
||||||
|
"displayDescription": "ì¹´í\u0085ê³ ë¦¬ì ë§ë ì¤ëì ëª\u0085ì¸ì ì ê³µí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì¤ë íì´ ëë ë§ì ìë ¤ì¤",
|
||||||
|
"ì\u0085무 ìì ì ì ëª\u0085ì¸ íë ë³´ì¬ì¤",
|
||||||
|
"ì±ê³µì ê´í ì§§ì 문구를 ì¶ì²í´ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ëª\u0085ì¸",
|
||||||
|
"ì½í\u0085ì¸ "
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"category": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí ëª\u0085ì¸ ì¹´í\u0085ê³ ë¦¬"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "smp",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/smp_quote_daily",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": false,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "ef86d147-785e-3007-a158-f7e6ffd275de",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ì¤ìê° íì¨ ì¡°í",
|
||||||
|
"name": "smp_exchange_inquiry",
|
||||||
|
"description": "íµíì½ë를 기ì¤ì¼ë¡ íì¬ íì¨ ì 보를 ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° USD, EUR, JPY ë± í¹ì íµíì íì¨ì íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: íì ê±°ë를 ì¤ííê±°ë 과거 íì¨ íµê³ë¥¼ ë¶ìíë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì§ìëë íµíì½ëì ì¡°í ìì íì¨ë§ ë°ííë©° ì¤ì íì 기ë¥ì ì ê³µíì§ ìëë¤.",
|
||||||
|
"functionDescription": "íµíì½ë를 기ì¤ì¼ë¡ íì¬ íì¨ ì 보를 ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° USD, EUR, JPY ë± í¹ì íµíì íì¨ì íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "íì ê±°ë를 ì¤ííê±°ë 과거 íì¨ íµê³ë¥¼ ë¶ìíë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì§ìëë íµíì½ëì ì¡°í ìì íì¨ë§ ë°ííë©° ì¤ì íì 기ë¥ì ì ê³µíì§ ìëë¤.",
|
||||||
|
"displayDescription": "ì§ì í íµíì íì¬ íì¨ì ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì¤ë ë¬ë¬ íì¨ì ìë ¤ì¤",
|
||||||
|
"ìí íì¨ì´ ì¼ë§ì¸ì§ ì¡°íí´ì¤",
|
||||||
|
"ì ë¡ íì¨ì íì¸í´ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"íì¨",
|
||||||
|
"ê¸ìµ"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currencyCode": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí ISO íµíì½ë",
|
||||||
|
"pattern": "^[A-Z]{3}$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "smp",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/smp_exchange_inquiry",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "44508c3e-89a8-3fa8-92ec-ae236ca2cd88",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "MCP·TOOL íí¸ êµ¬ì±ì ì¡°í",
|
||||||
|
"name": "smp_team_list",
|
||||||
|
"description": "ì íë¼ì´í AX ì¶ì§íê³¼ MCP·TOOL íí¸ êµ¬ì±ì ë° ì§ê¸ì ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° íë¡ì í¸ ë´ë¹ìë íë³ êµ¬ì±ì ì 보를 묻ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì¸ì¬ ê°ì¸ì ë³´ë ì°ë½ì² ëë ì¡°ì§ ë³ê²½ ì´ë ¥ì ìì²íë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì¬ì ì ë±ë¡ë 구ì±ìì ì´ë¦ê³¼ ì§ê¸ë§ ë°ííë©° 민ê°í ì¸ì¬ì ë³´ë í¬í¨íì§ ìëë¤.",
|
||||||
|
"functionDescription": "ì íë¼ì´í AX ì¶ì§íê³¼ MCP·TOOL íí¸ êµ¬ì±ì ë° ì§ê¸ì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° íë¡ì í¸ ë´ë¹ìë íë³ êµ¬ì±ì ì 보를 묻ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì¸ì¬ ê°ì¸ì ë³´ë ì°ë½ì² ëë ì¡°ì§ ë³ê²½ ì´ë ¥ì ìì²íë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì¬ì ì ë±ë¡ë 구ì±ìì ì´ë¦ê³¼ ì§ê¸ë§ ë°ííë©° 민ê°í ì¸ì¬ì ë³´ë í¬í¨íì§ ìëë¤.",
|
||||||
|
"displayDescription": "ì íë¼ì´í MCP·TOOL íí¸ ë´ë¹ìì 구ì±ìì ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"MCP í ë´ë¹ì를 ìë ¤ì¤",
|
||||||
|
"TOOL íí¸ êµ¬ì±ìì´ ë구ì¸ì§ ë³´ì¬ì¤",
|
||||||
|
"AX ì¶ì§í ë´ë¹ì를 ì°¾ìì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ì¡°ì§",
|
||||||
|
"ë´ë¹ì"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"teamName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí í ì´ë¦ ëë ì ì²´"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "smp",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/smp_team_list",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "bc0f227e-3a9b-36a1-af6f-3f20c1964969",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "ëì ë ì¨ ì¡°í",
|
||||||
|
"name": "smp_weather_inquiry",
|
||||||
|
"description": "ëìëª\u0085ì 기ì¤ì¼ë¡ íì¬ ë ì¨, ì¨ëì íìì ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° í¹ì ëìì íì¬ ê¸°ì ì 보를 ìì²í ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì¥ê¸° ìë³´ë 기ì í¹ë³´ ëë ê³µì ì¬ëì ë³´ê° íìí ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì\u0085ë ¥í ëìì íì¬ ê´ì¸¡ ê¸°ë° ìí ì ë³´ë§ ë°ííë¤.",
|
||||||
|
"functionDescription": "ëìëª\u0085ì 기ì¤ì¼ë¡ íì¬ ë ì¨, ì¨ëì íìì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° í¹ì ëìì íì¬ ê¸°ì ì 보를 ìì²í ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì¥ê¸° ìë³´ë 기ì í¹ë³´ ëë ê³µì ì¬ëì ë³´ê° íìí ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì\u0085ë ¥í ëìì íì¬ ê´ì¸¡ ê¸°ë° ìí ì ë³´ë§ ë°ííë¤.",
|
||||||
|
"displayDescription": "ì§ì í ëìì íì¬ ë ì¨ ì 보를 ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ìì¸ ë ì¨ë¥¼ ìë ¤ì¤",
|
||||||
|
"ë¶ì°ì íì¬ ì¨ë를 ì¡°íí´ì¤",
|
||||||
|
"ì 주ë ë°ëì´ ì¼ë§ë ë¶ëì§ ìë ¤ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ë ì¨",
|
||||||
|
"ì¡°í"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ë ì¨ë¥¼ ì¡°íí ëìëª\u0085"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"city"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "smp",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/smp_weather_inquiry",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": null,
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "5fd282b9-6472-3fc3-a562-fe23e000ab7e",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "SOL ì뢰ì ìì¸ ì¡°í",
|
||||||
|
"name": "sol_request_detail",
|
||||||
|
"description": "SOL ì뢰ì ìë³ì를 기ì¤ì¼ë¡ ì뢰ì ìì¸ ë´ì©ì ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° í¹ì SOL ì뢰ìì ìì¸ ë´ì©ê³¼ ì§í ì 보를 íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: ì뢰ì 목ë¡ë§ 찾거ë ì뢰ì를 ë³ê²½ ëë ì²ë¦¬íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì íí ì뢰ì IDê° íìíë©° ë±ë¡ë ìì¸ ì ë³´ë§ ë°ííë¤.",
|
||||||
|
"functionDescription": "SOL ì뢰ì ìë³ì를 기ì¤ì¼ë¡ ì뢰ì ìì¸ ë´ì©ì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° í¹ì SOL ì뢰ìì ìì¸ ë´ì©ê³¼ ì§í ì 보를 íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "ì뢰ì 목ë¡ë§ 찾거ë ì뢰ì를 ë³ê²½ ëë ì²ë¦¬íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì íí ì뢰ì IDê° íìíë©° ë±ë¡ë ìì¸ ì ë³´ë§ ë°ííë¤.",
|
||||||
|
"displayDescription": "SOL ì뢰ì í ê±´ì ìì¸ ì 보를 ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì´ SOL ì뢰ì ìì¸ë¥¼ ë³´ì¬ì¤",
|
||||||
|
"ì뢰ì IDë¡ ì²ë¦¬ ë´ì©ì íì¸í´ì¤",
|
||||||
|
"ì íí ì뢰ìì ìì¸ ì 보를 ìë ¤ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"SOL",
|
||||||
|
"ì뢰ì"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"srId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ìì¸ ì¡°íí SOL ì뢰ì ID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"srId"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "sol",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/sol_request_detail",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "SOLG00000002",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "332361bb-488a-388a-974c-c28ec243c253",
|
||||||
|
"semver": "1.0.0",
|
||||||
|
"displayName": "SOL ì뢰ì ëª©ë¡ ì¡°í",
|
||||||
|
"name": "sol_request_list",
|
||||||
|
"description": "ì§íìí, ì¡°í기ê°ê³¼ ì¡°íëì ì¡°ê±´ì¼ë¡ SOL ì뢰ì 목ë¡ì ì¡°ííë¤.\nì¬ì© ìì : ì¬ì©ìê° ìì ì SOL ì뢰ìë ìíë³ ì뢰ì 목ë¡ì íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.\nì¬ì© ì ì¸: í¹ì ì뢰ìì ìì¸ ë´ì©ë§ ë³´ê±°ë ì뢰ì를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.\nì\u0085ì¶ë ¥ ì í: ì\u0085ë ¥ ì¡°ê±´ì í´ë¹íë ì뢰ì ìì½ ëª©ë¡ë§ ë°ííë¤.",
|
||||||
|
"functionDescription": "ì§íìí, ì¡°í기ê°ê³¼ ì¡°íëì ì¡°ê±´ì¼ë¡ SOL ì뢰ì 목ë¡ì ì¡°ííë¤.",
|
||||||
|
"whenToUse": "ì¬ì©ìê° ìì ì SOL ì뢰ìë ìíë³ ì뢰ì 목ë¡ì íì¸íë ¤ë ê²½ì° ì¬ì©íë¤.",
|
||||||
|
"whenNotToUse": "í¹ì ì뢰ìì ìì¸ ë´ì©ë§ ë³´ê±°ë ì뢰ì를 ë³ê²½íë ¤ë ê²½ì°ìë ì¬ì©íì§ ìëë¤.",
|
||||||
|
"ioLimits": "ì\u0085ë ¥ ì¡°ê±´ì í´ë¹íë ì뢰ì ìì½ ëª©ë¡ë§ ë°ííë¤.",
|
||||||
|
"displayDescription": "ì¡°ê±´ì ë§ë SOL ì뢰ì 목ë¡ì ì¡°íí©ëë¤.",
|
||||||
|
"exampleQueries": [
|
||||||
|
"ì§í ì¤ì¸ SOL ì뢰ì를 ë³´ì¬ì¤",
|
||||||
|
"ìµê·¼ í ë¬ê° ë´ ì뢰ì를 ì¡°íí´ì¤",
|
||||||
|
"ìë£ë ì뢰ì 목ë¡ì ìë ¤ì¤"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"SOL",
|
||||||
|
"ì뢰ì"
|
||||||
|
],
|
||||||
|
"ownerOrg": "MCP_TOOL",
|
||||||
|
"requiredEnvKeys": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"parametersSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí ì뢰ì ì§íìí"
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ì¡°íí ê¸°ê° ì¡°ê±´"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ëì ì\u0085무 ëë ì ì²´ ì¡°íëì"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"outputSchema": null,
|
||||||
|
"actionPrompts": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"categoryKey": "sol",
|
||||||
|
"endpoint": "http://localhost:8084/mcp/sol_request_list",
|
||||||
|
"podUrl": "http://localhost:8084",
|
||||||
|
"visible": true,
|
||||||
|
"enabled": true,
|
||||||
|
"isRegistered": false,
|
||||||
|
"requiresApproval": false,
|
||||||
|
"readOnlyHint": true,
|
||||||
|
"destructiveHint": false,
|
||||||
|
"idempotentHint": true,
|
||||||
|
"openWorldHint": true,
|
||||||
|
"integrationType": "REST",
|
||||||
|
"mciServiceId": "SOLG00000001",
|
||||||
|
"lastHeartbeat": null,
|
||||||
|
"failureRateThreshold": null,
|
||||||
|
"slidingWindowSize": null,
|
||||||
|
"rateLimitForPeriod": null,
|
||||||
|
"operationType": "READ",
|
||||||
|
"retryEnabled": true,
|
||||||
|
"circuitBreakerFailureThreshold": 0,
|
||||||
|
"circuitBreakerOpenMillis": 0,
|
||||||
|
"timeoutMillis": 5000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Count": 12
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user