DocumentationBrowse
MCP server
16 tools, each one the same call as an endpoint you can already reach over REST, in the order an agent should reach for them: check_citations, check_support and brief_check verify what a model produced, citation_lookup says what we hold on one authority, search_rules returns rule text word for word, search_texas_law retrieves from the fused index, and the rest cover deadlines, contract-law rules, timekeeping and the case-file scrambler. Same corpora, same auth, same rate limits and metering: a tool call counts as a call to the REST endpoint it wraps, so the two never disagree about your usage.
Why a separate endpoint
DocketRouter does not sell corpus downloads. The corpora are hosted as endpoints; MCP is the interface for an agent that would otherwise have to be told to call a dozen separate REST routes by hand.
If you are writing your own HTTP client rather than pointing an MCP-aware app at this endpoint, skip to the wire format.
Running this next to a case-law MCP server
Case-law breadth is not what this endpoint is for, and we will not pretend otherwise.
CourtListener, from the non-profit Free Law Project, publishes its own MCP server over case law for every state and federal court, the same public record our case-law shard is built from. If your agent needs to find cases anywhere in the country, mount theirs (access to it is arranged through CourtListener, as a membership benefit). Mount this one for the part their corpus does not carry: citation verification with found-or-unverified semantics, support checking with a verbatim-or-absent quote, court rules and statutes held word for word, and depth in the jurisdictions we serve. The two compose; nothing here is a claim of affiliation with them.
Connect a client
Point any MCP-aware client at the endpoint with your key as a bearer token. No local process, no stdio transport to manage.
Claude Code
claude mcp add docketrouter --transport http https://docketrouter.ai/api/mcp --header "Authorization: Bearer dr-…"
Claude Desktop
{
"mcpServers": {
"docketrouter": {
"url": "https://docketrouter.ai/api/mcp",
"transport": "http",
"headers": { "Authorization": "Bearer dr-…" }
}
}
}Cursor
{
"mcpServers": {
"docketrouter": {
"url": "https://docketrouter.ai/api/mcp",
"headers": { "Authorization": "Bearer dr-…" }
}
}
}A generic HTTP MCP client
POST JSON-RPC 2.0 to https://docketrouter.ai/api/mcp with Authorization: Bearer dr-… and content-type: application/json. Send initialize, then notifications/initialized, then tools/list or tools/call as needed. Server-sent events are not required: every response here is a single value, returned as one application/json body.
Tools (16)
Every tool is the same call as an endpoint you can already reach over REST. Nothing here is MCP-only, and nothing here is priced differently because it arrived as a tool call.
| Tool | Wraps | Auth | Required arguments |
|---|---|---|---|
check_citations | POST /api/v1/citations/check | key or session | citations |
check_support | POST /api/v1/citations/support | key or session | citation, proposition |
brief_check | POST /api/public/brief-check | no key | text |
citation_lookup | the /cite page's library-only resolution | no key | citation |
search_rules | POST /api/v1/rag/rules | key or session | q |
search_texas_law | POST /api/v1/rag/query | key or session | q |
deadline | computeDeadline() behind /deadlines (no HTTP) | no key | rule_set, trigger, days |
contract_rules | GET /api/v1/contracts/rules | no key | none required |
contract_review | POST /api/v1/contracts/review | key or session | text, jurisdiction |
legal_panel | POST /api/v1/panel | key or session | question |
docket_watch | GET /api/v1/watch (read only) | key or session | action |
time_entries_list | GET /api/v1/time | key or session | none required |
time_entry_create | POST /api/v1/time | key or session | started_at, narrative |
scramble | POST /api/v1/scramble | key or session | matter_id, text |
unscramble | POST /api/v1/unscramble | key or session | matter_id, text |
get_instructions | GET /llms.txt | no key | none required |
Each tool's full JSON Schema is in tools/list's response and on the Library as OpenAI and Anthropic function-calling JSON, generated from the same schema objects so all three renderings agree.
Callable with no key
5 of the 16 tools take no Authorization header at all, because the endpoint each one wraps is already free and account-free on the web: brief_check, citation_lookup, deadline, contract_rules, get_instructions. An agent that has not yet been given a key can still ask this server how to use it, check a whole brief, look up an authority or count a deadline. The two that read our stores, brief_check and citation_lookup, draw the same strict per-IP bucket as their public web form, so the free offer is not multiplied by coming through MCP. The remaining 11 need a key or a signed-in session, exactly as their REST endpoint does; the account-scoped ones return nothing at all for a key with no account behind it rather than guessing whose data to show.
Instructions for your agent
The standing brief every agent gets, in the same words, from three places: the MCP initialize handshake, the get_instructions tool, and plain GET /llms.txt.
Every initialize response's instructions field carries this text, so an MCP-aware client shows it to its model automatically on connect with no extra call. An agent that loses it out of context, or that only reads tools/list and never initialize, can ask for it back at any time by calling get_instructions with no arguments (no key required), or by fetching https://docketrouter.ai/llms.txt directly. All three return byte-identical text.
Wiring a system prompt by hand instead of going through MCP (a plain chat integration, a custom agent loop), paste the contents of /llms.txt into the system prompt once, and repeat the same call your model would otherwise make against check_citations and check_support before it shows a user a citation, since a hand-wired prompt gets none of MCP's per-call tool enforcement for free.
DocketRouter is a verification layer with a legal library behind it, not a national case-law search engine. Use it in this order. 1. check_citations: does this citation exist? Checked against an offline table of 18,123,788 reporter citations across 989 reporters, plus the case-law index. The answer is "found" or "unverified", never "nonexistent". 2. check_support: does the cited opinion actually support the proposition it is cited for? Returns supports, does_not_support or unclear, with a quote that is verbatim from the opinion or absent. 3. search_rules: the text of a court rule or statute section, 3,860 sections held word for word, so a rule is quoted rather than recalled. 4. search_texas_law: retrieval over the fused index, which covers Texas case law, Texas statutes, U.S. Supreme Court, Texas county and local rules, Texas court rules, California case law, New Jersey case law, Virgin …
The JSON-RPC shape
For writing your own client rather than using an MCP-aware app.
initialize
--> { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18" } }
<-- { "jsonrpc": "2.0", "id": 1, "result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": {} },
"serverInfo": { "name": "docketrouter", "version": "1.0.0" }
} }
--> { "jsonrpc": "2.0", "method": "notifications/initialized" } // no response: it is a notificationtools/list
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [
{ "name": "check_citations", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["citations"] } },
{ "name": "check_support", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["citation","proposition"] } },
{ "name": "brief_check", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["text"] } },
{ "name": "citation_lookup", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["citation"] } },
{ "name": "search_rules", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["q"] } },
{ "name": "search_texas_law", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["q"] } },
{ "name": "deadline", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["rule_set","trigger","days"] } },
{ "name": "contract_rules", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": [] } },
{ "name": "contract_review", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["text","jurisdiction"] } },
{ "name": "legal_panel", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["question"] } },
{ "name": "docket_watch", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["action"] } },
{ "name": "time_entries_list", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": [] } },
{ "name": "time_entry_create", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["started_at","narrative"] } },
{ "name": "scramble", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["matter_id","text"] } },
{ "name": "unscramble", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": ["matter_id","text"] } },
{ "name": "get_instructions", "description": "…", "inputSchema": { "type": "object", "properties": { … }, "required": [] } }
] } }tools/call
--> { "jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "check_citations", "arguments": { "citations": ["488 U.S. 222", "999 F.9th 1"] } } }
<-- { "jsonrpc": "2.0", "id": 3, "result": {
"content": [ { "type": "text", "text": "{\"results\":[…],\"checked\":2,\"found\":1,\"took_ms\":21}" } ],
"structuredContent": { "results": [ /* same object, not stringified */ ], "checked": 2, "found": 1, "took_ms": 21 }
} }content[0].text is the JSON-encoded form of structuredContent, for a client that only reads text content blocks. Both carry the exact same body the matching REST endpoint returns.
Batching
The body may be a single JSON-RPC object or a non-empty array of them. A batch is answered with an array of responses in the same order; a request with no id (a notification) never appears in the response, including when it fails.
Errors
Protocol-level failures use JSON-RPC's own reserved codes. Everything else (an invalid tool argument, an unresolvable auth token, a rate limit, the index being unavailable) carries the exact message the matching REST endpoint's error.message would have, so the two are never worded differently for the same failure.
| Code | Meaning |
|---|---|
-32700 | Parse error: the body was not valid JSON, or was an empty array. |
-32600 | Invalid request: not a JSON-RPC 2.0 object, or missing method. |
-32601 | Method not found. |
-32602 | Invalid params: an unknown tool name in tools/call, or a tool's own argument validation failed (same wording as the REST 400). |
-32603 | Internal error: an unexpected exception, degraded rather than propagated. |
-32000 | Server error: authentication failed, a rate limit was hit, or the underlying endpoint is unavailable (503-equivalent). Carries a data.status matching the REST status code. |
For a non-batch call, the overall HTTP status is the same one the wrapped REST endpoint would return (401, 429, …), with a JSON-RPC error object in the body naming the failing request's id. A batch always answers 200, since a batch response is an array covering requests that may have succeeded or failed independently.
Examples
curl https://docketrouter.ai/api/mcp \
-H "Authorization: Bearer dr-…" -H "content-type: application/json" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "search_texas_law", "arguments": { "q": "TRCP 21a service by email", "k": 3 } } }' \
| jq '.result.structuredContent.hits'Pricing and limits
Every tool call is priced exactly as the REST endpoint it wraps. Calling a tool over MCP and calling its REST route directly share one quota and one bill, not two.
| Tool | Price |
|---|---|
check_citations | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
check_support | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
brief_check | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
citation_lookup | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
search_rules | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
search_texas_law | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
deadline | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
contract_rules | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
contract_review | not invoiced on a host where review pricing is off (the default). Read the response's `billing` field, which carries `pricing_enabled` and `charged_usd` for THIS call rather than a promise made here. The measured model spend counts against your key's spend cap either way. |
legal_panel | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
docket_watch | free during beta, metered in the same per-key bucket as the REST endpoint this tool wraps, so calling both doors for the same work is one bill rather than two. |
time_entries_list | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
time_entry_create | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
scramble | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
unscramble | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
get_instructions | free. This tool is not invoiced, makes no upstream model call, and nothing it does counts against a spend cap. |
The metered retrieval and verification tools are currently free during beta and draw the same per-key rate-limit bucket as their endpoint (30 calls per minute, burst 10; legal_panel is 6 per minute, burst 3, because a panel is one generation per seat). The tools that call a route rather than a library are admitted, rate-limited and metered by that route and are deliberately not metered a second time here. See Retrieval limits and Citation limits for the per-field caps (query length, citation count, and so on) each tool's arguments still enforce.
A whole JSON-RPC body is capped at 256KB and the endpoint allows 60 seconds, so a long document, whether a large contract for contract_review or a long file for scramble, has to go to the REST route directly even though the route itself accepts more. File exports (CSV, LEDES, Clio from time_entries_list) and streaming also stay on REST: a tool result is one JSON value, and pretending otherwise would hand an agent a truncated file.
Something here wrong or missing? Mail hello@docketrouter.ai with the request_id and we will fix the docs or the API, whichever is broken.