mcp-zenskar 1.1.9 → 1.1.11

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 CHANGED
@@ -1,18 +1,17 @@
1
1
  # Zenskar MCP Server
2
2
 
3
- MCP server for the Zenskar API. 103 tools covering customers, contracts, invoices, payments, credit notes, accounting, products, plans, quotes, and more.
3
+ MCP server for the Zenskar API. 103 tools covering customers, contracts, invoices, payments, credit notes, accounting, products, plans, and more.
4
4
 
5
5
  ## What it does
6
6
 
7
- - Customers: list, search, create, update, addresses, contacts, payment methods
8
- - Contracts: create, read, update, delete, amend, add phases and pricing, expire
7
+ - Customers: list, search, create, update, delete, addresses, contacts, payment methods
8
+ - Contracts: create, read, update, delete, amend, add phases and pricing, pause/resume, expire
9
9
  - Invoices: list, get, approve, void, generate, credit notes, download
10
10
  - Payments: create, edit, refund, delete, auto-charge
11
11
  - Credit notes: list, create against invoice, get by ID
12
12
  - Accounting: chart of accounts, journal entries and lines, balance sheet, income statement, account balances
13
13
  - Products: CRUD, pricing configurations
14
14
  - Plans: list, create, add products, preview estimates
15
- - Quotes: create, preview, accept (converts to contract)
16
15
  - Business entities: list, get, create, update
17
16
  - Jobs: monitor async operations
18
17
  - Custom attributes and tax categories
@@ -91,6 +90,7 @@ Once configured, you can ask Claude to interact with your Zenskar data:
91
90
  | `getCustomerById` | Get a customer by ID |
92
91
  | `createCustomer` | Create a customer with address and tax info |
93
92
  | `updateCustomer` | Update customer details (partial update) |
93
+ | `deleteCustomer` | Permanently delete a customer by ID (only allowed when they have no active contracts or unpaid invoices) |
94
94
 
95
95
  ### Contacts
96
96
  | Tool | Description |
@@ -99,6 +99,7 @@ Once configured, you can ask Claude to interact with your Zenskar data:
99
99
  | `getContactById` | Get a contact by ID |
100
100
  | `createContact` | Create a contact for a customer |
101
101
  | `updateContact` | Update a contact's details |
102
+ | `deleteContact` | Delete a contact by ID |
102
103
 
103
104
  ### Contracts
104
105
  | Tool | Description |
@@ -112,6 +113,9 @@ Once configured, you can ask Claude to interact with your Zenskar data:
112
113
  | `createContractPhase` | Add a phase to a contract (add-ons, expansions) |
113
114
  | `createContractPhasePricing` | Add pricing to a contract phase |
114
115
  | `expireContract` | Expire an active contract |
116
+ | `pauseContract` | Pause an active contract from a given start date, with an unpause-extension policy (`extend` or `overlap`) and optional end date for auto-resume |
117
+ | `editPauseContract` | Edit an existing pause phase — set or change the resume date, shift the start, or change the unpause policy |
118
+ | `resumeContract` | Resume a paused contract |
115
119
  | `createContractPrompt` | Create a contract prompt |
116
120
  | `extractContractFromRaw` | Extract contract data from raw text using AI |
117
121
 
@@ -133,7 +137,8 @@ Once configured, you can ask Claude to interact with your Zenskar data:
133
137
  | `generateInvoicePaymentLink` | Generate a payment link for an invoice |
134
138
  | `payInvoice` | Initiate payment for an invoice |
135
139
  | `approveInvoice` | Approve an invoice for billing |
136
- | `voidInvoice` | Void an approved unpaid invoice |
140
+ | `voidInvoice` | Void an invoice |
141
+ | `deleteInvoice` | Delete a draft invoice |
137
142
  | `generateInvoice` | Generate an invoice for a contract and date range |
138
143
  | `createInvoiceCreditNote` | Create a credit note against an invoice |
139
144
  | `createInvoiceCharge` | Auto-charge an invoice via payment gateway |
@@ -171,8 +176,6 @@ Once configured, you can ask Claude to interact with your Zenskar data:
171
176
  | `listPlans` | List plan templates |
172
177
  | `getPlanById` | Get a plan by ID with phases and pricing |
173
178
  | `createPlan` | Create a plan template |
174
- | `addProductsToPlan` | Add products to an existing plan |
175
- | `previewPlanEstimate` | Preview estimated billing for a plan |
176
179
 
177
180
  ### Accounting
178
181
  | Tool | Description |
@@ -189,14 +192,6 @@ Once configured, you can ask Claude to interact with your Zenskar data:
189
192
  | `getAccountBalance` | Get balance for a specific GL account |
190
193
  | `recogniseRevenue` | Trigger revenue recognition up to a date |
191
194
 
192
- ### Quotes
193
- | Tool | Description |
194
- |---|---|
195
- | `createQuote` | Create a quote/proposal |
196
- | `previewQuoteEstimate` | Preview estimated billing for a quote |
197
- | `getQuoteById` | Get a quote by ID |
198
- | `acceptQuote` | Accept a quote, converting to a contract |
199
-
200
195
  ### Custom Attributes and Tax
201
196
  | Tool | Description |
202
197
  |---|---|
@@ -227,6 +222,7 @@ Once configured, you can ask Claude to interact with your Zenskar data:
227
222
  | `updateCustomerAddress` | Update a customer address |
228
223
  | `listPaymentMethods` | List payment methods for a customer |
229
224
  | `attachPaymentMethod` | Attach a payment method to a customer |
225
+ | `deletePaymentMethod` | Delete a payment method from a customer |
230
226
 
231
227
  ### Metrics and Usage Events
232
228
  | Tool | Description |
@@ -239,7 +235,7 @@ Once configured, you can ask Claude to interact with your Zenskar data:
239
235
  | `listRawMetrics` | List Usage Events with filtering; backend/API may also call these raw metrics |
240
236
  | `createRawMetric` | Create a Usage Event schema; backend/API calls this a raw metric |
241
237
  | `getRawMetricById` | Get a Usage Event by ID; backend/API may also call it a raw metric |
242
- | `getRawMetricLogs` | Get logs for a Usage Event (raw metric) |
238
+ | `getRawMetricLogs` | Get recent event rows for a Usage Event using the same preview-query path as the frontend |
243
239
  | `getRawMetricBySlug` | Get a Usage Event by API slug; backend/API may also call it a raw metric |
244
240
  | `ingestRawMetricEvent` | Ingest a usage event |
245
241
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-zenskar",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Model Context Protocol (MCP) server for Zenskar API - customer management, invoicing, and billing operations",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -586,7 +586,18 @@
586
586
  },
587
587
  {
588
588
  "name": "payInvoice",
589
- "description": "Initiate payment for an invoice using a payload.",
589
+ "description": "Initiate payment for an invoice using a payload. MONEY-MOVING ACTION — only call when the user explicitly authorizes payment for this specific invoice.",
590
+ "needsApproval": true,
591
+ "approvalConfig": {
592
+ "title": "Pay Invoice",
593
+ "description": "This will initiate a payment against the invoice using the provided payload.",
594
+ "warningText": "Money-moving action — funds will be charged. Verify the invoice and amount before confirming.",
595
+ "confirmText": "Pay Invoice",
596
+ "cancelText": "Cancel",
597
+ "sensitiveFields": [
598
+ "payload"
599
+ ]
600
+ },
590
601
  "args": [
591
602
  {
592
603
  "name": "payload",
@@ -1060,7 +1071,7 @@
1060
1071
  },
1061
1072
  {
1062
1073
  "name": "getRawMetricLogs",
1063
- "description": "Get Usage Event logs for a specific resource by ID. Backend/API terminology may also call this a raw metric.",
1074
+ "description": "Get Usage Event event rows for a specific resource by ID. This follows the same visual-preview query path used by the frontend meter page, so it returns recent event rows rather than only backend sample logs. Backend/API terminology may also call this a raw metric.",
1064
1075
  "args": [
1065
1076
  {
1066
1077
  "name": "rawMetricId",
@@ -1068,6 +1079,43 @@
1068
1079
  "type": "string",
1069
1080
  "required": true,
1070
1081
  "position": "path"
1082
+ },
1083
+ {
1084
+ "name": "filters",
1085
+ "description": "Optional visual-query filter array, matching the frontend usage-event viewer format. Default is an empty filter list.",
1086
+ "type": "array",
1087
+ "required": false,
1088
+ "position": "body"
1089
+ },
1090
+ {
1091
+ "name": "limit",
1092
+ "description": "Maximum number of event rows to return. Defaults to 20, matching frontend behavior.",
1093
+ "type": "integer",
1094
+ "required": false,
1095
+ "position": "body",
1096
+ "default": 20
1097
+ },
1098
+ {
1099
+ "name": "offset",
1100
+ "description": "Pagination offset for event rows. Defaults to 0.",
1101
+ "type": "integer",
1102
+ "required": false,
1103
+ "position": "body",
1104
+ "default": 0
1105
+ },
1106
+ {
1107
+ "name": "aggregate_operation",
1108
+ "description": "Optional aggregate operation object used by the frontend for event counts (for example COUNT on timestamp). Leave null for normal row listing.",
1109
+ "type": "object",
1110
+ "required": false,
1111
+ "position": "body"
1112
+ },
1113
+ {
1114
+ "name": "order_by",
1115
+ "description": "Optional ordering array. Defaults to [{column:'timestamp', type:'DESC'}] to match frontend event viewing.",
1116
+ "type": "array",
1117
+ "required": false,
1118
+ "position": "body"
1071
1119
  }
1072
1120
  ],
1073
1121
  "requestTemplate": {
@@ -1075,7 +1123,7 @@
1075
1123
  "method": "GET"
1076
1124
  },
1077
1125
  "responseTemplate": {
1078
- "prependBody": "## Usage Event Logs\n\nHere are the logs for the specified usage event:\n\n"
1126
+ "prependBody": "## Usage Event Logs\n\nHere are recent event rows for the specified usage event:\n\n"
1079
1127
  }
1080
1128
  },
1081
1129
  {
@@ -2682,7 +2730,18 @@
2682
2730
  },
2683
2731
  {
2684
2732
  "name": "deleteContract",
2685
- "description": "Delete a contract by its ID. Only works on draft contracts. Returns the deleted contract object with a deleted_at timestamp.",
2733
+ "description": "Permanently delete a DRAFT contract. ONLY works on contracts with status='draft'. Do NOT call on ACTIVE, EXPIRED, or PAUSED contracts. DESTRUCTIVE: removes all phases, products, and pricing associations. NEVER call as automatic recovery from another failed operation (e.g., do not retry as a fallback after expireContract or voidInvoice fails). ONLY call when the user explicitly says 'delete' or 'remove' for THIS draft contract. If a non-destructive action fails, surface the error verbatim and stop.",
2734
+ "needsApproval": true,
2735
+ "approvalConfig": {
2736
+ "title": "Delete Draft Contract",
2737
+ "description": "This will permanently delete the draft contract along with all its phases, products, and pricing associations.",
2738
+ "warningText": "DESTRUCTIVE — this cannot be undone. Only draft contracts may be deleted; verify status before confirming.",
2739
+ "confirmText": "Delete Contract",
2740
+ "cancelText": "Cancel",
2741
+ "sensitiveFields": [
2742
+ "contractId"
2743
+ ]
2744
+ },
2686
2745
  "args": [
2687
2746
  {
2688
2747
  "name": "contractId",
@@ -2919,7 +2978,19 @@
2919
2978
  },
2920
2979
  {
2921
2980
  "name": "expireContract",
2922
- "description": "Expire an active contract. Only works on contracts with status 'active'. Optionally specify an expiry_date (defaults to today). The contract expires at end of day 23:59:59.999999.",
2981
+ "description": "Expire an ACTIVE or PAUSED contract by adjusting its end_date. Allowed status transitions: ACTIVE→EXPIRED, PAUSED→EXPIRED. EXPIRED is terminal — calling on an already-expired contract returns 400; do NOT retry, do NOT escalate to deleteContract or any other destructive tool as a fallback. Verify contract.status via getContractById before calling. Idempotency: NO. The contract expires at end of day 23:59:59.999999.",
2982
+ "needsApproval": true,
2983
+ "approvalConfig": {
2984
+ "title": "Expire Contract",
2985
+ "description": "This will set the contract's end_date and transition status to EXPIRED. EXPIRED is terminal.",
2986
+ "warningText": "Once expired, the contract cannot be re-activated through this endpoint. Future-dated phases beyond the expiry will be removed.",
2987
+ "confirmText": "Expire Contract",
2988
+ "cancelText": "Cancel",
2989
+ "sensitiveFields": [
2990
+ "contractId",
2991
+ "expiry_date"
2992
+ ]
2993
+ },
2923
2994
  "args": [
2924
2995
  {
2925
2996
  "name": "contractId",
@@ -2930,7 +3001,7 @@
2930
3001
  },
2931
3002
  {
2932
3003
  "name": "expiry_date",
2933
- "description": "Date to expire the contract (e.g. 2026-12-31). Defaults to today if not provided.",
3004
+ "description": "Date to expire the contract (e.g. 2026-12-31). Defaults to today if not provided. Must not be earlier than contract.start_date.",
2934
3005
  "type": "string",
2935
3006
  "required": false,
2936
3007
  "position": "body"
@@ -2949,7 +3020,18 @@
2949
3020
  },
2950
3021
  {
2951
3022
  "name": "voidInvoice",
2952
- "description": "Void an invoice by its ID. Only works on approved invoices that have not been fully paid. Voiding reverses the invoice and creates appropriate accounting entries.",
3023
+ "description": "Void an invoice. ONLY works on approved or paid invoices with invoice_total == 0 (zero-balance, typically after a full credit-note offset). For non-zero approved invoices, issue a credit note via createInvoiceCreditNote first to bring the balance to zero, then void. If the call returns INVOICE_CANNOT_BE_VOIDED, surface that error verbatim — do NOT retry, do NOT escalate to deleteInvoice.",
3024
+ "needsApproval": true,
3025
+ "approvalConfig": {
3026
+ "title": "Void Invoice",
3027
+ "description": "This will set the invoice status to 'void' and post the corresponding accounting entries. Only zero-balance approved/paid invoices are eligible.",
3028
+ "warningText": "Voiding affects accounting records and cannot be easily undone.",
3029
+ "confirmText": "Void Invoice",
3030
+ "cancelText": "Cancel",
3031
+ "sensitiveFields": [
3032
+ "invoiceId"
3033
+ ]
3034
+ },
2953
3035
  "args": [
2954
3036
  {
2955
3037
  "name": "invoiceId",
@@ -2970,6 +3052,40 @@
2970
3052
  "prependBody": "## Invoice Voided\n\n"
2971
3053
  }
2972
3054
  },
3055
+ {
3056
+ "name": "deleteInvoice",
3057
+ "description": "Permanently delete a draft invoice. ONLY works on status='draft'. For approved invoices, use voidInvoice (with caveats) or createInvoiceCreditNote — do NOT call deleteInvoice on approved invoices. DESTRUCTIVE: record removed permanently. ONLY call when the user explicitly says 'delete' for THIS invoice; never call as automatic recovery from another failed operation.",
3058
+ "needsApproval": true,
3059
+ "approvalConfig": {
3060
+ "title": "Delete Invoice",
3061
+ "description": "This will permanently delete the draft invoice.",
3062
+ "warningText": "DESTRUCTIVE — invoice record will be removed and cannot be recovered.",
3063
+ "confirmText": "Delete Invoice",
3064
+ "cancelText": "Cancel",
3065
+ "sensitiveFields": [
3066
+ "invoiceId"
3067
+ ]
3068
+ },
3069
+ "args": [
3070
+ {
3071
+ "name": "invoiceId",
3072
+ "description": "The unique identifier (UUID) of the draft or upcoming invoice to delete.",
3073
+ "type": "string",
3074
+ "required": true,
3075
+ "position": "path"
3076
+ }
3077
+ ],
3078
+ "requestTemplate": {
3079
+ "url": "/invoices/{invoiceId}",
3080
+ "method": "DELETE",
3081
+ "headers": {
3082
+ "Content-Type": "application/json"
3083
+ }
3084
+ },
3085
+ "responseTemplate": {
3086
+ "prependBody": "## Invoice Deleted\n\n"
3087
+ }
3088
+ },
2973
3089
  {
2974
3090
  "name": "createInvoiceCreditNote",
2975
3091
  "description": "Create a credit note against a specific invoice. The credit_note_amount is in the invoice's currency (e.g. 25 for $25). The API stores amounts in cents internally. Returns the created credit note.",
@@ -3009,7 +3125,7 @@
3009
3125
  },
3010
3126
  {
3011
3127
  "name": "generateInvoice",
3012
- "description": "Generate an invoice for a customer given a contract and date range. Use this for billing replay or on-demand invoice generation.",
3128
+ "description": "Generate ONE invoice covering ALL products active in the (contract, customer, from_date, to_date) tuple. The invoice contains every applicable product as separate line items — do NOT call once per product. Granularity is per-phase, NOT per-product. To bill a multi-phase contract, call ONCE per phase using that phase's start/end as from_date/to_date — e.g., a 2-phase contract with 2 products = 2 calls (yielding 2 invoices, each with 2 line items), NOT 4 calls. Iterating per product produces fragmented duplicate invoices and is incorrect. Use for billing replay or on-demand invoice generation.",
3013
3129
  "args": [
3014
3130
  {
3015
3131
  "name": "contract_id",
@@ -3265,7 +3381,18 @@
3265
3381
  },
3266
3382
  {
3267
3383
  "name": "deleteManualPayment",
3268
- "description": "Delete a manual payment. Only works on payments that are in an eligible status for deletion.",
3384
+ "description": "Permanently delete a manual payment record. Only works on payments in an eligible status for deletion. DESTRUCTIVE — only call when the user explicitly says 'delete' for THIS payment; never as automatic recovery from another failed operation.",
3385
+ "needsApproval": true,
3386
+ "approvalConfig": {
3387
+ "title": "Delete Manual Payment",
3388
+ "description": "This will permanently remove the manual payment record.",
3389
+ "warningText": "DESTRUCTIVE — payment record will be removed and cannot be recovered. Linked invoice balance may shift as a result.",
3390
+ "confirmText": "Delete Payment",
3391
+ "cancelText": "Cancel",
3392
+ "sensitiveFields": [
3393
+ "paymentId"
3394
+ ]
3395
+ },
3269
3396
  "args": [
3270
3397
  {
3271
3398
  "name": "paymentId",
@@ -4089,7 +4216,7 @@
4089
4216
  },
4090
4217
  {
4091
4218
  "name": "createProductPricing",
4092
- "description": "Create a new pricing configuration for a product. The pricing_data object is a discriminated union — the 'pricing_type' field determines which pricing model schema to use.",
4219
+ "description": "Create a new pricing configuration for a product. The pricing_data object is a discriminated union — the 'pricing_type' field determines which pricing model schema. IMPORTANT: ask the user explicitly for currency, quantity_type (fixed|metered), and unit amount BEFORE calling. Do not silently default any of these. unit_amount is in MAJOR currency units (float), e.g. 3 means $3, not 300 cents.",
4093
4220
  "args": [
4094
4221
  {
4095
4222
  "name": "productId",
@@ -4114,11 +4241,18 @@
4114
4241
  },
4115
4242
  {
4116
4243
  "name": "pricing_data",
4117
- "description": "Pricing data object (required). Must include 'pricing_type' discriminator. Supported types: flat_fee: {pricing_type:'flat_fee', unit_amount:10000}. per_unit: {pricing_type:'per_unit', unit_amount:500}. tiered: {pricing_type:'tiered', unit_amount:[1000,500], up_to:[100,null]}. volume: {pricing_type:'volume', unit_amount:[1000,500], up_to:[100,null]}. percent: {pricing_type:'percent', percentage:5.0}. package: {pricing_type:'package', package_size:10, unit_amount:5000}. step: {pricing_type:'step', unit_amount:[...], up_to:[...]}. matrix: {pricing_type:'matrix', dimensions:[...], values:[...]}.",
4244
+ "description": "Pricing data object (required). MUST include 'pricing_type' discriminator AND 'currency' (ISO 4217, e.g. 'USD'). 'unit_amount' is in MAJOR currency units (float) — 3 means $3, NOT 300 cents. Do NOT convert to cents. Supported pricing_type values with examples: flat_fee: {pricing_type:'flat_fee', unit_amount:100, currency:'USD'}. per_unit: {pricing_type:'per_unit', unit_amount:3, currency:'USD'}. tiered: {pricing_type:'tiered', unit_amount:[10,5], up_to:[100,null], currency:'USD'}. volume: {pricing_type:'volume', unit_amount:[10,5], up_to:[100,null], currency:'USD'}. percent: {pricing_type:'percent', percentage:5.0, currency:'USD'}. package: {pricing_type:'package', package_size:10, unit_amount:50, currency:'USD'}. step: {pricing_type:'step', unit_amount:[...], up_to:[...], currency:'USD'}. matrix: {pricing_type:'matrix', dimensions:[...], values:[...], currency:'USD'}. Optional inside pricing_data for per_unit: 'proration_type' ('day_based'|'cadence_based'), 'charge_full_amount' (bool).",
4118
4245
  "type": "object",
4119
4246
  "required": true,
4120
4247
  "position": "body"
4121
4248
  },
4249
+ {
4250
+ "name": "quantity",
4251
+ "description": "Quantity configuration (REQUIRED for usage-based pricing — ask the user). Top-level object, NOT inside pricing_data. Shape: {type: 'fixed'|'metered', quantity?: number, unit?: string, aggregate_id?: UUID}. 'fixed' means a static quantity (e.g. seats). 'metered' means consumption tracked via a billable metric — set aggregate_id to the billable-metric UUID. Without a quantity object the resulting pricing has no billing_metric and renders as 0 in the UI.",
4252
+ "type": "object",
4253
+ "required": false,
4254
+ "position": "body"
4255
+ },
4122
4256
  {
4123
4257
  "name": "billing_period",
4124
4258
  "description": "Billing cadence for this pricing. Object with 'cadence' (ISO 8601 duration, e.g. 'P1M' for monthly, 'P3M' for quarterly, 'P1Y' for annually) and optional 'offset'. Example: {cadence: 'P1M'}.",
@@ -4154,7 +4288,7 @@
4154
4288
  },
4155
4289
  {
4156
4290
  "name": "listPlans",
4157
- "description": "Retrieve a paginated list of plans (templates v3) that define reusable contract structures.",
4291
+ "description": "Retrieve a paginated list of plans that define reusable contract structures. Hits /plans — the same data the Zenskar app's Plans page reads.",
4158
4292
  "args": [
4159
4293
  {
4160
4294
  "name": "cursor",
@@ -4186,7 +4320,7 @@
4186
4320
  }
4187
4321
  ],
4188
4322
  "requestTemplate": {
4189
- "url": "/templates/plan",
4323
+ "url": "/plans",
4190
4324
  "method": "GET",
4191
4325
  "headers": {
4192
4326
  "Content-Type": "application/json"
@@ -4209,7 +4343,7 @@
4209
4343
  }
4210
4344
  ],
4211
4345
  "requestTemplate": {
4212
- "url": "/templates/plan/{planId}",
4346
+ "url": "/plans/{planId}",
4213
4347
  "method": "GET",
4214
4348
  "headers": {
4215
4349
  "Content-Type": "application/json"
@@ -4221,7 +4355,7 @@
4221
4355
  },
4222
4356
  {
4223
4357
  "name": "createPlan",
4224
- "description": "Create a new plan (template) that defines a reusable contract structure with products and pricing.",
4358
+ "description": "Create a new plan — a reusable contract template with products and pricing. Hits POST /plans (same store the Zenskar app's Plans page reads).",
4225
4359
  "args": [
4226
4360
  {
4227
4361
  "name": "name",
@@ -4281,7 +4415,7 @@
4281
4415
  }
4282
4416
  ],
4283
4417
  "requestTemplate": {
4284
- "url": "/templates/plan",
4418
+ "url": "/plans",
4285
4419
  "method": "POST",
4286
4420
  "headers": {
4287
4421
  "Content-Type": "application/json"
@@ -4291,73 +4425,6 @@
4291
4425
  "prependBody": "## Created Plan\n\n"
4292
4426
  }
4293
4427
  },
4294
- {
4295
- "name": "addProductsToPlan",
4296
- "description": "Add products with pricing to an existing plan.",
4297
- "args": [
4298
- {
4299
- "name": "planId",
4300
- "description": "The unique identifier of the plan.",
4301
- "type": "string",
4302
- "required": true,
4303
- "position": "path"
4304
- },
4305
- {
4306
- "name": "products",
4307
- "description": "Array of product objects to add, each with product_id and pricing configuration.",
4308
- "type": "array",
4309
- "required": true,
4310
- "position": "body"
4311
- }
4312
- ],
4313
- "requestTemplate": {
4314
- "url": "/templates/plan/{planId}/products",
4315
- "method": "POST",
4316
- "headers": {
4317
- "Content-Type": "application/json"
4318
- }
4319
- },
4320
- "responseTemplate": {
4321
- "prependBody": "## Updated Plan Products\n\n"
4322
- }
4323
- },
4324
- {
4325
- "name": "previewPlanEstimate",
4326
- "description": "Preview the estimated billing for a plan, showing projected charges per phase.",
4327
- "args": [
4328
- {
4329
- "name": "planId",
4330
- "description": "The unique identifier of the plan.",
4331
- "type": "string",
4332
- "required": true,
4333
- "position": "path"
4334
- },
4335
- {
4336
- "name": "start_date",
4337
- "description": "Start date for the estimate (YYYY-MM-DD).",
4338
- "type": "string",
4339
- "required": true,
4340
- "position": "body"
4341
- },
4342
- {
4343
- "name": "end_date",
4344
- "description": "End date for the estimate (YYYY-MM-DD). Optional — calculated from plan duration if omitted.",
4345
- "type": "string",
4346
- "required": false,
4347
- "position": "body"
4348
- }
4349
- ],
4350
- "requestTemplate": {
4351
- "url": "/templates/plan/{planId}/preview",
4352
- "method": "POST",
4353
- "headers": {
4354
- "Content-Type": "application/json"
4355
- }
4356
- },
4357
- "responseTemplate": {
4358
- "prependBody": "## Plan Estimate Preview\n\n"
4359
- }
4360
- },
4361
4428
  {
4362
4429
  "name": "listCustomAttributes",
4363
4430
  "description": "Retrieve a list of custom attribute definitions configured for the organization.",
@@ -5070,7 +5137,18 @@
5070
5137
  },
5071
5138
  {
5072
5139
  "name": "deleteCustomer",
5073
- "description": "Delete a customer by ID. This is permanent and cannot be undone. The customer must not have active contracts or unpaid invoices.",
5140
+ "description": "Permanently delete a customer by ID. DESTRUCTIVE and cannot be undone. The customer must not have active contracts or unpaid invoices. ONLY call when the user explicitly says 'delete' for THIS customer; never as automatic recovery from another failed operation.",
5141
+ "needsApproval": true,
5142
+ "approvalConfig": {
5143
+ "title": "Delete Customer",
5144
+ "description": "This will permanently delete the customer and all associated records.",
5145
+ "warningText": "DESTRUCTIVE — customer record cannot be recovered. Verify there are no active contracts or unpaid invoices first.",
5146
+ "confirmText": "Delete Customer",
5147
+ "cancelText": "Cancel",
5148
+ "sensitiveFields": [
5149
+ "customerId"
5150
+ ]
5151
+ },
5074
5152
  "args": [
5075
5153
  {
5076
5154
  "name": "customerId",
@@ -5093,7 +5171,18 @@
5093
5171
  },
5094
5172
  {
5095
5173
  "name": "deleteContact",
5096
- "description": "Delete a contact by ID.",
5174
+ "description": "Permanently delete a contact by ID. DESTRUCTIVE — only call when the user explicitly says 'delete' for THIS contact; never as automatic recovery from another failed operation.",
5175
+ "needsApproval": true,
5176
+ "approvalConfig": {
5177
+ "title": "Delete Contact",
5178
+ "description": "This will permanently delete the contact record.",
5179
+ "warningText": "DESTRUCTIVE — contact record will be removed and cannot be recovered.",
5180
+ "confirmText": "Delete Contact",
5181
+ "cancelText": "Cancel",
5182
+ "sensitiveFields": [
5183
+ "contactId"
5184
+ ]
5185
+ },
5097
5186
  "args": [
5098
5187
  {
5099
5188
  "name": "contactId",
@@ -5116,7 +5205,19 @@
5116
5205
  },
5117
5206
  {
5118
5207
  "name": "deletePaymentMethod",
5119
- "description": "Delete a payment method from a customer.",
5208
+ "description": "Permanently delete a payment method from a customer. DESTRUCTIVE — saved card/bank details will be removed; recurring auto-charges using this method will fail. Only call when the user explicitly says 'delete' for THIS payment method.",
5209
+ "needsApproval": true,
5210
+ "approvalConfig": {
5211
+ "title": "Delete Payment Method",
5212
+ "description": "This will permanently delete the payment method from the customer's profile.",
5213
+ "warningText": "DESTRUCTIVE — saved payment details will be removed. Any auto-charge or scheduled charge using this method will fail.",
5214
+ "confirmText": "Delete Payment Method",
5215
+ "cancelText": "Cancel",
5216
+ "sensitiveFields": [
5217
+ "customerId",
5218
+ "paymentMethodId"
5219
+ ]
5220
+ },
5120
5221
  "args": [
5121
5222
  {
5122
5223
  "name": "customerId",
@@ -5146,7 +5247,7 @@
5146
5247
  },
5147
5248
  {
5148
5249
  "name": "pauseContract",
5149
- "description": "Pause an active contract. Requires a start_date for the pause and an unpause_extension_policy.",
5250
+ "description": "Pause an active contract. ALWAYS ask the user explicitly for both 'start_date' and 'unpause_extension_policy' before calling — do NOT silently default. A future-dated start_date will create a scheduled pause that has not yet begun; the contract's top-level status stays 'active' until start_date passes. Use 'editPauseContract' to adjust the pause window or set a resume date afterward.",
5150
5251
  "args": [
5151
5252
  {
5152
5253
  "name": "contractId",
@@ -5157,14 +5258,14 @@
5157
5258
  },
5158
5259
  {
5159
5260
  "name": "start_date",
5160
- "description": "Date when the pause begins (ISO 8601 format, e.g. 2026-04-01T00:00:00).",
5261
+ "description": "Date when the pause begins (ISO 8601 format, e.g. 2026-04-01T00:00:00). REQUIRED — ask the user; never default to today or a future date silently.",
5161
5262
  "type": "string",
5162
5263
  "required": true,
5163
5264
  "position": "body"
5164
5265
  },
5165
5266
  {
5166
5267
  "name": "unpause_extension_policy",
5167
- "description": "How to handle the contract end date when unpaused.",
5268
+ "description": "How to handle the contract end date when unpaused. 'extend' pushes the end_date out by the pause duration; 'overlap' keeps end_date fixed. ASK the user.",
5168
5269
  "type": "string",
5169
5270
  "required": true,
5170
5271
  "position": "body",
@@ -5192,9 +5293,57 @@
5192
5293
  "prependBody": "## Contract Paused\n\n"
5193
5294
  }
5194
5295
  },
5296
+ {
5297
+ "name": "editPauseContract",
5298
+ "description": "Edit an existing pause phase on a contract. Use this when 'resumeContract' returns 'pause phase not found' for a future-dated pause, or when the user wants to set a resume date (pause end_date), shift the pause start, or change the unpause_extension_policy. Hits PATCH /contract_v2/{contractId}/pause.",
5299
+ "args": [
5300
+ {
5301
+ "name": "contractId",
5302
+ "description": "The unique identifier (UUID) of the contract whose pause phase to edit.",
5303
+ "type": "string",
5304
+ "required": true,
5305
+ "position": "path"
5306
+ },
5307
+ {
5308
+ "name": "start_date",
5309
+ "description": "New pause start date (ISO 8601). Optional.",
5310
+ "type": "string",
5311
+ "required": false,
5312
+ "position": "body"
5313
+ },
5314
+ {
5315
+ "name": "end_date",
5316
+ "description": "Resume date — when the pause ends (ISO 8601). Set this to schedule a future resume.",
5317
+ "type": "string",
5318
+ "required": false,
5319
+ "position": "body"
5320
+ },
5321
+ {
5322
+ "name": "unpause_extension_policy",
5323
+ "description": "How to handle the contract end date when unpaused. 'extend' or 'overlap'.",
5324
+ "type": "string",
5325
+ "required": false,
5326
+ "position": "body",
5327
+ "enum": [
5328
+ "extend",
5329
+ "overlap"
5330
+ ]
5331
+ }
5332
+ ],
5333
+ "requestTemplate": {
5334
+ "url": "/contract_v2/{contractId}/pause",
5335
+ "method": "PATCH",
5336
+ "headers": {
5337
+ "Content-Type": "application/json"
5338
+ }
5339
+ },
5340
+ "responseTemplate": {
5341
+ "prependBody": "## Pause Phase Updated\n\n"
5342
+ }
5343
+ },
5195
5344
  {
5196
5345
  "name": "resumeContract",
5197
- "description": "Resume a paused contract. No request body needed.",
5346
+ "description": "Resume an actively-running pause on a contract. PRECONDITIONS: the pause's start_date must be strictly in the past (not today, not future). For pauses scheduled for a future start, or to set a future resume date, use 'editPauseContract' instead (sets the pause's end_date). The error 'pause phase not found' means no pause phase has yet started — NOT that no pause exists. If this call fails, surface the error verbatim and consider 'editPauseContract'; do NOT escalate to deleteContract or any destructive fallback.",
5198
5347
  "args": [
5199
5348
  {
5200
5349
  "name": "contractId",
package/src/server.js CHANGED
@@ -211,6 +211,20 @@ function normalizeAccountCategory(category) {
211
211
  return normalized;
212
212
  }
213
213
 
214
+ function toDisplayAmount(value) {
215
+ const num = Number(value);
216
+ if (!Number.isFinite(num)) return null;
217
+ return num / 100;
218
+ }
219
+
220
+ function normalizeReportAccountCategory(reportType, category) {
221
+ const normalized = normalizeAccountCategory(category);
222
+ if (reportType === 'getBalanceSheet' && normalized === 'Liabilities') {
223
+ return 'Liabilities & Equity';
224
+ }
225
+ return normalized;
226
+ }
227
+
214
228
  function getCategoryOrder(reportType, category) {
215
229
  const balanceSheetOrder = {
216
230
  Assets: 1,
@@ -247,7 +261,7 @@ function buildAccountingStatementView(reportType, rows, accountLookup) {
247
261
  const sectionsMap = new Map();
248
262
 
249
263
  rows.forEach(row => {
250
- const category = normalizeAccountCategory(row.account_category);
264
+ const category = normalizeReportAccountCategory(reportType, row.account_category);
251
265
  const periodKey = buildPeriodKey(row);
252
266
  const periodEntry = periodMap.get(periodKey) || {
253
267
  key: periodKey,
@@ -290,8 +304,11 @@ function buildAccountingStatementView(reportType, rows, accountLookup) {
290
304
  interval_start: row.interval_start || null,
291
305
  interval_end: row.interval_end || null,
292
306
  balance: row.balance ?? 0,
307
+ display_balance: toDisplayAmount(row.balance ?? 0),
293
308
  debits: row.debits ?? 0,
294
- credits: row.credits ?? 0
309
+ display_debits: toDisplayAmount(row.debits ?? 0),
310
+ credits: row.credits ?? 0,
311
+ display_credits: toDisplayAmount(row.credits ?? 0)
295
312
  };
296
313
  account.total_balance += Number(row.balance || 0);
297
314
  account.total_debits += Number(row.debits || 0);
@@ -302,11 +319,17 @@ function buildAccountingStatementView(reportType, rows, accountLookup) {
302
319
  interval_end: row.interval_end || null,
303
320
  balance: 0,
304
321
  debits: 0,
305
- credits: 0
322
+ credits: 0,
323
+ display_balance: 0,
324
+ display_debits: 0,
325
+ display_credits: 0
306
326
  };
307
327
  sectionPeriod.balance += Number(row.balance || 0);
308
328
  sectionPeriod.debits += Number(row.debits || 0);
309
329
  sectionPeriod.credits += Number(row.credits || 0);
330
+ sectionPeriod.display_balance = toDisplayAmount(sectionPeriod.balance);
331
+ sectionPeriod.display_debits = toDisplayAmount(sectionPeriod.debits);
332
+ sectionPeriod.display_credits = toDisplayAmount(sectionPeriod.credits);
310
333
  section.totals_by_period[periodKey] = sectionPeriod;
311
334
  section.total_balance += Number(row.balance || 0);
312
335
  section.total_debits += Number(row.debits || 0);
@@ -328,8 +351,11 @@ function buildAccountingStatementView(reportType, rows, accountLookup) {
328
351
  })),
329
352
  totals_by_period: summarizePeriods(section.totals_by_period),
330
353
  total_balance: section.total_balance,
354
+ display_total_balance: toDisplayAmount(section.total_balance),
331
355
  total_debits: section.total_debits,
332
- total_credits: section.total_credits
356
+ display_total_debits: toDisplayAmount(section.total_debits),
357
+ total_credits: section.total_credits,
358
+ display_total_credits: toDisplayAmount(section.total_credits)
333
359
  }));
334
360
 
335
361
  return {
@@ -339,6 +365,127 @@ function buildAccountingStatementView(reportType, rows, accountLookup) {
339
365
  };
340
366
  }
341
367
 
368
+ function normalizeChartAccountCategory(account) {
369
+ if (!account) return '';
370
+ const category = account.account_category || '';
371
+ if (category === 'Liabilities') return 'Liabilities & Equity';
372
+ return category;
373
+ }
374
+
375
+ function buildChartOfAccountsView(accounts) {
376
+ const baseAccounts = [...accounts]
377
+ .map(account => ({
378
+ ...account,
379
+ account_category: normalizeChartAccountCategory(account)
380
+ }))
381
+ .sort((a, b) => {
382
+ const categoryDiff = normalizeChartAccountCategory(a).localeCompare(normalizeChartAccountCategory(b));
383
+ return categoryDiff !== 0 ? categoryDiff : String(a.name || '').localeCompare(String(b.name || ''));
384
+ });
385
+
386
+ const grouped = new Map();
387
+ baseAccounts.forEach(account => {
388
+ const category = normalizeChartAccountCategory(account) || 'Uncategorized';
389
+ if (!grouped.has(category)) grouped.set(category, []);
390
+ grouped.get(category).push(account);
391
+ });
392
+
393
+ const syntheticGroups = ['Assets', 'Liabilities & Equity', 'Equity', 'Income', 'Expenses'];
394
+ syntheticGroups.forEach(category => {
395
+ if (!grouped.has(category)) grouped.set(category, []);
396
+ });
397
+
398
+ const sections = Array.from(grouped.entries())
399
+ .sort((a, b) => a[0].localeCompare(b[0]))
400
+ .map(([category, entries]) => ({
401
+ id: category,
402
+ name: category,
403
+ description: category,
404
+ account_category: category,
405
+ is_parent: true,
406
+ parent_path: null,
407
+ children: entries.map(entry => ({
408
+ ...entry,
409
+ parent_path: entry.parent_path || category
410
+ }))
411
+ }));
412
+
413
+ const liabEqSection = sections.find(section => section.name === 'Liabilities & Equity');
414
+ if (liabEqSection) {
415
+ const hasEquity = liabEqSection.children.some(child => child.name === 'Equity');
416
+ if (!hasEquity) {
417
+ liabEqSection.children.push({
418
+ id: 'Equity',
419
+ name: 'Equity',
420
+ description: 'Equity',
421
+ account_category: 'Liabilities & Equity',
422
+ balance_normality: 'credit',
423
+ is_parent: true,
424
+ parent_path: 'Liabilities & Equity',
425
+ custom_data: { default_account: false },
426
+ children: []
427
+ });
428
+ liabEqSection.children.push({
429
+ id: 'Equity:Retained Earnings',
430
+ name: 'Retained Earnings',
431
+ description: 'Retained Earnings',
432
+ account_category: 'Liabilities & Equity',
433
+ balance_normality: 'credit',
434
+ is_parent: false,
435
+ parent_path: 'Equity',
436
+ custom_data: { default_account: false }
437
+ });
438
+ }
439
+ }
440
+
441
+ return sections;
442
+ }
443
+
444
+ function enrichJobsResult(result) {
445
+ if (!result || !Array.isArray(result.results)) return result;
446
+ const counts = result.results.reduce((acc, job) => {
447
+ const status = job.status || 'unknown';
448
+ acc[status] = (acc[status] || 0) + 1;
449
+ return acc;
450
+ }, {});
451
+
452
+ return {
453
+ ...result,
454
+ summary: {
455
+ total_count: result.total_count ?? result.results.length,
456
+ returned_count: result.results.length,
457
+ page_status_counts: counts,
458
+ has_more: !!result.next,
459
+ note: result.next
460
+ ? `Showing ${result.results.length} jobs from the current page. Use the cursor to continue through the remaining jobs.`
461
+ : `Showing ${result.results.length} jobs from the current page.`
462
+ }
463
+ };
464
+ }
465
+
466
+ function enrichListAccountsResult(result) {
467
+ if (!result || !Array.isArray(result.results)) return result;
468
+ const sortedResults = [...result.results].sort((a, b) => {
469
+ const categoryA = normalizeAccountCategory(a.account_category);
470
+ const categoryB = normalizeAccountCategory(b.account_category);
471
+ const categoryDiff = categoryA.localeCompare(categoryB);
472
+ return categoryDiff !== 0 ? categoryDiff : String(a.name || '').localeCompare(String(b.name || ''));
473
+ });
474
+
475
+ const grouped = sortedResults.reduce((acc, account) => {
476
+ const category = normalizeAccountCategory(account.account_category);
477
+ acc[category] = acc[category] || [];
478
+ acc[category].push(account);
479
+ return acc;
480
+ }, {});
481
+
482
+ return {
483
+ ...result,
484
+ results: sortedResults,
485
+ grouped_view: grouped
486
+ };
487
+ }
488
+
342
489
  async function fetchJson(url, headers) {
343
490
  const response = await fetch(url, { method: 'GET', headers });
344
491
  const text = await response.text();
@@ -353,6 +500,58 @@ async function fetchJson(url, headers) {
353
500
  }
354
501
  }
355
502
 
503
+ async function executeFrontendStyleRawMetricLogs(args, headers, baseUrl) {
504
+ const rawMetricId = args.rawMetricId;
505
+ const rawMetricUrl = `${baseUrl}/rawmetric/${encodeURIComponent(rawMetricId)}`;
506
+ const rawMetric = await fetchJson(rawMetricUrl, headers);
507
+
508
+ if (!rawMetric?.api_slug) {
509
+ throw new Error(`Unable to resolve api_slug for raw metric ${rawMetricId}`);
510
+ }
511
+
512
+ const previewHeaders = {
513
+ ...headers,
514
+ apiversion: '20240301'
515
+ };
516
+
517
+ const payload = {
518
+ limit: args.limit ?? 20,
519
+ offset: args.offset ?? 0,
520
+ order_by: args.order_by ?? [{ column: 'timestamp', type: 'DESC' }],
521
+ aggregate_operation: args.aggregate_operation ?? null,
522
+ customer_mapping: null,
523
+ end_date_mapping: null,
524
+ start_date_mapping: null,
525
+ table_name: `raw_metric_${rawMetric.api_slug}`,
526
+ visual_query: {
527
+ groups: [
528
+ {
529
+ filters: Array.isArray(args.filters) ? args.filters : [],
530
+ logic: 'AND'
531
+ }
532
+ ],
533
+ logic: 'AND'
534
+ }
535
+ };
536
+
537
+ const response = await fetch(`${baseUrl}/aggregate/visualquery/preview`, {
538
+ method: 'POST',
539
+ headers: previewHeaders,
540
+ body: JSON.stringify(payload)
541
+ });
542
+
543
+ const responseText = await response.text();
544
+ if (!response.ok) {
545
+ throw new Error(`API request failed: ${response.status} ${response.statusText}\nResponse: ${responseText}`);
546
+ }
547
+
548
+ try {
549
+ return JSON.parse(responseText);
550
+ } catch (_error) {
551
+ return responseText;
552
+ }
553
+ }
554
+
356
555
  async function enrichAccountingReportResult(toolName, result, headers, baseUrl) {
357
556
  if (!result || !Array.isArray(result.results)) {
358
557
  return result;
@@ -374,16 +573,92 @@ async function enrichAccountingReportResult(toolName, result, headers, baseUrl)
374
573
  ...row,
375
574
  account_name: account.name || row.account_id || null,
376
575
  account_description: account.description || null,
377
- account_category: normalizeAccountCategory(account.account_category),
576
+ account_category: normalizeReportAccountCategory(toolName, account.account_category),
378
577
  account_parent_path: account.parent_path || null,
379
- balance_normality: account.balance_normality || null
578
+ balance_normality: account.balance_normality || null,
579
+ display_balance: toDisplayAmount(row.balance ?? 0),
580
+ display_debits: toDisplayAmount(row.debits ?? 0),
581
+ display_credits: toDisplayAmount(row.credits ?? 0)
380
582
  };
381
583
  });
382
584
 
585
+ let statementView = buildAccountingStatementView(toolName, enrichedRows, accountLookup);
586
+
587
+ if (toolName === 'getBalanceSheet') {
588
+ const params = new URLSearchParams();
589
+ if (headers.apiversion) params.set('apiversion', headers.apiversion);
590
+ const incomeStatementUrl = `${baseUrl}/accounting_new/income_statement/v2`;
591
+ const incomeStatement = await fetchJson(incomeStatementUrl, headers);
592
+ const incomeRows = Array.isArray(incomeStatement?.results) ? incomeStatement.results : [];
593
+ const retainedByPeriod = {};
594
+
595
+ incomeRows.forEach(row => {
596
+ const periodKey = buildPeriodKey(row);
597
+ const period = retainedByPeriod[periodKey] || {
598
+ interval_start: row.interval_start || null,
599
+ interval_end: row.interval_end || null,
600
+ balance: 0,
601
+ debits: 0,
602
+ credits: 0
603
+ };
604
+ period.balance += Number(row.balance || 0);
605
+ period.debits += Number(row.debits || 0);
606
+ period.credits += Number(row.credits || 0);
607
+ retainedByPeriod[periodKey] = period;
608
+ });
609
+
610
+ const retainedPeriods = summarizePeriods(
611
+ Object.fromEntries(
612
+ Object.entries(retainedByPeriod).map(([key, value]) => [
613
+ key,
614
+ {
615
+ ...value,
616
+ display_balance: toDisplayAmount(value.balance),
617
+ display_debits: toDisplayAmount(value.debits),
618
+ display_credits: toDisplayAmount(value.credits)
619
+ }
620
+ ])
621
+ )
622
+ );
623
+
624
+ const retainedAccount = {
625
+ account_id: 'Equity:Retained Earnings',
626
+ account_name: 'Retained Earnings (Derived)',
627
+ account_description: 'Derived from the companion income statement for MCP presentation.',
628
+ account_category: 'Liabilities & Equity',
629
+ parent_path: 'Equity',
630
+ balance_normality: 'credit',
631
+ periods: retainedPeriods,
632
+ total_balance: retainedPeriods.reduce((sum, period) => sum + Number(period.balance || 0), 0),
633
+ display_total_balance: toDisplayAmount(retainedPeriods.reduce((sum, period) => sum + Number(period.balance || 0), 0)),
634
+ total_debits: retainedPeriods.reduce((sum, period) => sum + Number(period.debits || 0), 0),
635
+ display_total_debits: toDisplayAmount(retainedPeriods.reduce((sum, period) => sum + Number(period.debits || 0), 0)),
636
+ total_credits: retainedPeriods.reduce((sum, period) => sum + Number(period.credits || 0), 0),
637
+ display_total_credits: toDisplayAmount(retainedPeriods.reduce((sum, period) => sum + Number(period.credits || 0), 0))
638
+ };
639
+
640
+ let liabEqSection = statementView.sections.find(section => section.category === 'Liabilities & Equity');
641
+ if (!liabEqSection) {
642
+ liabEqSection = {
643
+ category: 'Liabilities & Equity',
644
+ accounts: [],
645
+ totals_by_period: [],
646
+ total_balance: 0,
647
+ display_total_balance: 0,
648
+ total_debits: 0,
649
+ display_total_debits: 0,
650
+ total_credits: 0,
651
+ display_total_credits: 0
652
+ };
653
+ statementView.sections.push(liabEqSection);
654
+ }
655
+ liabEqSection.accounts.push(retainedAccount);
656
+ }
657
+
383
658
  return {
384
659
  ...result,
385
660
  results: enrichedRows,
386
- statement_view: buildAccountingStatementView(toolName, enrichedRows, accountLookup)
661
+ statement_view: statementView
387
662
  };
388
663
  } catch (error) {
389
664
  logger.warn(`[${toolName}] Failed to enrich accounting report output; returning raw report`, {
@@ -770,6 +1045,13 @@ async function executeAPICall(tool, args) {
770
1045
  });
771
1046
 
772
1047
  try {
1048
+ if (tool.name === 'getRawMetricLogs') {
1049
+ const baseUrl = process.env.ZENSKAR_API_BASE_URL || 'https://api.zenskar.com';
1050
+ const result = await executeFrontendStyleRawMetricLogs(cleanArgs, headers, baseUrl);
1051
+ logger.debug(`[${tool.name}] Successfully processed frontend-style usage-event logs`);
1052
+ return result;
1053
+ }
1054
+
773
1055
  const response = await fetch(fullUrl, {
774
1056
  method,
775
1057
  headers,
@@ -801,6 +1083,18 @@ async function executeAPICall(tool, args) {
801
1083
  const baseUrl = process.env.ZENSKAR_API_BASE_URL || 'https://api.zenskar.com';
802
1084
  result = await enrichAccountingReportResult(tool.name, result, headers, baseUrl);
803
1085
  }
1086
+ if (tool.name === 'listJobs') {
1087
+ result = enrichJobsResult(result);
1088
+ }
1089
+ if (tool.name === 'listAccounts') {
1090
+ result = enrichListAccountsResult(result);
1091
+ }
1092
+ if (tool.name === 'getChartOfAccounts' && Array.isArray(result)) {
1093
+ result = {
1094
+ raw_accounts: result,
1095
+ chart_view: buildChartOfAccountsView(result)
1096
+ };
1097
+ }
804
1098
 
805
1099
  // Apply response template if available
806
1100
  if (tool.responseTemplate?.prependBody) {