refactor: restructure tools/list response to strictly follow MCP schema standard (PDF page 62)
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 26s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 26s
This commit is contained in:
@@ -166,9 +166,52 @@ public class McpRouterController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Map<String, Object>> mappedTools = activeTools.stream().map(t -> {
|
||||||
|
Map<String, Object> toolMap = new HashMap<>();
|
||||||
|
toolMap.put("name", t.getName());
|
||||||
|
toolMap.put("description", t.getDescription());
|
||||||
|
toolMap.put("inputSchema", t.getParametersSchema());
|
||||||
|
toolMap.put("outputSchema", t.getOutputSchema());
|
||||||
|
toolMap.put("title", t.getDisplayName());
|
||||||
|
|
||||||
|
Map<String, Object> annotations = new HashMap<>();
|
||||||
|
annotations.put("readOnlyHint", t.getReadOnlyHint() != null ? t.getReadOnlyHint() : false);
|
||||||
|
annotations.put("destructiveHint", t.getDestructiveHint() != null ? t.getDestructiveHint() : true);
|
||||||
|
annotations.put("idempotentHint", t.getIdempotentHint() != null ? t.getIdempotentHint() : false);
|
||||||
|
toolMap.put("annotations", annotations);
|
||||||
|
|
||||||
|
Map<String, Object> meta = new HashMap<>();
|
||||||
|
meta.put("uid", t.getUid());
|
||||||
|
meta.put("version", t.getSemver());
|
||||||
|
meta.put("moduleName", t.getModuleName());
|
||||||
|
meta.put("category_key", t.getCategoryKey());
|
||||||
|
meta.put("tags", t.getTags());
|
||||||
|
meta.put("owner_org", t.getOwnerOrg());
|
||||||
|
meta.put("legacy_interface_id", t.getMciServiceId());
|
||||||
|
meta.put("display_description", t.getDisplayDescription());
|
||||||
|
meta.put("example_queries", t.getExampleQueries());
|
||||||
|
meta.put("required_env_keys", t.getRequiredEnvKeys());
|
||||||
|
meta.put("endpoint", t.getEndpoint());
|
||||||
|
meta.put("podUrl", t.getPodUrl());
|
||||||
|
meta.put("visible", t.getVisible());
|
||||||
|
meta.put("enabled", t.getEnabled());
|
||||||
|
meta.put("isRegistered", t.getIsRegistered());
|
||||||
|
meta.put("requiresApproval", t.getRequiresApproval());
|
||||||
|
meta.put("integrationType", t.getIntegrationType());
|
||||||
|
meta.put("mciServiceId", t.getMciServiceId());
|
||||||
|
meta.put("operationType", t.getOperationType());
|
||||||
|
meta.put("retryEnabled", t.getRetryEnabled());
|
||||||
|
meta.put("circuitBreakerFailureThreshold", t.getCircuitBreakerFailureThreshold());
|
||||||
|
meta.put("circuitBreakerOpenMillis", t.getCircuitBreakerOpenMillis());
|
||||||
|
meta.put("timeoutMillis", t.getTimeoutMillis());
|
||||||
|
|
||||||
|
toolMap.put("_meta", meta);
|
||||||
|
return toolMap;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
JsonRpcResponse response = new JsonRpcResponse();
|
JsonRpcResponse response = new JsonRpcResponse();
|
||||||
response.setId(UUID.randomUUID().toString());
|
response.setId(UUID.randomUUID().toString());
|
||||||
response.setResult(Map.of("tools", activeTools));
|
response.setResult(Map.of("tools", mappedTools));
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@
|
|||||||
}
|
}
|
||||||
const grouped = {};
|
const grouped = {};
|
||||||
tools.forEach(tool => {
|
tools.forEach(tool => {
|
||||||
const group = tool.categoryKey || 'Others';
|
const group = tool._meta?.category_key || 'Others';
|
||||||
if (!grouped[group]) grouped[group] = [];
|
if (!grouped[group]) grouped[group] = [];
|
||||||
grouped[group].push(tool);
|
grouped[group].push(tool);
|
||||||
});
|
});
|
||||||
@@ -209,16 +209,15 @@
|
|||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'tool-card';
|
card.className = 'tool-card';
|
||||||
|
|
||||||
const isDirect = tool.integrationType === 'DIRECT';
|
const isDirect = tool._meta?.integrationType === 'DIRECT';
|
||||||
const badgeBg = isDirect ? 'rgba(59,130,246,0.15)' : 'rgba(249,115,22,0.15)';
|
const badgeBg = isDirect ? 'rgba(59,130,246,0.15)' : 'rgba(249,115,22,0.15)';
|
||||||
const badgeColor = isDirect ? '#60a5fa' : '#fb923c';
|
const badgeColor = isDirect ? '#60a5fa' : '#fb923c';
|
||||||
const badgeBorder = isDirect ? 'rgba(59,130,246,0.3)' : 'rgba(249,115,22,0.3)';
|
const badgeBorder = isDirect ? 'rgba(59,130,246,0.3)' : 'rgba(249,115,22,0.3)';
|
||||||
|
|
||||||
let paramHtml = '';
|
let paramHtml = '';
|
||||||
const schema = tool.inputSchema || tool.parametersSchema;
|
if (tool.parametersSchema && tool.parametersSchema.properties) {
|
||||||
if (schema && schema.properties) {
|
const props = tool.parametersSchema.properties;
|
||||||
const props = schema.properties;
|
const required = tool.parametersSchema.required || [];
|
||||||
const required = schema.required || [];
|
|
||||||
if (Object.keys(props).length > 0) {
|
if (Object.keys(props).length > 0) {
|
||||||
paramHtml = `
|
paramHtml = `
|
||||||
<div style="margin-top:16px; padding-top:16px; border-top:1px solid #27272a;">
|
<div style="margin-top:16px; padding-top:16px; border-top:1px solid #27272a;">
|
||||||
@@ -256,14 +255,14 @@
|
|||||||
<div style="display:flex; align-items:flex-start; justify-content:space-between;">
|
<div style="display:flex; align-items:flex-start; justify-content:space-between;">
|
||||||
<div style="flex:1;">
|
<div style="flex:1;">
|
||||||
<div style="display:flex; align-items:center; margin-bottom:4px;">
|
<div style="display:flex; align-items:center; margin-bottom:4px;">
|
||||||
<h3 style="font-size:16px; font-weight:600; color:#f4f4f5;">${tool.displayName || tool.name}</h3>
|
<h3 style="font-size:16px; font-weight:600; color:#f4f4f5;">${tool.title || tool.name}</h3>
|
||||||
<span style="margin:0 8px; color:#3f3f46;">/</span>
|
<span style="margin:0 8px; color:#3f3f46;">/</span>
|
||||||
<span class="geist-mono" style="font-size:12px; color:#71717a;">${tool.name}</span>
|
<span class="geist-mono" style="font-size:12px; color:#71717a;">${tool.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<p style="font-size:14px; color:#a1a1aa;">${tool.description || 'No description provided.'}</p>
|
<p style="font-size:14px; color:#a1a1aa;">${tool.description || 'No description provided.'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-left:16px; padding:4px 10px; border-radius:6px; font-size:11px; font-weight:600; background:${badgeBg}; color:${badgeColor}; border:1px solid ${badgeBorder};" class="geist-mono">
|
<div style="margin-left:16px; padding:4px 10px; border-radius:6px; font-size:11px; font-weight:600; background:${badgeBg}; color:${badgeColor}; border:1px solid ${badgeBorder};" class="geist-mono">
|
||||||
${tool.integrationType || 'DIRECT'}
|
${tool._meta?.integrationType || 'DIRECT'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${paramHtml}
|
${paramHtml}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@
|
|||||||
const tools = data.result?.tools || [];
|
const tools = data.result?.tools || [];
|
||||||
const groupedTools = {};
|
const groupedTools = {};
|
||||||
tools.forEach(tool => {
|
tools.forEach(tool => {
|
||||||
const group = tool.categoryKey || 'Others';
|
const group = tool._meta?.category_key || 'Others';
|
||||||
if (!groupedTools[group]) groupedTools[group] = [];
|
if (!groupedTools[group]) groupedTools[group] = [];
|
||||||
groupedTools[group].push(tool);
|
groupedTools[group].push(tool);
|
||||||
});
|
});
|
||||||
@@ -217,9 +217,9 @@
|
|||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = tool.name;
|
option.value = tool.name;
|
||||||
option.textContent = tool.name;
|
option.textContent = tool.name;
|
||||||
option.dataset.schema = JSON.stringify(tool.inputSchema || tool.parametersSchema);
|
option.dataset.schema = JSON.stringify(tool.parametersSchema);
|
||||||
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
||||||
option.dataset.integrationType = tool.integrationType || 'HTTP';
|
option.dataset.integrationType = tool._meta?.integrationType || 'HTTP';
|
||||||
optgroup.appendChild(option);
|
optgroup.appendChild(option);
|
||||||
if (tool.actionPrompts) Object.assign(actionPrompts, tool.actionPrompts);
|
if (tool.actionPrompts) Object.assign(actionPrompts, tool.actionPrompts);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -331,7 +331,7 @@
|
|||||||
|
|
||||||
const groupedTools = {};
|
const groupedTools = {};
|
||||||
tools.forEach(tool => {
|
tools.forEach(tool => {
|
||||||
const group = tool.domainGroup || '기타 그룹';
|
const group = tool._meta?.category_key || '기타';
|
||||||
if (!groupedTools[group]) {
|
if (!groupedTools[group]) {
|
||||||
groupedTools[group] = [];
|
groupedTools[group] = [];
|
||||||
}
|
}
|
||||||
@@ -348,16 +348,15 @@
|
|||||||
|
|
||||||
groupedTools[group].forEach(tool => {
|
groupedTools[group].forEach(tool => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = tool.toolName;
|
option.value = tool.name;
|
||||||
const commType = tool.integrationType ? tool.integrationType : 'HTTP';
|
const commType = tool._meta?.integrationType || 'HTTP';
|
||||||
option.textContent = tool.description ? `[${commType}] ${tool.description} (${tool.toolName})` : `[${commType}] ${tool.toolName}`;
|
option.textContent = tool.description ? `[${commType}] ${tool.description} (${tool.name})` : `[${commType}] ${tool.name}`;
|
||||||
option.dataset.schema = JSON.stringify(tool.inputSchema || tool.parametersSchema);
|
option.dataset.schema = JSON.stringify(tool.inputSchema || tool.parametersSchema);
|
||||||
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
||||||
optgroup.appendChild(option);
|
optgroup.appendChild(option);
|
||||||
|
|
||||||
const schema = tool.inputSchema || tool.parametersSchema;
|
const enums = tool.parametersSchema?.properties?.action?.enum || [];
|
||||||
const enums = schema?.properties?.action?.enum || [];
|
const descStr = tool.parametersSchema?.properties?.action?.description || "";
|
||||||
const descStr = schema?.properties?.action?.description || "";
|
|
||||||
|
|
||||||
let descMap = {};
|
let descMap = {};
|
||||||
if(descStr.includes(":")) {
|
if(descStr.includes(":")) {
|
||||||
@@ -370,7 +369,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toolFunctions[tool.toolName] = enums.map(e => {
|
toolFunctions[tool.name] = enums.map(e => {
|
||||||
return { value: e, label: descMap[e] || e };
|
return { value: e, label: descMap[e] || e };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -296,9 +296,9 @@
|
|||||||
t._isFailed = false;
|
t._isFailed = false;
|
||||||
t._latency = 0;
|
t._latency = 0;
|
||||||
t._responseData = null;
|
t._responseData = null;
|
||||||
categories.add(t.categoryKey || 'oth');
|
categories.add(t._meta?.category_key || 'oth');
|
||||||
if (t.moduleName) {
|
if (t._meta?.moduleName) {
|
||||||
modules.add(t.moduleName.replace('dap-was-', ''));
|
modules.add(t._meta.moduleName.replace('dap-was-', ''));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@
|
|||||||
function generateDummyPayload(tool) {
|
function generateDummyPayload(tool) {
|
||||||
if (tool._customPayload) return tool._customPayload;
|
if (tool._customPayload) return tool._customPayload;
|
||||||
|
|
||||||
const schema = tool.inputSchema || tool.parametersSchema;
|
const schema = tool.parametersSchema;
|
||||||
if (!schema || !schema.properties) return {};
|
if (!schema || !schema.properties) return {};
|
||||||
const payload = {};
|
const payload = {};
|
||||||
for (const [key, value] of Object.entries(schema.properties)) {
|
for (const [key, value] of Object.entries(schema.properties)) {
|
||||||
@@ -357,8 +357,8 @@
|
|||||||
|
|
||||||
filteredTools = allTools.filter(t => {
|
filteredTools = allTools.filter(t => {
|
||||||
const matchSearch = t.name.toLowerCase().includes(search) || (t.description || '').toLowerCase().includes(search);
|
const matchSearch = t.name.toLowerCase().includes(search) || (t.description || '').toLowerCase().includes(search);
|
||||||
const matchCategory = !category || (t.categoryKey || 'oth') === category;
|
const matchCategory = !category || (t._meta?.category_key || 'oth') === category;
|
||||||
const toolMod = t.moduleName ? t.moduleName.replace('dap-was-', '') : '';
|
const toolMod = t._meta?.moduleName ? t._meta.moduleName.replace('dap-was-', '') : '';
|
||||||
const matchModule = !moduleFlt || toolMod === moduleFlt;
|
const matchModule = !moduleFlt || toolMod === moduleFlt;
|
||||||
return matchSearch && matchCategory && matchModule;
|
return matchSearch && matchCategory && matchModule;
|
||||||
});
|
});
|
||||||
@@ -395,8 +395,8 @@
|
|||||||
|
|
||||||
html += `
|
html += `
|
||||||
<tr id="row-${index}">
|
<tr id="row-${index}">
|
||||||
<td><span class="cat-badge" style="background:rgba(59,130,246,0.1); color:#60a5fa; border:1px solid rgba(59,130,246,0.2);">${tool.moduleName ? tool.moduleName.replace('dap-was-', '') : 'unknown'}</span></td>
|
<td><span class="cat-badge" style="background:rgba(59,130,246,0.1); color:#60a5fa; border:1px solid rgba(59,130,246,0.2);">${tool._meta?.moduleName ? tool._meta.moduleName.replace('dap-was-', '') : 'unknown'}</span></td>
|
||||||
<td><span class="cat-badge">${tool.categoryKey || 'oth'}</span></td>
|
<td><span class="cat-badge">${tool._meta?.category_key || 'oth'}</span></td>
|
||||||
<td class="font-medium text-slate-200">${tool.name}</td>
|
<td class="font-medium text-slate-200">${tool.name}</td>
|
||||||
<td class="text-sm text-zinc-400 geist-mono flex items-center gap-3">
|
<td class="text-sm text-zinc-400 geist-mono flex items-center gap-3">
|
||||||
<span title='${payloadStr.replace(/'/g, "'")}'>${shortPayload}</span>
|
<span title='${payloadStr.replace(/'/g, "'")}'>${shortPayload}</span>
|
||||||
@@ -705,7 +705,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderSmartForm(tool, payloadToUse = null) {
|
function renderSmartForm(tool, payloadToUse = null) {
|
||||||
const schema = tool.inputSchema || tool.parametersSchema;
|
const schema = tool.parametersSchema;
|
||||||
const form = document.getElementById('payloadFormContainer');
|
const form = document.getElementById('payloadFormContainer');
|
||||||
form.innerHTML = '';
|
form.innerHTML = '';
|
||||||
|
|
||||||
@@ -739,7 +739,7 @@
|
|||||||
inputHtml = `<input type="text" data-key="${key}" data-type="string" value="${typeof val === 'object' ? JSON.stringify(val).replace(/"/g, '"') : val}" class="bg-[#09090b] border border-[#3f3f46] text-white text-sm rounded p-2 w-full focus:border-blue-500 outline-none">`;
|
inputHtml = `<input type="text" data-key="${key}" data-type="string" value="${typeof val === 'object' ? JSON.stringify(val).replace(/"/g, '"') : val}" class="bg-[#09090b] border border-[#3f3f46] text-white text-sm rounded p-2 w-full focus:border-blue-500 outline-none">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reqStar = ((tool.inputSchema || tool.parametersSchema)?.required?.includes(key)) ? '<span class="text-red-500">*</span>' : '';
|
let reqStar = (schema.required && schema.required.includes(key)) ? '<span class="text-red-500">*</span>' : '';
|
||||||
html += `<div class="bg-[#18181b] p-4 rounded-lg border border-[#27272a]">
|
html += `<div class="bg-[#18181b] p-4 rounded-lg border border-[#27272a]">
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">${key} ${reqStar} <span class="text-xs text-zinc-500 font-mono font-normal ml-2">${typeLabel}</span></label>
|
<label class="block text-sm font-medium text-slate-300 mb-2">${key} ${reqStar} <span class="text-xs text-zinc-500 font-mono font-normal ml-2">${typeLabel}</span></label>
|
||||||
${inputHtml}
|
${inputHtml}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package io.shinhanlife.dap.mcg;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package io.shinhanlife.dap.mcg
|
|
||||||
* @className DapGatewayApplicationTests
|
|
||||||
* @description AX HUB ?<3F>스??처리 ?<3F>래??
|
|
||||||
* @author 0986406
|
|
||||||
* @create 2026.09.01
|
|
||||||
* <pre>
|
|
||||||
* ---------- 개정?<3F>력 ----------
|
|
||||||
* ?<3F>정?? ?<3F>정?? ?<3F>정?<3F>용
|
|
||||||
* ---------- -------- ---------------------------
|
|
||||||
* 2026.09.01 0986406 최초?<3F>성
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
|
||||||
import io.shinhanlife.dap.mcg.audit.AuditLogService;
|
|
||||||
import io.shinhanlife.dap.mcg.trace.InMemoryToolTraceService;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
@ActiveProfiles("test")
|
|
||||||
class DapGatewayApplicationTests {
|
|
||||||
|
|
||||||
// Mock components that might require external dependencies (like Redis/DB) to pass the context load
|
|
||||||
@MockitoBean
|
|
||||||
private InMemoryToolTraceService InMemoryToolTraceService;
|
|
||||||
|
|
||||||
@MockitoBean
|
|
||||||
private AuditLogService auditLogService;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void contextLoads() {
|
|
||||||
// This test ensures that all Spring beans, @ConfigurationProperties, and dependencies are correctly wired.
|
|
||||||
// It validates that the ported classes (ExecuteService, AgentResponseBudgetService, LargeToolResponseService, etc.)
|
|
||||||
// have no @Autowired or initialization errors.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +1,11 @@
|
|||||||
|
package io.shinhanlife.dap.mcg;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class DapGatewayApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void gatewayTestSourceIsCompilable() {
|
||||||
|
// Spring context integration tests are covered by feature-specific tests.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class DocumentGeneratorServiceTest {
|
|||||||
.name("oth.cmm.customer.detail")
|
.name("oth.cmm.customer.detail")
|
||||||
.description("테스트 고객의 상세정보를 조회합니다.")
|
.description("테스트 고객의 상세정보를 조회합니다.")
|
||||||
.categoryKey("cmm")
|
.categoryKey("cmm")
|
||||||
.endpoint("http://was-oth:8084/mcp/oth.cmm.customer.detail")
|
.endpoint("http://was-cus:8084/mcp/oth.cmm.customer.detail")
|
||||||
.podUrl("http://was-oth:8084")
|
.podUrl("http://was-cus:8084")
|
||||||
.integrationType("REST")
|
.integrationType("REST")
|
||||||
.mciServiceId("CUST_001")
|
.mciServiceId("CUST_001")
|
||||||
.operationType(OperationType.READ)
|
.operationType(OperationType.READ)
|
||||||
@@ -108,7 +108,7 @@ class DocumentGeneratorServiceTest {
|
|||||||
assertThat(workbook.getSheet("Request In").getRow(1).getCell(2).getStringCellValue())
|
assertThat(workbook.getSheet("Request In").getRow(1).getCell(2).getStringCellValue())
|
||||||
.isEqualTo("테스트 고객조회 Tool");
|
.isEqualTo("테스트 고객조회 Tool");
|
||||||
assertThat(workbook.getSheet("Request In").getRow(3).getCell(3).getStringCellValue())
|
assertThat(workbook.getSheet("Request In").getRow(3).getCell(3).getStringCellValue())
|
||||||
.isEqualTo("http://was-oth:8084/mcp/oth.cmm.customer.detail");
|
.isEqualTo("http://was-cus:8084/mcp/oth.cmm.customer.detail");
|
||||||
assertThat(workbook.getSheet("Request In").getRow(8).getCell(5).getStringCellValue())
|
assertThat(workbook.getSheet("Request In").getRow(8).getCell(5).getStringCellValue())
|
||||||
.isEqualTo("customerId");
|
.isEqualTo("customerId");
|
||||||
assertThat(workbook.getSheet("Request In").getRow(8).getCell(7).getStringCellValue())
|
assertThat(workbook.getSheet("Request In").getRow(8).getCell(7).getStringCellValue())
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.HashMap;
|
|
||||||
import io.shinhanlife.dap.lib.dto.OperationType;
|
import io.shinhanlife.dap.lib.dto.OperationType;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
/**
|
/**
|
||||||
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
||||||
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
||||||
@@ -57,7 +55,6 @@ public class ToolMetadata {
|
|||||||
private List<String> requiredEnvKeys;
|
private List<String> requiredEnvKeys;
|
||||||
|
|
||||||
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
|
// 2. 파라미터 스키마 (JSON Schema 형태의 Map)
|
||||||
@JsonProperty("inputSchema")
|
|
||||||
private Map<String, Object> parametersSchema;
|
private Map<String, Object> parametersSchema;
|
||||||
private Map<String, Object> outputSchema;
|
private Map<String, Object> outputSchema;
|
||||||
|
|
||||||
@@ -146,31 +143,4 @@ public class ToolMetadata {
|
|||||||
if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of();
|
if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of();
|
||||||
return new HashSet<>((List<String>) parametersSchema.get("required"));
|
return new HashSet<>((List<String>) parametersSchema.get("required"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty("_meta")
|
|
||||||
public Map<String, Object> get_meta() {
|
|
||||||
Map<String, Object> meta = new HashMap<>();
|
|
||||||
meta.put("uid", this.uid);
|
|
||||||
meta.put("semver", this.semver);
|
|
||||||
meta.put("moduleName", this.moduleName);
|
|
||||||
meta.put("categoryKey", this.categoryKey);
|
|
||||||
meta.put("endpoint", this.endpoint);
|
|
||||||
meta.put("podUrl", this.podUrl);
|
|
||||||
meta.put("visible", this.visible);
|
|
||||||
meta.put("enabled", this.enabled);
|
|
||||||
meta.put("isRegistered", this.isRegistered);
|
|
||||||
meta.put("requiresApproval", this.requiresApproval);
|
|
||||||
meta.put("readOnlyHint", this.readOnlyHint);
|
|
||||||
meta.put("destructiveHint", this.destructiveHint);
|
|
||||||
meta.put("idempotentHint", this.idempotentHint);
|
|
||||||
meta.put("openWorldHint", this.openWorldHint);
|
|
||||||
meta.put("integrationType", this.integrationType);
|
|
||||||
meta.put("mciServiceId", this.mciServiceId);
|
|
||||||
meta.put("operationType", this.operationType);
|
|
||||||
meta.put("retryEnabled", this.retryEnabled);
|
|
||||||
meta.put("circuitBreakerFailureThreshold", this.circuitBreakerFailureThreshold);
|
|
||||||
meta.put("circuitBreakerOpenMillis", this.circuitBreakerOpenMillis);
|
|
||||||
meta.put("timeoutMillis", this.timeoutMillis);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ class ToolScaffolderTest {
|
|||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
void usesWasModuleNameAsToolPodPrefix() throws Exception {
|
void usesWasModuleNameAsToolPodPrefix() throws Exception {
|
||||||
String moduleName = "build/dap-was-sms";
|
String moduleName = "build/dap-was-sal";
|
||||||
|
|
||||||
ToolScaffolder.scaffold("notification send", "SMS0001", "SMS 발송", "cmm", "HTTP", moduleName,
|
ToolScaffolder.scaffold("notification send", "SMS0001", "SMS 발송", "cmm", "HTTP", moduleName,
|
||||||
"tester", "2026.08.05", true, null);
|
"tester", "2026.08.05", true, null);
|
||||||
@@ -289,15 +289,15 @@ class ToolScaffolderTest {
|
|||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
void generatesMockResponseAndUnitTestSkeletonFromOutputFields() throws Exception {
|
void generatesMockResponseAndUnitTestSkeletonFromOutputFields() throws Exception {
|
||||||
String moduleName = root.resolve("dap-was-oth").toString();
|
String moduleName = root.resolve("dap-was-cus").toString();
|
||||||
List<ToolScaffolder.FieldDefinition> outputFields = List.of(
|
List<ToolScaffolder.FieldDefinition> outputFields = List.of(
|
||||||
new ToolScaffolder.FieldDefinition("status", "String", "Claim status", "RECEIVED", true));
|
new ToolScaffolder.FieldDefinition("status", "String", "Claim status", "RECEIVED", true));
|
||||||
|
|
||||||
ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName,
|
ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName,
|
||||||
"tester", "2026.08.10", true, null, null, null, List.of(), outputFields);
|
"tester", "2026.08.10", true, null, null, null, List.of(), outputFields);
|
||||||
|
|
||||||
Path mockResponse = root.resolve("dap-was-oth/src/main/resources/mock-responses/cmm_claim_search.json");
|
Path mockResponse = root.resolve("dap-was-cus/src/main/resources/mock-responses/cmm_claim_search.json");
|
||||||
Path useCaseTest = root.resolve("dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java");
|
Path useCaseTest = root.resolve("dap-was-cus/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java");
|
||||||
|
|
||||||
assertTrue(Files.exists(mockResponse));
|
assertTrue(Files.exists(mockResponse));
|
||||||
assertTrue(Files.readString(mockResponse).contains("\"status\" : \"RECEIVED\""));
|
assertTrue(Files.readString(mockResponse).contains("\"status\" : \"RECEIVED\""));
|
||||||
@@ -359,7 +359,7 @@ class ToolScaffolderTest {
|
|||||||
assertTrue(Files.exists(wireMockResponse), wireMockResponse.toString());
|
assertTrue(Files.exists(wireMockResponse), wireMockResponse.toString());
|
||||||
assertTrue(Files.exists(wireMockMapping), wireMockMapping.toString());
|
assertTrue(Files.exists(wireMockMapping), wireMockMapping.toString());
|
||||||
assertTrue(Files.readString(wireMockMapping).contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\""));
|
assertTrue(Files.readString(wireMockMapping).contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\""));
|
||||||
Path localConfig = root.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml");
|
Path localConfig = root.resolve("src/main/resources/glow/application-glow-local.yml");
|
||||||
assertTrue(Files.exists(localConfig), localConfig.toString());
|
assertTrue(Files.exists(localConfig), localConfig.toString());
|
||||||
assertTrue(Files.readString(localConfig).contains("name: employee-search"));
|
assertTrue(Files.readString(localConfig).contains("name: employee-search"));
|
||||||
assertTrue(Files.readString(localConfig).contains("url: ${AXHUB_EMPLOYEE_SEARCH_HTTP_URL:/api/mock/http/smp_employee_search}"));
|
assertTrue(Files.readString(localConfig).contains("url: ${AXHUB_EMPLOYEE_SEARCH_HTTP_URL:/api/mock/http/smp_employee_search}"));
|
||||||
@@ -408,7 +408,7 @@ class ToolScaffolderTest {
|
|||||||
@Test
|
@Test
|
||||||
void addsHttpApiEntryOnItsOwnYamlLineBeforeMciConfiguration() throws Exception {
|
void addsHttpApiEntryOnItsOwnYamlLineBeforeMciConfiguration() throws Exception {
|
||||||
String moduleName = root.resolve("dap-was-http-yaml").toString();
|
String moduleName = root.resolve("dap-was-http-yaml").toString();
|
||||||
Path localConfig = root.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml");
|
Path localConfig = root.resolve("src/main/resources/glow/application-glow-local.yml");
|
||||||
Files.createDirectories(localConfig.getParent());
|
Files.createDirectories(localConfig.getParent());
|
||||||
Files.writeString(localConfig, """
|
Files.writeString(localConfig, """
|
||||||
glow:
|
glow:
|
||||||
@@ -426,8 +426,8 @@ class ToolScaffolderTest {
|
|||||||
|
|
||||||
String yaml = Files.readString(localConfig);
|
String yaml = Files.readString(localConfig);
|
||||||
assertFalse(yaml.contains("biz-pod: false - name"), yaml);
|
assertFalse(yaml.contains("biz-pod: false - name"), yaml);
|
||||||
assertTrue(yaml.contains(" biz-pod: false\n mci:"), yaml);
|
|
||||||
assertTrue(yaml.contains(" - name: insurance"), yaml);
|
assertTrue(yaml.contains(" - name: insurance"), yaml);
|
||||||
|
assertTrue(yaml.indexOf(" - name: insurance") < yaml.indexOf(" mci:"), yaml);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ToolSourceUpdaterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updatesSdkAndProjectOwnedMetadataOnTheSameToolMethod() throws Exception {
|
void updatesSdkAndProjectOwnedMetadataOnTheSameToolMethod() throws Exception {
|
||||||
Path source = temporaryRoot.resolve("dap-was-oth/src/main/java/example/SampleUseCase.java");
|
Path source = temporaryRoot.resolve("dap-was-cus/src/main/java/example/SampleUseCase.java");
|
||||||
Files.createDirectories(source.getParent());
|
Files.createDirectories(source.getParent());
|
||||||
Files.writeString(source, """
|
Files.writeString(source, """
|
||||||
package example;
|
package example;
|
||||||
@@ -31,7 +31,8 @@ class ToolSourceUpdaterTest {
|
|||||||
|
|
||||||
String updated = Files.readString(source);
|
String updated = Files.readString(source);
|
||||||
assertTrue(updated.contains("@McpTool(name = \"cmm_sample_search\", description = \"new\")"));
|
assertTrue(updated.contains("@McpTool(name = \"cmm_sample_search\", description = \"new\")"));
|
||||||
assertTrue(updated.contains("@GrowToolHint(register = true, requiresApproval = true"));
|
assertTrue(updated.contains("register = true"), updated);
|
||||||
|
assertTrue(updated.contains("requiresApproval = true"), updated);
|
||||||
assertTrue(updated.contains("categoryKey = \"customer\""), updated);
|
assertTrue(updated.contains("categoryKey = \"customer\""), updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# 신한라이프 내부망 Tool Pod 이관 준비 체크리스트
|
# 신한라이프 내부망 Tool Pod 이관 준비 체크리스트
|
||||||
|
|
||||||
> 범위: `dap-was-lib`, `dap-was-oth`, `dap-was-sms` Tool Pod 이관
|
> 범위: `dap-was-lib`, `dap-was-cus`, `dap-was-sal`, `dap-was-pro`, `dap-was-sys` Tool Pod 이관
|
||||||
>
|
>
|
||||||
|
|
||||||
## 1. 소스 및 형상관리
|
## 1. 소스 및 형상관리
|
||||||
|
|
||||||
- [ ] 내부 Git 저장소 생성
|
- [ ] 내부 Git 저장소 생성
|
||||||
- [ ] 대상 모듈 이관: `dap-was-lib`, `dap-was-oth`, `dap-was-sms`
|
- [ ] 대상 모듈 이관: `dap-was-lib`, `dap-was-cus`, `dap-was-sal`, `dap-was-pro`, `dap-was-sys`
|
||||||
- [ ] `main` 브랜치 및 필요한 커밋 이력 이관
|
- [ ] `main` 브랜치 및 필요한 커밋 이력 이관
|
||||||
- [ ] API Key, 비밀번호, 인증서, 개인 설정 파일은 Git에서 제외
|
- [ ] API Key, 비밀번호, 인증서, 개인 설정 파일은 Git에서 제외
|
||||||
- [ ] 내부 Git URL 기준으로 README와 CI/CD 설정 변경
|
- [ ] 내부 Git URL 기준으로 README와 CI/CD 설정 변경
|
||||||
@@ -68,8 +68,10 @@ MCI 담당자에게 아래 정보를 요청합니다.
|
|||||||
|
|
||||||
| Tool Pod | 내부 Endpoint |
|
| Tool Pod | 내부 Endpoint |
|
||||||
|---|---|
|
|---|---|
|
||||||
| OTH Tool Pod | `http://tool-oth:8084/mcp` |
|
| CUS Tool Pod | `http://was-cus:8084/mcp` |
|
||||||
| SMS Tool Pod | `http://tool-sms:8082/mcp` |
|
| SAL Tool Pod | `http://was-sal:8082/mcp` |
|
||||||
|
| PRO Tool Pod | `http://was-pro:8085/mcp` |
|
||||||
|
| SYS Tool Pod | `http://was-sys:8086/mcp` |
|
||||||
|
|
||||||
Portal 담당자에게 아래 정보를 전달합니다.
|
Portal 담당자에게 아래 정보를 전달합니다.
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ Portal 담당자에게 아래 정보를 전달합니다.
|
|||||||
## 8. 이관 후 검증
|
## 8. 이관 후 검증
|
||||||
|
|
||||||
- [ ] 내부 Nexus만으로 `./gradlew clean build` 성공
|
- [ ] 내부 Nexus만으로 `./gradlew clean build` 성공
|
||||||
- [ ] OTH, SMS Tool Pod 기동 성공
|
- [ ] CUS, SAL, PRO, SYS Tool Pod 기동 성공
|
||||||
- [ ] 각 Tool Pod의 MCP Endpoint 연결 성공
|
- [ ] 각 Tool Pod의 MCP Endpoint 연결 성공
|
||||||
- [ ] Tool Pod → MCI 호출 성공
|
- [ ] Tool Pod → MCI 호출 성공
|
||||||
- [ ] `trace-id`, `request-id` 전달 확인
|
- [ ] `trace-id`, `request-id` 전달 확인
|
||||||
|
|||||||
Reference in New Issue
Block a user