Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8529d5e
Create webflow_logo.png (#62)
jamesmosier Sep 11, 2025
b734951
docs: updated developer mode docs.
victoriaplummer Sep 16, 2025
88b662c
fine grained directions for windsurf and claude
victoriaplummer Sep 16, 2025
0ed3d91
differentiate between local and remote
victoriaplummer Sep 16, 2025
e582be3
adjusted instructions
victoriaplummer Sep 16, 2025
9426b48
updated local instructions
victoriaplummer Sep 16, 2025
15b7d61
removd local installation info
victoriaplummer Sep 16, 2025
478ecfd
added local installation information
victoriaplummer Sep 16, 2025
c20f9fe
added compatibility section and auth section back
victoriaplummer Sep 16, 2025
45503cf
Merge pull request #63 from webflow/designer-readmer
victoriaplummer Sep 17, 2025
40129c5
Merge branch 'main' into local-installation
victoriaplummer Sep 17, 2025
65fa68a
updated docs
victoriaplummer Sep 17, 2025
5709c98
Update README.md
viratatwebflow Sep 17, 2025
4c61ba8
Update README.md : Remote MCP instruction for cursor
viratatwebflow Sep 17, 2025
190435f
Update README.md
viratatwebflow Sep 17, 2025
ca01e12
Merge pull request #64 from webflow/local-installation
zplata Sep 17, 2025
36fd48c
Enhance response formatting with new types and utility functions
viratatwebflow Sep 30, 2025
e30e6cf
CMS Tool consolidation
viratatwebflow Sep 30, 2025
5d0a445
Sites tool consolidation
viratatwebflow Sep 30, 2025
f7ac218
Script tool consolidation
viratatwebflow Sep 30, 2025
e4c21ca
Data Page tool consolidation
viratatwebflow Sep 30, 2025
fef7bcc
Data Component tool consolidation
viratatwebflow Sep 30, 2025
81a368c
fixed cms tool description
viratatwebflow Sep 30, 2025
791097c
Refactor CMS tool for improved type safety and consistency
viratatwebflow Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 178 additions & 170 deletions README.md

Large diffs are not rendered by default.

857 changes: 500 additions & 357 deletions src/tools/cms.ts

Large diffs are not rendered by default.

423 changes: 251 additions & 172 deletions src/tools/components.ts

Large diffs are not rendered by default.

382 changes: 221 additions & 161 deletions src/tools/pages.ts

Large diffs are not rendered by default.

258 changes: 157 additions & 101 deletions src/tools/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,128 +4,184 @@ import { ScriptApplyLocation } from "webflow-api/api/types/ScriptApplyLocation";
import { z } from "zod";
import { requestOptions } from "../mcp";
import { RegisterInlineSiteScriptSchema } from "../schemas";
import { formatErrorResponse, formatResponse, isApiError } from "../utils";
import {
type Content,
formatErrorResponse,
textContent,
toolResponse,
isApiError,
} from "../utils";

export function registerScriptsTools(
server: McpServer,
getClient: () => WebflowClient
) {
// GET https://api.webflow.com/v2/sites/:site_id/registered_scripts
server.tool(
"site_registered_scripts_list",
"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.",
{
site_id: z.string().describe("Unique identifier for the site."),
},
async ({ site_id }) => {
try {
const response = await getClient().scripts.list(
site_id,
requestOptions
);
return formatResponse(response);
} catch (error) {
return formatErrorResponse(error);
}
}
);
const listRegisteredScripts = async (arg: { site_id: string }) => {
const response = await getClient().scripts.list(
arg.site_id,
requestOptions
);
return response;
};

// GET https://api.webflow.com/v2/sites/:site_id/custom_code
server.tool(
"site_applied_scripts_list",
"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.",
{
site_id: z.string().describe("Unique identifier for the site."),
},
async ({ site_id }) => {
try {
const response = await getClient().sites.scripts.getCustomCode(
site_id,
requestOptions
);
return formatResponse(response);
} catch (error) {
return formatErrorResponse(error);
}
}
);
const listAppliedScripts = async (arg: { site_id: string }) => {
const response = await getClient().sites.scripts.getCustomCode(
arg.site_id,
requestOptions
);
return response;
};

// POST https://api.webflow.com/v2/sites/:site_id/registered_scripts/inline
server.tool(
"add_inline_site_script",
"Register an inline script for a site. Inline scripts are limited to 2000 characters. ",
{
site_id: z.string().describe("Unique identifier for the site."),
request: RegisterInlineSiteScriptSchema,
},
async ({ site_id, request }) => {
const registerScriptResponse = await getClient().scripts.registerInline(
site_id,
{
sourceCode: request.sourceCode,
version: request.version,
displayName: request.displayName,
canCopy: request.canCopy !== undefined ? request.canCopy : true,
},
const addInlineSiteScript = async (arg: {
site_id: string;
request: {
sourceCode: string;
version: string;
displayName: string;
location?: string;
canCopy?: boolean;
attributes?: Record<string, any>;
};
}) => {
const registerScriptResponse = await getClient().scripts.registerInline(
arg.site_id,
{
sourceCode: arg.request.sourceCode,
version: arg.request.version,
displayName: arg.request.displayName,
canCopy: arg.request.canCopy !== undefined ? arg.request.canCopy : true,
},
requestOptions
);

let existingScripts: any[] = [];
try {
const allScriptsResponse = await getClient().sites.scripts.getCustomCode(
arg.site_id,
requestOptions
);
existingScripts = allScriptsResponse.scripts || [];
} catch (error) {
existingScripts = [];
}

let existingScripts: any[] = [];
try {
const allScriptsResponse =
await getClient().sites.scripts.getCustomCode(
site_id,
requestOptions
);
existingScripts = allScriptsResponse.scripts || [];
} catch (error) {
formatErrorResponse(error);
existingScripts = [];
}
const newScript = {
id: registerScriptResponse.id ?? " ",
location:
arg.request.location === "footer"
? ScriptApplyLocation.Footer
: ScriptApplyLocation.Header,
version: registerScriptResponse.version ?? " ",
attributes: arg.request.attributes,
};

const newScript = {
id: registerScriptResponse.id ?? " ",
location:
request.location === "footer"
? ScriptApplyLocation.Footer
: ScriptApplyLocation.Header,
version: registerScriptResponse.version ?? " ",
attributes: request.attributes,
};
existingScripts.push(newScript);

existingScripts.push(newScript);
await getClient().sites.scripts.upsertCustomCode(
arg.site_id,
{
scripts: existingScripts,
},
requestOptions
);

const addedSiteCustomCoderesponse =
await getClient().sites.scripts.upsertCustomCode(
site_id,
{
scripts: existingScripts,
},
requestOptions
);
return registerScriptResponse;
};

return formatResponse(registerScriptResponse);
const deleteAllSiteScripts = async (arg: { site_id: string }) => {
try {
await getClient().sites.scripts.deleteCustomCode(
arg.site_id,
requestOptions
);
return "Custom Code Deleted";
} catch (error) {
// If it's a 404, we'll try to clear the scripts another way
if (isApiError(error) && error.status === 404) {
return error.message ?? "No custom code found";
}
throw error;
}
);
};

server.tool(
"delete_all_site_scripts",
"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",
{
site_id: z.string(),
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 ({ site_id }) => {
async ({ actions }) => {
const result: Content[] = [];
try {
const response = await getClient().sites.scripts.deleteCustomCode(
site_id,
requestOptions
);
return formatResponse("Custom Code Deleted");
} catch (error) {
// If it's a 404, we'll try to clear the scripts another way
if (isApiError(error) && error.status === 404) {
return formatResponse(error.message ?? "No custom code found");
for (const action of actions) {
if (action.list_registered_scripts) {
const content = await listRegisteredScripts(
action.list_registered_scripts
);
result.push(textContent(content));
}
if (action.list_applied_scripts) {
const content = await listAppliedScripts(
action.list_applied_scripts
);
result.push(textContent(content));
}
if (action.add_inline_site_script) {
const content = await addInlineSiteScript(
action.add_inline_site_script
);
result.push(textContent(content));
}
if (action.delete_all_site_scripts) {
const content = await deleteAllSiteScripts(
action.delete_all_site_scripts
);
result.push(textContent(content));
}
}
throw error;
return toolResponse(result);
} catch (error) {
return formatErrorResponse(error);
}
}
);
Expand Down
Loading