Skip to content

Custom Connections created after 29 April 2026 fail with "Client credentials scope validation failed" due to deprecated hardcoded scopes #175

Description

@jdavidson-dotcom

I'm a US based CPA trying to use this for a single firm's books. I am a Xero Partner

Summary
The MCP server fails to authenticate against any Xero Custom Connection created on or after 29 April 2026, because CustomConnectionsXeroClient.getClientCredentialsToken() hardcodes the deprecated accounting.transactions scope (and others) that are no longer issuable to new Custom Connections under Xero's granular scopes model.
Environment

Package: @xeroapi/xero-mcp-server@latest (installed via npx)
Node.js: v22 LTS
Client: Claude Desktop on Windows 11
Xero Custom Connection created: 28 May 2026 (after the 29 April 2026 cutoff)
Xero region: US

Reproduction

Create a new Custom Connection at developer.xero.com (any connection created after 29 April 2026 will exhibit this).
Select scopes — only granular scopes (accounting.invoices, accounting.payments, accounting.banktransactions, accounting.manualjournals, etc.) are now available; the broad accounting.transactions scope is no longer offered for new connections.
Authorize the connection against the Xero Demo Company (US).
Configure the MCP server with the Client ID and Client Secret in claude_desktop_config.json.
Restart the host application and attempt any tool call (e.g., list-accounts).

Expected behavior
The MCP server obtains a client_credentials token and returns the chart of accounts.
Actual behavior
Error listing accounts: Failed to get Xero token with V2 scopes:
{"error":"invalid_scope","error_description":"Client credentials scope validation failed"}
The same error occurs when overriding via XERO_SCOPES with the granular scope names (accounting.invoices accounting.payments accounting.banktransactions accounting.manualjournals accounting.contacts accounting.settings accounting.reports.read).
Root cause
In src/clients/xero-client.ts, getClientCredentialsToken() hardcodes:
tsconst scope =
"accounting.transactions accounting.contacts accounting.settings accounting.reports.read payroll.settings payroll.employees payroll.timesheets";
Per Xero's Custom Integration FAQ:

Connections created from 29 April 2026: these use new granular scopes, which don't include journal access.

And:

You'll now use granular scopes for custom connections. ... Update your authorisation link to remove the broad scope and add the appropriate granular scopes. For example you might replace accounting.transactions with accounting.invoices.

The hardcoded accounting.transactions scope is no longer valid for newly-created Custom Connections. The V1-to-V2 fallback in the server does not appear to translate to the new granular scope names, so the token request fails outright.
Impact
Every new user who follows the README setup instructions after 29 April 2026 will hit this error immediately on first tool call. The server is effectively unusable for new Custom Connections without source modification.
Suggested fix
Replace the hardcoded broad scopes with the granular equivalents:
tsconst scope =
"accounting.invoices accounting.payments accounting.banktransactions accounting.manualjournals accounting.contacts accounting.settings accounting.reports.read";
(Or accounting.reports.aged.read accounting.reports.balancesheet.read accounting.reports.profitandloss.read accounting.reports.trialbalance.read etc., since accounting.reports.read is also marked deprecated in the granular scopes list.)
Drop the payroll scopes from the default — they're region-specific (NZ/UK only) and shouldn't be required for users who only need accounting data.
Confirm the XERO_SCOPES environment variable override actually reaches the client_credentials token request — currently it appears to be ignored or filtered for client_credentials grant.
Workarounds attempted

Setting XERO_SCOPES env var with various combinations of granular scope names — all fail with the same error
Granting all available scopes (read and write) on the Custom Connection — does not change behavior
Re-authorizing the connection after scope changes — does not change behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions