@frihet/mcp-server 1.16.6 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -31
- package/dist/agent-onboarding.d.ts +98 -0
- package/dist/agent-onboarding.js +275 -0
- package/dist/api-origin.d.ts +9 -0
- package/dist/api-origin.js +41 -0
- package/dist/canary/mcp-harness.d.ts +77 -0
- package/dist/canary/mcp-harness.js +549 -0
- package/dist/capability-truth.d.ts +29 -0
- package/dist/capability-truth.js +254 -0
- package/dist/client-interface.d.ts +22 -14
- package/dist/client.d.ts +114 -18
- package/dist/client.js +488 -56
- package/dist/demo-client.d.ts +16 -13
- package/dist/demo-client.js +171 -17
- package/dist/demo-fixtures.d.ts +3 -3
- package/dist/demo-fixtures.js +7 -7
- package/dist/fiscal-aliases.d.ts +44 -0
- package/dist/fiscal-aliases.js +62 -0
- package/dist/index.js +41 -56
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +140 -8
- package/dist/observability.d.ts +18 -29
- package/dist/observability.js +192 -105
- package/dist/openai-profile.d.ts +25 -21
- package/dist/openai-profile.js +1166 -156
- package/dist/openai-review-contract.d.ts +15 -6
- package/dist/openai-review-contract.js +715 -41
- package/dist/openai-review-oauth.d.ts +47 -0
- package/dist/openai-review-oauth.js +73 -3
- package/dist/prompts/register-all.d.ts +2 -0
- package/dist/prompts/register-all.js +2 -0
- package/dist/public-capability-contract.d.ts +39 -0
- package/dist/public-capability-contract.js +186 -0
- package/dist/redaction.d.ts +10 -0
- package/dist/redaction.js +74 -0
- package/dist/resources/register-all.js +6 -12
- package/dist/server-composition.d.ts +16 -0
- package/dist/server-composition.js +44 -0
- package/dist/tool-exposure.d.ts +25 -22
- package/dist/tool-exposure.js +199 -77
- package/dist/tools/accountingClose.d.ts +14 -5
- package/dist/tools/accountingClose.js +41 -23
- package/dist/tools/backend-availability.d.ts +3 -3
- package/dist/tools/backend-availability.js +3 -3
- package/dist/tools/clients.js +9 -9
- package/dist/tools/crm.js +22 -21
- package/dist/tools/deposits.js +30 -11
- package/dist/tools/einvoice.d.ts +1 -1
- package/dist/tools/expenses.d.ts +16 -0
- package/dist/tools/expenses.js +47 -27
- package/dist/tools/hr.d.ts +2 -3
- package/dist/tools/hr.js +8 -9
- package/dist/tools/intelligence.js +2 -2
- package/dist/tools/invoices.js +150 -47
- package/dist/tools/payroll.d.ts +5 -8
- package/dist/tools/payroll.js +19 -32
- package/dist/tools/permissions.d.ts +4 -7
- package/dist/tools/permissions.js +11 -14
- package/dist/tools/products.js +6 -6
- package/dist/tools/quotes.js +109 -24
- package/dist/tools/shared.d.ts +1431 -75
- package/dist/tools/shared.js +386 -76
- package/dist/tools/team.js +18 -14
- package/dist/tools/vendors.js +9 -9
- package/dist/tools/webhooks.js +35 -30
- package/dist/types.d.ts +28 -4
- package/docs/agent-onboarding.json +381 -0
- package/package.json +30 -7
package/dist/tools/invoices.js
CHANGED
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
* Invoice tools for the Frihet MCP server.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
|
-
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, enrichResponse, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, DELETE_ANNOTATIONS, paginatedOutput,
|
|
5
|
+
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, enrichResponse, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, DELETE_ANNOTATIONS, paginatedOutput, documentDeleteResultOutput, invoiceItemOutput, actionResultOutput, creditNoteResultOutput, pdfResultOutput, einvoiceResultOutput } from "./shared.js";
|
|
6
6
|
const invoiceItemSchema = z.object({
|
|
7
|
-
description: z.string().describe("Description of the line item / Descripcion del concepto"),
|
|
8
|
-
quantity: z.number().describe("Quantity / Cantidad"),
|
|
9
|
-
unitPrice: z.number().describe("Unit price in EUR / Precio unitario en EUR"),
|
|
10
|
-
});
|
|
7
|
+
description: z.string().trim().min(1).max(500).describe("Description of the line item / Descripcion del concepto"),
|
|
8
|
+
quantity: z.number().finite().min(0.0001).max(1_000_000).describe("Quantity / Cantidad"),
|
|
9
|
+
unitPrice: z.number().finite().min(0).max(10_000_000).describe("Unit price in EUR / Precio unitario en EUR"),
|
|
10
|
+
}).strict();
|
|
11
|
+
function documentResultMetadata(result) {
|
|
12
|
+
return {
|
|
13
|
+
id: result.id,
|
|
14
|
+
contentType: result.contentType,
|
|
15
|
+
sizeBytes: result.sizeBytes,
|
|
16
|
+
...(result.filename ? { filename: result.filename } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
11
19
|
// Optional client-identity fields the Frihet API accepts on invoice/quote
|
|
12
|
-
// create+update. When clientId is supplied the server
|
|
13
|
-
//
|
|
20
|
+
// create+update. When clientId is supplied the server snapshots the stored
|
|
21
|
+
// client record; the other fields can override document-specific details.
|
|
14
22
|
const clientIdentityFields = {
|
|
15
23
|
clientId: z
|
|
16
24
|
.string()
|
|
17
25
|
.optional()
|
|
18
|
-
.describe("Existing client ID —
|
|
26
|
+
.describe("Existing client ID — stored client details are used for the document / ID de cliente existente"),
|
|
19
27
|
clientTaxId: z
|
|
20
28
|
.string()
|
|
21
29
|
.optional()
|
|
@@ -133,8 +141,8 @@ export function registerInvoiceTools(server, client) {
|
|
|
133
141
|
const result = await client.listInvoices({ limit, offset, after, fields, status, from, to, clientId, seriesId });
|
|
134
142
|
const hints = enrichResponse("invoices", "list", result.data);
|
|
135
143
|
return {
|
|
136
|
-
content: [listContent(formatPaginatedResponse("invoices", result))],
|
|
137
|
-
structuredContent: { ...result
|
|
144
|
+
content: [listContent(formatPaginatedResponse("invoices", result) + hints)],
|
|
145
|
+
structuredContent: { ...result },
|
|
138
146
|
};
|
|
139
147
|
}));
|
|
140
148
|
// -- get_invoice --
|
|
@@ -165,18 +173,18 @@ export function registerInvoiceTools(server, client) {
|
|
|
165
173
|
"Soporta retencion IRPF (autonomos ES), recargo de equivalencia, serie, anticipo y descuento global.",
|
|
166
174
|
annotations: CREATE_ANNOTATIONS,
|
|
167
175
|
inputSchema: {
|
|
168
|
-
clientName: z.string().describe("Client/customer name / Nombre del cliente"),
|
|
176
|
+
clientName: z.string().trim().min(1).max(200).describe("Client/customer name / Nombre del cliente"),
|
|
169
177
|
...clientIdentityFields,
|
|
170
178
|
items: z
|
|
171
179
|
.array(invoiceItemSchema)
|
|
172
180
|
.min(1)
|
|
173
181
|
.describe("Line items (each with description, quantity, unitPrice) / Conceptos de la factura"),
|
|
174
182
|
issueDate: z
|
|
175
|
-
.
|
|
183
|
+
.iso.date()
|
|
176
184
|
.optional()
|
|
177
185
|
.describe("Issue date in ISO 8601 format (YYYY-MM-DD), defaults to today / Fecha de emision"),
|
|
178
186
|
dueDate: z
|
|
179
|
-
.
|
|
187
|
+
.iso.date()
|
|
180
188
|
.optional()
|
|
181
189
|
.describe("Due date in ISO 8601 format (YYYY-MM-DD) / Fecha de vencimiento"),
|
|
182
190
|
status: z
|
|
@@ -185,6 +193,7 @@ export function registerInvoiceTools(server, client) {
|
|
|
185
193
|
.describe("Invoice status (default: draft) / Estado de la factura"),
|
|
186
194
|
notes: z
|
|
187
195
|
.string()
|
|
196
|
+
.max(10_000)
|
|
188
197
|
.optional()
|
|
189
198
|
.describe("Additional notes shown on the invoice / Notas adicionales"),
|
|
190
199
|
taxRate: z
|
|
@@ -200,8 +209,8 @@ export function registerInvoiceTools(server, client) {
|
|
|
200
209
|
const result = await client.createInvoice(input);
|
|
201
210
|
const hints = enrichResponse("invoices", "create", result);
|
|
202
211
|
return {
|
|
203
|
-
content: [mutateContent(formatRecord("Invoice created", result))],
|
|
204
|
-
structuredContent: { ...result
|
|
212
|
+
content: [mutateContent(formatRecord("Invoice created", result) + hints)],
|
|
213
|
+
structuredContent: { ...result },
|
|
205
214
|
};
|
|
206
215
|
}));
|
|
207
216
|
// -- update_invoice --
|
|
@@ -235,26 +244,73 @@ export function registerInvoiceTools(server, client) {
|
|
|
235
244
|
const result = await client.updateInvoice(id, data);
|
|
236
245
|
const hints = enrichResponse("invoices", "update", result);
|
|
237
246
|
return {
|
|
238
|
-
content: [mutateContent(formatRecord("Invoice updated", result))],
|
|
239
|
-
structuredContent: { ...result
|
|
247
|
+
content: [mutateContent(formatRecord("Invoice updated", result) + hints)],
|
|
248
|
+
structuredContent: { ...result },
|
|
240
249
|
};
|
|
241
250
|
}));
|
|
242
251
|
// -- delete_invoice --
|
|
243
252
|
server.registerTool("delete_invoice", {
|
|
244
253
|
title: "Delete Invoice",
|
|
245
|
-
description: "
|
|
246
|
-
"
|
|
254
|
+
description: "Delete an invoice by its ID. Requires confirm=true. " +
|
|
255
|
+
"Only a DRAFT invoice is removed permanently. A sent/paid/overdue invoice is NOT destroyed: " +
|
|
256
|
+
"the backend CANCELS it (status=cancelled) so the VeriFactu hash chain stays intact, and the " +
|
|
257
|
+
"document remains readable via get_invoice. The result reports which happened in `outcome` " +
|
|
258
|
+
"(\"deleted\" or \"cancelled\"). " +
|
|
259
|
+
"/ Elimina una factura por su ID. Requiere confirm=true. Solo una factura en BORRADOR se elimina " +
|
|
260
|
+
"de forma permanente. Una factura emitida/pagada NO se destruye: se CANCELA (status=cancelled) para " +
|
|
261
|
+
"preservar la cadena de hash VeriFactu y sigue consultable con get_invoice.",
|
|
247
262
|
annotations: DELETE_ANNOTATIONS,
|
|
248
263
|
inputSchema: {
|
|
249
264
|
id: z.string().describe("Invoice ID / ID de la factura"),
|
|
265
|
+
confirm: z
|
|
266
|
+
.boolean()
|
|
267
|
+
.describe("Must be true to confirm deletion / Debe ser true para confirmar la eliminacion"),
|
|
250
268
|
},
|
|
251
|
-
outputSchema:
|
|
252
|
-
}, async ({ id }) => withToolLogging("delete_invoice", async () => {
|
|
253
|
-
|
|
269
|
+
outputSchema: documentDeleteResultOutput,
|
|
270
|
+
}, async ({ id, confirm }) => withToolLogging("delete_invoice", async () => {
|
|
271
|
+
if (!confirm) {
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: "Error: confirm=true is required to delete an invoice. " +
|
|
277
|
+
"A draft invoice is removed permanently; a sent/paid invoice is CANCELLED " +
|
|
278
|
+
"(status=cancelled) instead, because VeriFactu forbids breaking the hash chain. " +
|
|
279
|
+
"Either way the invoice stops counting as a live receivable. Set confirm=true to proceed. / " +
|
|
280
|
+
"Se requiere confirm=true para eliminar una factura.",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
isError: true,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// 204 → undefined (draft row destroyed). 200 → soft-cancel body (document
|
|
287
|
+
// kept, status=cancelled). Reporting both as "deleted" is the GAP-12 lie.
|
|
288
|
+
const outcome = await client.deleteInvoice(id);
|
|
289
|
+
const cancelled = !!outcome && typeof outcome === "object";
|
|
290
|
+
const body = cancelled ? outcome : {};
|
|
254
291
|
const hints = enrichResponse("invoices", "delete", { id });
|
|
292
|
+
const previous = typeof body["previousStatus"] === "string" ? ` (was ${body["previousStatus"]})` : "";
|
|
293
|
+
// R3 fix (ERP #1580): extract the per-branch body text to a single
|
|
294
|
+
// variable and append `+ hints` ONCE, after the ternary closes. R2's
|
|
295
|
+
// `+ hints` was branch-local (only in the DELETED branch), so the
|
|
296
|
+
// CANCELLED branch silently dropped the "cannot be undone" warning —
|
|
297
|
+
// 15/16 behavioral paths had warnings, not 16/16. The OUTER concat
|
|
298
|
+
// guarantees both terminal branches carry the enrichment. The
|
|
299
|
+
// `paginated-strict-output.test.ts` runtime tests pin BOTH branches.
|
|
300
|
+
const bodyText = cancelled
|
|
301
|
+
? `Invoice ${id} was CANCELLED, not deleted${previous}: it still exists with ` +
|
|
302
|
+
"status=cancelled because VeriFactu forbids destroying an issued invoice. / " +
|
|
303
|
+
`Factura ${id} CANCELADA, no eliminada: sigue existiendo con status=cancelled (VeriFactu).`
|
|
304
|
+
: `Invoice ${id} deleted permanently (it was a draft). / ` +
|
|
305
|
+
`Factura ${id} eliminada permanentemente (era un borrador).`;
|
|
255
306
|
return {
|
|
256
|
-
content: [mutateContent(
|
|
257
|
-
structuredContent: {
|
|
307
|
+
content: [mutateContent(bodyText + hints)],
|
|
308
|
+
structuredContent: {
|
|
309
|
+
success: true,
|
|
310
|
+
id,
|
|
311
|
+
...body,
|
|
312
|
+
outcome: cancelled ? "cancelled" : "deleted",
|
|
313
|
+
},
|
|
258
314
|
};
|
|
259
315
|
}));
|
|
260
316
|
// -- search_invoices --
|
|
@@ -299,24 +355,71 @@ export function registerInvoiceTools(server, client) {
|
|
|
299
355
|
const label = query ? `invoices matching "${query}"` : "invoices";
|
|
300
356
|
const hints = enrichResponse("invoices", "list", result.data);
|
|
301
357
|
return {
|
|
302
|
-
content: [listContent(formatPaginatedResponse(label, result))],
|
|
303
|
-
structuredContent: { ...result
|
|
358
|
+
content: [listContent(formatPaginatedResponse(label, result) + hints)],
|
|
359
|
+
structuredContent: { ...result },
|
|
304
360
|
};
|
|
305
361
|
}));
|
|
306
362
|
// -- send_invoice --
|
|
307
363
|
server.registerTool("send_invoice", {
|
|
308
364
|
title: "Send Invoice",
|
|
309
|
-
description: "Send an invoice to the client via email.
|
|
365
|
+
description: "Send an invoice to the client via email. Requires confirm=true. " +
|
|
366
|
+
"Optionally override the recipient email address. " +
|
|
310
367
|
"The invoice must exist and should not already be cancelled. " +
|
|
311
|
-
"
|
|
368
|
+
"Sending reaches a third party and cannot be recalled. " +
|
|
369
|
+
"/ Envia una factura al cliente por email. Requiere confirm=true. " +
|
|
370
|
+
"Opcionalmente se puede cambiar el email destinatario. El envio llega a un tercero y no se puede anular.",
|
|
312
371
|
annotations: UPDATE_ANNOTATIONS,
|
|
313
372
|
inputSchema: {
|
|
314
373
|
id: z.string().describe("Invoice ID / ID de la factura"),
|
|
315
374
|
to: z.string().optional().describe("Override recipient email / Email destinatario alternativo"),
|
|
375
|
+
confirm: z
|
|
376
|
+
.boolean()
|
|
377
|
+
.describe("Must be true to confirm sending / Debe ser true para confirmar el envio"),
|
|
316
378
|
},
|
|
317
379
|
outputSchema: actionResultOutput,
|
|
318
|
-
}, async ({ id, to }) => withToolLogging("send_invoice", async () => {
|
|
319
|
-
|
|
380
|
+
}, async ({ id, to, confirm }) => withToolLogging("send_invoice", async () => {
|
|
381
|
+
if (!confirm) {
|
|
382
|
+
return {
|
|
383
|
+
content: [
|
|
384
|
+
{
|
|
385
|
+
type: "text",
|
|
386
|
+
text: "Error: confirm=true is required to send an invoice. " +
|
|
387
|
+
"This delivers an email to the client — a third party outside this workspace — " +
|
|
388
|
+
"and it cannot be recalled once sent. Set confirm=true to proceed. / " +
|
|
389
|
+
"Se requiere confirm=true para enviar una factura por email a un tercero.",
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
isError: true,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const invoice = await client.getInvoice(id);
|
|
396
|
+
if (invoice["status"] === "cancelled") {
|
|
397
|
+
return {
|
|
398
|
+
content: [{ type: "text", text: "Error: a cancelled invoice cannot be sent." }],
|
|
399
|
+
isError: true,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
let recipientEmail = to;
|
|
403
|
+
if (!recipientEmail) {
|
|
404
|
+
const clientId = typeof invoice["clientId"] === "string" ? invoice["clientId"] : undefined;
|
|
405
|
+
if (!clientId) {
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: "text", text: "Error: this invoice has no linked client with a saved email address." }],
|
|
408
|
+
isError: true,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const linkedClient = await client.getClient(clientId);
|
|
412
|
+
recipientEmail = typeof linkedClient["email"] === "string" && linkedClient["email"].trim()
|
|
413
|
+
? linkedClient["email"].trim()
|
|
414
|
+
: undefined;
|
|
415
|
+
if (!recipientEmail) {
|
|
416
|
+
return {
|
|
417
|
+
content: [{ type: "text", text: "Error: the linked client has no saved email address." }],
|
|
418
|
+
isError: true,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const result = await client.sendInvoice(id, recipientEmail);
|
|
320
423
|
return {
|
|
321
424
|
content: [mutateContent(formatRecord("Invoice sent", result))],
|
|
322
425
|
structuredContent: result,
|
|
@@ -344,8 +447,10 @@ export function registerInvoiceTools(server, client) {
|
|
|
344
447
|
// -- get_invoice_pdf --
|
|
345
448
|
server.registerTool("get_invoice_pdf", {
|
|
346
449
|
title: "Get Invoice PDF",
|
|
347
|
-
description: "Get the PDF for an invoice.
|
|
348
|
-
"
|
|
450
|
+
description: "Get the PDF for an invoice. The body is served as raw `application/pdf` bytes, base64-encoded in `base64` " +
|
|
451
|
+
"with `sizeBytes` and `contentType`. PDFs larger than 25 MiB are rejected with `413 payload_too_large`. " +
|
|
452
|
+
"/ Obtiene el PDF de una factura. El cuerpo se sirve como bytes `application/pdf` en bruto, codificados en base64 " +
|
|
453
|
+
"en `base64`, con `sizeBytes` y `contentType`. PDFs mayores de 25 MiB se rechazan con `413 payload_too_large`.",
|
|
349
454
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
350
455
|
inputSchema: {
|
|
351
456
|
id: z.string().describe("Invoice ID / ID de la factura"),
|
|
@@ -354,33 +459,31 @@ export function registerInvoiceTools(server, client) {
|
|
|
354
459
|
}, async ({ id }) => withToolLogging("get_invoice_pdf", async () => {
|
|
355
460
|
const result = await client.getInvoicePdf(id);
|
|
356
461
|
return {
|
|
357
|
-
content: [getContent(formatRecord("Invoice PDF", result))],
|
|
462
|
+
content: [getContent(formatRecord("Invoice PDF", documentResultMetadata(result)))],
|
|
358
463
|
structuredContent: result,
|
|
359
464
|
};
|
|
360
465
|
}));
|
|
361
466
|
// -- get_invoice_einvoice --
|
|
362
467
|
server.registerTool("get_invoice_einvoice", {
|
|
363
|
-
title: "Get Invoice E-Invoice
|
|
364
|
-
description: "Download the e-invoice
|
|
365
|
-
"
|
|
468
|
+
title: "Get Invoice E-Invoice Artifact",
|
|
469
|
+
description: "Download the stored e-invoice artifact for an invoice. XML formats return strict UTF-8 text in `xml`; " +
|
|
470
|
+
"Factur-X returns `application/pdf` bytes encoded in `base64`. Both include `id`, `contentType`, and `sizeBytes`. " +
|
|
471
|
+
"XML larger than 5 MiB or PDF larger than 25 MiB is rejected with `413 payload_too_large`. " +
|
|
366
472
|
"Only available after the invoice has been saved/sent. " +
|
|
367
|
-
"/ Descarga el
|
|
368
|
-
"
|
|
473
|
+
"/ Descarga el artefacto de factura electronica guardado. Los formatos XML devuelven texto UTF-8 estricto en `xml`; " +
|
|
474
|
+
"Factur-X devuelve bytes `application/pdf` codificados en `base64`. Ambos incluyen `id`, `contentType` y `sizeBytes`. " +
|
|
475
|
+
"XML mayor de 5 MiB o PDF mayor de 25 MiB se rechaza con `413 payload_too_large`. " +
|
|
369
476
|
"Solo disponible despues de guardar o enviar la factura.",
|
|
370
477
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
371
478
|
inputSchema: {
|
|
372
479
|
id: z.string().describe("Invoice ID / ID de la factura"),
|
|
373
480
|
},
|
|
374
|
-
outputSchema:
|
|
375
|
-
xml: z.string().optional(),
|
|
376
|
-
filename: z.string().optional(),
|
|
377
|
-
format: z.string().optional(),
|
|
378
|
-
}).passthrough(),
|
|
481
|
+
outputSchema: einvoiceResultOutput,
|
|
379
482
|
}, async ({ id }) => withToolLogging("get_invoice_einvoice", async () => {
|
|
380
483
|
const result = await client.getInvoiceEInvoice(id);
|
|
381
484
|
return {
|
|
382
|
-
content: [
|
|
383
|
-
structuredContent:
|
|
485
|
+
content: [getContent(formatRecord("E-invoice artifact", documentResultMetadata(result)))],
|
|
486
|
+
structuredContent: result,
|
|
384
487
|
};
|
|
385
488
|
}));
|
|
386
489
|
// -- create_credit_note --
|
|
@@ -439,8 +542,8 @@ export function registerInvoiceTools(server, client) {
|
|
|
439
542
|
}, idempotencyKey);
|
|
440
543
|
const hints = enrichResponse("invoices", "create", result);
|
|
441
544
|
return {
|
|
442
|
-
content: [mutateContent(formatRecord("Credit note created", result))],
|
|
443
|
-
structuredContent: { ...result
|
|
545
|
+
content: [mutateContent(formatRecord("Credit note created", result) + hints)],
|
|
546
|
+
structuredContent: { ...result },
|
|
444
547
|
};
|
|
445
548
|
}));
|
|
446
549
|
// -- apply_late_fee --
|
package/dist/tools/payroll.d.ts
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
* Payroll preparation tools for the Frihet MCP server — D4-B megasprint (2 tools).
|
|
3
3
|
*
|
|
4
4
|
* Tools:
|
|
5
|
-
* 1. payroll_export —
|
|
6
|
-
* 2. payroll_checklist —
|
|
5
|
+
* 1. payroll_export — read the normalized export-ready employee dataset
|
|
6
|
+
* 2. payroll_checklist — read payroll-profile readiness for payable employees
|
|
7
7
|
*
|
|
8
8
|
* REST surface: /v1/payroll/prep/export, /v1/payroll/prep/employees
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* NOTE: ERP backend endpoints land in parallel D4-A wave. 404s propagate as isError
|
|
15
|
-
* until backend ships. TODO: confirm A3 columns + SILTRA file extension.
|
|
10
|
+
* The ERP validates and echoes a destination-format label but returns the same
|
|
11
|
+
* normalized JSON dataset for each value. It does not generate a format-specific
|
|
12
|
+
* file, and Frihet does not process payroll. These read routes are live.
|
|
16
13
|
*/
|
|
17
14
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
15
|
import type { IFrihetClient } from "../client-interface.js";
|
package/dist/tools/payroll.js
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
* Payroll preparation tools for the Frihet MCP server — D4-B megasprint (2 tools).
|
|
3
3
|
*
|
|
4
4
|
* Tools:
|
|
5
|
-
* 1. payroll_export —
|
|
6
|
-
* 2. payroll_checklist —
|
|
5
|
+
* 1. payroll_export — read the normalized export-ready employee dataset
|
|
6
|
+
* 2. payroll_checklist — read payroll-profile readiness for payable employees
|
|
7
7
|
*
|
|
8
8
|
* REST surface: /v1/payroll/prep/export, /v1/payroll/prep/employees
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* NOTE: ERP backend endpoints land in parallel D4-A wave. 404s propagate as isError
|
|
15
|
-
* until backend ships. TODO: confirm A3 columns + SILTRA file extension.
|
|
10
|
+
* The ERP validates and echoes a destination-format label but returns the same
|
|
11
|
+
* normalized JSON dataset for each value. It does not generate a format-specific
|
|
12
|
+
* file, and Frihet does not process payroll. These read routes are live.
|
|
16
13
|
*/
|
|
17
14
|
import { z } from "zod/v4";
|
|
18
15
|
import { withToolLogging, formatRecord, getContent, READ_ONLY_ANNOTATIONS, payrollExportOutput, payrollChecklistOutput, } from "./shared.js";
|
|
@@ -20,24 +17,18 @@ import { withBackendGuard } from "./backend-availability.js";
|
|
|
20
17
|
export function registerPayrollTools(server, client) {
|
|
21
18
|
// -- payroll_export --
|
|
22
19
|
server.registerTool("payroll_export", {
|
|
23
|
-
title: "
|
|
24
|
-
description: "
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
" - 'holded' — Holded import format\n" +
|
|
30
|
-
" - 'siltra' — SILTRA Seguridad Social XML\n" +
|
|
31
|
-
"\n" +
|
|
32
|
-
"Frihet does NOT calculate payroll — it exports staged data for the gestoria. " +
|
|
33
|
-
"Month format: 'YYYY-MM'. " +
|
|
34
|
-
"/ Exporta datos de nominas en formato compatible con gestoria. Mes en formato 'YYYY-MM'.",
|
|
20
|
+
title: "Payroll Export-Ready Data",
|
|
21
|
+
description: "Read the normalized payroll-ready employee dataset for a month. " +
|
|
22
|
+
"The ERP accepts 'a3', 'contasol', 'sage', or 'siltra' as a destination label and echoes it; " +
|
|
23
|
+
"the response is JSON and is identical across formats apart from that label. " +
|
|
24
|
+
"No CSV, XML, PDF, or provider-specific file is generated. Frihet does not calculate payroll. " +
|
|
25
|
+
"/ Lee los datos normalizados listos para nomina; no genera un archivo especifico del proveedor.",
|
|
35
26
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
36
27
|
inputSchema: {
|
|
37
28
|
format: z
|
|
38
|
-
.enum(["a3", "contasol", "sage", "
|
|
39
|
-
.describe("
|
|
40
|
-
month: z.string().regex(/^\d{4}
|
|
29
|
+
.enum(["a3", "contasol", "sage", "siltra"])
|
|
30
|
+
.describe("Echoed destination label; does not change JSON serialization / Etiqueta de destino"),
|
|
31
|
+
month: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).describe("Month in 'YYYY-MM' format / Mes formato 'YYYY-MM'"),
|
|
41
32
|
},
|
|
42
33
|
outputSchema: payrollExportOutput,
|
|
43
34
|
}, async ({ format, month }) => withToolLogging("payroll_export", () => withBackendGuard("payroll_export", "/v1/payroll/prep/export", async () => {
|
|
@@ -50,17 +41,13 @@ export function registerPayrollTools(server, client) {
|
|
|
50
41
|
// -- payroll_checklist --
|
|
51
42
|
server.registerTool("payroll_checklist", {
|
|
52
43
|
title: "Payroll Readiness Checklist",
|
|
53
|
-
description: "List
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
" - 'blocked' — manually blocked or data inconsistency detected\n" +
|
|
58
|
-
"\n" +
|
|
59
|
-
"Use BEFORE payroll_export to identify gaps. Month format: 'YYYY-MM'. " +
|
|
60
|
-
"/ Lista empleados con estado de preparacion para nomina. Usar antes de payroll_export.",
|
|
44
|
+
description: "List payable employees (active or on leave) for a month, whether each has a payroll profile, " +
|
|
45
|
+
"whether its required fields are ready, which fields are missing, and monthly review state. " +
|
|
46
|
+
"Suspended and offboarded employees are excluded. Use before payroll_export. " +
|
|
47
|
+
"/ Lista empleados pagables y la preparacion real de su perfil de nomina.",
|
|
61
48
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
62
49
|
inputSchema: {
|
|
63
|
-
month: z.string().regex(/^\d{4}
|
|
50
|
+
month: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).describe("Month in 'YYYY-MM' format / Mes formato 'YYYY-MM'"),
|
|
64
51
|
},
|
|
65
52
|
outputSchema: payrollChecklistOutput,
|
|
66
53
|
}, async ({ month }) => withToolLogging("payroll_checklist", () => withBackendGuard("payroll_checklist", "/v1/payroll/prep/employees", async () => {
|
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
* Permissions tools for the Frihet MCP server — D4-B megasprint (2 tools).
|
|
3
3
|
*
|
|
4
4
|
* Tools:
|
|
5
|
-
* 1. permissions_matrix —
|
|
6
|
-
* 2. permissions_me —
|
|
5
|
+
* 1. permissions_matrix — documented RBAC-model snapshot
|
|
6
|
+
* 2. permissions_me — RBAC model + actual API-key scope reporting
|
|
7
7
|
*
|
|
8
8
|
* REST surface: /v1/permissions/matrix, /v1/permissions/me
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* NOTE: ERP backend endpoints land in parallel D4-A wave. 404s propagate as isError
|
|
14
|
-
* until backend ships.
|
|
10
|
+
* These read-only reports are useful for discovery, but neither is an exhaustive
|
|
11
|
+
* runtime-authorization prediction. A backend 403 remains authoritative.
|
|
15
12
|
*/
|
|
16
13
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
14
|
import type { IFrihetClient } from "../client-interface.js";
|
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
* Permissions tools for the Frihet MCP server — D4-B megasprint (2 tools).
|
|
3
3
|
*
|
|
4
4
|
* Tools:
|
|
5
|
-
* 1. permissions_matrix —
|
|
6
|
-
* 2. permissions_me —
|
|
5
|
+
* 1. permissions_matrix — documented RBAC-model snapshot
|
|
6
|
+
* 2. permissions_me — RBAC model + actual API-key scope reporting
|
|
7
7
|
*
|
|
8
8
|
* REST surface: /v1/permissions/matrix, /v1/permissions/me
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* NOTE: ERP backend endpoints land in parallel D4-A wave. 404s propagate as isError
|
|
14
|
-
* until backend ships.
|
|
10
|
+
* These read-only reports are useful for discovery, but neither is an exhaustive
|
|
11
|
+
* runtime-authorization prediction. A backend 403 remains authoritative.
|
|
15
12
|
*/
|
|
16
13
|
import { withToolLogging, formatRecord, getContent, READ_ONLY_ANNOTATIONS, permissionsMatrixOutput, permissionsMeOutput, } from "./shared.js";
|
|
17
14
|
import { withBackendGuard } from "./backend-availability.js";
|
|
@@ -19,10 +16,9 @@ export function registerPermissionsTools(server, client) {
|
|
|
19
16
|
// -- permissions_matrix --
|
|
20
17
|
server.registerTool("permissions_matrix", {
|
|
21
18
|
title: "Permissions Matrix",
|
|
22
|
-
description: "Return the
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"/ Devuelve la matriz completa de roles y permisos del workspace.",
|
|
19
|
+
description: "Return the backend's documented RBAC-model snapshot by role, resource, and action. " +
|
|
20
|
+
"It is not derived from Firestore rules and is not a runtime authorization guarantee; " +
|
|
21
|
+
"a backend 403 is authoritative. / Devuelve el modelo RBAC documentado, no una garantia exhaustiva de autorizacion.",
|
|
26
22
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
27
23
|
inputSchema: {},
|
|
28
24
|
outputSchema: permissionsMatrixOutput,
|
|
@@ -36,9 +32,10 @@ export function registerPermissionsTools(server, client) {
|
|
|
36
32
|
// -- permissions_me --
|
|
37
33
|
server.registerTool("permissions_me", {
|
|
38
34
|
title: "My Permissions",
|
|
39
|
-
description: "Return the caller's
|
|
40
|
-
"
|
|
41
|
-
"
|
|
35
|
+
description: "Return the caller's backwards-compatible RBAC-model fields separately from actual API-key scopes, " +
|
|
36
|
+
"unrestricted-key semantics, known e-invoice scope denial, and surfaces not reported here. " +
|
|
37
|
+
"This is non-exhaustive; use the exact backend 403 as authority. " +
|
|
38
|
+
"/ Separa el modelo RBAC de los scopes reales de la API key; no es una autorizacion exhaustiva.",
|
|
42
39
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
43
40
|
inputSchema: {},
|
|
44
41
|
outputSchema: permissionsMeOutput,
|
package/dist/tools/products.js
CHANGED
|
@@ -67,9 +67,9 @@ export function registerProductTools(server, client) {
|
|
|
67
67
|
"Los productos se pueden usar al crear facturas y presupuestos para entrada rapida de datos.",
|
|
68
68
|
annotations: CREATE_ANNOTATIONS,
|
|
69
69
|
inputSchema: {
|
|
70
|
-
name: z.string().describe("Product/service name / Nombre del producto o servicio"),
|
|
71
|
-
unitPrice: z.number().describe("Unit price in EUR / Precio unitario en EUR"),
|
|
72
|
-
description: z.string().optional().describe("Product description / Descripcion"),
|
|
70
|
+
name: z.string().trim().min(1).max(200).describe("Product/service name / Nombre del producto o servicio"),
|
|
71
|
+
unitPrice: z.number().finite().min(0).max(10_000_000).describe("Unit price in EUR / Precio unitario en EUR"),
|
|
72
|
+
description: z.string().max(2000).optional().describe("Product description / Descripcion"),
|
|
73
73
|
taxRate: z
|
|
74
74
|
.number()
|
|
75
75
|
.min(0)
|
|
@@ -94,9 +94,9 @@ export function registerProductTools(server, client) {
|
|
|
94
94
|
annotations: UPDATE_ANNOTATIONS,
|
|
95
95
|
inputSchema: {
|
|
96
96
|
id: z.string().describe("Product ID / ID del producto"),
|
|
97
|
-
name: z.string().optional().describe("Name / Nombre"),
|
|
98
|
-
unitPrice: z.number().optional().describe("Unit price / Precio unitario"),
|
|
99
|
-
description: z.string().optional().describe("Description / Descripcion"),
|
|
97
|
+
name: z.string().trim().min(1).max(200).optional().describe("Name / Nombre"),
|
|
98
|
+
unitPrice: z.number().finite().min(0).max(10_000_000).optional().describe("Unit price / Precio unitario"),
|
|
99
|
+
description: z.string().max(2000).optional().describe("Description / Descripcion"),
|
|
100
100
|
taxRate: z.number().min(0).max(100).optional().describe("Tax rate % / IVA %"),
|
|
101
101
|
},
|
|
102
102
|
outputSchema: productItemOutput,
|