xero-connect
Sets up or reconnects the Xero OAuth 2.0 MCP integration. Use when connecting Xero for the first time, re-authenticating after token expiry, troubleshooting Xero MCP errors, or adding Xero to a new pr
SKILL.mdskills/xero-connect/SKILL.md
GitHub/xero-connect
Connect or reconnect the Xero accounting API via OAuth 2.0 MCP server.
When to Use
- Setting up Xero on a new project
- Re-authenticating after token expiry or "Failed to get Xero token" errors
- Troubleshooting Xero MCP connection issues
- Migrating from Custom Connections to OAuth 2.0
Key Constraints
- Xero is READ ONLY — never write to Xero under any circumstances
- Custom Connections are NOT available in Canada — must use OAuth 2.0 auth code flow
- Uses the
wspringer/xero-mcp-serverfork which supports browser-based OAuth with auto token refresh - Tokens are stored at
~/.xero-mcp-tokens.json(shared across all projects) - Access tokens expire after 30 minutes; refresh tokens are single-use (each refresh returns a new one)
Workflow
Step 1: Detect Current State
Check what already exists:
- Look for
xeroentry in the project's.mcp.json - Check if
xero_client_idandxero_client_secretexist in Vault (via$OUTWORKOS_ROOT/scripts/get-secret.sh) - Check if
~/.xero-mcp-tokens.jsonexists and contains valid tokens - Check if
scripts/xero-auth.shexists
Report findings and determine which steps to skip.
Step 2: Xero Developer App Setup
If no credentials exist, guide the user through app creation:
- Direct user to: https://developer.xero.com/app/manage
- Click "New app"
- Settings:
- App name: Any name (e.g., "OutworkOS")
- Integration type: Web app
- Company or application URL: Any URL
- Redirect URI:
http://localhost:8749/callback
- After creation, ask user for the Client ID and Client Secret
If credentials already exist, ask if they want to use the existing ones or enter new ones.
Step 3: Store Credentials in Vault
Store the Xero credentials in Vault:
"$OUTWORKOS_ROOT/scripts/set-secret.sh" xero_client_id "<client-id>" "Xero OAuth client ID (READ ONLY)"
"$OUTWORKOS_ROOT/scripts/set-secret.sh" xero_client_secret "<client-secret>" "Xero OAuth client secret (READ ONLY)"
Step 4: Configure .mcp.json
Add or update the xero entry in .mcp.json:
"xero": {
"command": "npx",
"args": ["-y", "github:wspringer/xero-mcp-server"],
"env": {
"XERO_CLIENT_ID": "<client-id>",
"XERO_CLIENT_SECRET": "<client-secret>",
"XERO_USE_BROWSER_AUTH": "true"
}
}
Critical: Must use github:wspringer/xero-mcp-server (NOT @xeroapi/xero-mcp-server@latest). The official package does not support OAuth 2.0 browser auth. The XERO_USE_BROWSER_AUTH env var activates the auth code flow in the fork.
Step 5: Ensure .gitignore Protection
Before committing anything, verify .gitignore includes:
.mcp.json
If no .gitignore exists, create one. If it exists but is missing these entries, add them.
Step 6: Deploy Auth Script
Check if scripts/xero-auth.sh exists in the project. If not, copy it from the template at ${CLAUDE_SKILL_DIR}/scripts/xero-auth.sh.
The auth script:
- Reads credentials from Vault via
get-secret.sh - Starts a local callback server on port 8749
- Opens browser for Xero login with PKCE
- Exchanges auth code for tokens
- Saves tokens to
~/.xero-mcp-tokens.json(mode 0600) - Supports
--force(re-auth) and--check(test only) flags
Make executable: chmod +x scripts/xero-auth.sh
Step 7: Authenticate
Run the auth script:
./scripts/xero-auth.sh
This opens a browser. The user logs into Xero and grants access. The script captures the callback, exchanges the code for tokens, and saves them.
If re-authenticating: ./scripts/xero-auth.sh --force
Important: The MCP server subprocess cannot reliably open a browser on its own. The standalone auth script handles initial authentication. After that, the MCP server uses saved tokens and refreshes them automatically.
Step 8: Restart and Verify
- Tell the user to restart Claude Code (so the MCP server reloads with new config)
- After restart, use
ToolSearchto findxero organisation - Call
mcp__xero__list-organisation-details - If successful, report the org name, currency, and country
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "Failed to get Xero token: [object Object]" | MCP server using Custom Connections flow (not browser auth) | Verify .mcp.json has XERO_USE_BROWSER_AUTH: "true" and uses github:wspringer/xero-mcp-server. Restart Claude Code. |
| "Using custom connections authentication" in logs | XERO_USE_BROWSER_AUTH env var not reaching the server |
Check .mcp.json env block. The var must be inside the env object. |
| Token file exists but API returns 401 | Refresh token expired (unused for 60 days) | Run ./scripts/xero-auth.sh --force |
| "No such tool: mcp__xero__*" | Xero MCP server failed to start | Check npx -y github:wspringer/xero-mcp-server runs manually. May need npm cache clean --force. |
| Port 8749 in use | Another process on the callback port | lsof -i :8749 to identify and kill it |
| "Custom Connections not available" | Xero org is in Canada (or other unsupported region) | This is why we use OAuth 2.0 — Custom Connections only work in AU/NZ/UK/US |
Architecture Notes
- Token storage:
~/.xero-mcp-tokens.json— shared across all projects, contains access_token, refresh_token, expires_at - Token refresh: The wspringer fork auto-refreshes tokens when they're within 5 minutes of expiry
- Callback port: 8749 (hardcoded in the fork) — must match the redirect URI in the Xero app
- Scopes requested:
openid profile email offline_access accounting.transactions.read accounting.contacts.read accounting.reports.read accounting.journals.read accounting.settings.read payroll.employees.read payroll.timesheets.read - PKCE: The auth flow uses S256 code challenge for security