flutterflow-mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -72,6 +72,15 @@ const TOPIC_MAP = {
|
|
|
72
72
|
scaffold: "02-pages.md",
|
|
73
73
|
components: "03-components.md",
|
|
74
74
|
component: "03-components.md",
|
|
75
|
+
createcomponent: "03-components.md",
|
|
76
|
+
refactor: "03-components.md",
|
|
77
|
+
refactoring: "03-components.md",
|
|
78
|
+
isdummyroot: "03-components.md",
|
|
79
|
+
dummyroot: "03-components.md",
|
|
80
|
+
componentclasskeyref: "03-components.md",
|
|
81
|
+
parametervalues: "03-components.md",
|
|
82
|
+
callback: "03-components.md",
|
|
83
|
+
executecallbackaction: "03-components.md",
|
|
75
84
|
// Universal patterns
|
|
76
85
|
inputvalue: "README.md",
|
|
77
86
|
mostrecentinputvalue: "README.md",
|
|
@@ -411,3 +411,294 @@ node:
|
|
|
411
411
|
- **Component node files** follow the exact same structure as page node files. The only difference is the file key prefix (`component/` vs `page/`).
|
|
412
412
|
- **The tree outline key** is `component-widget-tree-outline` (not `page-widget-tree-outline`).
|
|
413
413
|
- **CALLBACK triggers** appear when an Action parameter is executed from a child widget that is itself a component reference. The trigger key includes the param key (e.g., `CALLBACK-bins86`).
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 10. Creating a New Component
|
|
418
|
+
|
|
419
|
+
Creating a component requires pushing multiple files in a **single** `update_project_yaml` call, similar to adding widgets to a page.
|
|
420
|
+
|
|
421
|
+
### Required files
|
|
422
|
+
|
|
423
|
+
| # | File Key | Purpose |
|
|
424
|
+
|---|----------|---------|
|
|
425
|
+
| 1 | `component/id-Container_XXX` | Component metadata (name, params, state) |
|
|
426
|
+
| 2 | `component/id-Container_XXX/component-widget-tree-outline` | Widget tree hierarchy |
|
|
427
|
+
| 3 | `component/id-Container_XXX/component-widget-tree-outline/node/id-Container_XXX` | Root Container node (`isDummyRoot: true`) |
|
|
428
|
+
| 4 | `component/id-Container_XXX/component-widget-tree-outline/node/id-Widget_YYY` | One file per child widget |
|
|
429
|
+
|
|
430
|
+
### Step-by-step example
|
|
431
|
+
|
|
432
|
+
**Task:** Create a reusable `DealCard` component with `title` (String), `subtitle` (String), and `onTap` (Action) parameters.
|
|
433
|
+
|
|
434
|
+
**File 1 — Component metadata:**
|
|
435
|
+
```yaml
|
|
436
|
+
# File key: component/id-Container_dc01root
|
|
437
|
+
name: DealCard
|
|
438
|
+
description: "Reusable card displaying a deal with title, subtitle, and tap action"
|
|
439
|
+
params:
|
|
440
|
+
p1titl:
|
|
441
|
+
identifier:
|
|
442
|
+
name: title
|
|
443
|
+
key: p1titl
|
|
444
|
+
dataType:
|
|
445
|
+
scalarType: String
|
|
446
|
+
nonNullable: true
|
|
447
|
+
p2subt:
|
|
448
|
+
identifier:
|
|
449
|
+
name: subtitle
|
|
450
|
+
key: p2subt
|
|
451
|
+
dataType:
|
|
452
|
+
scalarType: String
|
|
453
|
+
nonNullable: false
|
|
454
|
+
p3tap:
|
|
455
|
+
identifier:
|
|
456
|
+
name: onTap
|
|
457
|
+
key: p3tap
|
|
458
|
+
dataType:
|
|
459
|
+
scalarType: Action
|
|
460
|
+
nonNullable: false
|
|
461
|
+
node:
|
|
462
|
+
key: Container_dc01root
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**File 2 — Widget tree outline:**
|
|
466
|
+
```yaml
|
|
467
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline
|
|
468
|
+
node:
|
|
469
|
+
key: Container_dc01root
|
|
470
|
+
children:
|
|
471
|
+
- key: Container_dc01card
|
|
472
|
+
children:
|
|
473
|
+
- key: Column_dc01col
|
|
474
|
+
children:
|
|
475
|
+
- key: Text_dc01titl
|
|
476
|
+
- key: Text_dc01sub
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**File 3 — Root Container node:**
|
|
480
|
+
```yaml
|
|
481
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline/node/id-Container_dc01root
|
|
482
|
+
key: Container_dc01root
|
|
483
|
+
type: Container
|
|
484
|
+
props:
|
|
485
|
+
container:
|
|
486
|
+
boxDecoration:
|
|
487
|
+
colorValue:
|
|
488
|
+
inputValue:
|
|
489
|
+
value: "0"
|
|
490
|
+
mostRecentInputValue:
|
|
491
|
+
value: "0"
|
|
492
|
+
name: DealCard
|
|
493
|
+
isDummyRoot: true
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
> The root Container must have `isDummyRoot: true` and a transparent background (`value: "0"`). This marks it as the component boundary — the actual visual content starts with the first child.
|
|
497
|
+
|
|
498
|
+
**File 4 — Card container node:**
|
|
499
|
+
```yaml
|
|
500
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline/node/id-Container_dc01card
|
|
501
|
+
key: Container_dc01card
|
|
502
|
+
type: Container
|
|
503
|
+
props:
|
|
504
|
+
container:
|
|
505
|
+
boxDecoration:
|
|
506
|
+
colorValue:
|
|
507
|
+
inputValue:
|
|
508
|
+
themeColor: SECONDARY_BACKGROUND
|
|
509
|
+
mostRecentInputValue:
|
|
510
|
+
themeColor: SECONDARY_BACKGROUND
|
|
511
|
+
borderRadiusValue:
|
|
512
|
+
inputValue: 12
|
|
513
|
+
mostRecentInputValue: 12
|
|
514
|
+
paddingValue:
|
|
515
|
+
inputValue: "16,16,16,16"
|
|
516
|
+
mostRecentInputValue: "16,16,16,16"
|
|
517
|
+
widthValue:
|
|
518
|
+
inputValue: "double.infinity"
|
|
519
|
+
mostRecentInputValue: "double.infinity"
|
|
520
|
+
responsiveVisibility: {}
|
|
521
|
+
parameterValues: {}
|
|
522
|
+
valueKey: {}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**File 5 — Column node:**
|
|
526
|
+
```yaml
|
|
527
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline/node/id-Column_dc01col
|
|
528
|
+
key: Column_dc01col
|
|
529
|
+
type: Column
|
|
530
|
+
props:
|
|
531
|
+
column:
|
|
532
|
+
crossAxisAlignmentValue:
|
|
533
|
+
inputValue: START
|
|
534
|
+
mostRecentInputValue: START
|
|
535
|
+
responsiveVisibility: {}
|
|
536
|
+
parameterValues: {}
|
|
537
|
+
valueKey: {}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**File 6 — Title text (references component parameter):**
|
|
541
|
+
```yaml
|
|
542
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline/node/id-Text_dc01titl
|
|
543
|
+
key: Text_dc01titl
|
|
544
|
+
type: Text
|
|
545
|
+
props:
|
|
546
|
+
text:
|
|
547
|
+
themeStyle: TITLE_MEDIUM
|
|
548
|
+
selectable: false
|
|
549
|
+
textValue:
|
|
550
|
+
variable:
|
|
551
|
+
source: WIDGET_CLASS_PARAMETER
|
|
552
|
+
baseVariable:
|
|
553
|
+
widgetClass:
|
|
554
|
+
paramIdentifier:
|
|
555
|
+
name: title
|
|
556
|
+
key: p1titl
|
|
557
|
+
nodeKeyRef:
|
|
558
|
+
key: Container_dc01root
|
|
559
|
+
colorValue:
|
|
560
|
+
inputValue:
|
|
561
|
+
themeColor: PRIMARY_TEXT
|
|
562
|
+
fontWeightValue:
|
|
563
|
+
inputValue: w600
|
|
564
|
+
responsiveVisibility: {}
|
|
565
|
+
parameterValues: {}
|
|
566
|
+
valueKey: {}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
> To display a component parameter, use `source: WIDGET_CLASS_PARAMETER` with `baseVariable.widgetClass.paramIdentifier` pointing to the param key. `nodeKeyRef.key` must point to the component's root Container ID.
|
|
570
|
+
|
|
571
|
+
**File 7 — Subtitle text:**
|
|
572
|
+
```yaml
|
|
573
|
+
# File key: component/id-Container_dc01root/component-widget-tree-outline/node/id-Text_dc01sub
|
|
574
|
+
key: Text_dc01sub
|
|
575
|
+
type: Text
|
|
576
|
+
props:
|
|
577
|
+
text:
|
|
578
|
+
themeStyle: BODY_MEDIUM
|
|
579
|
+
selectable: false
|
|
580
|
+
textValue:
|
|
581
|
+
variable:
|
|
582
|
+
source: WIDGET_CLASS_PARAMETER
|
|
583
|
+
baseVariable:
|
|
584
|
+
widgetClass:
|
|
585
|
+
paramIdentifier:
|
|
586
|
+
name: subtitle
|
|
587
|
+
key: p2subt
|
|
588
|
+
nodeKeyRef:
|
|
589
|
+
key: Container_dc01root
|
|
590
|
+
colorValue:
|
|
591
|
+
inputValue:
|
|
592
|
+
themeColor: SECONDARY_TEXT
|
|
593
|
+
responsiveVisibility: {}
|
|
594
|
+
parameterValues: {}
|
|
595
|
+
valueKey: {}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Push all files in one call:**
|
|
599
|
+
```
|
|
600
|
+
update_project_yaml(projectId, {
|
|
601
|
+
"component/id-Container_dc01root": metadataYaml,
|
|
602
|
+
"component/id-Container_dc01root/component-widget-tree-outline": treeOutlineYaml,
|
|
603
|
+
"component/id-Container_dc01root/component-widget-tree-outline/node/id-Container_dc01root": rootNodeYaml,
|
|
604
|
+
"component/id-Container_dc01root/component-widget-tree-outline/node/id-Container_dc01card": cardNodeYaml,
|
|
605
|
+
"component/id-Container_dc01root/component-widget-tree-outline/node/id-Column_dc01col": columnNodeYaml,
|
|
606
|
+
"component/id-Container_dc01root/component-widget-tree-outline/node/id-Text_dc01titl": titleNodeYaml,
|
|
607
|
+
"component/id-Container_dc01root/component-widget-tree-outline/node/id-Text_dc01sub": subtitleNodeYaml
|
|
608
|
+
})
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Using the new component in a page
|
|
612
|
+
|
|
613
|
+
After creating the component, embed it in a page by adding a Container node with `componentClassKeyRef`:
|
|
614
|
+
|
|
615
|
+
```yaml
|
|
616
|
+
# Node in the host page
|
|
617
|
+
key: Container_hostref1
|
|
618
|
+
type: Container
|
|
619
|
+
props:
|
|
620
|
+
expanded:
|
|
621
|
+
expandedType: UNEXPANDED
|
|
622
|
+
responsiveVisibility: {}
|
|
623
|
+
parameterValues:
|
|
624
|
+
parameterPasses:
|
|
625
|
+
p1titl:
|
|
626
|
+
paramIdentifier:
|
|
627
|
+
name: title
|
|
628
|
+
key: p1titl
|
|
629
|
+
inputValue:
|
|
630
|
+
serializedValue: "50% Off Coffee"
|
|
631
|
+
p2subt:
|
|
632
|
+
paramIdentifier:
|
|
633
|
+
name: subtitle
|
|
634
|
+
key: p2subt
|
|
635
|
+
inputValue:
|
|
636
|
+
serializedValue: "Valid until end of month"
|
|
637
|
+
widgetClassNodeKeyRef:
|
|
638
|
+
key: Container_dc01root
|
|
639
|
+
componentClassKeyRef:
|
|
640
|
+
key: Container_dc01root
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
For Action-type parameters, use `executeCallbackAction` inside the component:
|
|
644
|
+
```yaml
|
|
645
|
+
# Action inside the component that invokes the onTap callback
|
|
646
|
+
key: act01tap
|
|
647
|
+
executeCallbackAction:
|
|
648
|
+
parameterIdentifier:
|
|
649
|
+
name: onTap
|
|
650
|
+
key: p3tap
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## 11. Refactoring Page Widgets into a Component
|
|
656
|
+
|
|
657
|
+
To extract existing page widgets into a reusable component:
|
|
658
|
+
|
|
659
|
+
### Step 1: Identify the widget subtree to extract
|
|
660
|
+
|
|
661
|
+
Use `get_page_summary` or `get_page_by_name` to find the subtree you want to extract. Note the root widget key and all descendant keys.
|
|
662
|
+
|
|
663
|
+
### Step 2: Decide on component parameters
|
|
664
|
+
|
|
665
|
+
Any data that was previously hardcoded or came from page state/params needs to become a component parameter. Common patterns:
|
|
666
|
+
|
|
667
|
+
| Was | Becomes |
|
|
668
|
+
|-----|---------|
|
|
669
|
+
| Hardcoded text | String parameter |
|
|
670
|
+
| Page state variable | Parameter passed from page |
|
|
671
|
+
| Page parameter | Parameter passed from page |
|
|
672
|
+
| Navigation action | Action (callback) parameter |
|
|
673
|
+
| API call result | Parameter or kept internal |
|
|
674
|
+
|
|
675
|
+
### Step 3: Create component files
|
|
676
|
+
|
|
677
|
+
1. **Metadata file** — Define `name`, `params`, and optionally `classModel.stateFields`
|
|
678
|
+
2. **Tree outline** — Copy the subtree from the page's `page-widget-tree-outline`, wrapping it under a new root Container with `isDummyRoot: true`
|
|
679
|
+
3. **Root node** — Create the `isDummyRoot: true` Container
|
|
680
|
+
4. **Child nodes** — Copy existing node files from the page, changing file key prefix from `page/id-Scaffold_XXX/page-widget-tree-outline/node/` to `component/id-Container_XXX/component-widget-tree-outline/node/`
|
|
681
|
+
5. **Update data references** — Replace hardcoded values with `WIDGET_CLASS_PARAMETER` variable references
|
|
682
|
+
|
|
683
|
+
### Step 4: Update the page
|
|
684
|
+
|
|
685
|
+
1. **Remove extracted nodes** from the page's tree outline
|
|
686
|
+
2. **Replace with a component reference** — Add a single Container node with `componentClassKeyRef` pointing to the new component
|
|
687
|
+
3. **Pass parameters** via `parameterValues.parameterPasses`
|
|
688
|
+
|
|
689
|
+
### Step 5: Push everything in one call
|
|
690
|
+
|
|
691
|
+
Push all component files AND the updated page files in a single `update_project_yaml` call to avoid an inconsistent state.
|
|
692
|
+
|
|
693
|
+
### Checklist
|
|
694
|
+
|
|
695
|
+
- [ ] Component metadata has correct `name` and `node.key`
|
|
696
|
+
- [ ] All params have unique keys (5-6 char alphanumeric)
|
|
697
|
+
- [ ] Root Container has `isDummyRoot: true` and transparent background
|
|
698
|
+
- [ ] Tree outline uses `children` (not `body`) under the root node
|
|
699
|
+
- [ ] All file keys use `component/` prefix (not `page/`)
|
|
700
|
+
- [ ] Tree outline key is `component-widget-tree-outline` (not `page-widget-tree-outline`)
|
|
701
|
+
- [ ] Parameter references use `source: WIDGET_CLASS_PARAMETER` with correct `nodeKeyRef`
|
|
702
|
+
- [ ] Host Container has both `componentClassKeyRef.key` and `parameterValues.widgetClassNodeKeyRef.key` pointing to the same component root ID
|
|
703
|
+
- [ ] Action callbacks use `executeCallbackAction` with correct `parameterIdentifier`
|
|
704
|
+
- [ ] Validated all files before pushing
|
|
@@ -413,7 +413,28 @@ When passing YAML content to `validate_yaml` and `update_project_yaml`:
|
|
|
413
413
|
|
|
414
414
|
---
|
|
415
415
|
|
|
416
|
-
## 8.
|
|
416
|
+
## 8. Creating and Refactoring Components
|
|
417
|
+
|
|
418
|
+
For full details on creating new components and refactoring page widgets into reusable components, see **[03-components.md](03-components.md)** sections 10-11.
|
|
419
|
+
|
|
420
|
+
### Quick reference
|
|
421
|
+
|
|
422
|
+
**Creating a new component** requires pushing in a single call:
|
|
423
|
+
1. Component metadata (`component/id-Container_XXX`) — name, params, state
|
|
424
|
+
2. Widget tree outline (`component/id-Container_XXX/component-widget-tree-outline`) — uses `children` not `body`
|
|
425
|
+
3. Root Container node with `isDummyRoot: true` — transparent background
|
|
426
|
+
4. Individual child node files — same structure as page nodes
|
|
427
|
+
|
|
428
|
+
**Refactoring page widgets into a component:**
|
|
429
|
+
1. Identify the subtree to extract
|
|
430
|
+
2. Create component files (metadata + tree outline + root node + child nodes)
|
|
431
|
+
3. Replace original page nodes with a single Container referencing the component via `componentClassKeyRef`
|
|
432
|
+
4. Pass data through `parameterValues.parameterPasses`
|
|
433
|
+
5. Push all files (component + updated page) in one `update_project_yaml` call
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 9. Recommended Workflow Patterns
|
|
417
438
|
|
|
418
439
|
### Inspecting a project for the first time
|
|
419
440
|
|
|
@@ -445,6 +466,28 @@ list_pages(projectId)
|
|
|
445
466
|
--> update_project_yaml(projectId, { treeOutlineKey: ..., nodeKey1: ..., nodeKey2: ... })
|
|
446
467
|
```
|
|
447
468
|
|
|
469
|
+
### Creating a reusable component
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
[design component params and widget tree]
|
|
473
|
+
--> [construct metadata + tree outline + root node + child nodes]
|
|
474
|
+
--> validate_yaml for each file
|
|
475
|
+
--> update_project_yaml(projectId, { metadataKey: ..., treeKey: ..., rootNode: ..., childNodes: ... })
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
See [03-components.md](03-components.md) sections 10-11 for complete walkthrough.
|
|
479
|
+
|
|
480
|
+
### Refactoring page widgets into a component
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
get_page_by_name(projectId, "PageName")
|
|
484
|
+
--> [identify subtree to extract, note all widget keys]
|
|
485
|
+
--> [create component files: metadata, tree outline, root node, child nodes]
|
|
486
|
+
--> [replace page subtree with componentClassKeyRef Container]
|
|
487
|
+
--> validate_yaml for all files
|
|
488
|
+
--> update_project_yaml(projectId, { ...componentFiles, ...updatedPageFiles })
|
|
489
|
+
```
|
|
490
|
+
|
|
448
491
|
### Understanding navigation flow
|
|
449
492
|
|
|
450
493
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flutterflow-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for the FlutterFlow Project API — AI-assisted FlutterFlow development through Claude and other MCP-compatible clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
package/skills/ff-yaml-dev.md
CHANGED
|
@@ -32,6 +32,18 @@ list_pages → get_page_by_name → (node-level fetch) → validate_yaml → upd
|
|
|
32
32
|
list_pages → get_page_by_name → update widget-tree-outline + push individual node files → validate_yaml → update_project_yaml
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Creating a Reusable Component
|
|
36
|
+
```
|
|
37
|
+
Design params → construct metadata + tree outline + root node (isDummyRoot) + child nodes → validate_yaml → update_project_yaml
|
|
38
|
+
```
|
|
39
|
+
See `get_yaml_docs(topic: "create component")` for full walkthrough with examples.
|
|
40
|
+
|
|
41
|
+
### Refactoring Page Widgets into a Component
|
|
42
|
+
```
|
|
43
|
+
get_page_by_name → identify subtree → create component files → replace page subtree with componentClassKeyRef → push all files together
|
|
44
|
+
```
|
|
45
|
+
See `get_yaml_docs(topic: "refactor")` for step-by-step guide.
|
|
46
|
+
|
|
35
47
|
### Finding Usages
|
|
36
48
|
```
|
|
37
49
|
find_component_usages(componentName: "MyComponent") — where is a component used?
|