@sakupa/mcp 0.3.0 → 0.4.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 +25 -12
  2. package/dist/index.js +25 -12
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1057,7 +1057,7 @@ async function analyzeProject(projectDir, opts = {}) {
1057
1057
 
1058
1058
  // src/project-file.ts
1059
1059
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1060
- import { join as join2 } from "node:path";
1060
+ import { dirname, join as join2 } from "node:path";
1061
1061
  var SITE_DIR = ".sakupa";
1062
1062
  var SITE_FILE = "site.json";
1063
1063
  function siteFilePath(projectDir) {
@@ -1100,9 +1100,22 @@ function writeSiteFile(projectDir, file) {
1100
1100
  } catch {
1101
1101
  }
1102
1102
  }
1103
+ function isInsideGitRepo(projectDir) {
1104
+ let dir = projectDir;
1105
+ for (; ; ) {
1106
+ if (existsSync(join2(dir, ".git"))) return true;
1107
+ const parent = dirname(dir);
1108
+ if (parent === dir) return false;
1109
+ dir = parent;
1110
+ }
1111
+ }
1112
+ function credentialGitReminder(projectDir) {
1113
+ if (!isInsideGitRepo(projectDir)) return "";
1114
+ return '\nNOTE: this project is inside a git repository. The management credential in .sakupa/site.json is the key to this site \u2014 do NOT commit it to a PUBLIC repository (add ".sakupa/" to .gitignore yourself if you want to keep it out of version control).';
1115
+ }
1103
1116
 
1104
1117
  // src/version.ts
1105
- var MCP_VERSION = "0.3.0";
1118
+ var MCP_VERSION = "0.4.0";
1106
1119
  var CLIENT_TYPE = "sakupa-mcp";
1107
1120
 
1108
1121
  // src/tools/context.ts
@@ -1318,7 +1331,7 @@ Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
1318
1331
  ` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
1319
1332
  ` : "") + `
1320
1333
  This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh_site extends the validity; subscribing the site (subscribe_site) makes this URL permanent. The management credential was saved to .sakupa/site.json \u2014 keep that file: it is the only way to manage this site.
1321
- ` + (finalized2.warnings.length > 0 ? `
1334
+ ` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
1322
1335
  Warnings:
1323
1336
  ${JSON.stringify(finalized2.warnings, null, 2)}` : "")
1324
1337
  );
@@ -1449,9 +1462,11 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1449
1462
  server.registerTool(
1450
1463
  "bind_domain",
1451
1464
  {
1452
- description: "Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent {shortId}.sakupa.com URL keeps working alongside it. Requires an ACTIVE subscription (subscribe_site). Ownership is proven ONLY by DNS control of the registered apex domain \u2014 payment never grants ownership. A binding request never reserves the hostname: whoever proves DNS control first gets it, and unverified requests expire after 72 hours. Call again with verificationId to check progress.",
1465
+ description: "Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent {shortId}.sakupa.com URL keeps working alongside it. The binding unit is the APEX domain: binding example.com automatically includes www.example.com (both serve the same content, one apex TXT verification covers both), and one site binds at most ONE apex domain \u2014 a second domain needs a second subscribed site. Requires an ACTIVE subscription (subscribe_site). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership. A binding request never reserves the domain: whoever proves DNS control first gets it, and unverified requests expire after 72 hours. Call again with verificationId to check progress.",
1453
1466
  inputSchema: {
1454
- hostname: z.string().describe('Hostname to bind, e.g. "example.com" or "www.example.com".'),
1467
+ hostname: z.string().describe(
1468
+ 'Domain to bind: the apex ("example.com") or its www form ("www.example.com") \u2014 both mean the same unit. Other subdomains cannot be bound.'
1469
+ ),
1455
1470
  verificationId: z.string().optional().describe("Check an existing DNS verification instead of starting a new one.")
1456
1471
  }
1457
1472
  },
@@ -1466,7 +1481,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1466
1481
  return text(
1467
1482
  `DNS verification ${res2.verificationId}: ${res2.status}
1468
1483
  ${res2.message}
1469
- ` + (res2.provisioningJobId ? `Provisioning started (job ${res2.provisioningJobId}). HTTPS certificate and serving setup are in progress; check again with bind_domain + verificationId later.
1484
+ ` + (res2.provisioningJobId ? `Provisioning started (job ${res2.provisioningJobId}). HTTPS certificates and serving setup are in progress; check again with bind_domain + verificationId later.
1470
1485
  ` : "") + (res2.pendingDnsRecords.length > 0 ? `
1471
1486
  DNS records still required:
1472
1487
  ${JSON.stringify(res2.pendingDnsRecords, null, 2)}` : "")
@@ -1477,18 +1492,16 @@ ${JSON.stringify(res2.pendingDnsRecords, null, 2)}` : "")
1477
1492
  hostname: args.hostname
1478
1493
  };
1479
1494
  const res = await ctx.client.bindDomain(site.credential, req);
1480
- const servingHint = res.isApex ? `"${res.hostname}" is an apex domain: your DNS provider must support ALIAS, ANAME, CNAME flattening, or alias records to point it at the assigned CDN domain.` : `"${res.hostname}" is a subdomain: create a CNAME record pointing it at the assigned CDN domain once provisioning completes.`;
1481
1495
  return text(
1482
- `Domain binding started for ${res.hostname}.
1496
+ `Domain binding started for ${res.apexDomain} (includes: ${res.includedHostnames.join(", ")} \u2014 both will serve this site).
1483
1497
 
1484
1498
  1. Prove control of ${res.apexDomain} by creating this DNS record:
1485
1499
  name: ${res.verificationRecord.name}
1486
1500
  type: ${res.verificationRecord.type}
1487
1501
  value: ${res.verificationRecord.value}
1488
- Ownership comes ONLY from DNS control; paying never grants it. This request does not reserve the hostname \u2014 the first verified request wins, and this challenge expires after 72 hours.
1502
+ Ownership comes ONLY from DNS control; paying never grants it. This request does not reserve the domain \u2014 the first verified request wins, and this challenge expires after 72 hours.
1489
1503
 
1490
- 2. Serving DNS (after verification): ${servingHint}
1491
- Server guidance: ${res.servingInstructions}
1504
+ 2. Serving DNS (after verification): ${res.servingInstructions}
1492
1505
 
1493
1506
  Then run bind_domain again with verificationId: "${res.verificationId}" to check verification and start provisioning.`
1494
1507
  );
@@ -1593,7 +1606,7 @@ Site: ${res.siteId} (hostnames: ${res.boundHostnames.join(", ") || "(none)"})
1593
1606
  Previous credentials revoked: ${res.revokedPreviousCredentials ? "YES" : "no (preserved on request)"}
1594
1607
 
1595
1608
  A NEW management credential was written to .sakupa/site.json in this project \u2014 this project now manages the site.
1596
-
1609
+ ` + credentialGitReminder(ctx.projectDir) + `
1597
1610
  Download the current site content (signed URL):
1598
1611
  ${res.archiveUrl}`
1599
1612
  );
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var MCP_VERSION = "0.3.0";
2
+ var MCP_VERSION = "0.4.0";
3
3
  var CLIENT_TYPE = "sakupa-mcp";
4
4
 
5
5
  // ../core/dist/domain/constants.js
@@ -673,7 +673,7 @@ var HttpApiClient = class {
673
673
 
674
674
  // src/project-file.ts
675
675
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
676
- import { join } from "node:path";
676
+ import { dirname, join } from "node:path";
677
677
  var SITE_DIR = ".sakupa";
678
678
  var SITE_FILE = "site.json";
679
679
  function siteFilePath(projectDir) {
@@ -722,6 +722,19 @@ function deleteSiteFile(projectDir) {
722
722
  rmSync(path, { force: true });
723
723
  }
724
724
  }
725
+ function isInsideGitRepo(projectDir) {
726
+ let dir = projectDir;
727
+ for (; ; ) {
728
+ if (existsSync(join(dir, ".git"))) return true;
729
+ const parent = dirname(dir);
730
+ if (parent === dir) return false;
731
+ dir = parent;
732
+ }
733
+ }
734
+ function credentialGitReminder(projectDir) {
735
+ if (!isInsideGitRepo(projectDir)) return "";
736
+ return '\nNOTE: this project is inside a git repository. The management credential in .sakupa/site.json is the key to this site \u2014 do NOT commit it to a PUBLIC repository (add ".sakupa/" to .gitignore yourself if you want to keep it out of version control).';
737
+ }
725
738
 
726
739
  // src/analyze/analyzer.ts
727
740
  import { promises as fs } from "node:fs";
@@ -1366,7 +1379,7 @@ Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
1366
1379
  ` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
1367
1380
  ` : "") + `
1368
1381
  This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh_site extends the validity; subscribing the site (subscribe_site) makes this URL permanent. The management credential was saved to .sakupa/site.json \u2014 keep that file: it is the only way to manage this site.
1369
- ` + (finalized2.warnings.length > 0 ? `
1382
+ ` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
1370
1383
  Warnings:
1371
1384
  ${JSON.stringify(finalized2.warnings, null, 2)}` : "")
1372
1385
  );
@@ -1497,9 +1510,11 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1497
1510
  server.registerTool(
1498
1511
  "bind_domain",
1499
1512
  {
1500
- description: "Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent {shortId}.sakupa.com URL keeps working alongside it. Requires an ACTIVE subscription (subscribe_site). Ownership is proven ONLY by DNS control of the registered apex domain \u2014 payment never grants ownership. A binding request never reserves the hostname: whoever proves DNS control first gets it, and unverified requests expire after 72 hours. Call again with verificationId to check progress.",
1513
+ description: "Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent {shortId}.sakupa.com URL keeps working alongside it. The binding unit is the APEX domain: binding example.com automatically includes www.example.com (both serve the same content, one apex TXT verification covers both), and one site binds at most ONE apex domain \u2014 a second domain needs a second subscribed site. Requires an ACTIVE subscription (subscribe_site). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership. A binding request never reserves the domain: whoever proves DNS control first gets it, and unverified requests expire after 72 hours. Call again with verificationId to check progress.",
1501
1514
  inputSchema: {
1502
- hostname: z.string().describe('Hostname to bind, e.g. "example.com" or "www.example.com".'),
1515
+ hostname: z.string().describe(
1516
+ 'Domain to bind: the apex ("example.com") or its www form ("www.example.com") \u2014 both mean the same unit. Other subdomains cannot be bound.'
1517
+ ),
1503
1518
  verificationId: z.string().optional().describe("Check an existing DNS verification instead of starting a new one.")
1504
1519
  }
1505
1520
  },
@@ -1514,7 +1529,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1514
1529
  return text(
1515
1530
  `DNS verification ${res2.verificationId}: ${res2.status}
1516
1531
  ${res2.message}
1517
- ` + (res2.provisioningJobId ? `Provisioning started (job ${res2.provisioningJobId}). HTTPS certificate and serving setup are in progress; check again with bind_domain + verificationId later.
1532
+ ` + (res2.provisioningJobId ? `Provisioning started (job ${res2.provisioningJobId}). HTTPS certificates and serving setup are in progress; check again with bind_domain + verificationId later.
1518
1533
  ` : "") + (res2.pendingDnsRecords.length > 0 ? `
1519
1534
  DNS records still required:
1520
1535
  ${JSON.stringify(res2.pendingDnsRecords, null, 2)}` : "")
@@ -1525,18 +1540,16 @@ ${JSON.stringify(res2.pendingDnsRecords, null, 2)}` : "")
1525
1540
  hostname: args.hostname
1526
1541
  };
1527
1542
  const res = await ctx.client.bindDomain(site.credential, req);
1528
- const servingHint = res.isApex ? `"${res.hostname}" is an apex domain: your DNS provider must support ALIAS, ANAME, CNAME flattening, or alias records to point it at the assigned CDN domain.` : `"${res.hostname}" is a subdomain: create a CNAME record pointing it at the assigned CDN domain once provisioning completes.`;
1529
1543
  return text(
1530
- `Domain binding started for ${res.hostname}.
1544
+ `Domain binding started for ${res.apexDomain} (includes: ${res.includedHostnames.join(", ")} \u2014 both will serve this site).
1531
1545
 
1532
1546
  1. Prove control of ${res.apexDomain} by creating this DNS record:
1533
1547
  name: ${res.verificationRecord.name}
1534
1548
  type: ${res.verificationRecord.type}
1535
1549
  value: ${res.verificationRecord.value}
1536
- Ownership comes ONLY from DNS control; paying never grants it. This request does not reserve the hostname \u2014 the first verified request wins, and this challenge expires after 72 hours.
1550
+ Ownership comes ONLY from DNS control; paying never grants it. This request does not reserve the domain \u2014 the first verified request wins, and this challenge expires after 72 hours.
1537
1551
 
1538
- 2. Serving DNS (after verification): ${servingHint}
1539
- Server guidance: ${res.servingInstructions}
1552
+ 2. Serving DNS (after verification): ${res.servingInstructions}
1540
1553
 
1541
1554
  Then run bind_domain again with verificationId: "${res.verificationId}" to check verification and start provisioning.`
1542
1555
  );
@@ -1641,7 +1654,7 @@ Site: ${res.siteId} (hostnames: ${res.boundHostnames.join(", ") || "(none)"})
1641
1654
  Previous credentials revoked: ${res.revokedPreviousCredentials ? "YES" : "no (preserved on request)"}
1642
1655
 
1643
1656
  A NEW management credential was written to .sakupa/site.json in this project \u2014 this project now manages the site.
1644
-
1657
+ ` + credentialGitReminder(ctx.projectDir) + `
1645
1658
  Download the current site content (signed URL):
1646
1659
  ${res.archiveUrl}`
1647
1660
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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",