From e200dbd5b898290b7e9ac64a488a8d7594b9c220 Mon Sep 17 00:00:00 2001 From: jade Date: Tue, 18 Aug 2026 21:01:01 +0900 Subject: [PATCH] use bundleId to dynamically match module-wide tool manifest requests --- .../dap/lib/manifest/ToolManifestService.java | 23 +++++++++++++++++++ .../presentation/ToolManifestController.java | 13 ++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java index 5215ee99b..1ac3961cd 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java @@ -43,12 +43,17 @@ public class ToolManifestService { } public ToolManifestResponse currentManifest() { + return currentManifest(null); + } + + public ToolManifestResponse currentManifest(String categoryKey) { String bundleId = properties.getManifest() == null ? null : properties.getManifest().getBundleId(); if (bundleId == null || bundleId.isBlank()) { throw new IllegalStateException("mcp.manifest.bundle-id must be configured"); } List tools = toolSupplier.get().stream() + .filter(tool -> isMatchCategory(categoryKey, tool.getCategoryKey(), bundleId)) .map(this::toManifestItem) .sorted(Comparator.comparing(ToolManifestItem::name)) .toList(); @@ -56,6 +61,24 @@ public class ToolManifestService { return new ToolManifestResponse(bundleId, revision(bundleId, tools), tools); } + private boolean isMatchCategory(String requestedCategory, String toolCategory, String bundleId) { + if (requestedCategory == null) { + return true; + } + if (requestedCategory.equalsIgnoreCase(toolCategory)) { + return true; + } + + // Return all tools in this module if the requested category matches the module's bundle ID + if (bundleId != null && + (bundleId.equalsIgnoreCase(requestedCategory) || + bundleId.equalsIgnoreCase("was-" + requestedCategory))) { + return true; + } + + return false; + } + private ToolManifestItem toManifestItem(ToolMetadata tool) { String title = tool.getDisplayName() == null || tool.getDisplayName().isBlank() ? tool.getName() : tool.getDisplayName(); diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java index a8eeea7fc..0d20f50bd 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java @@ -8,6 +8,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; /** Read-only Tool Service manifest endpoint for MCP background discovery. */ @RestController @@ -22,7 +23,17 @@ public class ToolManifestController { @GetMapping(value = "/tool-manifest", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getManifest( @RequestHeader(value = HttpHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { - ToolManifestResponse manifest = toolManifestService.currentManifest(); + return handleManifest(toolManifestService.currentManifest(), ifNoneMatch); + } + + @GetMapping(value = "/tool-manifest/{categoryKey}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getManifestByCategory( + @PathVariable("categoryKey") String categoryKey, + @RequestHeader(value = HttpHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { + return handleManifest(toolManifestService.currentManifest(categoryKey), ifNoneMatch); + } + + private ResponseEntity handleManifest(ToolManifestResponse manifest, String ifNoneMatch) { String eTag = '"' + manifest.revision() + '"'; if (eTag.equals(ifNoneMatch)) { return ResponseEntity.status(304).eTag(eTag).build();