To compose legal MCPs, add each server to one client's mcpServers config (CourtListener for US case law, CanLII for Canadian decisions, a statutes server for US primary law), then add one short router prompt that tells the model which server owns which jurisdiction. The client merges all three tool catalogs into a single toolbox, and a cross-jurisdiction legal research workflow runs in one chat thread. The full config and routing prompt are below.
TL;DR
- A legal MCP workflow built on three servers covers three jurisdictions and three data lanes: US case law and eCFR via CourtListener, Canadian decisions and legislation via CanLII, and US primary law (Constitution, USC, CFR, Federal Rules, 50-state codes, Executive Orders) via a statutes server.
- Running multiple MCP servers for legal research is a configuration problem plus one short system prompt that teaches the model which drawer to open first.
- That router prompt does the work most teams try to solve with custom agents or LangGraph state machines. You can ship the whole thing in an afternoon.
- The hard part is rate-limit budgeting, key management, and verification on top of whatever the model returns.
In this stack, what is CanLII's hard daily request cap?
Part of our MCP and developer guide series.
New to single-server setup first? Start with the CourtListener MCP and the CanLII MCP, then come back here to compose them. If you have never connected an MCP server to Claude, use Claude for US legal research over MCP walks the first install, and the MCP registry guide covers where these servers are published.
Why compose multiple legal MCP servers
A war story to anchor the rest. Earlier this year I watched a junior associate at a small US firm handle a cross-border employment dispute. The senior partner asked her to check whether a non-compete written under California law would also bind the engineer when he moved to a Toronto subsidiary.
She had GPT-4 open in one tab and was pasting in clauses. The model confidently told her Edwards v. Arthur Andersen controlled in Ontario. She almost sent that.
Edwards is a California Supreme Court case. In Ontario, Shafron v KRG controls. One model, one corpus, one wrong answer. The takeaway: one general-purpose LLM cannot reach across jurisdictions, and pasting in clauses does not fix that.
No single legal API will give you what that matter needed. CourtListener has every published US opinion but does not give you a clean section-by-section view of 17 CFR. CanLII has every Canadian decision and statute but stops at the border.
If your client's contract has a cross-border choice-of-law clause, you need at least two corpora. Before MCP, the answer was either glue code (you write an orchestrator, you maintain it, you eat the bug surface) or context-stuffing. Both fall over the moment a partner asks a follow-up.
MCP changes the shape. Each provider ships a server. Your editor connects to all of them. The model treats the tools as one toolbox. The only piece of code you write is the system prompt that tells the model which drawer to open first.
The three servers at a glance
| Server | What it covers | Cost | Auth |
|---|---|---|---|
| CourtListener | US federal and state opinions, dockets, RECAP, eCFR, oral argument audio, judges | Free for non-commercial use | BYOK CourtListener token |
| CanLII | Canadian federal and provincial decisions plus legislation metadata, bilingual | Free API, key required | BYOK CanLII key |
| Statutes server | US Constitution, USC, CFR, Federal Rules, all 52 state and territory codes, state constitutions, state court rules, Executive Orders since 2015 | Commercial, per-seat tier | API key |
A few details that matter when you compose them.

CourtListener is the largest open collection of US court data, covering federal and state opinions, dockets, the RECAP archive of PACER filings, oral argument audio, and judges. For a multiple-MCP-servers legal research setup it is the case-law lane: any time the question touches a US case name, a reporter cite, a docket, or a judge, the model should route there.
One constraint to design around: it is a free resource, so an agent that free-runs in an exploratory loop can burn through a lot of requests fast. Budget for that in the router prompt, not after the bill.
CanLII is operated by the Federation of Law Societies of Canada. The API returns metadata, not full document text. That is a real constraint: you can ask "what does R v Jordan cite" and get a clean answer; you cannot ask "summarize the reasoning in paragraph 47" and expect the MCP to return text.
For full text you fall back to the public website. The hard cap is 5,000 requests per day, and you will hit it faster than you think when an agent decides to expand a citation graph two hops out.
The statutes server's corpus: USC, CFR, federal rules, and all 52 state and territory codes, retrieved section by section.
The US-primary-law server is the commercial piece in this stack. Public statutes APIs in this category expose structured access to USC, CFR, state codes, federal rules, and EOs.
There is no case-law endpoint and no ask-a-legal-question endpoint on the statutes API surface; case law lives in CourtListener. For statutes work this is the difference between an agent answering "what does 17 CFR 240.10b-5 say verbatim" in two seconds versus the agent improvising and getting it 80% right.
One config, three servers
Drop this into ~/Library/Application Support/Claude/claude_desktop_config.json (or the equivalent on your platform). Cursor's mcp.json, VS Code's .vscode/settings.json, and Claude Code's claude mcp add all take equivalent inputs.
{
"mcpServers": {
"courtlistener": {
"url": "https://courtlistener-mcp.vaquill.ai/mcp/",
"headers": {
"X-CourtListener-Token": "YOUR_COURTLISTENER_TOKEN"
}
},
"canlii": {
"url": "https://canlii-mcp.vaquill.ai/mcp",
"headers": {
"Authorization": "Bearer YOUR_CANLII_API_KEY"
}
},
"statutes": {
"command": "uvx",
"args": ["vaquill-mcp"],
"env": {
"VAQUILL_API_KEY": "vq_key_your_key_here"
}
}
}
}
Three lines of substantive config, three keys, one restart of the editor. That is the whole installation.
Both hosted endpoints use streamable HTTP transport. CanLII uses an Authorization: Bearer header (hosted gate plus key forwarding).
CourtListener uses X-CourtListener-Token because the upstream CourtListener API uses that header name and we wanted the MCP to be a transparent pass-through rather than re-map headers. The statutes server runs locally via uvx because it ships as a Python package and most users prefer to keep the API key in their own environment.
The router prompt
The model does not automatically know that "non-compete in Ontario" goes to CanLII and "17 CFR 240.10b-5" goes to the statutes server. Without a router prompt it guesses, and the guess is wrong about a third of the time once you stack three servers.
The fix is a short, opinionated system prompt that names the tools and the lanes they cover.
This is the one I run. It lives in the Claude project system prompt, the Cursor .cursorrules file, or the equivalent.
You have three legal MCP servers connected. Use them as follows.
JURISDICTION ROUTING
- US case law, dockets, PACER filings, judges, oral argument audio, eCFR
text: ALWAYS use the courtlistener server.
- Canadian decisions, Canadian federal/provincial legislation metadata:
ALWAYS use the canlii server. Note that CanLII returns metadata only;
for full text, point the user at canlii.org.
- US Constitution, US Code (USC), Code of Federal Regulations (CFR text
and structure), Federal Rules of Civil/Criminal/Evidence Procedure,
state statutes and constitutions, state court rules, Executive Orders
since 2015: ALWAYS use the statutes server.
COMPOSITION RULES
- If the question spans multiple jurisdictions or data lanes, call all
relevant servers IN PARALLEL, then synthesize.
- Never substitute a guessed citation when a tool call would resolve it.
- When you cite a US case, confirm the citation with the courtlistener
citation_lookup_citation tool before including it in output.
- When you cite a US statute or regulation, retrieve the section text
before paraphrasing it.
CONSTRAINTS
- CanLII has a 5,000-call/day cap. Prefer get_case over list_cases for
targeted questions; only browse a court's catalog if the user asks.
- CourtListener is a free nonprofit resource. Batch citation lookups
with citation_batch_lookup_citations when you have more than two.
- If you are unsure which server applies, ask the user one clarifying
question rather than guessing.
A few notes on shape.
The lanes are stated in terms of data the user might mention, not in terms of MCP tool names. The model is better at routing "Ontario" to a server than at routing "a question that could be answered by list_legislation". Tool names go in the prompt for the verification rules where the call site matters, not for the routing rules where the user's words matter.
Parallel calls are explicit because Claude will otherwise serialize them. Three sequential round trips feel slow even when each call is fast. With the parallel instruction in place, a cross-jurisdiction question that touches all three servers usually returns in five to eight seconds.
The "ask one clarifying question" line is the most important rule and the one most engineers want to delete. Keep it.
The router cannot anticipate every legal phrasing. When the model is confused, one clarifying question is dramatically cheaper than five wrong tool calls.
For quick reference, here is the routing table in one shot:
| User utterance contains | Route to | Verification action |
|---|---|---|
| US case name, reporter cite, docket, judge, PACER | courtlistener | citation_lookup_citation before reasoning |
| USC, CFR, state code, federal rule, EO | statutes server | retrieve get_section_body text |
| Canadian province, SCC, FCA, neutral cite | canlii | get_case for metadata, send user to canlii.org for text |
| Two or more of the above | all relevant, in parallel | verify each lane independently |
Three worked examples
Example 1: cross-border non-compete enforceability
Prompt: "Compare non-compete enforceability for a senior engineer leaving for a competitor: California (US) vs Ontario (Canada). I need controlling authority on both sides."
The router fires three tools in parallel:
1. statutes.search_statutes
{ state: "ca", query: "business and professions code 16600 non-compete" }
-> Cal. Bus. & Prof. Code section 16600
2. courtlistener.search_opinions
{ q: "non-compete employee mobility California Edwards v Arthur Andersen",
court: "cal" }
-> Edwards v. Arthur Andersen LLP, 44 Cal. 4th 937 (2008)
3. canlii.list_cases
{ databaseId: "onca", search: "restrictive covenant employee non-compete" }
-> Shafron v KRG Insurance Brokers (Western) Inc, 2009 SCC 6
-> Tank Lining Corp v Dunlop Industrial Ltd, 1982 CanLII 1730 (ON CA)
The model then runs get_section_body for the actual text of section 16600, get_opinion for Edwards, and get_case for Shafron plus one Ontario CA decision. Final synthesis lays the California rule (non-competes are void with narrow statutory exceptions) next to the Canadian rule (Shafron's "reasonable in geography, time, and activity" test) with cited authority.
The shape of the answer matters. The US side cites a statute and a controlling case. The Canadian side cites two controlling cases and notes that Ontario follows the SCC's Shafron framework. Every authority has been resolved through a tool call.
Example 2: regulation plus interpretive case law
Prompt: "What does 17 CFR 240.10b-5 say, and what are the most-cited cases interpreting it?"
Cleaner: two servers, no Canadian content. The router fires:
1. statutes.get_section_body
{ code: "cfr", title: 17, section: "240.10b-5" }
-> full text of the regulation
2. courtlistener.search_opinions
{ q: "10b-5 securities fraud scienter reliance", court: "scotus",
order_by: "citeCount desc" }
-> Basic Inc. v. Levinson, 485 U.S. 224 (1988)
-> Dura Pharmaceuticals v. Broudo, 544 U.S. 336 (2005)
-> Halliburton Co. v. Erica P. John Fund, 573 U.S. 258 (2014)
What is interesting is what does not happen. The model does not call CanLII at all. The router prompt is explicit that Canadian law is out of scope unless the user mentions it.
Without that line, I have seen Claude fire a CanLII call "for completeness" on a pure US securities question. CanLII's 5,000/day budget evaporates fast when an agent thinks "for completeness" is a feature.
Example 3: cite-checking a brief
Prompt: "Cite-check this draft motion to dismiss. Flag any case that does not resolve, any statute that does not exist, and anything where the cited proposition does not match what the court actually held." (User pastes a ten-page brief with thirty-five citations.)
This is where composition pays for itself.
1. courtlistener.citation_extract_citations_from_text
{ text: "<the brief>" }
-> 35 citation candidates
2. courtlistener.citation_batch_lookup_citations
{ citations: [<the 35>] }
-> 32 resolved, 3 fail-to-resolve
3. For each statute citation in the brief (5 of them):
statutes.get_section
-> 5 resolved, 0 missing
4. For each of the top 5 most-relied-on cases:
courtlistener.get_opinion
-> read the opinion to check the cited proposition
The three failed cases are the interesting output. One was a transposed reporter ("455 U.S. 745" instead of "457 U.S. 745"). One was a citation to a slip opinion that had been replaced by a published version with a different page. One was a hallucinated case that did not exist.
That last one is why this workflow matters: a model writing a brief from its training data alone will, with some non-trivial probability, invent a citation. A model with citation_lookup_citation plugged in will not, because invented citations fail the resolution step and get flagged before they reach the partner.
I run this on every brief I review. It takes maybe ninety seconds. The partner sees a clean draft instead of a "this case does not exist" follow-up email at 11pm.
What the numbers look like
I ran example 1 (the California vs Ontario non-compete question) ten times against Claude Sonnet 4.5 in Claude Desktop on macOS, residential US Pacific connection, with the config above. Rough numbers from those runs:
| Metric | Median | p95 |
|---|---|---|
| End-to-end wall clock | 6.4s | 9.1s |
| Tool calls per run | 8 | 11 |
| CanLII calls per run | 2 | 3 |
| CourtListener calls per run | 3 | 5 |
| Statutes server calls per run | 2 | 3 |
Two takeaways. First, the parallel-call instruction does most of the work on latency; without it the same workflow ran 12 to 18 seconds.
Second, at 8 tool calls per run, a five-lawyer firm doing twenty cross-border questions per day uses roughly 5 × 20 × 22 ≈ 2,200 tool calls across all three servers per working month, well inside CanLII's 5,000/day per-key cap (one shared key). You only start worrying when an agent enters a loop, which is what max_tool_calls is for.
A reasonable production wrapper config:
{
"agent": {
"max_tool_calls_per_turn": 15,
"retry": { "5xx": { "max": 3, "backoff_ms": [500, 1500, 4000] } },
"alerts": {
"tool_calls_per_60s": 50,
"citation_resolution_fail_rate_pct": 5
}
}
}
Wire those into whatever you use for observability (Datadog, OpenTelemetry, or a print statement in a shell wrapper) and you will catch the runaway runs before they bill out the daily CanLII quota.
Gotchas
Rate limits add up. CanLII is 5,000 calls/day, hard cap. CourtListener is generous but it is a free resource, and "your AI agent in an exploratory loop" is exactly the usage pattern that abuses it. The statutes server has its own per-tier limits.
If you let an agent free-run on a research question for ten minutes, it can fire a thousand tool calls without breaking a sweat. Put a max-tool-calls cap on the agent and short-circuit aggressive expansion (citation graphs two hops out are usually noise anyway).
A reproducible budget check before you ship: have the model run example 1 above ten times in a row and log tool-call counts. If a single cross-border question routinely fires more than 8 to 12 tool calls, your router prompt is too loose. Tighten the verification rules to "verify on output, not on every intermediate hop."
BYOK key management. Three keys means three rotation schedules and three audit trails. For a solo, a .env and a yearly rotation is fine.
For anything multi-seat, run the keys through a vault and inject them at editor start. The hosted MCP endpoints are pass-through; if you leak the CourtListener token, you leak access to your CourtListener quota, not to ours.
Verification is still on you. MCP wires the model to real data. It does not eliminate the hallucination layer between the data and the output. The model can still misread a case, misquote a statute, or paraphrase in a way that flips the meaning.
The cite-checking pattern above is a partial answer: resolve every citation, retrieve the section text for every statute claim, and have a human read anything that goes out the door.
The hard part is not wiring the servers. It is the verification, key management, and rate-limit budgeting on top of whatever the model returns.
Tool-call order matters more than it should. Claude is good at parallel calls when told to parallel. Without the explicit instruction, it serializes, and on a three-server question you eat three round trips.
Cursor does not handle this as gracefully as Claude Desktop in my testing. If you are on Cursor and the response feels slow, that is usually the reason.
Canadian metadata is metadata. The CanLII MCP returns titles, citations, dates, keywords, and the citation graph. It does not return full opinion text.
For text, the model has to send the user to canlii.org. Put that fallback in the router prompt or the model will improvise summaries from metadata alone, which is bad.
Troubleshooting in five steps
When the workflow misbehaves, this is the order I check things.
- Slow responses. Confirm the router prompt actually says "in parallel." Without it Claude serializes calls and you eat 3x the latency.
- Runaway tool calls. Set
max_tool_callsper turn. If you do not have a wrapper that supports this, cap the agent's loop by turn count instead. - Unresolved citations. Switch from individual
citation_lookup_citationcalls tocitation_batch_lookup_citations. The batch tool also returns clearer error reasons. - Empty CanLII results. Check the
databaseId. The router will sometimes pick the wrong court (e.g., a tribunal database when you wanted the Court of Appeal). Force alist_case_databasescall first when unsure. - Auth failures. Confirm header names:
X-CourtListener-Tokenfor CourtListener,Authorization: Bearerfor CanLII. They are not interchangeable, and Cursor's MCP setting UI silently lowercases header keys in some builds.
FAQ
What does it mean to compose legal MCPs?
Composing legal MCPs means connecting several legal MCP servers to one AI client at the same time and letting the model treat their combined tools as a single toolbox. You add each server to one mcpServers config block, add a short router prompt so the model knows which server owns which jurisdiction, and ask one question that may touch all of them. The client routes each tool call to the right server behind the scenes.
How do I run multiple MCP servers for legal research at once?
Add each server as its own entry in the client's mcpServers config (hosted servers use a url plus headers, local servers use a command), supply each server's API key, and restart the client. Claude Desktop, Claude Code, and Cursor all read the same JSON shape. There is no hard cap on server count, but keep the total active tool list manageable; most legal stacks run three to five servers comfortably.
How does the client decide which MCP server to call?
The model picks the tool from the merged catalog based on the tool descriptions and your prompt, then the MCP client routes that call to the server that owns the tool. Routing is invisible to the model, which only sees one action space. A short router system prompt makes the choice reliable by mapping plain-language cues (a Canadian province, a US reporter cite, a CFR section) to the right server.
Can one prompt query US and Canadian law together?
Yes. With CourtListener, CanLII, and a US statutes server connected, a single cross-border question (for example, non-compete enforceability in California versus Ontario) can fire calls to all three in parallel and return one synthesized answer. The router prompt should tell the model to call relevant servers in parallel and verify each jurisdiction's authority independently.
Does Vaquill AI offer a case-law API?
No. The Vaquill AI statutes server is US primary law only: the Constitution, US Code, CFR, Federal Rules, all 52 state and territory codes, state constitutions and court rules, and Executive Orders since 2015. US case law comes through the CourtListener MCP, which is a separate server in this stack.
How do I keep an MCP agent from blowing through rate limits?
Cap tool calls per turn, prefer targeted lookups over catalog browsing, and verify citations on the final output rather than on every intermediate hop. CanLII enforces a 5,000-call-per-day limit, so a runaway agent in an exploratory loop is the main risk. A reproducible budget check is to run one representative question ten times and log the tool-call counts; if a single question routinely fires more than 8 to 12 calls, tighten the router prompt.
Which clients support multiple MCP servers?
Claude Desktop, Claude Code, and Cursor all support multiple servers from one config file, and they share the same JSON format. Hosted servers connect over streamable HTTP with a url and headers; local servers launch with a command. After editing the config, fully quit and reopen the client so it reloads the server list.
What this composes to
Three MCP servers, one config block, one router prompt, and you have an AI legal research environment that handles US case law, US primary law, and Canadian decisions and statutes in one chat thread, with grounded citations, parallel tool calls, and a verification path you can defend.
Most of the value is in the routing, not in any individual server. Each server is a clean data lane, and the router prompt is the thing that turns three lanes into one cross-jurisdiction answer with grounded citations.
Vaquill AI publishes the statutes MCP, the CourtListener MCP, and the CanLII MCP as separate servers, exactly so you can compose them this way. The statutes server is US primary law only; US case law comes through the CourtListener MCP, not a Vaquill AI case-law API. If you want the composed workflow without wiring it yourself, the same stack runs inside Vaquill AI's legal research workbench, or email contact@vaquill.ai.
New legal AI guides, weekly.
Further Reading
Replace Harvey AI: Build Your Own Legal AI Stack on Claude + MCP (2026)
Read postLegal Research from Inside Cursor and Claude Code: The MCP Way
Read postCourtListener MCP Server: Setup Guide for US Legal Research
Read postSearching US Statutes and Regulations With AI: Is the Section Still Current?
Read postThe MCP Registry Is the New App Store for Legal AI. Here's How to Read It.
Read postNon-Compete Enforceability by State: A 2026 Map
Read post
Co-Founder & CTO
Priyansh leads engineering and AI at Vaquill, from the matter workbench to drafting, document comparison, document matrix, and citation-verified research.