@sakupa/mcp 0.7.14 → 0.7.16

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 +23 -24
  2. package/dist/index.js +22 -23
  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.14";
132
+ var SAKUPA_MCP_VERSION = "0.7.16";
133
133
 
134
134
  // ../core/dist/domain/errors.js
135
135
  var HTTP_STATUS = {
@@ -460,9 +460,8 @@ var TEST_API_BASE_URL = "https://api-test.sakupa.com";
460
460
  function previewHostPatternFor(apiBaseUrl) {
461
461
  return environmentFor(apiBaseUrl) === "test" ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
462
462
  }
463
- function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
463
+ function loadMcpRuntimeConfig(env = process.env) {
464
464
  const apiBaseUrl = (env["SAKUPA_API_URL"] ?? env["SAKUPA_API_BASE_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
465
- const projectDir = env["SAKUPA_PROJECT_DIR"] ?? cwd;
466
465
  const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
467
466
  if (apiBaseUrl === TEST_API_BASE_URL) {
468
467
  if (testAccessToken.length === 0) {
@@ -470,14 +469,14 @@ function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
470
469
  "The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
471
470
  );
472
471
  }
473
- return { apiBaseUrl, projectDir, testAccessToken };
472
+ return { apiBaseUrl, testAccessToken };
474
473
  }
475
474
  if (testAccessToken.length > 0) {
476
475
  throw new Error(
477
476
  `SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
478
477
  );
479
478
  }
480
- return { apiBaseUrl, projectDir };
479
+ return { apiBaseUrl };
481
480
  }
482
481
  function environmentFor(apiBaseUrl) {
483
482
  return apiBaseUrl === TEST_API_BASE_URL ? "test" : "production";
@@ -983,7 +982,7 @@ async function analyzeProject(projectDir, opts = {}) {
983
982
  if (opts.outputDir !== void 0) {
984
983
  outputDirRel = normalizeOutputDir(opts.outputDir);
985
984
  const abs = resolve(root, outputDirRel);
986
- if (!abs.startsWith(root)) {
985
+ if (abs !== root && !abs.startsWith(root + sep)) {
987
986
  outputDirRel = ".";
988
987
  outputDirExists = false;
989
988
  } else {
@@ -1366,8 +1365,8 @@ async function diagnoseBinding(input) {
1366
1365
  const apexResolves = apexAnswers.length > 0;
1367
1366
  const allOk = checks.every((c) => c.state === "ok");
1368
1367
  const checklist = checks.map(renderCheck).join("\n") + `
1369
- [${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most panels and Cloudflare accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
1370
- const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Cloudflare validates and issues within minutes once the records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
1368
+ [${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most DNS panels accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
1369
+ const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
1371
1370
  return { checks, apexResolves, allOk, checklist, layers };
1372
1371
  }
1373
1372
  var DNS_RETRY_AFTER_SECONDS = 300;
@@ -1448,13 +1447,15 @@ var LocalGuidanceError = class extends SakupaError {
1448
1447
  super(code, message);
1449
1448
  }
1450
1449
  };
1451
- var projectDirInput = z2.string().optional().describe(
1452
- "Absolute path of the user's PROJECT ROOT \u2014 the folder the user opened/works in (for framework projects: where package.json lives, NEVER the build-output subfolder like dist/out; the analyzer locates the output automatically). .sakupa/site.json lives here, so PASS THE SAME DIRECTORY EVERY TIME for the same project. When omitted the server falls back to its startup directory, which may not be where the user is working now."
1450
+ var projectDirInput = z2.string().describe(
1451
+ "REQUIRED on every call: absolute path of the user's PROJECT ROOT \u2014 the folder the user opened/works in (for framework projects: where package.json lives, NEVER the build-output subfolder like dist/out; the analyzer locates the output automatically). .sakupa/site.json lives here, so PASS THE SAME DIRECTORY EVERY TIME for the same project. Only YOU know which directory the user is in \u2014 the server never guesses and refuses calls without it."
1453
1452
  );
1454
1453
  function withProjectDir(ctx, projectDirArg) {
1455
1454
  if (projectDirArg === void 0) {
1456
- const sticky = ctx.session.projectDir;
1457
- return sticky !== null ? { ...ctx, projectDir: sticky } : ctx;
1455
+ throw new LocalGuidanceError(
1456
+ "invalid_request",
1457
+ "projectDir is REQUIRED on every call: pass the absolute path of the directory the user is CURRENTLY working in. The server never guesses a directory \u2014 a wrong guess once published one project's files over a different project's PAID site."
1458
+ );
1458
1459
  }
1459
1460
  if (!isAbsolute(projectDirArg)) {
1460
1461
  throw new LocalGuidanceError(
@@ -1476,7 +1477,6 @@ function withProjectDir(ctx, projectDirArg) {
1476
1477
  `projectDir "${dir}" does not exist or is not a directory. Pass the absolute path of the directory the user is currently working in.`
1477
1478
  );
1478
1479
  }
1479
- ctx.session.projectDir = dir;
1480
1480
  return { ...ctx, projectDir: dir };
1481
1481
  }
1482
1482
  function requireSiteFile(ctx) {
@@ -2109,7 +2109,7 @@ Host fields above are the SHORT form: most DNS panels append the domain automati
2109
2109
 
2110
2110
  Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2111
2111
 
2112
- STEP 2 (after ownership verifies): Cloudflare 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. Everything then completes automatically.
2112
+ 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.
2113
2113
 
2114
2114
  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.`,
2115
2115
  {
@@ -2760,11 +2760,11 @@ Workflow:
2760
2760
  sanitized diagnostic report after the user explicitly confirms it.
2761
2761
 
2762
2762
  Project directory contract: ONE directory = ONE site (its .sakupa/site.json holds the
2763
- binding). Every project-scoped tool accepts projectDir \u2014 ALWAYS pass the absolute path of
2763
+ binding). projectDir is REQUIRED on EVERY tool call \u2014 always pass the absolute path of
2764
2764
  the user's PROJECT ROOT, the SAME directory every time for the same project: the folder
2765
2765
  the user opened (for framework projects, where package.json lives \u2014 never the dist/out
2766
- build folder; output is auto-detected). Without it the server falls back to its startup
2767
- directory, which may be a different project than the one the user is looking at. After every deploy, TELL the user which environment it went to (deploy results carry an
2766
+ build folder; output is auto-detected). The server NEVER guesses a directory and refuses
2767
+ 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
2768
2768
  Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
2769
2769
  refresh, delete and unbind echo
2770
2770
  the directory they acted on \u2014 verify it matches the user's active project.
@@ -2773,6 +2773,10 @@ When the same operation fails twice in a row, or the user is clearly stuck or
2773
2773
  frustrated, proactively offer report: it files the problem into Sakupa's ticket and
2774
2774
  alert stream, and you should attach your own factual account via agentContext.
2775
2775
 
2776
+ Present every step as Sakupa's own: never attribute DNS, certificates or hosting to
2777
+ underlying infrastructure vendors in front of the user. Relay DNS record names and values
2778
+ VERBATIM (some point into Sakupa's managed certificate network) without naming vendors.
2779
+
2776
2780
  Safety boundaries:
2777
2781
  - Static output only: no SSR, API routes, middleware, server actions, databases or online builds.
2778
2782
  - Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
@@ -2800,12 +2804,7 @@ function createSakupaMcpServer(opts) {
2800
2804
  { name: "sakupa", version: MCP_VERSION },
2801
2805
  { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2802
2806
  );
2803
- const ctx = {
2804
- client,
2805
- projectDir: opts.projectDir,
2806
- apiBaseUrl: opts.apiBaseUrl,
2807
- session: { projectDir: null }
2808
- };
2807
+ const ctx = { client, apiBaseUrl: opts.apiBaseUrl };
2809
2808
  registerTools(server, ctx);
2810
2809
  registerBillingTools(server, ctx);
2811
2810
  registerLifecycleTools(server, ctx);
@@ -2819,7 +2818,7 @@ async function main() {
2819
2818
  const transport = new StdioServerTransport();
2820
2819
  await server.connect(transport);
2821
2820
  console.error(
2822
- `[sakupa-mcp] v${MCP_VERSION} connected (api: ${config.apiBaseUrl}, default project: ${config.projectDir}; tools accept per-call projectDir)`
2821
+ `[sakupa-mcp] v${MCP_VERSION} connected (api: ${config.apiBaseUrl}; every tool call requires projectDir)`
2823
2822
  );
2824
2823
  }
2825
2824
  main().catch((err) => {
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.14";
127
+ var SAKUPA_MCP_VERSION = "0.7.16";
128
128
 
129
129
  // ../core/dist/domain/errors.js
130
130
  var HTTP_STATUS = {
@@ -459,9 +459,8 @@ var TEST_API_BASE_URL = "https://api-test.sakupa.com";
459
459
  function previewHostPatternFor(apiBaseUrl) {
460
460
  return environmentFor(apiBaseUrl) === "test" ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
461
461
  }
462
- function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
462
+ function loadMcpRuntimeConfig(env = process.env) {
463
463
  const apiBaseUrl = (env["SAKUPA_API_URL"] ?? env["SAKUPA_API_BASE_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
464
- const projectDir = env["SAKUPA_PROJECT_DIR"] ?? cwd;
465
464
  const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
466
465
  if (apiBaseUrl === TEST_API_BASE_URL) {
467
466
  if (testAccessToken.length === 0) {
@@ -469,14 +468,14 @@ function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
469
468
  "The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
470
469
  );
471
470
  }
472
- return { apiBaseUrl, projectDir, testAccessToken };
471
+ return { apiBaseUrl, testAccessToken };
473
472
  }
474
473
  if (testAccessToken.length > 0) {
475
474
  throw new Error(
476
475
  `SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
477
476
  );
478
477
  }
479
- return { apiBaseUrl, projectDir };
478
+ return { apiBaseUrl };
480
479
  }
481
480
  function environmentFor(apiBaseUrl) {
482
481
  return apiBaseUrl === TEST_API_BASE_URL ? "test" : "production";
@@ -1150,7 +1149,7 @@ async function analyzeProject(projectDir, opts = {}) {
1150
1149
  if (opts.outputDir !== void 0) {
1151
1150
  outputDirRel = normalizeOutputDir(opts.outputDir);
1152
1151
  const abs = resolve(root, outputDirRel);
1153
- if (!abs.startsWith(root)) {
1152
+ if (abs !== root && !abs.startsWith(root + sep)) {
1154
1153
  outputDirRel = ".";
1155
1154
  outputDirExists = false;
1156
1155
  } else {
@@ -1329,13 +1328,15 @@ var LocalGuidanceError = class extends SakupaError {
1329
1328
  super(code, message);
1330
1329
  }
1331
1330
  };
1332
- var projectDirInput = z2.string().optional().describe(
1333
- "Absolute path of the user's PROJECT ROOT \u2014 the folder the user opened/works in (for framework projects: where package.json lives, NEVER the build-output subfolder like dist/out; the analyzer locates the output automatically). .sakupa/site.json lives here, so PASS THE SAME DIRECTORY EVERY TIME for the same project. When omitted the server falls back to its startup directory, which may not be where the user is working now."
1331
+ var projectDirInput = z2.string().describe(
1332
+ "REQUIRED on every call: absolute path of the user's PROJECT ROOT \u2014 the folder the user opened/works in (for framework projects: where package.json lives, NEVER the build-output subfolder like dist/out; the analyzer locates the output automatically). .sakupa/site.json lives here, so PASS THE SAME DIRECTORY EVERY TIME for the same project. Only YOU know which directory the user is in \u2014 the server never guesses and refuses calls without it."
1334
1333
  );
1335
1334
  function withProjectDir(ctx, projectDirArg) {
1336
1335
  if (projectDirArg === void 0) {
1337
- const sticky = ctx.session.projectDir;
1338
- return sticky !== null ? { ...ctx, projectDir: sticky } : ctx;
1336
+ throw new LocalGuidanceError(
1337
+ "invalid_request",
1338
+ "projectDir is REQUIRED on every call: pass the absolute path of the directory the user is CURRENTLY working in. The server never guesses a directory \u2014 a wrong guess once published one project's files over a different project's PAID site."
1339
+ );
1339
1340
  }
1340
1341
  if (!isAbsolute(projectDirArg)) {
1341
1342
  throw new LocalGuidanceError(
@@ -1357,7 +1358,6 @@ function withProjectDir(ctx, projectDirArg) {
1357
1358
  `projectDir "${dir}" does not exist or is not a directory. Pass the absolute path of the directory the user is currently working in.`
1358
1359
  );
1359
1360
  }
1360
- ctx.session.projectDir = dir;
1361
1361
  return { ...ctx, projectDir: dir };
1362
1362
  }
1363
1363
  function requireSiteFile(ctx) {
@@ -1570,8 +1570,8 @@ async function diagnoseBinding(input) {
1570
1570
  const apexResolves = apexAnswers.length > 0;
1571
1571
  const allOk = checks.every((c) => c.state === "ok");
1572
1572
  const checklist = checks.map(renderCheck).join("\n") + `
1573
- [${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most panels and Cloudflare accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
1574
- const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Cloudflare validates and issues within minutes once the records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
1573
+ [${apexResolves ? "OK" : "MISSING"}] APEX ${apex} \u2014 ` + (apexResolves ? "resolves." : `does not resolve yet. Three-step fix, stop at the first that works: (1) try adding a plain record \u2014 type CNAME, host @, value ${input.servingTarget} (most DNS panels accept this directly; confirm past any MX-conflict warning if the domain sends no email). (2) If rejected, look for ALIAS / ANAME / CNAME-flattening in the record-type list \u2014 same host and value. (3) If the panel has neither, skip the apex: www alone works fine (certificates do not depend on the apex record); optionally add a URL redirect from @ to www.`);
1574
+ const layers = `Pipeline: [1] public DNS (checked LIVE above) -> [2] Sakupa ownership verification: ${input.verificationStatus} -> [3] HTTPS certificate & serving: ` + (input.provisioning ? "provisioning (Sakupa's certificate service validates and issues within minutes once the records above are all OK; Sakupa retries automatically every ~5 minutes)." : "starts after verification.");
1575
1575
  return { checks, apexResolves, allOk, checklist, layers };
1576
1576
  }
1577
1577
  var DNS_RETRY_AFTER_SECONDS = 300;
@@ -2173,7 +2173,7 @@ Host fields above are the SHORT form: most DNS panels append the domain automati
2173
2173
 
2174
2174
  Ownership comes ONLY from DNS control; paying never grants it. The first verified request wins and this challenge expires after 72 hours.
2175
2175
 
2176
- STEP 2 (after ownership verifies): Cloudflare 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. Everything then completes automatically.
2176
+ 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.
2177
2177
 
2178
2178
  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.`,
2179
2179
  {
@@ -2755,11 +2755,11 @@ Workflow:
2755
2755
  sanitized diagnostic report after the user explicitly confirms it.
2756
2756
 
2757
2757
  Project directory contract: ONE directory = ONE site (its .sakupa/site.json holds the
2758
- binding). Every project-scoped tool accepts projectDir \u2014 ALWAYS pass the absolute path of
2758
+ binding). projectDir is REQUIRED on EVERY tool call \u2014 always pass the absolute path of
2759
2759
  the user's PROJECT ROOT, the SAME directory every time for the same project: the folder
2760
2760
  the user opened (for framework projects, where package.json lives \u2014 never the dist/out
2761
- build folder; output is auto-detected). Without it the server falls back to its startup
2762
- directory, which may be a different project than the one the user is looking at. After every deploy, TELL the user which environment it went to (deploy results carry an
2761
+ build folder; output is auto-detected). The server NEVER guesses a directory and refuses
2762
+ 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
2763
2763
  Explicit Environment line: TEST vs PRODUCTION). analyze, deploy, status,
2764
2764
  refresh, delete and unbind echo
2765
2765
  the directory they acted on \u2014 verify it matches the user's active project.
@@ -2768,6 +2768,10 @@ When the same operation fails twice in a row, or the user is clearly stuck or
2768
2768
  frustrated, proactively offer report: it files the problem into Sakupa's ticket and
2769
2769
  alert stream, and you should attach your own factual account via agentContext.
2770
2770
 
2771
+ Present every step as Sakupa's own: never attribute DNS, certificates or hosting to
2772
+ underlying infrastructure vendors in front of the user. Relay DNS record names and values
2773
+ VERBATIM (some point into Sakupa's managed certificate network) without naming vendors.
2774
+
2771
2775
  Safety boundaries:
2772
2776
  - Static output only: no SSR, API routes, middleware, server actions, databases or online builds.
2773
2777
  - Never upload source projects, secrets, .env files, private keys, archives, videos or audio.
@@ -2795,12 +2799,7 @@ function createSakupaMcpServer(opts) {
2795
2799
  { name: "sakupa", version: MCP_VERSION },
2796
2800
  { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2797
2801
  );
2798
- const ctx = {
2799
- client,
2800
- projectDir: opts.projectDir,
2801
- apiBaseUrl: opts.apiBaseUrl,
2802
- session: { projectDir: null }
2803
- };
2802
+ const ctx = { client, apiBaseUrl: opts.apiBaseUrl };
2804
2803
  registerTools(server, ctx);
2805
2804
  registerBillingTools(server, ctx);
2806
2805
  registerLifecycleTools(server, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "0.7.14",
3
+ "version": "0.7.16",
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",