Files
webstudio/apps/builder/app/shared/builder-data.ts
Oleg IsonenandClaude Fable 5 071a4d9a85 feat: MCP update 3 (#5864)
## Summary

This update adds new MCP and CLI capabilities for project auditing,
structured data, project-specific agent instructions, focused project
search, safer bulk editing, image descriptions, font discovery, visual
verification, and external registry inspection.

## New Capabilities

### Audit projects

- Run a single `audit` command across accessibility, SEO, security,
assets, styles, and performance.
- Limit an audit to selected scopes, pages, or severity levels.
- Receive stable findings with locations and suggested fixes.
- Run rendered audits across desktop, mobile, and Builder breakpoint
widths.
- Detect horizontal overflow, broken images, oversized images, eager
below-fold images, render-blocking resources, and legacy font formats.
- Identify unused assets, tokens, CSS variables, local styles, and
breakpoints.
- Identify duplicate token declarations.
- Detect accessibility issues involving names, labels, headings,
landmarks, IDs, keyboard focus, ARIA, iframes, and autoplaying media.
- Detect SEO issues involving titles, descriptions, language, social
images, and structured data.
- Detect unsafe new-tab links and write resources exposed during page
rendering.

### Search an entire project

- Search project text, labels, props, resources, assets, and styles
through `search-project`.
- Find relevant records without downloading and reparsing the full
project snapshot.
- Scope searches to the relevant project-data areas.

### Add validated JSON-LD

- Add structured data with a dedicated JSON-LD component.
- Place JSON-LD inside the page Head Slot.
- Edit JSON in a purpose-built code editor.
- Prevent structurally invalid JSON-LD from being saved.
- Validate Schema.org types, properties, and values.
- Find invalid, missing, unknown, deprecated, or incompatible structured
data through the SEO audit.
- Let agents discover the correct JSON-LD workflow without using page
custom metadata or a generic HTML embed.

### Configure project-specific agent instructions

- Add AI-agent instructions from the new **Agents** section in Project
Settings.
- Synchronize those instructions into a managed root `AGENTS.md`.
- Update or remove the managed file automatically during project sync.
- Protect existing user-owned `AGENTS.md` files from being overwritten.
- Share the instructions with authenticated CLI and MCP agents.
- Keep agent instructions out of publicly readable production bundles.

### Generate and manage image descriptions

- Audit image assets for missing descriptions.
- Let a vision-capable agent inspect each image in its rendered context.
- Save generated descriptions to multiple image assets in one operation.
- Mark images as intentionally decorative.
- Reuse the saved asset description wherever the image is used.
- Re-run the audit to verify that missing-description findings are
resolved.

### Perform bounded bulk replacements

- Replace matching literal text across selected pages with
`replace-text`.
- Replace matching fixed prop text with `replace-prop-text`.
- Replace matching resource names or URLs with `replace-resource-text`.
- Restrict replacements by page, instance, prop, resource, field, match
mode, and maximum change count.
- Leave dynamic expressions unchanged.

### Inspect element ancestry

- Return an element’s direct parent in instance listings.
- Request the complete ancestor chain when inspecting an element.
- Understand tree placement without rebuilding the instance hierarchy
from raw project data.

### Discover available fonts

- List uploaded font families through `list-fonts`.
- Discover available system font stacks.
- Inspect font family, style, weight, format, and associated asset
information before applying typography.

### Inspect external component registries

- Inspect a local or remote shadcn-style registry item.
- Read its description, files, dependencies, targets, and documentation.
- Receive an explicit Webstudio compatibility report.
- Inspect registry metadata without installing or executing external
code.

### Capture generated project routes directly

- Run `webstudio screenshot --path /route` against the configured
project.
- Generate a temporary production-style preview automatically.
- Capture the requested route.
- Stop the temporary preview after capture.
- Avoid manually building and starting a preview for a single
screenshot.

### Assert visual changes

- Add expected text to screenshot comparisons.
- Set maximum pixel mismatch limits.
- Set minimum or maximum changed-region counts.
- Assert whether overall brightness or a color channel moved in the
expected direction.
- Use these assertions as pass/fail evidence for visual changes.

### Require compatible CLI versions

- Send the project-bundle contract version with CLI synchronization
requests.
- Stop incompatible CLI versions before they receive incomplete project
data.
- Tell users exactly how to update the CLI or run the latest version
once.
- Prevent older clients from silently dropping project settings.

## Upgrade Note

This branch introduces a new synchronized project-bundle contract. Older
CLI versions must update before synchronizing against the new API.

```sh
npm install -g webstudio@latest
```

Or run the latest version once:

```sh
npx webstudio@latest <command>
```

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:27:58 +02:00

126 lines
3.7 KiB
TypeScript

import { getStyleDeclKey, type WebstudioData } from "@webstudio-is/sdk";
import { migratePages } from "@webstudio-is/project-migrations/pages";
import {
type MarketplaceProduct,
type ProjectSettings,
} from "@webstudio-is/project-build";
import type { Project } from "@webstudio-is/project";
import type { inferRouterOutputs } from "@trpc/server";
import type { AppRouter } from "~/services/trcp-router.server";
import { $project } from "~/shared/sync/data-stores";
import {
$assets,
$breakpoints,
$dataSources,
$instances,
$marketplaceProduct,
$projectSettings,
$pages,
$props,
$resources,
$styleSourceSelections,
$styleSources,
$styles,
} from "~/shared/sync/data-stores";
import { nativeClient } from "~/shared/trpc/trpc-client";
export type BuilderData = WebstudioData & {
marketplaceProduct: undefined | MarketplaceProduct;
projectSettings: ProjectSettings;
project: Project;
};
type LoadDataOutput = inferRouterOutputs<AppRouter>["build"]["loadData"];
export type LoadedBuilderData = BuilderData &
Pick<LoadDataOutput, "id" | "version" | "publisherHost" | "projectId">;
export const getBuilderData = (): BuilderData => {
const pages = $pages.get();
if (pages === undefined) {
throw Error(`Cannot get webstudio data with empty pages`);
}
const project = $project.get();
if (project === undefined) {
throw Error(`Cannot get webstudio data with empty project`);
}
const projectSettings = $projectSettings.get();
if (projectSettings === undefined) {
throw Error(`Cannot get webstudio data with empty project settings`);
}
return {
pages,
project,
instances: $instances.get(),
props: $props.get(),
dataSources: $dataSources.get(),
resources: $resources.get(),
breakpoints: $breakpoints.get(),
styleSourceSelections: $styleSourceSelections.get(),
styleSources: $styleSources.get(),
styles: $styles.get(),
assets: $assets.get(),
marketplaceProduct: $marketplaceProduct.get(),
projectSettings,
};
};
const getPair = <Item extends { id: string }>(item: Item) =>
[item.id, item] as const;
const hydrateIdMap = <Item extends { id: string }>(items: Item[]) =>
new Map(items.map(getPair));
const hydrateMap = <Item, Key>(items: Item[], getKey: (item: Item) => Key) =>
new Map(items.map((item) => [getKey(item), item] as const));
export const loadBuilderData = async ({
projectId,
signal,
}: {
projectId: string;
signal: AbortSignal;
}): Promise<LoadedBuilderData> => {
try {
const data = await nativeClient.build.loadData.query(
{ projectId },
{ signal }
);
const pages = migratePages(data.pages);
return {
id: data.id,
version: data.version,
projectId: data.projectId,
project: data.project,
publisherHost: data.publisherHost,
assets: hydrateIdMap(data.assets),
instances: hydrateIdMap(data.instances),
dataSources: hydrateIdMap(data.dataSources),
resources: hydrateIdMap(data.resources),
props: hydrateIdMap(data.props),
pages,
breakpoints: hydrateIdMap(data.breakpoints),
styleSources: hydrateIdMap(data.styleSources),
styleSourceSelections: hydrateMap(
data.styleSourceSelections,
(item) => item.instanceId
),
styles: hydrateMap(data.styles, (item) => getStyleDeclKey(item)),
marketplaceProduct: data.marketplaceProduct,
projectSettings: data.projectSettings,
};
} catch (error) {
const message =
error instanceof Error ? error.message : JSON.stringify(error);
if (signal.aborted === false) {
// No toasts available in this context
alert(`Unable to load builder data. ${message}`);
}
throw new Error(`Unable to load builder data. ${message}`, {
cause: error,
});
}
};