mcp-courtwatch

mcp-courtwatch

MCP server for free U.S. case-law and court-docket search via CourtListener, providing tools for opinion search, docket lookup, citation verification, and more.

Category
Visit Server

README

mcp-courtwatch

MCP server for free U.S. case-law and court-docket search, over CourtListener (the Free Law Project's open legal database). Built for legal-aid orgs, tenant-defense and pro-se litigants, and public-interest lawyers who cannot afford Westlaw or PACER.

It wraps CourtListener's REST API v4, normalizing the raw JSON (caseName, dateFiled, cluster_id, docket_absolute_url, and so on) into clean, documented tool outputs.

Tools

Tool Arguments Returns
opinion_search q (required), court, filed_after, filed_before, order_by, limit, cursor Full-text case-law search (/search/?type=o). Per hit: case name, court, date filed, citations, docket number, snippet, citation count, cluster id, link. Also returns next_cursor (pass it back as cursor for the next page).
docket_lookup q and/or docket_number (at least one), court, limit, cursor Docket search (/search/?type=r). Per hit: case name, court, docket number, filed/terminated dates, nature of suit, docket id, link. Also returns next_cursor (pass it back as cursor for the next page).
court_list jurisdiction, q, limit Courts and their ids (/courts/), the values used as the court filter above. Optional jurisdiction filter and a name substring filter applied across the full courts table (paged server-side).
case_detail id (required), type (cluster or opinion) Full case by id. A cluster (/clusters/{id}/) gives case name, citations, date, judges, and its opinion ids. An opinion (/opinions/{id}/) gives the full opinion text. Requires a token.
citation_lookup text (required) Verify citations (POST /citation-lookup/). Pass free text (a brief, a draft) or a single citation string; every citation recognized is checked against the database of real cases. Per citation: FOUND (with matched case name, date, link) or an explicit NOT_FOUND / UNKNOWN_REPORTER flag. Requires a token.
judge_lookup name_last and/or name_first (at least one), limit Judges / people (/people/). Per person: id, assembled name, birth and death dates and place, gender, count of positions on file.

order_by for opinion_search is one of relevance (default), newest, oldest, most_cited. Court jurisdiction codes include F (federal appellate and other), FD (federal district), FB (bankruptcy), S (state), SA (state appellate), SS (state supreme).

Data source

  • Base URL: https://www.courtlistener.com/api/rest/v4
  • Auth: a free API token, sent as the header Authorization: Token <token>.
  • Envelope: search and list endpoints return the DRF shape { count, next, previous, results: [...] }. /search/ paginates by opaque cursor; the list endpoints (/courts/, /people/) paginate by page number (?page=N). Detail endpoints return a bare object. POST /citation-lookup/ returns a bare JSON array (one item per citation recognized in the text).
  • Access: /search/, /courts/, and /people/ answer without a token at a low rate limit, so those tools attach the token only when it is set (a token raises the limit). /clusters/{id}/, /opinions/{id}/, and POST /citation-lookup/ return HTTP 401 without a token, so case_detail and citation_lookup require one.
  • Citation-lookup limits (server-side): text max 64,000 characters (enforced pre-flight here with a clear error; the tool never truncates, since a dropped tail would mean unchecked citations); the first 250 citations per call are looked up and any beyond that come back flagged per-item as not checked; rate limit 60 citations/min.

Sources:

  • REST API v4 overview and auth: https://www.courtlistener.com/help/api/rest/ (redirects to https://wiki.free.law/c/courtlistener/help/api/rest/v4/overview)
  • Search API (params, type enum, response fields): https://www.courtlistener.com/help/api/rest/search/ (redirects to https://wiki.free.law/c/courtlistener/help/api/rest/v4/search)
  • Case Law API (clusters, opinions): https://www.courtlistener.com/help/api/rest/case-law/ (redirects to https://wiki.free.law/c/courtlistener/help/api/rest/v4/case-law)
  • Citation Lookup API: https://www.courtlistener.com/help/api/rest/citation-lookup/ (redirects to https://wiki.free.law/c/courtlistener/help/api/rest/v4/citation-lookup), plus the endpoint's source in the CourtListener repo: cl/citations/api_views.py and api_serializers.py (request/response fields, per-citation status codes) and cl/settings/project/citations.py (the 250-citations-per-request cap). The endpoint returns HTTP 401 without a token.
  • The live API itself, for the search / courts / people field names: /search/?type=o, /search/?type=r, /courts/, /people/ (all answer unauthenticated GETs).

Field map (CourtListener to normalized output)

CourtListener field Normalized field Where
caseName (fallback caseNameFull) case_name opinion_search, docket_lookup
court_id, court court_id, court search hits
dateFiled date_filed search hits
citation (array) citations opinion_search
docketNumber docket_number search hits
opinions[].snippet snippet opinion_search
cluster_id, docket_id cluster_id, docket_id search hits
absolute_url / docket_absolute_url absolute_url (made a full link) search hits
dateTerminated, suitNature date_terminated, nature_of_suit docket_lookup
id, full_name, short_name, jurisdiction, citation_string, url id, full_name, short_name, jurisdiction, citation_string, website court_list
citations (array of {volume, reporter, page}) citations (formatted strings) case_detail (cluster)
sub_opinions (array of URLs) sub_opinion_ids case_detail (cluster)
plain_text (fallback html_with_citations) text (+ text_source, text_truncated) case_detail (opinion)
citation, normalized_citations, start_index, end_index same names citation_lookup (per citation)
status (200/300/400/404/429), error_message status + verdict (FOUND, FOUND_MULTIPLE, UNKNOWN_REPORTER, NOT_FOUND, NOT_CHECKED_OVER_CAP) + verified, error_message citation_lookup (per citation)
clusters (array of cluster objects) matches (cluster id, case name, date, citations, link) citation_lookup (per citation)

Install

No build step. Runs directly on tsx.

git clone https://github.com/haksanlulz/mcp-courtwatch.git
cd mcp-courtwatch
npm install

API token

case_detail and citation_lookup need a free CourtListener token, and the other tools run faster (higher rate limit) with one. Create a free account, open Profile then the API page, and copy the token. Docs: https://www.courtlistener.com/help/api/rest/

Expose it as COURTLISTENER_API_TOKEN:

export COURTLISTENER_API_TOKEN=your-token-here   # macOS / Linux
setx COURTLISTENER_API_TOKEN your-token-here      # Windows (new shells)

Without the token, opinion_search, docket_lookup, court_list, and judge_lookup still work at CourtListener's unauthenticated rate limit. case_detail and citation_lookup return a clear error telling you to set the token. The token is never logged.

MCP client config

Point your MCP client at index.ts via tsx. Use an absolute path.

{
  "mcpServers": {
    "courtwatch": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/mcp-courtwatch/index.ts"],
      "env": { "COURTLISTENER_API_TOKEN": "your-token-here" }
    }
  }
}

Example

Call opinion_search with { "q": "warrantless search", "court": "scotus", "order_by": "most_cited", "limit": 1 }:

{
  "query": { "q": "warrantless search", "court": "scotus", "filed_after": null, "filed_before": null, "order_by": "most_cited" },
  "total_matches": 161,
  "returned": 1,
  "results": [
    {
      "case_name": "Illinois v. Gates",
      "court": "Supreme Court of the United States",
      "court_id": "scotus",
      "date_filed": "1983-06-08",
      "citations": ["76 L. Ed. 2d 527", "103 S. Ct. 2317", "462 U.S. 213", "1983 U.S. LEXIS 54", "51 U.S.L.W. 4709"],
      "docket_number": "81-430",
      "cite_count": 16741,
      "status": "Published",
      "snippet": "Justice White, concurring in the judgment. In my view, the question regarding modification of the exclusionary rule ...",
      "cluster_id": 110959,
      "docket_id": 638808,
      "absolute_url": "https://www.courtlistener.com/opinion/110959/illinois-v-gates/"
    }
  ]
}

Then pass the cluster_id to case_detail ({ "id": 110959 }) for the citations, judges, and opinion ids, or case_detail with { "id": <opinion id>, "type": "opinion" } for the full opinion text.

Example: verifying citations before filing

Courts have sanctioned filings built on citations that do not exist. A legal-aid worker or a pro-se litigant can run a draft's citations through citation_lookup before anything reaches a judge:

Call citation_lookup with { "text": "Tenants are protected here. See Roe v. Wade, 410 U.S. 113 (1973); Smith v. Imaginary, 999 U.S. 9999 (2099)." }:

{
  "query": { "text_chars": 107 },
  "citations_checked": 2,
  "found": 1,
  "not_found": 1,
  "invalid": 0,
  "not_checked": 0,
  "all_verified": false,
  "warning": "1 of 2 citation(s) did NOT verify: 1 not found in CourtListener (likely fabricated or mis-cited). Do not cite unverified authorities — check them by hand before filing.",
  "results": [
    {
      "citation": "410 U.S. 113",
      "verified": true,
      "verdict": "FOUND",
      "status": 200,
      "error_message": null,
      "normalized_citations": ["410 U.S. 113"],
      "start_index": 45,
      "end_index": 57,
      "matches": [
        {
          "cluster_id": 108713,
          "case_name": "Roe v. Wade",
          "date_filed": "1973-01-22",
          "citations": ["410 U.S. 113", "93 S. Ct. 705", "35 L. Ed. 2d 147"],
          "precedential_status": "Published",
          "citation_count": 12030,
          "judges": "Blackmun",
          "docket_id": 4463,
          "absolute_url": "https://www.courtlistener.com/opinion/108713/roe-v-wade/"
        }
      ]
    },
    {
      "citation": "999 U.S. 9999",
      "verified": false,
      "verdict": "NOT_FOUND",
      "status": 404,
      "error_message": "Citation not found: '999 U.S. 9999'",
      "normalized_citations": ["999 U.S. 9999"],
      "start_index": 86,
      "end_index": 99,
      "matches": []
    }
  ]
}

The fabricated citation comes back NOT_FOUND with a top-level warning. Per-citation status mirrors the API's own codes: 200 found, 300 found with multiple matching clusters (FOUND_MULTIPLE — a real citation, ambiguous mapping), 400 unknown reporter, 404 not found, 429 past the 250-citations-per-call cap (NOT_CHECKED_OVER_CAP — split the text and re-run the rest). A lookup that recognizes zero citations says so in a note instead of pretending to have verified anything.

Caveats

Built without a token, so:

  • The case_detail field names (cluster and opinion objects) come from the Case Law API docs plus the search-result shape, not from a live authenticated GET (the /clusters/ and /opinions/ endpoints return HTTP 401 without a token). The normalizer is defensive: unknown or missing values coerce to null (or empty arrays) rather than throwing, and citations accept either string or object form. Run npm run smoke with a real token to confirm these end to end.
  • The citation_lookup response shape (per-citation fields, the 200/300/400/404/429 status codes, the serializer-level 64,000-char text cap, the 250-citation per-request cap) comes from the CourtListener source (cl/citations/api_views.py, api_serializers.py, cl/settings/project/citations.py) and the Citation Lookup docs, not from a live authenticated POST (the endpoint returns HTTP 401 without a token). The normalizer is defensive like the rest. Run npm run smoke with a token: it checks one real citation (410 U.S. 113, Roe v. Wade) plus one fabricated one and fails unless the real one resolves and the fake comes back NOT_FOUND.
  • The clusters returned by citation_lookup do not include the court (in CourtListener's model the court hangs off the docket, not the cluster). For the court, follow the match's absolute_url or pass its cluster_id to case_detail and the docket_id onward. Deliberately not auto-fetched here: a 250-citation brief would fan out into hundreds of extra docket calls.
  • docket_number is folded into the free-text q term rather than sent as a dedicated field, so matching is best-effort. If a known docket number under-returns, also pass the case name in q.
  • court_list applies its q name filter across the full courts table, walked one page at a time server-side (the /courts/ endpoint ignores page_size, so the ~3,400 courts span ~170 pages of ~20 up to a safety cap). Scope by jurisdiction to page less; a token is recommended for the unfiltered full-table scan.
  • opinion_search and docket_lookup return one fixed /search/ page of ~20 results; for more, pass the response's next_cursor back as the cursor argument. The endpoint ignores page_size, so limit cannot exceed one page (it is capped at 20 rather than advertising an unreachable number).
  • The order_by values, the court / filed_after / filed_before filters, and the search / courts / people field names match the live API, as does the fixed ~20-row page size of /search/ and /courts/ (both ignore page_size). The token-gated behavior of case_detail is the main thing the keyed smoke should confirm.

Develop

npm test         # vitest, fetch mocked with the documented response shapes (no token needed)
npm run smoke    # one live call per tool (needs COURTLISTENER_API_TOKEN; skips cleanly without)
npm run typecheck

License

MIT. See LICENSE. Data from CourtListener / the Free Law Project (public court records and openly licensed legal data). Unofficial, not affiliated with CourtListener or the Free Law Project.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured