@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
JavaScript
/**
* 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