@sakupa/mcp 1.4.1 → 1.5.0

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 +224 -12
  2. package/dist/index.js +224 -12
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -403,7 +403,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
403
403
  }
404
404
 
405
405
  // ../core/dist/domain/version.js
406
- var SAKUPA_MCP_VERSION = "1.4.1";
406
+ var SAKUPA_MCP_VERSION = "1.5.0";
407
407
 
408
408
  // ../core/dist/domain/errors.js
409
409
  var HTTP_STATUS = {
@@ -2208,15 +2208,15 @@ function strFromU8(dat, latin1) {
2208
2208
  var slzh = function(d, b) {
2209
2209
  return b + 30 + b2(d, b + 26) + b2(d, b + 28);
2210
2210
  };
2211
- var zh = function(d, b, z7) {
2211
+ var zh = function(d, b, z8) {
2212
2212
  var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
2213
- var _a2 = z64hs(d, es, efl, z7, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
2213
+ var _a2 = z64hs(d, es, efl, z8, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
2214
2214
  return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
2215
2215
  };
2216
- var z64hs = function(d, b, l, z7, sc, su, off) {
2216
+ var z64hs = function(d, b, l, z8, sc, su, off) {
2217
2217
  var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
2218
2218
  var nf = nsc + nsu + noff;
2219
- if (z7 && nf) {
2219
+ if (z8 && nf) {
2220
2220
  for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
2221
2221
  if (b2(d, b) == 1) {
2222
2222
  return [
@@ -2227,7 +2227,7 @@ var z64hs = function(d, b, l, z7, sc, su, off) {
2227
2227
  ];
2228
2228
  }
2229
2229
  }
2230
- if (z7 < 2)
2230
+ if (z8 < 2)
2231
2231
  err(13);
2232
2232
  }
2233
2233
  return [sc, su, off, 0];
@@ -2244,18 +2244,18 @@ function unzipSync(data, opts) {
2244
2244
  if (!c)
2245
2245
  return {};
2246
2246
  var o = b4(data, e + 16);
2247
- var z7 = b4(data, e - 20) == 117853008;
2248
- if (z7) {
2247
+ var z8 = b4(data, e - 20) == 117853008;
2248
+ if (z8) {
2249
2249
  var ze = b4(data, e - 12);
2250
- z7 = b4(data, ze) == 101075792;
2251
- if (z7) {
2250
+ z8 = b4(data, ze) == 101075792;
2251
+ if (z8) {
2252
2252
  c = b4(data, ze + 32);
2253
2253
  o = b4(data, ze + 48);
2254
2254
  }
2255
2255
  }
2256
2256
  var fltr = opts && opts.filter;
2257
2257
  for (var i = 0; i < c; ++i) {
2258
- var _a2 = zh(data, o, z7), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2258
+ var _a2 = zh(data, o, z8), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
2259
2259
  o = no;
2260
2260
  if (!fltr || fltr({
2261
2261
  name: fn,
@@ -3436,7 +3436,8 @@ var TARGET_MCP_TOOL_NAMES = [
3436
3436
  "change",
3437
3437
  "support",
3438
3438
  "report",
3439
- "apps"
3439
+ "apps",
3440
+ "delete"
3440
3441
  ];
3441
3442
  var STRUCTURED_TOOL_OUTPUT_SCHEMA = z.object({
3442
3443
  schemaVersion: z.literal(1),
@@ -6170,6 +6171,7 @@ var TOOL_TOPICS = [
6170
6171
  "support",
6171
6172
  "report",
6172
6173
  "apps",
6174
+ "delete",
6173
6175
  "help"
6174
6176
  ];
6175
6177
  var HELP_TOPICS = ["diagnose", "overview", "terminology", ...TOOL_TOPICS];
@@ -6280,6 +6282,18 @@ var TOOL_MANUALS = {
6280
6282
  nextStep: "Use the preview resumeWith arguments only after the user confirms.",
6281
6283
  terminology: ["credentialRotation"]
6282
6284
  },
6285
+ delete: {
6286
+ purpose: "Delete the bound FREE site after an explicit preview and confirmation.",
6287
+ sideEffects: "Preview is read-only. Confirmed deletion removes content, the public URL, installed apps, stored submissions and the local .sakupa/site.json.",
6288
+ preconditions: "A valid local site credential and a free site. A subscribed site is refused until the subscription ends (change \u2192 period-end cancellation) and the site reverts to free.",
6289
+ parameterNames: ["confirmed", "operationId", "confirmation"],
6290
+ parameters: "No arguments for the preview; confirmed=true with the exact operationId and confirmation copied from the preview decision.",
6291
+ warnings: [
6292
+ "Irreversible; the next deploy creates a brand-new site with a new URL.",
6293
+ "Never propose delete to free up the free-site allowance; use the deploy handoff decision instead."
6294
+ ],
6295
+ nextStep: "Present every decision option; after confirmation copy the option arguments verbatim."
6296
+ },
6283
6297
  plans: {
6284
6298
  purpose: "Read the authoritative hosting plan catalog and rules.",
6285
6299
  sideEffects: "Read-only public API request.",
@@ -7258,6 +7272,196 @@ function registerAppsTools(server, baseCtx) {
7258
7272
  );
7259
7273
  }
7260
7274
 
7275
+ // src/tools/delete.ts
7276
+ import { randomUUID as randomUUID7 } from "node:crypto";
7277
+ import { z as z7 } from "zod";
7278
+ var confirmationSchema = z7.object({
7279
+ siteId: z7.string(),
7280
+ expectedSiteUpdatedAt: z7.string(),
7281
+ expectedStatus: z7.string(),
7282
+ expectedMode: z7.enum(["free", "paid"]),
7283
+ expectedServingMode: z7.string(),
7284
+ expectedShortId: z7.string().optional(),
7285
+ expectedSubscriptionStatus: z7.string().optional(),
7286
+ expectedPlan: z7.string().optional(),
7287
+ expectedCancelAtPeriodEnd: z7.boolean().optional(),
7288
+ expectedCurrentPeriodEnd: z7.string().optional(),
7289
+ expectedLastDeploymentId: z7.string().optional(),
7290
+ expectedBoundHostnames: z7.array(z7.string()),
7291
+ acknowledge: z7.literal("delete_and_cancel_renewal")
7292
+ });
7293
+ function registerDeleteTools(server, baseCtx) {
7294
+ server.registerTool(
7295
+ "delete",
7296
+ {
7297
+ title: "Delete site",
7298
+ description: "Delete the site bound to this project. Without arguments it only previews (zero writes) and returns a decision; after the user confirms, call again with the exact preview arguments (confirmed: true, operationId, confirmation). A FREE site is deleted immediately: content, public URL, installed apps and stored submissions are gone and the local .sakupa/site.json is removed, so the next deploy creates a brand-new site. A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end cancellation on Stripe); when it ends the site reverts to a free site and delete becomes available. Owner-only.",
7299
+ inputSchema: z7.object({
7300
+ confirmed: z7.boolean().optional().describe("true only from the exact decision arguments returned by the preview."),
7301
+ operationId: z7.string().optional().describe("Copied verbatim from the preview."),
7302
+ confirmation: confirmationSchema.optional().describe("Copied verbatim from the preview; the cloud rejects any drift.")
7303
+ }),
7304
+ outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
7305
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
7306
+ },
7307
+ withDecisionReentry("delete", async (args, call) => {
7308
+ try {
7309
+ const ctx = await withProjectDir(baseCtx, call);
7310
+ const site = requireSiteFile(ctx);
7311
+ if (args.confirmed === true) {
7312
+ if (!args.operationId || !args.confirmation) {
7313
+ return structuredToolResult({
7314
+ schemaVersion: 1,
7315
+ outcome: "blocked",
7316
+ resultCode: "delete_preview_required",
7317
+ summary: summaryMarkdown({
7318
+ title: "Nothing was deleted \u2014 run the preview first",
7319
+ lead: "A confirmed delete must carry the exact operationId and confirmation from the preview. Call delete with no arguments, show the preview to the user, then reuse its decision arguments.",
7320
+ next: ["`delete` with no arguments"]
7321
+ }),
7322
+ data: { siteId: site.siteId },
7323
+ nextActions: [{ tool: "delete", arguments: {}, allowed: true }]
7324
+ });
7325
+ }
7326
+ const result = await ctx.client.deleteSite(site.siteId, site.credential, {
7327
+ operationId: args.operationId,
7328
+ confirmation: args.confirmation
7329
+ });
7330
+ deleteSiteFile(ctx.projectDir);
7331
+ removeCreation(site.siteId);
7332
+ return structuredToolResult({
7333
+ schemaVersion: 1,
7334
+ outcome: "completed",
7335
+ resultCode: result.alreadyDeleted ? "site_already_deleted" : "site_deleted",
7336
+ summary: summaryMarkdown({
7337
+ title: result.alreadyDeleted ? "Site was already deleted" : "Site deleted",
7338
+ lead: `${site.url ?? site.siteId} no longer serves anything (visitors get HTTP 410). The local credential file .sakupa/site.json was removed; this project is no longer bound to any site.`,
7339
+ facts: [
7340
+ ["Site ID", result.siteId],
7341
+ ["Public URL released", site.url],
7342
+ [
7343
+ "Custom hostnames removed",
7344
+ result.removedHostnames.length ? result.removedHostnames.join(", ") : "none"
7345
+ ],
7346
+ ["Serving cleanup still pending", result.servingDeletionPending]
7347
+ ],
7348
+ notes: [
7349
+ "Installed apps and stored form submissions were deleted with the site.",
7350
+ "The free-site allowance this site used on its deployment network is released."
7351
+ ],
7352
+ next: ["`deploy` to publish a brand-new site (new URL, new credential)"]
7353
+ }),
7354
+ data: {
7355
+ ...result,
7356
+ projectDir: ctx.projectDir,
7357
+ localSiteFileRemoved: true,
7358
+ releasedUrl: site.url
7359
+ },
7360
+ nextActions: []
7361
+ });
7362
+ }
7363
+ const operationId = randomUUID7();
7364
+ const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
7365
+ operationId
7366
+ });
7367
+ const c = preview.consequences;
7368
+ if (c.requiresFreeModeBeforeDelete) {
7369
+ const cancellationScheduled = preview.confirmation.expectedCancelAtPeriodEnd === true;
7370
+ return structuredToolResult({
7371
+ schemaVersion: 1,
7372
+ outcome: "blocked",
7373
+ resultCode: "delete_requires_free_mode",
7374
+ summary: summaryMarkdown({
7375
+ title: "This site is subscription-backed \u2014 it cannot be deleted yet",
7376
+ lead: "Nothing was changed. Sakupa deletes only FREE sites. End the subscription first; when the paid period ends the site automatically becomes a free 30-day site, and delete becomes available.",
7377
+ facts: [
7378
+ ["Site", site.url ?? site.siteId],
7379
+ ["Plan", preview.confirmation.expectedPlan],
7380
+ ["Period-end cancellation already scheduled", cancellationScheduled ? "yes" : "no"],
7381
+ [
7382
+ "Paid access ends",
7383
+ c.currentPaidAccessEndsAt ? timestampForAgent(c.currentPaidAccessEndsAt) : void 0
7384
+ ],
7385
+ [
7386
+ "Custom hostnames removed when it reverts",
7387
+ c.removesCustomHostnames.join(", ") || "none"
7388
+ ]
7389
+ ],
7390
+ steps: cancellationScheduled ? [
7391
+ "Wait for the paid period to end; Sakupa then reverts the site to a free 30-day site (content kept, custom domains removed).",
7392
+ "Call delete again at that point \u2014 or simply let the free site expire."
7393
+ ] : [
7394
+ "Call change and open the Stripe-hosted link; the user chooses period-end cancellation there (Sakupa never cancels on its own).",
7395
+ "When the paid period ends, Sakupa reverts the site to a free 30-day site (content kept, custom domains removed).",
7396
+ "Call delete again at that point \u2014 or simply let the free site expire."
7397
+ ],
7398
+ next: cancellationScheduled ? ["`billing`"] : ["`change`", "`billing`"]
7399
+ }),
7400
+ data: {
7401
+ siteId: site.siteId,
7402
+ operationId: preview.operationId,
7403
+ consequences: c,
7404
+ cancellationScheduled
7405
+ },
7406
+ nextActions: [
7407
+ { tool: cancellationScheduled ? "billing" : "change", arguments: {}, allowed: true }
7408
+ ]
7409
+ });
7410
+ }
7411
+ const confirmArguments = {
7412
+ confirmed: true,
7413
+ operationId: preview.operationId,
7414
+ confirmation: preview.confirmation
7415
+ };
7416
+ return presentDecision(baseCtx.decisions, call, "delete", {
7417
+ resultCode: "delete_confirmation_required",
7418
+ summary: summaryMarkdown({
7419
+ title: `Delete ${site.url ?? site.siteId}? Nothing was changed.`,
7420
+ lead: "This is a preview. Deleting cannot be undone: the content, the public URL, installed apps and stored form submissions disappear at once, and the local credential file is removed.",
7421
+ facts: [
7422
+ ["Site ID", site.siteId],
7423
+ ["Public URL", site.url],
7424
+ ["Permanent URL released", c.releasesPermanentUrl],
7425
+ ["Custom hostnames removed", c.removesCustomHostnames.join(", ") || "none"],
7426
+ ["Exact confirm arguments", JSON.stringify(confirmArguments)]
7427
+ ],
7428
+ notes: [
7429
+ "After deletion this project is unbound; the next deploy creates a brand-new site with a new URL and credential.",
7430
+ "To keep the URL but replace the content, deploy again instead of deleting."
7431
+ ]
7432
+ }),
7433
+ data: {
7434
+ siteId: site.siteId,
7435
+ operationId: preview.operationId,
7436
+ consequences: c,
7437
+ confirmation: preview.confirmation,
7438
+ confirmArguments
7439
+ },
7440
+ prompt: `Delete the site ${site.url ?? site.siteId} now? This cannot be undone.`,
7441
+ options: [
7442
+ callToolDecisionOption({
7443
+ id: "delete_site",
7444
+ label: `Delete ${site.url ?? site.siteId}`,
7445
+ description: "Remove the site, its content, apps and submissions, and release the URL.",
7446
+ consequences: [
7447
+ "Visitors get HTTP 410 immediately.",
7448
+ "The local credential file is removed; this project is unbound."
7449
+ ],
7450
+ tool: "delete",
7451
+ arguments: confirmArguments,
7452
+ reasonCode: "explicit_site_delete_confirmation"
7453
+ }),
7454
+ noActionDecisionOption({ description: "Keep the site exactly as it is." })
7455
+ ],
7456
+ legacyUserAction: { type: "confirm_in_mcp", provider: "sakupa" }
7457
+ });
7458
+ } catch (error) {
7459
+ return toolError(error);
7460
+ }
7461
+ })
7462
+ );
7463
+ }
7464
+
7261
7465
  // src/transport.ts
7262
7466
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
7263
7467
  var DEFAULT_UPLOAD_TIMEOUT_MS = 3e4;
@@ -7445,6 +7649,13 @@ Workflow:
7445
7649
  payment, refund and other customer-service requests. report is the LAST resort only when
7446
7650
  help explicitly recommends a product bug report, and submission still requires user review.
7447
7651
 
7652
+ Deleting a site: delete with no arguments only previews and returns a decision; after the user
7653
+ confirms, call delete again with the exact preview arguments. A FREE site is deleted immediately
7654
+ (content, URL, apps and submissions gone; .sakupa/site.json removed; the next deploy creates a new
7655
+ site). A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end
7656
+ cancellation on Stripe); when it ends the site reverts to a free 30-day site and delete becomes
7657
+ available. Never suggest delete as a way to free up the free-site allowance during deploy.
7658
+
7448
7659
  Decision-options contract: when a result contains decision, present EVERY numbered option from the
7449
7660
  tool to the user and wait for their selection. No option is selected by default. Never choose from
7450
7661
  context, paraphrase a selection into different arguments, or invent another option. After the user
@@ -7601,6 +7812,7 @@ function createSakupaMcpServer(opts) {
7601
7812
  registerBillingTools(server, ctx);
7602
7813
  registerCredentialTools(server, ctx);
7603
7814
  registerAppsTools(server, ctx);
7815
+ registerDeleteTools(server, ctx);
7604
7816
  registerHelpTools(server, ctx);
7605
7817
  return server;
7606
7818
  }
package/dist/index.js CHANGED
@@ -148,7 +148,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
148
148
  }
149
149
 
150
150
  // ../core/dist/domain/version.js
151
- var SAKUPA_MCP_VERSION = "1.4.1";
151
+ var SAKUPA_MCP_VERSION = "1.5.0";
152
152
 
153
153
  // ../core/dist/domain/errors.js
154
154
  var HTTP_STATUS = {
@@ -2243,7 +2243,8 @@ var TARGET_MCP_TOOL_NAMES = [
2243
2243
  "change",
2244
2244
  "support",
2245
2245
  "report",
2246
- "apps"
2246
+ "apps",
2247
+ "delete"
2247
2248
  ];
2248
2249
  var STRUCTURED_TOOL_OUTPUT_SCHEMA = z.object({
2249
2250
  schemaVersion: z.literal(1),
@@ -3084,15 +3085,15 @@ function strFromU8(dat, latin1) {
3084
3085
  var slzh = function(d, b) {
3085
3086
  return b + 30 + b2(d, b + 26) + b2(d, b + 28);
3086
3087
  };
3087
- var zh = function(d, b, z7) {
3088
+ var zh = function(d, b, z8) {
3088
3089
  var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
3089
- var _a2 = z64hs(d, es, efl, z7, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
3090
+ var _a2 = z64hs(d, es, efl, z8, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
3090
3091
  return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
3091
3092
  };
3092
- var z64hs = function(d, b, l, z7, sc, su, off) {
3093
+ var z64hs = function(d, b, l, z8, sc, su, off) {
3093
3094
  var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
3094
3095
  var nf = nsc + nsu + noff;
3095
- if (z7 && nf) {
3096
+ if (z8 && nf) {
3096
3097
  for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
3097
3098
  if (b2(d, b) == 1) {
3098
3099
  return [
@@ -3103,7 +3104,7 @@ var z64hs = function(d, b, l, z7, sc, su, off) {
3103
3104
  ];
3104
3105
  }
3105
3106
  }
3106
- if (z7 < 2)
3107
+ if (z8 < 2)
3107
3108
  err(13);
3108
3109
  }
3109
3110
  return [sc, su, off, 0];
@@ -3120,18 +3121,18 @@ function unzipSync(data, opts) {
3120
3121
  if (!c)
3121
3122
  return {};
3122
3123
  var o = b4(data, e + 16);
3123
- var z7 = b4(data, e - 20) == 117853008;
3124
- if (z7) {
3124
+ var z8 = b4(data, e - 20) == 117853008;
3125
+ if (z8) {
3125
3126
  var ze = b4(data, e - 12);
3126
- z7 = b4(data, ze) == 101075792;
3127
- if (z7) {
3127
+ z8 = b4(data, ze) == 101075792;
3128
+ if (z8) {
3128
3129
  c = b4(data, ze + 32);
3129
3130
  o = b4(data, ze + 48);
3130
3131
  }
3131
3132
  }
3132
3133
  var fltr = opts && opts.filter;
3133
3134
  for (var i = 0; i < c; ++i) {
3134
- var _a2 = zh(data, o, z7), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
3135
+ var _a2 = zh(data, o, z8), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
3135
3136
  o = no;
3136
3137
  if (!fltr || fltr({
3137
3138
  name: fn,
@@ -6289,6 +6290,7 @@ var TOOL_TOPICS = [
6289
6290
  "support",
6290
6291
  "report",
6291
6292
  "apps",
6293
+ "delete",
6292
6294
  "help"
6293
6295
  ];
6294
6296
  var HELP_TOPICS = ["diagnose", "overview", "terminology", ...TOOL_TOPICS];
@@ -6399,6 +6401,18 @@ var TOOL_MANUALS = {
6399
6401
  nextStep: "Use the preview resumeWith arguments only after the user confirms.",
6400
6402
  terminology: ["credentialRotation"]
6401
6403
  },
6404
+ delete: {
6405
+ purpose: "Delete the bound FREE site after an explicit preview and confirmation.",
6406
+ sideEffects: "Preview is read-only. Confirmed deletion removes content, the public URL, installed apps, stored submissions and the local .sakupa/site.json.",
6407
+ preconditions: "A valid local site credential and a free site. A subscribed site is refused until the subscription ends (change \u2192 period-end cancellation) and the site reverts to free.",
6408
+ parameterNames: ["confirmed", "operationId", "confirmation"],
6409
+ parameters: "No arguments for the preview; confirmed=true with the exact operationId and confirmation copied from the preview decision.",
6410
+ warnings: [
6411
+ "Irreversible; the next deploy creates a brand-new site with a new URL.",
6412
+ "Never propose delete to free up the free-site allowance; use the deploy handoff decision instead."
6413
+ ],
6414
+ nextStep: "Present every decision option; after confirmation copy the option arguments verbatim."
6415
+ },
6402
6416
  plans: {
6403
6417
  purpose: "Read the authoritative hosting plan catalog and rules.",
6404
6418
  sideEffects: "Read-only public API request.",
@@ -7377,6 +7391,196 @@ function registerAppsTools(server, baseCtx) {
7377
7391
  );
7378
7392
  }
7379
7393
 
7394
+ // src/tools/delete.ts
7395
+ import { randomUUID as randomUUID7 } from "node:crypto";
7396
+ import { z as z7 } from "zod";
7397
+ var confirmationSchema = z7.object({
7398
+ siteId: z7.string(),
7399
+ expectedSiteUpdatedAt: z7.string(),
7400
+ expectedStatus: z7.string(),
7401
+ expectedMode: z7.enum(["free", "paid"]),
7402
+ expectedServingMode: z7.string(),
7403
+ expectedShortId: z7.string().optional(),
7404
+ expectedSubscriptionStatus: z7.string().optional(),
7405
+ expectedPlan: z7.string().optional(),
7406
+ expectedCancelAtPeriodEnd: z7.boolean().optional(),
7407
+ expectedCurrentPeriodEnd: z7.string().optional(),
7408
+ expectedLastDeploymentId: z7.string().optional(),
7409
+ expectedBoundHostnames: z7.array(z7.string()),
7410
+ acknowledge: z7.literal("delete_and_cancel_renewal")
7411
+ });
7412
+ function registerDeleteTools(server, baseCtx) {
7413
+ server.registerTool(
7414
+ "delete",
7415
+ {
7416
+ title: "Delete site",
7417
+ description: "Delete the site bound to this project. Without arguments it only previews (zero writes) and returns a decision; after the user confirms, call again with the exact preview arguments (confirmed: true, operationId, confirmation). A FREE site is deleted immediately: content, public URL, installed apps and stored submissions are gone and the local .sakupa/site.json is removed, so the next deploy creates a brand-new site. A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end cancellation on Stripe); when it ends the site reverts to a free site and delete becomes available. Owner-only.",
7418
+ inputSchema: z7.object({
7419
+ confirmed: z7.boolean().optional().describe("true only from the exact decision arguments returned by the preview."),
7420
+ operationId: z7.string().optional().describe("Copied verbatim from the preview."),
7421
+ confirmation: confirmationSchema.optional().describe("Copied verbatim from the preview; the cloud rejects any drift.")
7422
+ }),
7423
+ outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
7424
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
7425
+ },
7426
+ withDecisionReentry("delete", async (args, call) => {
7427
+ try {
7428
+ const ctx = await withProjectDir(baseCtx, call);
7429
+ const site = requireSiteFile(ctx);
7430
+ if (args.confirmed === true) {
7431
+ if (!args.operationId || !args.confirmation) {
7432
+ return structuredToolResult({
7433
+ schemaVersion: 1,
7434
+ outcome: "blocked",
7435
+ resultCode: "delete_preview_required",
7436
+ summary: summaryMarkdown({
7437
+ title: "Nothing was deleted \u2014 run the preview first",
7438
+ lead: "A confirmed delete must carry the exact operationId and confirmation from the preview. Call delete with no arguments, show the preview to the user, then reuse its decision arguments.",
7439
+ next: ["`delete` with no arguments"]
7440
+ }),
7441
+ data: { siteId: site.siteId },
7442
+ nextActions: [{ tool: "delete", arguments: {}, allowed: true }]
7443
+ });
7444
+ }
7445
+ const result = await ctx.client.deleteSite(site.siteId, site.credential, {
7446
+ operationId: args.operationId,
7447
+ confirmation: args.confirmation
7448
+ });
7449
+ deleteSiteFile(ctx.projectDir);
7450
+ removeCreation(site.siteId);
7451
+ return structuredToolResult({
7452
+ schemaVersion: 1,
7453
+ outcome: "completed",
7454
+ resultCode: result.alreadyDeleted ? "site_already_deleted" : "site_deleted",
7455
+ summary: summaryMarkdown({
7456
+ title: result.alreadyDeleted ? "Site was already deleted" : "Site deleted",
7457
+ lead: `${site.url ?? site.siteId} no longer serves anything (visitors get HTTP 410). The local credential file .sakupa/site.json was removed; this project is no longer bound to any site.`,
7458
+ facts: [
7459
+ ["Site ID", result.siteId],
7460
+ ["Public URL released", site.url],
7461
+ [
7462
+ "Custom hostnames removed",
7463
+ result.removedHostnames.length ? result.removedHostnames.join(", ") : "none"
7464
+ ],
7465
+ ["Serving cleanup still pending", result.servingDeletionPending]
7466
+ ],
7467
+ notes: [
7468
+ "Installed apps and stored form submissions were deleted with the site.",
7469
+ "The free-site allowance this site used on its deployment network is released."
7470
+ ],
7471
+ next: ["`deploy` to publish a brand-new site (new URL, new credential)"]
7472
+ }),
7473
+ data: {
7474
+ ...result,
7475
+ projectDir: ctx.projectDir,
7476
+ localSiteFileRemoved: true,
7477
+ releasedUrl: site.url
7478
+ },
7479
+ nextActions: []
7480
+ });
7481
+ }
7482
+ const operationId = randomUUID7();
7483
+ const preview = await ctx.client.previewDeleteSite(site.siteId, site.credential, {
7484
+ operationId
7485
+ });
7486
+ const c = preview.consequences;
7487
+ if (c.requiresFreeModeBeforeDelete) {
7488
+ const cancellationScheduled = preview.confirmation.expectedCancelAtPeriodEnd === true;
7489
+ return structuredToolResult({
7490
+ schemaVersion: 1,
7491
+ outcome: "blocked",
7492
+ resultCode: "delete_requires_free_mode",
7493
+ summary: summaryMarkdown({
7494
+ title: "This site is subscription-backed \u2014 it cannot be deleted yet",
7495
+ lead: "Nothing was changed. Sakupa deletes only FREE sites. End the subscription first; when the paid period ends the site automatically becomes a free 30-day site, and delete becomes available.",
7496
+ facts: [
7497
+ ["Site", site.url ?? site.siteId],
7498
+ ["Plan", preview.confirmation.expectedPlan],
7499
+ ["Period-end cancellation already scheduled", cancellationScheduled ? "yes" : "no"],
7500
+ [
7501
+ "Paid access ends",
7502
+ c.currentPaidAccessEndsAt ? timestampForAgent(c.currentPaidAccessEndsAt) : void 0
7503
+ ],
7504
+ [
7505
+ "Custom hostnames removed when it reverts",
7506
+ c.removesCustomHostnames.join(", ") || "none"
7507
+ ]
7508
+ ],
7509
+ steps: cancellationScheduled ? [
7510
+ "Wait for the paid period to end; Sakupa then reverts the site to a free 30-day site (content kept, custom domains removed).",
7511
+ "Call delete again at that point \u2014 or simply let the free site expire."
7512
+ ] : [
7513
+ "Call change and open the Stripe-hosted link; the user chooses period-end cancellation there (Sakupa never cancels on its own).",
7514
+ "When the paid period ends, Sakupa reverts the site to a free 30-day site (content kept, custom domains removed).",
7515
+ "Call delete again at that point \u2014 or simply let the free site expire."
7516
+ ],
7517
+ next: cancellationScheduled ? ["`billing`"] : ["`change`", "`billing`"]
7518
+ }),
7519
+ data: {
7520
+ siteId: site.siteId,
7521
+ operationId: preview.operationId,
7522
+ consequences: c,
7523
+ cancellationScheduled
7524
+ },
7525
+ nextActions: [
7526
+ { tool: cancellationScheduled ? "billing" : "change", arguments: {}, allowed: true }
7527
+ ]
7528
+ });
7529
+ }
7530
+ const confirmArguments = {
7531
+ confirmed: true,
7532
+ operationId: preview.operationId,
7533
+ confirmation: preview.confirmation
7534
+ };
7535
+ return presentDecision(baseCtx.decisions, call, "delete", {
7536
+ resultCode: "delete_confirmation_required",
7537
+ summary: summaryMarkdown({
7538
+ title: `Delete ${site.url ?? site.siteId}? Nothing was changed.`,
7539
+ lead: "This is a preview. Deleting cannot be undone: the content, the public URL, installed apps and stored form submissions disappear at once, and the local credential file is removed.",
7540
+ facts: [
7541
+ ["Site ID", site.siteId],
7542
+ ["Public URL", site.url],
7543
+ ["Permanent URL released", c.releasesPermanentUrl],
7544
+ ["Custom hostnames removed", c.removesCustomHostnames.join(", ") || "none"],
7545
+ ["Exact confirm arguments", JSON.stringify(confirmArguments)]
7546
+ ],
7547
+ notes: [
7548
+ "After deletion this project is unbound; the next deploy creates a brand-new site with a new URL and credential.",
7549
+ "To keep the URL but replace the content, deploy again instead of deleting."
7550
+ ]
7551
+ }),
7552
+ data: {
7553
+ siteId: site.siteId,
7554
+ operationId: preview.operationId,
7555
+ consequences: c,
7556
+ confirmation: preview.confirmation,
7557
+ confirmArguments
7558
+ },
7559
+ prompt: `Delete the site ${site.url ?? site.siteId} now? This cannot be undone.`,
7560
+ options: [
7561
+ callToolDecisionOption({
7562
+ id: "delete_site",
7563
+ label: `Delete ${site.url ?? site.siteId}`,
7564
+ description: "Remove the site, its content, apps and submissions, and release the URL.",
7565
+ consequences: [
7566
+ "Visitors get HTTP 410 immediately.",
7567
+ "The local credential file is removed; this project is unbound."
7568
+ ],
7569
+ tool: "delete",
7570
+ arguments: confirmArguments,
7571
+ reasonCode: "explicit_site_delete_confirmation"
7572
+ }),
7573
+ noActionDecisionOption({ description: "Keep the site exactly as it is." })
7574
+ ],
7575
+ legacyUserAction: { type: "confirm_in_mcp", provider: "sakupa" }
7576
+ });
7577
+ } catch (error) {
7578
+ return toolError(error);
7579
+ }
7580
+ })
7581
+ );
7582
+ }
7583
+
7380
7584
  // src/server.ts
7381
7585
  import { resolve as resolve6 } from "node:path";
7382
7586
  var instructionsFor = (hostPattern) => `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
@@ -7413,6 +7617,13 @@ Workflow:
7413
7617
  payment, refund and other customer-service requests. report is the LAST resort only when
7414
7618
  help explicitly recommends a product bug report, and submission still requires user review.
7415
7619
 
7620
+ Deleting a site: delete with no arguments only previews and returns a decision; after the user
7621
+ confirms, call delete again with the exact preview arguments. A FREE site is deleted immediately
7622
+ (content, URL, apps and submissions gone; .sakupa/site.json removed; the next deploy creates a new
7623
+ site). A subscribed site cannot be deleted: end the subscription first (change \u2192 period-end
7624
+ cancellation on Stripe); when it ends the site reverts to a free 30-day site and delete becomes
7625
+ available. Never suggest delete as a way to free up the free-site allowance during deploy.
7626
+
7416
7627
  Decision-options contract: when a result contains decision, present EVERY numbered option from the
7417
7628
  tool to the user and wait for their selection. No option is selected by default. Never choose from
7418
7629
  context, paraphrase a selection into different arguments, or invent another option. After the user
@@ -7569,6 +7780,7 @@ function createSakupaMcpServer(opts) {
7569
7780
  registerBillingTools(server, ctx);
7570
7781
  registerCredentialTools(server, ctx);
7571
7782
  registerAppsTools(server, ctx);
7783
+ registerDeleteTools(server, ctx);
7572
7784
  registerHelpTools(server, ctx);
7573
7785
  return server;
7574
7786
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.myerwang/sakupa",
5
5
  "description": "Sakupa MCP server: publish AI-made static sites from your AI tool. AI-made pages, live in seconds.",
6
6
  "homepage": "https://sakupa.com/manual/",