UNPKG

exactmcp

Version:

MCP server for Exact Online API integration

240 lines 9.64 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExactOnlineClient = void 0; const axios_1 = __importDefault(require("axios")); const dotenv = __importStar(require("dotenv")); dotenv.config(); class ExactOnlineClient { constructor(clientId, clientSecret, redirectUri) { this.clientId = clientId; this.clientSecret = clientSecret; this.redirectUri = redirectUri; this.accessToken = null; this.refreshToken = null; this.expiresAt = null; this.baseUrl = 'https://start.exactonline.nl'; this.httpClient = axios_1.default.create({ timeout: 30000, }); } getAuthorizationUrl() { const params = new URLSearchParams({ response_type: 'code', client_id: this.clientId, redirect_uri: this.redirectUri, state: 'mcp-server', }); return `${this.baseUrl}/api/oauth2/auth?${params.toString()}`; } async exchangeCodeForToken(authCode) { const tokenUrl = `${this.baseUrl}/api/oauth2/token`; const data = new URLSearchParams({ grant_type: 'authorization_code', client_id: this.clientId, client_secret: this.clientSecret, code: authCode, redirect_uri: this.redirectUri, }); try { const response = await this.httpClient.post(tokenUrl, data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); const tokenData = response.data; this.accessToken = tokenData.access_token; this.refreshToken = tokenData.refresh_token; this.expiresAt = Date.now() + (tokenData.expires_in * 1000); return { ...tokenData, expires_at: this.expiresAt, }; } catch (error) { if (axios_1.default.isAxiosError(error)) { throw new Error(`Token exchange failed: ${error.response?.data?.error_description || error.message}`); } throw error; } } async refreshAccessToken() { if (!this.refreshToken) { throw new Error('No refresh token available'); } const tokenUrl = `${this.baseUrl}/api/oauth2/token`; const data = new URLSearchParams({ grant_type: 'refresh_token', client_id: this.clientId, client_secret: this.clientSecret, refresh_token: this.refreshToken, }); try { const response = await this.httpClient.post(tokenUrl, data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); const tokenData = response.data; this.accessToken = tokenData.access_token; this.refreshToken = tokenData.refresh_token; this.expiresAt = Date.now() + (tokenData.expires_in * 1000); } catch (error) { if (axios_1.default.isAxiosError(error)) { throw new Error(`Token refresh failed: ${error.response?.data?.error_description || error.message}`); } throw error; } } async ensureValidToken() { if (!this.accessToken) { throw new Error('Not authenticated. Please authenticate first.'); } // Refresh token if it expires within 5 minutes if (this.expiresAt && (this.expiresAt - Date.now()) < 300000) { await this.refreshAccessToken(); } } async makeApiRequest(url) { await this.ensureValidToken(); try { const response = await this.httpClient.get(url, { headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Accept': 'application/json', }, }); return response.data.d?.results || response.data.d || response.data; } catch (error) { if (axios_1.default.isAxiosError(error)) { if (error.response?.status === 401) { // Try to refresh token once await this.refreshAccessToken(); const retryResponse = await this.httpClient.get(url, { headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Accept': 'application/json', }, }); return retryResponse.data.d?.results || retryResponse.data.d || retryResponse.data; } throw new Error(`API request failed: ${error.response?.data?.error_description || error.message}`); } throw error; } } isAuthenticated() { return !!this.accessToken && !!this.expiresAt && this.expiresAt > Date.now(); } async getDivisions() { const url = `${this.baseUrl}/api/v1/current/Me`; const userData = await this.makeApiRequest(url); // Get divisions from user data const divisionsUrl = `${this.baseUrl}/api/v1/current/System/Divisions`; const divisions = await this.makeApiRequest(divisionsUrl); return divisions; } async getSalesOrders(divisionCode, options = {}) { const { filter, select, top = 50, skip = 0 } = options; let url = `${this.baseUrl}/api/v1/${divisionCode}/salesorder/SalesOrders`; const queryParams = new URLSearchParams(); if (filter) queryParams.append('$filter', filter); if (select) queryParams.append('$select', select); queryParams.append('$top', top.toString()); if (skip > 0) queryParams.append('$skip', skip.toString()); if (queryParams.toString()) { url += `?${queryParams.toString()}`; } return await this.makeApiRequest(url); } async getItems(divisionCode, options = {}) { const { filter, select, top = 50, skip = 0 } = options; let url = `${this.baseUrl}/api/v1/${divisionCode}/logistics/Items`; const queryParams = new URLSearchParams(); if (filter) queryParams.append('$filter', filter); if (select) queryParams.append('$select', select); queryParams.append('$top', top.toString()); if (skip > 0) queryParams.append('$skip', skip.toString()); if (queryParams.toString()) { url += `?${queryParams.toString()}`; } return await this.makeApiRequest(url); } async getAccounts(divisionCode, options = {}) { const { filter, select, top = 50, skip = 0 } = options; let url = `${this.baseUrl}/api/v1/${divisionCode}/crm/Accounts`; const queryParams = new URLSearchParams(); if (filter) queryParams.append('$filter', filter); if (select) queryParams.append('$select', select); queryParams.append('$top', top.toString()); if (skip > 0) queryParams.append('$skip', skip.toString()); if (queryParams.toString()) { url += `?${queryParams.toString()}`; } return await this.makeApiRequest(url); } async getInvoices(divisionCode, options = {}) { const { filter, select, top = 50, skip = 0 } = options; let url = `${this.baseUrl}/api/v1/${divisionCode}/salesinvoice/SalesInvoices`; const queryParams = new URLSearchParams(); if (filter) queryParams.append('$filter', filter); if (select) queryParams.append('$select', select); queryParams.append('$top', top.toString()); if (skip > 0) queryParams.append('$skip', skip.toString()); if (queryParams.toString()) { url += `?${queryParams.toString()}`; } return await this.makeApiRequest(url); } } exports.ExactOnlineClient = ExactOnlineClient; //# sourceMappingURL=exact-client.js.map