@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
|
@@ -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"),
|
package/dist/tools/audit_gl.js
CHANGED
|
@@ -44,20 +44,20 @@ export function registerAuditGLTools(server, client) {
|
|
|
44
44
|
// -- frihet_gl_entry_reject ------------------------------------------------
|
|
45
45
|
server.registerTool("frihet_gl_entry_reject", {
|
|
46
46
|
title: "Reject GL Entry",
|
|
47
|
-
description: "Reject a General Ledger journal entry pending review with a required
|
|
48
|
-
"Sets entry status to 'rejected' and records the rejector +
|
|
47
|
+
description: "Reject a General Ledger journal entry pending review with a required note. " +
|
|
48
|
+
"Sets entry status to 'rejected' and records the rejector + note in the audit trail. " +
|
|
49
49
|
"Requires gestor/admin role. This is a TRUST AREA action. " +
|
|
50
|
-
"Example: entryId='gl_2026_q1_042',
|
|
51
|
-
"/ Rechaza un asiento contable pendiente con una
|
|
50
|
+
"Example: entryId='gl_2026_q1_042', note='Importe incorrecto, revisar factura F-2026-042'. " +
|
|
51
|
+
"/ Rechaza un asiento contable pendiente con una nota obligatoria. " +
|
|
52
52
|
"Requiere rol gestor/admin.",
|
|
53
53
|
annotations: UPDATE_ANNOTATIONS,
|
|
54
54
|
inputSchema: {
|
|
55
55
|
entryId: z.string().describe("GL entry ID to reject / ID del asiento contable a rechazar"),
|
|
56
|
-
|
|
56
|
+
note: z.string().min(1).describe("Mandatory rejection note (visible to submitter) / Nota del rechazo (obligatoria, visible al emisor)"),
|
|
57
57
|
},
|
|
58
|
-
outputSchema: openObjectOutput("Rejected GL entry record with
|
|
59
|
-
}, async ({ entryId,
|
|
60
|
-
const result = await client.rejectGLEntry(entryId,
|
|
58
|
+
outputSchema: openObjectOutput("Rejected GL entry record with note and audit trail entry / Asiento contable rechazado con nota y entrada de auditoría"),
|
|
59
|
+
}, async ({ entryId, note }) => withToolLogging("frihet_gl_entry_reject", () => withBackendGuard("frihet_gl_entry_reject", "/v1/gl/entries/reject", async () => {
|
|
60
|
+
const result = await client.rejectGLEntry(entryId, note);
|
|
61
61
|
return {
|
|
62
62
|
content: [
|
|
63
63
|
mutateContent(formatRecord(`GL Entry ${entryId} rejected`, result)),
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backend-availability guard for MCP tool handlers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* rules `/v1/banking/rules`, gestoria `/v1/gestoria/*`, GL audit `/v1/gl/*`,
|
|
7
|
-
* portal domain/onboard, IGIC, IS, VIES onboarding, payroll prep, permissions,
|
|
8
|
-
* HR leaves/anomalies, accounting periods, webhook test). Until the CF deploys,
|
|
9
|
-
* the API returns a genuine HTTP 404.
|
|
4
|
+
* HTTP 404 can mean an unavailable record, an unknown route, or an endpoint
|
|
5
|
+
* that is not enabled. The status alone cannot establish deployment state.
|
|
10
6
|
*
|
|
11
7
|
* If that raw 404 reaches `handleToolError` it is mapped to the generic message
|
|
12
8
|
* "Resource not found. / Recurso no encontrado." — which an LLM reads as "the
|
|
@@ -14,9 +10,9 @@
|
|
|
14
10
|
* ("you have no Modelo 303 to file", "this workspace has no permissions matrix").
|
|
15
11
|
* That is a Trust-Area failure: the model invents fiscal/compliance facts.
|
|
16
12
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* Preserve typed application NOT_FOUND errors for the normal denial handler.
|
|
14
|
+
* Ambiguous 404s remain explicit errors with no inferred data or deployment
|
|
15
|
+
* state. Only notDeployedError states that a route is not deployed.
|
|
20
16
|
* Any other error (401/403/429/5xx/network) is RE-THROWN unchanged so the normal
|
|
21
17
|
* `withToolLogging` → `handleToolError` path still surfaces it.
|
|
22
18
|
*
|
|
@@ -46,27 +42,10 @@ export interface BackendGuardErrorResult {
|
|
|
46
42
|
* Duck-typed so it works regardless of which client implementation threw.
|
|
47
43
|
*/
|
|
48
44
|
export declare function isBackendNotFound(error: unknown): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Builds the explicit, LLM-safe "backend not available" tool error.
|
|
51
|
-
*/
|
|
45
|
+
/** Builds the explicit error for a route known not to be deployed. */
|
|
52
46
|
export declare function backendUnavailableError(toolName: string, endpoint?: string): BackendGuardErrorResult;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* structured backend-unavailable error instead of letting the raw 404 surface as
|
|
56
|
-
* a generic "resource not found" that the LLM would treat as real data. Any other
|
|
57
|
-
* error is re-thrown so `withToolLogging`/`handleToolError` handle it normally.
|
|
58
|
-
*
|
|
59
|
-
* Usage:
|
|
60
|
-
* ```ts
|
|
61
|
-
* async ({ period }) => withToolLogging("get_modelo_303_summary", () =>
|
|
62
|
-
* withBackendGuard("get_modelo_303_summary", "/v1/fiscal/303", async () => {
|
|
63
|
-
* const result = await client.getFiscalModeloSummary("303", period);
|
|
64
|
-
* return { content: [getContent(formatRecord("Modelo 303 Summary", result))],
|
|
65
|
-
* structuredContent: result as unknown as Record<string, unknown> };
|
|
66
|
-
* }),
|
|
67
|
-
* )
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
47
|
+
export declare function notDeployedError(toolName: string, endpoint: string): BackendGuardErrorResult;
|
|
48
|
+
/** Keep application denials intact; ambiguous HTTP 404s are never empty data. */
|
|
70
49
|
export declare function withBackendGuard<T extends {
|
|
71
50
|
content: unknown[];
|
|
72
51
|
}>(toolName: string, endpoint: string | undefined, fn: () => Promise<T>): Promise<T | BackendGuardErrorResult>;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backend-availability guard for MCP tool handlers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* rules `/v1/banking/rules`, gestoria `/v1/gestoria/*`, GL audit `/v1/gl/*`,
|
|
7
|
-
* portal domain/onboard, IGIC, IS, VIES onboarding, payroll prep, permissions,
|
|
8
|
-
* HR leaves/anomalies, accounting periods, webhook test). Until the CF deploys,
|
|
9
|
-
* the API returns a genuine HTTP 404.
|
|
4
|
+
* HTTP 404 can mean an unavailable record, an unknown route, or an endpoint
|
|
5
|
+
* that is not enabled. The status alone cannot establish deployment state.
|
|
10
6
|
*
|
|
11
7
|
* If that raw 404 reaches `handleToolError` it is mapped to the generic message
|
|
12
8
|
* "Resource not found. / Recurso no encontrado." — which an LLM reads as "the
|
|
@@ -14,9 +10,9 @@
|
|
|
14
10
|
* ("you have no Modelo 303 to file", "this workspace has no permissions matrix").
|
|
15
11
|
* That is a Trust-Area failure: the model invents fiscal/compliance facts.
|
|
16
12
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* Preserve typed application NOT_FOUND errors for the normal denial handler.
|
|
14
|
+
* Ambiguous 404s remain explicit errors with no inferred data or deployment
|
|
15
|
+
* state. Only notDeployedError states that a route is not deployed.
|
|
20
16
|
* Any other error (401/403/429/5xx/network) is RE-THROWN unchanged so the normal
|
|
21
17
|
* `withToolLogging` → `handleToolError` path still surfaces it.
|
|
22
18
|
*
|
|
@@ -38,9 +34,7 @@ export function isBackendNotFound(error) {
|
|
|
38
34
|
}
|
|
39
35
|
return false;
|
|
40
36
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Builds the explicit, LLM-safe "backend not available" tool error.
|
|
43
|
-
*/
|
|
37
|
+
/** Builds the explicit error for a route known not to be deployed. */
|
|
44
38
|
export function backendUnavailableError(toolName, endpoint) {
|
|
45
39
|
const message = `The backend for "${toolName}" is not available yet, so no data could be retrieved. ` +
|
|
46
40
|
`This is NOT an empty or zero result — the underlying Frihet ERP endpoint ` +
|
|
@@ -63,30 +57,37 @@ export function backendUnavailableError(toolName, endpoint) {
|
|
|
63
57
|
isError: true,
|
|
64
58
|
};
|
|
65
59
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
/** An ambiguous 404 establishes neither missing business data nor deployment. */
|
|
61
|
+
function lookupUnavailableError(toolName, endpoint) {
|
|
62
|
+
const message = `Frihet could not resolve the requested resource or endpoint for "${toolName}" (HTTP 404). ` +
|
|
63
|
+
`No data could be retrieved. Do not infer an empty or zero result, missing business records, or deployment status. ` +
|
|
64
|
+
`Check the record ID and feature availability before deciding whether to retry. ` +
|
|
65
|
+
`/ Frihet no pudo resolver el recurso o endpoint solicitado (HTTP 404). ` +
|
|
66
|
+
`No se recuperaron datos. No deduzcas un resultado vacío o cero, la ausencia de registros ni el estado del despliegue.`;
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: `Error: ${message}`, annotations: ERROR_CONTENT_ANNOTATIONS }],
|
|
69
|
+
structuredContent: {
|
|
70
|
+
error: "lookup_unavailable", code: "LOOKUP_UNAVAILABLE", tool: toolName, message,
|
|
71
|
+
...(endpoint ? { endpoint } : {}),
|
|
72
|
+
},
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function notDeployedError(toolName, endpoint) {
|
|
77
|
+
const result = backendUnavailableError(toolName, endpoint);
|
|
78
|
+
result.structuredContent = { ...result.structuredContent, code: "NOT_DEPLOYED" };
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/** Keep application denials intact; ambiguous HTTP 404s are never empty data. */
|
|
83
82
|
export async function withBackendGuard(toolName, endpoint, fn) {
|
|
84
83
|
try {
|
|
85
84
|
return await fn();
|
|
86
85
|
}
|
|
87
86
|
catch (err) {
|
|
88
87
|
if (isBackendNotFound(err)) {
|
|
89
|
-
|
|
88
|
+
if (err.errorCode === "NOT_FOUND")
|
|
89
|
+
throw err;
|
|
90
|
+
return lookupUnavailableError(toolName, endpoint);
|
|
90
91
|
}
|
|
91
92
|
throw err;
|
|
92
93
|
}
|
package/dist/tools/bank_rules.js
CHANGED
|
@@ -23,6 +23,13 @@ import { withBackendGuard } from "./backend-availability.js";
|
|
|
23
23
|
const bankRuleItemOutput = z.object({
|
|
24
24
|
id: z.string(),
|
|
25
25
|
name: z.string(),
|
|
26
|
+
bankConditions: z.array(z.object({
|
|
27
|
+
field: z.string(),
|
|
28
|
+
operator: z.string(),
|
|
29
|
+
value: z.string(),
|
|
30
|
+
})).optional(),
|
|
31
|
+
action: z.string().optional(),
|
|
32
|
+
actionConfig: z.record(z.string(), z.unknown()).optional(),
|
|
26
33
|
conditions: z.array(z.object({
|
|
27
34
|
field: z.string(),
|
|
28
35
|
operator: z.string(),
|
|
@@ -35,8 +42,39 @@ const bankRuleItemOutput = z.object({
|
|
|
35
42
|
isActive: z.boolean().optional(),
|
|
36
43
|
createdAt: z.string().optional(),
|
|
37
44
|
}).passthrough();
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
// ERP authority: functions/src/banking/bankRuleCreate.ts
|
|
46
|
+
// BANK_RULE_FIELDS = ['description', 'reference', 'amount', 'counterparty']
|
|
47
|
+
// BANK_RULE_OPERATORS = ['contains', 'exact', 'starts_with', 'ends_with', 'regex',
|
|
48
|
+
// 'amount_above', 'amount_below', 'amount_between']
|
|
49
|
+
// BANK_RULE_ACTIONS = ['categorize_expense', 'match_invoice', 'match_client',
|
|
50
|
+
// 'ignore', 'create_expense', 'flag_review']
|
|
51
|
+
//
|
|
52
|
+
// Per-action required actionConfig shape (ERP):
|
|
53
|
+
// categorize_expense → { category: string }
|
|
54
|
+
// match_client → { clientId: string }
|
|
55
|
+
// create_expense → { expenseDefaults: { vendor?, category?, taxRate?, description? } }
|
|
56
|
+
// flag_review → { flag: string }
|
|
57
|
+
// match_invoice → (no required config)
|
|
58
|
+
// ignore → (no required config)
|
|
59
|
+
const CONDITION_FIELD = z.enum(["description", "reference", "amount", "counterparty"]);
|
|
60
|
+
const CONDITION_OPERATOR = z.enum([
|
|
61
|
+
"contains",
|
|
62
|
+
"exact",
|
|
63
|
+
"starts_with",
|
|
64
|
+
"ends_with",
|
|
65
|
+
"regex",
|
|
66
|
+
"amount_above",
|
|
67
|
+
"amount_below",
|
|
68
|
+
"amount_between",
|
|
69
|
+
]);
|
|
70
|
+
const RULE_ACTION = z.enum([
|
|
71
|
+
"categorize_expense",
|
|
72
|
+
"match_invoice",
|
|
73
|
+
"match_client",
|
|
74
|
+
"ignore",
|
|
75
|
+
"create_expense",
|
|
76
|
+
"flag_review",
|
|
77
|
+
]);
|
|
40
78
|
export function registerBankRulesTools(server, client) {
|
|
41
79
|
// -- frihet_bank_rules_list -----------------------------------------------
|
|
42
80
|
server.registerTool("frihet_bank_rules_list", {
|
|
@@ -65,28 +103,41 @@ export function registerBankRulesTools(server, client) {
|
|
|
65
103
|
title: "Create Bank Categorization Rule",
|
|
66
104
|
description: "Create a new bank auto-categorization rule. " +
|
|
67
105
|
"Rules apply automatically to matching incoming transactions. " +
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
106
|
+
"The ERP new-engine vocabulary: `bankConditions` (AND logic) + a single `action` " +
|
|
107
|
+
"(`categorize_expense` | `match_invoice` | `match_client` | `ignore` | " +
|
|
108
|
+
"`create_expense` | `flag_review`) and its `actionConfig` payload. " +
|
|
109
|
+
"Required actionConfig per action: categorize_expense→{category}, " +
|
|
110
|
+
"match_client→{clientId}, create_expense→{expenseDefaults:{vendor,category,…}}, " +
|
|
111
|
+
"flag_review→{flag}; match_invoice and ignore require no config. " +
|
|
112
|
+
"Example: name='Mercadona groceries', bankConditions=[{field:'description', " +
|
|
113
|
+
"operator:'contains', value:'MERCADONA'}], action='categorize_expense', " +
|
|
114
|
+
"actionConfig={category:'groceries'}. " +
|
|
71
115
|
"/ Crea una nueva regla de categorizacion automatica de transacciones bancarias. " +
|
|
72
|
-
"
|
|
116
|
+
"El nuevo motor ERP espera `bankConditions` (logica AND) y un unico `action` con su `actionConfig`.",
|
|
73
117
|
annotations: CREATE_ANNOTATIONS,
|
|
74
118
|
inputSchema: {
|
|
75
119
|
name: z.string().describe("Rule name for identification / Nombre de la regla"),
|
|
76
|
-
|
|
120
|
+
bankConditions: z
|
|
121
|
+
.array(z.object({
|
|
77
122
|
field: CONDITION_FIELD.describe("Transaction field to match / Campo de transaccion a comparar"),
|
|
78
123
|
operator: CONDITION_OPERATOR.describe("Comparison operator / Operador de comparacion"),
|
|
79
124
|
value: z.string().describe("Value to match against / Valor a comparar"),
|
|
80
|
-
}))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
125
|
+
}))
|
|
126
|
+
.min(1)
|
|
127
|
+
.describe("Rule conditions (AND logic — all must match) / Condiciones (logica AND — todas deben cumplirse)"),
|
|
128
|
+
action: RULE_ACTION.describe("Action to apply when the rule matches / Accion a aplicar cuando la regla se cumple"),
|
|
129
|
+
actionConfig: z
|
|
130
|
+
.record(z.string(), z.unknown())
|
|
131
|
+
.describe("Action-specific payload. categorize_expense requires {category:string}. " +
|
|
132
|
+
"match_client requires {clientId:string}. create_expense requires " +
|
|
133
|
+
"{expenseDefaults:{vendor:string, category:string, taxRate?:number, description?:string}}. " +
|
|
134
|
+
"flag_review requires {flag:string}. match_invoice and ignore require no config. " +
|
|
135
|
+
"/ Payload segun la accion."),
|
|
85
136
|
isActive: z.boolean().optional().describe("Whether the rule is active (default true) / Si la regla esta activa (por defecto true)"),
|
|
86
137
|
},
|
|
87
|
-
outputSchema: openObjectOutput("Created bank rule record with ID, conditions and
|
|
88
|
-
}, async ({ name,
|
|
89
|
-
const result = await client.createBankRule({ name,
|
|
138
|
+
outputSchema: openObjectOutput("Created bank rule record with ID, conditions and action / Regla bancaria creada con ID, condiciones y accion"),
|
|
139
|
+
}, async ({ name, bankConditions, action, actionConfig, isActive }) => withToolLogging("frihet_bank_rule_create", () => withBackendGuard("frihet_bank_rule_create", "/v1/banking/rules", async () => {
|
|
140
|
+
const result = await client.createBankRule({ name, bankConditions, action, actionConfig, isActive });
|
|
90
141
|
return {
|
|
91
142
|
content: [
|
|
92
143
|
mutateContent(formatRecord(`Bank rule created: ${name}`, result)),
|
package/dist/tools/banking.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* src/__tests__/banking-client-contract.test.ts.
|
|
17
17
|
*/
|
|
18
18
|
import { z } from "zod/v4";
|
|
19
|
-
import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, READ_ONLY_ANNOTATIONS, UPDATE_ANNOTATIONS, paginatedOutput, bankAccountItemOutput, bankTransactionItemOutput, } from "./shared.js";
|
|
19
|
+
import { withToolLogging, confirmationRequiredError, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, READ_ONLY_ANNOTATIONS, UPDATE_ANNOTATIONS, paginatedOutput, bankAccountItemOutput, bankTransactionItemOutput, } from "./shared.js";
|
|
20
20
|
export function registerBankingTools(server, client) {
|
|
21
21
|
// -- list_bank_accounts --
|
|
22
22
|
server.registerTool("list_bank_accounts", {
|
|
@@ -134,18 +134,10 @@ export function registerBankingTools(server, client) {
|
|
|
134
134
|
outputSchema: bankTransactionItemOutput,
|
|
135
135
|
}, async ({ transactionId, documentId, documentType, confirm, notes }) => withToolLogging("match_transaction_to_invoice", async () => {
|
|
136
136
|
if (!confirm) {
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
text: "Error: confirm=true is required to perform fiscal reconciliation. " +
|
|
142
|
-
"This action links a bank transaction to a fiscal document and cannot be undone easily. " +
|
|
143
|
-
"Set confirm=true when you are certain about the match. / " +
|
|
144
|
-
"Se requiere confirm=true para realizar la conciliacion fiscal.",
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
isError: true,
|
|
148
|
-
};
|
|
137
|
+
return confirmationRequiredError("Error: confirm=true is required to perform fiscal reconciliation. " +
|
|
138
|
+
"This action links a bank transaction to a fiscal document and cannot be undone easily. " +
|
|
139
|
+
"Set confirm=true when you are certain about the match. / " +
|
|
140
|
+
"Se requiere confirm=true para realizar la conciliacion fiscal.");
|
|
149
141
|
}
|
|
150
142
|
const result = await client.matchTransactionToDocument(transactionId, { documentId, documentType, notes });
|
|
151
143
|
return {
|