mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 21:15:18 +09:00
## Summary This combines the three reported MCP improvements into one release-ready change: - Safer mutation planning and confirmation for destructive operations. - Much smaller MCP handshake and default discovery responses. - Better agent workflows for checkpoints, multi-project work, and page duplication. ## Safer destructive operations - Exposes `dryRun` in MCP schemas for every local-capable mutation. - Returns planned transactions through `meta.session.transaction`. - Requires a two-step confirmation flow for destructive semantic operations. - Returns a short-lived token bound to the project, build version, input, and exact generated patch plan. - Rejects expired tokens, altered inputs, changed versions, and changed plans. - Sets `destructiveHint: true` only for operations that actually require confirmation. - Explicitly protects destructive operations whose names do not begin with delete or replace, including replacing all redirects and clearing all styles from a style source. ## Smaller, focused MCP context - Defers oversized tool input schemas from the initial MCP handshake. - Keeps complete focused schemas available through `meta.get_more_tools` and CLI schema inspection. - Makes template registry lists compact while preserving complete payloads through focused get calls. - Makes component summaries compact by default and supports paginated component details. - Replaces placeholder operation descriptions with useful command descriptions. - Constrains previously untyped JSON schema fields so clients can validate inputs earlier. - Keeps runtime output schemas compatible with strict MCP clients. ## Better agent workflows - Allows read-only discovery and inspection while a workflow checkpoint is awaiting acknowledgement. - Continues blocking mutations until the checkpoint is acknowledged. - Checks persisted checkpoints before every workflow call rather than only the first call. - Adds `--project` support so saved projects can be selected without relinking the current directory. - Isolates project sessions and checkpoints by selected project. - Adds atomic `duplicate-page` substitutions for copied fixed text and copied variable values. - Prevents substitutions from modifying source content or global root state. - Rejects ambiguous or missing copied variables without leaving a partial duplicate. ## Realistic agent verification Minimal-context tests used a weak agent against the local packaged CLI and stdio MCP: - Planned a page deletion, stopped for explicit user confirmation, committed the unchanged plan, and verified the page was absent. - Tried to reuse a confirmation token with altered input; Webstudio returned `DESTRUCTIVE_CONFIRMATION_INVALID` and preserved both pages. - Verified compact handshake schemas and focused retrieval of the complete oversized schema. - Verified compact template and component discovery responses. - Selected a saved project explicitly without relying on the directory link. - Verified checkpointed workflows permit reads but block mutations. - Duplicated a London page into a Paris page with atomic text and variable substitutions. - Verified source content remained London, copied content became Paris, and temporary pages were removed. ## Validation - Project-build focused suites: 188 tests passed. - CLI focused suites: 183 tests passed. - Protocol focused suites: 21 tests passed. - CLI, project-build, and protocol typechecks passed. - Repository lint passed with zero warnings. - Package-boundary validation passed. - Generated runtime operation contracts were regenerated from source. - Real local CLI/MCP tests passed against a running Builder server. Closes #5887 Closes #5888 Closes #5889
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import { cwd } from "node:process";
|
|
import { join } from "node:path";
|
|
import {
|
|
GLOBAL_CONFIG_FILE,
|
|
LOCAL_CONFIG_FILE,
|
|
jsonToGlobalConfig,
|
|
jsonToLocalConfig,
|
|
} from "./config";
|
|
import { isFileExists } from "./fs-utils";
|
|
|
|
export type ApiConnection = {
|
|
origin: string;
|
|
authToken: string;
|
|
projectId: string;
|
|
headers?: Record<string, string | undefined>;
|
|
};
|
|
|
|
export type ApiConnectionDependencies = {
|
|
isFileExists: typeof isFileExists;
|
|
readFile: (path: string, encoding: "utf-8") => Promise<string>;
|
|
};
|
|
|
|
export const defaultApiConnectionDependencies: ApiConnectionDependencies = {
|
|
isFileExists,
|
|
readFile,
|
|
};
|
|
|
|
export const resolveApiConnection = async (
|
|
dependencies = defaultApiConnectionDependencies,
|
|
projectRoot = cwd(),
|
|
projectId?: string
|
|
): Promise<ApiConnection> => {
|
|
const isExplicitProject = projectId !== undefined;
|
|
if (projectId === undefined) {
|
|
const localConfigFile = join(projectRoot, LOCAL_CONFIG_FILE);
|
|
if ((await dependencies.isFileExists(localConfigFile)) === false) {
|
|
throw new Error(
|
|
"Local config file is not found. Run webstudio init --link <api-share-link> from a Webstudio project."
|
|
);
|
|
}
|
|
projectId = jsonToLocalConfig(
|
|
JSON.parse(await dependencies.readFile(localConfigFile, "utf-8"))
|
|
).projectId;
|
|
}
|
|
|
|
if (projectId.trim() === "") {
|
|
throw new Error("Project id must not be empty.");
|
|
}
|
|
const globalConfig = jsonToGlobalConfig(
|
|
JSON.parse(await dependencies.readFile(GLOBAL_CONFIG_FILE, "utf-8"))
|
|
);
|
|
const projectConfig = globalConfig[projectId];
|
|
if (projectConfig === undefined) {
|
|
throw new Error(
|
|
isExplicitProject
|
|
? `Project config for "${projectId}" is not found. Run webstudio init --link <api-share-link> once to save its credentials.`
|
|
: "Project config is not found. Run webstudio init --link <api-share-link>."
|
|
);
|
|
}
|
|
|
|
return {
|
|
origin: projectConfig.origin,
|
|
authToken: projectConfig.token,
|
|
projectId,
|
|
};
|
|
};
|