@iflow-mcp/georgejeffers-uk-case-law-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ // src/formatters.ts
2
+ // ============================================================================
3
+ // OUTPUT FORMATTERS
4
+ // ============================================================================
5
+ //
6
+ // Format search results and case content for Claude.
7
+ // Designed to be readable and include proper citations.
8
+ // ============================================================================
9
+ // ============================================================================
10
+ // PARAGRAPH RANGE UTILITIES
11
+ // ============================================================================
12
+ export function parseParaRange(range) {
13
+ const match = range.match(/^(\d+)\s*-\s*(\d+)$/);
14
+ if (!match || !match[1] || !match[2])
15
+ return null;
16
+ return {
17
+ start: parseInt(match[1], 10),
18
+ end: parseInt(match[2], 10),
19
+ };
20
+ }
21
+ // Maximum characters to return (roughly 8000 tokens)
22
+ const MAX_OUTPUT_CHARS = 32000;
23
+ export function applyParaRange(paragraphs, range) {
24
+ if (!range) {
25
+ // No range specified - apply token limit
26
+ return truncateToLimit(paragraphs);
27
+ }
28
+ const parsed = parseParaRange(range);
29
+ if (!parsed) {
30
+ return truncateToLimit(paragraphs);
31
+ }
32
+ const filtered = paragraphs.filter(p => p.number >= parsed.start && p.number <= parsed.end);
33
+ return truncateToLimit(filtered);
34
+ }
35
+ function truncateToLimit(paragraphs) {
36
+ let totalChars = 0;
37
+ const output = [];
38
+ for (const para of paragraphs) {
39
+ if (totalChars + para.text.length > MAX_OUTPUT_CHARS) {
40
+ break;
41
+ }
42
+ output.push(para);
43
+ totalChars += para.text.length;
44
+ }
45
+ return {
46
+ paragraphs: output,
47
+ truncated: output.length < paragraphs.length,
48
+ remaining: paragraphs.length - output.length,
49
+ };
50
+ }
51
+ // ============================================================================
52
+ // SEARCH RESULTS FORMATTER
53
+ // ============================================================================
54
+ export function formatSearchResults(results) {
55
+ if (results.length === 0) {
56
+ return 'No cases found matching your search query.';
57
+ }
58
+ let output = `Found ${results.length} case${results.length === 1 ? '' : 's'}:\n\n`;
59
+ for (const result of results) {
60
+ // Citation or URI as header
61
+ const citation = result.neutralCitation || result.documentUri;
62
+ output += `**${citation}**\n`;
63
+ // Title
64
+ output += `${result.title}\n`;
65
+ // Court and date
66
+ const parts = [];
67
+ if (result.court)
68
+ parts.push(result.court);
69
+ if (result.date)
70
+ parts.push(result.date);
71
+ if (parts.length > 0) {
72
+ output += `${parts.join(' | ')}\n`;
73
+ }
74
+ // Snippet if available
75
+ if (result.snippet) {
76
+ const cleanSnippet = result.snippet
77
+ .replace(/\s+/g, ' ')
78
+ .trim()
79
+ .substring(0, 200);
80
+ output += `> ${cleanSnippet}${cleanSnippet.length >= 200 ? '...' : ''}\n`;
81
+ }
82
+ // Add document links
83
+ output += `[View on TNA](${result.urls.web}) | [PDF](${result.urls.pdf})\n`;
84
+ output += '\n';
85
+ }
86
+ return output.trim();
87
+ }
88
+ export function formatCaseContent(caseData, options = {}) {
89
+ const { includeMetadata = true, paragraphRange } = options;
90
+ let output = '';
91
+ // Metadata header
92
+ if (includeMetadata) {
93
+ output += `# ${caseData.metadata.title}\n\n`;
94
+ if (caseData.metadata.neutralCitation) {
95
+ output += `**Citation:** ${caseData.metadata.neutralCitation}\n`;
96
+ }
97
+ output += `**Court:** ${caseData.metadata.courtName || caseData.metadata.court}\n`;
98
+ if (caseData.metadata.date) {
99
+ output += `**Date:** ${caseData.metadata.date}\n`;
100
+ }
101
+ if (caseData.judges && caseData.judges.length > 0) {
102
+ output += `**Judges:** ${caseData.judges.join(', ')}\n`;
103
+ }
104
+ if (caseData.parties) {
105
+ if (caseData.parties.claimants.length > 0) {
106
+ output += `**Claimant(s):** ${caseData.parties.claimants.join(', ')}\n`;
107
+ }
108
+ if (caseData.parties.defendants.length > 0) {
109
+ output += `**Defendant(s):** ${caseData.parties.defendants.join(', ')}\n`;
110
+ }
111
+ }
112
+ // Add document links
113
+ output += `\n[View on TNA](${caseData.metadata.urls.web}) | [PDF](${caseData.metadata.urls.pdf})\n`;
114
+ output += '\n---\n\n';
115
+ }
116
+ // Apply paragraph range and truncation
117
+ const { paragraphs, truncated, remaining } = applyParaRange(caseData.paragraphs, paragraphRange);
118
+ // Format paragraphs with numbers for citation
119
+ for (const para of paragraphs) {
120
+ output += `[${para.number}] ${para.text}\n\n`;
121
+ }
122
+ // Truncation notice
123
+ if (truncated) {
124
+ output += `---\n\n`;
125
+ output += `*Output truncated. ${remaining} paragraph${remaining === 1 ? '' : 's'} remaining. `;
126
+ const lastPara = paragraphs[paragraphs.length - 1];
127
+ if (lastPara) {
128
+ output += `Use the paragraphs parameter (e.g., "${lastPara.number + 1}-${lastPara.number + 50}") to retrieve more.*\n`;
129
+ }
130
+ }
131
+ return output.trim();
132
+ }
133
+ // ============================================================================
134
+ // CITATIONS FORMATTER
135
+ // ============================================================================
136
+ export function formatCitations(citations, direction) {
137
+ let output = '';
138
+ if (direction === 'citing' || direction === 'both') {
139
+ output += `## Cases Citing This Decision\n\n`;
140
+ if (citations.citing.length === 0) {
141
+ output += `No citing cases found.\n\n`;
142
+ }
143
+ else {
144
+ for (const c of citations.citing) {
145
+ output += `- **${c.citation || c.title}** (${c.court}, ${c.date})\n`;
146
+ }
147
+ output += '\n';
148
+ }
149
+ }
150
+ if (direction === 'cited' || direction === 'both') {
151
+ output += `## Cases Cited By This Decision\n\n`;
152
+ if (citations.cited.length === 0) {
153
+ output += `No cited cases found.\n\n`;
154
+ }
155
+ else {
156
+ for (const c of citations.cited) {
157
+ output += `- **${c.citation || c.title}** (${c.court}, ${c.date})\n`;
158
+ }
159
+ output += '\n';
160
+ }
161
+ }
162
+ return output.trim();
163
+ }
164
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { SearchResult } from './types.js';
2
+ export interface SearchParams {
3
+ query: string;
4
+ legalArea?: string;
5
+ court?: string;
6
+ yearFrom?: number;
7
+ yearTo?: number;
8
+ limit?: number;
9
+ page?: number;
10
+ }
11
+ export declare function searchCaseLaw(params: SearchParams): Promise<SearchResult[]>;
12
+ export declare function reciprocalRankFusion(resultLists: SearchResult[][], limit: number, k?: number): SearchResult[];
13
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAkCjF;AAcD,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,YAAY,EAAE,EAAE,EAC7B,KAAK,EAAE,MAAM,EACb,CAAC,GAAE,MAAW,GACb,YAAY,EAAE,CA8BhB"}
package/dist/search.js ADDED
@@ -0,0 +1,81 @@
1
+ // src/search.ts
2
+ // ============================================================================
3
+ // UNIFIED SEARCH LAYER
4
+ // ============================================================================
5
+ //
6
+ // Combines results from:
7
+ // 1. TNA API (2003+)
8
+ // 2. Local PostgreSQL (pre-2003 BAILII content) - optional, when database configured
9
+ //
10
+ // Results are merged using Reciprocal Rank Fusion (RRF).
11
+ // ============================================================================
12
+ import { searchTna, COURT_CODE_MAP, LEGAL_AREA_COURTS } from './tna-client.js';
13
+ export async function searchCaseLaw(params) {
14
+ const limit = params.limit || 10;
15
+ // Determine which TNA courts to search
16
+ let tnaCourts;
17
+ if (params.court && params.court !== 'any') {
18
+ tnaCourts = COURT_CODE_MAP[params.court];
19
+ }
20
+ if (params.legalArea && params.legalArea !== 'any') {
21
+ const areaCourts = LEGAL_AREA_COURTS[params.legalArea];
22
+ if (areaCourts) {
23
+ // Intersect with court filter if both specified
24
+ if (tnaCourts) {
25
+ tnaCourts = tnaCourts.filter(c => areaCourts.includes(c));
26
+ }
27
+ else {
28
+ tnaCourts = areaCourts;
29
+ }
30
+ }
31
+ }
32
+ // For MVP, only use TNA API
33
+ // TODO: Add local database search when PostgreSQL is configured
34
+ const tnaResults = await searchTna({
35
+ query: params.query,
36
+ courts: tnaCourts,
37
+ yearFrom: params.yearFrom,
38
+ yearTo: params.yearTo,
39
+ limit: limit,
40
+ page: params.page,
41
+ });
42
+ return tnaResults.slice(0, limit);
43
+ }
44
+ // ============================================================================
45
+ // RECIPROCAL RANK FUSION (for future use with multiple sources)
46
+ // ============================================================================
47
+ //
48
+ // Combines ranked lists from different sources.
49
+ // Each document gets a score based on its rank in each list:
50
+ // score = sum(1 / (k + rank)) for each list
51
+ //
52
+ // k=60 is a standard constant that prevents top results from
53
+ // dominating too heavily.
54
+ // ============================================================================
55
+ export function reciprocalRankFusion(resultLists, limit, k = 60) {
56
+ const scores = new Map();
57
+ const docMap = new Map();
58
+ for (const results of resultLists) {
59
+ for (let rank = 0; rank < results.length; rank++) {
60
+ const result = results[rank];
61
+ if (!result)
62
+ continue;
63
+ const docId = result.documentUri || result.neutralCitation || result.title;
64
+ const currentScore = scores.get(docId) || 0;
65
+ scores.set(docId, currentScore + 1 / (k + rank + 1));
66
+ // Keep the first occurrence (usually has more complete metadata)
67
+ if (!docMap.has(docId)) {
68
+ docMap.set(docId, result);
69
+ }
70
+ }
71
+ }
72
+ // Sort by RRF score
73
+ const sortedIds = [...scores.entries()]
74
+ .sort((a, b) => b[1] - a[1])
75
+ .map(([id]) => id);
76
+ return sortedIds
77
+ .slice(0, limit)
78
+ .map(id => docMap.get(id))
79
+ .filter((r) => r !== undefined);
80
+ }
81
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
package/dist/server.js ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ // src/server.ts
3
+ // ============================================================================
4
+ // UK CASE LAW MCP SERVER
5
+ // ============================================================================
6
+ //
7
+ // Provides Claude with tools to search and retrieve UK case law.
8
+ //
9
+ // Tools provided:
10
+ // - uklaw_search: Search across all case law
11
+ // - uklaw_get_case: Get full text of a specific case
12
+ // ============================================================================
13
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
14
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
15
+ import { z } from 'zod';
16
+ import { searchCaseLaw } from './search.js';
17
+ import { getCaseByUri, getCaseByCitation } from './cases.js';
18
+ import { formatSearchResults, formatCaseContent } from './formatters.js';
19
+ // ============================================================================
20
+ // SERVER INITIALIZATION
21
+ // ============================================================================
22
+ const server = new McpServer({
23
+ name: 'uk-case-law',
24
+ version: '1.0.0',
25
+ });
26
+ // ============================================================================
27
+ // TOOL: uklaw_search
28
+ // ============================================================================
29
+ //
30
+ // Primary search tool. Searches TNA API (2003+).
31
+ // ============================================================================
32
+ server.tool('uklaw_search', `Search UK case law across all courts and tribunals.
33
+
34
+ Returns ranked results with:
35
+ - Neutral citations (e.g., [2024] UKSC 1)
36
+ - Case titles
37
+ - Courts and dates
38
+ - Brief snippets
39
+
40
+ Use filters to narrow results by court, legal area, or date range.
41
+
42
+ Examples:
43
+ - "patent obviousness test" - finds patent validity cases
44
+ - "unfair dismissal procedure" - finds employment cases
45
+ - "breach of fiduciary duty director" - finds company law cases`, {
46
+ query: z.string()
47
+ .min(2)
48
+ .describe('Search terms - legal concepts, party names, or keywords'),
49
+ legal_area: z.enum([
50
+ 'any',
51
+ 'intellectual_property',
52
+ 'commercial',
53
+ 'company',
54
+ 'employment',
55
+ 'property',
56
+ 'family',
57
+ 'criminal',
58
+ 'public_law',
59
+ 'immigration',
60
+ 'personal_injury'
61
+ ])
62
+ .default('any')
63
+ .describe('Filter by area of law'),
64
+ court: z.enum([
65
+ 'any',
66
+ 'supreme_court',
67
+ 'court_of_appeal',
68
+ 'high_court',
69
+ 'crown_court',
70
+ 'tribunals'
71
+ ])
72
+ .default('any')
73
+ .describe('Filter by court level'),
74
+ year_from: z.number()
75
+ .int()
76
+ .min(1800)
77
+ .max(2025)
78
+ .optional()
79
+ .describe('Earliest decision year'),
80
+ year_to: z.number()
81
+ .int()
82
+ .min(1800)
83
+ .max(2025)
84
+ .optional()
85
+ .describe('Latest decision year'),
86
+ limit: z.number()
87
+ .int()
88
+ .min(1)
89
+ .max(50)
90
+ .default(10)
91
+ .describe('Maximum results to return'),
92
+ page: z.number()
93
+ .int()
94
+ .min(1)
95
+ .default(1)
96
+ .describe('Page number for pagination. If results seem truncated or date range is not met, try next page.'),
97
+ }, async ({ query, legal_area, court, year_from, year_to, limit, page }) => {
98
+ try {
99
+ const params = {
100
+ query,
101
+ legalArea: legal_area === 'any' ? undefined : legal_area,
102
+ court: court === 'any' ? undefined : court,
103
+ yearFrom: year_from,
104
+ yearTo: year_to,
105
+ limit,
106
+ page,
107
+ };
108
+ const results = await searchCaseLaw(params);
109
+ const formatted = formatSearchResults(results);
110
+ return {
111
+ content: [{ type: 'text', text: formatted }]
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: `Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`
119
+ }],
120
+ isError: true
121
+ };
122
+ }
123
+ });
124
+ // ============================================================================
125
+ // TOOL: uklaw_get_case
126
+ // ============================================================================
127
+ //
128
+ // Retrieves full text of a specific case. For TNA cases, fetches from API.
129
+ //
130
+ // Output includes numbered paragraphs for precise citation.
131
+ // ============================================================================
132
+ server.tool('uklaw_get_case', `Retrieve the full text of a specific UK case.
133
+
134
+ Accepts either:
135
+ - Neutral citation: "[2007] EWCA Civ 588"
136
+ - Document URI: "ewca/civ/2007/588"
137
+
138
+ Returns the judgment with numbered paragraphs. Use paragraph numbers
139
+ when citing specific passages, e.g., "as stated at [23]".
140
+
141
+ For long judgments, use the paragraphs parameter to request a specific
142
+ range (e.g., "1-50" for the first 50 paragraphs).`, {
143
+ citation: z.string()
144
+ .describe('Neutral citation (e.g., "[2024] UKSC 1") or document URI (e.g., "uksc/2024/1")'),
145
+ paragraphs: z.string()
146
+ .optional()
147
+ .describe('Paragraph range to retrieve, e.g., "1-50" or "23-45". Omit for full text.'),
148
+ include_metadata: z.boolean()
149
+ .default(true)
150
+ .describe('Include case metadata (judges, date, court)')
151
+ }, async ({ citation, paragraphs, include_metadata }) => {
152
+ try {
153
+ // Determine if this is a neutral citation or URI
154
+ const isNeutralCitation = citation.startsWith('[');
155
+ const caseData = isNeutralCitation
156
+ ? await getCaseByCitation(citation)
157
+ : await getCaseByUri(citation);
158
+ if (!caseData) {
159
+ return {
160
+ content: [{ type: 'text', text: `Case not found: ${citation}` }],
161
+ isError: true
162
+ };
163
+ }
164
+ const formatted = formatCaseContent(caseData, {
165
+ includeMetadata: include_metadata,
166
+ paragraphRange: paragraphs,
167
+ });
168
+ return {
169
+ content: [{ type: 'text', text: formatted }]
170
+ };
171
+ }
172
+ catch (error) {
173
+ return {
174
+ content: [{
175
+ type: 'text',
176
+ text: `Failed to retrieve case: ${error instanceof Error ? error.message : 'Unknown error'}`
177
+ }],
178
+ isError: true
179
+ };
180
+ }
181
+ });
182
+ // ============================================================================
183
+ // START SERVER
184
+ // ============================================================================
185
+ async function main() {
186
+ const transport = new StdioServerTransport();
187
+ await server.connect(transport);
188
+ console.error('UK Case Law MCP server running');
189
+ }
190
+ main().catch(console.error);
191
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/test.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":""}