Skip to main content
Glama

Xero MCP Server

Official
MIT License
160
60
  • Apple
  • Linux
import { Organisation, IXeroClientConfig, XeroClient, TokenSet, } from "xero-node"; import dotenv from "dotenv"; import axios, { AxiosError } from "axios"; import { ensureError } from "../helpers/ensure-error.js"; dotenv.config(); const client_id = process.env.XERO_CLIENT_ID; const client_secret = process.env.XERO_CLIENT_SECRET; const bearer_token = process.env.XERO_CLIENT_BEARER_TOKEN; const grant_type = "client_credentials"; if (!bearer_token && (!client_id || !client_secret)) { throw Error("Environment Variables not set - please check your .env file"); } abstract class MCPXeroClient extends XeroClient { public tenantId: string; private shortCode: string; protected constructor(config?: IXeroClientConfig) { super(config); this.tenantId = ""; this.shortCode = ""; } public abstract authenticate(): Promise<void>; // eslint-disable-next-line @typescript-eslint/no-explicit-any override async updateTenants(fullOrgDetails?: boolean): Promise<any[]> { await super.updateTenants(fullOrgDetails); if (this.tenants && this.tenants.length > 0) { this.tenantId = this.tenants[0].tenantId; } return this.tenants; } private async getOrganisation(): Promise<Organisation> { await this.authenticate(); const organisationResponse = await this.accountingApi.getOrganisations( this.tenantId || "", ); const organisation = organisationResponse.body.organisations?.[0]; if (!organisation) { throw new Error("Failed to retrieve organisation"); } return organisation; } public async getShortCode(): Promise<string | undefined> { if (!this.shortCode) { try { const organisation = await this.getOrganisation(); this.shortCode = organisation.shortCode ?? ""; } catch (error: unknown) { const err = ensureError(error); throw new Error( `Failed to get Organisation short code: ${err.message}`, ); } } return this.shortCode; } } class CustomConnectionsXeroClient extends MCPXeroClient { private readonly clientId: string; private readonly clientSecret: string; constructor(config: { clientId: string; clientSecret: string; grantType: string; }) { super(config); this.clientId = config.clientId; this.clientSecret = config.clientSecret; } public async getClientCredentialsToken(): Promise<TokenSet> { const scope = "accounting.transactions accounting.contacts accounting.settings accounting.reports.read payroll.employees.read"; const credentials = Buffer.from( `${this.clientId}:${this.clientSecret}`, ).toString("base64"); try { const response = await axios.post( "https://identity.xero.com/connect/token", `grant_type=client_credentials&scope=${encodeURIComponent(scope)}`, { headers: { Authorization: `Basic ${credentials}`, "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, }, ); // Get the tenant ID from the connections endpoint const token = response.data.access_token; const connectionsResponse = await axios.get( "https://api.xero.com/connections", { headers: { Authorization: `Bearer ${token}`, Accept: "application/json", }, }, ); if (connectionsResponse.data && connectionsResponse.data.length > 0) { this.tenantId = connectionsResponse.data[0].tenantId; } return response.data; } catch (error) { const axiosError = error as AxiosError; throw new Error( `Failed to get Xero token: ${axiosError.response?.data || axiosError.message}`, ); } } public async authenticate() { const tokenResponse = await this.getClientCredentialsToken(); this.setTokenSet({ access_token: tokenResponse.access_token, expires_in: tokenResponse.expires_in, token_type: tokenResponse.token_type, }); } } class BearerTokenXeroClient extends MCPXeroClient { private readonly bearerToken: string; constructor(config: { bearerToken: string }) { super(); this.bearerToken = config.bearerToken; } async authenticate(): Promise<void> { this.setTokenSet({ access_token: this.bearerToken, }); await this.updateTenants(); } } export const xeroClient = bearer_token ? new BearerTokenXeroClient({ bearerToken: bearer_token, }) : new CustomConnectionsXeroClient({ clientId: client_id!, clientSecret: client_secret!, grantType: grant_type, });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/XeroAPI/xero-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server