@frihet/mcp-server 1.16.6 → 2.0.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 +168 -82
- 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 +552 -0
- package/dist/capability-truth.d.ts +29 -0
- package/dist/capability-truth.js +262 -0
- package/dist/claude-candidate-contract.d.ts +482 -0
- package/dist/claude-candidate-contract.js +87 -0
- package/dist/claude-output-privacy.d.ts +17 -0
- package/dist/claude-output-privacy.js +240 -0
- package/dist/claude-profile.d.ts +18 -0
- package/dist/claude-profile.js +260 -0
- package/dist/client-interface.d.ts +47 -33
- package/dist/client.d.ts +172 -40
- package/dist/client.js +708 -165
- package/dist/demo-client.d.ts +41 -32
- package/dist/demo-client.js +202 -31
- package/dist/demo-fixtures.d.ts +3 -3
- package/dist/demo-fixtures.js +7 -7
- package/dist/deposit-contract.d.ts +29 -0
- package/dist/deposit-contract.js +76 -0
- package/dist/fetch-no-redirect.d.ts +3 -0
- package/dist/fetch-no-redirect.js +16 -0
- package/dist/fiscal-aliases.d.ts +44 -0
- package/dist/fiscal-aliases.js +62 -0
- package/dist/fiscal-period.d.ts +26 -0
- package/dist/fiscal-period.js +31 -0
- package/dist/google-profile.d.ts +19 -0
- package/dist/google-profile.js +122 -0
- package/dist/index.js +68 -59
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +140 -8
- package/dist/muse-profile.d.ts +19 -0
- package/dist/muse-profile.js +122 -0
- package/dist/observability.d.ts +18 -29
- package/dist/observability.js +194 -106
- package/dist/openai-profile.d.ts +26 -22
- package/dist/openai-profile.js +1193 -157
- package/dist/openai-review-contract.d.ts +15 -6
- package/dist/openai-review-contract.js +714 -41
- package/dist/openai-review-oauth.d.ts +149 -2
- package/dist/openai-review-oauth.js +220 -6
- package/dist/prompts/register-all.d.ts +2 -0
- package/dist/prompts/register-all.js +34 -78
- package/dist/public-capability-contract.d.ts +39 -0
- package/dist/public-capability-contract.js +186 -0
- package/dist/redaction.d.ts +15 -0
- package/dist/redaction.js +83 -0
- package/dist/resources/register-all.js +154 -64
- package/dist/retry-after.d.ts +19 -0
- package/dist/retry-after.js +84 -0
- package/dist/server-composition.d.ts +19 -0
- package/dist/server-composition.js +74 -0
- package/dist/tool-exposure.d.ts +31 -27
- package/dist/tool-exposure.js +211 -87
- package/dist/tools/accountingClose.d.ts +14 -5
- package/dist/tools/accountingClose.js +41 -23
- package/dist/tools/audit_gl.js +8 -8
- package/dist/tools/backend-availability.d.ts +8 -29
- package/dist/tools/backend-availability.js +31 -30
- package/dist/tools/bank_rules.js +66 -15
- package/dist/tools/banking.js +5 -13
- package/dist/tools/clients.js +261 -15
- package/dist/tools/crm.js +22 -21
- package/dist/tools/deposits.js +114 -85
- package/dist/tools/einvoice.d.ts +16 -2
- package/dist/tools/einvoice.js +59 -12
- package/dist/tools/expenses.d.ts +16 -0
- package/dist/tools/expenses.js +47 -27
- package/dist/tools/fiscal.d.ts +3 -4
- package/dist/tools/fiscal.js +93 -60
- package/dist/tools/gestoria.js +15 -7
- package/dist/tools/hr.d.ts +2 -3
- package/dist/tools/hr.js +49 -20
- package/dist/tools/igic.d.ts +9 -8
- package/dist/tools/igic.js +34 -64
- package/dist/tools/impuesto_sociedades.d.ts +4 -5
- package/dist/tools/impuesto_sociedades.js +16 -37
- package/dist/tools/intelligence.js +166 -12
- package/dist/tools/invoices.js +224 -54
- package/dist/tools/kitchen.js +19 -9
- 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 +172 -26
- package/dist/tools/recurring.js +26 -25
- package/dist/tools/register-all.d.ts +2 -2
- package/dist/tools/register-all.js +9 -6
- package/dist/tools/search.d.ts +19 -0
- package/dist/tools/search.js +84 -0
- package/dist/tools/shared.d.ts +1445 -109
- package/dist/tools/shared.js +478 -102
- package/dist/tools/team.js +24 -28
- package/dist/tools/time.js +4 -12
- package/dist/tools/vendors.js +249 -15
- package/dist/tools/webhooks.js +35 -30
- package/dist/types.d.ts +31 -5
- package/dist/workflows/daily-operational-brief.d.ts +163 -0
- package/dist/workflows/daily-operational-brief.js +881 -0
- package/docs/agent-onboarding.json +391 -0
- package/package.json +49 -12
- package/dist/tools/onboarding.d.ts +0 -19
- package/dist/tools/onboarding.js +0 -64
package/dist/tools/deposits.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* Deposit tools for the Frihet MCP server.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
|
-
import {
|
|
5
|
+
import { applyDepositFields, depositFields, DepositInputError } from "../deposit-contract.js";
|
|
6
|
+
import { withToolLogging, confirmationRequiredError, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, enrichResponse, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, DELETE_ANNOTATIONS, ERROR_CONTENT_ANNOTATIONS, paginatedOutput, deleteResultOutput, depositItemOutput, actionResultOutput } from "./shared.js";
|
|
7
|
+
function depositValidationResponse(error) {
|
|
8
|
+
return {
|
|
9
|
+
isError: true,
|
|
10
|
+
content: [{ type: "text", text: `Error: ${error.message}`, annotations: ERROR_CONTENT_ANNOTATIONS }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
6
13
|
export function registerDepositTools(server, client) {
|
|
7
14
|
// -- list_deposits --
|
|
8
15
|
server.registerTool("list_deposits", {
|
|
@@ -45,8 +52,8 @@ export function registerDepositTools(server, client) {
|
|
|
45
52
|
const result = await client.listDeposits({ limit, offset, after, fields, from, to, clientId, status });
|
|
46
53
|
const hints = enrichResponse("deposits", "list", result.data);
|
|
47
54
|
return {
|
|
48
|
-
content: [listContent(formatPaginatedResponse("deposits", result))],
|
|
49
|
-
structuredContent: { ...result
|
|
55
|
+
content: [listContent(formatPaginatedResponse("deposits", result) + hints)],
|
|
56
|
+
structuredContent: { ...result },
|
|
50
57
|
};
|
|
51
58
|
}));
|
|
52
59
|
// -- get_deposit --
|
|
@@ -69,69 +76,71 @@ export function registerDepositTools(server, client) {
|
|
|
69
76
|
// -- create_deposit --
|
|
70
77
|
server.registerTool("create_deposit", {
|
|
71
78
|
title: "Create Deposit",
|
|
72
|
-
description: "Record a
|
|
73
|
-
"
|
|
74
|
-
"Example: clientId='abc123', amount=500,
|
|
75
|
-
"/ Registra un
|
|
76
|
-
"
|
|
79
|
+
description: "Record a deposit in Frihet's bookkeeping; no money is transferred. Requires clientId, clientName, amount, date and description. " +
|
|
80
|
+
"The API initializes status='active'; status and notes are not writable. " +
|
|
81
|
+
"Example: clientId='abc123', clientName='Example Client', amount=500, date='2026-10-21', description='Project retainer' " +
|
|
82
|
+
"/ Registra un depósito contable sin transferir dinero. Requiere clientId, clientName, amount, date y description. " +
|
|
83
|
+
"La API inicia el estado 'active'; status y notes no son campos editables.",
|
|
77
84
|
annotations: CREATE_ANNOTATIONS,
|
|
78
|
-
inputSchema: {
|
|
79
|
-
clientId:
|
|
80
|
-
clientName:
|
|
81
|
-
amount:
|
|
82
|
-
currency:
|
|
83
|
-
date:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
status: z
|
|
89
|
-
.string()
|
|
90
|
-
.optional()
|
|
91
|
-
.describe("Status (e.g. 'pending', 'applied', 'refunded') / Estado"),
|
|
92
|
-
paymentMethod: z
|
|
93
|
-
.string()
|
|
94
|
-
.optional()
|
|
95
|
-
.describe("Payment method (e.g. 'bank_transfer', 'card') / Metodo de pago"),
|
|
96
|
-
reference: z.string().optional().describe("External reference or receipt number / Referencia externa"),
|
|
97
|
-
notes: z.string().optional().describe("Internal notes / Notas internas"),
|
|
98
|
-
},
|
|
85
|
+
inputSchema: z.object({
|
|
86
|
+
clientId: depositFields.clientId.describe("Client ID / ID del cliente"),
|
|
87
|
+
clientName: depositFields.clientName.describe("Client name / Nombre del cliente"),
|
|
88
|
+
amount: depositFields.amount.describe("Positive deposit amount / Importe positivo del depósito"),
|
|
89
|
+
currency: depositFields.currency.optional().describe("Currency code, defaults to EUR / Moneda, EUR por defecto"),
|
|
90
|
+
date: depositFields.receivedDate.describe("Received date (YYYY-MM-DD), sent as receivedDate / Fecha de recepción"),
|
|
91
|
+
description: depositFields.description.describe("Deposit description / Descripción del depósito"),
|
|
92
|
+
paymentMethod: depositFields.paymentMethod.describe("Payment method (e.g. bank_transfer) / Método de pago"),
|
|
93
|
+
reference: depositFields.paymentReference.describe("Payment reference, sent as paymentReference / Referencia del pago"),
|
|
94
|
+
}).strict(),
|
|
99
95
|
outputSchema: depositItemOutput,
|
|
100
96
|
}, async (input) => withToolLogging("create_deposit", async () => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
try {
|
|
98
|
+
const result = await client.createDeposit(input);
|
|
99
|
+
const hints = enrichResponse("deposits", "create", result);
|
|
100
|
+
return {
|
|
101
|
+
content: [mutateContent(formatRecord("Deposit created", result) + hints)],
|
|
102
|
+
structuredContent: { ...result },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error instanceof DepositInputError)
|
|
107
|
+
return depositValidationResponse(error);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
107
110
|
}));
|
|
108
111
|
// -- update_deposit --
|
|
109
112
|
server.registerTool("update_deposit", {
|
|
110
113
|
title: "Update Deposit",
|
|
111
114
|
description: "Update an existing deposit using PATCH semantics. Only the provided fields will be changed. " +
|
|
112
|
-
"
|
|
113
|
-
"
|
|
115
|
+
"At least one writable field is required; status and notes are not writable. " +
|
|
116
|
+
"Example: id='abc123', reference='RECEIPT-42' " +
|
|
117
|
+
"/ Actualiza un depósito existente. Requiere al menos un campo editable; status y notes no se pueden guardar.",
|
|
114
118
|
annotations: UPDATE_ANNOTATIONS,
|
|
115
|
-
inputSchema: {
|
|
116
|
-
id: z.string().describe("Deposit ID / ID del
|
|
117
|
-
clientId:
|
|
118
|
-
clientName:
|
|
119
|
-
amount:
|
|
120
|
-
currency:
|
|
121
|
-
date:
|
|
122
|
-
description:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
notes: z.string().optional().describe("Notes / Notas"),
|
|
127
|
-
},
|
|
119
|
+
inputSchema: z.object({
|
|
120
|
+
id: z.string().min(1).describe("Deposit ID / ID del depósito"),
|
|
121
|
+
clientId: depositFields.clientId.optional().describe("Client ID / ID del cliente"),
|
|
122
|
+
clientName: depositFields.clientName.optional().describe("Client name / Nombre del cliente"),
|
|
123
|
+
amount: depositFields.amount.optional().describe("Positive amount / Importe positivo"),
|
|
124
|
+
currency: depositFields.currency.optional().describe("Currency code / Moneda"),
|
|
125
|
+
date: depositFields.receivedDate.optional().describe("Received date (YYYY-MM-DD) / Fecha de recepción"),
|
|
126
|
+
description: depositFields.description.optional().describe("Description / Descripción"),
|
|
127
|
+
paymentMethod: depositFields.paymentMethod.describe("Payment method / Método de pago"),
|
|
128
|
+
reference: depositFields.paymentReference.describe("Payment reference / Referencia del pago"),
|
|
129
|
+
}).strict().refine(({ id: _id, ...fields }) => Object.values(fields).some((value) => value !== undefined), { message: "Provide at least one writable deposit field / Indica al menos un campo del depósito" }),
|
|
128
130
|
outputSchema: depositItemOutput,
|
|
129
131
|
}, async ({ id, ...data }) => withToolLogging("update_deposit", async () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
try {
|
|
133
|
+
const result = await client.updateDeposit(id, data);
|
|
134
|
+
return {
|
|
135
|
+
content: [mutateContent(formatRecord("Deposit updated", result))],
|
|
136
|
+
structuredContent: result,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof DepositInputError)
|
|
141
|
+
return depositValidationResponse(error);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
135
144
|
}));
|
|
136
145
|
// -- delete_deposit --
|
|
137
146
|
server.registerTool("delete_deposit", {
|
|
@@ -147,8 +156,8 @@ export function registerDepositTools(server, client) {
|
|
|
147
156
|
await client.deleteDeposit(id);
|
|
148
157
|
const hints = enrichResponse("deposits", "delete", { id });
|
|
149
158
|
return {
|
|
150
|
-
content: [mutateContent(`Deposit ${id} deleted successfully. / Deposito ${id} eliminado correctamente.`)],
|
|
151
|
-
structuredContent: { success: true, id
|
|
159
|
+
content: [mutateContent(`Deposit ${id} deleted successfully. / Deposito ${id} eliminado correctamente.` + hints)],
|
|
160
|
+
structuredContent: { success: true, id },
|
|
152
161
|
};
|
|
153
162
|
}));
|
|
154
163
|
// -- apply_deposit --
|
|
@@ -156,43 +165,63 @@ export function registerDepositTools(server, client) {
|
|
|
156
165
|
title: "Apply Deposit",
|
|
157
166
|
description: "Apply a deposit to an invoice or mark it as used. " +
|
|
158
167
|
"Transitions the deposit status to 'applied'. " +
|
|
159
|
-
"
|
|
160
|
-
"
|
|
168
|
+
"ERP requires invoiceId + invoiceNumber + amount on the body; id is the deposit ID; notes is not writable. " +
|
|
169
|
+
"Example: id='dep_abc123', invoiceId='inv_xyz', invoiceNumber='F-2026-007', amount=150 " +
|
|
170
|
+
"/ Aplica un deposito a una factura o lo marca como utilizado. Requiere invoiceId, invoiceNumber y amount; notes no se puede guardar.",
|
|
161
171
|
annotations: UPDATE_ANNOTATIONS,
|
|
162
|
-
inputSchema: {
|
|
163
|
-
id: z.string().describe("Deposit ID / ID del deposito"),
|
|
164
|
-
invoiceId:
|
|
165
|
-
.string()
|
|
166
|
-
.optional()
|
|
172
|
+
inputSchema: z.object({
|
|
173
|
+
id: z.string().min(1).describe("Deposit ID / ID del deposito"),
|
|
174
|
+
invoiceId: applyDepositFields.invoiceId
|
|
167
175
|
.describe("Invoice ID to apply the deposit to / ID de la factura a la que aplicar el deposito"),
|
|
168
|
-
|
|
169
|
-
|
|
176
|
+
invoiceNumber: applyDepositFields.invoiceNumber
|
|
177
|
+
.describe("Invoice number (document number) the deposit applies to / Numero de la factura"),
|
|
178
|
+
amount: applyDepositFields.amount
|
|
179
|
+
.describe("Amount of the deposit to apply in EUR / Importe del deposito a aplicar en EUR"),
|
|
180
|
+
}).strict(),
|
|
170
181
|
outputSchema: actionResultOutput,
|
|
171
182
|
}, async ({ id, ...data }) => withToolLogging("apply_deposit", async () => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
try {
|
|
184
|
+
const result = await client.applyDeposit(id, data);
|
|
185
|
+
return {
|
|
186
|
+
content: [mutateContent(formatRecord("Deposit applied", result))],
|
|
187
|
+
structuredContent: result,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error instanceof DepositInputError)
|
|
192
|
+
return depositValidationResponse(error);
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
177
195
|
}));
|
|
178
196
|
// -- refund_deposit --
|
|
179
197
|
server.registerTool("refund_deposit", {
|
|
180
|
-
title: "
|
|
181
|
-
description: "
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
198
|
+
title: "Record Deposit Refund",
|
|
199
|
+
description: "Record a full or partial deposit refund in Frihet's bookkeeping; no money is transferred. Requires confirm=true. " +
|
|
200
|
+
"Omit amount to record a refund of the remaining balance. Only a full refund sets status='refunded'. " +
|
|
201
|
+
"A new call can record another partial refund; after an uncertain result, check get_deposit before retrying. " +
|
|
202
|
+
"Example: id='dep_abc123', amount=50, confirm=true " +
|
|
203
|
+
"/ Registra una devolución total o parcial del depósito sin transferir dinero. Requiere confirm=true. " +
|
|
204
|
+
"Omite amount para registrar la devolución del saldo restante. Solo la devolución total marca el estado 'refunded'. " +
|
|
205
|
+
"Una nueva llamada puede registrar otra devolución parcial; ante un resultado incierto, consulta get_deposit antes de repetir.",
|
|
206
|
+
annotations: { ...UPDATE_ANNOTATIONS, destructiveHint: true, idempotentHint: false },
|
|
207
|
+
inputSchema: z.object({
|
|
208
|
+
id: z.string().min(1).describe("Deposit ID / ID del depósito"),
|
|
209
|
+
amount: z.number().positive().optional().describe("Positive refund amount in the deposit currency; omit for the remaining balance / Importe positivo en la moneda del depósito; omite para el saldo restante"),
|
|
210
|
+
confirm: z
|
|
211
|
+
.boolean()
|
|
212
|
+
.describe("Must be true to record the refund; no money is transferred / Debe ser true para registrar la devolución; no se transfiere dinero"),
|
|
213
|
+
}).strict(),
|
|
191
214
|
outputSchema: actionResultOutput,
|
|
192
|
-
}, async ({ id,
|
|
193
|
-
|
|
215
|
+
}, async ({ id, confirm, amount }) => withToolLogging("refund_deposit", async () => {
|
|
216
|
+
if (confirm !== true) {
|
|
217
|
+
return confirmationRequiredError("Error: confirm=true is required to record a deposit refund. " +
|
|
218
|
+
"This reduces the recorded remaining balance; no money is transferred and this tool cannot undo the entry. / " +
|
|
219
|
+
"Se requiere confirm=true para registrar la devolución y reducir el saldo contable. " +
|
|
220
|
+
"No se transfiere dinero y esta herramienta no permite deshacer el registro.");
|
|
221
|
+
}
|
|
222
|
+
const result = await client.refundDeposit(id, amount === undefined ? {} : { amount });
|
|
194
223
|
return {
|
|
195
|
-
content: [mutateContent(formatRecord("Deposit
|
|
224
|
+
content: [mutateContent(formatRecord("Deposit refund recorded (no money transferred)", result))],
|
|
196
225
|
structuredContent: result,
|
|
197
226
|
};
|
|
198
227
|
}));
|
package/dist/tools/einvoice.d.ts
CHANGED
|
@@ -52,15 +52,29 @@ export declare const eInvoiceFormatSchema: z.ZodEnum<{
|
|
|
52
52
|
"xrechnung-cii": "xrechnung-cii";
|
|
53
53
|
"xrechnung-ubl": "xrechnung-ubl";
|
|
54
54
|
"facturx-en16931": "facturx-en16931";
|
|
55
|
+
fatturapa: "fatturapa";
|
|
56
|
+
"peppol-bis-3": "peppol-bis-3";
|
|
57
|
+
ubl: "ubl";
|
|
58
|
+
cii: "cii";
|
|
55
59
|
"facturx-extended": "facturx-extended";
|
|
56
60
|
"facturx-basic": "facturx-basic";
|
|
57
61
|
"facturx-minimum": "facturx-minimum";
|
|
62
|
+
}>;
|
|
63
|
+
export type EInvoiceFormat = z.infer<typeof eInvoiceFormatSchema>;
|
|
64
|
+
/** Export is narrower than dispatch/validation. The ERP public export API
|
|
65
|
+
* exposes Factur-X EN16931 only; accepting Extended/Basic/Minimum here and
|
|
66
|
+
* silently mapping them to EN16931 would produce the wrong fiscal profile. */
|
|
67
|
+
export declare const eInvoiceExportFormatSchema: z.ZodEnum<{
|
|
68
|
+
facturae: "facturae";
|
|
69
|
+
"xrechnung-cii": "xrechnung-cii";
|
|
70
|
+
"xrechnung-ubl": "xrechnung-ubl";
|
|
71
|
+
"facturx-en16931": "facturx-en16931";
|
|
58
72
|
fatturapa: "fatturapa";
|
|
73
|
+
"peppol-bis-3": "peppol-bis-3";
|
|
74
|
+
"fa-2-ksef": "fa-2-ksef";
|
|
59
75
|
ubl: "ubl";
|
|
60
76
|
cii: "cii";
|
|
61
|
-
"peppol-bis-3": "peppol-bis-3";
|
|
62
77
|
}>;
|
|
63
|
-
export type EInvoiceFormat = z.infer<typeof eInvoiceFormatSchema>;
|
|
64
78
|
export declare const sendEInvoiceOutput: z.ZodObject<{
|
|
65
79
|
workflowRunId: z.ZodString;
|
|
66
80
|
status: z.ZodLiteral<"queued">;
|
package/dist/tools/einvoice.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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:
|
|
425
|
-
"ES B2G→facturae, DE→xrechnung-cii/ubl, FR→facturx
|
|
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
|
-
`
|
|
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: "
|
|
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: "
|
|
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 {
|
package/dist/tools/expenses.d.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
* Expense tools for the Frihet MCP server.
|
|
3
3
|
*/
|
|
4
4
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod/v4";
|
|
5
6
|
import type { IFrihetClient } from "../client-interface.js";
|
|
7
|
+
/**
|
|
8
|
+
* Public Zod schema for `create_expense` input — exported for tests so the
|
|
9
|
+
* validation contract can be exercised directly without spinning up a stub
|
|
10
|
+
* MCP server. The registered tool's inputSchema MUST wrap this exact object
|
|
11
|
+
* (MCP serializes the live Zod object to JSON Schema).
|
|
12
|
+
*/
|
|
13
|
+
export declare const createExpenseInputSchema: z.ZodObject<{
|
|
14
|
+
description: z.ZodString;
|
|
15
|
+
amount: z.ZodNumber;
|
|
16
|
+
category: z.ZodOptional<z.ZodString>;
|
|
17
|
+
date: z.ZodOptional<z.ZodISODate>;
|
|
18
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
19
|
+
taxDeductible: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
paidDate: z.ZodOptional<z.ZodISODate>;
|
|
21
|
+
}, z.core.$strict>;
|
|
6
22
|
export declare function registerExpenseTools(server: McpServer, client: IFrihetClient): void;
|
|
7
23
|
//# sourceMappingURL=expenses.d.ts.map
|
package/dist/tools/expenses.js
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
5
|
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, enrichResponse, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, DELETE_ANNOTATIONS, paginatedOutput, deleteResultOutput, expenseItemOutput } from "./shared.js";
|
|
6
|
+
/**
|
|
7
|
+
* Public Zod schema for `create_expense` input — exported for tests so the
|
|
8
|
+
* validation contract can be exercised directly without spinning up a stub
|
|
9
|
+
* MCP server. The registered tool's inputSchema MUST wrap this exact object
|
|
10
|
+
* (MCP serializes the live Zod object to JSON Schema).
|
|
11
|
+
*/
|
|
12
|
+
export const createExpenseInputSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
description: z.string().trim().min(1).max(1000).describe("Expense description / Descripcion del gasto"),
|
|
15
|
+
amount: z.number().finite().min(0.01).max(10_000_000).describe("Amount in EUR / Importe en EUR"),
|
|
16
|
+
category: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(10_000)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Expense category (e.g. 'office', 'travel', 'software') / Categoria"),
|
|
21
|
+
date: z
|
|
22
|
+
.iso.date()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Expense date in ISO 8601 (YYYY-MM-DD) / Fecha del gasto"),
|
|
25
|
+
vendor: z.string().trim().max(200).optional().describe("Vendor/supplier name / Nombre del proveedor"),
|
|
26
|
+
taxDeductible: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Whether the expense is tax deductible / Si el gasto es deducible fiscalmente"),
|
|
30
|
+
paidDate: z
|
|
31
|
+
.iso.date()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Optional payment date in ISO 8601 (YYYY-MM-DD). Determines when the expense is treated as paid. " +
|
|
34
|
+
"Must be a real calendar date (2028-02-29 is valid, 2026-02-29 is not). " +
|
|
35
|
+
"/ Fecha de pago opcional en ISO 8601 (YYYY-MM-DD). Determina cuando se considera pagado el gasto."),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
6
38
|
export function registerExpenseTools(server, client) {
|
|
7
39
|
// -- list_expenses --
|
|
8
40
|
server.registerTool("list_expenses", {
|
|
@@ -45,8 +77,8 @@ export function registerExpenseTools(server, client) {
|
|
|
45
77
|
const result = await client.listExpenses({ limit, offset, after, fields, from, to, vendorId, category });
|
|
46
78
|
const hints = enrichResponse("expenses", "list", result.data);
|
|
47
79
|
return {
|
|
48
|
-
content: [listContent(formatPaginatedResponse("expenses", result))],
|
|
49
|
-
structuredContent: { ...result
|
|
80
|
+
content: [listContent(formatPaginatedResponse("expenses", result) + hints)],
|
|
81
|
+
structuredContent: { ...result },
|
|
50
82
|
};
|
|
51
83
|
}));
|
|
52
84
|
// -- get_expense --
|
|
@@ -67,38 +99,26 @@ export function registerExpenseTools(server, client) {
|
|
|
67
99
|
};
|
|
68
100
|
}));
|
|
69
101
|
// -- create_expense --
|
|
102
|
+
// paidDate (#1062): optional ISO calendar date. Strict shape (zero-padded
|
|
103
|
+
// YYYY-MM-DD) AND real-calendar validity (rejects 2026-02-29, 2026-04-31,
|
|
104
|
+
// non-zero-padded forms). Forwards verbatim to the REST paidDate field —
|
|
105
|
+
// the existing pass-through in client.createExpense handles the wire shape.
|
|
70
106
|
server.registerTool("create_expense", {
|
|
71
107
|
title: "Create Expense",
|
|
72
108
|
description: "Record a new expense. Requires a description and amount. " +
|
|
73
109
|
"Useful for tracking business costs, deductible expenses, and vendor payments. " +
|
|
74
|
-
"Example: description='Office supplies', amount=49.99, category='office', vendor='Amazon', taxDeductible=true " +
|
|
110
|
+
"Example: description='Office supplies', amount=49.99, category='office', vendor='Amazon', taxDeductible=true, paidDate='2026-03-15' " +
|
|
75
111
|
"/ Registra un nuevo gasto. Requiere descripcion e importe. " +
|
|
76
112
|
"Util para seguimiento de costes, gastos deducibles y pagos a proveedores.",
|
|
77
113
|
annotations: CREATE_ANNOTATIONS,
|
|
78
|
-
inputSchema:
|
|
79
|
-
description: z.string().describe("Expense description / Descripcion del gasto"),
|
|
80
|
-
amount: z.number().describe("Amount in EUR / Importe en EUR"),
|
|
81
|
-
category: z
|
|
82
|
-
.string()
|
|
83
|
-
.optional()
|
|
84
|
-
.describe("Expense category (e.g. 'office', 'travel', 'software') / Categoria"),
|
|
85
|
-
date: z
|
|
86
|
-
.string()
|
|
87
|
-
.optional()
|
|
88
|
-
.describe("Expense date in ISO 8601 (YYYY-MM-DD) / Fecha del gasto"),
|
|
89
|
-
vendor: z.string().optional().describe("Vendor/supplier name / Nombre del proveedor"),
|
|
90
|
-
taxDeductible: z
|
|
91
|
-
.boolean()
|
|
92
|
-
.optional()
|
|
93
|
-
.describe("Whether the expense is tax deductible / Si el gasto es deducible fiscalmente"),
|
|
94
|
-
},
|
|
114
|
+
inputSchema: createExpenseInputSchema,
|
|
95
115
|
outputSchema: expenseItemOutput,
|
|
96
116
|
}, async (input) => withToolLogging("create_expense", async () => {
|
|
97
117
|
const result = await client.createExpense(input);
|
|
98
118
|
const hints = enrichResponse("expenses", "create", result);
|
|
99
119
|
return {
|
|
100
|
-
content: [mutateContent(formatRecord("Expense created", result))],
|
|
101
|
-
structuredContent: { ...result
|
|
120
|
+
content: [mutateContent(formatRecord("Expense created", result) + hints)],
|
|
121
|
+
structuredContent: { ...result },
|
|
102
122
|
};
|
|
103
123
|
}));
|
|
104
124
|
// -- update_expense --
|
|
@@ -110,10 +130,10 @@ export function registerExpenseTools(server, client) {
|
|
|
110
130
|
annotations: UPDATE_ANNOTATIONS,
|
|
111
131
|
inputSchema: {
|
|
112
132
|
id: z.string().describe("Expense ID / ID del gasto"),
|
|
113
|
-
description: z.string().optional().describe("Description / Descripcion"),
|
|
133
|
+
description: z.string().max(10_000).optional().describe("Description / Descripcion"),
|
|
114
134
|
amount: z.number().optional().describe("Amount in EUR / Importe"),
|
|
115
|
-
category: z.string().optional().describe("Category / Categoria"),
|
|
116
|
-
date: z.
|
|
135
|
+
category: z.string().max(10_000).optional().describe("Category / Categoria"),
|
|
136
|
+
date: z.iso.date().optional().describe("Date (YYYY-MM-DD) / Fecha"),
|
|
117
137
|
vendor: z.string().optional().describe("Vendor / Proveedor"),
|
|
118
138
|
taxDeductible: z.boolean().optional().describe("Tax deductible / Deducible"),
|
|
119
139
|
},
|
|
@@ -139,8 +159,8 @@ export function registerExpenseTools(server, client) {
|
|
|
139
159
|
await client.deleteExpense(id);
|
|
140
160
|
const hints = enrichResponse("expenses", "delete", { id });
|
|
141
161
|
return {
|
|
142
|
-
content: [mutateContent(`Expense ${id} deleted successfully. / Gasto ${id} eliminado correctamente.`)],
|
|
143
|
-
structuredContent: { success: true, id
|
|
162
|
+
content: [mutateContent(`Expense ${id} deleted successfully. / Gasto ${id} eliminado correctamente.` + hints)],
|
|
163
|
+
structuredContent: { success: true, id },
|
|
144
164
|
};
|
|
145
165
|
}));
|
|
146
166
|
}
|
package/dist/tools/fiscal.d.ts
CHANGED
|
@@ -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: /
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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";
|