VATBuild's MCP server is directly compatible with Claude Desktop, Cursor, and Zed.
This guide covers all three clients, a recommended agent workflow, and how to get an
API key.
Endpoint: POST https://vatbuild.com/mcp
Protocol: Streamable HTTP, JSON-RPC 2.0, stateless
Auth: Authorization: Bearer <api-key> — see Getting an API key
3. Cursor
4. Zed
8. Rate limits
10. Troubleshooting
Option A — Register via MCP (zero-click):
Call create_account with no auth header. The tool returns a vb_live_... API key
immediately and creates a free VATBuild account. Copy the key and add it to your
client config.
Option B — Web dashboard:
Log in at vatbuild.com, navigate to Settings → API Keys,
and create a key. Tick the scopes you need (see Scopes reference).
Keys are shown only once — copy and store them securely.
Option C — Existing account, forgotten key:
Call authenticate_account with your email address. A fresh key is returned and a
sign-in link is sent to your inbox.
Claude Desktop reads MCP server configuration from claude_desktop_config.json.
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
Windows:
%APPDATA%\Claude\claude_desktop_config.json
Linux:
~/.config/Claude/claude_desktop_config.json
Add (or merge) the following into the mcpServers object, replacing
vb_live_YOUR_API_KEY_HERE with your actual VATBuild API key:
{
"mcpServers": {
"vatbuild": {
"url": "https://vatbuild.com/mcp",
"headers": {
"Authorization": "Bearer vb_live_YOUR_API_KEY_HERE"
}
}
}
}
Quit and restart Claude Desktop after saving (quit and relaunch — a window
close is not sufficient). On next launch, VATBuild tools are
available in the tool picker. Try:
Use the check_line_item tool to classify "Supply and fit underfloor heating" for a
new-build detached house under VAT431NB rules.
Claude Desktop sends requests server-to-server (no Origin header), which VATBuild
allows by design. No CORS or browser-origin configuration is needed.
check_line_item is public — no API key is required to classify line items without
creating projects or uploading invoices. You can omit the headers block entirely for
a read-only connection.
For the full workflow — account creation, project setup, invoice submission, line-item
routing — the key needs all five scopes. When creating the key in the dashboard, tick:
projects:readprojects:writeline_items:writejobs:readinvoices:writeIf you already have other MCP servers configured, add VATBuild as an additional
entry in the mcpServers object:
{
"mcpServers": {
"other-server": {
"command": "npx",
"args": ["-y", "some-mcp-server"]
},
"vatbuild": {
"url": "https://vatbuild.com/mcp",
"headers": {
"Authorization": "Bearer vb_live_YOUR_API_KEY_HERE"
}
}
}
}
Cursor reads per-project MCP server config from .cursor/mcp.json in the workspace
root, or from a global user config at ~/.cursor/mcp.json.
Project-level (recommended — keeps the key scoped to this project):
.cursor/mcp.json
{
"mcpServers": {
"vatbuild": {
"url": "https://vatbuild.com/mcp",
"headers": {
"Authorization": "Bearer vb_live_YOUR_API_KEY_HERE"
}
}
}
}
Global (available across all Cursor workspaces):
~/.cursor/mcp.json — same format as above.
After saving, open Cursor Settings → MCP and verify VATBuild appears with a green
status indicator. If the file was not auto-detected, click Refresh.
> Secret hygiene: Add .cursor/mcp.json to .gitignore if the file contains a
> live API key, to prevent accidental key exposure in version control.
Cursor's Composer agent can then call VATBuild tools inline. For example:
@vatbuild classify this invoice line: "Scaffolding erection and hire, net £3,500, VAT £700"
under VAT431NB rules for a new-build detached house.
Zed reads MCP server configuration from settings.json. Open Zed's settings via
Zed → Settings → Open Settings (macOS) or File → Settings (Linux/Windows),
then add the assistant.mcp_servers key:
{
"assistant": {
"mcp_servers": {
"vatbuild": {
"url": "https://vatbuild.com/mcp",
"headers": {
"Authorization": "Bearer vb_live_YOUR_API_KEY_HERE"
}
}
}
}
}
Restart the Zed assistant panel after saving. VATBuild tools are then available when
using the assistant's tool-use mode.
> Tip: Zed's Streamable HTTP MCP support (url key under assistant.mcp_servers)
> is GA. If tools do not appear after restarting the assistant panel, check
> Zed changelog to confirm you are on a recent release.
The recommended end-to-end flow for a new VATBuild user via a Claude agent:
Step 1 — create_account
Input: { "firstName": "Jane", "email": "jane@example.com" }
Output: { "apiKey": "vb_live_...", "nextStep": "set_up_project" }
→ Store apiKey; use it as the Bearer token for all subsequent calls.
Step 2 — set_up_project
Input: { "claimantRoute": "self_build_431nb", "buildingType": "detached house",
"address": "12 Elm Lane, Bristol" }
Output: { "projectId": "uuid", "profileConfirmed": false, "projectUrl": "..." }
→ Surface projectUrl so the user can confirm their profile in the VATBuild web app.
Profile confirmation is required before invoices can be submitted.
Step 3 — assess_project_eligibility (optional, recommended)
Input: { "projectId": "uuid" }
Output: { "eligibilityStatus": "likely_eligible", "keyFindings": [...] }
→ Surface any "warning" or "blocker" findings to the user before they proceed.
Step 4 — submit_invoice_data (once profile is confirmed in the web app)
Input: { "projectId": "uuid", "imageData": "<base64>", "imageMimeType": "image/jpeg",
"supplierName": "ABC Builders Ltd", "invoiceRef": "INV-042" }
Output: { "documentId": "uuid", "status": "extracting" }
→ VATBuild queues AI extraction in the background.
→ imageMimeType accepts "image/jpeg", "image/png", "image/webp", or "application/pdf".
→ For PDFs, pass the raw PDF bytes as base64 — do not render pages to images first.
Step 5 — Poll list_invoices until extractionStatus = "complete"
Input: { "projectId": "uuid" }
→ Check each item's extractionStatus. Repeat every 5–10 seconds.
→ Once complete, proceed to step 6.
Step 6 — list_line_items
Input: { "projectId": "uuid", "status": "pending" }
Output: array of line items; each has claimRoute and vatComplexType
Step 7a — route_line_item (for items WITHOUT a pending_complex_answer)
Input: { "projectId": "uuid", "lineItemId": "uuid",
"item": { "lineText": "...", "netAmount": "...", "vatCharged": "...",
"supplyType": "labour" } }
Output: { "claimRoute": "zero_at_source", "reclaimAmount": "0.00" }
Step 7b — answer_vat_complex_question (for items WITH claimRoute: "pending_complex_answer")
Use this — not route_line_item — when an item requires a specific complex-VAT
confirmation. It resolves the vatComplexType question and persists the decision.
Input: { "projectId": "uuid", "lineItemId": "uuid", "confirmedAnswer": true }
Output: { "claimRoute": "supplier_correction", "crossItemContextRequired": false }
→ If crossItemContextRequired is true, call reclassify_companion_items next.
Step 8 — reclassify_companion_items (when crossItemContextRequired = true)
Input: { "projectId": "uuid", "documentId": "uuid" }
→ Re-routes sibling items on the same invoice using updated context.
Handling pending_complex_answer items:
When list_line_items returns an item with claimRoute: "pending_complex_answer",
read vatComplexType to know which question to ask the user. Common cases:
vatComplexType | Question to ask |
|---|---|
vague_description | "What does this line actually represent — labour, materials, a professional fee, or something else?" |
esm_install | "Is this the supply and installation of an energy-saving material (insulation, solar PV, heat pump) by the same contractor?" |
disability_adaptation | "Is this work being carried out for a disabled or chronically sick person under the HMRC disability adaptation relief?" |
fitted_furniture | "Was this fitted kitchen both supplied and installed by the same contractor?" |
soft_landscaping | "Is this landscaping required by a planning condition?" |
Do not guess the answer. Ask the user, then call answer_vat_complex_question.
For the full confirmedAnswer value table per vatComplexType, see
| Tool | Auth required | What it does |
|---|---|---|
check_line_item | None | Classify any invoice line against HMRC Notice 708 |
create_account | None | Create a new VATBuild account |
authenticate_account | None | Sign in and retrieve a fresh API key |
check_account_status | Bearer key | Check account verification and plan |
set_up_project | projects:write | Create a new project |
assess_project_eligibility | projects:read | Run an HMRC eligibility check |
list_projects | projects:read | List all projects in your account |
list_line_items | projects:read | Retrieve invoice line items for a project |
route_line_item | line_items:write | Classify and persist a VAT routing decision |
answer_vat_complex_question | line_items:write | Resolve a pending complex-VAT confirmation |
When creating an API key, grant only the scopes the agent needs.
| Scope | Tools covered |
|---|---|
| *(none)* | check_line_item, create_account, authenticate_account |
projects:read (or any scope) | check_account_status |
projects:read | list_projects, list_line_items, assess_project_eligibility |
projects:write | set_up_project |
line_items:write | route_line_item, answer_vat_complex_question, reclassify_companion_items, submit_invoice_data |
jobs:read | Job status polling |
invoices:write | submit_invoice_data (upload-only; no line-item override access) |
Recommended scope sets:
| Use case | Scopes |
|---|---|
| Read-only classification | *(none required)* |
| Read-only project access | projects:read |
| Full end-to-end workflow | All five scopes |
See gemini-integration.md — Rate limits for
the full rate-limit table. In summary:
| Request type | Limit |
|---|---|
| All requests per API key or IP | 60 per minute |
| Write tools | 10 per minute |
create_account / authenticate_account | 3 per hour per IP |
HTTP 429 responses include a Retry-After header. Implement exponential back-off
when polling list_invoices after submit_invoice_data.
Instead of embedding a long-lived API key, agents that act on behalf of individual
users can authenticate using VATBuild's OAuth 2.1 authorization server with PKCE.
Discovery:
GET https://vatbuild.com/.well-known/oauth-authorization-server
GET https://vatbuild.com/.well-known/oauth-protected-resource
Both endpoints return RFC 8414 / RFC 9728 metadata and are always live.
PKCE flow:
1. Dynamic registration — POST /oauth/register with your client metadata
(redirect_uris, client_name). Receive client_id.
2. Authorization — Redirect the user to `/oauth/authorize?client_id=...&scope=...
&code_challenge=...&code_challenge_method=S256`.
3. Token exchange — POST /oauth/token with the auth code and code_verifier.
Receive access_token and refresh_token.
4. MCP calls — Use Authorization: Bearer <access_token> on all /mcp requests.
For the full PKCE parameter reference and discovery endpoint response, see
gemini-integration.md — OAuth / PKCE alternative.
> Prerequisite: The VATBuild deployment must have MCP_OAUTH_ENABLED=true for
> the /oauth/authorize, /oauth/token, and /oauth/register endpoints to be
> active. The discovery endpoints (/.well-known/...) are always live regardless of
> this flag. Contact the VATBuild operator if the OAuth endpoints return 404.
For agents where a single service account is appropriate (rather than per-user OAuth),
the static API key approach is simpler and recommended.
| Symptom | Cause | Fix |
|---|---|---|
| VATBuild tools do not appear in Claude | Config file not saved or Claude not fully restarted | Quit and relaunch Claude; validate JSON with a linter |
Tool calls return registration_required | Bearer key missing or wrong header name | Confirm the header is Authorization: Bearer vb_live_... (not X-Api-Key) |
| HTTP 403 on a specific tool | API key lacks the required scope | Regenerate the key with the missing scope ticked |
| HTTP 429 | Rate limit exceeded | Wait for Retry-After seconds |
submit_invoice_data returns ASSESSMENT_NOT_CONFIRMED | Profile wizard not completed | User must visit projectUrl in the VATBuild web app and confirm their profile |
| Claude Desktop doesn't list VATBuild tools | Config file syntax error or wrong path | Validate JSON with a linter; confirm the file is at the path for your OS |
| Cursor shows VATBuild as "disconnected" | Stale config or network issue | Open Cursor Settings → MCP and click Refresh; check for JSON syntax errors in .cursor/mcp.json |
| Zed tools unavailable | Zed version doesn't support Streamable HTTP yet | Update Zed to the latest release and check release notes for MCP HTTP support |
check_line_item always returns pending_complex_answer | vatComplexTypeHint matches a confirm type and confirmedAnswer is null | Pass confirmedAnswer with the user's answer, or omit vatComplexTypeHint and let the engine auto-detect |