From f16d3e5798ae38fd5a8941fe1359f3aff4514b2e Mon Sep 17 00:00:00 2001 From: devmrko Date: Mon, 27 Jul 2026 14:23:17 +0900 Subject: [PATCH] add game query plan MCP tool --- .../vpdbackoffice/service/McpSseService.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java index 9d2f6ee..bad0b42 100644 --- a/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java +++ b/vpd-backoffice/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java @@ -16,6 +16,7 @@ public class McpSseService { private static final String SELECT_AI_TOOL_PATH = "/mcp (tools/call)"; private static final String GAME_CATALOG_TOOL = "oracle.select_ai.game_catalog_resolve"; + private static final String GAME_QUERY_PLAN_TOOL = "oracle.select_ai.game_query_plan"; private final SelectAiService selectAiService; private final ObjectMapper objectMapper; @@ -81,7 +82,7 @@ public class McpSseService { public List registeredTools() { return List.of( selectAiQueryView(), selectAiShowpromptView(), qaVectorSearchView(), - qaVectorStoreView(), fewShotNl2SqlView(), gameScopeView(), gameCatalogView()); + qaVectorStoreView(), fewShotNl2SqlView(), gameScopeView(), gameCatalogView(), gameQueryPlanView()); } private ObjectNode initializeResult(String contextPath) { @@ -107,6 +108,7 @@ public class McpSseService { tools.add(toolDefinition(fewShotNl2SqlView())); tools.add(toolDefinition(gameScopeView())); tools.add(toolDefinition(gameCatalogView())); + tools.add(toolDefinition(gameQueryPlanView())); result.set("tools", tools); return result; } @@ -122,7 +124,7 @@ public class McpSseService { ArrayNode required = objectMapper.createArrayNode(); if (qaVectorSearchToolName().equals(toolView.name()) || gameScopeToolName().equals(toolView.name()) - || GAME_CATALOG_TOOL.equals(toolView.name())) { + || GAME_CATALOG_TOOL.equals(toolView.name()) || GAME_QUERY_PLAN_TOOL.equals(toolView.name())) { addStringProperty(properties, "question", "few-shot 예제 SQL을 찾을 현재 질문입니다.", 4000); ObjectNode topK = properties.putObject("topK"); topK.put("type", "integer"); @@ -168,8 +170,9 @@ public class McpSseService { boolean fewShotNl2SqlTool = fewShotNl2SqlToolName().equals(calledToolName); boolean gameScopeTool = gameScopeToolName().equals(calledToolName); boolean gameCatalogTool = GAME_CATALOG_TOOL.equals(calledToolName); + boolean gameQueryPlanTool = GAME_QUERY_PLAN_TOOL.equals(calledToolName); if (!queryTool && !showpromptTool && !qaVectorSearchTool && !qaVectorStoreTool && !fewShotNl2SqlTool - && !gameScopeTool && !gameCatalogTool) { + && !gameScopeTool && !gameCatalogTool && !gameQueryPlanTool) { throw new AppException("등록되지 않은 MCP tool입니다: " + calledToolName); } @@ -196,6 +199,9 @@ public class McpSseService { } else if (gameCatalogTool) { response = gameCatalogVectorResponse(gameCatalogVectorService.resolve( token, arguments.path("question").asText(""), arguments.path("topK").asInt(5))); + } else if (gameQueryPlanTool) { + response = gameQueryPlanResponse(gameCatalogVectorService.resolve( + token, arguments.path("question").asText(""), arguments.path("topK").asInt(5))); } else { String prompt = arguments.path("prompt").asText(""); response = queryTool @@ -362,6 +368,24 @@ public class McpSseService { -1L, "게임 카탈로그 범위 확인", SELECT_AI_TOOL_PATH); } + private McpToolView gameQueryPlanView() { + return new McpToolView(GAME_QUERY_PLAN_TOOL, + "질문에서 게임 범위와 실행 모드를 판정합니다. SQL은 실행하지 않습니다.", + -1L, "게임 질의 계획", SELECT_AI_TOOL_PATH); + } + + private ObjectNode gameQueryPlanResponse(GameCatalogVectorService.Resolution resolution) { + ObjectNode response = gameCatalogVectorResponse(resolution); + String mode = switch (resolution.scopeType()) { + case "SINGLE_GAME" -> "SINGLE"; + case "MULTI_GAME" -> "FAN_OUT"; + case "ALL_GAMES" -> "GROUPED"; + default -> "BLOCKED"; + }; + response.put("executionMode", mode); + return response; + } + private String selectAiProfile() { BackofficeProperties.SelectAi selectAi = properties == null ? null : properties.selectAi(); if (selectAi == null || selectAi.profile() == null || selectAi.profile().isBlank()) {