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:
102
src/index.ts
Normal file
102
src/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Israel Law MCP Server -- stdio entry point.
|
||||
*
|
||||
* Provides Israeli legislation search via Model Context Protocol.
|
||||
*/
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import Database from '@ansvar/mcp-sqlite';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createHash } from 'crypto';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { registerTools, type AboutContext } from './tools/registry.js';
|
||||
import { detectCapabilities, readDbMetadata } from './capabilities.js';
|
||||
import {
|
||||
DB_ENV_VAR,
|
||||
SERVER_NAME,
|
||||
SERVER_VERSION,
|
||||
} from './constants.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function resolveDbPath(): string {
|
||||
if (process.env[DB_ENV_VAR]) {
|
||||
return process.env[DB_ENV_VAR];
|
||||
}
|
||||
return join(__dirname, '..', 'data', 'database.db');
|
||||
}
|
||||
|
||||
let db: InstanceType<typeof Database> | null = null;
|
||||
|
||||
function getDb(): InstanceType<typeof Database> {
|
||||
if (!db) {
|
||||
const dbPath = resolveDbPath();
|
||||
db = new Database(dbPath, { readonly: true });
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
const caps = detectCapabilities(db);
|
||||
const meta = readDbMetadata(db);
|
||||
console.error(`[${SERVER_NAME}] DB opened: tier=${meta.tier}, caps=[${[...caps].join(',')}]`);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
function computeAboutContext(): AboutContext {
|
||||
const dbPath = resolveDbPath();
|
||||
let fingerprint = 'unknown';
|
||||
let dbBuilt = 'unknown';
|
||||
|
||||
try {
|
||||
const buf = readFileSync(dbPath);
|
||||
fingerprint = createHash('sha256').update(buf).digest('hex').slice(0, 12);
|
||||
} catch {
|
||||
// DB might not exist in dev
|
||||
}
|
||||
|
||||
try {
|
||||
const database = getDb();
|
||||
const row = database.prepare("SELECT value FROM db_metadata WHERE key = 'built_at'").get() as { value: string } | undefined;
|
||||
if (row) dbBuilt = row.value;
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return { version: SERVER_VERSION, fingerprint, dbBuilt };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const database = getDb();
|
||||
const aboutContext = computeAboutContext();
|
||||
|
||||
const server = new Server(
|
||||
{ name: SERVER_NAME, version: SERVER_VERSION },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
registerTools(server, database, aboutContext);
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error(`[${SERVER_NAME}] Server running on stdio`);
|
||||
|
||||
const cleanup = () => {
|
||||
if (db) {
|
||||
db.close();
|
||||
db = null;
|
||||
}
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', cleanup);
|
||||
process.on('SIGTERM', cleanup);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`[${SERVER_NAME}] Fatal error:`, err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user