Skip to content

feat(de-components): add per-instance property override actions#127

Open
shaynelarocque wants to merge 1 commit intowebflow:mainfrom
shaynelarocque:feat/de-component-set-instance-props
Open

feat(de-components): add per-instance property override actions#127
shaynelarocque wants to merge 1 commit intowebflow:mainfrom
shaynelarocque:feat/de-component-set-instance-props

Conversation

@shaynelarocque
Copy link
Copy Markdown

Refs #96.

Heads up: this PR was drafted by Claude Code, aimed at the pain point in #96 (no MCP surface for setting per-instance component props — element_tool.set_text errors on instances, and Data API update_static_content rejects the primary locale). Totally fine if the team prefers to implement this themselves; wanted to put a concrete shape on the table.

What

Adds three actions to the existing de_component_tool, mirroring the Designer SDK's per-instance prop APIs on ComponentElement:

Action Wraps
set_component_instance_props setProps(entries)
get_component_instance_props getProps() / getResolvedProps() (via resolved: true)
reset_component_instance_props resetAllProps()

Schema

set_component_instance_props: {
  id: { component: string; element: string };
  overrides: Array<{ prop_id: string; value: unknown | null }>; // null = reset this prop
}

get_component_instance_props: {
  id: { component: string; element: string };
  resolved?: boolean; // default false -> returns entries with hasOverride
}

reset_component_instance_props: {
  id: { component: string; element: string };
}

Example

{
  "name": "de_component_tool",
  "arguments": {
    "siteId": "",
    "actions": [{
      "set_component_instance_props": {
        "id": { "component": "c_123", "element": "e_456" },
        "overrides": [{ "prop_id": "Text", "value": "Hello from MCP" }]
      }
    }]
  }
}

⚠️ Companion change required

The MCP server is a generic RPC forwarder (src/modules/designerAppBridge.ts). These actions are emitted verbatim over the socket to the Webflow MCP Bridge App, which needs a matching handler mapping each action to the SDK call. This PR ships the MCP-side schema only — a companion Bridge-App change is required for #96 to actually close.

I intentionally used Refs #96 rather than Closes #96 for that reason.

Testing

  • npm run build clean (before and after).
  • npx tsc --noEmit clean.
  • Zod schema smoke cases covered: static string / number / boolean overrides, binding-object value, null reset-per-prop, empty-overrides rejected, resolved: true, reset-only action, empty-action rejected.
  • No live Designer smoke test (couldn't attach to a session from where I'm working).
  • Repo has no test framework; did not introduce one.

Files

  • src/tools/deComponents.ts — three new action keys on de_component_tool, refine guard updated.
  • src/tools/rules.ts — three guidance lines added to webflow_guide_tool.

Adds three actions to de_component_tool that wrap the Designer SDK's
per-instance prop APIs on ComponentElement:

- set_component_instance_props  -> setProps
- get_component_instance_props  -> getProps / getResolvedProps
- reset_component_instance_props -> resetAllProps

Unblocks populating text on component instances (Heading text, Button
label, Rich Text content) which element_tool.set_text cannot reach
and Data API update_static_content rejects for the primary locale.

Refs webflow#96.
@shaynelarocque shaynelarocque requested a review from a team as a code owner April 24, 2026 20:31
@shaynelarocque shaynelarocque requested review from tterb and removed request for a team April 24, 2026 20:31
Copy link
Copy Markdown

@tterb tterb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look good to me, but it might be worth getting a quick check from @memo-pineda since he has a lot more MCP context 👍

Comment thread src/tools/deComponents.ts
Comment on lines +255 to +256
value: z
.any()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think z.any() allows undefined, functions, ect... Is that the behavior that we want or would something like this be better here?

z.union([
  z.string(), z.number(), z.boolean(), z.null(),
  z.record(z.unknown()),
])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants