@sakupa/mcp 0.7.17 → 0.7.19

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 +16 -101
  2. package/dist/index.js +16 -101
  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.17";
132
+ var SAKUPA_MCP_VERSION = "0.7.19";
133
133
 
134
134
  // ../core/dist/domain/errors.js
135
135
  var HTTP_STATUS = {
@@ -603,20 +603,6 @@ var HttpApiClient = class {
603
603
  }
604
604
  );
605
605
  }
606
- async unbindDomain(siteId, credential, req) {
607
- return this.call(
608
- "POST",
609
- `/v1/sites/${encodeURIComponent(siteId)}/domain/unbind`,
610
- { credential, body: req }
611
- );
612
- }
613
- async previewUnbindDomain(siteId, credential, req) {
614
- return this.call(
615
- "POST",
616
- `/v1/sites/${encodeURIComponent(siteId)}/domain/unbind/preview`,
617
- { credential, body: req }
618
- );
619
- }
620
606
  async recoverDomain(req) {
621
607
  return this.call("POST", "/v1/domains/recover", { body: req });
622
608
  }
@@ -2097,40 +2083,37 @@ ${res2.message}
2097
2083
  const res = await ctx.client.bindDomain(site.credential, req);
2098
2084
  const apex = res.apexDomain;
2099
2085
  const txtShort = shortHostFor(res.verificationRecord.name, apex);
2086
+ const switchNotice = res.supersedes ? `NOTE FOR THE USER: this site already serves ${res.supersedes}. ${res.supersedes} keeps working the whole time; once www.${apex} is confirmed live, it automatically REPLACES ${res.supersedes} (zero downtime). Tell the user this before they start.
2087
+
2088
+ ` : "";
2100
2089
  return text(
2101
2090
  "domain_verification_started",
2102
- `Domain binding started for ${apex} (includes: ${res.includedHostnames.join(", ")} \u2014 both will serve this site).
2103
-
2104
- STEP 1 of 2 \u2014 add ALL THREE records NOW:
2091
+ switchNotice + `Domain binding started for ${apex} (will serve: ${res.includedHostnames.join(", ")}).
2105
2092
 
2106
- 1) TXT host: ${txtShort} value: ${res.verificationRecord.value}
2107
- 2) CNAME host: www value: ${res.servingTarget}
2108
- 3) APEX host: @ -> ${res.servingTarget}. Try a plain CNAME at host @ first (most panels accept it); if rejected use ALIAS / ANAME / CNAME-flattening; if the panel has neither, skip it \u2014 www alone works, and a URL redirect from @ to www covers bare-domain visitors.
2093
+ This is a STEP-BY-STEP setup \u2014 give the user ONE record at a time so they do not get overwhelmed and give up.
2109
2094
 
2110
- Host fields above are the SHORT form: most DNS panels append the domain automatically. After saving, the record list must NOT show ${apex} twice in one name \u2014 that means the full name was pasted into an auto-appending field.
2095
+ STEP 1 of 2 \u2014 prove ownership. Add ONE record:
2111
2096
 
2112
- Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2097
+ TXT host: ${txtShort} value: ${res.verificationRecord.value}
2113
2098
 
2114
- STEP 2 (after ownership verifies): Sakupa issues certificate-validation TXT records \u2014 the verification result and bind "status" list them the moment they exist; relay each to the user and add them too (copy names and values EXACTLY as given). Everything then completes automatically.
2099
+ Host is the SHORT form: most panels append the domain automatically (the saved record must NOT show ${apex} twice in one name). Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2115
2100
 
2116
- Drive the whole flow with bind "status": it live-checks every record and names the exact fix for anything wrong. Re-check every 5 minutes (up to 10 times). Any later session can resume with action "status" alone; the verificationId is optional.`,
2101
+ When the user says the TXT is set, run bind "status". It verifies ownership and then hands back STEP 2 \u2014 a SINGLE www CNAME (there are NO certificate TXT records; HTTPS validates automatically over that CNAME). Each "status" checks the previous step and, unless something is misconfigured, advances to the next \u2014 so run it whenever the user reports a step done, NOT on a timer. Any later session can resume with action "status" alone; the verificationId is optional.`,
2117
2102
  {
2118
2103
  verificationId: res.verificationId,
2119
2104
  apexDomain: apex,
2120
2105
  includedHostnames: res.includedHostnames,
2121
2106
  verificationRecord: res.verificationRecord,
2122
2107
  servingTarget: res.servingTarget,
2108
+ ...res.supersedes ? { supersedes: res.supersedes } : {},
2123
2109
  requiredRecords: [
2124
2110
  {
2125
2111
  type: "TXT",
2126
2112
  shortHost: txtShort,
2127
2113
  name: res.verificationRecord.name,
2128
2114
  value: res.verificationRecord.value
2129
- },
2130
- { type: "CNAME", shortHost: "www", name: `www.${apex}`, value: res.servingTarget },
2131
- { type: "ALIAS", shortHost: "@", name: apex, value: res.servingTarget }
2115
+ }
2132
2116
  ],
2133
- servingInstructions: res.servingInstructions,
2134
2117
  retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
2135
2118
  maxAttempts: DNS_MAX_ATTEMPTS
2136
2119
  },
@@ -2536,15 +2519,6 @@ var deleteConfirmation = z5.object({
2536
2519
  expectedBoundHostnames: z5.array(z5.string()),
2537
2520
  acknowledge: z5.literal("delete_and_cancel_renewal")
2538
2521
  });
2539
- var unbindConfirmation = z5.object({
2540
- siteId: z5.string().min(1),
2541
- bindingId: z5.string().min(1),
2542
- expectedBindingUpdatedAt: z5.string().datetime(),
2543
- expectedBindingStatus: z5.enum(["provisioning", "active"]),
2544
- apexDomain: z5.string().min(1),
2545
- expectedBoundHostnames: z5.array(z5.string()),
2546
- acknowledge: z5.literal("unbind_and_remove_custom_hostnames")
2547
- });
2548
2522
  function registerLifecycleTools(server, baseCtx) {
2549
2523
  server.registerTool(
2550
2524
  "delete",
@@ -2606,66 +2580,6 @@ function registerLifecycleTools(server, baseCtx) {
2606
2580
  }
2607
2581
  }
2608
2582
  );
2609
- server.registerTool(
2610
- "unbind",
2611
- {
2612
- description: "Preview or execute removal of the custom apex/www serving surface while preserving the subscription and permanent Sakupa URL.",
2613
- inputSchema: {
2614
- projectDir: projectDirInput,
2615
- action: z5.enum(["preview", "confirm"]),
2616
- operationId: z5.string().min(1).optional(),
2617
- confirmation: unbindConfirmation.optional()
2618
- },
2619
- outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2620
- annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
2621
- },
2622
- async (args) => {
2623
- try {
2624
- const ctx = withProjectDir(baseCtx, args.projectDir);
2625
- const site = requireSiteFile(ctx);
2626
- const operationId = args.operationId ?? randomUUID2();
2627
- if (args.action === "preview") {
2628
- const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
2629
- operationId
2630
- });
2631
- return structuredToolResult({
2632
- schemaVersion: 1,
2633
- outcome: "waiting_user",
2634
- resultCode: "unbind_confirmation_required",
2635
- operationId,
2636
- summary: `Exact binding snapshot returned; unbinding removes ONLY the custom domain \u2014 the subscription, content, and permanent URL stay unchanged. To proceed, call unbind again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
2637
- data: { preview },
2638
- nextActions: [
2639
- { tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
2640
- ]
2641
- });
2642
- }
2643
- if (!args.confirmation) {
2644
- throw new LocalGuidanceError(
2645
- "invalid_request",
2646
- 'To CONFIRM unbind, first call unbind with action:"preview" to get the exact confirmation object bound to the current binding, then call again with action:"confirm", the same operationId, and that confirmation object.'
2647
- );
2648
- }
2649
- const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
2650
- operationId,
2651
- confirmation: args.confirmation
2652
- });
2653
- const { boundDomain: _removed, ...remaining } = site;
2654
- writeSiteFile(ctx.projectDir, remaining);
2655
- return structuredToolResult({
2656
- schemaVersion: 1,
2657
- outcome: result.servingDeletionPending ? "pending_provider" : "completed",
2658
- resultCode: "domain_unbound",
2659
- operationId,
2660
- summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
2661
- data: { result },
2662
- nextActions: [{ tool: "status", allowed: true }]
2663
- });
2664
- } catch (error) {
2665
- return toolError(error);
2666
- }
2667
- }
2668
- );
2669
2583
  }
2670
2584
 
2671
2585
  // src/transport.ts
@@ -2764,8 +2678,9 @@ Workflow:
2764
2678
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
2765
2679
  first verified request wins; unverified requests expire after 72 hours. billing,
2766
2680
  upgrade, portal and recover manage the paid
2767
- lifecycle; unbind removes the custom domain (the permanent URL keeps serving) and
2768
- delete tears the whole site down after explicit confirmation.
2681
+ lifecycle; delete tears the whole site down after explicit confirmation. Binding a
2682
+ NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
2683
+ until the new domain's www is confirmed live, then it is replaced automatically.
2769
2684
  Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
2770
2685
  A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
2771
2686
  24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
@@ -2779,7 +2694,7 @@ the user opened (for framework projects, where package.json lives \u2014 never t
2779
2694
  build folder; output is auto-detected). The server NEVER guesses a directory and refuses
2780
2695
  calls without one: only you can see which directory the user is actually in. After every deploy, TELL the user which environment it went to (deploy results carry an
2781
2696
  Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
2782
- refresh, delete and unbind echo
2697
+ refresh and delete echo
2783
2698
  the directory they acted on \u2014 verify it matches the user's active project.
2784
2699
 
2785
2700
  When the same operation fails twice in a row, or the user is clearly stuck or
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.17";
127
+ var SAKUPA_MCP_VERSION = "0.7.19";
128
128
 
129
129
  // ../core/dist/domain/errors.js
130
130
  var HTTP_STATUS = {
@@ -673,20 +673,6 @@ var HttpApiClient = class {
673
673
  }
674
674
  );
675
675
  }
676
- async unbindDomain(siteId, credential, req) {
677
- return this.call(
678
- "POST",
679
- `/v1/sites/${encodeURIComponent(siteId)}/domain/unbind`,
680
- { credential, body: req }
681
- );
682
- }
683
- async previewUnbindDomain(siteId, credential, req) {
684
- return this.call(
685
- "POST",
686
- `/v1/sites/${encodeURIComponent(siteId)}/domain/unbind/preview`,
687
- { credential, body: req }
688
- );
689
- }
690
676
  async recoverDomain(req) {
691
677
  return this.call("POST", "/v1/domains/recover", { body: req });
692
678
  }
@@ -2163,40 +2149,37 @@ ${res2.message}
2163
2149
  const res = await ctx.client.bindDomain(site.credential, req);
2164
2150
  const apex = res.apexDomain;
2165
2151
  const txtShort = shortHostFor(res.verificationRecord.name, apex);
2152
+ const switchNotice = res.supersedes ? `NOTE FOR THE USER: this site already serves ${res.supersedes}. ${res.supersedes} keeps working the whole time; once www.${apex} is confirmed live, it automatically REPLACES ${res.supersedes} (zero downtime). Tell the user this before they start.
2153
+
2154
+ ` : "";
2166
2155
  return text(
2167
2156
  "domain_verification_started",
2168
- `Domain binding started for ${apex} (includes: ${res.includedHostnames.join(", ")} \u2014 both will serve this site).
2169
-
2170
- STEP 1 of 2 \u2014 add ALL THREE records NOW:
2157
+ switchNotice + `Domain binding started for ${apex} (will serve: ${res.includedHostnames.join(", ")}).
2171
2158
 
2172
- 1) TXT host: ${txtShort} value: ${res.verificationRecord.value}
2173
- 2) CNAME host: www value: ${res.servingTarget}
2174
- 3) APEX host: @ -> ${res.servingTarget}. Try a plain CNAME at host @ first (most panels accept it); if rejected use ALIAS / ANAME / CNAME-flattening; if the panel has neither, skip it \u2014 www alone works, and a URL redirect from @ to www covers bare-domain visitors.
2159
+ This is a STEP-BY-STEP setup \u2014 give the user ONE record at a time so they do not get overwhelmed and give up.
2175
2160
 
2176
- Host fields above are the SHORT form: most DNS panels append the domain automatically. After saving, the record list must NOT show ${apex} twice in one name \u2014 that means the full name was pasted into an auto-appending field.
2161
+ STEP 1 of 2 \u2014 prove ownership. Add ONE record:
2177
2162
 
2178
- Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2163
+ TXT host: ${txtShort} value: ${res.verificationRecord.value}
2179
2164
 
2180
- STEP 2 (after ownership verifies): Sakupa issues certificate-validation TXT records \u2014 the verification result and bind "status" list them the moment they exist; relay each to the user and add them too (copy names and values EXACTLY as given). Everything then completes automatically.
2165
+ Host is the SHORT form: most panels append the domain automatically (the saved record must NOT show ${apex} twice in one name). Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2181
2166
 
2182
- Drive the whole flow with bind "status": it live-checks every record and names the exact fix for anything wrong. Re-check every 5 minutes (up to 10 times). Any later session can resume with action "status" alone; the verificationId is optional.`,
2167
+ When the user says the TXT is set, run bind "status". It verifies ownership and then hands back STEP 2 \u2014 a SINGLE www CNAME (there are NO certificate TXT records; HTTPS validates automatically over that CNAME). Each "status" checks the previous step and, unless something is misconfigured, advances to the next \u2014 so run it whenever the user reports a step done, NOT on a timer. Any later session can resume with action "status" alone; the verificationId is optional.`,
2183
2168
  {
2184
2169
  verificationId: res.verificationId,
2185
2170
  apexDomain: apex,
2186
2171
  includedHostnames: res.includedHostnames,
2187
2172
  verificationRecord: res.verificationRecord,
2188
2173
  servingTarget: res.servingTarget,
2174
+ ...res.supersedes ? { supersedes: res.supersedes } : {},
2189
2175
  requiredRecords: [
2190
2176
  {
2191
2177
  type: "TXT",
2192
2178
  shortHost: txtShort,
2193
2179
  name: res.verificationRecord.name,
2194
2180
  value: res.verificationRecord.value
2195
- },
2196
- { type: "CNAME", shortHost: "www", name: `www.${apex}`, value: res.servingTarget },
2197
- { type: "ALIAS", shortHost: "@", name: apex, value: res.servingTarget }
2181
+ }
2198
2182
  ],
2199
- servingInstructions: res.servingInstructions,
2200
2183
  retryAfterSeconds: DNS_RETRY_AFTER_SECONDS,
2201
2184
  maxAttempts: DNS_MAX_ATTEMPTS
2202
2185
  },
@@ -2530,15 +2513,6 @@ var deleteConfirmation = z4.object({
2530
2513
  expectedBoundHostnames: z4.array(z4.string()),
2531
2514
  acknowledge: z4.literal("delete_and_cancel_renewal")
2532
2515
  });
2533
- var unbindConfirmation = z4.object({
2534
- siteId: z4.string().min(1),
2535
- bindingId: z4.string().min(1),
2536
- expectedBindingUpdatedAt: z4.string().datetime(),
2537
- expectedBindingStatus: z4.enum(["provisioning", "active"]),
2538
- apexDomain: z4.string().min(1),
2539
- expectedBoundHostnames: z4.array(z4.string()),
2540
- acknowledge: z4.literal("unbind_and_remove_custom_hostnames")
2541
- });
2542
2516
  function registerLifecycleTools(server, baseCtx) {
2543
2517
  server.registerTool(
2544
2518
  "delete",
@@ -2600,66 +2574,6 @@ function registerLifecycleTools(server, baseCtx) {
2600
2574
  }
2601
2575
  }
2602
2576
  );
2603
- server.registerTool(
2604
- "unbind",
2605
- {
2606
- description: "Preview or execute removal of the custom apex/www serving surface while preserving the subscription and permanent Sakupa URL.",
2607
- inputSchema: {
2608
- projectDir: projectDirInput,
2609
- action: z4.enum(["preview", "confirm"]),
2610
- operationId: z4.string().min(1).optional(),
2611
- confirmation: unbindConfirmation.optional()
2612
- },
2613
- outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
2614
- annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
2615
- },
2616
- async (args) => {
2617
- try {
2618
- const ctx = withProjectDir(baseCtx, args.projectDir);
2619
- const site = requireSiteFile(ctx);
2620
- const operationId = args.operationId ?? randomUUID2();
2621
- if (args.action === "preview") {
2622
- const preview = await ctx.client.previewUnbindDomain(site.siteId, site.credential, {
2623
- operationId
2624
- });
2625
- return structuredToolResult({
2626
- schemaVersion: 1,
2627
- outcome: "waiting_user",
2628
- resultCode: "unbind_confirmation_required",
2629
- operationId,
2630
- summary: `Exact binding snapshot returned; unbinding removes ONLY the custom domain \u2014 the subscription, content, and permanent URL stay unchanged. To proceed, call unbind again with action:"confirm", operationId:"${operationId}", and the confirmation object from data.preview.confirmation.`,
2631
- data: { preview },
2632
- nextActions: [
2633
- { tool: "unbind", allowed: true, reasonCode: "exact_confirmation_required" }
2634
- ]
2635
- });
2636
- }
2637
- if (!args.confirmation) {
2638
- throw new LocalGuidanceError(
2639
- "invalid_request",
2640
- 'To CONFIRM unbind, first call unbind with action:"preview" to get the exact confirmation object bound to the current binding, then call again with action:"confirm", the same operationId, and that confirmation object.'
2641
- );
2642
- }
2643
- const result = await ctx.client.unbindDomain(site.siteId, site.credential, {
2644
- operationId,
2645
- confirmation: args.confirmation
2646
- });
2647
- const { boundDomain: _removed, ...remaining } = site;
2648
- writeSiteFile(ctx.projectDir, remaining);
2649
- return structuredToolResult({
2650
- schemaVersion: 1,
2651
- outcome: result.servingDeletionPending ? "pending_provider" : "completed",
2652
- resultCode: "domain_unbound",
2653
- operationId,
2654
- summary: `Custom domain unbound; the subscription, deployed content, and permanent Sakupa URL are unchanged. (project: ${ctx.projectDir})`,
2655
- data: { result },
2656
- nextActions: [{ tool: "status", allowed: true }]
2657
- });
2658
- } catch (error) {
2659
- return toolError(error);
2660
- }
2661
- }
2662
- );
2663
2577
  }
2664
2578
 
2665
2579
  // src/server.ts
@@ -2759,8 +2673,9 @@ Workflow:
2759
2673
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
2760
2674
  first verified request wins; unverified requests expire after 72 hours. billing,
2761
2675
  upgrade, portal and recover manage the paid
2762
- lifecycle; unbind removes the custom domain (the permanent URL keeps serving) and
2763
- delete tears the whole site down after explicit confirmation.
2676
+ lifecycle; delete tears the whole site down after explicit confirmation. Binding a
2677
+ NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
2678
+ until the new domain's www is confirmed live, then it is replaced automatically.
2764
2679
  Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
2765
2680
  A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
2766
2681
  24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
@@ -2774,7 +2689,7 @@ the user opened (for framework projects, where package.json lives \u2014 never t
2774
2689
  build folder; output is auto-detected). The server NEVER guesses a directory and refuses
2775
2690
  calls without one: only you can see which directory the user is actually in. After every deploy, TELL the user which environment it went to (deploy results carry an
2776
2691
  Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
2777
- refresh, delete and unbind echo
2692
+ refresh and delete echo
2778
2693
  the directory they acted on \u2014 verify it matches the user's active project.
2779
2694
 
2780
2695
  When the same operation fails twice in a row, or the user is clearly stuck or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "0.7.17",
3
+ "version": "0.7.19",
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",