Using VATBuild MCP with Claude

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


Contents

1. Getting an API key

2. OAuth / PKCE

3. Claude Desktop

4. Cursor

5. Zed

6. Canonical agent workflow

7. Available tools

8. Scopes reference

9. Rate limits

10. Troubleshooting


Getting an API key

Option A — OAuth 2.1 with PKCE (recommended):

VATBuild supports OAuth 2.1 with PKCE. Claude and other MCP clients that implement

RFC 9728 auto-discover the auth endpoints and drive the PKCE flow — no static key

required. See OAuth / PKCE for discovery URLs and the

full PKCE parameter reference.

Option B — Web dashboard (bearer key):

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 — Device-code flow via MCP (new or existing account):

create_account and authenticate_account are public tools (no key required).

> Note for Claude Desktop and other session MCP clients: Session clients can complete the initialize handshake without credentials and call check_line_item with no key. Auth is enforced per-tool: calling a gated tool without a valid Bearer token returns HTTP 401 with WWW-Authenticate: Bearer resource_metadata=… (RFC 9728), triggering OAuth discovery. Claude Desktop handles this automatically by opening a browser for the PKCE consent flow. Option C (device-code flow) is also available to all callers.

is returned and no account is created until the user clicks the link.** Once confirmed,

call authenticate_account to begin the device-code flow.

Both paths complete the same way: authenticate_account returns a user_code and

request_id. Display the user_code to the user, ask them to check their email and

click "Approve API access", then poll poll_authentication with the request_id

every 3–5 seconds. Once approved, the tool returns { status: "ready", key_retrieval_url }

give the user key_retrieval_url and ask them to open it in their browser. The page

shows their API key and the ready-to-paste config snippet. Never display the key in

the chat and never pass it as a tool argument. After adding the key the user must

fully quit and relaunch the client (closing the chat window is not sufficient).

> Note: Claude Desktop supports OAuth 2.1 / PKCE (Option A above) and drives the

> browser consent flow automatically — no API key needs to be placed in the config.

> The device-code flow (Option C) is available for clients that cannot use OAuth.


Claude Desktop

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:


{
  "mcpServers": {
    "vatbuild": {
      "type": "http",
      "url": "https://vatbuild.com/mcp"
    }
  }
}

No API key is needed in the config — Claude Desktop auto-discovers the OAuth

endpoints and opens a browser window for sign-in on first use. Quit and restart

Claude Desktop after saving the config file (quit and relaunch — a window

close is not sufficient). On next launch, VATBuild tools are available and

the browser sign-in prompt appears automatically. 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.

Minimal setup (read-only, no project data)

check_line_item is a public tool and requires no key — Claude Desktop can call it

immediately after initialize without any credentials. Include a Bearer key in the

headers block to use the project tools (routing, invoices, reports); a key with no

scopes is sufficient for check_line_item-only access and avoids any OAuth prompt.

Full setup (end-to-end workflow)

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:

Multiple servers

If 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": {
      "type": "http",
      "url": "https://vatbuild.com/mcp",
      "headers": {
        "Authorization": "Bearer vb_live_YOUR_API_KEY_HERE"
      }
    }
  }
}

Cursor

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": {
      "type": "http",
      "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

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.


Canonical agent workflow

The recommended end-to-end flow for a new VATBuild user via a Claude agent:


Step 1 — create_account → authenticate_account → poll_authentication
  create_account input:  { "firstName": "Jane", "email": "jane@example.com" }
  Output: { "status": "verification_email_sent", "verified": false, "nextStep": "check_email" }
  → No API key returned. Tell the user a confirmation email has been sent.
    The account only activates when they click the link.

  Once confirmed, call authenticate_account:
  Input:  { "email": "jane@example.com" }
  Output: { "status": "magic_link_sent", "user_code": "BCDF-7823", "request_id": "..." }
  → Display user_code to the user. Ask them to check their inbox and click
    "Approve API access" — the approval page shows the same code for verification.

  Poll poll_authentication every 3–5 s until status is "ready":
  Input:  { "request_id": "..." }
  Output: { "status": "ready", "key_retrieval_url": "https://vatbuild.com/..." }
  → Give the user key_retrieval_url and ask them to open it in their browser.
    The page shows the API key and the ready-to-paste config snippet.
    Do NOT display the key in the chat. After adding the key the user must
    fully quit and relaunch the client. Never pass the key as a tool argument.

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: { "data.outcome.claimRoute": "zero_at_source", "data.reclaimAmount": "0" }

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: { "data.outcome.claimRoute": "supplier_correction", "data.crossItemContextRequired": false }
  → If data.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:

|---|---|

vatComplexTypeQuestion 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"Is this either (a) a fitted kitchen that was both supplied and installed by the same contractor, or (b) a basic fitted wardrobe consisting only of a wall-to-wall unit with a hanging rail or shelf for clothes and shelves — with no drawers, shoe racks, or decorative panelling?"
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

mcp-tool-reference.md.


Available tools

|---|---|---|

ToolAuth requiredWhat it does
check_line_itemNoneClassify any invoice line against HMRC Notice 708
create_accountNoneCreate a new VATBuild account
authenticate_accountNoneSign in and retrieve a fresh API key
check_account_statusBearer keyCheck account verification and plan
set_up_projectprojects:writeCreate a new project
assess_project_eligibilityprojects:readRun an HMRC eligibility check
list_projectsprojects:readList all projects in your account
list_line_itemsprojects:readRetrieve invoice line items for a project
route_line_itemline_items:writeClassify and persist a VAT routing decision
answer_vat_complex_questionline_items:writeResolve a pending complex-VAT confirmation

Scopes reference

When creating an API key, grant only the scopes the agent needs.

|---|---|

ScopeTools covered
*(none)*check_line_item, create_account, authenticate_account
projects:read (or any scope)check_account_status
projects:readlist_projects, list_line_items, assess_project_eligibility
projects:writeset_up_project
line_items:writeroute_line_item, answer_vat_complex_question, reclassify_companion_items, submit_invoice_data
jobs:readJob status polling
invoices:writesubmit_invoice_data (upload-only; no line-item override access)

Recommended scope sets:

|---|---|

Use caseScopes
Read-only classification*(none required)*
Read-only project accessprojects:read
Full end-to-end workflowAll five scopes

Rate limits

See gemini-integration.md — Rate limits for

the full rate-limit table. In summary:

|---|---|

Request typeLimit
All requests per authenticated user (userId or API key) or unauthenticated IP60 per minute
Write tools10 per minute
create_account / authenticate_account3 per hour per IP

HTTP 429 responses include a Retry-After header. Implement exponential back-off

when polling list_invoices after submit_invoice_data.


OAuth / PKCE alternative

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 registrationPOST /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 exchangePOST /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 bearer API key approach (Option B in Getting an API key) is simpler.


Troubleshooting

|---|---|---|

SymptomCauseFix
VATBuild tools do not appear in ClaudeConfig file not saved or Claude not fully restartedQuit and relaunch Claude; validate JSON with a linter
Tool calls return registration_requiredBearer key missing or wrong header nameConfirm the header is Authorization: Bearer vb_live_... (not X-Api-Key)
HTTP 403 on a specific toolAPI key lacks the required scopeRegenerate the key with the missing scope ticked
HTTP 429Rate limit exceededWait for Retry-After seconds
submit_invoice_data returns ASSESSMENT_NOT_CONFIRMEDProfile wizard not completedUser must visit projectUrl in the VATBuild web app and confirm their profile
Claude Desktop doesn't list VATBuild toolsConfig file syntax error or wrong pathValidate JSON with a linter; confirm the file is at the path for your OS
Cursor shows VATBuild as "disconnected"Stale config or network issueOpen Cursor Settings → MCP and click Refresh; check for JSON syntax errors in .cursor/mcp.json
Zed tools unavailableZed version doesn't support Streamable HTTP yetUpdate Zed to the latest release and check release notes for MCP HTTP support
check_line_item always returns pending_complex_answervatComplexTypeHint matches a confirm type and confirmedAnswer is nullPass confirmedAnswer with the user's answer, or omit vatComplexTypeHint and let the engine auto-detect