@carrierllc/mcp 0.9.0 → 0.9.2

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
@@ -37,7 +37,7 @@ import {
37
37
  subscriberIdParams,
38
38
  usageOverPeriodParams,
39
39
  verifyStorefront
40
- } from "./chunk-VACB3Z5V.js";
40
+ } from "./chunk-4XHSOF62.js";
41
41
  import "./chunk-SHKKVIIA.js";
42
42
 
43
43
  // src/index.ts
@@ -98,17 +98,213 @@ var OcsClient = class {
98
98
  }
99
99
  };
100
100
 
101
+ // ../../packages/carrier-ai/dist/index.js
102
+ import { AwsClient } from "aws4fetch";
103
+ var AI_TIER_POLICY = {
104
+ free: { limit: 5, window: "day" },
105
+ pro: { limit: 1e3, window: "month" },
106
+ enterprise: { limit: 1e4, window: "month" },
107
+ // Finite, deliberately. This was `Infinity` when the tier was introduced in
108
+ // #762, and Infinity breaks in two ways that both hide themselves:
109
+ //
110
+ // 1. JSON.stringify(Infinity) is "null". The quota endpoint returned
111
+ // {"limit":null,"remaining":null} and the console badge rendered
112
+ // "1 of null used". That is the whole reason this was investigated.
113
+ // 2. An infinite allowance is unmetered Bedrock. A staff account could
114
+ // spend without bound and nothing would refuse it or show a number.
115
+ //
116
+ // 100,000 is roughly $580 of Haiku at the measured $0.0058/call. Nobody on
117
+ // staff will reach it by using the product, and a runaway loop stops.
118
+ superadmin: { limit: 1e5, window: "month" }
119
+ };
120
+ for (const [tier, policy] of Object.entries(AI_TIER_POLICY)) {
121
+ if (!Number.isFinite(policy.limit) || policy.limit <= 0) {
122
+ throw new Error(
123
+ `AI_TIER_POLICY.${tier}.limit must be a positive finite number, got ${policy.limit}. A non-finite limit serialises to null in JSON and leaves spend unmetered.`
124
+ );
125
+ }
126
+ }
127
+ var PRELUDE_BYTES = 12;
128
+ var MESSAGE_CRC_BYTES = 4;
129
+ var MIN_FRAME_BYTES = PRELUDE_BYTES + MESSAGE_CRC_BYTES;
130
+ var MAX_FRAME_BYTES = 16 * 1024 * 1024;
131
+ var MAX_ENTRIES = 16;
132
+ var MAX_KEY_LEN = 256;
133
+ var MAX_VALUE_LEN = 256;
134
+ var ALLOWED = /[^a-zA-Z0-9\s:_@$#=/+,\-.]/g;
135
+ function clean(value, max) {
136
+ return value.replace(ALLOWED, "").slice(0, max);
137
+ }
138
+ var REQUEST_METADATA_HEADER = "X-Amzn-Bedrock-Request-Metadata";
139
+ function requestMetadata(attr2) {
140
+ const out = {};
141
+ const pairs = [
142
+ ["product", "carrier"],
143
+ ["org", attr2.orgId],
144
+ ["subaccount", attr2.subAccountId],
145
+ ["surface", attr2.surface],
146
+ ["tier", attr2.tier]
147
+ ];
148
+ for (const [k, v] of pairs) {
149
+ if (v === void 0 || v === "") continue;
150
+ const key = clean(k, MAX_KEY_LEN);
151
+ const value = clean(v, MAX_VALUE_LEN);
152
+ if (key && value && Object.keys(out).length < MAX_ENTRIES) out[key] = value;
153
+ }
154
+ return Object.keys(out).length > 0 ? out : null;
155
+ }
156
+ var ZERO_USAGE = {
157
+ inputTokens: 0,
158
+ outputTokens: 0,
159
+ cacheReadTokens: 0,
160
+ cacheWriteTokens: 0
161
+ };
162
+ function extractUsage(payload) {
163
+ if (!payload || typeof payload !== "object") return ZERO_USAGE;
164
+ const root = payload;
165
+ const holder = root.usage ?? root.message?.usage;
166
+ if (!holder) return ZERO_USAGE;
167
+ const num = (v) => {
168
+ const n = Number(v);
169
+ return Number.isFinite(n) && n > 0 ? n : 0;
170
+ };
171
+ return {
172
+ inputTokens: num(holder.input_tokens),
173
+ outputTokens: num(holder.output_tokens),
174
+ cacheReadTokens: num(holder.cache_read_input_tokens),
175
+ cacheWriteTokens: num(holder.cache_creation_input_tokens)
176
+ };
177
+ }
178
+ var HAIKU_RATES = {
179
+ inputPerMTok: 1,
180
+ outputPerMTok: 5,
181
+ cacheReadPerMTok: 0.1,
182
+ cacheWritePerMTok: 1.25,
183
+ asOf: "2026-08-22"
184
+ };
185
+ function estimateCostUsd(u, rates = HAIKU_RATES) {
186
+ const perTok = (mtok) => mtok / 1e6;
187
+ return u.inputTokens * perTok(rates.inputPerMTok) + u.outputTokens * perTok(rates.outputPerMTok) + u.cacheReadTokens * perTok(rates.cacheReadPerMTok) + u.cacheWriteTokens * perTok(rates.cacheWritePerMTok);
188
+ }
189
+ var DEFAULT_BEDROCK_REGION = "us-east-1";
190
+ var DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
191
+ var BEDROCK_TIMEOUT_MS = 3e4;
192
+ var BedrockRateLimitError = class extends Error {
193
+ isRateLimit = true;
194
+ /**
195
+ * The raw `retry-after` header, when Bedrock sent one. Callers surface it as
196
+ * the delay they tell the user to wait; without it they have to guess.
197
+ */
198
+ retryAfter;
199
+ constructor(message, retryAfter = null) {
200
+ super(message);
201
+ this.name = "BedrockRateLimitError";
202
+ this.retryAfter = retryAfter;
203
+ }
204
+ };
205
+ var BedrockError = class extends Error {
206
+ constructor(message, status) {
207
+ super(message);
208
+ this.status = status;
209
+ this.name = "BedrockError";
210
+ }
211
+ status;
212
+ };
213
+ function client(creds) {
214
+ const region = creds.region ?? DEFAULT_BEDROCK_REGION;
215
+ return {
216
+ aws: new AwsClient({
217
+ accessKeyId: creds.accessKeyId,
218
+ secretAccessKey: creds.secretAccessKey,
219
+ ...creds.sessionToken ? { sessionToken: creds.sessionToken } : {},
220
+ region,
221
+ service: "bedrock"
222
+ }),
223
+ region,
224
+ modelId: creds.modelId ?? DEFAULT_MODEL_ID
225
+ };
226
+ }
227
+ function body(req) {
228
+ return {
229
+ anthropic_version: "bedrock-2023-05-31",
230
+ max_tokens: req.maxTokens,
231
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
232
+ system: [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }],
233
+ messages: req.messages.map((m) => ({ role: m.role, content: m.content })),
234
+ ...req.tools ? { tools: req.tools, tool_choice: { type: "any" } } : {}
235
+ };
236
+ }
237
+ function headersFor(attribution, accept) {
238
+ const headers = {
239
+ "Content-Type": "application/json",
240
+ Accept: accept
241
+ };
242
+ const meta = attribution ? requestMetadata(attribution) : null;
243
+ if (meta) headers[REQUEST_METADATA_HEADER] = JSON.stringify(meta);
244
+ return headers;
245
+ }
246
+ function reportUsage(req, usage) {
247
+ if (!req.onUsage) return;
248
+ try {
249
+ req.onUsage(usage);
250
+ } catch {
251
+ }
252
+ }
253
+ async function fail(resp) {
254
+ const text = await resp.text().catch(() => "");
255
+ if (resp.status === 429 || /throttl/i.test(text)) {
256
+ throw new BedrockRateLimitError(
257
+ text || "Bedrock throttled the request",
258
+ resp.headers.get("retry-after")
259
+ );
260
+ }
261
+ throw new BedrockError(text || `Bedrock returned HTTP ${resp.status}`, resp.status);
262
+ }
263
+ async function invokeTool(creds, req, signal) {
264
+ const json = await invokeModelRaw(
265
+ creds,
266
+ body(req),
267
+ { attribution: req.attribution, timeoutMs: req.timeoutMs },
268
+ signal
269
+ );
270
+ reportUsage(req, extractUsage(json));
271
+ const block = json.content?.find((b) => b.type === "tool_use");
272
+ return block?.name ? { name: block.name, input: block.input ?? {} } : null;
273
+ }
274
+ async function invokeModelRaw(creds, payload, opts = {}, signal) {
275
+ const { aws, region, modelId } = client(creds);
276
+ const controller = new AbortController();
277
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? BEDROCK_TIMEOUT_MS);
278
+ const onAbort = () => controller.abort();
279
+ signal?.addEventListener("abort", onAbort, { once: true });
280
+ try {
281
+ const resp = await aws.fetch(
282
+ `https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`,
283
+ {
284
+ method: "POST",
285
+ headers: headersFor(opts.attribution, "application/json"),
286
+ body: JSON.stringify(payload),
287
+ signal: controller.signal
288
+ }
289
+ );
290
+ if (!resp.ok) await fail(resp);
291
+ return await resp.json();
292
+ } finally {
293
+ clearTimeout(timer);
294
+ signal?.removeEventListener("abort", onAbort);
295
+ }
296
+ }
297
+
101
298
  // src/billing.ts
102
299
  var TIER_CALL_LIMITS = {
103
- free: 5e3,
104
- pro: 5e4,
105
- enterprise: Infinity
300
+ free: 1e3,
301
+ pro: Infinity,
302
+ enterprise: Infinity,
303
+ superadmin: Infinity
106
304
  };
305
+ var CARRIER_ASK_PRO_MONTHLY_LIMIT = AI_TIER_POLICY.pro.limit;
306
+ var CARRIER_ASK_FREE_DAILY_LIMIT = AI_TIER_POLICY.free.limit;
107
307
  var UPGRADE_URL = "https://mcp.carrier.llc/upgrade";
108
- function getBillingPrimary(env) {
109
- const raw = env.BILLING_PRIMARY;
110
- return raw === "clerk" ? "clerk" : "stripe";
111
- }
112
308
  function platformOrgId(env) {
113
309
  const raw = env.CARRIER_PLATFORM_ORG_ID;
114
310
  if (typeof raw !== "string") return null;
@@ -123,7 +319,7 @@ function isPlatformOperator(env, orgId) {
123
319
  async function checkCallQuota(env, sub, tier) {
124
320
  const limit = TIER_CALL_LIMITS[tier];
125
321
  const resetAt = firstDayNextMonth();
126
- if (tier === "enterprise") {
322
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") {
127
323
  return { allowed: true, remaining: Infinity, resetAt, tier };
128
324
  }
129
325
  if (!env.CARRIER_USERS) {
@@ -144,6 +340,7 @@ async function checkCallQuota(env, sub, tier) {
144
340
  function recordUsage(env, sub, tier) {
145
341
  if (!env.CARRIER_USERS) return;
146
342
  (async () => {
343
+ if (tier === "pro" || tier === "enterprise" || tier === "superadmin") return;
147
344
  const month = currentMonth();
148
345
  const usageKey = `usage:${sub}:${month}`;
149
346
  const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
@@ -154,35 +351,8 @@ function recordUsage(env, sub, tier) {
154
351
  JSON.stringify({ calls: next, updated_at: (/* @__PURE__ */ new Date()).toISOString() }),
155
352
  { expirationTtl: 35 * 24 * 60 * 60 }
156
353
  );
157
- if (tier === "pro" && next % 100 === 0 && getBillingPrimary(env) === "stripe") {
158
- await pushStripeUsageRecord(env, sub, 100).catch(() => {
159
- });
160
- }
161
354
  })();
162
355
  }
163
- async function pushStripeUsageRecord(env, sub, quantity) {
164
- const stripeKey = env.STRIPE_SECRET_KEY;
165
- if (!stripeKey) return;
166
- if (!env.CARRIER_USERS) return;
167
- const subItemId = await env.CARRIER_USERS.get(`stripe_sub_item_id:${sub}`);
168
- if (!subItemId) return;
169
- const body2 = new URLSearchParams({
170
- quantity: String(quantity),
171
- timestamp: String(Math.floor(Date.now() / 1e3)),
172
- action: "increment"
173
- });
174
- await fetch(
175
- `https://api.stripe.com/v1/subscription_items/${subItemId}/usage_records`,
176
- {
177
- method: "POST",
178
- headers: {
179
- Authorization: `Bearer ${stripeKey}`,
180
- "Content-Type": "application/x-www-form-urlencoded"
181
- },
182
- body: body2.toString()
183
- }
184
- );
185
- }
186
356
  function currentMonth() {
187
357
  const now = /* @__PURE__ */ new Date();
188
358
  return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -531,8 +701,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
531
701
  {
532
702
  type: "text",
533
703
  text: [
534
- `Quota exceeded: your ${quota.tier} plan has reached the safety limit of 100,000 regular tool calls/month.`,
535
- `This limit exists to prevent runaway automation. Resets ${quota.resetAt}.`,
704
+ `Quota exceeded: your ${quota.tier} plan has reached the monthly limit of ${TIER_CALL_LIMITS.free.toLocaleString()} regular tool calls.`,
705
+ `Resets ${quota.resetAt}.`,
536
706
  `Upgrade to Pro for unlimited calls at ${UPGRADE_URL}`
537
707
  ].join(" ")
538
708
  }
@@ -4675,196 +4845,6 @@ function registerAllBacklogTools(server2, ctx) {
4675
4845
  );
4676
4846
  }
4677
4847
 
4678
- // ../../packages/carrier-ai/dist/index.js
4679
- import { AwsClient } from "aws4fetch";
4680
- var AI_TIER_POLICY = {
4681
- free: { limit: 5, window: "day" },
4682
- pro: { limit: 1e3, window: "month" },
4683
- enterprise: { limit: 1e4, window: "month" },
4684
- // Finite, deliberately. This was `Infinity` when the tier was introduced in
4685
- // #762, and Infinity breaks in two ways that both hide themselves:
4686
- //
4687
- // 1. JSON.stringify(Infinity) is "null". The quota endpoint returned
4688
- // {"limit":null,"remaining":null} and the console badge rendered
4689
- // "1 of null used". That is the whole reason this was investigated.
4690
- // 2. An infinite allowance is unmetered Bedrock. A staff account could
4691
- // spend without bound and nothing would refuse it or show a number.
4692
- //
4693
- // 100,000 is roughly $580 of Haiku at the measured $0.0058/call. Nobody on
4694
- // staff will reach it by using the product, and a runaway loop stops.
4695
- superadmin: { limit: 1e5, window: "month" }
4696
- };
4697
- for (const [tier, policy] of Object.entries(AI_TIER_POLICY)) {
4698
- if (!Number.isFinite(policy.limit) || policy.limit <= 0) {
4699
- throw new Error(
4700
- `AI_TIER_POLICY.${tier}.limit must be a positive finite number, got ${policy.limit}. A non-finite limit serialises to null in JSON and leaves spend unmetered.`
4701
- );
4702
- }
4703
- }
4704
- var PRELUDE_BYTES = 12;
4705
- var MESSAGE_CRC_BYTES = 4;
4706
- var MIN_FRAME_BYTES = PRELUDE_BYTES + MESSAGE_CRC_BYTES;
4707
- var MAX_FRAME_BYTES = 16 * 1024 * 1024;
4708
- var MAX_ENTRIES = 16;
4709
- var MAX_KEY_LEN = 256;
4710
- var MAX_VALUE_LEN = 256;
4711
- var ALLOWED = /[^a-zA-Z0-9\s:_@$#=/+,\-.]/g;
4712
- function clean(value, max) {
4713
- return value.replace(ALLOWED, "").slice(0, max);
4714
- }
4715
- var REQUEST_METADATA_HEADER = "X-Amzn-Bedrock-Request-Metadata";
4716
- function requestMetadata(attr2) {
4717
- const out = {};
4718
- const pairs = [
4719
- ["product", "carrier"],
4720
- ["org", attr2.orgId],
4721
- ["subaccount", attr2.subAccountId],
4722
- ["surface", attr2.surface],
4723
- ["tier", attr2.tier]
4724
- ];
4725
- for (const [k, v] of pairs) {
4726
- if (v === void 0 || v === "") continue;
4727
- const key = clean(k, MAX_KEY_LEN);
4728
- const value = clean(v, MAX_VALUE_LEN);
4729
- if (key && value && Object.keys(out).length < MAX_ENTRIES) out[key] = value;
4730
- }
4731
- return Object.keys(out).length > 0 ? out : null;
4732
- }
4733
- var ZERO_USAGE = {
4734
- inputTokens: 0,
4735
- outputTokens: 0,
4736
- cacheReadTokens: 0,
4737
- cacheWriteTokens: 0
4738
- };
4739
- function extractUsage(payload) {
4740
- if (!payload || typeof payload !== "object") return ZERO_USAGE;
4741
- const root = payload;
4742
- const holder = root.usage ?? root.message?.usage;
4743
- if (!holder) return ZERO_USAGE;
4744
- const num = (v) => {
4745
- const n = Number(v);
4746
- return Number.isFinite(n) && n > 0 ? n : 0;
4747
- };
4748
- return {
4749
- inputTokens: num(holder.input_tokens),
4750
- outputTokens: num(holder.output_tokens),
4751
- cacheReadTokens: num(holder.cache_read_input_tokens),
4752
- cacheWriteTokens: num(holder.cache_creation_input_tokens)
4753
- };
4754
- }
4755
- var HAIKU_RATES = {
4756
- inputPerMTok: 1,
4757
- outputPerMTok: 5,
4758
- cacheReadPerMTok: 0.1,
4759
- cacheWritePerMTok: 1.25,
4760
- asOf: "2026-08-22"
4761
- };
4762
- function estimateCostUsd(u, rates = HAIKU_RATES) {
4763
- const perTok = (mtok) => mtok / 1e6;
4764
- return u.inputTokens * perTok(rates.inputPerMTok) + u.outputTokens * perTok(rates.outputPerMTok) + u.cacheReadTokens * perTok(rates.cacheReadPerMTok) + u.cacheWriteTokens * perTok(rates.cacheWritePerMTok);
4765
- }
4766
- var DEFAULT_BEDROCK_REGION = "us-east-1";
4767
- var DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
4768
- var BEDROCK_TIMEOUT_MS = 3e4;
4769
- var BedrockRateLimitError = class extends Error {
4770
- isRateLimit = true;
4771
- /**
4772
- * The raw `retry-after` header, when Bedrock sent one. Callers surface it as
4773
- * the delay they tell the user to wait; without it they have to guess.
4774
- */
4775
- retryAfter;
4776
- constructor(message, retryAfter = null) {
4777
- super(message);
4778
- this.name = "BedrockRateLimitError";
4779
- this.retryAfter = retryAfter;
4780
- }
4781
- };
4782
- var BedrockError = class extends Error {
4783
- constructor(message, status) {
4784
- super(message);
4785
- this.status = status;
4786
- this.name = "BedrockError";
4787
- }
4788
- status;
4789
- };
4790
- function client(creds) {
4791
- const region = creds.region ?? DEFAULT_BEDROCK_REGION;
4792
- return {
4793
- aws: new AwsClient({
4794
- accessKeyId: creds.accessKeyId,
4795
- secretAccessKey: creds.secretAccessKey,
4796
- ...creds.sessionToken ? { sessionToken: creds.sessionToken } : {},
4797
- region,
4798
- service: "bedrock"
4799
- }),
4800
- region,
4801
- modelId: creds.modelId ?? DEFAULT_MODEL_ID
4802
- };
4803
- }
4804
- function body(req) {
4805
- return {
4806
- anthropic_version: "bedrock-2023-05-31",
4807
- max_tokens: req.maxTokens,
4808
- ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
4809
- system: [{ type: "text", text: req.system, cache_control: { type: "ephemeral" } }],
4810
- messages: req.messages.map((m) => ({ role: m.role, content: m.content })),
4811
- ...req.tools ? { tools: req.tools, tool_choice: { type: "any" } } : {}
4812
- };
4813
- }
4814
- function headersFor(req, accept) {
4815
- const headers = {
4816
- "Content-Type": "application/json",
4817
- Accept: accept
4818
- };
4819
- const meta = req.attribution ? requestMetadata(req.attribution) : null;
4820
- if (meta) headers[REQUEST_METADATA_HEADER] = JSON.stringify(meta);
4821
- return headers;
4822
- }
4823
- function reportUsage(req, usage) {
4824
- if (!req.onUsage) return;
4825
- try {
4826
- req.onUsage(usage);
4827
- } catch {
4828
- }
4829
- }
4830
- async function fail(resp) {
4831
- const text = await resp.text().catch(() => "");
4832
- if (resp.status === 429 || /throttl/i.test(text)) {
4833
- throw new BedrockRateLimitError(
4834
- text || "Bedrock throttled the request",
4835
- resp.headers.get("retry-after")
4836
- );
4837
- }
4838
- throw new BedrockError(text || `Bedrock returned HTTP ${resp.status}`, resp.status);
4839
- }
4840
- async function invokeTool(creds, req, signal) {
4841
- const { aws, region, modelId } = client(creds);
4842
- const controller = new AbortController();
4843
- const timer = setTimeout(() => controller.abort(), req.timeoutMs ?? BEDROCK_TIMEOUT_MS);
4844
- const onAbort = () => controller.abort();
4845
- signal?.addEventListener("abort", onAbort, { once: true });
4846
- try {
4847
- const headers = headersFor(req, "application/json");
4848
- const resp = await aws.fetch(
4849
- `https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`,
4850
- {
4851
- method: "POST",
4852
- headers,
4853
- body: JSON.stringify(body(req)),
4854
- signal: controller.signal
4855
- }
4856
- );
4857
- if (!resp.ok) await fail(resp);
4858
- const json = await resp.json();
4859
- reportUsage(req, extractUsage(json));
4860
- const block = json.content?.find((b) => b.type === "tool_use");
4861
- return block?.name ? { name: block.name, input: block.input ?? {} } : null;
4862
- } finally {
4863
- clearTimeout(timer);
4864
- signal?.removeEventListener("abort", onAbort);
4865
- }
4866
- }
4867
-
4868
4848
  // src/tools-carrier-ask.ts
4869
4849
  import { z as z11 } from "zod";
4870
4850
 
@@ -5132,6 +5112,7 @@ var UI_AGENT_TOOL_SCOPES = {
5132
5112
  ui_create_steering_list: "write",
5133
5113
  ui_build_steering_list: "write",
5134
5114
  ui_set_account_steering_list: "write",
5115
+ ui_request_reseller_relay_change: "write",
5135
5116
  ui_create_account: "admin",
5136
5117
  ui_create_destination_list: "write",
5137
5118
  ui_edit_destination_list: "write",
@@ -5217,6 +5198,66 @@ Open account ID ${args.account_id}.
5217
5198
  `
5218
5199
  )
5219
5200
  );
5201
+ server2.registerTool(
5202
+ "ui_request_reseller_relay_change",
5203
+ {
5204
+ title: "Request Reseller Relay Change",
5205
+ description: "Drafts a parent-reseller/support request for Relay Gy, calls/SMS relay, or VoIP relay when those portal controls are read-only at the current reseller tier. Does not send the request or mutate the portal.",
5206
+ inputSchema: {
5207
+ reseller_id: z4.number().optional().describe("Reseller id; defaults to the authenticated reseller"),
5208
+ relay_gy: z4.boolean().optional().describe("Desired Relay Gy state"),
5209
+ relay_calls_sms: z4.boolean().optional().describe("Desired calls/SMS relay state"),
5210
+ relay_voip: z4.boolean().optional().describe("Desired VoIP relay state"),
5211
+ reason: z4.string().optional().describe("Operational reason for the requested change")
5212
+ }
5213
+ },
5214
+ async (args) => {
5215
+ if (!ctx.props.scope.includes("write")) {
5216
+ return {
5217
+ isError: true,
5218
+ content: [{ type: "text", text: "Scope denied: tool 'ui_request_reseller_relay_change' requires 'write' scope." }]
5219
+ };
5220
+ }
5221
+ const resellerId = args.reseller_id ?? ctx.props.reseller_id;
5222
+ const requested = [
5223
+ args.relay_gy === void 0 ? null : `Relay Gy: ${args.relay_gy ? "enable" : "disable"}`,
5224
+ args.relay_calls_sms === void 0 ? null : `Calls/SMS relay: ${args.relay_calls_sms ? "enable" : "disable"}`,
5225
+ args.relay_voip === void 0 ? null : `VoIP relay: ${args.relay_voip ? "enable" : "disable"}`
5226
+ ].filter((value) => value !== null);
5227
+ if (requested.length === 0) {
5228
+ return {
5229
+ isError: true,
5230
+ content: [{ type: "text", text: JSON.stringify({ error: "relay_state_required", message: "Choose at least one relay flag and desired state." }) }]
5231
+ };
5232
+ }
5233
+ const draft = [
5234
+ `Please update the parent-controlled OCS relay settings for reseller ${resellerId}.`,
5235
+ "",
5236
+ ...requested.map((line) => `- ${line}`),
5237
+ ...args.reason ? ["", `Reason: ${args.reason}`] : [],
5238
+ "",
5239
+ "Please confirm once the change is active so we can verify the dependent OCS operation."
5240
+ ].join("\n");
5241
+ ctx.audit({
5242
+ tool_name: "ui_request_reseller_relay_change",
5243
+ ocs_method: "[ui-agent:G-20]",
5244
+ status: "parent_request_drafted",
5245
+ dry_run: true,
5246
+ duration_ms: 0,
5247
+ event_type: "ui_agent_dispatch"
5248
+ });
5249
+ return {
5250
+ content: [{ type: "text", text: JSON.stringify({
5251
+ status: "parent_request_drafted",
5252
+ capability: "parent_controlled",
5253
+ reseller_id: resellerId,
5254
+ parent_reseller: resellerId === 1170 ? "Bridge4IP" : "parent reseller/support",
5255
+ draft,
5256
+ sent: false
5257
+ }, null, 2) }]
5258
+ };
5259
+ }
5260
+ );
5220
5261
  server2.registerTool(
5221
5262
  "ui_create_account",
5222
5263
  {
@@ -5494,6 +5535,21 @@ function computeExpiresAt(askedAt) {
5494
5535
  if (isNaN(asked)) return "";
5495
5536
  return new Date(asked + PENDING_ASK_TTL_SECONDS * 1e3).toISOString();
5496
5537
  }
5538
+ async function callerOwnsTask(ctx, taskId) {
5539
+ let raw;
5540
+ try {
5541
+ raw = await ctx.env.CARRIER_USERS.get(`steel_task:${taskId}`);
5542
+ } catch {
5543
+ return false;
5544
+ }
5545
+ if (!raw) return false;
5546
+ try {
5547
+ const task = JSON.parse(raw);
5548
+ return task.owner_sub !== void 0 && task.owner_sub === ctx.props.sub;
5549
+ } catch {
5550
+ return false;
5551
+ }
5552
+ }
5497
5553
  function registerUiAgentAskTools(server2, ctx) {
5498
5554
  server2.registerTool(
5499
5555
  "ui_agent_reply",
@@ -5543,7 +5599,7 @@ function registerUiAgentAskTools(server2, ctx) {
5543
5599
  }
5544
5600
  const pendingKey = `${PENDING_ASK_PREFIX}${task_id}`;
5545
5601
  const pendingRaw = await ctx.env.CARRIER_USERS.get(pendingKey);
5546
- if (pendingRaw === null) {
5602
+ if (pendingRaw === null || !await callerOwnsTask(ctx, task_id)) {
5547
5603
  return {
5548
5604
  isError: true,
5549
5605
  content: [
@@ -5685,6 +5741,8 @@ function registerUiAgentAskTools(server2, ctx) {
5685
5741
  if (!raw) return null;
5686
5742
  try {
5687
5743
  const parsed = JSON.parse(raw);
5744
+ const taskId = parsed.task_id ?? name.slice(PENDING_ASK_PREFIX.length);
5745
+ if (!await callerOwnsTask(ctx, taskId)) return null;
5688
5746
  return {
5689
5747
  ...parsed,
5690
5748
  expires_at: computeExpiresAt(parsed.asked_at)
@@ -6662,6 +6720,33 @@ async function resolveBillingSub(env, orgId) {
6662
6720
  return pointer ?? orgId;
6663
6721
  }
6664
6722
 
6723
+ // src/scope-guard.ts
6724
+ function denyUnlessScoped(props2, toolName, scopes) {
6725
+ const required = scopes[toolName];
6726
+ if (required === void 0) {
6727
+ return {
6728
+ isError: true,
6729
+ content: [
6730
+ {
6731
+ type: "text",
6732
+ text: `Scope denied: tool '${toolName}' declares no scope, so it cannot be authorised. This is a server-side omission \u2014 add it to the tool's scope table.`
6733
+ }
6734
+ ]
6735
+ };
6736
+ }
6737
+ const held = props2.scope ?? [];
6738
+ if (held.includes(required)) return null;
6739
+ return {
6740
+ isError: true,
6741
+ content: [
6742
+ {
6743
+ type: "text",
6744
+ text: `Scope denied: tool '${toolName}' requires '${required}' scope. Your token has: [${held.join(", ")}].`
6745
+ }
6746
+ ]
6747
+ };
6748
+ }
6749
+
6665
6750
  // src/wallet-tools.ts
6666
6751
  var PACKS = [
6667
6752
  { id: "pack_500", eurCents: 5e4, bonusPct: 0 },
@@ -6858,6 +6943,8 @@ function registerWalletTools(server2, ctx) {
6858
6943
  annotations: annotationsFor("wallet_balance", "read")
6859
6944
  },
6860
6945
  async () => {
6946
+ const denied = denyUnlessScoped(props2, "wallet_balance", WALLET_TOOL_SCOPES);
6947
+ if (denied) return denied;
6861
6948
  const orgId = defaultOrgId;
6862
6949
  const start = Date.now();
6863
6950
  try {
@@ -6900,6 +6987,8 @@ function registerWalletTools(server2, ctx) {
6900
6987
  annotations: annotationsFor("wallet_topup_checkout", "write")
6901
6988
  },
6902
6989
  async ({ pack }) => {
6990
+ const denied = denyUnlessScoped(props2, "wallet_topup_checkout", WALLET_TOOL_SCOPES);
6991
+ if (denied) return denied;
6903
6992
  const orgId = defaultOrgId;
6904
6993
  const start = Date.now();
6905
6994
  if (!pack) {
@@ -6959,6 +7048,8 @@ function registerWalletTools(server2, ctx) {
6959
7048
  annotations: annotationsFor("wallet_auto_topup", "write")
6960
7049
  },
6961
7050
  async ({ pack_cents }) => {
7051
+ const denied = denyUnlessScoped(props2, "wallet_auto_topup", WALLET_TOOL_SCOPES);
7052
+ if (denied) return denied;
6962
7053
  const orgId = defaultOrgId;
6963
7054
  const start = Date.now();
6964
7055
  try {
@@ -7069,6 +7160,8 @@ function registerStripeConnectTools(server2, ctx) {
7069
7160
  annotations: annotationsFor("stripe_connect_status", "read")
7070
7161
  },
7071
7162
  async ({ operator_id }) => {
7163
+ const denied = denyUnlessScoped(props2, "stripe_connect_status", STRIPE_CONNECT_TOOL_SCOPES);
7164
+ if (denied) return denied;
7072
7165
  if (operator_id !== void 0 && operator_id !== operatorId) {
7073
7166
  if (!isPlatformOperator(env, props2.org_id)) {
7074
7167
  return err2(
@@ -7129,6 +7222,8 @@ function registerStripeConnectTools(server2, ctx) {
7129
7222
  annotations: annotationsFor("stripe_connect_payouts", "read")
7130
7223
  },
7131
7224
  async ({ limit, status }) => {
7225
+ const denied = denyUnlessScoped(props2, "stripe_connect_payouts", STRIPE_CONNECT_TOOL_SCOPES);
7226
+ if (denied) return denied;
7132
7227
  const start = Date.now();
7133
7228
  try {
7134
7229
  const stripeKey = env.STRIPE_SECRET_KEY;
@@ -7164,6 +7259,8 @@ function registerStripeConnectTools(server2, ctx) {
7164
7259
  annotations: annotationsFor("stripe_connect_balance", "read")
7165
7260
  },
7166
7261
  async () => {
7262
+ const denied = denyUnlessScoped(props2, "stripe_connect_balance", STRIPE_CONNECT_TOOL_SCOPES);
7263
+ if (denied) return denied;
7167
7264
  const start = Date.now();
7168
7265
  try {
7169
7266
  const stripeKey = env.STRIPE_SECRET_KEY;
@@ -7206,6 +7303,8 @@ function registerStripeConnectTools(server2, ctx) {
7206
7303
  annotations: annotationsFor("stripe_connect_refund", "write")
7207
7304
  },
7208
7305
  async ({ charge_id, amount_cents, reason, confirm_token }) => {
7306
+ const denied = denyUnlessScoped(props2, "stripe_connect_refund", STRIPE_CONNECT_TOOL_SCOPES);
7307
+ if (denied) return denied;
7209
7308
  const start = Date.now();
7210
7309
  const toolName = "stripe_connect_refund";
7211
7310
  if (!confirm_token) {
@@ -7258,6 +7357,8 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7258
7357
  annotations: annotationsFor("stripe_connect_dispute_list", "read")
7259
7358
  },
7260
7359
  async ({ limit, status }) => {
7360
+ const denied = denyUnlessScoped(props2, "stripe_connect_dispute_list", STRIPE_CONNECT_TOOL_SCOPES);
7361
+ if (denied) return denied;
7261
7362
  const start = Date.now();
7262
7363
  try {
7263
7364
  const stripeKey = env.STRIPE_SECRET_KEY;
@@ -7296,6 +7397,13 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7296
7397
  annotations: annotationsFor("radar_review_list", "read")
7297
7398
  },
7298
7399
  async ({ open_only, limit }) => {
7400
+ const denied = denyUnlessScoped(props2, "radar_review_list", STRIPE_CONNECT_TOOL_SCOPES);
7401
+ if (denied) return denied;
7402
+ if (!isPlatformOperator(env, props2.org_id)) {
7403
+ return err2(
7404
+ "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7405
+ );
7406
+ }
7299
7407
  const start = Date.now();
7300
7408
  try {
7301
7409
  const stripeKey = env.STRIPE_SECRET_KEY;
@@ -7332,6 +7440,13 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7332
7440
  annotations: annotationsFor("radar_review_approve", "admin")
7333
7441
  },
7334
7442
  async ({ review_id, confirm_token }) => {
7443
+ const denied = denyUnlessScoped(props2, "radar_review_approve", STRIPE_CONNECT_TOOL_SCOPES);
7444
+ if (denied) return denied;
7445
+ if (!isPlatformOperator(env, props2.org_id)) {
7446
+ return err2(
7447
+ "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7448
+ );
7449
+ }
7335
7450
  const toolName = "radar_review_approve";
7336
7451
  const start = Date.now();
7337
7452
  if (!confirm_token) {
@@ -7381,6 +7496,13 @@ Call again with confirm_token="${token2}" to execute. Expires in 5 minutes.`
7381
7496
  annotations: annotationsFor("radar_review_decline", "admin")
7382
7497
  },
7383
7498
  async ({ review_id, confirm_token }) => {
7499
+ const denied = denyUnlessScoped(props2, "radar_review_decline", STRIPE_CONNECT_TOOL_SCOPES);
7500
+ if (denied) return denied;
7501
+ if (!isPlatformOperator(env, props2.org_id)) {
7502
+ return err2(
7503
+ "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7504
+ );
7505
+ }
7384
7506
  const toolName = "radar_review_decline";
7385
7507
  const start = Date.now();
7386
7508
  if (!confirm_token) {
@@ -7431,6 +7553,13 @@ Expires in 5 minutes.`
7431
7553
  annotations: annotationsFor("radar_value_list_add", "admin")
7432
7554
  },
7433
7555
  async ({ value_list_id, value, confirm_token }) => {
7556
+ const denied = denyUnlessScoped(props2, "radar_value_list_add", STRIPE_CONNECT_TOOL_SCOPES);
7557
+ if (denied) return denied;
7558
+ if (!isPlatformOperator(env, props2.org_id)) {
7559
+ return err2(
7560
+ "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7561
+ );
7562
+ }
7434
7563
  const toolName = "radar_value_list_add";
7435
7564
  const start = Date.now();
7436
7565
  if (!confirm_token) {
@@ -7480,6 +7609,13 @@ Expires in 5 minutes.`
7480
7609
  annotations: annotationsFor("radar_rule_toggle", "admin")
7481
7610
  },
7482
7611
  async ({ rule_id, enabled }) => {
7612
+ const denied = denyUnlessScoped(props2, "radar_rule_toggle", STRIPE_CONNECT_TOOL_SCOPES);
7613
+ if (denied) return denied;
7614
+ if (!isPlatformOperator(env, props2.org_id)) {
7615
+ return err2(
7616
+ "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7617
+ );
7618
+ }
7483
7619
  return ok2(
7484
7620
  `Stripe Radar does not expose rule enable/disable via the public API.
7485
7621
  To ${enabled ? "enable" : "disable"} rule ${rule_id}:
@@ -7692,7 +7828,8 @@ function registerStorefrontDeployTools(server2) {
7692
7828
  var TIER_CREDIT_ALLOTMENTS = {
7693
7829
  free: 5e3,
7694
7830
  pro: 1e5,
7695
- enterprise: Infinity
7831
+ enterprise: Infinity,
7832
+ superadmin: Infinity
7696
7833
  };
7697
7834
  var DAILY_FREE_CREDITS = 50;
7698
7835
  var SCOPE_CREDIT_COSTS = {
@@ -7715,7 +7852,7 @@ async function checkCredits(env, sub, tier, scope) {
7715
7852
  const month = currentMonth2();
7716
7853
  const today = currentDay();
7717
7854
  const resetAt = firstDayNextMonth2();
7718
- if (tier === "enterprise") {
7855
+ if (tier === "enterprise" || tier === "superadmin") {
7719
7856
  return {
7720
7857
  allowed: true,
7721
7858
  credits_remaining: Infinity,
@@ -7755,7 +7892,7 @@ function deductCredits(env, sub, tier, scope) {
7755
7892
  const cost = SCOPE_CREDIT_COSTS[scope];
7756
7893
  const month = currentMonth2();
7757
7894
  const today = currentDay();
7758
- if (tier === "enterprise") return;
7895
+ if (tier === "enterprise" || tier === "superadmin") return;
7759
7896
  const dailyFree = await getDailyFreeCredits(env, sub, today);
7760
7897
  const dailyRemaining = dailyFree.granted - dailyFree.consumed;
7761
7898
  if (dailyRemaining >= cost) {
@@ -7961,7 +8098,7 @@ async function resolveUserTier(env, sub) {
7961
8098
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
7962
8099
  if (raw && typeof raw === "object" && "tier" in raw) {
7963
8100
  const t = raw.tier;
7964
- if (t === "pro" || t === "enterprise") return t;
8101
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
7965
8102
  }
7966
8103
  return "free";
7967
8104
  }
@@ -8011,6 +8148,14 @@ var DEFAULT_THRESHOLD_CONFIGS = {
8011
8148
  auto_pause_on_cap: false,
8012
8149
  notification_email: null,
8013
8150
  webhook_url: null
8151
+ },
8152
+ superadmin: {
8153
+ notification_thresholds: [],
8154
+ hard_cap_cents: 0,
8155
+ invoice_threshold_cents: 0,
8156
+ auto_pause_on_cap: false,
8157
+ notification_email: null,
8158
+ webhook_url: null
8014
8159
  }
8015
8160
  };
8016
8161
  async function getThresholdConfig(env, sub, tier) {
@@ -8033,7 +8178,7 @@ async function resolveUserTier2(env, sub) {
8033
8178
  const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
8034
8179
  if (raw && typeof raw === "object" && "tier" in raw) {
8035
8180
  const t = raw.tier;
8036
- if (t === "pro" || t === "enterprise") return t;
8181
+ if (t === "pro" || t === "enterprise" || t === "superadmin") return t;
8037
8182
  }
8038
8183
  return "free";
8039
8184
  }
@@ -9971,7 +10116,82 @@ function buildExamples(toolName) {
9971
10116
  // src/list-recent-ocs-events.ts
9972
10117
  import { z as z12 } from "zod";
9973
10118
 
10119
+ // src/ocs-scoping.ts
10120
+ function managedOwnAccountId(auth) {
10121
+ if (!auth.managed || auth.accountId === void 0) return void 0;
10122
+ const n = Number(auth.accountId);
10123
+ return Number.isFinite(n) ? n : void 0;
10124
+ }
10125
+ function subscriberAccountId(data) {
10126
+ if (!data || typeof data !== "object") return void 0;
10127
+ const root = data;
10128
+ const sub = root.subscriber && typeof root.subscriber === "object" ? root.subscriber : root;
10129
+ const raw = sub.accountId ?? sub.account_id ?? sub.account;
10130
+ if (raw === void 0 || raw === null) return void 0;
10131
+ const n = Number(raw);
10132
+ return Number.isFinite(n) ? n : void 0;
10133
+ }
10134
+ function isManagedSubscriberDenied(auth, subscriberData) {
10135
+ const ownId = managedOwnAccountId(auth);
10136
+ if (ownId === void 0) return false;
10137
+ const subAccountId = subscriberAccountId(subscriberData);
10138
+ if (subAccountId === void 0) return true;
10139
+ return subAccountId !== ownId;
10140
+ }
10141
+ async function assertManagedIccidAccess(baseUrl2, auth, iccid) {
10142
+ if (managedOwnAccountId(auth) === void 0) return { ok: true };
10143
+ try {
10144
+ const client2 = new OcsClient(baseUrl2, auth.token);
10145
+ const data = await client2.call("getSingleSubscriber", { iccid });
10146
+ if (isManagedSubscriberDenied(auth, data)) {
10147
+ return { ok: false, error: "Subscriber not found" };
10148
+ }
10149
+ return { ok: true };
10150
+ } catch (err7) {
10151
+ return {
10152
+ ok: false,
10153
+ error: err7 instanceof Error ? err7.message : String(err7)
10154
+ };
10155
+ }
10156
+ }
10157
+
9974
10158
  // ../../packages/ocs-spec/src/ocs-event-buffer-read.ts
10159
+ function ocsEventRingBufferIccidFromDigitsReference(raw) {
10160
+ const digits = raw.replace(/\D/g, "");
10161
+ if (digits.length === 0) {
10162
+ throw new Error("reference must contain at least one digit");
10163
+ }
10164
+ if (digits.length > 20) {
10165
+ return digits.slice(-20);
10166
+ }
10167
+ if (digits.length >= 19) {
10168
+ return digits;
10169
+ }
10170
+ return digits.padStart(19, "0");
10171
+ }
10172
+ function msisdnFieldFromSubscriberPayload(data) {
10173
+ if (!data || typeof data !== "object") return null;
10174
+ const r = data;
10175
+ const v = r["msisdn"] ?? r["MSISDN"];
10176
+ if (v === void 0 || v === null) return null;
10177
+ if (typeof v === "string") return /\d/.test(v) ? v : null;
10178
+ if (typeof v === "number") return String(v);
10179
+ return null;
10180
+ }
10181
+ function mergeOcsEventOutputsNewestFirst(parts, limit) {
10182
+ const seen = /* @__PURE__ */ new Set();
10183
+ const out = [];
10184
+ const combined = [...parts].flat().sort(
10185
+ (x, y) => x.timestamp < y.timestamp ? 1 : x.timestamp > y.timestamp ? -1 : x.event_id.localeCompare(y.event_id)
10186
+ );
10187
+ for (const e of combined) {
10188
+ if (seen.has(e.event_id)) continue;
10189
+ seen.add(e.event_id);
10190
+ out.push(e);
10191
+ if (out.length >= limit) break;
10192
+ }
10193
+ return out;
10194
+ }
9975
10195
  async function listRecentOcsEvents(iccid, limit, eventTypes, since, kv) {
9976
10196
  const routingRaw = await kv.get(`iccid:${iccid}`, "json");
9977
10197
  const resellerId = routingRaw?.reseller_id ?? 0;
@@ -10054,7 +10274,8 @@ var listRecentOcsEventsSchema = {
10054
10274
  async function listRecentOcsEvents2(iccid, limit, eventTypes, since, kv) {
10055
10275
  return listRecentOcsEvents(iccid, limit, eventTypes, since, kv);
10056
10276
  }
10057
- function registerListRecentOcsEventsTool(server2, env) {
10277
+ function registerListRecentOcsEventsTool(server2, ctx) {
10278
+ const env = ctx.env;
10058
10279
  server2.registerTool(
10059
10280
  "list_recent_ocs_events",
10060
10281
  {
@@ -10070,6 +10291,19 @@ function registerListRecentOcsEventsTool(server2, env) {
10070
10291
  async (args) => {
10071
10292
  const { iccid, limit, event_types, since } = args;
10072
10293
  try {
10294
+ if (managedOwnAccountId(ctx.managedAuth) !== void 0) {
10295
+ const access = await assertManagedIccidAccess(
10296
+ env.CARRIER_OCS_BASE_URL,
10297
+ { token: await ctx.getUserToken(ctx.props.sub), ...ctx.managedAuth },
10298
+ iccid
10299
+ );
10300
+ if (!access.ok) {
10301
+ return {
10302
+ isError: true,
10303
+ content: [{ type: "text", text: access.error }]
10304
+ };
10305
+ }
10306
+ }
10073
10307
  const result2 = await listRecentOcsEvents2(
10074
10308
  iccid,
10075
10309
  limit,
@@ -10187,6 +10421,20 @@ function ok5(payload) {
10187
10421
  function err5(message) {
10188
10422
  return { isError: true, content: [{ type: "text", text: message }] };
10189
10423
  }
10424
+ async function ownershipToken(ctx) {
10425
+ if (managedOwnAccountId(ctx.managedAuth) === void 0) return null;
10426
+ return ctx.getUserToken(ctx.props.sub);
10427
+ }
10428
+ async function ownsEvent(ctx, token2, iccid) {
10429
+ if (token2 === null) return true;
10430
+ if (!iccid) return false;
10431
+ const access = await assertManagedIccidAccess(
10432
+ ctx.env.CARRIER_OCS_BASE_URL,
10433
+ { token: token2, ...ctx.managedAuth },
10434
+ iccid
10435
+ );
10436
+ return access.ok;
10437
+ }
10190
10438
  function registerDepletionEventsTool(server2, ctx) {
10191
10439
  server2.registerTool(
10192
10440
  "subscriber_depletion_events",
@@ -10216,6 +10464,14 @@ function registerDepletionEventsTool(server2, ctx) {
10216
10464
  );
10217
10465
  }
10218
10466
  const { subscriberId, since } = args;
10467
+ let scopeToken;
10468
+ try {
10469
+ scopeToken = await ownershipToken(ctx);
10470
+ } catch (e) {
10471
+ return err5(
10472
+ `Could not resolve the OCS credential needed to scope this read: ${e instanceof Error ? e.message : String(e)}`
10473
+ );
10474
+ }
10219
10475
  if (subscriberId) {
10220
10476
  const raw = await kv.get(`bundle-depleted:${subscriberId}`, "text");
10221
10477
  if (!raw) {
@@ -10237,6 +10493,14 @@ function registerDepletionEventsTool(server2, ctx) {
10237
10493
  note: "Stored depletion entry was invalid JSON and was ignored."
10238
10494
  });
10239
10495
  }
10496
+ if (!await ownsEvent(ctx, scopeToken, event.iccid)) {
10497
+ return ok5({
10498
+ subscriber_id: subscriberId,
10499
+ depleted: false,
10500
+ event: null,
10501
+ note: "No bundle depletion event found in the last 7 days."
10502
+ });
10503
+ }
10240
10504
  if (since && event.depletedAt < since) {
10241
10505
  return ok5({
10242
10506
  subscriber_id: subscriberId,
@@ -10273,6 +10537,7 @@ function registerDepletionEventsTool(server2, ctx) {
10273
10537
  try {
10274
10538
  const ev = JSON.parse(raw);
10275
10539
  if (since && ev.depletedAt < since) return;
10540
+ if (!await ownsEvent(ctx, scopeToken, ev.iccid)) return;
10276
10541
  events.push({
10277
10542
  subscriber_id: ev.subscriberId,
10278
10543
  iccid: ev.iccid,
@@ -10310,19 +10575,46 @@ function registerCountryHistoryTool(server2, ctx) {
10310
10575
  },
10311
10576
  wrapHandler(
10312
10577
  "subscriber_country_history",
10313
- "[country-history:kv]",
10578
+ "getSingleSubscriber",
10314
10579
  "read",
10315
10580
  ctx,
10316
- async ({ subscriberId, limit }) => {
10581
+ async ({ subscriberId, limit }, token2) => {
10317
10582
  const effectiveLimit = limit ?? 20;
10318
- const result2 = await listRecentOcsEvents2(
10583
+ const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
10584
+ const subscriberPayload = await client2.call("getSingleSubscriber", {
10585
+ iccid: subscriberId
10586
+ });
10587
+ let syntheticRingIccid = null;
10588
+ const msisdnRaw = msisdnFieldFromSubscriberPayload(subscriberPayload);
10589
+ if (msisdnRaw) {
10590
+ try {
10591
+ const k = ocsEventRingBufferIccidFromDigitsReference(msisdnRaw);
10592
+ if (k !== subscriberId) syntheticRingIccid = k;
10593
+ } catch {
10594
+ syntheticRingIccid = null;
10595
+ }
10596
+ }
10597
+ const primary = await listRecentOcsEvents(
10319
10598
  subscriberId,
10320
10599
  effectiveLimit,
10321
10600
  ["country.entered"],
10322
10601
  void 0,
10323
10602
  ctx.env.OCS_EVENT_ROUTING
10324
10603
  );
10325
- const countryEvents = result2.events;
10604
+ let countryEvents = primary.events;
10605
+ if (syntheticRingIccid) {
10606
+ const secondary = await listRecentOcsEvents(
10607
+ syntheticRingIccid,
10608
+ effectiveLimit,
10609
+ ["country.entered"],
10610
+ void 0,
10611
+ ctx.env.OCS_EVENT_ROUTING
10612
+ );
10613
+ countryEvents = mergeOcsEventOutputsNewestFirst(
10614
+ [primary.events, secondary.events],
10615
+ effectiveLimit
10616
+ );
10617
+ }
10326
10618
  return {
10327
10619
  content: [
10328
10620
  {
@@ -10340,7 +10632,7 @@ function registerCountryHistoryTool(server2, ctx) {
10340
10632
  source: e.data["source"] ?? "relay-lu"
10341
10633
  })),
10342
10634
  total_country_events: countryEvents.length,
10343
- total_in_buffer: result2.total_in_buffer,
10635
+ total_in_buffer: primary.total_in_buffer,
10344
10636
  note: countryEvents.length === 0 ? "No cross-border events recorded in the 24h ring-buffer for this subscriber." : void 0
10345
10637
  },
10346
10638
  null,
@@ -11906,7 +12198,13 @@ var props = {
11906
12198
  var audit = (_row) => {
11907
12199
  };
11908
12200
  var getUserToken = async (_sub) => token;
11909
- var toolCtx = { env: stdioEnv, props, audit, getUserToken };
12201
+ var toolCtx = {
12202
+ env: stdioEnv,
12203
+ props,
12204
+ managedAuth: {},
12205
+ audit,
12206
+ getUserToken
12207
+ };
11910
12208
  var server = new McpServer(
11911
12209
  { name: "carrier-mcp", version: CARRIER_VERSION },
11912
12210
  {
@@ -11918,7 +12216,7 @@ registerAllTools(server, toolCtx);
11918
12216
  registerIntelligenceTools(server, toolCtx);
11919
12217
  registerAllBacklogTools(server, toolCtx);
11920
12218
  registerAllCarrierAskTools(server, toolCtx);
11921
- registerListRecentOcsEventsTool(server, stdioEnv);
12219
+ registerListRecentOcsEventsTool(server, toolCtx);
11922
12220
  registerRateLimitStatusTool(server, toolCtx);
11923
12221
  registerCountryHistoryTool(server, toolCtx);
11924
12222
  registerDepletionEventsTool(server, toolCtx);