VATBuild's MCP server is directly compatible with the ChatGPT desktop app, ChatGPT
in-browser connectors, and custom GPT Actions. This guide covers all three entry
points, the OAuth 2.1 PKCE flow that ChatGPT uses for user-delegated auth, and a
recommended agent workflow.
Endpoint: POST https://vatbuild.com/mcp
Protocol: Streamable HTTP, JSON-RPC 2.0, stateless
Auth: Authorization: Bearer <api-key> (for Desktop/API) or OAuth 2.1 with PKCE (for Connectors)
3. ChatGPT in-browser connector (Tools panel)
7. Rate limits
8. OAuth 2.1 PKCE — user-delegated auth
Option A — OAuth 2.1 with PKCE (recommended):
VATBuild supports OAuth 2.1 with PKCE. ChatGPT connectors and Custom GPT Actions that
implement RFC 9728 auto-discover the auth endpoints and drive the PKCE flow per user —
no static key required. See OAuth 2.1 PKCE — user-delegated auth
for discovery URLs, authorization server metadata, and the four-step PKCE flow.
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).
create_account — VATBuild sends a confirmation email. **No API keyis returned and no account is created until the user clicks the link.** Once confirmed,
call authenticate_account to begin the device-code flow.
authenticate_account directly with your email address.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: ChatGPT 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.
The ChatGPT desktop app (macOS and Windows) supports MCP servers through a
shared TOML configuration file used by the ChatGPT desktop app, Codex CLI, and
IDE extension. MCP support for the ChatGPT desktop app is documented on
platform.openai.com — the exact URL may
change as OpenAI continues to roll out MCP support; if a direct link is stale,
search "MCP" on the OpenAI platform docs or use the in-app UI setup described
below, which is always authoritative.
> Note: The ChatGPT desktop app MCP feature requires a recent version of
> the app and is rolling out progressively. If you do not see **Settings → MCP
> servers**, update to the latest release.
1. Open Settings in the ChatGPT desktop app, then select MCP servers.
2. Select Add server.
3. Enter a name (e.g. vatbuild), choose Streamable HTTP, and enter the
URL: https://vatbuild.com/mcp
4. Save the server, then select Restart.
In the composer, type /mcp to view connected servers and confirm VATBuild is
listed.
The config file is the same path on both macOS and Windows:
~/.codex/config.toml
Add a [mcp_servers.vatbuild] block:
[mcp_servers.vatbuild]
url = "https://vatbuild.com/mcp"
[mcp_servers.vatbuild.http_headers]
Authorization = "Bearer vb_live_YOUR_API_KEY_HERE"
Alternatively, keep the key out of the file by sourcing it from an environment
variable:
[mcp_servers.vatbuild]
url = "https://vatbuild.com/mcp"
bearer_token_env_var = "VATBUILD_API_KEY"
Set VATBUILD_API_KEY in your shell profile (~/.zshrc, ~/.bashrc, or the
Windows system environment variables), then restart ChatGPT.
> Config file vs. UI: ~/.codex/config.toml is the shared configuration
> file for the ChatGPT desktop app, Codex CLI, and IDE extension. If you have
> also added the server via Settings → MCP servers in the ChatGPT UI, the
> UI setting takes precedence. When in doubt, use the UI setup — it is always
> the authoritative source.
Quit and restart ChatGPT after saving config.toml. On next launch, VATBuild
tools appear 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.
The desktop app sends requests server-to-server (no Origin header), which VATBuild
allows by design. No CORS configuration is needed.
check_line_item is a public tool and requires no key — the ChatGPT connector can call
it immediately after initialize without any credentials. Include a Bearer key in the
config 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.
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:writeChatGPT Plus, Team, and Enterprise users can connect MCP servers from the ChatGPT
web interface without installing the desktop app.
1. Open chatgpt.com and start a new chat.
2. Click the Tools icon (grid/wrench icon) near the message input.
3. Select Connect more tools → Custom connector (or Add MCP server,
depending on your plan tier and rollout).
4. Enter the server URL: https://vatbuild.com/mcp
5. In the Authentication section, choose Bearer token and paste your
vb_live_... API key.
6. Click Connect. ChatGPT discovers the available tools automatically.
> Note: In-browser connectors persist per account but not per conversation. If
> VATBuild tools are missing in a new chat, re-open the Tools panel and re-enable
> the connector for the session.
If you are building a Custom GPT (GPT Builder), you can connect VATBuild as an
Action using the OpenAPI specification.
1. In GPT Builder, go to Configure → Actions
and click Add action.
2. Set the Authentication type to Bearer token and enter your vb_live_...
key, or choose OAuth to use user-delegated auth (see
OAuth 2.1 PKCE below).
3. Import the VATBuild OpenAPI specification from:
```
https://vatbuild.com/docs/openapi.yaml
```
This exposes the REST companion endpoint (POST /api/v1/check-line-item) and
the project/invoice management routes. For the full MCP tool surface, the
Streamable HTTP endpoint (https://vatbuild.com/mcp) is the primary integration
point — use the desktop app or in-browser connector rather than GPT Actions for
multi-tool MCP workflows.
> Tip: Custom GPT Actions work best for focused, single-purpose workflows (e.g.
> "Classify this line item"). For the full VATBuild agent workflow across multiple
> tools, the desktop app MCP connector is a better fit.
The recommended end-to-end flow for a new VATBuild user via a ChatGPT 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.
→ Requires the `invoices:write` scope (or `line_items:write`). See
[Scopes reference](#scopes-reference) for details.
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" }
Valid supplyType values:
"materials" — goods only, no installation by the same party
"labour" — construction labour (time and skill, no goods)
"subcontractor" — subcontracted construction works
"supply_and_install" — goods supplied and installed by the same contractor
"installation_service" — installation only (goods supplied separately)
"professional_services" — architect, engineer, QS, planning consultant, etc.
null — unknown; engine infers from line text heuristics
See mcp-tool-reference.md → supplyType values for the full reference table.
> **Breaking change (v2 → v3) — esmStatus rename:**
> If you pass `item.esmStatus`, use the new values:
> - Old `"supply_and_install"` → **`"qualifying_esm"`**
> - Old `"none"` → **`null`** (omit the field or pass `null`)
> Sending the old strings is treated as `null`, which means ESM items will
> be silently misclassified and will not receive ESM zero-rating.
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.
For full details on handling pending_complex_answer items, see
When creating an API key, grant only the scopes the agent needs.
| Scope | MCP tools covered | REST endpoints |
|---|---|---|
| *(none)* | check_line_item, create_account, authenticate_account | GET /api/v1/check-line-item |
projects:read | check_account_status, list_projects, list_line_items, list_invoices, assess_project_eligibility | GET /api/v1/projects, GET /api/v1/line-items |
projects:write | set_up_project | POST /api/v1/projects |
line_items:write | route_line_item, answer_vat_complex_question, reclassify_companion_items, submit_invoice_data | — |
jobs:read | — | GET /api/v1/jobs/{jobId} |
invoices:write | submit_invoice_data (upload-only) | POST /api/v1/invoices, POST /api/v1/invoices/lock |
Recommended scope sets:
| Use case | Scopes |
|---|---|
| Read-only analysis (classify only) | *(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 authenticated user (userId or API key) or unauthenticated IP | 60 per minute |
Write tools (route_line_item, answer_vat_complex_question, reclassify_companion_items, submit_invoice_data) | 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.
ChatGPT is the MCP client that most commonly uses OAuth rather than a static API key,
because GPT Actions and the in-browser connector can drive a full browser-based
auth flow for each end-user. This is the recommended approach for multi-tenant GPTs
where each end-user holds their own VATBuild account.
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.
Authorization server metadata (key fields):
{
"issuer": "https://vatbuild.com",
"authorization_endpoint": "https://vatbuild.com/oauth/authorize",
"token_endpoint": "https://vatbuild.com/oauth/token",
"registration_endpoint": "https://vatbuild.com/oauth/register",
"scopes_supported": ["projects:read", "projects:write", "line_items:write", "jobs:read", "invoices:write"],
"code_challenge_methods_supported": ["S256"],
"pkce_required": true,
"grant_types_supported": ["authorization_code", "refresh_token"]
}
PKCE flow (four steps):
1. Dynamic registration — POST /oauth/register with your client metadata
(redirect_uris, client_name). Receive client_id. In GPT Builder, enter
this client_id in the OAuth configuration panel.
2. Authorization — ChatGPT redirects the user to:
```
https://vatbuild.com/oauth/authorize
?client_id=<your_client_id>
&response_type=code
&scope=projects:read projects:write line_items:write
&redirect_uri=<your_redirect_uri>
&code_challenge=<S256_challenge>
&code_challenge_method=S256
&state=<csrf_state>
```
The user logs in to VATBuild (or creates an account) and grants consent.
3. Token exchange — POST /oauth/token with the auth code and code_verifier.
Receive access_token and refresh_token.
4. MCP calls — ChatGPT uses Authorization: Bearer <access_token> on all /mcp
requests on behalf of the user.
Configuring OAuth in GPT Builder:
| Field | Value |
|---|---|
| Authentication type | OAuth |
| Grant type | Authorization code with PKCE |
| Authorization URL | https://vatbuild.com/oauth/authorize |
| Token URL | https://vatbuild.com/oauth/token |
| Scope | projects:read projects:write line_items:write jobs:read invoices:write |
| Token exchange method | Default (POST body) |
> 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 single-tenant agents where one service account is appropriate, the static API key
approach (Bearer token in headers) is simpler and equally secure.
| Symptom | Cause | Fix |
|---|---|---|
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 429 on tool calls | Rate limit exceeded | Wait for Retry-After seconds; reduce polling frequency |
| HTTP 403 on a specific tool | API key lacks the required scope | Regenerate the key with the missing scope ticked |
| OAuth redirect returns 404 | MCP_OAUTH_ENABLED not set | Contact the VATBuild operator to enable the OAuth endpoints |
submit_invoice_data returns ASSESSMENT_NOT_CONFIRMED | Profile wizard not completed | User must visit projectUrl in the VATBuild web app and confirm their profile |
| Tools panel shows "Connection failed" | ChatGPT cannot reach the MCP endpoint | Verify the URL is exactly https://vatbuild.com/mcp (no trailing slash) |
| GPT Action always returns an error schema | OpenAPI import issue | Re-import from https://vatbuild.com/docs/openapi.yaml and check the schema validates |
| Connector does not appear after setup | ChatGPT session not refreshed | Reload the ChatGPT page and start a new conversation |
| OAuth authorisation page returns 404 | OAuth not enabled on the VATBuild deployment | Contact the VATBuild operator to enable MCP_OAUTH_ENABLED=true |