@carrierllc/mcp 0.13.38 → 0.13.40

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 CHANGED
@@ -80,7 +80,7 @@ import {
80
80
  verifyStorefront,
81
81
  withOcsListSummary,
82
82
  withTemplateResellerId
83
- } from "./chunk-D37X6FBD.js";
83
+ } from "./chunk-WVXII7ZR.js";
84
84
  import "./chunk-SHKKVIIA.js";
85
85
 
86
86
  // src/index.ts
@@ -89,8 +89,95 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
89
89
 
90
90
  // src/tools.ts
91
91
  import { z } from "zod";
92
+
93
+ // src/durable-issuance.ts
94
+ async function issueInventory(ctx, args, extra) {
95
+ const key = args.request_key;
96
+ const error = (code, message, detail = {}) => ({ isError: true, content: [{ type: "text", text: JSON.stringify({ ...detail, code, message, request_key: key }) }] });
97
+ const token2 = await ctx.getApiToken?.(extra);
98
+ if (!token2) return error("CARRIER_API_AUTH_REQUIRED", "An authenticated Carrier API credential is required. No dispatch made.");
99
+ const body2 = {
100
+ packageTemplateId: args.packageTemplateId,
101
+ ...args.account_for_subs !== void 0 ? { accountId: args.account_for_subs } : {},
102
+ ...Object.fromEntries(["startDate", "endDate", "validityPeriod"].filter((k) => args[k] !== void 0).map((k) => [k, args[k]])),
103
+ ...args.business_reference !== void 0 ? { businessReference: args.business_reference } : {}
104
+ };
105
+ try {
106
+ const response = await fetch(`${ctx.env.CARRIER_API_BASE_URL ?? "https://api.carrier.llc"}/v1/subscribers/issue-from-inventory`, {
107
+ method: "POST",
108
+ redirect: "error",
109
+ headers: { "Authorization": `Bearer ${token2}`, "Content-Type": "application/json", "X-Idempotency-Key": String(key) },
110
+ body: JSON.stringify(body2)
111
+ });
112
+ const payload = await response.json();
113
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || response.ok && !payload.data) throw new Error("Malformed API response");
114
+ if (response.status >= 500 && payload.code !== "OUTCOME_UNKNOWN" && payload.upstream_dispatched !== false) {
115
+ return error("OUTCOME_UNKNOWN", "Issuance outcome is unknown. Preserve this request_key for reconciliation; do not issue with a new key.", { api_error: payload });
116
+ }
117
+ return { ...!response.ok ? { isError: true } : {}, content: [{ type: "text", text: JSON.stringify({ ...payload, request_key: key }) }] };
118
+ } catch {
119
+ return error("OUTCOME_UNKNOWN", "Issuance outcome is unknown. Preserve this request_key for reconciliation; do not issue with a new key.");
120
+ }
121
+ }
122
+
123
+ // src/tools.ts
92
124
  import * as Sentry from "@sentry/cloudflare";
93
125
 
126
+ // src/ocs-scoping.ts
127
+ function positiveAccountId(raw) {
128
+ if (typeof raw !== "number" && (typeof raw !== "string" || !/^[1-9]\d*$/.test(raw))) return void 0;
129
+ const id = Number(raw);
130
+ return Number.isSafeInteger(id) && id > 0 ? id : void 0;
131
+ }
132
+ function tenantAccountIds(auth) {
133
+ if (auth.managed) {
134
+ const id = positiveAccountId(auth.accountId);
135
+ return id === void 0 ? [] : [id];
136
+ }
137
+ if (auth.restricted) return (auth.allowedAccountIds ?? []).filter((id) => positiveAccountId(id) !== void 0);
138
+ if (auth.preferredAccountId !== void 0) {
139
+ const id = positiveAccountId(auth.preferredAccountId);
140
+ return id === void 0 ? [] : [id];
141
+ }
142
+ return void 0;
143
+ }
144
+ function managedOwnAccountId(auth) {
145
+ const ids = tenantAccountIds(auth);
146
+ return ids === void 0 ? void 0 : ids[0] ?? -1;
147
+ }
148
+ function subscriberAccountId(data) {
149
+ if (!data || typeof data !== "object") return void 0;
150
+ const root = data;
151
+ const sub = root.subscriber && typeof root.subscriber === "object" ? root.subscriber : root;
152
+ const raw = sub.accountId ?? sub.account_id ?? sub.account;
153
+ if (raw === void 0 || raw === null) return void 0;
154
+ return positiveAccountId(raw);
155
+ }
156
+ function isManagedSubscriberDenied(auth, subscriberData) {
157
+ const ids = tenantAccountIds(auth);
158
+ if (ids === void 0) return false;
159
+ const subAccountId = subscriberAccountId(subscriberData);
160
+ return subAccountId === void 0 || !ids.includes(subAccountId);
161
+ }
162
+ async function assertManagedIccidAccess(baseUrl2, auth, iccid) {
163
+ const ids = tenantAccountIds(auth);
164
+ if (ids === void 0) return { ok: true };
165
+ if (ids.length === 0) return { ok: false, error: "Subscriber not found" };
166
+ try {
167
+ const client2 = new OcsClient(baseUrl2, auth.token);
168
+ const data = await client2.call("getSingleSubscriber", { iccid });
169
+ if (isManagedSubscriberDenied(auth, data)) {
170
+ return { ok: false, error: "Subscriber not found" };
171
+ }
172
+ return { ok: true };
173
+ } catch (err9) {
174
+ return {
175
+ ok: false,
176
+ error: err9 instanceof Error ? err9.message : String(err9)
177
+ };
178
+ }
179
+ }
180
+
94
181
  // ../../packages/carrier-ai/dist/index.js
95
182
  import { AwsClient } from "aws4fetch";
96
183
  var AI_TIER_POLICY = {
@@ -1211,8 +1298,19 @@ var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
1211
1298
  // S6 live-smoke gap fill (CAR-105)
1212
1299
  "change_network_profile_of_location_zone"
1213
1300
  ]);
1301
+ function assignPackageRefusal(args) {
1302
+ const hasIccid = typeof args.iccid === "string" && args.iccid.trim().length > 0;
1303
+ const conflict = hasIccid && (args.account_for_subs !== void 0 || args.business_reference !== void 0);
1304
+ const invalidKey = !hasIccid && (typeof args.request_key !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(args.request_key));
1305
+ if (conflict || invalidKey) return { isError: true, content: [{ type: "text", text: JSON.stringify({
1306
+ code: conflict ? "INVALID_ASSIGN_PACKAGE_INPUT" : "IDEMPOTENCY_KEY_REQUIRED",
1307
+ message: conflict ? "iccid and inventory/business reference are mutually exclusive. No changes made." : "Inventory requires a stable request_key (1\u2013200 characters: letters, digits, underscore, dot, colon, hyphen). No changes made.",
1308
+ request_key: args.request_key
1309
+ }) }] };
1310
+ return void 0;
1311
+ }
1214
1312
  function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler, options) {
1215
- return async (args) => {
1313
+ return async (args, extra) => {
1216
1314
  const start = Date.now();
1217
1315
  const isDryRun = args.dry_run === true;
1218
1316
  if (!ctx.props.scope.includes(requiredScope)) {
@@ -1256,6 +1354,11 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler, options)
1256
1354
  ]
1257
1355
  };
1258
1356
  }
1357
+ const refusal = options?.validateArgs?.(args);
1358
+ if (refusal) {
1359
+ ctx.audit({ tool_name: toolName, ocs_method: ocsMethod, status: "error", dry_run: isDryRun, duration_ms: 0 });
1360
+ return refusal;
1361
+ }
1259
1362
  if (isDryRun && DESTRUCTIVE_TOOLS.has(toolName)) {
1260
1363
  ctx.audit({
1261
1364
  tool_name: toolName,
@@ -1276,8 +1379,9 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler, options)
1276
1379
  }
1277
1380
  let result2;
1278
1381
  try {
1279
- const token2 = await ctx.getUserToken(ctx.props.sub);
1280
- result2 = await handler(args, token2);
1382
+ const skipToken = typeof options?.skipUserToken === "function" ? options.skipUserToken(args) : options?.skipUserToken;
1383
+ const token2 = skipToken ? "" : await ctx.getUserToken(ctx.props.sub);
1384
+ result2 = extra === void 0 ? await handler(args, token2) : await handler(args, token2, extra);
1281
1385
  } catch (err9) {
1282
1386
  try {
1283
1387
  Sentry.captureException(err9, {
@@ -1792,7 +1896,7 @@ function registerAllTools(server2, ctx) {
1792
1896
  TOOL_SCOPES["modify_subscriber_steering_list"],
1793
1897
  ctx,
1794
1898
  async ({ iccid, steeringListId }, token2) => ocsCall(ctx.env, token2, "modifySubscriberSteeringList", {
1795
- subscriber: iccid,
1899
+ subscriber: { iccid },
1796
1900
  steeringListId
1797
1901
  })
1798
1902
  )
@@ -1931,16 +2035,21 @@ function registerAllTools(server2, ctx) {
1931
2035
  server2.registerTool(
1932
2036
  "assign_package",
1933
2037
  {
1934
- title: "Assign Package to Subscriber",
1935
- description: "Use this to assign a one-time prepaid data/voice package to a subscriber from an existing template. The package is active immediately (or at first usage, depending on template settings). Params: `iccid` (subscriber identifier), `packageTemplateId` (integer from `list_package_templates`), `account_for_subs` (integer, optional \u2014 when provided instead of resolving a subscriber, OCS auto-selects a free eSIM from that account and assigns the package; this is the bulk auto-provisioning path). Returns: created package record with `packageId`, `startDate`, `endDate`, and allowances. Do NOT use this for packages that should auto-renew \u2014 use `assign_recurring_package` instead. Do NOT use this to provision a new subscriber end-to-end \u2014 consider `provision_esim_wizard` for a guided flow with dry-run preview.",
1936
- inputSchema: {
1937
- iccid: z.string().describe("The subscriber ICCID"),
1938
- packageTemplateId: z.number().describe("The package template ID to assign"),
1939
- account_for_subs: z.number().optional().describe(
1940
- "Account ID \u2014 when provided, OCS auto-selects a free eSIM from this account for bulk provisioning"
1941
- ),
2038
+ title: "Assign Package or Issue From Inventory",
2039
+ description: "Assign a one-time prepaid package to an existing subscriber using iccid + packageTemplateId. Inventory issuance uses authenticated Carrier API POST /v1/subscribers/issue-from-inventory with request_key as X-Idempotency-Key. API authorization and its durable ledger are authoritative. Keep the same key after unknown outcomes; never automatically issue with a new key. Do not send both iccid and account_for_subs. Use assign_recurring_package for auto-renew.",
2040
+ inputSchema: z.object({
2041
+ iccid: z.string().optional().describe("Existing subscriber ICCID"),
2042
+ packageTemplateId: z.number().int().positive().describe("The package template ID to assign"),
2043
+ account_for_subs: z.number().int().positive().optional().describe("Inventory account sent as API accountId"),
2044
+ request_key: z.string().max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/).optional().describe("Required stable inventory key; preserve after unknown outcomes"),
2045
+ startDate: z.string().optional(),
2046
+ endDate: z.string().optional(),
2047
+ validityPeriod: z.number().int().positive().optional(),
2048
+ business_reference: z.object({ kind: z.literal("storefront_order"), id: z.string().min(1), line: z.literal("esim"), verb: z.literal("issue") }).strict().optional().describe("API-authorized existing order reference; not proof of ownership"),
1942
2049
  ...DRY_RUN_FIELD
1943
- },
2050
+ }).refine((d) => !((d.account_for_subs !== void 0 || d.business_reference !== void 0) && typeof d.iccid === "string" && d.iccid.trim().length > 0), {
2051
+ message: "iccid and account_for_subs are mutually exclusive \u2014 choose one path"
2052
+ }),
1944
2053
  annotations: { destructiveHint: true }
1945
2054
  },
1946
2055
  wrapHandler(
@@ -1948,17 +2057,34 @@ function registerAllTools(server2, ctx) {
1948
2057
  "affectPackageToSubscriber",
1949
2058
  TOOL_SCOPES["assign_package"],
1950
2059
  ctx,
1951
- async ({ iccid, packageTemplateId, account_for_subs }, token2) => {
1952
- if (account_for_subs !== void 0) {
1953
- return ocsCall(ctx.env, token2, "affectPackageToSubscriber", {
1954
- packageTemplateId,
1955
- accountForSubs: account_for_subs
1956
- });
1957
- }
2060
+ async (args, token2, extra) => {
2061
+ const { iccid, packageTemplateId } = args;
2062
+ if (typeof iccid !== "string" || !iccid.trim()) return issueInventory(ctx, args, extra);
2063
+ const access = await assertManagedIccidAccess(
2064
+ ctx.env.CARRIER_OCS_BASE_URL,
2065
+ { ...ctx.managedAuth, token: token2 },
2066
+ iccid
2067
+ );
2068
+ if (!access.ok) throw new Error(access.error);
1958
2069
  return ocsCall(ctx.env, token2, "affectPackageToSubscriber", {
1959
2070
  subscriber: { iccid },
1960
2071
  packageTemplateId
1961
2072
  });
2073
+ },
2074
+ {
2075
+ validateArgs: assignPackageRefusal,
2076
+ skipUserToken: (args) => typeof args.iccid !== "string" || !args.iccid.trim(),
2077
+ dryRunPayload: (args) => typeof args.iccid === "string" && args.iccid.trim() ? { subscriber: { iccid: args.iccid }, packageTemplateId: args.packageTemplateId } : {
2078
+ method: "POST",
2079
+ path: "/v1/subscribers/issue-from-inventory",
2080
+ headers: { "X-Idempotency-Key": args.request_key },
2081
+ body: {
2082
+ packageTemplateId: args.packageTemplateId,
2083
+ ...args.account_for_subs !== void 0 ? { accountId: args.account_for_subs } : {},
2084
+ ...Object.fromEntries(["startDate", "endDate", "validityPeriod"].filter((k) => args[k] !== void 0).map((k) => [k, args[k]])),
2085
+ ...args.business_reference !== void 0 ? { businessReference: args.business_reference } : {}
2086
+ }
2087
+ }
1962
2088
  }
1963
2089
  )
1964
2090
  );
@@ -10586,45 +10712,6 @@ function buildExamples(toolName) {
10586
10712
  // src/list-recent-ocs-events.ts
10587
10713
  import { z as z16 } from "zod";
10588
10714
 
10589
- // src/ocs-scoping.ts
10590
- function managedOwnAccountId(auth) {
10591
- if (!auth.managed || auth.accountId === void 0) return void 0;
10592
- const n = Number(auth.accountId);
10593
- return Number.isFinite(n) ? n : void 0;
10594
- }
10595
- function subscriberAccountId(data) {
10596
- if (!data || typeof data !== "object") return void 0;
10597
- const root = data;
10598
- const sub = root.subscriber && typeof root.subscriber === "object" ? root.subscriber : root;
10599
- const raw = sub.accountId ?? sub.account_id ?? sub.account;
10600
- if (raw === void 0 || raw === null) return void 0;
10601
- const n = Number(raw);
10602
- return Number.isFinite(n) ? n : void 0;
10603
- }
10604
- function isManagedSubscriberDenied(auth, subscriberData) {
10605
- const ownId = managedOwnAccountId(auth);
10606
- if (ownId === void 0) return false;
10607
- const subAccountId = subscriberAccountId(subscriberData);
10608
- if (subAccountId === void 0) return true;
10609
- return subAccountId !== ownId;
10610
- }
10611
- async function assertManagedIccidAccess(baseUrl2, auth, iccid) {
10612
- if (managedOwnAccountId(auth) === void 0) return { ok: true };
10613
- try {
10614
- const client2 = new OcsClient(baseUrl2, auth.token);
10615
- const data = await client2.call("getSingleSubscriber", { iccid });
10616
- if (isManagedSubscriberDenied(auth, data)) {
10617
- return { ok: false, error: "Subscriber not found" };
10618
- }
10619
- return { ok: true };
10620
- } catch (err9) {
10621
- return {
10622
- ok: false,
10623
- error: err9 instanceof Error ? err9.message : String(err9)
10624
- };
10625
- }
10626
- }
10627
-
10628
10715
  // ../../packages/ocs-spec/src/ocs-event-buffer-read.ts
10629
10716
  var LIFECYCLE_RESELLER_ID2 = 1170;
10630
10717
  function ocsEventRingBufferIccidFromDigitsReference(raw) {
@@ -12857,13 +12944,14 @@ function registerUiAgentGenericTools(server2, ctx, runtime) {
12857
12944
  // src/index.ts
12858
12945
  var baseUrl = process.env.CARRIER_OCS_BASE_URL ?? process.env.ESIMVAULT_BASE_URL ?? "https://ocs.esimvault.cloud";
12859
12946
  var token = process.env.CARRIER_OCS_API_TOKEN ?? process.env.ESIMVAULT_API_TOKEN;
12860
- if (!token) {
12947
+ if (!token && !process.env.CARRIER_API_TOKEN) {
12861
12948
  console.error(
12862
- "ERROR: CARRIER_OCS_API_TOKEN (or legacy ESIMVAULT_API_TOKEN) must be set."
12949
+ "ERROR: Set CARRIER_API_TOKEN for durable inventory or CARRIER_OCS_API_TOKEN for explicit OCS tools."
12863
12950
  );
12864
12951
  process.exit(1);
12865
12952
  }
12866
12953
  var stdioEnv = {
12954
+ CARRIER_API_BASE_URL: process.env.CARRIER_API_BASE_URL ?? "https://api.carrier.llc",
12867
12955
  CARRIER_OCS_BASE_URL: baseUrl,
12868
12956
  // Empty on purpose. rotate_credentials reads this and throws a clear
12869
12957
  // message rather than pretending a local install can rotate the vault.
@@ -12931,13 +13019,17 @@ var props = {
12931
13019
  };
12932
13020
  var audit = (_row) => {
12933
13021
  };
12934
- var getUserToken = async (_sub) => token;
13022
+ var getUserToken = async (_sub) => {
13023
+ if (!token) throw new Error("CARRIER_OCS_API_TOKEN is required for explicit OCS tools");
13024
+ return token;
13025
+ };
12935
13026
  var toolCtx = {
12936
13027
  env: stdioEnv,
12937
13028
  props,
12938
13029
  managedAuth: {},
12939
13030
  audit,
12940
- getUserToken
13031
+ getUserToken,
13032
+ getApiToken: async () => process.env.CARRIER_API_TOKEN || void 0
12941
13033
  };
12942
13034
  var server = new McpServer(
12943
13035
  { name: "carrier-mcp", version: CARRIER_VERSION },