refs #702: align HMM MCP contract and settings
This commit is contained in:
@@ -8,6 +8,7 @@ public record BackofficeProperties(
|
||||
Security security,
|
||||
Token token,
|
||||
Ords ords,
|
||||
Mcp mcp,
|
||||
Ai ai
|
||||
) {
|
||||
|
||||
@@ -54,6 +55,13 @@ public record BackofficeProperties(
|
||||
public record Ords(String baseUrl, Duration timeout, Duration agentTimeout) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public HMM MCP endpoint used by Agent Factory and by operators who need to inspect the
|
||||
* deployed tool contract. It is deliberately separate from the optional legacy ORDS URL.
|
||||
*/
|
||||
public record Mcp(String publicUrl) {
|
||||
}
|
||||
|
||||
public record Ai(
|
||||
boolean enabled,
|
||||
String provider,
|
||||
|
||||
@@ -10,4 +10,6 @@ public interface SettingMapper {
|
||||
BackofficeSetting findByKey(@Param("settingKey") String settingKey);
|
||||
|
||||
void upsert(@Param("settingKey") String settingKey, @Param("settingValue") String settingValue);
|
||||
|
||||
void deleteByKey(@Param("settingKey") String settingKey);
|
||||
}
|
||||
|
||||
@@ -449,6 +449,12 @@ public class BackofficeSchemaService {
|
||||
}
|
||||
|
||||
private void seedDefaultSettings(List<SchemaActionResult> results) {
|
||||
if (properties.ords().baseUrl() == null || properties.ords().baseUrl().isBlank()) {
|
||||
results.add(new SchemaActionResult("ORDS_BASE_URL", "SETTING", "SKIPPED",
|
||||
"HMM 기본 구성은 ORDS를 사용하지 않습니다. 레거시 ORDS 연동이 필요한 경우에만 설정하세요.",
|
||||
SETTINGS_MERGE_SQL));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
jdbcTemplate.update(SETTINGS_MERGE_SQL, properties.ords().baseUrl());
|
||||
results.add(new SchemaActionResult("ORDS_BASE_URL", "SETTING", "MERGED",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/** Executes a pre-approved HMM DBMS_CLOUD_AI_AGENT tool. */
|
||||
@FunctionalInterface
|
||||
public interface HmmAiAgentToolRunner {
|
||||
|
||||
JsonNode run(String toolName, ObjectNode input);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.setting.BackofficeSetting;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.SettingMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** Removes the retired KB demo ORDS address without touching an operator-supplied ORDS endpoint. */
|
||||
@Component
|
||||
@Order(1)
|
||||
public class HmmRuntimeSettingsInitializer implements ApplicationRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HmmRuntimeSettingsInitializer.class);
|
||||
private static final String RETIRED_KB_ORDS_URL =
|
||||
"https://g329127dfd380ad-kbaipoc.adb.ap-osaka-1.oraclecloudapps.com/ords";
|
||||
|
||||
private final SettingMapper settingMapper;
|
||||
|
||||
public HmmRuntimeSettingsInitializer(SettingMapper settingMapper) {
|
||||
this.settingMapper = settingMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
try {
|
||||
BackofficeSetting setting = settingMapper.findByKey(SettingService.ORDS_BASE_URL);
|
||||
if (setting != null && RETIRED_KB_ORDS_URL.equals(normalize(setting.settingValue()))) {
|
||||
settingMapper.deleteByKey(SettingService.ORDS_BASE_URL);
|
||||
log.info("Removed retired KB demo ORDS setting; HMM MCP is the active integration");
|
||||
}
|
||||
} catch (DataAccessException exception) {
|
||||
// Do not prevent the backoffice from starting while its support schema is being prepared.
|
||||
log.warn("Skipped HMM runtime setting migration: {}", exception.getMostSpecificCause().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String normalize(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
String normalized = value.trim();
|
||||
while (normalized.endsWith("/")) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import java.sql.Clob;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** JDBC implementation shared with the standalone HMM MCP facade. */
|
||||
@Service
|
||||
public class JdbcHmmAiAgentToolRunner implements HmmAiAgentToolRunner {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JdbcHmmAiAgentToolRunner(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode run(String toolName, com.fasterxml.jackson.databind.node.ObjectNode input) {
|
||||
try {
|
||||
String request = objectMapper.writeValueAsString(input);
|
||||
String response = jdbcTemplate.queryForObject("""
|
||||
SELECT DBMS_CLOUD_AI_AGENT.RUN_TOOL(?, TO_CLOB(?))
|
||||
FROM dual
|
||||
""", (resultSet, rowNum) -> {
|
||||
Clob clob = resultSet.getClob(1);
|
||||
return clob == null ? "" : clob.getSubString(1, (int) clob.length());
|
||||
}, toolName, request);
|
||||
if (response == null || response.isBlank()) {
|
||||
return objectMapper.createObjectNode();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readTree(response);
|
||||
} catch (Exception ignored) {
|
||||
return TextNode.valueOf(response);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new AppException("HMM AI Agent Tool 실행에 실패했습니다: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,30 +8,39 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** MCP boundary exposing only the row-access-aware GPT-5.4-mini Select AI query tool. */
|
||||
/** Exposes the same read-only HMM HR tool contract as hmm-mcp.cloud-handson.com. */
|
||||
@Service
|
||||
public class McpSseService {
|
||||
|
||||
private static final String SELECT_AI_VPD_QUERY_TOOL = "ords.query.kb_select_ai_vpd";
|
||||
private static final String SELECT_AI_VPD_QUERY_PATH = "cb-ords/kb-select-ai-vpd/query";
|
||||
private static final String SELECT_AI_VPD_QUERY_PROFILE =
|
||||
"KB_AIDP_SELECTAI_GPT54_MINI_FULLMETA_PROFILE_V1";
|
||||
private static final McpToolView SELECT_AI_VPD_QUERY_VIEW = new McpToolView(
|
||||
SELECT_AI_VPD_QUERY_TOOL,
|
||||
"GPT-5.4-mini Select AI 자연어 질의를 행 접근 컨텍스트로 실행합니다. 테이블/컬럼 comment, annotation, constraint 메타데이터를 사용하고 생성 SQL은 KB 업무 테이블의 읽기 전용 SELECT/WITH만 허용합니다.",
|
||||
-1L,
|
||||
"KB Select AI 행 접근 자연어 조회",
|
||||
SELECT_AI_VPD_QUERY_PATH
|
||||
private static final List<ToolSpec> HMM_TOOLS = List.of(
|
||||
new ToolSpec(
|
||||
"resolve_hr_term",
|
||||
"HMM_HR_TERM_RESOLVER",
|
||||
"term",
|
||||
"P_TERM",
|
||||
"휴가·근태 표현을 HMM 표준 용어와 코드로 변환합니다. 모호한 표현은 데이터 조회 전에 이 도구를 사용합니다.",
|
||||
"HMM HR 용어 표준화"),
|
||||
new ToolSpec(
|
||||
"search_hr_data",
|
||||
"HMM_HR_NORMALIZED_DATA_SEARCH",
|
||||
"query",
|
||||
"P_QUERY",
|
||||
"조직, 직원, 휴가 잔여·신청, 근태 데이터를 읽기 전용 Select AI로 조회합니다.",
|
||||
"HMM HR 데이터 조회"),
|
||||
new ToolSpec(
|
||||
"search_hr_policy",
|
||||
"HMM_HR_POLICY_SEARCH",
|
||||
"query",
|
||||
"P_QUERY",
|
||||
"HR 규정 PDF의 문서 메타데이터, Abstract, 관련 청크를 계층형 벡터 검색으로 조회합니다.",
|
||||
"HMM HR 규정 검색")
|
||||
);
|
||||
|
||||
private final SelectAiAgentOrdsService selectAiAgentOrdsService;
|
||||
private final HmmAiAgentToolRunner agentToolRunner;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public McpSseService(
|
||||
SelectAiAgentOrdsService selectAiAgentOrdsService,
|
||||
ObjectMapper objectMapper
|
||||
) {
|
||||
this.selectAiAgentOrdsService = selectAiAgentOrdsService;
|
||||
public McpSseService(HmmAiAgentToolRunner agentToolRunner, ObjectMapper objectMapper) {
|
||||
this.agentToolRunner = agentToolRunner;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -40,9 +49,10 @@ public class McpSseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTTP bearer token is the business-user subject token; no separate MCP token is used.
|
||||
* Backoffice authentication protects this compatibility endpoint. The public
|
||||
* HMM MCP endpoint performs its own fixed Bearer-token validation in Nginx.
|
||||
*/
|
||||
public ObjectNode handle(String contextPath, JsonNode request, String vpdBearerToken) {
|
||||
public ObjectNode handle(String contextPath, JsonNode request, String ignoredAuthorization) {
|
||||
ObjectNode response = objectMapper.createObjectNode();
|
||||
response.put("jsonrpc", "2.0");
|
||||
if (request != null && request.has("id")) {
|
||||
@@ -56,30 +66,32 @@ public class McpSseService {
|
||||
case "initialize" -> initializeResult(contextPath);
|
||||
case "notifications/initialized" -> objectMapper.createObjectNode();
|
||||
case "tools/list" -> toolsListResult();
|
||||
case "tools/call" -> toolsCallResult(parameters, vpdBearerToken);
|
||||
case "tools/call" -> toolsCallResult(parameters);
|
||||
default -> throw new AppException("지원하지 않는 MCP method입니다: " + method);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
} catch (Exception exception) {
|
||||
response.remove("result");
|
||||
ObjectNode error = objectMapper.createObjectNode();
|
||||
error.put("code", -32000);
|
||||
error.put("message", e.getMessage());
|
||||
error.put("message", exception.getMessage());
|
||||
response.set("error", error);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/** The only tool registered by this MCP server. */
|
||||
public List<McpToolView> registeredTools() {
|
||||
return List.of(SELECT_AI_VPD_QUERY_VIEW);
|
||||
return HMM_TOOLS.stream()
|
||||
.map(tool -> new McpToolView(
|
||||
tool.name(), tool.description(), -1L, tool.displayName(), tool.agentToolName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private ObjectNode initializeResult(String contextPath) {
|
||||
ObjectNode result = objectMapper.createObjectNode();
|
||||
result.put("protocolVersion", "2024-11-05");
|
||||
ObjectNode serverInfo = objectMapper.createObjectNode();
|
||||
serverInfo.put("name", "vpd-ords-backoffice-" + contextPath);
|
||||
serverInfo.put("version", "0.1.0");
|
||||
serverInfo.put("name", "hmm-hr-backoffice-" + contextPath);
|
||||
serverInfo.put("version", "1.0.0");
|
||||
result.set("serverInfo", serverInfo);
|
||||
ObjectNode capabilities = objectMapper.createObjectNode();
|
||||
capabilities.set("tools", objectMapper.createObjectNode());
|
||||
@@ -90,70 +102,50 @@ public class McpSseService {
|
||||
private ObjectNode toolsListResult() {
|
||||
ObjectNode result = objectMapper.createObjectNode();
|
||||
ArrayNode tools = objectMapper.createArrayNode();
|
||||
tools.add(selectAiVpdQueryTool());
|
||||
HMM_TOOLS.forEach(tool -> tools.add(toolDefinition(tool)));
|
||||
result.set("tools", tools);
|
||||
return result;
|
||||
}
|
||||
|
||||
private ObjectNode selectAiVpdQueryTool() {
|
||||
private ObjectNode toolDefinition(ToolSpec tool) {
|
||||
ObjectNode item = objectMapper.createObjectNode();
|
||||
item.put("name", SELECT_AI_VPD_QUERY_TOOL);
|
||||
item.put("description", SELECT_AI_VPD_QUERY_VIEW.description());
|
||||
|
||||
item.put("name", tool.name());
|
||||
item.put("description", tool.description());
|
||||
ObjectNode schema = objectMapper.createObjectNode();
|
||||
schema.put("type", "object");
|
||||
ObjectNode properties = objectMapper.createObjectNode();
|
||||
|
||||
ObjectNode prompt = objectMapper.createObjectNode();
|
||||
prompt.put("type", "string");
|
||||
prompt.put("description", "KB 업무 원장에 대해 조회할 내용을 자연어로 입력합니다.");
|
||||
prompt.put("maxLength", 4000);
|
||||
properties.set("prompt", prompt);
|
||||
|
||||
ObjectNode limit = objectMapper.createObjectNode();
|
||||
limit.put("type", "integer");
|
||||
limit.put("description", "최대 반환 행 수. 1부터 100까지 허용하며 기본값은 50입니다.");
|
||||
limit.put("minimum", 1);
|
||||
limit.put("maximum", 100);
|
||||
properties.set("limit", limit);
|
||||
|
||||
ObjectNode argument = objectMapper.createObjectNode();
|
||||
argument.put("type", "string");
|
||||
argument.put("description", tool.argumentDescription());
|
||||
argument.put("maxLength", 4000);
|
||||
properties.set(tool.argumentName(), argument);
|
||||
schema.set("properties", properties);
|
||||
ArrayNode required = objectMapper.createArrayNode();
|
||||
required.add("prompt");
|
||||
required.add(tool.argumentName());
|
||||
schema.set("required", required);
|
||||
schema.put("additionalProperties", false);
|
||||
item.set("inputSchema", schema);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ObjectNode toolsCallResult(JsonNode params, String vpdBearerToken) {
|
||||
String toolName = params.path("name").asText("");
|
||||
if (!SELECT_AI_VPD_QUERY_TOOL.equals(toolName)) {
|
||||
throw new AppException("등록되지 않은 MCP tool입니다: " + toolName);
|
||||
}
|
||||
|
||||
JsonNode arguments = params.path("arguments");
|
||||
String token = vpdBearerToken == null ? "" : vpdBearerToken.trim();
|
||||
if (token.isBlank()) {
|
||||
return tokenAccessDeniedResult();
|
||||
}
|
||||
JsonNode response;
|
||||
try {
|
||||
response = selectAiAgentOrdsService.run(
|
||||
token,
|
||||
arguments.path("prompt").asText(""),
|
||||
normalizeLimit(arguments.path("limit").asInt(50))
|
||||
);
|
||||
} catch (VpdTokenAccessDeniedException ignored) {
|
||||
return tokenAccessDeniedResult();
|
||||
private ObjectNode toolsCallResult(JsonNode params) {
|
||||
String requestedName = params.path("name").asText("");
|
||||
ToolSpec tool = HMM_TOOLS.stream()
|
||||
.filter(candidate -> candidate.name().equals(requestedName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AppException("등록되지 않은 HMM MCP tool입니다: " + requestedName));
|
||||
String argument = params.path("arguments").path(tool.argumentName()).asText("").trim();
|
||||
if (argument.isBlank()) {
|
||||
throw new AppException(tool.argumentName() + " 입력값은 비워둘 수 없습니다.");
|
||||
}
|
||||
ObjectNode input = objectMapper.createObjectNode();
|
||||
input.put(tool.agentParameterName(), argument);
|
||||
JsonNode toolResponse = agentToolRunner.run(tool.agentToolName(), input);
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("toolName", SELECT_AI_VPD_QUERY_TOOL);
|
||||
payload.put("profile", SELECT_AI_VPD_QUERY_PROFILE);
|
||||
payload.put("ordsPath", SELECT_AI_VPD_QUERY_PATH);
|
||||
payload.set("response", response);
|
||||
|
||||
payload.put("toolName", tool.name());
|
||||
payload.put("agentTool", tool.agentToolName());
|
||||
payload.set("response", toolResponse);
|
||||
ObjectNode result = objectMapper.createObjectNode();
|
||||
ArrayNode content = objectMapper.createArrayNode();
|
||||
ObjectNode text = objectMapper.createObjectNode();
|
||||
@@ -165,34 +157,26 @@ public class McpSseService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ObjectNode tokenAccessDeniedResult() {
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("status", "VPD_TOKEN_DENIED");
|
||||
payload.put("message", "토큰이 없거나 유효하지 않아 이 요청을 수행할 권한이 없습니다.");
|
||||
|
||||
ObjectNode result = objectMapper.createObjectNode();
|
||||
ArrayNode content = objectMapper.createArrayNode();
|
||||
ObjectNode text = objectMapper.createObjectNode();
|
||||
text.put("type", "text");
|
||||
text.put("text", pretty(payload));
|
||||
content.add(text);
|
||||
result.set("content", content);
|
||||
result.put("isError", true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int normalizeLimit(int limit) {
|
||||
if (limit < 1) {
|
||||
return 50;
|
||||
}
|
||||
return Math.min(limit, 100);
|
||||
}
|
||||
|
||||
private String pretty(Object value) {
|
||||
try {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception exception) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
private record ToolSpec(
|
||||
String name,
|
||||
String agentToolName,
|
||||
String argumentName,
|
||||
String agentParameterName,
|
||||
String description,
|
||||
String displayName
|
||||
) {
|
||||
String argumentDescription() {
|
||||
return "term".equals(argumentName)
|
||||
? "확인할 휴가·근태 용어, 동의어 또는 코드입니다."
|
||||
: "조직, 직원, 휴가, 근태 또는 규정에 대한 완전한 자연어 질문입니다.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ public class SettingService {
|
||||
@Transactional
|
||||
public void updateOrdsBaseUrl(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new AppException("ORDS Base URL은 필수입니다.");
|
||||
settingMapper.deleteByKey(ORDS_BASE_URL);
|
||||
return;
|
||||
}
|
||||
String normalized = normalizeUrl(value);
|
||||
if (!normalized.startsWith("https://") && !normalized.startsWith("http://")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
|
||||
import com.cloudhandson.vpdbackoffice.domain.mcp.McpReasoningCommand;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.UserMapper;
|
||||
import com.cloudhandson.vpdbackoffice.service.BearerTokenService;
|
||||
@@ -23,19 +24,22 @@ public class McpReasoningController {
|
||||
private final McpReasoningService reasoningService;
|
||||
private final BearerTokenService tokenService;
|
||||
private final UserMapper userMapper;
|
||||
private final BackofficeProperties properties;
|
||||
|
||||
public McpReasoningController(
|
||||
McpToolRegistry toolRegistry,
|
||||
McpSseService mcpSseService,
|
||||
McpReasoningService reasoningService,
|
||||
BearerTokenService tokenService,
|
||||
UserMapper userMapper
|
||||
UserMapper userMapper,
|
||||
BackofficeProperties properties
|
||||
) {
|
||||
this.toolRegistry = toolRegistry;
|
||||
this.mcpSseService = mcpSseService;
|
||||
this.reasoningService = reasoningService;
|
||||
this.tokenService = tokenService;
|
||||
this.userMapper = userMapper;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@GetMapping("/mcp-reasoning")
|
||||
@@ -61,6 +65,7 @@ public class McpReasoningController {
|
||||
@GetMapping("/mcp-sse")
|
||||
public String ssePage(Model model) {
|
||||
model.addAttribute("tools", mcpSseService.registeredTools());
|
||||
model.addAttribute("hmmMcpPublicUrl", properties.mcp().publicUrl());
|
||||
return "mcp-sse";
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.service.BackofficeSchemaService;
|
||||
import com.cloudhandson.vpdbackoffice.service.SettingService;
|
||||
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -15,14 +16,21 @@ public class SettingController {
|
||||
|
||||
private final SettingService settingService;
|
||||
private final BackofficeSchemaService backofficeSchemaService;
|
||||
private final BackofficeProperties properties;
|
||||
|
||||
public SettingController(SettingService settingService, BackofficeSchemaService backofficeSchemaService) {
|
||||
public SettingController(
|
||||
SettingService settingService,
|
||||
BackofficeSchemaService backofficeSchemaService,
|
||||
BackofficeProperties properties
|
||||
) {
|
||||
this.settingService = settingService;
|
||||
this.backofficeSchemaService = backofficeSchemaService;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String settings(Model model) {
|
||||
model.addAttribute("hmmMcpPublicUrl", properties.mcp().publicUrl());
|
||||
try {
|
||||
model.addAttribute("ordsBaseUrl", settingService.ordsBaseUrl());
|
||||
} catch (DataAccessException exception) {
|
||||
@@ -52,7 +60,9 @@ public class SettingController {
|
||||
RedirectAttributes redirectAttributes
|
||||
) {
|
||||
settingService.updateOrdsBaseUrl(ordsBaseUrl);
|
||||
redirectAttributes.addFlashAttribute("message", "ORDS 설정을 저장했습니다.");
|
||||
redirectAttributes.addFlashAttribute("message", ordsBaseUrl == null || ordsBaseUrl.isBlank()
|
||||
? "레거시 ORDS 설정을 해제했습니다. HMM MCP 및 HR 질의에는 영향이 없습니다."
|
||||
: "레거시 ORDS 설정을 저장했습니다.");
|
||||
return "redirect:/settings";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user