@sakupa/mcp 0.7.47 → 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 +154 -36
- package/dist/index.js +156 -36
- 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
|
}
|
|
@@ -3229,6 +3237,19 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
3229
3237
|
operationId: z.string().optional(),
|
|
3230
3238
|
summary: z.string(),
|
|
3231
3239
|
data: z.record(z.string(), z.unknown()),
|
|
3240
|
+
presentation: z.object({
|
|
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"),
|
|
3247
|
+
translateFields: 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())
|
|
3252
|
+
}).optional(),
|
|
3232
3253
|
decision: z.object({
|
|
3233
3254
|
decisionVersion: z.literal(1),
|
|
3234
3255
|
prompt: z.string(),
|
|
@@ -3282,9 +3303,54 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
3282
3303
|
)
|
|
3283
3304
|
};
|
|
3284
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.";
|
|
3285
3349
|
return {
|
|
3286
|
-
content: [{ type: "text", text: envelope.summary
|
|
3287
|
-
|
|
3350
|
+
content: [{ type: "text", text: `${envelope.summary}
|
|
3351
|
+
|
|
3352
|
+
${presentationFallback}` }],
|
|
3353
|
+
structuredContent: structuredEnvelope
|
|
3288
3354
|
};
|
|
3289
3355
|
}
|
|
3290
3356
|
|
|
@@ -3435,14 +3501,22 @@ function toolError(e) {
|
|
|
3435
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;
|
|
3436
3502
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
3437
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}`;
|
|
3438
3507
|
const result = structuredToolResult({
|
|
3439
3508
|
schemaVersion: 1,
|
|
3440
3509
|
outcome: "failed",
|
|
3441
3510
|
resultCode: `error_${errorCode}`,
|
|
3442
|
-
summary:
|
|
3511
|
+
summary: userFacingSummary,
|
|
3443
3512
|
data: {
|
|
3444
3513
|
errorCode,
|
|
3445
3514
|
retryable,
|
|
3515
|
+
customerMeaning,
|
|
3516
|
+
diagnosticContext: {
|
|
3517
|
+
technicalMessage: safeSummary,
|
|
3518
|
+
source: serverGuidance ? "sakupa_business_guidance" : "mcp_safe_diagnostic"
|
|
3519
|
+
},
|
|
3446
3520
|
...safeDetails && Object.keys(safeDetails).length > 0 ? { details: safeDetails } : {}
|
|
3447
3521
|
},
|
|
3448
3522
|
nextActions: [
|
|
@@ -3458,6 +3532,26 @@ function toolError(e) {
|
|
|
3458
3532
|
}
|
|
3459
3533
|
|
|
3460
3534
|
// src/tools/decision.ts
|
|
3535
|
+
var DECISION_PRESENTATION_POLICY = {
|
|
3536
|
+
translateFields: [
|
|
3537
|
+
"decision.prompt",
|
|
3538
|
+
"decision.options[].label",
|
|
3539
|
+
"decision.options[].description",
|
|
3540
|
+
"decision.options[].consequences[]"
|
|
3541
|
+
],
|
|
3542
|
+
preserveExactFields: [
|
|
3543
|
+
"decision.options[].id",
|
|
3544
|
+
"decision.options[].nextAction",
|
|
3545
|
+
"userAction.resumeWith",
|
|
3546
|
+
"userAction.options[].value",
|
|
3547
|
+
"nextActions",
|
|
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."
|
|
3553
|
+
]
|
|
3554
|
+
};
|
|
3461
3555
|
var FORBIDDEN_DECISION_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
|
|
3462
3556
|
"credential",
|
|
3463
3557
|
"candidatecredential",
|
|
@@ -3598,6 +3692,7 @@ function decisionToolResult(input) {
|
|
|
3598
3692
|
|
|
3599
3693
|
${formatDecisionFallback(decision)}`,
|
|
3600
3694
|
data: input.data,
|
|
3695
|
+
presentation: DECISION_PRESENTATION_POLICY,
|
|
3601
3696
|
decision,
|
|
3602
3697
|
...userAction === void 0 ? {} : { userAction },
|
|
3603
3698
|
nextActions
|
|
@@ -3655,6 +3750,15 @@ var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
|
3655
3750
|
function planCatalog() {
|
|
3656
3751
|
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
3657
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
|
+
}
|
|
3658
3762
|
var ticketCategoryEnum = z2.enum([
|
|
3659
3763
|
"billing",
|
|
3660
3764
|
"payment",
|
|
@@ -3905,7 +4009,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
3905
4009
|
server.registerTool(
|
|
3906
4010
|
"deploy",
|
|
3907
4011
|
{
|
|
3908
|
-
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.`,
|
|
3909
4013
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
3910
4014
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
3911
4015
|
inputSchema: {
|
|
@@ -4396,12 +4500,11 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4396
4500
|
return text(
|
|
4397
4501
|
"site_published",
|
|
4398
4502
|
`Site published: ${finalized2.url}
|
|
4399
|
-
|
|
4400
|
-
Project directory: ${ctx.projectDir}
|
|
4503
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4401
4504
|
Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
|
|
4402
4505
|
` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
|
|
4403
4506
|
` : "") + `
|
|
4404
|
-
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.
|
|
4405
4508
|
` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
|
|
4406
4509
|
Warnings:
|
|
4407
4510
|
${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
@@ -4486,14 +4589,13 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
4486
4589
|
return text(
|
|
4487
4590
|
handoffPerformed ? "free_site_slot_reassigned" : "site_updated",
|
|
4488
4591
|
`Site updated: ${finalized.url}
|
|
4489
|
-
|
|
4490
|
-
Project directory: ${ctx.projectDir}
|
|
4592
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4491
4593
|
Files uploaded: ${uploaded} (${finalized.totalBytes} bytes)
|
|
4492
4594
|
` + (finalized.expiresAt ? `Validity refreshed \u2014 expires at: ${finalized.expiresAt}
|
|
4493
4595
|
` : "") + (credentialRelocatedFrom.length > 0 ? `Credential binding relocated from ${credentialRelocatedFrom.join(", ")} to ${ctx.projectDir}/.sakupa; the existing site was preserved.
|
|
4494
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" ? `
|
|
4495
|
-
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call.
|
|
4496
|
-
` : "\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 ? `
|
|
4497
4599
|
Warnings:
|
|
4498
4600
|
${JSON.stringify(finalized.warnings, null, 2)}` : "") + (credentialSecurity?.rotationRecommended ? `
|
|
4499
4601
|
|
|
@@ -4549,7 +4651,7 @@ Optional security recommendation: this management credential was created at ${cr
|
|
|
4549
4651
|
server.registerTool(
|
|
4550
4652
|
"refresh",
|
|
4551
4653
|
{
|
|
4552
|
-
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.",
|
|
4553
4655
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4554
4656
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4555
4657
|
inputSchema: {}
|
|
@@ -4613,7 +4715,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4613
4715
|
server.registerTool(
|
|
4614
4716
|
"subscribe",
|
|
4615
4717
|
{
|
|
4616
|
-
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.`,
|
|
4617
4719
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4618
4720
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4619
4721
|
inputSchema: {
|
|
@@ -4640,7 +4742,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4640
4742
|
${res.checkoutUrl}
|
|
4641
4743
|
|
|
4642
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.
|
|
4643
|
-
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.`,
|
|
4644
4746
|
{
|
|
4645
4747
|
siteId: res.siteId,
|
|
4646
4748
|
plan: res.plan,
|
|
@@ -4659,7 +4761,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
4659
4761
|
server.registerTool(
|
|
4660
4762
|
"bind",
|
|
4661
4763
|
{
|
|
4662
|
-
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.`,
|
|
4663
4765
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4664
4766
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4665
4767
|
inputSchema: {
|
|
@@ -4787,7 +4889,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
4787
4889
|
noteSiteMode(res.siteId, res.mode);
|
|
4788
4890
|
const lines = [
|
|
4789
4891
|
`AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
|
|
4790
|
-
res.permanentUrl ? `
|
|
4892
|
+
res.permanentUrl ? `Subscription-backed Sakupa URL: ${res.permanentUrl}` : void 0,
|
|
4791
4893
|
res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
|
|
4792
4894
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
4793
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,
|
|
@@ -5486,7 +5588,7 @@ var TOOL_MANUALS = {
|
|
|
5486
5588
|
init: {
|
|
5487
5589
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5488
5590
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5489
|
-
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.",
|
|
5490
5592
|
parameterNames: [],
|
|
5491
5593
|
parameters: "No parameters and no path argument.",
|
|
5492
5594
|
warnings: [
|
|
@@ -5535,7 +5637,9 @@ var TOOL_MANUALS = {
|
|
|
5535
5637
|
preconditions: "A valid local site credential.",
|
|
5536
5638
|
parameterNames: [],
|
|
5537
5639
|
parameters: "No parameters.",
|
|
5538
|
-
warnings: [
|
|
5640
|
+
warnings: [
|
|
5641
|
+
"Subscription-backed sites have no free-site expiry while the subscription remains active and do not need refresh."
|
|
5642
|
+
],
|
|
5539
5643
|
nextStep: "Call status to verify the new expiry."
|
|
5540
5644
|
},
|
|
5541
5645
|
status: {
|
|
@@ -5677,7 +5781,7 @@ function registerHelpTools(server, baseCtx) {
|
|
|
5677
5781
|
server.registerTool(
|
|
5678
5782
|
"init",
|
|
5679
5783
|
{
|
|
5680
|
-
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.",
|
|
5681
5785
|
inputSchema: {},
|
|
5682
5786
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5683
5787
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -6154,14 +6258,14 @@ Workflow:
|
|
|
6154
6258
|
current deployment and serving state at any time. Every update checks the credential's
|
|
6155
6259
|
server-side issue time. When it is older than 7 days, deploy succeeds and only SUGGESTS the
|
|
6156
6260
|
optional rotate tool; never rotate without the user's explicit confirmation.
|
|
6157
|
-
3. To
|
|
6261
|
+
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
6158
6262
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
6159
|
-
water/personal/share/business).
|
|
6160
|
-
${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
|
|
6161
6265
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
6162
6266
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
6163
6267
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
6164
|
-
serving surface alongside the
|
|
6268
|
+
serving surface alongside the subscription-backed Sakupa URL. Ownership is proven only by DNS control; the
|
|
6165
6269
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
6166
6270
|
change, portal and recover manage the paid lifecycle. Binding a
|
|
6167
6271
|
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
@@ -6181,13 +6285,17 @@ context, paraphrase a selection into different arguments, or invent another opti
|
|
|
6181
6285
|
selects, copy that option's exact nextAction. Legacy userAction and nextActions mirror the same
|
|
6182
6286
|
choice for older clients; decision is the authoritative choice set. A type "none" option means call
|
|
6183
6287
|
no tool and change nothing. Stripe-hosted links remain direct links because Stripe itself owns plan
|
|
6184
|
-
selection and confirmation.
|
|
6288
|
+
selection and confirmation. Before showing any decision, infer the language the user is using and
|
|
6289
|
+
translate every human-readable summary, prompt, option label, description and consequence into that
|
|
6290
|
+
language. Keep option IDs, tool names, exact arguments, URLs, field names and confirmation values
|
|
6291
|
+
unchanged.
|
|
6185
6292
|
|
|
6186
|
-
Project directory contract: before the first deploy or a new recovery,
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
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.
|
|
6191
6299
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6192
6300
|
and public_recovery portal remain project-independent.
|
|
6193
6301
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6200,11 +6308,21 @@ before retrying with outputDirChangeConfirmed: true.
|
|
|
6200
6308
|
After init, require sakupaAtProjectRoot=true. Before deploy, Sakupa checks every directory segment
|
|
6201
6309
|
between the project Root and outputDir for a misplaced .sakupa and safely relocates only validated,
|
|
6202
6310
|
non-conflicting state; never copy, delete or overwrite site.json by shell command.
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
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
|
|
6206
6315
|
the Roots-first locked directory they acted on.
|
|
6207
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
|
+
|
|
6208
6326
|
On any difficulty, call help before retrying or escalating. Only offer report when help returns
|
|
6209
6327
|
reportRecommended:true; attach your own factual account via agentContext and show the exact
|
|
6210
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
|
}
|
|
@@ -2039,6 +2047,19 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
2039
2047
|
operationId: z.string().optional(),
|
|
2040
2048
|
summary: z.string(),
|
|
2041
2049
|
data: z.record(z.string(), z.unknown()),
|
|
2050
|
+
presentation: z.object({
|
|
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"),
|
|
2057
|
+
translateFields: 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())
|
|
2062
|
+
}).optional(),
|
|
2042
2063
|
decision: z.object({
|
|
2043
2064
|
decisionVersion: z.literal(1),
|
|
2044
2065
|
prompt: z.string(),
|
|
@@ -2092,9 +2113,54 @@ var STRUCTURED_TOOL_OUTPUT_SCHEMA = {
|
|
|
2092
2113
|
)
|
|
2093
2114
|
};
|
|
2094
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.";
|
|
2095
2159
|
return {
|
|
2096
|
-
content: [{ type: "text", text: envelope.summary
|
|
2097
|
-
|
|
2160
|
+
content: [{ type: "text", text: `${envelope.summary}
|
|
2161
|
+
|
|
2162
|
+
${presentationFallback}` }],
|
|
2163
|
+
structuredContent: structuredEnvelope
|
|
2098
2164
|
};
|
|
2099
2165
|
}
|
|
2100
2166
|
|
|
@@ -2245,14 +2311,22 @@ function toolError(e) {
|
|
|
2245
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;
|
|
2246
2312
|
const serverGuidance = isSakupaError(e) && errorCode !== "internal" && errorCode !== "unauthorized" && errorCode !== "upgrade_required" && e.message.trim().length > 0 ? e.message : void 0;
|
|
2247
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}`;
|
|
2248
2317
|
const result = structuredToolResult({
|
|
2249
2318
|
schemaVersion: 1,
|
|
2250
2319
|
outcome: "failed",
|
|
2251
2320
|
resultCode: `error_${errorCode}`,
|
|
2252
|
-
summary:
|
|
2321
|
+
summary: userFacingSummary,
|
|
2253
2322
|
data: {
|
|
2254
2323
|
errorCode,
|
|
2255
2324
|
retryable,
|
|
2325
|
+
customerMeaning,
|
|
2326
|
+
diagnosticContext: {
|
|
2327
|
+
technicalMessage: safeSummary,
|
|
2328
|
+
source: serverGuidance ? "sakupa_business_guidance" : "mcp_safe_diagnostic"
|
|
2329
|
+
},
|
|
2256
2330
|
...safeDetails && Object.keys(safeDetails).length > 0 ? { details: safeDetails } : {}
|
|
2257
2331
|
},
|
|
2258
2332
|
nextActions: [
|
|
@@ -3574,6 +3648,26 @@ async function resumeCredentialRotation(client, projectDir, site, apiBaseUrl) {
|
|
|
3574
3648
|
}
|
|
3575
3649
|
|
|
3576
3650
|
// src/tools/decision.ts
|
|
3651
|
+
var DECISION_PRESENTATION_POLICY = {
|
|
3652
|
+
translateFields: [
|
|
3653
|
+
"decision.prompt",
|
|
3654
|
+
"decision.options[].label",
|
|
3655
|
+
"decision.options[].description",
|
|
3656
|
+
"decision.options[].consequences[]"
|
|
3657
|
+
],
|
|
3658
|
+
preserveExactFields: [
|
|
3659
|
+
"decision.options[].id",
|
|
3660
|
+
"decision.options[].nextAction",
|
|
3661
|
+
"userAction.resumeWith",
|
|
3662
|
+
"userAction.options[].value",
|
|
3663
|
+
"nextActions",
|
|
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."
|
|
3669
|
+
]
|
|
3670
|
+
};
|
|
3577
3671
|
var FORBIDDEN_DECISION_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
|
|
3578
3672
|
"credential",
|
|
3579
3673
|
"candidatecredential",
|
|
@@ -3714,6 +3808,7 @@ function decisionToolResult(input) {
|
|
|
3714
3808
|
|
|
3715
3809
|
${formatDecisionFallback(decision)}`,
|
|
3716
3810
|
data: input.data,
|
|
3811
|
+
presentation: DECISION_PRESENTATION_POLICY,
|
|
3717
3812
|
decision,
|
|
3718
3813
|
...userAction === void 0 ? {} : { userAction },
|
|
3719
3814
|
nextActions
|
|
@@ -3771,6 +3866,15 @@ var severityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
|
3771
3866
|
function planCatalog() {
|
|
3772
3867
|
return TIER_ORDER.map((p) => `${p} JPY ${tierPriceJpy(p)}/month`).join(", ");
|
|
3773
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
|
+
}
|
|
3774
3878
|
var ticketCategoryEnum = z2.enum([
|
|
3775
3879
|
"billing",
|
|
3776
3880
|
"payment",
|
|
@@ -4021,7 +4125,7 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4021
4125
|
server.registerTool(
|
|
4022
4126
|
"deploy",
|
|
4023
4127
|
{
|
|
4024
|
-
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.`,
|
|
4025
4129
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4026
4130
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4027
4131
|
inputSchema: {
|
|
@@ -4512,12 +4616,11 @@ Next action: ${analysis.suggestedNextAction}`,
|
|
|
4512
4616
|
return text(
|
|
4513
4617
|
"site_published",
|
|
4514
4618
|
`Site published: ${finalized2.url}
|
|
4515
|
-
|
|
4516
|
-
Project directory: ${ctx.projectDir}
|
|
4619
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4517
4620
|
Files uploaded: ${uploaded2} (${finalized2.totalBytes} bytes)
|
|
4518
4621
|
` + (finalized2.expiresAt ? `Expires at: ${finalized2.expiresAt}
|
|
4519
4622
|
` : "") + `
|
|
4520
|
-
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.
|
|
4521
4624
|
` + credentialGitReminder(ctx.projectDir) + (finalized2.warnings.length > 0 ? `
|
|
4522
4625
|
Warnings:
|
|
4523
4626
|
${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
@@ -4602,14 +4705,13 @@ ${JSON.stringify(finalized2.warnings, null, 2)}` : ""),
|
|
|
4602
4705
|
return text(
|
|
4603
4706
|
handoffPerformed ? "free_site_slot_reassigned" : "site_updated",
|
|
4604
4707
|
`Site updated: ${finalized.url}
|
|
4605
|
-
|
|
4606
|
-
Project directory: ${ctx.projectDir}
|
|
4708
|
+
` + deploymentEnvironmentContext(ctx.apiBaseUrl) + `Project directory: ${ctx.projectDir}
|
|
4607
4709
|
Files uploaded: ${uploaded} (${finalized.totalBytes} bytes)
|
|
4608
4710
|
` + (finalized.expiresAt ? `Validity refreshed \u2014 expires at: ${finalized.expiresAt}
|
|
4609
4711
|
` : "") + (credentialRelocatedFrom.length > 0 ? `Credential binding relocated from ${credentialRelocatedFrom.join(", ")} to ${ctx.projectDir}/.sakupa; the existing site was preserved.
|
|
4610
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" ? `
|
|
4611
|
-
Reminder: free sites stay live for ${FREE_SITE_TTL_HOURS} hours after the last deploy or refresh call.
|
|
4612
|
-
` : "\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 ? `
|
|
4613
4715
|
Warnings:
|
|
4614
4716
|
${JSON.stringify(finalized.warnings, null, 2)}` : "") + (credentialSecurity?.rotationRecommended ? `
|
|
4615
4717
|
|
|
@@ -4665,7 +4767,7 @@ Optional security recommendation: this management credential was created at ${cr
|
|
|
4665
4767
|
server.registerTool(
|
|
4666
4768
|
"refresh",
|
|
4667
4769
|
{
|
|
4668
|
-
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.",
|
|
4669
4771
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4670
4772
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4671
4773
|
inputSchema: {}
|
|
@@ -4729,7 +4831,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4729
4831
|
server.registerTool(
|
|
4730
4832
|
"subscribe",
|
|
4731
4833
|
{
|
|
4732
|
-
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.`,
|
|
4733
4835
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4734
4836
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
4735
4837
|
inputSchema: {
|
|
@@ -4756,7 +4858,7 @@ NO content was uploaded or changed by this call \u2014 to publish new or edited
|
|
|
4756
4858
|
${res.checkoutUrl}
|
|
4757
4859
|
|
|
4758
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.
|
|
4759
|
-
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.`,
|
|
4760
4862
|
{
|
|
4761
4863
|
siteId: res.siteId,
|
|
4762
4864
|
plan: res.plan,
|
|
@@ -4775,7 +4877,7 @@ Once payment confirms, the site becomes permanent on its current URL. Binding a
|
|
|
4775
4877
|
server.registerTool(
|
|
4776
4878
|
"bind",
|
|
4777
4879
|
{
|
|
4778
|
-
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.`,
|
|
4779
4881
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
4780
4882
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
4781
4883
|
inputSchema: {
|
|
@@ -4903,7 +5005,7 @@ When the user says the TXT is set, run bind "status". It verifies ownership and
|
|
|
4903
5005
|
noteSiteMode(res.siteId, res.mode);
|
|
4904
5006
|
const lines = [
|
|
4905
5007
|
`AUTHORITATIVE BILLING SNAPSHOT for site ${res.siteId} (mode: ${res.mode})`,
|
|
4906
|
-
res.permanentUrl ? `
|
|
5008
|
+
res.permanentUrl ? `Subscription-backed Sakupa URL: ${res.permanentUrl}` : void 0,
|
|
4907
5009
|
res.plan ? `Current plan: ${res.plan} (JPY ${res.monthlyPriceJpy ?? tierPriceJpy(res.plan)}/month)` : "Current plan: (no subscription yet)",
|
|
4908
5010
|
res.subscriptionStatus ? `Subscription payment state: ${res.subscriptionStatus}` : void 0,
|
|
4909
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,
|
|
@@ -5605,7 +5707,7 @@ var TOOL_MANUALS = {
|
|
|
5605
5707
|
init: {
|
|
5606
5708
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5607
5709
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5608
|
-
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.",
|
|
5609
5711
|
parameterNames: [],
|
|
5610
5712
|
parameters: "No parameters and no path argument.",
|
|
5611
5713
|
warnings: [
|
|
@@ -5654,7 +5756,9 @@ var TOOL_MANUALS = {
|
|
|
5654
5756
|
preconditions: "A valid local site credential.",
|
|
5655
5757
|
parameterNames: [],
|
|
5656
5758
|
parameters: "No parameters.",
|
|
5657
|
-
warnings: [
|
|
5759
|
+
warnings: [
|
|
5760
|
+
"Subscription-backed sites have no free-site expiry while the subscription remains active and do not need refresh."
|
|
5761
|
+
],
|
|
5658
5762
|
nextStep: "Call status to verify the new expiry."
|
|
5659
5763
|
},
|
|
5660
5764
|
status: {
|
|
@@ -5796,7 +5900,7 @@ function registerHelpTools(server, baseCtx) {
|
|
|
5796
5900
|
server.registerTool(
|
|
5797
5901
|
"init",
|
|
5798
5902
|
{
|
|
5799
|
-
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.",
|
|
5800
5904
|
inputSchema: {},
|
|
5801
5905
|
outputSchema: STRUCTURED_TOOL_OUTPUT_SCHEMA,
|
|
5802
5906
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
@@ -6122,14 +6226,14 @@ Workflow:
|
|
|
6122
6226
|
current deployment and serving state at any time. Every update checks the credential's
|
|
6123
6227
|
server-side issue time. When it is older than 7 days, deploy succeeds and only SUGGESTS the
|
|
6124
6228
|
optional rotate tool; never rotate without the user's explicit confirmation.
|
|
6125
|
-
3. To
|
|
6229
|
+
3. To keep the site live beyond the free period, subscribe it to a monthly hosting plan (plans
|
|
6126
6230
|
shows the catalog; subscribe -> Stripe-hosted checkout;
|
|
6127
|
-
water/personal/share/business).
|
|
6128
|
-
${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
|
|
6129
6233
|
shows an over-limit notice by default. An external AI may periodically query usage and
|
|
6130
6234
|
recommend a plan, but Sakupa never changes a subscription automatically.
|
|
6131
6235
|
4. Optionally bind a custom domain to the subscribed site (bind): an included extra
|
|
6132
|
-
serving surface alongside the
|
|
6236
|
+
serving surface alongside the subscription-backed Sakupa URL. Ownership is proven only by DNS control; the
|
|
6133
6237
|
first verified request wins; unverified requests expire after 72 hours. billing,
|
|
6134
6238
|
change, portal and recover manage the paid lifecycle. Binding a
|
|
6135
6239
|
NEW domain while one is live is a zero-downtime SWITCH: the old domain keeps serving
|
|
@@ -6149,13 +6253,17 @@ context, paraphrase a selection into different arguments, or invent another opti
|
|
|
6149
6253
|
selects, copy that option's exact nextAction. Legacy userAction and nextActions mirror the same
|
|
6150
6254
|
choice for older clients; decision is the authoritative choice set. A type "none" option means call
|
|
6151
6255
|
no tool and change nothing. Stripe-hosted links remain direct links because Stripe itself owns plan
|
|
6152
|
-
selection and confirmation.
|
|
6256
|
+
selection and confirmation. Before showing any decision, infer the language the user is using and
|
|
6257
|
+
translate every human-readable summary, prompt, option label, description and consequence into that
|
|
6258
|
+
language. Keep option IDs, tool names, exact arguments, URLs, field names and confirmation values
|
|
6259
|
+
unchanged.
|
|
6153
6260
|
|
|
6154
|
-
Project directory contract: before the first deploy or a new recovery,
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
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.
|
|
6159
6267
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6160
6268
|
and public_recovery portal remain project-independent.
|
|
6161
6269
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6168,11 +6276,21 @@ before retrying with outputDirChangeConfirmed: true.
|
|
|
6168
6276
|
After init, require sakupaAtProjectRoot=true. Before deploy, Sakupa checks every directory segment
|
|
6169
6277
|
between the project Root and outputDir for a misplaced .sakupa and safely relocates only validated,
|
|
6170
6278
|
non-conflicting state; never copy, delete or overwrite site.json by shell command.
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
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
|
|
6174
6283
|
the Roots-first locked directory they acted on.
|
|
6175
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
|
+
|
|
6176
6294
|
On any difficulty, call help before retrying or escalating. Only offer report when help returns
|
|
6177
6295
|
reportRecommended:true; attach your own factual account via agentContext and show the exact
|
|
6178
6296
|
sanitized preview before asking the user to confirm submission.
|
|
@@ -6256,10 +6374,12 @@ export {
|
|
|
6256
6374
|
FetchTransport,
|
|
6257
6375
|
HttpApiClient,
|
|
6258
6376
|
MCP_VERSION,
|
|
6377
|
+
PRODUCTION_API_BASE_URL,
|
|
6259
6378
|
TEST_API_BASE_URL,
|
|
6260
6379
|
analyzeProject,
|
|
6261
6380
|
createSakupaMcpServer,
|
|
6262
6381
|
deleteSiteFile,
|
|
6382
|
+
environmentFor,
|
|
6263
6383
|
loadMcpRuntimeConfig,
|
|
6264
6384
|
loadSiteFile,
|
|
6265
6385
|
registerTools,
|