feat(mcp): expose corpus_search, korrespond_refine, extract_text tools

Restores the 3 tools (manifest + invoke arms + invokeExtract helper),
the citation-atom RAG lever in LegalTools/corpus-search, and the catalog
icons. These were live on prod via rsync but uncommitted, so a git-pull
deploy reverted the manifest from 22 to 19 tools.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 16:45:41 +02:00
parent 2d2502a037
commit 5a0ef89dca
4 changed files with 186 additions and 2 deletions
+47
View File
@@ -47,6 +47,46 @@ try {
$catClause = $category !== null ? ' AND d.category = ?' : '';
$excludeLike = '%' . EXCLUDED_DOMAIN . '%';
// Exact-identifier routing: the FULLTEXT tokenizer drops "§ 4-12" / "Art. 8"
// to stopword fragments, so a citation query never matches. Route those to a
// verbatim LIKE lookup and pin the hits ahead of the fuzzy BM25 results.
$exactHits = [];
$exactChunkIds = [];
$citationAtoms = DbnLegalToolsService::citationAtoms($query);
if (!empty($citationAtoms)) {
$atomClauses = [];
$atomParams = [1];
foreach ($citationAtoms as $atom) {
$like = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $atom) . '%';
$atomClauses[] = '(c.content LIKE ? OR c.section_title LIKE ?)';
$atomParams[] = $like;
$atomParams[] = $like;
}
$atomParams[] = $excludeLike;
$exactSql = "SELECT d.id AS document_id, d.title, d.category,
d.source_url, c.id AS chunk_id, c.content AS excerpt,
c.section_title AS section, d.language, 1.0 AS score
FROM chunks c
JOIN documents d ON c.document_id = d.id
WHERE d.corpus_id = ? AND d.status = 'ready'
AND (" . implode(' OR ', $atomClauses) . ")
AND d.source_url NOT LIKE ?
$catClause
LIMIT $limit";
$exactParams = $atomParams;
if ($category !== null) $exactParams[] = $category;
try {
$stmt = $ragDb->prepare($exactSql);
$stmt->execute($exactParams);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
$exactHits[] = $r;
if (isset($r['chunk_id'])) $exactChunkIds[(int)$r['chunk_id']] = true;
}
} catch (Throwable $e) {
// Non-fatal — fall through to fuzzy BM25.
}
}
// Try FULLTEXT index first
try {
$sql = "SELECT d.id AS document_id, d.title, d.category,
@@ -89,6 +129,12 @@ try {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Exact identifier hits lead; drop fuzzy rows that duplicate them.
if (!empty($exactChunkIds)) {
$rows = array_values(array_filter($rows, fn($r) => empty($exactChunkIds[(int)($r['chunk_id'] ?? 0)])));
}
$rows = array_slice(array_merge($exactHits, $rows), 0, $limit);
$hits = array_map(fn($r) => [
'title' => $r['title'] ?? '',
'category' => $r['category'] ?? '',
@@ -100,6 +146,7 @@ try {
'chunk_id' => isset($r['chunk_id']) ? (int)$r['chunk_id'] : null,
'source_url' => $r['source_url'] ?? null,
'language' => $r['language'] ?? null,
'exact_match' => !empty($exactChunkIds[(int)($r['chunk_id'] ?? 0)]),
], $rows);
dbnToolsRespond(['ok' => true, 'hits' => $hits, 'mode' => 'bm25', 'query' => $query]);
}