Search Cases
search_casesSearch Australian case law using queries with neutral citation fallbacks. Retrieve structured results with citation metadata and jurisdiction filtering.
Instructions
Search Australian case law with neutral citation fallbacks.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | json | |
| jurisdiction | No | ||
| limit | No | ||
| query | Yes |
Implementation Reference
- src/index.ts:63-82 (registration)Registers the 'search_cases' MCP tool, specifying title, description, input schema, and handler function that delegates to searchAustLii and formatter.
server.registerTool( "search_cases", { title: "Search Cases", description: "Search Australian case law with neutral citation fallbacks. Supports 'auto' (default), 'relevance', or 'date' sorting. Auto mode intelligently detects case name queries (e.g., 'X v Y') and uses relevance sorting to find the specific case, while using date sorting for topic searches.", inputSchema: searchCasesShape, }, async (rawInput) => { const { query, jurisdiction, limit, format, sortBy } = searchCasesParser.parse(rawInput); const results = await searchAustLii(query, { type: "case", jurisdiction, limit, sortBy, }); return formatSearchResults(results, format ?? "json"); }, ); - src/index.ts:54-61 (schema)Zod input schema definition for the search_cases tool (query required, optional jurisdiction, limit, format, sortBy).
const searchCasesShape = { query: z.string().min(1, "Query cannot be empty."), jurisdiction: jurisdictionEnum.optional(), limit: z.number().int().min(1).max(50).optional(), format: formatEnum.optional(), sortBy: sortByEnum.optional(), }; const searchCasesParser = z.object(searchCasesShape); - src/index.ts:71-81 (handler)Inline handler function for search_cases tool: validates input, invokes searchAustLii with case type, formats and returns results.
async (rawInput) => { const { query, jurisdiction, limit, format, sortBy } = searchCasesParser.parse(rawInput); const results = await searchAustLii(query, { type: "case", jurisdiction, limit, sortBy, }); return formatSearchResults(results, format ?? "json"); }, - src/services/austlii.ts:96-199 (helper)Main searchAustLii helper function: builds AustLII search URL, fetches and parses HTML results, filters by type (case/legislation), extracts metadata, applies smart sorting/boosting.
export async function searchAustLii( query: string, options: SearchOptions, ): Promise<SearchResult[]> { try { const searchParams = buildSearchParams(query, options); const limit = options.limit ?? 10; // Determine sort mode (auto-detect or use explicit setting) const sortMode = determineSortMode(query, options); const searchUrl = new URL(AUSTLII_SEARCH_BASE); searchUrl.searchParams.set("method", "boolean"); searchUrl.searchParams.set("query", searchParams.query); searchUrl.searchParams.set("meta", searchParams.meta); searchUrl.searchParams.set("results", String(limit)); // Set sort order based on mode if (sortMode === "relevance") { searchUrl.searchParams.set("view", "relevance"); } else { searchUrl.searchParams.set("view", "date"); } const response = await axios.get(searchUrl.toString(), { headers: { "User-Agent": "auslaw-mcp/0.1.0 (legal research tool)", }, timeout: 15000, }); const html = response.data; const $ = cheerio.load(html); const results: SearchResult[] = []; // Parse search results - AustLII returns results in an <OL> ordered list $("ol li").each((_, element) => { const $li = $(element); const $link = $li.find("a").first(); const title = $link.text().trim(); let url = $link.attr("href") || ""; // Make URL absolute if relative if (url && !url.startsWith("http")) { url = `http://classic.austlii.edu.au${url}`; } if (title && url) { // Always skip journal articles - we only want primary sources if (url.includes("/journals/")) { return; // Skip journal articles } // For cases, only include actual case databases if (options.type === "case" && !url.includes("/cases/")) { return; // Skip non-case results } // For legislation, only include legislation databases if (options.type === "legislation" && !url.includes("/legis/")) { return; // Skip non-legislation results } // Try to extract citation from title const citationMatch = title.match(/\[(\d{4})\]\s*([A-Z]+)\s*(\d+)/); const neutralCitation = citationMatch ? citationMatch[0] : undefined; const year = citationMatch ? citationMatch[1] : undefined; // Extract jurisdiction from URL const jurisdictionMatch = url.match(/\/au\/cases\/(cth|vic|nsw|qld|sa|wa|tas|nt|act)\//i); const jurisdiction = jurisdictionMatch?.[1]?.toLowerCase(); // Extract summary from <small> tag if present const $small = $li.find("small"); const summary = $small.length > 0 ? $small.text().trim() : undefined; results.push({ title, citation: undefined, neutralCitation, url, source: "austlii", summary, jurisdiction, year, type: options.type, }); } }); // Apply title matching boost when using relevance sorting let finalResults = results; if (sortMode === "relevance" && isCaseNameQuery(query)) { finalResults = boostTitleMatches(results, query); } return finalResults.slice(0, limit); } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`AustLII search failed: ${error.message}`); } throw error; } } - src/utils/formatter.ts:19-68 (helper)formatSearchResults helper: converts SearchResult[] to MCP CallToolResult in json, html, markdown, or text formats.
export function formatSearchResults( results: SearchResult[], format: ResponseFormat, ): CallToolResult { switch (format) { case "json": return { content: ensureContent(JSON.stringify(results, null, 2)), structuredContent: { format: "json", data: results, }, }; case "html": { const rows = results .map((result) => { const citation = result.citation ?? result.neutralCitation ?? ""; const summary = result.summary ? `<p>${escapeHtml(result.summary)}</p>` : ""; return `<li><a href="${escapeHtml(result.url)}">${escapeHtml(result.title)}</a>${ citation ? ` (${escapeHtml(citation)})` : "" }${summary}</li>`; }) .join("\n"); return { content: ensureContent(`<ul>\n${rows}\n</ul>`), }; } case "markdown": { const lines = results.map((result) => { const citation = result.citation ?? result.neutralCitation ?? ""; const summary = result.summary ? ` — ${result.summary}` : ""; return `- [${result.title}](${result.url})${citation ? ` (${citation})` : ""}${summary}`; }); return { content: ensureContent(lines.join("\n")), }; } case "text": default: { const lines = results.map((result, idx) => { const citation = result.citation ?? result.neutralCitation ?? ""; const summary = result.summary ? `\n ${result.summary}` : ""; return `${idx + 1}. ${result.title}${citation ? ` (${citation})` : ""}\n ${result.url}${summary}`; }); return { content: ensureContent(lines.join("\n")), }; } } }