@sakupa/mcp 0.7.14 → 0.7.15

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 -21
  2. package/dist/index.js +15 -20
  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.15";
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 {
@@ -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) {
@@ -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.
@@ -2800,12 +2800,7 @@ function createSakupaMcpServer(opts) {
2800
2800
  { name: "sakupa", version: MCP_VERSION },
2801
2801
  { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2802
2802
  );
2803
- const ctx = {
2804
- client,
2805
- projectDir: opts.projectDir,
2806
- apiBaseUrl: opts.apiBaseUrl,
2807
- session: { projectDir: null }
2808
- };
2803
+ const ctx = { client, apiBaseUrl: opts.apiBaseUrl };
2809
2804
  registerTools(server, ctx);
2810
2805
  registerBillingTools(server, ctx);
2811
2806
  registerLifecycleTools(server, ctx);
@@ -2819,7 +2814,7 @@ async function main() {
2819
2814
  const transport = new StdioServerTransport();
2820
2815
  await server.connect(transport);
2821
2816
  console.error(
2822
- `[sakupa-mcp] v${MCP_VERSION} connected (api: ${config.apiBaseUrl}, default project: ${config.projectDir}; tools accept per-call projectDir)`
2817
+ `[sakupa-mcp] v${MCP_VERSION} connected (api: ${config.apiBaseUrl}; every tool call requires projectDir)`
2823
2818
  );
2824
2819
  }
2825
2820
  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.15";
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) {
@@ -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.
@@ -2795,12 +2795,7 @@ function createSakupaMcpServer(opts) {
2795
2795
  { name: "sakupa", version: MCP_VERSION },
2796
2796
  { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2797
2797
  );
2798
- const ctx = {
2799
- client,
2800
- projectDir: opts.projectDir,
2801
- apiBaseUrl: opts.apiBaseUrl,
2802
- session: { projectDir: null }
2803
- };
2798
+ const ctx = { client, apiBaseUrl: opts.apiBaseUrl };
2804
2799
  registerTools(server, ctx);
2805
2800
  registerBillingTools(server, ctx);
2806
2801
  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.15",
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",