@sakupa/mcp 0.7.0 → 0.7.2

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 +64 -17
  2. package/dist/index.js +64 -13
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -7,6 +7,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
7
7
  var SERVICE_DOMAIN = "sakupa.com";
8
8
  var DEFAULT_API_BASE_URL = "https://api.sakupa.com";
9
9
  var FREE_SITE_URL_SUFFIX = `.${SERVICE_DOMAIN}`;
10
+ var TEST_ACCESS_HEADER = "x-sakupa-test-token";
10
11
  var FREE_SITE_TTL_HOURS = 24;
11
12
  var FREE_SITE_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
12
13
  var PAID_SITE_MAX_TOTAL_BYTES = 2 * 1024 * 1024 * 1024;
@@ -127,7 +128,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
127
128
  var ALLOWED_HIDDEN_PATHS = [".well-known/"];
128
129
 
129
130
  // ../core/dist/domain/version.js
130
- var SAKUPA_MCP_VERSION = "0.7.0";
131
+ var SAKUPA_MCP_VERSION = "0.7.2";
131
132
 
132
133
  // ../core/dist/domain/errors.js
133
134
  var HTTP_STATUS = {
@@ -464,6 +465,31 @@ var utf8Encoder = new TextEncoder();
464
465
  // ../core/dist/services/subscriptions.js
465
466
  var WEBHOOK_PROCESSING_LEASE_MS = 5 * 60 * 1e3;
466
467
 
468
+ // src/config.ts
469
+ var TEST_API_BASE_URL = "https://api-test.sakupa.com";
470
+ function previewHostPatternFor(apiBaseUrl) {
471
+ return apiBaseUrl === TEST_API_BASE_URL ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
472
+ }
473
+ function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
474
+ const apiBaseUrl = (env["SAKUPA_API_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
475
+ const projectDir = env["SAKUPA_PROJECT_DIR"] ?? cwd;
476
+ const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
477
+ if (apiBaseUrl === TEST_API_BASE_URL) {
478
+ if (testAccessToken.length === 0) {
479
+ throw new Error(
480
+ "The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
481
+ );
482
+ }
483
+ return { apiBaseUrl, projectDir, testAccessToken };
484
+ }
485
+ if (testAccessToken.length > 0) {
486
+ throw new Error(
487
+ `SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
488
+ );
489
+ }
490
+ return { apiBaseUrl, projectDir };
491
+ }
492
+
467
493
  // src/server.ts
468
494
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
469
495
 
@@ -1371,6 +1397,7 @@ async function uploadAll(ctx, targets, files, outputAbs) {
1371
1397
  return targets.length;
1372
1398
  }
1373
1399
  function registerTools(server, ctx) {
1400
+ const previewHostPattern = previewHostPatternFor(ctx.apiBaseUrl);
1374
1401
  server.registerTool(
1375
1402
  "analyze_site",
1376
1403
  {
@@ -1404,7 +1431,7 @@ Next action: ${analysis.suggestedNextAction}`,
1404
1431
  server.registerTool(
1405
1432
  "deploy_site",
1406
1433
  {
1407
- description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_HOURS}h, public URL like https://{shortId}.sakupa.com) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscribed sites are permanent). Runs analyze_site first and refuses to upload source projects, secrets, .env files, archives, media or server code. Never uploads anything when the analysis says the project is not deployable.`,
1434
+ description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_HOURS}h, public URL like https://${previewHostPattern}) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscribed sites are permanent). Runs analyze_site first and refuses to upload source projects, secrets, .env files, archives, media or server code. Never uploads anything when the analysis says the project is not deployable.`,
1408
1435
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1409
1436
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1410
1437
  inputSchema: {
@@ -1587,7 +1614,7 @@ Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refre
1587
1614
  server.registerTool(
1588
1615
  "subscribe_site",
1589
1616
  {
1590
- description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). Paying makes the site PERMANENT on its {shortId}.sakupa.com URL \u2014 no more 24h expiry; that is the core value of paying. Binding a custom domain afterwards (bind_domain) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy_site first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
1617
+ description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). Paying makes the site PERMANENT on its ${previewHostPattern} URL \u2014 no more 24h expiry; that is the core value of paying. Binding a custom domain afterwards (bind_domain) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy_site first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
1591
1618
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1592
1619
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1593
1620
  inputSchema: {
@@ -1632,7 +1659,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1632
1659
  server.registerTool(
1633
1660
  "bind_domain",
1634
1661
  {
1635
- 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.",
1662
+ description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} 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.`,
1636
1663
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1637
1664
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1638
1665
  inputSchema: {
@@ -2195,8 +2222,23 @@ function registerLifecycleTools(server, ctx) {
2195
2222
  // src/transport.ts
2196
2223
  var FetchTransport = class {
2197
2224
  baseUrl;
2198
- constructor(baseUrl) {
2225
+ testAccessToken;
2226
+ constructor(baseUrl, options = {}) {
2199
2227
  this.baseUrl = baseUrl.replace(/\/+$/, "");
2228
+ if (options.testAccessToken && this.baseUrl !== TEST_API_BASE_URL) {
2229
+ throw new Error(`Test access credentials may only be sent to ${TEST_API_BASE_URL}.`);
2230
+ }
2231
+ this.testAccessToken = options.testAccessToken;
2232
+ }
2233
+ testAccessHeadersFor(_url) {
2234
+ if (!this.testAccessToken) return {};
2235
+ let target;
2236
+ try {
2237
+ target = new URL(_url);
2238
+ } catch {
2239
+ return {};
2240
+ }
2241
+ return target.origin === TEST_API_BASE_URL ? { [TEST_ACCESS_HEADER]: this.testAccessToken } : {};
2200
2242
  }
2201
2243
  async request(req) {
2202
2244
  let url = `${this.baseUrl}${req.path}`;
@@ -2207,7 +2249,8 @@ var FetchTransport = class {
2207
2249
  accept: "application/json",
2208
2250
  [MCP_VERSION_HEADER]: MCP_VERSION,
2209
2251
  ...req.body !== void 0 ? { "content-type": "application/json" } : {},
2210
- ...req.headers
2252
+ ...req.headers,
2253
+ ...this.testAccessHeadersFor(url)
2211
2254
  };
2212
2255
  const res = await fetch(url, {
2213
2256
  method: req.method,
@@ -2233,7 +2276,10 @@ var FetchTransport = class {
2233
2276
  }
2234
2277
  const res = await fetch(target.url, {
2235
2278
  method: target.method,
2236
- headers: target.headers,
2279
+ headers: {
2280
+ ...target.headers,
2281
+ ...this.testAccessHeadersFor(target.url)
2282
+ },
2237
2283
  body
2238
2284
  });
2239
2285
  if (!res.ok) {
@@ -2246,25 +2292,25 @@ var FetchTransport = class {
2246
2292
  };
2247
2293
 
2248
2294
  // src/server.ts
2249
- var INSTRUCTIONS = `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
2295
+ var instructionsFor = (hostPattern) => `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
2250
2296
 
2251
2297
  Workflow:
2252
2298
  1. analyze_site \u2014 check whether this project has deployable static output. If a build is needed
2253
2299
  (Vite/Vue/React/Svelte/Astro/Next static export/Nuxt generate), run the build LOCALLY first,
2254
2300
  then re-run analyze_site.
2255
2301
  2. deploy_site \u2014 uploads ONLY the built static output. The first deploy creates a free temporary
2256
- site (public URL {shortId}.sakupa.com, valid 24 hours, free banner shown) and stores the
2302
+ site (public URL ${hostPattern}, valid 24 hours, free banner shown) and stores the
2257
2303
  management credential in .sakupa/site.json. Deploying again updates the site and refreshes
2258
2304
  its validity; refresh_site extends validity without uploading.
2259
2305
  3. To make the site PERMANENT, subscribe it to a monthly hosting plan (subscribe_site ->
2260
2306
  Stripe-hosted checkout; water/personal/share/business). Paying makes the
2261
- {shortId}.sakupa.com URL permanent \u2014 that is what payment buys. Usage over the chosen plan
2307
+ ${hostPattern} URL permanent \u2014 that is what payment buys. Usage over the chosen plan
2262
2308
  shows an over-limit notice by default. An external AI may periodically query usage and
2263
2309
  recommend a plan, but Sakupa never changes a subscription automatically.
2264
2310
  4. Optionally bind a custom domain to the subscribed site (bind_domain): an included extra
2265
2311
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
2266
2312
  first verified request wins; unverified requests expire after 72 hours. billing_status,
2267
- billing_status, change_subscription_plan, manage_billing and recover_domain_site manage the paid
2313
+ change_subscription_plan, manage_billing and recover_domain_site manage the paid
2268
2314
  lifecycle. Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
2269
2315
  A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
2270
2316
  24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
@@ -2279,10 +2325,12 @@ Safety boundaries:
2279
2325
  Stripe's public no-code portal login, where the customer verifies the checkout email with a
2280
2326
  Stripe one-time passcode; it never restores site authority.`;
2281
2327
  function createSakupaMcpServer(opts) {
2282
- const client = opts.client ?? new HttpApiClient(new FetchTransport(opts.apiBaseUrl));
2328
+ const client = opts.client ?? new HttpApiClient(
2329
+ new FetchTransport(opts.apiBaseUrl, { testAccessToken: opts.testAccessToken })
2330
+ );
2283
2331
  const server = new McpServer(
2284
2332
  { name: "sakupa", version: MCP_VERSION },
2285
- { instructions: INSTRUCTIONS }
2333
+ { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2286
2334
  );
2287
2335
  registerTools(server, {
2288
2336
  client,
@@ -2304,13 +2352,12 @@ function createSakupaMcpServer(opts) {
2304
2352
 
2305
2353
  // src/bin.ts
2306
2354
  async function main() {
2307
- const apiBaseUrl = process.env["SAKUPA_API_URL"] ?? DEFAULT_API_BASE_URL;
2308
- const projectDir = process.env["SAKUPA_PROJECT_DIR"] ?? process.cwd();
2309
- const server = createSakupaMcpServer({ apiBaseUrl, projectDir });
2355
+ const config = loadMcpRuntimeConfig();
2356
+ const server = createSakupaMcpServer(config);
2310
2357
  const transport = new StdioServerTransport();
2311
2358
  await server.connect(transport);
2312
2359
  console.error(
2313
- `[sakupa-mcp] v${MCP_VERSION} connected (api: ${apiBaseUrl}, project: ${projectDir})`
2360
+ `[sakupa-mcp] v${MCP_VERSION} connected (api: ${config.apiBaseUrl}, project: ${config.projectDir})`
2314
2361
  );
2315
2362
  }
2316
2363
  main().catch((err) => {
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // ../core/dist/domain/constants.js
2
2
  var SERVICE_DOMAIN = "sakupa.com";
3
+ var DEFAULT_API_BASE_URL = "https://api.sakupa.com";
3
4
  var FREE_SITE_URL_SUFFIX = `.${SERVICE_DOMAIN}`;
5
+ var TEST_ACCESS_HEADER = "x-sakupa-test-token";
4
6
  var FREE_SITE_TTL_HOURS = 24;
5
7
  var FREE_SITE_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
6
8
  var PAID_SITE_MAX_TOTAL_BYTES = 2 * 1024 * 1024 * 1024;
@@ -121,7 +123,7 @@ var FORBIDDEN_PATH_SEGMENTS = [
121
123
  var ALLOWED_HIDDEN_PATHS = [".well-known/"];
122
124
 
123
125
  // ../core/dist/domain/version.js
124
- var SAKUPA_MCP_VERSION = "0.7.0";
126
+ var SAKUPA_MCP_VERSION = "0.7.2";
125
127
 
126
128
  // ../core/dist/domain/errors.js
127
129
  var HTTP_STATUS = {
@@ -462,11 +464,51 @@ var WEBHOOK_PROCESSING_LEASE_MS = 5 * 60 * 1e3;
462
464
  var MCP_VERSION = SAKUPA_MCP_VERSION;
463
465
  var CLIENT_TYPE = "sakupa-mcp";
464
466
 
467
+ // src/config.ts
468
+ var TEST_API_BASE_URL = "https://api-test.sakupa.com";
469
+ function previewHostPatternFor(apiBaseUrl) {
470
+ return apiBaseUrl === TEST_API_BASE_URL ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
471
+ }
472
+ function loadMcpRuntimeConfig(env = process.env, cwd = process.cwd()) {
473
+ const apiBaseUrl = (env["SAKUPA_API_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
474
+ const projectDir = env["SAKUPA_PROJECT_DIR"] ?? cwd;
475
+ const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
476
+ if (apiBaseUrl === TEST_API_BASE_URL) {
477
+ if (testAccessToken.length === 0) {
478
+ throw new Error(
479
+ "The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
480
+ );
481
+ }
482
+ return { apiBaseUrl, projectDir, testAccessToken };
483
+ }
484
+ if (testAccessToken.length > 0) {
485
+ throw new Error(
486
+ `SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
487
+ );
488
+ }
489
+ return { apiBaseUrl, projectDir };
490
+ }
491
+
465
492
  // src/transport.ts
466
493
  var FetchTransport = class {
467
494
  baseUrl;
468
- constructor(baseUrl) {
495
+ testAccessToken;
496
+ constructor(baseUrl, options = {}) {
469
497
  this.baseUrl = baseUrl.replace(/\/+$/, "");
498
+ if (options.testAccessToken && this.baseUrl !== TEST_API_BASE_URL) {
499
+ throw new Error(`Test access credentials may only be sent to ${TEST_API_BASE_URL}.`);
500
+ }
501
+ this.testAccessToken = options.testAccessToken;
502
+ }
503
+ testAccessHeadersFor(_url) {
504
+ if (!this.testAccessToken) return {};
505
+ let target;
506
+ try {
507
+ target = new URL(_url);
508
+ } catch {
509
+ return {};
510
+ }
511
+ return target.origin === TEST_API_BASE_URL ? { [TEST_ACCESS_HEADER]: this.testAccessToken } : {};
470
512
  }
471
513
  async request(req) {
472
514
  let url = `${this.baseUrl}${req.path}`;
@@ -477,7 +519,8 @@ var FetchTransport = class {
477
519
  accept: "application/json",
478
520
  [MCP_VERSION_HEADER]: MCP_VERSION,
479
521
  ...req.body !== void 0 ? { "content-type": "application/json" } : {},
480
- ...req.headers
522
+ ...req.headers,
523
+ ...this.testAccessHeadersFor(url)
481
524
  };
482
525
  const res = await fetch(url, {
483
526
  method: req.method,
@@ -503,7 +546,10 @@ var FetchTransport = class {
503
546
  }
504
547
  const res = await fetch(target.url, {
505
548
  method: target.method,
506
- headers: target.headers,
549
+ headers: {
550
+ ...target.headers,
551
+ ...this.testAccessHeadersFor(target.url)
552
+ },
507
553
  body
508
554
  });
509
555
  if (!res.ok) {
@@ -1413,6 +1459,7 @@ async function uploadAll(ctx, targets, files, outputAbs) {
1413
1459
  return targets.length;
1414
1460
  }
1415
1461
  function registerTools(server, ctx) {
1462
+ const previewHostPattern = previewHostPatternFor(ctx.apiBaseUrl);
1416
1463
  server.registerTool(
1417
1464
  "analyze_site",
1418
1465
  {
@@ -1446,7 +1493,7 @@ Next action: ${analysis.suggestedNextAction}`,
1446
1493
  server.registerTool(
1447
1494
  "deploy_site",
1448
1495
  {
1449
- description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_HOURS}h, public URL like https://{shortId}.sakupa.com) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscribed sites are permanent). Runs analyze_site first and refuses to upload source projects, secrets, .env files, archives, media or server code. Never uploads anything when the analysis says the project is not deployable.`,
1496
+ description: `Deploy the local static output to Sakupa. First deploy creates a free temporary site (valid ${FREE_SITE_TTL_HOURS}h, public URL like https://${previewHostPattern}) and stores the management credential in .sakupa/site.json. Later runs update the existing site (free sites also refresh their validity; subscribed sites are permanent). Runs analyze_site first and refuses to upload source projects, secrets, .env files, archives, media or server code. Never uploads anything when the analysis says the project is not deployable.`,
1450
1497
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1451
1498
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1452
1499
  inputSchema: {
@@ -1629,7 +1676,7 @@ Free sites stay live for ${FREE_SITE_TTL_HOURS} hours after each deploy or refre
1629
1676
  server.registerTool(
1630
1677
  "subscribe_site",
1631
1678
  {
1632
- description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). Paying makes the site PERMANENT on its {shortId}.sakupa.com URL \u2014 no more 24h expiry; that is the core value of paying. Binding a custom domain afterwards (bind_domain) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy_site first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
1679
+ description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). Paying makes the site PERMANENT on its ${previewHostPattern} URL \u2014 no more 24h expiry; that is the core value of paying. Binding a custom domain afterwards (bind_domain) is an optional included extra and requires DNS control of that domain. Owner-only: requires this project's site credential (.sakupa/site.json) \u2014 deploy_site first. If the site outgrows its plan, Sakupa shows an over-limit notice and never changes billing automatically. The owner can explicitly choose another plan through Stripe Customer Portal. Card details are entered only on the Stripe-hosted page \u2014 never through the AI tool. Opening and completing Stripe Checkout is the final subscription confirmation.`,
1633
1680
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1634
1681
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1635
1682
  inputSchema: {
@@ -1674,7 +1721,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
1674
1721
  server.registerTool(
1675
1722
  "bind_domain",
1676
1723
  {
1677
- 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.",
1724
+ description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the permanent ${previewHostPattern} 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.`,
1678
1725
  outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
1679
1726
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1680
1727
  inputSchema: {
@@ -2238,25 +2285,25 @@ function registerBillingTools(server, ctx) {
2238
2285
  }
2239
2286
 
2240
2287
  // src/server.ts
2241
- var INSTRUCTIONS = `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
2288
+ var instructionsFor = (hostPattern) => `Sakupa publishes AI-made static websites. AI-made pages, live in seconds.
2242
2289
 
2243
2290
  Workflow:
2244
2291
  1. analyze_site \u2014 check whether this project has deployable static output. If a build is needed
2245
2292
  (Vite/Vue/React/Svelte/Astro/Next static export/Nuxt generate), run the build LOCALLY first,
2246
2293
  then re-run analyze_site.
2247
2294
  2. deploy_site \u2014 uploads ONLY the built static output. The first deploy creates a free temporary
2248
- site (public URL {shortId}.sakupa.com, valid 24 hours, free banner shown) and stores the
2295
+ site (public URL ${hostPattern}, valid 24 hours, free banner shown) and stores the
2249
2296
  management credential in .sakupa/site.json. Deploying again updates the site and refreshes
2250
2297
  its validity; refresh_site extends validity without uploading.
2251
2298
  3. To make the site PERMANENT, subscribe it to a monthly hosting plan (subscribe_site ->
2252
2299
  Stripe-hosted checkout; water/personal/share/business). Paying makes the
2253
- {shortId}.sakupa.com URL permanent \u2014 that is what payment buys. Usage over the chosen plan
2300
+ ${hostPattern} URL permanent \u2014 that is what payment buys. Usage over the chosen plan
2254
2301
  shows an over-limit notice by default. An external AI may periodically query usage and
2255
2302
  recommend a plan, but Sakupa never changes a subscription automatically.
2256
2303
  4. Optionally bind a custom domain to the subscribed site (bind_domain): an included extra
2257
2304
  serving surface alongside the permanent URL. Ownership is proven only by DNS control; the
2258
2305
  first verified request wins; unverified requests expire after 72 hours. billing_status,
2259
- billing_status, change_subscription_plan, manage_billing and recover_domain_site manage the paid
2306
+ change_subscription_plan, manage_billing and recover_domain_site manage the paid
2260
2307
  lifecycle. Plan changes are confirmed only on Stripe and synchronized by Stripe webhook.
2261
2308
  A cancellation keeps the site paid through the current period. Sakupa reverts it to a free
2262
2309
  24h site and removes paid data after Stripe sends the signed final-cancellation webhook.
@@ -2271,10 +2318,12 @@ Safety boundaries:
2271
2318
  Stripe's public no-code portal login, where the customer verifies the checkout email with a
2272
2319
  Stripe one-time passcode; it never restores site authority.`;
2273
2320
  function createSakupaMcpServer(opts) {
2274
- const client = opts.client ?? new HttpApiClient(new FetchTransport(opts.apiBaseUrl));
2321
+ const client = opts.client ?? new HttpApiClient(
2322
+ new FetchTransport(opts.apiBaseUrl, { testAccessToken: opts.testAccessToken })
2323
+ );
2275
2324
  const server = new McpServer(
2276
2325
  { name: "sakupa", version: MCP_VERSION },
2277
- { instructions: INSTRUCTIONS }
2326
+ { instructions: instructionsFor(previewHostPatternFor(opts.apiBaseUrl)) }
2278
2327
  );
2279
2328
  registerTools(server, {
2280
2329
  client,
@@ -2298,9 +2347,11 @@ export {
2298
2347
  FetchTransport,
2299
2348
  HttpApiClient,
2300
2349
  MCP_VERSION,
2350
+ TEST_API_BASE_URL,
2301
2351
  analyzeProject,
2302
2352
  createSakupaMcpServer,
2303
2353
  deleteSiteFile,
2354
+ loadMcpRuntimeConfig,
2304
2355
  readSiteFile,
2305
2356
  registerLifecycleTools,
2306
2357
  registerTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakupa/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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",