hmrc-mtd-mcp
v2.10.0
Published
HMRC Making Tax Digital API client and MCP server — reusable across projects
Maintainers
Readme
HMRC MTD MCP Server
MCP server for HMRC Making Tax Digital Income Tax Self Assessment APIs. Full End-to-End: quarterly updates, annual submissions, BSAS, losses, and Final Declaration.
Quick Setup
# 1. Install & build
npm install
npm run build
# 2. Create .env (gitignored) — see .env.example
cp .env.example .env
# Edit .env with your HMRC sandbox credentials
# 3. Register with Claude Code
claude mcp add hmrc -- env-file .env node /path/to/hmrc-mtd-mcp/build/index.jsOr add manually to ~/.claude/settings.json:
{
"mcpServers": {
"hmrc": {
"command": "node",
"args": ["/absolute/path/to/hmrc-mtd-mcp/build/index.js"],
"env": {
"HMRC_NINO": "AA999999A",
"HMRC_BUSINESS_ID": "your-biz-id",
"HMRC_TAX_YEAR": "2025-26",
"HMRC_ACCESS_TOKEN": "your-token"
}
}
}
}Environment Auto-Switching
The server auto-detects sandbox vs production from your git branch:
| Branch | Environment |
|---|---|
| main / master | production (api.service.hmrc.gov.uk) |
| Everything else | sandbox (test-api.service.hmrc.gov.uk) |
Override with HMRC_ENV=production or HMRC_ENV=sandbox in your .env.
Available Tools (27)
Auth (3)
| Tool | Description |
|---|---|
| hmrc_auth_status | Check auth status & environment config |
| hmrc_refresh_token | Refresh OAuth access token |
| hmrc_validate_fraud_headers | Validate fraud headers (sandbox) |
Phase 1 — In-Year (7)
| Tool | API Version | Description |
|---|---|---|
| hmrc_list_businesses | v2 | List businesses for NINO |
| hmrc_get_business | v2 | Get business details |
| hmrc_get_obligations | v3 | Get quarterly deadlines & status |
| hmrc_submit_quarterly | v5 | Submit cumulative quarterly return (all 15 expense fields) |
| hmrc_get_quarterly | v5 | Retrieve quarterly submission |
| hmrc_trigger_calculation | v8 | Trigger in-year / intent-to-finalise / intent-to-amend |
| hmrc_list_calculations | v8 | List calculations |
| hmrc_get_calculation | v8 | Retrieve calculation detail |
Phase 2 — End of Year (12)
| Tool | API Version | Description |
|---|---|---|
| hmrc_submit_annual | v5 | Submit annual allowances & adjustments |
| hmrc_get_annual | v5 | Retrieve annual submission |
| hmrc_delete_annual | v5 | Delete annual submission |
| hmrc_trigger_bsas | v7 | Trigger BSAS |
| hmrc_list_bsas | v7 | List BSAS calculations |
| hmrc_get_bsas | v7 | Retrieve BSAS detail |
| hmrc_adjust_bsas | v7 | Submit BSAS accounting adjustments |
| hmrc_get_losses_and_claims | v7 | Retrieve losses-and-claims record for a tax year |
| hmrc_set_losses_and_claims | v7 | Create/amend losses-and-claims record (single PUT upsert; 204 on success) |
| hmrc_delete_losses_and_claims | v7 | Delete losses-and-claims record for a tax year |
| hmrc_final_declaration | v8 | Submit Final Declaration (IRREVERSIBLE) |
| hmrc_confirm_amendment | v8 | Confirm post-FD amendment |
MCP Resources (4)
| Resource URI | Description |
|---|---|
| hmrc://categories | 15 HMRC expense fields with SA form box references |
| hmrc://quarters/{taxYear} | Quarter date ranges & deadlines |
| hmrc://error-codes | HMRC error code → message mapping |
| hmrc://tax-rates/{taxYear} | Income tax & NIC rates |
HMRC Compliance
Per-API Accept Header Versioning
Each API family uses its required version — and sandbox vs production may diverge. The version map in api-versions.ts is keyed per-environment; getAcceptHeader(path, env) picks the right one based on HmrcConfig.env.
| API | Sandbox | Production |
|---|---|---|
| Business Details | application/vnd.hmrc.2.0+json | application/vnd.hmrc.2.0+json |
| Obligations | application/vnd.hmrc.3.0+json | application/vnd.hmrc.3.0+json |
| Self Employment | application/vnd.hmrc.5.0+json | application/vnd.hmrc.5.0+json |
| Losses and Claims ⚠️ | application/vnd.hmrc.7.0+json | application/vnd.hmrc.4.0+json |
| BSAS | application/vnd.hmrc.7.0+json | application/vnd.hmrc.7.0+json |
| Individual Calculations | application/vnd.hmrc.8.0+json | application/vnd.hmrc.8.0+json |
| Fraud header validator | application/vnd.hmrc.1.0+json | application/vnd.hmrc.1.0+json |
⚠️ Losses and Claims is on v7 in sandbox, v4 in production. v7 is a single resource per {nino, businessId, taxYear} with three operations: GET, PUT (upsert, 204), DELETE. The PUT body uses nested claims.{carryBack,carrySideways,preferenceOrder,carryForward} and losses.broughtForwardLosses; cross-field business rules (RULE_INCORRECT_OR_EMPTY_BODY_SUBMITTED, RULE_MISSING_PREFERENCE_ORDER, RULE_PREFERENCE_ORDER_NOT_ALLOWED, RULE_CARRY_FORWARD_AND_TERMINAL_LOSS_NOT_ALLOWED) are enforced client-side via Zod refines before the network call. Production v4.0 is unverified pending production credentials.
Fraud Prevention Headers
All 12 HMRC-mandated headers are sent on every request:
Gov-Client-Connection-Method:DESKTOP_APP_DIRECTGov-Client-Device-ID: stable UUID (from env or auto-generated)Gov-Client-User-IDs: OS usernameGov-Client-Timezone: UTC offset (e.g.UTC+01:00)Gov-Client-Local-IPs: detected from OS network interfacesGov-Client-Local-IPs-Timestamp: ISO timestampGov-Client-Screens: terminal/screen dimensionsGov-Client-Window-Size: terminal dimensionsGov-Client-User-Agent: OS/device info in HMRC key=value formatGov-Vendor-Version: configurable (VENDOR_PRODUCT_NAME=VERSION)Gov-Vendor-Product-Name: configurable (defaults tohmrc-mtd-mcp)Gov-Vendor-License-IDs: empty
Quarterly Submission — Cumulative YTD Model
MTD requires cumulative totals from 6 April, not per-quarter deltas:
- Q1: totals 6 Apr → 5 Jul
- Q2: totals 6 Apr → 5 Oct (not just Q2 figures)
- Q3: totals 6 Apr → 5 Jan
- Q4: totals 6 Apr → 5 Apr (full year)
All 15 HMRC expense fields are supported, plus disallowable expenses.
Testing
npm test # run all tests
npm run test:watch # watch mode
npm run test:coverage # with coverage60 tests covering:
- API version resolution for all 7 HMRC API families
- Environment auto-detection from git branches
- All 12 fraud prevention headers
- HTTP client (GET/POST/PUT/DELETE) with correct headers
- Quarter dates, expense categories, error codes, tax rates
Security
- Tokens read from env vars only — never hardcoded
- Nothing logged to stdout (would break MCP JSON-RPC stream)
- Diagnostic logs to stderr only
- Sandbox on feature/develop/staging branches, production on main/master only
- Vendor name configurable — not tied to any specific product
