UNPKG

@ansvar/singapore-law-mcp

Version:

Complete Singapore law database — 523 Acts, 28K+ provisions from Singapore Statutes Online (sso.agc.gov.sg) with full-text search, definitions, and citation support

365 lines 17.1 kB
/** * Tool registry for Singapore Law MCP Server. * Shared between stdio (index.ts) and HTTP (api/mcp.ts) entry points. */ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { searchLegislation } from './search-legislation.js'; import { getProvision } from './get-provision.js'; import { validateCitationTool } from './validate-citation.js'; import { buildLegalStance } from './build-legal-stance.js'; import { formatCitationTool } from './format-citation.js'; import { checkCurrency } from './check-currency.js'; import { getEUBasis } from './get-eu-basis.js'; import { getSingaporeImplementations } from './get-singapore-implementations.js'; import { searchEUImplementations } from './search-eu-implementations.js'; import { getProvisionEUBasis } from './get-provision-eu-basis.js'; import { validateEUCompliance } from './validate-eu-compliance.js'; import { listSources } from './list-sources.js'; import { getAbout } from './about.js'; const ABOUT_TOOL = { name: 'about', description: 'Server metadata, dataset statistics, freshness, and provenance. ' + 'Call this to verify data coverage, currency, and content basis before relying on results.', inputSchema: { type: 'object', properties: {} }, }; const LIST_SOURCES_TOOL = { name: 'list_sources', description: 'Returns detailed provenance metadata for all data sources used by this server, ' + 'including Singapore Statutes Online (Attorney-General\'s Chambers). ' + 'Use this to understand what data is available, its authority, coverage scope, and known limitations. ' + 'Also returns dataset statistics (document counts, provision counts) and database build timestamp. ' + 'Call this FIRST when you need to understand what Singapore legal data this server covers.', inputSchema: { type: 'object', properties: {} }, }; export const TOOLS = [ { name: 'search_legislation', description: 'Search Singapore statutes and regulations by keyword using full-text search (FTS5 with BM25 ranking). ' + 'Returns matching provisions with document context, snippets with >>> <<< markers around matched terms, and relevance scores. ' + 'Supports FTS5 syntax: quoted phrases ("exact match"), boolean operators (AND, OR, NOT), and prefix wildcards (term*). ' + 'Results are in English (Singapore\'s sole legislative language). ' + 'Default limit is 10 results. For broad topics, increase the limit. ' + 'Do NOT use this for retrieving a known provision — use get_provision instead.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query in English. Supports FTS5 syntax: ' + '"personal data" for exact phrase, term* for prefix.', }, document_id: { type: 'string', description: 'Optional: filter results to a specific statute by its document ID.', }, status: { type: 'string', enum: ['in_force', 'amended', 'repealed'], description: 'Optional: filter by legislative status.', }, limit: { type: 'number', description: 'Maximum results to return (default: 10, max: 50).', default: 10, }, }, required: ['query'], }, }, { name: 'get_provision', description: 'Retrieve the full text of a specific provision (section) from a Singapore statute. ' + 'Specify a document_id (Act title, abbreviation, or internal ID) and optionally a section or provision_ref. ' + 'Omit section/provision_ref to get ALL provisions in the statute (use sparingly — can be large). ' + 'Returns provision text, chapter, section number, and metadata. ' + 'Supports Act title references (e.g., "Personal Data Protection Act 2012"), abbreviations (e.g., "PDPA", "CMA"). ' + 'Use this when you know WHICH provision you want. For discovery, use search_legislation instead.', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Statute identifier: Act title (e.g., "Personal Data Protection Act 2012"), ' + 'abbreviation (e.g., "PDPA", "CMA", "CA2018"), or internal document ID.', }, section: { type: 'string', description: 'Section number (e.g., "2", "26D"). Omit to get all provisions.', }, provision_ref: { type: 'string', description: 'Direct provision reference (e.g., "s2"). Alternative to section parameter.', }, }, required: ['document_id'], }, }, { name: 'validate_citation', description: 'Validate a Singapore legal citation against the database — zero-hallucination check. ' + 'Parses the citation, checks that the document and provision exist, and returns warnings about status ' + '(repealed, amended). Use this to verify any citation BEFORE including it in a legal analysis. ' + 'Supports formats: "Section 2 PDPA", "s 26D Personal Data Protection Act 2012".', inputSchema: { type: 'object', properties: { citation: { type: 'string', description: 'Citation string to validate. Examples: "Section 2 PDPA", "s 3 Cybersecurity Act 2018".', }, }, required: ['citation'], }, }, { name: 'build_legal_stance', description: 'Build a comprehensive set of citations for a legal question by searching across all Singapore statutes simultaneously. ' + 'Returns aggregated results from multiple relevant provisions, useful for legal research on a topic. ' + 'Use this for broad legal questions like "What are the penalties for data breaches in Singapore?" ' + 'rather than looking up a specific known provision.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Legal question or topic to research (e.g., "data breach notification", "cybersecurity").', }, document_id: { type: 'string', description: 'Optional: limit search to one statute by document ID.', }, limit: { type: 'number', description: 'Max results per category (default: 5, max: 20).', default: 5, }, }, required: ['query'], }, }, { name: 'format_citation', description: 'Format a Singapore legal citation per standard conventions. ' + 'Three formats: "full" (formal, e.g., "Section 2 Personal Data Protection Act 2012"), ' + '"short" (abbreviated, e.g., "s 2 PDPA"), "pinpoint" (section reference only, e.g., "s 2").', inputSchema: { type: 'object', properties: { citation: { type: 'string', description: 'Citation string to format.' }, format: { type: 'string', enum: ['full', 'short', 'pinpoint'], description: 'Output format (default: "full").', default: 'full', }, }, required: ['citation'], }, }, { name: 'check_currency', description: 'Check whether a Singapore statute or provision is currently in force, amended, repealed, or not yet in force. ' + 'Returns the document status, issued date, in-force date, and warnings. ' + 'Essential before citing any provision — always verify currency.', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Statute identifier (Act title, abbreviation, or internal ID).', }, provision_ref: { type: 'string', description: 'Optional: provision reference to check a specific section.', }, }, required: ['document_id'], }, }, { name: 'get_eu_basis', description: 'Get the EU/international legal basis that a Singapore statute aligns with or references. ' + 'Singapore is not an EU member but many Singapore laws align with EU regulations ' + '(e.g., PDPA aligns with GDPR, Cybersecurity Act aligns with NIS Directive). ' + 'Returns EU/international document identifiers, reference types, and implementation status.', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Singapore statute identifier.' }, include_articles: { type: 'boolean', description: 'Include specific EU article references (default: false).', default: false, }, }, required: ['document_id'], }, }, { name: 'get_singapore_implementations', description: 'Find all Singapore statutes that align with or implement a specific EU directive or regulation. ' + 'Given an EU document ID (e.g., "regulation:2016/679" for GDPR), returns matching Singapore statutes. ' + 'Note: Singapore implements international standards through autonomous alignment, not EU transposition.', inputSchema: { type: 'object', properties: { eu_document_id: { type: 'string', description: 'EU document ID (e.g., "regulation:2016/679" for GDPR, "directive:2016/1148" for NIS).', }, primary_only: { type: 'boolean', description: 'Return only primary implementing statutes (default: false).', default: false, }, in_force_only: { type: 'boolean', description: 'Return only currently in-force statutes (default: false).', default: false, }, }, required: ['eu_document_id'], }, }, { name: 'search_eu_implementations', description: 'Search for EU directives and regulations that have Singapore implementing/aligning legislation. ' + 'Search by keyword, type (directive/regulation), or year range.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Keyword search across EU document titles.' }, type: { type: 'string', enum: ['directive', 'regulation'], description: 'Filter by EU document type.' }, year_from: { type: 'number', description: 'Filter by year (from).' }, year_to: { type: 'number', description: 'Filter by year (to).' }, has_singapore_implementation: { type: 'boolean', description: 'If true, only return EU documents with Singapore aligning legislation.', }, limit: { type: 'number', description: 'Max results (default: 20, max: 100).', default: 20 }, }, }, }, { name: 'get_provision_eu_basis', description: 'Get the EU/international legal basis for a SPECIFIC provision within a Singapore statute. ' + 'More granular than get_eu_basis (which operates at the statute level). ' + 'Use this for pinpoint EU/international compliance checks at the provision level.', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Singapore statute identifier.' }, provision_ref: { type: 'string', description: 'Provision reference (e.g., "s2" or "2").' }, }, required: ['document_id', 'provision_ref'], }, }, { name: 'validate_eu_compliance', description: 'Check EU/international alignment status for a Singapore statute or provision. ' + 'Detects references to repealed EU directives, missing alignment status, outdated references. ' + 'Returns compliance status (compliant, partial, unclear, not_applicable) with warnings.', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Singapore statute identifier.' }, provision_ref: { type: 'string', description: 'Optional: check for a specific provision.' }, eu_document_id: { type: 'string', description: 'Optional: check against a specific EU document.' }, }, required: ['document_id'], }, }, ]; export function buildTools(db, context) { const tools = [...TOOLS, LIST_SOURCES_TOOL]; if (db) { try { db.prepare('SELECT 1 FROM definitions LIMIT 1').get(); // Could add a get_definitions tool here when definitions table exists } catch { // definitions table doesn't exist } } if (context) { tools.push(ABOUT_TOOL); } return tools; } export function registerTools(server, db, context) { const allTools = buildTools(db, context); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: allTools }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { case 'search_legislation': result = await searchLegislation(db, args); break; case 'get_provision': result = await getProvision(db, args); break; case 'validate_citation': result = await validateCitationTool(db, args); break; case 'build_legal_stance': result = await buildLegalStance(db, args); break; case 'format_citation': result = await formatCitationTool(args); break; case 'check_currency': result = await checkCurrency(db, args); break; case 'get_eu_basis': result = await getEUBasis(db, args); break; case 'get_singapore_implementations': result = await getSingaporeImplementations(db, args); break; case 'search_eu_implementations': result = await searchEUImplementations(db, args); break; case 'get_provision_eu_basis': result = await getProvisionEUBasis(db, args); break; case 'validate_eu_compliance': result = await validateEUCompliance(db, args); break; case 'list_sources': result = await listSources(db); break; case 'about': if (context) { result = getAbout(db, context); } else { return { content: [{ type: 'text', text: 'About tool not configured.' }], isError: true, }; } break; default: return { content: [{ type: 'text', text: `Error: Unknown tool "${name}".` }], isError: true, }; } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true, }; } }); } //# sourceMappingURL=registry.js.map