feat: golden-ify Israel Law MCP — census, skipIf tests, dual transport
Add data/census.json (10 laws, 135 provisions, jurisdiction IL) generated from database.db. Rewrite golden.test.ts to golden standard pattern with describe.skipIf guards so tests skip gracefully in CI without DB artifacts. Update server.json to packages format with streamable-http Vercel endpoint. Bump version to 1.1.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -7,7 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.0] - 2026-XX-XX
|
||||
## [1.1.0] - 2026-02-22
|
||||
### Added
|
||||
- `data/census.json` — full law census (10 laws, 135 provisions, jurisdiction IL)
|
||||
- Dual transport in `server.json` (stdio + streamable-http via Vercel)
|
||||
- Census consistency tests validating DB matches census
|
||||
- `describe.skipIf` guards on all DB-dependent test suites (CI-safe)
|
||||
|
||||
### Changed
|
||||
- Rewrote `__tests__/contract/golden.test.ts` to golden standard pattern
|
||||
- DB integrity, key law presence, provision retrieval, FTS search, negative tests
|
||||
- All describe blocks skip gracefully when `data/database.db` is absent
|
||||
- Updated `server.json` to `packages` format with Vercel endpoint
|
||||
|
||||
## [1.0.0] - 2026-02-19
|
||||
### Added
|
||||
- Initial release of Israel Law MCP
|
||||
- `search_legislation` tool for full-text search across all Israeli statutes
|
||||
@@ -23,5 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- npm package with stdio transport
|
||||
- MCP Registry publishing
|
||||
|
||||
[Unreleased]: https://github.com/Ansvar-Systems/israel-law-mcp/compare/v1.0.0...HEAD
|
||||
[Unreleased]: https://github.com/Ansvar-Systems/israel-law-mcp/compare/v1.1.0...HEAD
|
||||
[1.1.0]: https://github.com/Ansvar-Systems/israel-law-mcp/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/Ansvar-Systems/israel-law-mcp/releases/tag/v1.0.0
|
||||
|
||||
@@ -1,73 +1,198 @@
|
||||
/**
|
||||
* Golden contract tests for Israel Law MCP.
|
||||
* Validates DB integrity for full official-portal ingestion.
|
||||
*
|
||||
* Tests tool outputs against the golden-tests.json fixture file.
|
||||
* These tests verify that the MCP server returns expected data
|
||||
* for well-known Israeli legal provisions.
|
||||
* Skipped automatically when the database file is absent (e.g. CI without artifacts).
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import Database from 'better-sqlite3';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const fixturesPath = join(__dirname, '../../fixtures/golden-tests.json');
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const DB_PATH = path.resolve(__dirname, '../../data/database.db');
|
||||
const CENSUS_PATH = path.resolve(__dirname, '../../data/census.json');
|
||||
const FIXTURE_PATH = path.resolve(__dirname, '../../fixtures/golden-tests.json');
|
||||
|
||||
interface GoldenTest {
|
||||
const DB_EXISTS = fs.existsSync(DB_PATH);
|
||||
|
||||
interface CensusLaw {
|
||||
id: string;
|
||||
category: string;
|
||||
description: string;
|
||||
tool: string;
|
||||
input: Record<string, unknown>;
|
||||
assertions: {
|
||||
result_not_empty?: boolean;
|
||||
any_result_contains?: string[];
|
||||
fields_present?: string[];
|
||||
text_not_empty?: boolean;
|
||||
min_results?: number;
|
||||
citation_url_pattern?: string;
|
||||
handles_gracefully?: boolean;
|
||||
};
|
||||
title: string;
|
||||
provisions: number;
|
||||
}
|
||||
|
||||
interface GoldenFixture {
|
||||
version: string;
|
||||
mcp_name: string;
|
||||
tests: GoldenTest[];
|
||||
interface Census {
|
||||
schema_version: string;
|
||||
jurisdiction: string;
|
||||
total_laws: number;
|
||||
total_provisions: number;
|
||||
laws: CensusLaw[];
|
||||
}
|
||||
|
||||
const fixture: GoldenFixture = JSON.parse(readFileSync(fixturesPath, 'utf-8'));
|
||||
let db: InstanceType<typeof Database>;
|
||||
let census: Census;
|
||||
|
||||
describe('Golden contract tests', () => {
|
||||
it('fixture file is valid', () => {
|
||||
describe.skipIf(!DB_EXISTS)('Database integrity', () => {
|
||||
beforeAll(() => {
|
||||
db = new Database(DB_PATH, { readonly: true });
|
||||
db.pragma('journal_mode = DELETE');
|
||||
census = JSON.parse(fs.readFileSync(CENSUS_PATH, 'utf-8')) as Census;
|
||||
});
|
||||
|
||||
it('should have correct number of legal documents', () => {
|
||||
const row = db.prepare('SELECT COUNT(*) as cnt FROM legal_documents').get() as { cnt: number };
|
||||
expect(row.cnt).toBe(census.total_laws);
|
||||
});
|
||||
|
||||
it('should have correct total provision count', () => {
|
||||
const row = db.prepare('SELECT COUNT(*) as cnt FROM legal_provisions').get() as { cnt: number };
|
||||
expect(row.cnt).toBe(census.total_provisions);
|
||||
});
|
||||
|
||||
it('should have FTS index populated', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM provisions_fts WHERE provisions_fts MATCH 'privacy OR פרטיות'"
|
||||
).get() as { cnt: number };
|
||||
expect(row.cnt).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have db_metadata table populated', () => {
|
||||
const row = db.prepare('SELECT COUNT(*) as cnt FROM db_metadata').get() as { cnt: number };
|
||||
expect(row.cnt).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!DB_EXISTS)('All key laws are present', () => {
|
||||
beforeAll(() => {
|
||||
if (!db) {
|
||||
db = new Database(DB_PATH, { readonly: true });
|
||||
db.pragma('journal_mode = DELETE');
|
||||
}
|
||||
});
|
||||
|
||||
const expectedDocs = [
|
||||
'privacy-protection-law-1981',
|
||||
'data-security-regulations-2017',
|
||||
'computer-law-1995',
|
||||
'companies-law-1999',
|
||||
'electronic-signature-law-2001',
|
||||
'credit-data-law-2002',
|
||||
'freedom-of-information-law-1998',
|
||||
'communications-law-1982',
|
||||
'basic-law-human-dignity-1992',
|
||||
'regulation-of-security-1998',
|
||||
];
|
||||
|
||||
for (const docId of expectedDocs) {
|
||||
it(`should contain document: ${docId}`, () => {
|
||||
const row = db.prepare('SELECT id FROM legal_documents WHERE id = ?').get(docId) as { id: string } | undefined;
|
||||
expect(row).toBeDefined();
|
||||
expect(row!.id).toBe(docId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe.skipIf(!DB_EXISTS)('Provision retrieval and search', () => {
|
||||
beforeAll(() => {
|
||||
if (!db) {
|
||||
db = new Database(DB_PATH, { readonly: true });
|
||||
db.pragma('journal_mode = DELETE');
|
||||
}
|
||||
});
|
||||
|
||||
it('should retrieve section 1 from Privacy Protection Law 1981', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT content FROM legal_provisions WHERE document_id = 'privacy-protection-law-1981' AND section = '1'"
|
||||
).get() as { content: string } | undefined;
|
||||
|
||||
expect(row).toBeDefined();
|
||||
expect(row!.content.length).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
it('should retrieve section 1 from Computer Law 1995', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT content FROM legal_provisions WHERE document_id = 'computer-law-1995' AND section = '1'"
|
||||
).get() as { content: string } | undefined;
|
||||
|
||||
expect(row).toBeDefined();
|
||||
expect(row!.content.length).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
it('should find results via FTS search for database/מאגר', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM provisions_fts WHERE provisions_fts MATCH 'database OR מאגר'"
|
||||
).get() as { cnt: number };
|
||||
expect(row.cnt).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!DB_EXISTS)('Census consistency', () => {
|
||||
beforeAll(() => {
|
||||
if (!db) {
|
||||
db = new Database(DB_PATH, { readonly: true });
|
||||
db.pragma('journal_mode = DELETE');
|
||||
}
|
||||
if (!census) {
|
||||
census = JSON.parse(fs.readFileSync(CENSUS_PATH, 'utf-8')) as Census;
|
||||
}
|
||||
});
|
||||
|
||||
it('census law count matches database document count', () => {
|
||||
const row = db.prepare('SELECT COUNT(*) as cnt FROM legal_documents').get() as { cnt: number };
|
||||
expect(row.cnt).toBe(census.laws.length);
|
||||
});
|
||||
|
||||
it('each census law exists in database', () => {
|
||||
for (const law of census.laws) {
|
||||
const row = db.prepare('SELECT id FROM legal_documents WHERE id = ?').get(law.id) as { id: string } | undefined;
|
||||
expect(row, `Missing law: ${law.id}`).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('provision counts match census per law', () => {
|
||||
for (const law of census.laws) {
|
||||
const row = db.prepare(
|
||||
'SELECT COUNT(*) as cnt FROM legal_provisions WHERE document_id = ?'
|
||||
).get(law.id) as { cnt: number };
|
||||
expect(row.cnt, `Mismatch for ${law.id}`).toBe(law.provisions);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!DB_EXISTS)('Negative tests', () => {
|
||||
beforeAll(() => {
|
||||
if (!db) {
|
||||
db = new Database(DB_PATH, { readonly: true });
|
||||
db.pragma('journal_mode = DELETE');
|
||||
}
|
||||
});
|
||||
|
||||
it('should return no results for fictional document', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM legal_provisions WHERE document_id = 'fictional-law-2099'"
|
||||
).get() as { cnt: number };
|
||||
expect(row.cnt).toBe(0);
|
||||
});
|
||||
|
||||
it('should return no results for invalid section', () => {
|
||||
const row = db.prepare(
|
||||
"SELECT COUNT(*) as cnt FROM legal_provisions WHERE document_id = 'privacy-protection-law-1981' AND section = '999ZZZ-INVALID'"
|
||||
).get() as { cnt: number };
|
||||
expect(row.cnt).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Golden fixture file validation', () => {
|
||||
const HAS_FIXTURE = fs.existsSync(FIXTURE_PATH);
|
||||
|
||||
it.skipIf(!HAS_FIXTURE)('fixture file is valid', () => {
|
||||
const fixture = JSON.parse(fs.readFileSync(FIXTURE_PATH, 'utf-8'));
|
||||
expect(fixture.version).toBe('1.0');
|
||||
expect(fixture.mcp_name).toBe('Israel Law MCP');
|
||||
expect(fixture.tests.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
for (const test of fixture.tests) {
|
||||
it(`${test.id}: ${test.description}`, () => {
|
||||
// Contract tests validate the fixture structure.
|
||||
// Full integration tests require a running server with a database.
|
||||
// In CI, these run in CONTRACT_MODE=nightly for live assertions.
|
||||
|
||||
expect(test.id).toBeTruthy();
|
||||
expect(test.tool).toBeTruthy();
|
||||
expect(test.assertions).toBeTruthy();
|
||||
|
||||
// Validate assertion structure
|
||||
if (test.assertions.any_result_contains) {
|
||||
expect(Array.isArray(test.assertions.any_result_contains)).toBe(true);
|
||||
}
|
||||
|
||||
if (test.assertions.fields_present) {
|
||||
expect(Array.isArray(test.assertions.fields_present)).toBe(true);
|
||||
}
|
||||
|
||||
if (test.assertions.min_results !== undefined) {
|
||||
expect(typeof test.assertions.min_results).toBe('number');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
60
data/census.json
Normal file
60
data/census.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"jurisdiction": "IL",
|
||||
"portal": "knesset.gov.il + gov.il",
|
||||
"generated": "2026-02-22",
|
||||
"total_laws": 10,
|
||||
"total_provisions": 135,
|
||||
"laws": [
|
||||
{
|
||||
"id": "basic-law-human-dignity-1992",
|
||||
"title": "חוק יסוד: כבוד האדם וחירותו",
|
||||
"provisions": 13
|
||||
},
|
||||
{
|
||||
"id": "communications-law-1982",
|
||||
"title": "חוק התקשורת (בזק ושידורים), תשמ\"ב-1982",
|
||||
"provisions": 6
|
||||
},
|
||||
{
|
||||
"id": "companies-law-1999",
|
||||
"title": "חוק החברות, תשנ\"ט-1999",
|
||||
"provisions": 6
|
||||
},
|
||||
{
|
||||
"id": "computer-law-1995",
|
||||
"title": "חוק המחשבים, תשנ\"ה-1995",
|
||||
"provisions": 21
|
||||
},
|
||||
{
|
||||
"id": "credit-data-law-2002",
|
||||
"title": "חוק נתוני אשראי, תס\"ב-2002",
|
||||
"provisions": 6
|
||||
},
|
||||
{
|
||||
"id": "data-security-regulations-2017",
|
||||
"title": "תקנות הגנת הפרטיות (אבטחת מידע), תשע\"ז-2017",
|
||||
"provisions": 11
|
||||
},
|
||||
{
|
||||
"id": "electronic-signature-law-2001",
|
||||
"title": "חוק חתימה אלקטרונית, תס\"א-2001",
|
||||
"provisions": 6
|
||||
},
|
||||
{
|
||||
"id": "freedom-of-information-law-1998",
|
||||
"title": "חוק חופש המידע, תשנ\"ח-1998",
|
||||
"provisions": 6
|
||||
},
|
||||
{
|
||||
"id": "privacy-protection-law-1981",
|
||||
"title": "חוק הגנת הפרטיות, תשמ\"א-1981",
|
||||
"provisions": 55
|
||||
},
|
||||
{
|
||||
"id": "regulation-of-security-1998",
|
||||
"title": "חוק הסדרת האבטחה בגופים ציבוריים, תשנ\"ח-1998",
|
||||
"provisions": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ansvar/israel-law-mcp",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"mcpName": "eu.ansvar/israel-law-mcp",
|
||||
"description": "Israel law database covering Privacy Protection Law, Data Security Regulations, Computer Law, Companies Law, Electronic Signature Law, and Credit Data Law with full-text search",
|
||||
"author": "Ansvar Systems AB <hello@ansvar.ai>",
|
||||
|
||||
19
server.json
19
server.json
@@ -1,20 +1,31 @@
|
||||
{
|
||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||
"name": "eu.ansvar/israel-law-mcp",
|
||||
"description": "Israel law database covering Privacy Protection Law, Data Security Regulations, Computer Law, Companies Law, Electronic Signature Law, and Credit Data Law with full-text search",
|
||||
"description": "Israel legislation via MCP — full-text search across statutes and provisions",
|
||||
"repository": {
|
||||
"url": "https://github.com/Ansvar-Systems/israel-law-mcp",
|
||||
"source": "github"
|
||||
},
|
||||
"homepage": "https://ansvar.eu",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "npm",
|
||||
"identifier": "@ansvar/israel-law-mcp",
|
||||
"version": "1.0.0",
|
||||
"transport": { "type": "stdio" }
|
||||
"version": "1.1.0",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"registryType": "npm",
|
||||
"identifier": "@ansvar/israel-law-mcp",
|
||||
"version": "1.1.0",
|
||||
"transport": {
|
||||
"type": "streamable-http",
|
||||
"url": "https://israel-law-mcp.vercel.app/mcp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const SERVER_NAME = 'israel-law-mcp';
|
||||
export const SERVER_VERSION = '1.0.0';
|
||||
export const SERVER_VERSION = '1.1.0';
|
||||
export const SERVER_LABEL = 'Israel Law MCP';
|
||||
export const PACKAGE_NAME = '@ansvar/israel-law-mcp';
|
||||
export const REPOSITORY_URL = 'https://github.com/Ansvar-Systems/israel-law-mcp';
|
||||
|
||||
Reference in New Issue
Block a user