@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
@@ -28,7 +28,7 @@
28
28
  * POST /v1/invoices/:id/ksef/submit — Poland KSeF (stub — endpoint not yet exposed; returns unavailable)
29
29
  */
30
30
  import { z } from "zod/v4";
31
- import { withToolLogging, mutateContent, getContent, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, } from "./shared.js";
31
+ import { withToolLogging, mutateContent, getContent, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, } from "./shared.js";
32
32
  /* ------------------------------------------------------------------ */
33
33
  /* Honest "backend endpoint not yet available" result */
34
34
  /* ------------------------------------------------------------------ */
@@ -99,6 +99,20 @@ export const eInvoiceFormatSchema = z.enum([
99
99
  "peppol-bis-3",
100
100
  "facturae",
101
101
  ]);
102
+ /** Export is narrower than dispatch/validation. The ERP public export API
103
+ * exposes Factur-X EN16931 only; accepting Extended/Basic/Minimum here and
104
+ * silently mapping them to EN16931 would produce the wrong fiscal profile. */
105
+ export const eInvoiceExportFormatSchema = z.enum([
106
+ "xrechnung-cii",
107
+ "xrechnung-ubl",
108
+ "facturx-en16931",
109
+ "fatturapa",
110
+ "ubl",
111
+ "cii",
112
+ "peppol-bis-3",
113
+ "fa-2-ksef",
114
+ "facturae",
115
+ ]);
102
116
  /* ------------------------------------------------------------------ */
103
117
  /* Output schemas */
104
118
  /* ------------------------------------------------------------------ */
@@ -410,19 +424,23 @@ export function registerEInvoiceTools(server, _client) {
410
424
  // -- einvoice_export --
411
425
  server.registerTool("einvoice_export", {
412
426
  title: "Export E-Invoice XML",
413
- description: "Export an invoice as e-invoicing XML in a specific format. " +
427
+ description: "Export an invoice as inline e-invoicing XML in a specific format. " +
414
428
  "Supports Facturae (ES B2G), XRechnung-CII (DE), XRechnung-UBL (DE), " +
415
- "Factur-X profiles (FR), FatturaPA (IT), PEPPOL-BIS-3 (EU network), UBL and CII (generic). " +
429
+ "Factur-X EN16931 (FR), FatturaPA (IT), PEPPOL-BIS-3 (EU network), FA-2-KSeF (PL), UBL and CII (generic). " +
430
+ "Factur-X Extended, Basic and Minimum are not exposed by this endpoint and are refused rather than collapsed to a different profile. " +
416
431
  "For Facturae, set signed=true to get XAdES-enveloped XML for FACe/AEAT submission. " +
417
- "Returns a signed download URL valid for 24 hours. " +
432
+ "Returns the generated XML inline with its MIME type and filename. " +
418
433
  "\n\n" +
419
434
  "/ Exporta una factura como XML de facturacion electronica en el formato especificado. " +
420
435
  "Para Facturae con signed=true devuelve XML firmado XAdES para envio a FACe/AEAT.",
421
- annotations: READ_ONLY_ANNOTATIONS,
436
+ // Conservative static classification: signed=true exercises a workspace
437
+ // signing certificate, so the tool cannot truthfully be advertised as a
438
+ // read-only operation even though unsigned generation is side-effect free.
439
+ annotations: UPDATE_ANNOTATIONS,
422
440
  inputSchema: {
423
441
  invoiceId: z.string().describe("Frihet invoice ID to export / ID de la factura a exportar"),
424
- format: eInvoiceFormatSchema.describe("E-invoice format. Choose based on recipient country: " +
425
- "ES B2G→facturae, DE→xrechnung-cii/ubl, FR→facturx-*, IT→fatturapa, EU PEPPOL→peppol-bis-3, " +
442
+ format: eInvoiceExportFormatSchema.describe("E-invoice format. Choose based on recipient country: " +
443
+ "ES B2G→facturae, DE→xrechnung-cii/ubl, FR→facturx-en16931, IT→fatturapa, PL→fa-2-ksef, EU PEPPOL→peppol-bis-3, " +
426
444
  "generic→ubl or cii / Formato de factura electronica."),
427
445
  signed: z.boolean().optional().describe("If true, returns XAdES-enveloped signed XML (Facturae only). Requires workspace signing certificate configured. " +
428
446
  "/ Si true, devuelve XML firmado XAdES (solo Facturae). Requiere certificado de firma configurado."),
@@ -439,7 +457,8 @@ export function registerEInvoiceTools(server, _client) {
439
457
  ` Format: ${format}\n` +
440
458
  ` Signed: ${signed ?? false}\n` +
441
459
  ` Filename: ${result.filename}\n` +
442
- ` Download URL: ${result.xmlUrl}`),
460
+ ` Content-Type: ${result.contentType}\n` +
461
+ ` XML: returned inline (${result.xml.length} characters)`),
443
462
  ],
444
463
  structuredContent: result,
445
464
  };
@@ -460,7 +479,8 @@ export function registerEInvoiceTools(server, _client) {
460
479
  // -- face_submit --
461
480
  server.registerTool("face_submit", {
462
481
  title: "Submit to FACe (Spain B2G)",
463
- description: "Submit a Facturae-formatted invoice to the Spanish FACe (Punto General de Entrada de Facturas Electrónicas) B2G portal. " +
482
+ description: "Requires confirm=true as a local client/agent interlock before submitting a Facturae invoice to Spain's FACe B2G portal. " +
483
+ "This flag is not server-verifiable human attestation and is never forwarded to the API. " +
464
484
  "The invoice must have a recipient with DIR3 administrative unit codes (órgano gestor, unidad tramitadora, oficina contable). " +
465
485
  "Returns a submission reference (registro) that can be used with face_status to poll the portal. " +
466
486
  "\n\n" +
@@ -477,9 +497,22 @@ export function registerEInvoiceTools(server, _client) {
477
497
  invoiceId: z.string().describe("Frihet invoice ID to submit / ID de la factura a enviar a FACe"),
478
498
  mode: z.enum(["mock", "sandbox", "production"]).optional().describe("Submission mode: mock (local sim), sandbox (FACe pre-prod), production (live). Default: production. " +
479
499
  "/ Modo de envío: mock (simulación local), sandbox (pre-producción FACe), production (real). Por defecto: production."),
500
+ confirm: z.boolean().describe("Must be true to pass the local submission interlock in every mode, including mock and sandbox. " +
501
+ "This is not actor/resource/payload/expiry-bound attestation. / Debe ser true para superar el interlock local en todos los modos."),
480
502
  },
481
503
  outputSchema: faceSubmitOutput,
482
- }, async ({ invoiceId, mode }) => withToolLogging("face_submit", async () => {
504
+ }, async ({ invoiceId, mode, confirm }) => withToolLogging("face_submit", async () => {
505
+ if (!confirm) {
506
+ return {
507
+ content: [{
508
+ type: "text",
509
+ text: "Error: face_submit requires confirm=true before any FACe submission or simulation. " +
510
+ "It is a local client/agent interlock, not server-verifiable human attestation. " +
511
+ "/ Se requiere confirm=true antes de cualquier envío o simulación FACe.",
512
+ }],
513
+ isError: true,
514
+ };
515
+ }
483
516
  const plannedEndpoint = `/v1/invoices/${invoiceId}/face/submit`;
484
517
  const resolvedMode = mode ?? "production";
485
518
  try {
@@ -562,7 +595,8 @@ export function registerEInvoiceTools(server, _client) {
562
595
  // -- ticketbai_submit --
563
596
  server.registerTool("ticketbai_submit", {
564
597
  title: "Submit TicketBAI (Basque Country)",
565
- description: "Submit an invoice to the Basque Country TicketBAI e-invoicing system. " +
598
+ description: "Requires confirm=true as a local client/agent interlock before submitting an invoice to TicketBAI. " +
599
+ "This flag is not server-verifiable human attestation and is never forwarded to the API. " +
566
600
  "Territory is auto-detected from the workspace address (Bizkaia → BATUZ/LROE, Gipuzkoa → Gipuzkoa TicketBAI, Araba → Araba TicketBAI). " +
567
601
  "Returns the submission TicketBAI ID (TBAI identifier) and QR code URL for printing on the invoice. " +
568
602
  "\n\n" +
@@ -577,9 +611,22 @@ export function registerEInvoiceTools(server, _client) {
577
611
  invoiceId: z.string().describe("Frihet invoice ID to submit / ID de la factura a enviar"),
578
612
  sandbox: z.boolean().optional().describe("If true, submits to the hacienda test endpoint instead of production. Default: false. " +
579
613
  "/ Si true, envía al entorno de pruebas de la hacienda. Por defecto: false."),
614
+ confirm: z.boolean().describe("Must be true to pass the local submission interlock in both sandbox and production. " +
615
+ "This is not actor/resource/payload/expiry-bound attestation. / Debe ser true para superar el interlock local."),
580
616
  },
581
617
  outputSchema: ticketbaiSubmitOutput,
582
- }, async ({ invoiceId, sandbox }) => withToolLogging("ticketbai_submit", async () => {
618
+ }, async ({ invoiceId, sandbox, confirm }) => withToolLogging("ticketbai_submit", async () => {
619
+ if (!confirm) {
620
+ return {
621
+ content: [{
622
+ type: "text",
623
+ text: "Error: ticketbai_submit requires confirm=true before any TicketBAI submission. " +
624
+ "It is a local client/agent interlock, not server-verifiable human attestation. " +
625
+ "/ Se requiere confirm=true antes de cualquier envío TicketBAI.",
626
+ }],
627
+ isError: true,
628
+ };
629
+ }
583
630
  const plannedEndpoint = `/v1/invoices/${invoiceId}/ticketbai/submit`;
584
631
  const isSandbox = sandbox ?? false;
585
632
  try {
@@ -12,10 +12,9 @@
12
12
  *
13
13
  * NOTE: ticketbai_status is registered by einvoice.ts (canonical, more complete impl).
14
14
  *
15
- * REST surface: /v1/fiscal/* (documented: pending — backend ships separately)
16
- *
17
- * NOTE: ERP backend endpoints /v1/fiscal/* are planned. Tools are wired
18
- * and will surface 404 errors until the backend ships.
15
+ * REST surface: Frihet-ERP functions/src/publicApi.ts serves READ-ONLY
16
+ * GET /v1/fiscal/modelo/{303,130,390,347}. Modelo 180 has no route; its tool
17
+ * returns NOT_DEPLOYED without calling. Period params: src/fiscal-period.ts.
19
18
  */
20
19
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
20
  import type { IFrihetClient } from "../client-interface.js";
@@ -12,14 +12,82 @@
12
12
  *
13
13
  * NOTE: ticketbai_status is registered by einvoice.ts (canonical, more complete impl).
14
14
  *
15
- * REST surface: /v1/fiscal/* (documented: pending — backend ships separately)
16
- *
17
- * NOTE: ERP backend endpoints /v1/fiscal/* are planned. Tools are wired
18
- * and will surface 404 errors until the backend ships.
15
+ * REST surface: Frihet-ERP functions/src/publicApi.ts serves READ-ONLY
16
+ * GET /v1/fiscal/modelo/{303,130,390,347}. Modelo 180 has no route; its tool
17
+ * returns NOT_DEPLOYED without calling. Period params: src/fiscal-period.ts.
19
18
  */
20
19
  import { z } from "zod/v4";
21
- import { withToolLogging, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, ERROR_CONTENT_ANNOTATIONS, fiscalModeloSummaryOutput, verifactuStatusOutput, } from "./shared.js";
22
- import { withBackendGuard, isBackendNotFound } from "./backend-availability.js";
20
+ import { withToolLogging, confirmationRequiredError, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, ERROR_CONTENT_ANNOTATIONS, fiscalModeloSummaryOutput, verifactuStatusOutput, } from "./shared.js";
21
+ import { withBackendGuard, isBackendNotFound, notDeployedError } from "./backend-availability.js";
22
+ import { FISCAL_PERIOD_RULES } from "../fiscal-period.js";
23
+ function fiscalPeriodError(toolName, structured, message) {
24
+ return {
25
+ content: [{ type: "text", text: `Error: ${message}`, annotations: ERROR_CONTENT_ANNOTATIONS }],
26
+ structuredContent: { ...structured, tool: toolName, message },
27
+ isError: true,
28
+ };
29
+ }
30
+ /**
31
+ * READ-ONLY modelo summary with a fail-closed period contract:
32
+ * 1. a supplied period must match the backend's own format, or no call is made;
33
+ * 2. the modelo the backend reports (`modeloCode`/`model`) must be the one
34
+ * requested, or the figures are withheld (MODELO_MISMATCH);
35
+ * 3. the period the backend reports must be well-formed and, when one was
36
+ * requested, identical to it — otherwise the figures belong to another
37
+ * period and are withheld (PERIOD_MISMATCH), never shown under the
38
+ * requested label.
39
+ */
40
+ function invalidFiscalPeriod(toolName, modeloCode, period) {
41
+ const rule = FISCAL_PERIOD_RULES[modeloCode];
42
+ if (!rule) {
43
+ return notDeployedError(toolName, `/v1/fiscal/modelo/${modeloCode}`);
44
+ }
45
+ if (period === undefined || rule.pattern.test(period))
46
+ return undefined;
47
+ return fiscalPeriodError(toolName, { error: "invalid_period", code: "INVALID_PERIOD", modeloCode, requested: period, expectedFormat: rule.format }, `Invalid period for Modelo ${modeloCode}: use ${rule.format}. No request was sent. ` +
48
+ `/ Periodo no valido para el Modelo ${modeloCode}: usa ${rule.format}. No se ha enviado ninguna peticion.`);
49
+ }
50
+ async function fiscalModeloSummary(client, toolName, modeloCode, period, title) {
51
+ const rule = FISCAL_PERIOD_RULES[modeloCode];
52
+ if (!rule) {
53
+ return notDeployedError(toolName, `/v1/fiscal/modelo/${modeloCode}`);
54
+ }
55
+ const result = await client.getFiscalModeloSummary(modeloCode, period);
56
+ // The backend names the modelo in `modeloCode` and/or legacy `model`
57
+ // (Frihet-ERP publicApi.ts). Every key present must name the requested
58
+ // modelo, and at least one must be present; otherwise another modelo's
59
+ // figures would be shown under this one's label.
60
+ const returnedModelos = [result["modeloCode"], result["model"]].filter((v) => v !== undefined);
61
+ const modeloAgrees = returnedModelos.length > 0 && returnedModelos.every((v) => v === modeloCode);
62
+ if (!modeloAgrees) {
63
+ return fiscalPeriodError(toolName, {
64
+ error: "modelo_mismatch",
65
+ code: "MODELO_MISMATCH",
66
+ requestedModelo: modeloCode,
67
+ returnedModelo: returnedModelos.map((v) => (typeof v === "string" ? v : null)),
68
+ }, `Frihet returned a summary that is not identifiable as Modelo ${modeloCode}, so no figures are shown ` +
69
+ `(they would be mislabelled). Do NOT state any amount for this modelo; retry or contact support. ` +
70
+ `/ Frihet devolvio un resumen que no corresponde al Modelo ${modeloCode}; no se muestran cifras. ` +
71
+ `NO indiques ningun importe para este modelo.`);
72
+ }
73
+ const returned = typeof result["period"] === "string" ? result["period"] : null;
74
+ const year = result["year"];
75
+ const yearAgrees = year === undefined || (returned !== null && String(year) === returned);
76
+ const matches = returned !== null &&
77
+ rule.pattern.test(returned) &&
78
+ yearAgrees &&
79
+ (period === undefined || returned === period);
80
+ if (!matches) {
81
+ return fiscalPeriodError(toolName, { error: "period_mismatch", code: "PERIOD_MISMATCH", modeloCode, requested: period ?? null, returned }, `Frihet returned a Modelo ${modeloCode} summary whose period does not match the request, so no figures are shown ` +
82
+ `(they would be mislabelled). Do NOT state any amount for this period; retry or contact support. ` +
83
+ `/ Frihet devolvio un resumen del Modelo ${modeloCode} de un periodo distinto al solicitado; no se muestran cifras. ` +
84
+ `NO indiques ningun importe para este periodo.`);
85
+ }
86
+ return {
87
+ content: [getContent(formatRecord(title, result))],
88
+ structuredContent: result,
89
+ };
90
+ }
23
91
  export function registerFiscalTools(server, client) {
24
92
  // -- get_modelo_303_summary --
25
93
  server.registerTool("get_modelo_303_summary", {
@@ -34,16 +102,11 @@ export function registerFiscalTools(server, client) {
34
102
  period: z
35
103
  .string()
36
104
  .optional()
37
- .describe("Period in format YYYY-QN (e.g. '2026-Q1') or YYYY for annual / Periodo en formato YYYY-QN o YYYY"),
105
+ .describe("Quarter in format YYYY-QN (e.g. '2026-Q1'); defaults to the current quarter / Trimestre en formato YYYY-QN; por defecto el trimestre actual"),
38
106
  },
39
107
  outputSchema: fiscalModeloSummaryOutput,
40
- }, async ({ period }) => withToolLogging("get_modelo_303_summary", () => withBackendGuard("get_modelo_303_summary", "/v1/fiscal/303", async () => {
41
- const result = await client.getFiscalModeloSummary("303", period);
42
- return {
43
- content: [getContent(formatRecord("Modelo 303 Summary", result))],
44
- structuredContent: result,
45
- };
46
- })));
108
+ }, async ({ period }) => withToolLogging("get_modelo_303_summary", async () => invalidFiscalPeriod("get_modelo_303_summary", "303", period) ??
109
+ withBackendGuard("get_modelo_303_summary", "/v1/fiscal/modelo/303", () => fiscalModeloSummary(client, "get_modelo_303_summary", "303", period, "Modelo 303 Summary"))));
47
110
  // -- get_modelo_130_summary --
48
111
  server.registerTool("get_modelo_130_summary", {
49
112
  title: "Get Modelo 130 Summary (IRPF Estimated Payment)",
@@ -57,16 +120,11 @@ export function registerFiscalTools(server, client) {
57
120
  period: z
58
121
  .string()
59
122
  .optional()
60
- .describe("Period in format YYYY-QN (e.g. '2026-Q1') / Periodo en formato YYYY-QN"),
123
+ .describe("Quarter in format YYYY-QN (e.g. '2026-Q1'); defaults to the current quarter / Trimestre en formato YYYY-QN; por defecto el trimestre actual"),
61
124
  },
62
125
  outputSchema: fiscalModeloSummaryOutput,
63
- }, async ({ period }) => withToolLogging("get_modelo_130_summary", () => withBackendGuard("get_modelo_130_summary", "/v1/fiscal/130", async () => {
64
- const result = await client.getFiscalModeloSummary("130", period);
65
- return {
66
- content: [getContent(formatRecord("Modelo 130 Summary", result))],
67
- structuredContent: result,
68
- };
69
- })));
126
+ }, async ({ period }) => withToolLogging("get_modelo_130_summary", async () => invalidFiscalPeriod("get_modelo_130_summary", "130", period) ??
127
+ withBackendGuard("get_modelo_130_summary", "/v1/fiscal/modelo/130", () => fiscalModeloSummary(client, "get_modelo_130_summary", "130", period, "Modelo 130 Summary"))));
70
128
  // -- get_modelo_390_summary --
71
129
  server.registerTool("get_modelo_390_summary", {
72
130
  title: "Get Modelo 390 Summary (IVA Annual Recap)",
@@ -83,21 +141,15 @@ export function registerFiscalTools(server, client) {
83
141
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
84
142
  },
85
143
  outputSchema: fiscalModeloSummaryOutput,
86
- }, async ({ period }) => withToolLogging("get_modelo_390_summary", () => withBackendGuard("get_modelo_390_summary", "/v1/fiscal/390", async () => {
87
- const result = await client.getFiscalModeloSummary("390", period);
88
- return {
89
- content: [getContent(formatRecord("Modelo 390 Summary", result))],
90
- structuredContent: result,
91
- };
92
- })));
144
+ }, async ({ period }) => withToolLogging("get_modelo_390_summary", async () => invalidFiscalPeriod("get_modelo_390_summary", "390", period) ??
145
+ withBackendGuard("get_modelo_390_summary", "/v1/fiscal/modelo/390", () => fiscalModeloSummary(client, "get_modelo_390_summary", "390", period, "Modelo 390 Summary"))));
93
146
  // -- get_modelo_180_summary --
94
147
  server.registerTool("get_modelo_180_summary", {
95
148
  title: "Get Modelo 180 Summary (IRPF Rentals Annual)",
96
- description: "Get IRPF annual informative summary for rental income withholdings (Modelo 180, Spain). " +
97
- "Returns total retentions per tenant, property, and annual aggregate. " +
98
- "Example: period='2025' / " +
99
- "Obtiene el resumen anual de retenciones sobre alquileres para el Modelo 180. " +
100
- "Devuelve retenciones totales por inquilino, inmueble y agregado anual.",
149
+ description: "NOT DEPLOYED: Modelo 180 (annual informative return of IRPF withholdings on rental income, Spain) has no Frihet backend yet; " +
150
+ "calling this tool returns a NOT_DEPLOYED error and never data. " +
151
+ "/ NO DESPLEGADO: el Modelo 180 (resumen anual de retenciones sobre alquileres) aun no tiene backend en Frihet; " +
152
+ "esta herramienta devuelve un error NOT_DEPLOYED y nunca datos.",
101
153
  annotations: READ_ONLY_ANNOTATIONS,
102
154
  inputSchema: {
103
155
  period: z
@@ -106,13 +158,7 @@ export function registerFiscalTools(server, client) {
106
158
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
107
159
  },
108
160
  outputSchema: fiscalModeloSummaryOutput,
109
- }, async ({ period }) => withToolLogging("get_modelo_180_summary", () => withBackendGuard("get_modelo_180_summary", "/v1/fiscal/180", async () => {
110
- const result = await client.getFiscalModeloSummary("180", period);
111
- return {
112
- content: [getContent(formatRecord("Modelo 180 Summary", result))],
113
- structuredContent: result,
114
- };
115
- })));
161
+ }, async () => withToolLogging("get_modelo_180_summary", async () => notDeployedError("get_modelo_180_summary", "/v1/fiscal/modelo/180")));
116
162
  // -- get_modelo_347_summary --
117
163
  server.registerTool("get_modelo_347_summary", {
118
164
  title: "Get Modelo 347 Summary (Operations >€3,005 Annual Recap)",
@@ -129,13 +175,8 @@ export function registerFiscalTools(server, client) {
129
175
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
130
176
  },
131
177
  outputSchema: fiscalModeloSummaryOutput,
132
- }, async ({ period }) => withToolLogging("get_modelo_347_summary", () => withBackendGuard("get_modelo_347_summary", "/v1/fiscal/347", async () => {
133
- const result = await client.getFiscalModeloSummary("347", period);
134
- return {
135
- content: [getContent(formatRecord("Modelo 347 Summary", result))],
136
- structuredContent: result,
137
- };
138
- })));
178
+ }, async ({ period }) => withToolLogging("get_modelo_347_summary", async () => invalidFiscalPeriod("get_modelo_347_summary", "347", period) ??
179
+ withBackendGuard("get_modelo_347_summary", "/v1/fiscal/modelo/347", () => fiscalModeloSummary(client, "get_modelo_347_summary", "347", period, "Modelo 347 Summary"))));
139
180
  // -- verifactu_status --
140
181
  server.registerTool("verifactu_status", {
141
182
  title: "Get VeriFactu Submission Status",
@@ -222,18 +263,10 @@ export function registerFiscalTools(server, client) {
222
263
  outputSchema: verifactuStatusOutput,
223
264
  }, async ({ invoiceId, confirm }) => withToolLogging("verifactu_resubmit", async () => {
224
265
  if (!confirm) {
225
- return {
226
- content: [
227
- {
228
- type: "text",
229
- text: "Error: confirm=true is required for VeriFactu resubmission. " +
230
- "This is a COMPLIANCE action that submits fiscal data to AEAT. " +
231
- "Verify the invoice data is correct before setting confirm=true. / " +
232
- "Se requiere confirm=true para el reenvio VeriFactu. Accion de COMPLIANCE que envia datos fiscales a AEAT.",
233
- },
234
- ],
235
- isError: true,
236
- };
266
+ return confirmationRequiredError("Error: confirm=true is required for VeriFactu resubmission. " +
267
+ "This is a COMPLIANCE action that submits fiscal data to AEAT. " +
268
+ "Verify the invoice data is correct before setting confirm=true. / " +
269
+ "Se requiere confirm=true para el reenvio VeriFactu. Accion de COMPLIANCE que envia datos fiscales a AEAT.");
237
270
  }
238
271
  return withBackendGuard("verifactu_resubmit", "/v1/fiscal/verifactu/resubmit", async () => {
239
272
  const result = await client.resubmitVerifactu(invoiceId);
@@ -224,22 +224,30 @@ export function registerGestoriaTools(server, client) {
224
224
  title: "Consolidated AR Aging (Gestoria)",
225
225
  description: "Get a cross-client AR aging report for a gestor — totals bucketed by current / 30-60 / " +
226
226
  "60-90 / 90+ days overdue, a per-workspace breakdown, and the top overdue invoices. " +
227
- "Defaults to the authenticated gestor; pass `ownerUid` to query a specific gestor " +
228
- "(requires elevated scope). " +
227
+ "The ERP requires POST /v1/gestoria/aging with a body listing the workspaces to include " +
228
+ "(defaults to the authenticated gestor when `workspaceIds` is omitted). " +
229
229
  "Useful for dunning prioritisation and end-of-month chase lists. " +
230
- "/ Obtiene un informe de antiguedad de saldos cruzando todos los clientes del gestor — " +
230
+ "/ Obtiene un informe de antiguedad de saldos cruzando los clientes del gestor — " +
231
231
  "totales por tramo (al dia / 30-60 / 60-90 / 90+ dias), desglose por espacio y top vencidas. " +
232
232
  "Util para priorizar reclamaciones y listas de cobro de fin de mes.",
233
233
  annotations: READ_ONLY_ANNOTATIONS,
234
234
  inputSchema: {
235
- ownerUid: z
235
+ workspaceIds: z
236
+ .array(z.string().min(1))
237
+ .optional()
238
+ .describe("Workspace IDs to include (defaults to the authenticated gestor when omitted) / IDs de espacios a incluir (por defecto los del gestor)"),
239
+ asOf: z
236
240
  .string()
237
241
  .optional()
238
- .describe("Gestor UID to query (defaults to authenticated caller; elevated scope required for other UIDs) / UID del gestor (por defecto el llamante)"),
242
+ .describe("Reference date in ISO 8601 (YYYY-MM-DD). Defaults to today / Fecha de referencia"),
243
+ bustCache: z
244
+ .boolean()
245
+ .optional()
246
+ .describe("Force-refresh server cache (default false) / Forzar refresco de cache del servidor"),
239
247
  },
240
248
  outputSchema: gestoriaAgingConsolidatedOutput,
241
- }, async ({ ownerUid }) => withToolLogging("gestoria_aging_consolidated", () => withBackendGuard("gestoria_aging_consolidated", "/v1/gestoria/aging/consolidated", async () => {
242
- const result = await client.getGestoriaAgingConsolidated({ ownerUid });
249
+ }, async ({ workspaceIds, asOf, bustCache }) => withToolLogging("gestoria_aging_consolidated", () => withBackendGuard("gestoria_aging_consolidated", "/v1/gestoria/aging", async () => {
250
+ const result = await client.getGestoriaAgingConsolidated({ workspaceIds, asOf, bustCache });
243
251
  return {
244
252
  content: [getContent(formatRecord("Aging consolidated", result))],
245
253
  structuredContent: result,
package/dist/tools/hr.js CHANGED
@@ -25,13 +25,15 @@ export function registerHrTools(server, client) {
25
25
  server.registerTool("leave_request_create", {
26
26
  title: "Create Leave Request",
27
27
  description: "Create a new leave/PTO request for an employee. " +
28
- "Types: 'vacation', 'sick', 'personal', 'parental', 'unpaid', 'training'. " +
28
+ "Types: 'vacation', 'sick', 'personal', 'parental', 'bereavement', 'other'. " +
29
29
  "Dates must be ISO 8601 (YYYY-MM-DD). Status starts as 'pending' awaiting manager approval. " +
30
30
  "/ Crea una nueva solicitud de vacaciones/permiso. Estado inicial 'pending' pendiente aprobacion.",
31
31
  annotations: CREATE_ANNOTATIONS,
32
32
  inputSchema: {
33
33
  employeeId: z.string().describe("Employee ID / ID del empleado"),
34
- type: z.string().describe("Leave type slug (vacation, sick, personal, parental, unpaid, training) / Tipo de permiso"),
34
+ type: z
35
+ .enum(["vacation", "sick", "personal", "parental", "bereavement", "other"])
36
+ .describe("Leave type slug (vacation, sick, personal, parental, bereavement, other) / Tipo de permiso"),
35
37
  startDate: z.string().describe("Start date ISO 8601 (YYYY-MM-DD) / Fecha inicio"),
36
38
  endDate: z.string().describe("End date ISO 8601 (YYYY-MM-DD) / Fecha fin"),
37
39
  reason: z.string().optional().describe("Optional reason / Motivo opcional"),
@@ -140,10 +142,19 @@ export function registerHrTools(server, client) {
140
142
  employeeId: z.string().describe("Employee ID / ID del empleado"),
141
143
  mood: z.string().optional().describe("Optional mood slug (e.g. 'great','ok','tired') / Estado de animo"),
142
144
  location: z.string().optional().describe("Optional location ('remote','office','site') / Ubicacion"),
145
+ idempotencyKey: z
146
+ .string()
147
+ .max(64)
148
+ .optional()
149
+ .describe("Optional idempotency key, max 64 chars. One is generated per call when omitted or blank; " +
150
+ "pass your own to make a retry replay the stored open entry instead of creating a second clock-in. " +
151
+ "Reusing a key with a different body returns 409 IDEMPOTENCY_KEY_REUSED — reconcile, do not " +
152
+ "retry with a new key. / Clave de idempotencia opcional (max 64). Se genera una por llamada si se omite."),
143
153
  },
144
154
  outputSchema: attendanceEntryItemOutput,
145
155
  }, async (input) => withToolLogging("attendance_clock_in", () => withBackendGuard("attendance_clock_in", "/v1/time-entries/clock-in", async () => {
146
- const result = await client.attendanceClockIn(input);
156
+ const { idempotencyKey, ...data } = input;
157
+ const result = await client.attendanceClockIn(data, idempotencyKey);
147
158
  return {
148
159
  content: [mutateContent(formatRecord("Clocked in", result))],
149
160
  structuredContent: result,
@@ -158,10 +169,18 @@ export function registerHrTools(server, client) {
158
169
  annotations: UPDATE_ANNOTATIONS,
159
170
  inputSchema: {
160
171
  entryId: z.string().describe("Open attendance entry ID / ID de la entrada abierta"),
172
+ idempotencyKey: z
173
+ .string()
174
+ .max(64)
175
+ .optional()
176
+ .describe("Optional idempotency key, max 64 chars. One is generated per call when omitted or blank; " +
177
+ "pass your own to make a retry replay the stored close instead of erroring on a closed entry. " +
178
+ "/ Clave de idempotencia opcional (max 64). Se genera una por llamada si se omite."),
161
179
  },
162
180
  outputSchema: attendanceEntryItemOutput,
163
- }, async ({ entryId }) => withToolLogging("attendance_clock_out", () => withBackendGuard("attendance_clock_out", "/v1/time-entries/clock-out", async () => {
164
- const result = await client.attendanceClockOut(entryId);
181
+ }, async (input) => withToolLogging("attendance_clock_out", () => withBackendGuard("attendance_clock_out", "/v1/time-entries/clock-out", async () => {
182
+ const { entryId, idempotencyKey } = input;
183
+ const result = await client.attendanceClockOut(entryId, idempotencyKey);
165
184
  return {
166
185
  content: [mutateContent(formatRecord("Clocked out", result))],
167
186
  structuredContent: result,
@@ -191,19 +210,30 @@ export function registerHrTools(server, client) {
191
210
  server.registerTool("anomaly_list", {
192
211
  title: "List Anomalies",
193
212
  description: "List HR / operational / financial anomalies detected by the system. " +
194
- "Filter by type (duplicate_clock_in, overtime_spike, missing_clock_out, expense_outlier, etc.), " +
195
- "severity (low/medium/high/critical), or period. " +
213
+ "Filter by type (daily_exceeded, weekly_exceeded, annual_approaching, " +
214
+ "annual_exceeded, missing_break), severity (warning/critical), or period. " +
215
+ "Anomalies are COMPUTE-ON-READ over the live attendance records (Art.34/35 ET " +
216
+ "overtime engine) — there is no stored anomalies collection. " +
196
217
  "Useful for daily HR review and compliance audits. " +
197
218
  "/ Lista anomalias detectadas (RRHH/operativas/financieras) con filtros opcionales.",
198
219
  annotations: READ_ONLY_ANNOTATIONS,
199
220
  inputSchema: {
200
- type: z.string().optional().describe("Filter by anomaly type slug / Tipo"),
221
+ type: z
222
+ .enum([
223
+ "daily_exceeded",
224
+ "weekly_exceeded",
225
+ "annual_approaching",
226
+ "annual_exceeded",
227
+ "missing_break",
228
+ ])
229
+ .optional()
230
+ .describe("Filter by anomaly type slug / Tipo"),
201
231
  severity: z
202
- .enum(["low", "medium", "high", "critical"])
232
+ .enum(["warning", "critical"])
203
233
  .optional()
204
234
  .describe("Filter by severity / Severidad"),
205
- from: z.string().optional().describe("Period start ISO 8601 / Inicio"),
206
- to: z.string().optional().describe("Period end ISO 8601 / Fin"),
235
+ from: z.string().optional().describe("Period start ISO 8601 (YYYY-MM-DD) / Inicio"),
236
+ to: z.string().optional().describe("Period end ISO 8601 (YYYY-MM-DD) / Fin"),
207
237
  limit: z.number().int().min(1).max(100).optional().describe("Max results / Maximos"),
208
238
  offset: z.number().int().min(0).optional().describe("Offset / Desplazamiento"),
209
239
  },
@@ -2,18 +2,19 @@
2
2
  * IGIC (Canary Islands indirect tax) tools for the Frihet MCP server — Day 1 Megasprint (4 tools).
3
3
  *
4
4
  * Tools:
5
- * 1. frihet_modelo_415_summary — M415 operations >€3,005 annual (Canarias)
6
- * 2. frihet_modelo_425_summary — M425 resumen anual IGIC (Canarias)
7
- * 3. frihet_modelo_418_summary — M418 mensual grandes empresas IGIC (Canarias)
8
- * 4. frihet_aiem_calculate — Arbitrio sobre Importaciones y Entrega de Mercancías calculation
5
+ * 1. frihet_modelo_415_summary — M415 operations >€3,005 annual (Canarias) — NOT DEPLOYED
6
+ * 2. frihet_modelo_425_summary — M425 resumen anual IGIC (Canarias) — NOT DEPLOYED
7
+ * 3. frihet_modelo_418_summary — M418 autoliquidacion mensual individual, regimen especial
8
+ * del grupo de entidades (ATC) — NOT DEPLOYED
9
+ * 4. frihet_aiem_calculate — AIEM (Arbitrio sobre Importaciones y Entrega de Mercancías) — NOT DEPLOYED
9
10
  *
10
11
  * NOTE: ATC SOAP integration skipped — internal infrastructure, not exposed via MCP.
11
12
  *
12
- * REST surface: /v1/igic/* — service-layer reads of workspace fiscal data.
13
- * Backend callables: #390 IGIC services (Frihet-ERP PR #390).
14
- * Tools surface 404 until REST shell ships.
13
+ * REST surface: Frihet-ERP functions/src/publicApi.ts has NO /v1/igic/* route
14
+ * (neither modelo summaries nor AIEM), so all four tools return NOT_DEPLOYED
15
+ * without calling (capability truth: `unavailable`, src/capability-truth.ts).
15
16
  */
16
17
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
18
  import type { IFrihetClient } from "../client-interface.js";
18
- export declare function registerIgicTools(server: McpServer, client: IFrihetClient): void;
19
+ export declare function registerIgicTools(server: McpServer, _client: IFrihetClient): void;
19
20
  //# sourceMappingURL=igic.d.ts.map