@sakupa/mcp 1.1.0 → 1.2.0
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 +111 -66
- package/dist/index.js +116 -71
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -402,7 +402,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
// ../core/dist/domain/version.js
|
|
405
|
-
var SAKUPA_MCP_VERSION = "1.
|
|
405
|
+
var SAKUPA_MCP_VERSION = "1.2.0";
|
|
406
406
|
|
|
407
407
|
// ../core/dist/domain/errors.js
|
|
408
408
|
var HTTP_STATUS = {
|
|
@@ -738,13 +738,15 @@ function previewHostPatternFor(apiBaseUrl) {
|
|
|
738
738
|
function loadMcpRuntimeConfig(env = process.env) {
|
|
739
739
|
const apiBaseUrl = (env["SAKUPA_API_URL"] ?? env["SAKUPA_API_BASE_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
740
740
|
const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
|
|
741
|
+
const projectRoot = env["SAKUPA_PROJECT_ROOT"]?.trim() ?? "";
|
|
742
|
+
const projectRootConfig = projectRoot.length > 0 ? { projectRoot } : {};
|
|
741
743
|
if (apiBaseUrl === TEST_API_BASE_URL) {
|
|
742
744
|
if (testAccessToken.length === 0) {
|
|
743
745
|
throw new Error(
|
|
744
746
|
"The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
|
|
745
747
|
);
|
|
746
748
|
}
|
|
747
|
-
return { apiBaseUrl, testAccessToken };
|
|
749
|
+
return { apiBaseUrl, testAccessToken, ...projectRootConfig };
|
|
748
750
|
}
|
|
749
751
|
if (apiBaseUrl !== PRODUCTION_API_BASE_URL) {
|
|
750
752
|
throw new Error(
|
|
@@ -756,7 +758,7 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
756
758
|
`SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
|
|
757
759
|
);
|
|
758
760
|
}
|
|
759
|
-
return { apiBaseUrl };
|
|
761
|
+
return { apiBaseUrl, ...projectRootConfig };
|
|
760
762
|
}
|
|
761
763
|
function environmentFor(apiBaseUrl) {
|
|
762
764
|
if (apiBaseUrl === TEST_API_BASE_URL) return "test";
|
|
@@ -2957,7 +2959,7 @@ import {
|
|
|
2957
2959
|
|
|
2958
2960
|
// src/project-binding.ts
|
|
2959
2961
|
import { fileURLToPath } from "node:url";
|
|
2960
|
-
import { resolve as resolve4 } from "node:path";
|
|
2962
|
+
import { isAbsolute as isAbsolute4, resolve as resolve4 } from "node:path";
|
|
2961
2963
|
var MCP_ROOTS_TIMEOUT_MS = 5e3;
|
|
2962
2964
|
var McpRootsPending = class extends Error {
|
|
2963
2965
|
constructor() {
|
|
@@ -2974,10 +2976,11 @@ var ProjectBindingError = class extends Error {
|
|
|
2974
2976
|
}
|
|
2975
2977
|
};
|
|
2976
2978
|
var ProjectBindingResolver = class {
|
|
2977
|
-
constructor(processCwd, rootsProvider, rootsTimeoutMs = MCP_ROOTS_TIMEOUT_MS) {
|
|
2979
|
+
constructor(processCwd, rootsProvider, rootsTimeoutMs = MCP_ROOTS_TIMEOUT_MS, configuredRoot) {
|
|
2978
2980
|
this.processCwd = processCwd;
|
|
2979
2981
|
this.rootsProvider = rootsProvider;
|
|
2980
2982
|
this.rootsTimeoutMs = rootsTimeoutMs;
|
|
2983
|
+
this.configuredRoot = configuredRoot;
|
|
2981
2984
|
}
|
|
2982
2985
|
bound;
|
|
2983
2986
|
boundState;
|
|
@@ -3016,28 +3019,41 @@ var ProjectBindingResolver = class {
|
|
|
3016
3019
|
throw new ProjectBindingError(inspection.diagnostics);
|
|
3017
3020
|
}
|
|
3018
3021
|
const initialized = initializeProject(inspection.initializableRoot);
|
|
3019
|
-
this.bound = {
|
|
3022
|
+
this.bound = {
|
|
3023
|
+
...initialized,
|
|
3024
|
+
bindingSource: inspection.initializableSource ?? "mcp_root"
|
|
3025
|
+
};
|
|
3020
3026
|
this.boundState = boundDiagnostics(
|
|
3021
3027
|
this.processCwd,
|
|
3022
3028
|
this.bound,
|
|
3023
|
-
{ supported:
|
|
3024
|
-
inspection.diagnostics.rootCandidates
|
|
3029
|
+
{ supported: inspection.diagnostics.mcpRootsSupported, roots: [] },
|
|
3030
|
+
inspection.diagnostics.rootCandidates,
|
|
3031
|
+
inspection.diagnostics.configuredProjectRoot
|
|
3025
3032
|
);
|
|
3026
3033
|
return this.bound;
|
|
3027
3034
|
}
|
|
3028
3035
|
async inspect(forInitialization = false, call) {
|
|
3029
3036
|
const snapshot = await safeRootsSnapshot(this.rootsProvider, this.rootsTimeoutMs, call);
|
|
3030
3037
|
const rootCandidates = snapshot.roots.map(inspectRoot);
|
|
3038
|
+
const configured = this.configuredRoot === void 0 ? void 0 : inspectConfiguredRoot(this.configuredRoot);
|
|
3039
|
+
const diagnose = (code, guidance) => diagnostic(code, snapshot, this.processCwd, rootCandidates, guidance, configured);
|
|
3040
|
+
const bound = (selected) => ({
|
|
3041
|
+
selected,
|
|
3042
|
+
diagnostics: boundDiagnostics(
|
|
3043
|
+
this.processCwd,
|
|
3044
|
+
selected,
|
|
3045
|
+
snapshot,
|
|
3046
|
+
rootCandidates,
|
|
3047
|
+
configured
|
|
3048
|
+
)
|
|
3049
|
+
});
|
|
3031
3050
|
const initializedRoots = rootCandidates.filter(
|
|
3032
3051
|
(candidate) => candidate.initialized && candidate.path !== void 0
|
|
3033
3052
|
);
|
|
3034
3053
|
if (snapshot.supported && snapshot.error) {
|
|
3035
3054
|
return {
|
|
3036
|
-
diagnostics:
|
|
3055
|
+
diagnostics: diagnose(
|
|
3037
3056
|
"roots_request_failed",
|
|
3038
|
-
snapshot,
|
|
3039
|
-
this.processCwd,
|
|
3040
|
-
rootCandidates,
|
|
3041
3057
|
"The IDE advertised MCP Roots, but the Roots request failed. Retry help after the IDE finishes loading the workspace. If it persists, restart the MCP connection; do not initialize or deploy from the IDE installation directory."
|
|
3042
3058
|
)
|
|
3043
3059
|
};
|
|
@@ -3046,19 +3062,12 @@ var ProjectBindingResolver = class {
|
|
|
3046
3062
|
const initializedRoot = initializedRoots[0];
|
|
3047
3063
|
if (!initializedRoot) throw new Error("initialized Root disappeared during resolution");
|
|
3048
3064
|
const project = resolveLockedProjectRoot(initializedRoot.path);
|
|
3049
|
-
|
|
3050
|
-
return {
|
|
3051
|
-
selected,
|
|
3052
|
-
diagnostics: boundDiagnostics(this.processCwd, selected, snapshot, rootCandidates)
|
|
3053
|
-
};
|
|
3065
|
+
return bound({ ...project, bindingSource: "mcp_root" });
|
|
3054
3066
|
}
|
|
3055
3067
|
if (initializedRoots.length > 1) {
|
|
3056
3068
|
return {
|
|
3057
|
-
diagnostics:
|
|
3069
|
+
diagnostics: diagnose(
|
|
3058
3070
|
"multiple_initialized_roots",
|
|
3059
|
-
snapshot,
|
|
3060
|
-
this.processCwd,
|
|
3061
|
-
rootCandidates,
|
|
3062
3071
|
"More than one IDE workspace Root is already initialized for Sakupa. Close the unrelated workspaces and retry help; Sakupa will not guess which site to manage."
|
|
3063
3072
|
)
|
|
3064
3073
|
};
|
|
@@ -3072,22 +3081,17 @@ var ProjectBindingResolver = class {
|
|
|
3072
3081
|
if (!validRoot) throw new Error("workspace Root disappeared during initialization");
|
|
3073
3082
|
return {
|
|
3074
3083
|
initializableRoot: validRoot.path,
|
|
3075
|
-
|
|
3084
|
+
initializableSource: "mcp_root",
|
|
3085
|
+
diagnostics: diagnose(
|
|
3076
3086
|
"workspace_not_initialized",
|
|
3077
|
-
snapshot,
|
|
3078
|
-
this.processCwd,
|
|
3079
|
-
rootCandidates,
|
|
3080
3087
|
`The active MCP workspace ${validRoot.path} is ready to initialize.`
|
|
3081
3088
|
)
|
|
3082
3089
|
};
|
|
3083
3090
|
}
|
|
3084
3091
|
if (validRoots.length > 1) {
|
|
3085
3092
|
return {
|
|
3086
|
-
diagnostics:
|
|
3093
|
+
diagnostics: diagnose(
|
|
3087
3094
|
"multiple_uninitialized_roots",
|
|
3088
|
-
snapshot,
|
|
3089
|
-
this.processCwd,
|
|
3090
|
-
rootCandidates,
|
|
3091
3095
|
"The IDE exposes multiple uninitialized workspace Roots. Open only the intended project before calling init; Sakupa will not choose a directory for the user."
|
|
3092
3096
|
)
|
|
3093
3097
|
};
|
|
@@ -3097,54 +3101,59 @@ var ProjectBindingResolver = class {
|
|
|
3097
3101
|
const validRoot = validRoots[0];
|
|
3098
3102
|
if (!validRoot) throw new Error("workspace Root disappeared during diagnosis");
|
|
3099
3103
|
return {
|
|
3100
|
-
diagnostics:
|
|
3104
|
+
diagnostics: diagnose(
|
|
3101
3105
|
"workspace_not_initialized",
|
|
3102
|
-
snapshot,
|
|
3103
|
-
this.processCwd,
|
|
3104
|
-
rootCandidates,
|
|
3105
3106
|
`The IDE workspace ${validRoot.path} is not initialized. Call init with no path arguments; it will create .sakupa directly in that workspace Root.`
|
|
3106
3107
|
)
|
|
3107
3108
|
};
|
|
3108
3109
|
}
|
|
3109
3110
|
if (snapshot.supported && validRoots.length > 1) {
|
|
3110
3111
|
return {
|
|
3111
|
-
diagnostics:
|
|
3112
|
+
diagnostics: diagnose(
|
|
3112
3113
|
"multiple_uninitialized_roots",
|
|
3113
|
-
snapshot,
|
|
3114
|
-
this.processCwd,
|
|
3115
|
-
rootCandidates,
|
|
3116
3114
|
"The IDE exposes multiple uninitialized workspace Roots. Open only the intended project, then call init. Sakupa will not guess a project directory."
|
|
3117
3115
|
)
|
|
3118
3116
|
};
|
|
3119
3117
|
}
|
|
3118
|
+
if (configured) {
|
|
3119
|
+
if (configured.problem !== void 0 || configured.path === void 0) {
|
|
3120
|
+
return {
|
|
3121
|
+
diagnostics: diagnose(
|
|
3122
|
+
"invalid_configured_root",
|
|
3123
|
+
`SAKUPA_PROJECT_ROOT is set to ${configured.configured} but it is not usable: ${configured.problem ?? "unknown problem"}. Fix the MCP server configuration (an absolute path to an existing project directory) and retry help; Sakupa will not fall back to another directory.`
|
|
3124
|
+
)
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
if (configured.initialized) {
|
|
3128
|
+
const project = resolveLockedProjectRoot(configured.path);
|
|
3129
|
+
return bound({ ...project, bindingSource: "configured_root" });
|
|
3130
|
+
}
|
|
3131
|
+
return {
|
|
3132
|
+
...forInitialization ? { initializableRoot: configured.path, initializableSource: "configured_root" } : {},
|
|
3133
|
+
diagnostics: diagnose(
|
|
3134
|
+
"workspace_not_initialized",
|
|
3135
|
+
`The configured project root ${configured.path} (SAKUPA_PROJECT_ROOT) is not initialized. Call init with no path arguments; it will create .sakupa directly there.`
|
|
3136
|
+
)
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3120
3139
|
if (snapshot.supported) {
|
|
3121
3140
|
return {
|
|
3122
|
-
diagnostics:
|
|
3141
|
+
diagnostics: diagnose(
|
|
3123
3142
|
"workspace_not_initialized",
|
|
3124
|
-
snapshot,
|
|
3125
|
-
this.processCwd,
|
|
3126
|
-
rootCandidates,
|
|
3127
3143
|
"The IDE did not expose one usable file workspace Root. Open exactly one local project workspace, then retry help before calling init or deploy."
|
|
3128
3144
|
)
|
|
3129
3145
|
};
|
|
3130
3146
|
}
|
|
3131
3147
|
try {
|
|
3132
3148
|
const cwdProject = resolveLockedProjectRoot(this.processCwd);
|
|
3133
|
-
|
|
3134
|
-
return {
|
|
3135
|
-
selected,
|
|
3136
|
-
diagnostics: boundDiagnostics(this.processCwd, selected, snapshot, rootCandidates)
|
|
3137
|
-
};
|
|
3149
|
+
return bound({ ...cwdProject, bindingSource: "process_cwd" });
|
|
3138
3150
|
} catch {
|
|
3139
3151
|
}
|
|
3140
3152
|
const cwdProblem = inspectDirectory(this.processCwd);
|
|
3141
3153
|
return {
|
|
3142
|
-
diagnostics:
|
|
3154
|
+
diagnostics: diagnose(
|
|
3143
3155
|
cwdProblem.problem ? "invalid_process_cwd" : "process_cwd_is_not_workspace",
|
|
3144
|
-
|
|
3145
|
-
this.processCwd,
|
|
3146
|
-
rootCandidates,
|
|
3147
|
-
"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."
|
|
3156
|
+
"This IDE did not provide MCP Roots, SAKUPA_PROJECT_ROOT is not configured, 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, or the MCP server configuration may set SAKUPA_PROJECT_ROOT to the absolute project path. Never ask the user to run it."
|
|
3148
3157
|
)
|
|
3149
3158
|
};
|
|
3150
3159
|
}
|
|
@@ -3157,11 +3166,7 @@ function fileRootUriToPath(uri, windows = process.platform === "win32") {
|
|
|
3157
3166
|
async function safeRootsSnapshot(provider, timeoutMs = MCP_ROOTS_TIMEOUT_MS, call) {
|
|
3158
3167
|
if (!provider) return { supported: false, roots: [] };
|
|
3159
3168
|
try {
|
|
3160
|
-
return await withOperationTimeout(
|
|
3161
|
-
"MCP Roots request",
|
|
3162
|
-
timeoutMs,
|
|
3163
|
-
() => provider(call)
|
|
3164
|
-
);
|
|
3169
|
+
return await withOperationTimeout("MCP Roots request", timeoutMs, () => provider(call));
|
|
3165
3170
|
} catch (error) {
|
|
3166
3171
|
if (error instanceof McpRootsPending) throw error;
|
|
3167
3172
|
return {
|
|
@@ -3191,6 +3196,31 @@ function inspectRoot(root) {
|
|
|
3191
3196
|
};
|
|
3192
3197
|
}
|
|
3193
3198
|
}
|
|
3199
|
+
function inspectConfiguredRoot(configured) {
|
|
3200
|
+
if (!isAbsolute4(configured)) {
|
|
3201
|
+
return {
|
|
3202
|
+
configured,
|
|
3203
|
+
initialized: false,
|
|
3204
|
+
problem: "the value must be an absolute path"
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
try {
|
|
3208
|
+
const path = canonicalProjectDirectory(configured);
|
|
3209
|
+
const marker = loadProjectMarker(path);
|
|
3210
|
+
return {
|
|
3211
|
+
configured,
|
|
3212
|
+
path,
|
|
3213
|
+
initialized: marker.kind === "ok",
|
|
3214
|
+
...marker.kind === "corrupted" ? { problem: marker.problem } : {}
|
|
3215
|
+
};
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
return {
|
|
3218
|
+
configured,
|
|
3219
|
+
initialized: false,
|
|
3220
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3194
3224
|
function inspectDirectory(path) {
|
|
3195
3225
|
try {
|
|
3196
3226
|
return { path: canonicalProjectDirectory(resolve4(path)) };
|
|
@@ -3198,22 +3228,24 @@ function inspectDirectory(path) {
|
|
|
3198
3228
|
return { problem: error instanceof Error ? error.message : String(error) };
|
|
3199
3229
|
}
|
|
3200
3230
|
}
|
|
3201
|
-
function diagnostic(diagnosisCode, snapshot, processCwd, rootCandidates, guidance) {
|
|
3231
|
+
function diagnostic(diagnosisCode, snapshot, processCwd, rootCandidates, guidance, configured) {
|
|
3202
3232
|
return {
|
|
3203
3233
|
diagnosisCode,
|
|
3204
3234
|
mcpRootsSupported: snapshot.supported,
|
|
3205
3235
|
processCwd,
|
|
3206
3236
|
rootCandidates,
|
|
3237
|
+
...configured ? { configuredProjectRoot: configured } : {},
|
|
3207
3238
|
guidance,
|
|
3208
3239
|
reportRecommended: false
|
|
3209
3240
|
};
|
|
3210
3241
|
}
|
|
3211
|
-
function boundDiagnostics(processCwd, selected, snapshot = { supported: false, roots: [] }, rootCandidates = []) {
|
|
3242
|
+
function boundDiagnostics(processCwd, selected, snapshot = { supported: false, roots: [] }, rootCandidates = [], configured) {
|
|
3212
3243
|
return {
|
|
3213
3244
|
diagnosisCode: "project_bound",
|
|
3214
3245
|
mcpRootsSupported: snapshot.supported,
|
|
3215
3246
|
processCwd,
|
|
3216
3247
|
rootCandidates,
|
|
3248
|
+
...configured ? { configuredProjectRoot: configured } : {},
|
|
3217
3249
|
selectedProjectDir: selected.projectDir,
|
|
3218
3250
|
bindingSource: selected.bindingSource,
|
|
3219
3251
|
guidance: `Sakupa is locked to ${selected.projectDir} from ${selected.bindingSource}.`,
|
|
@@ -3432,7 +3464,12 @@ function resolverFor(ctx) {
|
|
|
3432
3464
|
if (ctx.projectBinding) return ctx.projectBinding;
|
|
3433
3465
|
let resolver = fallbackResolvers.get(ctx);
|
|
3434
3466
|
if (!resolver) {
|
|
3435
|
-
resolver = new ProjectBindingResolver(
|
|
3467
|
+
resolver = new ProjectBindingResolver(
|
|
3468
|
+
ctx.projectDir,
|
|
3469
|
+
ctx.rootsProvider,
|
|
3470
|
+
MCP_ROOTS_TIMEOUT_MS,
|
|
3471
|
+
ctx.configuredProjectRoot
|
|
3472
|
+
);
|
|
3436
3473
|
fallbackResolvers.set(ctx, resolver);
|
|
3437
3474
|
}
|
|
3438
3475
|
return resolver;
|
|
@@ -5698,7 +5735,7 @@ var TOOL_MANUALS = {
|
|
|
5698
5735
|
init: {
|
|
5699
5736
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5700
5737
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5701
|
-
preconditions: "Exactly one usable MCP workspace Root. If
|
|
5738
|
+
preconditions: "Exactly one usable MCP workspace Root, or the SAKUPA_PROJECT_ROOT directory configured for this server when the client provides no Roots. If neither exists, help may authorize the AI to use CLI init.",
|
|
5702
5739
|
parameterNames: [],
|
|
5703
5740
|
parameters: "No parameters and no path argument.",
|
|
5704
5741
|
warnings: [
|
|
@@ -6406,11 +6443,13 @@ language. Keep option IDs, tool names, exact arguments, URLs, field names and co
|
|
|
6406
6443
|
unchanged.
|
|
6407
6444
|
|
|
6408
6445
|
Project directory contract: before the first deploy or a new recovery, CALL the init MCP tool with
|
|
6409
|
-
NO path argument. init uses the IDE's exact MCP Root
|
|
6446
|
+
NO path argument. init uses the IDE's exact MCP Root \u2014 or, when the client provides no Roots, the
|
|
6447
|
+
SAKUPA_PROJECT_ROOT directory configured for this MCP server \u2014 and creates the non-secret
|
|
6410
6448
|
.sakupa/project.json directly there. Do not merely print installation or CLI instructions when the
|
|
6411
|
-
init tool is available. Only after help confirms that the client
|
|
6412
|
-
AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a
|
|
6413
|
-
run it. The CLI also accepts NO path argument. ONE MCP process = ONE
|
|
6449
|
+
init tool is available. Only after help confirms that the client provides neither MCP Roots nor
|
|
6450
|
+
SAKUPA_PROJECT_ROOT may the AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a
|
|
6451
|
+
fallback; never ask the user to run it. The CLI also accepts NO path argument. ONE MCP process = ONE
|
|
6452
|
+
locked project = ONE site.
|
|
6414
6453
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6415
6454
|
and public_recovery portal remain project-independent.
|
|
6416
6455
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6496,7 +6535,13 @@ function createSakupaMcpServer(opts) {
|
|
|
6496
6535
|
apiBaseUrl: opts.apiBaseUrl,
|
|
6497
6536
|
projectDir: processCwd,
|
|
6498
6537
|
rootsProvider,
|
|
6499
|
-
|
|
6538
|
+
...opts.projectRoot !== void 0 ? { configuredProjectRoot: opts.projectRoot } : {},
|
|
6539
|
+
projectBinding: new ProjectBindingResolver(
|
|
6540
|
+
processCwd,
|
|
6541
|
+
rootsProvider,
|
|
6542
|
+
MCP_ROOTS_TIMEOUT_MS,
|
|
6543
|
+
opts.projectRoot
|
|
6544
|
+
)
|
|
6500
6545
|
};
|
|
6501
6546
|
registerTools(server, ctx);
|
|
6502
6547
|
registerBillingTools(server, ctx);
|
|
@@ -6561,7 +6606,7 @@ async function main() {
|
|
|
6561
6606
|
onerror: (error) => console.error("[sakupa-mcp] transport error:", error.message)
|
|
6562
6607
|
});
|
|
6563
6608
|
console.error(
|
|
6564
|
-
`[sakupa-mcp] v${MCP_VERSION} serving stdio (api: ${config.apiBaseUrl}; process cwd fallback: ${process.cwd()}; MCP Roots preferred)`
|
|
6609
|
+
`[sakupa-mcp] v${MCP_VERSION} serving stdio (api: ${config.apiBaseUrl}; configured root: ${config.projectRoot ?? "none"}; process cwd fallback: ${process.cwd()}; MCP Roots preferred)`
|
|
6565
6610
|
);
|
|
6566
6611
|
}
|
|
6567
6612
|
main().catch((err2) => {
|
package/dist/index.js
CHANGED
|
@@ -147,7 +147,7 @@ function isFreeSiteAllowanceNetworkReference(value) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// ../core/dist/domain/version.js
|
|
150
|
-
var SAKUPA_MCP_VERSION = "1.
|
|
150
|
+
var SAKUPA_MCP_VERSION = "1.2.0";
|
|
151
151
|
|
|
152
152
|
// ../core/dist/domain/errors.js
|
|
153
153
|
var HTTP_STATUS = {
|
|
@@ -487,13 +487,15 @@ function previewHostPatternFor(apiBaseUrl) {
|
|
|
487
487
|
function loadMcpRuntimeConfig(env = process.env) {
|
|
488
488
|
const apiBaseUrl = (env["SAKUPA_API_URL"] ?? env["SAKUPA_API_BASE_URL"] ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
489
489
|
const testAccessToken = env["SAKUPA_TEST_ACCESS_TOKEN"]?.trim() ?? "";
|
|
490
|
+
const projectRoot = env["SAKUPA_PROJECT_ROOT"]?.trim() ?? "";
|
|
491
|
+
const projectRootConfig = projectRoot.length > 0 ? { projectRoot } : {};
|
|
490
492
|
if (apiBaseUrl === TEST_API_BASE_URL) {
|
|
491
493
|
if (testAccessToken.length === 0) {
|
|
492
494
|
throw new Error(
|
|
493
495
|
"The Sakupa Test API requires SAKUPA_TEST_ACCESS_TOKEN. Anonymous Test access is disabled."
|
|
494
496
|
);
|
|
495
497
|
}
|
|
496
|
-
return { apiBaseUrl, testAccessToken };
|
|
498
|
+
return { apiBaseUrl, testAccessToken, ...projectRootConfig };
|
|
497
499
|
}
|
|
498
500
|
if (apiBaseUrl !== PRODUCTION_API_BASE_URL) {
|
|
499
501
|
throw new Error(
|
|
@@ -505,7 +507,7 @@ function loadMcpRuntimeConfig(env = process.env) {
|
|
|
505
507
|
`SAKUPA_TEST_ACCESS_TOKEN may only be used with ${TEST_API_BASE_URL}. Remove it before connecting to any other API.`
|
|
506
508
|
);
|
|
507
509
|
}
|
|
508
|
-
return { apiBaseUrl };
|
|
510
|
+
return { apiBaseUrl, ...projectRootConfig };
|
|
509
511
|
}
|
|
510
512
|
function environmentFor(apiBaseUrl) {
|
|
511
513
|
if (apiBaseUrl === TEST_API_BASE_URL) return "test";
|
|
@@ -1543,7 +1545,7 @@ import {
|
|
|
1543
1545
|
|
|
1544
1546
|
// src/project-binding.ts
|
|
1545
1547
|
import { fileURLToPath } from "node:url";
|
|
1546
|
-
import { resolve as resolve3 } from "node:path";
|
|
1548
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
1547
1549
|
|
|
1548
1550
|
// src/project-root.ts
|
|
1549
1551
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -1783,10 +1785,11 @@ var ProjectBindingError = class extends Error {
|
|
|
1783
1785
|
}
|
|
1784
1786
|
};
|
|
1785
1787
|
var ProjectBindingResolver = class {
|
|
1786
|
-
constructor(processCwd, rootsProvider, rootsTimeoutMs = MCP_ROOTS_TIMEOUT_MS) {
|
|
1788
|
+
constructor(processCwd, rootsProvider, rootsTimeoutMs = MCP_ROOTS_TIMEOUT_MS, configuredRoot) {
|
|
1787
1789
|
this.processCwd = processCwd;
|
|
1788
1790
|
this.rootsProvider = rootsProvider;
|
|
1789
1791
|
this.rootsTimeoutMs = rootsTimeoutMs;
|
|
1792
|
+
this.configuredRoot = configuredRoot;
|
|
1790
1793
|
}
|
|
1791
1794
|
bound;
|
|
1792
1795
|
boundState;
|
|
@@ -1825,28 +1828,41 @@ var ProjectBindingResolver = class {
|
|
|
1825
1828
|
throw new ProjectBindingError(inspection.diagnostics);
|
|
1826
1829
|
}
|
|
1827
1830
|
const initialized = initializeProject(inspection.initializableRoot);
|
|
1828
|
-
this.bound = {
|
|
1831
|
+
this.bound = {
|
|
1832
|
+
...initialized,
|
|
1833
|
+
bindingSource: inspection.initializableSource ?? "mcp_root"
|
|
1834
|
+
};
|
|
1829
1835
|
this.boundState = boundDiagnostics(
|
|
1830
1836
|
this.processCwd,
|
|
1831
1837
|
this.bound,
|
|
1832
|
-
{ supported:
|
|
1833
|
-
inspection.diagnostics.rootCandidates
|
|
1838
|
+
{ supported: inspection.diagnostics.mcpRootsSupported, roots: [] },
|
|
1839
|
+
inspection.diagnostics.rootCandidates,
|
|
1840
|
+
inspection.diagnostics.configuredProjectRoot
|
|
1834
1841
|
);
|
|
1835
1842
|
return this.bound;
|
|
1836
1843
|
}
|
|
1837
1844
|
async inspect(forInitialization = false, call) {
|
|
1838
1845
|
const snapshot = await safeRootsSnapshot(this.rootsProvider, this.rootsTimeoutMs, call);
|
|
1839
1846
|
const rootCandidates = snapshot.roots.map(inspectRoot);
|
|
1847
|
+
const configured = this.configuredRoot === void 0 ? void 0 : inspectConfiguredRoot(this.configuredRoot);
|
|
1848
|
+
const diagnose = (code, guidance) => diagnostic(code, snapshot, this.processCwd, rootCandidates, guidance, configured);
|
|
1849
|
+
const bound = (selected) => ({
|
|
1850
|
+
selected,
|
|
1851
|
+
diagnostics: boundDiagnostics(
|
|
1852
|
+
this.processCwd,
|
|
1853
|
+
selected,
|
|
1854
|
+
snapshot,
|
|
1855
|
+
rootCandidates,
|
|
1856
|
+
configured
|
|
1857
|
+
)
|
|
1858
|
+
});
|
|
1840
1859
|
const initializedRoots = rootCandidates.filter(
|
|
1841
1860
|
(candidate) => candidate.initialized && candidate.path !== void 0
|
|
1842
1861
|
);
|
|
1843
1862
|
if (snapshot.supported && snapshot.error) {
|
|
1844
1863
|
return {
|
|
1845
|
-
diagnostics:
|
|
1864
|
+
diagnostics: diagnose(
|
|
1846
1865
|
"roots_request_failed",
|
|
1847
|
-
snapshot,
|
|
1848
|
-
this.processCwd,
|
|
1849
|
-
rootCandidates,
|
|
1850
1866
|
"The IDE advertised MCP Roots, but the Roots request failed. Retry help after the IDE finishes loading the workspace. If it persists, restart the MCP connection; do not initialize or deploy from the IDE installation directory."
|
|
1851
1867
|
)
|
|
1852
1868
|
};
|
|
@@ -1855,19 +1871,12 @@ var ProjectBindingResolver = class {
|
|
|
1855
1871
|
const initializedRoot = initializedRoots[0];
|
|
1856
1872
|
if (!initializedRoot) throw new Error("initialized Root disappeared during resolution");
|
|
1857
1873
|
const project = resolveLockedProjectRoot(initializedRoot.path);
|
|
1858
|
-
|
|
1859
|
-
return {
|
|
1860
|
-
selected,
|
|
1861
|
-
diagnostics: boundDiagnostics(this.processCwd, selected, snapshot, rootCandidates)
|
|
1862
|
-
};
|
|
1874
|
+
return bound({ ...project, bindingSource: "mcp_root" });
|
|
1863
1875
|
}
|
|
1864
1876
|
if (initializedRoots.length > 1) {
|
|
1865
1877
|
return {
|
|
1866
|
-
diagnostics:
|
|
1878
|
+
diagnostics: diagnose(
|
|
1867
1879
|
"multiple_initialized_roots",
|
|
1868
|
-
snapshot,
|
|
1869
|
-
this.processCwd,
|
|
1870
|
-
rootCandidates,
|
|
1871
1880
|
"More than one IDE workspace Root is already initialized for Sakupa. Close the unrelated workspaces and retry help; Sakupa will not guess which site to manage."
|
|
1872
1881
|
)
|
|
1873
1882
|
};
|
|
@@ -1881,22 +1890,17 @@ var ProjectBindingResolver = class {
|
|
|
1881
1890
|
if (!validRoot) throw new Error("workspace Root disappeared during initialization");
|
|
1882
1891
|
return {
|
|
1883
1892
|
initializableRoot: validRoot.path,
|
|
1884
|
-
|
|
1893
|
+
initializableSource: "mcp_root",
|
|
1894
|
+
diagnostics: diagnose(
|
|
1885
1895
|
"workspace_not_initialized",
|
|
1886
|
-
snapshot,
|
|
1887
|
-
this.processCwd,
|
|
1888
|
-
rootCandidates,
|
|
1889
1896
|
`The active MCP workspace ${validRoot.path} is ready to initialize.`
|
|
1890
1897
|
)
|
|
1891
1898
|
};
|
|
1892
1899
|
}
|
|
1893
1900
|
if (validRoots.length > 1) {
|
|
1894
1901
|
return {
|
|
1895
|
-
diagnostics:
|
|
1902
|
+
diagnostics: diagnose(
|
|
1896
1903
|
"multiple_uninitialized_roots",
|
|
1897
|
-
snapshot,
|
|
1898
|
-
this.processCwd,
|
|
1899
|
-
rootCandidates,
|
|
1900
1904
|
"The IDE exposes multiple uninitialized workspace Roots. Open only the intended project before calling init; Sakupa will not choose a directory for the user."
|
|
1901
1905
|
)
|
|
1902
1906
|
};
|
|
@@ -1906,54 +1910,59 @@ var ProjectBindingResolver = class {
|
|
|
1906
1910
|
const validRoot = validRoots[0];
|
|
1907
1911
|
if (!validRoot) throw new Error("workspace Root disappeared during diagnosis");
|
|
1908
1912
|
return {
|
|
1909
|
-
diagnostics:
|
|
1913
|
+
diagnostics: diagnose(
|
|
1910
1914
|
"workspace_not_initialized",
|
|
1911
|
-
snapshot,
|
|
1912
|
-
this.processCwd,
|
|
1913
|
-
rootCandidates,
|
|
1914
1915
|
`The IDE workspace ${validRoot.path} is not initialized. Call init with no path arguments; it will create .sakupa directly in that workspace Root.`
|
|
1915
1916
|
)
|
|
1916
1917
|
};
|
|
1917
1918
|
}
|
|
1918
1919
|
if (snapshot.supported && validRoots.length > 1) {
|
|
1919
1920
|
return {
|
|
1920
|
-
diagnostics:
|
|
1921
|
+
diagnostics: diagnose(
|
|
1921
1922
|
"multiple_uninitialized_roots",
|
|
1922
|
-
snapshot,
|
|
1923
|
-
this.processCwd,
|
|
1924
|
-
rootCandidates,
|
|
1925
1923
|
"The IDE exposes multiple uninitialized workspace Roots. Open only the intended project, then call init. Sakupa will not guess a project directory."
|
|
1926
1924
|
)
|
|
1927
1925
|
};
|
|
1928
1926
|
}
|
|
1927
|
+
if (configured) {
|
|
1928
|
+
if (configured.problem !== void 0 || configured.path === void 0) {
|
|
1929
|
+
return {
|
|
1930
|
+
diagnostics: diagnose(
|
|
1931
|
+
"invalid_configured_root",
|
|
1932
|
+
`SAKUPA_PROJECT_ROOT is set to ${configured.configured} but it is not usable: ${configured.problem ?? "unknown problem"}. Fix the MCP server configuration (an absolute path to an existing project directory) and retry help; Sakupa will not fall back to another directory.`
|
|
1933
|
+
)
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
if (configured.initialized) {
|
|
1937
|
+
const project = resolveLockedProjectRoot(configured.path);
|
|
1938
|
+
return bound({ ...project, bindingSource: "configured_root" });
|
|
1939
|
+
}
|
|
1940
|
+
return {
|
|
1941
|
+
...forInitialization ? { initializableRoot: configured.path, initializableSource: "configured_root" } : {},
|
|
1942
|
+
diagnostics: diagnose(
|
|
1943
|
+
"workspace_not_initialized",
|
|
1944
|
+
`The configured project root ${configured.path} (SAKUPA_PROJECT_ROOT) is not initialized. Call init with no path arguments; it will create .sakupa directly there.`
|
|
1945
|
+
)
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1929
1948
|
if (snapshot.supported) {
|
|
1930
1949
|
return {
|
|
1931
|
-
diagnostics:
|
|
1950
|
+
diagnostics: diagnose(
|
|
1932
1951
|
"workspace_not_initialized",
|
|
1933
|
-
snapshot,
|
|
1934
|
-
this.processCwd,
|
|
1935
|
-
rootCandidates,
|
|
1936
1952
|
"The IDE did not expose one usable file workspace Root. Open exactly one local project workspace, then retry help before calling init or deploy."
|
|
1937
1953
|
)
|
|
1938
1954
|
};
|
|
1939
1955
|
}
|
|
1940
1956
|
try {
|
|
1941
1957
|
const cwdProject = resolveLockedProjectRoot(this.processCwd);
|
|
1942
|
-
|
|
1943
|
-
return {
|
|
1944
|
-
selected,
|
|
1945
|
-
diagnostics: boundDiagnostics(this.processCwd, selected, snapshot, rootCandidates)
|
|
1946
|
-
};
|
|
1958
|
+
return bound({ ...cwdProject, bindingSource: "process_cwd" });
|
|
1947
1959
|
} catch {
|
|
1948
1960
|
}
|
|
1949
1961
|
const cwdProblem = inspectDirectory(this.processCwd);
|
|
1950
1962
|
return {
|
|
1951
|
-
diagnostics:
|
|
1963
|
+
diagnostics: diagnose(
|
|
1952
1964
|
cwdProblem.problem ? "invalid_process_cwd" : "process_cwd_is_not_workspace",
|
|
1953
|
-
|
|
1954
|
-
this.processCwd,
|
|
1955
|
-
rootCandidates,
|
|
1956
|
-
"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."
|
|
1965
|
+
"This IDE did not provide MCP Roots, SAKUPA_PROJECT_ROOT is not configured, 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, or the MCP server configuration may set SAKUPA_PROJECT_ROOT to the absolute project path. Never ask the user to run it."
|
|
1957
1966
|
)
|
|
1958
1967
|
};
|
|
1959
1968
|
}
|
|
@@ -1966,11 +1975,7 @@ function fileRootUriToPath(uri, windows = process.platform === "win32") {
|
|
|
1966
1975
|
async function safeRootsSnapshot(provider, timeoutMs = MCP_ROOTS_TIMEOUT_MS, call) {
|
|
1967
1976
|
if (!provider) return { supported: false, roots: [] };
|
|
1968
1977
|
try {
|
|
1969
|
-
return await withOperationTimeout(
|
|
1970
|
-
"MCP Roots request",
|
|
1971
|
-
timeoutMs,
|
|
1972
|
-
() => provider(call)
|
|
1973
|
-
);
|
|
1978
|
+
return await withOperationTimeout("MCP Roots request", timeoutMs, () => provider(call));
|
|
1974
1979
|
} catch (error) {
|
|
1975
1980
|
if (error instanceof McpRootsPending) throw error;
|
|
1976
1981
|
return {
|
|
@@ -2000,6 +2005,31 @@ function inspectRoot(root) {
|
|
|
2000
2005
|
};
|
|
2001
2006
|
}
|
|
2002
2007
|
}
|
|
2008
|
+
function inspectConfiguredRoot(configured) {
|
|
2009
|
+
if (!isAbsolute2(configured)) {
|
|
2010
|
+
return {
|
|
2011
|
+
configured,
|
|
2012
|
+
initialized: false,
|
|
2013
|
+
problem: "the value must be an absolute path"
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
try {
|
|
2017
|
+
const path = canonicalProjectDirectory(configured);
|
|
2018
|
+
const marker = loadProjectMarker(path);
|
|
2019
|
+
return {
|
|
2020
|
+
configured,
|
|
2021
|
+
path,
|
|
2022
|
+
initialized: marker.kind === "ok",
|
|
2023
|
+
...marker.kind === "corrupted" ? { problem: marker.problem } : {}
|
|
2024
|
+
};
|
|
2025
|
+
} catch (error) {
|
|
2026
|
+
return {
|
|
2027
|
+
configured,
|
|
2028
|
+
initialized: false,
|
|
2029
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2003
2033
|
function inspectDirectory(path) {
|
|
2004
2034
|
try {
|
|
2005
2035
|
return { path: canonicalProjectDirectory(resolve3(path)) };
|
|
@@ -2007,22 +2037,24 @@ function inspectDirectory(path) {
|
|
|
2007
2037
|
return { problem: error instanceof Error ? error.message : String(error) };
|
|
2008
2038
|
}
|
|
2009
2039
|
}
|
|
2010
|
-
function diagnostic(diagnosisCode, snapshot, processCwd, rootCandidates, guidance) {
|
|
2040
|
+
function diagnostic(diagnosisCode, snapshot, processCwd, rootCandidates, guidance, configured) {
|
|
2011
2041
|
return {
|
|
2012
2042
|
diagnosisCode,
|
|
2013
2043
|
mcpRootsSupported: snapshot.supported,
|
|
2014
2044
|
processCwd,
|
|
2015
2045
|
rootCandidates,
|
|
2046
|
+
...configured ? { configuredProjectRoot: configured } : {},
|
|
2016
2047
|
guidance,
|
|
2017
2048
|
reportRecommended: false
|
|
2018
2049
|
};
|
|
2019
2050
|
}
|
|
2020
|
-
function boundDiagnostics(processCwd, selected, snapshot = { supported: false, roots: [] }, rootCandidates = []) {
|
|
2051
|
+
function boundDiagnostics(processCwd, selected, snapshot = { supported: false, roots: [] }, rootCandidates = [], configured) {
|
|
2021
2052
|
return {
|
|
2022
2053
|
diagnosisCode: "project_bound",
|
|
2023
2054
|
mcpRootsSupported: snapshot.supported,
|
|
2024
2055
|
processCwd,
|
|
2025
2056
|
rootCandidates,
|
|
2057
|
+
...configured ? { configuredProjectRoot: configured } : {},
|
|
2026
2058
|
selectedProjectDir: selected.projectDir,
|
|
2027
2059
|
bindingSource: selected.bindingSource,
|
|
2028
2060
|
guidance: `Sakupa is locked to ${selected.projectDir} from ${selected.bindingSource}.`,
|
|
@@ -2241,7 +2273,12 @@ function resolverFor(ctx) {
|
|
|
2241
2273
|
if (ctx.projectBinding) return ctx.projectBinding;
|
|
2242
2274
|
let resolver = fallbackResolvers.get(ctx);
|
|
2243
2275
|
if (!resolver) {
|
|
2244
|
-
resolver = new ProjectBindingResolver(
|
|
2276
|
+
resolver = new ProjectBindingResolver(
|
|
2277
|
+
ctx.projectDir,
|
|
2278
|
+
ctx.rootsProvider,
|
|
2279
|
+
MCP_ROOTS_TIMEOUT_MS,
|
|
2280
|
+
ctx.configuredProjectRoot
|
|
2281
|
+
);
|
|
2245
2282
|
fallbackResolvers.set(ctx, resolver);
|
|
2246
2283
|
}
|
|
2247
2284
|
return resolver;
|
|
@@ -2418,7 +2455,7 @@ import { z as z2 } from "zod";
|
|
|
2418
2455
|
// src/recovery-archive.ts
|
|
2419
2456
|
import { existsSync as existsSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
2420
2457
|
import { mkdtemp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2421
|
-
import { dirname as dirname2, isAbsolute as
|
|
2458
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve4, sep as sep3 } from "node:path";
|
|
2422
2459
|
|
|
2423
2460
|
// ../../node_modules/fflate/esm/index.mjs
|
|
2424
2461
|
import { createRequire } from "module";
|
|
@@ -2905,13 +2942,13 @@ function unzipSync(data, opts) {
|
|
|
2905
2942
|
|
|
2906
2943
|
// src/recovery-archive.ts
|
|
2907
2944
|
function safeOutputPath(projectDir, outputDir) {
|
|
2908
|
-
if (outputDir.length === 0 ||
|
|
2945
|
+
if (outputDir.length === 0 || isAbsolute3(outputDir)) {
|
|
2909
2946
|
throw new SakupaError("invalid_request", "Recovery outputDir must be a relative directory");
|
|
2910
2947
|
}
|
|
2911
2948
|
const root = realpathSync2(resolve4(projectDir));
|
|
2912
2949
|
const target = resolve4(root, outputDir);
|
|
2913
2950
|
const rel = relative2(root, target);
|
|
2914
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${sep3}`) ||
|
|
2951
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute3(rel)) {
|
|
2915
2952
|
throw new SakupaError("invalid_request", "Recovery outputDir must stay inside projectDir");
|
|
2916
2953
|
}
|
|
2917
2954
|
if (rel === ".sakupa" || rel.startsWith(`.sakupa${sep3}`)) {
|
|
@@ -2926,7 +2963,7 @@ function safeOutputPath(projectDir, outputDir) {
|
|
|
2926
2963
|
const physicalAncestor = realpathSync2(existingAncestor);
|
|
2927
2964
|
const physicalTarget = resolve4(physicalAncestor, relative2(existingAncestor, target));
|
|
2928
2965
|
const physicalRel = relative2(root, physicalTarget);
|
|
2929
|
-
if (physicalRel === ".." || physicalRel.startsWith(`..${sep3}`) ||
|
|
2966
|
+
if (physicalRel === ".." || physicalRel.startsWith(`..${sep3}`) || isAbsolute3(physicalRel)) {
|
|
2930
2967
|
throw new SakupaError(
|
|
2931
2968
|
"invalid_request",
|
|
2932
2969
|
"Recovery outputDir resolves through a symlink outside projectDir"
|
|
@@ -3292,7 +3329,7 @@ import {
|
|
|
3292
3329
|
writeFileSync as writeFileSync5
|
|
3293
3330
|
} from "node:fs";
|
|
3294
3331
|
import { createHash } from "node:crypto";
|
|
3295
|
-
import { dirname as dirname5, isAbsolute as
|
|
3332
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join7 } from "node:path";
|
|
3296
3333
|
var HANDOFF_LOCK_TTL_MS = 15 * 60 * 1e3;
|
|
3297
3334
|
function normalizeSiteUrl(raw) {
|
|
3298
3335
|
const url = new URL(raw);
|
|
@@ -3319,7 +3356,7 @@ function resolveReusableSite(rawUrl, currentProjectDir, nowMs, apiBaseUrl) {
|
|
|
3319
3356
|
throw new Error(`More than one local free-site record matches ${siteUrl}.`);
|
|
3320
3357
|
const record = matches2[0];
|
|
3321
3358
|
if (!record) throw new Error("The selected existing free site disappeared during resolution.");
|
|
3322
|
-
if (!
|
|
3359
|
+
if (!isAbsolute4(record.projectDir)) {
|
|
3323
3360
|
throw new Error("The selected free-site project path is not absolute; refusing cwd lookup.");
|
|
3324
3361
|
}
|
|
3325
3362
|
const sourceProjectDir = canonicalProjectDirectory(record.projectDir);
|
|
@@ -5817,7 +5854,7 @@ var TOOL_MANUALS = {
|
|
|
5817
5854
|
init: {
|
|
5818
5855
|
purpose: "Initialize the active IDE workspace as one Sakupa project.",
|
|
5819
5856
|
sideEffects: "Creates only .sakupa/project.json locally; no API call, site or charge.",
|
|
5820
|
-
preconditions: "Exactly one usable MCP workspace Root. If
|
|
5857
|
+
preconditions: "Exactly one usable MCP workspace Root, or the SAKUPA_PROJECT_ROOT directory configured for this server when the client provides no Roots. If neither exists, help may authorize the AI to use CLI init.",
|
|
5821
5858
|
parameterNames: [],
|
|
5822
5859
|
parameters: "No parameters and no path argument.",
|
|
5823
5860
|
warnings: [
|
|
@@ -6374,11 +6411,13 @@ language. Keep option IDs, tool names, exact arguments, URLs, field names and co
|
|
|
6374
6411
|
unchanged.
|
|
6375
6412
|
|
|
6376
6413
|
Project directory contract: before the first deploy or a new recovery, CALL the init MCP tool with
|
|
6377
|
-
NO path argument. init uses the IDE's exact MCP Root
|
|
6414
|
+
NO path argument. init uses the IDE's exact MCP Root \u2014 or, when the client provides no Roots, the
|
|
6415
|
+
SAKUPA_PROJECT_ROOT directory configured for this MCP server \u2014 and creates the non-secret
|
|
6378
6416
|
.sakupa/project.json directly there. Do not merely print installation or CLI instructions when the
|
|
6379
|
-
init tool is available. Only after help confirms that the client
|
|
6380
|
-
AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a
|
|
6381
|
-
run it. The CLI also accepts NO path argument. ONE MCP process = ONE
|
|
6417
|
+
init tool is available. Only after help confirms that the client provides neither MCP Roots nor
|
|
6418
|
+
SAKUPA_PROJECT_ROOT may the AI itself use the CLI command "npx -y @sakupa/mcp@latest init" as a
|
|
6419
|
+
fallback; never ask the user to run it. The CLI also accepts NO path argument. ONE MCP process = ONE
|
|
6420
|
+
locked project = ONE site.
|
|
6382
6421
|
Site tools do not accept projectDir and cannot select another root; help, plans and report preview
|
|
6383
6422
|
and public_recovery portal remain project-independent.
|
|
6384
6423
|
Sakupa stores .sakupa/site.json and recovery state only in the locked directory; it never uses
|
|
@@ -6464,7 +6503,13 @@ function createSakupaMcpServer(opts) {
|
|
|
6464
6503
|
apiBaseUrl: opts.apiBaseUrl,
|
|
6465
6504
|
projectDir: processCwd,
|
|
6466
6505
|
rootsProvider,
|
|
6467
|
-
|
|
6506
|
+
...opts.projectRoot !== void 0 ? { configuredProjectRoot: opts.projectRoot } : {},
|
|
6507
|
+
projectBinding: new ProjectBindingResolver(
|
|
6508
|
+
processCwd,
|
|
6509
|
+
rootsProvider,
|
|
6510
|
+
MCP_ROOTS_TIMEOUT_MS,
|
|
6511
|
+
opts.projectRoot
|
|
6512
|
+
)
|
|
6468
6513
|
};
|
|
6469
6514
|
registerTools(server, ctx);
|
|
6470
6515
|
registerBillingTools(server, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sakupa/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.myerwang/sakupa",
|
|
5
5
|
"description": "Sakupa MCP server: publish AI-made static sites from your AI tool. AI-made pages, live in seconds.",
|
|
6
6
|
"homepage": "https://sakupa.com/manual/",
|