@armor/zuora-mcp 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +29 -2
- package/dist/resources.js.map +1 -1
- package/dist/tools.d.ts +288 -6
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +568 -27
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +139 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/zuora-client.d.ts +65 -1
- package/dist/zuora-client.d.ts.map +1 -1
- package/dist/zuora-client.js +176 -2
- package/dist/zuora-client.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -79,6 +79,12 @@ export const schemas = {
|
|
|
79
79
|
.string()
|
|
80
80
|
.min(1)
|
|
81
81
|
.describe("Account ID or account number"),
|
|
82
|
+
maxResults: z
|
|
83
|
+
.number()
|
|
84
|
+
.int()
|
|
85
|
+
.min(1)
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Maximum number of subscriptions to return. When specified, results are truncated server-side. Omit to return all subscriptions for the account."),
|
|
82
88
|
}),
|
|
83
89
|
// Payment Tools
|
|
84
90
|
getPayment: z.object({
|
|
@@ -116,15 +122,27 @@ export const schemas = {
|
|
|
116
122
|
"Syntax: SELECT field1, field2 FROM ObjectName WHERE condition. " +
|
|
117
123
|
"Key objects: Account, Invoice, Payment, Subscription, RatePlan, " +
|
|
118
124
|
"RatePlanCharge, Product, ProductRatePlan, Contact. " +
|
|
119
|
-
"Limitations: No JOINs. Max 2000 records per call. " +
|
|
125
|
+
"Limitations: No JOINs supported. Max 2000 records per call. " +
|
|
120
126
|
"Use continue_zoql_query with queryLocator for pagination. " +
|
|
121
127
|
"Example: SELECT Id, AccountNumber, Balance FROM Account WHERE Status = 'Active'"),
|
|
128
|
+
maxResults: z
|
|
129
|
+
.number()
|
|
130
|
+
.int()
|
|
131
|
+
.min(1)
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Maximum records to return. When specified, results are truncated server-side to exactly this count. ZOQL has no LIMIT clause, so this parameter handles it at the application layer. Omit to return all records from the first page (up to 2000)."),
|
|
122
134
|
}),
|
|
123
135
|
continueZoqlQuery: z.object({
|
|
124
136
|
queryLocator: z
|
|
125
137
|
.string()
|
|
126
138
|
.min(1)
|
|
127
139
|
.describe("Query locator from a previous ZOQL query result for pagination"),
|
|
140
|
+
maxResults: z
|
|
141
|
+
.number()
|
|
142
|
+
.int()
|
|
143
|
+
.min(1)
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Maximum records to return from this continuation page. When specified, results are truncated server-side. Omit to return all records from the page (up to 2000)."),
|
|
128
146
|
}),
|
|
129
147
|
// Product Catalog Tools
|
|
130
148
|
listProducts: z.object({
|
|
@@ -198,6 +216,12 @@ export const schemas = {
|
|
|
198
216
|
.enum(["=", "!=", "LIKE", ">", "<", ">=", "<="])
|
|
199
217
|
.default("=")
|
|
200
218
|
.describe("Comparison operator (default: =). Use LIKE for partial name matches with % wildcard."),
|
|
219
|
+
maxResults: z
|
|
220
|
+
.number()
|
|
221
|
+
.int()
|
|
222
|
+
.min(1)
|
|
223
|
+
.optional()
|
|
224
|
+
.describe("Maximum number of accounts to return. When specified, results are truncated server-side to exactly this count. Supports any count including values beyond the 2000-per-page ZOQL limit via auto-pagination. Omit to return all matching records."),
|
|
201
225
|
}),
|
|
202
226
|
listUsage: z.object({
|
|
203
227
|
accountKey: z
|
|
@@ -218,6 +242,73 @@ export const schemas = {
|
|
|
218
242
|
.default(20)
|
|
219
243
|
.describe("Records per page (default: 20, max: 40)"),
|
|
220
244
|
}),
|
|
245
|
+
// User Management Tools
|
|
246
|
+
listUsers: z.object({
|
|
247
|
+
startIndex: z
|
|
248
|
+
.number()
|
|
249
|
+
.int()
|
|
250
|
+
.min(1)
|
|
251
|
+
.default(1)
|
|
252
|
+
.describe("1-based start index for SCIM pagination (default: 1)"),
|
|
253
|
+
count: z
|
|
254
|
+
.number()
|
|
255
|
+
.int()
|
|
256
|
+
.min(1)
|
|
257
|
+
.max(100)
|
|
258
|
+
.default(100)
|
|
259
|
+
.describe("Number of users to return per page (default: 100, max: 100)"),
|
|
260
|
+
filter: z
|
|
261
|
+
.string()
|
|
262
|
+
.max(500)
|
|
263
|
+
.optional()
|
|
264
|
+
.describe("SCIM filter string (optional). " +
|
|
265
|
+
"Examples: status eq \"Active\", userName eq \"user@example.com\""),
|
|
266
|
+
}),
|
|
267
|
+
getUser: z.object({
|
|
268
|
+
userId: z
|
|
269
|
+
.string()
|
|
270
|
+
.uuid("User ID must be a valid UUID")
|
|
271
|
+
.describe("Zuora user ID (UUID format)"),
|
|
272
|
+
}),
|
|
273
|
+
// Bill Run Read Tools
|
|
274
|
+
getBillRun: z.object({
|
|
275
|
+
billRunId: z
|
|
276
|
+
.string()
|
|
277
|
+
.min(1)
|
|
278
|
+
.describe("Bill run ID (e.g., 2c92c0f8...)"),
|
|
279
|
+
}),
|
|
280
|
+
listBillRuns: z.object({
|
|
281
|
+
page: z
|
|
282
|
+
.number()
|
|
283
|
+
.int()
|
|
284
|
+
.min(1)
|
|
285
|
+
.default(1)
|
|
286
|
+
.describe("Page number (default: 1)"),
|
|
287
|
+
pageSize: z
|
|
288
|
+
.number()
|
|
289
|
+
.int()
|
|
290
|
+
.min(1)
|
|
291
|
+
.max(40)
|
|
292
|
+
.default(20)
|
|
293
|
+
.describe("Records per page (default: 20, max: 40)"),
|
|
294
|
+
}),
|
|
295
|
+
// Contact Read Tools
|
|
296
|
+
getContact: z.object({
|
|
297
|
+
contactId: z
|
|
298
|
+
.string()
|
|
299
|
+
.min(1)
|
|
300
|
+
.describe("Contact ID (e.g., 2c92c0f8...)"),
|
|
301
|
+
}),
|
|
302
|
+
// Describe API
|
|
303
|
+
describeObject: z.object({
|
|
304
|
+
objectType: z
|
|
305
|
+
.string()
|
|
306
|
+
.min(1)
|
|
307
|
+
.max(100)
|
|
308
|
+
.regex(/^[A-Z][a-zA-Z]*$/, "Object type must be PascalCase (e.g., Account, Invoice, Subscription)")
|
|
309
|
+
.describe("Zuora object type name in PascalCase (e.g., Account, Invoice, Subscription, " +
|
|
310
|
+
"Payment, RatePlan, RatePlanCharge, Product, ProductRatePlan, BillRun, Contact)"),
|
|
311
|
+
}),
|
|
221
312
|
// Phase 3: Write Operations
|
|
222
313
|
createPayment: z.object({
|
|
223
314
|
accountId: z
|
|
@@ -597,6 +688,126 @@ export const schemas = {
|
|
|
597
688
|
.describe("UUID to prevent duplicate refunds on retries. " +
|
|
598
689
|
"Generate a new UUID v4 for each distinct refund intent."),
|
|
599
690
|
}),
|
|
691
|
+
// Bill Run Write Tools
|
|
692
|
+
createBillRun: z.object({
|
|
693
|
+
targetDate: z
|
|
694
|
+
.string()
|
|
695
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be YYYY-MM-DD format")
|
|
696
|
+
.refine(isValidCalendarDate, "Must be a valid calendar date")
|
|
697
|
+
.describe("Target date for the bill run in YYYY-MM-DD format. Invoices are generated for charges through this date."),
|
|
698
|
+
invoiceDate: z
|
|
699
|
+
.string()
|
|
700
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be YYYY-MM-DD format")
|
|
701
|
+
.refine(isValidCalendarDate, "Must be a valid calendar date")
|
|
702
|
+
.optional()
|
|
703
|
+
.describe("Invoice date in YYYY-MM-DD format (optional, defaults to targetDate)"),
|
|
704
|
+
autoPost: z
|
|
705
|
+
.boolean()
|
|
706
|
+
.default(false)
|
|
707
|
+
.describe("Automatically post invoices after generation (default: false)"),
|
|
708
|
+
autoEmail: z
|
|
709
|
+
.boolean()
|
|
710
|
+
.default(false)
|
|
711
|
+
.describe("Automatically email invoices after posting (default: false). Requires autoPost to be true."),
|
|
712
|
+
name: z
|
|
713
|
+
.string()
|
|
714
|
+
.max(255)
|
|
715
|
+
.optional()
|
|
716
|
+
.describe("Optional name/label for the bill run"),
|
|
717
|
+
idempotencyKey: z
|
|
718
|
+
.string()
|
|
719
|
+
.uuid("Idempotency key must be a valid UUID")
|
|
720
|
+
.describe("UUID to prevent duplicate bill runs on retries. " +
|
|
721
|
+
"Generate a new UUID v4 for each distinct bill run intent."),
|
|
722
|
+
}).superRefine((data, ctx) => {
|
|
723
|
+
if (data.autoEmail && !data.autoPost) {
|
|
724
|
+
ctx.addIssue({
|
|
725
|
+
code: z.ZodIssueCode.custom,
|
|
726
|
+
path: ["autoEmail"],
|
|
727
|
+
message: "autoEmail requires autoPost to be true — invoices must be posted before they can be emailed",
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}),
|
|
731
|
+
// Contact Write Tools
|
|
732
|
+
createContact: z.object({
|
|
733
|
+
accountId: z
|
|
734
|
+
.string()
|
|
735
|
+
.min(1)
|
|
736
|
+
.describe("Zuora account UUID to create the contact for"),
|
|
737
|
+
firstName: z
|
|
738
|
+
.string()
|
|
739
|
+
.min(1)
|
|
740
|
+
.max(100)
|
|
741
|
+
.describe("Contact first name"),
|
|
742
|
+
lastName: z
|
|
743
|
+
.string()
|
|
744
|
+
.min(1)
|
|
745
|
+
.max(100)
|
|
746
|
+
.describe("Contact last name"),
|
|
747
|
+
workEmail: z
|
|
748
|
+
.string()
|
|
749
|
+
.email("Must be a valid email address")
|
|
750
|
+
.optional()
|
|
751
|
+
.describe("Work email address"),
|
|
752
|
+
personalEmail: z
|
|
753
|
+
.string()
|
|
754
|
+
.email("Must be a valid email address")
|
|
755
|
+
.optional()
|
|
756
|
+
.describe("Personal email address"),
|
|
757
|
+
workPhone: z.string().max(40).optional().describe("Work phone number"),
|
|
758
|
+
homePhone: z.string().max(40).optional().describe("Home phone number"),
|
|
759
|
+
mobilePhone: z.string().max(40).optional().describe("Mobile phone number"),
|
|
760
|
+
fax: z.string().max(40).optional().describe("Fax number"),
|
|
761
|
+
address1: z.string().max(255).optional().describe("Street address line 1"),
|
|
762
|
+
address2: z.string().max(255).optional().describe("Street address line 2"),
|
|
763
|
+
city: z.string().max(100).optional().describe("City"),
|
|
764
|
+
state: z.string().max(100).optional().describe("State or province"),
|
|
765
|
+
country: z.string().max(100).optional().describe("Country (ISO 3166 name or code)"),
|
|
766
|
+
postalCode: z.string().max(20).optional().describe("Postal/ZIP code"),
|
|
767
|
+
county: z.string().max(100).optional().describe("County"),
|
|
768
|
+
taxRegion: z.string().max(100).optional().describe("Tax region"),
|
|
769
|
+
description: z.string().max(2000).optional().describe("Contact description/notes"),
|
|
770
|
+
nickname: z.string().max(100).optional().describe("Contact nickname"),
|
|
771
|
+
idempotencyKey: z
|
|
772
|
+
.string()
|
|
773
|
+
.uuid("Idempotency key must be a valid UUID")
|
|
774
|
+
.describe("UUID to prevent duplicate contact creation on retries. " +
|
|
775
|
+
"Generate a new UUID v4 for each distinct contact intent."),
|
|
776
|
+
}),
|
|
777
|
+
updateContact: z.object({
|
|
778
|
+
contactId: z
|
|
779
|
+
.string()
|
|
780
|
+
.min(1)
|
|
781
|
+
.describe("Contact ID to update"),
|
|
782
|
+
firstName: z.string().min(1).max(100).optional().describe("Updated first name"),
|
|
783
|
+
lastName: z.string().min(1).max(100).optional().describe("Updated last name"),
|
|
784
|
+
workEmail: z.string().email("Must be a valid email address").optional().describe("Updated work email"),
|
|
785
|
+
personalEmail: z.string().email("Must be a valid email address").optional().describe("Updated personal email"),
|
|
786
|
+
workPhone: z.string().max(40).optional().describe("Updated work phone"),
|
|
787
|
+
homePhone: z.string().max(40).optional().describe("Updated home phone"),
|
|
788
|
+
mobilePhone: z.string().max(40).optional().describe("Updated mobile phone"),
|
|
789
|
+
fax: z.string().max(40).optional().describe("Updated fax number"),
|
|
790
|
+
address1: z.string().max(255).optional().describe("Updated street address line 1"),
|
|
791
|
+
address2: z.string().max(255).optional().describe("Updated street address line 2"),
|
|
792
|
+
city: z.string().max(100).optional().describe("Updated city"),
|
|
793
|
+
state: z.string().max(100).optional().describe("Updated state or province"),
|
|
794
|
+
country: z.string().max(100).optional().describe("Updated country"),
|
|
795
|
+
postalCode: z.string().max(20).optional().describe("Updated postal/ZIP code"),
|
|
796
|
+
county: z.string().max(100).optional().describe("Updated county"),
|
|
797
|
+
taxRegion: z.string().max(100).optional().describe("Updated tax region"),
|
|
798
|
+
description: z.string().max(2000).optional().describe("Updated description/notes"),
|
|
799
|
+
nickname: z.string().max(100).optional().describe("Updated nickname"),
|
|
800
|
+
}).superRefine((data, ctx) => {
|
|
801
|
+
const { contactId: _, ...updateFields } = data;
|
|
802
|
+
const hasUpdate = Object.values(updateFields).some((v) => v !== undefined);
|
|
803
|
+
if (!hasUpdate) {
|
|
804
|
+
ctx.addIssue({
|
|
805
|
+
code: z.ZodIssueCode.custom,
|
|
806
|
+
path: [],
|
|
807
|
+
message: "At least one field to update must be provided",
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}),
|
|
600
811
|
// ==================== Composite Tool Schemas ====================
|
|
601
812
|
findAccountsByProduct: z.object({
|
|
602
813
|
productName: z
|
|
@@ -648,6 +859,27 @@ export const schemas = {
|
|
|
648
859
|
.string()
|
|
649
860
|
.min(1)
|
|
650
861
|
.describe("Account number or ID (e.g., A00012345)"),
|
|
862
|
+
maxInvoices: z
|
|
863
|
+
.number()
|
|
864
|
+
.int()
|
|
865
|
+
.min(1)
|
|
866
|
+
.optional()
|
|
867
|
+
.default(20)
|
|
868
|
+
.describe("Maximum number of recent invoices to fetch (default: 20)."),
|
|
869
|
+
maxPayments: z
|
|
870
|
+
.number()
|
|
871
|
+
.int()
|
|
872
|
+
.min(1)
|
|
873
|
+
.optional()
|
|
874
|
+
.default(10)
|
|
875
|
+
.describe("Maximum number of recent payments to fetch (default: 10)."),
|
|
876
|
+
maxSubscriptions: z
|
|
877
|
+
.number()
|
|
878
|
+
.int()
|
|
879
|
+
.min(1)
|
|
880
|
+
.optional()
|
|
881
|
+
.default(10)
|
|
882
|
+
.describe("Maximum number of active subscriptions to include (default: 10)."),
|
|
651
883
|
}),
|
|
652
884
|
getRevenueByProduct: z.object({
|
|
653
885
|
limit: z
|
|
@@ -824,13 +1056,23 @@ export class ToolHandlers {
|
|
|
824
1056
|
}
|
|
825
1057
|
async listSubscriptions(input) {
|
|
826
1058
|
try {
|
|
827
|
-
const { accountKey } = schemas.listSubscriptions.parse(input);
|
|
1059
|
+
const { accountKey, maxResults } = schemas.listSubscriptions.parse(input);
|
|
828
1060
|
const result = await this.client.listSubscriptionsByAccount(accountKey);
|
|
829
|
-
const
|
|
1061
|
+
const allSubs = result.subscriptions ?? [];
|
|
1062
|
+
const subscriptions = maxResults !== undefined
|
|
1063
|
+
? allSubs.slice(0, maxResults)
|
|
1064
|
+
: allSubs;
|
|
830
1065
|
return {
|
|
831
1066
|
success: true,
|
|
832
|
-
message:
|
|
833
|
-
|
|
1067
|
+
message: maxResults !== undefined && allSubs.length > maxResults
|
|
1068
|
+
? `Returning ${subscriptions.length} of ${allSubs.length} subscription(s) for account ${accountKey}`
|
|
1069
|
+
: `Found ${subscriptions.length} subscription(s) for account ${accountKey}`,
|
|
1070
|
+
data: {
|
|
1071
|
+
subscriptions,
|
|
1072
|
+
...(maxResults !== undefined && allSubs.length > maxResults
|
|
1073
|
+
? { totalAvailable: allSubs.length }
|
|
1074
|
+
: {}),
|
|
1075
|
+
},
|
|
834
1076
|
};
|
|
835
1077
|
}
|
|
836
1078
|
catch (error) {
|
|
@@ -884,7 +1126,22 @@ export class ToolHandlers {
|
|
|
884
1126
|
// --- ZOQL Query Handlers ---
|
|
885
1127
|
async executeZoqlQuery(input) {
|
|
886
1128
|
try {
|
|
887
|
-
const { zoqlQuery } = schemas.executeZoqlQuery.parse(input);
|
|
1129
|
+
const { zoqlQuery, maxResults } = schemas.executeZoqlQuery.parse(input);
|
|
1130
|
+
if (maxResults !== undefined) {
|
|
1131
|
+
// Auto-paginate and truncate to exact count requested
|
|
1132
|
+
const allRecords = await this.client.queryAll(zoqlQuery, maxResults);
|
|
1133
|
+
const records = allRecords.slice(0, maxResults);
|
|
1134
|
+
return {
|
|
1135
|
+
success: true,
|
|
1136
|
+
message: `Returning ${records.length} record(s)`,
|
|
1137
|
+
data: {
|
|
1138
|
+
size: records.length,
|
|
1139
|
+
records,
|
|
1140
|
+
done: true,
|
|
1141
|
+
},
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
// No maxResults — return first page as before
|
|
888
1145
|
const result = await this.client.executeQuery(zoqlQuery);
|
|
889
1146
|
const moreAvailable = result.queryLocator
|
|
890
1147
|
? " (more available via continue_zoql_query)"
|
|
@@ -904,8 +1161,25 @@ export class ToolHandlers {
|
|
|
904
1161
|
}
|
|
905
1162
|
async continueZoqlQuery(input) {
|
|
906
1163
|
try {
|
|
907
|
-
const { queryLocator } = schemas.continueZoqlQuery.parse(input);
|
|
1164
|
+
const { queryLocator, maxResults } = schemas.continueZoqlQuery.parse(input);
|
|
908
1165
|
const result = await this.client.queryMore(queryLocator);
|
|
1166
|
+
if (maxResults !== undefined) {
|
|
1167
|
+
const allRecords = result.records ?? [];
|
|
1168
|
+
const records = allRecords.slice(0, maxResults);
|
|
1169
|
+
const moreAvailable = (allRecords.length > maxResults || result.queryLocator)
|
|
1170
|
+
? " (more available)"
|
|
1171
|
+
: "";
|
|
1172
|
+
return {
|
|
1173
|
+
success: true,
|
|
1174
|
+
message: `Continuation returned ${records.length} record(s)${moreAvailable}`,
|
|
1175
|
+
data: {
|
|
1176
|
+
size: records.length,
|
|
1177
|
+
records,
|
|
1178
|
+
queryLocator: result.queryLocator,
|
|
1179
|
+
done: result.done,
|
|
1180
|
+
},
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
909
1183
|
const moreAvailable = result.queryLocator
|
|
910
1184
|
? " (more available)"
|
|
911
1185
|
: "";
|
|
@@ -998,7 +1272,21 @@ export class ToolHandlers {
|
|
|
998
1272
|
}
|
|
999
1273
|
async searchAccounts(input) {
|
|
1000
1274
|
try {
|
|
1001
|
-
const { field, value, operator } = schemas.searchAccounts.parse(input);
|
|
1275
|
+
const { field, value, operator, maxResults } = schemas.searchAccounts.parse(input);
|
|
1276
|
+
if (maxResults !== undefined) {
|
|
1277
|
+
// Use queryAll for auto-pagination, then truncate to exact count
|
|
1278
|
+
const allRecords = await this.client.searchAccountsWithLimit(field, value, operator, maxResults);
|
|
1279
|
+
return {
|
|
1280
|
+
success: true,
|
|
1281
|
+
message: `Returning ${allRecords.length} account(s) matching search criteria`,
|
|
1282
|
+
data: {
|
|
1283
|
+
size: allRecords.length,
|
|
1284
|
+
records: allRecords,
|
|
1285
|
+
done: true,
|
|
1286
|
+
},
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
// No maxResults — return full result as before
|
|
1002
1290
|
const result = await this.client.searchAccounts(field, value, operator);
|
|
1003
1291
|
const count = result.records?.length ?? 0;
|
|
1004
1292
|
return {
|
|
@@ -1032,6 +1320,115 @@ export class ToolHandlers {
|
|
|
1032
1320
|
};
|
|
1033
1321
|
}
|
|
1034
1322
|
}
|
|
1323
|
+
// --- User Management Handlers ---
|
|
1324
|
+
async listUsers(input) {
|
|
1325
|
+
try {
|
|
1326
|
+
const { startIndex, count, filter } = schemas.listUsers.parse(input);
|
|
1327
|
+
const result = await this.client.listUsers(startIndex, count, filter);
|
|
1328
|
+
const userCount = result.Resources?.length ?? 0;
|
|
1329
|
+
const filterNote = filter ? ` (filter: ${filter})` : "";
|
|
1330
|
+
return {
|
|
1331
|
+
success: true,
|
|
1332
|
+
message: `Found ${userCount} user(s) of ${result.totalResults} total${filterNote}`,
|
|
1333
|
+
data: result,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
catch (error) {
|
|
1337
|
+
return {
|
|
1338
|
+
success: false,
|
|
1339
|
+
message: `Failed to list users: ${error instanceof Error ? error.message : String(error)}`,
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async getUser(input) {
|
|
1344
|
+
try {
|
|
1345
|
+
const { userId } = schemas.getUser.parse(input);
|
|
1346
|
+
const user = await this.client.getUser(userId);
|
|
1347
|
+
return {
|
|
1348
|
+
success: true,
|
|
1349
|
+
message: `Retrieved user ${user.userName ?? userId}`,
|
|
1350
|
+
data: user,
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
catch (error) {
|
|
1354
|
+
return {
|
|
1355
|
+
success: false,
|
|
1356
|
+
message: `Failed to get user: ${error instanceof Error ? error.message : String(error)}`,
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
// --- Bill Run Handlers ---
|
|
1361
|
+
async getBillRun(input) {
|
|
1362
|
+
try {
|
|
1363
|
+
const { billRunId } = schemas.getBillRun.parse(input);
|
|
1364
|
+
const billRun = await this.client.getBillRun(billRunId);
|
|
1365
|
+
return {
|
|
1366
|
+
success: true,
|
|
1367
|
+
message: `Retrieved bill run ${billRun.billRunNumber ?? billRunId} (status: ${billRun.status})`,
|
|
1368
|
+
data: billRun,
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
catch (error) {
|
|
1372
|
+
return {
|
|
1373
|
+
success: false,
|
|
1374
|
+
message: `Failed to get bill run: ${error instanceof Error ? error.message : String(error)}`,
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
async listBillRuns(input) {
|
|
1379
|
+
try {
|
|
1380
|
+
const { page, pageSize } = schemas.listBillRuns.parse(input);
|
|
1381
|
+
const result = await this.client.listBillRuns(page, pageSize);
|
|
1382
|
+
const count = result.billRuns?.length ?? 0;
|
|
1383
|
+
return {
|
|
1384
|
+
success: true,
|
|
1385
|
+
message: `Found ${count} bill run(s)`,
|
|
1386
|
+
data: result,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
catch (error) {
|
|
1390
|
+
return {
|
|
1391
|
+
success: false,
|
|
1392
|
+
message: `Failed to list bill runs: ${error instanceof Error ? error.message : String(error)}`,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
// --- Contact Handlers ---
|
|
1397
|
+
async getContact(input) {
|
|
1398
|
+
try {
|
|
1399
|
+
const { contactId } = schemas.getContact.parse(input);
|
|
1400
|
+
const contact = await this.client.getContact(contactId);
|
|
1401
|
+
return {
|
|
1402
|
+
success: true,
|
|
1403
|
+
message: `Retrieved contact ${contact.firstName} ${contact.lastName} (${contactId})`,
|
|
1404
|
+
data: contact,
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
catch (error) {
|
|
1408
|
+
return {
|
|
1409
|
+
success: false,
|
|
1410
|
+
message: `Failed to get contact: ${error instanceof Error ? error.message : String(error)}`,
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
// --- Describe API Handlers ---
|
|
1415
|
+
async describeObject(input) {
|
|
1416
|
+
try {
|
|
1417
|
+
const { objectType } = schemas.describeObject.parse(input);
|
|
1418
|
+
const result = await this.client.describeObject(objectType);
|
|
1419
|
+
return {
|
|
1420
|
+
success: true,
|
|
1421
|
+
message: `Described ${result.objectName}: ${result.fieldCount} field(s)`,
|
|
1422
|
+
data: result,
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
catch (error) {
|
|
1426
|
+
return {
|
|
1427
|
+
success: false,
|
|
1428
|
+
message: `Failed to describe object: ${error instanceof Error ? error.message : String(error)}`,
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1035
1432
|
// --- Phase 3: Write Operation Handlers ---
|
|
1036
1433
|
async createPayment(input) {
|
|
1037
1434
|
try {
|
|
@@ -1059,7 +1456,7 @@ export class ToolHandlers {
|
|
|
1059
1456
|
});
|
|
1060
1457
|
return {
|
|
1061
1458
|
success: true,
|
|
1062
|
-
message: `Applied payment ${result.
|
|
1459
|
+
message: `Applied payment ${paymentId} (applied: ${result.appliedAmount}) successfully`,
|
|
1063
1460
|
data: result,
|
|
1064
1461
|
};
|
|
1065
1462
|
}
|
|
@@ -1207,6 +1604,59 @@ export class ToolHandlers {
|
|
|
1207
1604
|
};
|
|
1208
1605
|
}
|
|
1209
1606
|
}
|
|
1607
|
+
// --- Bill Run Write Handlers ---
|
|
1608
|
+
async createBillRun(input) {
|
|
1609
|
+
try {
|
|
1610
|
+
const { idempotencyKey, ...billRunData } = schemas.createBillRun.parse(input);
|
|
1611
|
+
const result = await this.client.createBillRun(billRunData, idempotencyKey);
|
|
1612
|
+
return {
|
|
1613
|
+
success: true,
|
|
1614
|
+
message: `Created bill run ${result.billRunNumber} (status: ${result.status})`,
|
|
1615
|
+
data: result,
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
catch (error) {
|
|
1619
|
+
return {
|
|
1620
|
+
success: false,
|
|
1621
|
+
message: `Failed to create bill run: ${error instanceof Error ? error.message : String(error)}`,
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
// --- Contact Write Handlers ---
|
|
1626
|
+
async createContact(input) {
|
|
1627
|
+
try {
|
|
1628
|
+
const { idempotencyKey, ...contactData } = schemas.createContact.parse(input);
|
|
1629
|
+
const result = await this.client.createContact(contactData, idempotencyKey);
|
|
1630
|
+
return {
|
|
1631
|
+
success: true,
|
|
1632
|
+
message: `Created contact (id: ${result.id}) for account ${contactData.accountId}`,
|
|
1633
|
+
data: result,
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
catch (error) {
|
|
1637
|
+
return {
|
|
1638
|
+
success: false,
|
|
1639
|
+
message: `Failed to create contact: ${error instanceof Error ? error.message : String(error)}`,
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
async updateContact(input) {
|
|
1644
|
+
try {
|
|
1645
|
+
const { contactId, ...updateData } = schemas.updateContact.parse(input);
|
|
1646
|
+
const result = await this.client.updateContact(contactId, updateData);
|
|
1647
|
+
return {
|
|
1648
|
+
success: true,
|
|
1649
|
+
message: `Updated contact ${contactId} successfully`,
|
|
1650
|
+
data: result,
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
catch (error) {
|
|
1654
|
+
return {
|
|
1655
|
+
success: false,
|
|
1656
|
+
message: `Failed to update contact: ${error instanceof Error ? error.message : String(error)}`,
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1210
1660
|
// --- Composite Tool Handlers ---
|
|
1211
1661
|
async findAccountsByProduct(input) {
|
|
1212
1662
|
try {
|
|
@@ -1319,7 +1769,7 @@ export class ToolHandlers {
|
|
|
1319
1769
|
// Query overdue invoices: Posted, has balance, past due date
|
|
1320
1770
|
const invoices = await queryAll(this.client, `SELECT Id, InvoiceNumber, AccountId, InvoiceDate, DueDate, Amount, Balance ` +
|
|
1321
1771
|
`FROM Invoice ` +
|
|
1322
|
-
`WHERE Status = 'Posted' AND Balance > ${minBalance} AND DueDate < '${today}'`, limit);
|
|
1772
|
+
`WHERE Status = 'Posted' AND Balance > ${Number(minBalance)} AND DueDate < '${today}'`, limit);
|
|
1323
1773
|
if (invoices.length === 0) {
|
|
1324
1774
|
return {
|
|
1325
1775
|
success: true,
|
|
@@ -1416,7 +1866,7 @@ export class ToolHandlers {
|
|
|
1416
1866
|
accountName: getString(acct ?? {}, "Name") ?? "",
|
|
1417
1867
|
status: getString(sub, "Status") ?? "",
|
|
1418
1868
|
termEndDate: termEnd,
|
|
1419
|
-
autoRenew: sub
|
|
1869
|
+
autoRenew: getString(sub, "AutoRenew") === "true",
|
|
1420
1870
|
contractedMrr: getNumber(sub, "ContractedMrr") ?? 0,
|
|
1421
1871
|
ratePlanNames: rpNamesBySub.get(subId) ?? [],
|
|
1422
1872
|
daysUntilExpiry: daysBetween(today, termEnd),
|
|
@@ -1445,17 +1895,17 @@ export class ToolHandlers {
|
|
|
1445
1895
|
}
|
|
1446
1896
|
async getAccountBillingOverview(input) {
|
|
1447
1897
|
try {
|
|
1448
|
-
const { accountKey } = schemas.getAccountBillingOverview.parse(input);
|
|
1898
|
+
const { accountKey, maxInvoices, maxPayments, maxSubscriptions } = schemas.getAccountBillingOverview.parse(input);
|
|
1449
1899
|
// Step 1: Get account via REST API
|
|
1450
1900
|
const account = await this.client.getAccount(accountKey);
|
|
1451
1901
|
// Step 2: Get recent invoices via ZOQL
|
|
1452
1902
|
const invoices = await queryAll(this.client, `SELECT Id, InvoiceNumber, InvoiceDate, DueDate, Amount, Balance, Status ` +
|
|
1453
1903
|
`FROM Invoice WHERE AccountId = '${escapeZoql(account.id)}' ` +
|
|
1454
|
-
`ORDER BY InvoiceDate DESC`,
|
|
1904
|
+
`ORDER BY InvoiceDate DESC`, maxInvoices);
|
|
1455
1905
|
// Step 3: Get recent payments via ZOQL
|
|
1456
1906
|
const payments = await queryAll(this.client, `SELECT Id, PaymentNumber, Amount, EffectiveDate, Status ` +
|
|
1457
1907
|
`FROM Payment WHERE AccountId = '${escapeZoql(account.id)}' ` +
|
|
1458
|
-
`ORDER BY EffectiveDate DESC`,
|
|
1908
|
+
`ORDER BY EffectiveDate DESC`, maxPayments);
|
|
1459
1909
|
// Step 4: Get subscriptions via REST API
|
|
1460
1910
|
const subResult = await this.client.listSubscriptionsByAccount(accountKey);
|
|
1461
1911
|
const subscriptions = subResult.subscriptions ?? [];
|
|
@@ -1493,7 +1943,7 @@ export class ToolHandlers {
|
|
|
1493
1943
|
totalOutstanding,
|
|
1494
1944
|
overdueCount,
|
|
1495
1945
|
overdueAmount,
|
|
1496
|
-
recentInvoices: invoices.slice(0,
|
|
1946
|
+
recentInvoices: invoices.slice(0, maxInvoices).map((inv) => ({
|
|
1497
1947
|
invoiceNumber: getString(inv, "InvoiceNumber") ?? "",
|
|
1498
1948
|
invoiceDate: getString(inv, "InvoiceDate") ?? "",
|
|
1499
1949
|
dueDate: getString(inv, "DueDate") ?? "",
|
|
@@ -1503,7 +1953,7 @@ export class ToolHandlers {
|
|
|
1503
1953
|
})),
|
|
1504
1954
|
},
|
|
1505
1955
|
paymentSummary: {
|
|
1506
|
-
recentPayments: payments.slice(0,
|
|
1956
|
+
recentPayments: payments.slice(0, maxPayments).map((p) => ({
|
|
1507
1957
|
paymentNumber: getString(p, "PaymentNumber") ?? "",
|
|
1508
1958
|
amount: getNumber(p, "Amount") ?? 0,
|
|
1509
1959
|
effectiveDate: getString(p, "EffectiveDate") ?? "",
|
|
@@ -1513,7 +1963,7 @@ export class ToolHandlers {
|
|
|
1513
1963
|
subscriptionSummary: {
|
|
1514
1964
|
activeCount: activeSubscriptions.length,
|
|
1515
1965
|
totalMrr,
|
|
1516
|
-
subscriptions: activeSubscriptions.slice(0,
|
|
1966
|
+
subscriptions: activeSubscriptions.slice(0, maxSubscriptions).map((s) => ({
|
|
1517
1967
|
subscriptionNumber: s.subscriptionNumber,
|
|
1518
1968
|
status: s.status,
|
|
1519
1969
|
termEndDate: s.termEndDate,
|
|
@@ -1539,7 +1989,7 @@ export class ToolHandlers {
|
|
|
1539
1989
|
try {
|
|
1540
1990
|
const { limit } = schemas.getRevenueByProduct.parse(input);
|
|
1541
1991
|
// Step 1: Get all active subscriptions with MRR
|
|
1542
|
-
const subscriptions = await queryAll(this.client, `SELECT Id, AccountId, SubscriptionNumber, ContractedMrr, Status ` +
|
|
1992
|
+
const subscriptions = await queryAll(this.client, `SELECT Id, AccountId, SubscriptionNumber, ContractedMrr, TotalContractedValue, Status ` +
|
|
1543
1993
|
`FROM Subscription WHERE Status = 'Active'`, 5000);
|
|
1544
1994
|
if (subscriptions.length === 0) {
|
|
1545
1995
|
return {
|
|
@@ -1588,6 +2038,7 @@ export class ToolHandlers {
|
|
|
1588
2038
|
};
|
|
1589
2039
|
existing.subscriptionCount++;
|
|
1590
2040
|
existing.totalMrr += mrr;
|
|
2041
|
+
existing.totalTcv += getNumber(sub, "TotalContractedValue") ?? 0;
|
|
1591
2042
|
existing.subscriptions.push({
|
|
1592
2043
|
subscriptionNumber: getString(sub, "SubscriptionNumber") ?? "",
|
|
1593
2044
|
accountNumber: getString(acct ?? {}, "AccountNumber") ?? "",
|
|
@@ -1693,9 +2144,9 @@ export class ToolHandlers {
|
|
|
1693
2144
|
try {
|
|
1694
2145
|
const { daysBack, limit } = schemas.getRecentlyCancelledSubscriptions.parse(input);
|
|
1695
2146
|
const sinceDate = subtractDays(new Date(), daysBack);
|
|
1696
|
-
const subscriptions = await queryAll(this.client, `SELECT Id, SubscriptionNumber, AccountId, Status, TermEndDate,
|
|
2147
|
+
const subscriptions = await queryAll(this.client, `SELECT Id, SubscriptionNumber, AccountId, Status, TermEndDate, CancelledDate, ContractedMrr ` +
|
|
1697
2148
|
`FROM Subscription ` +
|
|
1698
|
-
`WHERE Status = 'Cancelled' AND
|
|
2149
|
+
`WHERE Status = 'Cancelled' AND CancelledDate >= '${sinceDate}'`, limit * 2);
|
|
1699
2150
|
if (subscriptions.length === 0) {
|
|
1700
2151
|
return {
|
|
1701
2152
|
success: true,
|
|
@@ -1730,7 +2181,7 @@ export class ToolHandlers {
|
|
|
1730
2181
|
accountNumber: getString(acct ?? {}, "AccountNumber") ?? "",
|
|
1731
2182
|
accountName: getString(acct ?? {}, "Name") ?? "",
|
|
1732
2183
|
status: getString(sub, "Status") ?? "",
|
|
1733
|
-
cancelledDate: getString(sub, "
|
|
2184
|
+
cancelledDate: getString(sub, "CancelledDate") ?? "",
|
|
1734
2185
|
termEndDate: getString(sub, "TermEndDate") ?? "",
|
|
1735
2186
|
contractedMrr: getNumber(sub, "ContractedMrr") ?? 0,
|
|
1736
2187
|
ratePlanNames: rpNamesBySub.get(subId) ?? [],
|
|
@@ -1845,7 +2296,7 @@ export class ToolHandlers {
|
|
|
1845
2296
|
// Signal 2: Expiring subscriptions (next 30 days, not auto-renewing)
|
|
1846
2297
|
const expiringSubscriptions = await queryAll(this.client, `SELECT Id, AccountId, ContractedMrr, TermEndDate ` +
|
|
1847
2298
|
`FROM Subscription ` +
|
|
1848
|
-
`WHERE Status = 'Active' AND TermEndDate >= '${today}' AND TermEndDate <= '${thirtyDaysAhead}' AND AutoRenew = false`, 2000);
|
|
2299
|
+
`WHERE Status = 'Active' AND TermEndDate >= '${today}' AND TermEndDate <= '${thirtyDaysAhead}' AND AutoRenew = 'false'`, 2000);
|
|
1849
2300
|
// Signal 3: Recent payment failures
|
|
1850
2301
|
const failedPayments = await queryAll(this.client, `SELECT Id, AccountId ` +
|
|
1851
2302
|
`FROM Payment ` +
|
|
@@ -1997,7 +2448,7 @@ export class ToolHandlers {
|
|
|
1997
2448
|
}
|
|
1998
2449
|
const rpcIds = collectIds(ratePlanCharges, "Id");
|
|
1999
2450
|
// Step 3: Find InvoiceItems by RatePlanChargeId
|
|
2000
|
-
const invoiceItems = await queryWithBatchedIds(this.client, "Id, InvoiceId, ChargeAmount, ChargeName, ServiceStartDate, ServiceEndDate, SubscriptionId", "InvoiceItem", "RatePlanChargeId", rpcIds, undefined, limit);
|
|
2451
|
+
const invoiceItems = await queryWithBatchedIds(this.client, "Id, InvoiceId, ChargeAmount, ChargeName, ServiceStartDate, ServiceEndDate, SubscriptionId, RatePlanChargeId", "InvoiceItem", "RatePlanChargeId", rpcIds, undefined, limit);
|
|
2001
2452
|
if (invoiceItems.length === 0) {
|
|
2002
2453
|
return {
|
|
2003
2454
|
success: true,
|
|
@@ -2100,7 +2551,8 @@ export const toolRegistrations = [
|
|
|
2100
2551
|
{
|
|
2101
2552
|
name: "list_subscriptions",
|
|
2102
2553
|
description: "List all subscriptions for a Zuora account. " +
|
|
2103
|
-
"Returns subscription names, statuses, and term dates."
|
|
2554
|
+
"Returns subscription names, statuses, and term dates. " +
|
|
2555
|
+
"Use the maxResults parameter when the user requests a specific number of subscriptions.",
|
|
2104
2556
|
inputSchema: schemas.listSubscriptions,
|
|
2105
2557
|
invoke: (handlers, args) => handlers.listSubscriptions(args),
|
|
2106
2558
|
},
|
|
@@ -2126,8 +2578,9 @@ export const toolRegistrations = [
|
|
|
2126
2578
|
"Syntax: SELECT field1, field2 FROM ObjectName WHERE condition. " +
|
|
2127
2579
|
"Key objects: Account, Invoice, Payment, Subscription, RatePlan, " +
|
|
2128
2580
|
"RatePlanCharge, Product, ProductRatePlan, Contact. " +
|
|
2129
|
-
"Limitations: No JOINs supported.
|
|
2130
|
-
"
|
|
2581
|
+
"Limitations: No JOINs supported. ZOQL has no LIMIT clause — use the maxResults " +
|
|
2582
|
+
"parameter to control how many records are returned (e.g., maxResults=10). " +
|
|
2583
|
+
"Use continue_zoql_query with queryLocator for manual pagination when maxResults is not set. " +
|
|
2131
2584
|
"Example: SELECT Id, AccountNumber, Name, Balance FROM Account WHERE Status = 'Active'",
|
|
2132
2585
|
inputSchema: schemas.executeZoqlQuery,
|
|
2133
2586
|
invoke: (handlers, args) => handlers.executeZoqlQuery(args),
|
|
@@ -2135,7 +2588,8 @@ export const toolRegistrations = [
|
|
|
2135
2588
|
{
|
|
2136
2589
|
name: "continue_zoql_query",
|
|
2137
2590
|
description: "Continue a previous ZOQL query that returned a queryLocator " +
|
|
2138
|
-
"(indicates more records available). Returns the next batch of up to 2000 records."
|
|
2591
|
+
"(indicates more records available). Returns the next batch of up to 2000 records. " +
|
|
2592
|
+
"Use the maxResults parameter to return fewer records from the continuation page.",
|
|
2139
2593
|
inputSchema: schemas.continueZoqlQuery,
|
|
2140
2594
|
invoke: (handlers, args) => handlers.continueZoqlQuery(args),
|
|
2141
2595
|
},
|
|
@@ -2179,6 +2633,8 @@ export const toolRegistrations = [
|
|
|
2179
2633
|
"AccountNumber, Status, Currency, or Balance. Use LIKE operator with % " +
|
|
2180
2634
|
"wildcard for partial name matching (e.g., field='Name', operator='LIKE', " +
|
|
2181
2635
|
"value='Acme%'). Returns Id, AccountNumber, Name, Status, Balance, Currency. " +
|
|
2636
|
+
"Use the maxResults parameter when the user requests a specific number of records " +
|
|
2637
|
+
"(e.g., 'get 10 active accounts' → maxResults=10). " +
|
|
2182
2638
|
"For additional fields, use execute_zoql_query directly.",
|
|
2183
2639
|
inputSchema: schemas.searchAccounts,
|
|
2184
2640
|
invoke: (handlers, args) => handlers.searchAccounts(args),
|
|
@@ -2191,6 +2647,59 @@ export const toolRegistrations = [
|
|
|
2191
2647
|
inputSchema: schemas.listUsage,
|
|
2192
2648
|
invoke: (handlers, args) => handlers.listUsage(args),
|
|
2193
2649
|
},
|
|
2650
|
+
// User Management
|
|
2651
|
+
{
|
|
2652
|
+
name: "list_users",
|
|
2653
|
+
description: "List Zuora platform users with optional SCIM filter (e.g., status eq 'Active'). " +
|
|
2654
|
+
"Uses REST API, not ZOQL — the User object is not ZOQL-queryable. " +
|
|
2655
|
+
"Supports pagination via startIndex/count. " +
|
|
2656
|
+
"Returns user names, emails, statuses, roles, and last login times.",
|
|
2657
|
+
inputSchema: schemas.listUsers,
|
|
2658
|
+
invoke: (handlers, args) => handlers.listUsers(args),
|
|
2659
|
+
},
|
|
2660
|
+
{
|
|
2661
|
+
name: "get_user",
|
|
2662
|
+
description: "Get details of a specific Zuora platform user by their user ID (UUID). " +
|
|
2663
|
+
"Returns user name, email, status, role, profile, and last login time.",
|
|
2664
|
+
inputSchema: schemas.getUser,
|
|
2665
|
+
invoke: (handlers, args) => handlers.getUser(args),
|
|
2666
|
+
},
|
|
2667
|
+
// Bill Run Tools
|
|
2668
|
+
{
|
|
2669
|
+
name: "get_bill_run",
|
|
2670
|
+
description: "Get details and status of a Zuora bill run by bill run ID. " +
|
|
2671
|
+
"Returns bill run number, status (Pending/Processing/Completed/Error/Canceled/Posted), " +
|
|
2672
|
+
"target date, invoice date, and auto-post/email settings.",
|
|
2673
|
+
inputSchema: schemas.getBillRun,
|
|
2674
|
+
invoke: (handlers, args) => handlers.getBillRun(args),
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
name: "list_bill_runs",
|
|
2678
|
+
description: "List bill runs in Zuora with pagination. " +
|
|
2679
|
+
"Returns bill run numbers, statuses, target dates, and settings.",
|
|
2680
|
+
inputSchema: schemas.listBillRuns,
|
|
2681
|
+
invoke: (handlers, args) => handlers.listBillRuns(args),
|
|
2682
|
+
},
|
|
2683
|
+
// Contact Tools
|
|
2684
|
+
{
|
|
2685
|
+
name: "get_contact",
|
|
2686
|
+
description: "Get full details of a Zuora contact by contact ID. " +
|
|
2687
|
+
"Returns name, emails, phone numbers, address, tax region, and timestamps. " +
|
|
2688
|
+
"Use ZOQL to find contact IDs: SELECT Id, FirstName, LastName FROM Contact WHERE AccountId = '...'",
|
|
2689
|
+
inputSchema: schemas.getContact,
|
|
2690
|
+
invoke: (handlers, args) => handlers.getContact(args),
|
|
2691
|
+
},
|
|
2692
|
+
// Describe API
|
|
2693
|
+
{
|
|
2694
|
+
name: "describe_object",
|
|
2695
|
+
description: "Get field metadata for any Zuora object type. Returns all fields with names, types, " +
|
|
2696
|
+
"and properties (selectable, createable, updateable, filterable, required). " +
|
|
2697
|
+
"Essential for building correct ZOQL queries — use this to discover available fields " +
|
|
2698
|
+
"before writing SELECT statements. Object type must be PascalCase (e.g., Account, " +
|
|
2699
|
+
"Invoice, Subscription, Payment, RatePlan, RatePlanCharge, BillRun, Contact).",
|
|
2700
|
+
inputSchema: schemas.describeObject,
|
|
2701
|
+
invoke: (handlers, args) => handlers.describeObject(args),
|
|
2702
|
+
},
|
|
2194
2703
|
// Phase 3: Write Operations (require confirmation before calling)
|
|
2195
2704
|
// Payment Creation (HIGH risk)
|
|
2196
2705
|
{
|
|
@@ -2312,6 +2821,38 @@ export const toolRegistrations = [
|
|
|
2312
2821
|
inputSchema: schemas.createRefund,
|
|
2313
2822
|
invoke: (handlers, args) => handlers.createRefund(args),
|
|
2314
2823
|
},
|
|
2824
|
+
// Bill Run Creation (MEDIUM risk)
|
|
2825
|
+
{
|
|
2826
|
+
name: "create_bill_run",
|
|
2827
|
+
description: "FINANCIAL WRITE OPERATION (MEDIUM RISK): Create a bill run to generate invoices for a billing cycle. " +
|
|
2828
|
+
"Bill runs process all eligible charges through the target date and generate draft invoices. " +
|
|
2829
|
+
"Confirm the target date with the user before calling. " +
|
|
2830
|
+
"Set autoPost=true to automatically post generated invoices. " +
|
|
2831
|
+
"Set autoEmail=true (requires autoPost) to email invoices to customers. " +
|
|
2832
|
+
"Requires a UUID idempotency key to prevent duplicate bill runs. " +
|
|
2833
|
+
"The bill run starts in Pending status and progresses through Processing to Completed.",
|
|
2834
|
+
inputSchema: schemas.createBillRun,
|
|
2835
|
+
invoke: (handlers, args) => handlers.createBillRun(args),
|
|
2836
|
+
},
|
|
2837
|
+
// Contact Creation (LOW risk)
|
|
2838
|
+
{
|
|
2839
|
+
name: "create_contact",
|
|
2840
|
+
description: "WRITE OPERATION (LOW RISK): Create a new contact for a Zuora account. " +
|
|
2841
|
+
"Requires account ID, first name, and last name. " +
|
|
2842
|
+
"Contacts can be used as bill-to or sold-to addresses via update_account. " +
|
|
2843
|
+
"Requires a UUID idempotency key to prevent duplicate contacts.",
|
|
2844
|
+
inputSchema: schemas.createContact,
|
|
2845
|
+
invoke: (handlers, args) => handlers.createContact(args),
|
|
2846
|
+
},
|
|
2847
|
+
// Contact Update (LOW risk)
|
|
2848
|
+
{
|
|
2849
|
+
name: "update_contact",
|
|
2850
|
+
description: "WRITE OPERATION (LOW RISK): Update an existing Zuora contact's information. " +
|
|
2851
|
+
"Only include fields that need to change. " +
|
|
2852
|
+
"ALWAYS use get_contact to review current details before updating.",
|
|
2853
|
+
inputSchema: schemas.updateContact,
|
|
2854
|
+
invoke: (handlers, args) => handlers.updateContact(args),
|
|
2855
|
+
},
|
|
2315
2856
|
// ==================== Composite Tools (Read-Only) ====================
|
|
2316
2857
|
{
|
|
2317
2858
|
name: "find_accounts_by_product",
|