diff --git a/package-lock.json b/package-lock.json index feaa700..5f10b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,11 @@ "name": "webflow-mcp-server", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "agents": "^0.0.59", + "@modelcontextprotocol/sdk": "1.21.1", "cors": "^2.8.5", "express": "^5.1.0", "socket.io": "^4.8.1", - "webflow-api": "3.1.1", + "webflow-api": "3.2.1", "zod": "^3.24.2" }, "bin": { @@ -29,13 +28,6 @@ "typescript": "^5.8.2" } }, - "node_modules/@cloudflare/workers-types": { - "version": "4.20250409.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250409.0.tgz", - "integrity": "sha512-yPxxwE5nr168huEfLNOB6904OsvIWcq0tWT23NMD6jT5SIp2ds3oOGANw7wz39r5y3jZYC2h1OnGwnZXJDDCOg==", - "license": "MIT OR Apache-2.0", - "peer": true - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -618,15 +610,18 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", - "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.21.1.tgz", + "integrity": "sha512-UyLFcJLDvUuZbGnaQqXFT32CpPpGj7VS19roLut6gkQVhb439xUzYWbsUvdI3ZPL+2hnFosuugtYWE0Mcs1rmQ==", "license": "MIT", "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", @@ -636,6 +631,14 @@ }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + } } }, "node_modules/@pkgjs/parseargs": { @@ -1082,17 +1085,37 @@ "node": ">= 0.6" } }, - "node_modules/agents": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.59.tgz", - "integrity": "sha512-OiUg1QffKszZ+FdSB60KvBQF2Owzp96WIeP3XK2ShB3dYeAGwlsg6SmE7QomNfR+oe1KgvI4AmBKy9cLB7qY6A==", + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.9.0", - "cron-schedule": "^5.0.4", - "nanoid": "^5.1.5", - "partyserver": "^0.0.66", - "partysocket": "1.1.3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, "node_modules/ansi-regex": { @@ -1764,15 +1787,6 @@ "sha.js": "^2.4.8" } }, - "node_modules/cron-schedule": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-5.0.4.tgz", - "integrity": "sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2122,12 +2136,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-polyfill": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", - "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==", - "license": "MIT" - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -2257,6 +2265,28 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2802,11 +2832,17 @@ } }, "node_modules/js-base64": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", - "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", "license": "BSD-3-Clause" }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2992,24 +3028,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -3176,27 +3194,6 @@ "node": ">= 0.8" } }, - "node_modules/partyserver": { - "version": "0.0.66", - "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.66.tgz", - "integrity": "sha512-GyC1uy4dvC4zPkwdzHqCkQ1J1CMiI0swIJQ0qqsJh16WNkEo5QHuU3l3ikLO8t+Yq0cRr0qO8++xbr11h+107w==", - "license": "ISC", - "dependencies": { - "nanoid": "^5.1.2" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20240729.0" - } - }, - "node_modules/partysocket": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.3.tgz", - "integrity": "sha512-87Jd/nqPoWnVfzHE6Z12WLWTJ+TAgxs0b7i2S163HfQSrVDUK5tW/FC64T5N8L5ss+gqF+EV0BwjZMWggMY3UA==", - "license": "ISC", - "dependencies": { - "event-target-polyfill": "^0.0.4" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3493,6 +3490,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -4426,35 +4432,20 @@ } }, "node_modules/webflow-api": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/webflow-api/-/webflow-api-3.1.1.tgz", - "integrity": "sha512-WypXB9Vz9fXx5cVUBtO9O702//FWFoDSvmDkw0VHh36PZ2X/XcKSYwT/uYMap2E4k242Zv8a63MUn6JkF36Rig==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/webflow-api/-/webflow-api-3.2.1.tgz", + "integrity": "sha512-NvBH15JPvVIdKHEDpzKzrJWnJytwqaBuUDVxlhW91fgK5hSr9kXrD3t67ZqIS1zzi3AnnGSVJl/sauJS0TqBDg==", "dependencies": { "crypto-browserify": "^3.12.1", "form-data": "^4.0.0", "formdata-node": "^6.0.3", - "js-base64": "3.7.2", - "node-fetch": "2.7.0", - "qs": "6.11.2", + "js-base64": "3.7.7", + "node-fetch": "^2.7.0", + "qs": "^6.13.1", "readable-stream": "^4.5.2", "url-join": "4.0.1" } }, - "node_modules/webflow-api/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index c1d247f..55d155f 100644 --- a/package.json +++ b/package.json @@ -9,20 +9,15 @@ "start": "concurrently \"npm run dev:local\" \"npm run inspector:local\"", "dev:local": "npm run build:watch", "inspector:local": "npx @modelcontextprotocol/inspector -- nodemon --env-file=.env -q --watch dist dist/index.js", - "dev:cf": "wrangler dev src/index.worker.ts", - "inspector:cf": "npx @modelcontextprotocol/inspector", - "deploy:cf": "npm run build && wrangler deploy", - "types:cf": "wrangler types", - "build": "tsup src/index.ts src/index.worker.ts --external=cloudflare:workers --dts --clean", - "build:watch": "tsup src/index.ts src/index.worker.ts --external=cloudflare:workers --dts --watch" + "build": "tsup src/index.ts --dts --clean", + "build:watch": "tsup src/index.ts --dts --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "agents": "^0.0.59", + "@modelcontextprotocol/sdk": "1.21.1", "cors": "^2.8.5", "express": "^5.1.0", "socket.io": "^4.8.1", - "webflow-api": "3.1.1", + "webflow-api": "3.2.1", "zod": "^3.24.2" }, "devDependencies": { diff --git a/src/mcp.ts b/src/mcp.ts index ffb2161..701eaaf 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -53,10 +53,7 @@ export function registerTools( registerSiteTools(server, getClient); } -export function registerDesignerTools( - server: McpServer, - rpc: RPCType -) { +export function registerDesignerTools(server: McpServer, rpc: RPCType) { registerDEAssetTools(server, rpc); registerDEComponentsTools(server, rpc); registerDEElementTools(server, rpc); @@ -72,9 +69,6 @@ export function registerMiscTools(server: McpServer) { /** * IMPORTANT: registerLocalTools is only valid for OSS MCP Version */ -export function registerLocalTools( - server: McpServer, - rpc: RPCType -) { +export function registerLocalTools(server: McpServer, rpc: RPCType) { registerLocalDeMCPConnectionTools(server, rpc); } diff --git a/src/schemas/StaticFieldSchema.ts b/src/schemas/StaticFieldSchema.ts index 3d407f2..8df013f 100644 --- a/src/schemas/StaticFieldSchema.ts +++ b/src/schemas/StaticFieldSchema.ts @@ -24,7 +24,7 @@ export const StaticFieldSchema = z.object({ z.literal("PlainText"), z.literal("RichText"), z.literal("Switch"), - z.literal("Video"), + z.literal("VideoLink"), ]) .describe("Type of the field. Choose of these appropriate field types."), displayName: z.string().describe("Name of the field."), diff --git a/src/tools/aiChat.ts b/src/tools/aiChat.ts index 5db5fdf..04d70b5 100644 --- a/src/tools/aiChat.ts +++ b/src/tools/aiChat.ts @@ -6,10 +6,18 @@ const BASE_URL = "https://developers.webflow.com/"; const X_FERN_HOST = "developers.webflow.com"; export function registerAiChatTools(server: McpServer) { - server.tool( + server.registerTool( "ask_webflow_ai", - "Ask Webflow AI about anything related to Webflow API.", - { message: z.string() }, + { + description: "Ask Webflow AI about anything related to Webflow API.", + title: "Ask Webflow AI", + annotations: { + openWorldHint: true, + }, + inputSchema: { + message: z.string().describe("The message to ask Webflow AI about."), + }, + }, async ({ message }) => { const result = await postChat(message); return { diff --git a/src/tools/cms.ts b/src/tools/cms.ts index 9e497b0..ac0672e 100644 --- a/src/tools/cms.ts +++ b/src/tools/cms.ts @@ -205,243 +205,255 @@ export function registerCmsTools( return response; }; - server.tool( + server.registerTool( "data_cms_tool", - "Data tool - CMS tool to perform actions like get collection list, get collection details, create collection, create collection fields (static/option/reference), update collection field, list collection items, create collection items, update collection items, publish collection items, and delete collection items", { - actions: z.array( - z.object({ - // GET https://api.webflow.com/v2/sites/:site_id/collections - get_collection_list: z - .object({ - ...SiteIdSchema, - }) - .optional() - .describe( - "List all CMS collections in a site. Returns collection metadata including IDs, names, and schemas." - ), - // GET https://api.webflow.com/v2/collections/:collection_id - get_collection_details: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - }) - .optional() - .describe( - "Get detailed information about a specific CMS collection including its schema and field definitions." - ), - // POST https://api.webflow.com/v2/sites/:site_id/collections - create_collection: z - .object({ - ...SiteIdSchema, - request: WebflowCollectionsCreateRequestSchema, - }) - .optional() - .describe( - "Create a new CMS collection in a site with specified name and schema." - ), - // POST https://api.webflow.com/v2/collections/:collection_id/fields - create_collection_static_field: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: StaticFieldSchema, - }) - .optional() - .describe( - "Create a new static field in a CMS collection (e.g., text, number, date, etc.)." - ), - // POST https://api.webflow.com/v2/collections/:collection_id/fields - create_collection_option_field: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: OptionFieldSchema, - }) - .optional() - .describe( - "Create a new option field in a CMS collection with predefined choices." - ), - // POST https://api.webflow.com/v2/collections/:collection_id/fields - create_collection_reference_field: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: ReferenceFieldSchema, - }) - .optional() - .describe( - "Create a new reference field in a CMS collection that links to items in another collection." - ), - // PATCH https://api.webflow.com/v2/collections/:collection_id/fields/:field_id - update_collection_field: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - field_id: z.string().describe("Unique identifier for the Field."), - request: WebflowCollectionsFieldUpdateSchema, - }) - .optional() - .describe( - "Update properties of an existing field in a CMS collection." - ), - // // POST https://api.webflow.com/v2/collections/:collection_id/items/live - // //NOTE: Cursor agent seems to struggle when provided with z.union(...), so we simplify the type here - // create_collection_items_live:z.object({ - // collection_id: z.string().describe("Unique identifier for the Collection."), - // request: WebflowCollectionsItemsCreateItemLiveRequestSchema, - // }).optional().describe("Create and publish new items in a CMS collection directly to the live site."), - // // PATCH https://api.webflow.com/v2/collections/:collection_id/items/live - // update_collection_items_live:z.object({ - // collection_id: z.string().describe("Unique identifier for the Collection."), - // request: WebflowCollectionsItemsUpdateItemsLiveRequestSchema, - // }).optional().describe("Update and publish existing items in a CMS collection directly to the live site."), - // GET https://api.webflow.com/v2/collections/:collection_id/items - list_collection_items: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: z - .object({ - cmsLocaleId: z - .string() - .optional() - .describe( - "Unique identifier for the locale of the CMS Item." - ), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - name: z.string().optional().describe("Name of the field."), - slug: z - .string() - .optional() - .describe( - "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug." - ), - sortBy: WebflowCollectionsItemsListItemsRequestSortBySchema, - sortOrder: - WebflowCollectionsItemsListItemsRequestSortOrderSchema, - }) - .optional() - .describe("Filter and sort items in a CMS collection."), - }) - .optional() - .describe( - "List items in a CMS collection with optional filtering and sorting." - ), - // POST https://api.webflow.com/v2/collections/:collection_id/items/bulk - create_collection_items: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: z - .object({ - cmsLocaleIds: z - .array(z.string()) - .optional() - .describe( - "Unique identifier for the locale of the CMS Item." - ), - isArchived: z - .boolean() - .optional() - .describe("Indicates if the item is archived."), - isDraft: z - .boolean() - .optional() - .describe("Indicates if the item is a draft."), - fieldData: z - .array( - z.record(z.any()).and( + title: "Data CMS Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Data tool - CMS tool to perform actions like get collection list, get collection details, create collection, create collection fields (static/option/reference), update collection field, list collection items, create collection items, update collection items, publish collection items, and delete collection items", + inputSchema: { + actions: z.array( + z.object({ + // GET https://api.webflow.com/v2/sites/:site_id/collections + get_collection_list: z + .object({ + ...SiteIdSchema, + }) + .optional() + .describe( + "List all CMS collections in a site. Returns collection metadata including IDs, names, and schemas." + ), + // GET https://api.webflow.com/v2/collections/:collection_id + get_collection_details: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + }) + .optional() + .describe( + "Get detailed information about a specific CMS collection including its schema and field definitions." + ), + // POST https://api.webflow.com/v2/sites/:site_id/collections + create_collection: z + .object({ + ...SiteIdSchema, + request: WebflowCollectionsCreateRequestSchema, + }) + .optional() + .describe( + "Create a new CMS collection in a site with specified name and schema." + ), + // POST https://api.webflow.com/v2/collections/:collection_id/fields + create_collection_static_field: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: StaticFieldSchema, + }) + .optional() + .describe( + "Create a new static field in a CMS collection (e.g., text, number, date, etc.)." + ), + // POST https://api.webflow.com/v2/collections/:collection_id/fields + create_collection_option_field: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: OptionFieldSchema, + }) + .optional() + .describe( + "Create a new option field in a CMS collection with predefined choices." + ), + // POST https://api.webflow.com/v2/collections/:collection_id/fields + create_collection_reference_field: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: ReferenceFieldSchema, + }) + .optional() + .describe( + "Create a new reference field in a CMS collection that links to items in another collection." + ), + // PATCH https://api.webflow.com/v2/collections/:collection_id/fields/:field_id + update_collection_field: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + field_id: z + .string() + .describe("Unique identifier for the Field."), + request: WebflowCollectionsFieldUpdateSchema, + }) + .optional() + .describe( + "Update properties of an existing field in a CMS collection." + ), + // // POST https://api.webflow.com/v2/collections/:collection_id/items/live + // //NOTE: Cursor agent seems to struggle when provided with z.union(...), so we simplify the type here + // create_collection_items_live:z.object({ + // collection_id: z.string().describe("Unique identifier for the Collection."), + // request: WebflowCollectionsItemsCreateItemLiveRequestSchema, + // }).optional().describe("Create and publish new items in a CMS collection directly to the live site."), + // // PATCH https://api.webflow.com/v2/collections/:collection_id/items/live + // update_collection_items_live:z.object({ + // collection_id: z.string().describe("Unique identifier for the Collection."), + // request: WebflowCollectionsItemsUpdateItemsLiveRequestSchema, + // }).optional().describe("Update and publish existing items in a CMS collection directly to the live site."), + // GET https://api.webflow.com/v2/collections/:collection_id/items + list_collection_items: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: z + .object({ + cmsLocaleId: z + .string() + .optional() + .describe( + "Unique identifier for the locale of the CMS Item." + ), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + name: z.string().optional().describe("Name of the field."), + slug: z + .string() + .optional() + .describe( + "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug." + ), + sortBy: WebflowCollectionsItemsListItemsRequestSortBySchema, + sortOrder: + WebflowCollectionsItemsListItemsRequestSortOrderSchema, + }) + .optional() + .describe("Filter and sort items in a CMS collection."), + }) + .optional() + .describe( + "List items in a CMS collection with optional filtering and sorting." + ), + // POST https://api.webflow.com/v2/collections/:collection_id/items/bulk + create_collection_items: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: z + .object({ + cmsLocaleIds: z + .array(z.string()) + .optional() + .describe( + "Unique identifier for the locale of the CMS Item." + ), + isArchived: z + .boolean() + .optional() + .describe("Indicates if the item is archived."), + isDraft: z + .boolean() + .optional() + .describe("Indicates if the item is a draft."), + fieldData: z + .array( + z.record(z.any()).and( + z.object({ + name: z.string().describe("Name of the field."), + slug: z + .string() + .describe( + "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug." + ), + }) + ) + ) + .describe("Data of the item."), + }) + .describe("Array of items to be created."), + }) + .optional() + .describe("Create new items in a CMS collection as drafts."), + //PATCH https://api.webflow.com/v2/collections/:collection_id/items + update_collection_items: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: + WebflowCollectionsItemsUpdateItemsRequestSchema.describe( + "Array of items to be updated." + ), + }) + .optional() + .describe("Update existing items in a CMS collection as drafts."), + // POST https://api.webflow.com/v2/collections/:collection_id/items/publish + publish_collection_items: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: z + .object({ + itemIds: z + .array(z.string()) + .describe("Array of item IDs to be published."), + }) + .describe("Array of items to be published."), + }) + .optional() + .describe( + "Publish existing items in a CMS collection as drafts." + ), + // DEL https://api.webflow.com/v2/collections/:collection_id/items + delete_collection_items: z + .object({ + collection_id: z + .string() + .describe("Unique identifier for the Collection."), + request: z + .object({ + items: z + .array( z.object({ - name: z.string().describe("Name of the field."), - slug: z - .string() + id: z.string().describe("Item ID to be deleted."), + cmsLocaleIds: z + .array(z.string()) + .optional() .describe( - "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug." + "Unique identifier for the locale of the CMS Item." ), }) ) - ) - .describe("Data of the item."), - }) - .describe("Array of items to be created."), - }) - .optional() - .describe("Create new items in a CMS collection as drafts."), - //PATCH https://api.webflow.com/v2/collections/:collection_id/items - update_collection_items: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: WebflowCollectionsItemsUpdateItemsRequestSchema.describe( - "Array of items to be updated." - ), - }) - .optional() - .describe("Update existing items in a CMS collection as drafts."), - // POST https://api.webflow.com/v2/collections/:collection_id/items/publish - publish_collection_items: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: z - .object({ - itemIds: z - .array(z.string()) - .describe("Array of item IDs to be published."), - }) - .describe("Array of items to be published."), - }) - .optional() - .describe("Publish existing items in a CMS collection as drafts."), - // DEL https://api.webflow.com/v2/collections/:collection_id/items - delete_collection_items: z - .object({ - collection_id: z - .string() - .describe("Unique identifier for the Collection."), - request: z - .object({ - items: z - .array( - z.object({ - id: z.string().describe("Item ID to be deleted."), - cmsLocaleIds: z - .array(z.string()) - .optional() - .describe( - "Unique identifier for the locale of the CMS Item." - ), - }) - ) - .describe("Array of items to be deleted."), - }) - .describe("Array of items to be deleted."), - }) - .optional() - .describe("Delete existing items in a CMS collection as drafts."), - }) - ), + .describe("Array of items to be deleted."), + }) + .describe("Array of items to be deleted."), + }) + .optional() + .describe("Delete existing items in a CMS collection as drafts."), + }) + ), + }, }, async ({ actions }) => { const result: Content[] = []; diff --git a/src/tools/components.ts b/src/tools/components.ts index 4e4c389..48f00e9 100644 --- a/src/tools/components.ts +++ b/src/tools/components.ts @@ -109,131 +109,138 @@ export function registerComponentsTools( return response; }; - server.tool( + server.registerTool( "data_components_tool", - "Data tool - Components tool to perform actions like list components, get component content, update component content, get component properties, and update component properties", { - actions: z.array( - z.object({ - // GET https://api.webflow.com/v2/sites/:site_id/components - list_components: z - .object({ - site_id: z.string().describe("Unique identifier for the Site."), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - }) - .optional() - .describe( - "List all components in a site. Returns component metadata including IDs, names, and versions." - ), - // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom - get_component_content: z - .object({ - site_id: z.string().describe("Unique identifier for the Site."), - component_id: z - .string() - .describe("Unique identifier for the Component."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - }) - .optional() - .describe( - "Get the content structure and data for a specific component including text, images, and nested components." - ), - // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom - update_component_content: z - .object({ - site_id: z.string().describe("Unique identifier for the Site."), - component_id: z - .string() - .describe("Unique identifier for the Component."), - localeId: z - .string() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - nodes: ComponentDomWriteNodesItemSchema, - }) - .optional() - .describe( - "Update content on a component in secondary locales by modifying text nodes and property overrides." - ), - // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties - get_component_properties: z - .object({ - site_id: z.string().describe("Unique identifier for the Site."), - component_id: z - .string() - .describe("Unique identifier for the Component."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - }) - .optional() - .describe( - "Get component properties including default values and configuration for a specific component." - ), - // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties - update_component_properties: z - .object({ - site_id: z.string().describe("Unique identifier for the Site."), - component_id: z - .string() - .describe("Unique identifier for the Component."), - localeId: z - .string() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - properties: ComponentPropertyUpdateSchema, - }) - .optional() - .describe( - "Update component properties for localization to customize behavior in different languages." - ), - }) - ), + title: "Data Components Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Data tool - Components tool to perform actions like list components, get component content, update component content, get component properties, and update component properties", + inputSchema: { + actions: z.array( + z.object({ + // GET https://api.webflow.com/v2/sites/:site_id/components + list_components: z + .object({ + site_id: z.string().describe("Unique identifier for the Site."), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + }) + .optional() + .describe( + "List all components in a site. Returns component metadata including IDs, names, and versions." + ), + // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom + get_component_content: z + .object({ + site_id: z.string().describe("Unique identifier for the Site."), + component_id: z + .string() + .describe("Unique identifier for the Component."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + }) + .optional() + .describe( + "Get the content structure and data for a specific component including text, images, and nested components." + ), + // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom + update_component_content: z + .object({ + site_id: z.string().describe("Unique identifier for the Site."), + component_id: z + .string() + .describe("Unique identifier for the Component."), + localeId: z + .string() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + nodes: ComponentDomWriteNodesItemSchema, + }) + .optional() + .describe( + "Update content on a component in secondary locales by modifying text nodes and property overrides." + ), + // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties + get_component_properties: z + .object({ + site_id: z.string().describe("Unique identifier for the Site."), + component_id: z + .string() + .describe("Unique identifier for the Component."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + }) + .optional() + .describe( + "Get component properties including default values and configuration for a specific component." + ), + // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties + update_component_properties: z + .object({ + site_id: z.string().describe("Unique identifier for the Site."), + component_id: z + .string() + .describe("Unique identifier for the Component."), + localeId: z + .string() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + properties: ComponentPropertyUpdateSchema, + }) + .optional() + .describe( + "Update component properties for localization to customize behavior in different languages." + ), + }) + ), + }, }, async ({ actions }) => { const result: Content[] = []; diff --git a/src/tools/deAsset.ts b/src/tools/deAsset.ts index 0257f02..97c498e 100644 --- a/src/tools/deAsset.ts +++ b/src/tools/deAsset.ts @@ -2,142 +2,120 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; import { SiteIdSchema } from "../schemas"; -import { - formatErrorResponse, - formatResponse, -} from "../utils"; +import { formatErrorResponse, formatResponse } from "../utils"; -export function registerDEAssetTools( - server: McpServer, - rpc: RPCType -) { - const assetToolRPCCall = async ( - siteId: string, - actions: any - ) => { +export function registerDEAssetTools(server: McpServer, rpc: RPCType) { + const assetToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("asset_tool", { siteId, actions: actions || [], }); }; - const getImagePreviewFromURL = async ( - url: string, - siteId: string - ) => { + const getImagePreviewFromURL = async (url: string, siteId: string) => { const response = await fetch(url); - const contentType = - response.headers.get("content-type"); + const contentType = response.headers.get("content-type"); if (!contentType || !contentType.startsWith("image/")) { throw new Error( - `Expected an image but received MIME type: ${ - contentType || "unknown" - }` + `Expected an image but received MIME type: ${contentType || "unknown"}` ); } const arrayBuffer = await response.arrayBuffer(); - const binary = String.fromCharCode( - ...new Uint8Array(arrayBuffer) - ); + const binary = String.fromCharCode(...new Uint8Array(arrayBuffer)); const base64 = btoa(binary); return { data: base64, mimeType: contentType, siteId }; }; - server.tool( + server.registerTool( "asset_tool", - "Designer Tool - Asset tool to perform actions like create folder, get all assets and folders, update assets and folders", { - ...SiteIdSchema, - actions: z.array( - z.object({ - create_folder: z - .object({ - name: z - .string() - .describe( - "The name of the folder to create" - ), - parent_folder_id: z - .string() - .optional() - .describe( - "The id of the parent folder to move the folder to." - ), - }) - .optional() - .describe("Create a folder on the site"), - get_all_assets_and_folders: z - .object({ - query: z - .enum(["all", "folders", "assets"]) - .describe( - "Query to get all assets and folders on the site" - ), - filter_assets_by_ids: z - .array(z.string()) - .describe("Filter assets by ids") - .optional(), - }) - .optional() - .describe( - "Get all assets and folders on the site" - ), - update_asset: z - .object({ - asset_id: z - .string() - .describe("The id of the asset to update"), - name: z - .string() - .optional() - .describe( - "The name of the asset to update" - ), - alt_text: z - .string() - .optional() - .describe( - "The alt text of the asset to update" - ), - parent_folder_id: z - .string() - .optional() - .describe( - "The id of the parent folder to move the asset to." - ), - }) - .optional() - .describe("Update an asset on the site"), - }) - ), + title: "Designer Asset Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Asset tool to perform actions like create folder, get all assets and folders, update assets and folders", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + create_folder: z + .object({ + name: z.string().describe("The name of the folder to create"), + parent_folder_id: z + .string() + .optional() + .describe( + "The id of the parent folder to move the folder to." + ), + }) + .optional() + .describe("Create a folder on the site"), + get_all_assets_and_folders: z + .object({ + query: z + .enum(["all", "folders", "assets"]) + .describe("Query to get all assets and folders on the site"), + filter_assets_by_ids: z + .array(z.string()) + .describe("Filter assets by ids") + .optional(), + }) + .optional() + .describe("Get all assets and folders on the site"), + update_asset: z + .object({ + asset_id: z.string().describe("The id of the asset to update"), + name: z + .string() + .optional() + .describe("The name of the asset to update"), + alt_text: z + .string() + .optional() + .describe("The alt text of the asset to update"), + parent_folder_id: z + .string() + .optional() + .describe( + "The id of the parent folder to move the asset to." + ), + }) + .optional() + .describe("Update an asset on the site"), + }) + ), + }, }, async ({ siteId, actions }) => { try { - return formatResponse( - await assetToolRPCCall(siteId, actions) - ); + return formatResponse(await assetToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } } ); - server.tool( + server.registerTool( "get_image_preview", - "Designer Tool - Get image preview from url. this is helpful to get image preview from url.", { - url: z - .string() - .describe( - "The URL of the image to get the preview from" - ), - ...SiteIdSchema, + title: "Get Webflow Image Preview", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Get image preview from url. this is helpful to get image preview from url. Only supports JPG, PNG, GIF, WEBP, WEBP and AVIF formats.", + inputSchema: { + url: z + .string() + .describe("The URL of the image to get the preview from"), + ...SiteIdSchema, + }, }, async ({ url, siteId }) => { try { - const { data, mimeType } = - await getImagePreviewFromURL(url, siteId); + const { data, mimeType } = await getImagePreviewFromURL(url, siteId); return { content: [ { diff --git a/src/tools/deComponents.ts b/src/tools/deComponents.ts index ba3235f..ea73d0a 100644 --- a/src/tools/deComponents.ts +++ b/src/tools/deComponents.ts @@ -1,112 +1,93 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; -import { - DEElementIDSchema, - SiteIdSchema, -} from "../schemas"; -import { - formatErrorResponse, - formatResponse, -} from "../utils"; +import { DEElementIDSchema, SiteIdSchema } from "../schemas"; +import { formatErrorResponse, formatResponse } from "../utils"; -export function registerDEComponentsTools( - server: McpServer, - rpc: RPCType -) { - const componentsToolRPCCall = async ( - siteId: string, - actions: any - ) => { +export function registerDEComponentsTools(server: McpServer, rpc: RPCType) { + const componentsToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("component_tool", { siteId, actions: actions || [], }); }; - server.tool( + server.registerTool( "de_component_tool", - "Designer tool - Component tool to perform actions like create component instances, get all components and more.", { - ...SiteIdSchema, - actions: z.array( - z.object({ - check_if_inside_component_view: z - .boolean() - .optional() - .describe( - "Check if inside component view. this helpful to make changes to the component" - ), - transform_element_to_component: z - .object({ - ...DEElementIDSchema, - name: z - .string() - .describe("The name of the component"), - }) - .optional() - .describe( - "Transform an element to a component" - ), - insert_component_instance: z - .object({ - parent_element_id: DEElementIDSchema.id, - component_id: z - .string() - .describe( - "The id of the component to insert" - ), - creation_position: z - .enum(["append", "prepend"]) - .describe( - "The position to create component instance on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element." - ), - }) - .optional() - .describe( - "Insert a component on current active page." - ), - open_component_view: z - .object({ - component_instance_id: DEElementIDSchema.id, - }) - .optional() - .describe( - "Open a component instance view for changes or reading." - ), - close_component_view: z - .boolean() - .optional() - .describe( - "Close a component instance view. it will close and open the page view." - ), - get_all_components: z - .boolean() - .optional() - .describe( - "Get all components, only valid if you are connected to Webflow Designer." - ), - rename_component: z - .object({ - component_id: z - .string() - .describe( - "The id of the component to rename" - ), - new_name: z - .string() - .describe("The name of the component"), - }) - .optional() - .describe("Rename a component."), - }) - ), + title: "Designer Component Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer tool - Component tool to perform actions like create component instances, get all components and more.", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + check_if_inside_component_view: z + .boolean() + .optional() + .describe( + "Check if inside component view. this helpful to make changes to the component" + ), + transform_element_to_component: z + .object({ + ...DEElementIDSchema, + name: z.string().describe("The name of the component"), + }) + .optional() + .describe("Transform an element to a component"), + insert_component_instance: z + .object({ + parent_element_id: DEElementIDSchema.id, + component_id: z + .string() + .describe("The id of the component to insert"), + creation_position: z + .enum(["append", "prepend"]) + .describe( + "The position to create component instance on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element." + ), + }) + .optional() + .describe("Insert a component on current active page."), + open_component_view: z + .object({ + component_instance_id: DEElementIDSchema.id, + }) + .optional() + .describe( + "Open a component instance view for changes or reading." + ), + close_component_view: z + .boolean() + .optional() + .describe( + "Close a component instance view. it will close and open the page view." + ), + get_all_components: z + .boolean() + .optional() + .describe( + "Get all components, only valid if you are connected to Webflow Designer." + ), + rename_component: z + .object({ + component_id: z + .string() + .describe("The id of the component to rename"), + new_name: z.string().describe("The name of the component"), + }) + .optional() + .describe("Rename a component."), + }) + ), + }, }, async ({ siteId, actions }) => { try { - return formatResponse( - await componentsToolRPCCall(siteId, actions) - ); + return formatResponse(await componentsToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } diff --git a/src/tools/deElement.ts b/src/tools/deElement.ts index 47e13c3..4605a68 100644 --- a/src/tools/deElement.ts +++ b/src/tools/deElement.ts @@ -1,284 +1,229 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; -import { - SiteIdSchema, - DEElementIDSchema, - DEElementSchema, -} from "../schemas"; -import { - formatErrorResponse, - formatResponse, -} from "../utils"; +import { SiteIdSchema, DEElementIDSchema, DEElementSchema } from "../schemas"; +import { formatErrorResponse, formatResponse } from "../utils"; -export const registerDEElementTools = ( - server: McpServer, - rpc: RPCType -) => { - const elementBuilderRPCCall = async ( - siteId: string, - actions: any - ) => { +export const registerDEElementTools = (server: McpServer, rpc: RPCType) => { + const elementBuilderRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("element_builder", { siteId, actions: actions || [], }); }; - const elementToolRPCCall = async ( - siteId: string, - actions: any - ) => { + const elementToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("element_tool", { siteId, actions: actions || [], }); }; - server.tool( + server.registerTool( "element_builder", - "Designer Tool - Element builder to create element on current active page. only create elements upto max 3 levels deep. divide your elements into smaller elements to create complex structures. recall this tool to create more elements. but max level is upto 3 levels. you can have as many children as you want. but max level is 3 levels.", { - ...SiteIdSchema, - actions: z.array( - z.object({ - parent_element_id: z - .object({ - component: z - .string() - .describe( - "The component id of the element to perform action on." - ), - element: z - .string() - .describe( - "The element id of the element to perform action on." - ), - }) - .describe( - "The id of the parent element to create element on, you can find it from id field on element. e.g id:{component:123,element:456}." - ), - creation_position: z - .enum(["append", "prepend"]) - .describe( - "The position to create element on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element." - ), - element_schema: DEElementSchema.extend({ - children: z - .array( - DEElementSchema.extend({ - children: z - .array( - DEElementSchema.extend({ - children: z - .array( - DEElementSchema.extend({ - children: z - .array(DEElementSchema) - .optional(), - }) - ) - .optional(), - }) - ) - .optional(), - }) - ) - .optional() + description: + "Designer Tool - Element builder to create element on current active page. only create elements upto max 3 levels deep. divide your elements into smaller elements to create complex structures. recall this tool to create more elements. but max level is upto 3 levels. you can have as many children as you want. but max level is 3 levels.", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + parent_element_id: z + .object({ + component: z + .string() + .describe( + "The component id of the element to perform action on." + ), + element: z + .string() + .describe( + "The element id of the element to perform action on." + ), + }) + .describe( + "The id of the parent element to create element on, you can find it from id field on element. e.g id:{component:123,element:456}." + ), + creation_position: z + .enum(["append", "prepend"]) .describe( - "The children of the element. only valid for container, section, div block, valid DOM elements." + "The position to create element on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element." ), - }).describe( - "element schema of element to create." - ), - }) - ), + element_schema: DEElementSchema.extend({ + children: z + .array( + DEElementSchema.extend({ + children: z + .array( + DEElementSchema.extend({ + children: z + .array( + DEElementSchema.extend({ + children: z.array(DEElementSchema).optional(), + }) + ) + .optional(), + }) + ) + .optional(), + }) + ) + .optional() + .describe( + "The children of the element. only valid for container, section, div block, valid DOM elements." + ), + }).describe("element schema of element to create."), + }) + ), + }, }, async ({ actions, siteId }) => { try { - return formatResponse( - await elementBuilderRPCCall(siteId, actions) - ); + return formatResponse(await elementBuilderRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } } ); - server.tool( + server.registerTool( "element_tool", - "Designer Tool - Element tool to perform actions like get all elements, get selected element, select element on current active page. and more", { - ...SiteIdSchema, - actions: z.array( - z.object({ - get_all_elements: z - .object({ - query: z - .enum(["all"]) - .describe("Query to get all elements"), - include_style_properties: z - .boolean() - .optional() - .describe("Include style properties"), - include_all_breakpoint_styles: z - .boolean() - .optional() - .describe("Include all breakpoints styles"), - }) - .optional() - .describe( - "Get all elements on the current active page" - ), - get_selected_element: z - .boolean() - .optional() - .describe( - "Get selected element on the current active page" - ), - select_element: z - .object({ - ...DEElementIDSchema, - }) - .optional() - .describe( - "Select an element on the current active page" - ), - add_or_update_attribute: z - .object({ - ...DEElementIDSchema, - attributes: z - .array( - z.object({ - name: z - .string() - .describe( - "The name of the attribute to add or update." - ), - value: z - .string() - .describe( - "The value of the attribute to add or update." - ), - }) - ) - .describe( - "The attributes to add or update." - ), - }) - .optional() - .describe( - "Add or update an attribute on the element" - ), - remove_attribute: z - .object({ - ...DEElementIDSchema, - attribute_names: z - .array(z.string()) - .describe( - "The names of the attributes to remove." - ), - }) - .optional() - .describe( - "Remove an attribute from the element" - ), - update_id_attribute: z - .object({ - ...DEElementIDSchema, - new_id: z - .string() - .describe( - "The new #id of the element to update the id attribute to." - ), - }) - .optional() - .describe( - "Update the #id attribute of the element" - ), - set_text: z - .object({ - ...DEElementIDSchema, - text: z - .string() - .describe( - "The text to set on the element." - ), - }) - .optional() - .describe("Set text on the element"), - set_style: z - .object({ - ...DEElementIDSchema, - style_names: z - .array(z.string()) - .describe( - "The style names to set on the element." - ), - }) - .optional() - .describe( - "Set style on the element. it will remove all other styles on the element. and set only the styles passed in style_names." - ), - set_link: z - .object({ - ...DEElementIDSchema, - linkType: z - .enum([ - "url", - "file", - "page", - "element", - "email", - "phone", - ]) - .describe( - "The type of the link to update." - ), - link: z - .string() - .describe( - "The link to set on the element. for page pass page id, for element pass json string of id object. e.g id:{component:123,element:456}. for email pass email address. for phone pass phone number. for file pass asset id. for url pass url." - ), - }) - .optional() - .describe("Set link on the element"), - set_heading_level: z - .object({ - ...DEElementIDSchema, - heading_level: z - .number() - .min(1) - .max(6) - .describe( - "The heading level to set on the element. 1 to 6." - ), - }) - .optional() - .describe( - "Set heading level on the heading element." - ), - set_image_asset: z - .object({ - ...DEElementIDSchema, - image_asset_id: z - .string() - .describe( - "The image asset id to set on the element." - ), - }) - .optional() - .describe( - "Set image asset on the image element" - ), - }) - ), + title: "Designer Element Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Element tool to perform actions like get all elements, get selected element, select element on current active page. and more", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + get_all_elements: z + .object({ + query: z.enum(["all"]).describe("Query to get all elements"), + include_style_properties: z + .boolean() + .optional() + .describe("Include style properties"), + include_all_breakpoint_styles: z + .boolean() + .optional() + .describe("Include all breakpoints styles"), + }) + .optional() + .describe("Get all elements on the current active page"), + get_selected_element: z + .boolean() + .optional() + .describe("Get selected element on the current active page"), + select_element: z + .object({ + ...DEElementIDSchema, + }) + .optional() + .describe("Select an element on the current active page"), + add_or_update_attribute: z + .object({ + ...DEElementIDSchema, + attributes: z + .array( + z.object({ + name: z + .string() + .describe( + "The name of the attribute to add or update." + ), + value: z + .string() + .describe( + "The value of the attribute to add or update." + ), + }) + ) + .describe("The attributes to add or update."), + }) + .optional() + .describe("Add or update an attribute on the element"), + remove_attribute: z + .object({ + ...DEElementIDSchema, + attribute_names: z + .array(z.string()) + .describe("The names of the attributes to remove."), + }) + .optional() + .describe("Remove an attribute from the element"), + update_id_attribute: z + .object({ + ...DEElementIDSchema, + new_id: z + .string() + .describe( + "The new #id of the element to update the id attribute to." + ), + }) + .optional() + .describe("Update the #id attribute of the element"), + set_text: z + .object({ + ...DEElementIDSchema, + text: z.string().describe("The text to set on the element."), + }) + .optional() + .describe("Set text on the element"), + set_style: z + .object({ + ...DEElementIDSchema, + style_names: z + .array(z.string()) + .describe("The style names to set on the element."), + }) + .optional() + .describe( + "Set style on the element. it will remove all other styles on the element. and set only the styles passed in style_names." + ), + set_link: z + .object({ + ...DEElementIDSchema, + linkType: z + .enum(["url", "file", "page", "element", "email", "phone"]) + .describe("The type of the link to update."), + link: z + .string() + .describe( + "The link to set on the element. for page pass page id, for element pass json string of id object. e.g id:{component:123,element:456}. for email pass email address. for phone pass phone number. for file pass asset id. for url pass url." + ), + }) + .optional() + .describe("Set link on the element"), + set_heading_level: z + .object({ + ...DEElementIDSchema, + heading_level: z + .number() + .min(1) + .max(6) + .describe("The heading level to set on the element. 1 to 6."), + }) + .optional() + .describe("Set heading level on the heading element."), + set_image_asset: z + .object({ + ...DEElementIDSchema, + image_asset_id: z + .string() + .describe("The image asset id to set on the element."), + }) + .optional() + .describe("Set image asset on the image element"), + }) + ), + }, }, async ({ actions, siteId }) => { try { - return formatResponse( - await elementToolRPCCall(siteId, actions) - ); + return formatResponse(await elementToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } diff --git a/src/tools/dePages.ts b/src/tools/dePages.ts index 4cde9a4..58451aa 100644 --- a/src/tools/dePages.ts +++ b/src/tools/dePages.ts @@ -2,100 +2,82 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; import { SiteIdSchema } from "../schemas"; -import { - formatErrorResponse, - formatResponse, -} from "../utils"; +import { formatErrorResponse, formatResponse } from "../utils"; -export function registerDEPagesTools( - server: McpServer, - rpc: RPCType -) { - const pageToolRPCCall = async ( - siteId: string, - actions: any - ) => { +export function registerDEPagesTools(server: McpServer, rpc: RPCType) { + const pageToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("page_tool", { siteId, actions: actions || [], }); }; - server.tool( + server.registerTool( "de_page_tool", - "Designer Tool - Page tool to perform actions like create page, create page folder, get current page, switch page", { - ...SiteIdSchema, - actions: z.array( - z.object({ - create_page: z - .object({ - page_name: z - .string() - .describe("The name of the page to create"), - meta_title: z - .string() - .describe( - "The meta title of the page to create" - ), - meta_description: z - .string() - .optional() - .describe( - "The meta description of the page to create" - ), - page_parent_folder_id: z - .string() - .optional() - .describe( - "The id of the parent page folder to create the page in" - ), - }) - .optional() - .describe("Create new page"), - create_page_folder: z - .object({ - page_folder_name: z - .string() - .describe( - "The name of the page folder to create" - ), - page_folder_parent_id: z - .string() - .optional() - .describe( - "The id of the parent page folder to create the page folder in" - ), - }) - .optional() - .describe("Create new page folder"), + title: "Designer Page Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Page tool to perform actions like create page, create page folder, get current page, switch page", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + create_page: z + .object({ + page_name: z + .string() + .describe("The name of the page to create"), + meta_title: z + .string() + .describe("The meta title of the page to create"), + meta_description: z + .string() + .optional() + .describe("The meta description of the page to create"), + page_parent_folder_id: z + .string() + .optional() + .describe( + "The id of the parent page folder to create the page in" + ), + }) + .optional() + .describe("Create new page"), + create_page_folder: z + .object({ + page_folder_name: z + .string() + .describe("The name of the page folder to create"), + page_folder_parent_id: z + .string() + .optional() + .describe( + "The id of the parent page folder to create the page folder in" + ), + }) + .optional() + .describe("Create new page folder"), - get_current_page: z - .boolean() - .optional() - .describe( - "Get current page active on webflow designer" - ), - switch_page: z - .object({ - page_id: z - .string() - .describe( - "The id of the page to switch to" - ), - }) - .optional() - .describe( - "Switch to a page on webflow designer" - ), - }) - ), + get_current_page: z + .boolean() + .optional() + .describe("Get current page active on webflow designer"), + switch_page: z + .object({ + page_id: z.string().describe("The id of the page to switch to"), + }) + .optional() + .describe("Switch to a page on webflow designer"), + }) + ), + }, }, async ({ siteId, actions }) => { try { - return formatResponse( - await pageToolRPCCall(siteId, actions) - ); + return formatResponse(await pageToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } diff --git a/src/tools/deStyle.ts b/src/tools/deStyle.ts index c7914c0..33a66ae 100644 --- a/src/tools/deStyle.ts +++ b/src/tools/deStyle.ts @@ -2,195 +2,175 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; import { SiteIdSchema } from "../schemas"; -import { - formatErrorResponse, - formatResponse, - supportDEStyles, -} from "../utils"; +import { formatErrorResponse, formatResponse, supportDEStyles } from "../utils"; -export function registerDEStyleTools( - server: McpServer, - rpc: RPCType -) { - const styleToolRPCCall = async ( - siteId: string, - actions: any - ) => { +export function registerDEStyleTools(server: McpServer, rpc: RPCType) { + const styleToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("style_tool", { siteId, actions: actions || [], }); }; - server.tool( + server.registerTool( "style_tool", - "Designer Tool - Style tool to perform actions like create style, get all styles, update styles", { - ...SiteIdSchema, - actions: z.array( - z.object({ - create_style: z - .object({ - name: z - .string() - .describe("The name of the style"), - properties: z - .array( - z.object({ - property_name: z - .string() - .describe("The name of the property"), - property_value: z - .string() - .optional() - .describe( - "The value of the property" - ), - variable_as_value: z - .string() - .optional() - .describe( - "The variable id to use as the value" - ), - }) - ) - .describe( - "The properties of the style. if you are looking to link a variable as the value, then use the variable_as_value field. but do not use both property_value and variable_as_value" - ), - parent_style_name: z - .string() - .optional() - .describe( - "The name of the parent style to create the new style in. this will use to create combo class" - ), - }) - .optional() - .describe("Create a new style"), - get_styles: z - .object({ - skip_properties: z - .boolean() - .optional() - .describe( - "Whether to skip the properties of the style. to get minimal data." - ), - include_all_breakpoints: z - .boolean() - .optional() - .describe( - "Whether to include all breakpoints styles or not. very data intensive." - ), - query: z - .enum(["all", "filtered"]) - .describe( - "The query to get all styles or filtered styles" - ), - filter_ids: z - .array(z.string()) - .optional() - .describe( - "The ids of the styles to filter by. should be used with query filtered" - ), - }) - .optional() - .describe("Get all styles"), - update_style: z - .object({ - style_name: z - .string() - .describe( - "The name of the style to update" - ), - breakpoint_id: z - .enum([ - "xxl", - "xl", - "large", - "main", - "medium", - "small", - "tiny", - ]) - .optional() - .describe( - "The breakpoint to update the style for" - ), - pseudo: z - .enum([ - "noPseudo", - "nth-child(odd)", - "nth-child(even)", - "first-child", - "last-child", - "hover", - "active", - "pressed", - "visited", - "focus", - "focus-visible", - "focus-within", - "placeholder", - "empty", - "before", - "after", - ]) - .optional() - .describe( - "The pseudo class to update the style for" - ), - properties: z - .array( - z.object({ - property_name: z - .string() - .describe("The name of the property"), - property_value: z - .string() - .optional() - .describe( - "The value of the property" - ), - variable_as_value: z - .string() - .optional() - .describe( - "The variable id to use as the value" - ), - }) - ) - .optional() - .describe( - "The properties to update or add to the style for" - ), - remove_properties: z - .array(z.string()) - .optional() - .describe( - "The properties to remove from the style" - ), - }) - .optional() - .describe("Update a style"), - }) - ), + title: "Designer Style Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Style tool to perform actions like create style, get all styles, update styles", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + create_style: z + .object({ + name: z.string().describe("The name of the style"), + properties: z + .array( + z.object({ + property_name: z + .string() + .describe("The name of the property"), + property_value: z + .string() + .optional() + .describe("The value of the property"), + variable_as_value: z + .string() + .optional() + .describe("The variable id to use as the value"), + }) + ) + .describe( + "The properties of the style. if you are looking to link a variable as the value, then use the variable_as_value field. but do not use both property_value and variable_as_value" + ), + parent_style_name: z + .string() + .optional() + .describe( + "The name of the parent style to create the new style in. this will use to create combo class" + ), + }) + .optional() + .describe("Create a new style"), + get_styles: z + .object({ + skip_properties: z + .boolean() + .optional() + .describe( + "Whether to skip the properties of the style. to get minimal data." + ), + include_all_breakpoints: z + .boolean() + .optional() + .describe( + "Whether to include all breakpoints styles or not. very data intensive." + ), + query: z + .enum(["all", "filtered"]) + .describe("The query to get all styles or filtered styles"), + filter_ids: z + .array(z.string()) + .optional() + .describe( + "The ids of the styles to filter by. should be used with query filtered" + ), + }) + .optional() + .describe("Get all styles"), + update_style: z + .object({ + style_name: z + .string() + .describe("The name of the style to update"), + breakpoint_id: z + .enum([ + "xxl", + "xl", + "large", + "main", + "medium", + "small", + "tiny", + ]) + .optional() + .describe("The breakpoint to update the style for"), + pseudo: z + .enum([ + "noPseudo", + "nth-child(odd)", + "nth-child(even)", + "first-child", + "last-child", + "hover", + "active", + "pressed", + "visited", + "focus", + "focus-visible", + "focus-within", + "placeholder", + "empty", + "before", + "after", + ]) + .optional() + .describe("The pseudo class to update the style for"), + properties: z + .array( + z.object({ + property_name: z + .string() + .describe("The name of the property"), + property_value: z + .string() + .optional() + .describe("The value of the property"), + variable_as_value: z + .string() + .optional() + .describe("The variable id to use as the value"), + }) + ) + .optional() + .describe("The properties to update or add to the style for"), + remove_properties: z + .array(z.string()) + .optional() + .describe("The properties to remove from the style"), + }) + .optional() + .describe("Update a style"), + }) + ), + }, }, async ({ siteId, actions }) => { try { - return formatResponse( - await styleToolRPCCall(siteId, actions) - ); + return formatResponse(await styleToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } } ); - server.tool( + server.registerTool( "de_learn_more_about_styles", - "Designer tool - Learn more about styles supported by Webflow Designer." + - "Please do not use any other styles which is not supported by Webflow Designer." + - "Please use the long-form alias of a CSS property when managing styles. For example, the property row-gap has a long-form alias of grid-row-gap, margin has long-form alias of margin-top, margin-right, margin-bottom, margin-left, etc.", - {}, + { + title: "Designer Learn More About Webflow Styles", + annotations: { + readOnlyHint: true, + }, + description: + "Designer tool - Learn more about styles supported by Webflow Designer." + + "Please do not use any other styles which is not supported by Webflow Designer." + + "Please use the long-form alias of a CSS property when managing styles. For example, the property row-gap has a long-form alias of grid-row-gap, margin has long-form alias of margin-top, margin-right, margin-bottom, margin-left, etc.", + inputSchema: {}, + }, async ({}) => formatResponse(supportDEStyles) ); } diff --git a/src/tools/deVariable.ts b/src/tools/deVariable.ts index ed04e8e..9849235 100644 --- a/src/tools/deVariable.ts +++ b/src/tools/deVariable.ts @@ -2,234 +2,214 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; import z from "zod"; import { SiteIdSchema } from "../schemas"; -import { - formatErrorResponse, - formatResponse, -} from "../utils"; +import { formatErrorResponse, formatResponse } from "../utils"; -export function registerDEVariableTools( - server: McpServer, - rpc: RPCType -) { - const variableToolRPCCall = async ( - siteId: string, - actions: any - ) => { +export function registerDEVariableTools(server: McpServer, rpc: RPCType) { + const variableToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("variable_tool", { siteId, actions: actions || [], }); }; - server.tool( + server.registerTool( "variable_tool", - "Designer Tool - Variable tool to perform actions like create variable, get all variables, update variable", { - ...SiteIdSchema, - actions: z.array( - z.object({ - create_variable_collection: z - .object({ - name: z - .string() - .describe( - "The name of the variable collection to create" - ), - }) - .optional() - .describe("Create a new variable collection"), - create_variable_mode: z - .object({ - variable_collection_id: z - .string() - .describe( - "The id of the variable collection to create the variable mode in" - ), - name: z - .string() - .describe( - "The name of the variable mode to create" - ), - }) - .optional() - .describe( - "Create a new variable mode in a variable collection" - ), - get_variable_collections: z - .object({ - query: z - .enum(["all", "filtered"]) - .describe( - "The query to get all variable collections" - ), - filter_collections_by_ids: z - .array(z.string()) - .optional() - .describe( - "The ids of the variable collections to filter by" - ), - }) - .optional() - .describe( - "Get all variable collections and its modes" - ), - get_variables: z - .object({ - variable_collection_id: z - .string() - .describe( - "The id of the variable collection to get the variables from" - ), - include_all_modes: z - .boolean() - .optional() - .describe( - "Whether to include all modes or not" - ), - filter_variables_by_ids: z - .array(z.string()) - .optional() - .describe( - "The ids of the variables to filter by" - ), - }) - .optional() - .describe( - "Get all variables in a variable collection and its modes" - ), - create_color_variable: z - .object({ - variable_collection_id: z.string(), - variable_name: z.string(), - value: z.object({ - static_value: z.string().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Create a new color variable"), - create_size_variable: z - .object({ - variable_collection_id: z.string(), - variable_name: z.string(), - value: z.object({ - static_value: z - .object({ - value: z.number(), - unit: z.string(), - }) - .optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Create a new size variable"), - create_number_variable: z - .object({ - variable_collection_id: z.string(), - variable_name: z.string(), - value: z.object({ - static_value: z.number().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Create a new number variable"), - create_percentage_variable: z - .object({ - variable_collection_id: z.string(), - variable_name: z.string(), - value: z.object({ - static_value: z.number().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Create a new percentage variable"), - create_font_family_variable: z - .object({ - variable_collection_id: z.string(), - variable_name: z.string(), - value: z.object({ - static_value: z.string().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Create a new font family variable"), - update_color_variable: z - .object({ - variable_collection_id: z.string(), - variable_id: z.string(), - mode_id: z.string().optional(), - value: z.object({ - static_value: z.string().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Update a color variable"), - update_size_variable: z - .object({ - variable_collection_id: z.string(), - variable_id: z.string(), - mode_id: z.string().optional(), - value: z.object({ - static_value: z - .object({ - value: z.number(), - unit: z.string(), - }) - .optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Update a size variable"), - update_number_variable: z - .object({ - variable_collection_id: z.string(), - variable_id: z.string(), - mode_id: z.string().optional(), - value: z.object({ - static_value: z.number().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Update a number variable"), - update_percentage_variable: z - .object({ - variable_collection_id: z.string(), - variable_id: z.string(), - mode_id: z.string().optional(), - value: z.object({ - static_value: z.number().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Update a percentage variable"), - update_font_family_variable: z - .object({ - variable_collection_id: z.string(), - variable_id: z.string(), - mode_id: z.string().optional(), - value: z.object({ - static_value: z.string().optional(), - existing_variable_id: z.string().optional(), - }), - }) - .optional() - .describe("Update a font family variable"), - }) - ), + title: "Designer Variable Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Designer Tool - Variable tool to perform actions like create variable, get all variables, update variable", + inputSchema: { + ...SiteIdSchema, + actions: z.array( + z.object({ + create_variable_collection: z + .object({ + name: z + .string() + .describe("The name of the variable collection to create"), + }) + .optional() + .describe("Create a new variable collection"), + create_variable_mode: z + .object({ + variable_collection_id: z + .string() + .describe( + "The id of the variable collection to create the variable mode in" + ), + name: z + .string() + .describe("The name of the variable mode to create"), + }) + .optional() + .describe("Create a new variable mode in a variable collection"), + get_variable_collections: z + .object({ + query: z + .enum(["all", "filtered"]) + .describe("The query to get all variable collections"), + filter_collections_by_ids: z + .array(z.string()) + .optional() + .describe("The ids of the variable collections to filter by"), + }) + .optional() + .describe("Get all variable collections and its modes"), + get_variables: z + .object({ + variable_collection_id: z + .string() + .describe( + "The id of the variable collection to get the variables from" + ), + include_all_modes: z + .boolean() + .optional() + .describe("Whether to include all modes or not"), + filter_variables_by_ids: z + .array(z.string()) + .optional() + .describe("The ids of the variables to filter by"), + }) + .optional() + .describe( + "Get all variables in a variable collection and its modes" + ), + create_color_variable: z + .object({ + variable_collection_id: z.string(), + variable_name: z.string(), + value: z.object({ + static_value: z.string().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Create a new color variable"), + create_size_variable: z + .object({ + variable_collection_id: z.string(), + variable_name: z.string(), + value: z.object({ + static_value: z + .object({ + value: z.number(), + unit: z.string(), + }) + .optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Create a new size variable"), + create_number_variable: z + .object({ + variable_collection_id: z.string(), + variable_name: z.string(), + value: z.object({ + static_value: z.number().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Create a new number variable"), + create_percentage_variable: z + .object({ + variable_collection_id: z.string(), + variable_name: z.string(), + value: z.object({ + static_value: z.number().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Create a new percentage variable"), + create_font_family_variable: z + .object({ + variable_collection_id: z.string(), + variable_name: z.string(), + value: z.object({ + static_value: z.string().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Create a new font family variable"), + update_color_variable: z + .object({ + variable_collection_id: z.string(), + variable_id: z.string(), + mode_id: z.string().optional(), + value: z.object({ + static_value: z.string().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Update a color variable"), + update_size_variable: z + .object({ + variable_collection_id: z.string(), + variable_id: z.string(), + mode_id: z.string().optional(), + value: z.object({ + static_value: z + .object({ + value: z.number(), + unit: z.string(), + }) + .optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Update a size variable"), + update_number_variable: z + .object({ + variable_collection_id: z.string(), + variable_id: z.string(), + mode_id: z.string().optional(), + value: z.object({ + static_value: z.number().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Update a number variable"), + update_percentage_variable: z + .object({ + variable_collection_id: z.string(), + variable_id: z.string(), + mode_id: z.string().optional(), + value: z.object({ + static_value: z.number().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Update a percentage variable"), + update_font_family_variable: z + .object({ + variable_collection_id: z.string(), + variable_id: z.string(), + mode_id: z.string().optional(), + value: z.object({ + static_value: z.string().optional(), + existing_variable_id: z.string().optional(), + }), + }) + .optional() + .describe("Update a font family variable"), + }) + ), + }, }, async ({ siteId, actions }) => { try { - return formatResponse( - await variableToolRPCCall(siteId, actions) - ); + return formatResponse(await variableToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } diff --git a/src/tools/localDeMCPConnection.ts b/src/tools/localDeMCPConnection.ts index 23b98af..80a6922 100644 --- a/src/tools/localDeMCPConnection.ts +++ b/src/tools/localDeMCPConnection.ts @@ -1,10 +1,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { RPCType } from "../types/RPCType"; -import { - formatErrorResponse, - formatResponse, -} from "../utils/formatResponse"; +import { formatErrorResponse, formatResponse } from "../utils/formatResponse"; export function registerLocalDeMCPConnectionTools( server: McpServer, rpc: RPCType @@ -13,15 +10,20 @@ export function registerLocalDeMCPConnectionTools( return rpc.callTool("local_de_mcp_connection_tool", {}); }; - server.tool( + server.registerTool( "get_designer_app_connection_info", - "Get Webflow MCP App Connection Info. if user ask to get Webflow MCP app connection info, use this tool", - {}, + { + title: "Get Webflow MCP App Connection Info", + annotations: { + readOnlyHint: true, + }, + description: + "Get Webflow MCP App Connection Info. if user ask to get Webflow MCP app connection info, use this tool", + inputSchema: {}, + }, async () => { try { - return formatResponse( - await localDeMCPConnectionToolRPCCall() - ); + return formatResponse(await localDeMCPConnectionToolRPCCall()); } catch (error) { return formatErrorResponse(error); } diff --git a/src/tools/pages.ts b/src/tools/pages.ts index 0d26ca6..4b37d81 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -99,116 +99,123 @@ export function registerPagesTools( return response; }; - server.tool( + server.registerTool( "data_pages_tool", - "Data tool - Pages tool to perform actions like list pages, get page metadata, update page settings, get page content, and update static content", { - actions: z.array( - z.object({ - // GET https://api.webflow.com/v2/sites/:site_id/pages - list_pages: z - .object({ - site_id: z - .string() - .describe("The site's unique ID, used to list its pages."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - }) - .optional() - .describe( - "List all pages within a site. Returns page metadata including IDs, titles, and slugs." - ), - // GET https://api.webflow.com/v2/pages/:page_id - get_page_metadata: z - .object({ - page_id: z.string().describe("Unique identifier for the page."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - }) - .optional() - .describe( - "Get metadata for a specific page including SEO settings, Open Graph data, and page status (draft/published)." - ), - // PUT https://api.webflow.com/v2/pages/:page_id - update_page_settings: z - .object({ - page_id: z.string().describe("Unique identifier for the page."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - body: WebflowPageSchema, - }) - .optional() - .describe( - "Update page settings including SEO metadata, Open Graph data, slug, and publishing status." - ), - // GET https://api.webflow.com/v2/pages/:page_id/dom - get_page_content: z - .object({ - page_id: z.string().describe("Unique identifier for the page."), - localeId: z - .string() - .optional() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - limit: z - .number() - .optional() - .describe( - "Maximum number of records to be returned (max limit: 100)" - ), - offset: z - .number() - .optional() - .describe( - "Offset used for pagination if the results have more than limit records." - ), - }) - .optional() - .describe( - "Get the content structure and data for a specific page including all elements and their properties for localization." - ), - // POST https://api.webflow.com/v2/pages/:page_id/dom - update_static_content: z - .object({ - page_id: z.string().describe("Unique identifier for the page."), - localeId: z - .string() - .describe( - "Unique identifier for a specific locale. Applicable when using localization." - ), - nodes: WebflowPageDomWriteNodesItemSchema, - }) - .optional() - .describe( - "Update content on a static page in secondary locales by modifying text nodes and property overrides." - ), - }) - ), + title: "Data Pages Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Data tool - Pages tool to perform actions like list pages, get page metadata, update page settings, get page content, and update static content", + inputSchema: { + actions: z.array( + z.object({ + // GET https://api.webflow.com/v2/sites/:site_id/pages + list_pages: z + .object({ + site_id: z + .string() + .describe("The site's unique ID, used to list its pages."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + }) + .optional() + .describe( + "List all pages within a site. Returns page metadata including IDs, titles, and slugs." + ), + // GET https://api.webflow.com/v2/pages/:page_id + get_page_metadata: z + .object({ + page_id: z.string().describe("Unique identifier for the page."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + }) + .optional() + .describe( + "Get metadata for a specific page including SEO settings, Open Graph data, and page status (draft/published)." + ), + // PUT https://api.webflow.com/v2/pages/:page_id + update_page_settings: z + .object({ + page_id: z.string().describe("Unique identifier for the page."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + body: WebflowPageSchema, + }) + .optional() + .describe( + "Update page settings including SEO metadata, Open Graph data, slug, and publishing status." + ), + // GET https://api.webflow.com/v2/pages/:page_id/dom + get_page_content: z + .object({ + page_id: z.string().describe("Unique identifier for the page."), + localeId: z + .string() + .optional() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + limit: z + .number() + .optional() + .describe( + "Maximum number of records to be returned (max limit: 100)" + ), + offset: z + .number() + .optional() + .describe( + "Offset used for pagination if the results have more than limit records." + ), + }) + .optional() + .describe( + "Get the content structure and data for a specific page including all elements and their properties for localization." + ), + // POST https://api.webflow.com/v2/pages/:page_id/dom + update_static_content: z + .object({ + page_id: z.string().describe("Unique identifier for the page."), + localeId: z + .string() + .describe( + "Unique identifier for a specific locale. Applicable when using localization." + ), + nodes: WebflowPageDomWriteNodesItemSchema, + }) + .optional() + .describe( + "Update content on a static page in secondary locales by modifying text nodes and property overrides." + ), + }) + ), + }, }, async ({ actions }) => { const result: Content[] = []; diff --git a/src/tools/rules.ts b/src/tools/rules.ts index 657042a..3dcbcdd 100644 --- a/src/tools/rules.ts +++ b/src/tools/rules.ts @@ -1,10 +1,18 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerRulesTools(server: McpServer) { - server.tool( + server.registerTool( "webflow_guide_tool", - "Provides essential guidelines and best practices for effectively using the Webflow tools. Call this tool to understand recommended workflows and important considerations before performing actions. ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS. ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS. ", - {}, + { + title: "Webflow Guide Tool", + annotations: { + readOnlyHint: true, + openWorldHint: false, + }, + description: + "Provides essential guidelines and best practices for effectively using the Webflow tools. Call this tool to understand recommended workflows and important considerations before performing actions. ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS. ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS. ", + inputSchema: {}, + }, async ({}) => ({ content: [ { diff --git a/src/tools/scripts.ts b/src/tools/scripts.ts index dcf6c2e..b40a92e 100644 --- a/src/tools/scripts.ts +++ b/src/tools/scripts.ts @@ -104,51 +104,58 @@ export function registerScriptsTools( } }; - server.tool( + server.registerTool( "data_scripts_tool", - "Data tool - Scripts tool to perform actions like list registered scripts, list applied scripts, add inline site script, and delete all site scripts", { - actions: z.array( - z.object({ - // GET https://api.webflow.com/v2/sites/:site_id/registered_scripts - list_registered_scripts: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - }) - .optional() - .describe( - "List all registered scripts for a site. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints." - ), - // GET https://api.webflow.com/v2/sites/:site_id/custom_code - list_applied_scripts: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - }) - .optional() - .describe( - "Get all scripts applied to a site by the App. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints." - ), - // POST https://api.webflow.com/v2/sites/:site_id/registered_scripts/inline - add_inline_site_script: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - request: RegisterInlineSiteScriptSchema, - }) - .optional() - .describe( - "Register an inline script for a site. Inline scripts are limited to 2000 characters." - ), - // DELETE https://api.webflow.com/v2/sites/:site_id/custom_code - delete_all_site_scripts: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - }) - .optional() - .describe( - "Delete all custom scripts applied to a site by the App." - ), - }) - ), + title: "Data Scripts Tool", + annotations: { + readOnlyHint: false, + }, + description: + "Data tool - Scripts tool to perform actions like list registered scripts, list applied scripts, add inline site script, and delete all site scripts", + inputSchema: { + actions: z.array( + z.object({ + // GET https://api.webflow.com/v2/sites/:site_id/registered_scripts + list_registered_scripts: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + }) + .optional() + .describe( + "List all registered scripts for a site. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints." + ), + // GET https://api.webflow.com/v2/sites/:site_id/custom_code + list_applied_scripts: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + }) + .optional() + .describe( + "Get all scripts applied to a site by the App. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints." + ), + // POST https://api.webflow.com/v2/sites/:site_id/registered_scripts/inline + add_inline_site_script: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + request: RegisterInlineSiteScriptSchema, + }) + .optional() + .describe( + "Register an inline script for a site. Inline scripts are limited to 2000 characters." + ), + // DELETE https://api.webflow.com/v2/sites/:site_id/custom_code + delete_all_site_scripts: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + }) + .optional() + .describe( + "Delete all custom scripts applied to a site by the App." + ), + }) + ), + }, }, async ({ actions }) => { const result: Content[] = []; diff --git a/src/tools/sites.ts b/src/tools/sites.ts index ca8840c..e8d8b67 100644 --- a/src/tools/sites.ts +++ b/src/tools/sites.ts @@ -39,47 +39,50 @@ export function registerSiteTools( return response; }; - server.tool( + server.registerTool( "data_sites_tool", - "Data tool - Sites tool to perform actions like list sites, get site details, and publish sites", { - actions: z.array( - z.object({ - // GET https://api.webflow.com/v2/sites - list_sites: z - .object({}) - .optional() - .describe( - "List all sites accessible to the authenticated user. Returns basic site information including site ID, name, and last published date." - ), - // GET https://api.webflow.com/v2/sites/:site_id - get_site: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - }) - .optional() - .describe( - "Get detailed information about a specific site including its settings, domains, and publishing status." - ), - // POST https://api.webflow.com/v2/sites/:site_id/publish - publish_site: z - .object({ - site_id: z.string().describe("Unique identifier for the site."), - customDomains: z - .array(z.string()) - .optional() - .describe("Array of custom domains to publish the site to."), - publishToWebflowSubdomain: z - .boolean() - .optional() - .describe("Whether to publish to the Webflow subdomain."), - }) - .optional() - .describe( - "Publish a site to specified domains. This will make the latest changes live on the specified domains." - ), - }) - ), + description: + "Data tool - Sites tool to perform actions like list sites, get site details, and publish sites", + inputSchema: { + actions: z.array( + z.object({ + // GET https://api.webflow.com/v2/sites + list_sites: z + .object({}) + .optional() + .describe( + "List all sites accessible to the authenticated user. Returns basic site information including site ID, name, and last published date." + ), + // GET https://api.webflow.com/v2/sites/:site_id + get_site: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + }) + .optional() + .describe( + "Get detailed information about a specific site including its settings, domains, and publishing status." + ), + // POST https://api.webflow.com/v2/sites/:site_id/publish + publish_site: z + .object({ + site_id: z.string().describe("Unique identifier for the site."), + customDomains: z + .array(z.string()) + .optional() + .describe("Array of custom domains to publish the site to."), + publishToWebflowSubdomain: z + .boolean() + .optional() + .describe("Whether to publish to the Webflow subdomain."), + }) + .optional() + .describe( + "Publish a site to specified domains. This will make the latest changes live on the specified domains." + ), + }) + ), + }, }, async ({ actions }) => { const result: Content[] = [];