mcp-zenskar 1.1.8 → 1.1.10
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 +16 -17
- package/package.json +1 -1
- package/src/mcp-config.json +79 -120
- package/src/response-processor.js +2 -2
- package/src/server.js +500 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ MCP server for the Zenskar API. 103 tools covering customers, contracts, invoice
|
|
|
8
8
|
- Contracts: create, read, update, delete, amend, add phases and pricing, expire
|
|
9
9
|
- Invoices: list, get, approve, void, generate, credit notes, download
|
|
10
10
|
- Payments: create, edit, refund, delete, auto-charge
|
|
11
|
-
- Credit notes: list, create
|
|
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
|
|
@@ -124,9 +124,9 @@ Once configured, you can ask Claude to interact with your Zenskar data:
|
|
|
124
124
|
| `getInvoiceGenerationStatus` | Check invoice generation status |
|
|
125
125
|
| `downloadInvoice` | Download invoice in JSON format |
|
|
126
126
|
| `getInvoiceContractJsonActuals` | Get contract actuals for an invoice |
|
|
127
|
-
| `getInvoicePayments` | Get payments
|
|
128
|
-
| `getInvoicePaymentsById` | Get
|
|
129
|
-
| `getInvoicePaymentsWithoutRefunds` | Get invoice
|
|
127
|
+
| `getInvoicePayments` | Get successful payments currently mapped to an invoice |
|
|
128
|
+
| `getInvoicePaymentsById` | Get successful payments for a specific invoice ID |
|
|
129
|
+
| `getInvoicePaymentsWithoutRefunds` | Get original payment records for an invoice, excluding refund payment rows |
|
|
130
130
|
| `getInvoiceLineItems` | Get invoice line items and pricing details |
|
|
131
131
|
| `getInvoiceSummary` | Get invoice summary |
|
|
132
132
|
| `getAllInvoiceTags` | Get all available invoice tags |
|
|
@@ -141,10 +141,10 @@ Once configured, you can ask Claude to interact with your Zenskar data:
|
|
|
141
141
|
### Payments
|
|
142
142
|
| Tool | Description |
|
|
143
143
|
|---|---|
|
|
144
|
-
| `listAllPayments` | List all payments with filtering and sorting |
|
|
144
|
+
| `listAllPayments` | List all payments with filtering and sorting, including refund records |
|
|
145
145
|
| `getPaymentById` | Get a payment by ID |
|
|
146
146
|
| `createPayment` | Record a payment against an invoice |
|
|
147
|
-
| `updatePayment` | Update a payment's
|
|
147
|
+
| `updatePayment` | Update a payment's invoice allocations (`payment_parts`) |
|
|
148
148
|
| `deleteManualPayment` | Delete a manual payment |
|
|
149
149
|
| `editManualPayment` | Edit a manual payment's amount or method |
|
|
150
150
|
| `refundPayment` | Refund a payment (full or partial) |
|
|
@@ -154,7 +154,6 @@ Once configured, you can ask Claude to interact with your Zenskar data:
|
|
|
154
154
|
|---|---|
|
|
155
155
|
| `listCreditNotes` | List credit notes with pagination |
|
|
156
156
|
| `getCreditNoteById` | Get a credit note by ID |
|
|
157
|
-
| `createCreditNote` | Create a standalone credit note |
|
|
158
157
|
|
|
159
158
|
### Products and Pricing
|
|
160
159
|
| Tool | Description |
|
|
@@ -232,16 +231,16 @@ Once configured, you can ask Claude to interact with your Zenskar data:
|
|
|
232
231
|
### Metrics and Usage Events
|
|
233
232
|
| Tool | Description |
|
|
234
233
|
|---|---|
|
|
235
|
-
| `listAggregates` | List
|
|
236
|
-
| `getAggregateSchemas` |
|
|
237
|
-
| `getAggregateEstimates` | Get
|
|
238
|
-
| `getAggregateById` | Get a
|
|
239
|
-
| `getAggregateLogs` | Get logs for a
|
|
240
|
-
| `listRawMetrics` | List
|
|
241
|
-
| `createRawMetric` | Create a
|
|
242
|
-
| `getRawMetricById` | Get a
|
|
243
|
-
| `getRawMetricLogs` | Get
|
|
244
|
-
| `getRawMetricBySlug` | Get a
|
|
234
|
+
| `listAggregates` | List Billable Metrics with filtering; backend/API may also call these aggregates |
|
|
235
|
+
| `getAggregateSchemas` | Show the underlying schemas for Billable Metrics (Aggregates); mainly useful for debugging or integration work |
|
|
236
|
+
| `getAggregateEstimates` | Get Billable Metric estimates; backend/API may also call these aggregates |
|
|
237
|
+
| `getAggregateById` | Get a Billable Metric by ID; backend/API may also call it an aggregate |
|
|
238
|
+
| `getAggregateLogs` | Get logs for a Billable Metric (Aggregate) |
|
|
239
|
+
| `listRawMetrics` | List Usage Events with filtering; backend/API may also call these raw metrics |
|
|
240
|
+
| `createRawMetric` | Create a Usage Event schema; backend/API calls this a raw metric |
|
|
241
|
+
| `getRawMetricById` | Get a Usage Event by ID; backend/API may also call it a raw metric |
|
|
242
|
+
| `getRawMetricLogs` | Get recent event rows for a Usage Event using the same preview-query path as the frontend |
|
|
243
|
+
| `getRawMetricBySlug` | Get a Usage Event by API slug; backend/API may also call it a raw metric |
|
|
245
244
|
| `ingestRawMetricEvent` | Ingest a usage event |
|
|
246
245
|
|
|
247
246
|
### Other
|
package/package.json
CHANGED
package/src/mcp-config.json
CHANGED
|
@@ -539,7 +539,7 @@
|
|
|
539
539
|
},
|
|
540
540
|
{
|
|
541
541
|
"name": "getInvoicePayments",
|
|
542
|
-
"description": "Get payments associated with a specific invoice.",
|
|
542
|
+
"description": "Get successful payments currently associated with a specific invoice. This view may omit refund records and non-success payment states.",
|
|
543
543
|
"args": [
|
|
544
544
|
{
|
|
545
545
|
"name": "invoiceId",
|
|
@@ -670,7 +670,7 @@
|
|
|
670
670
|
},
|
|
671
671
|
{
|
|
672
672
|
"name": "listAggregates",
|
|
673
|
-
"description": "
|
|
673
|
+
"description": "List Billable Metrics (also called aggregates in backend/API terminology) in paginated form, with filtering by ID, name, and data source.",
|
|
674
674
|
"args": [
|
|
675
675
|
{
|
|
676
676
|
"name": "cursor",
|
|
@@ -744,28 +744,28 @@
|
|
|
744
744
|
"method": "GET"
|
|
745
745
|
},
|
|
746
746
|
"responseTemplate": {
|
|
747
|
-
"prependBody": "## Billable Metrics
|
|
747
|
+
"prependBody": "## Billable Metrics (Aggregates)\n\nHere is the list of billable metrics. Backend/API terminology may also call these aggregates:\n\n"
|
|
748
748
|
}
|
|
749
749
|
},
|
|
750
750
|
{
|
|
751
751
|
"name": "getAggregateSchemas",
|
|
752
|
-
"description": "
|
|
752
|
+
"description": "Show the underlying storage/schema definitions for Billable Metrics (also called aggregates). Mainly useful for advanced debugging, SQL/schema inspection, and integration work rather than day-to-day business operations.",
|
|
753
753
|
"args": [],
|
|
754
754
|
"requestTemplate": {
|
|
755
755
|
"url": "/aggregates/tables/schema",
|
|
756
756
|
"method": "GET"
|
|
757
757
|
},
|
|
758
758
|
"responseTemplate": {
|
|
759
|
-
"prependBody": "## Billable Metric Schemas\n\nHere are the schemas for billable metrics:\n\n"
|
|
759
|
+
"prependBody": "## Billable Metric Schemas\n\nHere are the underlying schemas for billable metrics (aggregates):\n\n"
|
|
760
760
|
}
|
|
761
761
|
},
|
|
762
762
|
{
|
|
763
763
|
"name": "getAggregateEstimates",
|
|
764
|
-
"description": "Get
|
|
764
|
+
"description": "Get Billable Metric estimates based on the specified criteria. Backend/API terminology may also call a billable metric an aggregate.",
|
|
765
765
|
"args": [
|
|
766
766
|
{
|
|
767
767
|
"name": "aggregate_id",
|
|
768
|
-
"description": "The ID of the
|
|
768
|
+
"description": "The ID of the Billable Metric (aggregate).",
|
|
769
769
|
"type": "string",
|
|
770
770
|
"required": true,
|
|
771
771
|
"position": "query"
|
|
@@ -805,16 +805,16 @@
|
|
|
805
805
|
"method": "GET"
|
|
806
806
|
},
|
|
807
807
|
"responseTemplate": {
|
|
808
|
-
"prependBody": "## Billable Metric Estimates\n\nHere are the estimates for the
|
|
808
|
+
"prependBody": "## Billable Metric Estimates\n\nHere are the estimates for the Billable Metric (aggregate):\n\n"
|
|
809
809
|
}
|
|
810
810
|
},
|
|
811
811
|
{
|
|
812
812
|
"name": "getAggregateById",
|
|
813
|
-
"description": "Get the details of a
|
|
813
|
+
"description": "Get the details of a Billable Metric by its ID. Backend/API terminology may also call this an aggregate.",
|
|
814
814
|
"args": [
|
|
815
815
|
{
|
|
816
816
|
"name": "aggregateId",
|
|
817
|
-
"description": "The ID of the
|
|
817
|
+
"description": "The ID of the Billable Metric (aggregate).",
|
|
818
818
|
"type": "string",
|
|
819
819
|
"required": true,
|
|
820
820
|
"position": "path"
|
|
@@ -825,16 +825,16 @@
|
|
|
825
825
|
"method": "GET"
|
|
826
826
|
},
|
|
827
827
|
"responseTemplate": {
|
|
828
|
-
"prependBody": "## Billable Metric Details\n\nHere are the details for the specified
|
|
828
|
+
"prependBody": "## Billable Metric Details\n\nHere are the details for the specified Billable Metric (aggregate):\n\n"
|
|
829
829
|
}
|
|
830
830
|
},
|
|
831
831
|
{
|
|
832
832
|
"name": "getAggregateLogs",
|
|
833
|
-
"description": "Get
|
|
833
|
+
"description": "Get logs related to a Billable Metric (also called an aggregate) by ID, with optional date and customer filters.",
|
|
834
834
|
"args": [
|
|
835
835
|
{
|
|
836
836
|
"name": "aggregateId",
|
|
837
|
-
"description": "The ID of the
|
|
837
|
+
"description": "The ID of the Billable Metric (aggregate).",
|
|
838
838
|
"type": "string",
|
|
839
839
|
"required": true,
|
|
840
840
|
"position": "path"
|
|
@@ -881,12 +881,12 @@
|
|
|
881
881
|
"method": "GET"
|
|
882
882
|
},
|
|
883
883
|
"responseTemplate": {
|
|
884
|
-
"prependBody": "## Billable Metric Logs\n\nHere are the logs for the specified
|
|
884
|
+
"prependBody": "## Billable Metric Logs\n\nHere are the logs for the specified Billable Metric (aggregate):\n\n"
|
|
885
885
|
}
|
|
886
886
|
},
|
|
887
887
|
{
|
|
888
888
|
"name": "listRawMetrics",
|
|
889
|
-
"description": "
|
|
889
|
+
"description": "List Usage Events. In backend/API terminology these are also called raw metrics. Supports filtering by ID, name, and API slug.",
|
|
890
890
|
"args": [
|
|
891
891
|
{
|
|
892
892
|
"name": "cursor",
|
|
@@ -960,18 +960,18 @@
|
|
|
960
960
|
"method": "GET"
|
|
961
961
|
},
|
|
962
962
|
"responseTemplate": {
|
|
963
|
-
"prependBody": "## Usage Events
|
|
963
|
+
"prependBody": "## Usage Events\n\nHere is the list of usage events. Backend/API terminology may also call these raw metrics:\n\n"
|
|
964
964
|
}
|
|
965
965
|
},
|
|
966
966
|
{
|
|
967
967
|
"name": "createRawMetric",
|
|
968
|
-
"description": "Create a new raw metric
|
|
968
|
+
"description": "Create a new Usage Event schema. In backend/API terminology this resource is called a raw metric. This defines the schema used for ingesting usage events.",
|
|
969
969
|
"needsApproval": true,
|
|
970
970
|
"approvalConfig": {
|
|
971
|
-
"title": "Create
|
|
972
|
-
"description": "This will create a new raw metric and provision its event schema. Please review the payload carefully.",
|
|
971
|
+
"title": "Create Usage Event Schema",
|
|
972
|
+
"description": "This will create a new Usage Event schema (called a raw metric in backend/API terminology) and provision its event schema. Please review the payload carefully.",
|
|
973
973
|
"warningText": "Ensure the dataschema and slug values are correct before continuing.",
|
|
974
|
-
"confirmText": "Create
|
|
974
|
+
"confirmText": "Create Usage Event Schema",
|
|
975
975
|
"cancelText": "Cancel",
|
|
976
976
|
"sensitiveFields": [
|
|
977
977
|
"name",
|
|
@@ -983,21 +983,21 @@
|
|
|
983
983
|
"args": [
|
|
984
984
|
{
|
|
985
985
|
"name": "name",
|
|
986
|
-
"description": "Human-friendly name for the raw metric.",
|
|
986
|
+
"description": "Human-friendly name for the Usage Event schema (raw metric).",
|
|
987
987
|
"type": "string",
|
|
988
988
|
"required": true,
|
|
989
989
|
"position": "body"
|
|
990
990
|
},
|
|
991
991
|
{
|
|
992
992
|
"name": "api_slug",
|
|
993
|
-
"description": "API slug used when ingesting
|
|
993
|
+
"description": "API slug used when ingesting Usage Events (lowercase, underscores).",
|
|
994
994
|
"type": "string",
|
|
995
995
|
"required": true,
|
|
996
996
|
"position": "body"
|
|
997
997
|
},
|
|
998
998
|
{
|
|
999
999
|
"name": "dataschema",
|
|
1000
|
-
"description": "Data schema of raw metric
|
|
1000
|
+
"description": "Data schema of the Usage Event resource (called a raw metric in backend/API terminology). Structure: {\"data\": {custom_fields}, \"timestamp\": \"DateTime64\", \"customer_id\": \"String\"}. The 'timestamp' and 'customer_id' fields are mandatory schema fields for usage ingestion. The 'data' map must include only valid ClickHouse types (String, Int64, Float64, Date32, DateTime64, UUID, Bool); use 'Bool' instead of 'Boolean'.",
|
|
1001
1001
|
"type": "object",
|
|
1002
1002
|
"required": true,
|
|
1003
1003
|
"position": "body",
|
|
@@ -1035,16 +1035,16 @@
|
|
|
1035
1035
|
}
|
|
1036
1036
|
},
|
|
1037
1037
|
"responseTemplate": {
|
|
1038
|
-
"prependBody": "##
|
|
1038
|
+
"prependBody": "## Usage Event Schema Created\n\nThe Usage Event schema (raw metric) was created successfully:\n\n"
|
|
1039
1039
|
}
|
|
1040
1040
|
},
|
|
1041
1041
|
{
|
|
1042
1042
|
"name": "getRawMetricById",
|
|
1043
|
-
"description": "Get
|
|
1043
|
+
"description": "Get Usage Event details by ID. Backend/API terminology may also call this a raw metric.",
|
|
1044
1044
|
"args": [
|
|
1045
1045
|
{
|
|
1046
1046
|
"name": "rawMetricId",
|
|
1047
|
-
"description": "The ID of the raw metric.",
|
|
1047
|
+
"description": "The ID of the Usage Event resource (raw metric).",
|
|
1048
1048
|
"type": "string",
|
|
1049
1049
|
"required": true,
|
|
1050
1050
|
"position": "path"
|
|
@@ -1060,14 +1060,51 @@
|
|
|
1060
1060
|
},
|
|
1061
1061
|
{
|
|
1062
1062
|
"name": "getRawMetricLogs",
|
|
1063
|
-
"description": "Get
|
|
1063
|
+
"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
1064
|
"args": [
|
|
1065
1065
|
{
|
|
1066
1066
|
"name": "rawMetricId",
|
|
1067
|
-
"description": "The ID of the raw metric.",
|
|
1067
|
+
"description": "The ID of the Usage Event resource (raw metric).",
|
|
1068
1068
|
"type": "string",
|
|
1069
1069
|
"required": true,
|
|
1070
1070
|
"position": "path"
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
"name": "filters",
|
|
1074
|
+
"description": "Optional visual-query filter array, matching the frontend usage-event viewer format. Default is an empty filter list.",
|
|
1075
|
+
"type": "array",
|
|
1076
|
+
"required": false,
|
|
1077
|
+
"position": "body"
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
"name": "limit",
|
|
1081
|
+
"description": "Maximum number of event rows to return. Defaults to 20, matching frontend behavior.",
|
|
1082
|
+
"type": "integer",
|
|
1083
|
+
"required": false,
|
|
1084
|
+
"position": "body",
|
|
1085
|
+
"default": 20
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
"name": "offset",
|
|
1089
|
+
"description": "Pagination offset for event rows. Defaults to 0.",
|
|
1090
|
+
"type": "integer",
|
|
1091
|
+
"required": false,
|
|
1092
|
+
"position": "body",
|
|
1093
|
+
"default": 0
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
"name": "aggregate_operation",
|
|
1097
|
+
"description": "Optional aggregate operation object used by the frontend for event counts (for example COUNT on timestamp). Leave null for normal row listing.",
|
|
1098
|
+
"type": "object",
|
|
1099
|
+
"required": false,
|
|
1100
|
+
"position": "body"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
"name": "order_by",
|
|
1104
|
+
"description": "Optional ordering array. Defaults to [{column:'timestamp', type:'DESC'}] to match frontend event viewing.",
|
|
1105
|
+
"type": "array",
|
|
1106
|
+
"required": false,
|
|
1107
|
+
"position": "body"
|
|
1071
1108
|
}
|
|
1072
1109
|
],
|
|
1073
1110
|
"requestTemplate": {
|
|
@@ -1075,16 +1112,16 @@
|
|
|
1075
1112
|
"method": "GET"
|
|
1076
1113
|
},
|
|
1077
1114
|
"responseTemplate": {
|
|
1078
|
-
"prependBody": "## Usage Event Logs\n\nHere are
|
|
1115
|
+
"prependBody": "## Usage Event Logs\n\nHere are recent event rows for the specified usage event:\n\n"
|
|
1079
1116
|
}
|
|
1080
1117
|
},
|
|
1081
1118
|
{
|
|
1082
1119
|
"name": "getRawMetricBySlug",
|
|
1083
|
-
"description": "Get
|
|
1120
|
+
"description": "Get Usage Event details by API slug. Backend/API terminology may also call this a raw metric.",
|
|
1084
1121
|
"args": [
|
|
1085
1122
|
{
|
|
1086
1123
|
"name": "rawMetricSlug",
|
|
1087
|
-
"description": "The API slug of the raw metric.",
|
|
1124
|
+
"description": "The API slug of the Usage Event resource (raw metric).",
|
|
1088
1125
|
"type": "string",
|
|
1089
1126
|
"required": true,
|
|
1090
1127
|
"position": "path"
|
|
@@ -1100,7 +1137,7 @@
|
|
|
1100
1137
|
},
|
|
1101
1138
|
{
|
|
1102
1139
|
"name": "getInvoicePaymentsById",
|
|
1103
|
-
"description": "Get payments associated with a specific invoice ID.",
|
|
1140
|
+
"description": "Get successful payments associated with a specific invoice ID. This endpoint is success-only and may not show refund rows.",
|
|
1104
1141
|
"args": [
|
|
1105
1142
|
{
|
|
1106
1143
|
"name": "invoiceId",
|
|
@@ -1120,7 +1157,7 @@
|
|
|
1120
1157
|
},
|
|
1121
1158
|
{
|
|
1122
1159
|
"name": "getInvoicePaymentsWithoutRefunds",
|
|
1123
|
-
"description": "Get
|
|
1160
|
+
"description": "Get original payment records for an invoice, excluding refund payment rows. Refunded or partially refunded original payments may still appear here.",
|
|
1124
1161
|
"args": [
|
|
1125
1162
|
{
|
|
1126
1163
|
"name": "invoiceId",
|
|
@@ -1172,7 +1209,7 @@
|
|
|
1172
1209
|
},
|
|
1173
1210
|
{
|
|
1174
1211
|
"name": "listAllPayments",
|
|
1175
|
-
"description": "Retrieve a paginated list of all payments, with extensive filtering and sorting options.",
|
|
1212
|
+
"description": "Retrieve a paginated list of all payments, including refund records, with extensive filtering and sorting options.",
|
|
1176
1213
|
"args": [
|
|
1177
1214
|
{
|
|
1178
1215
|
"name": "cursor",
|
|
@@ -2268,12 +2305,12 @@
|
|
|
2268
2305
|
},
|
|
2269
2306
|
{
|
|
2270
2307
|
"name": "ingestRawMetricEvent",
|
|
2271
|
-
"description": "Ingest a
|
|
2308
|
+
"description": "Ingest a Usage Event for the specified resource slug. Backend/API terminology may also call the target resource a raw metric.",
|
|
2272
2309
|
"needsApproval": true,
|
|
2273
2310
|
"approvalConfig": {
|
|
2274
2311
|
"title": "Ingest Usage Event",
|
|
2275
|
-
"description": "This will ingest a
|
|
2276
|
-
"warningText": "Ensure the data payload matches the raw metric schema before proceeding.",
|
|
2312
|
+
"description": "This will ingest a Usage Event for the selected Usage Event schema (called a raw metric in backend/API terminology). Review the payload carefully.",
|
|
2313
|
+
"warningText": "Ensure the data payload matches the Usage Event schema (raw metric schema) before proceeding.",
|
|
2277
2314
|
"confirmText": "Ingest Usage Event",
|
|
2278
2315
|
"cancelText": "Cancel",
|
|
2279
2316
|
"sensitiveFields": [
|
|
@@ -2284,14 +2321,14 @@
|
|
|
2284
2321
|
"args": [
|
|
2285
2322
|
{
|
|
2286
2323
|
"name": "rawMetricSlug",
|
|
2287
|
-
"description": "Slug of the raw metric
|
|
2324
|
+
"description": "Slug of the Usage Event resource (raw metric), e.g. api_usage_events.",
|
|
2288
2325
|
"type": "string",
|
|
2289
2326
|
"required": true,
|
|
2290
2327
|
"position": "path"
|
|
2291
2328
|
},
|
|
2292
2329
|
{
|
|
2293
2330
|
"name": "event",
|
|
2294
|
-
"description": "Usage
|
|
2331
|
+
"description": "Usage Event payload containing customer_id, timestamp, and a data map with schema-compliant types. customer_id and timestamp are mandatory event fields and must be provided in the payload; MCP does not auto-add them. Before constructing the event, fetch the schema via getRawMetricBySlug (or reuse the response from createRawMetric) so you only include fields that exist in the Usage Event schema (raw metric schema). Use the provided type buckets such as String/Int64 or the exact column names returned by sample logs; do not invent new keys.",
|
|
2295
2332
|
"type": "object",
|
|
2296
2333
|
"required": true,
|
|
2297
2334
|
"position": "body",
|
|
@@ -3108,84 +3145,6 @@
|
|
|
3108
3145
|
"prependBody": "## Credit Notes\n\n"
|
|
3109
3146
|
}
|
|
3110
3147
|
},
|
|
3111
|
-
{
|
|
3112
|
-
"name": "createCreditNote",
|
|
3113
|
-
"description": "Create a standalone credit note (not tied to a specific invoice). Status enum: in_progress, issued, void, paid. Repayment method enum: credits, invoice_adjusted, external_payment, credits_against_payment, original_payment_method.",
|
|
3114
|
-
"args": [
|
|
3115
|
-
{
|
|
3116
|
-
"name": "amount",
|
|
3117
|
-
"description": "Credit note amount in the specified currency.",
|
|
3118
|
-
"type": "number",
|
|
3119
|
-
"required": true,
|
|
3120
|
-
"position": "body"
|
|
3121
|
-
},
|
|
3122
|
-
{
|
|
3123
|
-
"name": "currency",
|
|
3124
|
-
"description": "Three-letter ISO 4217 currency code (e.g. USD).",
|
|
3125
|
-
"type": "string",
|
|
3126
|
-
"required": true,
|
|
3127
|
-
"position": "body"
|
|
3128
|
-
},
|
|
3129
|
-
{
|
|
3130
|
-
"name": "customer_id",
|
|
3131
|
-
"description": "UUID of the customer this credit note is for.",
|
|
3132
|
-
"type": "string",
|
|
3133
|
-
"required": true,
|
|
3134
|
-
"position": "body"
|
|
3135
|
-
},
|
|
3136
|
-
{
|
|
3137
|
-
"name": "status",
|
|
3138
|
-
"description": "Credit note status: in_progress, issued, void, or paid.",
|
|
3139
|
-
"type": "string",
|
|
3140
|
-
"required": true,
|
|
3141
|
-
"position": "body",
|
|
3142
|
-
"enum": [
|
|
3143
|
-
"in_progress",
|
|
3144
|
-
"issued",
|
|
3145
|
-
"void",
|
|
3146
|
-
"paid"
|
|
3147
|
-
]
|
|
3148
|
-
},
|
|
3149
|
-
{
|
|
3150
|
-
"name": "repayment_method",
|
|
3151
|
-
"description": "How the credit is applied: credits, invoice_adjusted, external_payment, credits_against_payment, or original_payment_method.",
|
|
3152
|
-
"type": "string",
|
|
3153
|
-
"required": true,
|
|
3154
|
-
"position": "body",
|
|
3155
|
-
"enum": [
|
|
3156
|
-
"credits",
|
|
3157
|
-
"invoice_adjusted",
|
|
3158
|
-
"external_payment",
|
|
3159
|
-
"credits_against_payment",
|
|
3160
|
-
"original_payment_method"
|
|
3161
|
-
]
|
|
3162
|
-
},
|
|
3163
|
-
{
|
|
3164
|
-
"name": "line_items",
|
|
3165
|
-
"description": "Line items object describing the credit note breakdown.",
|
|
3166
|
-
"type": "object",
|
|
3167
|
-
"required": true,
|
|
3168
|
-
"position": "body"
|
|
3169
|
-
},
|
|
3170
|
-
{
|
|
3171
|
-
"name": "invoice_id",
|
|
3172
|
-
"description": "Optional invoice UUID to associate the credit note with.",
|
|
3173
|
-
"type": "string",
|
|
3174
|
-
"required": false,
|
|
3175
|
-
"position": "body"
|
|
3176
|
-
}
|
|
3177
|
-
],
|
|
3178
|
-
"requestTemplate": {
|
|
3179
|
-
"url": "/credit_notes",
|
|
3180
|
-
"method": "POST",
|
|
3181
|
-
"headers": {
|
|
3182
|
-
"Content-Type": "application/json"
|
|
3183
|
-
}
|
|
3184
|
-
},
|
|
3185
|
-
"responseTemplate": {
|
|
3186
|
-
"prependBody": "## Credit Note Created\n\n"
|
|
3187
|
-
}
|
|
3188
|
-
},
|
|
3189
3148
|
{
|
|
3190
3149
|
"name": "getCreditNoteById",
|
|
3191
3150
|
"description": "Retrieve a credit note by its ID, including amount, status, customer, invoice association, and line items URL.",
|
|
@@ -3313,7 +3272,7 @@
|
|
|
3313
3272
|
},
|
|
3314
3273
|
{
|
|
3315
3274
|
"name": "updatePayment",
|
|
3316
|
-
"description": "Update a payment's invoice allocations
|
|
3275
|
+
"description": "Update a payment's invoice allocations. The API requires a non-empty request body with payment_parts.",
|
|
3317
3276
|
"args": [
|
|
3318
3277
|
{
|
|
3319
3278
|
"name": "paymentId",
|
|
@@ -3324,9 +3283,9 @@
|
|
|
3324
3283
|
},
|
|
3325
3284
|
{
|
|
3326
3285
|
"name": "payment_parts",
|
|
3327
|
-
"description": "Updated array of invoice allocations. Each element: {invoice_id: 'UUID', amount: 10000}.",
|
|
3286
|
+
"description": "Updated array of invoice allocations. Each element: {invoice_id: 'UUID', amount: 10000}. This field is required by the API.",
|
|
3328
3287
|
"type": "array",
|
|
3329
|
-
"required":
|
|
3288
|
+
"required": true,
|
|
3330
3289
|
"position": "body"
|
|
3331
3290
|
}
|
|
3332
3291
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
class ResponseProcessor {
|
|
3
3
|
constructor() {
|
|
4
4
|
// Minimal configuration
|
|
5
|
-
this.maxResponseLength =
|
|
5
|
+
this.maxResponseLength = 2000000;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
processResponse(responseData, toolName) {
|
|
@@ -25,4 +25,4 @@ class ResponseProcessor {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
module.exports = ResponseProcessor;
|
|
28
|
+
module.exports = ResponseProcessor;
|
package/src/server.js
CHANGED
|
@@ -196,6 +196,478 @@ function normalizeRawMetricDataschema(schema) {
|
|
|
196
196
|
return normalized;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
function extractListPayload(data) {
|
|
200
|
+
if (!data) return [];
|
|
201
|
+
if (Array.isArray(data)) return data;
|
|
202
|
+
if (Array.isArray(data.results)) return data.results;
|
|
203
|
+
if (data.api_response !== undefined) return extractListPayload(data.api_response);
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeAccountCategory(category) {
|
|
208
|
+
if (!category || typeof category !== 'string') return 'Uncategorized';
|
|
209
|
+
const normalized = category.trim();
|
|
210
|
+
if (!normalized) return 'Uncategorized';
|
|
211
|
+
return normalized;
|
|
212
|
+
}
|
|
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
|
+
|
|
228
|
+
function getCategoryOrder(reportType, category) {
|
|
229
|
+
const balanceSheetOrder = {
|
|
230
|
+
Assets: 1,
|
|
231
|
+
Liabilities: 2,
|
|
232
|
+
Equity: 3
|
|
233
|
+
};
|
|
234
|
+
const incomeStatementOrder = {
|
|
235
|
+
Income: 1,
|
|
236
|
+
Revenue: 1,
|
|
237
|
+
'Cost of Goods Sold': 2,
|
|
238
|
+
Expense: 3,
|
|
239
|
+
Expenses: 3,
|
|
240
|
+
'Other Income': 4,
|
|
241
|
+
'Other Expense': 5,
|
|
242
|
+
'Other Expenses': 5
|
|
243
|
+
};
|
|
244
|
+
const map = reportType === 'getBalanceSheet' ? balanceSheetOrder : incomeStatementOrder;
|
|
245
|
+
return map[category] || 999;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function buildPeriodKey(row) {
|
|
249
|
+
const start = row?.interval_start || 'unknown_start';
|
|
250
|
+
const end = row?.interval_end || 'unknown_end';
|
|
251
|
+
return `${start}__${end}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function summarizePeriods(periodMap) {
|
|
255
|
+
return Object.values(periodMap)
|
|
256
|
+
.sort((a, b) => String(a.interval_start).localeCompare(String(b.interval_start)));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function buildAccountingStatementView(reportType, rows, accountLookup) {
|
|
260
|
+
const periodMap = new Map();
|
|
261
|
+
const sectionsMap = new Map();
|
|
262
|
+
|
|
263
|
+
rows.forEach(row => {
|
|
264
|
+
const category = normalizeReportAccountCategory(reportType, row.account_category);
|
|
265
|
+
const periodKey = buildPeriodKey(row);
|
|
266
|
+
const periodEntry = periodMap.get(periodKey) || {
|
|
267
|
+
key: periodKey,
|
|
268
|
+
interval_start: row.interval_start || null,
|
|
269
|
+
interval_end: row.interval_end || null
|
|
270
|
+
};
|
|
271
|
+
periodMap.set(periodKey, periodEntry);
|
|
272
|
+
|
|
273
|
+
if (!sectionsMap.has(category)) {
|
|
274
|
+
sectionsMap.set(category, {
|
|
275
|
+
category,
|
|
276
|
+
accounts: new Map(),
|
|
277
|
+
totals_by_period: {},
|
|
278
|
+
total_balance: 0,
|
|
279
|
+
total_debits: 0,
|
|
280
|
+
total_credits: 0
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const section = sectionsMap.get(category);
|
|
285
|
+
const accountId = row.account_id || 'unknown_account';
|
|
286
|
+
const accountMeta = accountLookup.get(accountId) || {};
|
|
287
|
+
if (!section.accounts.has(accountId)) {
|
|
288
|
+
section.accounts.set(accountId, {
|
|
289
|
+
account_id: accountId,
|
|
290
|
+
account_name: row.account_name || accountMeta.name || accountId,
|
|
291
|
+
account_description: row.account_description || accountMeta.description || null,
|
|
292
|
+
account_category: category,
|
|
293
|
+
parent_path: row.account_parent_path || accountMeta.parent_path || null,
|
|
294
|
+
balance_normality: row.balance_normality || accountMeta.balance_normality || null,
|
|
295
|
+
periods: {},
|
|
296
|
+
total_balance: 0,
|
|
297
|
+
total_debits: 0,
|
|
298
|
+
total_credits: 0
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const account = section.accounts.get(accountId);
|
|
303
|
+
account.periods[periodKey] = {
|
|
304
|
+
interval_start: row.interval_start || null,
|
|
305
|
+
interval_end: row.interval_end || null,
|
|
306
|
+
balance: row.balance ?? 0,
|
|
307
|
+
display_balance: toDisplayAmount(row.balance ?? 0),
|
|
308
|
+
debits: row.debits ?? 0,
|
|
309
|
+
display_debits: toDisplayAmount(row.debits ?? 0),
|
|
310
|
+
credits: row.credits ?? 0,
|
|
311
|
+
display_credits: toDisplayAmount(row.credits ?? 0)
|
|
312
|
+
};
|
|
313
|
+
account.total_balance += Number(row.balance || 0);
|
|
314
|
+
account.total_debits += Number(row.debits || 0);
|
|
315
|
+
account.total_credits += Number(row.credits || 0);
|
|
316
|
+
|
|
317
|
+
const sectionPeriod = section.totals_by_period[periodKey] || {
|
|
318
|
+
interval_start: row.interval_start || null,
|
|
319
|
+
interval_end: row.interval_end || null,
|
|
320
|
+
balance: 0,
|
|
321
|
+
debits: 0,
|
|
322
|
+
credits: 0,
|
|
323
|
+
display_balance: 0,
|
|
324
|
+
display_debits: 0,
|
|
325
|
+
display_credits: 0
|
|
326
|
+
};
|
|
327
|
+
sectionPeriod.balance += Number(row.balance || 0);
|
|
328
|
+
sectionPeriod.debits += Number(row.debits || 0);
|
|
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);
|
|
333
|
+
section.totals_by_period[periodKey] = sectionPeriod;
|
|
334
|
+
section.total_balance += Number(row.balance || 0);
|
|
335
|
+
section.total_debits += Number(row.debits || 0);
|
|
336
|
+
section.total_credits += Number(row.credits || 0);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const sections = Array.from(sectionsMap.values())
|
|
340
|
+
.sort((a, b) => {
|
|
341
|
+
const orderDiff = getCategoryOrder(reportType, a.category) - getCategoryOrder(reportType, b.category);
|
|
342
|
+
return orderDiff !== 0 ? orderDiff : a.category.localeCompare(b.category);
|
|
343
|
+
})
|
|
344
|
+
.map(section => ({
|
|
345
|
+
category: section.category,
|
|
346
|
+
accounts: Array.from(section.accounts.values())
|
|
347
|
+
.sort((a, b) => a.account_name.localeCompare(b.account_name))
|
|
348
|
+
.map(account => ({
|
|
349
|
+
...account,
|
|
350
|
+
periods: summarizePeriods(account.periods)
|
|
351
|
+
})),
|
|
352
|
+
totals_by_period: summarizePeriods(section.totals_by_period),
|
|
353
|
+
total_balance: section.total_balance,
|
|
354
|
+
display_total_balance: toDisplayAmount(section.total_balance),
|
|
355
|
+
total_debits: section.total_debits,
|
|
356
|
+
display_total_debits: toDisplayAmount(section.total_debits),
|
|
357
|
+
total_credits: section.total_credits,
|
|
358
|
+
display_total_credits: toDisplayAmount(section.total_credits)
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
report_type: reportType === 'getBalanceSheet' ? 'balance_sheet' : 'income_statement',
|
|
363
|
+
periods: Array.from(periodMap.values()).sort((a, b) => String(a.interval_start).localeCompare(String(b.interval_start))),
|
|
364
|
+
sections
|
|
365
|
+
};
|
|
366
|
+
}
|
|
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
|
+
|
|
489
|
+
async function fetchJson(url, headers) {
|
|
490
|
+
const response = await fetch(url, { method: 'GET', headers });
|
|
491
|
+
const text = await response.text();
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
throw new Error(`Supplemental fetch failed: ${response.status} ${response.statusText}\nResponse: ${text}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
return JSON.parse(text);
|
|
498
|
+
} catch (_error) {
|
|
499
|
+
throw new Error(`Supplemental fetch returned non-JSON response: ${text}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
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
|
+
|
|
555
|
+
async function enrichAccountingReportResult(toolName, result, headers, baseUrl) {
|
|
556
|
+
if (!result || !Array.isArray(result.results)) {
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
const chartUrl = `${baseUrl}/accounting_new/chart_of_accounts`;
|
|
562
|
+
const chartData = await fetchJson(chartUrl, headers);
|
|
563
|
+
const chartAccounts = extractListPayload(chartData);
|
|
564
|
+
const accountLookup = new Map(
|
|
565
|
+
chartAccounts
|
|
566
|
+
.filter(account => account && account.id)
|
|
567
|
+
.map(account => [account.id, account])
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const enrichedRows = result.results.map(row => {
|
|
571
|
+
const account = accountLookup.get(row.account_id) || {};
|
|
572
|
+
return {
|
|
573
|
+
...row,
|
|
574
|
+
account_name: account.name || row.account_id || null,
|
|
575
|
+
account_description: account.description || null,
|
|
576
|
+
account_category: normalizeReportAccountCategory(toolName, account.account_category),
|
|
577
|
+
account_parent_path: account.parent_path || 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)
|
|
582
|
+
};
|
|
583
|
+
});
|
|
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
|
+
|
|
658
|
+
return {
|
|
659
|
+
...result,
|
|
660
|
+
results: enrichedRows,
|
|
661
|
+
statement_view: statementView
|
|
662
|
+
};
|
|
663
|
+
} catch (error) {
|
|
664
|
+
logger.warn(`[${toolName}] Failed to enrich accounting report output; returning raw report`, {
|
|
665
|
+
error: error.message
|
|
666
|
+
});
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
199
671
|
// Helper function to convert OpenAPI args to Zod schema
|
|
200
672
|
function convertArgsToZodSchema(args) {
|
|
201
673
|
const schemaObj = {};
|
|
@@ -573,6 +1045,13 @@ async function executeAPICall(tool, args) {
|
|
|
573
1045
|
});
|
|
574
1046
|
|
|
575
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
|
+
|
|
576
1055
|
const response = await fetch(fullUrl, {
|
|
577
1056
|
method,
|
|
578
1057
|
headers,
|
|
@@ -587,7 +1066,9 @@ async function executeAPICall(tool, args) {
|
|
|
587
1066
|
|
|
588
1067
|
if (!response.ok) {
|
|
589
1068
|
logger.error(`[${tool.name}] API Error Response:`, responseText);
|
|
590
|
-
|
|
1069
|
+
let errorMessage = `API request failed: ${response.status} ${response.statusText}\nResponse: ${responseText}`;
|
|
1070
|
+
|
|
1071
|
+
throw new Error(errorMessage);
|
|
591
1072
|
}
|
|
592
1073
|
|
|
593
1074
|
let result;
|
|
@@ -597,6 +1078,23 @@ async function executeAPICall(tool, args) {
|
|
|
597
1078
|
logger.debug(`[${tool.name}] Failed to parse JSON response, returning as text`);
|
|
598
1079
|
result = responseText;
|
|
599
1080
|
}
|
|
1081
|
+
|
|
1082
|
+
if (tool.name === 'getBalanceSheet' || tool.name === 'getIncomeStatement') {
|
|
1083
|
+
const baseUrl = process.env.ZENSKAR_API_BASE_URL || 'https://api.zenskar.com';
|
|
1084
|
+
result = await enrichAccountingReportResult(tool.name, result, headers, baseUrl);
|
|
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
|
+
}
|
|
600
1098
|
|
|
601
1099
|
// Apply response template if available
|
|
602
1100
|
if (tool.responseTemplate?.prependBody) {
|
|
@@ -815,7 +1313,7 @@ if (mcpConfig.tools && mcpConfig.tools.length > 0) {
|
|
|
815
1313
|
|
|
816
1314
|
// Use adjusted args with enforced limits
|
|
817
1315
|
const adjustedArgs = limitsValidation.adjustedArgs;
|
|
818
|
-
|
|
1316
|
+
|
|
819
1317
|
// Track limit adjustments
|
|
820
1318
|
if (args.limit && adjustedArgs.limit && args.limit !== adjustedArgs.limit) {
|
|
821
1319
|
limitRequested = args.limit;
|