@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
|
@@ -2,35 +2,48 @@
|
|
|
2
2
|
* Period close tools for the Frihet MCP server — D4-B megasprint (3 tools).
|
|
3
3
|
*
|
|
4
4
|
* Tools:
|
|
5
|
-
* 1. period_close_status — get the current
|
|
5
|
+
* 1. period_close_status — get the current or selected fiscal-year state
|
|
6
6
|
* 2. period_close — close a monthly or quarterly period (TRUST AREA)
|
|
7
7
|
* 3. period_reopen — reopen a closed period with required reason (TRUST AREA)
|
|
8
8
|
*
|
|
9
9
|
* REST surface: /v1/periods/current, /v1/periods/{id}, /v1/periods/close, /v1/periods/{id}/reopen
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* STATE OF THE WORLD (verified against erp-main functions/src/publicApi/families/
|
|
12
|
+
* periods.ts): only the READ route is implemented. Both WRITE routes answer
|
|
13
|
+
* HTTP 501 NOT_IMPLEMENTED — "deferred to the upcoming fiscal-write wave (Trust,
|
|
14
|
+
* serialized). Use year-end closing in the app for now." Nothing is frozen, and
|
|
15
|
+
* there is no idempotent no-op to rely on, because there is no handler at all.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* The descriptions below therefore state the 501 in the present tense. Do not
|
|
18
|
+
* restore aspirational prose about freezing or idempotency until a handler exists
|
|
19
|
+
* — GAP-13 was exactly that: an agent planning a year-end close read a mechanism
|
|
20
|
+
* the backend never had.
|
|
21
|
+
*
|
|
22
|
+
* `withBackendGuard` converts 404 only (backend-availability.ts isBackendNotFound),
|
|
23
|
+
* so a 501 surfaces through the generic error path as isError with the backend's
|
|
24
|
+
* own NOT_IMPLEMENTED message — an honest failure, not a fake success.
|
|
16
25
|
*/
|
|
17
26
|
import { z } from "zod/v4";
|
|
18
|
-
import { withToolLogging, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, periodStatusOutput, } from "./shared.js";
|
|
27
|
+
import { withToolLogging, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, currentPeriodOutput, periodStatusOutput, } from "./shared.js";
|
|
19
28
|
import { withBackendGuard } from "./backend-availability.js";
|
|
20
29
|
export function registerAccountingCloseTools(server, client) {
|
|
21
30
|
// -- period_close_status --
|
|
22
31
|
server.registerTool("period_close_status", {
|
|
23
32
|
title: "Period Close Status",
|
|
24
|
-
description: "Get the
|
|
25
|
-
"
|
|
26
|
-
"/ Devuelve el estado del
|
|
33
|
+
description: "Get the open or closed state of the current fiscal year, including its fiscal-year start, " +
|
|
34
|
+
"inclusive date range, and nullable closing details. Pass a four-digit fiscal-year label to read it explicitly. " +
|
|
35
|
+
"/ Devuelve el estado abierto o cerrado del ejercicio fiscal actual o indicado.",
|
|
27
36
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
28
37
|
inputSchema: {
|
|
29
|
-
periodId: z
|
|
38
|
+
periodId: z
|
|
39
|
+
.string()
|
|
40
|
+
.regex(/^\d{4}$/)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Compatibility field: fiscal-year label YYYY, not an arbitrary period ID (default: current) / Ejercicio fiscal YYYY"),
|
|
30
43
|
},
|
|
31
|
-
outputSchema:
|
|
32
|
-
}, async ({ periodId }) => withToolLogging("period_close_status", () => withBackendGuard("period_close_status", "/v1/periods/current", async () => {
|
|
33
|
-
const result = await client.getCurrentPeriod({ periodId });
|
|
44
|
+
outputSchema: currentPeriodOutput,
|
|
45
|
+
}, async ({ periodId }) => withToolLogging("period_close_status", () => withBackendGuard("period_close_status", periodId ? `/v1/periods/${periodId}` : "/v1/periods/current", async () => {
|
|
46
|
+
const result = await client.getCurrentPeriod({ fiscalYear: periodId });
|
|
34
47
|
return {
|
|
35
48
|
content: [getContent(formatRecord("Period status", result))],
|
|
36
49
|
structuredContent: result,
|
|
@@ -39,12 +52,15 @@ export function registerAccountingCloseTools(server, client) {
|
|
|
39
52
|
// -- period_close --
|
|
40
53
|
server.registerTool("period_close", {
|
|
41
54
|
title: "Close Accounting Period",
|
|
42
|
-
description: "TRUST AREA — FISCAL CLOSE.
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
55
|
+
description: "TRUST AREA — FISCAL CLOSE. NOT IMPLEMENTED SERVER-SIDE TODAY: the backend answers " +
|
|
56
|
+
"HTTP 501 NOT_IMPLEMENTED to POST /v1/periods/close, so calling this tool closes nothing. " +
|
|
57
|
+
"No invoices, expenses, journal entries or bank reconciliations are frozen, and there is no " +
|
|
58
|
+
"idempotent no-op to rely on. Close the period in the Frihet app instead. " +
|
|
59
|
+
"The call is still gated behind confirm=true and will surface the 501 as an error. " +
|
|
60
|
+
"Use period_close_status (implemented) to read the current period state. " +
|
|
61
|
+
"/ AREA DE CONFIANZA — CIERRE FISCAL. HOY NO IMPLEMENTADO EN EL SERVIDOR: el backend responde " +
|
|
62
|
+
"HTTP 501 a POST /v1/periods/close, asi que esta herramienta no cierra nada ni congela nada. " +
|
|
63
|
+
"El cierre debe hacerse en la app de Frihet.",
|
|
48
64
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
49
65
|
inputSchema: {
|
|
50
66
|
type: z
|
|
@@ -81,10 +97,12 @@ export function registerAccountingCloseTools(server, client) {
|
|
|
81
97
|
// -- period_reopen --
|
|
82
98
|
server.registerTool("period_reopen", {
|
|
83
99
|
title: "Reopen Accounting Period",
|
|
84
|
-
description: "TRUST AREA — FISCAL REOPEN.
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
100
|
+
description: "TRUST AREA — FISCAL REOPEN. NOT IMPLEMENTED SERVER-SIDE TODAY: the backend answers " +
|
|
101
|
+
"HTTP 501 NOT_IMPLEMENTED to POST /v1/periods/{id}/reopen, so calling this tool reopens nothing " +
|
|
102
|
+
"and writes no audit entry. Reopen the period in the Frihet app instead. " +
|
|
103
|
+
"The call is still gated behind confirm=true plus a reason, and will surface the 501 as an error. " +
|
|
104
|
+
"/ AREA DE CONFIANZA — REAPERTURA FISCAL. HOY NO IMPLEMENTADO EN EL SERVIDOR: el backend responde " +
|
|
105
|
+
"HTTP 501, asi que no se reabre nada ni se registra auditoria. Hazlo en la app de Frihet.",
|
|
88
106
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
89
107
|
inputSchema: {
|
|
90
108
|
periodId: z.string().describe("Closed period ID to reopen / ID del periodo cerrado"),
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* PROBLEM (the bug this prevents): several tool families are wired here AHEAD of
|
|
5
5
|
* their Frihet-ERP backend Cloud Functions shipping (fiscal `/v1/fiscal/*`, bank
|
|
6
6
|
* rules `/v1/banking/rules`, gestoria `/v1/gestoria/*`, GL audit `/v1/gl/*`,
|
|
7
|
-
* portal domain/onboard, IGIC, IS, VIES onboarding,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* portal domain/onboard, IGIC, IS, VIES onboarding, and webhook test). Until a
|
|
8
|
+
* staged CF deploys, the API returns a genuine HTTP 404. Live HR, payroll-read,
|
|
9
|
+
* and period-read tools also retain this guard as workspace-rollout safety.
|
|
10
10
|
*
|
|
11
11
|
* If that raw 404 reaches `handleToolError` it is mapped to the generic message
|
|
12
12
|
* "Resource not found. / Recurso no encontrado." — which an LLM reads as "the
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* PROBLEM (the bug this prevents): several tool families are wired here AHEAD of
|
|
5
5
|
* their Frihet-ERP backend Cloud Functions shipping (fiscal `/v1/fiscal/*`, bank
|
|
6
6
|
* rules `/v1/banking/rules`, gestoria `/v1/gestoria/*`, GL audit `/v1/gl/*`,
|
|
7
|
-
* portal domain/onboard, IGIC, IS, VIES onboarding,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* portal domain/onboard, IGIC, IS, VIES onboarding, and webhook test). Until a
|
|
8
|
+
* staged CF deploys, the API returns a genuine HTTP 404. Live HR, payroll-read,
|
|
9
|
+
* and period-read tools also retain this guard as workspace-rollout safety.
|
|
10
10
|
*
|
|
11
11
|
* If that raw 404 reaches `handleToolError` it is mapped to the generic message
|
|
12
12
|
* "Resource not found. / Recurso no encontrado." — which an LLM reads as "the
|
package/dist/tools/clients.js
CHANGED
|
@@ -77,9 +77,9 @@ export function registerClientTools(server, client) {
|
|
|
77
77
|
"Los clientes se usan al crear facturas y presupuestos.",
|
|
78
78
|
annotations: CREATE_ANNOTATIONS,
|
|
79
79
|
inputSchema: {
|
|
80
|
-
name: z.string().describe("Client/company name / Nombre del cliente o empresa"),
|
|
81
|
-
email: z.string().optional().describe("Email address / Correo electronico"),
|
|
82
|
-
phone: z.string().optional().describe("Phone number / Telefono"),
|
|
80
|
+
name: z.string().trim().min(1).max(200).describe("Client/company name / Nombre del cliente o empresa"),
|
|
81
|
+
email: z.string().trim().email().max(255).optional().describe("Email address / Correo electronico"),
|
|
82
|
+
phone: z.string().trim().min(1).max(50).optional().describe("Phone number / Telefono"),
|
|
83
83
|
taxId: z.string().optional().describe("Tax ID (NIF/CIF/VAT) / NIF o CIF"),
|
|
84
84
|
address: addressSchema,
|
|
85
85
|
},
|
|
@@ -88,8 +88,8 @@ export function registerClientTools(server, client) {
|
|
|
88
88
|
const result = await client.createClient(input);
|
|
89
89
|
const hints = enrichResponse("clients", "create", result);
|
|
90
90
|
return {
|
|
91
|
-
content: [mutateContent(formatRecord("Client created", result))],
|
|
92
|
-
structuredContent: { ...result
|
|
91
|
+
content: [mutateContent(formatRecord("Client created", result) + hints)],
|
|
92
|
+
structuredContent: { ...result },
|
|
93
93
|
};
|
|
94
94
|
}));
|
|
95
95
|
// -- update_client --
|
|
@@ -100,10 +100,10 @@ export function registerClientTools(server, client) {
|
|
|
100
100
|
"/ Actualiza un cliente existente. Solo se modifican los campos proporcionados.",
|
|
101
101
|
annotations: UPDATE_ANNOTATIONS,
|
|
102
102
|
inputSchema: {
|
|
103
|
-
id: z.string().describe("Client ID / ID del cliente"),
|
|
104
|
-
name: z.string().optional().describe("Name / Nombre"),
|
|
105
|
-
email: z.string().optional().describe("Email / Correo"),
|
|
106
|
-
phone: z.string().optional().describe("Phone / Telefono"),
|
|
103
|
+
id: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
104
|
+
name: z.string().trim().min(1).max(200).optional().describe("Name / Nombre"),
|
|
105
|
+
email: z.string().trim().email().max(255).optional().describe("Email / Correo"),
|
|
106
|
+
phone: z.string().trim().min(1).max(50).optional().describe("Phone / Telefono"),
|
|
107
107
|
taxId: z.string().optional().describe("Tax ID / NIF/CIF"),
|
|
108
108
|
address: addressSchema,
|
|
109
109
|
},
|
package/dist/tools/crm.js
CHANGED
|
@@ -37,11 +37,11 @@ export function registerCrmTools(server, client) {
|
|
|
37
37
|
"/ Anade un nuevo contacto a un cliente. Requiere al menos un nombre.",
|
|
38
38
|
annotations: CREATE_ANNOTATIONS,
|
|
39
39
|
inputSchema: {
|
|
40
|
-
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
41
|
-
name: z.string().describe("Contact name / Nombre del contacto"),
|
|
42
|
-
email: z.string().optional().describe("Email address / Correo electronico"),
|
|
43
|
-
phone: z.string().optional().describe("Phone number / Telefono"),
|
|
44
|
-
role: z.string().optional().describe("Role or title (e.g. CEO, CTO, Billing) / Cargo o rol"),
|
|
40
|
+
clientId: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
41
|
+
name: z.string().trim().min(1).max(200).describe("Contact name / Nombre del contacto"),
|
|
42
|
+
email: z.string().trim().email().max(255).optional().describe("Email address / Correo electronico"),
|
|
43
|
+
phone: z.string().trim().min(1).max(50).optional().describe("Phone number / Telefono"),
|
|
44
|
+
role: z.string().trim().min(1).max(200).optional().describe("Role or title (e.g. CEO, CTO, Billing) / Cargo o rol"),
|
|
45
45
|
isPrimary: z.boolean().optional().describe("Whether this is the primary contact / Si es el contacto principal"),
|
|
46
46
|
},
|
|
47
47
|
outputSchema: contactItemOutput,
|
|
@@ -49,8 +49,8 @@ export function registerCrmTools(server, client) {
|
|
|
49
49
|
const result = await client.createClientContact(clientId, data);
|
|
50
50
|
const hints = enrichResponse("contacts", "create", result);
|
|
51
51
|
return {
|
|
52
|
-
content: [mutateContent(formatRecord("Contact created", result))],
|
|
53
|
-
structuredContent: { ...result
|
|
52
|
+
content: [mutateContent(formatRecord("Contact created", result) + hints)],
|
|
53
|
+
structuredContent: { ...result },
|
|
54
54
|
};
|
|
55
55
|
}));
|
|
56
56
|
// -- delete_client_contact --
|
|
@@ -60,8 +60,8 @@ export function registerCrmTools(server, client) {
|
|
|
60
60
|
"/ Elimina permanentemente un contacto de un cliente. Esta accion no se puede deshacer.",
|
|
61
61
|
annotations: DELETE_ANNOTATIONS,
|
|
62
62
|
inputSchema: {
|
|
63
|
-
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
64
|
-
contactId: z.string().describe("Contact ID / ID del contacto"),
|
|
63
|
+
clientId: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
64
|
+
contactId: z.string().trim().min(1).max(128).describe("Contact ID / ID del contacto"),
|
|
65
65
|
},
|
|
66
66
|
outputSchema: deleteResultOutput,
|
|
67
67
|
}, async ({ clientId, contactId }) => withToolLogging("delete_client_contact", async () => {
|
|
@@ -82,7 +82,7 @@ export function registerCrmTools(server, client) {
|
|
|
82
82
|
"/ Lista todas las actividades CRM de un cliente con paginacion opcional.",
|
|
83
83
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
84
84
|
inputSchema: {
|
|
85
|
-
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
85
|
+
clientId: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
86
86
|
limit: z.number().int().min(1).max(100).optional().describe("Max results (1-100) / Resultados maximos"),
|
|
87
87
|
offset: z.number().int().min(0).optional().describe("Offset / Desplazamiento"),
|
|
88
88
|
},
|
|
@@ -98,18 +98,18 @@ export function registerCrmTools(server, client) {
|
|
|
98
98
|
server.registerTool("log_client_activity", {
|
|
99
99
|
title: "Log Client Activity",
|
|
100
100
|
description: "Log a CRM activity against a client. Use to track calls, emails, meetings, or tasks. " +
|
|
101
|
+
"Do not include passwords, credentials, payment-card data, health data, or official/government identifiers in free-text fields. " +
|
|
101
102
|
"Example: clientId='abc123', type='call', title='Discussed Q2 proposal', description='Client interested in upgrade' " +
|
|
102
|
-
"/ Registra una actividad CRM para un cliente.
|
|
103
|
+
"/ Registra una actividad CRM para un cliente. No incluyas contrasenas, credenciales, datos de tarjetas, datos de salud ni identificadores oficiales en los campos de texto libre.",
|
|
103
104
|
annotations: CREATE_ANNOTATIONS,
|
|
104
|
-
inputSchema: {
|
|
105
|
+
inputSchema: z.object({
|
|
105
106
|
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
106
107
|
type: z
|
|
107
108
|
.enum(["call", "email", "meeting", "task"])
|
|
108
109
|
.describe("Activity type / Tipo de actividad"),
|
|
109
|
-
title: z.string().describe("Activity title / Titulo de la actividad"),
|
|
110
|
-
description: z.string().optional().describe("Detailed description / Descripcion detallada"),
|
|
111
|
-
|
|
112
|
-
},
|
|
110
|
+
title: z.string().trim().min(1).max(500).describe("Activity title / Titulo de la actividad"),
|
|
111
|
+
description: z.string().trim().min(1).max(5000).optional().describe("Detailed description; exclude secrets, credentials, card or health data, and official identifiers / Descripcion detallada; excluye secretos, credenciales, datos de tarjetas o salud e identificadores oficiales"),
|
|
112
|
+
}).strict(),
|
|
113
113
|
outputSchema: activityItemOutput,
|
|
114
114
|
}, async ({ clientId, ...data }) => withToolLogging("log_client_activity", async () => {
|
|
115
115
|
const result = await client.logClientActivity(clientId, data);
|
|
@@ -144,12 +144,13 @@ export function registerCrmTools(server, client) {
|
|
|
144
144
|
server.registerTool("create_client_note", {
|
|
145
145
|
title: "Create Client Note",
|
|
146
146
|
description: "Add a note to a client. Notes are free-form text entries useful for keeping context. " +
|
|
147
|
+
"Do not include passwords, credentials, payment-card data, health data, or official/government identifiers. " +
|
|
147
148
|
"Example: clientId='abc123', content='Prefers invoices in English. Payment NET 30.' " +
|
|
148
|
-
"/ Anade una nota a un cliente.
|
|
149
|
+
"/ Anade una nota a un cliente. No incluyas contrasenas, credenciales, datos de tarjetas, datos de salud ni identificadores oficiales.",
|
|
149
150
|
annotations: CREATE_ANNOTATIONS,
|
|
150
151
|
inputSchema: {
|
|
151
|
-
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
152
|
-
content: z.string().describe("Note content / Contenido de
|
|
152
|
+
clientId: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
153
|
+
content: z.string().trim().min(1).max(10000).describe("Note content; exclude secrets, credentials, card or health data, and official identifiers / Contenido; excluye secretos, credenciales, datos de tarjetas o salud e identificadores oficiales"),
|
|
153
154
|
},
|
|
154
155
|
outputSchema: noteItemOutput,
|
|
155
156
|
}, async ({ clientId, content }) => withToolLogging("create_client_note", async () => {
|
|
@@ -166,8 +167,8 @@ export function registerCrmTools(server, client) {
|
|
|
166
167
|
"/ Elimina permanentemente una nota de un cliente. Esta accion no se puede deshacer.",
|
|
167
168
|
annotations: DELETE_ANNOTATIONS,
|
|
168
169
|
inputSchema: {
|
|
169
|
-
clientId: z.string().describe("Client ID / ID del cliente"),
|
|
170
|
-
noteId: z.string().describe("Note ID / ID de la nota"),
|
|
170
|
+
clientId: z.string().trim().min(1).max(128).describe("Client ID / ID del cliente"),
|
|
171
|
+
noteId: z.string().trim().min(1).max(128).describe("Note ID / ID de la nota"),
|
|
171
172
|
},
|
|
172
173
|
outputSchema: deleteResultOutput,
|
|
173
174
|
}, async ({ clientId, noteId }) => withToolLogging("delete_client_note", async () => {
|
package/dist/tools/deposits.js
CHANGED
|
@@ -45,8 +45,8 @@ export function registerDepositTools(server, client) {
|
|
|
45
45
|
const result = await client.listDeposits({ limit, offset, after, fields, from, to, clientId, status });
|
|
46
46
|
const hints = enrichResponse("deposits", "list", result.data);
|
|
47
47
|
return {
|
|
48
|
-
content: [listContent(formatPaginatedResponse("deposits", result))],
|
|
49
|
-
structuredContent: { ...result
|
|
48
|
+
content: [listContent(formatPaginatedResponse("deposits", result) + hints)],
|
|
49
|
+
structuredContent: { ...result },
|
|
50
50
|
};
|
|
51
51
|
}));
|
|
52
52
|
// -- get_deposit --
|
|
@@ -101,8 +101,8 @@ export function registerDepositTools(server, client) {
|
|
|
101
101
|
const result = await client.createDeposit(input);
|
|
102
102
|
const hints = enrichResponse("deposits", "create", result);
|
|
103
103
|
return {
|
|
104
|
-
content: [mutateContent(formatRecord("Deposit created", result))],
|
|
105
|
-
structuredContent: { ...result
|
|
104
|
+
content: [mutateContent(formatRecord("Deposit created", result) + hints)],
|
|
105
|
+
structuredContent: { ...result },
|
|
106
106
|
};
|
|
107
107
|
}));
|
|
108
108
|
// -- update_deposit --
|
|
@@ -147,8 +147,8 @@ export function registerDepositTools(server, client) {
|
|
|
147
147
|
await client.deleteDeposit(id);
|
|
148
148
|
const hints = enrichResponse("deposits", "delete", { id });
|
|
149
149
|
return {
|
|
150
|
-
content: [mutateContent(`Deposit ${id} deleted successfully. / Deposito ${id} eliminado correctamente.`)],
|
|
151
|
-
structuredContent: { success: true, id
|
|
150
|
+
content: [mutateContent(`Deposit ${id} deleted successfully. / Deposito ${id} eliminado correctamente.` + hints)],
|
|
151
|
+
structuredContent: { success: true, id },
|
|
152
152
|
};
|
|
153
153
|
}));
|
|
154
154
|
// -- apply_deposit --
|
|
@@ -178,18 +178,37 @@ export function registerDepositTools(server, client) {
|
|
|
178
178
|
// -- refund_deposit --
|
|
179
179
|
server.registerTool("refund_deposit", {
|
|
180
180
|
title: "Refund Deposit",
|
|
181
|
-
description: "Refund a deposit back to the client. " +
|
|
182
|
-
"Transitions the deposit status to 'refunded'
|
|
183
|
-
"
|
|
184
|
-
"
|
|
181
|
+
description: "Refund a deposit back to the client. Requires confirm=true. " +
|
|
182
|
+
"Transitions the deposit status to 'refunded' — a money movement that is not reversible " +
|
|
183
|
+
"from this tool. " +
|
|
184
|
+
"Example: id='dep_abc123', reason='Project cancelled', confirm=true " +
|
|
185
|
+
"/ Devuelve un deposito al cliente. Requiere confirm=true. " +
|
|
186
|
+
"Es un movimiento de dinero que no se puede revertir desde esta herramienta.",
|
|
185
187
|
annotations: UPDATE_ANNOTATIONS,
|
|
186
188
|
inputSchema: {
|
|
187
189
|
id: z.string().describe("Deposit ID / ID del deposito"),
|
|
188
190
|
reason: z.string().optional().describe("Reason for the refund / Motivo de la devolucion"),
|
|
189
191
|
notes: z.string().optional().describe("Refund notes / Notas de la devolucion"),
|
|
192
|
+
confirm: z
|
|
193
|
+
.boolean()
|
|
194
|
+
.describe("Must be true to confirm the refund / Debe ser true para confirmar la devolucion"),
|
|
190
195
|
},
|
|
191
196
|
outputSchema: actionResultOutput,
|
|
192
|
-
}, async ({ id, ...data }) => withToolLogging("refund_deposit", async () => {
|
|
197
|
+
}, async ({ id, confirm, ...data }) => withToolLogging("refund_deposit", async () => {
|
|
198
|
+
if (!confirm) {
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: "Error: confirm=true is required to refund a deposit. " +
|
|
204
|
+
"This returns the client's money and sets the deposit to status=refunded; " +
|
|
205
|
+
"there is no un-refund tool. Set confirm=true when you are certain. / " +
|
|
206
|
+
"Se requiere confirm=true para devolver un deposito. Es un movimiento de dinero irreversible.",
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
193
212
|
const result = await client.refundDeposit(id, data);
|
|
194
213
|
return {
|
|
195
214
|
content: [mutateContent(formatRecord("Deposit refunded", result))],
|
package/dist/tools/einvoice.d.ts
CHANGED
|
@@ -48,7 +48,6 @@ import type { IFrihetClient } from "../client-interface.js";
|
|
|
48
48
|
* | facturae | Facturae | Spain (ES B2G mandatory) | Facturae 3.2.x — AEAT/FACe submission |
|
|
49
49
|
*/
|
|
50
50
|
export declare const eInvoiceFormatSchema: z.ZodEnum<{
|
|
51
|
-
facturae: "facturae";
|
|
52
51
|
"xrechnung-cii": "xrechnung-cii";
|
|
53
52
|
"xrechnung-ubl": "xrechnung-ubl";
|
|
54
53
|
"facturx-en16931": "facturx-en16931";
|
|
@@ -59,6 +58,7 @@ export declare const eInvoiceFormatSchema: z.ZodEnum<{
|
|
|
59
58
|
ubl: "ubl";
|
|
60
59
|
cii: "cii";
|
|
61
60
|
"peppol-bis-3": "peppol-bis-3";
|
|
61
|
+
facturae: "facturae";
|
|
62
62
|
}>;
|
|
63
63
|
export type EInvoiceFormat = z.infer<typeof eInvoiceFormatSchema>;
|
|
64
64
|
export declare const sendEInvoiceOutput: z.ZodObject<{
|
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/hr.d.ts
CHANGED
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* REST surface: /v1/leaves, /v1/time-entries, /v1/anomalies
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* logLeaveDecision callable when REST shell lands.
|
|
17
|
+
* These ERP REST routes are live. The backend guard remains a rollout/workspace
|
|
18
|
+
* safety net so an unavailable route is never misreported as an empty result.
|
|
20
19
|
*/
|
|
21
20
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22
21
|
import type { IFrihetClient } from "../client-interface.js";
|
package/dist/tools/hr.js
CHANGED
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* REST surface: /v1/leaves, /v1/time-entries, /v1/anomalies
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* logLeaveDecision callable when REST shell lands.
|
|
17
|
+
* These ERP REST routes are live. The backend guard remains a rollout/workspace
|
|
18
|
+
* safety net so an unavailable route is never misreported as an empty result.
|
|
20
19
|
*/
|
|
21
20
|
import { z } from "zod/v4";
|
|
22
21
|
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, paginatedOutput, leaveRequestItemOutput, attendanceEntryItemOutput, overtimeReportOutput, anomalyItemOutput, } from "./shared.js";
|
|
@@ -171,14 +170,14 @@ export function registerHrTools(server, client) {
|
|
|
171
170
|
// -- overtime_report --
|
|
172
171
|
server.registerTool("overtime_report", {
|
|
173
172
|
title: "Overtime Report",
|
|
174
|
-
description: "
|
|
175
|
-
"
|
|
176
|
-
"Period format: 'YYYY-MM'
|
|
177
|
-
"/
|
|
173
|
+
description: "Read the ERP overtime computation for a calendar month or year: daily and weekly overtime, " +
|
|
174
|
+
"worked/regular/overtime minutes, overtime hours, and compliance alerts computed from records in the selected period. " +
|
|
175
|
+
"Optionally filter the attendance records by employee. Period format: 'YYYY-MM' or 'YYYY'. " +
|
|
176
|
+
"/ Lee desgloses diarios y semanales, y minutos/horas agregados calculados con los registros del periodo seleccionado, con alertas.",
|
|
178
177
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
179
178
|
inputSchema: {
|
|
180
|
-
period: z.string().describe("Period (YYYY-MM
|
|
181
|
-
employeeId: z.string().optional().describe("Optional filter by employee / Filtrar por empleado opcional"),
|
|
179
|
+
period: z.string().regex(/^\d{4}(-(0[1-9]|1[0-2]))?$/).describe("Period (YYYY-MM or YYYY) / Periodo"),
|
|
180
|
+
employeeId: z.string().min(1).optional().describe("Optional filter by employee / Filtrar por empleado opcional"),
|
|
182
181
|
},
|
|
183
182
|
outputSchema: overtimeReportOutput,
|
|
184
183
|
}, async ({ period, employeeId }) => withToolLogging("overtime_report", () => withBackendGuard("overtime_report", "/v1/time-entries/overtime", async () => {
|
|
@@ -66,7 +66,7 @@ const businessContextOutput = z.object({
|
|
|
66
66
|
invoiceCount: z.number(),
|
|
67
67
|
expenseCount: z.number(),
|
|
68
68
|
}).passthrough(),
|
|
69
|
-
}).passthrough().describe("Business context snapshot: workspace profile,
|
|
69
|
+
}).passthrough().describe("Business context snapshot: workspace profile, defaults, plan usage, recent activity and current-month performance");
|
|
70
70
|
const monthlySummaryOutput = z.object({
|
|
71
71
|
period: z.string(),
|
|
72
72
|
revenue: z.object({
|
|
@@ -101,7 +101,7 @@ const monthlySummaryOutput = z.object({
|
|
|
101
101
|
irpfRetained: z.number(),
|
|
102
102
|
estimatedModel303: z.number(),
|
|
103
103
|
}).passthrough(),
|
|
104
|
-
}).passthrough().describe("Monthly
|
|
104
|
+
}).passthrough().describe("Monthly business summary with revenue, expenses, profit, invoice status, top clients and expense categories");
|
|
105
105
|
export function registerIntelligenceTools(server, client) {
|
|
106
106
|
// -- get_business_context --
|
|
107
107
|
server.registerTool("get_business_context", {
|