@sakupa/mcp 0.7.48 → 0.7.49
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 +132 -46
- package/dist/index.js +134 -46
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -120,7 +120,7 @@ function resolveLockedProjectRoot(projectDir) {
|
|
|
120
120
|
if (markerState.kind === "absent") {
|
|
121
121
|
throw new ProjectRootError(
|
|
122
122
|
"not_initialized",
|
|
123
|
-
`The MCP working directory ${canonical} is not initialized.
|
|
123
|
+
`The MCP working directory ${canonical} is not initialized. Call the init MCP tool with no path argument. If help confirms that this client has no MCP Roots, the AI may run \`npx -y @sakupa/mcp@latest init\` itself as the fallback.`
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
return {
|
|
@@ -400,7 +400,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// ../core/dist/domain/version.js
|
|
403
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
403
|
+
var SAKUPA_MCP_VERSION = "0.7.49";
|
|
404
404
|
|
|
405
405
|
// ../core/dist/domain/errors.js
|
|
406
406
|
var HTTP_STATUS = {
|
|
@@ -729,6 +729,7 @@ var EPHEMERAL_RETENTION_HOURS = 90 * 24;
|
|
|
729
729
|
|
|
730
730
|
// src/config.ts
|
|
731
731
|
var TEST_API_BASE_URL = "https://api-test.sakupa.com";
|
|
732
|
+
var PRODUCTION_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
732
733
|
function previewHostPatternFor(apiBaseUrl) {
|
|
733
734
|
return environmentFor(apiBaseUrl) === "test" ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
|
|
734
735
|
}
|
|
@@ -743,6 +744,11 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
743
744
|
}
|
|
744
745
|
return { apiBaseUrl, testAccessToken };
|
|
745
746
|
}
|
|
747
|
+
if (apiBaseUrl !== PRODUCTION_API_BASE_URL) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`Unsupported Sakupa API origin: ${apiBaseUrl}. Only ${PRODUCTION_API_BASE_URL} and ${TEST_API_BASE_URL} are accepted; refusing to classify an unknown endpoint as Production.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
746
752
|
if (testAccessToken.length > 0) {
|
|
747
753
|
throw new Error(
|
|
748
754
|
`SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
|
|
@@ -751,7 +757,9 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
751
757
|
return { apiBaseUrl };
|
|
752
758
|
}
|
|
753
759
|
function environmentFor(apiBaseUrl) {
|
|
754
|
-
|
|
760
|
+
if (apiBaseUrl === TEST_API_BASE_URL) return "test";
|
|
761
|
+
if (apiBaseUrl === PRODUCTION_API_BASE_URL) return "production";
|
|
762
|
+
return "unknown";
|
|
755
763
|
}
|
|
756
764
|
|
|
757
765
|
// src/server.ts
|
|
@@ -3122,7 +3130,7 @@ var ProjectBindingResolver = class {
|
|
|
3122
3130
|
snapshot,
|
|
3123
3131
|
this.processCwd,
|
|
3124
3132
|
rootCandidates,
|
|
3125
|
-
"This IDE did not provide MCP Roots and the MCP process cwd is not an initialized project. Do not write into the IDE installation directory. Run `npx -y @sakupa/mcp@latest init` with no path arguments from
|
|
3133
|
+
"This IDE did not provide MCP Roots and the MCP process cwd is not an initialized project. Do not write into the IDE installation directory. Run help from the intended project context. If help confirms the missing-Roots diagnosis, the AI may run `npx -y @sakupa/mcp@latest init` with no path arguments from that directory. Never ask the user to run it."
|
|
3126
3134
|
)
|
|
3127
3135
|
};
|
|
3128
3136
|
}
|
|
@@ -3231,8 +3239,16 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
3231
3239
|
data: z.record(z.string(), z.unknown()),
|
|
3232
3240
|
presentation: z.object({
|
|
3233
3241
|
userLanguage: z.literal("infer_from_conversation"),
|
|
3242
|
+
userTimeZone: z.literal("infer_from_conversation"),
|
|
3243
|
+
answerScope: z.literal("current_user_question"),
|
|
3244
|
+
retainTechnicalContext: z.literal(true),
|
|
3245
|
+
productionEnvironmentDisclosure: z.literal("only_when_asked_or_relevant"),
|
|
3246
|
+
nonProductionEnvironmentDisclosure: z.literal("always"),
|
|
3234
3247
|
translateFields: z.array(z.string()),
|
|
3235
|
-
preserveExactFields: z.array(z.string())
|
|
3248
|
+
preserveExactFields: z.array(z.string()),
|
|
3249
|
+
mustTellUser: z.array(z.string()),
|
|
3250
|
+
tellWhenRelevant: z.array(z.string()),
|
|
3251
|
+
agentInstructions: z.array(z.string())
|
|
3236
3252
|
}).optional(),
|
|
3237
3253
|
decision: z.object({
|
|
3238
3254
|
decisionVersion: z.literal(1),
|
|
@@ -3287,9 +3303,54 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
3287
3303
|
)
|
|
3288
3304
|
};
|
|
3289
3305
|
function structuredToolResult(envelope) {
|
|
3306
|
+
const presentation = {
|
|
3307
|
+
userLanguage: "infer_from_conversation",
|
|
3308
|
+
userTimeZone: "infer_from_conversation",
|
|
3309
|
+
answerScope: "current_user_question",
|
|
3310
|
+
retainTechnicalContext: true,
|
|
3311
|
+
productionEnvironmentDisclosure: "only_when_asked_or_relevant",
|
|
3312
|
+
nonProductionEnvironmentDisclosure: "always",
|
|
3313
|
+
translateFields: [
|
|
3314
|
+
"summary",
|
|
3315
|
+
"userAction.expectedOutcome",
|
|
3316
|
+
"userAction.options[].label",
|
|
3317
|
+
"userAction.options[].expectedOutcome",
|
|
3318
|
+
...envelope.presentation?.translateFields ?? []
|
|
3319
|
+
],
|
|
3320
|
+
preserveExactFields: [
|
|
3321
|
+
"data",
|
|
3322
|
+
"nextActions",
|
|
3323
|
+
"operationId",
|
|
3324
|
+
"embedded URLs, paths, IDs, code tokens, tool names, arguments, DNS values, field names, amounts and exact timestamps",
|
|
3325
|
+
...envelope.presentation?.preserveExactFields ?? []
|
|
3326
|
+
],
|
|
3327
|
+
mustTellUser: [
|
|
3328
|
+
"the requested result and whether the operation changed anything",
|
|
3329
|
+
"any action the user must personally complete",
|
|
3330
|
+
"material charges, deadlines, cancellation, replacement, credential revocation and other security or irreversible consequences",
|
|
3331
|
+
...envelope.presentation?.mustTellUser ?? []
|
|
3332
|
+
],
|
|
3333
|
+
tellWhenRelevant: [
|
|
3334
|
+
"local paths, internal IDs, raw enums, exact arguments, provider state and diagnostic details",
|
|
3335
|
+
"normal Production environment details, unless the user asks or environment identity matters to the task",
|
|
3336
|
+
...envelope.presentation?.tellWhenRelevant ?? []
|
|
3337
|
+
],
|
|
3338
|
+
agentInstructions: [
|
|
3339
|
+
"Keep every technical fact available for reasoning and exact continuation; never discard it merely because it is technical.",
|
|
3340
|
+
"Answer the current user question instead of mechanically dumping every returned field. Explain relevant technical terms in plain language while retaining the exact term when it helps accuracy.",
|
|
3341
|
+
"Do not ask the user to copy or run tool arguments that this AI can execute. Use exact nextActions internally after any required user choice.",
|
|
3342
|
+
"Translate human-readable explanations into the language the user is using and present dates and sizes in locally understandable form alongside exact values when precision matters.",
|
|
3343
|
+
"Do not proactively mention a normal Production environment. Always disclose Test or unknown/non-production environments prominently.",
|
|
3344
|
+
...envelope.presentation?.agentInstructions ?? []
|
|
3345
|
+
]
|
|
3346
|
+
};
|
|
3347
|
+
const structuredEnvelope = { ...envelope, presentation };
|
|
3348
|
+
const presentationFallback = "AGENT PRESENTATION CONTRACT: Retain every technical fact below for reasoning and exact follow-up. Tell the user the requested result, required next step, and every material charge, deadline, security or irreversible consequence. Answer only the current question instead of dumping all fields; explain relevant technical terms in plain language while keeping exact terms available. Translate human-readable explanations into the user's language, but preserve URLs, paths, IDs, tool names, arguments, DNS values, field names, amounts and exact timestamps. Do not proactively show a normal Production environment; always disclose Test or unknown environments. Never ask the user to run exact tool arguments that this AI can execute.";
|
|
3290
3349
|
return {
|
|
3291
|
-
content: [{ type: "text", text: envelope.summary
|
|
3292
|
-
|
|
3350
|
+
content: [{ type: "text", text: `${envelope.summary}
|
|
3351
|
+
|
|
3352
|
+
${presentationFallback}` }],
|
|
3353
|
+
structuredContent: structuredEnvelope
|
|
3293
3354
|
};
|
|
3294
3355
|
}
|
|
3295
3356
|
|
|
@@ -3440,14 +3501,22 @@ function toolError(e) {
|
|
|
3440
3501
|
const timeoutSummary = timedOut ? timeoutRetrySafe ? `${timeoutOperation} exceeded its ${timeoutMs ?? "configured"}ms deadline. Sakupa made zero automatic retries; this was a read, so retry it manually or run help if it repeats.` : `${timeoutOperation} exceeded its ${timeoutMs ?? "configured"}ms deadline. Sakupa made zero automatic retries. The remote outcome may be unknown; do not repeat the write automatically. Query current status or run help first.` : void 0;
|
|
3441
3502
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
3442
3503
|
const safeSummary = timeoutSummary ?? (e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : opaqueUnclassified ? "This failed with an error Sakupa could not classify, and retrying the same call will not help. Run help with the failed tool and error code first; only use report if help explicitly recommends it." : "The operation failed; no server-internal details are exposed."));
|
|
3504
|
+
const customerMeaning = timedOut ? timeoutRetrySafe ? "Sakupa did not receive this read result before the deadline; no automatic retry occurred." : "Sakupa did not receive a final result before the deadline, so the AI must check current state before attempting another write." : errorCode === "unauthorized" ? "The cloud site is still intact, but this project no longer has a valid management credential for it." : errorCode === "upgrade_required" ? "The installed Sakupa MCP version is too old for the current API and must be refreshed before retrying." : errorCode === "payment_required" ? "This action needs an active subscription or a payment issue must be resolved first." : errorCode === "rate_limited" ? "Sakupa temporarily refused this operation because a usage or frequency limit was reached." : errorCode === "not_found" ? "The requested Sakupa site, project binding, or operation could not be found." : errorCode === "forbidden" ? "Sakupa refused this operation because the current authority or site state does not allow it." : errorCode === "conflict" || errorCode === "state_conflict" || errorCode === "confirmation_required" ? "Sakupa safely stopped because the site, billing state, or required confirmation no longer matches." : errorCode === "invalid_request" || errorCode === "validation_failed" ? "Sakupa could not complete the operation because required input or current state was invalid." : retryable ? "A temporary Sakupa dependency problem prevented completion." : "Sakupa did not complete the operation; use the retained diagnostics to determine the safe next step.";
|
|
3505
|
+
const userFacingSummary = `Customer meaning: ${customerMeaning}
|
|
3506
|
+
Technical context for the AI: ${safeSummary}`;
|
|
3443
3507
|
const result = structuredToolResult({
|
|
3444
3508
|
schemaVersion: 1,
|
|
3445
3509
|
outcome: "failed",
|
|
3446
3510
|
resultCode: `error_${errorCode}`,
|
|
3447
|
-
summary:
|
|
3511
|
+
summary: userFacingSummary,
|
|
3448
3512
|
data: {
|
|
3449
3513
|
errorCode,
|
|
3450
3514
|
retryable,
|
|
3515
|
+
customerMeaning,
|
|
3516
|
+
diagnosticContext: {
|
|
3517
|
+
technicalMessage: safeSummary,
|
|
3518
|
+
source: serverGuidance ? "sakupa_business_guidance" : "mcp_safe_diagnostic"
|
|
3519
|
+
},
|
|
3451
3520
|
...safeDetails && Object.keys(safeDetails).length > 0 ? { details: safeDetails } : {}
|
|
3452
3521
|
},
|
|
3453
3522
|
nextActions: [
|
|
@@ -3464,16 +3533,11 @@ function toolError(e) {
|
|
|
3464
3533
|
|
|
3465
3534
|
// src/tools/decision.ts
|
|
3466
3535
|
var DECISION_PRESENTATION_POLICY = {
|
|
3467
|
-
userLanguage: "infer_from_conversation",
|
|
3468
3536
|
translateFields: [
|
|
3469
|
-
"summary",
|
|
3470
3537
|
"decision.prompt",
|
|
3471
3538
|
"decision.options[].label",
|
|
3472
3539
|
"decision.options[].description",
|
|
3473
|
-
"decision.options[].consequences[]"
|
|
3474
|
-
"userAction.expectedOutcome",
|
|
3475
|
-
"userAction.options[].label",
|
|
3476
|
-
"userAction.options[].expectedOutcome"
|
|
3540
|
+
"decision.options[].consequences[]"
|
|
3477
3541
|
],
|
|
3478
3542
|
preserveExactFields: [
|
|
3479
3543
|
"decision.options[].id",
|
|
@@ -3481,7 +3545,11 @@ var DECISION_PRESENTATION_POLICY = {
|
|
|
3481
3545
|
"userAction.resumeWith",
|
|
3482
3546
|
"userAction.options[].value",
|
|
3483
3547
|
"nextActions",
|
|
3484
|
-
"
|
|
3548
|
+
"decision option IDs and confirmation values"
|
|
3549
|
+
],
|
|
3550
|
+
mustTellUser: ["every decision option, with no option selected by default"],
|
|
3551
|
+
agentInstructions: [
|
|
3552
|
+
"Wait for the user's selection, then copy only that option's exact nextAction without reconstructing arguments."
|
|
3485
3553
|
]
|
|
3486
3554
|
};
|
|
3487
3555
|
var FORBIDDEN_DECISION_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3573,9 +3641,7 @@ function formatDecisionFallback(decision) {
|
|
|
3573
3641
|
${option.description}${consequences}
|
|
3574
3642
|
${exactAction}`;
|
|
3575
3643
|
});
|
|
3576
|
-
return `
|
|
3577
|
-
|
|
3578
|
-
USER DECISION REQUIRED: ${decision.prompt}
|
|
3644
|
+
return `USER DECISION REQUIRED: ${decision.prompt}
|
|
3579
3645
|
No option is selected by default. Present every option to the user, do not choose on their behalf, and never reconstruct or guess tool arguments.
|
|
3580
3646
|
|
|
3581
3647
|
` + options.join("\n\n");
|
|
@@ -3684,6 +3750,15 @@ var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
|
3684
3750
|
function planCatalog() {
|
|
3685
3751
|
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
3686
3752
|
}
|
|
3753
|
+
function deploymentEnvironmentContext(apiBaseUrl) {
|
|
3754
|
+
const environment = environmentFor(apiBaseUrl);
|
|
3755
|
+
if (environment === "production") {
|
|
3756
|
+
return `Agent context \u2014 environment: PRODUCTION (${apiBaseUrl}). Retain this fact, but do not proactively show it to the user unless asked or relevant.
|
|
3757
|
+
`;
|
|
3758
|
+
}
|
|
3759
|
+
return `IMPORTANT USER NOTICE \u2014 environment: ${environment.toUpperCase()} (${apiBaseUrl}). The user must be told prominently that this is not the normal Production environment.
|
|
3760
|
+
`;
|
|
3761
|
+
}
|
|
3687
3762
|
var ticketCategoryEnum = z2.enum([
|
|
3688
3763
|
"billing",
|
|
3689
3764
|
"payment",
|
|
@@ -3934,7 +4009,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
3934
4009
|
server.registerTool(
|
|
3935
4010
|
"deploy",
|
|
3936
4011
|
{
|
|
3937
|
-
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;
|
|
4012
|
+
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; subscription-backed sites have no free-site expiry while the subscription remains active). Runs analyze first and refuses to upload source projects, secrets, .env files, archives, media or server code. The MCP process is locked to the current directory initialized by the no-argument init MCP tool; no tool argument can change that root. outputDir is a separate REQUIRED relative path supplied from the current project inspection. Never uploads anything when analysis says the project is not deployable.`,
|
|
3938
4013
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
3939
4014
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
3940
4015
|
inputSchema: {
|
|
@@ -4425,12 +4500,11 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4425
4500
|
return text(
|
|
4426
4501
|
"site_published",
|
|
4427
4502
|
`Site published: ${finalized2.url}
|
|
4428
|
-
|
|
4429
|
-
Project directory: ${ctx.projectDir}
|
|
4503
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4430
4504
|
Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
|
|
4431
4505
|
` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
|
|
4432
4506
|
` : "") + `
|
|
4433
|
-
This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh extends the validity;
|
|
4507
|
+
This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh extends the validity; while a subscription remains active, this URL stays live without the free-site expiry. The management credential was saved to .sakupa/site.json \u2014 keep that file: it is the only way to manage this site.
|
|
4434
4508
|
` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
|
|
4435
4509
|
Warnings:
|
|
4436
4510
|
${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
@@ -4515,14 +4589,13 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
4515
4589
|
return text(
|
|
4516
4590
|
handoffPerformed ? "free_site_slot_reassigned" : "site_updated",
|
|
4517
4591
|
`Site updated: ${finalized.url}
|
|
4518
|
-
|
|
4519
|
-
Project directory: ${ctx.projectDir}
|
|
4592
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4520
4593
|
Files uploaded: ${uploaded} (${finalized.totalBytes} bytes)
|
|
4521
4594
|
` + (finalized.expiresAt ? `Validity refreshed \u2014 expires at: ${finalized.expiresAt}
|
|
4522
4595
|
` : "") + (credentialRelocatedFrom.length > 0 ? `Credential binding relocated from ${credentialRelocatedFrom.join(", ")} to ${ctx.projectDir}/.sakupa; the existing site was preserved.
|
|
4523
4596
|
` : "") + (handoffPerformed ? `Site handoff completed from the authenticated device list. The existing free-site URL stayed the same, the cloud site was NOT deleted, and its content was replaced. Sakupa issued a fresh project credential and revoked ${handoffRevokedCredentials} previous credential(s), so no old project can continue managing this URL.` + (handoffCleanup?.sourceCredentialRemoved ? " A matching obsolete local site.json was removed automatically.\n" : "\n") : "") + (credentialRotationResumed ? "A previously confirmed credential rotation was resumed safely before this deploy; every older credential is revoked.\n" : "") + (finalized.mode === "free" ? `
|
|
4524
|
-
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call.
|
|
4525
|
-
` : "\nThis site is
|
|
4597
|
+
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call. While a subscription remains active, the site stays live without this free-site expiry.
|
|
4598
|
+
` : "\nThis site is subscription-backed and has no free-site expiry while the subscription remains active.\n") + (finalized.warnings.length > 0 ? `
|
|
4526
4599
|
Warnings:
|
|
4527
4600
|
${JSON.stringify(finalized.warnings, null, 2)}` : "") + (credentialSecurity?.rotationRecommended ? `
|
|
4528
4601
|
|
|
@@ -4578,7 +4651,7 @@ Optional security recommendation: this management credential was created at ${cr
|
|
|
4578
4651
|
server.registerTool(
|
|
4579
4652
|
"refresh",
|
|
4580
4653
|
{
|
|
4581
|
-
description: "Refresh the validity of the free temporary site WITHOUT uploading content. Uses the local credential in .sakupa/site.json.
|
|
4654
|
+
description: "Refresh the validity of the free temporary site WITHOUT uploading content. Uses the local credential in .sakupa/site.json. Subscription-backed sites have no free-site expiry while the subscription remains active and need no refresh.",
|
|
4582
4655
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4583
4656
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4584
4657
|
inputSchema: {}
|
|
@@ -4642,7 +4715,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4642
4715
|
server.registerTool(
|
|
4643
4716
|
"subscribe",
|
|
4644
4717
|
{
|
|
4645
|
-
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}).
|
|
4718
|
+
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free 24-hour expiry. Binding a custom domain afterwards (bind) 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 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.`,
|
|
4646
4719
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4647
4720
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4648
4721
|
inputSchema: {
|
|
@@ -4669,7 +4742,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4669
4742
|
${res.checkoutUrl}
|
|
4670
4743
|
|
|
4671
4744
|
Open this link in a browser to subscribe. Card data is entered only on the Stripe-hosted page \u2014 never give card numbers, passwords or security codes to the AI tool.
|
|
4672
|
-
Once payment
|
|
4745
|
+
Once Stripe confirms payment and Sakupa synchronizes the subscription, the current URL stays live while that subscription remains active. Binding a custom domain (bind) is optional and still requires DNS verification.`,
|
|
4673
4746
|
{
|
|
4674
4747
|
siteId: res.siteId,
|
|
4675
4748
|
plan: res.plan,
|
|
@@ -4688,7 +4761,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
4688
4761
|
server.registerTool(
|
|
4689
4762
|
"bind",
|
|
4690
4763
|
{
|
|
4691
|
-
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the
|
|
4764
|
+
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the subscription-backed ${previewHostPattern} URL keeps working alongside it while the subscription is active. The binding unit is the APEX domain: binding example.com reserves routes for example.com and www.example.com, but ONLY www is required and judged for activation; the naked apex is optional because many DNS providers cannot point it. 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). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership, and bindings are ALWAYS challengeable: whoever proves CURRENT DNS control takes the domain, even from an existing binding (the displaced site keeps its subscription, content and subscription-backed Sakupa URL). Unverified requests expire after 72 hours. Call again with action "status" to check progress.`,
|
|
4692
4765
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4693
4766
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4694
4767
|
inputSchema: {
|
|
@@ -4816,7 +4889,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
4816
4889
|
noteSiteMode(res.siteId, res.mode);
|
|
4817
4890
|
const lines = [
|
|
4818
4891
|
`AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
|
|
4819
|
-
res.permanentUrl ? `
|
|
4892
|
+
res.permanentUrl ? `Subscription-backed Sakupa URL: ${res.permanentUrl}` : void 0,
|
|
4820
4893
|
res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
|
|
4821
4894
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
4822
4895
|
res.cancelAtPeriodEnd === true ? `Renewal: CANCELED \u2014 the site reverts to free at ${res.cancellationEffectiveAt ?? res.currentPeriodEnd ?? "the end of the already-paid month"}` : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : res.cancelAtPeriodEnd === false ? "Renewal cancellation: no" : void 0,
|
|
@@ -5515,7 +5588,7 @@ var TOOL_MANUALS = {
|
|
|
5515
5588
|
init: {
|
|
5516
5589
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5517
5590
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5518
|
-
preconditions: "Exactly one usable MCP workspace Root.
|
|
5591
|
+
preconditions: "Exactly one usable MCP workspace Root. If Roots are unavailable, help may authorize the AI to use CLI init.",
|
|
5519
5592
|
parameterNames: [],
|
|
5520
5593
|
parameters: "No parameters and no path argument.",
|
|
5521
5594
|
warnings: [
|
|
@@ -5564,7 +5637,9 @@ var TOOL_MANUALS = {
|
|
|
5564
5637
|
preconditions: "A valid local site credential.",
|
|
5565
5638
|
parameterNames: [],
|
|
5566
5639
|
parameters: "No parameters.",
|
|
5567
|
-
warnings: [
|
|
5640
|
+
warnings: [
|
|
5641
|
+
"Subscription-backed sites have no free-site expiry while the subscription remains active and do not need refresh."
|
|
5642
|
+
],
|
|
5568
5643
|
nextStep: "Call status to verify the new expiry."
|
|
5569
5644
|
},
|
|
5570
5645
|
status: {
|
|
@@ -5706,7 +5781,7 @@ function registerHelpTools(server, baseCtx) {
|
|
|
5706
5781
|
server.registerTool(
|
|
5707
5782
|
"init",
|
|
5708
5783
|
{
|
|
5709
|
-
description: "Initialize the active MCP workspace Root as a Sakupa project. Takes no path argument, creates only .sakupa/project.json at that exact Root, preserves site/recovery state, makes no API call and is idempotent.
|
|
5784
|
+
description: "Initialize the active MCP workspace Root as a Sakupa project. Takes no path argument, creates only .sakupa/project.json at that exact Root, preserves site/recovery state, makes no API call and is idempotent. If MCP Roots are unavailable, call help; the AI may then use the no-argument CLI init itself.",
|
|
5710
5785
|
inputSchema: {},
|
|
5711
5786
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5712
5787
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -6183,14 +6258,14 @@ Workflow:
|
|
|
6183
6258
|
current deployment and serving state at any time. Every update checks the credential's
|
|
6184
6259
|
server-side issue time. When it is older than 7 days, deploy succeeds and only SUGGESTS the
|
|
6185
6260
|
optional rotate tool; never rotate without the user's explicit confirmation.
|
|
6186
|
-
3. To
|
|
6261
|
+
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
6187
6262
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
6188
|
-
water/personal/share/business).
|
|
6189
|
-
${hostPattern} URL
|
|
6263
|
+
water/personal/share/business). While the subscription remains active, the
|
|
6264
|
+
${hostPattern} URL stays live without the free 24-hour expiry. Usage over the chosen plan
|
|
6190
6265
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
6191
6266
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
6192
6267
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
6193
|
-
serving surface alongside the
|
|
6268
|
+
serving surface alongside the subscription-backed Sakupa URL. Ownership is proven only by DNS control; the
|
|
6194
6269
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
6195
6270
|
change, portal and recover manage the paid lifecycle. Binding a
|
|
6196
6271
|
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
@@ -6215,11 +6290,12 @@ translate every human-readable summary, prompt, option label, description and co
|
|
|
6215
6290
|
language. Keep option IDs, tool names, exact arguments, URLs, field names and confirmation values
|
|
6216
6291
|
unchanged.
|
|
6217
6292
|
|
|
6218
|
-
Project directory contract: before the first deploy or a new recovery,
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6293
|
+
Project directory contract: before the first deploy or a new recovery, CALL the init MCP tool with
|
|
6294
|
+
NO path argument. init uses the IDE's exact MCP Root and creates the non-secret
|
|
6295
|
+
.sakupa/project.json directly there. Do not merely print installation or CLI instructions when the
|
|
6296
|
+
init tool is available. Only after help confirms that the client does not provide MCP Roots may the
|
|
6297
|
+
AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a fallback; never ask the user to
|
|
6298
|
+
run it. The CLI also accepts NO path argument. ONE MCP process = ONE Roots-first locked project = ONE site.
|
|
6223
6299
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6224
6300
|
and public_recovery portal remain project-independent.
|
|
6225
6301
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6232,11 +6308,21 @@ before retrying with outputDirChangeConfirmed: true.
|
|
|
6232
6308
|
After init, require sakupaAtProjectRoot=true. Before deploy, Sakupa checks every directory segment
|
|
6233
6309
|
between the project Root and outputDir for a misplaced .sakupa and safely relocates only validated,
|
|
6234
6310
|
non-conflicting state; never copy, delete or overwrite site.json by shell command.
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6311
|
+
Every result retains the exact environment for AI reasoning. Prominently tell the user when the
|
|
6312
|
+
environment is TEST or unknown/non-production. Do not proactively mention a normal PRODUCTION
|
|
6313
|
+
environment unless the user asks or the distinction is relevant to the current question. analyze,
|
|
6314
|
+
deploy, status, and refresh echo
|
|
6238
6315
|
the Roots-first locked directory they acted on.
|
|
6239
6316
|
|
|
6317
|
+
Presentation contract: preserve every returned technical fact for reasoning and exact continuation,
|
|
6318
|
+
including paths, IDs, enums, tool names, arguments, DNS values, provider states, amounts and exact
|
|
6319
|
+
timestamps. Do not mechanically dump every field. Answer the user's current question first, explain
|
|
6320
|
+
relevant technical terms in plain language while retaining the exact term, translate human-readable
|
|
6321
|
+
explanations into the user's language, and always disclose material charges, deadlines, cancellation,
|
|
6322
|
+
replacement, credential revocation, security effects and irreversible consequences. A technical fact
|
|
6323
|
+
may be omitted from the immediate user-facing answer only when it is irrelevant to the current
|
|
6324
|
+
question; it must remain available in the tool result for follow-up.
|
|
6325
|
+
|
|
6240
6326
|
On any difficulty, call help before retrying or escalating. Only offer report when help returns
|
|
6241
6327
|
reportRecommended:true; attach your own factual account via agentContext and show the exact
|
|
6242
6328
|
sanitized preview before asking the user to confirm submission.
|
package/dist/index.js
CHANGED
|
@@ -145,7 +145,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// ../core/dist/domain/version.js
|
|
148
|
-
var SAKUPA_MCP_VERSION = "0.7.
|
|
148
|
+
var SAKUPA_MCP_VERSION = "0.7.49";
|
|
149
149
|
|
|
150
150
|
// ../core/dist/domain/errors.js
|
|
151
151
|
var HTTP_STATUS = {
|
|
@@ -478,6 +478,7 @@ var CLIENT_TYPE = "sakupa-mcp";
|
|
|
478
478
|
|
|
479
479
|
// src/config.ts
|
|
480
480
|
var TEST_API_BASE_URL = "https://api-test.sakupa.com";
|
|
481
|
+
var PRODUCTION_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
481
482
|
function previewHostPatternFor(apiBaseUrl) {
|
|
482
483
|
return environmentFor(apiBaseUrl) === "test" ? "{shortId}-test.sakupa.com" : "{shortId}.sakupa.com";
|
|
483
484
|
}
|
|
@@ -492,6 +493,11 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
492
493
|
}
|
|
493
494
|
return { apiBaseUrl, testAccessToken };
|
|
494
495
|
}
|
|
496
|
+
if (apiBaseUrl !== PRODUCTION_API_BASE_URL) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
`Unsupported Sakupa API origin: ${apiBaseUrl}. Only ${PRODUCTION_API_BASE_URL} and ${TEST_API_BASE_URL} are accepted; refusing to classify an unknown endpoint as Production.`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
495
501
|
if (testAccessToken.length > 0) {
|
|
496
502
|
throw new Error(
|
|
497
503
|
`SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
|
|
@@ -500,7 +506,9 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
500
506
|
return { apiBaseUrl };
|
|
501
507
|
}
|
|
502
508
|
function environmentFor(apiBaseUrl) {
|
|
503
|
-
|
|
509
|
+
if (apiBaseUrl === TEST_API_BASE_URL) return "test";
|
|
510
|
+
if (apiBaseUrl === PRODUCTION_API_BASE_URL) return "production";
|
|
511
|
+
return "unknown";
|
|
504
512
|
}
|
|
505
513
|
|
|
506
514
|
// src/timeout.ts
|
|
@@ -1646,7 +1654,7 @@ function resolveLockedProjectRoot(projectDir) {
|
|
|
1646
1654
|
if (markerState.kind === "absent") {
|
|
1647
1655
|
throw new ProjectRootError(
|
|
1648
1656
|
"not_initialized",
|
|
1649
|
-
`The MCP working directory ${canonical} is not initialized.
|
|
1657
|
+
`The MCP working directory ${canonical} is not initialized. Call the init MCP tool with no path argument. If help confirms that this client has no MCP Roots, the AI may run \`npx -y @sakupa/mcp@latest init\` itself as the fallback.`
|
|
1650
1658
|
);
|
|
1651
1659
|
}
|
|
1652
1660
|
return {
|
|
@@ -1932,7 +1940,7 @@ var ProjectBindingResolver = class {
|
|
|
1932
1940
|
snapshot,
|
|
1933
1941
|
this.processCwd,
|
|
1934
1942
|
rootCandidates,
|
|
1935
|
-
"This IDE did not provide MCP Roots and the MCP process cwd is not an initialized project. Do not write into the IDE installation directory. Run `npx -y @sakupa/mcp@latest init` with no path arguments from
|
|
1943
|
+
"This IDE did not provide MCP Roots and the MCP process cwd is not an initialized project. Do not write into the IDE installation directory. Run help from the intended project context. If help confirms the missing-Roots diagnosis, the AI may run `npx -y @sakupa/mcp@latest init` with no path arguments from that directory. Never ask the user to run it."
|
|
1936
1944
|
)
|
|
1937
1945
|
};
|
|
1938
1946
|
}
|
|
@@ -2041,8 +2049,16 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
2041
2049
|
data: z.record(z.string(), z.unknown()),
|
|
2042
2050
|
presentation: z.object({
|
|
2043
2051
|
userLanguage: z.literal("infer_from_conversation"),
|
|
2052
|
+
userTimeZone: z.literal("infer_from_conversation"),
|
|
2053
|
+
answerScope: z.literal("current_user_question"),
|
|
2054
|
+
retainTechnicalContext: z.literal(true),
|
|
2055
|
+
productionEnvironmentDisclosure: z.literal("only_when_asked_or_relevant"),
|
|
2056
|
+
nonProductionEnvironmentDisclosure: z.literal("always"),
|
|
2044
2057
|
translateFields: z.array(z.string()),
|
|
2045
|
-
preserveExactFields: z.array(z.string())
|
|
2058
|
+
preserveExactFields: z.array(z.string()),
|
|
2059
|
+
mustTellUser: z.array(z.string()),
|
|
2060
|
+
tellWhenRelevant: z.array(z.string()),
|
|
2061
|
+
agentInstructions: z.array(z.string())
|
|
2046
2062
|
}).optional(),
|
|
2047
2063
|
decision: z.object({
|
|
2048
2064
|
decisionVersion: z.literal(1),
|
|
@@ -2097,9 +2113,54 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
2097
2113
|
)
|
|
2098
2114
|
};
|
|
2099
2115
|
function structuredToolResult(envelope) {
|
|
2116
|
+
const presentation = {
|
|
2117
|
+
userLanguage: "infer_from_conversation",
|
|
2118
|
+
userTimeZone: "infer_from_conversation",
|
|
2119
|
+
answerScope: "current_user_question",
|
|
2120
|
+
retainTechnicalContext: true,
|
|
2121
|
+
productionEnvironmentDisclosure: "only_when_asked_or_relevant",
|
|
2122
|
+
nonProductionEnvironmentDisclosure: "always",
|
|
2123
|
+
translateFields: [
|
|
2124
|
+
"summary",
|
|
2125
|
+
"userAction.expectedOutcome",
|
|
2126
|
+
"userAction.options[].label",
|
|
2127
|
+
"userAction.options[].expectedOutcome",
|
|
2128
|
+
...envelope.presentation?.translateFields ?? []
|
|
2129
|
+
],
|
|
2130
|
+
preserveExactFields: [
|
|
2131
|
+
"data",
|
|
2132
|
+
"nextActions",
|
|
2133
|
+
"operationId",
|
|
2134
|
+
"embedded URLs, paths, IDs, code tokens, tool names, arguments, DNS values, field names, amounts and exact timestamps",
|
|
2135
|
+
...envelope.presentation?.preserveExactFields ?? []
|
|
2136
|
+
],
|
|
2137
|
+
mustTellUser: [
|
|
2138
|
+
"the requested result and whether the operation changed anything",
|
|
2139
|
+
"any action the user must personally complete",
|
|
2140
|
+
"material charges, deadlines, cancellation, replacement, credential revocation and other security or irreversible consequences",
|
|
2141
|
+
...envelope.presentation?.mustTellUser ?? []
|
|
2142
|
+
],
|
|
2143
|
+
tellWhenRelevant: [
|
|
2144
|
+
"local paths, internal IDs, raw enums, exact arguments, provider state and diagnostic details",
|
|
2145
|
+
"normal Production environment details, unless the user asks or environment identity matters to the task",
|
|
2146
|
+
...envelope.presentation?.tellWhenRelevant ?? []
|
|
2147
|
+
],
|
|
2148
|
+
agentInstructions: [
|
|
2149
|
+
"Keep every technical fact available for reasoning and exact continuation; never discard it merely because it is technical.",
|
|
2150
|
+
"Answer the current user question instead of mechanically dumping every returned field. Explain relevant technical terms in plain language while retaining the exact term when it helps accuracy.",
|
|
2151
|
+
"Do not ask the user to copy or run tool arguments that this AI can execute. Use exact nextActions internally after any required user choice.",
|
|
2152
|
+
"Translate human-readable explanations into the language the user is using and present dates and sizes in locally understandable form alongside exact values when precision matters.",
|
|
2153
|
+
"Do not proactively mention a normal Production environment. Always disclose Test or unknown/non-production environments prominently.",
|
|
2154
|
+
...envelope.presentation?.agentInstructions ?? []
|
|
2155
|
+
]
|
|
2156
|
+
};
|
|
2157
|
+
const structuredEnvelope = { ...envelope, presentation };
|
|
2158
|
+
const presentationFallback = "AGENT PRESENTATION CONTRACT: Retain every technical fact below for reasoning and exact follow-up. Tell the user the requested result, required next step, and every material charge, deadline, security or irreversible consequence. Answer only the current question instead of dumping all fields; explain relevant technical terms in plain language while keeping exact terms available. Translate human-readable explanations into the user's language, but preserve URLs, paths, IDs, tool names, arguments, DNS values, field names, amounts and exact timestamps. Do not proactively show a normal Production environment; always disclose Test or unknown environments. Never ask the user to run exact tool arguments that this AI can execute.";
|
|
2100
2159
|
return {
|
|
2101
|
-
content: [{ type: "text", text: envelope.summary
|
|
2102
|
-
|
|
2160
|
+
content: [{ type: "text", text: `${envelope.summary}
|
|
2161
|
+
|
|
2162
|
+
${presentationFallback}` }],
|
|
2163
|
+
structuredContent: structuredEnvelope
|
|
2103
2164
|
};
|
|
2104
2165
|
}
|
|
2105
2166
|
|
|
@@ -2250,14 +2311,22 @@ function toolError(e) {
|
|
|
2250
2311
|
const timeoutSummary = timedOut ? timeoutRetrySafe ? `${timeoutOperation} exceeded its ${timeoutMs ?? "configured"}ms deadline. Sakupa made zero automatic retries; this was a read, so retry it manually or run help if it repeats.` : `${timeoutOperation} exceeded its ${timeoutMs ?? "configured"}ms deadline. Sakupa made zero automatic retries. The remote outcome may be unknown; do not repeat the write automatically. Query current status or run help first.` : void 0;
|
|
2251
2312
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
2252
2313
|
const safeSummary = timeoutSummary ?? (e instanceof LocalGuidanceError ? e.message : errorCode === "upgrade_required" ? `This Sakupa MCP client is v${MCP_VERSION}, older than the server's minimum supported version${minimumVersion !== void 0 ? ` (v${minimumVersion})` : ""}, so the server refused the call. To fix it: ask the user to fully restart their MCP client session \u2014 "npx -y @sakupa/mcp@latest" setups fetch the current version on restart (run "npx clear-npx-cache" first if the old version persists); global installs need "npm install -g @sakupa/mcp@latest". After the restart, retry this exact tool call.` : errorCode === "unauthorized" ? UNAUTHORIZED_SUMMARY : serverGuidance ?? (retryable ? "An upstream service is temporarily unavailable or busy; retry shortly." : opaqueUnclassified ? "This failed with an error Sakupa could not classify, and retrying the same call will not help. Run help with the failed tool and error code first; only use report if help explicitly recommends it." : "The operation failed; no server-internal details are exposed."));
|
|
2314
|
+
const customerMeaning = timedOut ? timeoutRetrySafe ? "Sakupa did not receive this read result before the deadline; no automatic retry occurred." : "Sakupa did not receive a final result before the deadline, so the AI must check current state before attempting another write." : errorCode === "unauthorized" ? "The cloud site is still intact, but this project no longer has a valid management credential for it." : errorCode === "upgrade_required" ? "The installed Sakupa MCP version is too old for the current API and must be refreshed before retrying." : errorCode === "payment_required" ? "This action needs an active subscription or a payment issue must be resolved first." : errorCode === "rate_limited" ? "Sakupa temporarily refused this operation because a usage or frequency limit was reached." : errorCode === "not_found" ? "The requested Sakupa site, project binding, or operation could not be found." : errorCode === "forbidden" ? "Sakupa refused this operation because the current authority or site state does not allow it." : errorCode === "conflict" || errorCode === "state_conflict" || errorCode === "confirmation_required" ? "Sakupa safely stopped because the site, billing state, or required confirmation no longer matches." : errorCode === "invalid_request" || errorCode === "validation_failed" ? "Sakupa could not complete the operation because required input or current state was invalid." : retryable ? "A temporary Sakupa dependency problem prevented completion." : "Sakupa did not complete the operation; use the retained diagnostics to determine the safe next step.";
|
|
2315
|
+
const userFacingSummary = `Customer meaning: ${customerMeaning}
|
|
2316
|
+
Technical context for the AI: ${safeSummary}`;
|
|
2253
2317
|
const result = structuredToolResult({
|
|
2254
2318
|
schemaVersion: 1,
|
|
2255
2319
|
outcome: "failed",
|
|
2256
2320
|
resultCode: `error_${errorCode}`,
|
|
2257
|
-
summary:
|
|
2321
|
+
summary: userFacingSummary,
|
|
2258
2322
|
data: {
|
|
2259
2323
|
errorCode,
|
|
2260
2324
|
retryable,
|
|
2325
|
+
customerMeaning,
|
|
2326
|
+
diagnosticContext: {
|
|
2327
|
+
technicalMessage: safeSummary,
|
|
2328
|
+
source: serverGuidance ? "sakupa_business_guidance" : "mcp_safe_diagnostic"
|
|
2329
|
+
},
|
|
2261
2330
|
...safeDetails && Object.keys(safeDetails).length > 0 ? { details: safeDetails } : {}
|
|
2262
2331
|
},
|
|
2263
2332
|
nextActions: [
|
|
@@ -3580,16 +3649,11 @@ async function resumeCredentialRotation(client, projectDir, site, apiBaseUrl) {
|
|
|
3580
3649
|
|
|
3581
3650
|
// src/tools/decision.ts
|
|
3582
3651
|
var DECISION_PRESENTATION_POLICY = {
|
|
3583
|
-
userLanguage: "infer_from_conversation",
|
|
3584
3652
|
translateFields: [
|
|
3585
|
-
"summary",
|
|
3586
3653
|
"decision.prompt",
|
|
3587
3654
|
"decision.options[].label",
|
|
3588
3655
|
"decision.options[].description",
|
|
3589
|
-
"decision.options[].consequences[]"
|
|
3590
|
-
"userAction.expectedOutcome",
|
|
3591
|
-
"userAction.options[].label",
|
|
3592
|
-
"userAction.options[].expectedOutcome"
|
|
3656
|
+
"decision.options[].consequences[]"
|
|
3593
3657
|
],
|
|
3594
3658
|
preserveExactFields: [
|
|
3595
3659
|
"decision.options[].id",
|
|
@@ -3597,7 +3661,11 @@ var DECISION_PRESENTATION_POLICY = {
|
|
|
3597
3661
|
"userAction.resumeWith",
|
|
3598
3662
|
"userAction.options[].value",
|
|
3599
3663
|
"nextActions",
|
|
3600
|
-
"
|
|
3664
|
+
"decision option IDs and confirmation values"
|
|
3665
|
+
],
|
|
3666
|
+
mustTellUser: ["every decision option, with no option selected by default"],
|
|
3667
|
+
agentInstructions: [
|
|
3668
|
+
"Wait for the user's selection, then copy only that option's exact nextAction without reconstructing arguments."
|
|
3601
3669
|
]
|
|
3602
3670
|
};
|
|
3603
3671
|
var FORBIDDEN_DECISION_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3689,9 +3757,7 @@ function formatDecisionFallback(decision) {
|
|
|
3689
3757
|
${option.description}${consequences}
|
|
3690
3758
|
${exactAction}`;
|
|
3691
3759
|
});
|
|
3692
|
-
return `
|
|
3693
|
-
|
|
3694
|
-
USER DECISION REQUIRED: ${decision.prompt}
|
|
3760
|
+
return `USER DECISION REQUIRED: ${decision.prompt}
|
|
3695
3761
|
No option is selected by default. Present every option to the user, do not choose on their behalf, and never reconstruct or guess tool arguments.
|
|
3696
3762
|
|
|
3697
3763
|
` + options.join("\n\n");
|
|
@@ -3800,6 +3866,15 @@ var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
|
3800
3866
|
function planCatalog() {
|
|
3801
3867
|
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
3802
3868
|
}
|
|
3869
|
+
function deploymentEnvironmentContext(apiBaseUrl) {
|
|
3870
|
+
const environment = environmentFor(apiBaseUrl);
|
|
3871
|
+
if (environment === "production") {
|
|
3872
|
+
return `Agent context \u2014 environment: PRODUCTION (${apiBaseUrl}). Retain this fact, but do not proactively show it to the user unless asked or relevant.
|
|
3873
|
+
`;
|
|
3874
|
+
}
|
|
3875
|
+
return `IMPORTANT USER NOTICE \u2014 environment: ${environment.toUpperCase()} (${apiBaseUrl}). The user must be told prominently that this is not the normal Production environment.
|
|
3876
|
+
`;
|
|
3877
|
+
}
|
|
3803
3878
|
var ticketCategoryEnum = z2.enum([
|
|
3804
3879
|
"billing",
|
|
3805
3880
|
"payment",
|
|
@@ -4050,7 +4125,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4050
4125
|
server.registerTool(
|
|
4051
4126
|
"deploy",
|
|
4052
4127
|
{
|
|
4053
|
-
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;
|
|
4128
|
+
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; subscription-backed sites have no free-site expiry while the subscription remains active). Runs analyze first and refuses to upload source projects, secrets, .env files, archives, media or server code. The MCP process is locked to the current directory initialized by the no-argument init MCP tool; no tool argument can change that root. outputDir is a separate REQUIRED relative path supplied from the current project inspection. Never uploads anything when analysis says the project is not deployable.`,
|
|
4054
4129
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4055
4130
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4056
4131
|
inputSchema: {
|
|
@@ -4541,12 +4616,11 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4541
4616
|
return text(
|
|
4542
4617
|
"site_published",
|
|
4543
4618
|
`Site published: ${finalized2.url}
|
|
4544
|
-
|
|
4545
|
-
Project directory: ${ctx.projectDir}
|
|
4619
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4546
4620
|
Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
|
|
4547
4621
|
` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
|
|
4548
4622
|
` : "") + `
|
|
4549
|
-
This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh extends the validity;
|
|
4623
|
+
This is a FREE temporary preview: it stays live for ${FREE_SITE_TTL_HOURS} hours. Deploying again or calling refresh extends the validity; while a subscription remains active, this URL stays live without the free-site expiry. The management credential was saved to .sakupa/site.json \u2014 keep that file: it is the only way to manage this site.
|
|
4550
4624
|
` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
|
|
4551
4625
|
Warnings:
|
|
4552
4626
|
${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
@@ -4631,14 +4705,13 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
4631
4705
|
return text(
|
|
4632
4706
|
handoffPerformed ? "free_site_slot_reassigned" : "site_updated",
|
|
4633
4707
|
`Site updated: ${finalized.url}
|
|
4634
|
-
|
|
4635
|
-
Project directory: ${ctx.projectDir}
|
|
4708
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4636
4709
|
Files uploaded: ${uploaded} (${finalized.totalBytes} bytes)
|
|
4637
4710
|
` + (finalized.expiresAt ? `Validity refreshed \u2014 expires at: ${finalized.expiresAt}
|
|
4638
4711
|
` : "") + (credentialRelocatedFrom.length > 0 ? `Credential binding relocated from ${credentialRelocatedFrom.join(", ")} to ${ctx.projectDir}/.sakupa; the existing site was preserved.
|
|
4639
4712
|
` : "") + (handoffPerformed ? `Site handoff completed from the authenticated device list. The existing free-site URL stayed the same, the cloud site was NOT deleted, and its content was replaced. Sakupa issued a fresh project credential and revoked ${handoffRevokedCredentials} previous credential(s), so no old project can continue managing this URL.` + (handoffCleanup?.sourceCredentialRemoved ? " A matching obsolete local site.json was removed automatically.\n" : "\n") : "") + (credentialRotationResumed ? "A previously confirmed credential rotation was resumed safely before this deploy; every older credential is revoked.\n" : "") + (finalized.mode === "free" ? `
|
|
4640
|
-
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call.
|
|
4641
|
-
` : "\nThis site is
|
|
4713
|
+
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call. While a subscription remains active, the site stays live without this free-site expiry.
|
|
4714
|
+
` : "\nThis site is subscription-backed and has no free-site expiry while the subscription remains active.\n") + (finalized.warnings.length > 0 ? `
|
|
4642
4715
|
Warnings:
|
|
4643
4716
|
${JSON.stringify(finalized.warnings, null, 2)}` : "") + (credentialSecurity?.rotationRecommended ? `
|
|
4644
4717
|
|
|
@@ -4694,7 +4767,7 @@ Optional security recommendation: this management credential was created at ${cr
|
|
|
4694
4767
|
server.registerTool(
|
|
4695
4768
|
"refresh",
|
|
4696
4769
|
{
|
|
4697
|
-
description: "Refresh the validity of the free temporary site WITHOUT uploading content. Uses the local credential in .sakupa/site.json.
|
|
4770
|
+
description: "Refresh the validity of the free temporary site WITHOUT uploading content. Uses the local credential in .sakupa/site.json. Subscription-backed sites have no free-site expiry while the subscription remains active and need no refresh.",
|
|
4698
4771
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4699
4772
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4700
4773
|
inputSchema: {}
|
|
@@ -4758,7 +4831,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4758
4831
|
server.registerTool(
|
|
4759
4832
|
"subscribe",
|
|
4760
4833
|
{
|
|
4761
|
-
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}).
|
|
4834
|
+
description: `Create a Stripe Checkout link that subscribes THIS site to a Sakupa Hosting monthly plan (${planCatalog()}). While the subscription remains active, its ${previewHostPattern} URL stays live without the free 24-hour expiry. Binding a custom domain afterwards (bind) 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 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.`,
|
|
4762
4835
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4763
4836
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4764
4837
|
inputSchema: {
|
|
@@ -4785,7 +4858,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4785
4858
|
${res.checkoutUrl}
|
|
4786
4859
|
|
|
4787
4860
|
Open this link in a browser to subscribe. Card data is entered only on the Stripe-hosted page \u2014 never give card numbers, passwords or security codes to the AI tool.
|
|
4788
|
-
Once payment
|
|
4861
|
+
Once Stripe confirms payment and Sakupa synchronizes the subscription, the current URL stays live while that subscription remains active. Binding a custom domain (bind) is optional and still requires DNS verification.`,
|
|
4789
4862
|
{
|
|
4790
4863
|
siteId: res.siteId,
|
|
4791
4864
|
plan: res.plan,
|
|
@@ -4804,7 +4877,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
4804
4877
|
server.registerTool(
|
|
4805
4878
|
"bind",
|
|
4806
4879
|
{
|
|
4807
|
-
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the
|
|
4880
|
+
description: `Bind a custom domain to this subscribed site \u2014 an OPTIONAL extra serving surface; the subscription-backed ${previewHostPattern} URL keeps working alongside it while the subscription is active. The binding unit is the APEX domain: binding example.com reserves routes for example.com and www.example.com, but ONLY www is required and judged for activation; the naked apex is optional because many DNS providers cannot point it. 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). Ownership is proven ONLY by DNS control of the apex \u2014 payment never grants ownership, and bindings are ALWAYS challengeable: whoever proves CURRENT DNS control takes the domain, even from an existing binding (the displaced site keeps its subscription, content and subscription-backed Sakupa URL). Unverified requests expire after 72 hours. Call again with action "status" to check progress.`,
|
|
4808
4881
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4809
4882
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4810
4883
|
inputSchema: {
|
|
@@ -4932,7 +5005,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
4932
5005
|
noteSiteMode(res.siteId, res.mode);
|
|
4933
5006
|
const lines = [
|
|
4934
5007
|
`AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
|
|
4935
|
-
res.permanentUrl ? `
|
|
5008
|
+
res.permanentUrl ? `Subscription-backed Sakupa URL: ${res.permanentUrl}` : void 0,
|
|
4936
5009
|
res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
|
|
4937
5010
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
4938
5011
|
res.cancelAtPeriodEnd === true ? `Renewal: CANCELED \u2014 the site reverts to free at ${res.cancellationEffectiveAt ?? res.currentPeriodEnd ?? "the end of the already-paid month"}` : res.renewalPlan ? `Next renewal plan: ${res.renewalPlan} (JPY ${res.renewalMonthlyPriceJpy ?? tierPriceJpy(res.renewalPlan)}/month)` + (res.renewalEffectiveAt ? `, effective ${res.renewalEffectiveAt}` : " (unchanged)") : res.cancelAtPeriodEnd === false ? "Renewal cancellation: no" : void 0,
|
|
@@ -5634,7 +5707,7 @@ var TOOL_MANUALS = {
|
|
|
5634
5707
|
init: {
|
|
5635
5708
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5636
5709
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5637
|
-
preconditions: "Exactly one usable MCP workspace Root.
|
|
5710
|
+
preconditions: "Exactly one usable MCP workspace Root. If Roots are unavailable, help may authorize the AI to use CLI init.",
|
|
5638
5711
|
parameterNames: [],
|
|
5639
5712
|
parameters: "No parameters and no path argument.",
|
|
5640
5713
|
warnings: [
|
|
@@ -5683,7 +5756,9 @@ var TOOL_MANUALS = {
|
|
|
5683
5756
|
preconditions: "A valid local site credential.",
|
|
5684
5757
|
parameterNames: [],
|
|
5685
5758
|
parameters: "No parameters.",
|
|
5686
|
-
warnings: [
|
|
5759
|
+
warnings: [
|
|
5760
|
+
"Subscription-backed sites have no free-site expiry while the subscription remains active and do not need refresh."
|
|
5761
|
+
],
|
|
5687
5762
|
nextStep: "Call status to verify the new expiry."
|
|
5688
5763
|
},
|
|
5689
5764
|
status: {
|
|
@@ -5825,7 +5900,7 @@ function registerHelpTools(server, baseCtx) {
|
|
|
5825
5900
|
server.registerTool(
|
|
5826
5901
|
"init",
|
|
5827
5902
|
{
|
|
5828
|
-
description: "Initialize the active MCP workspace Root as a Sakupa project. Takes no path argument, creates only .sakupa/project.json at that exact Root, preserves site/recovery state, makes no API call and is idempotent.
|
|
5903
|
+
description: "Initialize the active MCP workspace Root as a Sakupa project. Takes no path argument, creates only .sakupa/project.json at that exact Root, preserves site/recovery state, makes no API call and is idempotent. If MCP Roots are unavailable, call help; the AI may then use the no-argument CLI init itself.",
|
|
5829
5904
|
inputSchema: {},
|
|
5830
5905
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5831
5906
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -6151,14 +6226,14 @@ Workflow:
|
|
|
6151
6226
|
current deployment and serving state at any time. Every update checks the credential's
|
|
6152
6227
|
server-side issue time. When it is older than 7 days, deploy succeeds and only SUGGESTS the
|
|
6153
6228
|
optional rotate tool; never rotate without the user's explicit confirmation.
|
|
6154
|
-
3. To
|
|
6229
|
+
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
6155
6230
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
6156
|
-
water/personal/share/business).
|
|
6157
|
-
${hostPattern} URL
|
|
6231
|
+
water/personal/share/business). While the subscription remains active, the
|
|
6232
|
+
${hostPattern} URL stays live without the free 24-hour expiry. Usage over the chosen plan
|
|
6158
6233
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
6159
6234
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
6160
6235
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
6161
|
-
serving surface alongside the
|
|
6236
|
+
serving surface alongside the subscription-backed Sakupa URL. Ownership is proven only by DNS control; the
|
|
6162
6237
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
6163
6238
|
change, portal and recover manage the paid lifecycle. Binding a
|
|
6164
6239
|
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
@@ -6183,11 +6258,12 @@ translate every human-readable summary, prompt, option label, description and co
|
|
|
6183
6258
|
language. Keep option IDs, tool names, exact arguments, URLs, field names and confirmation values
|
|
6184
6259
|
unchanged.
|
|
6185
6260
|
|
|
6186
|
-
Project directory contract: before the first deploy or a new recovery,
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6261
|
+
Project directory contract: before the first deploy or a new recovery, CALL the init MCP tool with
|
|
6262
|
+
NO path argument. init uses the IDE's exact MCP Root and creates the non-secret
|
|
6263
|
+
.sakupa/project.json directly there. Do not merely print installation or CLI instructions when the
|
|
6264
|
+
init tool is available. Only after help confirms that the client does not provide MCP Roots may the
|
|
6265
|
+
AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a fallback; never ask the user to
|
|
6266
|
+
run it. The CLI also accepts NO path argument. ONE MCP process = ONE Roots-first locked project = ONE site.
|
|
6191
6267
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6192
6268
|
and public_recovery portal remain project-independent.
|
|
6193
6269
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6200,11 +6276,21 @@ before retrying with outputDirChangeConfirmed: true.
|
|
|
6200
6276
|
After init, require sakupaAtProjectRoot=true. Before deploy, Sakupa checks every directory segment
|
|
6201
6277
|
between the project Root and outputDir for a misplaced .sakupa and safely relocates only validated,
|
|
6202
6278
|
non-conflicting state; never copy, delete or overwrite site.json by shell command.
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6279
|
+
Every result retains the exact environment for AI reasoning. Prominently tell the user when the
|
|
6280
|
+
environment is TEST or unknown/non-production. Do not proactively mention a normal PRODUCTION
|
|
6281
|
+
environment unless the user asks or the distinction is relevant to the current question. analyze,
|
|
6282
|
+
deploy, status, and refresh echo
|
|
6206
6283
|
the Roots-first locked directory they acted on.
|
|
6207
6284
|
|
|
6285
|
+
Presentation contract: preserve every returned technical fact for reasoning and exact continuation,
|
|
6286
|
+
including paths, IDs, enums, tool names, arguments, DNS values, provider states, amounts and exact
|
|
6287
|
+
timestamps. Do not mechanically dump every field. Answer the user's current question first, explain
|
|
6288
|
+
relevant technical terms in plain language while retaining the exact term, translate human-readable
|
|
6289
|
+
explanations into the user's language, and always disclose material charges, deadlines, cancellation,
|
|
6290
|
+
replacement, credential revocation, security effects and irreversible consequences. A technical fact
|
|
6291
|
+
may be omitted from the immediate user-facing answer only when it is irrelevant to the current
|
|
6292
|
+
question; it must remain available in the tool result for follow-up.
|
|
6293
|
+
|
|
6208
6294
|
On any difficulty, call help before retrying or escalating. Only offer report when help returns
|
|
6209
6295
|
reportRecommended:true; attach your own factual account via agentContext and show the exact
|
|
6210
6296
|
sanitized preview before asking the user to confirm submission.
|
|
@@ -6288,10 +6374,12 @@ export {
|
|
|
6288
6374
|
FetchTransport,
|
|
6289
6375
|
HttpApiClient,
|
|
6290
6376
|
MCP_VERSION,
|
|
6377
|
+
PRODUCTION_API_BASE_URL,
|
|
6291
6378
|
TEST_API_BASE_URL,
|
|
6292
6379
|
analyzeProject,
|
|
6293
6380
|
createSakupaMcpServer,
|
|
6294
6381
|
deleteSiteFile,
|
|
6382
|
+
environmentFor,
|
|
6295
6383
|
loadMcpRuntimeConfig,
|
|
6296
6384
|
loadSiteFile,
|
|
6297
6385
|
registerTools,
|