Skip to main content
Glama
JoshAntBrown

xero-mcp-auth

by JoshAntBrown
README.md
# xero-mcp-auth

Run the [official Xero MCP server](https://github.com/XeroAPI/xero-mcp-server) from Claude Code, Claude Desktop, Cowork, Cursor, or any other MCP client, **without paying for a Xero Custom Connection.**

The official server supports two ways to authenticate:

- **Custom Connection.** Client ID and secret, tokens handled for you. Xero charges a monthly fee per organisation for this.
- **Bearer token.** Free, but you must supply an already-minted access token in an environment variable, and Xero access tokens expire after 30 minutes. The README says the MCP client should "execute an auth flow such as PKCE". None of them do.

`xero-mcp-auth` is that missing auth flow. It does the one-time PKCE browser sign-in against a free Xero app, stores the long-lived refresh token locally, and on every server start mints a fresh access token and hands it to the official server. It also persists the rotated refresh token, which Xero changes on every use.

Zero dependencies. Needs Node 18 or later, which you already have if you can run the official server.

## Setup

### 1. Create a free Xero app (two minutes)

1. Go to [developer.xero.com/app/manage](https://developer.xero.com/app/manage) and click **New app**.
2. Name it whatever you like. Integration type: **Mobile or desktop app**. This type uses PKCE and has no client secret, which is why it is free.
3. Company or application URL: anything, for example your GitHub profile.
4. Redirect URI: `http://localhost:8734/callback`
5. Save, then copy the **Client ID**.

Every user needs their own app. Uncertified Xero apps are limited to 25 connected organisations, so a shared client ID would stop working for everyone once that cap was hit.

### 2. Authorise

```sh
npx xero-mcp-auth auth <your-client-id>
```

A browser opens. Sign in to Xero and choose the organisation you want the MCP server to see. The refresh token is saved to a private file in your platform's config directory (see `xero-mcp-auth status` for the path).

### 3. Point your MCP client at it

**Claude Code**

```sh
claude mcp add xero -- npx -y xero-mcp-auth serve
```

**Claude Desktop and Cowork** (`claude_desktop_config.json`, via Settings → Developer → Edit Config)

```json
{
  "mcpServers": {
    "xero": {
      "command": "npx",
      "args": ["-y", "xero-mcp-auth", "serve"]
    }
  }
}
```

**Cursor, Windsurf, VS Code, Zed** use the same shape: command `npx`, args `["-y", "xero-mcp-auth", "serve"]`.

If your client cannot find `npx` (common when Node is installed through nvm or Homebrew and the client launches with a minimal PATH), install globally and use the absolute path instead:

```sh
npm install -g xero-mcp-auth
which xero-mcp-auth   # use this path as the command, with args ["serve"]
```

## Day to day

- **Access tokens last 30 minutes** from when the server starts. When Xero calls start failing with 401, restart or reconnect the MCP server in your client (in Claude Code: `/mcp`). That re-runs `serve`, which mints a new token.
- **Refresh tokens expire after 60 days unused.** Starting the server renews it. If you have been away longer than that, run `auth` again. `xero-mcp-auth status` shows how long ago it was last used.
- **Several clients on one machine** can share the credentials file. A lock around the refresh step stops two of them rotating the token at the same moment.
- **Several machines** should each run `auth` themselves. Each authorisation gets its own refresh token, and they count against the same single organisation connection, not the 25-org cap.

## Commands

```
xero-mcp-auth auth <client_id> [--port N] [--scopes "a b c"] [--profile NAME]
    One-time browser sign-in. Stores a refresh token locally.
xero-mcp-auth serve [--profile NAME]
    Mint an access token and run @xeroapi/xero-mcp-server. Point your MCP client here.
xero-mcp-auth token [--profile NAME]
    Print a fresh access token to stdout, for scripts or curl.
xero-mcp-auth status [--profile NAME]
    Show where credentials are and when they were last used.
```

## More than one Xero organisation

The official server has no organisation selector. After it gets a token it lists the organisations connected to the app and always uses the first one. So if you connect two organisations to the same Xero app, the server will silently pick one of them, and it may not be the one you meant.

Until that changes upstream, the reliable way to work with several organisations is **one Xero app and one profile per organisation**, exposed to your MCP client as separately named servers:

```sh
# a second free app, authorised while signed in to the second organisation
npx xero-mcp-auth auth <second-client-id> --profile shop

claude mcp add xero-club -- npx -y xero-mcp-auth serve --profile club
claude mcp add xero-shop -- npx -y xero-mcp-auth serve --profile shop
```

Each server carries its organisation in its name, so every tool call the model makes says which books it is touching. Profiles are just separate credentials files in the config directory; `XERO_MCP_AUTH_PROFILE` does the same job as `--profile` for clients that prefer environment variables.

## Scopes

By default `auth` requests the same scope list the official server uses, plus `offline_access` for the refresh token. These are Xero's V2 granular scopes (`accounting.invoices`, `accounting.contacts`, and so on). Apps created after March 2026 reject the older coarse scopes such as `accounting.transactions`, so do not substitute those.

To request less, pass `--scopes` to `auth`:

```sh
npx xero-mcp-auth auth <client-id> --scopes "offline_access accounting.invoices accounting.contacts"
```

## Environment variables

| Variable | Purpose |
|---|---|
| `XERO_MCP_AUTH_PROFILE` | Same as `--profile`. |
| `XERO_MCP_AUTH_CREDENTIALS` | Explicit path to a credentials file. Overrides the profile. |
| `XERO_MCP_AUTH_DIR` | Config directory. Defaults to `~/Library/Application Support/xero-mcp-auth` on macOS, `$XDG_CONFIG_HOME/xero-mcp-auth` on Linux, `%APPDATA%\xero-mcp-auth` on Windows. |

## Troubleshooting

**`invalid_grant` on start.** The stored refresh token is dead. Either it went 60 days unused, or a rotated token failed to save (a full disk, a read-only config directory). Run `auth` again.

**`invalid_scope` during auth.** Your Xero app was created after March 2026 and one of the requested scopes is a legacy one. Use the default list or the V2 names.

**Redirect URI mismatch.** The URI registered on the Xero app must be exactly `http://localhost:8734/callback`. If port 8734 is taken on your machine, pass `--port` to `auth` and register the matching URI.

**Nothing on stdout, client says the server died.** Run `npx xero-mcp-auth serve` in a terminal. The wrapper logs to stderr, so the real error will be visible there.

## Why not a pull request to the official server?

It would be the better home, and one may follow. But a mode that runs a browser flow and writes credentials to disk is a bigger ask of a vendor-maintained repo than a ten-line env-var addition, and in the meantime people are paying for a Custom Connection they do not need.

## Licence

MIT.