MCP
Integrate Midpage's legal database with AI assistants using the Model Context Protocol. Research opinions, trial filings, dockets, statutes, and regulations through five tools.
Note: The v4 tools are in preview. Their names, fields, defaults, and behavior can still change at
/mcp/v4without a new major version. Refreshtools/listwhen connecting and check the changelog when updating an integration. MCP v3 remains available as a stable contract.
Versioning
These docs describe the current MCP contract, v4.
Midpage uses major versions for tool contract changes that integrations may need to account for, such as output schema changes or newly introduced tools. If your integration depends on fixed tool schemas or output fields, use a pinned version endpoint.
Previous version docs and migration notes:
- MCP v3:
https://app.midpage.ai/mcp/v3 - MCP v2:
https://app.midpage.ai/mcp/v2 - MCP v1:
https://app.midpage.ai/mcp/v1 - MCP changelog
Base URL
https://app.midpage.ai/mcp/v4
Use this pinned URL for the v4 contract documented here. Older SSE clients can use /mcp/v4/sse with /mcp/v4/message.
If you want your integration to automatically receive the latest tool implementations, use the unversioned URL instead. It serves v4 today:
https://app.midpage.ai/mcp
Authentication
Midpage supports two authentication models for MCP integrations.
| Option | Best for | How it works |
|---|---|---|
| API key | Server-side integrations, internal tools, and quick prototypes | Your integration sends one shared API key with each request. |
| OAuth | End-user apps, partner integrations, and multi-tenant products | Each user signs in with their own Midpage account and your client sends that user's access token. |
1. API Key
Use API key auth for non-interactive access, server-side jobs, or any integration that should run under one shared credential.
Generate an API key in the Developer Portal. If you need help, contact support@midpage.ai. Then send it in your MCP request headers as:
Authorization: Bearer <api_key>
2. OAuth
Use OAuth when your client should connect each user to their own Midpage account. Each user will need a Midpage account before they can sign in and authorize your client.
For most customers, the easiest setup is:
- Point your client at
https://app.midpage.ai/mcp/v4 - Let your MCP or OAuth library follow Midpage's discovery metadata and register a client automatically if it supports dynamic client registration
- Let the user sign in and approve access when prompted
Most MCP clients should not need pre-provisioned OAuth app credentials. Midpage's Clerk auth server supports dynamic client registration, so compatible clients can create their own credentials automatically. If your client cannot do that and needs a pre-provisioned OAuth app, contact Midpage.
Need the manual OAuth settings?
If your client supports discovery, start there. In most cases, pointing it at https://app.midpage.ai/mcp/v4 is enough. Midpage publishes protected-resource metadata on app.midpage.ai, and that metadata points clients to the Clerk authorization server. The settings below are only for cases where you need to configure the OAuth flow yourself.
Discovery
- MCP protected-resource metadata:
https://app.midpage.ai/.well-known/oauth-protected-resource/mcp - Clerk OAuth metadata:
https://clerk.midpage.ai/.well-known/oauth-authorization-server - Clerk OIDC discovery:
https://clerk.midpage.ai/.well-known/openid-configuration
Some older MCP clients expect OAuth authorization-server metadata on the same origin as the MCP server. Midpage currently publishes protected-resource metadata on app.midpage.ai and authorization-server metadata on the Clerk domain above, so older clients may need manual configuration using those Clerk URLs.
The current metadata advertises:
- authorization server:
https://clerk.midpage.ai - dynamic client registration endpoint:
https://clerk.midpage.ai/oauth/register - PKCE challenge method:
S256 - resource scopes:
profile,email
Which OAuth flow to use
- Public clients such as desktop apps, mobile apps, browser apps, and local CLIs should use Authorization Code + PKCE (
S256). Do not embed aclient_secretin those clients. If you need this mode, Midpage can provision your Clerk OAuth app as a public client. - Confidential server-side apps can use Authorization Code and exchange the code on their backend with either
client_secret_basicorclient_secret_post. - If you need refresh tokens for long-lived sessions, request
offline_access.
Minimal flow
- Fetch the protected-resource metadata document and read
authorization_servers. - Fetch the Clerk authorization-server or OIDC metadata document and use the published endpoints.
- Start an authorization request with
response_type=code,client_id,redirect_uri,state, and theresourcevalue from the protected-resource metadata document. - Request
profile emailfor MCP access. Addopenidif your client expects an ID token. Addoffline_accessif you need a refresh token. - Public clients must also send
code_challengeandcode_challenge_method=S256. - Exchange the authorization
codefor tokens at the Clerk token endpoint, again including the sameresourcevalue. - Call the MCP server with
Authorization: Bearer <access_token>.
Example authorize request for a public client
GET https://clerk.midpage.ai/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/oauth/callback&
resource=https%3A%2F%2Fapp.midpage.ai&
scope=profile%20email%20openid%20offline_access&
state=RANDOM_VALUE&
code_challenge=BASE64URL_SHA256(code_verifier)&
code_challenge_method=S256
Example token exchange for a public client
curl -X POST "https://clerk.midpage.ai/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://your-app.com/oauth/callback" \
-d "resource=https://app.midpage.ai" \
-d "code_verifier=YOUR_CODE_VERIFIER"
Example token exchange for a confidential client
curl -X POST "https://clerk.midpage.ai/oauth/token" \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://your-app.com/oauth/callback" \
-d "resource=https://app.midpage.ai"
Calling the MCP server
Authorization: Bearer <access_token>
Testing in Hosted MCP Clients
If you just want to try Midpage in Claude, Codex, Perplexity, Cursor, or another hosted MCP client, add https://app.midpage.ai/mcp/v4 and sign in with your Midpage account when prompted.
You can create a free trial account at app.midpage.ai. For production integrations, use API key auth or OAuth as described above.
Choose a tool
| Tool | Use it for |
|---|---|
search |
Find opinions and available state trial filings. |
analyzeCaseDocument |
Read one opinion or filing against a question and return supporting quotations. |
analyzeCaseDocket |
Identify a known docket, answer questions about its metadata/activity, and locate filings. |
searchLaws |
Find statutes, regulations, constitutions, and agency guidance. Same contract as v3. |
analyzeLaw |
Analyze a law provision, navigate its children, or inspect its versions. Same contract as v3. |
For case research, search first and pass document.documentId to analyzeCaseDocument. For a known court and docket number, start with analyzeCaseDocket; pass a selected entry's documentReference plus your question to analyzeCaseDocument.
Search snippets and docket entry descriptions are discovery aids. Analyze the actual document before attributing arguments or holdings to it. analyzeCaseDocument lists the Negative and Caution citing opinions it finds under treatment.history. That is not a complete good-law check, and neutral treatment is not proof that an authority remains valid.
Use tools/list to discover the current schemas. Tool results are JSON serialized inside MCP content blocks of type text, rather than structuredContent. Check MCP isError before parsing: authentication, validation, and execution errors can be plain text. For JSON results, also inspect the tool's status or error; a partial search can contain usable results.
search
Send one to four phrasings of the same research question. The tool merges and deduplicates their results. Hits found by more queries rank first, then by their best rank within a query.
Input
The top-level field is queries, an array of one to four query objects. Each new query accepts:
| Field | Type | Meaning |
|---|---|---|
query |
string, required | Legal issue, keywords, verbatim text, reporter citation, or case name. For known dockets, prefer analyzeCaseDocket. |
jurisdiction |
string | federal, state, state_and_federal (default), or trial for state trial courts. |
courts |
string[] | Up to 20 Bluebook abbreviations, full court names, or circuit regions such as 9th Cir. region. Courts are OR-ed. Omit or send [] for no court filter. |
states |
string[] | State names. Narrows state courts only; federal courts are unaffected. Trial search needs exactly one state, supplied here or implied by named courts. |
startDate, endDate |
string | Inclusive decision/filing date bounds in YYYY-MM-DD. Either bound may be omitted; the start cannot follow the end. |
documentTypes |
string[] | Exact document types, OR-ed. Omit or send [] for opinion, order, and judgment. ["all"] alone removes the type restriction. |
publishStatus |
string | any (default), published, unpublished, or unknown. Applies only to the opinion-search source; trial results are unchanged. unknown selects missing or unknown status. Restrict only when requested. |
Filters compose with AND. For example, a state filter and a named court must agree. Unknown fields are rejected. Optional unused filters may be omitted or null.
The complete documentTypes vocabulary is:
all, opinion, brief, motion, order, transcript, other, unknown,
judgment, memorandum, answer, complaint, declaration, stipulation,
exhibit, notice, summons, certificate, letter, report,
expert_report, settlement_agreement, procedural
Outside state trial courts, search covers opinion documents. Non-opinion types require a state trial search; all does not enable federal filing search. Use analyzeCaseDocket for federal filings. A filtered miss does not establish that no document exists: classifications and coverage can be incomplete. Retry an empty type-filtered query with ["all"]; retry an empty named-court trial query once statewide without courts, keeping the state and subject.
{
"queries": [
{
"query": "PSLRA safe harbor statements of current fact",
"courts": ["9th Cir."]
},
{
"query": "forward looking statement mixed present historical facts",
"courts": ["9th Cir."]
}
]
}
A state trial filing search:
{
"queries": [
{
"query": "SUNTRUST BANK SKIDMORE IRRIGATION",
"jurisdiction": "trial",
"states": ["Florida"],
"documentTypes": ["motion", "order"]
}
]
}
Output
| Field | Meaning |
|---|---|
status |
ok: queries ran; partial: some queries were skipped or a source failed; error: the search could not run. An ok result can be empty. |
results |
One merged array of hits, described below. There is no totalResults field. |
nextCursor |
Optional continuation for this merged result set. |
skipped |
Optional array with query, reason, retryable, and sometimes nextAction. Follow the suggested action or repair the input; retry only retryable failures. |
warnings |
Optional descriptions of empty or narrowed coverage. |
error |
Optional explanation when the search could not complete. |
Each hit has a case with caseId, caseName, court, and optionally docketNumber and url. A readable document has document.documentId, documentType, and url. Opinion metadata is under document.opinion: citation, dateDecided when known, publishStatus, and treatment (status, citationCount, negative, caution). Filing hits can also have entry with available entryNumber and dateFiled; hits without a document may include entryId. Opinion hits omit entry. snippet is optional and is not a verified quotation.
Use UUIDs from the returned fields. The numeric ID inside an opinion reader URL is not a documentId.
Continue with only the returned cursor:
{
"queries": [{ "cursor": "COPY_NEXT_CURSOR_HERE" }]
}
Do not combine a cursor with a new query or filters. Cursors expire after 30 minutes. A search exposes at most 100 results across pages; when no cursor remains, refine the query if you need more. An exact federal docket lookup may instead return skipped[].nextAction directing you to analyzeCaseDocket.
analyzeCaseDocument
Read one opinion or filing against a legal question. The tool returns source-extracted quotations and an answer, rather than the full document text.
Input
| Field | Type | Meaning |
|---|---|---|
question |
string, required | What to determine from this document. |
documentId |
string | Document UUID from search or a docket's documentReference. |
citation |
string | Reporter citation, for example 556 U.S. 662. WL and LEXIS citations are not supported. |
entry |
object | caseId and entryNumber, with optional attachmentNumber. |
Provide one usable reference. If several are supplied, precedence is documentId, then citation, then entry. Omit or set unused references to null. For a main filing, omit attachmentNumber or send null, "", or "0". For an attachment, send its positive integer number as a string, such as "1"; "14-1" is not an attachment number.
{
"citation": "556 U.S. 662",
"question": "Are threadbare recitals of a cause of action entitled to an assumption of truth?"
}
To read a docket attachment, copy the case and entry reference returned by docket analysis:
{
"entry": {
"caseId": "8e00e661-a0c3-5c29-9002-1e7eb0d5730d",
"entryNumber": "14",
"attachmentNumber": "1"
},
"question": "What facts does this exhibit establish?"
}
The attachment example illustrates the input shape, not a claim that this case has that attachment. Use a returned reference for actual retrieval. Stored filings are preferred; a missing copy may require metered PACER acquisition.
Output
An ok result includes:
| Field | Meaning |
|---|---|
case |
Case identity and reader URL. |
document |
documentType, reader url, and documentId when known. Opinions may include opinion with citation, dateDecided, publishStatus, and the treatment block described below. A newly acquired filing may have no document ID; reuse its entry reference. |
entry |
Filing entry number and available attachment, date, and description fields. Omitted for opinions. |
answer |
Direct answer, with relevant procedural outcomes and what the source leaves open. |
supportedPropositions |
Array of proposition, verbatim quote, quoteTruncated, optional deeplinkUrl, and optional opinionSection. |
doesNotAddress |
Unanswered parts of the question. Do not cite this source for those points. |
sourceWarnings |
Optional warnings to resolve before citing: source-status caveats, Negative or material Caution treatment pointing at treatment.history, an unverified later history when the citator has no record, or a treatment check that could not run. |
Preserve the qualifications in each proposition and any concurrence/dissent or party attribution in opinionSection. A proposed order is not an entered ruling. quoteTruncated: true means the returned quotation was shortened. Use deeplinkUrl when present, otherwise document.url, and copy the URL exactly. Do not invent page pinpoints or infer them from PDF page positions.
document.opinion.treatment summarizes the opinion's later history across every stored copy of it:
| Field | Meaning |
|---|---|
status |
Worst treatment across copies and binding citers: Negative, Caution, or the citator's status. unknown means the citator has not seen the opinion. Neutral is not validation. |
citationCount |
Citing opinions across all copies. |
negative, caution |
Citing opinions with that treatment. When binding citers exist, each counts distinct binding citers. |
history |
Negative or Caution citing opinions, up to five: binding courts first, then Negative before Caution, then court rank, then earliest first. Each has category, bindingCourt, citation, court, and when known dateDecided, reason, and a documentId for analyzeCaseDocument. Empty when the citator found none; omitted when the later history is unknown or could not be read. |
more |
Binding citers beyond the five listed. Present only when nonzero. |
Read the listed citing opinions before relying on a flagged case.
Other statuses are invalid_reference, not_found, ambiguous, text_not_available, pacer_fetch_failed, and analysis_failed. Read message; candidates can support a corrected lookup and url can support manual reading. An unread filing must not be represented as analyzed.
analyzeCaseDocket
Answer questions about the case's metadata and recorded activity, including judges, parties, case category, and procedural history. Use it to locate filings, then analyze their contents separately.
Input
| Field | Type | Meaning |
|---|---|---|
question |
string, required | What to determine from the docket metadata or entries. |
caseId |
string | Case UUID from search or document analysis. |
docket |
object | court and docketNumber, plus optional caseName to disambiguate. Courts accept Bluebook abbreviations or full names. |
forceFetch |
boolean | Omit or use false for historical research or finding filings. Use true when the user needs the latest official PACER docket. |
Provide caseId or docket; caseId takes precedence. forceFetch may incur a PACER charge and is ignored with a warning when PACER cannot serve the case. V4 can read stored docket material without an upfront premium gate; live PACER acquisition is metered, and an explicit refresh requires premium access. A first acquisition can require PACER even when forceFetch is omitted.
{
"docket": {
"court": "N.D. Ill.",
"docketNumber": "1:16-cv-11057"
},
"question": "What was filed or entered during the past month?",
"forceFetch": true
}
Output
| Field | Meaning |
|---|---|
status |
ok on a successful analysis. |
case |
caseId, caseName, court, reader url, and available docket/date metadata. |
answer |
Answer based on available metadata and recorded entries. Metadata-only answers can have no selected entries. |
relevantEntries |
Up to 40 entries with hasDocument and available entryNumber, attachmentNumber, dateFiled, description, and documentReference. Stored entries may have entryId. |
doesNotAddress |
Unanswered parts of the question; empty when answered. |
coverage |
kind: docket_report, stored_entries, or opinion_only; entryCount: analyzed entries; truncated: source or returned selection is incomplete. Read warnings and narrow the question when needed. |
freshness |
cached, liveFetchSucceeded, fetchedAt (acquisition timestamp or null), and latestEntryDate (latest recorded entry date or null). |
retrievalSource |
midpage, recap, or pacer. |
docketReport |
Optional downloadUrl and suggestedFileName for the report file. |
warnings |
Optional source and retrieval caveats. |
documentReference is either { "documentId": "..." } or { "entry": { "caseId": "...", "entryNumber": "...", "attachmentNumber": null } }. Attachments use their number instead of null. Pass the object unchanged to analyzeCaseDocument and add question. A reference indicates an available retrieval path, not a guarantee that fetching will succeed.
Use the two report links for different purposes:
| Field | Purpose |
|---|---|
case.url |
Citations and reader links such as View docket. Opens the docket reader. |
docketReport.downloadUrl |
Fetching the original report for download or processing. Link it in an answer only when offering an explicit download. |
Copy these URLs exactly. Do not construct raw-download paths from reader URLs or use a download URL for a “View docket” link.
A recent acquisition timestamp does not prove the underlying report is current. In particular, a newly acquired RECAP report can be stale. A failed refresh does not establish that no new filings exist. Use liveFetchSucceeded, coverage, the requested period, and warnings when describing current status.
Other statuses are invalid_reference, not_found, ambiguous, pacer_fetch_failed, and analysis_failed, each with a message. Ambiguous lookups can return candidates. An analysis failure can also return a top-level downloadUrl for manual reading.
searchLaws
Full-text search over statutes, regulations, constitutions, and agency
guidance — US federal plus all 50 states and DC. Use it to FIND the governing
provision, then call analyzeLaw with the result's id before citing it.
Results are current law by default (see the
Laws API guide for the currency
derivation). Inputs are strict: unknown keys are rejected rather than
silently ignored.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Legal concepts / key terms (BM25 keyword search) |
states |
string[] | No | State names or USPS abbreviations ("California", "TX"); federal law is "Federal". Validated server-side — unrecognized values error with did-you-mean suggestions |
collectionTypes |
string[] | No | statute, regulation, constitution, guidance, executive_order, notice |
collections |
string[] | No | Specific codes, by id (e.g. "cfr", "wa-rcw") or human-readable name (e.g. "Code of Federal Regulations", "Texas Administrative Code") — resolved server-side; unresolvable values error with did-you-mean suggestions |
pathPrefix |
string | No | Subtree filter within one code — a dot-separated ltree path from a previous result (e.g. "cfr.t21" = all of 21 CFR, including descendants); pair with collections. Malformed prefixes error rather than matching nothing |
currentOnly |
boolean | No | Default true — only the law in force today |
page, pageSize |
number | No | Pagination (max 50 per page; results beyond the first 10,000 cannot be paginated) |
Returns:
| Field | Type | Description |
|---|---|---|
results |
array | Matching provisions: id, citation, title, number, jurisdiction and path fields, dates, isCurrent / isHistorical, snippets (matched fragments), and url (the Midpage page for that exact version) |
total |
number | Estimate for the whole query; a page can hold fewer than pageSize results |
stateCounts |
object | Estimated per-state hit counts for the WHOLE query — for state surveys, run one unfiltered query and read this, then verify per state with a filtered search |
error |
string | Set instead of results when a filter could not be resolved or the pagination window was exceeded — the message says how to fix the call |
Never cite from snippets — they are relevance context. Call analyzeLaw
with the result id and quote from its verified passages.
Example:
{
"query": "telehealth prescribing controlled substances",
"collectionTypes": ["regulation"],
"states": ["California", "Texas"]
}
analyzeLaw
Analyze a statute, regulation, constitutional provision, or agency guidance document against a specific legal question, returning verified verbatim passages — the document text is analyzed internally and raw text is never returned. Resolves to the CURRENT version by default (see the Laws API guide for how currency is derived); container nodes return their children for navigation; versioned provisions return the full version chain with dates.
Parameters (provide exactly one of the four lookup keys):
| Parameter | Type | Required | Description |
|---|---|---|---|
citation |
string | No* | Canonical citation (e.g., "42 U.S.C. § 1983", "Cal. Code Regs. tit. 22, § 51451"). Matching ignores punctuation, spacing, and §/sec./section style ("42 USC 1983" works), but not abbreviation differences — use the jurisdiction's canonical wording |
registerCitation |
string | No* | Federal Register / state register citation (e.g., "89 FR 12345") |
path |
string | No* | Full ltree path from a previous result (pair with collection) |
id |
string | No* | Document UUID from a previous result |
question |
string | Yes | The legal question to answer about the document |
collection |
string | No | Disambiguates path/citation/registerCitation lookups — a collection id (e.g., "cfr", "uscode") or human-readable name (e.g., "Code of Federal Regulations"); unresolvable values error with did-you-mean suggestions |
includeHistorical |
boolean | No | Allow superseded / future-effective versions in citation/path/registerCitation lookups (default: current law only); id always returns that exact version |
* Provide exactly one of citation, registerCitation, path, or id
Returns:
| Field | Type | Description |
|---|---|---|
citation, title, number |
string | Identification of the provision |
registerCitation |
string | FR / state-register cite, for regulations that carry one |
collectionId, collectionName |
string | The code the provision belongs to (e.g. cfr / "Code of Federal Regulations") |
effectiveDate, publicationDate |
string | Source-stated dates (null when the source states none) |
analysis |
object | summary, passages (point + verified verbatim quote + deeplinkUrl, a Midpage link that opens the provision at the quoted passage), doesNotAddress — aspects of the question the document does not cover; check it before citing. Absent for containers without their own text |
children |
array | For containers: up to 250 child provisions (id, number, citation, title, sortOrder) — navigation only, analyze a child by its id; childrenTotal is the true count |
versions |
array | Version chain with dates and isHistorical / isCurrent / isRequested flags (empty without history) |
parentId |
string | Container node id — look it up to zoom out |
isCurrent, isHistorical |
boolean | Whether this text is the law in force today / whether it has been superseded |
state, collectionType |
string | Jurisdiction ("Federal" or state name) and statute/regulation/constitution/guidance/executive_order/notice |
url, sourceUrl |
string | Midpage link for this exact version (non-current versions carry ?version=) and official source link |
ambiguousMatches |
array | When the reference matched several documents — candidate summaries; retry with one match's id |
error |
string | The reference could not be resolved or the provision could not be analyzed — the message says how to proceed (some provisions are review-only via url) |
Example:
{
"citation": "21 C.F.R. § 820.30",
"question": "What design control requirements apply to medical device manufacturers?"
}
Migrate from v3
| V3 | V4 preview |
|---|---|
search with opinion-focused filters and per-query results |
search with case/document identities, exact document types, one merged ranking, one status, and one cursor. |
analyzeOpinion and findInOpinion |
analyzeCaseDocument for question-driven opinion or filing analysis. No standalone findInOpinion tool. |
analyzeDocketReport |
analyzeCaseDocket for report metadata and recorded activity. |
analyzeDocketFiling |
analyzeCaseDocument with a document UUID or entry reference. |
searchLaws, analyzeLaw |
Same contracts. |
V3 docket tools keep their premium-access gate. V4 admits stored material without that upfront gate; actual PACER retrieval remains metered. V4 is not a drop-in replacement: update tool names, reference fields, response parsing, citation links, and pagination together. Keep integrations on their existing version until they are ready to follow preview changes.
Changelog
See the MCP changelog for version history and migration notes.