implement game catalog vector MCP resolver

This commit is contained in:
devmrko
2026-07-27 13:54:49 +09:00
parent 9089897d88
commit 5d4af888f3
3 changed files with 78 additions and 3 deletions

View File

@@ -0,0 +1,37 @@
package com.cloudhandson.vpdbackoffice.service;
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class GameCatalogVectorService {
private final BackofficeProperties properties;
private final BearerTokenService bearerTokenService;
public GameCatalogVectorService(BackofficeProperties properties, BearerTokenService bearerTokenService) {
this.properties = properties;
this.bearerTokenService = bearerTokenService;
}
public List<GameCandidate> search(String token, String question, int topK) {
if (token == null || token.isBlank() || bearerTokenService.findByPlainToken(token.trim()) == null) {
throw new VpdTokenAccessDeniedException();
}
BackofficeProperties.SelectAi db = properties.selectAi();
if (db == null || !db.configured()) throw new AppException("게임 카탈로그 DB 설정이 필요합니다.");
List<GameCandidate> result = new ArrayList<>();
try (Connection c = DriverManager.getConnection(db.dbUrl(), db.dbUsername(), db.dbPassword());
CallableStatement s = c.prepareCall("{ ? = call sg_game_catalog_search(?, ?) }")) {
s.registerOutParameter(1, Types.REF_CURSOR); s.setString(2, question); s.setInt(3, Math.min(Math.max(topK, 1), 20)); s.execute();
try (ResultSet rs = (ResultSet) s.getObject(1)) {
while (rs.next()) result.add(new GameCandidate(rs.getString("GAME_KEY"), rs.getString("GAME_ID"), rs.getString("GAME_PREFIX"), rs.getString("GAME_NM"), rs.getString("GAME_ALIAS_NM"), rs.getDouble("COSINE_DISTANCE")));
}
return result;
} catch (SQLException e) { throw new AppException("게임 카탈로그 벡터 검색 실패: " + e.getMessage()); }
}
public record GameCandidate(String gameKey, String gameId, String gamePrefix, String gameName, String aliases, double similarity) {}
}

View File

@@ -23,6 +23,7 @@ public class McpSseService {
private final McpProperties mcpProperties;
private final QaVectorService qaVectorService;
private final GameScopeService gameScopeService;
private final GameCatalogVectorService gameCatalogVectorService;
public McpSseService(
SelectAiService selectAiService,
@@ -30,7 +31,8 @@ public class McpSseService {
BackofficeProperties properties,
McpProperties mcpProperties,
QaVectorService qaVectorService,
GameScopeService gameScopeService
GameScopeService gameScopeService,
GameCatalogVectorService gameCatalogVectorService
) {
this.selectAiService = selectAiService;
this.objectMapper = objectMapper;
@@ -38,6 +40,7 @@ public class McpSseService {
this.mcpProperties = mcpProperties;
this.qaVectorService = qaVectorService;
this.gameScopeService = gameScopeService;
this.gameCatalogVectorService = gameCatalogVectorService;
}
public ObjectNode handle(String contextPath, JsonNode request) {
@@ -191,8 +194,8 @@ public class McpSseService {
response = gameScopeResponse(gameScopeService.resolve(
token, arguments.path("question").asText("")));
} else if (gameCatalogTool) {
response = gameScopeResponse(gameScopeService.resolve(
token, arguments.path("question").asText("")));
response = gameCatalogVectorResponse(gameCatalogVectorService.search(
token, arguments.path("question").asText(""), arguments.path("topK").asInt(5)));
} else {
String prompt = arguments.path("prompt").asText("");
response = queryTool
@@ -244,6 +247,19 @@ public class McpSseService {
return response;
}
private ObjectNode gameCatalogVectorResponse(List<GameCatalogVectorService.GameCandidate> candidates) {
ObjectNode response = objectMapper.createObjectNode();
response.put("status", candidates.isEmpty() ? "NO_MATCH" : "CANDIDATES");
ArrayNode items = response.putArray("matchedGames");
for (GameCatalogVectorService.GameCandidate c : candidates) {
ObjectNode item = items.addObject();
item.put("gameKey", c.gameKey()); item.put("gameId", c.gameId());
item.put("gamePrefix", c.gamePrefix()); item.put("gameName", c.gameName());
item.put("aliases", c.aliases()); item.put("cosineDistance", c.similarity());
}
return response;
}
private ObjectNode qaVectorStoreResponse(QaVectorService.VectorStoreResult stored) {
ObjectNode response = objectMapper.createObjectNode();
response.put("status", "QA_VECTOR_STORED");