# UK Legal Research (remote · uk-legal-mcp.fly.dev)

UK legal research — case law, legislation, Hansard, bills, votes, committees, HMRC, citations

- Trust score: 74/100 (medium)
- Change this week: +6
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `uk-legal-mcp.fly.dev`: 74/100 (this document), [markdown](https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp.md), [page](https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp)
- pypi · `uk-legal-mcp`: 10/100, [markdown](https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp-2.md), [page](https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp-2)

## Channel facts

- Endpoint: `https://uk-legal-mcp.fly.dev/mcp`
- Transports: `streamable-http`
- Auth: `none`
- Version: `0.4.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Endpoint Security**: 74/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - No authorisation is required to call this server. Every tool declares its destructiveHint and none is destructive, so open access doesn't expose one.
  - HTTPS is enforced; there's no plaintext access path.
  - HSTS check failed: the Strict-Transport-Security header is absent.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 75/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 9280 tokens (~257/item across 36 items; 35 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add --transport http paulieb89-uk-legal-mcp https://uk-legal-mcp.fly.dev/mcp
```

### Codex

```toml
[mcp_servers.paulieb89-uk-legal-mcp]
url = "https://uk-legal-mcp.fly.dev/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "paulieb89-uk-legal-mcp": {
      "type": "remote",
      "url": "https://uk-legal-mcp.fly.dev/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add paulieb89-uk-legal-mcp --url https://uk-legal-mcp.fly.dev/mcp --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  paulieb89-uk-legal-mcp:
    url: "https://uk-legal-mcp.fly.dev/mcp"
```

### Other

```json
{
  "mcpServers": {
    "paulieb89-uk-legal-mcp": {
      "type": "http",
      "url": "https://uk-legal-mcp.fly.dev/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-03 (score 74, +1)

No change was recorded against any check on this day. Stability & Change Management went from 23 to 27. That category is still filling its 30-day observation window: 7 days of observed history at the previous scan, 8 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-08-01 (score 73, +1)

No change was recorded against any check on this day. Stability & Change Management went from 17 to 20. That category is still filling its 30-day observation window: 5 days of observed history at the previous scan, 6 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-31 (score 72, +2)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-30 (score 70, +1)

No change was recorded against any check on this day. Stability & Change Management went from 10 to 13. That category is still filling its 30-day observation window: 3 days of observed history at the previous scan, 4 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-28 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-27 (score 68, +1)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-26 (score 67)

First indexed and scored.

## MCP tools (35)

### `judgment_get_header` (~110 tokens)

Get Judgment Header

USE THIS TOOL WHEN you have a judgment slug and need metadata (parties, judges, neutral citation, court, dates).

Call case_law_search FIRST to get the slug. AFTER calling, use
judgment_get_index to discover paragraphs, then judgment_get_paragraph to
read specific ones. Authoritative source for UK judgment metadata.

Input parameters:

- `slug` (string, required): Judgment slug, e.g. 'uksc/2024/12' or 'ewca/civ/2023/450'

### `judgment_get_index` (~111 tokens)

Get Judgment Paragraph Index

USE THIS TOOL WHEN you have a judgment slug and want the paragraph navigation index (eId + preview line for every paragraph).

Call case_law_search FIRST to get the slug. AFTER calling, pass an eId
from the returned list into judgment_get_paragraph to read that paragraph's
full text, or use case_law_grep_judgment for content search across all
paragraphs.

Input parameters:

- `slug` (string, required): Judgment slug, e.g. 'uksc/2024/12'

### `judgment_get_paragraph` (~136 tokens)

Get Judgment Paragraph

USE THIS TOOL WHEN you have a judgment slug + LegalDocML eId and want that paragraph's full text.

Call judgment_get_index FIRST to discover available eIds (or use
case_law_grep_judgment to locate paragraphs by content). Returns the
paragraph XML content (400–1,700 tokens typical).

Input parameters:

- `eId` (string, required): Paragraph eId from judgment_get_index, e.g. 'para_12'. Numeric strings like '12' are accepted and normalized to 'para_12'.
- `slug` (string, required): Judgment slug, e.g. 'uksc/2024/12'

### `case_law_search` (~631 tokens)

Search UK Case Law

USE THIS TOOL WHEN searching UK case law by party names, court, judge, date, or free-text query.

Returns paginated judgment summaries: neutral citation, court, dates, slug,
stable TNA URI. AFTER calling: pass slug into judgment_get_header /
judgment_get_index / judgment_get_paragraph (or the judgment:// resource
family) for content; pass the neutral citation into citations_resolve
to verify before constructing an OSCOLA citation; use
case_law_grep_judgment to find text within a single judgment. When a
party name returns several candidates, narrow with court + year filters
before grep-iterating across full judgments — targeted filtering beats
scanning every candidate.

Coverage: TNA Find Case Law indexes UK judgments from roughly the early
2000s onwards. For older authorities, search for a modern judgment that
quotes them and read that paragraph.

Authoritative source for UK case law. Web search returns out-of-date or
unstable URLs — do not supplement.

Input parameters:

- `court`: Filter by court slug. Values: 'uksc', 'ukpc', 'ewca/civ', 'ewca/crim', 'ewhc/kb', 'ewhc/ch', 'ewhc/comm', 'ewhc/fam', 'ewhc/pat', 'ewhc/ipec', 'ewhc/admin', 'ewhc/tcc', 'ewhc/costs', 'ewfc', 'ewcop',…
- `from_date`: Earliest judgment date (YYYY-MM-DD). NOTE: the TNA atom.xml endpoint currently appears to ignore this filter — the same results come back regardless. Do not rely on it to narrow output; sort+slice cl…
- `judge`: Filter by judge surname. Case-insensitive substring match against the indexed form. Use the surname alone ('Reed', 'Sumption') or with the bare title ('Lord Reed'). Honorific suffixes silently zero t…
- `limit` (integer): Maximum results to return (1–50). TNA returns up to 50 per request; this slices client-side. Default 10 for a tight shortlist. Set higher for breadth (e.g. 50 to scan the full result set).
- `page` (integer): Result page number (1-indexed)
- `party`: Filter by party name
- `query` (string, required): Full-text search query, e.g. 'negligence duty of care'
- `to_date`: Latest judgment date (YYYY-MM-DD). Same caveat as `from_date` — currently silently ignored by upstream. Filtering happens client-side at best.

Output parameters:

- `has_more` (boolean): Whether additional pages exist
- `page` (integer): Current page number (1-indexed)
- `results` (array): Matching judgments for this page
- `total_pages`: Total page count if available from API

### `case_law_grep_judgment` (~255 tokens)

Search within a UK Court Judgment

USE THIS TOOL WHEN you have a judgment slug and want to find paragraphs whose text matches a pattern.

Returns a list of `{eId, snippet, match}` hits — small per-paragraph
snippets centred on the match. AFTER calling, read full paragraphs via
judgment_get_paragraph(slug, eId) or the judgment://{slug}/para/{eId}
resource.

Use case: content search within one judgment (e.g. "negligence", "test
for foreseeability", "Donoghue"). For paragraph-number navigation by
eId, call judgment_get_index instead.

Pattern is regex; if it doesn't compile, falls back to literal substring
search.

Input parameters:

- `case_insensitive` (boolean): Default true. Set false for case-sensitive matching.
- `max_hits` (integer): Cap on number of hits returned.
- `pattern` (string, required): Regex pattern (or plain substring) to search within paragraph text. If the pattern doesn't compile as regex, falls back to literal substring match.
- `slug` (string, required): TNA judgment slug, e.g. 'uksc/2024/12' or 'ewca/civ/2023/450'.

Output parameters:

- `hits` (array): Matching paragraphs in document order
- `pattern` (string): The pattern that was searched for
- `slug` (string): The judgment slug that was searched
- `truncated` (boolean): True if hit count reached max_hits and more matches may exist

### `legislation_search` (~441 tokens)

Search UK Legislation

USE THIS TOOL WHEN searching UK Acts and Statutory Instruments by title, phrase, or full-text.

Returns ranked results: title, type, year, number, legislation.gov.uk URL,
and next_steps hints (toc URI, section template). AFTER calling, chain
to legislation_get_toc then legislation_get_section for structural drill-in.

Filter discipline: `type` and `year` are exact-match. Use only when you
already know the value. For currency-driven searches ("the recent
Renters' Rights Act"), query by phrase alone and read the year from the
results — guessing a year and filtering by it zeroes results when wrong.
For broader concept queries across content, set `fulltext=True`.

Authoritative source for UK primary and secondary legislation
(legislation.gov.uk).

Input parameters:

- `fulltext` (boolean): Default false → searches Act/SI titles only (best for finding a named Act, e.g. 'Housing Act 1988' returns ukpga/1988/50 first). Set true to search the full text of every Act/SI for the query (return…
- `limit` (integer): Maximum results to return (1–50). Passed to the upstream results-count param.
- `query` (string, required): Search query, e.g. 'Housing Act 1988' or 'data protection personal data'
- `type`: Filter by type: 'ukpga' (Acts), 'uksi' (SIs), 'asp' (Scottish Acts), 'nia' (NI Acts). Exact-match — omit if you don't already know whether you're looking for an Act vs an SI.
- `year`: Filter by year of enactment (exact-match — a single integer, not a range). Omit unless you already know the Act's year. Speculating a year (e.g. 'this is recent so it must be 2026') and getting it wr…

Output parameters:

- `results` (array): Matching legislation items
- `total` (integer): Total number of matches

### `legislation_get_section` (~342 tokens)

Get Legislation Section

USE THIS TOOL WHEN you have a known Act / SI and want the parsed text of a specific section, with extent and in-force metadata.

Returns full section text, territorial extent, in-force status, and
prospective flag. Content capped per max_chars (default 10,000,
\~2,500 tokens) — raise for unusually long definition sections; check
content_truncated in the response.

ALWAYS check `extent` — a section may apply to England & Wales but not
Scotland or Northern Ireland. Reciting a section without checking
extent is a recurring legal-research error.

Alternative: call read_resource(uri="legislation://{type}/{year}/{number}/
section/{section}") for raw CLML XML; use this tool when you want the
parsed structured response instead.

Input parameters:

- `max_chars` (integer): Maximum characters of section content to return. Default 10,000 (~2,500 tokens) covers almost every section. Raise to 50,000+ only for unusually long Finance Act definition sections. Check content_tr…
- `number` (integer, required): Chapter or SI number
- `section` (string, required): Section number, e.g. '47' or '12A'. Use the numeric part only — not 'section-47'. Schedules are not currently supported.
- `type` (string, required): Legislation type code: 'ukpga' (Acts), 'uksi' (SIs), 'asp' (Scottish Acts), 'nia' (NI Acts). Use the value from legislation_search results.
- `year` (integer, required): Year of enactment

Output parameters:

- `content` (string): Plain text content of the section, possibly truncated per max_chars. Check content_truncated and original_length for full-text information.
- `content_truncated` (boolean): True if content was cut to fit max_chars
- `extent` (array): Territorial extent: list of 'England', 'Wales', 'Scotland', 'Northern Ireland'. Empty list means unknown — do not assume full UK extent.
- `in_force`: False if the section is explicitly marked repealed in CLML. True only when an InForce element is present in the section body (rare). Null for most sections — the data.xml endpoint does not carry a pe…
- `original_length` (integer): Original plain-text length in characters before any truncation
- `prospective`: True if this section has not yet come into force; None if unknown
- `section_number` (string): Section number, e.g. '47', '12A', 'Schedule 2'
- `source_format` (string): Source parsed for this response. html_fallback means CLML XML was unavailable and text was parsed from the public HTML page.
- `title` (string): Section title or heading
- `version_date`: Date of the version retrieved
- `warnings` (array): Non-fatal retrieval or parsing warnings the caller should disclose where relevant.

### `legislation_get_toc` (~335 tokens)

Get Legislation Table of Contents

USE THIS TOOL WHEN you have a known Act / SI and want the structural table of contents (parts, chapters, sections, schedules).

Returns structural elements with XML id and title, e.g. 'section-47:
Definitions'. AFTER calling, pass the numeric section identifier (use
'47', NOT 'section-47') into legislation_get_section for full text.

Large statutes (Companies Act 2006 has many hundreds of items) are
paginated via offset/limit. Check has_more and total_items.

Alternative: call read_resource(uri="legislation://{type}/{year}/{number}/
toc") for the full TOC as a newline-separated `id: title` string (no
pagination). Use this tool when you need the structured response with
offset / limit / has_more for stepping through large statutes.

Input parameters:

- `limit` (integer): Maximum items to return in this call (default 200, max 1000). Raise only when you need a larger slice in one response. Check has_more and total_items to know if further pages exist.
- `number` (integer, required): Chapter or SI number
- `offset` (integer): Number of items to skip from the flattened TOC. Use with limit to page through very large statutes like the Companies Act 2006 (1300+ items).
- `type` (string, required): Legislation type code: 'ukpga' (Acts), 'uksi' (SIs), 'asp' (Scottish Acts), 'nia' (NI Acts). Use the value from legislation_search results.
- `year` (integer, required): Year of enactment

Output parameters:

- `has_more` (boolean): True if more items remain beyond offset+returned
- `items` (array): TOC entries in XML document order, formatted as '<id>: <title>', e.g. 'section-47: Definitions'. When calling legislation_get_section pass only the numeric part ('47', not 'section-47').
- `limit` (integer): Page size applied after offset
- `number` (integer): Chapter or SI number echoed from the request
- `offset` (integer): Offset applied to the full TOC item list
- `returned` (integer): Number of items in this response
- `total_items` (integer): Total structural items parsed from the XML, before offset/limit. Compare to `returned` and `has_more` to decide whether to paginate.
- `type` (string): Legislation type code echoed from the request
- `year` (integer): Year of enactment echoed from the request

### `parliament_search_hansard` (~692 tokens)

Search Hansard Debates

USE THIS TOOL WHEN searching Hansard by topic, bill title, or text phrase.

Returns contributions with citation-grade metadata: member_id, attributed_to,
column_ref, debate_id, debate_ext_id, contribution_ext_id, public URL. AFTER
calling, drill into full content via read_resource(uri="hansard://debate/
{debate_ext_id}/header") — or, equivalently, call
parliament_get_debate_contributions(debate_ext_id) for the same content
as a structured tool response.

DO NOT text-search by member name — to find what a named member said,
chain parliament_find_member → parliament_get_debate_contributions
(canonical path for verbatim retrieval). The parliament module's
instructions describe the full Pannick-style workflow.

Pagination: limit + offset honour the upstream paginated endpoint. For
breadth across a topic, see parliament_policy_position_summary.

Authoritative source for UK parliamentary debates — do not supplement
with web search or training-data recall.

Input parameters:

- `contribution_type` (string): Which Hansard section to paginate. 'Spoken' = chamber + Westminster Hall debates (the default; what a lawyer usually means). 'Written' = written answers and statements. 'Corrections' = published corr…
- `from_date`: Start date (YYYY-MM-DD)
- `house` (string): Restrict to one House. Default 'both' returns Commons + Lords contributions.
- `limit` (integer): Max contributions per call (1–100). Default 20. Paginate further with offset; total corpus size is in total_corpus on the response.
- `member_id`: Filter to contributions by a single member. Pass the integer Members API ID (resolve a name via parliament_find_member). The prior `member` field accepted a name string but Hansard's /search.json sil…
- `offset` (integer): Skip this many contributions before the page. Default 0. Re-call with offset=offset+returned to paginate; has_more flags whether more remain.
- `query` (string, required): Phrase to find in Hansard contribution text bodies. Hansard searches the words members actually said in their speeches — NOT debate titles, topic metadata, or written headlines. Pass tokens that woul…
- `text_mode` (string): 'preview' returns the upstream ~250-char snippet (fast, low context cost). 'full' returns ContributionTextFull (still capped at 3000 chars). For full contribution text without the cap, read the resou…
- `to_date`: End date (YYYY-MM-DD)

Output parameters:

- `contributions` (array): Matching Hansard contributions with full citation metadata.
- `date_range`: (min, max) SittingDate of returned contributions, or None if empty
- `from_date`: Start date filter applied, if any
- `has_more` (boolean): True if a full page was returned (more may exist; re-call with offset=offset+limit)
- `house` (string): House filter applied
- `house_breakdown` (object): Counts by house across the returned page
- `limit` (integer): Page size requested
- `member_id`: Members API integer ID filter applied, if any (echoed from input).
- `offset` (integer): Skip applied to this page (Hansard API: skip)
- `party_breakdown` (object): Counts by party across the returned page
- `query` (string): The phrase that was searched in Hansard
- `text_mode` (string): Whether contribution `text` carries the upstream preview or full body (still capped).
- `to_date`: End date filter applied, if any
- `top_debates` (array): Top-ranked debates touching this topic (from upstream Debates[] preview, capped at 4 by Hansard's /search.json). Each entry's `debate_ext_id` chains to hansard://debate/{debate_ext_id}/header.
- `top_divisions` (array): Top-ranked divisions touching this topic (from upstream Divisions[] preview, capped at 4). Each entry's `id` chains to votes_get_division; `debate_section_ext_id` chains back to the parent debate.
- `total` (integer): Number of contributions returned in this call
- `total_committees`: TotalCommittees.
- `total_corpus`: Total contributions in Hansard matching this query (TotalContributions). Use to decide whether to paginate further or escalate to parliament_policy_position_summary.
- `total_corrections`: TotalCorrections — published corrections to the Hansard record.
- `total_debates`: TotalDebates — distinct debates touching this topic.
- `total_divisions`: TotalDivisions. Non-zero → consider `top_divisions` previews below or chain to votes_search_divisions.
- `total_members`: TotalMembers — member-name matches in the corpus.
- `total_petitions`: TotalPetitions.
- `total_written_answers`: TotalWrittenAnswers.
- `total_written_statements`: TotalWrittenStatements.

### `parliament_policy_position_summary` (~512 tokens)

Hansard Policy Position Summary (deterministic facets)

USE THIS TOOL WHEN you want debate-level corpus signals on a topic — by_house, by_year, by_section breakdowns — without reading every contribution.

Aggregates Hansard debate-level signals on a topic. Pure counts — no LLM,
no editorial labels. Sweeps /search/Debates.json with pagination (up to
max_debates_scanned), then aggregates by_house, by_section, by_year,
by_month, and top_debates from debate metadata. Also captures the
corpus-wide envelope counts (total_contributions, total_written_statements,
total_divisions, etc.) from /search.json for cross-section scope.

AFTER calling, pick a debate from top_debates and pass its debate_ext_id
into parliament_get_debate_contributions to drill into who said what.

Note on member-level facets: Hansard's search API exposes debate
metadata, not per-contribution member identifiers, at the corpus
level. by_party and top_contributors are therefore omitted from this
deterministic summary. To see who spoke in a specific debate, read
hansard://debate/{debate_ext_id}/header for an ordered contribution
index, or call parliament_member_debates for one named member.

This is the authoritative source for UK Hansard corpus-level signals.

Input parameters:

- `from_date`: Start date (YYYY-MM-DD)
- `house` (string): Restrict to one House. Default 'both'.
- `max_debates_scanned` (integer): Hard cap on debates sampled from /search/Debates.json to compute facets. Default 200 issues ≤4 upstream calls (take=50 each). Raise to 2000 (≤40 calls) for an exhaustive sweep on a heavily-debated to…
- `to_date`: End date (YYYY-MM-DD)
- `topic` (string, required): Phrase to find in Hansard contribution text bodies for the facet aggregation. Same semantics as parliament_search_hansard.query: tokens that appear in members' actual speeches, not bill titles or top…

Output parameters:

- `by_house` (array): Counts of debates by house (Commons vs Lords)
- `by_month_recent_12` (array): Counts of debates by YYYY-MM for the most recent 12 months in the sample, desc by month
- `by_party` (array): Counts by party. ALWAYS EMPTY in this summary — Hansard's search API only exposes member identifiers at the per-debate level, not the corpus level. For party breakdown within one debate, read hansard…
- `by_section` (array): Counts of debates by Hansard section bucket (Chamber / Westminster Hall / Written Answers / Written Statements)
- `by_year` (array): Counts of debates by sitting year, desc by year
- `debates_scanned` (integer): Number of debates pulled from /search/Debates.json for the facet breakdown (≤ max_debates_scanned)
- `from_date`: Start date filter applied
- `house` (string): House filter applied
- `to_date`: End date filter applied
- `top_contributors` (array): ALWAYS EMPTY in this summary — see by_party note. Use parliament_member_debates after picking a debate from top_debates.
- `top_debates` (array): Top 20 debates ranked by upstream relevance_rank, with debate_ext_id for hansard://debate/{debate_ext_id}/header drill-down. contribution_count is null in this preview shape (would require a secondar…
- `topic` (string): Phrase searched in Hansard
- `total_contributions` (integer): Total contributions in Hansard matching topic+filters (TotalContributions)
- `total_debates` (integer): Total distinct debates touching this topic (TotalDebates)
- `total_divisions` (integer): TotalDivisions upstream count. Non-zero → consider votes_search_divisions.
- `total_written_answers` (integer): TotalWrittenAnswers upstream count
- `total_written_statements` (integer): TotalWrittenStatements upstream count

### `parliament_find_member` (~173 tokens)

Find Member of Parliament

USE THIS TOOL WHEN you have a member's name and need their integer member_id.

Returns all members matching the name query, each with the integer `id`,
party, constituency, house, and current-sitting status. Disambiguates
common-name matches (e.g. "Lord Smith" returns multiple peers).

CALL THIS BEFORE any tool that filters by member_id — including
parliament_get_debate_contributions, parliament_member_debates, and
parliament_member_interests. Name → ID first; ID-based filtering second.
Skipping this step and text-searching by name returns unrelated results
(see parliament_search_hansard's anti-bypass note for the Pannick case).

Input parameters:

- `name` (string, required): Name or partial name, e.g. 'Starmer', 'Baroness Hale'

Output parameters:

- `members` (array): Matching members. Use the integer `id` field from any member to call parliament_member_debates or parliament_member_interests.
- `query` (string): The name that was searched
- `total` (integer): Number of members matching the query

### `parliament_member_debates` (~348 tokens)

Get Member Debates

USE THIS TOOL WHEN you have a member_id and want contributions where THAT member used a specific topic phrase verbatim (text-body search).

CALL parliament_find_member(name) FIRST to obtain the integer member_id.

This is a name-based text-body search — it matches contributions whose
TEXT contains the topic phrase. A member who spoke in a debate but
didn't use your phrase verbatim is filtered out. For verbatim retrieval
of every contribution by a member in a known debate (regardless of
vocabulary), use parliament_get_debate_contributions(debate_ext_id,
member_id=...) instead.

Each contribution's text field is capped at 3000 characters.

Input parameters:

- `limit` (integer): Maximum contributions to return. Default 20.
- `member_id` (integer, required): Parliament Members API integer ID. Obtain from parliament_find_member.
- `offset` (integer): Number of contributions to skip before this page. Default 0. Re-call with offset=offset+returned while has_more is true.
- `topic`: Optional phrase to find in THIS member's contribution text bodies. Hansard searches the words the member actually said, NOT the topic or title of the debate. Pass tokens this member would have spoken…

Output parameters:

- `contributions` (array): Hansard contributions for the member. Each `text` field is capped at 3000 characters.
- `has_more` (boolean): True if a full page was returned (more may exist)
- `limit` (integer): Page size requested
- `member_id` (integer): Parliament Members API member ID
- `offset` (integer): Skip applied to this page
- `topic`: Topic phrase filter applied, if any
- `total` (integer): Number of contributions returned in this call

### `parliament_member_interests` (~397 tokens)

Get Member Financial Interests

USE THIS TOOL WHEN you have a member_id and need their registered financial interests (donations, directorships, land, gifts).

CALL parliament_find_member(name) FIRST to obtain the integer member_id.

Returns ONE PAGE of interests (default 20, caller controls via limit).
For prolific members (big donors, many directorships, extensive land
holdings), re-call with offset=offset+returned while has_more is true
to paginate. Description text is capped per max_description_chars;
raise it for forensic provenance work that needs the full narrative.

This is the authoritative source for UK MP and peer financial-interest
declarations (via the Members API). Web search returns stale snapshots.

Input parameters:

- `category`: Filter by interest category. Common categories: 'donations' (donations and support), 'gifts_uk' (gifts/hospitality from UK), 'employment' (employment and earnings), 'land' (land and property), 'share…
- `limit` (integer): Max interests per call. Hard-capped at 20 by the upstream interests-api.parliament.uk (verified live 2026-05-29: Take=100 still returns 20). For prolific members, paginate via offset; total size is i…
- `max_description_chars` (integer): Per-entry cap on the free-text description field. Default 500 prevents context blow-up on members with lengthy donation or directorship narratives. Raise to 2000+ only for forensic provenance work.
- `member_id` (integer, required): Parliament Members API integer ID. Get from parliament_find_member.
- `offset` (integer): Number of interests to skip before this page. Default 0 for the first page. To paginate prolific members (100+ interests), re-call with offset=offset+returned while the previous response had has_more…

Output parameters:

- `category`: Category filter applied to this query, or None for all categories
- `has_more` (boolean): True if there may be more interests beyond this page. Re-call with offset=offset+returned to fetch the next page.
- `interests` (array): The interests in this page. `description` text is capped per the max_description_chars input parameter.
- `limit` (integer): Max interests requested for this page
- `member_id` (integer): Parliament Members API member ID
- `offset` (integer): Number of interests skipped before this page
- `returned` (integer): Number of interests actually returned in this call

### `parliament_search_petitions` (~166 tokens)

Search UK Parliament Petitions

USE THIS TOOL WHEN searching UK Parliament petitions by keyword or topic.

Returns petition title, state, signature count, and dates for government
response or parliamentary debate if applicable. Filter by state (open,
closed, debated, etc.) to narrow to live or historical petitions.

This is the authoritative source for UK Parliament petitions
(petition.parliament.uk).

Input parameters:

- `limit` (integer): Maximum petitions to return. Default 20.
- `offset` (integer): Number of petitions to skip before this page. Default 0. Re-call with offset=offset+returned while has_more is true.
- `query` (string, required): Search term for petition titles, e.g. 'ban trophy hunting' or 'NHS funding'.
- `state` (string): Filter by petition state.

Output parameters:

- `has_more` (boolean): True if a full page was returned (more may exist)
- `limit` (integer): Page size requested
- `offset` (integer): Skip applied to this page
- `petitions` (array): Matching petitions (title, state, signature count, key dates, URL).
- `query` (string): The term that was searched in petitions
- `state` (string): Petition state filter applied to this query
- `total` (integer): Number of petitions returned in this call

### `parliament_get_debate_divisions` (~284 tokens)

Get Divisions Held In A Debate

USE THIS TOOL WHEN you have a debate_ext_id and want the divisions (formal votes) held within it.

Most debates contain no divisions — Business of the House sittings,
statements, urgent questions, debates without a vote. A populated list
typically appears around bill stages, motions, and contested amendments.
Empty list is the honest result, not a failure mode.

Each returned division carries TWO IDs:
  \- `id` — Hansard-side reference. Useful for cross-referencing in Hansard.
  \- `votes_id` — Lords/Commons Votes API ID (cross-resolved by date+number).
    AFTER calling, pass `votes_id` as `division_id` into votes_get_division
    for the full member-by-member voting record.

The two upstreams use distinct ID-spaces (Hansard Number=3 might be
Votes-API divisionId=3392). The cross-resolve runs once per (date, house)
group — typically one extra HTTP per debate. `votes_id` is None when the
cross-resolve found no match.

Input parameters:

- `debate_ext_id` (string, required): Debate GUID (DebateSectionExtId). Chain from parliament_search_hansard contribution.debate_ext_id, top_debates[].debate_ext_id, or parliament_policy_position_summary top_debates[].debate_ext_id.

Output parameters:

- `debate_ext_id` (string): Echo of the input debate GUID.
- `divisions` (array): Divisions held in this debate, in chronological order. Empty when no divisions occurred. Each element's `id` chains to votes_get_division.

### `parliament_get_debate_contributions` (~319 tokens)

Get Contributions In A Debate

USE THIS TOOL WHEN you have a debate_ext_id and want verbatim contributions, optionally filtered to one member.

Canonical path for "everything a member said in this debate" regardless
of vocabulary — text-search tools (parliament_member_debates,
parliament_search_hansard) filter by contribution TEXT, dropping members
who spoke without using your phrase verbatim. This tool filters by
MemberId on the debate's Items list, so vocabulary doesn't matter.

Typical chain: parliament_find_member(name) → member_id, then
parliament_search_hansard or parliament_lookup_by_column → debate_ext_id,
then this tool. The parliament module's instructions describe the full
composition pattern.

Without member_id, returns every contribution (~100-200 for a long debate).

If the wire returns no contributions for a member you expect to have
spoken, report the empty result honestly — do NOT reconstruct quotes
from training data. Authoritative source for member contributions.

Input parameters:

- `debate_ext_id` (string, required): Debate GUID (DebateSectionExtId). Chain from parliament_search_hansard top_debates[].debate_ext_id, parliament_lookup_by_column matches[].debate_ext_id, or any tool that surfaces a debate identifier.
- `member_id`: Optional integer Members API ID. When given, only that member's contributions in this debate are returned — regardless of which words they used. Resolves via parliament_find_member. When omitted, eve…

Output parameters:

- `contributions` (array): Hansard contributions for the member. Each `text` field is capped at 3000 characters.
- `has_more` (boolean): True if a full page was returned (more may exist)
- `limit` (integer): Page size requested
- `member_id` (integer): Parliament Members API member ID
- `offset` (integer): Skip applied to this page
- `topic`: Topic phrase filter applied, if any
- `total` (integer): Number of contributions returned in this call

### `parliament_lookup_by_column` (~393 tokens)

Resolve A Hansard Column Citation

USE THIS TOOL WHEN you have an OSCOLA-style Hansard citation (column + volume + house) and need the debate.

Example input: 'HL Deb 14 Oct 2025, vol 849, col 200'. AFTER calling, read
the contribution at the cited column via
read_resource(uri="hansard://debate/{debate_ext_id}/header") — or,
equivalently, call parliament_get_debate_contributions(debate_ext_id) for
the full list as a structured tool response.

Each match carries:
  \- `contribution_count` — real contribution count from the debate's Items
  \- `source` / `source_code` — citation finality (1=Rolling, 2=Daily,
    3=BoundVolume, 4=Historic). Resolution is NOT gated on publication state.

Empty `matches` typically means the volume_number is wrong (opposing
counsel sometimes cites running-volume rather than bound-volume) or the
column is in a Written Statement (use the 'W'-suffixed column as-is).
It does NOT mean the citation is fabricated — surface the failure.

Authoritative source for OSCOLA Hansard column resolution.

Input parameters:

- `column_number` (string, required): Hansard column number from an OSCOLA footnote, e.g. '200' for 'HL Deb 14 Oct 2025, vol 849, col 200'. String (not integer) to accommodate column suffixes like '1162W' for written answers.
- `house` (string): Restrict to one House. Default 'both' searches across both Houses.
- `volume_number` (integer, required): Hansard volume number (the 'vol 849' part of an OSCOLA citation). Required — the endpoint only resolves citations when given the volume; sitting date is NOT a substitute (verified live 2026-05-29).

Output parameters:

- `column_number` (string): Echo of the requested column number.
- `house` (string): House filter applied.
- `matches` (array): Debate sections containing the cited column, in upstream relevance order. Each element's `debate_ext_id` chains to hansard://debate/{debate_ext_id}/header, and carries `source`/`source_code` for the…
- `total_results` (integer): Number of debate matches found.
- `volume_number` (integer): Echo of the requested volume number.

### `bills_search_bills` (~267 tokens)

Search Parliamentary Bills

USE THIS TOOL WHEN searching UK parliamentary bills by keyword, session, house, or legislative stage.

Returns a paginated page of bill summaries (title, current stage, whether
it became an Act). AFTER calling, pass a bill_id into bills_get_bill for
full detail (sponsors, long title, Royal Assent date).

Authoritative source for UK parliamentary bill status.

Input parameters:

- `house`: Filter by originating house. Omit for all houses.
- `limit` (integer): Maximum bills to return in this call. Default 20 keeps responses focused; raise up to 100 for bulk exports.
- `offset` (integer): Number of results to skip before this page. Default 0 for the first page. Re-call with offset=offset+returned while has_more is true to paginate.
- `query` (string, required): Search term for bill titles and descriptions, e.g. 'online safety' or 'financial services'.
- `session`: Numeric parliamentary session ID (e.g. 40 = 2024-25, 39 = 2023-24). NOT a year string like '2025'. If you only know the year, omit this and filter the results instead. Omit to search all sessions.
- `stage`: Filter by current legislative stage.

Output parameters:

- `bills` (array): Matching bills. Use the integer `id` field from any bill to call bills_get_bill for full detail.
- `has_more` (boolean): True if more results exist beyond this page. Re-call with offset=offset+returned to fetch the next page.
- `limit` (integer): Maximum results requested in this call
- `offset` (integer): Number of results skipped before this page
- `query` (string): The search term that was used
- `returned` (integer): Number of results actually on this page
- `total`: Total results matching the query across all pages, if the upstream API reported it. None if unknown.

### `bills_get_bill` (~189 tokens)

Get Bill Detail

USE THIS TOOL WHEN you have a bill_id (from bills_search_bills) and want the full detail.

Returns sponsors, current stage, long title, summary, and Royal Assent
date if enacted. Summary text is capped per max_summary_chars — check
summary_truncated in the response.

AFTER calling, use parliament_search_hansard(query=bill_short_title) to
find the bill's parliamentary debates, or bills_search_bills with a
related keyword for adjacent bills.

Input parameters:

- `bill_id` (integer, required): Bill ID from bills_search_bills results.
- `max_summary_chars` (integer): Maximum characters of the bill summary text to return. Default 5,000 (~1,250 tokens) covers most bills. Raise for substantive government bills (Finance Act, Levelling-up) whose summary runs longer. C…

Output parameters:

- `current_house`: House where the bill currently sits
- `current_stage`: Current legislative stage
- `id` (integer): Bill ID
- `is_act` (boolean): Whether the bill has received Royal Assent
- `long_title`: Full long title
- `originating_house`: House where the bill was introduced
- `royal_assent_date`: Date Royal Assent was given
- `short_title` (string): Short title of the bill
- `sponsors` (array): Bill sponsors
- `stages` (array): Legislative stages the bill has passed through
- `summary`: Bill summary text, possibly truncated per max_summary_chars. Check summary_truncated and summary_original_length for full-text info.
- `summary_original_length` (integer): Original summary length in characters before any truncation
- `summary_truncated` (boolean): True if summary was cut to fit max_summary_chars
- `url` (string): Parliament URL for this bill

### `votes_search_divisions` (~217 tokens)

Search Parliamentary Divisions

USE THIS TOOL WHEN searching Commons or Lords formal votes by topic, date, or member.

Returns division summaries (title, date, vote counts, pass/fail). AFTER
calling, pass division_id + house into votes_get_division for the full
member-by-member voter lists.

Authoritative source for UK parliamentary vote records.

Input parameters:

- `from_date`: Start date (YYYY-MM-DD).
- `house` (string): Which house to search.
- `limit` (integer): Maximum divisions to return. Default 25 (Commons API max-per-page).
- `member_id`: Filter to divisions where this member voted. Get the member ID from parliament_find_member.
- `offset` (integer): Number of divisions to skip before this page. Default 0. Re-call with offset=offset+returned while has_more is true.
- `query`: Search term for division titles, e.g. 'Rwanda' or 'Online Safety Bill'. Omit to browse recent divisions.
- `to_date`: End date (YYYY-MM-DD).

Output parameters:

- `divisions` (array): Matching divisions. Use the integer `id` field with votes_get_division to fetch the full voter list.
- `has_more` (boolean): True if a full page was returned (more may exist)
- `house` (string): Commons or Lords
- `limit` (integer): Page size requested
- `offset` (integer): Skip applied to this page
- `query`: The search term, if any (None = browse recent)
- `total` (integer): Number of divisions returned in this call

### `votes_get_division` (~119 tokens)

Get Division Detail

USE THIS TOOL WHEN you have a division_id + house and want the full member-by-member voting record.

Voter lists are truncated to 100 per side to fit response limits; total
voter counts are always accurate regardless of truncation. Chain from
votes_search_divisions or parliament_get_debate_divisions (which
cross-resolves Hansard division refs into votes-API division_ids).

Input parameters:

- `division_id` (integer, required): Division ID from votes_search_divisions results.
- `house` (string): Which house this division belongs to.

Output parameters:

- `aye_voters` (array): Members who voted Aye (may be truncated)
- `ayes_count` (integer): Total Aye votes
- `date` (string): Date of the division
- `house` (string): Commons or Lords
- `id` (integer): Division ID
- `is_government_win`: Whether the government won (Lords only)
- `noe_voters` (array): Members who voted No (may be truncated)
- `noes_count` (integer): Total No votes
- `passed` (boolean): Whether the motion passed
- `title` (string): Division title / motion text
- `total_aye_voters` (integer): Total number of Aye voters before truncation
- `total_noe_voters` (integer): Total number of No voters before truncation
- `truncated` (boolean): Whether voter lists were truncated to fit response limits

### `committees_search_committees` (~170 tokens)

Search Parliamentary Committees

USE THIS TOOL WHEN searching or listing UK parliamentary select committees by name, house, or active status.

Returns committee summaries (name, house, active status, ID). AFTER
calling, pass committee_id into committees_get_committee for current
membership, or into committees_search_evidence to retrieve oral and
written evidence submitted to that committee.

Input parameters:

- `active_only` (boolean): If true, only return currently active committees.
- `house`: Filter by house.
- `limit` (integer): Maximum committees to return. Default 100 comfortably covers all currently-active UK select committees. Raise only for historical sweeps.
- `query`: Search term for committee names, e.g. 'defence' or 'treasury'. Filtered client-side against committee names. Omit to list all committees.

Output parameters:

- `active_only` (boolean): Whether results were restricted to currently active committees
- `committees` (array): Matching committees. Use committees_get_committee for membership detail.
- `house`: House filter applied, or None
- `query`: Name substring filter applied, or None
- `total` (integer): Number of committees returned in this call

### `committees_get_committee` (~80 tokens)

Get Committee Detail

USE THIS TOOL WHEN you have a committee_id and want the metadata + current membership.

Fetches committee detail and member list in parallel. AFTER calling,
pass committee_id into committees_search_evidence to see what evidence
has been submitted to this committee on what topics.

Input parameters:

- `committee_id` (integer, required): Committee ID from committees_search_committees results.

Output parameters:

- `email`: Contact email
- `house`: Commons, Lords, or Joint
- `id` (integer): Committee ID
- `members` (array): Current committee members
- `name` (string): Committee name
- `phone`: Contact phone number
- `url`: Parliament URL for this committee

### `committees_search_evidence` (~238 tokens)

Search Committee Evidence

USE THIS TOOL WHEN you have a committee_id and want the oral and written evidence submitted to it.

Returns ONE PAGE of evidence (default 20). Free-text titles are capped
per max_title_chars; witness lists are capped at 10 per item. For
committees with many submissions, re-call with offset=offset+returned
while has_more is true.

Authoritative source for parliamentary committee evidence.

Input parameters:

- `committee_id` (integer, required): Committee ID from committees_search_committees results.
- `evidence_type` (string): Type of evidence to search.
- `limit` (integer): Maximum evidence items to return. Default 20. When evidence_type='both' the limit is split across oral and written (roughly half each).
- `max_title_chars` (integer): Per-item cap on the free-text title field. Default 300 prevents context blow-up from verbose inquiry titles. Raise to 1000+ only when you need the full title text.
- `offset` (integer): Number of evidence items to skip before this page. Default 0. Re-call with offset=offset+returned while has_more is true.

Output parameters:

- `committee_id` (integer): Committee ID this page belongs to
- `evidence` (array): Evidence items in this page. Titles are capped per max_title_chars; witness lists are capped at 10 per item.
- `evidence_type` (string): Evidence type filter applied to this query
- `has_more` (boolean): True if there may be more evidence beyond this page. Re-call with offset=offset+returned to fetch the next page. Conservative: when evidence_type='both', True if either oral or written upstream page…
- `limit` (integer): Max evidence items requested for this page
- `offset` (integer): Number of evidence items skipped before this page
- `returned` (integer): Number of evidence items actually returned in this call

### `citations_parse` (~396 tokens)

Parse OSCOLA Citations

USE THIS TOOL WHEN you have free text (a memo, an email, a clause) and want every OSCOLA-style citation it contains extracted and classified.

Identifies: neutral citations ([2024] UKSC 12), law reports ([2024] 1 WLR
100), legislation sections (s.47 Companies Act 2006), SIs (SI 2018/1234),
retained EU law (Regulation (EU) 2016/679).

Parsing is pure regex by default. Ambiguous citations (e.g. bare [2024]
EWHC without division) can OPTIONALLY be disambiguated by setting
disambiguate=True, which asks the CONNECTED CLIENT's own model (not this
server) to resolve the division via MCP sampling — off by default.
Citations resolve to TNA / legislation.gov.uk URLs when possible.

AFTER calling, pass each citation through citations_resolve to verify it
points at a real document before quoting or formatting it — the parser
recognises the SHAPE of a citation but does not confirm the document
exists.

Input parameters:

- `disambiguate` (boolean): Default False — pure-regex parsing, no model in the loop. If True, ambiguous citations (e.g. bare EWHC without a division) are sent to the connected client's own LLM, via MCP sampling, to resolve the…
- `text` (string, required): Free text containing OSCOLA citations to extract. Supported: neutral citations ([2024] UKSC 12), law reports ([2024] 1 WLR 100), legislation sections (s.47 Companies Act 2006), SIs (SI 2018/1234), re…

Output parameters:

- `ambiguous` (array): Citations with confidence < 0.7; may have been partially disambiguated via sampling
- `citations` (array): All successfully parsed citations (confidence >= 0.7)
- `parse_duration_ms` (integer): Time taken to parse, in milliseconds
- `text_length` (integer): Character length of the input text

### `citations_resolve` (~260 tokens)

Resolve Single OSCOLA Citation

USE THIS TOOL BEFORE constructing an OSCOLA citation string from known fields, OR to confirm a citation points at a real document.

Parses + resolves a single citation (neutral citation, SI, legislation
section, retained EU law) and returns parsed fields plus resolved_url.
For neutral citations, performs a live TNA HEAD check — non-200 sets
confidence to 0.0 (document absent). Do NOT format or quote a
confidence-0.0 citation.

If the TNA HEAD check fails (timeout, connection error), raises ToolError
with {"error_category": "transient", "is_retryable": true}. One retry is
attempted — retry this call or proceed without TNA verification.

Formatting a citation from "known" fields without prior resolution is the
most common fabrication route. If this tool raises or returns no
resolved_url, do NOT manufacture a citation — surface the failure and ask
the user for the source URL.

Authoritative source for UK legal-citation resolution.

Input parameters:

- `citation` (string, required): A single OSCOLA citation to parse and resolve. E.g. '[2024] UKSC 12', 'SI 2018/1234', 's.47 Companies Act 2006'

Output parameters:

- `confidence` (number): Parse confidence 0.0–1.0. Citations below 0.7 are ambiguous and may have been sent for LLM disambiguation.
- `court`: Court code: UKSC, UKPC, EWCA Civ, EWCA Crim, EWHC (KB), EWHC (Ch), EWHC (Comm), EWHC (Fam), EWHC (Pat), EWHC (IPEC), UKUT (IAC), UKUT (TCC), UKUT (AAC), UKUT (LC), EAT, UKFTT (TC), UKFTT (GRC)
- `legislation_title`: Title of legislation (for s.NN Act YYYY citations)
- `number`: Judgment number within the year
- `page`: Starting page in the law report
- `raw` (string): Original citation text as found in the source
- `report_series`: Law report series abbreviation: WLR, AC, QB, KB, Ch, All ER, EWCA Civ, etc.
- `resolved_url`: TNA Find Case Law or legislation.gov.uk URL if successfully resolved
- `section`: Section number referenced
- `si_number`: SI number
- `si_year`: SI year (for SI YYYY/NNN citations)
- `type` (string): Classification of the citation type
- `volume`: Report volume number (for law reports)
- `year`: Year component of the citation

### `citations_network` (~190 tokens)

Get Case Citation Network

USE THIS TOOL WHEN you have a judgment slug and want to map every citation it makes — cases cited, legislation referenced, SIs, retained EU law.

Fetches the judgment XML from TNA and parses all OSCOLA citations
within. Returns citations grouped by type, deduplicated and sorted.
AFTER calling, pass any individual citation through citations_resolve
to confirm it resolves and to retrieve its canonical URL.

Useful for authority-network analysis (what did this judgment rely on?)
and for surfacing the legislative landscape a case sits inside.

Input parameters:

- `case_uri` (string, required): TNA judgment URI slug, e.g. 'uksc/2024/12' or 'ewca/civ/2023/450'. Use the 'uri' field from case_law_search results — not the full URL. Do not include the 'https://caselaw.nationalarchives.gov.uk/' p…

Output parameters:

- `case_uri` (string): The judgment URI that was fetched and parsed
- `eu_refs` (array): Retained EU law references, e.g. 'Regulation (EU) 2016/679'
- `law_report_refs` (array): Law report citations, e.g. '[2020] 1 WLR 100'
- `legislation_refs` (array): Legislation section references, e.g. 's.47 Companies Act 2006'
- `neutral_citations` (array): Neutral citations referenced, e.g. '[2020] UKSC 14'
- `si_refs` (array): Statutory Instrument references, e.g. 'SI 2018/1234'
- `total_citations` (integer): Sum of all de-duplicated citations across every bucket

### `citations_format_oscola` (~512 tokens)

Format OSCOLA Citation String

USE THIS TOOL AFTER citations_resolve to produce the correctly formatted OSCOLA citation string.

Pass the parsed fields returned by citations_resolve directly into this
tool. Formats per OSCOLA 4th edition rules for each citation type.

Refuses (status: upstream_validation) if confidence is 0.0 — TNA confirmed
the document does not exist — or if a neutral citation has no resolved_url
(ambiguous court code, e.g. bare EWHC without a division). In either case,
do NOT manufacture a citation; surface the failure and ask the user for
the source URL or better identifying details.

DO NOT construct the input fields yourself. The structured input must come
from citations_resolve — guessing fields is the primary citation-fabrication
route and this tool is the guard against it.

Authoritative OSCOLA formatting for UK legal citations (no network call).

Input parameters:

- `citation_type` (string, required): 'type' field from citations_resolve result.
- `confidence` (number, required): 'confidence' from citations_resolve. Refuses to format if 0.0 — that means TNA confirmed the document does not exist. Pass only the value citations_resolve returned; do not guess.
- `court`: 'court' from citations_resolve, e.g. 'UKSC', 'EWCA CIV', 'EWHC (KB)'.
- `legislation_title`: 'legislation_title' from citations_resolve, e.g. 'Companies Act 2006'.
- `number`: 'number' from citations_resolve (judgment number within the year).
- `page`: 'page' from citations_resolve (starting page in the law report).
- `raw`: 'raw' from citations_resolve. Used as-is for EU retained law — the original text preserves the Regulation/Directive distinction.
- `report_series`: 'report_series' from citations_resolve, e.g. 'WLR', 'AC', 'QB'.
- `resolved_url`: 'resolved_url' from citations_resolve. Must be non-null for neutral citations.
- `section`: 'section' from citations_resolve, e.g. '47', '12', '20A'.
- `si_number`: 'si_number' from citations_resolve.
- `si_year`: 'si_year' from citations_resolve.
- `volume`: 'volume' from citations_resolve (law report volume, if any).
- `year`: 'year' from citations_resolve.

### `hmrc_get_vat_rate` (~146 tokens)

Get VAT Rate for Commodity

USE THIS TOOL WHEN you have a UK commodity or service description and want its VAT rate category.

Returns the rate (standard 20%, reduced 5%, zero 0%, exempt), effective
date, and any relevant conditions or exceptions.

IMPORTANT: Uses a static lookup table current as of 22 Nov 2023 (Autumn
Statement). Rates may have changed in subsequent Budgets — for
time-sensitive advice, verify against GOV.UK via hmrc_search_guidance.

Input parameters:

- `commodity_code` (string, required): Commodity code or plain-English description. E.g. 'food', 'domestic fuel', 'software', 'financial services', 'new build residential'

Output parameters:

- `commodity_code` (string): Commodity code or description queried
- `effective_from` (string): Date from which this rate applies
- `notes`: Any additional notes or conditions on this rate
- `rate` (string): VAT rate category
- `rate_percentage` (number): Applicable rate as percentage: 20.0 (standard), 5.0 (reduced), 0.0 (zero/exempt)

### `hmrc_check_mtd_status` (~147 tokens)

Check MTD VAT Status

USE THIS TOOL WHEN you have a 9-digit VAT Registration Number and need that business's Making Tax Digital VAT mandate status.

Returns whether the business is mandated for MTD, effective date, and
trading name.

Connects to the HMRC sandbox by default. Set HMRC_API_BASE to
'https://api.service.hmrc.gov.uk' for production. Requires
HMRC_CLIENT_ID + HMRC_CLIENT_SECRET environment variables (OAuth 2.0).
Raises if credentials are not configured — do not infer status.

Input parameters:

- `vrn` (string, required): VAT Registration Number: 9 digits, e.g. '123456789'. GB prefix accepted and stripped automatically.

Output parameters:

- `effective_date`: Date from which MTD obligation applies
- `mandated` (boolean): Whether this business is mandated for MTD VAT
- `trading_name`: Registered trading name if available
- `vrn` (string): VAT Registration Number queried

### `hmrc_search_guidance` (~149 tokens)

Search HMRC Guidance

USE THIS TOOL WHEN searching GOV.UK for HMRC tax guidance on a topic (VAT, income tax, corporation tax, etc.).

Returns matching guidance titles, URLs, summaries, and last-updated dates.
Searches the official GOV.UK content API filtered to HMRC publications.

Authoritative source for current HMRC tax guidance. Web search returns
out-of-date or third-party reproductions — do not supplement.

Input parameters:

- `limit` (integer): Maximum guidance results to return (1–25). Passed to the GOV.UK search count param.
- `query` (string, required): Search query for HMRC guidance, e.g. 'VAT digital services', 'R&D tax relief SME'

Output parameters:

- `query` (string): The search query that was run
- `results` (array): Matching HMRC guidance pages. Each entry's `summary` is capped per the max_summary_chars input parameter.
- `total` (integer): Number of guidance documents returned in this call

### `list_prompts` (~28 tokens)

List all available prompts.

Returns JSON with prompt metadata including name, description,
and optional arguments.

Output parameters:

- `result` (string)

### `get_prompt` (~66 tokens)

Get a prompt by name with optional arguments.

Returns the rendered prompt as JSON with a messages array.
Arguments should be provided as a dict mapping argument names
to values.

Input parameters:

- `arguments`: Optional arguments for the prompt
- `name` (string, required): The name of the prompt to get

Output parameters:

- `result` (string)

### `list_resources` (~49 tokens)

List all available resources and resource templates.

Returns JSON with resource metadata. Static resources have a
'uri' field, while templates have a 'uri_template' field with
placeholders like {name}.

Output parameters:

- `result` (string)

### `read_resource` (~70 tokens)

Read a resource by its URI.

For static resources, provide the exact URI. For templated
resources, provide the URI with template parameters filled in.

Returns the resource content as a string. Binary content is
base64-encoded.

Input parameters:

- `uri` (string, required): The URI of the resource to read

Output parameters:

- `result` (string)

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp#diagnostics

## Score history

- 2026-08-03: 74
- 2026-08-02: 73
- 2026-08-01: 73
- 2026-07-31: 72
- 2026-07-30: 70
- 2026-07-29: 69
- 2026-07-28: 69
- 2026-07-27: 68
- 2026-07-26: 67

## Links

- Remote endpoint: https://uk-legal-mcp.fly.dev/mcp
- Repository: https://github.com/paulieb89/uk-legal-mcp
- Website: https://bouch.dev/products/legal-research
- Changelog RSS feed: https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp/changelog.json
- HTML version of this page: https://verifymcp.io/servers/paulieb89-uk-legal-mcp/uk-legal-mcp
