@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
@@ -19,9 +19,10 @@
19
19
  */
20
20
  import type { IFrihetClient } from "./client-interface.js";
21
21
  import type { CreateWebhookInput, CreateWebhookResult, PaginatedResponse, UpdateWebhookInput, Webhook } from "./types.js";
22
- import type { BinaryDocument, EInvoiceDocument } from "./client.js";
22
+ import { type BinaryDocument, type EInvoiceDocument, type EInvoiceExportResult, type McpEInvoiceExportFormat } from "./client.js";
23
23
  type Rec = Record<string, unknown>;
24
24
  export declare class DemoFrihetClient implements IFrihetClient {
25
+ readonly isDemoClient: true;
25
26
  listInvoices(params?: {
26
27
  limit?: number;
27
28
  offset?: number;
@@ -99,6 +100,21 @@ export declare class DemoFrihetClient implements IFrihetClient {
99
100
  createWebhook(data: CreateWebhookInput): Promise<CreateWebhookResult>;
100
101
  updateWebhook(id: string, data: UpdateWebhookInput): Promise<Webhook>;
101
102
  deleteWebhook(_id: string): Promise<void>;
103
+ globalSearch(params: {
104
+ q: string;
105
+ types?: ReadonlyArray<"invoices" | "expenses" | "vendors" | "clients" | "products">;
106
+ limit?: number;
107
+ offset?: number;
108
+ }): Promise<{
109
+ data: Record<string, unknown>[];
110
+ total: number;
111
+ limit: number;
112
+ offset: number;
113
+ hasMore: boolean;
114
+ query: string;
115
+ types: ReadonlyArray<"invoices" | "expenses" | "vendors" | "clients" | "products">;
116
+ truncated?: boolean;
117
+ }>;
102
118
  listClientContacts(_clientId: string, params?: {
103
119
  limit?: number;
104
120
  offset?: number;
@@ -173,14 +189,9 @@ export declare class DemoFrihetClient implements IFrihetClient {
173
189
  }>;
174
190
  exportEInvoice(params: {
175
191
  invoiceId: string;
176
- format: string;
192
+ format: McpEInvoiceExportFormat;
177
193
  signed?: boolean;
178
- }): Promise<{
179
- xmlUrl: string;
180
- filename: string;
181
- format: string;
182
- signed: boolean;
183
- }>;
194
+ }): Promise<EInvoiceExportResult>;
184
195
  faceSubmit(params: {
185
196
  invoiceId: string;
186
197
  mode: "mock" | "sandbox" | "production";
@@ -314,7 +325,7 @@ export declare class DemoFrihetClient implements IFrihetClient {
314
325
  updateTeamMemberRole(memberId: string, role: string): Promise<Rec>;
315
326
  removeTeamMember(_memberId: string): Promise<void>;
316
327
  approveGLEntry(entryId: string, notes?: string): Promise<Rec>;
317
- rejectGLEntry(entryId: string, reason: string): Promise<Rec>;
328
+ rejectGLEntry(entryId: string, note: string): Promise<Rec>;
318
329
  getGLEntryAuditLog(entryId: string): Promise<Rec>;
319
330
  addCustomPortalDomain(data: {
320
331
  domain: string;
@@ -355,15 +366,13 @@ export declare class DemoFrihetClient implements IFrihetClient {
355
366
  }): Promise<PaginatedResponse<Rec>>;
356
367
  createBankRule(data: {
357
368
  name: string;
358
- conditions: Array<{
369
+ bankConditions: Array<{
359
370
  field: string;
360
371
  operator: string;
361
372
  value: string;
362
373
  }>;
363
- actions: Array<{
364
- type: string;
365
- value: string;
366
- }>;
374
+ action: string;
375
+ actionConfig: Record<string, unknown>;
367
376
  isActive?: boolean;
368
377
  }): Promise<Rec>;
369
378
  sendGestoriaMessage(data: {
@@ -406,7 +415,9 @@ export declare class DemoFrihetClient implements IFrihetClient {
406
415
  };
407
416
  }): Promise<Rec>;
408
417
  getGestoriaAgingConsolidated(_params?: {
409
- ownerUid?: string;
418
+ workspaceIds?: string[];
419
+ asOf?: string;
420
+ bustCache?: boolean;
410
421
  }): Promise<Rec>;
411
422
  listLeaves(params?: {
412
423
  limit?: number;
@@ -430,8 +441,8 @@ export declare class DemoFrihetClient implements IFrihetClient {
430
441
  employeeId: string;
431
442
  mood?: string;
432
443
  location?: string;
433
- }): Promise<Rec>;
434
- attendanceClockOut(entryId: string): Promise<Rec>;
444
+ }, _idempotencyKey?: string): Promise<Rec>;
445
+ attendanceClockOut(entryId: string, _idempotencyKey?: string): Promise<Rec>;
435
446
  getOvertimeReport(params: {
436
447
  period: string;
437
448
  employeeId?: string;
@@ -450,10 +461,6 @@ export declare class DemoFrihetClient implements IFrihetClient {
450
461
  getPayrollChecklist(params: {
451
462
  month: string;
452
463
  }): Promise<Rec>;
453
- getOnboardingStatus(): Promise<Rec>;
454
- setOnboardingPersona(data: {
455
- persona: "autonomo" | "empresa" | "agencia" | "gestoria";
456
- }): Promise<Rec>;
457
464
  getPermissionsMatrix(): Promise<Rec>;
458
465
  getMyPermissions(): Promise<Rec>;
459
466
  getCurrentPeriod(params?: {
@@ -17,6 +17,7 @@
17
17
  * - delete / remove methods → resolve void (interface return type forbids a
18
18
  * stamp; this is the one accepted, documented gap).
19
19
  */
20
+ import { toApiEInvoiceExportFormat, } from "./client.js";
20
21
  import { Buffer } from "node:buffer";
21
22
  import { READ_STAMP, FISCAL_STAMP, DEMO_NOW, demoPage, demoEmptyPage, findOrStub, demoId, simulateWrite, simulateAction, demoClients, demoProducts, demoInvoices, demoExpenses, demoQuotes, demoVendors, demoBankAccounts, demoTransactions, } from "./demo-fixtures.js";
22
23
  const DEMO_RBAC_ROLES = ["owner", "admin", "manager", "sales", "accountant", "employee", "viewer"];
@@ -74,6 +75,7 @@ const DEMO_OWNER_CAPABILITIES = Object.entries(DEMO_PERMISSION_MATRIX.owner)
74
75
  .flatMap(([resource, actions]) => actions.map((action) => `${resource}:${action}`))
75
76
  .sort();
76
77
  export class DemoFrihetClient {
78
+ isDemoClient = true;
77
79
  // ---------------------------------------------------------------- Invoices
78
80
  async listInvoices(params) {
79
81
  return demoPage(demoInvoices, params);
@@ -299,6 +301,20 @@ export class DemoFrihetClient {
299
301
  async deleteWebhook(_id) {
300
302
  return;
301
303
  }
304
+ // ---------------------------------------------------------------- Cross-resource search
305
+ // ----------------------------------------------------------------
306
+ async globalSearch(params) {
307
+ return {
308
+ data: [],
309
+ total: 0,
310
+ limit: params.limit ?? 25,
311
+ offset: params.offset ?? 0,
312
+ hasMore: false,
313
+ query: params.q,
314
+ types: params.types ?? ["invoices", "expenses", "vendors", "clients", "products"],
315
+ truncated: false,
316
+ };
317
+ }
302
318
  // ---------------------------------------------------------------- CRM: Contacts
303
319
  async listClientContacts(_clientId, params) {
304
320
  return demoEmptyPage(params);
@@ -426,7 +442,15 @@ export class DemoFrihetClient {
426
442
  return { fileUrl: "https://app.frihet.io/demo/datev.csv", filename: "demo-datev.csv", rowCount: 24, fiscalPeriod: `${params.periodStart}..${params.periodEnd}`, encoding: "cp1252", ...FISCAL_STAMP };
427
443
  }
428
444
  async exportEInvoice(params) {
429
- return { xmlUrl: "https://app.frihet.io/demo/einvoice.xml", filename: `${params.invoiceId}.xml`, format: params.format, signed: params.signed ?? false, ...FISCAL_STAMP };
445
+ const format = toApiEInvoiceExportFormat(params.format);
446
+ return {
447
+ xml: `<?xml version="1.0" encoding="UTF-8"?><DemoEInvoice format="${format}"/>`,
448
+ contentType: "application/xml",
449
+ filename: `${params.invoiceId}.xml`,
450
+ format,
451
+ signed: params.signed ?? false,
452
+ ...FISCAL_STAMP,
453
+ };
430
454
  }
431
455
  async faceSubmit(params) {
432
456
  return { registroFACe: "DEMO-FACE-0001", status: "submitted", submittedAt: DEMO_NOW, mode: params.mode, ...FISCAL_STAMP };
@@ -503,7 +527,8 @@ export class DemoFrihetClient {
503
527
  }
504
528
  // ---------------------------------------------------------------- Fiscal (simulated / read summaries)
505
529
  async getFiscalModeloSummary(modeloCode, period) {
506
- return { model: modeloCode, period: period ?? "2026-Q2", readonly: true, summary: { totalRevenue: 3960.4, totalExpenses: 405.6 }, ...FISCAL_STAMP };
530
+ const defaultPeriod = modeloCode === "390" || modeloCode === "347" ? "2026" : "2026-Q2";
531
+ return { model: modeloCode, period: period ?? defaultPeriod, readonly: true, summary: { totalRevenue: 3960.4, totalExpenses: 405.6 }, ...FISCAL_STAMP };
507
532
  }
508
533
  async getVerifactuStatus(invoiceId) {
509
534
  return { invoiceId, status: "accepted", accepted: true, submittedAt: DEMO_NOW, ...FISCAL_STAMP };
@@ -575,8 +600,8 @@ export class DemoFrihetClient {
575
600
  async approveGLEntry(entryId, notes) {
576
601
  return simulateAction(entryId, { status: "approved", ...(notes ? { notes } : {}) });
577
602
  }
578
- async rejectGLEntry(entryId, reason) {
579
- return simulateAction(entryId, { status: "rejected", reason });
603
+ async rejectGLEntry(entryId, note) {
604
+ return simulateAction(entryId, { status: "rejected", note });
580
605
  }
581
606
  async getGLEntryAuditLog(entryId) {
582
607
  return { entryId, log: [], ...READ_STAMP };
@@ -614,7 +639,7 @@ export class DemoFrihetClient {
614
639
  return demoEmptyPage(params);
615
640
  }
616
641
  async createBankRule(data) {
617
- return simulateWrite("demo_rule", { name: data.name, conditions: data.conditions, actions: data.actions }, { isActive: data.isActive ?? true });
642
+ return simulateWrite("demo_rule", { name: data.name, bankConditions: data.bankConditions, action: data.action, actionConfig: data.actionConfig }, { isActive: data.isActive ?? true });
618
643
  }
619
644
  // ---------------------------------------------------------------- Gestoria
620
645
  async sendGestoriaMessage(data) {
@@ -648,10 +673,10 @@ export class DemoFrihetClient {
648
673
  async cancelLeave(leaveId) {
649
674
  return simulateAction(leaveId, { status: "cancelled" });
650
675
  }
651
- async attendanceClockIn(data) {
676
+ async attendanceClockIn(data, _idempotencyKey) {
652
677
  return simulateWrite("demo_att", { employeeId: data.employeeId, ...(data.mood ? { mood: data.mood } : {}), ...(data.location ? { location: data.location } : {}) }, { status: "open", clockInAt: DEMO_NOW });
653
678
  }
654
- async attendanceClockOut(entryId) {
679
+ async attendanceClockOut(entryId, _idempotencyKey) {
655
680
  return simulateAction(entryId, { status: "closed", clockOutAt: DEMO_NOW });
656
681
  }
657
682
  async getOvertimeReport(params) {
@@ -692,13 +717,6 @@ export class DemoFrihetClient {
692
717
  ...READ_STAMP,
693
718
  };
694
719
  }
695
- // ---------------------------------------------------------------- Onboarding
696
- async getOnboardingStatus() {
697
- return { workspaceId: "demo_ws_001", persona: "autonomo", completedSteps: ["profile"], pendingSteps: ["bank", "invoice"], percentComplete: 33, startedAt: DEMO_NOW, ...READ_STAMP };
698
- }
699
- async setOnboardingPersona(data) {
700
- return { workspaceId: "demo_ws_001", persona: data.persona, updatedAt: DEMO_NOW, ...READ_STAMP };
701
- }
702
720
  // ---------------------------------------------------------------- Permissions
703
721
  async getPermissionsMatrix() {
704
722
  return {
@@ -0,0 +1,29 @@
1
+ import { z } from "zod/v4";
2
+ /** Locally generated guidance only: never includes input values or provider text. */
3
+ export declare class DepositInputError extends Error {
4
+ readonly statusCode = 400;
5
+ readonly errorCode = "invalid_deposit_input";
6
+ constructor(message: string);
7
+ }
8
+ /** Writable fields accepted by publicApi v132's strict deposits schema. */
9
+ export declare const depositFields: {
10
+ clientId: z.ZodString;
11
+ clientName: z.ZodString;
12
+ amount: z.ZodNumber;
13
+ currency: z.ZodString;
14
+ description: z.ZodString;
15
+ receivedDate: z.ZodString;
16
+ paymentMethod: z.ZodOptional<z.ZodString>;
17
+ paymentReference: z.ZodOptional<z.ZodString>;
18
+ };
19
+ /** Writable fields accepted by publicApi v132's strict /deposits/:id/apply schema. */
20
+ export declare const applyDepositFields: {
21
+ invoiceId: z.ZodString;
22
+ invoiceNumber: z.ZodString;
23
+ amount: z.ZodNumber;
24
+ };
25
+ /** Validate deposit application payload against the strict backend contract before request I/O. */
26
+ export declare function normalizeApplyDepositBody(input?: Record<string, unknown>): Record<string, unknown>;
27
+ /** Preserve the established MCP date/reference names on the canonical API wire. */
28
+ export declare function normalizeDepositBody(input: Record<string, unknown>, partial: boolean): Record<string, unknown>;
29
+ //# sourceMappingURL=deposit-contract.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { z } from "zod/v4";
2
+ /** Locally generated guidance only: never includes input values or provider text. */
3
+ export class DepositInputError extends Error {
4
+ statusCode = 400;
5
+ errorCode = "invalid_deposit_input";
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "DepositInputError";
9
+ }
10
+ }
11
+ /** Writable fields accepted by publicApi v132's strict deposits schema. */
12
+ export const depositFields = {
13
+ clientId: z.string().min(1).max(128),
14
+ clientName: z.string().min(1).max(500),
15
+ amount: z.number().positive(),
16
+ currency: z.string().length(3),
17
+ description: z.string().min(1).max(2000),
18
+ receivedDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
19
+ paymentMethod: z.string().max(200).optional(),
20
+ paymentReference: z.string().max(200).optional(),
21
+ };
22
+ /** Writable fields accepted by publicApi v132's strict /deposits/:id/apply schema. */
23
+ export const applyDepositFields = {
24
+ invoiceId: z.string().min(1).max(128),
25
+ invoiceNumber: z.string().min(1).max(200),
26
+ amount: z.number().positive(),
27
+ };
28
+ const applyDepositBody = z.object(applyDepositFields).strict();
29
+ /** Validate deposit application payload against the strict backend contract before request I/O. */
30
+ export function normalizeApplyDepositBody(input = {}) {
31
+ if (Object.prototype.hasOwnProperty.call(input, "notes")) {
32
+ throw new DepositInputError("Deposit application field 'notes' is not writable; the API does not accept it. / El campo 'notes' no se puede guardar al aplicar el depósito.");
33
+ }
34
+ const parsed = applyDepositBody.safeParse(input);
35
+ if (!parsed.success) {
36
+ const known = new Set(Object.keys(applyDepositFields));
37
+ const fields = [...new Set(parsed.error.issues.map((issue) => issue.path[0]).filter((field) => typeof field === "string" && known.has(field)))];
38
+ throw new DepositInputError(`Invalid deposit application input${fields.length ? ` (${fields.join(", ")})` : ""}. Provide invoiceId, invoiceNumber and a positive amount. / Indica invoiceId, invoiceNumber y un importe positivo.`);
39
+ }
40
+ return parsed.data;
41
+ }
42
+ const createDepositBody = z.object({
43
+ ...depositFields,
44
+ currency: depositFields.currency.default("EUR"),
45
+ }).strict();
46
+ // PATCH must not inject the create default or turn an empty update into a write.
47
+ const updateDepositBody = z.object(depositFields).partial().strict().refine((fields) => Object.values(fields).some((value) => value !== undefined), { message: "Provide at least one writable deposit field / Indica al menos un campo del depósito" });
48
+ /** Preserve the established MCP date/reference names on the canonical API wire. */
49
+ export function normalizeDepositBody(input, partial) {
50
+ const body = { ...input };
51
+ for (const unsupported of ["status", "notes"]) {
52
+ if (Object.prototype.hasOwnProperty.call(body, unsupported)) {
53
+ throw new DepositInputError(`Deposit field '${unsupported}' is not writable; the API does not accept it. / El campo '${unsupported}' no se puede guardar en el depósito.`);
54
+ }
55
+ }
56
+ for (const [alias, canonical] of [["date", "receivedDate"], ["reference", "paymentReference"]]) {
57
+ if (body[alias] !== undefined) {
58
+ if (body[canonical] !== undefined && body[canonical] !== body[alias]) {
59
+ throw new DepositInputError(`Conflicting deposit fields '${alias}' and '${canonical}'. / Los campos '${alias}' y '${canonical}' no coinciden.`);
60
+ }
61
+ body[canonical] = body[alias];
62
+ }
63
+ delete body[alias];
64
+ }
65
+ const parsed = (partial ? updateDepositBody : createDepositBody).safeParse(body);
66
+ if (!parsed.success) {
67
+ const known = new Set(Object.keys(depositFields));
68
+ const fields = [...new Set(parsed.error.issues.map((issue) => issue.path[0]).filter((field) => typeof field === "string" && known.has(field)))];
69
+ const guidance = partial
70
+ ? "Provide at least one valid writable deposit field; date uses YYYY-MM-DD. / Indica al menos un campo editable válido; date usa YYYY-MM-DD."
71
+ : "Provide clientId, clientName, a positive amount, description and receivedDate (date) in YYYY-MM-DD format. / Indica clientId, clientName, un importe positivo, description y receivedDate (date) con formato YYYY-MM-DD.";
72
+ throw new DepositInputError(`Invalid deposit input${fields.length ? ` (${fields.join(", ")})` : ""}. ${guidance}`);
73
+ }
74
+ return parsed.data;
75
+ }
76
+ //# sourceMappingURL=deposit-contract.js.map
@@ -0,0 +1,3 @@
1
+ /** Validate a manual-mode fetch response without owning any network authority. */
2
+ export declare function rejectRedirectResponse(response: Response): Promise<Response>;
3
+ //# sourceMappingURL=fetch-no-redirect.d.ts.map
@@ -0,0 +1,16 @@
1
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
2
+ /** Validate a manual-mode fetch response without owning any network authority. */
3
+ export async function rejectRedirectResponse(response) {
4
+ if (REDIRECT_STATUSES.has(response.status)) {
5
+ try {
6
+ await response.body?.cancel();
7
+ }
8
+ catch {
9
+ /* Best effort; still reject. */
10
+ }
11
+ // Never include Location, URL, headers or body in the error.
12
+ throw new TypeError("Redirect responses are not allowed");
13
+ }
14
+ return response;
15
+ }
16
+ //# sourceMappingURL=fetch-no-redirect.js.map
@@ -18,12 +18,12 @@
18
18
  * pointing an alias key at the SAME registered-tool object makes it callable
19
19
  * with zero duplication and zero drift risk.
20
20
  *
21
- * This intentionally does NOT touch `src/tools/*.ts` (where the 157-tool
21
+ * This intentionally does NOT touch `src/tools/*.ts` (where the 158-tool
22
22
  * audit / pin tests count literal `registerTool(` call sites) and is applied
23
23
  * to the REAL `McpServer` instance only, AFTER `registerAllTools()` — the
24
24
  * `tool-exposure.test.ts` / `openai-*.test.ts` suites exercise
25
25
  * `registerAllTools()` against a bare `StubMcpServer` and never call
26
- * `applyFiscalAliases`, so the 157-tool pins are untouched by design.
26
+ * `applyFiscalAliases`, so the 158-tool pins are untouched by design.
27
27
  *
28
28
  * KNOWN LIMITATION: this relies on the SDK's internal (unprefixed-by-type,
29
29
  * but underscore-private) `_registeredTools` field. It is FAIL-OPEN — if a
@@ -18,12 +18,12 @@
18
18
  * pointing an alias key at the SAME registered-tool object makes it callable
19
19
  * with zero duplication and zero drift risk.
20
20
  *
21
- * This intentionally does NOT touch `src/tools/*.ts` (where the 157-tool
21
+ * This intentionally does NOT touch `src/tools/*.ts` (where the 158-tool
22
22
  * audit / pin tests count literal `registerTool(` call sites) and is applied
23
23
  * to the REAL `McpServer` instance only, AFTER `registerAllTools()` — the
24
24
  * `tool-exposure.test.ts` / `openai-*.test.ts` suites exercise
25
25
  * `registerAllTools()` against a bare `StubMcpServer` and never call
26
- * `applyFiscalAliases`, so the 157-tool pins are untouched by design.
26
+ * `applyFiscalAliases`, so the 158-tool pins are untouched by design.
27
27
  *
28
28
  * KNOWN LIMITATION: this relies on the SDK's internal (unprefixed-by-type,
29
29
  * but underscore-private) `_registeredTools` field. It is FAIL-OPEN — if a
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Period contract for the READ-ONLY fiscal modelo summaries.
3
+ *
4
+ * Single source for BOTH the client (which query param to send) and the tools
5
+ * (what to validate before calling, what to compare after). Mirrors the
6
+ * deployed Frihet-ERP backend (functions/src/publicApi.ts):
7
+ * - 303/130 read `?quarter=YYYY-Q[1-4]`, default current quarter
8
+ * - 390 reads `?year=YYYY`, default current year
9
+ * - 347 reads `?year=YYYY`, default current year
10
+ * Every other modelo code has no backend route (404 "Unknown fiscal model").
11
+ * The backend silently answers the CURRENT period for any param it does not
12
+ * read, so the param name here is load-bearing.
13
+ */
14
+ export interface FiscalPeriodRule {
15
+ /** Query param the backend reads. */
16
+ param: "quarter" | "year";
17
+ pattern: RegExp;
18
+ format: string;
19
+ }
20
+ export declare const FISCAL_PERIOD_RULES: Readonly<Record<string, FiscalPeriodRule>>;
21
+ /**
22
+ * Query object for GET /fiscal/modelo/{code}. Throws for a modelo code without
23
+ * a backend rule so no caller can fall back to an ignored `?period=` param.
24
+ */
25
+ export declare function fiscalModeloQuery(modeloCode: string, period?: string): Record<string, string | undefined>;
26
+ //# sourceMappingURL=fiscal-period.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Period contract for the READ-ONLY fiscal modelo summaries.
3
+ *
4
+ * Single source for BOTH the client (which query param to send) and the tools
5
+ * (what to validate before calling, what to compare after). Mirrors the
6
+ * deployed Frihet-ERP backend (functions/src/publicApi.ts):
7
+ * - 303/130 read `?quarter=YYYY-Q[1-4]`, default current quarter
8
+ * - 390 reads `?year=YYYY`, default current year
9
+ * - 347 reads `?year=YYYY`, default current year
10
+ * Every other modelo code has no backend route (404 "Unknown fiscal model").
11
+ * The backend silently answers the CURRENT period for any param it does not
12
+ * read, so the param name here is load-bearing.
13
+ */
14
+ export const FISCAL_PERIOD_RULES = {
15
+ "303": { param: "quarter", pattern: /^\d{4}-Q[1-4]$/, format: "YYYY-Q1..YYYY-Q4" },
16
+ "130": { param: "quarter", pattern: /^\d{4}-Q[1-4]$/, format: "YYYY-Q1..YYYY-Q4" },
17
+ "390": { param: "year", pattern: /^\d{4}$/, format: "YYYY" },
18
+ "347": { param: "year", pattern: /^\d{4}$/, format: "YYYY" },
19
+ };
20
+ /**
21
+ * Query object for GET /fiscal/modelo/{code}. Throws for a modelo code without
22
+ * a backend rule so no caller can fall back to an ignored `?period=` param.
23
+ */
24
+ export function fiscalModeloQuery(modeloCode, period) {
25
+ const rule = FISCAL_PERIOD_RULES[modeloCode];
26
+ if (!rule) {
27
+ throw new Error(`No deployed fiscal summary backend for Modelo ${modeloCode}`);
28
+ }
29
+ return { [rule.param]: period };
30
+ }
31
+ //# sourceMappingURL=fiscal-period.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Google Connected Apps Profile for the Frihet MCP Server.
3
+ *
4
+ * Implements the curated 18-tool financial management surface submitted
5
+ * for the Google Connected Apps Platform Developer Preview (Gemini app & Google Search).
6
+ *
7
+ * Requirements:
8
+ * - Curated catalogue: 18 high-value small business & freelancer tools.
9
+ * - Human-in-the-loop: Invoices and financial documents are created strictly in draft status.
10
+ * - Zero unconfirmed destructive actions (no direct AEAT / VeriFactu fiscal submissions).
11
+ * - Standard output redaction for sensitive fields.
12
+ */
13
+ export declare const GOOGLE_CANDIDATE_TOOLS: ReadonlySet<string>;
14
+ export declare const GOOGLE_CANDIDATE_TOOL_COUNT: number;
15
+ export declare const GOOGLE_INSTRUCTIONS = "Frihet ERP \u2014 natural-language business and financial management for Google Connected Apps (Gemini & Search).\nUse get_business_context for complete company health and plan limits.\nQuery and review invoices, expenses, quotes, clients, and products with pagination.\nAll created invoices are drafts. Confirm parameters with the user before creating records.";
16
+ export declare function googleToolResult(result: unknown): unknown;
17
+ export declare const GOOGLE_FORCED_INPUT_VALUES: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
18
+ export declare function applyGoogleProfile(server: any): void;
19
+ //# sourceMappingURL=google-profile.d.ts.map
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Google Connected Apps Profile for the Frihet MCP Server.
3
+ *
4
+ * Implements the curated 18-tool financial management surface submitted
5
+ * for the Google Connected Apps Platform Developer Preview (Gemini app & Google Search).
6
+ *
7
+ * Requirements:
8
+ * - Curated catalogue: 18 high-value small business & freelancer tools.
9
+ * - Human-in-the-loop: Invoices and financial documents are created strictly in draft status.
10
+ * - Zero unconfirmed destructive actions (no direct AEAT / VeriFactu fiscal submissions).
11
+ * - Standard output redaction for sensitive fields.
12
+ */
13
+ import { deepRedact, redactText } from "./redaction.js";
14
+ export const GOOGLE_CANDIDATE_TOOLS = new Set([
15
+ "get_business_context",
16
+ "get_monthly_summary",
17
+ "get_quarterly_taxes",
18
+ "list_invoices",
19
+ "get_invoice",
20
+ "search_invoices",
21
+ "list_expenses",
22
+ "get_expense",
23
+ "create_expense",
24
+ "list_clients",
25
+ "get_client",
26
+ "create_client",
27
+ "list_products",
28
+ "get_product",
29
+ "list_quotes",
30
+ "get_quote",
31
+ "create_quote",
32
+ "create_invoice",
33
+ ]);
34
+ export const GOOGLE_CANDIDATE_TOOL_COUNT = GOOGLE_CANDIDATE_TOOLS.size; // 18
35
+ export const GOOGLE_INSTRUCTIONS = `Frihet ERP — natural-language business and financial management for Google Connected Apps (Gemini & Search).
36
+ Use get_business_context for complete company health and plan limits.
37
+ Query and review invoices, expenses, quotes, clients, and products with pagination.
38
+ All created invoices are drafts. Confirm parameters with the user before creating records.`;
39
+ export function googleToolResult(result) {
40
+ if (result === null || typeof result !== "object")
41
+ return result;
42
+ const res = result;
43
+ // Deep-redact structured output if present (in-place deletion of sensitive fields)
44
+ if (res.structuredContent && typeof res.structuredContent === "object") {
45
+ deepRedact(res.structuredContent);
46
+ }
47
+ // Redact text content items
48
+ if (Array.isArray(res.content)) {
49
+ res.content = res.content.map((item) => {
50
+ if (item && typeof item === "object" && "text" in item && typeof item.text === "string") {
51
+ return {
52
+ ...item,
53
+ text: redactText(item.text),
54
+ };
55
+ }
56
+ return item;
57
+ });
58
+ }
59
+ return res;
60
+ }
61
+ export const GOOGLE_FORCED_INPUT_VALUES = {
62
+ create_invoice: { status: "draft" },
63
+ create_quote: { status: "draft" },
64
+ };
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ export function applyGoogleProfile(server) {
67
+ const registerTool = server.registerTool.bind(server);
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ server.registerTool = (name, config, handler) => {
70
+ if (!GOOGLE_CANDIDATE_TOOLS.has(name))
71
+ return undefined;
72
+ let description = config.description ?? "";
73
+ let inputSchema = config.inputSchema;
74
+ if (name === "create_invoice") {
75
+ description = `${description} [Google Connected Apps: Generates an invoice strictly in 'draft' status for human review. No irreversible fiscal submission occurs.]`;
76
+ if (inputSchema && typeof inputSchema === "object" && "status" in inputSchema) {
77
+ const { status: _omittedStatus, ...rest } = inputSchema;
78
+ inputSchema = rest;
79
+ }
80
+ }
81
+ else if (name === "create_quote") {
82
+ description = `${description} [Google Connected Apps: Generates a quote strictly in 'draft' status for human review.]`;
83
+ if (inputSchema && typeof inputSchema === "object" && "status" in inputSchema) {
84
+ const { status: _omittedStatus, ...rest } = inputSchema;
85
+ inputSchema = rest;
86
+ }
87
+ }
88
+ return registerTool(name, {
89
+ ...config,
90
+ description,
91
+ inputSchema,
92
+ annotations: {
93
+ ...config.annotations,
94
+ ...(config.annotations?.readOnlyHint !== true ? { destructiveHint: true } : {}),
95
+ },
96
+ },
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ async (...args) => {
99
+ try {
100
+ let input = args[0];
101
+ if (input && typeof input === "object") {
102
+ const forced = GOOGLE_FORCED_INPUT_VALUES[name];
103
+ if (forced) {
104
+ input = { ...input, ...forced };
105
+ }
106
+ }
107
+ const res = await handler(input, ...args.slice(1));
108
+ return googleToolResult(res);
109
+ }
110
+ catch {
111
+ return googleToolResult({
112
+ isError: true,
113
+ _meta: { "io.frihet/operationOutcomeUnknown": true },
114
+ });
115
+ }
116
+ });
117
+ };
118
+ // Google Connected Apps profile intentionally registers 0 raw prompts or dynamic resources
119
+ server.registerResource = () => undefined;
120
+ server.registerPrompt = () => undefined;
121
+ }
122
+ //# sourceMappingURL=google-profile.js.map
package/dist/index.js CHANGED
@@ -30,6 +30,9 @@ const PKG_VERSION = (() => {
30
30
  }
31
31
  })();
32
32
  import { OPENAI_ALLOWED_TOOL_COUNT, OPENAI_EXCLUDED_COUNT, OPENAI_EXCLUDED_RESOURCE_COUNT } from "./openai-profile.js";
33
+ import { CLAUDE_CANDIDATE_INSTRUCTIONS, CLAUDE_CANDIDATE_TOOLS } from "./claude-profile.js";
34
+ import { GOOGLE_INSTRUCTIONS, GOOGLE_CANDIDATE_TOOL_COUNT } from "./google-profile.js";
35
+ import { MUSE_INSTRUCTIONS, MUSE_CANDIDATE_TOOL_COUNT } from "./muse-profile.js";
33
36
  import { resolveToolMode, GROUPED_META_TOOL_COUNT } from "./tool-exposure.js";
34
37
  import { localMcpSurfaceComposition, registerMcpSurface, } from "./server-composition.js";
35
38
  import { AGENT_SERVER_INSTRUCTIONS, API_KEY_LOCATION, } from "./agent-onboarding.js";
@@ -37,6 +40,15 @@ import { log } from "./logger.js";
37
40
  import { registerShutdownHook } from "./metrics.js";
38
41
  import { setTraceContext } from "./observability.js";
39
42
  function main() {
43
+ const openaiMode = process.env.FRIHET_OPENAI_MODE === "true";
44
+ const claudeMode = process.env.FRIHET_CLAUDE_MODE === "true";
45
+ const googleMode = process.env.FRIHET_GOOGLE_MODE === "true";
46
+ const museMode = process.env.FRIHET_MUSE_MODE === "true";
47
+ const activeModes = [openaiMode, claudeMode, googleMode, museMode].filter(Boolean).length;
48
+ if (activeModes > 1) {
49
+ console.error("Error: FRIHET_OPENAI_MODE, FRIHET_CLAUDE_MODE, FRIHET_GOOGLE_MODE, and FRIHET_MUSE_MODE are mutually exclusive");
50
+ process.exit(1);
51
+ }
40
52
  const apiKey = process.env.FRIHET_API_KEY;
41
53
  // Demo mode: FRIHET_DEMO=1|true serves fixture-backed example data with NO
42
54
  // network calls and NO API key required. It's an explicit opt-in, so it wins
@@ -95,17 +107,29 @@ function main() {
95
107
  const server = new McpServer({
96
108
  name: "frihet-erp",
97
109
  version: PKG_VERSION,
98
- description: "AI-native MCP server for Frihet ERP — invoices, expenses, clients, products, quotes, webhooks, and deposits. " +
99
- "Provides a catalogue of 157 canonical operations; fiscal aliases and optional grouped discovery names are reported separately. " +
100
- "The local package serves 11 resources (7 static + 4 API-backed) and 10 workflow prompts " +
101
- "with full Spanish tax compliance (IVA, IGIC, IPSI).",
110
+ description: claudeMode
111
+ ? `Local Claude candidate for Frihet: ${CLAUDE_CANDIDATE_TOOLS.size} canonical operations with reviewed fiscal aliases, 1 currencies resource and 2 receivables prompts. Optional grouped discovery adds 3 names. API-dependent operations require live workspace verification; this is not a published Directory connector.`
112
+ : googleMode
113
+ ? `Frihet ERP profile for Google Connected Apps (Gemini & Search): ${GOOGLE_CANDIDATE_TOOL_COUNT} canonical operations with draft-only human-in-the-loop protection.`
114
+ : museMode
115
+ ? `Frihet ERP connector for Meta Muse: ${MUSE_CANDIDATE_TOOL_COUNT} canonical operations with draft-only human-in-the-loop protection.`
116
+ :
117
+ "AI-native MCP server for Frihet ERP — invoices, expenses, clients, products, quotes, webhooks, and deposits. " +
118
+ "Provides a catalogue of 158 canonical operations; fiscal aliases and optional grouped discovery names are reported separately. " +
119
+ "The local package serves 11 resources (7 static + 4 API-backed) and 10 workflow prompts " +
120
+ "with full Spanish tax compliance (IVA, IGIC, IPSI).",
102
121
  }, {
103
122
  // Delivered by `initialize` to every MCP client, which hands it to the model
104
123
  // before the first tool call. This is the only onboarding channel that costs
105
124
  // the operator zero configuration. Source: src/agent-onboarding.ts.
106
- instructions: AGENT_SERVER_INSTRUCTIONS,
125
+ instructions: claudeMode
126
+ ? CLAUDE_CANDIDATE_INSTRUCTIONS
127
+ : googleMode
128
+ ? GOOGLE_INSTRUCTIONS
129
+ : museMode
130
+ ? MUSE_INSTRUCTIONS
131
+ : AGENT_SERVER_INSTRUCTIONS,
107
132
  });
108
- const openaiMode = process.env.FRIHET_OPENAI_MODE === "true";
109
133
  const toolMode = resolveToolMode();
110
134
  if (toolMode === "grouped") {
111
135
  log({
@@ -121,7 +145,7 @@ function main() {
121
145
  operation: "startup",
122
146
  });
123
147
  }
124
- registerMcpSurface(server, client, localMcpSurfaceComposition(openaiMode, toolMode === "grouped"));
148
+ registerMcpSurface(server, client, localMcpSurfaceComposition(openaiMode, toolMode === "grouped", claudeMode, googleMode, museMode));
125
149
  // Register shutdown hook to log final metrics summary
126
150
  registerShutdownHook();
127
151
  // Connect via stdio transport