@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.
Files changed (68) hide show
  1. package/README.md +92 -31
  2. package/dist/agent-onboarding.d.ts +98 -0
  3. package/dist/agent-onboarding.js +275 -0
  4. package/dist/api-origin.d.ts +9 -0
  5. package/dist/api-origin.js +41 -0
  6. package/dist/canary/mcp-harness.d.ts +77 -0
  7. package/dist/canary/mcp-harness.js +549 -0
  8. package/dist/capability-truth.d.ts +29 -0
  9. package/dist/capability-truth.js +254 -0
  10. package/dist/client-interface.d.ts +22 -14
  11. package/dist/client.d.ts +114 -18
  12. package/dist/client.js +488 -56
  13. package/dist/demo-client.d.ts +16 -13
  14. package/dist/demo-client.js +171 -17
  15. package/dist/demo-fixtures.d.ts +3 -3
  16. package/dist/demo-fixtures.js +7 -7
  17. package/dist/fiscal-aliases.d.ts +44 -0
  18. package/dist/fiscal-aliases.js +62 -0
  19. package/dist/index.js +41 -56
  20. package/dist/logger.d.ts +2 -2
  21. package/dist/logger.js +140 -8
  22. package/dist/observability.d.ts +18 -29
  23. package/dist/observability.js +192 -105
  24. package/dist/openai-profile.d.ts +25 -21
  25. package/dist/openai-profile.js +1166 -156
  26. package/dist/openai-review-contract.d.ts +15 -6
  27. package/dist/openai-review-contract.js +715 -41
  28. package/dist/openai-review-oauth.d.ts +47 -0
  29. package/dist/openai-review-oauth.js +73 -3
  30. package/dist/prompts/register-all.d.ts +2 -0
  31. package/dist/prompts/register-all.js +2 -0
  32. package/dist/public-capability-contract.d.ts +39 -0
  33. package/dist/public-capability-contract.js +186 -0
  34. package/dist/redaction.d.ts +10 -0
  35. package/dist/redaction.js +74 -0
  36. package/dist/resources/register-all.js +6 -12
  37. package/dist/server-composition.d.ts +16 -0
  38. package/dist/server-composition.js +44 -0
  39. package/dist/tool-exposure.d.ts +25 -22
  40. package/dist/tool-exposure.js +199 -77
  41. package/dist/tools/accountingClose.d.ts +14 -5
  42. package/dist/tools/accountingClose.js +41 -23
  43. package/dist/tools/backend-availability.d.ts +3 -3
  44. package/dist/tools/backend-availability.js +3 -3
  45. package/dist/tools/clients.js +9 -9
  46. package/dist/tools/crm.js +22 -21
  47. package/dist/tools/deposits.js +30 -11
  48. package/dist/tools/einvoice.d.ts +1 -1
  49. package/dist/tools/expenses.d.ts +16 -0
  50. package/dist/tools/expenses.js +47 -27
  51. package/dist/tools/hr.d.ts +2 -3
  52. package/dist/tools/hr.js +8 -9
  53. package/dist/tools/intelligence.js +2 -2
  54. package/dist/tools/invoices.js +150 -47
  55. package/dist/tools/payroll.d.ts +5 -8
  56. package/dist/tools/payroll.js +19 -32
  57. package/dist/tools/permissions.d.ts +4 -7
  58. package/dist/tools/permissions.js +11 -14
  59. package/dist/tools/products.js +6 -6
  60. package/dist/tools/quotes.js +109 -24
  61. package/dist/tools/shared.d.ts +1431 -75
  62. package/dist/tools/shared.js +386 -76
  63. package/dist/tools/team.js +18 -14
  64. package/dist/tools/vendors.js +9 -9
  65. package/dist/tools/webhooks.js +35 -30
  66. package/dist/types.d.ts +28 -4
  67. package/docs/agent-onboarding.json +381 -0
  68. package/package.json +30 -7
@@ -2,19 +2,19 @@
2
2
  * Quote 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, deleteResultOutput, quoteItemOutput, actionResultOutput } from "./shared.js";
5
+ import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, enrichResponse, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, DELETE_ANNOTATIONS, paginatedOutput, documentDeleteResultOutput, quoteItemOutput, actionResultOutput } from "./shared.js";
6
6
  const quoteItemSchema = 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
11
  // Optional client-identity + ES fiscal fields the Frihet API accepts on quote
12
12
  // create+update (mirrors the invoice subset the backend supports for quotes).
13
13
  const quoteOptionalFields = {
14
14
  clientId: z
15
15
  .string()
16
16
  .optional()
17
- .describe("Existing client ID — server back-fills taxId/address / ID de cliente existente"),
17
+ .describe("Existing client ID — stored client details are used for the document / ID de cliente existente"),
18
18
  clientTaxId: z
19
19
  .string()
20
20
  .optional()
@@ -24,11 +24,11 @@ const quoteOptionalFields = {
24
24
  .optional()
25
25
  .describe("Client billing address / Direccion fiscal del cliente"),
26
26
  issueDate: z
27
- .string()
27
+ .iso.date()
28
28
  .optional()
29
29
  .describe("Issue date in ISO 8601 (YYYY-MM-DD), defaults to today / Fecha de emision"),
30
30
  dueDate: z
31
- .string()
31
+ .iso.date()
32
32
  .optional()
33
33
  .describe("Due date in ISO 8601 (YYYY-MM-DD) / Fecha de vencimiento"),
34
34
  taxRate: z
@@ -132,17 +132,17 @@ export function registerQuoteTools(server, client) {
132
132
  "Los presupuestos se pueden convertir en facturas despues.",
133
133
  annotations: CREATE_ANNOTATIONS,
134
134
  inputSchema: {
135
- clientName: z.string().describe("Client name / Nombre del cliente"),
135
+ clientName: z.string().trim().min(1).max(200).describe("Client name / Nombre del cliente"),
136
136
  ...quoteOptionalFields,
137
137
  items: z
138
138
  .array(quoteItemSchema)
139
139
  .min(1)
140
140
  .describe("Line items (each with description, quantity, unitPrice) / Conceptos del presupuesto"),
141
141
  validUntil: z
142
- .string()
142
+ .iso.date()
143
143
  .optional()
144
144
  .describe("Expiry date in ISO 8601 (YYYY-MM-DD) / Fecha de validez"),
145
- notes: z.string().optional().describe("Additional notes shown on the quote / Notas adicionales"),
145
+ notes: z.string().max(10_000).optional().describe("Additional notes shown on the quote / Notas adicionales"),
146
146
  status: z
147
147
  .enum(["draft", "sent", "accepted", "rejected", "expired"])
148
148
  .optional()
@@ -153,8 +153,8 @@ export function registerQuoteTools(server, client) {
153
153
  const result = await client.createQuote(input);
154
154
  const hints = enrichResponse("quotes", "create", result);
155
155
  return {
156
- content: [mutateContent(formatRecord("Quote created", result))],
157
- structuredContent: { ...result, ...hints },
156
+ content: [mutateContent(formatRecord("Quote created", result) + hints)],
157
+ structuredContent: { ...result },
158
158
  };
159
159
  }));
160
160
  // -- update_quote --
@@ -187,34 +187,119 @@ export function registerQuoteTools(server, client) {
187
187
  // -- delete_quote --
188
188
  server.registerTool("delete_quote", {
189
189
  title: "Delete Quote",
190
- description: "Permanently delete a quote by its ID. This action cannot be undone. " +
191
- "/ Elimina permanentemente un presupuesto por su ID. Esta accion no se puede deshacer.",
190
+ description: "Delete a quote by its ID. Requires confirm=true. " +
191
+ "Only a clean DRAFT with no delivery, response, attachment, or conversion evidence is removed permanently; a protected draft is refused and left unchanged. A sent/accepted/rejected/expired quote is NOT " +
192
+ "destroyed: the backend CANCELS it (status=cancelled) — the same non-destructive path invoices " +
193
+ "take — and it stays readable via get_quote. The result reports which happened in `outcome` " +
194
+ "(\"deleted\" or \"cancelled\"). " +
195
+ "/ Elimina un presupuesto por su ID. Requiere confirm=true. Solo un BORRADOR limpio sin evidencia de entrega, respuesta, adjuntos o conversion se " +
196
+ "elimina de forma permanente; un borrador protegido se rechaza sin cambios. Uno ya enviado/aceptado/rechazado NO se destruye: se CANCELA " +
197
+ "(status=cancelled) y sigue consultable con get_quote.",
192
198
  annotations: DELETE_ANNOTATIONS,
193
199
  inputSchema: {
194
200
  id: z.string().describe("Quote ID / ID del presupuesto"),
201
+ confirm: z
202
+ .boolean()
203
+ .describe("Must be true to confirm deletion / Debe ser true para confirmar la eliminacion"),
195
204
  },
196
- outputSchema: deleteResultOutput,
197
- }, async ({ id }) => withToolLogging("delete_quote", async () => {
198
- await client.deleteQuote(id);
205
+ outputSchema: documentDeleteResultOutput,
206
+ }, async ({ id, confirm }) => withToolLogging("delete_quote", async () => {
207
+ if (!confirm) {
208
+ return {
209
+ content: [
210
+ {
211
+ type: "text",
212
+ text: "Error: confirm=true is required to delete a quote. " +
213
+ "A clean draft without delivery, response, attachment, or conversion evidence is removed permanently; a protected draft is refused; a sent/accepted quote is CANCELLED " +
214
+ "(status=cancelled) instead and remains in the record. " +
215
+ "Set confirm=true to proceed. / " +
216
+ "Se requiere confirm=true para eliminar un presupuesto.",
217
+ },
218
+ ],
219
+ isError: true,
220
+ };
221
+ }
222
+ // 204 → undefined (draft row destroyed). 200 → soft-cancel body. See GAP-12.
223
+ const outcome = await client.deleteQuote(id);
224
+ const cancelled = !!outcome && typeof outcome === "object";
225
+ const body = cancelled ? outcome : {};
226
+ const previous = typeof body["previousStatus"] === "string" ? ` (was ${body["previousStatus"]})` : "";
199
227
  return {
200
- content: [mutateContent(`Quote ${id} deleted successfully. / Presupuesto ${id} eliminado correctamente.`)],
201
- structuredContent: { success: true, id },
228
+ content: [mutateContent(cancelled
229
+ ? `Quote ${id} was CANCELLED, not deleted${previous}: it still exists with status=cancelled. / ` +
230
+ `Presupuesto ${id} CANCELADO, no eliminado: sigue existiendo con status=cancelled.`
231
+ : `Quote ${id} deleted permanently (it was a draft). / ` +
232
+ `Presupuesto ${id} eliminado permanentemente (era un borrador).`)],
233
+ structuredContent: {
234
+ success: true,
235
+ id,
236
+ ...body,
237
+ outcome: cancelled ? "cancelled" : "deleted",
238
+ },
202
239
  };
203
240
  }));
204
241
  // -- send_quote --
205
242
  server.registerTool("send_quote", {
206
243
  title: "Send Quote",
207
- description: "Send a quote/estimate to the client via email. Optionally override the recipient email address. " +
244
+ description: "Send a quote/estimate to the client via email. Requires confirm=true. " +
245
+ "Optionally override the recipient email address. " +
208
246
  "The quote must exist and should not already be expired or rejected. " +
209
- "/ Envia un presupuesto al cliente por email. Opcionalmente se puede cambiar el email destinatario.",
247
+ "Sending reaches a third party and cannot be recalled. " +
248
+ "/ Envia un presupuesto al cliente por email. Requiere confirm=true. " +
249
+ "Opcionalmente se puede cambiar el email destinatario. El envio llega a un tercero y no se puede anular.",
210
250
  annotations: UPDATE_ANNOTATIONS,
211
251
  inputSchema: {
212
252
  id: z.string().describe("Quote ID / ID del presupuesto"),
213
253
  to: z.string().optional().describe("Override recipient email / Email destinatario alternativo"),
254
+ confirm: z
255
+ .boolean()
256
+ .describe("Must be true to confirm sending / Debe ser true para confirmar el envio"),
214
257
  },
215
258
  outputSchema: actionResultOutput,
216
- }, async ({ id, to }) => withToolLogging("send_quote", async () => {
217
- const result = await client.sendQuote(id, to);
259
+ }, async ({ id, to, confirm }) => withToolLogging("send_quote", async () => {
260
+ if (!confirm) {
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: "Error: confirm=true is required to send a quote. " +
266
+ "This delivers an email to the client — a third party outside this workspace — " +
267
+ "and it cannot be recalled once sent. Set confirm=true to proceed. / " +
268
+ "Se requiere confirm=true para enviar un presupuesto por email a un tercero.",
269
+ },
270
+ ],
271
+ isError: true,
272
+ };
273
+ }
274
+ const quote = await client.getQuote(id);
275
+ const status = typeof quote["status"] === "string" ? quote["status"] : undefined;
276
+ if (status && ["expired", "rejected", "cancelled"].includes(status)) {
277
+ return {
278
+ content: [{ type: "text", text: `Error: a ${status} quote cannot be sent.` }],
279
+ isError: true,
280
+ };
281
+ }
282
+ let recipientEmail = to;
283
+ if (!recipientEmail) {
284
+ const clientId = typeof quote["clientId"] === "string" ? quote["clientId"] : undefined;
285
+ if (!clientId) {
286
+ return {
287
+ content: [{ type: "text", text: "Error: this quote has no linked client with a saved email address." }],
288
+ isError: true,
289
+ };
290
+ }
291
+ const linkedClient = await client.getClient(clientId);
292
+ recipientEmail = typeof linkedClient["email"] === "string" && linkedClient["email"].trim()
293
+ ? linkedClient["email"].trim()
294
+ : undefined;
295
+ if (!recipientEmail) {
296
+ return {
297
+ content: [{ type: "text", text: "Error: the linked client has no saved email address." }],
298
+ isError: true,
299
+ };
300
+ }
301
+ }
302
+ const result = await client.sendQuote(id, recipientEmail);
218
303
  return {
219
304
  content: [mutateContent(formatRecord("Quote sent", result))],
220
305
  structuredContent: result,