import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
formatFetchResponse,
formatSearchResults,
} from "./utils/formatter.js";
import { fetchDocumentText } from "./services/fetcher.js";
import { searchAustLii } from "./services/austlii.js";
const formatEnum = z.enum(["json", "text", "markdown", "html"]).default("json");
const jurisdictionEnum = z.enum(["cth", "vic", "federal", "other"]);
const sortByEnum = z.enum(["relevance", "date", "auto"]).default("auto");
async function main() {
const server = new McpServer({
name: "auslaw-mcp",
version: "0.1.0",
description:
"Australian legislation and case law searcher with OCR-aware document retrieval.",
});
const searchLegislationShape = {
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 searchLegislationParser = z.object(searchLegislationShape);
server.registerTool(
"search_legislation",
{
title: "Search Legislation",
description:
"Search Australian legislation (Commonwealth and Victorian focus) and return structured matches.",
inputSchema: searchLegislationShape,
},
async (rawInput) => {
const { query, jurisdiction, limit, format, sortBy } =
searchLegislationParser.parse(rawInput);
const results = await searchAustLii(query, {
type: "legislation",
jurisdiction,
limit,
sortBy,
});
return formatSearchResults(results, format ?? "json");
},
);
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);
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");
},
);
const fetchDocumentShape = {
url: z.string().url("URL must be valid."),
format: formatEnum.optional(),
};
const fetchDocumentParser = z.object(fetchDocumentShape);
server.registerTool(
"fetch_document_text",
{
title: "Fetch Document Text",
description:
"Fetch full text for a legislation or case URL, with OCR fallback for scanned PDFs.",
inputSchema: fetchDocumentShape,
},
async (rawInput) => {
const { url, format } = fetchDocumentParser.parse(rawInput);
const response = await fetchDocumentText(url);
return formatFetchResponse(response, format ?? "json");
},
);
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error("Fatal server error", error);
process.exit(1);
});