@frihet/mcp-server 1.16.3 → 1.16.5

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 (209) hide show
  1. package/README.md +1 -1
  2. package/dist/client-interface.d.ts +1 -1
  3. package/dist/client.d.ts +13 -1
  4. package/dist/client.js +70 -6
  5. package/dist/demo-client.d.ts +1 -1
  6. package/dist/demo-client.js +23 -3
  7. package/dist/openai-profile.js +1 -1
  8. package/dist/tools/invoices.js +24 -11
  9. package/package.json +11 -5
  10. package/scripts/postinstall.js +30 -8
  11. package/dist/__tests__/audit-server-version.test.d.ts +0 -13
  12. package/dist/__tests__/audit-server-version.test.d.ts.map +0 -1
  13. package/dist/__tests__/audit-server-version.test.js +0 -96
  14. package/dist/__tests__/audit-server-version.test.js.map +0 -1
  15. package/dist/__tests__/banking-client-contract.test.d.ts +0 -20
  16. package/dist/__tests__/banking-client-contract.test.d.ts.map +0 -1
  17. package/dist/__tests__/banking-client-contract.test.js +0 -133
  18. package/dist/__tests__/banking-client-contract.test.js.map +0 -1
  19. package/dist/__tests__/banking-tools.test.d.ts +0 -18
  20. package/dist/__tests__/banking-tools.test.d.ts.map +0 -1
  21. package/dist/__tests__/banking-tools.test.js +0 -252
  22. package/dist/__tests__/banking-tools.test.js.map +0 -1
  23. package/dist/__tests__/contract.test.d.ts +0 -33
  24. package/dist/__tests__/contract.test.d.ts.map +0 -1
  25. package/dist/__tests__/contract.test.js +0 -208
  26. package/dist/__tests__/contract.test.js.map +0 -1
  27. package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.d.ts +0 -13
  28. package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.d.ts.map +0 -1
  29. package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js +0 -420
  30. package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js.map +0 -1
  31. package/dist/__tests__/demo-mode.test.d.ts +0 -11
  32. package/dist/__tests__/demo-mode.test.d.ts.map +0 -1
  33. package/dist/__tests__/demo-mode.test.js +0 -119
  34. package/dist/__tests__/demo-mode.test.js.map +0 -1
  35. package/dist/__tests__/einvoice-day4-tools.test.d.ts +0 -16
  36. package/dist/__tests__/einvoice-day4-tools.test.d.ts.map +0 -1
  37. package/dist/__tests__/einvoice-day4-tools.test.js +0 -421
  38. package/dist/__tests__/einvoice-day4-tools.test.js.map +0 -1
  39. package/dist/__tests__/einvoice-integration.test.d.ts +0 -24
  40. package/dist/__tests__/einvoice-integration.test.d.ts.map +0 -1
  41. package/dist/__tests__/einvoice-integration.test.js +0 -142
  42. package/dist/__tests__/einvoice-integration.test.js.map +0 -1
  43. package/dist/__tests__/einvoice-tools.test.d.ts +0 -16
  44. package/dist/__tests__/einvoice-tools.test.d.ts.map +0 -1
  45. package/dist/__tests__/einvoice-tools.test.js +0 -411
  46. package/dist/__tests__/einvoice-tools.test.js.map +0 -1
  47. package/dist/__tests__/fiscal-tools.test.d.ts +0 -21
  48. package/dist/__tests__/fiscal-tools.test.d.ts.map +0 -1
  49. package/dist/__tests__/fiscal-tools.test.js +0 -271
  50. package/dist/__tests__/fiscal-tools.test.js.map +0 -1
  51. package/dist/__tests__/get-envelope-unwrap-regression.test.d.ts +0 -27
  52. package/dist/__tests__/get-envelope-unwrap-regression.test.d.ts.map +0 -1
  53. package/dist/__tests__/get-envelope-unwrap-regression.test.js +0 -223
  54. package/dist/__tests__/get-envelope-unwrap-regression.test.js.map +0 -1
  55. package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts +0 -14
  56. package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts.map +0 -1
  57. package/dist/__tests__/intelligence-duplicate-invoice.test.js +0 -108
  58. package/dist/__tests__/intelligence-duplicate-invoice.test.js.map +0 -1
  59. package/dist/__tests__/kitchen-tools.test.d.ts +0 -18
  60. package/dist/__tests__/kitchen-tools.test.d.ts.map +0 -1
  61. package/dist/__tests__/kitchen-tools.test.js +0 -399
  62. package/dist/__tests__/kitchen-tools.test.js.map +0 -1
  63. package/dist/__tests__/mutation-unwrap-and-schema-regression.test.d.ts +0 -30
  64. package/dist/__tests__/mutation-unwrap-and-schema-regression.test.d.ts.map +0 -1
  65. package/dist/__tests__/mutation-unwrap-and-schema-regression.test.js +0 -164
  66. package/dist/__tests__/mutation-unwrap-and-schema-regression.test.js.map +0 -1
  67. package/dist/__tests__/observability-redaction.test.d.ts +0 -13
  68. package/dist/__tests__/observability-redaction.test.d.ts.map +0 -1
  69. package/dist/__tests__/observability-redaction.test.js +0 -94
  70. package/dist/__tests__/observability-redaction.test.js.map +0 -1
  71. package/dist/__tests__/openai-grouped-compose.test.d.ts +0 -26
  72. package/dist/__tests__/openai-grouped-compose.test.d.ts.map +0 -1
  73. package/dist/__tests__/openai-grouped-compose.test.js +0 -245
  74. package/dist/__tests__/openai-grouped-compose.test.js.map +0 -1
  75. package/dist/__tests__/openai-profile.test.d.ts +0 -10
  76. package/dist/__tests__/openai-profile.test.d.ts.map +0 -1
  77. package/dist/__tests__/openai-profile.test.js +0 -168
  78. package/dist/__tests__/openai-profile.test.js.map +0 -1
  79. package/dist/__tests__/pagination-cursor-param.test.d.ts +0 -27
  80. package/dist/__tests__/pagination-cursor-param.test.d.ts.map +0 -1
  81. package/dist/__tests__/pagination-cursor-param.test.js +0 -96
  82. package/dist/__tests__/pagination-cursor-param.test.js.map +0 -1
  83. package/dist/__tests__/pos-tools.test.d.ts +0 -16
  84. package/dist/__tests__/pos-tools.test.d.ts.map +0 -1
  85. package/dist/__tests__/pos-tools.test.js +0 -244
  86. package/dist/__tests__/pos-tools.test.js.map +0 -1
  87. package/dist/__tests__/recurring-tools.test.d.ts +0 -23
  88. package/dist/__tests__/recurring-tools.test.d.ts.map +0 -1
  89. package/dist/__tests__/recurring-tools.test.js +0 -352
  90. package/dist/__tests__/recurring-tools.test.js.map +0 -1
  91. package/dist/__tests__/schema-envelope-guard.test.d.ts +0 -18
  92. package/dist/__tests__/schema-envelope-guard.test.d.ts.map +0 -1
  93. package/dist/__tests__/schema-envelope-guard.test.js +0 -56
  94. package/dist/__tests__/schema-envelope-guard.test.js.map +0 -1
  95. package/dist/__tests__/stay-tools.test.d.ts +0 -17
  96. package/dist/__tests__/stay-tools.test.d.ts.map +0 -1
  97. package/dist/__tests__/stay-tools.test.js +0 -259
  98. package/dist/__tests__/stay-tools.test.js.map +0 -1
  99. package/dist/__tests__/team-tools.test.d.ts +0 -18
  100. package/dist/__tests__/team-tools.test.d.ts.map +0 -1
  101. package/dist/__tests__/team-tools.test.js +0 -228
  102. package/dist/__tests__/team-tools.test.js.map +0 -1
  103. package/dist/__tests__/time-tools.test.d.ts +0 -19
  104. package/dist/__tests__/time-tools.test.d.ts.map +0 -1
  105. package/dist/__tests__/time-tools.test.js +0 -286
  106. package/dist/__tests__/time-tools.test.js.map +0 -1
  107. package/dist/__tests__/tool-exposure.test.d.ts +0 -11
  108. package/dist/__tests__/tool-exposure.test.d.ts.map +0 -1
  109. package/dist/__tests__/tool-exposure.test.js +0 -264
  110. package/dist/__tests__/tool-exposure.test.js.map +0 -1
  111. package/dist/client-interface.d.ts.map +0 -1
  112. package/dist/client-interface.js.map +0 -1
  113. package/dist/client.d.ts.map +0 -1
  114. package/dist/client.js.map +0 -1
  115. package/dist/demo-client.d.ts.map +0 -1
  116. package/dist/demo-client.js.map +0 -1
  117. package/dist/demo-fixtures.d.ts.map +0 -1
  118. package/dist/demo-fixtures.js.map +0 -1
  119. package/dist/index.d.ts.map +0 -1
  120. package/dist/index.js.map +0 -1
  121. package/dist/logger.d.ts.map +0 -1
  122. package/dist/logger.js.map +0 -1
  123. package/dist/metrics.d.ts.map +0 -1
  124. package/dist/metrics.js.map +0 -1
  125. package/dist/observability.d.ts.map +0 -1
  126. package/dist/observability.js.map +0 -1
  127. package/dist/openai-profile.d.ts.map +0 -1
  128. package/dist/openai-profile.js.map +0 -1
  129. package/dist/prompts/register-all.d.ts.map +0 -1
  130. package/dist/prompts/register-all.js.map +0 -1
  131. package/dist/redaction.d.ts.map +0 -1
  132. package/dist/redaction.js.map +0 -1
  133. package/dist/resources/register-all.d.ts.map +0 -1
  134. package/dist/resources/register-all.js.map +0 -1
  135. package/dist/tool-exposure.d.ts.map +0 -1
  136. package/dist/tool-exposure.js.map +0 -1
  137. package/dist/tools/accountingClose.d.ts.map +0 -1
  138. package/dist/tools/accountingClose.js.map +0 -1
  139. package/dist/tools/audit_gl.d.ts.map +0 -1
  140. package/dist/tools/audit_gl.js.map +0 -1
  141. package/dist/tools/backend-availability.d.ts.map +0 -1
  142. package/dist/tools/backend-availability.js.map +0 -1
  143. package/dist/tools/bank_rules.d.ts.map +0 -1
  144. package/dist/tools/bank_rules.js.map +0 -1
  145. package/dist/tools/banking.d.ts.map +0 -1
  146. package/dist/tools/banking.js.map +0 -1
  147. package/dist/tools/clients.d.ts.map +0 -1
  148. package/dist/tools/clients.js.map +0 -1
  149. package/dist/tools/crm.d.ts.map +0 -1
  150. package/dist/tools/crm.js.map +0 -1
  151. package/dist/tools/deposits.d.ts.map +0 -1
  152. package/dist/tools/deposits.js.map +0 -1
  153. package/dist/tools/einvoice.d.ts.map +0 -1
  154. package/dist/tools/einvoice.js.map +0 -1
  155. package/dist/tools/expenses.d.ts.map +0 -1
  156. package/dist/tools/expenses.js.map +0 -1
  157. package/dist/tools/fiscal.d.ts.map +0 -1
  158. package/dist/tools/fiscal.js.map +0 -1
  159. package/dist/tools/gestoria.d.ts.map +0 -1
  160. package/dist/tools/gestoria.js.map +0 -1
  161. package/dist/tools/hr.d.ts.map +0 -1
  162. package/dist/tools/hr.js.map +0 -1
  163. package/dist/tools/igic.d.ts.map +0 -1
  164. package/dist/tools/igic.js.map +0 -1
  165. package/dist/tools/impuesto_sociedades.d.ts.map +0 -1
  166. package/dist/tools/impuesto_sociedades.js.map +0 -1
  167. package/dist/tools/intelligence.d.ts.map +0 -1
  168. package/dist/tools/intelligence.js.map +0 -1
  169. package/dist/tools/invoices.d.ts.map +0 -1
  170. package/dist/tools/invoices.js.map +0 -1
  171. package/dist/tools/kitchen.d.ts.map +0 -1
  172. package/dist/tools/kitchen.js.map +0 -1
  173. package/dist/tools/onboard_vies.d.ts.map +0 -1
  174. package/dist/tools/onboard_vies.js.map +0 -1
  175. package/dist/tools/onboarding.d.ts.map +0 -1
  176. package/dist/tools/onboarding.js.map +0 -1
  177. package/dist/tools/payroll.d.ts.map +0 -1
  178. package/dist/tools/payroll.js.map +0 -1
  179. package/dist/tools/permissions.d.ts.map +0 -1
  180. package/dist/tools/permissions.js.map +0 -1
  181. package/dist/tools/portal_domain.d.ts.map +0 -1
  182. package/dist/tools/portal_domain.js.map +0 -1
  183. package/dist/tools/pos.d.ts.map +0 -1
  184. package/dist/tools/pos.js.map +0 -1
  185. package/dist/tools/products.d.ts.map +0 -1
  186. package/dist/tools/products.js.map +0 -1
  187. package/dist/tools/quotes.d.ts.map +0 -1
  188. package/dist/tools/quotes.js.map +0 -1
  189. package/dist/tools/recurring.d.ts.map +0 -1
  190. package/dist/tools/recurring.js.map +0 -1
  191. package/dist/tools/register-all.d.ts.map +0 -1
  192. package/dist/tools/register-all.js.map +0 -1
  193. package/dist/tools/shared.d.ts.map +0 -1
  194. package/dist/tools/shared.js.map +0 -1
  195. package/dist/tools/stay.d.ts.map +0 -1
  196. package/dist/tools/stay.js.map +0 -1
  197. package/dist/tools/team.d.ts.map +0 -1
  198. package/dist/tools/team.js.map +0 -1
  199. package/dist/tools/time.d.ts.map +0 -1
  200. package/dist/tools/time.js.map +0 -1
  201. package/dist/tools/vendors.d.ts.map +0 -1
  202. package/dist/tools/vendors.js.map +0 -1
  203. package/dist/tools/webhooks.d.ts.map +0 -1
  204. package/dist/tools/webhooks.js.map +0 -1
  205. package/dist/types.d.ts.map +0 -1
  206. package/dist/types.js.map +0 -1
  207. package/scripts/audit-mcp-refs.mjs +0 -402
  208. package/scripts/no-public-leak.sh +0 -60
  209. package/scripts/test-openai-grouped-compose.mjs +0 -173
package/README.md CHANGED
@@ -47,7 +47,7 @@
47
47
  | **ChatGPT Apps** | Coming soon | [chatgpt.com](https://chatgpt.com) |
48
48
  | **Anthropic Claude Directory** | Coming soon | [claude.ai/settings/connectors](https://claude.ai/settings/connectors) |
49
49
 
50
- > **Tool count:** npm `latest` (1.16.3) ships all 157 tools, same as the remote endpoint (`mcp.frihet.io`).
50
+ > **Tool count:** the package (1.16.5) ships all 157 tools, same as the remote endpoint (`mcp.frihet.io`).
51
51
 
52
52
  ---
53
53
 
@@ -104,7 +104,7 @@ export interface IFrihetClient {
104
104
  reasonDescription?: string;
105
105
  fullCredit?: boolean;
106
106
  issueDate?: string;
107
- }): Promise<Record<string, unknown>>;
107
+ }, idempotencyKey?: string): Promise<Record<string, unknown>>;
108
108
  applyLateFee(invoiceId: string, data?: {
109
109
  amount?: number;
110
110
  daysOverdue?: number;
package/dist/client.d.ts CHANGED
@@ -153,12 +153,24 @@ export declare class FrihetClient {
153
153
  markInvoicePaid(id: string, paidDate?: string): Promise<Record<string, unknown>>;
154
154
  getInvoicePdf(id: string): Promise<Record<string, unknown>>;
155
155
  getInvoiceEInvoice(invoiceId: string): Promise<any>;
156
+ /**
157
+ * `POST /v1/invoices/:id/credit-note` — creates a rectificativa DRAFT.
158
+ *
159
+ * The backend REQUIRES an `Idempotency-Key` header (`400
160
+ * IDEMPOTENCY_KEY_REQUIRED` without it). `request` mints one for every
161
+ * mutation, so passing `idempotencyKey` is optional: supply it to make a
162
+ * caller-driven retry replay the stored 201 instead of creating a second
163
+ * draft. The backend marks that replay with `X-Idempotent-Replayed: true`,
164
+ * but this client reads no response headers, so the replayed 201 and the
165
+ * original are indistinguishable to the caller — both are the same draft,
166
+ * which is the property that matters here.
167
+ */
156
168
  createCreditNote(invoiceId: string, data: {
157
169
  reason: string;
158
170
  reasonDescription?: string;
159
171
  fullCredit?: boolean;
160
172
  issueDate?: string;
161
- }): Promise<Record<string, unknown>>;
173
+ }, idempotencyKey?: string): Promise<Record<string, unknown>>;
162
174
  applyLateFee(invoiceId: string, data?: {
163
175
  amount?: number;
164
176
  daysOverdue?: number;
package/dist/client.js CHANGED
@@ -16,6 +16,48 @@ const BASE_URL = "https://api.frihet.io/v1";
16
16
  const MAX_RETRIES = 3;
17
17
  const DEFAULT_RETRY_DELAY_MS = 1000;
18
18
  const REQUEST_TIMEOUT_MS = 30000;
19
+ /**
20
+ * HTTP methods for which the backend treats the request as a mutation and
21
+ * therefore accepts (and for some endpoints REQUIRES) an `Idempotency-Key`
22
+ * header. `POST /v1/invoices/:id/credit-note` rejects a keyless request with
23
+ * `400 IDEMPOTENCY_KEY_REQUIRED`, so a client that never sends one fails 100%
24
+ * of the time — see src/__tests__/idempotency-key-contract.test.ts.
25
+ */
26
+ const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
27
+ /**
28
+ * Fresh idempotency key, always a syntactically valid UUID v4.
29
+ *
30
+ * `crypto.randomUUID` is a global on the Cloudflare Workers runtime and on
31
+ * Node >= 19. On Node 18 — our declared `engines` floor — `globalThis.crypto`
32
+ * is behind `--experimental-global-webcrypto`, so the fallback is a REAL code
33
+ * path there, not a theoretical one. It therefore has to produce a UUID and
34
+ * not an ad-hoc string: the backend documents "UUID v4 recommended", and
35
+ * src/__tests__/idempotency-key-contract.test.ts asserts the shape.
36
+ */
37
+ function newIdempotencyKey() {
38
+ const c = globalThis.crypto;
39
+ if (typeof c?.randomUUID === "function") {
40
+ return c.randomUUID();
41
+ }
42
+ // RFC 4122 v4 layout from Math.random. Weaker entropy than the CSPRNG, but
43
+ // the key only has to be unique per caller, never unguessable.
44
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
45
+ const r = (Math.random() * 16) | 0;
46
+ const v = ch === "x" ? r : (r & 0x3) | 0x8;
47
+ return v.toString(16);
48
+ });
49
+ }
50
+ /**
51
+ * A caller-supplied key counts only if it carries a value. An empty or
52
+ * whitespace-only string is what an LLM client emits for "I have nothing to
53
+ * put here" on an optional string param — treating it as PRESENT would leave
54
+ * the request keyless and reproduce the very `400 IDEMPOTENCY_KEY_REQUIRED`
55
+ * this client exists to prevent.
56
+ */
57
+ function normalizeIdempotencyKey(key) {
58
+ const trimmed = key?.trim();
59
+ return trimmed ? trimmed : undefined;
60
+ }
19
61
  export class FrihetApiError extends Error {
20
62
  statusCode;
21
63
  errorCode;
@@ -40,8 +82,15 @@ export class FrihetClient {
40
82
  }
41
83
  // ------------------------------------------------------------------ HTTP
42
84
  // ------------------------------------------------------------------
43
- async request(method, path, body, query, retryCount = 0) {
85
+ async request(method, path, body, query, retryCount = 0, idempotencyKey) {
44
86
  const url = new URL(`${this.baseUrl}${path}`);
87
+ // Resolved once, at the top of the call chain: a caller-supplied key wins,
88
+ // otherwise mutations get a freshly minted one. The resolved value is then
89
+ // threaded through the 429 recursion below so a retry replays the SAME key
90
+ // — retrying with a new key is exactly the duplicate the key exists to
91
+ // prevent (a retried credit-note would create a second draft).
92
+ const resolvedIdempotencyKey = normalizeIdempotencyKey(idempotencyKey) ??
93
+ (MUTATING_METHODS.has(method) ? newIdempotencyKey() : undefined);
45
94
  if (query) {
46
95
  for (const [key, value] of Object.entries(query)) {
47
96
  if (value !== undefined) {
@@ -54,6 +103,9 @@ export class FrihetClient {
54
103
  "Content-Type": "application/json",
55
104
  Accept: "application/json",
56
105
  };
106
+ if (resolvedIdempotencyKey) {
107
+ headers["Idempotency-Key"] = resolvedIdempotencyKey;
108
+ }
57
109
  const controller = new AbortController();
58
110
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
59
111
  const startTime = Date.now();
@@ -91,7 +143,7 @@ export class FrihetClient {
91
143
  : DEFAULT_RETRY_DELAY_MS * Math.pow(2, retryCount);
92
144
  logRetry(method, path, retryCount, delayMs);
93
145
  await this.sleep(delayMs);
94
- return this.request(method, path, body, query, retryCount + 1);
146
+ return this.request(method, path, body, query, retryCount + 1, resolvedIdempotencyKey);
95
147
  }
96
148
  // Error responses
97
149
  if (!response.ok) {
@@ -150,8 +202,8 @@ export class FrihetClient {
150
202
  * not an envelope (legacy endpoints that return the item directly), it is
151
203
  * returned unchanged so existing callers keep working.
152
204
  */
153
- async requestUnwrapped(method, path, body, query) {
154
- const raw = await this.request(method, path, body, query);
205
+ async requestUnwrapped(method, path, body, query, idempotencyKey) {
206
+ const raw = await this.request(method, path, body, query, 0, idempotencyKey);
155
207
  if (raw !== null &&
156
208
  typeof raw === "object" &&
157
209
  !Array.isArray(raw) &&
@@ -358,8 +410,20 @@ export class FrihetClient {
358
410
  // (e.g. a bare XML string), so this is safe either way.
359
411
  return this.requestUnwrapped("GET", `/invoices/${encodeURIComponent(invoiceId)}/xml`);
360
412
  }
361
- async createCreditNote(invoiceId, data) {
362
- return this.requestUnwrapped("POST", `/invoices/${encodeURIComponent(invoiceId)}/credit-note`, data);
413
+ /**
414
+ * `POST /v1/invoices/:id/credit-note` — creates a rectificativa DRAFT.
415
+ *
416
+ * The backend REQUIRES an `Idempotency-Key` header (`400
417
+ * IDEMPOTENCY_KEY_REQUIRED` without it). `request` mints one for every
418
+ * mutation, so passing `idempotencyKey` is optional: supply it to make a
419
+ * caller-driven retry replay the stored 201 instead of creating a second
420
+ * draft. The backend marks that replay with `X-Idempotent-Replayed: true`,
421
+ * but this client reads no response headers, so the replayed 201 and the
422
+ * original are indistinguishable to the caller — both are the same draft,
423
+ * which is the property that matters here.
424
+ */
425
+ async createCreditNote(invoiceId, data, idempotencyKey) {
426
+ return this.requestUnwrapped("POST", `/invoices/${encodeURIComponent(invoiceId)}/credit-note`, data, undefined, idempotencyKey);
363
427
  }
364
428
  async applyLateFee(invoiceId, data) {
365
429
  return this.requestUnwrapped("POST", `/invoices/${encodeURIComponent(invoiceId)}/late-fee`, data ?? {});
@@ -82,7 +82,7 @@ export declare class DemoFrihetClient implements IFrihetClient {
82
82
  reasonDescription?: string;
83
83
  fullCredit?: boolean;
84
84
  issueDate?: string;
85
- }): Promise<Rec>;
85
+ }, _idempotencyKey?: string): Promise<Rec>;
86
86
  applyLateFee(invoiceId: string, data?: {
87
87
  amount?: number;
88
88
  daysOverdue?: number;
@@ -149,15 +149,35 @@ export class DemoFrihetClient {
149
149
  ...FISCAL_STAMP,
150
150
  };
151
151
  }
152
- async createCreditNote(invoiceId, data) {
152
+ // Mirrors the live 201 body: a DRAFT rectificativa, always by differences
153
+ // (`rectificationMethod: "I"`), R-type derived from `reason` server-side
154
+ // (`error` → R1, everything else → R4 — R2/R3/R5 are unreachable via the API).
155
+ async createCreditNote(invoiceId, data, _idempotencyKey) {
156
+ // Shape rules taken from the live 201 (functions/src/publicApi.ts) rather
157
+ // than invented:
158
+ // - documentNumber is `CN-<original document number>` and NEVER encodes
159
+ // the R-type (creditNoteService.ts: `CN-${originalDocNumber}`). An
160
+ // earlier fixture emitted `R1-DEMO-001`, teaching an agent to parse a
161
+ // field that carries no R-type live.
162
+ // - fullCredit is hardcoded `true` in the live body: `false` never
163
+ // reaches a 201, it is refused with 400 PARTIAL_CREDIT_NOT_IMPLEMENTED.
164
+ // Echoing the input would contradict this tool's own description.
165
+ // Documented divergence: demo mode never throws (the seam's contract), so
166
+ // `fullCredit: false` returns the same simulated draft instead of the live
167
+ // 400. Pinned in src/__tests__/demo-mode.test.ts.
168
+ const original = findOrStub(demoInvoices, invoiceId);
169
+ const originalNumber = typeof original.documentNumber === "string" ? original.documentNumber : "DEMO-001";
153
170
  return {
154
171
  success: true,
155
172
  creditNote: {
156
173
  id: demoId("demo_cn"),
157
- documentNumber: "R4-DEMO-001",
174
+ documentNumber: `CN-${originalNumber}`,
158
175
  originalInvoiceId: invoiceId,
159
176
  reason: data.reason,
160
- fullCredit: data.fullCredit ?? true,
177
+ fullCredit: true,
178
+ status: "draft",
179
+ rectificationMethod: "I",
180
+ totalCredited: 1210.0,
161
181
  },
162
182
  ...FISCAL_STAMP,
163
183
  };
@@ -267,7 +267,7 @@ const EXCLUDE_RESOURCES = new Set([
267
267
  export const OPENAI_CSP = "default-src 'self'; " +
268
268
  "script-src 'self' 'unsafe-inline' https://www.gstatic.com https://apis.google.com; " +
269
269
  "style-src 'self' 'unsafe-inline'; " +
270
- "connect-src 'self' https://api.frihet.io https://us-central1-gen-lang-client-0335716041.cloudfunctions.net " +
270
+ "connect-src 'self' https://api.frihet.io https://europe-west1-gen-lang-client-0335716041.cloudfunctions.net " +
271
271
  "https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://www.googleapis.com https://www.gstatic.com; " +
272
272
  "frame-src https://accounts.google.com https://github.com https://login.microsoftonline.com; " +
273
273
  "img-src 'self' data: https:; " +
@@ -386,13 +386,14 @@ export function registerInvoiceTools(server, client) {
386
386
  // -- create_credit_note --
387
387
  server.registerTool("create_credit_note", {
388
388
  title: "Create Credit Note",
389
- description: "Create a credit note (factura rectificativa) for an existing invoice. " +
390
- "This reverses all or part of an invoice for compliance. " +
391
- "Spanish market: generates VeriFactu-compliant R1-R5 rectificativa. " +
392
- "Other markets: standard credit note with negative amounts. " +
393
- "/ Crea una factura rectificativa para una factura existente. " +
394
- "Mercado espanol: genera rectificativa R1-R5 conforme a VeriFactu. " +
395
- "Otros mercados: nota de credito estandar con importes negativos.",
389
+ description: "Create a full credit note (factura rectificativa) for an existing invoice, as a DRAFT. " +
390
+ "It does NOT issue: the draft carries no fiscal number, no hash and is not sent to VeriFactu — " +
391
+ "issue it from the app when you are ready. Always rectifies by differences (TipoRectificativa = I). " +
392
+ "Spanish market: R-type is derived from `reason` (error -> R1, anything else -> R4). " +
393
+ "Partial credits are not supported. Requires the `pro` plan. " +
394
+ "/ Crea una factura rectificativa completa como BORRADOR. No emite: sin numero fiscal, sin hash, " +
395
+ "sin envio a VeriFactu. Siempre rectifica por diferencias (tipo I). El tipo R se deriva de `reason` " +
396
+ "(error -> R1, resto -> R4). No admite abonos parciales. Requiere plan `pro`.",
396
397
  annotations: CREATE_ANNOTATIONS,
397
398
  inputSchema: {
398
399
  invoiceId: z
@@ -409,21 +410,33 @@ export function registerInvoiceTools(server, client) {
409
410
  fullCredit: z
410
411
  .boolean()
411
412
  .optional()
412
- .describe("true = full credit (tipo S, sustitucion), false = partial (tipo I, diferencias). Default: true " +
413
- "/ true = abono total (tipo S), false = parcial (tipo I). Por defecto: true"),
413
+ .describe("Must be true (the default). `false` is rejected with 400 PARTIAL_CREDIT_NOT_IMPLEMENTED — " +
414
+ "the API has no line-level or partial credit. It does NOT select the rectification method: " +
415
+ "that is always I (por diferencias). " +
416
+ "/ Debe ser true (por defecto). `false` devuelve 400 PARTIAL_CREDIT_NOT_IMPLEMENTED. " +
417
+ "No elige el metodo de rectificacion: siempre es I (por diferencias)."),
414
418
  issueDate: z
415
419
  .string()
416
420
  .optional()
417
421
  .describe("ISO date for the credit note (YYYY-MM-DD). Defaults to today. / Fecha de emision (YYYY-MM-DD). Por defecto hoy."),
422
+ idempotencyKey: z
423
+ .string()
424
+ .max(64)
425
+ .optional()
426
+ .describe("Optional idempotency key, max 64 chars. One is generated per call when omitted or blank; " +
427
+ "pass your own to make a retry replay the stored result instead of creating a second draft. " +
428
+ "Reusing a key with a different body returns 409 IDEMPOTENCY_KEY_REUSED — reconcile, do not " +
429
+ "retry with a new key. " +
430
+ "/ Clave de idempotencia opcional (max 64). Se genera una por llamada si se omite o va vacia."),
418
431
  },
419
432
  outputSchema: creditNoteResultOutput,
420
- }, async ({ invoiceId, reason, reasonDescription, fullCredit, issueDate }) => withToolLogging("create_credit_note", async () => {
433
+ }, async ({ invoiceId, reason, reasonDescription, fullCredit, issueDate, idempotencyKey }) => withToolLogging("create_credit_note", async () => {
421
434
  const result = await client.createCreditNote(invoiceId, {
422
435
  reason,
423
436
  reasonDescription,
424
437
  fullCredit: fullCredit ?? true,
425
438
  issueDate,
426
- });
439
+ }, idempotencyKey);
427
440
  const hints = enrichResponse("invoices", "create", result);
428
441
  return {
429
442
  content: [mutateContent(formatRecord("Credit note created", result))],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frihet/mcp-server",
3
- "version": "1.16.3",
3
+ "version": "1.16.5",
4
4
  "description": "AI-native MCP server for Frihet ERP — 157 tools: invoicing, expenses, CRM, banking, POS + ES/EU fiscal compliance (VeriFactu, TicketBAI, Facturae). Zero-install at mcp.frihet.io. Works with Claude, ChatGPT, Cursor, Windsurf, Cline & any MCP client.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,13 +9,17 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc",
12
- "test": "npm run build && node --test dist/__tests__/openai-profile.test.js dist/__tests__/tool-exposure.test.js dist/__tests__/einvoice-tools.test.js dist/__tests__/einvoice-day4-tools.test.js dist/__tests__/stay-tools.test.js dist/__tests__/pos-tools.test.js dist/__tests__/kitchen-tools.test.js dist/__tests__/banking-tools.test.js dist/__tests__/banking-client-contract.test.js dist/__tests__/pagination-cursor-param.test.js dist/__tests__/fiscal-tools.test.js dist/__tests__/time-tools.test.js dist/__tests__/recurring-tools.test.js dist/__tests__/team-tools.test.js dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js dist/__tests__/audit-server-version.test.js dist/__tests__/openai-grouped-compose.test.js dist/__tests__/contract.test.js dist/__tests__/observability-redaction.test.js dist/__tests__/intelligence-duplicate-invoice.test.js dist/__tests__/get-envelope-unwrap-regression.test.js dist/__tests__/mutation-unwrap-and-schema-regression.test.js dist/__tests__/schema-envelope-guard.test.js dist/__tests__/demo-mode.test.js",
12
+ "test": "npm run build && node --test dist/__tests__/openai-profile.test.js dist/__tests__/tool-exposure.test.js dist/__tests__/einvoice-tools.test.js dist/__tests__/einvoice-day4-tools.test.js dist/__tests__/stay-tools.test.js dist/__tests__/pos-tools.test.js dist/__tests__/kitchen-tools.test.js dist/__tests__/banking-tools.test.js dist/__tests__/banking-client-contract.test.js dist/__tests__/pagination-cursor-param.test.js dist/__tests__/fiscal-tools.test.js dist/__tests__/time-tools.test.js dist/__tests__/recurring-tools.test.js dist/__tests__/team-tools.test.js dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js dist/__tests__/audit-server-version.test.js dist/__tests__/openai-grouped-compose.test.js dist/__tests__/contract.test.js dist/__tests__/observability-redaction.test.js dist/__tests__/intelligence-duplicate-invoice.test.js dist/__tests__/get-envelope-unwrap-regression.test.js dist/__tests__/mutation-unwrap-and-schema-regression.test.js dist/__tests__/schema-envelope-guard.test.js dist/__tests__/demo-mode.test.js dist/__tests__/idempotency-key-contract.test.js",
13
13
  "start": "node dist/index.js",
14
14
  "postinstall": "node scripts/postinstall.js || true",
15
- "prepublishOnly": "npm run build && npm run audit:mcp-refs -- --repo frihet-mcp",
15
+ "prepublishOnly": "npm run build && npm run audit:mcp-refs -- --repo frihet-mcp && npm run gate:no-legacy-region",
16
16
  "audit:mcp-refs": "node scripts/audit-mcp-refs.mjs",
17
17
  "audit:mcp-refs:fix": "node scripts/audit-mcp-refs.mjs --fix",
18
- "gate:no-leak": "bash scripts/no-public-leak.sh"
18
+ "gate:no-leak": "bash scripts/no-public-leak.sh",
19
+ "gate:no-legacy-region": "bash scripts/no-legacy-region.sh",
20
+ "sync:openapi": "node scripts/sync-openapi.mjs",
21
+ "gate:openapi-fresh": "node scripts/sync-openapi.mjs --check",
22
+ "gate:openapi-fresh:live": "node scripts/sync-openapi.mjs --check --live"
19
23
  },
20
24
  "keywords": [
21
25
  "accounting",
@@ -76,7 +80,9 @@
76
80
  },
77
81
  "files": [
78
82
  "dist",
79
- "scripts",
83
+ "!dist/__tests__",
84
+ "!dist/**/*.map",
85
+ "scripts/postinstall.js",
80
86
  "assets/banner.svg",
81
87
  "assets/banner-light.svg",
82
88
  "assets/logo-400.png",
@@ -2,13 +2,35 @@
2
2
  // Only show on interactive install, not CI
3
3
  if (process.env.CI || process.env.FRIHET_QUIET) process.exit(0);
4
4
 
5
+ // Version is read from package.json at runtime — never hardcode it here.
6
+ // A hardcoded literal drifts behind the published version (the shipped 1.16.3
7
+ // banner still said "v1.5.2"); the audit:mcp-refs gate also scans this file to
8
+ // block any re-introduced literal.
9
+ import { readFileSync } from 'node:fs';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { dirname, join } from 'node:path';
12
+
13
+ let version = '';
14
+ try {
15
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
16
+ version = 'v' + JSON.parse(readFileSync(pkgPath, 'utf8')).version;
17
+ } catch {
18
+ version = '';
19
+ }
20
+
21
+ const WIDTH = 62;
22
+ const pad = (s) => s + ' '.repeat(Math.max(0, WIDTH - s.length));
23
+ const lines = [
24
+ pad(` @frihet/mcp-server ${version} installed`),
25
+ pad(''),
26
+ pad(' Docs: https://docs.frihet.io/desarrolladores/mcp-server'),
27
+ pad(' GitHub: https://github.com/Frihet-io/frihet-mcp'),
28
+ pad(''),
29
+ pad(' Star us if you find it useful!'),
30
+ ];
31
+ const border = '═'.repeat(WIDTH);
5
32
  console.log(`
6
- ╔══════════════════════════════════════════════════════╗
7
- ║ @frihet/mcp-server v1.5.2 installed ║
8
- ║ ║
9
- ║ Docs: https://docs.frihet.io/mcp ║
10
- ║ GitHub: https://github.com/Frihet-io/frihet-mcp ║
11
- ║ ║
12
- ║ Star us if you find it useful! ║
13
- ╚══════════════════════════════════════════════════════╝
33
+ ╔${border}╗
34
+ ${lines.map((l) => ` ║${l}║`).join('\n')}
35
+ ╚${border}╝
14
36
  `);
@@ -1,13 +0,0 @@
1
- /**
2
- * Tests for the server.json version gate in scripts/audit-mcp-refs.mjs.
3
- *
4
- * Regression guard: server.json carries the package version as BARE JSON values
5
- * (root `.version` and `.packages[0].version`). The audit's generic line-scan
6
- * version check requires an MCP marker on the same line, which never matched
7
- * those bare `"version": "x.y.z"` lines — so a desynced server.json passed the
8
- * publish gate silently and caused a Registry 400 "duplicate version" in
9
- * release 1.13.1. The `checkServerJsonVersion` helper closes that gap; these
10
- * tests prove it FAILS on drift and PASSES when synced.
11
- */
12
- export {};
13
- //# sourceMappingURL=audit-server-version.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"audit-server-version.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/audit-server-version.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -1,96 +0,0 @@
1
- /**
2
- * Tests for the server.json version gate in scripts/audit-mcp-refs.mjs.
3
- *
4
- * Regression guard: server.json carries the package version as BARE JSON values
5
- * (root `.version` and `.packages[0].version`). The audit's generic line-scan
6
- * version check requires an MCP marker on the same line, which never matched
7
- * those bare `"version": "x.y.z"` lines — so a desynced server.json passed the
8
- * publish gate silently and caused a Registry 400 "duplicate version" in
9
- * release 1.13.1. The `checkServerJsonVersion` helper closes that gap; these
10
- * tests prove it FAILS on drift and PASSES when synced.
11
- */
12
- import { describe, test } from "node:test";
13
- import assert from "node:assert/strict";
14
- import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
15
- import { tmpdir } from "node:os";
16
- import { join, resolve } from "node:path";
17
- import { fileURLToPath, pathToFileURL } from "node:url";
18
- // dist/__tests__/ → repo root is ../../ ; scripts + server.json live at root.
19
- const HERE = resolve(fileURLToPath(import.meta.url), "..");
20
- const REPO_ROOT = resolve(HERE, "..", "..");
21
- const SERVER_JSON_PATH = join(REPO_ROOT, "server.json");
22
- const PKG_PATH = join(REPO_ROOT, "package.json");
23
- // Import the pure helper from the audit script. The script is import-safe:
24
- // its CLI body is guarded by an `if (isMain)` check, so importing it does not
25
- // run the audit or call process.exit.
26
- const auditScriptUrl = pathToFileURL(resolve(REPO_ROOT, "scripts", "audit-mcp-refs.mjs")).href;
27
- const auditMod = await import(auditScriptUrl);
28
- const checkServerJsonVersion = auditMod.checkServerJsonVersion;
29
- const SOT_VERSION = JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
30
- describe("server.json version gate", () => {
31
- test("exports a checkServerJsonVersion helper", () => {
32
- assert.equal(typeof checkServerJsonVersion, "function");
33
- });
34
- test("real server.json is in sync with package.json (no drift)", () => {
35
- const serverJson = JSON.parse(readFileSync(SERVER_JSON_PATH, "utf8"));
36
- const drifts = checkServerJsonVersion(serverJson, SOT_VERSION);
37
- assert.deepEqual(drifts, [], `server.json must match SoT ${SOT_VERSION} — drift: ${JSON.stringify(drifts)}`);
38
- });
39
- test("reports STALE when root .version drifts", () => {
40
- const serverJson = JSON.parse(readFileSync(SERVER_JSON_PATH, "utf8"));
41
- serverJson.version = "9.9.9";
42
- const drifts = checkServerJsonVersion(serverJson, SOT_VERSION);
43
- assert.equal(drifts.length, 1, "exactly one drift expected");
44
- assert.equal(drifts[0].jsonPath, ".version");
45
- assert.equal(drifts[0].found, "9.9.9");
46
- assert.equal(drifts[0].expected, SOT_VERSION);
47
- });
48
- test("reports STALE when .packages[0].version drifts", () => {
49
- const serverJson = JSON.parse(readFileSync(SERVER_JSON_PATH, "utf8"));
50
- serverJson.packages[0].version = "0.0.1";
51
- const drifts = checkServerJsonVersion(serverJson, SOT_VERSION);
52
- assert.equal(drifts.length, 1);
53
- assert.equal(drifts[0].jsonPath, ".packages[0].version");
54
- assert.equal(drifts[0].found, "0.0.1");
55
- });
56
- test("reports BOTH fields when both drift", () => {
57
- const serverJson = JSON.parse(readFileSync(SERVER_JSON_PATH, "utf8"));
58
- serverJson.version = "2.0.0";
59
- serverJson.packages[0].version = "2.0.0";
60
- const drifts = checkServerJsonVersion(serverJson, SOT_VERSION);
61
- assert.equal(drifts.length, 2);
62
- assert.deepEqual(drifts.map((d) => d.jsonPath).sort(), [".packages[0].version", ".version"]);
63
- });
64
- test("treats a missing version field as drift (not a silent pass)", () => {
65
- const serverJson = JSON.parse(readFileSync(SERVER_JSON_PATH, "utf8"));
66
- delete serverJson.version;
67
- const drifts = checkServerJsonVersion(serverJson, SOT_VERSION);
68
- assert.equal(drifts.length, 1);
69
- assert.equal(drifts[0].jsonPath, ".version");
70
- assert.equal(drifts[0].found, undefined);
71
- });
72
- // End-to-end proof the gate fires on a real desynced file, written to a temp
73
- // copy so the repo's server.json is never mutated. We reproduce the exact
74
- // helper-driven check the CLI runs, then confirm the synced copy is clean.
75
- test("temp-file round trip: desync fails, sync passes", () => {
76
- const dir = mkdtempSync(join(tmpdir(), "frihet-serverjson-"));
77
- try {
78
- const original = readFileSync(SERVER_JSON_PATH, "utf8");
79
- const tmpFile = join(dir, "server.json");
80
- // Desync the temp copy.
81
- const desynced = JSON.parse(original);
82
- desynced.version = "0.0.0-stale";
83
- writeFileSync(tmpFile, JSON.stringify(desynced, null, 2) + "\n");
84
- const staleDrifts = checkServerJsonVersion(JSON.parse(readFileSync(tmpFile, "utf8")), SOT_VERSION);
85
- assert.ok(staleDrifts.length > 0, "desynced temp file must report STALE");
86
- // Re-sync and confirm clean.
87
- writeFileSync(tmpFile, original);
88
- const cleanDrifts = checkServerJsonVersion(JSON.parse(readFileSync(tmpFile, "utf8")), SOT_VERSION);
89
- assert.deepEqual(cleanDrifts, [], "synced temp file must pass");
90
- }
91
- finally {
92
- rmSync(dir, { recursive: true, force: true });
93
- }
94
- });
95
- });
96
- //# sourceMappingURL=audit-server-version.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"audit-server-version.test.js","sourceRoot":"","sources":["../../src/__tests__/audit-server-version.test.ts"],"names":[],"mappings":"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"}
@@ -1,20 +0,0 @@
1
- /**
2
- * Client-contract smoke for the 5 banking client methods — Megasprint 1 (#848).
3
- *
4
- * The banking *tools* (banking-tools.test.ts) mock IFrihetClient, so they never
5
- * exercise the real HTTP layer. This file does: it boots a local node:http server
6
- * acting as the Frihet ERP backend, points a REAL FrihetClient at it, and asserts
7
- * every banking method hits the exact verb + path + payload the LIVE server actions
8
- * in Frihet-ERP expect (#848). Catches path/verb/body drift that a mock cannot.
9
- *
10
- * Live REST surface (verbatim from shipped server actions):
11
- * GET /v1/banking/accounts
12
- * GET /v1/banking/accounts/:id
13
- * GET /v1/banking/transactions
14
- * PATCH /v1/banking/transactions/:id/categorize
15
- * POST /v1/banking/transactions/:id/match
16
- *
17
- * Run: npm test (after build)
18
- */
19
- export {};
20
- //# sourceMappingURL=banking-client-contract.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"banking-client-contract.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/banking-client-contract.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}