Skip to main content
Glama
ccchow

Washington Law MCP Server

by ccchow
database.ts7.95 kB
import Database from 'better-sqlite3'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { RCWSection, WACSection, SearchResult } from '../types.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const DB_PATH = join(__dirname, '../../data/washington-laws.db'); export class LawDatabase { private db: Database.Database; constructor() { this.db = new Database(DB_PATH, { readonly: true }); this.db.pragma('journal_mode = WAL'); } getRCW(citation: string): RCWSection | null { const stmt = this.db.prepare(` SELECT citation, title_num as titleNum, chapter_num as chapterNum, section_num as sectionNum, title_name as titleName, chapter_name as chapterName, section_name as sectionName, full_text as fullText, effective_date as effectiveDate, last_amended as lastAmended FROM rcw WHERE citation = ? `); const result = stmt.get(citation) as RCWSection | undefined; return result || null; } getWAC(citation: string): WACSection | null { const stmt = this.db.prepare(` SELECT citation, title_num as titleNum, chapter_num as chapterNum, section_num as sectionNum, title_name as titleName, chapter_name as chapterName, section_name as sectionName, full_text as fullText, effective_date as effectiveDate FROM wac WHERE citation = ? `); const result = stmt.get(citation) as WACSection | undefined; return result || null; } getCourtRule(ruleSet: string, ruleNumber: string): any | null { // First try exact match let stmt = this.db.prepare(` SELECT rule_set as ruleSet, rule_number as ruleNumber, rule_name as ruleName, full_text as fullText, updated_at as updatedAt FROM court_rules WHERE rule_set = ? AND rule_number = ? `); let result = stmt.get(ruleSet, ruleNumber); // If no exact match and number doesn't contain a decimal, try adding .0 if (!result && !ruleNumber.includes('.')) { result = stmt.get(ruleSet, ruleNumber + '.0'); } return result || null; } listCourtRules(ruleSet?: string): Array<{ ruleSet: string; ruleNumber: string; ruleName: string }> { let stmt; if (ruleSet) { stmt = this.db.prepare(` SELECT rule_set as ruleSet, rule_number as ruleNumber, rule_name as ruleName FROM court_rules WHERE rule_set = ? ORDER BY CAST(SUBSTR(rule_number, 1, INSTR(rule_number || '.', '.') - 1) AS INTEGER), CAST(SUBSTR(rule_number, INSTR(rule_number || '.', '.') + 1) AS INTEGER) `); return stmt.all(ruleSet) as any[]; } else { stmt = this.db.prepare(` SELECT rule_set as ruleSet, rule_number as ruleNumber, rule_name as ruleName FROM court_rules ORDER BY rule_set, CAST(SUBSTR(rule_number, 1, INSTR(rule_number || '.', '.') - 1) AS INTEGER), CAST(SUBSTR(rule_number, INSTR(rule_number || '.', '.') + 1) AS INTEGER) `); return stmt.all() as any[]; } } searchLaws(query: string, limit: number = 20): SearchResult[] { const results: SearchResult[] = []; const perTypeLimit = Math.floor(limit / 3); // Search RCW const rcwStmt = this.db.prepare(` SELECT r.citation, r.title_name as titleName, r.chapter_name as chapterName, r.section_name as sectionName, snippet(rcw_fts, 4, '<b>', '</b>', '...', 64) as snippet, rank as score FROM rcw_fts JOIN rcw r ON rcw_fts.rowid = r.id WHERE rcw_fts MATCH ? ORDER BY rank LIMIT ? `); const rcwResults = rcwStmt.all(query, perTypeLimit) as any[]; for (const row of rcwResults) { results.push({ type: 'RCW', citation: row.citation, titleName: row.titleName, chapterName: row.chapterName, sectionName: row.sectionName, snippet: row.snippet, score: Math.abs(row.score) }); } // Search WAC const wacStmt = this.db.prepare(` SELECT w.citation, w.title_name as titleName, w.chapter_name as chapterName, w.section_name as sectionName, snippet(wac_fts, 4, '<b>', '</b>', '...', 64) as snippet, rank as score FROM wac_fts JOIN wac w ON wac_fts.rowid = w.id WHERE wac_fts MATCH ? ORDER BY rank LIMIT ? `); const wacResults = wacStmt.all(query, perTypeLimit) as any[]; for (const row of wacResults) { results.push({ type: 'WAC', citation: row.citation, titleName: row.titleName, chapterName: row.chapterName, sectionName: row.sectionName, snippet: row.snippet, score: Math.abs(row.score) }); } // Search Court Rules try { const courtRulesStmt = this.db.prepare(` SELECT cr.rule_set || ' ' || cr.rule_number as citation, cr.rule_set as titleName, cr.rule_name as sectionName, snippet(court_rules_fts, 3, '<b>', '</b>', '...', 64) as snippet, rank as score FROM court_rules_fts JOIN court_rules cr ON court_rules_fts.rowid = cr.id WHERE court_rules_fts MATCH ? ORDER BY rank LIMIT ? `); const courtResults = courtRulesStmt.all(query, perTypeLimit) as any[]; for (const row of courtResults) { results.push({ type: 'Court Rule' as any, citation: row.citation, titleName: row.titleName, chapterName: '', sectionName: row.sectionName, snippet: row.snippet, score: Math.abs(row.score) }); } } catch (e) { // Court rules table might not exist in older databases } // Sort combined results by score results.sort((a, b) => (b.score || 0) - (a.score || 0)); return results.slice(0, limit); } listRCWTitles(): Array<{ titleNum: string; titleName: string; count: number }> { const stmt = this.db.prepare(` SELECT title_num as titleNum, title_name as titleName, COUNT(*) as count FROM rcw GROUP BY title_num, title_name ORDER BY CAST(title_num AS INTEGER), title_num `); return stmt.all() as any[]; } listRCWChapters(titleNum: string): Array<{ chapterNum: string; chapterName: string; count: number }> { const stmt = this.db.prepare(` SELECT chapter_num as chapterNum, chapter_name as chapterName, COUNT(*) as count FROM rcw WHERE title_num = ? GROUP BY chapter_num, chapter_name ORDER BY chapter_num `); return stmt.all(titleNum) as any[]; } listRCWSections(chapterNum: string): Array<{ citation: string; sectionName: string }> { const stmt = this.db.prepare(` SELECT citation, section_name as sectionName FROM rcw WHERE chapter_num = ? ORDER BY citation `); return stmt.all(chapterNum) as any[]; } getStatistics(): { rcwCount: number; wacCount: number; courtRulesCount: number; lastUpdate: string } { const stats = this.db.prepare(` SELECT (SELECT COUNT(*) FROM rcw) as rcwCount, (SELECT COUNT(*) FROM wac) as wacCount, (SELECT COUNT(*) FROM court_rules) as courtRulesCount, (SELECT value FROM metadata WHERE key = 'last_update') as lastUpdate `).get() as any; return { rcwCount: stats.rcwCount || 0, wacCount: stats.wacCount || 0, courtRulesCount: stats.courtRulesCount || 0, lastUpdate: stats.lastUpdate || 'Unknown' }; } close(): void { this.db.close(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ccchow/washington-law-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server