flutterflow-mcp 0.3.0 → 0.3.2
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.
- package/README.md +156 -21
- package/build/tools/find-component-usages.js +2 -2
- package/build/tools/find-page-navigations.js +2 -2
- package/build/tools/get-api-endpoints.js +2 -2
- package/build/tools/get-app-settings.js +2 -2
- package/build/tools/get-app-state.js +2 -2
- package/build/tools/get-component-summary.js +2 -2
- package/build/tools/get-custom-code.js +2 -2
- package/build/tools/get-data-models.js +2 -2
- package/build/tools/get-general-settings.js +2 -2
- package/build/tools/get-in-app-purchases.js +2 -2
- package/build/tools/get-integrations.js +2 -2
- package/build/tools/get-page-summary.js +2 -2
- package/build/tools/get-project-setup.js +2 -2
- package/build/tools/get-theme.js +2 -2
- package/build/tools/list-files.js +2 -2
- package/build/tools/search-project-files.js +2 -2
- package/build/utils/cache.d.ts +5 -0
- package/build/utils/cache.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,21 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for the FlutterFlow Project API. Enables AI-assisted FlutterFlow development through Claude and other MCP-compatible clients.
|
|
4
4
|
|
|
5
|
+
## What This MCP Does Best
|
|
6
|
+
|
|
7
|
+
This MCP excels at **reading, exploring, and understanding** your FlutterFlow projects. Here's what it's great at:
|
|
8
|
+
|
|
9
|
+
- **Project exploration** — Browse your projects, pages, components, and file structure
|
|
10
|
+
- **Understanding functionality** — Get clear summaries of what a page or component does, including its widget tree, actions, parameters, and state
|
|
11
|
+
- **Tracing usage** — Find everywhere a component is used, or every navigation action that leads to a page
|
|
12
|
+
- **Inspecting configuration** — View theme settings, API endpoints, data models, custom code, app state, integrations, and more
|
|
13
|
+
- **Searching** — Search project files by keyword, prefix, or regex to find exactly what you need
|
|
14
|
+
- **YAML reference** — Built-in documentation for FlutterFlow's YAML schema, so your AI assistant can understand and generate valid YAML
|
|
15
|
+
- **Guided editing** — A workflow guide (`get_editing_guide`) that walks through the correct steps before making any YAML changes
|
|
16
|
+
|
|
17
|
+
> **In short:** Think of it as giving your AI assistant full read access to your FlutterFlow project, plus careful write access when needed.
|
|
18
|
+
|
|
5
19
|
## Quick Start
|
|
6
20
|
|
|
7
|
-
### 1. Get
|
|
21
|
+
### 1. Get Your FlutterFlow API Token
|
|
22
|
+
|
|
23
|
+
You need a FlutterFlow API token to authenticate. Here's how to get one:
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
1. Open [FlutterFlow](https://app.flutterflow.io/) and log in
|
|
26
|
+
2. Click your **profile picture** (bottom-left corner)
|
|
27
|
+
3. Go to **Account Settings**
|
|
28
|
+
4. Scroll to the **API Token** section
|
|
29
|
+
5. Click **Copy** to copy your token
|
|
10
30
|
|
|
11
|
-
|
|
31
|
+
> **Note:** The API token requires a **paid FlutterFlow subscription** (Standard plan or above). Free-tier accounts do not have API access.
|
|
12
32
|
|
|
13
|
-
|
|
33
|
+
### 2. Add to Your MCP Client
|
|
34
|
+
|
|
35
|
+
Choose your AI client below and follow the setup instructions.
|
|
36
|
+
|
|
37
|
+
<details>
|
|
38
|
+
<summary><strong>Claude Code (CLI)</strong></summary>
|
|
39
|
+
|
|
40
|
+
Run this command to add the MCP server:
|
|
14
41
|
|
|
15
42
|
```bash
|
|
16
|
-
claude mcp add flutterflow -- npx -y flutterflow-mcp
|
|
43
|
+
claude mcp add flutterflow -e FLUTTERFLOW_API_TOKEN=your_token_here -- npx -y flutterflow-mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace `your_token_here` with the token you copied in step 1.
|
|
47
|
+
|
|
48
|
+
This adds the server to your project's `.claude/settings.json`. You can also manually edit the config:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"flutterflow": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["-y", "flutterflow-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"FLUTTERFLOW_API_TOKEN": "your_token_here"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
17
62
|
```
|
|
18
63
|
|
|
19
|
-
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary><strong>Claude Desktop</strong></summary>
|
|
68
|
+
|
|
69
|
+
Open **Settings > Developer > Edit Config** and add:
|
|
20
70
|
|
|
21
71
|
```json
|
|
22
72
|
{
|
|
@@ -32,9 +82,14 @@ Then set your token in the MCP config (`~/.claude/settings.json` or project `.cl
|
|
|
32
82
|
}
|
|
33
83
|
```
|
|
34
84
|
|
|
35
|
-
|
|
85
|
+
Replace `your_token_here` with your token. Restart Claude Desktop after saving.
|
|
36
86
|
|
|
37
|
-
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary><strong>Cursor</strong></summary>
|
|
91
|
+
|
|
92
|
+
Open **Settings > MCP** and add a new server with this configuration:
|
|
38
93
|
|
|
39
94
|
```json
|
|
40
95
|
{
|
|
@@ -50,27 +105,107 @@ Add to your MCP configuration:
|
|
|
50
105
|
}
|
|
51
106
|
```
|
|
52
107
|
|
|
53
|
-
|
|
108
|
+
</details>
|
|
54
109
|
|
|
55
|
-
|
|
110
|
+
<details>
|
|
111
|
+
<summary><strong>Windsurf / Other MCP Clients</strong></summary>
|
|
112
|
+
|
|
113
|
+
Add the following to your MCP configuration file (check your client's docs for the exact location):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"mcpServers": {
|
|
118
|
+
"flutterflow": {
|
|
119
|
+
"command": "npx",
|
|
120
|
+
"args": ["-y", "flutterflow-mcp"],
|
|
121
|
+
"env": {
|
|
122
|
+
"FLUTTERFLOW_API_TOKEN": "your_token_here"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</details>
|
|
130
|
+
|
|
131
|
+
### 3. Start Building
|
|
132
|
+
|
|
133
|
+
Ask your AI assistant to list your FlutterFlow projects, inspect pages, or explore your app — it handles the rest.
|
|
134
|
+
|
|
135
|
+
**Example prompts to get started:**
|
|
136
|
+
- *"List my FlutterFlow projects"*
|
|
137
|
+
- *"Show me all the pages in project X"*
|
|
138
|
+
- *"What does the HomePage do? Walk me through its widget tree"*
|
|
139
|
+
- *"Where is the PaywallCard component used?"*
|
|
140
|
+
- *"What API endpoints are configured?"*
|
|
141
|
+
- *"Show me the theme colors"*
|
|
142
|
+
|
|
143
|
+
## Important: Limitations and Cautions
|
|
144
|
+
|
|
145
|
+
### Editing Requires Care
|
|
146
|
+
|
|
147
|
+
While this MCP can push YAML changes to your FlutterFlow project, **edits should be treated with caution**:
|
|
148
|
+
|
|
149
|
+
- **Always validate first** — Use `validate_yaml` before every `update_project_yaml` call. Validation catches syntax errors but cannot catch all semantic mistakes.
|
|
150
|
+
- **Review before pushing** — Ask your AI assistant to show you the exact YAML changes before they are pushed. Understand what will change.
|
|
151
|
+
- **FlutterFlow has no undo for API changes** — Changes pushed through the API are applied immediately. There is no built-in undo button for API-pushed edits. You can revert using FlutterFlow's version history, but it's better to prevent bad edits than to fix them.
|
|
152
|
+
- **Start with read-only exploration** — Get comfortable using the read tools (`get_page_summary`, `find_component_usages`, etc.) before attempting edits.
|
|
153
|
+
- **Node-level edits are safer** — Edit individual widgets via node-level file keys instead of replacing entire page YAML. This reduces the blast radius of mistakes.
|
|
154
|
+
|
|
155
|
+
### Known Technical Limitations
|
|
156
|
+
|
|
157
|
+
- **Large pages may fail** — Some large pages can exceed buffer limits during ZIP decode. Use node-level sub-files for these pages instead.
|
|
158
|
+
- **Full project YAML fetch can be slow** — Fetching all YAML without a `fileName` parameter may exceed buffer/transport limits on large projects. Use `sync_project` to cache everything locally first.
|
|
159
|
+
- **Rate limiting** — `list_pages` batches requests 5 at a time to avoid FlutterFlow API rate limits. Pages that fail to fetch still appear with scaffold ID and folder info.
|
|
160
|
+
- **Cache staleness** — Cache-based tools (`get_page_summary`, `get_component_summary`, etc.) depend on a local cache created by `sync_project`. If your project has been edited in FlutterFlow since the last sync, re-run `sync_project` with `force: true` to refresh.
|
|
161
|
+
- **No real-time sync** — This is a snapshot-based tool. It reads and writes YAML at a point in time. It does not watch for live changes in the FlutterFlow editor.
|
|
56
162
|
|
|
57
163
|
## Tools
|
|
58
164
|
|
|
165
|
+
### Discovery and Exploration
|
|
166
|
+
|
|
59
167
|
| Tool | Description |
|
|
60
168
|
|------|-------------|
|
|
61
|
-
| `list_projects` | List all FlutterFlow projects |
|
|
62
|
-
| `list_project_files` | List YAML
|
|
63
|
-
| `list_pages` | List all pages with names, scaffold IDs, and folders |
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
| `
|
|
169
|
+
| `list_projects` | List all FlutterFlow projects for your account |
|
|
170
|
+
| `list_project_files` | List YAML file keys in a project (supports prefix filter) |
|
|
171
|
+
| `list_pages` | List all pages with human-readable names, scaffold IDs, and folders |
|
|
172
|
+
| `search_project_files` | Search file keys by keyword, prefix, or regex |
|
|
173
|
+
| `sync_project` | Bulk download all project YAML to local cache for fast offline reads |
|
|
174
|
+
|
|
175
|
+
### Reading and Understanding
|
|
176
|
+
|
|
177
|
+
| Tool | Description |
|
|
178
|
+
|------|-------------|
|
|
179
|
+
| `get_page_by_name` | Fetch a page by its human-readable name |
|
|
180
|
+
| `get_project_yaml` | Download specific YAML files by file key |
|
|
181
|
+
| `get_page_summary` | Quick page overview from cache — widget tree, actions, params, state |
|
|
182
|
+
| `get_component_summary` | Quick component overview from cache — widget tree, actions, params |
|
|
183
|
+
| `find_component_usages` | Find all pages and components where a given component is used |
|
|
72
184
|
| `find_page_navigations` | Find all actions that navigate to a given page |
|
|
185
|
+
|
|
186
|
+
### Project Configuration
|
|
187
|
+
|
|
188
|
+
| Tool | Description |
|
|
189
|
+
|------|-------------|
|
|
190
|
+
| `get_theme` | Theme colors, typography, breakpoints, and widget defaults |
|
|
191
|
+
| `get_app_state` | App state variables, constants, and environment settings |
|
|
192
|
+
| `get_api_endpoints` | API endpoint definitions — method, URL, variables, headers, response |
|
|
193
|
+
| `get_data_models` | Data structs, enums, Firestore collections, and Supabase tables |
|
|
194
|
+
| `get_custom_code` | Custom actions, functions, widgets, AI agents, and app-action components |
|
|
195
|
+
| `get_general_settings` | App Details, App Assets, Nav Bar & App Bar settings |
|
|
196
|
+
| `get_project_setup` | Firebase, Languages, Platforms, Permissions, Dependencies |
|
|
197
|
+
| `get_app_settings` | Authentication, Push Notifications, Mobile/Web Deployment |
|
|
198
|
+
| `get_in_app_purchases` | Stripe, Braintree, RevenueCat, Razorpay configuration |
|
|
199
|
+
| `get_integrations` | Supabase, SQLite, GitHub, Algolia, Google Maps, AdMob, and more |
|
|
200
|
+
|
|
201
|
+
### Editing
|
|
202
|
+
|
|
203
|
+
| Tool | Description |
|
|
204
|
+
|------|-------------|
|
|
205
|
+
| `get_editing_guide` | Get the recommended workflow and docs for an editing task — **call this before modifying any YAML** |
|
|
73
206
|
| `get_yaml_docs` | Search/retrieve FlutterFlow YAML reference docs by topic or file |
|
|
207
|
+
| `validate_yaml` | Validate YAML content before pushing — **always call before `update_project_yaml`** |
|
|
208
|
+
| `update_project_yaml` | Push YAML changes to a project |
|
|
74
209
|
|
|
75
210
|
## Resources
|
|
76
211
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys, } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys, } from "../utils/cache.js";
|
|
4
4
|
import { resolveComponent } from "./get-component-summary.js";
|
|
5
5
|
import { batchProcess } from "../utils/batch-process.js";
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -210,7 +210,7 @@ export function registerFindComponentUsagesTool(server) {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
return {
|
|
213
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
213
|
+
content: [{ type: "text", text: lines.join("\n") + cacheAgeFooter(meta) }],
|
|
214
214
|
};
|
|
215
215
|
});
|
|
216
216
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys, } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys, } from "../utils/cache.js";
|
|
4
4
|
import { resolvePage } from "./get-page-summary.js";
|
|
5
5
|
import { batchProcess } from "../utils/batch-process.js";
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -214,7 +214,7 @@ export function registerFindPageNavigationsTool(server) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
return {
|
|
217
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
217
|
+
content: [{ type: "text", text: lines.join("\n") + cacheAgeFooter(meta) }],
|
|
218
218
|
};
|
|
219
219
|
});
|
|
220
220
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys } from "../utils/cache.js";
|
|
4
4
|
import { batchProcess } from "../utils/batch-process.js";
|
|
5
5
|
function parseEndpoint(content) {
|
|
6
6
|
const doc = YAML.parse(content);
|
|
@@ -120,7 +120,7 @@ export function registerGetApiEndpointsTool(server) {
|
|
|
120
120
|
endpoints = filtered;
|
|
121
121
|
}
|
|
122
122
|
return {
|
|
123
|
-
content: [{ type: "text", text: formatEndpoints(endpoints) }],
|
|
123
|
+
content: [{ type: "text", text: formatEndpoints(endpoints) + cacheAgeFooter(meta) }],
|
|
124
124
|
};
|
|
125
125
|
});
|
|
126
126
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
async function resolvePageName(projectId, scaffoldId) {
|
|
5
5
|
const content = await cacheRead(projectId, `page/id-${scaffoldId}`);
|
|
6
6
|
if (!content)
|
|
@@ -163,7 +163,7 @@ export function registerGetAppSettingsTool(server) {
|
|
|
163
163
|
sections.push(`(not configured)`);
|
|
164
164
|
}
|
|
165
165
|
return {
|
|
166
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
166
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
167
167
|
};
|
|
168
168
|
});
|
|
169
169
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
import { resolveDataType } from "../utils/resolve-data-type.js";
|
|
5
5
|
export function registerGetAppStateTool(server) {
|
|
6
6
|
server.tool("get_app_state", "Get app state variables, constants, and environment settings from local cache. No API calls. Run sync_project first if not cached.", {
|
|
@@ -90,7 +90,7 @@ export function registerGetAppStateTool(server) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return {
|
|
93
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
93
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
94
94
|
};
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys, } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys, } from "../utils/cache.js";
|
|
4
4
|
import { parseTreeOutline } from "../utils/page-summary/tree-walker.js";
|
|
5
5
|
import { extractNodeInfo } from "../utils/page-summary/node-extractor.js";
|
|
6
6
|
import { summarizeTriggers } from "../utils/page-summary/action-summarizer.js";
|
|
@@ -189,7 +189,7 @@ export function registerGetComponentSummaryTool(server) {
|
|
|
189
189
|
// Format output
|
|
190
190
|
const summary = formatComponentSummary(componentMeta, enrichedTree);
|
|
191
191
|
return {
|
|
192
|
-
content: [{ type: "text", text: summary }],
|
|
192
|
+
content: [{ type: "text", text: summary + cacheAgeFooter(meta) }],
|
|
193
193
|
};
|
|
194
194
|
});
|
|
195
195
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys } from "../utils/cache.js";
|
|
4
4
|
import { batchProcess } from "../utils/batch-process.js";
|
|
5
5
|
import { resolveDataType } from "../utils/resolve-data-type.js";
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -374,7 +374,7 @@ export function registerGetCustomCodeTool(server) {
|
|
|
374
374
|
}
|
|
375
375
|
const output = `# Custom Code\n\n${sections.join("\n\n")}`;
|
|
376
376
|
return {
|
|
377
|
-
content: [{ type: "text", text: output }],
|
|
377
|
+
content: [{ type: "text", text: output + cacheAgeFooter(meta) }],
|
|
378
378
|
};
|
|
379
379
|
});
|
|
380
380
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys } from "../utils/cache.js";
|
|
4
4
|
import { batchProcess } from "../utils/batch-process.js";
|
|
5
5
|
import { resolveDataType } from "../utils/resolve-data-type.js";
|
|
6
6
|
async function readStructs(projectId, nameFilter) {
|
|
@@ -260,7 +260,7 @@ export function registerGetDataModelsTool(server) {
|
|
|
260
260
|
}
|
|
261
261
|
const output = formatOutput(structs, enums, collections, supabaseTables, name);
|
|
262
262
|
return {
|
|
263
|
-
content: [{ type: "text", text: output }],
|
|
263
|
+
content: [{ type: "text", text: output + cacheAgeFooter(meta) }],
|
|
264
264
|
};
|
|
265
265
|
});
|
|
266
266
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
async function resolvePageName(projectId, scaffoldId) {
|
|
5
5
|
const content = await cacheRead(projectId, `page/id-${scaffoldId}`);
|
|
6
6
|
if (!content)
|
|
@@ -110,7 +110,7 @@ export function registerGetGeneralSettingsTool(server) {
|
|
|
110
110
|
sections.push(`\n## Nav Bar & App Bar`, `(not configured)`);
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
113
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
113
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
114
114
|
};
|
|
115
115
|
});
|
|
116
116
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
const PROVIDERS = [
|
|
5
5
|
{ key: "stripe", label: "Stripe" },
|
|
6
6
|
{ key: "braintree", label: "Braintree" },
|
|
@@ -45,7 +45,7 @@ export function registerGetInAppPurchasesTool(server) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
return {
|
|
48
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
48
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
49
49
|
};
|
|
50
50
|
});
|
|
51
51
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
/**
|
|
5
5
|
* Fields that should never be output — API keys, secrets, tokens, etc.
|
|
6
6
|
* Only IDs and non-sensitive configuration are shown.
|
|
@@ -131,7 +131,7 @@ export function registerGetIntegrationsTool(server) {
|
|
|
131
131
|
sections.push(lines.join("\n"));
|
|
132
132
|
}
|
|
133
133
|
return {
|
|
134
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
134
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
135
135
|
};
|
|
136
136
|
});
|
|
137
137
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta, listCachedKeys, } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter, listCachedKeys, } from "../utils/cache.js";
|
|
4
4
|
import { parseFolderMapping } from "../utils/parse-folders.js";
|
|
5
5
|
import { parseTreeOutline } from "../utils/page-summary/tree-walker.js";
|
|
6
6
|
import { extractNodeInfo } from "../utils/page-summary/node-extractor.js";
|
|
@@ -199,7 +199,7 @@ export function registerGetPageSummaryTool(server) {
|
|
|
199
199
|
// Format output
|
|
200
200
|
const summary = formatPageSummary(pageMeta, enrichedTree);
|
|
201
201
|
return {
|
|
202
|
-
content: [{ type: "text", text: summary }],
|
|
202
|
+
content: [{ type: "text", text: summary + cacheAgeFooter(meta) }],
|
|
203
203
|
};
|
|
204
204
|
});
|
|
205
205
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
import { resolveDataType } from "../utils/resolve-data-type.js";
|
|
5
5
|
export function registerGetProjectSetupTool(server) {
|
|
6
6
|
server.tool("get_project_setup", "Get Project Setup settings — Firebase services, Languages, Platforms, Permissions, Project Dependencies, Dev Environments. Mirrors the FlutterFlow 'Project Setup' settings section. Cache-based, no API calls. Run sync_project first.", {
|
|
@@ -206,7 +206,7 @@ export function registerGetProjectSetupTool(server) {
|
|
|
206
206
|
sections.push(`(not configured)`);
|
|
207
207
|
}
|
|
208
208
|
return {
|
|
209
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
209
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
210
210
|
};
|
|
211
211
|
});
|
|
212
212
|
}
|
package/build/tools/get-theme.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { cacheRead, cacheMeta } from "../utils/cache.js";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
4
|
function argbToHex(argbStr) {
|
|
5
5
|
const n = parseInt(argbStr, 10);
|
|
6
6
|
if (isNaN(n))
|
|
@@ -122,7 +122,7 @@ export function registerGetThemeTool(server) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
return {
|
|
125
|
-
content: [{ type: "text", text: sections.join("\n") }],
|
|
125
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
126
126
|
};
|
|
127
127
|
});
|
|
128
128
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { listCachedKeys, cacheMeta } from "../utils/cache.js";
|
|
2
|
+
import { listCachedKeys, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
3
3
|
export function registerListFilesTool(server, client) {
|
|
4
4
|
server.tool("list_project_files", "List all YAML file names in a FlutterFlow project. Supports optional prefix filter (e.g. 'page/', 'component/') to narrow results.", {
|
|
5
5
|
projectId: z.string().describe("The FlutterFlow project ID"),
|
|
@@ -15,7 +15,7 @@ export function registerListFilesTool(server, client) {
|
|
|
15
15
|
content: [
|
|
16
16
|
{
|
|
17
17
|
type: "text",
|
|
18
|
-
text: JSON.stringify({ value: { file_names: keys }, source: "cache", syncedAt: meta.lastSyncedAt }, null, 2),
|
|
18
|
+
text: JSON.stringify({ value: { file_names: keys }, source: "cache", syncedAt: meta.lastSyncedAt }, null, 2) + cacheAgeFooter(meta),
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { cacheMeta, listCachedKeys } from "../utils/cache.js";
|
|
2
|
+
import { cacheMeta, listCachedKeys, cacheAgeFooter } from "../utils/cache.js";
|
|
3
3
|
const MAX_RESULTS = 100;
|
|
4
4
|
export function registerSearchProjectFilesTool(server) {
|
|
5
5
|
server.tool("search_project_files", "Search cached project file keys by keyword, prefix, or regex. Returns matching file keys for use with get_project_yaml. Cache-only, no API calls. Run sync_project first.", {
|
|
@@ -63,7 +63,7 @@ export function registerSearchProjectFilesTool(server) {
|
|
|
63
63
|
lines.push(`- ${key}`);
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
66
|
+
content: [{ type: "text", text: lines.join("\n") + cacheAgeFooter(meta) }],
|
|
67
67
|
};
|
|
68
68
|
});
|
|
69
69
|
}
|
package/build/utils/cache.d.ts
CHANGED
|
@@ -51,3 +51,8 @@ export declare function cacheWriteMeta(projectId: string, meta: CacheMeta): Prom
|
|
|
51
51
|
* the path relative to the project cache dir.
|
|
52
52
|
*/
|
|
53
53
|
export declare function listCachedKeys(projectId: string, prefix?: string): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Format a human-readable footer indicating cache age.
|
|
56
|
+
* Appended to cache-based tool responses so the AI can judge staleness.
|
|
57
|
+
*/
|
|
58
|
+
export declare function cacheAgeFooter(meta: CacheMeta): string;
|
package/build/utils/cache.js
CHANGED
|
@@ -148,6 +148,28 @@ export async function listCachedKeys(projectId, prefix) {
|
|
|
148
148
|
return yamlKeys;
|
|
149
149
|
}
|
|
150
150
|
// ---------------------------------------------------------------------------
|
|
151
|
+
// Cache age footer
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* Format a human-readable footer indicating cache age.
|
|
155
|
+
* Appended to cache-based tool responses so the AI can judge staleness.
|
|
156
|
+
*/
|
|
157
|
+
export function cacheAgeFooter(meta) {
|
|
158
|
+
const syncedAt = new Date(meta.lastSyncedAt);
|
|
159
|
+
const diffMs = Date.now() - syncedAt.getTime();
|
|
160
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
161
|
+
let ago;
|
|
162
|
+
if (diffMin < 1)
|
|
163
|
+
ago = "just now";
|
|
164
|
+
else if (diffMin < 60)
|
|
165
|
+
ago = `${diffMin} min ago`;
|
|
166
|
+
else if (diffMin < 1440)
|
|
167
|
+
ago = `${Math.floor(diffMin / 60)}h ${diffMin % 60}m ago`;
|
|
168
|
+
else
|
|
169
|
+
ago = `${Math.floor(diffMin / 1440)}d ago`;
|
|
170
|
+
return `\n\n---\n_Synced: ${meta.lastSyncedAt} (${ago}). Call sync_project to refresh._`;
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
151
173
|
// Internal helpers
|
|
152
174
|
// ---------------------------------------------------------------------------
|
|
153
175
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flutterflow-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|