Tool Catalog
This document lists the 16 tools exposed over MCP tools/list and tools/call: 2 statute tools, 10 family-scoped tools and 4 Matters tools. Read Authentication for how scopes are granted, and Usage and units for how calls are metered. Each tool is gated by the scope(s) listed. A key sees only the tools its scopes unlock.
Metering: _meta.datafed.units. Every successful MCP tool result carries _meta.datafed.units, which DataFed meters from. Every tool that is not a verification reports one call unit: [{ "unit": "call", "count": 1 }]. verifyCitation reports verified_citation units instead, one per resolved citation: [{ "unit": "verified_citation", "count": 3 }]. A failed call or a rejected request reports no units. The REST citations/verify response carries the same units array in its body.
The server keeps this list in step with the documentation by an automated check, so a tool cannot be added or removed without the change showing up in review.
Annotations #
Every tool in tools/list carries MCP ToolAnnotations. Read, verification and parseDocument tools are readOnlyHint: true and idempotentHint: true. issueGapReport writes a frozen issuance, so it is readOnlyHint: false, and it is idempotent: issuing the same content again returns the existing issuance. triggerWorkflowRun enqueues a workflow that changes organisation state, so it is readOnlyHint: false, idempotentHint: false and destructiveHint: true; a client should confirm before calling it. Every tool is openWorldHint: false, because the corpus is closed. documents:write on parseDocument is a cost and abuse gate, not a mutation. Annotations are hints. Do not rely on them for security decisions.
| Tool | Scope | Annotations | Honesty note |
|---|---|---|---|
searchStatutes |
statutes:read |
read-only, idempotent | Full-text match over section text; not semantic |
getSection |
statutes:read |
read-only, idempotent | Exact CAP lookup; no currency check |
verifyCitation |
citation:verify |
read-only, idempotent | Resolves Act/Cap and section against the corpus. See How titles are matched and A pinpoint is attested or it is refused |
checkSourceExists |
citation:verify |
read-only, idempotent | Substring existence check on cap_number; prefer verifyCitation |
scoreConfidence |
citation:verify |
read-only, idempotent | Length heuristic, not a verification |
compareLaws |
benchmark:read |
read-only, idempotent | Title match, not a semantic comparison |
identifyGaps |
gap:read |
read-only, idempotent | Returns gap items the org recorded; does not compute them |
scoreAlignment |
gap:read |
read-only, idempotent | 1 - gaps/total; fixed 0.5 when total is omitted and gaps exist |
issueGapReport |
gap:issue |
write, idempotent | Issues the analysis's gap report as a frozen, hashed issuance. Identical content is reused and not billed. The first issuance per workspace is free |
listSkills |
skills:read |
read-only, idempotent | Lists the workflow registry, unfiltered |
triggerWorkflowRun |
skills:trigger |
write, not idempotent, destructive | Enqueues one workflow event; does not wait for the run |
parseDocument |
documents:write |
read-only, idempotent | Parses without persisting. documents:write is a cost and abuse gate, not a mutation |
listMatters |
matters:read |
read-only, idempotent | Ignores status and limit; returns all org Matters |
getMatter |
matters:read |
read-only, idempotent | Filters the org's Matters in memory |
getMatterDocuments |
matters:read |
read-only, idempotent | Empty list for a Matter outside the org |
getMatterConversations |
matters:read |
read-only, idempotent | Summaries only; no message bodies |
Statute tools #
Reachable over MCP, and over REST for search and section lookup. Unlocked by the statutes:read scope, or by the legacy tool-name scope on older keys.
statutes:read scope #
searchStatutes #
Search statute sections by full-text query across the corpus.
Honesty: Postgres
websearchfull-text match over section text. Not semantic search and not currency-aware.
{
"name": "searchStatutes",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search query for statute text" },
"jurisdiction": {
"type": "array",
"items": { "type": "string" },
"description": "Jurisdiction codes to restrict to, e.g. [\"BB\",\"TT\"]; [\"all\"] disables the filter. Omitted: the workspace home jurisdiction applies if set."
},
"limit": { "type": "number", "default": 10 }
},
"required": ["query"]
}
}Arguments:
query(required): the search text.jurisdiction(optional, array of codes): codes must come from the platform jurisdiction list.["all"]on its own searches every jurisdiction. Unknown codes return an error and no search runs. An empty array means "not provided" (see below).limit(optional): maximum results, default 10.
When jurisdiction is omitted, the caller's workspace home jurisdiction applies if one is set; a workspace without one searches every jurisdiction. The same rule applies over REST and MCP. An empty jurisdiction ([]) means "not provided", so the home jurisdiction applies. Use ["all"] to search everything.
Changed (2026-10-22). The response moved from a bare array of rows to { "results": [...], "applied_filter": {...} }. The jurisdiction input is now string[] (it was a string). A comma-separated string is still accepted. This is a deliberate, documented break.
Each result includes its jurisdiction. The response is { "results": [...], "applied_filter": { "jurisdictions": [...] | null, "source": "param" | "home" | "none" } }, so a caller can see which filter was applied.
getSection #
Retrieve sections of a statute by CAP number.
Honesty: exact CAP lookup returning what the corpus holds. No currency check.
{
"name": "getSection",
"inputSchema": {
"type": "object",
"properties": {
"capNumber": { "type": "string", "description": "CAP number (e.g., 309A)" },
"sectionId": { "type": "string", "description": "Specific section UUID" }
},
"required": ["capNumber"]
}
}Family-scoped tools #
Each of these is advertised and callable only when the key's scopes grant it explicitly.
citation:verify scope #
verifyCitation #
Resolves an Act/Cap and section citation against the shared corpus: a batch of citation strings, each resolved to a specific section_id (or statute_id) with a yes/no and a reason. Returns title_not_found, cap_not_found or section_not_found when it cannot resolve a citation. Also available over REST at POST /api/v1/citations/verify.
{
"name": "verifyCitation",
"inputSchema": {
"type": "object",
"properties": {
"citations": { "type": "array", "items": { "type": "string" }, "maxItems": 50 },
"citation": { "type": "string", "description": "A single citation; equivalent to a one-element citations array" },
"jurisdiction": { "type": "string", "description": "Jurisdiction hint, e.g. BB" },
"as_of": { "type": "string", "description": "ISO date (YYYY-MM-DD)" }
},
"required": []
}
}Arguments are validated by a zod schema before any corpus read; invalid input is a 400 with the issue list, not a 500. At most 50 citations per call, each at most 512 characters.
Response — one object per input citation, in input order:
{
"results": [
{
"citation": "Companies Act, Cap. 308, s. 12",
"found": true,
"match": "exact",
"statute_id": "…", "section_id": "…",
"title": "Companies Act",
"section_heading": "Incorporation",
"jurisdiction": "BB",
"source_url": "https://…",
"confidence": 0.99,
"reason": "cap_and_section",
"pinpoint": null,
"as_of": "2026-08-27",
"verified_at": "2026-08-27T18:04:11.000Z",
"current": null,
"repealed": null,
"amended_by": null,
"currency": { "status": "unknown", "effective_from": null, "effective_to": null,
"as_of_source_date": null, "source": null, "source_url": null,
"checked_at": null, "unapplied_effects": null, "confidence": null },
"currency_state": "unknown",
"unknown_reasons": ["amendment_source_never_consulted", "no_currency_source"],
"verdict": "resolved"
}
],
"verified_at": "2026-08-27T18:04:11.000Z",
"units": [{ "unit": "verified_citation", "count": 1 }]
}Every result carries a verdict, and the response carries the units the call bills. Both are described below.
verdict: what the resolver decided
verdict |
Derived when | Billed |
|---|---|---|
resolved |
Statute and section both resolved. Currency does not gate this. | 1 verified_citation |
not_found |
Nothing resolved. | 0 |
ambiguous |
More than one candidate statute matched. | 0 |
section_designation_unknown |
A statute resolved but no single section did: multi-section citation, section not in the statute, or a whole-statute citation. | 0 |
Rules are checked in this order: resolved; then section_designation_unknown for a multi-section citation; then ambiguous; then section_designation_unknown for a statute without a section; then not_found. Mandatum's resolver decides the verdict. The customer decides what to do with it. Unresolved never bills.
Resolution is what is counted; currency is reported alongside and may be unknown. currency_state is current, repealed or unknown, derived from current and repealed. A prospective provision reads unknown there; currency.status still says prospective.
units: what the call bills
units is [{ "unit": "verified_citation", "count": n }], where n is the number of resolved results. The entry is present when n is 0.
What each match value obliges you to do
match |
found |
Meaning | What a caller must do |
|---|---|---|---|
exact |
true |
A deterministic identifier resolved it: a chapter number, an Act number, or a short title that matched the corpus title token-for-token. If the citation named a section, that section exists. | Safe to act on the ids. |
title-fuzzy |
true |
No identifier matched. The statute was chosen by token overlap of at least 0.8 against the corpus short title, with instrument nouns stripped. It is a suggestion, not a verification. |
Show the resolved title to a human, or re-cite with a chapter/Act number. Never present as verified. |
not_found |
false |
The citation does not resolve. reason says why, and statute_id/title may still be populated when the statute resolved but the citation as written did not (section_not_found, cap_title_mismatch). |
Treat the citation as unverified. reason is stable enough to branch on. |
ambiguous |
false |
More than one statute answers to the citation, or the citation names more than one section. reason carries the count. |
Narrow it: pass jurisdiction, or split a multi-section citation into one call per section. |
confidence is the resolution confidence only (0.99 chapter + section, 0.95 chapter or exact title, 0.85 chapter recovered from title text, the overlap score capped at 0.9 for title-fuzzy, 0 for anything unresolved). Currency has its own confidence, in currency.confidence; see below.
Common reason values: cap_and_section, cap_number, act_title, act_title_and_section, act_title_fuzzy, act_number_year, cap_not_found, section_not_found, title_not_found, year_mismatch, act_number_mismatch, below_fuzzy_floor, discriminator_mismatch, instrument_mismatch, cap_title_mismatch, ambiguous_cap, ambiguous_cap_across_jurisdictions, ambiguous_title, act_title_cap_unverified, act_title_year_unverified, multi_section_citation, section_designation_unknown, section_partially_designated, ambiguous_section_in_corpus:*, not_a_standalone_citation:*, not_a_statutory_citation:*, unparseable_citation.
How titles are matched #
Barbados stores short titles without the instrument word (Accidental Fires) and writes Act numbers as a suffix (Data Protection Act, 2019-29). Citations use the form they are written in (the Accidental Fires Act, Data Protection Act 2019). The resolver compares them in this order:
- Chapter number.
Cap. Nmatches the stored chapter number. If none is stored, it matches aCap. Nin the stored title. When a citation names an Act and the chapter is not stored for that statute, the result isnot_foundwith reasonact_title_cap_unverified. Chapters compare after normalisation, so015and15are the same chapter. - Citable title. The Act name is reduced to a key (leading
the,section N of,, Cap. N, a trailing year and a trailing parenthetical are dropped, and the text is lowercased) and matched exactly. A year in the citation must match the stored year. A citation year the stored title does not carry resolves astitle-fuzzywith reasonact_title_year_unverified. Two matches areambiguous_title. - Stored title. The token and fuzzy path. Only this step can yield
title-fuzzywithout a year gap.
A citation resolved at step 2 with a year the title carries is exact. A citation that needs step 3 is title-fuzzy and must not be presented as verified. Step 2 depends on the citable-title data being in place. Until it is, that step returns no candidates and matching works as it did before.
A pinpoint is attested or it is refused #
section_designation is the designation the corpus holds for the resolved section, as the publisher prints it (for example 75, 78A or 33.1). section_designation_source says what attests it. Render section_designation, never the number you asked with. Both are null whenever no section resolved.
Earlier releases could report an exact match for a section number the corpus had not attested. The resolver now refuses such pinpoints, and three reasons say how a pinpoint failed. None of them says the section does not exist:
section_designation_unknown: the Act resolved, but the corpus holds no designations for its sections, so the cited section cannot be checked. Carries no count.section_partially_designated: the Act resolved and some of its rows carry designations, but the cited section is not among them, and some rows have none. Its absence is not asserted. Retry after the Act's backfill completes.ambiguous_section_in_corpus:N:Nrows claim the cited designation within the same container. This is a corpus defect, not a citation defect.
section_not_found is reserved for an Act whose every row carries a designation, where the cited section is not among them.
Currency is tri-state
current and repealed are tri-state: true, false or null. null means unknown and is never coerced to false.
currency.status |
current |
repealed |
When |
|---|---|---|---|
in_force |
true |
false |
An attested source says the section is in force, and every gate below passes. |
repealed |
false |
true |
The section is repealed as at as_of. Repeal evidence is reported even when the derived state is stale. |
prospective |
false |
false |
as_of is before effective_from. |
unknown |
null |
null |
Anything else. unknown_reasons says which gate failed. |
in_force is withheld, and reported as unknown, unless all of these hold: a currency source attests the section; the Act's amendment source has been consulted; the derived state and that coverage are each no more than 30 days old; and the evidence is not solely model-extracted (llm_assisted). A stored in-force section whose attested range has closed becomes unknown, not repealed, because the reason was not recorded.
amended_by is tri-state too. null means no amendment source was ever consulted for the Act (or the citation did not resolve to a section). [] means one was consulted and found none. Those two must not be conflated. Otherwise it lists the attested amendments, each with amendment_id, in_force, applied, source and confidence.
unknown_reasons holds the reasons that apply, in this vocabulary:
| Reason | Meaning |
|---|---|
section_not_resolved |
The citation did not resolve to a section, so there is nothing to date. |
no_currency_source |
No source attests currency for the section, or the currency read failed. |
amendment_source_never_consulted |
The Act has never had its amendment source checked. |
currency_state_stale |
The derived currency is older than 30 days. in_force is withheld. |
coverage_stale |
The amendment source was last consulted more than 30 days ago. in_force is withheld. |
llm_assisted_evidence_only |
Only model-extracted amendment evidence exists. in_force is withheld. |
unapplied_effects_pending |
Recorded amendments are not yet reflected in the text. Can accompany any status, and lowers currency.confidence. |
The example above is the honest case for a section never checked for amendments: nothing is known about its currency, and the reasons say so. A caller must not read found: true as "in force". It means the section exists in the corpus as we hold it. Check current (or currency.status) for currency.
Jurisdiction hints
Chapter numbers collide across Commonwealth jurisdictions — Cap. 308 exists in more than one. Without a jurisdiction hint, a colliding chapter number returns ambiguous, not a guess.
checkSourceExists #
Check whether a source reference (e.g., CAP number) exists in the shared corpus. This is a substring match on cap_number; prefer verifyCitation, which answers the same question with a reason and a section_id.
Honesty: a substring
ilikeoncap_number, so12also matches123. Returns existence only, not resolution.
{
"name": "checkSourceExists",
"inputSchema": {
"type": "object",
"properties": { "sourceRef": { "type": "string" } },
"required": ["sourceRef"]
}
}scoreConfidence #
Score citation confidence for a document heuristically.
Honesty: a length heuristic, not a measure of whether citations are accurate. It adds 0.2 per citation (capped at 0.8) and 0.1 or 0.2 for text under or over 50 characters. Do not present it as verification.
{
"name": "scoreConfidence",
"inputSchema": {
"type": "object",
"properties": {
"citations": { "type": "array", "items": {} },
"text": { "type": "string" }
},
"required": []
}
}benchmark:read scope #
compareLaws #
Title match of statutes across the requested jurisdictions; returns matching statute titles, not a semantic comparison.
Honesty: for each jurisdiction, an
ilikeon the statute title with the topic as a substring, up to 5 rows each. It does not compare provisions, and "no match" does not mean "no law on this topic". The tool name and scope name are unchanged for stability.
{
"name": "compareLaws",
"inputSchema": {
"type": "object",
"properties": {
"jurisdictions": { "type": "array", "items": { "type": "string" }, "description": "Jurisdiction codes to compare" },
"topic": { "type": "string", "description": "Topic or keyword to compare" }
},
"required": ["jurisdictions", "topic"]
}
}gap:read scope #
identifyGaps #
Identify compliance gaps for the calling organization against a statute. Org-bound: returns data scoped to the caller's organizationId only.
Honesty: returns the gap items the org has recorded for that statute. It does not analyse the statute or compute gaps.
{
"name": "identifyGaps",
"inputSchema": {
"type": "object",
"properties": {
"statuteId": { "type": "string", "description": "Statute UUID" }
},
"required": ["statuteId"]
}
}scoreAlignment #
Score compliance alignment for the calling organization against a statute. Org-bound.
Honesty:
1 - gaps / total, floored at 0.totalis the caller's provision count. If omitted it defaults to twice the gap count, so the score is a fixed0.5whenever any gap exists. It is not a compliance score unless the caller passes a realtotal.
{
"name": "scoreAlignment",
"inputSchema": {
"type": "object",
"properties": {
"statuteId": { "type": "string" },
"total": { "type": "number", "description": "Total provisions count" }
},
"required": ["statuteId"]
}
}gap:issue scope #
issueGapReport #
Issue the gap report for one analysis: its gap items and resolved citations as they stand now, frozen under a SHA-256 content hash. The analysis must belong to the caller's organisation; an analysis from another organisation returns Analysis not found.
Billing: issuing is the billable act. Issuing identical content again returns the existing issuance with
reused: trueand bills nothing. The first issuance in a workspace is free (is_free: true). Later ones are priced atprice_usd, fixed on the issuance when it is made. Downloading an issued report is never billed.
_meta.datafed.units reports one gap_report unit per new issuance, and count: 0 for a reused one: [{ "unit": "gap_report", "count": 1 }].
{
"name": "issueGapReport",
"inputSchema": {
"type": "object",
"properties": {
"analysisId": { "type": "string", "description": "Analysis (matter) UUID" }
},
"required": ["analysisId"]
}
}skills:read scope #
listSkills #
List available workflow skill definitions for the organization.
Honesty: returns the deployed workflow registry. It is not filtered by organization.
{
"name": "listSkills",
"inputSchema": { "type": "object", "properties": {}, "required": [] }
}skills:trigger scope #
triggerWorkflowRun #
Trigger a defined workflow run for the organization (trigger-only, D3: enqueues exactly one Inngest event, no orchestrator loop).
{
"name": "triggerWorkflowRun",
"inputSchema": {
"type": "object",
"properties": {
"matterId": { "type": "string", "description": "Matter (analysis) UUID" },
"workflowSlug": { "type": "string", "description": "Workflow definition slug" },
"inputs": { "type": "object", "description": "Optional workflow inputs" }
},
"required": ["matterId", "workflowSlug"]
}
}documents:write scope #
parseDocument #
Parse a PDF, DOCX, UTF-8 text, or Markdown document without persisting the
document. REST callers use POST /api/v1/documents with multipart form data;
MCP callers provide base64-encoded bytes. The default tree engine returns a
typed document tree. The optional docling engine returns raw DoclingDocument
JSON. REST uploads require a finite Content-Length; the deployment platform's
request-body limit is the outer bound. PDF page counting is a best-effort guard
because page dictionaries stored inside compressed PDF object streams are not
visible to the lightweight preflight scan; the same guard runs before either
engine.
{
"name": "parseDocument",
"inputSchema": {
"type": "object",
"properties": {
"filename": { "type": "string" },
"contentBase64": { "type": "string" },
"engine": { "type": "string", "enum": ["tree", "docling"], "default": "tree" }
},
"required": ["filename", "contentBase64"]
},
"annotations": { "readOnlyHint": false, "destructiveHint": false }
}matters:read scope #
Read-only Matters tools. Every read is limited to the workspace and to the Matters the key's owner belongs to. Handlers only read, and they trigger no workflows.
listMatters #
List Matters (vault) for the calling organization.
Honesty: the input schema advertises
statusandlimit, but the handler ignores both and returns every Matter for the org.
{
"name": "listMatters",
"inputSchema": {
"type": "object",
"properties": {
"status": { "type": "string", "description": "Optional status filter" },
"limit": { "type": "number", "description": "Maximum rows to return" }
},
"required": []
}
}Output: { "matters": [...] }.
getMatter #
Get one Matter by id.
Honesty: loads all of the org's Matters and filters in memory to the requested id. A Matter outside the caller's org returns
{ "matter": null }, the same as a missing id.
{
"name": "getMatter",
"inputSchema": {
"type": "object",
"properties": { "matterId": { "type": "string", "description": "Matter (analysis) UUID" } },
"required": ["matterId"]
}
}Output: { "matter": {...} | null }.
getMatterDocuments #
List the documents attached to a Matter.
Honesty: joined through
analyses.organization_id, so a Matter outside the caller's org yields an empty list, not an error.
{
"name": "getMatterDocuments",
"inputSchema": {
"type": "object",
"properties": { "matterId": { "type": "string", "description": "Matter (analysis) UUID" } },
"required": ["matterId"]
}
}Output: { "documents": [...] }.
getMatterConversations #
List Matter-linked chat conversations, as summaries.
Honesty: returns
id,title,modeandupdated_atonly. Message bodies are never returned. Only conversations with amatter_idare listed.
{
"name": "getMatterConversations",
"inputSchema": {
"type": "object",
"properties": { "matterId": { "type": "string", "description": "Matter (analysis) UUID" } },
"required": ["matterId"]
}
}Output: { "conversations": [...] }.
Deprecated scopes (no tool behind them) #
These scopes are still accepted when issuing or sharing keys, so stored keys keep validating. They unlock no tool. The tools they used to unlock were removed on 2026-10-22; see CHANGELOG.md.
| Scope | Formerly unlocked | Current behaviour |
|---|---|---|
gazette:read |
searchGazettes (removed) |
GET and POST /api/v1/gazette/search return 410 Gone |
draft:generate |
generateClause, generateRedline, formatAkomaNtoso (removed) |
POST /api/v1/drafts/generate returns 410 Gone |
REST-only surfaces (no MCP tool) #
Four scopes gate HTTP routes rather than tools, so they appear in ALL_SCOPES but not in this catalog and not in MCP tools/list:
| Scope | Surface |
|---|---|
runs:read |
GET /api/v1/runs/{id} |
webhooks:read |
GET /api/v1/webhooks, GET /api/v1/webhooks/{id}/deliveries |
webhooks:write |
Register and delete webhook endpoints, rotate their secrets, and replay deliveries |
usage:read |
GET /api/v1/usage |
See rest-api.md and webhooks.md.