@carrierllc/mcp 0.3.0 → 0.3.1

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
@@ -10,7 +10,7 @@ import {
10
10
  networkEventsOverPeriodParams,
11
11
  subscriberIdParams,
12
12
  usageOverPeriodParams
13
- } from "./chunk-ZII4CD4U.js";
13
+ } from "./chunk-TABEJ7SM.js";
14
14
 
15
15
  // src/index.ts
16
16
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -310,9 +310,9 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
310
310
  try {
311
311
  const token2 = await ctx.getUserToken(ctx.props.sub);
312
312
  result2 = await handler(args, token2);
313
- } catch (err5) {
313
+ } catch (err6) {
314
314
  try {
315
- Sentry.captureException(err5, {
315
+ Sentry.captureException(err6, {
316
316
  tags: {
317
317
  tool: toolName,
318
318
  feature: "mcp",
@@ -321,8 +321,8 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler) {
321
321
  });
322
322
  } catch {
323
323
  }
324
- const message = err5 instanceof Error ? err5.message : String(err5);
325
- const ocsCode = err5 instanceof OcsApiError ? err5.code : void 0;
324
+ const message = err6 instanceof Error ? err6.message : String(err6);
325
+ const ocsCode = err6 instanceof OcsApiError ? err6.code : void 0;
326
326
  ctx.audit({
327
327
  tool_name: toolName,
328
328
  ocs_method: ocsMethod,
@@ -3042,8 +3042,8 @@ async function safeCall(env, token2, method, params = {}) {
3042
3042
  const client = new OcsClient(env.CARRIER_OCS_BASE_URL, token2);
3043
3043
  const data = await client.call(method, params);
3044
3044
  return { data, error: null };
3045
- } catch (err5) {
3046
- return { data: null, error: err5 instanceof Error ? err5.message : String(err5) };
3045
+ } catch (err6) {
3046
+ return { data: null, error: err6 instanceof Error ? err6.message : String(err6) };
3047
3047
  }
3048
3048
  }
3049
3049
  async function fetchActiveSubscribers(env, token2, accountId, resellerId) {
@@ -4925,10 +4925,10 @@ async function callBedrock(env, payload) {
4925
4925
  const body = await resp.text();
4926
4926
  if (resp.status === 429) {
4927
4927
  const retryAfter = resp.headers.get("retry-after");
4928
- const err5 = new Error(`Bedrock rate limit: ${body}`);
4929
- err5.isRateLimit = true;
4930
- err5.retryAfter = retryAfter;
4931
- throw err5;
4928
+ const err6 = new Error(`Bedrock rate limit: ${body}`);
4929
+ err6.isRateLimit = true;
4930
+ err6.retryAfter = retryAfter;
4931
+ throw err6;
4932
4932
  }
4933
4933
  throw new Error(`Bedrock invoke failed: ${resp.status} ${body}`);
4934
4934
  }
@@ -4966,9 +4966,9 @@ Pre-resolved context: ${JSON.stringify(definedContext)}` : "";
4966
4966
  AWS_REGION: env.AWS_REGION,
4967
4967
  BEDROCK_MODEL_ID: env.BEDROCK_MODEL_ID
4968
4968
  }, payload);
4969
- } catch (err5) {
4970
- if (err5 instanceof Error && err5.isRateLimit) {
4971
- const retryAfter = err5.retryAfter;
4969
+ } catch (err6) {
4970
+ if (err6 instanceof Error && err6.isRateLimit) {
4971
+ const retryAfter = err6.retryAfter;
4972
4972
  const parsedSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
4973
4973
  return {
4974
4974
  match: "rate_limited",
@@ -4976,7 +4976,7 @@ Pre-resolved context: ${JSON.stringify(definedContext)}` : "";
4976
4976
  suggestion: "Bedrock rate limit reached. Please retry after the indicated delay."
4977
4977
  };
4978
4978
  }
4979
- throw err5;
4979
+ throw err6;
4980
4980
  }
4981
4981
  const toolUseBlock = response.content.find(
4982
4982
  (block) => block.type === "tool_use"
@@ -5150,7 +5150,7 @@ function registerAllCarrierAskTools(server2, ctx) {
5150
5150
  let route;
5151
5151
  try {
5152
5152
  route = await _routeIntent(intent, context, ctx.env);
5153
- } catch (err5) {
5153
+ } catch (err6) {
5154
5154
  writeCarrierAskAudit(ctx.env, {
5155
5155
  intent_hash: intentHash,
5156
5156
  match: "error",
@@ -5167,7 +5167,7 @@ function registerAllCarrierAskTools(server2, ctx) {
5167
5167
  type: "text",
5168
5168
  text: JSON.stringify({
5169
5169
  error: "routing_error",
5170
- message: err5 instanceof Error ? err5.message : "An unexpected error occurred during routing."
5170
+ message: err6 instanceof Error ? err6.message : "An unexpected error occurred during routing."
5171
5171
  })
5172
5172
  }
5173
5173
  ],
@@ -5419,8 +5419,8 @@ function registerListRecentOcsEventsTool(server2, env) {
5419
5419
  return {
5420
5420
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
5421
5421
  };
5422
- } catch (err5) {
5423
- const message = err5 instanceof Error ? err5.message : String(err5);
5422
+ } catch (err6) {
5423
+ const message = err6 instanceof Error ? err6.message : String(err6);
5424
5424
  return {
5425
5425
  isError: true,
5426
5426
  content: [{ type: "text", text: `Error reading OCS event buffer: ${message}` }]
@@ -5703,10 +5703,10 @@ import {
5703
5703
  async function safeCallWithToken(client, _token, method, params = {}) {
5704
5704
  try {
5705
5705
  return { data: await client.call(method, params), error: null };
5706
- } catch (err5) {
5706
+ } catch (err6) {
5707
5707
  return {
5708
5708
  data: null,
5709
- error: err5 instanceof Error ? err5.message : String(err5)
5709
+ error: err6 instanceof Error ? err6.message : String(err6)
5710
5710
  };
5711
5711
  }
5712
5712
  }
@@ -5904,10 +5904,10 @@ async function deleteWizardSession(env, sub, wizardId) {
5904
5904
  async function safeCallWithToken2(client, _token, method, params = {}) {
5905
5905
  try {
5906
5906
  return { data: await client.call(method, params), error: null };
5907
- } catch (err5) {
5907
+ } catch (err6) {
5908
5908
  return {
5909
5909
  data: null,
5910
- error: err5 instanceof Error ? err5.message : String(err5)
5910
+ error: err6 instanceof Error ? err6.message : String(err6)
5911
5911
  };
5912
5912
  }
5913
5913
  }
@@ -6267,10 +6267,10 @@ import {
6267
6267
  async function safeCallWithToken3(client, _token, method, params = {}) {
6268
6268
  try {
6269
6269
  return { data: await client.call(method, params), error: null };
6270
- } catch (err5) {
6270
+ } catch (err6) {
6271
6271
  return {
6272
6272
  data: null,
6273
- error: err5 instanceof Error ? err5.message : String(err5)
6273
+ error: err6 instanceof Error ? err6.message : String(err6)
6274
6274
  };
6275
6275
  }
6276
6276
  }
@@ -7931,8 +7931,8 @@ function buildPricingHandler(tool, ctx) {
7931
7931
  }
7932
7932
  ]
7933
7933
  };
7934
- } catch (err5) {
7935
- const message = err5 instanceof Error ? err5.message : String(err5);
7934
+ } catch (err6) {
7935
+ const message = err6 instanceof Error ? err6.message : String(err6);
7936
7936
  ctx.audit({
7937
7937
  tool_name: tool.name,
7938
7938
  ocs_method: "billing",
@@ -8252,8 +8252,8 @@ function writeUsageThresholdAudit(env, severity, remainingCredits, month, taskCo
8252
8252
  doubles: [remainingCredits],
8253
8253
  indexes: ["manus_usage"]
8254
8254
  });
8255
- } catch (err5) {
8256
- console.error(`[manus-usage] threshold audit write failed: ${err5.message}`);
8255
+ } catch (err6) {
8256
+ console.error(`[manus-usage] threshold audit write failed: ${err6.message}`);
8257
8257
  }
8258
8258
  }
8259
8259
 
@@ -8363,7 +8363,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8363
8363
  }
8364
8364
  ]
8365
8365
  };
8366
- } catch (err5) {
8366
+ } catch (err6) {
8367
8367
  ctx.audit({
8368
8368
  tool_name: "ui_agent_schedule_create",
8369
8369
  ocs_method: "[manus:schedule.create]",
@@ -8372,7 +8372,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8372
8372
  duration_ms: Date.now() - start,
8373
8373
  event_type: "ui_agent_dispatch"
8374
8374
  });
8375
- const msg = err5 instanceof ManusScheduleError ? `Manus API error (HTTP ${err5.statusCode}): ${err5.message}` : err5 instanceof Error ? err5.message : String(err5);
8375
+ const msg = err6 instanceof ManusScheduleError ? `Manus API error (HTTP ${err6.statusCode}): ${err6.message}` : err6 instanceof Error ? err6.message : String(err6);
8376
8376
  return { isError: true, content: [{ type: "text", text: msg }] };
8377
8377
  }
8378
8378
  }
@@ -8430,7 +8430,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8430
8430
  }
8431
8431
  ]
8432
8432
  };
8433
- } catch (err5) {
8433
+ } catch (err6) {
8434
8434
  ctx.audit({
8435
8435
  tool_name: "ui_agent_schedule_list",
8436
8436
  ocs_method: "[manus:schedule.list]",
@@ -8438,7 +8438,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8438
8438
  dry_run: false,
8439
8439
  duration_ms: Date.now() - start
8440
8440
  });
8441
- const msg = err5 instanceof ManusScheduleError ? `Manus API error (HTTP ${err5.statusCode}): ${err5.message}` : err5 instanceof Error ? err5.message : String(err5);
8441
+ const msg = err6 instanceof ManusScheduleError ? `Manus API error (HTTP ${err6.statusCode}): ${err6.message}` : err6 instanceof Error ? err6.message : String(err6);
8442
8442
  return { isError: true, content: [{ type: "text", text: msg }] };
8443
8443
  }
8444
8444
  }
@@ -8499,7 +8499,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8499
8499
  }
8500
8500
  ]
8501
8501
  };
8502
- } catch (err5) {
8502
+ } catch (err6) {
8503
8503
  ctx.audit({
8504
8504
  tool_name: "ui_agent_schedule_delete",
8505
8505
  ocs_method: "[manus:schedule.delete]",
@@ -8508,7 +8508,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8508
8508
  duration_ms: Date.now() - start,
8509
8509
  event_type: "ui_agent_dispatch"
8510
8510
  });
8511
- const msg = err5 instanceof ManusScheduleError ? `Manus API error (HTTP ${err5.statusCode}): ${err5.message}` : err5 instanceof Error ? err5.message : String(err5);
8511
+ const msg = err6 instanceof ManusScheduleError ? `Manus API error (HTTP ${err6.statusCode}): ${err6.message}` : err6 instanceof Error ? err6.message : String(err6);
8512
8512
  return { isError: true, content: [{ type: "text", text: msg }] };
8513
8513
  }
8514
8514
  }
@@ -8569,7 +8569,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8569
8569
  }
8570
8570
  ]
8571
8571
  };
8572
- } catch (err5) {
8572
+ } catch (err6) {
8573
8573
  ctx.audit({
8574
8574
  tool_name: "ui_agent_schedule_pause",
8575
8575
  ocs_method: "[manus:schedule.pause]",
@@ -8578,7 +8578,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8578
8578
  duration_ms: Date.now() - start,
8579
8579
  event_type: "ui_agent_dispatch"
8580
8580
  });
8581
- const msg = err5 instanceof ManusScheduleError ? `Manus API error (HTTP ${err5.statusCode}): ${err5.message}` : err5 instanceof Error ? err5.message : String(err5);
8581
+ const msg = err6 instanceof ManusScheduleError ? `Manus API error (HTTP ${err6.statusCode}): ${err6.message}` : err6 instanceof Error ? err6.message : String(err6);
8582
8582
  return { isError: true, content: [{ type: "text", text: msg }] };
8583
8583
  }
8584
8584
  }
@@ -8639,7 +8639,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8639
8639
  }
8640
8640
  ]
8641
8641
  };
8642
- } catch (err5) {
8642
+ } catch (err6) {
8643
8643
  ctx.audit({
8644
8644
  tool_name: "ui_agent_schedule_resume",
8645
8645
  ocs_method: "[manus:schedule.resume]",
@@ -8648,7 +8648,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8648
8648
  duration_ms: Date.now() - start,
8649
8649
  event_type: "ui_agent_dispatch"
8650
8650
  });
8651
- const msg = err5 instanceof ManusScheduleError ? `Manus API error (HTTP ${err5.statusCode}): ${err5.message}` : err5 instanceof Error ? err5.message : String(err5);
8651
+ const msg = err6 instanceof ManusScheduleError ? `Manus API error (HTTP ${err6.statusCode}): ${err6.message}` : err6 instanceof Error ? err6.message : String(err6);
8652
8652
  return { isError: true, content: [{ type: "text", text: msg }] };
8653
8653
  }
8654
8654
  }
@@ -8693,7 +8693,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8693
8693
  }
8694
8694
  ]
8695
8695
  };
8696
- } catch (err5) {
8696
+ } catch (err6) {
8697
8697
  ctx.audit({
8698
8698
  tool_name: "ui_agent_usage",
8699
8699
  ocs_method: "[manus:usage.get]",
@@ -8701,7 +8701,7 @@ function registerScheduleAndUsageTools(server2, ctx) {
8701
8701
  dry_run: false,
8702
8702
  duration_ms: Date.now() - start
8703
8703
  });
8704
- const msg = err5 instanceof Error ? err5.message : String(err5);
8704
+ const msg = err6 instanceof Error ? err6.message : String(err6);
8705
8705
  return { isError: true, content: [{ type: "text", text: msg }] };
8706
8706
  }
8707
8707
  }
@@ -8944,7 +8944,7 @@ Report success or failure with a clear summary.`;
8944
8944
  }
8945
8945
  ]
8946
8946
  };
8947
- } catch (err5) {
8947
+ } catch (err6) {
8948
8948
  ctx.audit({
8949
8949
  tool_name: toolName,
8950
8950
  ocs_method: `[ui-agent:${gapId}]`,
@@ -8958,7 +8958,7 @@ Report success or failure with a clear summary.`;
8958
8958
  content: [
8959
8959
  {
8960
8960
  type: "text",
8961
- text: `Error dispatching UI agent: ${err5 instanceof Error ? err5.message : String(err5)}`
8961
+ text: `Error dispatching UI agent: ${err6 instanceof Error ? err6.message : String(err6)}`
8962
8962
  }
8963
8963
  ]
8964
8964
  };
@@ -9382,7 +9382,7 @@ function registerUiAgentAskTools(server2, ctx) {
9382
9382
  try {
9383
9383
  const replyOutcome = await askReply(keys, task_id, reply);
9384
9384
  result2 = replyOutcome.data;
9385
- } catch (err5) {
9385
+ } catch (err6) {
9386
9386
  ctx.audit({
9387
9387
  tool_name: "ui_agent_reply",
9388
9388
  ocs_method: "[ui-agent:ask-reply]",
@@ -9397,7 +9397,7 @@ function registerUiAgentAskTools(server2, ctx) {
9397
9397
  content: [
9398
9398
  {
9399
9399
  type: "text",
9400
- text: `Error calling Manus task.reply: ${err5 instanceof Error ? err5.message : String(err5)}`
9400
+ text: `Error calling Manus task.reply: ${err6 instanceof Error ? err6.message : String(err6)}`
9401
9401
  }
9402
9402
  ]
9403
9403
  };
@@ -9475,13 +9475,13 @@ function registerUiAgentAskTools(server2, ctx) {
9475
9475
  try {
9476
9476
  const listing = await ctx.env.CARRIER_USERS.list({ prefix: PENDING_ASK_PREFIX });
9477
9477
  keys = listing.keys;
9478
- } catch (err5) {
9478
+ } catch (err6) {
9479
9479
  return {
9480
9480
  isError: true,
9481
9481
  content: [
9482
9482
  {
9483
9483
  type: "text",
9484
- text: `Error listing pending tasks: ${err5 instanceof Error ? err5.message : String(err5)}`
9484
+ text: `Error listing pending tasks: ${err6 instanceof Error ? err6.message : String(err6)}`
9485
9485
  }
9486
9486
  ]
9487
9487
  };
@@ -9989,8 +9989,407 @@ Note: If you need this automated, use the Radar for Platforms beta \u2014 contac
9989
9989
  );
9990
9990
  }
9991
9991
 
9992
- // src/tools-greenzone.ts
9992
+ // src/wallet-tools.ts
9993
9993
  import { z as z18 } from "zod";
9994
+ import * as Sentry3 from "@sentry/cloudflare";
9995
+
9996
+ // src/wallet-client.ts
9997
+ var DEFAULT_ATLAS_BASE_URL = "https://atlas.carrier.llc";
9998
+ var WalletClientError = class extends Error {
9999
+ status;
10000
+ constructor(message, status) {
10001
+ super(message);
10002
+ this.name = "WalletClientError";
10003
+ this.status = status;
10004
+ }
10005
+ };
10006
+ function resolveConfig(env) {
10007
+ const key = env.CARRIER_INTERNAL_API_KEY;
10008
+ if (!key) {
10009
+ throw new WalletClientError(
10010
+ "CARRIER_INTERNAL_API_KEY not configured \u2014 cannot reach ATLAS wallet",
10011
+ 0
10012
+ );
10013
+ }
10014
+ const baseUrl2 = (env.ATLAS_BASE_URL ?? DEFAULT_ATLAS_BASE_URL).replace(
10015
+ /\/+$/,
10016
+ ""
10017
+ );
10018
+ return { baseUrl: baseUrl2, key };
10019
+ }
10020
+ async function atlasFetch(env, path, init) {
10021
+ const { baseUrl: baseUrl2, key } = resolveConfig(env);
10022
+ const headers = { "X-Carrier-Internal-Key": key };
10023
+ let body;
10024
+ if (init.body !== void 0) {
10025
+ headers["Content-Type"] = "application/json";
10026
+ body = JSON.stringify(init.body);
10027
+ }
10028
+ let res;
10029
+ try {
10030
+ res = await fetch(`${baseUrl2}${path}`, { method: init.method, headers, body });
10031
+ } catch (e) {
10032
+ throw new WalletClientError(
10033
+ `ATLAS wallet request failed: ${e instanceof Error ? e.message : "network error"}`,
10034
+ 0
10035
+ );
10036
+ }
10037
+ if (!res.ok) {
10038
+ const detail = await res.text().catch(() => "");
10039
+ throw new WalletClientError(
10040
+ `ATLAS wallet ${init.method} ${path} -> HTTP ${res.status}${detail ? `: ${detail}` : ""}`,
10041
+ res.status
10042
+ );
10043
+ }
10044
+ return await res.json();
10045
+ }
10046
+ async function walletTopup(env, orgId, args) {
10047
+ return atlasFetch(
10048
+ env,
10049
+ `/internal/wallet/${encodeURIComponent(orgId)}/topup`,
10050
+ {
10051
+ method: "POST",
10052
+ body: {
10053
+ eur_cents: args.eurCents,
10054
+ ref: args.ref,
10055
+ ...args.type ? { type: args.type } : {}
10056
+ }
10057
+ }
10058
+ );
10059
+ }
10060
+ async function getWallet(env, orgId) {
10061
+ return atlasFetch(env, `/internal/wallet/${encodeURIComponent(orgId)}`, {
10062
+ method: "GET"
10063
+ });
10064
+ }
10065
+
10066
+ // src/billing-sub.ts
10067
+ async function resolveBillingSub(env, orgId) {
10068
+ if (!env.CARRIER_USERS) return orgId;
10069
+ const pointer = await env.CARRIER_USERS.get(
10070
+ `org:${orgId}:billing_owner_sub`
10071
+ ).catch(() => null);
10072
+ return pointer ?? orgId;
10073
+ }
10074
+
10075
+ // src/wallet-tools.ts
10076
+ var PACKS = [
10077
+ { id: "pack_500", eurCents: 5e4, bonusPct: 0 },
10078
+ { id: "pack_1000", eurCents: 1e5, bonusPct: 3 },
10079
+ { id: "pack_2500", eurCents: 25e4, bonusPct: 6 },
10080
+ { id: "pack_5000", eurCents: 5e5, bonusPct: 10 }
10081
+ ];
10082
+ function bonusCentsFor(pack) {
10083
+ return Math.floor(pack.eurCents * pack.bonusPct / 100);
10084
+ }
10085
+ function resolvePack(idOrCents) {
10086
+ if (typeof idOrCents === "number") {
10087
+ return PACKS.find((p) => p.eurCents === idOrCents);
10088
+ }
10089
+ return PACKS.find((p) => p.id === idOrCents) ?? PACKS.find((p) => String(p.eurCents) === idOrCents);
10090
+ }
10091
+ var STRIPE_API = "https://api.stripe.com";
10092
+ async function stripePostForm(stripeKey, path, params, opts) {
10093
+ const headers = {
10094
+ Authorization: `Bearer ${stripeKey}`,
10095
+ "Content-Type": "application/x-www-form-urlencoded"
10096
+ };
10097
+ if (opts?.idempotencyKey) {
10098
+ headers["Idempotency-Key"] = opts.idempotencyKey;
10099
+ }
10100
+ const res = await fetch(`${STRIPE_API}${path}`, {
10101
+ method: "POST",
10102
+ headers,
10103
+ body: params.toString()
10104
+ });
10105
+ const data = await res.json();
10106
+ return { ok: res.ok, status: res.status, data };
10107
+ }
10108
+ var DEFAULT_SUCCESS_URL = "https://app.carrier.llc/billing?topup=success";
10109
+ var DEFAULT_CANCEL_URL = "https://app.carrier.llc/billing?topup=cancelled";
10110
+ async function createTopupCheckout(env, args) {
10111
+ const stripeKey = env.STRIPE_SECRET_KEY;
10112
+ if (!stripeKey) throw new Error("Stripe not configured (STRIPE_SECRET_KEY absent)");
10113
+ const bonusCents = bonusCentsFor(args.pack);
10114
+ const successUrl = args.successUrl ?? env.WALLET_CHECKOUT_SUCCESS_URL ?? DEFAULT_SUCCESS_URL;
10115
+ const cancelUrl = args.cancelUrl ?? env.WALLET_CHECKOUT_CANCEL_URL ?? DEFAULT_CANCEL_URL;
10116
+ const bonusLabel = bonusCents > 0 ? ` (+\u20AC${(bonusCents / 100).toFixed(2)} bonus)` : "";
10117
+ const billingSub = await resolveBillingSub(env, args.orgId);
10118
+ const existingCustomer = await env.CARRIER_USERS.get(`stripe_customer_id:${billingSub}`).catch(() => null);
10119
+ const params = new URLSearchParams({
10120
+ mode: "payment",
10121
+ success_url: successUrl,
10122
+ cancel_url: cancelUrl,
10123
+ "line_items[0][quantity]": "1",
10124
+ "line_items[0][price_data][currency]": "eur",
10125
+ "line_items[0][price_data][unit_amount]": String(args.pack.eurCents),
10126
+ "line_items[0][price_data][product_data][name]": `Carrier wallet top-up \u2014 \u20AC${(args.pack.eurCents / 100).toFixed(2)}${bonusLabel}`,
10127
+ client_reference_id: args.orgId,
10128
+ "metadata[kind]": "wallet_topup",
10129
+ "metadata[org_id]": args.orgId,
10130
+ "metadata[pack_id]": args.pack.id,
10131
+ "metadata[pack_eur_cents]": String(args.pack.eurCents),
10132
+ "metadata[bonus_eur_cents]": String(bonusCents),
10133
+ // Save the payment method for reuse so subsequent off-session auto-top-up
10134
+ // charges (runAutoTopup) can charge it without the customer present.
10135
+ "payment_intent_data[setup_future_usage]": "off_session",
10136
+ // Mirror metadata onto the resulting PaymentIntent so the
10137
+ // payment_intent.succeeded webhook can also credit idempotently.
10138
+ "payment_intent_data[metadata][kind]": "wallet_topup",
10139
+ "payment_intent_data[metadata][org_id]": args.orgId,
10140
+ "payment_intent_data[metadata][pack_eur_cents]": String(args.pack.eurCents),
10141
+ "payment_intent_data[metadata][bonus_eur_cents]": String(bonusCents)
10142
+ });
10143
+ if (existingCustomer) {
10144
+ params.set("customer", existingCustomer);
10145
+ } else {
10146
+ params.set("customer_creation", "always");
10147
+ }
10148
+ const result2 = await stripePostForm(
10149
+ stripeKey,
10150
+ "/v1/checkout/sessions",
10151
+ params
10152
+ );
10153
+ if (!result2.ok) {
10154
+ throw new Error(`Stripe checkout session create failed: ${JSON.stringify(result2.data)}`);
10155
+ }
10156
+ return { id: result2.data.id, url: result2.data.url };
10157
+ }
10158
+ async function runAutoTopup(env, args) {
10159
+ const stripeKey = env.STRIPE_SECRET_KEY;
10160
+ if (!stripeKey) {
10161
+ return { charged: false, creditedCents: 0, reason: "stripe_not_configured" };
10162
+ }
10163
+ let packCents = args.packCents;
10164
+ try {
10165
+ const wallet = await getWallet(env, args.orgId);
10166
+ if (packCents === void 0) packCents = wallet.auto_topup_pack_cents;
10167
+ } catch (e) {
10168
+ if (e instanceof WalletClientError) {
10169
+ return { charged: false, creditedCents: 0, reason: `wallet_unreachable: ${e.message}` };
10170
+ }
10171
+ throw e;
10172
+ }
10173
+ if (!packCents || packCents <= 0) {
10174
+ return { charged: false, creditedCents: 0, reason: "no_pack_configured" };
10175
+ }
10176
+ const pack = resolvePack(packCents);
10177
+ if (!pack) {
10178
+ return { charged: false, creditedCents: 0, reason: `unknown_pack: ${packCents}` };
10179
+ }
10180
+ if (!env.CARRIER_USERS) {
10181
+ return { charged: false, creditedCents: 0, reason: "no_payment_method" };
10182
+ }
10183
+ const customerId = await env.CARRIER_USERS.get(`stripe_customer_id:${args.sub}`).catch(
10184
+ () => null
10185
+ );
10186
+ const pmId = await env.CARRIER_USERS.get(`stripe_pm_id:${args.sub}`).catch(() => null);
10187
+ if (!customerId || !pmId) {
10188
+ return { charged: false, creditedCents: 0, reason: "no_payment_method" };
10189
+ }
10190
+ const bonusCents = bonusCentsFor(pack);
10191
+ const body = new URLSearchParams({
10192
+ amount: String(pack.eurCents),
10193
+ currency: "eur",
10194
+ customer: customerId,
10195
+ payment_method: pmId,
10196
+ confirm: "true",
10197
+ off_session: "true",
10198
+ description: `Carrier wallet auto-top-up \u2014 \u20AC${(pack.eurCents / 100).toFixed(2)}`,
10199
+ "metadata[kind]": "wallet_topup",
10200
+ "metadata[org_id]": args.orgId,
10201
+ "metadata[pack_eur_cents]": String(pack.eurCents),
10202
+ "metadata[bonus_eur_cents]": String(bonusCents),
10203
+ "metadata[auto_topup]": "true"
10204
+ });
10205
+ const resp = await stripePostForm(
10206
+ stripeKey,
10207
+ "/v1/payment_intents",
10208
+ body
10209
+ );
10210
+ if (!resp.ok) {
10211
+ return {
10212
+ charged: false,
10213
+ creditedCents: 0,
10214
+ reason: `stripe_error: ${JSON.stringify(resp.data)}`
10215
+ };
10216
+ }
10217
+ if (resp.data.status !== "succeeded") {
10218
+ return {
10219
+ charged: false,
10220
+ creditedCents: 0,
10221
+ reason: `payment_status: ${resp.data.status}`,
10222
+ paymentIntentId: resp.data.id
10223
+ };
10224
+ }
10225
+ const creditedCents = pack.eurCents + bonusCents;
10226
+ try {
10227
+ const topup = await walletTopup(env, args.orgId, {
10228
+ eurCents: creditedCents,
10229
+ ref: resp.data.id,
10230
+ type: "auto_topup"
10231
+ });
10232
+ return {
10233
+ charged: true,
10234
+ creditedCents,
10235
+ balanceAfterCents: topup.balance_after_cents,
10236
+ paymentIntentId: resp.data.id
10237
+ };
10238
+ } catch (e) {
10239
+ Sentry3.captureException(e);
10240
+ return {
10241
+ charged: true,
10242
+ creditedCents,
10243
+ paymentIntentId: resp.data.id,
10244
+ reason: `credited_via_webhook_pending: ${e instanceof Error ? e.message : "atlas error"}`
10245
+ };
10246
+ }
10247
+ }
10248
+ function ok3(text) {
10249
+ return { content: [{ type: "text", text }] };
10250
+ }
10251
+ function err3(text) {
10252
+ return { isError: true, content: [{ type: "text", text }] };
10253
+ }
10254
+ function registerWalletTools(server2, ctx) {
10255
+ const { env, props: props2 } = ctx;
10256
+ const defaultOrgId = props2.org_id ?? props2.sub;
10257
+ server2.tool(
10258
+ "wallet_balance",
10259
+ "Read the caller organisation's Carrier prepaid wallet: current balance and auto-top-up settings. The wallet is what Carrier bills your own eSIM spend against \u2014 it is not an OCS balance and not your MCP plan credits. Params: none (the organisation is taken from the session). Returns: { org_id, balance_eur_cents, auto_topup_enabled, auto_topup_threshold_cents, auto_topup_pack_cents } \u2014 all amounts in EUR cents, not euros. Do NOT use this for a reseller account balance in OCS \u2014 use `get_reseller_info` or `list_reseller_accounts`. Do NOT use this for a subscriber's OCS balance \u2014 use `get_subscriber`. Do NOT use this for MCP plan credits \u2014 use `credit_balance`. Do NOT use this for money held at Stripe \u2014 use `stripe_connect_balance`.",
10260
+ {},
10261
+ async () => {
10262
+ const orgId = defaultOrgId;
10263
+ const start = Date.now();
10264
+ try {
10265
+ const wallet = await getWallet(env, orgId);
10266
+ writeAudit(env, {
10267
+ tool_name: "wallet_balance",
10268
+ ocs_method: "atlas.wallet.read",
10269
+ status: "ok",
10270
+ dry_run: false,
10271
+ duration_ms: Date.now() - start,
10272
+ sub: props2.sub,
10273
+ reseller_id: props2.reseller_id
10274
+ });
10275
+ return ok3(
10276
+ JSON.stringify({
10277
+ org_id: orgId,
10278
+ balance_eur_cents: wallet.balance_eur_cents,
10279
+ auto_topup_enabled: wallet.auto_topup_enabled,
10280
+ auto_topup_threshold_cents: wallet.auto_topup_threshold_cents,
10281
+ auto_topup_pack_cents: wallet.auto_topup_pack_cents
10282
+ })
10283
+ );
10284
+ } catch (e) {
10285
+ if (e instanceof WalletClientError) return err3(`Wallet error: ${e.message}`);
10286
+ Sentry3.captureException(e);
10287
+ return err3(`Error: ${e instanceof Error ? e.message : "unknown"}`);
10288
+ }
10289
+ }
10290
+ );
10291
+ server2.tool(
10292
+ "wallet_topup_checkout",
10293
+ "Create a Stripe Checkout link so a human can top up the Carrier prepaid wallet by card. This tool charges nothing itself: it returns a hosted URL, and the wallet is credited (pack amount + bonus) only after someone completes the payment in a browser. Params: `pack` (optional \u2014 pack id `pack_500`/`pack_1000`/`pack_2500`/`pack_5000`, or an exact EUR-cents amount matching a catalog pack; omit to list the packs and their bonuses without creating a session). Returns: { pack_id, pack_eur_cents, bonus_eur_cents, checkout_session_id, checkout_url } \u2014 amounts in EUR cents. Do NOT use this when the wallet must be funded now with no human in the loop \u2014 use `wallet_auto_topup`, which charges the saved card immediately. Do NOT use this to move money in OCS \u2014 `modify_account_balance` and `modify_subscriber_balance` are unrelated ledgers.",
10294
+ {
10295
+ pack: z18.string().optional().describe(
10296
+ "Pack id (pack_500/pack_1000/pack_2500/pack_5000) or exact EUR-cents amount. Omit to list packs."
10297
+ )
10298
+ },
10299
+ async ({ pack }) => {
10300
+ const orgId = defaultOrgId;
10301
+ const start = Date.now();
10302
+ if (!pack) {
10303
+ return ok3(
10304
+ JSON.stringify({
10305
+ packs: PACKS.map((p) => ({
10306
+ id: p.id,
10307
+ eur_cents: p.eurCents,
10308
+ bonus_pct: p.bonusPct,
10309
+ bonus_eur_cents: bonusCentsFor(p),
10310
+ credited_eur_cents: p.eurCents + bonusCentsFor(p)
10311
+ }))
10312
+ })
10313
+ );
10314
+ }
10315
+ const selected = resolvePack(pack);
10316
+ if (!selected) {
10317
+ return err3(
10318
+ `Unknown pack "${pack}". Valid: ${PACKS.map((p) => p.id).join(", ")}.`
10319
+ );
10320
+ }
10321
+ try {
10322
+ const session = await createTopupCheckout(env, { orgId, pack: selected });
10323
+ writeAudit(env, {
10324
+ tool_name: "wallet_topup_checkout",
10325
+ ocs_method: "stripe.checkout.session.create",
10326
+ status: "ok",
10327
+ dry_run: false,
10328
+ duration_ms: Date.now() - start,
10329
+ sub: props2.sub,
10330
+ reseller_id: props2.reseller_id
10331
+ });
10332
+ return ok3(
10333
+ JSON.stringify({
10334
+ org_id: orgId,
10335
+ pack_id: selected.id,
10336
+ pack_eur_cents: selected.eurCents,
10337
+ bonus_eur_cents: bonusCentsFor(selected),
10338
+ checkout_session_id: session.id,
10339
+ checkout_url: session.url
10340
+ })
10341
+ );
10342
+ } catch (e) {
10343
+ Sentry3.captureException(e);
10344
+ return err3(`Error: ${e instanceof Error ? e.message : "unknown"}`);
10345
+ }
10346
+ }
10347
+ );
10348
+ server2.tool(
10349
+ "wallet_auto_topup",
10350
+ "Charge the organisation's saved card off-session, right now, and credit the Carrier prepaid wallet with the pack. This spends real money without any browser step or human confirmation, so only call it when the user has asked to be charged. Params: `pack_cents` (optional integer, EUR cents \u2014 must match a catalog pack; omit to use the organisation's configured auto-top-up pack). Returns on success: { org_id, charged: true, credited_eur_cents, balance_after_cents, payment_intent_id } \u2014 amounts in EUR cents. Returns { charged: false, reason } as an error when the card declines or no payment method is saved. Do NOT use this to let a human pay in a browser \u2014 use `wallet_topup_checkout`, which only returns a link. Do NOT use this to change the threshold that triggers automatic top-ups; this tool performs one charge and does not edit the configuration. Do NOT use this to credit an OCS reseller account or subscriber \u2014 see `modify_account_balance` and `modify_subscriber_balance`.",
10351
+ {
10352
+ pack_cents: z18.number().int().positive().optional().describe("Override pack amount in EUR cents (must match a catalog pack).")
10353
+ },
10354
+ async ({ pack_cents }) => {
10355
+ const orgId = defaultOrgId;
10356
+ const start = Date.now();
10357
+ try {
10358
+ const result2 = await runAutoTopup(env, { orgId, sub: props2.sub, packCents: pack_cents });
10359
+ writeAudit(env, {
10360
+ tool_name: "wallet_auto_topup",
10361
+ ocs_method: "stripe.payment_intent.off_session",
10362
+ status: result2.charged ? "ok" : "error",
10363
+ dry_run: false,
10364
+ duration_ms: Date.now() - start,
10365
+ sub: props2.sub,
10366
+ reseller_id: props2.reseller_id
10367
+ });
10368
+ if (!result2.charged) {
10369
+ return err3(
10370
+ JSON.stringify({ org_id: orgId, charged: false, reason: result2.reason })
10371
+ );
10372
+ }
10373
+ return ok3(
10374
+ JSON.stringify({
10375
+ org_id: orgId,
10376
+ charged: true,
10377
+ credited_eur_cents: result2.creditedCents,
10378
+ balance_after_cents: result2.balanceAfterCents,
10379
+ payment_intent_id: result2.paymentIntentId,
10380
+ ...result2.reason ? { note: result2.reason } : {}
10381
+ })
10382
+ );
10383
+ } catch (e) {
10384
+ Sentry3.captureException(e);
10385
+ return err3(`Error: ${e instanceof Error ? e.message : "unknown"}`);
10386
+ }
10387
+ }
10388
+ );
10389
+ }
10390
+
10391
+ // src/tools-greenzone.ts
10392
+ import { z as z19 } from "zod";
9994
10393
 
9995
10394
  // src/greenzone-whitelist.ts
9996
10395
  var STATE_KEY = "greenzone:state";
@@ -10267,10 +10666,10 @@ function resolvePortalBaseUrl(env) {
10267
10666
  function getGreenzoneKv(env) {
10268
10667
  return env.GREENZONE_STATE_KV ?? null;
10269
10668
  }
10270
- function ok3(payload) {
10669
+ function ok4(payload) {
10271
10670
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
10272
10671
  }
10273
- function err3(message) {
10672
+ function err4(message) {
10274
10673
  return { isError: true, content: [{ type: "text", text: message }] };
10275
10674
  }
10276
10675
  var KAPTURE_UNAVAILABLE_MSG = "Execute each Kapture step using mcp__kapture__* tools in sequence. Ensure the Kapture extension is active in Chrome before invoking.";
@@ -10281,21 +10680,21 @@ function registerGreenzoneTools(server2, ctx) {
10281
10680
  title: "Greenzone Whitelist \u2014 Add",
10282
10681
  description: "Add a hostname and/or IP address to the Bridge4IP Greenzone whitelist. The Greenzone whitelist controls which hosts/IPs are exempt from data charging after a subscriber's bundle is depleted. This operates via browser automation (Kapture) against the OCS portal \u2014 no REST API exists for this surface. Provide `host` (e.g. 'example.com') and/or `ip` (e.g. '203.0.113.0/24'). At least one of host or ip must be provided. Returns Kapture step sequence for Claude Code to execute. Requires 'write' scope and Kapture running in Sam's Chrome.",
10283
10682
  inputSchema: {
10284
- host: z18.string().optional().describe("Hostname to whitelist (e.g. 'example.com')"),
10285
- ip: z18.string().optional().describe("IP address or CIDR to whitelist (e.g. '203.0.113.0/24')"),
10286
- dry_run: z18.boolean().optional().describe("If true, return the Kapture steps without executing portal automation")
10683
+ host: z19.string().optional().describe("Hostname to whitelist (e.g. 'example.com')"),
10684
+ ip: z19.string().optional().describe("IP address or CIDR to whitelist (e.g. '203.0.113.0/24')"),
10685
+ dry_run: z19.boolean().optional().describe("If true, return the Kapture steps without executing portal automation")
10287
10686
  },
10288
10687
  annotations: { destructiveHint: true }
10289
10688
  },
10290
10689
  async (args) => {
10291
10690
  if (!ctx.props.scope.includes("write")) {
10292
- return err3(
10691
+ return err4(
10293
10692
  `Scope denied: greenzone_whitelist_add requires 'write' scope. Your token has: [${ctx.props.scope.join(", ")}].`
10294
10693
  );
10295
10694
  }
10296
10695
  const { host, ip, dry_run } = args;
10297
10696
  if (!host && !ip) {
10298
- return err3("At least one of 'host' or 'ip' must be provided.");
10697
+ return err4("At least one of 'host' or 'ip' must be provided.");
10299
10698
  }
10300
10699
  const portalBaseUrl = resolvePortalBaseUrl(ctx.env);
10301
10700
  const steps = [
@@ -10303,7 +10702,7 @@ function registerGreenzoneTools(server2, ctx) {
10303
10702
  ...ip ? buildAddIpSteps(portalBaseUrl, ip) : []
10304
10703
  ];
10305
10704
  if (dry_run) {
10306
- return ok3({
10705
+ return ok4({
10307
10706
  dry_run: true,
10308
10707
  message: "Would execute the following Kapture steps against the OCS portal:",
10309
10708
  kapture_steps: steps,
@@ -10326,7 +10725,7 @@ function registerGreenzoneTools(server2, ctx) {
10326
10725
  } else {
10327
10726
  kvWarning = "GREENZONE_STATE_KV not bound in stdio mode \u2014 state not persisted.";
10328
10727
  }
10329
- return ok3({
10728
+ return ok4({
10330
10729
  requires_kapture: true,
10331
10730
  message: "Execute the Kapture steps below to apply the change in the OCS portal. " + KAPTURE_UNAVAILABLE_MSG,
10332
10731
  kapture_steps: steps,
@@ -10342,21 +10741,21 @@ function registerGreenzoneTools(server2, ctx) {
10342
10741
  title: "Greenzone Whitelist \u2014 Remove",
10343
10742
  description: "Remove a hostname and/or IP address from the Bridge4IP Greenzone whitelist. This operates via browser automation (Kapture) against the OCS portal. Provide `host` and/or `ip` to remove \u2014 at least one required. Returns Kapture step sequence for Claude Code to execute. Requires 'write' scope and Kapture running in Sam's Chrome.",
10344
10743
  inputSchema: {
10345
- host: z18.string().optional().describe("Hostname to remove from whitelist"),
10346
- ip: z18.string().optional().describe("IP address or CIDR to remove from whitelist"),
10347
- dry_run: z18.boolean().optional().describe("If true, return the Kapture steps without executing portal automation")
10744
+ host: z19.string().optional().describe("Hostname to remove from whitelist"),
10745
+ ip: z19.string().optional().describe("IP address or CIDR to remove from whitelist"),
10746
+ dry_run: z19.boolean().optional().describe("If true, return the Kapture steps without executing portal automation")
10348
10747
  },
10349
10748
  annotations: { destructiveHint: true }
10350
10749
  },
10351
10750
  async (args) => {
10352
10751
  if (!ctx.props.scope.includes("write")) {
10353
- return err3(
10752
+ return err4(
10354
10753
  `Scope denied: greenzone_whitelist_remove requires 'write' scope. Your token has: [${ctx.props.scope.join(", ")}].`
10355
10754
  );
10356
10755
  }
10357
10756
  const { host, ip, dry_run } = args;
10358
10757
  if (!host && !ip) {
10359
- return err3("At least one of 'host' or 'ip' must be provided.");
10758
+ return err4("At least one of 'host' or 'ip' must be provided.");
10360
10759
  }
10361
10760
  const portalBaseUrl = resolvePortalBaseUrl(ctx.env);
10362
10761
  const steps = [
@@ -10364,7 +10763,7 @@ function registerGreenzoneTools(server2, ctx) {
10364
10763
  ...ip ? buildRemoveIpSteps(portalBaseUrl, ip) : []
10365
10764
  ];
10366
10765
  if (dry_run) {
10367
- return ok3({
10766
+ return ok4({
10368
10767
  dry_run: true,
10369
10768
  message: "Would execute the following Kapture steps to remove from OCS portal:",
10370
10769
  kapture_steps: steps,
@@ -10387,7 +10786,7 @@ function registerGreenzoneTools(server2, ctx) {
10387
10786
  } else {
10388
10787
  kvWarning = "GREENZONE_STATE_KV not bound in stdio mode \u2014 state not persisted.";
10389
10788
  }
10390
- return ok3({
10789
+ return ok4({
10391
10790
  requires_kapture: true,
10392
10791
  message: "Execute the Kapture steps below to apply the removal in the OCS portal. " + KAPTURE_UNAVAILABLE_MSG,
10393
10792
  kapture_steps: steps,
@@ -10403,7 +10802,7 @@ function registerGreenzoneTools(server2, ctx) {
10403
10802
  title: "Greenzone Whitelist \u2014 List",
10404
10803
  description: "List current Greenzone whitelist entries from the KV cache. Returns all whitelisted hosts and IPs, when the state was last synced, and whether the last hourly reconciliation detected drift from the portal. This is a fast read from KV \u2014 no browser automation required. Use `from_portal: true` to trigger a live portal read via Kapture instead of KV. Requires 'read' scope.",
10405
10804
  inputSchema: {
10406
- from_portal: z18.boolean().optional().describe(
10805
+ from_portal: z19.boolean().optional().describe(
10407
10806
  "When true, return Kapture steps to read live portal state instead of KV cache"
10408
10807
  )
10409
10808
  },
@@ -10411,14 +10810,14 @@ function registerGreenzoneTools(server2, ctx) {
10411
10810
  },
10412
10811
  async (args) => {
10413
10812
  if (!ctx.props.scope.includes("read")) {
10414
- return err3(
10813
+ return err4(
10415
10814
  `Scope denied: greenzone_whitelist_list requires 'read' scope. Your token has: [${ctx.props.scope.join(", ")}].`
10416
10815
  );
10417
10816
  }
10418
10817
  const { from_portal } = args;
10419
10818
  if (from_portal) {
10420
10819
  const portalBaseUrl = resolvePortalBaseUrl(ctx.env);
10421
- return ok3({
10820
+ return ok4({
10422
10821
  requires_kapture: true,
10423
10822
  message: "Execute the Kapture steps below to read live Greenzone whitelist state from the OCS portal.",
10424
10823
  kapture_steps: buildListSteps(portalBaseUrl),
@@ -10427,7 +10826,7 @@ function registerGreenzoneTools(server2, ctx) {
10427
10826
  }
10428
10827
  const kv = getGreenzoneKv(ctx.env);
10429
10828
  if (!kv) {
10430
- return ok3({
10829
+ return ok4({
10431
10830
  source: "kv_cache",
10432
10831
  hosts: [],
10433
10832
  ips: [],
@@ -10439,7 +10838,7 @@ function registerGreenzoneTools(server2, ctx) {
10439
10838
  }
10440
10839
  try {
10441
10840
  const state = await readState(kv);
10442
- return ok3({
10841
+ return ok4({
10443
10842
  source: "kv_cache",
10444
10843
  hosts: state.hosts,
10445
10844
  ips: state.ips,
@@ -10448,14 +10847,14 @@ function registerGreenzoneTools(server2, ctx) {
10448
10847
  reconcile_drift: state.reconcile_drift ?? false
10449
10848
  });
10450
10849
  } catch (e) {
10451
- return err3(`Failed to read Greenzone state from KV: ${e instanceof Error ? e.message : String(e)}`);
10850
+ return err4(`Failed to read Greenzone state from KV: ${e instanceof Error ? e.message : String(e)}`);
10452
10851
  }
10453
10852
  }
10454
10853
  );
10455
10854
  }
10456
10855
 
10457
10856
  // src/tools-ui-agent-generic.ts
10458
- import { z as z19 } from "zod";
10857
+ import { z as z20 } from "zod";
10459
10858
  var STDIO_ERROR = {
10460
10859
  isError: true,
10461
10860
  content: [
@@ -10476,9 +10875,9 @@ function registerUiAgentGenericTools(server2, _ctx) {
10476
10875
  title: "Dispatch Generic Steel Browsing Agent",
10477
10876
  description: "Dispatch a Steel browsing agent task with a natural-language prompt. The agent will navigate the web, use Tavily web search if needed, and return the result. Use for ad-hoc research, page extraction, form filling outside the OCS portal. Pro/Enterprise scope required. Cost ~$0.20-1.00 per task depending on complexity. Returns task_id immediately \u2014 poll with ui_agent_status for completion. Set dry_run=true to preview cost estimate without executing. NOTE: requires remote Worker deployment (mcp.carrier.llc/mcp) \u2014 not available in stdio mode.",
10478
10877
  inputSchema: {
10479
- prompt: z19.string().min(1).max(4e3).describe("Natural-language task description for the Steel browsing agent."),
10480
- max_steps: z19.number().int().min(1).max(30).optional().describe("Maximum agent steps (1\u201330, default 15)."),
10481
- dry_run: z19.boolean().optional().describe("Preview cost estimate without dispatching.")
10878
+ prompt: z20.string().min(1).max(4e3).describe("Natural-language task description for the Steel browsing agent."),
10879
+ max_steps: z20.number().int().min(1).max(30).optional().describe("Maximum agent steps (1\u201330, default 15)."),
10880
+ dry_run: z20.boolean().optional().describe("Preview cost estimate without dispatching.")
10482
10881
  }
10483
10882
  },
10484
10883
  async (_args) => STDIO_ERROR
@@ -10489,7 +10888,7 @@ function registerUiAgentGenericTools(server2, _ctx) {
10489
10888
  title: "Get Steel Agent Task Status",
10490
10889
  description: "Poll the status of a Steel browser agent task dispatched by ui_agent_ask or any ui_* tool. Returns current status (pending | running | completed | failed) and result when available. NOTE: requires remote Worker deployment (mcp.carrier.llc/mcp) \u2014 not available in stdio mode.",
10491
10890
  inputSchema: {
10492
- task_id: z19.string().describe("Steel task ID returned by ui_agent_ask or any ui_* tool dispatch")
10891
+ task_id: z20.string().describe("Steel task ID returned by ui_agent_ask or any ui_* tool dispatch")
10493
10892
  }
10494
10893
  },
10495
10894
  async (_args) => STDIO_ERROR
@@ -10497,17 +10896,17 @@ function registerUiAgentGenericTools(server2, _ctx) {
10497
10896
  }
10498
10897
 
10499
10898
  // src/tools-storefront-logo.ts
10500
- import { z as z20 } from "zod";
10501
- var ok4 = (text) => ({ content: [{ type: "text", text }] });
10502
- var err4 = (text) => ({ isError: true, content: [{ type: "text", text }] });
10899
+ import { z as z21 } from "zod";
10900
+ var ok5 = (text) => ({ content: [{ type: "text", text }] });
10901
+ var err5 = (text) => ({ isError: true, content: [{ type: "text", text }] });
10503
10902
  function registerStorefrontLogoTools(server2, env = {}) {
10504
10903
  server2.tool(
10505
10904
  "generate_storefront_logo",
10506
10905
  "Generate a storefront logo (SVG always; PNG when an image key is configured on the Carrier worker). Works for any authenticated Carrier MCP user \u2014 no local image-API key required. Write the SVG to public/brand/logo.svg in the storefront.",
10507
10906
  {
10508
- name: z20.string().min(1).describe("Brand / shop name, e.g. Bananas"),
10509
- accent: z20.string().regex(/^#?[0-9A-Fa-f]{6}$/).optional().describe("Accent hex, e.g. #FF8A00"),
10510
- tagline: z20.string().optional().describe("Optional tagline for AI-enhanced marks")
10907
+ name: z21.string().min(1).describe("Brand / shop name, e.g. Bananas"),
10908
+ accent: z21.string().regex(/^#?[0-9A-Fa-f]{6}$/).optional().describe("Accent hex, e.g. #FF8A00"),
10909
+ tagline: z21.string().optional().describe("Optional tagline for AI-enhanced marks")
10511
10910
  },
10512
10911
  async ({ name, accent, tagline }) => {
10513
10912
  try {
@@ -10518,7 +10917,7 @@ function registerStorefrontLogoTools(server2, env = {}) {
10518
10917
  tagline,
10519
10918
  env: { ...env, ...process.env }
10520
10919
  });
10521
- return ok4(
10920
+ return ok5(
10522
10921
  JSON.stringify({
10523
10922
  brand: name,
10524
10923
  source: logo.source,
@@ -10530,7 +10929,7 @@ function registerStorefrontLogoTools(server2, env = {}) {
10530
10929
  })
10531
10930
  );
10532
10931
  } catch (e) {
10533
- return err4(e instanceof Error ? e.message : "logo generation failed");
10932
+ return err5(e instanceof Error ? e.message : "logo generation failed");
10534
10933
  }
10535
10934
  }
10536
10935
  );
@@ -10559,7 +10958,15 @@ var stdioEnv = {
10559
10958
  CARRIER_USERS: null,
10560
10959
  DOWNLOADS: null,
10561
10960
  MANUS_API_KEY: "",
10562
- STRIPE_SECRET_KEY: ""
10961
+ STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY ?? "",
10962
+ // Prepaid-wallet configuration. Read from the environment so a local operator
10963
+ // who holds these values gets working wallet tools; when absent the tools stay
10964
+ // registered (catalog parity with the Worker) and return an explicit
10965
+ // configuration error instead of silently disappearing from tools/list.
10966
+ ATLAS_BASE_URL: process.env.ATLAS_BASE_URL,
10967
+ CARRIER_INTERNAL_API_KEY: process.env.CARRIER_INTERNAL_API_KEY,
10968
+ WALLET_CHECKOUT_SUCCESS_URL: process.env.WALLET_CHECKOUT_SUCCESS_URL,
10969
+ WALLET_CHECKOUT_CANCEL_URL: process.env.WALLET_CHECKOUT_CANCEL_URL
10563
10970
  };
10564
10971
  var props = {
10565
10972
  sub: "stdio@local",
@@ -10592,6 +10999,7 @@ registerScheduleAndUsageTools(server, toolCtx);
10592
10999
  registerAllUiAgentTools(server, toolCtx);
10593
11000
  registerUiAgentAskTools(server, toolCtx);
10594
11001
  registerStripeConnectTools(server, { env: stdioEnv, props });
11002
+ registerWalletTools(server, { env: stdioEnv, props });
10595
11003
  registerGreenzoneTools(server, toolCtx);
10596
11004
  registerUiAgentGenericTools(server, toolCtx);
10597
11005
  registerStorefrontLogoTools(server, stdioEnv);