refactor(auth): remove legacy scanner-sourced x-dc3-ai path end-to-end

This commit is contained in:
pnoker
2026-06-19 01:07:13 +08:00
parent c6de71beef
commit b80909565e
10 changed files with 3 additions and 222 deletions
@@ -69,23 +69,8 @@ message GrpcScannedApiDTO {
// tree node on the permission resource page.
string api_group = 6;
// Declared MCP risk level (LOW / MEDIUM / HIGH); empty when derived automatically.
string risk_level = 7;
// Declared MCP destructive hint ("true" / "false"); empty when derived.
string destructive_hint = 8;
// Declared MCP open-world hint ("true" / "false"); empty when derived.
string open_world_hint = 9;
// Declared MCP idempotent hint ("true" / "false"); empty when derived.
string idempotent_hint = 10;
// AI-facing MCP tool description override; empty when the operation text is used.
string ai_description = 11;
// Whether the MCP tool is hidden from tools/list by default ("true" / "false"); empty = visible.
string hidden = 12;
reserved 7, 8, 9, 10, 11, 12;
reserved "risk_level", "destructive_hint", "open_world_hint", "idempotent_hint", "ai_description", "hidden";
}
// Response wrapper for the sync result.
@@ -651,12 +651,6 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
content.setTitle(spec.getTitle());
content.setUrl(spec.getPath());
content.setRemark(spec.getRemark());
content.setRiskLevel(spec.getRiskLevel());
content.setDestructiveHint(spec.getDestructiveHint());
content.setOpenWorldHint(spec.getOpenWorldHint());
content.setIdempotentHint(spec.getIdempotentHint());
content.setAiDescription(spec.getAiDescription());
content.setHidden(spec.getHidden());
return content;
}
@@ -672,13 +666,7 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
return Objects.isNull(a) && Objects.isNull(b);
}
return Objects.equals(a.getTitle(), b.getTitle()) && Objects.equals(a.getUrl(), b.getUrl())
&& Objects.equals(a.getRemark(), b.getRemark())
&& Objects.equals(a.getRiskLevel(), b.getRiskLevel())
&& Objects.equals(a.getDestructiveHint(), b.getDestructiveHint())
&& Objects.equals(a.getOpenWorldHint(), b.getOpenWorldHint())
&& Objects.equals(a.getIdempotentHint(), b.getIdempotentHint())
&& Objects.equals(a.getAiDescription(), b.getAiDescription())
&& Objects.equals(a.getHidden(), b.getHidden());
&& Objects.equals(a.getRemark(), b.getRemark());
}
@Override
@@ -72,34 +72,4 @@ public class ResourceRegistryScannedApi {
*/
private String apiGroup;
/**
* Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically.
*/
private String riskLevel;
/**
* Declared MCP destructive hint ("true" / "false"); blank when derived.
*/
private String destructiveHint;
/**
* Declared MCP open-world hint ("true" / "false"); blank when derived.
*/
private String openWorldHint;
/**
* Declared MCP idempotent hint ("true" / "false"); blank when derived.
*/
private String idempotentHint;
/**
* AI-facing MCP tool description override; blank when the operation text is used.
*/
private String aiDescription;
/**
* Whether the MCP tool is hidden from tools/list by default ("true" / "false"); blank = visible.
*/
private String hidden;
}
@@ -61,12 +61,6 @@ public class ResourceRegistryServer extends ResourceRegistryApiGrpc.ResourceRegi
.title(dto.getTitle())
.remark(dto.getRemark())
.apiGroup(dto.getApiGroup())
.riskLevel(dto.getRiskLevel())
.destructiveHint(dto.getDestructiveHint())
.openWorldHint(dto.getOpenWorldHint())
.idempotentHint(dto.getIdempotentHint())
.aiDescription(dto.getAiDescription())
.hidden(dto.getHidden())
.build());
}
return apis;
@@ -59,22 +59,4 @@ public class FacadeScannedApiBO {
*/
private String apiGroup;
private String riskLevel;
private String destructiveHint;
private String openWorldHint;
private String idempotentHint;
private String aiDescription;
private String hidden;
}
@@ -64,12 +64,6 @@ public class ResourceRegistryGrpcFacade implements ResourceRegistryFacade {
.setTitle(Objects.requireNonNullElse(api.getTitle(), ""))
.setRemark(Objects.requireNonNullElse(api.getRemark(), ""))
.setApiGroup(Objects.requireNonNullElse(api.getApiGroup(), ""))
.setRiskLevel(Objects.requireNonNullElse(api.getRiskLevel(), ""))
.setDestructiveHint(Objects.requireNonNullElse(api.getDestructiveHint(), ""))
.setOpenWorldHint(Objects.requireNonNullElse(api.getOpenWorldHint(), ""))
.setIdempotentHint(Objects.requireNonNullElse(api.getIdempotentHint(), ""))
.setAiDescription(Objects.requireNonNullElse(api.getAiDescription(), ""))
.setHidden(Objects.requireNonNullElse(api.getHidden(), ""))
.build());
}
}
@@ -61,12 +61,6 @@ public class ResourceRegistryLocalFacade implements ResourceRegistryFacade {
.title(bo.getTitle())
.remark(bo.getRemark())
.apiGroup(bo.getApiGroup())
.riskLevel(bo.getRiskLevel())
.destructiveHint(bo.getDestructiveHint())
.openWorldHint(bo.getOpenWorldHint())
.idempotentHint(bo.getIdempotentHint())
.aiDescription(bo.getAiDescription())
.hidden(bo.getHidden())
.build());
}
return out;
@@ -72,42 +72,6 @@ public class ApiExt extends BaseExt {
@Schema(description = "Free-form description remark of the API interface", example = "Returns the latest temperature reading for a device")
private String remark;
/**
* Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically.
*/
@Schema(description = "Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically", example = "MEDIUM")
private String riskLevel;
/**
* Declared destructive hint ("true" / "false"); blank when derived.
*/
@Schema(description = "Declared MCP destructive hint (\"true\" / \"false\"); blank when derived automatically", example = "false")
private String destructiveHint;
/**
* Declared open-world hint ("true" / "false"); blank when derived.
*/
@Schema(description = "Declared MCP open-world hint (\"true\" / \"false\"); blank when derived automatically", example = "false")
private String openWorldHint;
/**
* Declared idempotent hint ("true" / "false"); blank when derived.
*/
@Schema(description = "Declared MCP idempotent hint (\"true\" / \"false\"); blank when derived automatically", example = "true")
private String idempotentHint;
/**
* AI-facing description override; blank when the operation text is used.
*/
@Schema(description = "AI-facing MCP tool description override; blank when the operation text is used", example = "Fetch the current temperature for a given device id")
private String aiDescription;
/**
* Whether the tool is hidden from tools/list by default ("true" / "false"); blank = visible.
*/
@Schema(description = "Whether the tool is hidden from tools/list by default (\"true\" / \"false\"); blank means visible", example = "false")
private String hidden;
}
}
@@ -22,8 +22,6 @@ import io.github.pnoker.common.constant.common.SymbolConstant;
import io.github.pnoker.common.facade.entity.bo.FacadeScannedApiBO;
import io.github.pnoker.common.resource.registrar.config.ResourceRegistrarProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -176,7 +174,6 @@ public class ApiEndpointScanner {
String title = operation != null && StringUtils.isNotBlank(operation.summary())
? operation.summary() : handler.getMethod().getName();
String remark = operation != null ? StringUtils.defaultString(operation.description()) : "";
Map<String, String> ai = aiMetadata(operation);
out.put(key,
FacadeScannedApiBO.builder()
.method(method.name())
@@ -185,47 +182,11 @@ public class ApiEndpointScanner {
.title(title)
.remark(remark)
.apiGroup(handler.getBeanType().getSimpleName())
.riskLevel(ai.getOrDefault("riskLevel", ""))
.destructiveHint(ai.getOrDefault("destructive", ""))
.openWorldHint(ai.getOrDefault("openWorld", ""))
.idempotentHint(ai.getOrDefault("idempotent", ""))
.aiDescription(ai.getOrDefault("description", ""))
.hidden(ai.getOrDefault("hidden", ""))
.build());
}
}
}
/**
* Read AI tool metadata declared on the operation through the standard OpenAPI
* {@code x-dc3-ai} extension, e.g.
* <pre>
* &#64;Operation(summary = "...", extensions = &#64;Extension(name = "x-dc3-ai", properties = {
* &#64;ExtensionProperty(name = "riskLevel", value = "HIGH"),
* &#64;ExtensionProperty(name = "openWorld", value = "true")}))
* </pre>
* Recognised keys: {@code riskLevel}, {@code destructive}, {@code openWorld},
* {@code idempotent}, {@code description}, {@code hidden}. Absent keys stay unset so the
* tool catalog falls back to its own derivation.
*/
private Map<String, String> aiMetadata(Operation operation) {
if (operation == null) {
return Map.of();
}
Map<String, String> metadata = new LinkedHashMap<>();
for (Extension extension : operation.extensions()) {
if (!"x-dc3-ai".equalsIgnoreCase(extension.name())) {
continue;
}
for (ExtensionProperty property : extension.properties()) {
if (StringUtils.isNotBlank(property.name())) {
metadata.put(property.name(), StringUtils.defaultString(property.value()));
}
}
}
return metadata;
}
private boolean isPublicEndpoint(HandlerMethod handler) {
return handler.hasMethodAnnotation(PublicEndpoint.class)
|| handler.getBeanType().isAnnotationPresent(PublicEndpoint.class);
@@ -21,8 +21,6 @@ import io.github.pnoker.common.annotation.PublicEndpoint;
import io.github.pnoker.common.facade.entity.bo.FacadeScannedApiBO;
import io.github.pnoker.common.resource.registrar.config.ResourceRegistrarProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -89,36 +87,6 @@ class ApiEndpointScannerTest {
assertThat(fetched.getRemark()).isEmpty();
}
@Test
void xDc3AiExtensionPopulatesAiMetadataWithBlankDefaults() {
register(McpAnnotatedController.class);
ApiEndpointScanner scanner = new ApiEndpointScanner(handlerMapping, new ResourceRegistrarProperties());
List<FacadeScannedApiBO> apis = scanner.scan();
FacadeScannedApiBO command = apis.stream()
.filter(a -> "/api/mcp/command".equals(a.getPath()))
.findFirst().orElseThrow();
assertThat(command.getRiskLevel()).isEqualTo("HIGH");
assertThat(command.getOpenWorldHint()).isEqualTo("true");
assertThat(command.getHidden()).isEqualTo("true");
// Attributes left at their annotation defaults stay blank, so derivation still applies.
assertThat(command.getDestructiveHint()).isEmpty();
assertThat(command.getIdempotentHint()).isEmpty();
assertThat(command.getAiDescription()).isEmpty();
FacadeScannedApiBO plain = apis.stream()
.filter(a -> "/api/mcp/plain".equals(a.getPath()))
.findFirst().orElseThrow();
// No x-dc3-ai extension: every AI metadata field is blank so the catalog derives them.
assertThat(plain.getRiskLevel()).isEmpty();
assertThat(plain.getDestructiveHint()).isEmpty();
assertThat(plain.getOpenWorldHint()).isEmpty();
assertThat(plain.getIdempotentHint()).isEmpty();
assertThat(plain.getAiDescription()).isEmpty();
assertThat(plain.getHidden()).isEmpty();
}
@Test
void unsupportedMethodsAndDefaultExcludesAreFilteredOut() {
register(PatchOnlyController.class);
@@ -265,25 +233,6 @@ class ApiEndpointScannerTest {
}
}
@RestController
@RequestMapping("/api/mcp")
static class McpAnnotatedController {
@Operation(summary = "Issue Command", extensions = @Extension(name = "x-dc3-ai", properties = {
@ExtensionProperty(name = "riskLevel", value = "HIGH"),
@ExtensionProperty(name = "openWorld", value = "true"),
@ExtensionProperty(name = "hidden", value = "true")
}))
@PostMapping("/command")
public String command() {
return "ok";
}
@PostMapping("/plain")
public String plain() {
return "ok";
}
}
@RestController
@RequestMapping("/api/duplicate")
static class DuplicateController {