@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.
- package/dist/bin.js +64 -17
- package/dist/index.js +64 -13
- 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.
|
|
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
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
-
{
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
|
2308
|
-
const
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
-
{
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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,
|