feat: production MCP server with Israeli legislation (multi-source)

Complete production implementation with shell+adapter architecture,
13 MCP tools, SQLite FTS5 search, and multi-source ingestion pipeline.

Ingestion fetches from UCI mirror, UNODC SHERLOC PDFs, and Knesset
mobile PDFs (135 provisions, 33 definitions). 3 acts with full text,
7 acts metadata-only due to gov.il/nevo.co.il access restrictions.
Knesset OData API used for metadata enrichment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mortalus
2026-02-19 20:40:01 +01:00
parent 21aa81d2b0
commit 1e28f8a6b1
41 changed files with 9136 additions and 51 deletions

52
src/utils/fts-query.ts Normal file
View File

@@ -0,0 +1,52 @@
/**
* FTS5 query helpers for Israel Law MCP.
*
* Handles query sanitization and variant generation for SQLite FTS5.
*/
/**
* Sanitize user input for safe FTS5 queries.
* Removes characters that have special meaning in FTS5 syntax.
*/
export function sanitizeFtsInput(input: string): string {
return input
.replace(/['"(){}[\]^~*:]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
/**
* Build FTS5 query variants for a search term.
* Returns variants in order of specificity (most specific first):
* 1. Exact phrase match
* 2. All terms required (AND)
* 3. Prefix match on last term
*/
export function buildFtsQueryVariants(sanitized: string): string[] {
if (!sanitized || sanitized.trim().length === 0) {
return [];
}
const terms = sanitized.split(/\s+/).filter(t => t.length > 0);
if (terms.length === 0) return [];
const variants: string[] = [];
// Exact phrase
if (terms.length > 1) {
variants.push(`"${terms.join(' ')}"`);
}
// AND query
variants.push(terms.join(' AND '));
// Prefix match on last term (for autocomplete-like behavior)
if (terms.length === 1 && terms[0].length >= 3) {
variants.push(`${terms[0]}*`);
} else if (terms.length > 1) {
const prefix = [...terms.slice(0, -1), `${terms[terms.length - 1]}*`];
variants.push(prefix.join(' AND '));
}
return variants;
}