@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.
Files changed (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1040 -0
  3. package/dist/constants.d.ts +60 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +72 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/index.d.ts +11 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +86 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/schemas/accounting/account-balances.d.ts +19 -0
  12. package/dist/schemas/accounting/account-balances.d.ts.map +1 -0
  13. package/dist/schemas/accounting/account-balances.js +28 -0
  14. package/dist/schemas/accounting/account-balances.js.map +1 -0
  15. package/dist/schemas/accounting/accounts.d.ts +49 -0
  16. package/dist/schemas/accounting/accounts.d.ts.map +1 -0
  17. package/dist/schemas/accounting/accounts.js +50 -0
  18. package/dist/schemas/accounting/accounts.js.map +1 -0
  19. package/dist/schemas/accounting/daily-ledger.d.ts +45 -0
  20. package/dist/schemas/accounting/daily-ledger.d.ts.map +1 -0
  21. package/dist/schemas/accounting/daily-ledger.js +57 -0
  22. package/dist/schemas/accounting/daily-ledger.js.map +1 -0
  23. package/dist/schemas/common.d.ts +118 -0
  24. package/dist/schemas/common.d.ts.map +1 -0
  25. package/dist/schemas/common.js +126 -0
  26. package/dist/schemas/common.js.map +1 -0
  27. package/dist/schemas/crm/bookings.d.ts +108 -0
  28. package/dist/schemas/crm/bookings.d.ts.map +1 -0
  29. package/dist/schemas/crm/bookings.js +96 -0
  30. package/dist/schemas/crm/bookings.js.map +1 -0
  31. package/dist/schemas/crm/events.d.ts +57 -0
  32. package/dist/schemas/crm/events.d.ts.map +1 -0
  33. package/dist/schemas/crm/events.js +53 -0
  34. package/dist/schemas/crm/events.js.map +1 -0
  35. package/dist/schemas/crm/funnels.d.ts +60 -0
  36. package/dist/schemas/crm/funnels.d.ts.map +1 -0
  37. package/dist/schemas/crm/funnels.js +48 -0
  38. package/dist/schemas/crm/funnels.js.map +1 -0
  39. package/dist/schemas/crm/leads.d.ts +146 -0
  40. package/dist/schemas/crm/leads.d.ts.map +1 -0
  41. package/dist/schemas/crm/leads.js +129 -0
  42. package/dist/schemas/crm/leads.js.map +1 -0
  43. package/dist/schemas/invoicing/contacts.d.ts +352 -0
  44. package/dist/schemas/invoicing/contacts.d.ts.map +1 -0
  45. package/dist/schemas/invoicing/contacts.js +187 -0
  46. package/dist/schemas/invoicing/contacts.js.map +1 -0
  47. package/dist/schemas/invoicing/documents.d.ts +424 -0
  48. package/dist/schemas/invoicing/documents.d.ts.map +1 -0
  49. package/dist/schemas/invoicing/documents.js +217 -0
  50. package/dist/schemas/invoicing/documents.js.map +1 -0
  51. package/dist/schemas/invoicing/expenses-accounts.d.ts +47 -0
  52. package/dist/schemas/invoicing/expenses-accounts.d.ts.map +1 -0
  53. package/dist/schemas/invoicing/expenses-accounts.js +43 -0
  54. package/dist/schemas/invoicing/expenses-accounts.js.map +1 -0
  55. package/dist/schemas/invoicing/numbering-series.d.ts +94 -0
  56. package/dist/schemas/invoicing/numbering-series.d.ts.map +1 -0
  57. package/dist/schemas/invoicing/numbering-series.js +43 -0
  58. package/dist/schemas/invoicing/numbering-series.js.map +1 -0
  59. package/dist/schemas/invoicing/payments.d.ts +50 -0
  60. package/dist/schemas/invoicing/payments.d.ts.map +1 -0
  61. package/dist/schemas/invoicing/payments.js +46 -0
  62. package/dist/schemas/invoicing/payments.js.map +1 -0
  63. package/dist/schemas/invoicing/products.d.ts +176 -0
  64. package/dist/schemas/invoicing/products.d.ts.map +1 -0
  65. package/dist/schemas/invoicing/products.js +149 -0
  66. package/dist/schemas/invoicing/products.js.map +1 -0
  67. package/dist/schemas/invoicing/remittances.d.ts +21 -0
  68. package/dist/schemas/invoicing/remittances.d.ts.map +1 -0
  69. package/dist/schemas/invoicing/remittances.js +20 -0
  70. package/dist/schemas/invoicing/remittances.js.map +1 -0
  71. package/dist/schemas/invoicing/sales-channels.d.ts +45 -0
  72. package/dist/schemas/invoicing/sales-channels.d.ts.map +1 -0
  73. package/dist/schemas/invoicing/sales-channels.js +41 -0
  74. package/dist/schemas/invoicing/sales-channels.js.map +1 -0
  75. package/dist/schemas/invoicing/services.d.ts +55 -0
  76. package/dist/schemas/invoicing/services.d.ts.map +1 -0
  77. package/dist/schemas/invoicing/services.js +51 -0
  78. package/dist/schemas/invoicing/services.js.map +1 -0
  79. package/dist/schemas/invoicing/taxes.d.ts +12 -0
  80. package/dist/schemas/invoicing/taxes.d.ts.map +1 -0
  81. package/dist/schemas/invoicing/taxes.js +12 -0
  82. package/dist/schemas/invoicing/taxes.js.map +1 -0
  83. package/dist/schemas/invoicing/treasury.d.ts +42 -0
  84. package/dist/schemas/invoicing/treasury.d.ts.map +1 -0
  85. package/dist/schemas/invoicing/treasury.js +39 -0
  86. package/dist/schemas/invoicing/treasury.js.map +1 -0
  87. package/dist/schemas/invoicing/warehouses.d.ts +61 -0
  88. package/dist/schemas/invoicing/warehouses.d.ts.map +1 -0
  89. package/dist/schemas/invoicing/warehouses.js +43 -0
  90. package/dist/schemas/invoicing/warehouses.js.map +1 -0
  91. package/dist/schemas/projects/projects.d.ts +60 -0
  92. package/dist/schemas/projects/projects.d.ts.map +1 -0
  93. package/dist/schemas/projects/projects.js +55 -0
  94. package/dist/schemas/projects/projects.js.map +1 -0
  95. package/dist/schemas/projects/tasks.d.ts +68 -0
  96. package/dist/schemas/projects/tasks.d.ts.map +1 -0
  97. package/dist/schemas/projects/tasks.js +64 -0
  98. package/dist/schemas/projects/tasks.js.map +1 -0
  99. package/dist/schemas/projects/time-tracking.d.ts +80 -0
  100. package/dist/schemas/projects/time-tracking.d.ts.map +1 -0
  101. package/dist/schemas/projects/time-tracking.js +75 -0
  102. package/dist/schemas/projects/time-tracking.js.map +1 -0
  103. package/dist/schemas/team/employees.d.ts +135 -0
  104. package/dist/schemas/team/employees.d.ts.map +1 -0
  105. package/dist/schemas/team/employees.js +144 -0
  106. package/dist/schemas/team/employees.js.map +1 -0
  107. package/dist/schemas/team/time-tracking.d.ts +98 -0
  108. package/dist/schemas/team/time-tracking.d.ts.map +1 -0
  109. package/dist/schemas/team/time-tracking.js +89 -0
  110. package/dist/schemas/team/time-tracking.js.map +1 -0
  111. package/dist/services/api.d.ts +118 -0
  112. package/dist/services/api.d.ts.map +1 -0
  113. package/dist/services/api.js +441 -0
  114. package/dist/services/api.js.map +1 -0
  115. package/dist/tools/accounting/account-balances.d.ts +44 -0
  116. package/dist/tools/accounting/account-balances.d.ts.map +1 -0
  117. package/dist/tools/accounting/account-balances.js +240 -0
  118. package/dist/tools/accounting/account-balances.js.map +1 -0
  119. package/dist/tools/accounting/accounts.d.ts +18 -0
  120. package/dist/tools/accounting/accounts.d.ts.map +1 -0
  121. package/dist/tools/accounting/accounts.js +131 -0
  122. package/dist/tools/accounting/accounts.js.map +1 -0
  123. package/dist/tools/accounting/daily-ledger.d.ts +9 -0
  124. package/dist/tools/accounting/daily-ledger.d.ts.map +1 -0
  125. package/dist/tools/accounting/daily-ledger.js +117 -0
  126. package/dist/tools/accounting/daily-ledger.js.map +1 -0
  127. package/dist/tools/accounting/index.d.ts +9 -0
  128. package/dist/tools/accounting/index.d.ts.map +1 -0
  129. package/dist/tools/accounting/index.js +15 -0
  130. package/dist/tools/accounting/index.js.map +1 -0
  131. package/dist/tools/crm/bookings.d.ts +18 -0
  132. package/dist/tools/crm/bookings.d.ts.map +1 -0
  133. package/dist/tools/crm/bookings.js +272 -0
  134. package/dist/tools/crm/bookings.js.map +1 -0
  135. package/dist/tools/crm/events.d.ts +18 -0
  136. package/dist/tools/crm/events.d.ts.map +1 -0
  137. package/dist/tools/crm/events.js +134 -0
  138. package/dist/tools/crm/events.js.map +1 -0
  139. package/dist/tools/crm/funnels.d.ts +18 -0
  140. package/dist/tools/crm/funnels.d.ts.map +1 -0
  141. package/dist/tools/crm/funnels.js +113 -0
  142. package/dist/tools/crm/funnels.js.map +1 -0
  143. package/dist/tools/crm/index.d.ts +9 -0
  144. package/dist/tools/crm/index.d.ts.map +1 -0
  145. package/dist/tools/crm/index.js +17 -0
  146. package/dist/tools/crm/index.js.map +1 -0
  147. package/dist/tools/crm/leads.d.ts +18 -0
  148. package/dist/tools/crm/leads.d.ts.map +1 -0
  149. package/dist/tools/crm/leads.js +519 -0
  150. package/dist/tools/crm/leads.js.map +1 -0
  151. package/dist/tools/factory.d.ts +55 -0
  152. package/dist/tools/factory.d.ts.map +1 -0
  153. package/dist/tools/factory.js +145 -0
  154. package/dist/tools/factory.js.map +1 -0
  155. package/dist/tools/invoicing/contacts.d.ts +26 -0
  156. package/dist/tools/invoicing/contacts.d.ts.map +1 -0
  157. package/dist/tools/invoicing/contacts.js +358 -0
  158. package/dist/tools/invoicing/contacts.js.map +1 -0
  159. package/dist/tools/invoicing/documents.d.ts +18 -0
  160. package/dist/tools/invoicing/documents.d.ts.map +1 -0
  161. package/dist/tools/invoicing/documents.js +578 -0
  162. package/dist/tools/invoicing/documents.js.map +1 -0
  163. package/dist/tools/invoicing/expenses-accounts.d.ts +25 -0
  164. package/dist/tools/invoicing/expenses-accounts.d.ts.map +1 -0
  165. package/dist/tools/invoicing/expenses-accounts.js +111 -0
  166. package/dist/tools/invoicing/expenses-accounts.js.map +1 -0
  167. package/dist/tools/invoicing/index.d.ts +9 -0
  168. package/dist/tools/invoicing/index.d.ts.map +1 -0
  169. package/dist/tools/invoicing/index.js +33 -0
  170. package/dist/tools/invoicing/index.js.map +1 -0
  171. package/dist/tools/invoicing/numbering-series.d.ts +9 -0
  172. package/dist/tools/invoicing/numbering-series.d.ts.map +1 -0
  173. package/dist/tools/invoicing/numbering-series.js +161 -0
  174. package/dist/tools/invoicing/numbering-series.js.map +1 -0
  175. package/dist/tools/invoicing/payments.d.ts +18 -0
  176. package/dist/tools/invoicing/payments.d.ts.map +1 -0
  177. package/dist/tools/invoicing/payments.js +175 -0
  178. package/dist/tools/invoicing/payments.js.map +1 -0
  179. package/dist/tools/invoicing/products.d.ts +18 -0
  180. package/dist/tools/invoicing/products.d.ts.map +1 -0
  181. package/dist/tools/invoicing/products.js +389 -0
  182. package/dist/tools/invoicing/products.js.map +1 -0
  183. package/dist/tools/invoicing/remittances.d.ts +17 -0
  184. package/dist/tools/invoicing/remittances.d.ts.map +1 -0
  185. package/dist/tools/invoicing/remittances.js +76 -0
  186. package/dist/tools/invoicing/remittances.js.map +1 -0
  187. package/dist/tools/invoicing/sales-channels.d.ts +24 -0
  188. package/dist/tools/invoicing/sales-channels.d.ts.map +1 -0
  189. package/dist/tools/invoicing/sales-channels.js +105 -0
  190. package/dist/tools/invoicing/sales-channels.js.map +1 -0
  191. package/dist/tools/invoicing/services.d.ts +29 -0
  192. package/dist/tools/invoicing/services.d.ts.map +1 -0
  193. package/dist/tools/invoicing/services.js +124 -0
  194. package/dist/tools/invoicing/services.js.map +1 -0
  195. package/dist/tools/invoicing/taxes.d.ts +18 -0
  196. package/dist/tools/invoicing/taxes.d.ts.map +1 -0
  197. package/dist/tools/invoicing/taxes.js +58 -0
  198. package/dist/tools/invoicing/taxes.js.map +1 -0
  199. package/dist/tools/invoicing/treasury.d.ts +9 -0
  200. package/dist/tools/invoicing/treasury.d.ts.map +1 -0
  201. package/dist/tools/invoicing/treasury.js +196 -0
  202. package/dist/tools/invoicing/treasury.js.map +1 -0
  203. package/dist/tools/invoicing/warehouses.d.ts +18 -0
  204. package/dist/tools/invoicing/warehouses.d.ts.map +1 -0
  205. package/dist/tools/invoicing/warehouses.js +133 -0
  206. package/dist/tools/invoicing/warehouses.js.map +1 -0
  207. package/dist/tools/projects/index.d.ts +9 -0
  208. package/dist/tools/projects/index.d.ts.map +1 -0
  209. package/dist/tools/projects/index.js +15 -0
  210. package/dist/tools/projects/index.js.map +1 -0
  211. package/dist/tools/projects/projects.d.ts +18 -0
  212. package/dist/tools/projects/projects.d.ts.map +1 -0
  213. package/dist/tools/projects/projects.js +203 -0
  214. package/dist/tools/projects/projects.js.map +1 -0
  215. package/dist/tools/projects/tasks.d.ts +18 -0
  216. package/dist/tools/projects/tasks.d.ts.map +1 -0
  217. package/dist/tools/projects/tasks.js +154 -0
  218. package/dist/tools/projects/tasks.js.map +1 -0
  219. package/dist/tools/projects/time-tracking.d.ts +14 -0
  220. package/dist/tools/projects/time-tracking.d.ts.map +1 -0
  221. package/dist/tools/projects/time-tracking.js +291 -0
  222. package/dist/tools/projects/time-tracking.js.map +1 -0
  223. package/dist/tools/team/employees.d.ts +18 -0
  224. package/dist/tools/team/employees.d.ts.map +1 -0
  225. package/dist/tools/team/employees.js +149 -0
  226. package/dist/tools/team/employees.js.map +1 -0
  227. package/dist/tools/team/index.d.ts +9 -0
  228. package/dist/tools/team/index.d.ts.map +1 -0
  229. package/dist/tools/team/index.js +13 -0
  230. package/dist/tools/team/index.js.map +1 -0
  231. package/dist/tools/team/time-tracking.d.ts +18 -0
  232. package/dist/tools/team/time-tracking.d.ts.map +1 -0
  233. package/dist/tools/team/time-tracking.js +398 -0
  234. package/dist/tools/team/time-tracking.js.map +1 -0
  235. package/dist/tools/utilities.d.ts +45 -0
  236. package/dist/tools/utilities.d.ts.map +1 -0
  237. package/dist/tools/utilities.js +55 -0
  238. package/dist/tools/utilities.js.map +1 -0
  239. package/dist/types.d.ts +640 -0
  240. package/dist/types.d.ts.map +1 -0
  241. package/dist/types.js +29 -0
  242. package/dist/types.js.map +1 -0
  243. 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"}