@frihet/mcp-server 1.17.0 → 2.0.1

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 (87) hide show
  1. package/README.md +106 -348
  2. package/dist/agent-onboarding.d.ts +1 -1
  3. package/dist/agent-onboarding.js +3 -3
  4. package/dist/canary/mcp-harness.d.ts +1 -1
  5. package/dist/canary/mcp-harness.js +4 -1
  6. package/dist/capability-truth.js +20 -12
  7. package/dist/claude-candidate-contract.d.ts +482 -0
  8. package/dist/claude-candidate-contract.js +87 -0
  9. package/dist/claude-output-privacy.d.ts +17 -0
  10. package/dist/claude-output-privacy.js +249 -0
  11. package/dist/claude-profile.d.ts +18 -0
  12. package/dist/claude-profile.js +260 -0
  13. package/dist/client-interface.d.ts +26 -20
  14. package/dist/client.d.ts +65 -29
  15. package/dist/client.js +278 -167
  16. package/dist/demo-client.d.ts +28 -21
  17. package/dist/demo-client.js +32 -14
  18. package/dist/deposit-contract.d.ts +29 -0
  19. package/dist/deposit-contract.js +76 -0
  20. package/dist/fetch-no-redirect.d.ts +3 -0
  21. package/dist/fetch-no-redirect.js +16 -0
  22. package/dist/fiscal-aliases.d.ts +2 -2
  23. package/dist/fiscal-aliases.js +2 -2
  24. package/dist/fiscal-period.d.ts +26 -0
  25. package/dist/fiscal-period.js +31 -0
  26. package/dist/google-profile.d.ts +19 -0
  27. package/dist/google-profile.js +122 -0
  28. package/dist/index.js +31 -7
  29. package/dist/logger.d.ts +1 -1
  30. package/dist/logger.js +2 -2
  31. package/dist/muse-profile.d.ts +19 -0
  32. package/dist/muse-profile.js +122 -0
  33. package/dist/observability.js +3 -2
  34. package/dist/openai-profile.d.ts +2 -2
  35. package/dist/openai-profile.js +37 -11
  36. package/dist/openai-review-contract.d.ts +1 -1
  37. package/dist/openai-review-contract.js +4 -5
  38. package/dist/openai-review-oauth.d.ts +107 -7
  39. package/dist/openai-review-oauth.js +161 -17
  40. package/dist/prompts/register-all.js +32 -78
  41. package/dist/redaction.d.ts +5 -0
  42. package/dist/redaction.js +9 -0
  43. package/dist/resources/register-all.js +150 -54
  44. package/dist/retry-after.d.ts +19 -0
  45. package/dist/retry-after.js +84 -0
  46. package/dist/server-composition.d.ts +6 -2
  47. package/dist/server-composition.js +46 -5
  48. package/dist/tool-exposure.d.ts +7 -5
  49. package/dist/tool-exposure.js +21 -13
  50. package/dist/tools/audit_gl.js +8 -8
  51. package/dist/tools/backend-availability.d.ts +8 -29
  52. package/dist/tools/backend-availability.js +31 -30
  53. package/dist/tools/bank_rules.js +66 -15
  54. package/dist/tools/banking.js +5 -13
  55. package/dist/tools/clients.js +254 -6
  56. package/dist/tools/deposits.js +107 -97
  57. package/dist/tools/einvoice.d.ts +17 -3
  58. package/dist/tools/einvoice.js +59 -12
  59. package/dist/tools/fiscal.d.ts +3 -4
  60. package/dist/tools/fiscal.js +93 -60
  61. package/dist/tools/gestoria.js +15 -7
  62. package/dist/tools/hr.js +41 -11
  63. package/dist/tools/igic.d.ts +9 -8
  64. package/dist/tools/igic.js +34 -64
  65. package/dist/tools/impuesto_sociedades.d.ts +4 -5
  66. package/dist/tools/impuesto_sociedades.js +16 -37
  67. package/dist/tools/intelligence.js +164 -10
  68. package/dist/tools/invoices.js +109 -42
  69. package/dist/tools/kitchen.js +19 -9
  70. package/dist/tools/quotes.js +86 -25
  71. package/dist/tools/recurring.js +26 -25
  72. package/dist/tools/register-all.d.ts +2 -2
  73. package/dist/tools/register-all.js +9 -6
  74. package/dist/tools/search.d.ts +19 -0
  75. package/dist/tools/search.js +84 -0
  76. package/dist/tools/shared.d.ts +18 -38
  77. package/dist/tools/shared.js +96 -30
  78. package/dist/tools/team.js +8 -16
  79. package/dist/tools/time.js +4 -12
  80. package/dist/tools/vendors.js +242 -6
  81. package/dist/types.d.ts +3 -1
  82. package/dist/workflows/daily-operational-brief.d.ts +163 -0
  83. package/dist/workflows/daily-operational-brief.js +881 -0
  84. package/docs/agent-onboarding.json +15 -5
  85. package/package.json +26 -12
  86. package/dist/tools/onboarding.d.ts +0 -19
  87. package/dist/tools/onboarding.js +0 -64
@@ -14,7 +14,12 @@ export const MCP_RESOURCE_COUNT = MCP_STATIC_RESOURCE_COUNT + MCP_DYNAMIC_RESOUR
14
14
  /* ------------------------------------------------------------------ */
15
15
  /* Static data */
16
16
  /* ------------------------------------------------------------------ */
17
- const API_SCHEMA_SUMMARY = `Frihet ERP REST API — OpenAPI 3.1
17
+ const API_SCHEMA_SUMMARY = `Frihet ERP REST API — Informational Reference Summary Snapshot
18
+ =============================================================
19
+ Notice: This resource is an informational, non-exhaustive reference snapshot
20
+ of core REST endpoints for LLM context. The canonical, authoritative live
21
+ contract is the OpenAPI 3.1 specification at https://api.frihet.io/v1/openapi.json.
22
+
18
23
  Base URL: https://api.frihet.io/v1
19
24
  Docs: https://docs.frihet.io/desarrolladores/api-reference
20
25
 
@@ -79,8 +84,18 @@ Error responses:
79
84
  Content-Type: application/json
80
85
  Monetary values use the currency declared by each record; amounts are decimal major units.
81
86
  Dates in ISO 8601 format (YYYY-MM-DD or full datetime).`;
82
- const TAX_RATES = `Spanish Tax Rates by Fiscal Zone
83
- ================================
87
+ const TAX_RATES = `Spanish Tax Rates & Fiscal Zones — Informational Reference Schedule
88
+ ===================================================================
89
+ Authority:
90
+ - Peninsula & Balearics (IVA): Ley 37/1992 del IVA (AEAT)
91
+ - Canary Islands (IGIC): Ley 20/1991 del IGIC (Agencia Tributaria Canaria)
92
+ - Ceuta & Melilla (IPSI): Ley 8/1991 del IPSI (Servicios Tributarios locales)
93
+ - Withholding (IRPF): Ley 35/2006 del IRPF (AEAT)
94
+ - EU Intra-community: Council Directive 2006/112/EC (Reverse Charge / OSS)
95
+
96
+ Notice: This resource provides reference rate schedules and classification guidelines.
97
+ It is NOT a dynamic tax engine. Transaction rates must be validated against the operation
98
+ date, place of supply, and fiscal status. Server endpoints validate fiscal rules server-side.
84
99
 
85
100
  PENINSULA & BALEARIC ISLANDS — IVA (Impuesto sobre el Valor Añadido)
86
101
  General: 21% — Most goods and services
@@ -121,8 +136,15 @@ SPECIAL REGIMES
121
136
  Applies to: Retail businesses (comercio minorista) buying from wholesalers
122
137
  Simplified regime: Fixed quarterly quotas based on activity modules
123
138
  Agriculture: 12% / 10.5% flat-rate compensation`;
124
- const TAX_CALENDAR = `Spanish Quarterly Tax Calendar
125
- ===============================
139
+ const TAX_CALENDAR = `Spanish Tax Calendar — Statutory Filing Deadlines Reference
140
+ ===========================================================
141
+ Authority: Calendario del Contribuyente (Agencia Tributaria AEAT / ATC)
142
+
143
+ Notice: Informational reference of general quarterly and annual statutory filing
144
+ deadlines in Spain. Deadlines shifting to next business day on weekends/holidays
145
+ or direct-debit (domiciliación bancaria) advance deadlines (typically 5 days earlier)
146
+ are not modeled here. Always verify filing dates with your gestoría or official
147
+ AEAT/ATC electronic office.
126
148
 
127
149
  All deadlines apply to the corresponding fiscal quarter unless noted.
128
150
 
@@ -169,8 +191,15 @@ KEY DATES SUMMARY
169
191
  Oct 20 — Q3 filings
170
192
  Jan 30 — Q4 filings + annual summaries (390, 180, 190)
171
193
  Apr 1–Jun 30 — Annual income tax (Modelo 100)`;
172
- const EXPENSE_CATEGORIES = `Frihet Expense Categories & Deductibility Rules
173
- =================================================
194
+ const EXPENSE_CATEGORIES = `Frihet Expense Categories & Statutory Deductibility Guidelines
195
+ ==============================================================
196
+ Authority: Statutory deductibility rules derived from Ley 35/2006 (IRPF, art. 30)
197
+ and Ley 37/1992 (IVA, arts. 92-96).
198
+
199
+ Notice: Frihet standard business category mapping with statutory deductibility rules.
200
+ Deductibility requires valid invoices, direct correlation with business activity
201
+ (afectación exclusiva), and accounting registration. Consult your tax advisor
202
+ (gestoría) for specific amortizations or mixed-use deductions.
174
203
 
175
204
  1. OFFICE (oficina)
176
205
  Examples: Rent, utilities, internet, phone, office supplies, cleaning
@@ -227,13 +256,25 @@ const INVOICE_STATUSES = `Frihet Invoice Status Flow
227
256
  Statuses and transitions:
228
257
 
229
258
  DRAFT ──────► SENT ──────► PAID
230
- │ │
231
- │ └──────► OVERDUE ────► PAID
232
- │ │
233
- │ └────────► CANCELLED
234
- │
259
+ │ │ ▲
260
+ │ ├──────► PARTIAL ────┤
261
+ │ │ │ │
262
+ │ └──────► OVERDUE ────┤
263
+ │ │ │
264
+ │ └────────┼───► CANCELLED
265
+ │ │
266
+ │ ▼
267
+ │ REFUND / RECTIFICATIVE
235
268
  └─────────────────────────────────► CANCELLED
236
269
 
270
+ Legal transition paths:
271
+ draft -> sent | cancelled
272
+ sent -> paid | partial | overdue | cancelled
273
+ partial -> paid | overdue | cancelled
274
+ overdue -> paid | partial | cancelled
275
+ paid -> refund (via credit note / rectificative invoice)
276
+ partial -> refund (via credit note / rectificative invoice)
277
+
237
278
  Status definitions:
238
279
 
239
280
  draft — Invoice created but not yet sent to client.
@@ -244,31 +285,40 @@ Status definitions:
244
285
  Payment is expected. The invoice number and date become
245
286
  fiscally relevant — avoid modifications after this point.
246
287
 
247
- paid — Payment received in full. Terminal state.
288
+ partial — Invoice partially paid. A partial payment has been recorded,
289
+ with a remaining positive balance due. Can transition to
290
+ paid upon full payment, overdue if dueDate passes with
291
+ remaining balance, or cancelled/credit note.
292
+
293
+ paid — Payment received in full. Terminal state for settlement.
248
294
  Records the payment date. Invoice is complete.
249
295
 
250
- overdue — Payment deadline (dueDate) has passed without payment.
251
- Triggers follow-up workflows. Can transition to paid
252
- (late payment) or cancelled (write-off / bad debt).
296
+ overdue — Payment deadline (dueDate) has passed without full payment.
297
+ Applies to sent or partial invoices. Triggers follow-up
298
+ workflows. Can transition to paid (late payment), partial,
299
+ or cancelled (write-off / bad debt).
253
300
 
254
301
  cancelled — Invoice voided. Requires a corrective invoice or
255
302
  credit note for fiscal compliance if previously sent.
256
303
  Terminal state. Cannot transition to other statuses.
257
304
 
258
305
  Automation rules (when configured):
259
- - Auto-transition sent → overdue when dueDate passes (daily check)
260
- - Webhook events: invoice.created, invoice.sent, invoice.paid,
261
- invoice.overdue, invoice.cancelled
306
+ - Auto-transition sent/partial → overdue when dueDate passes (daily check)
307
+ - Webhook events: invoice.created, invoice.updated, invoice.generated,
308
+ invoice.one_off_created, invoice.paid, invoice.overdue, invoice.voided,
309
+ invoice.payment_status_updated, invoice.payment_failure
310
+ - A partial payment is reported via invoice.payment_status_updated;
311
+ cancellation is reported via invoice.voided
262
312
  - Overdue reminders can be configured per-client
263
313
 
264
314
  Best practices:
265
315
  - Always set a dueDate when creating invoices (default: 30 days)
266
316
  - Use draft status while iterating with the client
267
317
  - Once sent, create a new corrective invoice rather than editing
268
- - For partial payments, keep status as sent until full payment
318
+ - For partial payments, invoice transitions to partial with remaining balance tracked
269
319
  - cancelled requires a reason (notes field) for audit trail`;
270
320
  const CURRENCIES = JSON.stringify({
271
- EUR: { name: "Euro", symbol: "\u20ac", decimals: 2, format: "1.234,56 \u20ac", countries: ["ES", "DE", "FR", "IT", "NL", "PT", "BE", "AT", "IE", "FI", "GR", "LU", "SK", "SI", "EE", "LV", "LT", "CY", "MT"] },
321
+ EUR: { name: "Euro", symbol: "\u20ac", decimals: 2, format: "1.234,56 \u20ac", countries: ["ES", "DE", "FR", "IT", "NL", "PT", "BE", "AT", "IE", "FI", "GR", "LU", "SK", "SI", "EE", "LV", "LT", "CY", "MT", "BG", "HR"] },
272
322
  USD: { name: "US Dollar", symbol: "$", decimals: 2, format: "$1,234.56", countries: ["US"] },
273
323
  GBP: { name: "British Pound", symbol: "\u00a3", decimals: 2, format: "\u00a31,234.56", countries: ["GB"] },
274
324
  CHF: { name: "Swiss Franc", symbol: "CHF", decimals: 2, format: "CHF 1'234.56", countries: ["CH"] },
@@ -284,8 +334,8 @@ const CURRENCIES = JSON.stringify({
284
334
  CZK: { name: "Czech Koruna", symbol: "K\u010d", decimals: 2, format: "1 234,56 K\u010d", countries: ["CZ"] },
285
335
  HUF: { name: "Hungarian Forint", symbol: "Ft", decimals: 0, format: "1 234 Ft", countries: ["HU"] },
286
336
  RON: { name: "Romanian Leu", symbol: "lei", decimals: 2, format: "1.234,56 lei", countries: ["RO"] },
287
- BGN: { name: "Bulgarian Lev", symbol: "\u043b\u0432", decimals: 2, format: "1 234,56 \u043b\u0432", countries: ["BG"] },
288
- HRK: { name: "Croatian Kuna", symbol: "kn", decimals: 2, format: "1.234,56 kn", countries: ["HR"] },
337
+ BGN: { name: "Bulgarian Lev", symbol: "\u043b\u0432", decimals: 2, format: "1 234,56 \u043b\u0432", countries: [] },
338
+ HRK: { name: "Croatian Kuna", symbol: "kn", decimals: 2, format: "1.234,56 kn", countries: [] },
289
339
  ISK: { name: "Icelandic Kr\u00f3na", symbol: "kr", decimals: 0, format: "1.234 kr", countries: ["IS"] },
290
340
  TRY: { name: "Turkish Lira", symbol: "\u20ba", decimals: 2, format: "\u20ba1.234,56", countries: ["TR"] },
291
341
  ILS: { name: "Israeli Shekel", symbol: "\u20aa", decimals: 2, format: "\u20aa1,234.56", countries: ["IL"] },
@@ -322,12 +372,12 @@ const COUNTRIES = JSON.stringify([
322
372
  { name: "Portugal", code: "PT", fiscalZone: "eu", defaultTaxRate: 23, taxName: "IVA", currency: "EUR", invoicePrefix: "FT" },
323
373
  { name: "Austria", code: "AT", fiscalZone: "eu", defaultTaxRate: 20, taxName: "USt", currency: "EUR", invoicePrefix: "RE" },
324
374
  { name: "Ireland", code: "IE", fiscalZone: "eu", defaultTaxRate: 23, taxName: "VAT", currency: "EUR", invoicePrefix: "INV" },
325
- { name: "Finland", code: "FI", fiscalZone: "eu", defaultTaxRate: 24, taxName: "ALV", currency: "EUR", invoicePrefix: "INV" },
375
+ { name: "Finland", code: "FI", fiscalZone: "eu", defaultTaxRate: 25.5, taxName: "ALV", currency: "EUR", invoicePrefix: "INV" },
326
376
  { name: "Greece", code: "GR", fiscalZone: "eu", defaultTaxRate: 24, taxName: "FPA", currency: "EUR", invoicePrefix: "TIM" },
327
377
  { name: "Luxembourg", code: "LU", fiscalZone: "eu", defaultTaxRate: 17, taxName: "TVA", currency: "EUR", invoicePrefix: "F" },
328
- { name: "Slovakia", code: "SK", fiscalZone: "eu", defaultTaxRate: 20, taxName: "DPH", currency: "EUR", invoicePrefix: "F" },
378
+ { name: "Slovakia", code: "SK", fiscalZone: "eu", defaultTaxRate: 23, taxName: "DPH", currency: "EUR", invoicePrefix: "F" },
329
379
  { name: "Slovenia", code: "SI", fiscalZone: "eu", defaultTaxRate: 22, taxName: "DDV", currency: "EUR", invoicePrefix: "F" },
330
- { name: "Estonia", code: "EE", fiscalZone: "eu", defaultTaxRate: 22, taxName: "KM", currency: "EUR", invoicePrefix: "INV" },
380
+ { name: "Estonia", code: "EE", fiscalZone: "eu", defaultTaxRate: 24, taxName: "KM", currency: "EUR", invoicePrefix: "INV" },
331
381
  { name: "Latvia", code: "LV", fiscalZone: "eu", defaultTaxRate: 21, taxName: "PVN", currency: "EUR", invoicePrefix: "INV" },
332
382
  { name: "Lithuania", code: "LT", fiscalZone: "eu", defaultTaxRate: 21, taxName: "PVM", currency: "EUR", invoicePrefix: "SF" },
333
383
  { name: "Cyprus", code: "CY", fiscalZone: "eu", defaultTaxRate: 19, taxName: "VAT", currency: "EUR", invoicePrefix: "INV" },
@@ -337,8 +387,8 @@ const COUNTRIES = JSON.stringify([
337
387
  { name: "Poland", code: "PL", fiscalZone: "eu", defaultTaxRate: 23, taxName: "VAT", currency: "PLN", invoicePrefix: "FV" },
338
388
  { name: "Czech Republic", code: "CZ", fiscalZone: "eu", defaultTaxRate: 21, taxName: "DPH", currency: "CZK", invoicePrefix: "F" },
339
389
  { name: "Hungary", code: "HU", fiscalZone: "eu", defaultTaxRate: 27, taxName: "\u00c1FA", currency: "HUF", invoicePrefix: "SZ" },
340
- { name: "Romania", code: "RO", fiscalZone: "eu", defaultTaxRate: 19, taxName: "TVA", currency: "RON", invoicePrefix: "F" },
341
- { name: "Bulgaria", code: "BG", fiscalZone: "eu", defaultTaxRate: 20, taxName: "DDS", currency: "BGN", invoicePrefix: "F" },
390
+ { name: "Romania", code: "RO", fiscalZone: "eu", defaultTaxRate: 21, taxName: "TVA", currency: "RON", invoicePrefix: "F" },
391
+ { name: "Bulgaria", code: "BG", fiscalZone: "eu", defaultTaxRate: 20, taxName: "DDS", currency: "EUR", invoicePrefix: "F" },
342
392
  { name: "Croatia", code: "HR", fiscalZone: "eu", defaultTaxRate: 25, taxName: "PDV", currency: "EUR", invoicePrefix: "R" },
343
393
  { name: "United Kingdom", code: "GB", fiscalZone: "international", defaultTaxRate: 20, taxName: "VAT", currency: "GBP", invoicePrefix: "INV" },
344
394
  { name: "United States", code: "US", fiscalZone: "international", defaultTaxRate: 0, taxName: "Sales Tax", currency: "USD", invoicePrefix: "INV" },
@@ -377,8 +427,9 @@ const COUNTRIES = JSON.stringify([
377
427
  /* ------------------------------------------------------------------ */
378
428
  export function registerAllResources(server, client) {
379
429
  server.registerResource("api-schema", "frihet://api/schema", {
380
- description: "Selected core REST endpoint summary with authentication, rate limits, pagination, and error codes. " +
381
- "/ Resumen de endpoints REST principales, autenticación, límites, paginación y errores.",
430
+ description: "Informational snapshot summary of selected core REST endpoints, auth, and rate limits. " +
431
+ "Canonical contract: https://api.frihet.io/v1/openapi.json. " +
432
+ "/ Resumen informativo no exhaustivo de endpoints REST principales; el contrato canónico reside en openapi.json.",
382
433
  mimeType: "text/plain",
383
434
  }, async () => ({
384
435
  contents: [
@@ -390,9 +441,9 @@ export function registerAllResources(server, client) {
390
441
  ],
391
442
  }));
392
443
  server.registerResource("tax-rates", "frihet://tax/rates", {
393
- description: "Current tax rates by Spanish fiscal zone: Peninsula IVA (21/10/4%), Canary Islands IGIC (7/3/0%), " +
394
- "Ceuta IPSI, EU reverse charge, international exports, IRPF withholding, and special regimes. " +
395
- "/ Tipos impositivos por zona fiscal: IVA, IGIC, IPSI, intracomunitario, exportaciones, IRPF, regímenes especiales.",
444
+ description: "Spanish statutory tax rate reference (AEAT/ATC/IPSI, IVA/IGIC/IPSI/IRPF). " +
445
+ "Informational reference schedule; server validates tax math per transaction. " +
446
+ "/ Referencia normativa de tipos impositivos por zona fiscal en España.",
396
447
  mimeType: "text/plain",
397
448
  }, async () => ({
398
449
  contents: [
@@ -404,7 +455,8 @@ export function registerAllResources(server, client) {
404
455
  ],
405
456
  }));
406
457
  server.registerResource("tax-calendar", "frihet://tax/calendar", {
407
- description: "Spanish quarterly tax calendar with filing deadlines for Modelo 303, 130, 390, 420 (IGIC), and annual returns. " +
458
+ description: "Spanish tax filing calendar reference (Calendario del Contribuyente AEAT/ATC). " +
459
+ "Informational reference for statutory filing deadlines. " +
408
460
  "/ Calendario fiscal trimestral español con plazos de presentación de modelos.",
409
461
  mimeType: "text/plain",
410
462
  }, async () => ({
@@ -417,8 +469,8 @@ export function registerAllResources(server, client) {
417
469
  ],
418
470
  }));
419
471
  server.registerResource("expense-categories", "frihet://config/expense-categories", {
420
- description: "The 8 expense categories in Frihet with deductibility rules, IVA treatment, and amortization periods. " +
421
- "Essential for correctly categorizing business expenses. " +
472
+ description: "Frihet standard expense categories and statutory deductibility guidelines (LIRPF/LIVA). " +
473
+ "Informational classification guide. " +
422
474
  "/ Las 8 categorías de gastos con reglas de deducibilidad, IVA y amortización.",
423
475
  mimeType: "text/plain",
424
476
  }, async () => ({
@@ -445,9 +497,9 @@ export function registerAllResources(server, client) {
445
497
  ],
446
498
  }));
447
499
  server.registerResource("currencies", "frihet://config/currencies", {
448
- description: "40 supported currencies with ISO codes, symbols, decimal places, locale formatting examples, and associated countries. " +
449
- "Use to validate currency inputs and format monetary values. " +
450
- "/ 40 divisas soportadas con códigos ISO, símbolos, decimales, formato local y países asociados.",
500
+ description: "Currency codes, symbols and display formatting examples with associated countries, including historical codes. " +
501
+ "Decimals describe these display examples, not authoritative payment precision, conversion rates or API acceptance. " +
502
+ "/ Códigos y ejemplos de formato de divisas; no determinan precisión de pagos ni tipos de cambio.",
451
503
  mimeType: "application/json",
452
504
  }, async () => ({
453
505
  contents: [
@@ -459,9 +511,9 @@ export function registerAllResources(server, client) {
459
511
  ],
460
512
  }));
461
513
  server.registerResource("countries", "frihet://config/countries", {
462
- description: "61 supported countries with ISO codes, fiscal zones (peninsula, canarias, ceuta, melilla, eu, international), " +
463
- "default tax rates, tax names, default currencies, and invoice prefixes. " +
464
- "/ 61 países soportados con zonas fiscales, tipos impositivos, divisas y prefijos de factura.",
514
+ description: "Supported countries with ISO codes, fiscal zones (peninsula, canarias, ceuta, melilla, eu, international), " +
515
+ "default tax rates, tax names, default currencies, and invoice prefixes. Note: default rates are onboarding suggestions only, not legal determination. " +
516
+ "/ Países soportados con zonas fiscales, tipos impositivos por defecto y prefijos de factura.",
465
517
  mimeType: "application/json",
466
518
  }, async () => ({
467
519
  contents: [
@@ -511,33 +563,77 @@ export function registerAllResources(server, client) {
511
563
  };
512
564
  });
513
565
  server.registerResource("overdue-invoices", "frihet://overdue-invoices", {
514
- description: "Live list of all overdue invoices — invoices past their due date that haven't been paid. " +
515
- "Includes client names, amounts, due dates, and days overdue. Critical for cash flow management. " +
516
- "/ Lista en vivo de facturas vencidas — facturas cuya fecha de vencimiento ha pasado sin cobrar.",
566
+ description: "Live invoice records filtered by status overdue (first page up to 100 items). " +
567
+ "Returns stored fields and pagination bounds; does not calculate unpaid balances or days overdue. " +
568
+ "Paginate list_invoices without a status filter and select sent, partial and overdue records client-side for a complete receivables review. " +
569
+ "/ Registros con estado vencida (primera página hasta 100); no calcula saldos pendientes ni días de retraso.",
517
570
  mimeType: "application/json",
518
571
  }, async () => {
519
- const data = await client.listInvoices({ status: "overdue", limit: 100 });
572
+ const response = await client.listInvoices({ status: "overdue", limit: 100 });
573
+ const list = Array.isArray(response)
574
+ ? response
575
+ : Array.isArray(response.data)
576
+ ? (response.data)
577
+ : [];
578
+ const total = typeof response.total === "number"
579
+ ? (response.total)
580
+ : list.length;
581
+ const result = {
582
+ data: list,
583
+ total,
584
+ limit: 100,
585
+ offset: 0,
586
+ hasMore: total > 100 || list.length === 100,
587
+ note: "First 100 records with status overdue; not a complete receivables or unpaid-balance report",
588
+ };
520
589
  return {
521
590
  contents: [
522
591
  {
523
592
  uri: "frihet://overdue-invoices",
524
593
  mimeType: "application/json",
525
- text: JSON.stringify(data, null, 2),
594
+ text: JSON.stringify(result, null, 2),
526
595
  },
527
596
  ],
528
597
  };
529
598
  });
530
599
  server.registerResource("plan-limits", "frihet://status/plan-limits", {
531
- description: "Live plan limits and current usage — plan tier, invoices/month, AI messages/day, team members, " +
532
- "integrations, API requests/minute, and real-time usage counters. " +
533
- "/ Límites del plan y uso actual en vivo — tier, facturas/mes, mensajes IA/día, miembros, integraciones, API req/min.",
600
+ description: "Live plan tier, quotas, and usage counters (invoices, expenses, AI messages) from business context. " +
601
+ "/ Nivel de plan, cuotas y contadores de uso (facturas, gastos, mensajes IA) del contexto del negocio.",
534
602
  mimeType: "application/json",
535
603
  }, async () => {
536
604
  const ctx = await client.getBusinessContext();
537
- const plan = ctx.plan ?? "free";
538
- const limits = ctx.limits ?? {};
539
- const usage = ctx.usage ?? {};
540
- const result = { plan, limits, usage };
605
+ const planRaw = ctx.plan;
606
+ let plan = "free";
607
+ let limits = ctx.limits;
608
+ let usage = ctx.usage;
609
+ if (typeof planRaw === "string") {
610
+ plan = planRaw;
611
+ }
612
+ else if (typeof planRaw === "object" && planRaw !== null) {
613
+ const planObj = planRaw;
614
+ plan = typeof planObj.name === "string" ? planObj.name : "free";
615
+ if ((!limits || Object.keys(limits).length === 0) && (!usage || Object.keys(usage).length === 0)) {
616
+ const extractedLimits = {};
617
+ const extractedUsage = {};
618
+ for (const [key, val] of Object.entries(planObj)) {
619
+ if (key !== "name" && typeof val === "object" && val !== null) {
620
+ const item = val;
621
+ if ("limit" in item)
622
+ extractedLimits[key] = item.limit;
623
+ if ("used" in item)
624
+ extractedUsage[key] = item.used;
625
+ }
626
+ }
627
+ limits = extractedLimits;
628
+ usage = extractedUsage;
629
+ }
630
+ }
631
+ const result = {
632
+ plan,
633
+ limits: limits ?? {},
634
+ usage: usage ?? {},
635
+ ...(typeof planRaw === "object" && planRaw !== null ? { breakdown: planRaw } : {}),
636
+ };
541
637
  return {
542
638
  contents: [
543
639
  {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `Retry-After` parsing per RFC 9110 §10.2.3: either `delay-seconds`
3
+ * (1*DIGIT) or an HTTP-date. Returns the wait in milliseconds, or `null` when
4
+ * the value is absent or malformed so the caller falls back to its own
5
+ * backoff — never NaN, never negative.
6
+ *
7
+ * Semantics:
8
+ * - delay-seconds: strict non-negative integer digits only (no sign, decimal
9
+ * point, exponent or whitespace inside). Values too large to represent
10
+ * safely are clamped to MAX_SAFE_INTEGER ms, which always exceeds any retry
11
+ * budget and therefore defers instead of sleeping.
12
+ * - HTTP-date: IMF-fixdate, obsolete RFC 850 and ANSI C asctime() forms, all
13
+ * read as GMT. The wait is `date - now`; a date already in the past means
14
+ * the server's embargo has expired, so the wait is 0 (not negative). Clock
15
+ * skew is NOT compensated: shortening the wait would retry earlier than the
16
+ * server asked, lengthening it would hide a valid answer.
17
+ */
18
+ export declare function parseRetryAfter(value: string | null | undefined, nowMs: number): number | null;
19
+ //# sourceMappingURL=retry-after.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `Retry-After` parsing per RFC 9110 §10.2.3: either `delay-seconds`
3
+ * (1*DIGIT) or an HTTP-date. Returns the wait in milliseconds, or `null` when
4
+ * the value is absent or malformed so the caller falls back to its own
5
+ * backoff — never NaN, never negative.
6
+ *
7
+ * Semantics:
8
+ * - delay-seconds: strict non-negative integer digits only (no sign, decimal
9
+ * point, exponent or whitespace inside). Values too large to represent
10
+ * safely are clamped to MAX_SAFE_INTEGER ms, which always exceeds any retry
11
+ * budget and therefore defers instead of sleeping.
12
+ * - HTTP-date: IMF-fixdate, obsolete RFC 850 and ANSI C asctime() forms, all
13
+ * read as GMT. The wait is `date - now`; a date already in the past means
14
+ * the server's embargo has expired, so the wait is 0 (not negative). Clock
15
+ * skew is NOT compensated: shortening the wait would retry earlier than the
16
+ * server asked, lengthening it would hide a valid answer.
17
+ */
18
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
19
+ const DAYS_LONG = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
20
+ const DAYS_SHORT = DAYS_LONG.map((d) => d.slice(0, 3));
21
+ const IMF_FIXDATE = /^([A-Z][a-z]{2}), (\d{2}) ([A-Z][a-z]{2}) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/;
22
+ const RFC850_DATE = /^([A-Z][a-z]+day), (\d{2})-([A-Z][a-z]{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/;
23
+ const ASCTIME_DATE = /^([A-Z][a-z]{2}) ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/;
24
+ function utcMs(year, monthName, day, h, m, s) {
25
+ const month = MONTHS.indexOf(monthName);
26
+ if (month < 0 || h > 23 || m > 59 || s > 60)
27
+ return null;
28
+ const ms = Date.UTC(year, month, day, h, m, s);
29
+ const check = new Date(ms);
30
+ // Reject roll-over such as "31 Feb" that Date.UTC silently normalizes.
31
+ if (check.getUTCFullYear() !== year
32
+ || check.getUTCMonth() !== month
33
+ || check.getUTCDate() !== day) {
34
+ return null;
35
+ }
36
+ return ms;
37
+ }
38
+ function weekdayMatches(ms, name, list) {
39
+ // getUTCDay: 0 = Sunday; the lists start on Monday.
40
+ return list[(new Date(ms).getUTCDay() + 6) % 7] === name;
41
+ }
42
+ function parseHttpDate(value, nowMs) {
43
+ let m = IMF_FIXDATE.exec(value);
44
+ if (m) {
45
+ const ms = utcMs(+m[4], m[3], +m[2], +m[5], +m[6], +m[7]);
46
+ return ms !== null && weekdayMatches(ms, m[1], DAYS_SHORT) ? ms : null;
47
+ }
48
+ m = RFC850_DATE.exec(value);
49
+ if (m) {
50
+ // RFC 9110 §5.6.7: a two-digit year more than 50 years ahead is the past century.
51
+ const yy = +m[4];
52
+ const nowYear = new Date(nowMs).getUTCFullYear();
53
+ let year = Math.floor(nowYear / 100) * 100 + yy;
54
+ if (year > nowYear + 50)
55
+ year -= 100;
56
+ const ms = utcMs(year, m[3], +m[2], +m[5], +m[6], +m[7]);
57
+ return ms !== null && weekdayMatches(ms, m[1], DAYS_LONG) ? ms : null;
58
+ }
59
+ m = ASCTIME_DATE.exec(value);
60
+ if (m) {
61
+ const ms = utcMs(+m[7], m[2], +m[3].trim(), +m[4], +m[5], +m[6]);
62
+ return ms !== null && weekdayMatches(ms, m[1], DAYS_SHORT) ? ms : null;
63
+ }
64
+ return null;
65
+ }
66
+ export function parseRetryAfter(value, nowMs) {
67
+ if (value === null || value === undefined)
68
+ return null;
69
+ const v = value.trim();
70
+ if (v === "")
71
+ return null;
72
+ if (/^\d+$/.test(v)) {
73
+ const seconds = Number(v);
74
+ if (!Number.isFinite(seconds) || seconds * 1000 > Number.MAX_SAFE_INTEGER) {
75
+ return Number.MAX_SAFE_INTEGER;
76
+ }
77
+ return seconds * 1000;
78
+ }
79
+ const dateMs = parseHttpDate(v, nowMs);
80
+ if (dateMs === null)
81
+ return null;
82
+ return Math.max(0, dateMs - nowMs);
83
+ }
84
+ //# sourceMappingURL=retry-after.js.map
@@ -9,8 +9,12 @@ export interface McpSurfaceComposition {
9
9
  readonly openaiMode: boolean;
10
10
  readonly groupedMode: boolean;
11
11
  readonly includeDynamicResources: boolean;
12
+ readonly claudeMode?: boolean;
13
+ readonly googleMode?: boolean;
14
+ readonly museMode?: boolean;
15
+ readonly demoMode?: boolean;
12
16
  }
13
- export declare function localMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean): McpSurfaceComposition;
14
- export declare function remoteMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean): McpSurfaceComposition;
17
+ export declare function localMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean, claudeMode?: boolean, googleMode?: boolean, museMode?: boolean): McpSurfaceComposition;
18
+ export declare function remoteMcpSurfaceComposition(openaiMode: boolean, groupedMode: boolean, claudeMode?: boolean, googleMode?: boolean, museMode?: boolean): McpSurfaceComposition;
15
19
  export declare function registerMcpSurface(server: McpServer, client: IFrihetClient, options: McpSurfaceComposition): void;
16
20
  //# sourceMappingURL=server-composition.d.ts.map
@@ -5,37 +5,78 @@
5
5
  */
6
6
  import { applyPublicCapabilityTruth, buildLocalDiscoveryCapability, CAPABILITY_META_KEY, } from "./capability-truth.js";
7
7
  import { applyFiscalAliases } from "./fiscal-aliases.js";
8
+ import { applyClaudeCandidateProfile, CLAUDE_CANDIDATE_TOOLS, CLAUDE_GROUP_METADATA } from "./claude-profile.js";
9
+ import { applyGoogleProfile } from "./google-profile.js";
10
+ import { applyMuseProfile } from "./muse-profile.js";
8
11
  import { applyOpenAIProfile, applyOpenAIReviewProfiles, } from "./openai-profile.js";
9
12
  import { registerAllPrompts } from "./prompts/register-all.js";
10
13
  import { registerAllResources } from "./resources/register-all.js";
11
14
  import { applyToolExposureProfile } from "./tool-exposure.js";
12
15
  import { registerAllTools } from "./tools/register-all.js";
13
- export function localMcpSurfaceComposition(openaiMode, groupedMode) {
14
- return { openaiMode, groupedMode, includeDynamicResources: true };
16
+ export function localMcpSurfaceComposition(openaiMode, groupedMode, claudeMode = false, googleMode = false, museMode = false) {
17
+ return { openaiMode, groupedMode, includeDynamicResources: true, claudeMode, googleMode, museMode };
15
18
  }
16
- export function remoteMcpSurfaceComposition(openaiMode, groupedMode) {
17
- return { openaiMode, groupedMode, includeDynamicResources: false };
19
+ export function remoteMcpSurfaceComposition(openaiMode, groupedMode, claudeMode = false, googleMode = false, museMode = false) {
20
+ return { openaiMode, groupedMode, includeDynamicResources: false, claudeMode, googleMode, museMode };
18
21
  }
19
22
  export function registerMcpSurface(server, client, options) {
23
+ const activeModes = [options.openaiMode, options.claudeMode, options.googleMode, options.museMode].filter(Boolean).length;
24
+ if (activeModes > 1) {
25
+ throw new Error("FRIHET_OPENAI_MODE, FRIHET_CLAUDE_MODE, FRIHET_GOOGLE_MODE, and FRIHET_MUSE_MODE are mutually exclusive");
26
+ }
27
+ if (options.claudeMode) {
28
+ // Directory reads annotations.title. Install before grouped discovery eagerly
29
+ // registers its meta-tools, and clone annotations to preserve shared hints.
30
+ const registerTool = server.registerTool.bind(server);
31
+ server.registerTool = (name, config, handler) => registerTool(name, {
32
+ ...config,
33
+ annotations: { ...config.annotations, title: config.title },
34
+ }, handler);
35
+ }
36
+ let clientIsDemo = false;
37
+ try {
38
+ clientIsDemo =
39
+ client?.isDemoClient === true ||
40
+ client?.constructor?.name === "DemoFrihetClient";
41
+ }
42
+ catch {
43
+ clientIsDemo = false;
44
+ }
45
+ const isDemoMode = options.demoMode === true || clientIsDemo;
20
46
  if (options.groupedMode) {
21
47
  if (options.openaiMode) {
22
48
  applyOpenAIReviewProfiles(server);
23
49
  }
24
50
  else {
25
51
  applyToolExposureProfile(server, {
52
+ ...(options.claudeMode ? {
53
+ allowlist: CLAUDE_CANDIDATE_TOOLS,
54
+ groupMetadata: CLAUDE_GROUP_METADATA,
55
+ describeToolExample: "list_invoices",
56
+ } : {}),
26
57
  capabilityTruth: {
27
58
  metaKey: CAPABILITY_META_KEY,
28
59
  localDiscovery: buildLocalDiscoveryCapability,
29
60
  },
61
+ demoMode: isDemoMode,
30
62
  });
31
63
  }
32
64
  }
33
65
  else if (options.openaiMode) {
34
66
  applyOpenAIProfile(server);
35
67
  }
36
- if (!options.openaiMode) {
68
+ if (!options.openaiMode && !options.googleMode && !options.museMode) {
37
69
  applyPublicCapabilityTruth(server);
38
70
  }
71
+ if (options.claudeMode) {
72
+ applyClaudeCandidateProfile(server);
73
+ }
74
+ else if (options.googleMode) {
75
+ applyGoogleProfile(server);
76
+ }
77
+ else if (options.museMode) {
78
+ applyMuseProfile(server);
79
+ }
39
80
  registerAllTools(server, client);
40
81
  applyFiscalAliases(server);
41
82
  registerAllResources(server, options.includeDynamicResources ? client : undefined);
@@ -6,7 +6,7 @@
6
6
  * the outer public composition may still attach capability truth metadata.
7
7
  *
8
8
  * ── Why ──────────────────────────────────────────────────────────────────
9
- * Context rot is the 2026 problem: a flat list of 157 tool descriptions,
9
+ * Context rot is the 2026 problem: a flat list of 158 tool descriptions,
10
10
  * each a multi-paragraph bilingual blob, eats the agent's context window and
11
11
  * degrades tool selection before any work begins. Leaders cut flat lists.
12
12
  *
@@ -30,12 +30,12 @@
30
30
  * • search_tools(query) — fuzzy match → matching tool summaries
31
31
  * • describe_tool(name) — full original description + input fields
32
32
  *
33
- * The agent loads ~3 meta-tool descriptions + 157 terse one-liners instead of
34
- * 157 full bilingual blobs, then pulls full depth only for the handful of
33
+ * The agent loads ~3 meta-tool descriptions + 158 terse one-liners instead of
34
+ * 158 full bilingual blobs, then pulls full depth only for the handful of
35
35
  * tools it actually needs. Progressive disclosure, zero behavior change.
36
36
  *
37
37
  * IMPORTANT: this is purely an EXPOSURE layer. It does NOT live in
38
- * src/tools/*.ts, so the audited tool count stays 157 (+ meta). The meta-tools
38
+ * src/tools/*.ts, so the audited tool count stays 158 (+ meta). The meta-tools
39
39
  * are added only in grouped mode and are NOT counted as ERP tools.
40
40
  *
41
41
  * @see ./openai-profile.ts — the sibling interceptor this mirrors.
@@ -64,7 +64,7 @@ export declare const GROUPS: Record<ToolGroupId, GroupMeta>;
64
64
  export declare const FILE_TO_GROUP: Record<string, ToolGroupId>;
65
65
  /**
66
66
  * Assign a group by tool NAME. The name-based mapping reproduces the
67
- * source-file grouping exactly for all 157 current tools (verified), with the
67
+ * source-file grouping exactly for every current tool (verified), with the
68
68
  * eight cross-file cases pinned via NAME_OVERRIDES. Driven off the name (not a
69
69
  * hand-kept list) so a future tool lands somewhere sensible automatically.
70
70
  *
@@ -132,6 +132,7 @@ export declare function resolveToolMode(env?: Record<string, string | undefined>
132
132
  export declare function applyToolExposureProfile(server: any, options?: {
133
133
  allowlist?: ReadonlySet<string>;
134
134
  groupMetadata?: Partial<Record<ToolGroupId, GroupMeta>>;
135
+ describeToolExample?: string;
135
136
  capabilityTruth?: {
136
137
  metaKey: string;
137
138
  localDiscovery: (name: string) => object;
@@ -140,6 +141,7 @@ export declare function applyToolExposureProfile(server: any, options?: {
140
141
  type: "oauth2";
141
142
  scopes: readonly string[];
142
143
  }>;
144
+ demoMode?: boolean;
143
145
  }): ToolExposureHandle;
144
146
  /** Number of meta-tools added in grouped mode (for logging). */
145
147
  export declare const GROUPED_META_TOOL_COUNT: number;