@iflow-mcp/ansvar-systems-german-law-mcp
Version:
German legal research MCP server with statutes, case law, and preparatory works from gesetze-im-internet.de
1,097 lines (1,096 loc) • 35.9 kB
JavaScript
import Database from "@ansvar/mcp-sqlite";
import * as fs from "node:fs";
import * as path from "node:path";
import { parseGermanCitation } from "../citation/german-citation.js";
import { buildFtsQueryVariantsLegacy as buildFtsQueryVariants } from "../utils/fts-query.js";
import { detectCapabilities, readDbMetadata } from '../capabilities.js';
const DB_ENV_VAR = "GERMAN_LAW_DB_PATH";
const DEFAULT_DB_PATH = path.resolve(process.cwd(), "data", "database.db");
let dbInstance = null;
let dbAvailabilityChecked = false;
let dbAvailable = false;
let resolvedPathCache = "";
let dbCapabilities = null;
let dbMetadata = null;
export function searchGermanLawDocuments(query, limit, statuteId) {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return null;
}
// Resolve statuteId from title if it does not match a known ID
const resolvedStatuteId = statuteId
? resolveDocumentStatuteId(db, statuteId)
: undefined;
if (statuteId && resolvedStatuteId === null) {
return {
documents: [],
total: 0,
_metadata: { note: `No document found matching "${statuteId}"` },
};
}
// Fetch extra rows to account for deduplication
const clampedLimit = clampLimit(limit);
const fetchLimit = clampedLimit * 2;
let queryStrategy;
const exactRows = findExactCitationRows(db, query, fetchLimit);
const mergedRows = [];
const seen = new Set();
const filteredExact = resolvedStatuteId
? exactRows.filter((r) => matchesStatuteId(r, resolvedStatuteId))
: exactRows;
pushUniqueRows(mergedRows, seen, filteredExact, fetchLimit);
const variants = buildFtsQueryVariants(query);
if (mergedRows.length < fetchLimit && variants.primary) {
if (tableExists(db, "law_documents_fts")) {
const primaryRows = runLawFtsQuery(db, variants.primary, fetchLimit * 3);
if (primaryRows) {
const filtered = resolvedStatuteId
? primaryRows.filter((r) => matchesStatuteId(r, resolvedStatuteId))
: primaryRows;
pushUniqueRows(mergedRows, seen, filtered, fetchLimit);
}
if (mergedRows.length < fetchLimit && variants.fallback) {
const fallbackRows = runLawFtsQuery(db, variants.fallback, fetchLimit * 3);
if (fallbackRows && fallbackRows.length > 0) {
if (!queryStrategy)
queryStrategy = "broadened";
const filtered = resolvedStatuteId
? fallbackRows.filter((r) => matchesStatuteId(r, resolvedStatuteId))
: fallbackRows;
pushUniqueRows(mergedRows, seen, filtered, fetchLimit);
}
}
}
}
if (mergedRows.length < fetchLimit) {
const likeRows = runLawLikeQuery(db, query, fetchLimit * 3);
const filtered = resolvedStatuteId
? likeRows.filter((r) => matchesStatuteId(r, resolvedStatuteId))
: likeRows;
if (filtered.length > 0 && mergedRows.length === 0) {
queryStrategy = "like_fallback";
}
pushUniqueRows(mergedRows, seen, filtered, fetchLimit);
}
const documents = deduplicateDocuments(mergedRows.map(mapStatuteRowToLawDocument), clampedLimit);
const metadata = queryStrategy
? { query_strategy: queryStrategy }
: undefined;
return {
documents,
total: documents.length,
...(metadata ? { _metadata: metadata } : {}),
};
}
export function searchGermanCaseLawDocuments(request) {
const db = getDb();
if (!db || !tableExists(db, "case_law_documents")) {
return null;
}
const query = request.query.trim();
if (!query) {
return { documents: [], total: 0 };
}
const clampedLimit = clampLimit(request.limit ?? 20);
const filters = buildCaseFilterSql({
...(request.court === undefined ? {} : { court: request.court }),
...(request.dateFrom === undefined ? {} : { dateFrom: request.dateFrom }),
...(request.dateTo === undefined ? {} : { dateTo: request.dateTo }),
});
const mergedRows = [];
const seen = new Set();
const exactRows = findExactCaseRows(db, query, clampedLimit, filters);
pushUniqueRows(mergedRows, seen, exactRows, clampedLimit);
if (mergedRows.length >= clampedLimit) {
return {
documents: mergedRows.map(mapCaseLawRowToLawDocument),
total: mergedRows.length,
};
}
const remaining = clampedLimit - mergedRows.length;
const variants = buildFtsQueryVariants(query);
if (variants.primary && tableExists(db, "case_law_documents_fts")) {
const primaryRows = runCaseLawFtsQuery(db, variants.primary, remaining * 3, filters);
if (primaryRows) {
pushUniqueRows(mergedRows, seen, primaryRows, clampedLimit);
}
if (mergedRows.length >= clampedLimit) {
return {
documents: mergedRows.map(mapCaseLawRowToLawDocument),
total: mergedRows.length,
};
}
if (variants.fallback) {
const fallbackRows = runCaseLawFtsQuery(db, variants.fallback, remaining * 3, filters);
if (fallbackRows && fallbackRows.length > 0) {
pushUniqueRows(mergedRows, seen, fallbackRows, clampedLimit);
}
}
if (mergedRows.length >= clampedLimit) {
return {
documents: mergedRows.map(mapCaseLawRowToLawDocument),
total: mergedRows.length,
};
}
}
const likeRows = runCaseLawLikeQuery(db, query, remaining * 3, filters);
pushUniqueRows(mergedRows, seen, likeRows, clampedLimit);
return {
documents: mergedRows.map(mapCaseLawRowToLawDocument),
total: mergedRows.length,
};
}
export function searchGermanPreparatoryWorks(request) {
const db = getDb();
if (!db || !tableExists(db, "preparatory_works")) {
return null;
}
const hints = buildPreparatorySearchHints(request);
const clampedLimit = clampLimit(request.limit ?? 20);
const filters = buildPreparatoryFilterSql(request);
const mergedRows = [];
const seen = new Set();
if (hints.length > 0 && tableExists(db, "preparatory_works_fts")) {
const variants = buildFtsQueryVariants(hints[0] ?? "");
if (variants.primary) {
const primaryRows = runPreparatoryFtsQuery(db, variants.primary, clampedLimit * 3, filters);
if (primaryRows) {
pushUniqueRows(mergedRows, seen, primaryRows, clampedLimit);
}
if (mergedRows.length >= clampedLimit) {
return {
documents: mergedRows.map(mapPreparatoryWorkRowToLawDocument),
total: mergedRows.length,
};
}
if (variants.fallback) {
const fallbackRows = runPreparatoryFtsQuery(db, variants.fallback, clampedLimit * 3, filters);
if (fallbackRows && fallbackRows.length > 0) {
pushUniqueRows(mergedRows, seen, fallbackRows, clampedLimit);
}
}
if (mergedRows.length >= clampedLimit) {
return {
documents: mergedRows.map(mapPreparatoryWorkRowToLawDocument),
total: mergedRows.length,
};
}
}
}
const likeRows = runPreparatoryLikeQuery(db, hints, clampedLimit * 3, filters);
pushUniqueRows(mergedRows, seen, likeRows, clampedLimit);
if (mergedRows.length === 0 && hints.length === 0) {
const filteredRows = runPreparatoryFilteredQuery(db, clampedLimit, filters);
pushUniqueRows(mergedRows, seen, filteredRows, clampedLimit);
}
return {
documents: mergedRows.map(mapPreparatoryWorkRowToLawDocument),
total: mergedRows.length,
};
}
export function getGermanLawDocumentById(id) {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return undefined;
}
let row;
try {
row = db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE id = ?
LIMIT 1
`)
.get(id);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return undefined;
}
return row ? mapStatuteRowToLawDocument(row) : null;
}
export function getGermanDocumentByAnyId(id) {
const db = getDb();
if (!db) {
return undefined;
}
if (tableExists(db, "law_documents")) {
try {
const statuteRow = db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE id = ?
LIMIT 1
`)
.get(id);
if (statuteRow) {
return mapStatuteRowToLawDocument(statuteRow);
}
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return undefined;
}
}
if (tableExists(db, "case_law_documents")) {
try {
const caseLawRow = db
.prepare(`
SELECT
id,
country,
case_id,
ecli,
court,
decision_date,
file_number,
decision_type,
title,
citation,
source_url,
text_snippet,
metadata_json
FROM case_law_documents
WHERE id = ?
LIMIT 1
`)
.get(id);
if (caseLawRow) {
return mapCaseLawRowToLawDocument(caseLawRow);
}
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return undefined;
}
}
if (tableExists(db, "preparatory_works")) {
try {
const preparatoryRow = db
.prepare(`
SELECT
id,
country,
dip_id,
title,
statute_id,
statute_citation,
work_type,
publication_date,
source_url,
text_snippet,
metadata_json
FROM preparatory_works
WHERE id = ?
LIMIT 1
`)
.get(id);
if (preparatoryRow) {
return mapPreparatoryWorkRowToLawDocument(preparatoryRow);
}
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return undefined;
}
}
return null;
}
export function getGermanLawDocumentsByStatuteId(statuteId, limit = 200) {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return null;
}
const normalized = statuteId.trim().toLowerCase();
if (!normalized) {
return { documents: [], total: 0 };
}
const clampedLimit = clampLimit(limit);
try {
const rows = db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE lower(statute_id) = ?
ORDER BY id
LIMIT ?
`)
.all(normalized, clampedLimit);
return {
documents: rows.map(mapStatuteRowToLawDocument),
total: rows.length,
};
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function getGermanLawDocumentsByCitation(citation, limit = 200) {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return null;
}
const trimmed = citation.trim();
if (!trimmed) {
return { documents: [], total: 0 };
}
const parsed = parseGermanCitation(trimmed);
const candidates = dedupeStrings([
trimmed,
parsed?.normalized ?? "",
...(parsed?.lookupCitations ?? []),
])
.map((value) => value.toLowerCase())
.filter((value) => value.length > 0);
if (candidates.length === 0) {
return { documents: [], total: 0 };
}
const preferred = (parsed?.lookupCitations[0] ?? parsed?.normalized ?? trimmed).toLowerCase();
const clampedLimit = clampLimit(limit);
const placeholders = candidates.map(() => "?").join(", ");
try {
const rows = db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE lower(citation) IN (${placeholders})
ORDER BY
CASE WHEN lower(citation) = ? THEN 0 ELSE 1 END,
id
LIMIT ?
`)
.all(...candidates, preferred, clampedLimit);
return {
documents: rows.map(mapStatuteRowToLawDocument),
total: rows.length,
};
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function getGermanLawDocumentCount() {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return null;
}
try {
const row = db
.prepare("SELECT COUNT(*) AS count FROM law_documents")
.get();
return Number(row.count);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function getGermanCaseLawDocumentCount() {
const db = getDb();
if (!db || !tableExists(db, "case_law_documents")) {
return null;
}
try {
const row = db
.prepare("SELECT COUNT(*) AS count FROM case_law_documents")
.get();
return Number(row.count);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function getGermanPreparatoryWorkCount() {
const db = getDb();
if (!db || !tableExists(db, "preparatory_works")) {
return null;
}
try {
const row = db
.prepare("SELECT COUNT(*) AS count FROM preparatory_works")
.get();
return Number(row.count);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function citationExistsInGermanLawDatabase(citation) {
const db = getDb();
if (!db || !tableExists(db, "law_documents")) {
return null;
}
const parsed = parseGermanCitation(citation);
if (!parsed || parsed.lookupCitations.length === 0) {
return false;
}
try {
return existsByCitation(db, parsed.lookupCitations);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
export function resolveGermanLawDatabasePath() {
return process.env[DB_ENV_VAR]?.trim() || DEFAULT_DB_PATH;
}
export function resetGermanLawDatabaseCache() {
if (dbInstance) {
dbInstance.close();
dbInstance = null;
}
dbAvailabilityChecked = false;
dbAvailable = false;
resolvedPathCache = "";
}
export function getCapabilities() {
if (!dbCapabilities)
getDb();
return dbCapabilities ?? new Set();
}
export function getMetadata() {
if (!dbMetadata)
getDb();
return dbMetadata ?? { tier: 'unknown', schema_version: '1', built_at: 'unknown', builder: 'unknown' };
}
export function getDb() {
const resolvedPath = resolveGermanLawDatabasePath();
if (dbAvailabilityChecked && resolvedPath === resolvedPathCache) {
return dbAvailable ? dbInstance : null;
}
if (dbInstance) {
dbInstance.close();
dbInstance = null;
}
resolvedPathCache = resolvedPath;
dbAvailabilityChecked = true;
dbAvailable = false;
if (!fs.existsSync(resolvedPath)) {
return null;
}
try {
dbInstance = new Database(resolvedPath, { readonly: true });
dbAvailable = true;
// Detect capabilities on first open
dbCapabilities = detectCapabilities(dbInstance);
dbMetadata = readDbMetadata(dbInstance);
console.error(`[german-law-mcp] Database tier: ${dbMetadata.tier}, capabilities: ${[...dbCapabilities].join(', ')}`);
return dbInstance;
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
function tableExists(db, tableName) {
try {
const row = db
.prepare(`
SELECT 1 AS exists_flag
FROM sqlite_master
WHERE type IN ('table', 'view')
AND name = ?
LIMIT 1
`)
.get(tableName);
return Boolean(row?.exists_flag);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return false;
}
}
function runLawFtsQuery(db, ftsQuery, limit) {
try {
const rows = db
.prepare(`
SELECT d.id, d.country, d.kind, d.title, d.citation, d.source_url, d.effective_date, d.text_snippet, d.metadata_json
FROM law_documents_fts f
JOIN law_documents d ON d.rowid = f.rowid
WHERE law_documents_fts MATCH ?
ORDER BY bm25(law_documents_fts)
LIMIT ?
`)
.all(ftsQuery, limit);
return rows;
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
function findExactCitationRows(db, query, limit) {
const parsed = parseGermanCitation(query);
if (!parsed || parsed.lookupCitations.length === 0) {
return [];
}
const citations = dedupeStrings(parsed.lookupCitations).map((value) => value.toLowerCase());
const placeholders = citations.map(() => "?").join(", ");
try {
return db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE lower(citation) IN (${placeholders})
ORDER BY
CASE WHEN lower(citation) = ? THEN 0 ELSE 1 END,
id
LIMIT ?
`)
.all(...citations, citations[0] ?? "", limit);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function runLawLikeQuery(db, query, limit) {
const tokens = query
.normalize("NFC")
.split(/\s+/)
.map((token) => token.trim())
.filter((token) => token.length > 1);
const searchTerms = tokens.length > 0 ? tokens : [query.trim()];
const clauses = [];
const params = [];
for (const term of searchTerms) {
const like = `%${term}%`;
clauses.push("(title LIKE ? OR citation LIKE ? OR text_snippet LIKE ?)");
params.push(like, like, like);
}
const whereClause = clauses.length > 0 ? clauses.join(" AND ") : "1 = 0";
try {
return db
.prepare(`
SELECT id, country, kind, title, citation, source_url, effective_date, text_snippet, metadata_json
FROM law_documents
WHERE ${whereClause}
LIMIT ?
`)
.all(...params, limit);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function findExactCaseRows(db, query, limit, filters) {
const lower = query.toLowerCase();
const params = [lower, lower, lower, lower, lower, ...filters.params, limit];
try {
return db
.prepare(`
SELECT
id,
country,
case_id,
ecli,
court,
decision_date,
file_number,
decision_type,
title,
citation,
source_url,
text_snippet,
metadata_json
FROM case_law_documents
WHERE (
lower(ecli) = ?
OR lower(file_number) = ?
OR lower(citation) = ?
OR lower(case_id) = ?
OR lower(id) = ?
) ${filters.clause}
ORDER BY decision_date DESC, id DESC
LIMIT ?
`)
.all(...params);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function runCaseLawFtsQuery(db, ftsQuery, limit, filters) {
try {
const rows = db
.prepare(`
SELECT
c.id,
c.country,
c.case_id,
c.ecli,
c.court,
c.decision_date,
c.file_number,
c.decision_type,
c.title,
c.citation,
c.source_url,
c.text_snippet,
c.metadata_json
FROM case_law_documents_fts f
JOIN case_law_documents c ON c.rowid = f.rowid
WHERE case_law_documents_fts MATCH ? ${filters.clause}
ORDER BY bm25(case_law_documents_fts), c.decision_date DESC
LIMIT ?
`)
.all(ftsQuery, ...filters.params, limit);
return rows;
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
function runCaseLawLikeQuery(db, query, limit, filters) {
const tokens = query
.normalize("NFC")
.split(/\s+/)
.map((token) => token.trim())
.filter((token) => token.length > 1);
const searchTerms = tokens.length > 0 ? tokens : [query.trim()];
const clauses = [];
const params = [];
for (const term of searchTerms) {
const like = `%${term}%`;
clauses.push("(title LIKE ? OR citation LIKE ? OR text_snippet LIKE ? OR file_number LIKE ? OR court LIKE ? OR ecli LIKE ?)");
params.push(like, like, like, like, like, like);
}
const queryClause = clauses.length > 0 ? clauses.join(" AND ") : "1 = 0";
try {
return db
.prepare(`
SELECT
id,
country,
case_id,
ecli,
court,
decision_date,
file_number,
decision_type,
title,
citation,
source_url,
text_snippet,
metadata_json
FROM case_law_documents
WHERE (${queryClause}) ${filters.clause}
ORDER BY decision_date DESC, id DESC
LIMIT ?
`)
.all(...params, ...filters.params, limit);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function runPreparatoryFtsQuery(db, ftsQuery, limit, filters) {
try {
const rows = db
.prepare(`
SELECT
p.id,
p.country,
p.dip_id,
p.title,
p.statute_id,
p.statute_citation,
p.work_type,
p.publication_date,
p.source_url,
p.text_snippet,
p.metadata_json
FROM preparatory_works_fts f
JOIN preparatory_works p ON p.rowid = f.rowid
WHERE preparatory_works_fts MATCH ? ${filters.clause}
ORDER BY bm25(preparatory_works_fts), p.publication_date DESC
LIMIT ?
`)
.all(ftsQuery, ...filters.params, limit);
return rows;
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return null;
}
}
function runPreparatoryLikeQuery(db, hints, limit, filters) {
const tokens = hints.length > 0 ? tokenizeHints(hints) : [];
const clauses = [];
const params = [];
for (const token of tokens) {
const like = `%${token}%`;
clauses.push("(p.title LIKE ? OR p.statute_citation LIKE ? OR p.text_snippet LIKE ?)");
params.push(like, like, like);
}
const queryClause = clauses.length > 0 ? clauses.join(" AND ") : "1 = 0";
try {
return db
.prepare(`
SELECT
p.id,
p.country,
p.dip_id,
p.title,
p.statute_id,
p.statute_citation,
p.work_type,
p.publication_date,
p.source_url,
p.text_snippet,
p.metadata_json
FROM preparatory_works p
WHERE (${queryClause}) ${filters.clause}
ORDER BY publication_date DESC, id DESC
LIMIT ?
`)
.all(...params, ...filters.params, limit);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function runPreparatoryFilteredQuery(db, limit, filters) {
try {
return db
.prepare(`
SELECT
p.id,
p.country,
p.dip_id,
p.title,
p.statute_id,
p.statute_citation,
p.work_type,
p.publication_date,
p.source_url,
p.text_snippet,
p.metadata_json
FROM preparatory_works p
WHERE 1 = 1 ${filters.clause}
ORDER BY publication_date DESC, id DESC
LIMIT ?
`)
.all(...filters.params, limit);
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return [];
}
}
function buildPreparatorySearchHints(request) {
const hints = [];
if (request.query) {
hints.push(request.query);
}
if (request.statuteId) {
hints.push(request.statuteId);
}
if (request.citation) {
hints.push(request.citation);
const parsedCitation = parseGermanCitation(request.citation);
if (parsedCitation?.normalized) {
hints.push(parsedCitation.normalized);
}
const code = parsedCitation?.parsed.code;
if (code && typeof code === "string") {
hints.push(code);
}
}
return dedupeStrings(hints);
}
function buildCaseFilterSql(filters) {
const clauses = [];
const params = [];
if (filters.court?.trim()) {
clauses.push("AND court LIKE ?");
params.push(`%${filters.court.trim()}%`);
}
const dateFrom = normalizeIsoDate(filters.dateFrom);
if (dateFrom) {
clauses.push("AND decision_date >= ?");
params.push(dateFrom);
}
const dateTo = normalizeIsoDate(filters.dateTo);
if (dateTo) {
clauses.push("AND decision_date <= ?");
params.push(dateTo);
}
return {
clause: clauses.length > 0 ? ` ${clauses.join(" ")} ` : "",
params,
};
}
function buildPreparatoryFilterSql(request) {
const clauses = [];
const params = [];
if (request.statuteId?.trim()) {
clauses.push("AND lower(p.statute_id) = ?");
params.push(request.statuteId.trim().toLowerCase());
}
if (request.citation?.trim()) {
const parsed = parseGermanCitation(request.citation);
const tokens = dedupeStrings([
request.citation,
parsed?.normalized ?? "",
parsed?.parsed.code ?? "",
])
.map((token) => token.toLowerCase())
.filter((token) => token.length > 1);
if (tokens.length > 0) {
const tokenClauses = [];
for (const token of tokens) {
// Use table-qualified names to avoid ambiguity in FTS JOIN queries
tokenClauses.push("(lower(p.statute_citation) LIKE ? OR lower(p.title) LIKE ? OR lower(p.text_snippet) LIKE ?)");
const like = `%${token}%`;
params.push(like, like, like);
}
clauses.push(`AND (${tokenClauses.join(" OR ")})`);
}
}
return {
clause: clauses.length > 0 ? ` ${clauses.join(" ")} ` : "",
params,
};
}
function normalizeIsoDate(value) {
if (!value) {
return null;
}
const trimmed = value.trim();
if (!trimmed) {
return null;
}
if (!/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
return null;
}
return trimmed;
}
function tokenizeHints(hints) {
const tokens = hints
.flatMap((hint) => hint
.normalize("NFC")
.split(/\s+/)
.map((token) => token.trim()))
.filter((token) => token.length > 1);
return dedupeStrings(tokens);
}
function existsByCitation(db, citations) {
const values = dedupeStrings(citations).map((value) => value.toLowerCase());
if (values.length === 0) {
return false;
}
const placeholders = values.map(() => "?").join(", ");
const row = db
.prepare(`
SELECT 1 AS hit
FROM law_documents
WHERE lower(citation) IN (${placeholders})
LIMIT 1
`)
.get(...values);
return Boolean(row?.hit);
}
function pushUniqueRows(target, seen, rows, limit) {
for (const row of rows) {
if (seen.has(row.id)) {
continue;
}
seen.add(row.id);
target.push(row);
if (target.length >= limit) {
return;
}
}
}
function dedupeStrings(values) {
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
}
function mapStatuteRowToLawDocument(row) {
return {
id: row.id,
country: row.country,
kind: mapKind(row.kind),
title: row.title,
...(row.citation ? { citation: row.citation } : {}),
...(row.source_url ? { sourceUrl: row.source_url } : {}),
...(row.effective_date ? { effectiveDate: row.effective_date } : {}),
...(row.text_snippet ? { textSnippet: row.text_snippet } : {}),
...(row.metadata_json ? { metadata: parseMetadata(row.metadata_json) } : {}),
};
}
function mapCaseLawRowToLawDocument(row) {
const metadata = {
...(row.metadata_json ? parseMetadata(row.metadata_json) : {}),
...(row.case_id ? { case_id: row.case_id } : {}),
...(row.ecli ? { ecli: row.ecli } : {}),
...(row.court ? { court: row.court } : {}),
...(row.file_number ? { file_number: row.file_number } : {}),
...(row.decision_type ? { decision_type: row.decision_type } : {}),
};
return {
id: row.id,
country: row.country,
kind: "case",
title: row.title,
...(row.citation ? { citation: row.citation } : {}),
...(row.source_url ? { sourceUrl: row.source_url } : {}),
...(row.decision_date ? { effectiveDate: row.decision_date } : {}),
...(row.text_snippet ? { textSnippet: row.text_snippet } : {}),
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
};
}
function mapPreparatoryWorkRowToLawDocument(row) {
const metadata = {
...(row.metadata_json ? parseMetadata(row.metadata_json) : {}),
dip_id: row.dip_id,
...(row.statute_id ? { statute_id: row.statute_id } : {}),
...(row.work_type ? { work_type: row.work_type } : {}),
};
return {
id: row.id,
country: row.country,
kind: "preparatory_work",
title: row.title,
...(row.statute_citation ? { citation: row.statute_citation } : {}),
...(row.source_url ? { sourceUrl: row.source_url } : {}),
...(row.publication_date ? { effectiveDate: row.publication_date } : {}),
...(row.text_snippet ? { textSnippet: row.text_snippet } : {}),
metadata,
};
}
function mapKind(value) {
const kind = value.toLowerCase();
if (kind === "statute" ||
kind === "regulation" ||
kind === "case" ||
kind === "preparatory_work") {
return kind;
}
return "other";
}
function parseMetadata(raw) {
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return {};
}
return parsed;
}
catch (err) {
console.error("[german-law-mcp] DB query error:", err instanceof Error ? err.message : err);
return {};
}
}
/**
* Deduplicate LawDocument results by title + citation.
* Same provision with different IDs (numeric vs slug) appears as duplicates;
* this keeps the first (highest-ranked) occurrence per unique title+citation.
*/
function deduplicateDocuments(documents, limit) {
const seen = new Set();
const deduped = [];
for (const doc of documents) {
const key = `${doc.title}::${doc.citation ?? ""}`;
if (seen.has(key))
continue;
seen.add(key);
deduped.push(doc);
if (deduped.length >= limit)
break;
}
return deduped;
}
/**
* Resolve a statute identifier from user input.
* Accepts a statute_id directly, or a title / abbreviation.
* Returns the statute_id string if found, or null if no match.
*/
function resolveDocumentStatuteId(db, input) {
const trimmed = input.trim();
if (!trimmed)
return null;
// Step 1: Direct statute_id match
try {
const direct = db
.prepare("SELECT statute_id FROM law_documents WHERE lower(statute_id) = ? LIMIT 1")
.get(trimmed.toLowerCase());
if (direct)
return direct.statute_id;
}
catch {
// table/column may not exist
}
// Step 2: Direct ID match
try {
const byId = db
.prepare("SELECT statute_id FROM law_documents WHERE lower(id) = ? AND statute_id IS NOT NULL LIMIT 1")
.get(trimmed.toLowerCase());
if (byId)
return byId.statute_id;
}
catch {
// ignore
}
// Step 3: Title match (exact, case-insensitive)
try {
const byTitle = db
.prepare("SELECT statute_id FROM law_documents WHERE lower(title) = ? AND statute_id IS NOT NULL LIMIT 1")
.get(trimmed.toLowerCase());
if (byTitle)
return byTitle.statute_id;
}
catch {
// ignore
}
// Step 4: Substring title match — shortest wins
try {
const likeRows = db
.prepare("SELECT statute_id, title FROM law_documents WHERE lower(title) LIKE ? AND statute_id IS NOT NULL")
.all(`%${trimmed.toLowerCase()}%`);
if (likeRows.length > 0) {
likeRows.sort((a, b) => a.title.length - b.title.length);
return likeRows[0]?.statute_id ?? null;
}
}
catch {
// ignore
}
return null;
}
/**
* Check whether a statute row matches the resolved statute_id.
*/
function matchesStatuteId(row, statuteId) {
if (!row.id)
return false;
const colonIndex = row.id.indexOf(":");
if (colonIndex > 0) {
return row.id.slice(0, colonIndex).toLowerCase() === statuteId.toLowerCase();
}
return row.id.toLowerCase().startsWith(statuteId.toLowerCase());
}
function clampLimit(limit) {
if (!Number.isFinite(limit) || Number.isNaN(limit)) {
return 20;
}
if (limit < 1) {
return 1;
}
if (limit > 100) {
return 100;
}
return Math.trunc(limit);
}