Skip to main content
Glama

Search Cases

search_cases

Search 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

TableJSON Schema
NameRequiredDescriptionDefault
formatNojson
jurisdictionNo
limitNo
queryYes

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");
      },
    );
  • 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);
  • 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");
    },
  • 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;
      }
    }
  • 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")),
          };
        }
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'neutral citation fallbacks,' which adds some context about search behavior, but fails to cover critical aspects like whether this is a read-only operation, potential rate limits, authentication needs, or what happens on errors. For a search tool with zero annotation coverage, this leaves significant gaps in understanding its operational traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise with a single sentence that efficiently conveys the core functionality and a key feature ('neutral citation fallbacks'). It is front-loaded with the main action and resource, with no wasted words, making it easy for an agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a search tool with 4 parameters, 0% schema description coverage, no annotations, and no output schema, the description is insufficiently complete. It lacks details on return values, error handling, or operational constraints, which are crucial for an agent to use the tool effectively in varied contexts.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds no specific information about parameters beyond what the schema provides, such as explaining the 'jurisdiction' enums or 'query' requirements. With 0% schema description coverage, the baseline is low, but the description doesn't compensate by detailing parameter meanings or usage. However, it implies the tool handles Australian case law, which loosely relates to the 'jurisdiction' parameter, offering minimal added value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Search') and resource ('Australian case law'), making the purpose understandable. It adds specificity with 'with neutral citation fallbacks,' which distinguishes it from generic search tools. However, it doesn't explicitly differentiate from sibling tools like 'search_legislation,' which might handle similar queries but for different legal resources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, such as sibling tools 'fetch_document_text' or 'search_legislation.' It mentions 'neutral citation fallbacks,' which hints at a specific use case, but doesn't clarify scenarios where this is preferred or required, leaving the agent without explicit usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/russellbrenner/auslaw-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server