refactor: apply new Naming standard (uid, categoryKey, subToolName) across modules

This commit is contained in:
jade
2026-07-11 00:37:29 +09:00
parent 406f093044
commit b9aa8e0169
25 changed files with 135 additions and 117 deletions

View File

@@ -39,12 +39,12 @@ public class McpBridge {
HttpResponse<String> response = client.send(req, HttpResponse.BodyHandlers.ofString());
// Parse tools from response string manually (basic string manipulation)
// axhub-gateway returns: {"result":{"tools":[{"toolName":"other_get_sample_string","description":"...","parametersSchema":{...}}]}}
// axhub-gateway returns: {"result":{"tools":[{"subToolName":"other_get_sample_string","description":"...","parametersSchema":{...}}]}}
// We need to format it to standard MCP format
String rawJson = response.body();
// For simplicity, we can just proxy the response if it matches closely, but it doesn't.
// Let's just do a naive conversion by replacing "toolName" with "name" and "parametersSchema" with "inputSchema"
String transformed = rawJson.replace("\"toolName\"", "\"name\"").replace("\"parametersSchema\"", "\"inputSchema\"");
// Let's just do a naive conversion by replacing "subToolName" with "name" and "parametersSchema" with "inputSchema"
String transformed = rawJson.replace("\"subToolName\"", "\"name\"").replace("\"parametersSchema\"", "\"inputSchema\"");
// Add type: "object" to inputSchema if missing - too complex with string replace, let's hope axhub-gateway already provides correct schema
// The result format expects: {"tools": [ ... ]}

View File

@@ -36,7 +36,10 @@ import java.util.Map;
public class ToolMetadata {
// 1. Tool 기본 정보
private String toolName; // 툴 고유 명칭 (예: CustomerSearchTool)
private String uid; // UUID 형식의 고유 식별자
private String semver; // 버전 (예: 1.0.0)
private String name; // 사람이 읽는 라벨 (1-128자)
private String subToolName; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
@@ -45,8 +48,8 @@ public class ToolMetadata {
// 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨)
private Map<String, String> actionPrompts;
// 2-1. 도메인 부서 그룹명 (권한 관리에 사용)
private String domainGroup;
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
private String categoryKey;
// 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info)
private String endpoint;

View File

@@ -181,12 +181,13 @@ public class ToolScaffolder {
@Service
@McpTool(
routingType = "%s",
group = "%s"
categoryKey = "%s"
)
public class %sService extends AbstractMcpToolService {
@McpFunction(
name = "%s",
name = "%s",
subToolName = "%s",
description = "%s",
prompt = "%s",
mappingId = "%s",
@@ -200,8 +201,8 @@ public class ToolScaffolder {
""".formatted(
BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE, BASE_PACKAGE,
BASE_PACKAGE, baseName, author, createDate, createDate, author,
routingType, group, baseName,
toolName, description, description + " 해줘.", interfaceId, register,
routingType, group.toLowerCase(), baseName,
baseName, toolName, description, description + " 해줘.", interfaceId, register,
baseName, routingType, interfaceId
);

View File

@@ -36,7 +36,10 @@ import java.util.Map;
public class ToolMetadata {
// 1. Tool 기본 정보
private String toolName; // 툴 고유 명칭 (예: CustomerSearchTool)
private String uid; // UUID 형식의 고유 식별자
private String semver; // 버전 (예: 1.0.0)
private String name; // 사람이 읽는 라벨 (1-128자)
private String subToolName; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
@@ -45,8 +48,8 @@ public class ToolMetadata {
// 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨)
private Map<String, String> actionPrompts;
// 2-1. 도메인 부서 그룹명 (권한 관리에 사용)
private String domainGroup;
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
private String categoryKey;
// 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info)
private String endpoint;

View File

@@ -109,11 +109,11 @@ public class McpRouterController {
public ResponseEntity<JsonRpcResponse> listTools() {
List<ToolMetadata> activeTools = redisRegistryService.getAllTools()
.stream()
.filter(ToolMetadata::isVisible)
.filter(ToolMetadata::getVisible)
.collect(java.util.stream.Collectors.toList());
java.util.Set<String> knownTools = activeTools.stream()
.map(ToolMetadata::getToolName)
.map(ToolMetadata::getUid)
.collect(java.util.stream.Collectors.toSet());
java.util.Set<String> fallbackUrls = new java.util.HashSet<>(gatewayFallbackProperties.getRoutes().values());
@@ -130,9 +130,9 @@ public class McpRouterController {
if (localTools != null) {
for (ToolMetadata t : localTools) {
if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getToolName())) {
if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getUid())) {
activeTools.add(t);
knownTools.add(t.getToolName());
knownTools.add(t.getUid());
}
}
}
@@ -152,7 +152,7 @@ public class McpRouterController {
public ResponseEntity<String> generateToolsMarkdown() {
List<ToolMetadata> tools = redisRegistryService.getAllTools()
.stream()
.filter(ToolMetadata::isVisible)
.filter(ToolMetadata::getVisible)
.collect(java.util.stream.Collectors.toList());
StringBuilder md = new StringBuilder();
@@ -165,7 +165,7 @@ public class McpRouterController {
// Group by Domain Group
Map<String, List<ToolMetadata>> groupedTools = new java.util.HashMap<>();
for (ToolMetadata tool : tools) {
String group = tool.getDomainGroup() != null ? tool.getDomainGroup() : "기타 (Others)";
String group = tool.getCategoryKey() != null ? tool.getCategoryKey() : "기타 (Others)";
groupedTools.computeIfAbsent(group, k -> new java.util.ArrayList<>()).add(tool);
}
@@ -174,7 +174,7 @@ public class McpRouterController {
int index = 1;
for (ToolMetadata tool : entry.getValue()) {
md.append("### ").append(index++).append(". ").append(tool.getToolName()).append("\n");
md.append("### ").append(index++).append(". ").append(tool.getUid()).append("\n");
if (tool.getDescription() != null) {
md.append("- **설명**: ").append(tool.getDescription()).append("\n");
}
@@ -223,14 +223,14 @@ public class McpRouterController {
}
@PostMapping("/registry/deregister")
public ResponseEntity<String> deregisterTool(@RequestBody String toolName) {
redisRegistryService.removeTool(toolName);
public ResponseEntity<String> deregisterTool(@RequestBody String uid) {
redisRegistryService.removeTool(uid);
return ResponseEntity.ok("Deregistered");
}
@PostMapping("/registry/heartbeat")
public ResponseEntity<String> heartbeat(@RequestBody String toolName) {
boolean success = redisRegistryService.refreshHeartbeat(toolName);
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
boolean success = redisRegistryService.refreshHeartbeat(uid);
if (success) {
return ResponseEntity.ok("Heartbeat updated");
} else {

View File

@@ -39,28 +39,28 @@ public class RedisRegistryService {
* 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료)
*/
public void saveTool(ToolMetadata meta) {
String key = KEY_PREFIX + meta.getToolName();
String key = KEY_PREFIX + meta.getUid();
meta.setLastHeartbeat(System.currentTimeMillis());
redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL);
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getToolName());
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getUid());
}
/**
* 하트비트 갱신 (TTL 초기화)
*/
public boolean refreshHeartbeat(String toolName) {
String key = KEY_PREFIX + toolName;
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] 하트비트 갱신: {}", toolName);
log.debug(" [RedisRegistry] 하트비트 갱신: {}", uid);
return true;
} else {
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", toolName);
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
return false;
}
}
public List<String> getAvailablePods(String toolName) {
ToolMetadata tool = getTool(toolName);
public List<String> getAvailablePods(String uid) {
ToolMetadata tool = getTool(uid);
if (tool != null && tool.getPodUrl() != null) {
return List.of(tool.getPodUrl());
@@ -71,8 +71,8 @@ public class RedisRegistryService {
/**
* 실행 시 툴 정보 조회 (Tool Execution 시 참조)
*/
public ToolMetadata getTool(String toolName) {
return redisTemplate.opsForValue().get(KEY_PREFIX + toolName);
public ToolMetadata getTool(String uid) {
return redisTemplate.opsForValue().get(KEY_PREFIX + uid);
}
/**
@@ -90,8 +90,8 @@ public class RedisRegistryService {
/**
* 툴 명시적 제거 (Deregister)
*/
public void removeTool(String toolName) {
redisTemplate.delete(KEY_PREFIX + toolName);
log.info(" [RedisRegistry] 툴 삭제 완료: {}", toolName);
public void removeTool(String uid) {
redisTemplate.delete(KEY_PREFIX + uid);
log.info(" [RedisRegistry] 툴 삭제 완료: {}", uid);
}
}

View File

@@ -73,19 +73,19 @@ public class ToolPlanner {
log.info(" [Planner] Fallback 라우팅 매칭됨: {} -> {}", toolName, fallbackPodUrl);
toolMetadata = ToolMetadata.builder()
.toolName(toolName)
.uid(toolName)
.integrationType("DIRECT")
.podUrl(fallbackPodUrl)
.build();
}
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
if (tenantId != null && toolMetadata.getDomainGroup() != null) {
if (tenantId != null && toolMetadata.getCategoryKey() != null) {
List<String> allowedDomains = securityProperties.getTenantDomains().get(tenantId);
if (allowedDomains == null ||
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getDomainGroup()))) {
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getDomainGroup());
throw new SecurityException("해당 도메인(" + toolMetadata.getDomainGroup() + ")의 툴을 실행할 권한이 없습니다.");
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getCategoryKey()))) {
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey());
throw new SecurityException("해당 도메인(" + toolMetadata.getCategoryKey() + ")의 툴을 실행할 권한이 없습니다.");
}
}

View File

@@ -283,7 +283,7 @@
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">소속 도메인 그룹</label>
<input type="text" class="form-control" name="group" list="groupList" placeholder="선택 또는 직접 입력 (대문자)" oninput="this.value = this.value.toUpperCase()" required>
<input type="text" class="form-control" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" list="groupList" placeholder="선택 또는 직접 입력 (대문자)" oninput="this.value = this.value.toUpperCase()" required>
<datalist id="groupList">
<option value="COMMON">COMMON (공통)</option>
<option value="CUSTOMER">CUSTOMER (고객)</option>
@@ -353,7 +353,7 @@
<thead class="table-light">
<tr>
<th style="width: 10%; min-width: 100px;">그룹</th>
<th style="width: 20%; min-width: 150px;">Tool 이름</th>
<th style="width: 20%; min-width: 150px;">서브툴 이름</th>
<th style="width: 35%; min-width: 200px;">설명</th>
<th style="width: 15%; min-width: 180px;">Pod 모듈</th>
<th style="width: 20%; min-width: 150px;">인터페이스 (ID)</th>
@@ -386,12 +386,12 @@
<div class="modal-body">
<form id="editToolForm">
<div class="mb-3">
<label class="form-label">Tool 이름 (수정불가)</label>
<input type="text" class="form-control" id="editToolName" name="toolName" readonly>
<label class="form-label">서브툴 이름 (수정불가)</label>
<input type="text" class="form-control" id="editSubToolName" name="subToolName" readonly>
</div>
<div class="mb-3">
<label class="form-label">소속 도메인 그룹</label>
<input type="text" class="form-control" id="editDomainGroup" name="domainGroup" oninput="this.value = this.value.toUpperCase()">
<input type="text" class="form-control" id="editCategoryKey" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" oninput="this.value = this.value.toUpperCase()">
</div>
<div class="mb-3">
<label class="form-label">설명</label>
@@ -485,14 +485,14 @@
// 그룹명 기준으로 오름차순 정렬
tools.sort((a, b) => {
const gA = a.domainGroup || '';
const gB = b.domainGroup || '';
const gA = a.categoryKey || '';
const gB = b.categoryKey || '';
return gA.localeCompare(gB);
});
// 그룹 필터 옵션 채우기
const groupSet = new Set();
tools.forEach(t => groupSet.add(t.domainGroup || 'UNKNOWN'));
tools.forEach(t => groupSet.add(t.categoryKey || 'UNKNOWN'));
const groupSelect = document.getElementById('groupFilterSelect');
const currentSelectedGroup = groupSelect.value;
groupSelect.innerHTML = '<option value="ALL">ALL (전체 그룹)</option>';
@@ -513,10 +513,10 @@
return `
<tr class="tool-row">
<td class="text-nowrap"><span class="badge bg-secondary tool-group">${t.domainGroup || 'UNKNOWN'}</span></td>
<td class="text-nowrap"><span class="badge bg-secondary tool-group">${t.categoryKey || 'UNKNOWN'}</span></td>
<td class="fw-bold text-primary text-break tool-name">
<a href="javascript:void(0)" onclick="openEditModal('${t.toolName}', '${t.domainGroup}', '${escDesc}', ${isReg})" class="text-decoration-none">
${t.toolName}
<a href="javascript:void(0)" onclick="openEditModal('${t.subToolName}', '${t.categoryKey}', '${escDesc}', ${isReg})" class="text-decoration-none">
${t.subToolName}
</a>
${badgeHtml}
</td>
@@ -555,9 +555,9 @@
}
let editModalInstance = null;
function openEditModal(toolName, domainGroup, description, isReg) {
document.getElementById('editToolName').value = toolName;
document.getElementById('editDomainGroup').value = domainGroup !== 'null' ? domainGroup : '';
function openEditModal(subToolName, categoryKey, description, isReg) {
document.getElementById('editSubToolName').value = subToolName;
document.getElementById('editCategoryKey').value = categoryKey !== 'null' ? categoryKey : '';
document.getElementById('editDescription').value = description;
document.getElementById('editRegister').value = isReg ? 'true' : 'false';
@@ -568,8 +568,8 @@
}
function submitToolUpdate() {
const toolName = document.getElementById('editToolName').value;
const domainGroup = document.getElementById('editDomainGroup').value;
const subToolName = document.getElementById('editSubToolName').value;
const categoryKey = document.getElementById('editCategoryKey').value;
const description = document.getElementById('editDescription').value;
const register = document.getElementById('editRegister').value;
@@ -582,8 +582,8 @@
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
toolName: toolName,
domainGroup: domainGroup,
subToolName: subToolName,
categoryKey: categoryKey,
description: description,
register: register
})

View File

@@ -218,7 +218,7 @@
// Group by domain
const grouped = {};
tools.forEach(tool => {
const group = tool.domainGroup || '기타 (Others)';
const group = tool.categoryKey || '기타 (Others)';
if (!grouped[group]) grouped[group] = [];
grouped[group].push(tool);
});
@@ -317,7 +317,7 @@
<div class="absolute top-4 right-4 px-2 py-0.5 rounded text-[9px] font-bold font-mono border tracking-wider ${typeClass}">
${tool.integrationType || 'DIRECT'}
</div>
<h3 class="text-lg font-bold text-white mb-1 pr-16">${tool.toolName}</h3>
<h3 class="text-lg font-bold text-white mb-1 pr-16">${tool.subToolName}</h3>
<p class="text-sm text-slate-400 leading-relaxed min-h-[40px]">${tool.description || '설명이 없습니다.'}</p>
${paramHtml}

View File

@@ -6,7 +6,8 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface McpFunction {
String name();
String name(); // 사람이 읽는 라벨 (예: "고객 조회 툴")
String subToolName(); // MCP 서브툴 명칭 (예: "customer_search")
String description();
String prompt() default "";
String mappingId() default "";

View File

@@ -13,6 +13,6 @@ public @interface McpTool {
@AliasFor(annotation = Component.class)
String value() default "";
String group() default "COMMON";
String categoryKey() default "common";
String routingType() default "HTTP";
}

View File

@@ -28,11 +28,21 @@ import java.util.Map;
@NoArgsConstructor
@AllArgsConstructor
public class ToolMetadata {
private String toolName;
private String description;
// 1. Tool 기본 정보
private String uid; // UUID 형식의 고유 식별자
private String semver; // 버전 (예: 1.0.0)
private String name; // 사람이 읽는 라벨 (1-128자)
private String subToolName; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool)
private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능)
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
private Map<String, Object> parametersSchema;
// 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨)
private Map<String, String> actionPrompts;
private String domainGroup;
// 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식)
private String categoryKey;
private String endpoint;
private String podUrl;
private String integrationType;

View File

@@ -81,19 +81,23 @@ public class ToolRegistryHeartbeatSender {
McpFunction functionAnnotation = AnnotationUtils.findAnnotation(method, McpFunction.class);
if (functionAnnotation != null) {
String baseName = functionAnnotation.name();
String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
? mcpProperties.getNamespace() + "_" + baseName
: baseName;
String rawSubToolName = functionAnnotation.subToolName();
String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty()
? mcpProperties.getNamespace() + "_" + rawSubToolName
: rawSubToolName;
boolean isRegister = functionAnnotation.register();
if (!isRegister) {
log.info(" [HeartbeatSender] '{}' 툴은 어노테이션 설정에 의해 외부 등록(Redis) 대상에서 제외되었습니다. (최종 이름: {})", baseName, finalName);
log.info(" [HeartbeatSender] '{}' 툴은 어노테이션 설정에 의해 외부 등록(Redis) 대상에서 제외되었습니다. (최종 이름: {})", baseName, subToolName);
}
ToolMetadata meta = new ToolMetadata();
meta.setToolName(finalName);
meta.setUid(java.util.UUID.nameUUIDFromBytes(subToolName.getBytes()).toString());
meta.setName(baseName);
meta.setSubToolName(subToolName);
meta.setSemver("1.0.0");
meta.setDescription(functionAnnotation.description());
meta.setDomainGroup(toolAnnotation.group());
meta.setCategoryKey(toolAnnotation.categoryKey());
meta.setIntegrationType(toolAnnotation.routingType());
meta.setMciServiceId(functionAnnotation.mappingId());
meta.setPodUrl(podUrl);
@@ -105,7 +109,7 @@ public class ToolRegistryHeartbeatSender {
Map<String, String> prompts = new HashMap<>();
String promptText = functionAnnotation.prompt();
prompts.put(finalName, promptText);
prompts.put(subToolName, promptText);
meta.setActionPrompts(prompts);
if (method.getParameterCount() > 0) {
@@ -117,7 +121,7 @@ public class ToolRegistryHeartbeatSender {
finalSchema.put("properties", schema);
meta.setParametersSchema(finalSchema);
} catch (Exception e) {
log.error("Failed to generate schema for {}", finalName, e);
log.error("Failed to generate schema for {}", subToolName, e);
}
}
@@ -125,7 +129,7 @@ public class ToolRegistryHeartbeatSender {
registeredTools.add(meta);
}
allScannedTools.add(meta);
log.info(" [HeartbeatSender] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getToolName(), isRegister);
log.info(" [HeartbeatSender] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getUid(), isRegister);
}
}
}
@@ -141,15 +145,15 @@ public class ToolRegistryHeartbeatSender {
.uri(gatewayUrl + "/mcp/api/v1/registry/heartbeat")
.header("Content-Type", "application/json")
.header("X-API-KEY", "SHINHAN_MCP_TEST_KEY_9999")
.body(tool.getToolName())
.body(tool.getUid())
.retrieve()
.toEntity(String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.info(" [HeartbeatSender] 하트비트 전송 성공: {}", tool.getToolName());
log.info(" [HeartbeatSender] 하트비트 전송 성공: {}", tool.getUid());
}
} catch (Exception e) {
log.warn(" [HeartbeatSender] 하트비트 전송 실패 ({}): {}. 재등록을 시도합니다.", tool.getToolName(), e.getMessage());
log.warn(" [HeartbeatSender] 하트비트 전송 실패 ({}): {}. 재등록을 시도합니다.", tool.getUid(), e.getMessage());
registerTool(tool);
}
}
@@ -164,7 +168,7 @@ public class ToolRegistryHeartbeatSender {
.body(tool)
.retrieve()
.toBodilessEntity();
log.info(" [HeartbeatSender] 툴 재등록 성공: {}", tool.getToolName());
log.info(" [HeartbeatSender] 툴 재등록 성공: {}", tool.getUid());
} catch (Exception ex) {
log.error(" [HeartbeatSender] 툴 등록 실패: {}", ex.getMessage());
}

View File

@@ -24,10 +24,10 @@ import java.util.Map;
* </pre>
*/
@Slf4j
@McpTool(routingType = "EAI", group = "NOTIFICATION")
@McpTool(routingType = "EAI", categoryKey = "notification")
public class EmailToolService extends AbstractMcpToolService {
@McpFunction(name = "send_email", description = "이메일 발송", prompt = "고객에게 이메일을 발송해줘.", mappingId = "EMAIL_SEND_001")
@McpFunction(name = "send_email", subToolName = "send_email", description = "이메일 발송", prompt = "고객에게 이메일을 발송해줘.", mappingId = "EMAIL_SEND_001")
public Object sendEmail(EmailSendReq req) {
log.info("[Email] 이메일 발송 요청 수신. 수신자: {}", req.getEmailAddress());

View File

@@ -11,11 +11,10 @@ import java.util.Map;
@Slf4j
@Service
@McpTool(routingType = "MCI", group = "COMMON")
@McpTool(routingType = "MCI", categoryKey = "common")
public class BalanceService extends AbstractMcpToolService {
@McpFunction(
name = "balance",
@McpFunction(name = "balance 툴", subToolName = "balance",
description = "고객의 계좌 잔액을 조회합니다.",
prompt = "고객 계좌 잔액을 조회해줘.",
mappingId = "ACC_001"

View File

@@ -11,7 +11,7 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.BillingProcessReq;
@McpTool(
routingType = "MCI",
group = "CLAIM"
categoryKey = "claim"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -30,12 +30,12 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.BillingProcessReq;
@lombok.extern.slf4j.Slf4j
public class BillingProcessService extends AbstractMcpToolService {
@McpFunction(name = "status", description = "청구심사 상태 조회", prompt = "현재 접수된 청구건 상태를 알려줘.", mappingId = "BILL_001")
@McpFunction(name = "status", subToolName = "status", description = "청구심사 상태 조회", prompt = "현재 접수된 청구건 상태를 알려줘.", mappingId = "BILL_001")
public Object getStatus(BillingStatusReq data) {
return executeBillingLogic("BILL_001", data);
}
@McpFunction(name = "process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
@McpFunction(name = "process", subToolName = "process", description = "청구 처리", prompt = "현재 접수된 청구건에 대한 심사 처리를 진행해.", mappingId = "BILL_002")
public Object processBilling(BillingProcessReq data) {
return executeBillingLogic("BILL_002", data);
}

View File

@@ -7,7 +7,7 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.BondIssueReq;
@McpTool(
routingType = "EAI",
group = "POLICY"
categoryKey = "policy"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -25,12 +25,12 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.BondIssueReq;
*/
public class BondIssueService extends AbstractMcpToolService {
@McpFunction(name = "check", register = true, description = "발행 가능 여부 조회 테스트", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001")
@McpFunction(name = "check", subToolName = "check", register = true, description = "발행 가능 여부 조회 테스트", prompt = "디지털 증권 발행 한도가 충분한지 확인해줘.", mappingId = "BOND_001")
public Object check(BondCheckReq data) {
return executeLegacy("EAI", "BOND_001", data);
}
1 @McpFunction(name = "issue", register = true, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
@McpFunction(name = "issue 툴", subToolName = "issue", register = true, description = "증권 발행 테스트1", prompt = "디지털 증권 발행 프로세스를 실행해.", mappingId = "BOND_002")
public Object issue(BondIssueReq data) {
return executeLegacy("EAI", "BOND_002", data);
}

View File

@@ -8,7 +8,7 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.LeaveCountReq;
@McpTool(
routingType = "HTTP",
group = "HR"
categoryKey = "hr"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -26,17 +26,17 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.LeaveCountReq;
*/
public class CommonUtilityService extends AbstractMcpToolService {
@McpFunction(name = "register_vacation", description = "휴가 등록", prompt = "내일 하루 연차 휴가를 등록해줘.", mappingId = "HR_VAC_01")
@McpFunction(name = "register_vacation", subToolName = "register_vacation", description = "휴가 등록", prompt = "내일 하루 연차 휴가를 등록해줘.", mappingId = "HR_VAC_01")
public Object registerVacation(VacationRegisterReq data) {
return executeLegacy("HTTP", "HR_VAC_01", data);
}
@McpFunction(name = "get_leave_count", description = "연차 갯수 조회", prompt = "현재 사용 가능한 남은 연차 일수를 알려줘.", mappingId = "HR_VAC_02")
@McpFunction(name = "get_leave_count", subToolName = "get_leave_count", description = "연차 갯수 조회", prompt = "현재 사용 가능한 남은 연차 일수를 알려줘.", mappingId = "HR_VAC_02")
public Object getLeaveCount(LeaveCountReq data) {
return executeLegacy("HTTP", "HR_VAC_02", data);
}
@McpFunction(name = "secret_tool", description = "비공개 툴 테스트", prompt = "숨겨진 툴 강제 호출", mappingId = "SECRET_001", visible = false)
@McpFunction(name = "secret_tool", subToolName = "secret_tool", description = "비공개 툴 테스트", prompt = "숨겨진 툴 강제 호출", mappingId = "SECRET_001", visible = false)
public Object secretTool(LeaveCountReq data) {
return executeLegacy("HTTP", "SECRET_001", data);
}

View File

@@ -7,7 +7,7 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.ContractDetailReq;
@McpTool(
routingType = "HTTP",
group = "CONTRACT"
categoryKey = "contract"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -25,12 +25,12 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.ContractDetailReq;
*/
public class ContractInquiryService extends AbstractMcpToolService {
@McpFunction(name = "contract_status", description = "계약상태 조회", prompt = "김신한 고객의 현재 계약 상태를 조회해줘.", mappingId = "CNTR_001")
@McpFunction(name = "contract_status", subToolName = "contract_status", description = "계약상태 조회", prompt = "김신한 고객의 현재 계약 상태를 조회해줘.", mappingId = "CNTR_001")
public Object getStatus(ContractStatusReq data) {
return executeLegacy("HTTP", "CNTR_001", data);
}
@McpFunction(name = "contract_detail", description = "계약상세 조회", prompt = "김신한 고객의 계약 상세 내역을 알려줘.", mappingId = "CNTR_002")
@McpFunction(name = "contract_detail", subToolName = "contract_detail", description = "계약상세 조회", prompt = "김신한 고객의 계약 상세 내역을 알려줘.", mappingId = "CNTR_002")
public Object getDetail(ContractDetailReq data) {
return executeLegacy("HTTP", "CNTR_002", data);
}

View File

@@ -7,7 +7,7 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.CustomerDetailReq;
@McpTool(
routingType = "TCP",
group = "CUSTOMER"
categoryKey = "customer"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -25,12 +25,12 @@ import io.shinhanlife.axhub.biz.mcp.tool.dto.CustomerDetailReq;
*/
public class CustomerInfoService extends AbstractMcpToolService {
@McpFunction(name = "grade", description = "고객등급 조회", prompt = "이 고객의 VIP 등급을 조회해줘.", mappingId = "CRM_001")
@McpFunction(name = "grade", subToolName = "grade", description = "고객등급 조회", prompt = "이 고객의 VIP 등급을 조회해줘.", mappingId = "CRM_001")
public Object getGrade(CustomerGradeReq req) {
return executeLegacy("TCP", "CRM_001", req);
}
@McpFunction(name = "detail", description = "고객상세 정보 조회", prompt = "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘.", mappingId = "CRM_002")
@McpFunction(name = "detail", subToolName = "detail", description = "고객상세 정보 조회", prompt = "이 고객의 상세 기본정보(주소, 연락처 등)를 알려줘.", mappingId = "CRM_002")
public Object getDetail(CustomerDetailReq data) {
return executeLegacy("TCP", "CRM_002", data);
}

View File

@@ -25,12 +25,11 @@ import java.util.Map;
@Service
@McpTool(
routingType = "MCI_STRING",
group = "COMMON"
categoryKey = "common"
)
public class SampleStringToolService extends AbstractMcpToolService {
@McpFunction(
name = "get_sample_string",
@McpFunction(name = "get_sample_string 툴", subToolName = "get_sample_string",
description = "MCI String 버전과 GlowTrgmField 파싱을 테스트하는 샘플 툴입니다.",
prompt = "MCI 전문(String) 연계 및 고정 길이 파싱 테스트 해줘.",
mappingId = "TRGM_001"

View File

@@ -11,7 +11,7 @@ import java.util.Map;
@Service
@McpTool(
routingType = "HTTP",
group = "COMMON"
categoryKey = "common"
)
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.service
@@ -29,8 +29,7 @@ import java.util.Map;
*/
public class TemplateUtilityService extends AbstractMcpToolService {
@McpFunction(
name = "get_template_file_url",
@McpFunction(name = "get_template_file_url 툴", subToolName = "get_template_file_url",
description = "특정 템플릿의 양식 파일(엑셀, 워드 등)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.",
prompt = "요청하신 템플릿 양식 파일 다운로드 URL은 다음과 같습니다. 클릭하여 다운로드하세요:",
register = false

View File

@@ -27,12 +27,11 @@ import java.util.UUID;
@Service
@McpTool(
routingType = "MCI",
group = "PAYMENT"
categoryKey = "payment"
)
public class PaymentApprovalService extends AbstractMcpToolService {
@McpFunction(
name = "paymentapproval",
@McpFunction(name = "paymentapproval 툴", subToolName = "paymentapproval",
description = "결제 승인 처리",
prompt = "결제 승인 처리 해줘.",
mappingId = "PAY_001"

View File

@@ -25,10 +25,10 @@ import java.util.Map;
* </pre>
*/
@Slf4j
@McpTool(routingType = "EAI", group = "NOTIFICATION")
@McpTool(routingType = "EAI", categoryKey = "notification")
public class SmsToolService extends AbstractMcpToolService {
@McpFunction(name = "send_sms", description = "SMS 발송", prompt = "고객에게 SMS 메시지를 발송해줘.", mappingId = "SMS_SEND_001")
@McpFunction(name = "send_sms", subToolName = "send_sms", description = "SMS 발송", prompt = "고객에게 SMS 메시지를 발송해줘.", mappingId = "SMS_SEND_001")
public Object sendSms(SmsSendReq req) {
log.info("[SMS] SMS 발송 요청 수신. 수신자: {}", req.getPhoneNumber());

View File

@@ -15,7 +15,7 @@ services:
build:
context: ./axhub-gateway
ports:
- "8081:8081"
- "8281:8081"
volumes:
- ./:/src
depends_on:
@@ -33,7 +33,7 @@ services:
build:
context: ./axhub-tool-sms
ports:
- "8082:8082"
- "8282:8082"
depends_on:
- redis
environment:
@@ -53,7 +53,7 @@ services:
build:
context: ./axhub-tool-email
ports:
- "8083:8083"
- "8283:8083"
depends_on:
- redis
environment:
@@ -73,7 +73,7 @@ services:
build:
context: ./axhub-tool-other
ports:
- "8084:8084"
- "8284:8084"
depends_on:
- redis
environment:
@@ -93,7 +93,7 @@ services:
build:
context: ./axhub-tool-payment
ports:
- "8085:8085"
- "8285:8085"
depends_on:
- redis
environment: