# tw-legal-rag: a Taiwan legal MCP server and retrieval CLI that refuses to generate answers

> Taiwan Legal RAG connects Claude, ChatGPT, Codex or any MCP client to a semantic index of 22,578,975 Taiwan court judgments, then hands the results to your own model as a citation-checked bundle. It never calls an LLM itself, and that is the whole design argument.

**aa0101181514/tw-legal-rag** — 台灣法律 MCP 伺服器 + CLI（免費、免註冊、免 API key）：2,250 萬筆裁判書、行政函釋、憲法法庭裁判，附引用查核。Free Taiwan legal MCP server for Claude/ChatGPT/Codex — bring your own LLM, retrieval-only.

- Repository: https://github.com/aa0101181514/tw-legal-rag
- Website: https://dr-legal.com.tw
- Stars: 326 · Forks: 42
- Language: Python
- License: NOASSERTION
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/aa0101181514-tw-legal-rag

## The problem tw-legal-rag solves: retrieval without generation

Legal AI fails in a specific way. A model produces a citation that looks real, the judgment number checks out against a public database, and the holding attached to it was invented. The README names this failure mode directly: a real citation number with a fabricated opinion. Taiwan Legal RAG is built around that one problem.

The project is two things sharing one backend. There is a hosted MCP server at https://tlr.dr-legal.com.tw/mcp that you add to Claude, ChatGPT, Gemini CLI, Microsoft 365 Copilot, Codex or Cursor as a single URL, and there is a Python CLI named twlegalrag that talks to the same public retrieval endpoint. Neither one calls an LLM. The README is explicit that the tool does not generate legal advice and does not guarantee the semantic faithfulness of any third-party model output.

The audience is narrow and identifiable. It is a lawyer, a legal engineer, or a developer building an assistant for Taiwan law who already has a model they trust and wants the retrieval layer to be inspectable. If you want a product that reads a fact pattern and tells you the answer, this is the wrong shape entirely, and the README says so rather than leaving it implied.

## How the retrieval and citation-checking mechanism actually works

The CLI is a client. It does not bundle a judgment corpus, and it does not expose the backend model weights, the vector index, or the retrieval pipeline. Those stay server-side. What you get locally is a thin tool that issues a query and packages the response.

The main flow is the pack command, which produces a JSON bundle. According to the README, that bundle contains the query, a citation_id per judgment (J1, J2, and so on), citation_text, citation_url, doc_id, a Layer-1 listing, a fulltext_excerpt of the reasoning with a length cap, case_history, an allowed_citations whitelist, and a verification_instructions block that tells the downstream model to cite only judgments inside the bundle and to mark unsupported propositions as unverified. A notice is also printed to stderr.

The citation check is the part worth reading carefully, because the README spends more space on its limits than its capabilities. It is a bundle-level, best-effort string check. It verifies whether a cited judgment number exists in the bundle, whether the answer cites something outside the bundle, and whether a quoted sentence appears somewhere in the bundle text. It does not bind a quote to the specific judgment the answer attributes it to. It does not check whether the court's reasoning was read correctly. It does not catch a party's argument presented as the court's holding, obiter presented as authority, or a paraphrased holding that was never stated. The README states that a pass means the citation identities line up with the bundle, not that the legal inference is correct.

Two other mechanisms matter. case_history attaches the appeal chain recorded in the database, including flags for whether the judgment was reversed or dismissed, so you can see before citing whether a decision was overturned upstream. And the administrative interpretation tools keep 函釋 and judgments strictly separated, with an effectiveness status attached (verified valid, unverified, repealed, suspended, or superseded) so an interpretation cannot be cited as a court holding.

## Installing twlegalrag and running a first bundle

The package installs from PyPI and depends only on httpx, typer, rich, and tomli on Python below 3.11. No LLM SDK and no API key are involved.

```bash
pip install twlegalrag
```

Before running a query, check that the backend is reachable. The health command exists for exactly this.

```bash
twlegalrag health
```

The first real use is a plain search. The README's example searches for labour disputes about overtime pay and asks for five results with the reasoning text read in.

```bash
twlegalrag search "勞資 加班費" -n 5 --read
```

That returns a listing of matching judgments. To get something you can hand to a model, use pack with an output path. The README's example query asks what can be claimed after a car accident where the other party was fully at fault.

```bash
twlegalrag pack "車禍對方全責,我可以求償什麼?" -o bundle.json
```

The resulting bundle.json is what you paste into ChatGPT, Claude or Gemini, with the instruction that the model should cite only judgments present in the bundle. After the model produces an answer, save it as text and run the check.

```bash
twlegalrag check bundle.json answer.txt
```

Remember what that check proves. It compares the answer against the bundle contents only. If you later open a full judgment elsewhere and rewrite the answer, the check still sees only the excerpt that was packaged.

## Statute and interpretation lookup, and where the data stops

Version 2.3.0 added CLI access to statutes and interpretations, which previously lived only on the dr-legal.com.tw site. The law command takes a statute name and article number and returns the current consolidated text with the last amendment date and any repeal note. Common abbreviations such as 勞基法, 刑法 and 憲法 are resolved to official names automatically.

```bash
twlegalrag law 民法 184
```

The ref command looks up an interpretation by its document number and returns its effectiveness status; ref-search runs a semantic query across interpretations.

```bash
twlegalrag ref "台財稅第881945861號"
twlegalrag ref-search "扣繳義務人未依限申報之處罰" -n 5
```

There is a real boundary here that the README states plainly. The law command returns only the current consolidated version. If you need the statute as it stood at the time of the conduct, before an amendment, you have to go to the official legislative history. That is a meaningful gap for anyone working on an older dispute, and it is not a bug you can work around inside the tool.

The same applies to recency. Judgments sync daily from published Judicial Yuan data, but the README notes a lag of several days between a ruling and its publication, and says that for very recent decisions you should rely on the official site. The dataset figures are dated 2026-09-08 and are drawn from the production database rather than estimated.

## The honest limits: what a passing citation check does not mean

The most important limitation is already built into the product's own documentation, and it deserves to be restated because it is easy to forget once a check returns pass. The check is a string comparison over the bundle. It cannot tell whether the sentence a model quoted came from the judgment the model attributed it to. It cannot tell whether a court's reasoning was understood. It cannot detect a paraphrase that changes the holding while keeping the citation number intact.

There is a second failure mode around the bundle itself. Because the check only sees the packaged excerpt, and because fulltext_excerpt has a length cap, a model that reasons from a truncated passage is checked against that same truncated passage. The verification instructions inside the bundle ask the downstream model to do the reading that the check cannot do. That is a delegation, not a guarantee, and the README treats it as such.

A third limit is jurisdictional and linguistic. The corpus is Taiwan judgments, administrative interpretations and constitutional court decisions, in Traditional Chinese. There is no coverage of other jurisdictions, and the semantic search is tuned to Chinese legal phrasing. If your question is about a contract governed by another country's law, nothing here helps.

Finally, the CLI is not a research assistant. It retrieves and packages. Every judgment of relevance, every reading of a holding, and every legal conclusion remains the responsibility of the person using it.

## How it differs from wrapping the official court website

The natural alternative is a wrapper that forwards queries to the Judicial Yuan's own site search or the official statute database. The README draws the comparison itself, and the difference is not quality but positioning.

A site wrapper gets you immediacy and a direct line to the official source. What it typically does not give you is semantic retrieval, because it is keyword search over the official index. It also usually has no citation guardrail at all, and it inherits the availability of the official site, including its WAF and any redesign that breaks the scraping path. The README notes that wrappers often end up running a local browser to get around verification.

Taiwan Legal RAG takes the opposite trade. It runs its own semantic index over roughly 22.6 million judgments, so a query phrased differently from the judgment text can still match. It attaches case_history with reversal flags, and it ships the read-whitelist and citation check. What it gives up is immediacy: the README says that for very recently announced judgments you should consult the official site. The two approaches are complementary rather than competing, and the honest reading is that a wrapper is better for checking the newest ruling and this tool is better for finding the line of cases you did not know the keywords for.

## Licence, maintenance and the cost of upgrading

The licence is Elastic License 2.0, recorded in pyproject.toml as license = { text = "Elastic-2.0" }, while the repository's LICENSE file is what governs and the GitHub metadata reports NOASSERTION. Those two signals do not agree, so read the LICENSE and TERMS.md files before you build on it. Elastic-2.0 is source-available rather than a permissive open source licence, which typically restricts offering the software as a managed service. That is a general property of the licence family, not legal advice, and if you plan to host it for others you should have someone qualified read TERMS.md and TRADEMARK.md.

On maintenance, the last push was on 2026-09-08, and the release history shows v2.1.0 on 2026-08-20, v2.2.0 on 2026-08-23, and v2.3.0 on 2026-09-01. The release cadence over that window is roughly weekly, and the v2.2.0 note describes an endpoint migration to the Dr.Legal domain, which is the kind of change that can break a pinned client.

Upgrade cost is low in code terms. The dependency set is four small packages and the CLI is a client, so a new version mostly changes request and response shapes rather than local state. The real upgrade risk is the hosted endpoint: because retrieval runs server-side, a backend change can alter results without any local version bump. Pin your CLI version, and treat endpoint changes as the thing to watch in CHANGELOG.md. There is no local database to migrate and no index to rebuild.

## Conclusion

Adopt it if you are building a Taiwan legal research workflow on top of an LLM you already trust and you want retrieval separated from generation. Do not adopt it if you need a tool that answers legal questions by itself, if you need judgments published in the last few days, or if you need historical statute text as it stood before an amendment. Before relying on it, install the CLI with pip install twlegalrag, run twlegalrag health, and run twlegalrag pack on a query you already know the answer to so you can see exactly what the bundle contains and what the citation check does and does not verify.

## FAQ

### Does tw-legal-rag call an LLM or generate legal answers?

No. The README states that the tool does not call any LLM, does not generate legal advice, and does not endorse model output. It retrieves judgments and packages them into a bundle that you hand to your own AI tool.

### What does a passing tw-legal-rag citation check actually prove?

It proves that the judgment numbers cited in an answer match judgments present in the bundle. The README is explicit that it does not prove the quote came from the judgment the answer attributed it to, that the holding was read correctly, or that the legal inference is sound.

### How do I install tw-legal-rag and what does it depend on?

Install it with pip install twlegalrag. According to pyproject.toml it requires Python 3.9 or later and depends only on httpx, typer, rich, and tomli on Python versions below 3.11.

### Can tw-legal-rag retrieve a statute as it read before an amendment?

No. The README states that the law command provides only the current consolidated version with the last amendment date, and that historical versions as they stood at the time of conduct must be checked against official legislative history.

## Sources

- [aa0101181514/tw-legal-rag on GitHub](https://github.com/aa0101181514/tw-legal-rag)
- [Issues](https://github.com/aa0101181514/tw-legal-rag/issues)
- [Project website](https://dr-legal.com.tw)
- [README](https://github.com/aa0101181514/tw-legal-rag/blob/main/README.md)
- [Releases](https://github.com/aa0101181514/tw-legal-rag/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/aa0101181514-tw-legal-rag
