mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 22:14:29 +09:00
This PR expands Webstudio MCP and CLI into a more complete agent authoring interface. Agents can discover project capabilities, edit real Webstudio content, use data/resources, verify visual output, and report useful issues when workflows fail. ## MCP User Capabilities - Create, update, duplicate, reorder, and delete pages. - Create pages from templates and manage reusable page templates. - Inspect project pages, instances, props, styles, assets, variables, resources, and metadata. - Insert Webstudio components and authored JSX fragments into pages. - Edit text, props, styles, breakpoints, design tokens, and CSS variables. - Update page metadata, SEO fields, redirects, marketplace metadata, and project settings. - Create and use static data variables. - Create and use dynamic resources fetched over HTTP. - Use REST, GraphQL, and built-in system resources in authored pages. - Bind data variables and resources to visible page content. - Create resource-backed pages such as sitemap XML, structured SEO data, and API-driven content sections. - Preview current project state without requiring a publish step. - Capture screenshots for visual verification. - Verify responsive designs across desktop, tablet, and mobile breakpoints. - Use screenshot diff and OCR-style checks to inspect rendered output. - Reset and refresh local MCP project state when needed. - Discover available CLI/MCP commands from generated manuals. - Use clearer examples for resources, data variables, JSX authoring, previewing, and visual checks. - Read marketplace product metadata without requiring a full project snapshot. - Get safer guidance when using low-level patch tools. - Get a ready-to-copy Discord report template when MCP/CLI behavior is confusing or broken.
177 lines
4.1 KiB
TypeScript
177 lines
4.1 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import {
|
|
getInputJsonSchemaAdditionalProperties,
|
|
getInputJsonSchemaMetadata,
|
|
getInputJsonSchemaProperties,
|
|
inputJsonSchemaAcceptsType,
|
|
type InputJsonSchema,
|
|
} from "./input-json-schema";
|
|
|
|
test("derives field summaries from JSON input schemas", () => {
|
|
expect(
|
|
getInputJsonSchemaMetadata({
|
|
type: "object",
|
|
properties: {
|
|
title: { type: "string" },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
variants: {
|
|
anyOf: [
|
|
{ type: "string" },
|
|
{ type: "array", items: { type: "string" } },
|
|
],
|
|
},
|
|
pair: {
|
|
allOf: [
|
|
{ type: "array", items: { type: "string" } },
|
|
{ minItems: 2 },
|
|
],
|
|
},
|
|
},
|
|
required: ["title", "tags"],
|
|
})
|
|
).toEqual({
|
|
inputFields: ["title", "tags", "variants", "pair"],
|
|
requiredInputFields: ["title", "tags"],
|
|
inputFieldTypes: {
|
|
tags: "array",
|
|
variants: "array",
|
|
pair: "array",
|
|
},
|
|
});
|
|
});
|
|
|
|
test("derives field summaries through JSON schema branches", () => {
|
|
expect(
|
|
getInputJsonSchemaMetadata({
|
|
allOf: [
|
|
{
|
|
properties: {
|
|
title: { type: "string" },
|
|
},
|
|
required: ["title"],
|
|
},
|
|
{
|
|
properties: {
|
|
tags: { type: "array", items: { type: "string" } },
|
|
},
|
|
},
|
|
],
|
|
})
|
|
).toEqual({
|
|
inputFields: ["title", "tags"],
|
|
requiredInputFields: ["title"],
|
|
inputFieldTypes: {
|
|
tags: "array",
|
|
},
|
|
});
|
|
});
|
|
|
|
test("does not require fields that are only required by some anyOf branches", () => {
|
|
expect(
|
|
getInputJsonSchemaMetadata({
|
|
properties: {
|
|
title: { type: "string" },
|
|
slug: { type: "string" },
|
|
kind: { type: "string" },
|
|
},
|
|
anyOf: [{ required: ["title", "kind"] }, { required: ["slug", "kind"] }],
|
|
})
|
|
).toEqual({
|
|
inputFields: ["title", "slug", "kind"],
|
|
requiredInputFields: ["kind"],
|
|
inputFieldTypes: {},
|
|
});
|
|
});
|
|
|
|
test("can treat unconstrained schemas as accepting structured JSON", () => {
|
|
expect(inputJsonSchemaAcceptsType({}, "array")).toBe(false);
|
|
expect(
|
|
inputJsonSchemaAcceptsType({}, "array", {
|
|
treatUnconstrainedAsAny: true,
|
|
})
|
|
).toBe(true);
|
|
expect(
|
|
inputJsonSchemaAcceptsType(
|
|
{ description: "Any JSON value.", default: {} },
|
|
"object",
|
|
{
|
|
treatUnconstrainedAsAny: true,
|
|
}
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
inputJsonSchemaAcceptsType({ format: "uri" } as InputJsonSchema, "object", {
|
|
treatUnconstrainedAsAny: true,
|
|
})
|
|
).toBe(false);
|
|
});
|
|
|
|
test("does not treat empty allOf schemas as every JSON type", () => {
|
|
expect(inputJsonSchemaAcceptsType({ allOf: [] }, "array")).toBe(false);
|
|
expect(inputJsonSchemaAcceptsType({ allOf: [] }, "object")).toBe(false);
|
|
});
|
|
|
|
test("does not require fields from empty anyOf branches", () => {
|
|
expect(
|
|
getInputJsonSchemaMetadata({
|
|
properties: {
|
|
title: { type: "string" },
|
|
},
|
|
anyOf: [],
|
|
}).requiredInputFields
|
|
).toEqual([]);
|
|
});
|
|
|
|
test("collects properties through JSON schema branches", () => {
|
|
expect(
|
|
getInputJsonSchemaProperties({
|
|
properties: {
|
|
title: { type: "string" },
|
|
settings: { type: "string" },
|
|
},
|
|
allOf: [
|
|
{
|
|
properties: {
|
|
settings: { type: "object" },
|
|
enabled: { type: "boolean" },
|
|
},
|
|
},
|
|
],
|
|
})
|
|
).toEqual({
|
|
title: { type: "string" },
|
|
settings: {
|
|
allOf: [{ type: "string" }, { type: "object" }],
|
|
},
|
|
enabled: { type: "boolean" },
|
|
});
|
|
});
|
|
|
|
test("collects additionalProperties schemas through JSON schema branches", () => {
|
|
expect(
|
|
getInputJsonSchemaAdditionalProperties({
|
|
additionalProperties: true,
|
|
allOf: [
|
|
{
|
|
additionalProperties: {
|
|
type: "object",
|
|
properties: {
|
|
enabled: { type: "boolean" },
|
|
},
|
|
},
|
|
},
|
|
],
|
|
})
|
|
).toEqual({
|
|
allOf: [
|
|
{},
|
|
{
|
|
type: "object",
|
|
properties: {
|
|
enabled: { type: "boolean" },
|
|
},
|
|
},
|
|
],
|
|
});
|
|
});
|