xero-mcp
Version:
A Model Context Protocol server allows Clients to interact with Xero
71 lines (70 loc) • 2.83 kB
JavaScript
import { XeroClient } from "xero-node";
import "dotenv/config";
import { Auditor } from "./Auditor.js";
const client_id = process.env.XERO_CLIENT_ID;
const client_secret = process.env.XERO_CLIENT_SECRET;
const redirectUrl = process.env.XERO_REDIRECT_URI;
const scopes = "offline_access openid profile accounting.settings accounting.contacts accounting.invoices accounting.banktransactions accounting.payments.read accounting.reports.balancesheet.read";
if (!client_id || !client_secret || !redirectUrl) {
throw Error("Environment Variables not all set - please add the required environment variables to your config file.");
}
class XeroApiClient {
xeroClient;
_activeTenantId;
_refreshTimer;
static INACTIVITY_MS = 30 * 60 * 1000; // 30 minutes
static REFRESH_BUFFER_S = 2 * 60; // 2 minutes
constructor(config) {
this.xeroClient = new XeroClient({
clientId: config.clientId,
clientSecret: config.clientSecret,
redirectUris: [config.redirectUrl],
scopes: config.scopes,
});
}
isAuthenticated() {
return this.xeroClient.readTokenSet() ? true : false;
}
activeTenantId() {
return this._activeTenantId;
}
setActiveTenantId(tenantId) {
this._activeTenantId = tenantId;
}
scheduleTokenRefresh(tokenSet) {
if (this._refreshTimer)
clearTimeout(this._refreshTimer);
if (!tokenSet.expires_at)
return;
const now = Math.floor(Date.now() / 1000);
if (tokenSet.expires_at <= now)
return; // token already expired
let delaySeconds = tokenSet.expires_at - now;
if (delaySeconds > XeroApiClient.REFRESH_BUFFER_S) {
delaySeconds -= XeroApiClient.REFRESH_BUFFER_S; // refresh a bit before expiry
}
this._refreshTimer = setTimeout(async () => {
if (Date.now() - Auditor.lastRecordTime() >
XeroApiClient.INACTIVITY_MS) {
console.error(`No tool calls in the last ${XeroApiClient.INACTIVITY_MS / 60000} minutes, stop token refresh`);
return;
}
console.error("Refreshing Xero token...");
try {
const newTokenSet = await this.xeroClient.refreshToken();
this.xeroClient.setTokenSet(newTokenSet);
console.error("Xero token refreshed successfully");
this.scheduleTokenRefresh(newTokenSet);
}
catch (error) {
console.error("Error refreshing Xero token: ", error);
}
}, delaySeconds * 1000).unref();
}
}
export const XeroClientSession = new XeroApiClient({
clientId: client_id,
clientSecret: client_secret,
redirectUrl: redirectUrl,
scopes: scopes.split(" "),
});