@energio/holded-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1040 -0
- package/dist/constants.d.ts +60 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +72 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/accounting/account-balances.d.ts +19 -0
- package/dist/schemas/accounting/account-balances.d.ts.map +1 -0
- package/dist/schemas/accounting/account-balances.js +28 -0
- package/dist/schemas/accounting/account-balances.js.map +1 -0
- package/dist/schemas/accounting/accounts.d.ts +49 -0
- package/dist/schemas/accounting/accounts.d.ts.map +1 -0
- package/dist/schemas/accounting/accounts.js +50 -0
- package/dist/schemas/accounting/accounts.js.map +1 -0
- package/dist/schemas/accounting/daily-ledger.d.ts +45 -0
- package/dist/schemas/accounting/daily-ledger.d.ts.map +1 -0
- package/dist/schemas/accounting/daily-ledger.js +57 -0
- package/dist/schemas/accounting/daily-ledger.js.map +1 -0
- package/dist/schemas/common.d.ts +118 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +126 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/crm/bookings.d.ts +108 -0
- package/dist/schemas/crm/bookings.d.ts.map +1 -0
- package/dist/schemas/crm/bookings.js +96 -0
- package/dist/schemas/crm/bookings.js.map +1 -0
- package/dist/schemas/crm/events.d.ts +57 -0
- package/dist/schemas/crm/events.d.ts.map +1 -0
- package/dist/schemas/crm/events.js +53 -0
- package/dist/schemas/crm/events.js.map +1 -0
- package/dist/schemas/crm/funnels.d.ts +60 -0
- package/dist/schemas/crm/funnels.d.ts.map +1 -0
- package/dist/schemas/crm/funnels.js +48 -0
- package/dist/schemas/crm/funnels.js.map +1 -0
- package/dist/schemas/crm/leads.d.ts +146 -0
- package/dist/schemas/crm/leads.d.ts.map +1 -0
- package/dist/schemas/crm/leads.js +129 -0
- package/dist/schemas/crm/leads.js.map +1 -0
- package/dist/schemas/invoicing/contacts.d.ts +352 -0
- package/dist/schemas/invoicing/contacts.d.ts.map +1 -0
- package/dist/schemas/invoicing/contacts.js +187 -0
- package/dist/schemas/invoicing/contacts.js.map +1 -0
- package/dist/schemas/invoicing/documents.d.ts +424 -0
- package/dist/schemas/invoicing/documents.d.ts.map +1 -0
- package/dist/schemas/invoicing/documents.js +217 -0
- package/dist/schemas/invoicing/documents.js.map +1 -0
- package/dist/schemas/invoicing/expenses-accounts.d.ts +47 -0
- package/dist/schemas/invoicing/expenses-accounts.d.ts.map +1 -0
- package/dist/schemas/invoicing/expenses-accounts.js +43 -0
- package/dist/schemas/invoicing/expenses-accounts.js.map +1 -0
- package/dist/schemas/invoicing/numbering-series.d.ts +94 -0
- package/dist/schemas/invoicing/numbering-series.d.ts.map +1 -0
- package/dist/schemas/invoicing/numbering-series.js +43 -0
- package/dist/schemas/invoicing/numbering-series.js.map +1 -0
- package/dist/schemas/invoicing/payments.d.ts +50 -0
- package/dist/schemas/invoicing/payments.d.ts.map +1 -0
- package/dist/schemas/invoicing/payments.js +46 -0
- package/dist/schemas/invoicing/payments.js.map +1 -0
- package/dist/schemas/invoicing/products.d.ts +176 -0
- package/dist/schemas/invoicing/products.d.ts.map +1 -0
- package/dist/schemas/invoicing/products.js +149 -0
- package/dist/schemas/invoicing/products.js.map +1 -0
- package/dist/schemas/invoicing/remittances.d.ts +21 -0
- package/dist/schemas/invoicing/remittances.d.ts.map +1 -0
- package/dist/schemas/invoicing/remittances.js +20 -0
- package/dist/schemas/invoicing/remittances.js.map +1 -0
- package/dist/schemas/invoicing/sales-channels.d.ts +45 -0
- package/dist/schemas/invoicing/sales-channels.d.ts.map +1 -0
- package/dist/schemas/invoicing/sales-channels.js +41 -0
- package/dist/schemas/invoicing/sales-channels.js.map +1 -0
- package/dist/schemas/invoicing/services.d.ts +55 -0
- package/dist/schemas/invoicing/services.d.ts.map +1 -0
- package/dist/schemas/invoicing/services.js +51 -0
- package/dist/schemas/invoicing/services.js.map +1 -0
- package/dist/schemas/invoicing/taxes.d.ts +12 -0
- package/dist/schemas/invoicing/taxes.d.ts.map +1 -0
- package/dist/schemas/invoicing/taxes.js +12 -0
- package/dist/schemas/invoicing/taxes.js.map +1 -0
- package/dist/schemas/invoicing/treasury.d.ts +42 -0
- package/dist/schemas/invoicing/treasury.d.ts.map +1 -0
- package/dist/schemas/invoicing/treasury.js +39 -0
- package/dist/schemas/invoicing/treasury.js.map +1 -0
- package/dist/schemas/invoicing/warehouses.d.ts +61 -0
- package/dist/schemas/invoicing/warehouses.d.ts.map +1 -0
- package/dist/schemas/invoicing/warehouses.js +43 -0
- package/dist/schemas/invoicing/warehouses.js.map +1 -0
- package/dist/schemas/projects/projects.d.ts +60 -0
- package/dist/schemas/projects/projects.d.ts.map +1 -0
- package/dist/schemas/projects/projects.js +55 -0
- package/dist/schemas/projects/projects.js.map +1 -0
- package/dist/schemas/projects/tasks.d.ts +68 -0
- package/dist/schemas/projects/tasks.d.ts.map +1 -0
- package/dist/schemas/projects/tasks.js +64 -0
- package/dist/schemas/projects/tasks.js.map +1 -0
- package/dist/schemas/projects/time-tracking.d.ts +80 -0
- package/dist/schemas/projects/time-tracking.d.ts.map +1 -0
- package/dist/schemas/projects/time-tracking.js +75 -0
- package/dist/schemas/projects/time-tracking.js.map +1 -0
- package/dist/schemas/team/employees.d.ts +135 -0
- package/dist/schemas/team/employees.d.ts.map +1 -0
- package/dist/schemas/team/employees.js +144 -0
- package/dist/schemas/team/employees.js.map +1 -0
- package/dist/schemas/team/time-tracking.d.ts +98 -0
- package/dist/schemas/team/time-tracking.d.ts.map +1 -0
- package/dist/schemas/team/time-tracking.js +89 -0
- package/dist/schemas/team/time-tracking.js.map +1 -0
- package/dist/services/api.d.ts +118 -0
- package/dist/services/api.d.ts.map +1 -0
- package/dist/services/api.js +441 -0
- package/dist/services/api.js.map +1 -0
- package/dist/tools/accounting/account-balances.d.ts +44 -0
- package/dist/tools/accounting/account-balances.d.ts.map +1 -0
- package/dist/tools/accounting/account-balances.js +240 -0
- package/dist/tools/accounting/account-balances.js.map +1 -0
- package/dist/tools/accounting/accounts.d.ts +18 -0
- package/dist/tools/accounting/accounts.d.ts.map +1 -0
- package/dist/tools/accounting/accounts.js +131 -0
- package/dist/tools/accounting/accounts.js.map +1 -0
- package/dist/tools/accounting/daily-ledger.d.ts +9 -0
- package/dist/tools/accounting/daily-ledger.d.ts.map +1 -0
- package/dist/tools/accounting/daily-ledger.js +117 -0
- package/dist/tools/accounting/daily-ledger.js.map +1 -0
- package/dist/tools/accounting/index.d.ts +9 -0
- package/dist/tools/accounting/index.d.ts.map +1 -0
- package/dist/tools/accounting/index.js +15 -0
- package/dist/tools/accounting/index.js.map +1 -0
- package/dist/tools/crm/bookings.d.ts +18 -0
- package/dist/tools/crm/bookings.d.ts.map +1 -0
- package/dist/tools/crm/bookings.js +272 -0
- package/dist/tools/crm/bookings.js.map +1 -0
- package/dist/tools/crm/events.d.ts +18 -0
- package/dist/tools/crm/events.d.ts.map +1 -0
- package/dist/tools/crm/events.js +134 -0
- package/dist/tools/crm/events.js.map +1 -0
- package/dist/tools/crm/funnels.d.ts +18 -0
- package/dist/tools/crm/funnels.d.ts.map +1 -0
- package/dist/tools/crm/funnels.js +113 -0
- package/dist/tools/crm/funnels.js.map +1 -0
- package/dist/tools/crm/index.d.ts +9 -0
- package/dist/tools/crm/index.d.ts.map +1 -0
- package/dist/tools/crm/index.js +17 -0
- package/dist/tools/crm/index.js.map +1 -0
- package/dist/tools/crm/leads.d.ts +18 -0
- package/dist/tools/crm/leads.d.ts.map +1 -0
- package/dist/tools/crm/leads.js +519 -0
- package/dist/tools/crm/leads.js.map +1 -0
- package/dist/tools/factory.d.ts +55 -0
- package/dist/tools/factory.d.ts.map +1 -0
- package/dist/tools/factory.js +145 -0
- package/dist/tools/factory.js.map +1 -0
- package/dist/tools/invoicing/contacts.d.ts +26 -0
- package/dist/tools/invoicing/contacts.d.ts.map +1 -0
- package/dist/tools/invoicing/contacts.js +358 -0
- package/dist/tools/invoicing/contacts.js.map +1 -0
- package/dist/tools/invoicing/documents.d.ts +18 -0
- package/dist/tools/invoicing/documents.d.ts.map +1 -0
- package/dist/tools/invoicing/documents.js +578 -0
- package/dist/tools/invoicing/documents.js.map +1 -0
- package/dist/tools/invoicing/expenses-accounts.d.ts +25 -0
- package/dist/tools/invoicing/expenses-accounts.d.ts.map +1 -0
- package/dist/tools/invoicing/expenses-accounts.js +111 -0
- package/dist/tools/invoicing/expenses-accounts.js.map +1 -0
- package/dist/tools/invoicing/index.d.ts +9 -0
- package/dist/tools/invoicing/index.d.ts.map +1 -0
- package/dist/tools/invoicing/index.js +33 -0
- package/dist/tools/invoicing/index.js.map +1 -0
- package/dist/tools/invoicing/numbering-series.d.ts +9 -0
- package/dist/tools/invoicing/numbering-series.d.ts.map +1 -0
- package/dist/tools/invoicing/numbering-series.js +161 -0
- package/dist/tools/invoicing/numbering-series.js.map +1 -0
- package/dist/tools/invoicing/payments.d.ts +18 -0
- package/dist/tools/invoicing/payments.d.ts.map +1 -0
- package/dist/tools/invoicing/payments.js +175 -0
- package/dist/tools/invoicing/payments.js.map +1 -0
- package/dist/tools/invoicing/products.d.ts +18 -0
- package/dist/tools/invoicing/products.d.ts.map +1 -0
- package/dist/tools/invoicing/products.js +389 -0
- package/dist/tools/invoicing/products.js.map +1 -0
- package/dist/tools/invoicing/remittances.d.ts +17 -0
- package/dist/tools/invoicing/remittances.d.ts.map +1 -0
- package/dist/tools/invoicing/remittances.js +76 -0
- package/dist/tools/invoicing/remittances.js.map +1 -0
- package/dist/tools/invoicing/sales-channels.d.ts +24 -0
- package/dist/tools/invoicing/sales-channels.d.ts.map +1 -0
- package/dist/tools/invoicing/sales-channels.js +105 -0
- package/dist/tools/invoicing/sales-channels.js.map +1 -0
- package/dist/tools/invoicing/services.d.ts +29 -0
- package/dist/tools/invoicing/services.d.ts.map +1 -0
- package/dist/tools/invoicing/services.js +124 -0
- package/dist/tools/invoicing/services.js.map +1 -0
- package/dist/tools/invoicing/taxes.d.ts +18 -0
- package/dist/tools/invoicing/taxes.d.ts.map +1 -0
- package/dist/tools/invoicing/taxes.js +58 -0
- package/dist/tools/invoicing/taxes.js.map +1 -0
- package/dist/tools/invoicing/treasury.d.ts +9 -0
- package/dist/tools/invoicing/treasury.d.ts.map +1 -0
- package/dist/tools/invoicing/treasury.js +196 -0
- package/dist/tools/invoicing/treasury.js.map +1 -0
- package/dist/tools/invoicing/warehouses.d.ts +18 -0
- package/dist/tools/invoicing/warehouses.d.ts.map +1 -0
- package/dist/tools/invoicing/warehouses.js +133 -0
- package/dist/tools/invoicing/warehouses.js.map +1 -0
- package/dist/tools/projects/index.d.ts +9 -0
- package/dist/tools/projects/index.d.ts.map +1 -0
- package/dist/tools/projects/index.js +15 -0
- package/dist/tools/projects/index.js.map +1 -0
- package/dist/tools/projects/projects.d.ts +18 -0
- package/dist/tools/projects/projects.d.ts.map +1 -0
- package/dist/tools/projects/projects.js +203 -0
- package/dist/tools/projects/projects.js.map +1 -0
- package/dist/tools/projects/tasks.d.ts +18 -0
- package/dist/tools/projects/tasks.d.ts.map +1 -0
- package/dist/tools/projects/tasks.js +154 -0
- package/dist/tools/projects/tasks.js.map +1 -0
- package/dist/tools/projects/time-tracking.d.ts +14 -0
- package/dist/tools/projects/time-tracking.d.ts.map +1 -0
- package/dist/tools/projects/time-tracking.js +291 -0
- package/dist/tools/projects/time-tracking.js.map +1 -0
- package/dist/tools/team/employees.d.ts +18 -0
- package/dist/tools/team/employees.d.ts.map +1 -0
- package/dist/tools/team/employees.js +149 -0
- package/dist/tools/team/employees.js.map +1 -0
- package/dist/tools/team/index.d.ts +9 -0
- package/dist/tools/team/index.d.ts.map +1 -0
- package/dist/tools/team/index.js +13 -0
- package/dist/tools/team/index.js.map +1 -0
- package/dist/tools/team/time-tracking.d.ts +18 -0
- package/dist/tools/team/time-tracking.d.ts.map +1 -0
- package/dist/tools/team/time-tracking.js +398 -0
- package/dist/tools/team/time-tracking.js.map +1 -0
- package/dist/tools/utilities.d.ts +45 -0
- package/dist/tools/utilities.d.ts.map +1 -0
- package/dist/tools/utilities.js +55 -0
- package/dist/tools/utilities.js.map +1 -0
- package/dist/types.d.ts +640 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holded API client with authentication, error handling, and retry logic
|
|
3
|
+
*/
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import FormDataLib from "form-data";
|
|
6
|
+
import { API_ENDPOINTS } from "../constants.js";
|
|
7
|
+
let apiKey;
|
|
8
|
+
let axiosInstance;
|
|
9
|
+
/**
|
|
10
|
+
* Debug mode - set HOLDED_DEBUG=true to enable request logging
|
|
11
|
+
*/
|
|
12
|
+
const DEBUG = process.env.HOLDED_DEBUG === 'true';
|
|
13
|
+
/**
|
|
14
|
+
* Retry configuration
|
|
15
|
+
*/
|
|
16
|
+
const RETRY_CONFIG = {
|
|
17
|
+
/** Maximum number of retry attempts */
|
|
18
|
+
maxRetries: 3,
|
|
19
|
+
/** Initial delay in milliseconds before first retry */
|
|
20
|
+
initialDelayMs: 1000,
|
|
21
|
+
/** Maximum delay in milliseconds between retries */
|
|
22
|
+
maxDelayMs: 10000,
|
|
23
|
+
/** Multiplier for exponential backoff */
|
|
24
|
+
backoffMultiplier: 2,
|
|
25
|
+
/** HTTP status codes that should trigger a retry */
|
|
26
|
+
retryableStatuses: [429, 500, 502, 503, 504],
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Rate limiting configuration
|
|
30
|
+
* Set HOLDED_RATE_LIMIT_PER_SECOND to control request rate (default: 10 req/sec)
|
|
31
|
+
*/
|
|
32
|
+
const RATE_LIMIT_CONFIG = {
|
|
33
|
+
/** Maximum requests per second (configurable via env) */
|
|
34
|
+
requestsPerSecond: parseInt(process.env.HOLDED_RATE_LIMIT_PER_SECOND || '10', 10),
|
|
35
|
+
/** Enabled by default, set HOLDED_DISABLE_RATE_LIMIT=true to disable */
|
|
36
|
+
enabled: process.env.HOLDED_DISABLE_RATE_LIMIT !== 'true',
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Token bucket for rate limiting
|
|
40
|
+
* Exported for testing purposes
|
|
41
|
+
*/
|
|
42
|
+
export class TokenBucket {
|
|
43
|
+
tokens;
|
|
44
|
+
lastRefill;
|
|
45
|
+
capacity;
|
|
46
|
+
refillRate; // tokens per millisecond
|
|
47
|
+
constructor(requestsPerSecond) {
|
|
48
|
+
this.capacity = requestsPerSecond;
|
|
49
|
+
this.tokens = requestsPerSecond;
|
|
50
|
+
this.lastRefill = Date.now();
|
|
51
|
+
this.refillRate = requestsPerSecond / 1000; // tokens per ms
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Refill tokens based on time elapsed
|
|
55
|
+
*/
|
|
56
|
+
refill() {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const timePassed = now - this.lastRefill;
|
|
59
|
+
const tokensToAdd = timePassed * this.refillRate;
|
|
60
|
+
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
|
|
61
|
+
this.lastRefill = now;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Try to consume a token, wait if not available
|
|
65
|
+
*/
|
|
66
|
+
async consume() {
|
|
67
|
+
this.refill();
|
|
68
|
+
if (this.tokens >= 1) {
|
|
69
|
+
this.tokens -= 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Calculate wait time for next token
|
|
73
|
+
const tokensNeeded = 1 - this.tokens;
|
|
74
|
+
const waitMs = Math.ceil(tokensNeeded / this.refillRate);
|
|
75
|
+
if (DEBUG) {
|
|
76
|
+
console.error(`[Rate Limit] Waiting ${waitMs}ms for next available token`);
|
|
77
|
+
}
|
|
78
|
+
await sleep(waitMs);
|
|
79
|
+
// Refill and consume
|
|
80
|
+
this.refill();
|
|
81
|
+
this.tokens -= 1;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get current token count (for testing)
|
|
85
|
+
*/
|
|
86
|
+
getTokens() {
|
|
87
|
+
return this.tokens;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get capacity (for testing)
|
|
91
|
+
*/
|
|
92
|
+
getCapacity() {
|
|
93
|
+
return this.capacity;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Global rate limiter instance
|
|
98
|
+
*/
|
|
99
|
+
let rateLimiter = null;
|
|
100
|
+
/**
|
|
101
|
+
* Initialize rate limiter if enabled
|
|
102
|
+
*/
|
|
103
|
+
function getRateLimiter() {
|
|
104
|
+
if (!RATE_LIMIT_CONFIG.enabled) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (!rateLimiter) {
|
|
108
|
+
rateLimiter = new TokenBucket(RATE_LIMIT_CONFIG.requestsPerSecond);
|
|
109
|
+
}
|
|
110
|
+
return rateLimiter;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Sleep for a specified number of milliseconds
|
|
114
|
+
* Exported for testing purposes
|
|
115
|
+
*/
|
|
116
|
+
export function sleep(ms) {
|
|
117
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Calculate delay for retry with exponential backoff and jitter
|
|
121
|
+
* Exported for testing purposes
|
|
122
|
+
*/
|
|
123
|
+
export function calculateRetryDelay(attempt, retryAfterHeader) {
|
|
124
|
+
// If server specifies Retry-After, use that
|
|
125
|
+
if (retryAfterHeader) {
|
|
126
|
+
const retryAfterSeconds = parseInt(retryAfterHeader, 10);
|
|
127
|
+
if (!isNaN(retryAfterSeconds)) {
|
|
128
|
+
return retryAfterSeconds * 1000;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Exponential backoff with jitter
|
|
132
|
+
const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);
|
|
133
|
+
const jitter = Math.random() * 0.3 * exponentialDelay; // Add up to 30% jitter
|
|
134
|
+
const delay = Math.min(exponentialDelay + jitter, RETRY_CONFIG.maxDelayMs);
|
|
135
|
+
return Math.round(delay);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if an error is retryable based on status code
|
|
139
|
+
* Exported for testing purposes
|
|
140
|
+
*/
|
|
141
|
+
export function isRetryableError(error) {
|
|
142
|
+
if (axios.isAxiosError(error)) {
|
|
143
|
+
const status = error.response?.status;
|
|
144
|
+
if (status && RETRY_CONFIG.retryableStatuses.includes(status)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
// Also retry on network errors
|
|
148
|
+
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get Retry-After header value if present
|
|
156
|
+
* Exported for testing purposes
|
|
157
|
+
*/
|
|
158
|
+
export function getRetryAfterHeader(error) {
|
|
159
|
+
return error.response?.headers?.["retry-after"];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Initialize the API client with the Holded API key
|
|
163
|
+
*/
|
|
164
|
+
export function initializeApi(key) {
|
|
165
|
+
apiKey = key;
|
|
166
|
+
axiosInstance = axios.create({
|
|
167
|
+
timeout: 30000,
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
Accept: "application/json",
|
|
171
|
+
key: apiKey,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the API key from environment or throw error
|
|
177
|
+
*/
|
|
178
|
+
export function getApiKey() {
|
|
179
|
+
if (!apiKey) {
|
|
180
|
+
const envKey = process.env.HOLDED_API_KEY;
|
|
181
|
+
if (!envKey) {
|
|
182
|
+
throw new Error("HOLDED_API_KEY environment variable is required. " +
|
|
183
|
+
"Get your API key from https://app.holded.com/api");
|
|
184
|
+
}
|
|
185
|
+
initializeApi(envKey);
|
|
186
|
+
}
|
|
187
|
+
return apiKey;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get the axios instance, initializing if needed
|
|
191
|
+
*/
|
|
192
|
+
function getAxiosInstance() {
|
|
193
|
+
if (!axiosInstance) {
|
|
194
|
+
getApiKey(); // This will initialize the instance
|
|
195
|
+
}
|
|
196
|
+
return axiosInstance;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get the base URL for a specific API module
|
|
200
|
+
*/
|
|
201
|
+
function getBaseUrl(module) {
|
|
202
|
+
switch (module) {
|
|
203
|
+
case "invoicing":
|
|
204
|
+
return API_ENDPOINTS.INVOICING;
|
|
205
|
+
case "crm":
|
|
206
|
+
return API_ENDPOINTS.CRM;
|
|
207
|
+
case "projects":
|
|
208
|
+
return API_ENDPOINTS.PROJECTS;
|
|
209
|
+
case "accounting":
|
|
210
|
+
return API_ENDPOINTS.ACCOUNTING;
|
|
211
|
+
case "team":
|
|
212
|
+
return API_ENDPOINTS.TEAM;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Make an API request to the Holded API with automatic retry for transient errors
|
|
217
|
+
*
|
|
218
|
+
* Retries automatically on:
|
|
219
|
+
* - 429 (Rate limit exceeded)
|
|
220
|
+
* - 500, 502, 503, 504 (Server errors)
|
|
221
|
+
* - Network timeout errors
|
|
222
|
+
*
|
|
223
|
+
* Uses exponential backoff with jitter between retries.
|
|
224
|
+
* Includes proactive rate limiting to prevent hitting API limits.
|
|
225
|
+
*/
|
|
226
|
+
export async function makeApiRequest(module, endpoint, method = "GET", data, params) {
|
|
227
|
+
const client = getAxiosInstance();
|
|
228
|
+
const baseUrl = getBaseUrl(module);
|
|
229
|
+
const url = `${baseUrl}/${endpoint}`;
|
|
230
|
+
const config = {
|
|
231
|
+
method,
|
|
232
|
+
url,
|
|
233
|
+
params,
|
|
234
|
+
};
|
|
235
|
+
if (data && (method === "POST" || method === "PUT")) {
|
|
236
|
+
config.data = data;
|
|
237
|
+
}
|
|
238
|
+
let lastError;
|
|
239
|
+
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
240
|
+
try {
|
|
241
|
+
// Apply rate limiting before making request
|
|
242
|
+
const limiter = getRateLimiter();
|
|
243
|
+
if (limiter) {
|
|
244
|
+
await limiter.consume();
|
|
245
|
+
}
|
|
246
|
+
const response = await client.request(config);
|
|
247
|
+
return response.data;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
lastError = error;
|
|
251
|
+
// Check if we should retry
|
|
252
|
+
if (attempt < RETRY_CONFIG.maxRetries && isRetryableError(error)) {
|
|
253
|
+
const retryAfter = axios.isAxiosError(error) ? getRetryAfterHeader(error) : undefined;
|
|
254
|
+
const delay = calculateRetryDelay(attempt, retryAfter);
|
|
255
|
+
// Log retry attempt (only in debug mode)
|
|
256
|
+
if (DEBUG) {
|
|
257
|
+
const status = axios.isAxiosError(error) ? error.response?.status : "unknown";
|
|
258
|
+
console.error(`[Holded API] Request failed with status ${status}, retrying in ${delay}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries})`);
|
|
259
|
+
}
|
|
260
|
+
await sleep(delay);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
// Not retryable or max retries exceeded
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Should not reach here, but throw last error just in case
|
|
268
|
+
throw lastError;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Make a multipart/form-data API request for file uploads with automatic retry
|
|
272
|
+
*
|
|
273
|
+
* Retries automatically on transient errors (rate limit, server errors).
|
|
274
|
+
* Includes proactive rate limiting to prevent hitting API limits.
|
|
275
|
+
*/
|
|
276
|
+
export async function makeMultipartApiRequest(module, endpoint, fileBuffer, fileName, setMain) {
|
|
277
|
+
const key = getApiKey();
|
|
278
|
+
const baseUrl = getBaseUrl(module);
|
|
279
|
+
const url = `${baseUrl}/${endpoint}`;
|
|
280
|
+
let lastError;
|
|
281
|
+
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
282
|
+
// Create fresh FormData for each attempt (can't reuse after sending)
|
|
283
|
+
const formData = new FormDataLib();
|
|
284
|
+
formData.append("file", fileBuffer, fileName);
|
|
285
|
+
if (setMain !== undefined) {
|
|
286
|
+
formData.append("setMain", String(setMain));
|
|
287
|
+
}
|
|
288
|
+
// Create a new axios instance for multipart requests
|
|
289
|
+
const client = axios.create({
|
|
290
|
+
timeout: 60000, // Longer timeout for file uploads
|
|
291
|
+
headers: {
|
|
292
|
+
key,
|
|
293
|
+
...formData.getHeaders(), // This sets Content-Type with boundary
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
// Apply rate limiting before making request
|
|
298
|
+
const limiter = getRateLimiter();
|
|
299
|
+
if (limiter) {
|
|
300
|
+
await limiter.consume();
|
|
301
|
+
}
|
|
302
|
+
const response = await client.post(url, formData);
|
|
303
|
+
return response.data;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
lastError = error;
|
|
307
|
+
// Check if we should retry
|
|
308
|
+
if (attempt < RETRY_CONFIG.maxRetries && isRetryableError(error)) {
|
|
309
|
+
const retryAfter = axios.isAxiosError(error) ? getRetryAfterHeader(error) : undefined;
|
|
310
|
+
const delay = calculateRetryDelay(attempt, retryAfter);
|
|
311
|
+
if (DEBUG) {
|
|
312
|
+
const status = axios.isAxiosError(error) ? error.response?.status : "unknown";
|
|
313
|
+
console.error(`[Holded API] Multipart request failed with status ${status}, retrying in ${delay}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries})`);
|
|
314
|
+
}
|
|
315
|
+
await sleep(delay);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
throw lastError;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Handle API errors and return user-friendly messages with actionable remediation hints
|
|
325
|
+
*
|
|
326
|
+
* Error codes per Holded API documentation:
|
|
327
|
+
* - 400: Bad request
|
|
328
|
+
* - 401: Unauthorized (invalid API key)
|
|
329
|
+
* - 403: Forbidden
|
|
330
|
+
* - 404: Resource not found
|
|
331
|
+
* - 409: Conflict
|
|
332
|
+
* - 410: Gone (e.g., booking slot no longer available)
|
|
333
|
+
* - 422: Validation failed
|
|
334
|
+
* - 429: Rate limit exceeded
|
|
335
|
+
* - 500: Server error
|
|
336
|
+
*/
|
|
337
|
+
export function handleApiError(error) {
|
|
338
|
+
if (axios.isAxiosError(error)) {
|
|
339
|
+
const axiosError = error;
|
|
340
|
+
if (axiosError.response) {
|
|
341
|
+
const status = axiosError.response.status;
|
|
342
|
+
const data = axiosError.response.data;
|
|
343
|
+
const errorMessage = data?.message || data?.error || data?.info || "";
|
|
344
|
+
const url = axiosError.config?.url || "";
|
|
345
|
+
const method = axiosError.config?.method?.toUpperCase() || "";
|
|
346
|
+
switch (status) {
|
|
347
|
+
case 400:
|
|
348
|
+
return `Error: Bad request to ${method} ${url}. ${errorMessage || "The request parameters are invalid."}\n\nRemediation:\n- Verify all required parameters are provided\n- Check parameter types match the API specification\n- Ensure date/timestamp formats are correct (Unix timestamps)\n- Review nested object structures (e.g., stock updates, addresses)`;
|
|
349
|
+
case 401:
|
|
350
|
+
return `Error: Unauthorized request to ${url}.\n\nRemediation:\n- Verify HOLDED_API_KEY environment variable is set correctly\n- Check your API key is valid at https://app.holded.com/api\n- Ensure the API key has not expired\n- Confirm you're using the correct key header format`;
|
|
351
|
+
case 403:
|
|
352
|
+
return `Error: Forbidden access to ${url}. ${errorMessage || "You don't have permission to access this resource."}\n\nRemediation:\n- Verify your API key has the necessary permissions\n- Check if your Holded account has access to this feature\n- Ensure the resource belongs to your organization\n- Contact Holded support if you believe you should have access`;
|
|
353
|
+
case 404:
|
|
354
|
+
return `Error: Resource not found at ${url}. ${errorMessage || "The requested resource does not exist."}\n\nRemediation:\n- Verify the ID is correct and exists in your Holded account\n- Use list endpoints to find valid IDs (e.g., holded_invoicing_list_contacts)\n- Check for typos in the ID parameter\n- Ensure the resource wasn't deleted\n- Confirm you're using the correct document type for documents`;
|
|
355
|
+
case 409:
|
|
356
|
+
return `Error: Conflict with ${method} ${url}. ${errorMessage || "The resource may already exist or conflicts with the current state."}\n\nRemediation:\n- Check if a resource with the same unique identifier already exists\n- Verify the resource hasn't been modified since you last read it\n- For numbering series, ensure the format doesn't conflict with existing series\n- Try using update instead of create if the resource exists`;
|
|
357
|
+
case 410:
|
|
358
|
+
// Specific error for booking slots - per Holded CRM API v1.0
|
|
359
|
+
return `Error: Resource no longer available at ${url}. ${errorMessage || "The resource has been removed or is no longer accessible."}\n\nRemediation:\n- For bookings: Use holded_crm_get_available_slots to find current available time slots\n- The time slot may have been booked by another user\n- Try selecting a different date/time\n- Refresh your list of available resources`;
|
|
360
|
+
case 422:
|
|
361
|
+
return `Error: Validation failed for ${method} ${url}. ${errorMessage || "The request data is invalid."}\n\nRemediation:\n- Review the error message for specific field validation issues\n- Check required fields are present: ${errorMessage}\n- Verify field types (strings vs numbers, especially for timestamps)\n- For accounting entries: Ensure debits equal credits\n- For stock updates: Use correct nested object structure\n- For emails: Ensure email addresses are valid\n- For dates: Use Unix timestamps (seconds since epoch) as integers`;
|
|
362
|
+
case 429:
|
|
363
|
+
return `Error: Rate limit exceeded. ${errorMessage || "Too many requests sent to the API."}\n\nRemediation:\n- Wait a few seconds before retrying\n- The request will automatically retry with exponential backoff\n- Consider reducing request frequency\n- Adjust HOLDED_RATE_LIMIT_PER_SECOND environment variable (default: 10 req/sec)\n- Batch operations when possible to reduce API calls`;
|
|
364
|
+
case 500:
|
|
365
|
+
return `Error: Holded server error at ${url}. ${errorMessage || "An internal error occurred on the Holded server."}\n\nRemediation:\n- The request will automatically retry\n- Try again in a few moments\n- If the error persists, contact Holded support\n- Check Holded status page for any ongoing incidents`;
|
|
366
|
+
case 502:
|
|
367
|
+
case 503:
|
|
368
|
+
case 504:
|
|
369
|
+
return `Error: Holded service temporarily unavailable (${status}). ${errorMessage}\n\nRemediation:\n- The request will automatically retry with exponential backoff\n- Wait a few minutes and try again\n- Check if Holded is performing maintenance\n- Monitor Holded status page for service updates`;
|
|
370
|
+
default:
|
|
371
|
+
return `Error: API request failed with status ${status} for ${method} ${url}. ${errorMessage}\n\nRemediation:\n- Review the Holded API documentation for this endpoint\n- Check the API response for specific error details\n- Contact Holded support if the issue persists`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (axiosError.code === "ECONNABORTED") {
|
|
375
|
+
return `Error: Request timed out after ${axiosError.config?.timeout || 30000}ms.\n\nRemediation:\n- The operation took too long to complete\n- For file uploads: Check file size is reasonable\n- Try again with a stable internet connection\n- If timeout persists, the Holded API may be slow - try during off-peak hours`;
|
|
376
|
+
}
|
|
377
|
+
else if (axiosError.code === "ENOTFOUND" || axiosError.code === "ECONNREFUSED") {
|
|
378
|
+
return `Error: Unable to connect to Holded API.\n\nRemediation:\n- Check your internet connection\n- Verify DNS resolution is working\n- Check if you're behind a firewall or proxy\n- Ensure api.holded.com is accessible from your network\n- Try accessing https://api.holded.com in a browser`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}\n\nRemediation:\n- Check the error message above for details\n- Review your input parameters\n- Ensure all required dependencies are installed\n- Enable debug mode (HOLDED_DEBUG=true) for more details`;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Format a successful response with optional truncation
|
|
385
|
+
*/
|
|
386
|
+
export function formatResponse(data, characterLimit = 25000) {
|
|
387
|
+
let text = JSON.stringify(data, null, 2);
|
|
388
|
+
let truncated = false;
|
|
389
|
+
if (text.length > characterLimit) {
|
|
390
|
+
// Try to truncate the data array if it exists
|
|
391
|
+
if (Array.isArray(data)) {
|
|
392
|
+
const halfLength = Math.max(1, Math.floor(data.length / 2));
|
|
393
|
+
const truncatedData = data.slice(0, halfLength);
|
|
394
|
+
text = JSON.stringify({
|
|
395
|
+
data: truncatedData,
|
|
396
|
+
truncated: true,
|
|
397
|
+
message: `Response truncated from ${data.length} to ${halfLength} items. Use pagination parameters to see more results.`,
|
|
398
|
+
}, null, 2);
|
|
399
|
+
truncated = true;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
const suffix = "\n... (response truncated)";
|
|
403
|
+
text = text.substring(0, characterLimit - suffix.length) + suffix;
|
|
404
|
+
truncated = true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return { text, truncated };
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Build pagination query parameters
|
|
411
|
+
*/
|
|
412
|
+
export function buildPaginationParams(page) {
|
|
413
|
+
const params = {};
|
|
414
|
+
if (page !== undefined && page > 0) {
|
|
415
|
+
params.page = page;
|
|
416
|
+
}
|
|
417
|
+
return params;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Safely convert API response to Record<string, unknown> for structuredContent
|
|
421
|
+
* Handles cases where API might return string, object, array, or other types
|
|
422
|
+
*/
|
|
423
|
+
export function toStructuredContent(response) {
|
|
424
|
+
if (typeof response === "string") {
|
|
425
|
+
return { message: response, success: true };
|
|
426
|
+
}
|
|
427
|
+
if (response && typeof response === "object" && !Array.isArray(response)) {
|
|
428
|
+
return response;
|
|
429
|
+
}
|
|
430
|
+
// For arrays or other types, wrap in a data property
|
|
431
|
+
return { data: response, success: true };
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Reset internal state for testing purposes only
|
|
435
|
+
*/
|
|
436
|
+
export function _resetForTesting() {
|
|
437
|
+
rateLimiter = null;
|
|
438
|
+
axiosInstance = undefined;
|
|
439
|
+
apiKey = undefined;
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/services/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Balances tool for Holded API
|
|
3
|
+
*
|
|
4
|
+
* Computes accurate, date-scoped per-account debit/credit/balance totals
|
|
5
|
+
* by aggregating from individual daily ledger entries with cross-fiscal-year
|
|
6
|
+
* leak filtering.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import type { LedgerEntryLine, AccountBalance } from "../../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Filter out cross-fiscal-year leaked entries from daily ledger results.
|
|
12
|
+
*
|
|
13
|
+
* Detection uses two signals:
|
|
14
|
+
* 1. Entries sharing the same timestamp as the opening balance entry are candidates.
|
|
15
|
+
* 2. Candidates are confirmed as leaked if their entryNumber also appears at a
|
|
16
|
+
* different timestamp (duplicate = different fiscal year).
|
|
17
|
+
* 3. Fallback: candidates at the opening timestamp whose type is not "opening" or
|
|
18
|
+
* "vat_regularization" are excluded even without a duplicate entryNumber.
|
|
19
|
+
*
|
|
20
|
+
* Exported for unit testing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function filterLeakedEntries(entries: LedgerEntryLine[], includeOpening: boolean): {
|
|
23
|
+
filtered: LedgerEntryLine[];
|
|
24
|
+
leakedCount: number;
|
|
25
|
+
openingExcluded: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Aggregate daily ledger entries into per-account debit/credit totals.
|
|
29
|
+
*
|
|
30
|
+
* Exported for unit testing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function aggregateByAccount(entries: LedgerEntryLine[]): Map<number, {
|
|
33
|
+
debit: number;
|
|
34
|
+
credit: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Format account balances as markdown
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatAccountBalancesMarkdown(accounts: AccountBalance[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Register the account balances tool.
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerAccountBalancesTools(server: McpServer): void;
|
|
44
|
+
//# sourceMappingURL=account-balances.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-balances.d.ts","sourceRoot":"","sources":["../../../src/tools/accounting/account-balances.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA6DtE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EAAE,EAC1B,cAAc,EAAE,OAAO,GACtB;IAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CA2EhF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EAAE,GACzB,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAchD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAqBhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2GpE"}
|