@carrierllc/mcp 0.13.34 → 0.13.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -80,7 +80,7 @@ import {
80
80
  verifyStorefront,
81
81
  withOcsListSummary,
82
82
  withTemplateResellerId
83
- } from "./chunk-DCZ77IHR.js";
83
+ } from "./chunk-RQZW6BQM.js";
84
84
  import "./chunk-SHKKVIIA.js";
85
85
 
86
86
  // src/index.ts
@@ -1304,12 +1304,19 @@ function wrapHandler(toolName, ocsMethod, requiredScope, ctx, handler, options)
1304
1304
  content: [{ type: "text", text: `Error: ${message}` }]
1305
1305
  };
1306
1306
  }
1307
+ const handledOcsCode = result2.ocsStatusCode;
1308
+ if (handledOcsCode !== void 0) {
1309
+ const { ocsStatusCode: _auditOnly, ...clientResult } = result2;
1310
+ void _auditOnly;
1311
+ result2 = clientResult;
1312
+ }
1307
1313
  ctx.audit({
1308
1314
  tool_name: toolName,
1309
1315
  ocs_method: ocsMethod,
1310
1316
  status: result2.isError ? "error" : "ok",
1311
1317
  dry_run: isDryRun,
1312
- duration_ms: Date.now() - start
1318
+ duration_ms: Date.now() - start,
1319
+ ...handledOcsCode !== void 0 ? { ocs_status_code: handledOcsCode } : {}
1313
1320
  });
1314
1321
  if (!result2.isError && !isDryRun) {
1315
1322
  recordUsage(ctx.env, ctx.props.sub, ctx.props.tier);
@@ -5041,7 +5048,25 @@ function registerAllBacklogTools(server2, ctx) {
5041
5048
  ctx,
5042
5049
  async ({ voip_plan_id }, token2) => {
5043
5050
  const client2 = new OcsClient(ctx.env.CARRIER_OCS_BASE_URL, token2);
5044
- const result2 = await client2.call("listVoipTariffRule", voip_plan_id);
5051
+ let result2;
5052
+ try {
5053
+ result2 = await client2.call("listVoipTariffRule", voip_plan_id);
5054
+ } catch (err9) {
5055
+ if (err9 instanceof OcsApiError && err9.code === 11) {
5056
+ return {
5057
+ isError: true,
5058
+ // Returned, not thrown, so pass the code to wrapHandler's audit row.
5059
+ ocsStatusCode: 11,
5060
+ content: [
5061
+ {
5062
+ type: "text",
5063
+ text: `No VoIP tariff rules readable for VoIP plan ${voip_plan_id} under this reseller (OCS error 11: Reseller not found). OCS returns this when the plan is not owned by your reseller, for example the get_reseller_info chargingInfo.voipPlan id, which is the plan your parent reseller bills you on. Use a plan id from list_subscriber_voip_tariff; if that returns no rows, this reseller has no VoIP catalog configured.`
5064
+ }
5065
+ ]
5066
+ };
5067
+ }
5068
+ throw err9;
5069
+ }
5045
5070
  return {
5046
5071
  content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
5047
5072
  };
@@ -6900,6 +6925,9 @@ async function stripeGet(stripeKey, path, connectedAccountId) {
6900
6925
  const data = await res.json();
6901
6926
  return { ok: res.ok, status: res.status, data };
6902
6927
  }
6928
+ function declineNotSupported(reviewId) {
6929
+ return `Not declined: Stripe has no endpoint to decline a Radar review, so nothing was sent for ${reviewId}. To turn the payment away, refund the charge (POST /v1/refunds) on the account the review belongs to (a platform review needs a platform-scoped refund; stripe_connect_refund acts as the operator's connected account) or, if it was authorized but not captured, cancel its PaymentIntent (POST /v1/payment_intents/:id/cancel). Either one closes the review. See https://docs.stripe.com/radar/reviews.`;
6930
+ }
6903
6931
  async function stripePost(stripeKey, path, params, connectedAccountId, idempotencyKey) {
6904
6932
  const headers = buildStripeHeaders(stripeKey, {
6905
6933
  connectedAccountId,
@@ -7149,7 +7177,7 @@ function registerStripeConnectTools(server2, ctx) {
7149
7177
  "stripe_connect_refund",
7150
7178
  {
7151
7179
  title: "Refund a Charge (moves real money)",
7152
- description: "Refund money to a customer on one charge in the operator's connected Stripe account. This returns real funds and Stripe cannot reverse a refund once issued. Two-step by design: call without `confirm_token` to get a token and a summary of what would be refunded, then call again with that token to execute. The token expires in 5 minutes. Params: `charge_id` (Stripe charge id, `ch_...`), `amount_cents` (optional integer in the charge's smallest currency unit \u2014 omit for a full refund), `reason` ('duplicate' | 'fraudulent' | 'requested_by_customer', optional), `confirm_token`. Returns: the refund id and status. Requires `write` scope. Do NOT use this to stop a charge that has not settled yet \u2014 decline it with `radar_review_decline`. Do NOT use this to contest a chargeback \u2014 read the case with `stripe_connect_dispute_list` first, since refunding a disputed charge can cost the operator twice.",
7180
+ description: "Refund money to a customer on one charge in the operator's connected Stripe account. This returns real funds and Stripe cannot reverse a refund once issued. Two-step by design: call without `confirm_token` to get a token and a summary of what would be refunded, then call again with that token to execute. The token expires in 5 minutes. Params: `charge_id` (Stripe charge id, `ch_...`), `amount_cents` (optional integer in the charge's smallest currency unit \u2014 omit for a full refund), `reason` ('duplicate' | 'fraudulent' | 'requested_by_customer', optional), `confirm_token`. Returns: the refund id and status. Requires `write` scope. Do NOT use this on a payment that was authorized but not captured \u2014 cancel its PaymentIntent instead (Stripe POST /v1/payment_intents/:id/cancel). Do NOT use this to contest a chargeback \u2014 read the case with `stripe_connect_dispute_list` first, since refunding a disputed charge can cost the operator twice.",
7153
7181
  inputSchema: {
7154
7182
  charge_id: z8.string().min(1).describe("Stripe charge ID (ch_...)."),
7155
7183
  amount_cents: z8.number().int().min(1).optional().describe("Partial refund amount in cents. Omit for full refund."),
@@ -7251,9 +7279,9 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7251
7279
  "radar_review_list",
7252
7280
  {
7253
7281
  title: "List Radar Fraud Reviews",
7254
- description: "List the Stripe Radar fraud reviews waiting on a manual approve/decline decision, newest first. Reads only \u2014 deciding a review is a separate call. Params: `open_only` (boolean, default true \u2014 false also returns already-decided reviews), `limit` (1-50, default 10). Returns: { reviews: [{ id (`prv_...`), charge, reason, opened_reason, closed_reason, created, closed }], has_more }. Call this first to get the `review_id` that `radar_review_approve` and `radar_review_decline` require. Do NOT use this for chargebacks the customer has already raised with their bank \u2014 use `stripe_connect_dispute_list`.",
7282
+ description: "List the Stripe Radar fraud reviews waiting on a manual approve/decline decision, newest first. Reads only \u2014 deciding a review is a separate call. Params: `open_only` (boolean, default true; Stripe lists open reviews only, so false returns the same set), `limit` (1-50, default 10). Returns: { reviews: [{ id (`prv_...`), charge, reason, opened_reason, closed_reason, created, closed }], has_more }. Call this first to get the `review_id` that `radar_review_approve` and `radar_review_decline` require. Do NOT use this for chargebacks the customer has already raised with their bank \u2014 use `stripe_connect_dispute_list`.",
7255
7283
  inputSchema: {
7256
- open_only: z8.boolean().default(true).describe("If true, only returns open (undecided) reviews."),
7284
+ open_only: z8.boolean().default(true).describe("Stripe lists only open (undecided) reviews; kept for compatibility."),
7257
7285
  limit: z8.number().int().min(1).max(50).default(10)
7258
7286
  },
7259
7287
  annotations: annotationsFor("radar_review_list", "read")
@@ -7271,9 +7299,9 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7271
7299
  const stripeKey = env.STRIPE_ORG_SECRET_KEY ?? env.STRIPE_SECRET_KEY;
7272
7300
  if (!stripeKey) return err2("Stripe not configured");
7273
7301
  const params = new URLSearchParams({ limit: String(limit) });
7274
- if (open_only) params.set("open", "true");
7275
- const result2 = await stripeGet(stripeKey, `/v1/radar/reviews?${params.toString()}`);
7302
+ const result2 = await stripeGet(stripeKey, `/v1/reviews?${params.toString()}`);
7276
7303
  if (!result2.ok) return err2(`Stripe error: ${JSON.stringify(result2.data)}`);
7304
+ const reviews = open_only ? result2.data.data.filter((r) => r.open !== false) : result2.data.data;
7277
7305
  writeAudit(env, {
7278
7306
  tool_name: "radar_review_list",
7279
7307
  ocs_method: "stripe.radar.reviews.list",
@@ -7283,7 +7311,7 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7283
7311
  sub: props2.sub,
7284
7312
  reseller_id: props2.reseller_id
7285
7313
  });
7286
- return ok(JSON.stringify({ reviews: result2.data.data, has_more: result2.data.has_more }));
7314
+ return ok(JSON.stringify({ reviews, has_more: result2.data.has_more }));
7287
7315
  } catch (e) {
7288
7316
  Sentry3.captureException(e);
7289
7317
  return err2(`Error: ${e instanceof Error ? e.message : "unknown"}`);
@@ -7294,7 +7322,7 @@ Call again with confirm_token="${token2}" to execute. Token expires in 5 minutes
7294
7322
  "radar_review_approve",
7295
7323
  {
7296
7324
  title: "Approve Radar Review (captures charge)",
7297
- description: "Clear a Stripe Radar fraud review so the held charge is captured and the money is taken. Approving overrides Radar's fraud signal, so a genuinely fraudulent charge approved here usually returns later as a chargeback plus a fee. Two-step by design: call without `confirm_token` to get a token, then call again with it to execute. Params: `review_id` (`prv_...`, from `radar_review_list`), `confirm_token`. Requires `admin` scope. Do NOT call this to block a suspicious charge \u2014 that is `radar_review_decline`, the opposite outcome. Do NOT call this to return money on a charge that already went through \u2014 that is `stripe_connect_refund`.",
7325
+ description: "Clear a Stripe Radar fraud review so the held charge is captured and the money is taken. Approving overrides Radar's fraud signal, so a genuinely fraudulent charge approved here usually returns later as a chargeback plus a fee. Two-step by design: call without `confirm_token` to get a token, then call again with it to execute. Params: `review_id` (`prv_...`, from `radar_review_list`), `confirm_token`. Requires `admin` scope. Do NOT call this to turn a suspicious charge away \u2014 Stripe has no review decline; refund the charge (`stripe_connect_refund`) or cancel an uncaptured PaymentIntent. Do NOT call this to return money on a charge that already went through \u2014 that is `stripe_connect_refund`.",
7298
7326
  inputSchema: {
7299
7327
  review_id: z8.string().min(1).describe("Stripe Radar review ID (prv_...)."),
7300
7328
  confirm_token: z8.string().optional()
@@ -7326,7 +7354,8 @@ Call again with confirm_token="${token2}" to execute. Expires in 5 minutes.`
7326
7354
  if (!stripeKey) return err2("Stripe not configured");
7327
7355
  const result2 = await stripePost(
7328
7356
  stripeKey,
7329
- `/v1/radar/reviews/${review_id}/approve`,
7357
+ // POST /v1/reviews/:id/approve (docs.stripe.com/api/radar/reviews/approve).
7358
+ `/v1/reviews/${encodeURIComponent(review_id)}/approve`,
7330
7359
  {}
7331
7360
  );
7332
7361
  if (!result2.ok) return err2(`Stripe error: ${JSON.stringify(result2.data)}`);
@@ -7349,15 +7378,15 @@ Call again with confirm_token="${token2}" to execute. Expires in 5 minutes.`
7349
7378
  server2.registerTool(
7350
7379
  "radar_review_decline",
7351
7380
  {
7352
- title: "Decline Radar Review (blocks charge)",
7353
- description: "Close a Stripe Radar fraud review as fraudulent, so the held charge is blocked and the customer is never billed. The customer sees a failed payment; if they were legitimate, they have to pay again. Two-step by design: call without `confirm_token` to get a token, then call again with it to execute. Params: `review_id` (`prv_...`, from `radar_review_list`), `confirm_token`. Requires `admin` scope. Do NOT call this to let a charge through \u2014 that is `radar_review_approve`, the opposite outcome. Do NOT call this on a charge that already succeeded; a review only exists while the charge is held, and returning settled money is `stripe_connect_refund`.",
7381
+ title: "Decline Radar Review (not supported by Stripe)",
7382
+ description: "Always refuses and calls nothing: Stripe has no endpoint to decline a Radar review (its Reviews API only lists, retrieves and approves). Payments in review have usually already been processed. To turn one away, refund the charge on Carrier's platform account in Stripe itself (Dashboard, or platform-scoped POST /v1/refunds with no Stripe-Account header): `stripe_connect_refund` always acts as the operator's connected account, so it will NOT find a platform charge from this review list. or, for a payment authorized but not captured, cancel its PaymentIntent (Stripe POST /v1/payment_intents/:id/cancel); either closes the review. Params: `review_id` (`prv_...`), `confirm_token` (ignored). Requires `admin` scope. Do NOT call this expecting a charge to be blocked; nothing happens.",
7354
7383
  inputSchema: {
7355
7384
  review_id: z8.string().min(1),
7356
7385
  confirm_token: z8.string().optional()
7357
7386
  },
7358
7387
  annotations: annotationsFor("radar_review_decline", "admin")
7359
7388
  },
7360
- async ({ review_id, confirm_token }) => {
7389
+ async ({ review_id }) => {
7361
7390
  const denied = denyUnlessScoped(props2, "radar_review_decline", STRIPE_CONNECT_TOOL_SCOPES);
7362
7391
  if (denied) return denied;
7363
7392
  if (!isPlatformOperator(env, props2.org_id)) {
@@ -7365,41 +7394,7 @@ Call again with confirm_token="${token2}" to execute. Expires in 5 minutes.`
7365
7394
  "This tool acts on Carrier's platform Stripe account and is restricted to Carrier's own organization."
7366
7395
  );
7367
7396
  }
7368
- const toolName = "radar_review_decline";
7369
- const start = Date.now();
7370
- if (!confirm_token) {
7371
- const token2 = await issueConfirmToken(env, props2.sub, toolName);
7372
- return ok(
7373
- `HARD_BLOCK: Declining review ${review_id} will close/block the charge.
7374
- confirm_token: ${token2}
7375
- Expires in 5 minutes.`
7376
- );
7377
- }
7378
- const valid = await verifyConfirmToken(env, props2.sub, toolName, confirm_token);
7379
- if (!valid) return err2("Invalid or expired confirm_token.");
7380
- try {
7381
- const stripeKey = env.STRIPE_ORG_SECRET_KEY ?? env.STRIPE_SECRET_KEY;
7382
- if (!stripeKey) return err2("Stripe not configured");
7383
- const result2 = await stripePost(
7384
- stripeKey,
7385
- `/v1/radar/reviews/${review_id}/approve`,
7386
- { reason: "fraudulent" }
7387
- );
7388
- if (!result2.ok) return err2(`Stripe error: ${JSON.stringify(result2.data)}`);
7389
- writeAudit(env, {
7390
- tool_name: toolName,
7391
- ocs_method: "stripe.radar.review.decline",
7392
- status: "ok",
7393
- dry_run: false,
7394
- duration_ms: Date.now() - start,
7395
- sub: props2.sub,
7396
- reseller_id: props2.reseller_id
7397
- });
7398
- return ok(`Review ${review_id} declined.`);
7399
- } catch (e) {
7400
- Sentry3.captureException(e);
7401
- return err2(`Error: ${e instanceof Error ? e.message : "unknown"}`);
7402
- }
7397
+ return err2(declineNotSupported(review_id));
7403
7398
  }
7404
7399
  );
7405
7400
  server2.registerTool(