@sakupa/mcp 0.7.26 → 0.7.28

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.
Files changed (3) hide show
  1. package/dist/bin.js +27 -19
  2. package/dist/index.js +27 -19
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -129,7 +129,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
129
129
  var ALLOWED_HIDDEN_PATHS = [".well-known/"];
130
130
 
131
131
  // ../core/dist/domain/version.js
132
- var SAKUPA_MCP_VERSION = "0.7.26";
132
+ var SAKUPA_MCP_VERSION = "0.7.28";
133
133
 
134
134
  // ../core/dist/domain/errors.js
135
135
  var HTTP_STATUS = {
@@ -2631,7 +2631,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
2631
2631
  server.registerTool(
2632
2632
  "status",
2633
2633
  {
2634
- description: "Show the current status of this project's Sakupa site: URL, mode (free/paid), expiry, subscription state, custom domains, size, last deployment and warnings.",
2634
+ description: "Show the current status of this project's Sakupa site: URL, mode (free/paid), expiry, custom domains, size, last deployment and warnings. For a paid site this tool also automatically returns the complete authoritative billing snapshot; users never need to know or name a separate billing tool to get accurate subscription information.",
2635
2635
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2636
2636
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
2637
2637
  inputSchema: { projectDir: projectDirInput }
@@ -2642,12 +2642,18 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
2642
2642
  const site = requireSiteFile(ctx);
2643
2643
  const res = await ctx.client.getSiteStatus(site.siteId, site.credential);
2644
2644
  noteSiteMode(res.siteId, res.mode);
2645
+ const billing = res.mode === "paid" ? await ctx.client.getBillingStatus(site.siteId, site.credential) : void 0;
2645
2646
  const binding = res.pendingDomainBinding ? await describePendingBinding(ctx.client, site.credential, res.pendingDomainBinding) : void 0;
2646
- return textJson("status_returned", `Site status:${binding?.note ?? ""}`, {
2647
- ...res,
2648
- projectDir: ctx.projectDir,
2649
- ...binding?.checklist ? { dnsChecklist: binding.checklist } : {}
2650
- });
2647
+ return textJson(
2648
+ "status_returned",
2649
+ billing ? `Site status with AUTHORITATIVE BILLING SNAPSHOT. When answering any subscription question, use the nested billing object and report the current plan, scheduled renewal or cancellation, effective time, current entitlement, billing period, usage state and one-time carry when present:${binding?.note ?? ""}` : `Site status:${binding?.note ?? ""}`,
2650
+ {
2651
+ ...res,
2652
+ projectDir: ctx.projectDir,
2653
+ ...billing ? { billing } : {},
2654
+ ...binding?.checklist ? { dnsChecklist: binding.checklist } : {}
2655
+ }
2656
+ );
2651
2657
  } catch (e) {
2652
2658
  return toolError(e);
2653
2659
  }
@@ -2819,7 +2825,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
2819
2825
  server.registerTool(
2820
2826
  "billing",
2821
2827
  {
2822
- description: "Show this site's hosting subscription: current plan, next renewal plan, payment state, current paid entitlement, reconciled usage, estimated usage tier, bound custom domains and risks. Owner-only (uses the credential in .sakupa/site.json).",
2828
+ description: "Return the sole authoritative source for this site's hosting subscription: current plan, next renewal plan or cancellation, effective time, payment state, current paid entitlement, reconciled usage, estimated usage tier, bound custom domains and risks. Owner-only (uses the credential in .sakupa/site.json).",
2823
2829
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2824
2830
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
2825
2831
  inputSchema: { projectDir: projectDirInput }
@@ -2831,15 +2837,20 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
2831
2837
  const res = await ctx.client.getBillingStatus(site.siteId, site.credential);
2832
2838
  noteSiteMode(res.siteId, res.mode);
2833
2839
  const lines = [
2834
- `Billing status for site ${res.siteId} (mode: ${res.mode})`,
2840
+ `AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
2835
2841
  res.permanentUrl ? `Permanent URL: ${res.permanentUrl}` : void 0,
2836
2842
  res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
2837
2843
  res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
2838
- res.cancelAtPeriodEnd ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : void 0,
2844
+ res.cancelAtPeriodEnd === true ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : res.cancelAtPeriodEnd === false ? "Renewal cancellation: no" : void 0,
2839
2845
  res.periodEntitlementPlan ? `Current paid entitlement: ${res.periodEntitlementPlan}` : void 0,
2840
2846
  res.currentPeriodStart ? `Current paid period: ${res.currentPeriodStart} -> ${res.currentPeriodEnd ?? "?"}` : void 0,
2847
+ `Usage state: ${res.usageState}`,
2848
+ res.currentPeriodUsage ? `Current usage: storage ${res.currentPeriodUsage.storageBytes} bytes; transfer ${res.currentPeriodUsage.transferBytes} bytes; requests ${res.currentPeriodUsage.requests}` : void 0,
2849
+ res.currentPeriodCountedTransferBytes !== void 0 ? `Transfer counted against current plan: ${res.currentPeriodCountedTransferBytes} bytes` : void 0,
2850
+ res.currentPlanLimits ? `Current plan limits: storage ${res.currentPlanLimits.storageBytes} bytes; transfer ${res.currentPlanLimits.transferBytes} bytes; requests ${res.currentPlanLimits.requests}` : void 0,
2841
2851
  res.estimatedUsageTier ? `Estimated usage tier: ${res.estimatedUsageTier}` : void 0,
2842
2852
  res.lastReconciledAt ? `Last usage reconciliation: ${res.lastReconciledAt}` : void 0,
2853
+ res.usageLagSeconds !== void 0 ? `Usage lag: ${res.usageLagSeconds} seconds` : void 0,
2843
2854
  res.oneTimeTransferCarry ? `One-time transfer carry: ${res.oneTimeTransferCarry.remainingBytes} bytes remaining (granted ${res.oneTimeTransferCarry.grantedBytes}, expires ${res.oneTimeTransferCarry.expiresAt})` : void 0,
2844
2855
  res.boundHostnames.length > 0 ? `Bound custom domains: ${res.boundHostnames.join(", ")}` : void 0,
2845
2856
  res.risks.pastDue ? "ATTENTION: renewal payment failing \u2014 update the payment method (portal). Serving continues while Stripe retries; if Stripe gives up, the site reverts to free." : void 0
@@ -3322,7 +3333,6 @@ Summary: ${res.sanitizedSummary}`,
3322
3333
 
3323
3334
  // src/tools/billing.ts
3324
3335
  import { z as z4 } from "zod";
3325
- var plan = z4.enum(["water", "personal", "share", "business"]);
3326
3336
  function registerBillingTools(server, baseCtx) {
3327
3337
  server.registerTool(
3328
3338
  "plans",
@@ -3350,12 +3360,11 @@ function registerBillingTools(server, baseCtx) {
3350
3360
  }
3351
3361
  );
3352
3362
  server.registerTool(
3353
- "upgrade",
3363
+ "change",
3354
3364
  {
3355
- description: "Create a Stripe-hosted confirmation link for a manually selected subscription plan. Creating the link does not change billing; only the user can confirm on Stripe.",
3365
+ description: "Create one Stripe-hosted subscription-management link. The user chooses the plan or period-end cancellation on Stripe; Sakupa never infers intent from the conversation. Creating the link does not change billing.",
3356
3366
  inputSchema: {
3357
3367
  projectDir: projectDirInput,
3358
- targetPlan: plan,
3359
3368
  operationId: z4.string().min(1)
3360
3369
  },
3361
3370
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
@@ -3367,21 +3376,20 @@ function registerBillingTools(server, baseCtx) {
3367
3376
  const site = requireSiteFile(ctx);
3368
3377
  const result = await ctx.client.changeSubscriptionPlan(site.credential, {
3369
3378
  siteId: site.siteId,
3370
- targetPlan: args.targetPlan,
3371
3379
  operationId: args.operationId
3372
3380
  });
3373
3381
  return structuredToolResult({
3374
3382
  schemaVersion: 1,
3375
3383
  outcome: "waiting_user",
3376
- resultCode: "stripe_plan_change_confirmation_required",
3384
+ resultCode: "stripe_subscription_management_required",
3377
3385
  operationId: args.operationId,
3378
- summary: `Stripe confirmation link created for ${result.currentPlan} -> ${result.targetPlan}; the subscription has NOT changed yet. ` + (result.direction === "upgrade" ? "Confirming starts a new paid month immediately at the full target price; Sakupa applies the plan only after invoice payment and carries unused transfer once." : result.direction === "downgrade" ? "The downgrade is scheduled for the end of the already-paid period." : "Confirming cancels the scheduled plan change and keeps the current plan without a new charge."),
3386
+ summary: "Stripe subscription-management link created; the subscription has NOT changed yet. Stripe is the authoritative place to choose Water, Personal, Share, Business, or period-end cancellation. Query billing after the user finishes.",
3379
3387
  data: { result },
3380
3388
  userAction: {
3381
3389
  type: "open_url",
3382
3390
  provider: "stripe",
3383
3391
  url: result.portalUrl,
3384
- expectedOutcome: "After the user confirms on the Stripe-hosted page, the webhook updates the Sakupa subscription state"
3392
+ expectedOutcome: "After the user finishes on Stripe, webhooks update Sakupa and billing reports the authoritative result"
3385
3393
  },
3386
3394
  nextActions: [{ tool: "billing", allowed: true }]
3387
3395
  });
@@ -3611,7 +3619,7 @@ Workflow:
3611
3619
  4. Optionally bind a custom domain to the subscribed site (bind): an included extra
3612
3620
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
3613
3621
  first verified request wins; unverified requests expire after 72 hours. billing,
3614
- upgrade, portal and recover manage the paid
3622
+ change, portal and recover manage the paid
3615
3623
  lifecycle; delete tears the whole site down after explicit confirmation. Binding a
3616
3624
  NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
3617
3625
  until the new domain's www is confirmed live, then it is replaced automatically.
package/dist/index.js CHANGED
@@ -124,7 +124,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
124
124
  var ALLOWED_HIDDEN_PATHS = [".well-known/"];
125
125
 
126
126
  // ../core/dist/domain/version.js
127
- var SAKUPA_MCP_VERSION = "0.7.26";
127
+ var SAKUPA_MCP_VERSION = "0.7.28";
128
128
 
129
129
  // ../core/dist/domain/errors.js
130
130
  var HTTP_STATUS = {
@@ -2720,7 +2720,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
2720
2720
  server.registerTool(
2721
2721
  "status",
2722
2722
  {
2723
- description: "Show the current status of this project's Sakupa site: URL, mode (free/paid), expiry, subscription state, custom domains, size, last deployment and warnings.",
2723
+ description: "Show the current status of this project's Sakupa site: URL, mode (free/paid), expiry, custom domains, size, last deployment and warnings. For a paid site this tool also automatically returns the complete authoritative billing snapshot; users never need to know or name a separate billing tool to get accurate subscription information.",
2724
2724
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2725
2725
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
2726
2726
  inputSchema: { projectDir: projectDirInput }
@@ -2731,12 +2731,18 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
2731
2731
  const site = requireSiteFile(ctx);
2732
2732
  const res = await ctx.client.getSiteStatus(site.siteId, site.credential);
2733
2733
  noteSiteMode(res.siteId, res.mode);
2734
+ const billing = res.mode === "paid" ? await ctx.client.getBillingStatus(site.siteId, site.credential) : void 0;
2734
2735
  const binding = res.pendingDomainBinding ? await describePendingBinding(ctx.client, site.credential, res.pendingDomainBinding) : void 0;
2735
- return textJson("status_returned", `Site status:${binding?.note ?? ""}`, {
2736
- ...res,
2737
- projectDir: ctx.projectDir,
2738
- ...binding?.checklist ? { dnsChecklist: binding.checklist } : {}
2739
- });
2736
+ return textJson(
2737
+ "status_returned",
2738
+ billing ? `Site status with AUTHORITATIVE BILLING SNAPSHOT. When answering any subscription question, use the nested billing object and report the current plan, scheduled renewal or cancellation, effective time, current entitlement, billing period, usage state and one-time carry when present:${binding?.note ?? ""}` : `Site status:${binding?.note ?? ""}`,
2739
+ {
2740
+ ...res,
2741
+ projectDir: ctx.projectDir,
2742
+ ...billing ? { billing } : {},
2743
+ ...binding?.checklist ? { dnsChecklist: binding.checklist } : {}
2744
+ }
2745
+ );
2740
2746
  } catch (e) {
2741
2747
  return toolError(e);
2742
2748
  }
@@ -2908,7 +2914,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
2908
2914
  server.registerTool(
2909
2915
  "billing",
2910
2916
  {
2911
- description: "Show this site's hosting subscription: current plan, next renewal plan, payment state, current paid entitlement, reconciled usage, estimated usage tier, bound custom domains and risks. Owner-only (uses the credential in .sakupa/site.json).",
2917
+ description: "Return the sole authoritative source for this site's hosting subscription: current plan, next renewal plan or cancellation, effective time, payment state, current paid entitlement, reconciled usage, estimated usage tier, bound custom domains and risks. Owner-only (uses the credential in .sakupa/site.json).",
2912
2918
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2913
2919
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
2914
2920
  inputSchema: { projectDir: projectDirInput }
@@ -2920,15 +2926,20 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
2920
2926
  const res = await ctx.client.getBillingStatus(site.siteId, site.credential);
2921
2927
  noteSiteMode(res.siteId, res.mode);
2922
2928
  const lines = [
2923
- `Billing status for site ${res.siteId} (mode: ${res.mode})`,
2929
+ `AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
2924
2930
  res.permanentUrl ? `Permanent URL: ${res.permanentUrl}` : void 0,
2925
2931
  res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
2926
2932
  res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
2927
- res.cancelAtPeriodEnd ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : void 0,
2933
+ res.cancelAtPeriodEnd === true ? "Renewal: CANCELED \u2014 the site reverts to free at the end of the already-paid month" : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : res.cancelAtPeriodEnd === false ? "Renewal cancellation: no" : void 0,
2928
2934
  res.periodEntitlementPlan ? `Current paid entitlement: ${res.periodEntitlementPlan}` : void 0,
2929
2935
  res.currentPeriodStart ? `Current paid period: ${res.currentPeriodStart} -> ${res.currentPeriodEnd ?? "?"}` : void 0,
2936
+ `Usage state: ${res.usageState}`,
2937
+ res.currentPeriodUsage ? `Current usage: storage ${res.currentPeriodUsage.storageBytes} bytes; transfer ${res.currentPeriodUsage.transferBytes} bytes; requests ${res.currentPeriodUsage.requests}` : void 0,
2938
+ res.currentPeriodCountedTransferBytes !== void 0 ? `Transfer counted against current plan: ${res.currentPeriodCountedTransferBytes} bytes` : void 0,
2939
+ res.currentPlanLimits ? `Current plan limits: storage ${res.currentPlanLimits.storageBytes} bytes; transfer ${res.currentPlanLimits.transferBytes} bytes; requests ${res.currentPlanLimits.requests}` : void 0,
2930
2940
  res.estimatedUsageTier ? `Estimated usage tier: ${res.estimatedUsageTier}` : void 0,
2931
2941
  res.lastReconciledAt ? `Last usage reconciliation: ${res.lastReconciledAt}` : void 0,
2942
+ res.usageLagSeconds !== void 0 ? `Usage lag: ${res.usageLagSeconds} seconds` : void 0,
2932
2943
  res.oneTimeTransferCarry ? `One-time transfer carry: ${res.oneTimeTransferCarry.remainingBytes} bytes remaining (granted ${res.oneTimeTransferCarry.grantedBytes}, expires ${res.oneTimeTransferCarry.expiresAt})` : void 0,
2933
2944
  res.boundHostnames.length > 0 ? `Bound custom domains: ${res.boundHostnames.join(", ")}` : void 0,
2934
2945
  res.risks.pastDue ? "ATTENTION: renewal payment failing \u2014 update the payment method (portal). Serving continues while Stripe retries; if Stripe gives up, the site reverts to free." : void 0
@@ -3515,7 +3526,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3515
3526
 
3516
3527
  // src/tools/billing.ts
3517
3528
  import { z as z5 } from "zod";
3518
- var plan = z5.enum(["water", "personal", "share", "business"]);
3519
3529
  function registerBillingTools(server, baseCtx) {
3520
3530
  server.registerTool(
3521
3531
  "plans",
@@ -3543,12 +3553,11 @@ function registerBillingTools(server, baseCtx) {
3543
3553
  }
3544
3554
  );
3545
3555
  server.registerTool(
3546
- "upgrade",
3556
+ "change",
3547
3557
  {
3548
- description: "Create a Stripe-hosted confirmation link for a manually selected subscription plan. Creating the link does not change billing; only the user can confirm on Stripe.",
3558
+ description: "Create one Stripe-hosted subscription-management link. The user chooses the plan or period-end cancellation on Stripe; Sakupa never infers intent from the conversation. Creating the link does not change billing.",
3549
3559
  inputSchema: {
3550
3560
  projectDir: projectDirInput,
3551
- targetPlan: plan,
3552
3561
  operationId: z5.string().min(1)
3553
3562
  },
3554
3563
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
@@ -3560,21 +3569,20 @@ function registerBillingTools(server, baseCtx) {
3560
3569
  const site = requireSiteFile(ctx);
3561
3570
  const result = await ctx.client.changeSubscriptionPlan(site.credential, {
3562
3571
  siteId: site.siteId,
3563
- targetPlan: args.targetPlan,
3564
3572
  operationId: args.operationId
3565
3573
  });
3566
3574
  return structuredToolResult({
3567
3575
  schemaVersion: 1,
3568
3576
  outcome: "waiting_user",
3569
- resultCode: "stripe_plan_change_confirmation_required",
3577
+ resultCode: "stripe_subscription_management_required",
3570
3578
  operationId: args.operationId,
3571
- summary: `Stripe confirmation link created for ${result.currentPlan} -> ${result.targetPlan}; the subscription has NOT changed yet. ` + (result.direction === "upgrade" ? "Confirming starts a new paid month immediately at the full target price; Sakupa applies the plan only after invoice payment and carries unused transfer once." : result.direction === "downgrade" ? "The downgrade is scheduled for the end of the already-paid period." : "Confirming cancels the scheduled plan change and keeps the current plan without a new charge."),
3579
+ summary: "Stripe subscription-management link created; the subscription has NOT changed yet. Stripe is the authoritative place to choose Water, Personal, Share, Business, or period-end cancellation. Query billing after the user finishes.",
3572
3580
  data: { result },
3573
3581
  userAction: {
3574
3582
  type: "open_url",
3575
3583
  provider: "stripe",
3576
3584
  url: result.portalUrl,
3577
- expectedOutcome: "After the user confirms on the Stripe-hosted page, the webhook updates the Sakupa subscription state"
3585
+ expectedOutcome: "After the user finishes on Stripe, webhooks update Sakupa and billing reports the authoritative result"
3578
3586
  },
3579
3587
  nextActions: [{ tool: "billing", allowed: true }]
3580
3588
  });
@@ -3606,7 +3614,7 @@ Workflow:
3606
3614
  4. Optionally bind a custom domain to the subscribed site (bind): an included extra
3607
3615
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
3608
3616
  first verified request wins; unverified requests expire after 72 hours. billing,
3609
- upgrade, portal and recover manage the paid
3617
+ change, portal and recover manage the paid
3610
3618
  lifecycle; delete tears the whole site down after explicit confirmation. Binding a
3611
3619
  NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
3612
3620
  until the new domain's www is confirmed live, then it is replaced automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "0.7.26",
3
+ "version": "0.7.28",
4
4
  "description": "Sakupa MCP server: publish AI-made static sites from your AI tool. AI-made pages, live in seconds.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",