@carrierllc/mcp 0.0.1 → 0.1.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/tools.js ADDED
@@ -0,0 +1,702 @@
1
+ /**
2
+ * Carrier MCP stdio — OCS tool registrations (43 tools).
3
+ *
4
+ * Stdio variant: no scope enforcement, no audit hook, no Sentry.
5
+ * Single-user — token held in OcsClient class, not passed per-call.
6
+ *
7
+ * All 16 schema fixes from PR #10 are preserved:
8
+ * Fix #1: getSimProviderStatus — ICCID → simId lookup → bare integer
9
+ * Fix #2: hlrGetBitrate — ICCID → IMSI lookup → { imsi }
10
+ * Fix #3: hlrSetBitrate — ICCID → IMSI + bitrate → limit rename
11
+ * Fix #4: subscriberUsageOverPeriod — { subscriber: { iccid }, period }
12
+ * Fix #5: subscriberNetworkEventsOverPeriod — same nested shape
13
+ * Fix #6: modifySubscriberStatus — { subscriber, newStatus }
14
+ * Fix #7: modifySubscriberBalance — { subscriber, amount|setBalance }
15
+ * Fix #8: changeSimStatus — ICCID → simId + newStatus
16
+ * Fix #9: modifySubscriberContactInfo — name/mail/phone field names
17
+ * Fix #10: setSubscriberTrafficRestrictions — typed booleans
18
+ * Fix #11: sendMtSms — ICCID→IMSI, message→text, sender→senderId
19
+ * Fix #12: listSponsor — bare integer (resellerId)
20
+ * Fix #13: listSteeringList — bare integer (resellerId)
21
+ * Fix #14: getCustomerTariff — bare integer + listTariffRule key
22
+ * Fix #15: listDetailedLocationZone — bare integer (resellerId)
23
+ * Fix #16: modifySubscriberSteeringList — { subscriber, steeringListId }
24
+ */
25
+ import { z } from "zod";
26
+ import { OcsApiError } from "./client.js";
27
+ const DRY_RUN_FIELD = {
28
+ dry_run: z
29
+ .boolean()
30
+ .optional()
31
+ .describe("If true, do not call OCS — return the would-be request for confirmation"),
32
+ };
33
+ export const DESTRUCTIVE_TOOLS = new Set([
34
+ "modify_account_balance",
35
+ "modify_subscriber_balance",
36
+ "modify_subscriber_status",
37
+ "change_sim_status",
38
+ "modify_subscriber_contact_info",
39
+ "set_subscriber_traffic_restrictions",
40
+ "modify_subscriber_steering_list",
41
+ "move_subscriber_range_to_account",
42
+ "hlr_set_bitrate",
43
+ "assign_package",
44
+ "assign_recurring_package",
45
+ "modify_package_limits",
46
+ "modify_package_expiry",
47
+ "modify_package_status",
48
+ "stop_resume_recurring_package",
49
+ "delete_subscriber_package",
50
+ "clean_all_packages",
51
+ "modify_template_core",
52
+ "modify_template_recurring",
53
+ "modify_template_throttling",
54
+ "create_package_template",
55
+ "create_location_zone",
56
+ "send_sms",
57
+ ]);
58
+ async function ocsCall(client, method, params = {}) {
59
+ const result = await client.call(method, params);
60
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
61
+ }
62
+ async function resolveSubscriberByIccid(client, iccid, cache) {
63
+ const hit = cache.get(iccid);
64
+ if (hit)
65
+ return hit;
66
+ const record = await client.call("getSingleSubscriber", { iccid });
67
+ cache.set(iccid, record);
68
+ return record;
69
+ }
70
+ async function getDefaultResellerId(client) {
71
+ const info = await client.call("getResellerInfo", {});
72
+ const id = info?.id;
73
+ if (typeof id !== "number") {
74
+ throw new Error("Could not determine resellerId from getResellerInfo");
75
+ }
76
+ return id;
77
+ }
78
+ function makeHandler(toolName, _client, fn) {
79
+ return async (args) => {
80
+ if (args.dry_run === true && DESTRUCTIVE_TOOLS.has(toolName)) {
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: `[dry_run=true] Would execute '${toolName}' with args: ${JSON.stringify(args)}. No changes made.`,
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ try {
91
+ return await fn(args);
92
+ }
93
+ catch (err) {
94
+ const message = err instanceof Error ? err.message : String(err);
95
+ const prefix = err instanceof OcsApiError ? `OCS ${err.code}: ` : "";
96
+ return {
97
+ isError: true,
98
+ content: [{ type: "text", text: `Error: ${prefix}${message}` }],
99
+ };
100
+ }
101
+ };
102
+ }
103
+ export function registerAllTools(server, client) {
104
+ // =========================================================================
105
+ // 1. RESELLER TOOLS
106
+ // =========================================================================
107
+ server.registerTool("list_reseller_accounts", {
108
+ title: "List Reseller Accounts",
109
+ description: "List all accounts across all resellers, or filter by a specific reseller ID. Returns account names, balances, and package-only status.",
110
+ inputSchema: {
111
+ resellerId: z.number().optional().describe("Filter to a specific reseller by ID"),
112
+ },
113
+ annotations: { readOnlyHint: true },
114
+ }, makeHandler("list_reseller_accounts", client, async ({ resellerId }) => {
115
+ const params = {};
116
+ if (resellerId !== undefined)
117
+ params.resellerId = resellerId;
118
+ return ocsCall(client, "listResellerAccount", params);
119
+ }));
120
+ server.registerTool("modify_account_balance", {
121
+ title: "Modify Account Balance",
122
+ description: "Adjust or set the balance of a reseller account. 'adapt' adds/subtracts from current balance; 'set' replaces it.",
123
+ inputSchema: {
124
+ accountId: z.number().describe("The account ID to modify"),
125
+ amount: z.number().describe("Amount to add (adapt) or set to (set)"),
126
+ mode: z.enum(["adapt", "set"]).describe("'adapt' adds/subtracts, 'set' replaces the balance"),
127
+ ...DRY_RUN_FIELD,
128
+ },
129
+ annotations: { destructiveHint: true },
130
+ }, makeHandler("modify_account_balance", client, async ({ accountId, amount, mode }) => {
131
+ const params = { accountId };
132
+ if (mode === "adapt")
133
+ params.adaptBalance = amount;
134
+ else
135
+ params.setBalance = amount;
136
+ return ocsCall(client, "modifyAccountBalance", params);
137
+ }));
138
+ server.registerTool("get_reseller_info", {
139
+ title: "Get Reseller Info",
140
+ description: "Retrieve detailed information about a reseller. If no ID provided, returns info for the reseller owning the API token.",
141
+ inputSchema: {
142
+ resellerId: z.number().optional().describe("Reseller ID (omit for token owner)"),
143
+ },
144
+ annotations: { readOnlyHint: true },
145
+ }, makeHandler("get_reseller_info", client, async ({ resellerId }) => {
146
+ const params = {};
147
+ if (resellerId !== undefined)
148
+ params.resellerId = resellerId;
149
+ return ocsCall(client, "getResellerInfo", params);
150
+ }));
151
+ server.registerTool("esim_status_per_account", {
152
+ title: "eSIM Status Per Account",
153
+ description: "Get eSIM status breakdown per account — active, suspended, inventory counts.",
154
+ inputSchema: {
155
+ accountId: z.number().optional().describe("Filter to a specific account"),
156
+ },
157
+ annotations: { readOnlyHint: true },
158
+ }, makeHandler("esim_status_per_account", client, async ({ accountId }) => {
159
+ const params = {};
160
+ if (accountId !== undefined)
161
+ params.accountId = accountId;
162
+ return ocsCall(client, "esimStatusPerAccount", params);
163
+ }));
164
+ // Fix #12: bare integer
165
+ server.registerTool("list_sponsors", {
166
+ title: "List Sponsors",
167
+ description: "List all sponsor networks available to this reseller. Pass resellerId explicitly or omit to use the token owner's reseller.",
168
+ inputSchema: {
169
+ resellerId: z.number().optional().describe("Reseller ID (omit to use token owner's reseller)"),
170
+ },
171
+ annotations: { readOnlyHint: true },
172
+ }, makeHandler("list_sponsors", client, async ({ resellerId }) => {
173
+ const id = resellerId ?? (await getDefaultResellerId(client));
174
+ return ocsCall(client, "listSponsor", id);
175
+ }));
176
+ // Fix #13: bare integer
177
+ server.registerTool("list_steering_lists", {
178
+ title: "List Steering Lists",
179
+ description: "List all network steering lists for this reseller. Steering lists control which networks eSIMs prefer to connect to.",
180
+ inputSchema: {
181
+ resellerId: z.number().optional().describe("Reseller ID (omit to use token owner's reseller)"),
182
+ },
183
+ annotations: { readOnlyHint: true },
184
+ }, makeHandler("list_steering_lists", client, async ({ resellerId }) => {
185
+ const id = resellerId ?? (await getDefaultResellerId(client));
186
+ return ocsCall(client, "listSteeringList", id);
187
+ }));
188
+ // =========================================================================
189
+ // 2. SUBSCRIBER TOOLS
190
+ // =========================================================================
191
+ server.registerTool("get_subscriber", {
192
+ title: "Get Single Subscriber",
193
+ description: "Retrieve full details for a single subscriber by ICCID or MSISDN.",
194
+ inputSchema: {
195
+ iccid: z.string().optional().describe("The ICCID of the subscriber"),
196
+ msisdn: z.string().optional().describe("The MSISDN (phone number) of the subscriber"),
197
+ },
198
+ annotations: { readOnlyHint: true },
199
+ }, makeHandler("get_subscriber", client, async ({ iccid, msisdn }) => {
200
+ const params = {};
201
+ if (iccid)
202
+ params.iccid = iccid;
203
+ if (msisdn)
204
+ params.msisdn = msisdn;
205
+ return ocsCall(client, "getSingleSubscriber", params);
206
+ }));
207
+ server.registerTool("list_subscribers", {
208
+ title: "List Subscribers",
209
+ description: "List subscribers with optional filters. Supports pagination.",
210
+ inputSchema: {
211
+ accountId: z.number().optional().describe("Filter by account ID"),
212
+ status: z.string().optional().describe("Filter by status"),
213
+ offset: z.number().optional().describe("Pagination offset"),
214
+ limit: z.number().optional().describe("Max results to return"),
215
+ },
216
+ annotations: { readOnlyHint: true },
217
+ }, makeHandler("list_subscribers", client, async (args) => {
218
+ const params = {};
219
+ if (args.accountId !== undefined)
220
+ params.accountId = args.accountId;
221
+ if (args.status)
222
+ params.status = args.status;
223
+ if (args.offset !== undefined)
224
+ params.offset = args.offset;
225
+ if (args.limit !== undefined)
226
+ params.limit = args.limit;
227
+ return ocsCall(client, "listSubscriber", params);
228
+ }));
229
+ // Fix #7: { subscriber, amount } or { subscriber, setBalance }
230
+ server.registerTool("modify_subscriber_balance", {
231
+ title: "Modify Subscriber Balance",
232
+ description: "Adjust or set the balance of an individual subscriber. 'adapt' adds/subtracts; 'set' replaces.",
233
+ inputSchema: {
234
+ iccid: z.string().describe("The subscriber ICCID"),
235
+ amount: z.number().describe("Amount to add (adapt) or set to (set)"),
236
+ mode: z.enum(["adapt", "set"]).describe("'adapt' adds/subtracts, 'set' replaces"),
237
+ ...DRY_RUN_FIELD,
238
+ },
239
+ annotations: { destructiveHint: true },
240
+ }, makeHandler("modify_subscriber_balance", client, async ({ iccid, amount, mode }) => {
241
+ const params = { subscriber: iccid };
242
+ if (mode === "adapt")
243
+ params.amount = amount;
244
+ else
245
+ params.setBalance = amount;
246
+ return ocsCall(client, "modifySubscriberBalance", params);
247
+ }));
248
+ // Fix #6: { subscriber, newStatus }
249
+ server.registerTool("modify_subscriber_status", {
250
+ title: "Modify Subscriber Status",
251
+ description: "Change a subscriber's status (e.g., activate, suspend, terminate).",
252
+ inputSchema: {
253
+ iccid: z.string().describe("The subscriber ICCID"),
254
+ status: z.string().describe("New status value"),
255
+ ...DRY_RUN_FIELD,
256
+ },
257
+ annotations: { destructiveHint: true },
258
+ }, makeHandler("modify_subscriber_status", client, async ({ iccid, status }) => ocsCall(client, "modifySubscriberStatus", { subscriber: iccid, newStatus: status })));
259
+ // Fix #8: ICCID → simId → { simId, newStatus }
260
+ server.registerTool("change_sim_status", {
261
+ title: "Change SIM Status",
262
+ description: "Change the SIM card status at the provider level (enable/disable/delete). Internally resolves ICCID to simId before calling OCS.",
263
+ inputSchema: {
264
+ iccid: z.string().describe("The subscriber ICCID"),
265
+ simStatus: z.string().describe("New SIM status (e.g. ENABLED, DISABLED, DELETED)"),
266
+ ...DRY_RUN_FIELD,
267
+ },
268
+ annotations: { destructiveHint: true },
269
+ }, makeHandler("change_sim_status", client, async ({ iccid, simStatus }) => {
270
+ const cache = new Map();
271
+ const sub = await resolveSubscriberByIccid(client, iccid, cache);
272
+ const simId = sub.simId ?? sub.sim_id ?? sub.id;
273
+ if (simId === undefined) {
274
+ return {
275
+ isError: true,
276
+ content: [{ type: "text", text: `Error: Could not resolve simId for ICCID ${iccid}` }],
277
+ };
278
+ }
279
+ return ocsCall(client, "changeSimStatus", { simId: Number(simId), newStatus: simStatus });
280
+ }));
281
+ // Fix #1: ICCID → simId → bare integer
282
+ server.registerTool("get_sim_provider_status", {
283
+ title: "Get SIM Provider Status",
284
+ description: "Check the SIM provider-level status for a subscriber. Accepts ICCID; resolves to the numeric simId required by OCS internally.",
285
+ inputSchema: { iccid: z.string().describe("The subscriber ICCID") },
286
+ annotations: { readOnlyHint: true },
287
+ }, makeHandler("get_sim_provider_status", client, async ({ iccid }) => {
288
+ const cache = new Map();
289
+ const sub = await resolveSubscriberByIccid(client, iccid, cache);
290
+ const simId = sub.simId ?? sub.sim_id ?? sub.id;
291
+ if (simId === undefined) {
292
+ return {
293
+ isError: true,
294
+ content: [{ type: "text", text: `Error: Could not resolve simId for ICCID ${iccid}` }],
295
+ };
296
+ }
297
+ return ocsCall(client, "getSimProviderStatus", Number(simId));
298
+ }));
299
+ server.registerTool("get_subscriber_location", {
300
+ title: "Get Subscriber Location",
301
+ description: "Get the last known location of a subscriber.",
302
+ inputSchema: { iccid: z.string().describe("The subscriber ICCID") },
303
+ annotations: { readOnlyHint: true },
304
+ }, makeHandler("get_subscriber_location", client, async ({ iccid }) => ocsCall(client, "getSubscriberLocation", { iccid })));
305
+ // Fix #9: name/mail/phone field names
306
+ server.registerTool("modify_subscriber_contact_info", {
307
+ title: "Modify Subscriber Contact Info",
308
+ description: "Update contact information (name, email, etc.) for a subscriber.",
309
+ inputSchema: {
310
+ iccid: z.string().describe("The subscriber ICCID"),
311
+ firstName: z.string().optional().describe("First name"),
312
+ lastName: z.string().optional().describe("Last name"),
313
+ company: z.string().optional().describe("Company name"),
314
+ email: z.string().optional().describe("Email address"),
315
+ phoneNumber: z.string().optional().describe("Phone number"),
316
+ ...DRY_RUN_FIELD,
317
+ },
318
+ annotations: { destructiveHint: true },
319
+ }, makeHandler("modify_subscriber_contact_info", client, async ({ iccid, firstName, lastName, company, email, phoneNumber }) => {
320
+ const params = { subscriber: iccid };
321
+ const nameParts = [firstName, lastName].filter(Boolean);
322
+ if (nameParts.length > 0)
323
+ params.name = nameParts.join(" ");
324
+ if (company !== undefined)
325
+ params.company = company;
326
+ if (phoneNumber !== undefined)
327
+ params.phone = phoneNumber;
328
+ if (email !== undefined)
329
+ params.mail = email;
330
+ return ocsCall(client, "modifySubscriberContactInfo", params);
331
+ }));
332
+ // Fix #10: typed booleans
333
+ server.registerTool("set_subscriber_traffic_restrictions", {
334
+ title: "Set Traffic Restrictions",
335
+ description: "Configure traffic restrictions for a subscriber — control allowed/blocked traffic types (data, voice MO/MT, SMS MO).",
336
+ inputSchema: {
337
+ iccid: z.string().describe("The subscriber ICCID"),
338
+ mtcAllowed: z.boolean().optional().describe("Allow mobile-terminated calls"),
339
+ smsMoAllowed: z.boolean().optional().describe("Allow SMS mobile-originated"),
340
+ dataAllowed: z.boolean().optional().describe("Allow data traffic"),
341
+ mocAllowed: z.boolean().optional().describe("Allow mobile-originated calls"),
342
+ ...DRY_RUN_FIELD,
343
+ },
344
+ annotations: { destructiveHint: true },
345
+ }, makeHandler("set_subscriber_traffic_restrictions", client, async ({ iccid, mtcAllowed, smsMoAllowed, dataAllowed, mocAllowed }) => {
346
+ const params = { subscriber: iccid };
347
+ if (mtcAllowed !== undefined)
348
+ params.mtcAllowed = mtcAllowed;
349
+ if (smsMoAllowed !== undefined)
350
+ params.smsMoAllowed = smsMoAllowed;
351
+ if (dataAllowed !== undefined)
352
+ params.dataAllowed = dataAllowed;
353
+ if (mocAllowed !== undefined)
354
+ params.mocAllowed = mocAllowed;
355
+ return ocsCall(client, "setSubscriberTrafficRestrictions", params);
356
+ }));
357
+ // Fix #16: { subscriber, steeringListId }
358
+ server.registerTool("modify_subscriber_steering_list", {
359
+ title: "Modify Subscriber Steering List",
360
+ description: "Change the network steering list assigned to a subscriber.",
361
+ inputSchema: {
362
+ iccid: z.string().describe("The subscriber ICCID"),
363
+ steeringListId: z.number().describe("The steering list ID to assign"),
364
+ ...DRY_RUN_FIELD,
365
+ },
366
+ annotations: { destructiveHint: true },
367
+ }, makeHandler("modify_subscriber_steering_list", client, async ({ iccid, steeringListId }) => ocsCall(client, "modifySubscriberSteeringList", { subscriber: iccid, steeringListId })));
368
+ server.registerTool("move_subscriber_range_to_account", {
369
+ title: "Move Subscribers to Account",
370
+ description: "Move a range of subscribers to a different account.",
371
+ inputSchema: {
372
+ iccidFrom: z.string().describe("Start ICCID of range"),
373
+ iccidTo: z.string().describe("End ICCID of range"),
374
+ accountId: z.number().describe("Target account ID"),
375
+ ...DRY_RUN_FIELD,
376
+ },
377
+ annotations: { destructiveHint: true },
378
+ }, makeHandler("move_subscriber_range_to_account", client, async ({ iccidFrom, iccidTo, accountId }) => ocsCall(client, "moveSubscriberRangeToAccount", { iccidFrom, iccidTo, accountId })));
379
+ // Fix #3: ICCID → IMSI + bitrate → limit
380
+ server.registerTool("hlr_set_bitrate", {
381
+ title: "Set HLR Bitrate",
382
+ description: "Set the maximum data bitrate for a subscriber at the HLR level. Accepts ICCID; resolves to IMSI required by OCS internally.",
383
+ inputSchema: {
384
+ iccid: z.string().describe("The subscriber ICCID"),
385
+ bitrate: z.number().describe("Max bitrate in bps"),
386
+ ...DRY_RUN_FIELD,
387
+ },
388
+ annotations: { destructiveHint: true },
389
+ }, makeHandler("hlr_set_bitrate", client, async ({ iccid, bitrate }) => {
390
+ const cache = new Map();
391
+ const sub = await resolveSubscriberByIccid(client, iccid, cache);
392
+ const imsi = sub.imsi;
393
+ if (typeof imsi !== "string" || imsi.length === 0) {
394
+ return {
395
+ isError: true,
396
+ content: [{ type: "text", text: `Error: Could not resolve IMSI for ICCID ${iccid}` }],
397
+ };
398
+ }
399
+ return ocsCall(client, "hlrSetBitrate", { imsi, limit: bitrate });
400
+ }));
401
+ // Fix #2: ICCID → IMSI
402
+ server.registerTool("hlr_get_bitrate", {
403
+ title: "Get HLR Bitrate",
404
+ description: "Get the current HLR bitrate setting for a subscriber. Accepts ICCID; resolves to IMSI required by OCS internally.",
405
+ inputSchema: { iccid: z.string().describe("The subscriber ICCID") },
406
+ annotations: { readOnlyHint: true },
407
+ }, makeHandler("hlr_get_bitrate", client, async ({ iccid }) => {
408
+ const cache = new Map();
409
+ const sub = await resolveSubscriberByIccid(client, iccid, cache);
410
+ const imsi = sub.imsi;
411
+ if (typeof imsi !== "string" || imsi.length === 0) {
412
+ return {
413
+ isError: true,
414
+ content: [{ type: "text", text: `Error: Could not resolve IMSI for ICCID ${iccid}` }],
415
+ };
416
+ }
417
+ return ocsCall(client, "hlrGetBitrate", { imsi });
418
+ }));
419
+ // =========================================================================
420
+ // 3. PACKAGE TOOLS
421
+ // =========================================================================
422
+ server.registerTool("list_subscriber_packages", {
423
+ title: "List Subscriber Packages",
424
+ description: "List all prepaid packages assigned to a subscriber. Shows allowances, usage, expiry, status.",
425
+ inputSchema: { iccid: z.string().describe("The subscriber ICCID") },
426
+ annotations: { readOnlyHint: true },
427
+ }, makeHandler("list_subscriber_packages", client, async ({ iccid }) => ocsCall(client, "listSubscriberPrepaidPackages", { iccid })));
428
+ server.registerTool("assign_package", {
429
+ title: "Assign Package to Subscriber",
430
+ description: "Assign a prepaid package template to a subscriber.",
431
+ inputSchema: {
432
+ iccid: z.string().describe("The subscriber ICCID"),
433
+ packageTemplateId: z.number().describe("The package template ID to assign"),
434
+ ...DRY_RUN_FIELD,
435
+ },
436
+ annotations: { destructiveHint: true },
437
+ }, makeHandler("assign_package", client, async ({ iccid, packageTemplateId }) => ocsCall(client, "affectPackageToSubscriber", { subscriber: iccid, packageTemplateId })));
438
+ server.registerTool("assign_recurring_package", {
439
+ title: "Assign Recurring Package",
440
+ description: "Assign a recurring (auto-renewing) prepaid package to a subscriber.",
441
+ inputSchema: {
442
+ iccid: z.string().describe("The subscriber ICCID"),
443
+ packageTemplateId: z.number().describe("The package template ID"),
444
+ ...DRY_RUN_FIELD,
445
+ },
446
+ annotations: { destructiveHint: true },
447
+ }, makeHandler("assign_recurring_package", client, async ({ iccid, packageTemplateId }) => ocsCall(client, "affectRecurringPackageToSubscriber", { subscriber: iccid, packageTemplateId })));
448
+ server.registerTool("modify_package_limits", {
449
+ title: "Modify Package Limits",
450
+ description: "Change the data/voice/SMS limits on an active subscriber package.",
451
+ inputSchema: {
452
+ iccid: z.string().describe("The subscriber ICCID"),
453
+ packageId: z.number().describe("The active package ID"),
454
+ limits: z.string().describe("New limits as JSON string"),
455
+ ...DRY_RUN_FIELD,
456
+ },
457
+ annotations: { destructiveHint: true },
458
+ }, makeHandler("modify_package_limits", client, async ({ iccid, packageId, limits }) => ocsCall(client, "modifySubscriberPrepaidPackageLimits", {
459
+ iccid,
460
+ packageId,
461
+ ...JSON.parse(limits),
462
+ })));
463
+ server.registerTool("modify_package_expiry", {
464
+ title: "Modify Package Expiry Date",
465
+ description: "Change the expiration date of an active subscriber package.",
466
+ inputSchema: {
467
+ iccid: z.string().describe("The subscriber ICCID"),
468
+ packageId: z.number().describe("The active package ID"),
469
+ expirationDate: z.string().describe("New expiry date (ISO 8601 format)"),
470
+ ...DRY_RUN_FIELD,
471
+ },
472
+ annotations: { destructiveHint: true },
473
+ }, makeHandler("modify_package_expiry", client, async ({ iccid, packageId, expirationDate }) => ocsCall(client, "modifySubscriberPrepaidPackageExpDate", { iccid, packageId, expirationDate })));
474
+ server.registerTool("modify_package_status", {
475
+ title: "Modify Package Status",
476
+ description: "Activate or deactivate a subscriber's prepaid package.",
477
+ inputSchema: {
478
+ iccid: z.string().describe("The subscriber ICCID"),
479
+ packageId: z.number().describe("The active package ID"),
480
+ status: z.string().describe("New package status"),
481
+ ...DRY_RUN_FIELD,
482
+ },
483
+ annotations: { destructiveHint: true },
484
+ }, makeHandler("modify_package_status", client, async ({ iccid, packageId, status }) => ocsCall(client, "modifySubscriberPrepaidPackageStatus", { iccid, packageId, status })));
485
+ server.registerTool("stop_resume_recurring_package", {
486
+ title: "Stop/Resume Recurring Package",
487
+ description: "Stop or resume a recurring package's auto-renewal.",
488
+ inputSchema: {
489
+ iccid: z.string().describe("The subscriber ICCID"),
490
+ packageId: z.number().describe("The recurring package ID"),
491
+ action: z.enum(["stop", "resume"]).describe("Whether to stop or resume"),
492
+ ...DRY_RUN_FIELD,
493
+ },
494
+ annotations: { destructiveHint: true },
495
+ }, makeHandler("stop_resume_recurring_package", client, async ({ iccid, packageId, action }) => ocsCall(client, "stopResumeSubsRecurringPackage", { iccid, packageId, action })));
496
+ server.registerTool("delete_subscriber_package", {
497
+ title: "Delete Subscriber Package",
498
+ description: "Remove a package from a subscriber entirely.",
499
+ inputSchema: {
500
+ iccid: z.string().describe("The subscriber ICCID"),
501
+ packageId: z.number().describe("The package ID to delete"),
502
+ ...DRY_RUN_FIELD,
503
+ },
504
+ annotations: { destructiveHint: true },
505
+ }, makeHandler("delete_subscriber_package", client, async ({ iccid, packageId }) => ocsCall(client, "deleteSubscriberPackage", { iccid, packageId })));
506
+ server.registerTool("clean_all_packages", {
507
+ title: "Clean All Subscriber Packages",
508
+ description: "Remove ALL packages from a subscriber. Use with caution.",
509
+ inputSchema: {
510
+ iccid: z.string().describe("The subscriber ICCID"),
511
+ ...DRY_RUN_FIELD,
512
+ },
513
+ annotations: { destructiveHint: true },
514
+ }, makeHandler("clean_all_packages", client, async ({ iccid }) => ocsCall(client, "cleanSubscriberAllPackages", { iccid })));
515
+ // =========================================================================
516
+ // 4. PACKAGE TEMPLATE TOOLS
517
+ // =========================================================================
518
+ server.registerTool("list_package_templates", {
519
+ title: "List Package Templates",
520
+ description: "List all available prepaid package templates.",
521
+ inputSchema: {
522
+ accountId: z.number().optional().describe("Filter templates by account ID"),
523
+ },
524
+ annotations: { readOnlyHint: true },
525
+ }, makeHandler("list_package_templates", client, async ({ accountId }) => {
526
+ const params = {};
527
+ if (accountId !== undefined)
528
+ params.accountId = accountId;
529
+ return ocsCall(client, "listPrepaidPackageTemplate", params);
530
+ }));
531
+ server.registerTool("create_package_template", {
532
+ title: "Create Package Template",
533
+ description: "Create a new prepaid package template. Define allowances, pricing, zones, and validity.",
534
+ inputSchema: {
535
+ template: z.string().describe("Full template configuration as JSON string"),
536
+ ...DRY_RUN_FIELD,
537
+ },
538
+ annotations: { destructiveHint: true },
539
+ }, makeHandler("create_package_template", client, async ({ template }) => ocsCall(client, "createPrepaidPackageTemplate", JSON.parse(template))));
540
+ server.registerTool("modify_template_core", {
541
+ title: "Modify Template Core Settings",
542
+ description: "Modify core settings of a package template (name, limits, pricing).",
543
+ inputSchema: {
544
+ templateId: z.number().describe("The template ID"),
545
+ changes: z.string().describe("Core fields to modify as JSON string"),
546
+ ...DRY_RUN_FIELD,
547
+ },
548
+ annotations: { destructiveHint: true },
549
+ }, makeHandler("modify_template_core", client, async ({ templateId, changes }) => ocsCall(client, "modifyPPTCore", { templateId, ...JSON.parse(changes) })));
550
+ server.registerTool("modify_template_recurring", {
551
+ title: "Modify Template Recurring Settings",
552
+ description: "Modify recurring/renewal settings of a package template.",
553
+ inputSchema: {
554
+ templateId: z.number().describe("The template ID"),
555
+ changes: z.string().describe("Recurring fields to modify as JSON string"),
556
+ ...DRY_RUN_FIELD,
557
+ },
558
+ annotations: { destructiveHint: true },
559
+ }, makeHandler("modify_template_recurring", client, async ({ templateId, changes }) => ocsCall(client, "modifyPPTRecurring", { templateId, ...JSON.parse(changes) })));
560
+ server.registerTool("modify_template_throttling", {
561
+ title: "Modify Template Throttling",
562
+ description: "Modify throttling/speed settings of a package template.",
563
+ inputSchema: {
564
+ templateId: z.number().describe("The template ID"),
565
+ changes: z.string().describe("Throttling fields to modify as JSON string"),
566
+ ...DRY_RUN_FIELD,
567
+ },
568
+ annotations: { destructiveHint: true },
569
+ }, makeHandler("modify_template_throttling", client, async ({ templateId, changes }) => ocsCall(client, "modifyPPTThrottling", { templateId, ...JSON.parse(changes) })));
570
+ server.registerTool("list_location_zones", {
571
+ title: "List Location Zone Elements",
572
+ description: "List countries/networks in a location zone.",
573
+ inputSchema: {
574
+ locationZoneId: z.number().optional().describe("Filter by zone ID"),
575
+ },
576
+ annotations: { readOnlyHint: true },
577
+ }, makeHandler("list_location_zones", client, async ({ locationZoneId }) => {
578
+ const params = {};
579
+ if (locationZoneId !== undefined)
580
+ params.locationZoneId = locationZoneId;
581
+ return ocsCall(client, "listLocationZoneElement", params);
582
+ }));
583
+ // Fix #15: bare integer
584
+ server.registerTool("list_detailed_location_zones", {
585
+ title: "List Detailed Location Zones",
586
+ description: "Get detailed location zone definitions with included countries/networks. Pass resellerId explicitly or omit to use the token owner's reseller.",
587
+ inputSchema: {
588
+ resellerId: z.number().optional().describe("Reseller ID (omit to use token owner's reseller)"),
589
+ },
590
+ annotations: { readOnlyHint: true },
591
+ }, makeHandler("list_detailed_location_zones", client, async ({ resellerId }) => {
592
+ const id = resellerId ?? (await getDefaultResellerId(client));
593
+ return ocsCall(client, "listDetailedLocationZone", id);
594
+ }));
595
+ server.registerTool("list_destination_prefixes", {
596
+ title: "List Destination List Prefixes",
597
+ description: "List phone number prefixes in destination lists (for voice/SMS routing).",
598
+ inputSchema: {
599
+ destinationListId: z.number().optional().describe("Filter by destination list ID"),
600
+ },
601
+ annotations: { readOnlyHint: true },
602
+ }, makeHandler("list_destination_prefixes", client, async ({ destinationListId }) => {
603
+ const params = {};
604
+ if (destinationListId !== undefined)
605
+ params.destinationListId = destinationListId;
606
+ return ocsCall(client, "listDestinationListPrefix", params);
607
+ }));
608
+ server.registerTool("create_location_zone", {
609
+ title: "Create Location Zone",
610
+ description: "Create a new location zone for use in package templates.",
611
+ inputSchema: {
612
+ zone: z.string().describe("Zone configuration as JSON string"),
613
+ ...DRY_RUN_FIELD,
614
+ },
615
+ annotations: { destructiveHint: true },
616
+ }, makeHandler("create_location_zone", client, async ({ zone }) => ocsCall(client, "createLocationZone", JSON.parse(zone))));
617
+ // =========================================================================
618
+ // 5. STATISTICS TOOLS
619
+ // =========================================================================
620
+ // Fix #4: { subscriber: { iccid }, period: { start, end } }
621
+ server.registerTool("subscriber_usage", {
622
+ title: "Subscriber Usage Over Period",
623
+ description: "Get daily data/voice/SMS usage for a subscriber over a date range (max 7 days).",
624
+ inputSchema: {
625
+ iccid: z.string().describe("The subscriber ICCID"),
626
+ startDate: z.string().describe("Start date (YYYY-MM-DD, inclusive)"),
627
+ endDate: z.string().describe("End date (YYYY-MM-DD, inclusive, max 7 days from start)"),
628
+ },
629
+ annotations: { readOnlyHint: true },
630
+ }, makeHandler("subscriber_usage", client, async ({ iccid, startDate, endDate }) => ocsCall(client, "subscriberUsageOverPeriod", {
631
+ subscriber: { iccid },
632
+ period: { start: startDate, end: endDate },
633
+ })));
634
+ // Fix #5: same nested shape
635
+ server.registerTool("subscriber_network_events", {
636
+ title: "Subscriber Network Events",
637
+ description: "Get network events (attach, detach, location updates) for a subscriber over a period (max 7 days).",
638
+ inputSchema: {
639
+ iccid: z.string().describe("The subscriber ICCID"),
640
+ startDate: z.string().describe("Start date (YYYY-MM-DD, inclusive)"),
641
+ endDate: z.string().describe("End date (YYYY-MM-DD, inclusive)"),
642
+ },
643
+ annotations: { readOnlyHint: true },
644
+ }, makeHandler("subscriber_network_events", client, async ({ iccid, startDate, endDate }) => ocsCall(client, "subscriberNetworkEventsOverPeriod", {
645
+ subscriber: { iccid },
646
+ period: { start: startDate, end: endDate },
647
+ })));
648
+ server.registerTool("subscriber_active_period", {
649
+ title: "Get Subscriber Active Period",
650
+ description: "Get the active period (first to last use) for a subscriber.",
651
+ inputSchema: { iccid: z.string().describe("The subscriber ICCID") },
652
+ annotations: { readOnlyHint: true },
653
+ }, makeHandler("subscriber_active_period", client, async ({ iccid }) => ocsCall(client, "getSubscriberActivePeriod", { iccid })));
654
+ // =========================================================================
655
+ // 6. MISC TOOLS
656
+ // =========================================================================
657
+ // Fix #14: bare integer, listTariffRule response key
658
+ server.registerTool("get_tariff", {
659
+ title: "Get Customer Tariff",
660
+ description: "Retrieve the tariff/pricing table for this reseller (per-country, per-traffic-type rates). Pass resellerId explicitly or omit to use the token owner's reseller.",
661
+ inputSchema: {
662
+ resellerId: z.number().optional().describe("Reseller ID (omit to use token owner's reseller)"),
663
+ },
664
+ annotations: { readOnlyHint: true },
665
+ }, makeHandler("get_tariff", client, async ({ resellerId }) => {
666
+ const id = resellerId ?? (await getDefaultResellerId(client));
667
+ return ocsCall(client, "getCustomerTariff", id);
668
+ }));
669
+ // Fix #11: ICCID→IMSI, message→text, sender→senderId
670
+ server.registerTool("send_sms", {
671
+ title: "Send MT SMS",
672
+ description: "Send a mobile-terminated SMS to a subscriber. Accepts ICCID; resolves to IMSI required by OCS internally.",
673
+ inputSchema: {
674
+ iccid: z.string().describe("The target subscriber ICCID"),
675
+ msisdn: z.string().describe("The target MSISDN"),
676
+ message: z.string().describe("SMS text content"),
677
+ sender: z.string().optional().describe("Sender ID/number (senderId in OCS)"),
678
+ ...DRY_RUN_FIELD,
679
+ },
680
+ annotations: { destructiveHint: true },
681
+ }, makeHandler("send_sms", client, async ({ iccid, msisdn, message, sender }) => {
682
+ const cache = new Map();
683
+ const sub = await resolveSubscriberByIccid(client, iccid, cache);
684
+ const imsi = sub.imsi;
685
+ if (typeof imsi !== "string" || imsi.length === 0) {
686
+ return {
687
+ isError: true,
688
+ content: [{ type: "text", text: `Error: Could not resolve IMSI for ICCID ${iccid}` }],
689
+ };
690
+ }
691
+ const params = { imsi, msisdn, text: message };
692
+ if (sender)
693
+ params.senderId = sender;
694
+ return ocsCall(client, "sendMtSms", params);
695
+ }));
696
+ server.registerTool("list_network_profiles", {
697
+ title: "List Network Profiles",
698
+ description: "List all available network profiles.",
699
+ annotations: { readOnlyHint: true },
700
+ }, makeHandler("list_network_profiles", client, async (_args) => ocsCall(client, "listNetworkProfile")));
701
+ }
702
+ //# sourceMappingURL=tools.js.map