diff --git a/mateclaw-server/src/main/java/vip/mate/kbopen/auth/KbApiKeyService.java b/mateclaw-server/src/main/java/vip/mate/kbopen/auth/KbApiKeyService.java
index c0bd0260..db153008 100644
--- a/mateclaw-server/src/main/java/vip/mate/kbopen/auth/KbApiKeyService.java
+++ b/mateclaw-server/src/main/java/vip/mate/kbopen/auth/KbApiKeyService.java
@@ -229,7 +229,7 @@ public class KbApiKeyService {
}
return Set.of(scopes.split(",")).stream()
.map(String::trim)
- .collect(java.util.stream.Collectors.toUnmodifiableSet());
+ .collect(Collectors.toUnmodifiableSet());
}
/** Return value of {@link #create}. */
diff --git a/mateclaw-server/src/main/java/vip/mate/kbopen/controller/KbOpenApiController.java b/mateclaw-server/src/main/java/vip/mate/kbopen/controller/KbOpenApiController.java
new file mode 100644
index 00000000..089d247f
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/kbopen/controller/KbOpenApiController.java
@@ -0,0 +1,331 @@
+package vip.mate.kbopen.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import vip.mate.common.result.R;
+import vip.mate.exception.MateClawException;
+import vip.mate.kbopen.auth.RequireKbScope;
+import vip.mate.kbopen.dto.KbOpenApiDtos.*;
+import vip.mate.kbopen.service.KbOpenApiService;
+import vip.mate.wiki.dto.PageCitationWithRaw;
+import vip.mate.wiki.dto.PageSearchResult;
+import vip.mate.wiki.model.WikiChunkEntity;
+import vip.mate.wiki.model.WikiEntityEntity;
+import vip.mate.wiki.model.WikiEntityRelationEntity;
+import vip.mate.wiki.model.WikiKnowledgeBaseEntity;
+import vip.mate.wiki.model.WikiPageEntity;
+import vip.mate.wiki.repository.WikiChunkMapper;
+import vip.mate.wiki.repository.WikiEntityMapper;
+import vip.mate.wiki.repository.WikiEntityRelationMapper;
+import vip.mate.wiki.repository.WikiPageCitationMapper;
+import vip.mate.wiki.service.HybridRetriever;
+import vip.mate.wiki.service.WikiKnowledgeBaseService;
+import vip.mate.wiki.service.WikiPageService;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * KB Open API — 9 read-only endpoints for programmatic knowledge base access.
+ *
+ *
All endpoints are under {@code /api/v1/open/kb/**} (SecurityConfig
+ * permitAll), authenticated by {@code KbOpenApiAuthFilter} (API Key), and
+ * authorized by {@code @RequireKbScope} (scope + KB ownership).
+ *
+ *
A5: returns explicit DTOs, never raw entities.
+ * A6: service-layer methods return pure DTOs (no HTTP coupling).
+ */
+@Tag(name = "KB Open API")
+@RestController
+@RequestMapping("/api/v1/open/kb")
+@RequiredArgsConstructor
+public class KbOpenApiController {
+
+ private final WikiPageService pageService;
+ private final HybridRetriever hybridRetriever;
+ private final WikiKnowledgeBaseService kbService;
+ private final WikiPageCitationMapper citationMapper;
+ private final WikiChunkMapper chunkMapper;
+ private final WikiEntityMapper entityMapper;
+ private final WikiEntityRelationMapper relationMapper;
+ private final KbOpenApiService openApiService;
+
+ // ── 1. GET /pages/{slug} — entity card / page detail ──────────────────
+
+ @RequireKbScope("kb:read")
+ @GetMapping("/{kbId}/pages/{slug}")
+ @Operation(summary = "Get entity card / page detail (mode controls content depth)")
+ public R getPage(
+ @PathVariable Long kbId,
+ @PathVariable String slug,
+ @RequestParam(defaultValue = "summary") String mode,
+ @RequestParam(required = false) String fields) {
+ WikiPageEntity page = pageService.getBySlug(kbId, slug);
+ if (page == null) {
+ throw new MateClawException(404, "Page not found: " + slug);
+ }
+ return R.ok(openApiService.assembleCard(page, mode, fields));
+ }
+
+ // ── 2. POST /search — hybrid retrieval ────────────────────────────────
+
+ @RequireKbScope("kb:search")
+ @PostMapping("/{kbId}/search")
+ @Operation(summary = "Hybrid search (granularity controls result shape)")
+ public R