feat(mcp): expose tool guidance in metadata
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 5m4s

This commit is contained in:
jade
2026-08-17 14:48:09 +09:00
parent 59f9a8eaad
commit 0816c0ff0c
5 changed files with 62 additions and 6 deletions

View File

@@ -192,7 +192,9 @@ public class McpRouterController {
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("when_to_use", t.getWhenToUse());
meta.put("when_not_to_use", t.getWhenNotToUse());
meta.put("io_limits", t.getIoLimits());
meta.put("example_queries", t.getExampleQueries());
meta.put("required_env_keys", t.getRequiredEnvKeys());

View File

@@ -32,6 +32,9 @@ public final class ToolMetadataMcpMapper {
put(meta, "version", metadata.getSemver());
put(meta, "category_key", metadata.getCategoryKey());
put(meta, "display_description", metadata.getDisplayDescription());
put(meta, "when_to_use", metadata.getWhenToUse());
put(meta, "when_not_to_use", metadata.getWhenNotToUse());
put(meta, "io_limits", metadata.getIoLimits());
put(meta, "example_queries", metadata.getExampleQueries());
put(meta, "tags", metadata.getTags());
put(meta, "legacy_interface_id", metadata.getMciServiceId());

View File

@@ -207,10 +207,7 @@ public class ToolRegistryHeartbeatSender {
meta.setWhenToUse(definition.description().whenToUse());
meta.setWhenNotToUse(definition.description().whenNotToUse());
meta.setIoLimits(definition.description().ioLimits());
meta.setDescription(String.join("\n", definition.description().function(),
"사용 시점: " + definition.description().whenToUse(),
"사용 제외: " + definition.description().whenNotToUse(),
"입출력 제한: " + definition.description().ioLimits()));
meta.setDescription(definition.description().function());
meta.setDisplayDescription(definition.displayDescription());
meta.setExampleQueries(definition.exampleQueries());
meta.setReadOnlyHint(definition.readOnly());

View File

@@ -0,0 +1,21 @@
package io.shinhanlife.dap.lib.mcp;
import static org.assertj.core.api.Assertions.assertThat;
import io.shinhanlife.dap.mcc.dto.ToolMetadata;
import org.junit.jupiter.api.Test;
class ToolMetadataMcpMapperTest {
@Test
void includesUsageGuidanceInMcpMeta() {
ToolMetadata metadata = new ToolMetadata();
metadata.setWhenToUse("사용 시점");
metadata.setWhenNotToUse("사용 제외");
metadata.setIoLimits("입출력 제한");
assertThat(ToolMetadataMcpMapper.meta(metadata)).containsEntry("when_to_use", "사용 시점")
.containsEntry("when_not_to_use", "사용 제외")
.containsEntry("io_limits", "입출력 제한");
}
}

View File

@@ -7,6 +7,9 @@ import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.lib.config.McpProperties;
import io.shinhanlife.dap.lib.metadata.ToolDefinition;
import io.shinhanlife.dap.lib.metadata.ToolDefinitionRepository;
import io.shinhanlife.dap.lib.metadata.ToolDescription;
import io.shinhanlife.dap.lib.util.ToolSchemaResolver;
import java.lang.reflect.Field;
import java.util.List;
@@ -34,6 +37,30 @@ class ToolRegistryHeartbeatSenderTest {
assertThat(registeredTools(sender)).isEmpty();
}
@Test
void keepsOnlyFunctionDescriptionAndStoresUsageGuidanceSeparately() throws Exception {
ApplicationContext applicationContext = mock(ApplicationContext.class);
when(applicationContext.getBeansOfType(Object.class)).thenReturn(Map.of("tool", new EnabledTool()));
ToolDefinitionRepository definitions = mock(ToolDefinitionRepository.class);
when(definitions.findByName("test_enabled_tool")).thenReturn(java.util.Optional.of(new ToolDefinition(
"test_enabled_tool", "테스트 도구", "1.0.0", "test",
new ToolDescription("기능 설명", "사용 시점", "사용 제외", "입출력 제한"),
"도구 설명", List.of("질문 1", "질문 2", "질문 3"), true, false, true,
Map.of("type", "object", "properties", Map.of(), "additionalProperties", false), null,
List.of("test"), null, List.of(), "MCP_TOOL")));
ToolRegistryHeartbeatSender sender = new ToolRegistryHeartbeatSender(applicationContext, new ObjectMapper(),
new McpProperties(), mock(ToolSchemaResolver.class), definitions);
sender.init();
assertThat(sender.getAllScannedTools()).singleElement().satisfies(tool -> {
assertThat(tool.getDescription()).isEqualTo("기능 설명");
assertThat(tool.getWhenToUse()).isEqualTo("사용 시점");
assertThat(tool.getWhenNotToUse()).isEqualTo("사용 제외");
assertThat(tool.getIoLimits()).isEqualTo("입출력 제한");
});
}
private void setField(Object target, String name, Object value) throws Exception {
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
@@ -53,4 +80,10 @@ class ToolRegistryHeartbeatSenderTest {
void execute() {
}
}
}
static class EnabledTool {
@McpTool(name = "test_enabled_tool")
void execute() {
}
}
}