flutterflow-mcp 0.2.5 → 0.3.1
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/build/index.js +14 -2
- package/build/prompts/generate-page.js +6 -5
- package/build/prompts/modify-component.js +6 -5
- 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.d.ts +2 -0
- package/build/tools/get-app-settings.js +169 -0
- 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 +101 -5
- package/build/tools/get-data-models.js +2 -2
- package/build/tools/get-editing-guide.d.ts +7 -0
- package/build/tools/get-editing-guide.js +185 -0
- package/build/tools/get-general-settings.d.ts +2 -0
- package/build/tools/get-general-settings.js +116 -0
- package/build/tools/get-in-app-purchases.d.ts +2 -0
- package/build/tools/get-in-app-purchases.js +51 -0
- package/build/tools/get-integrations.d.ts +2 -0
- package/build/tools/get-integrations.js +137 -0
- package/build/tools/get-page-summary.js +2 -2
- package/build/tools/get-project-setup.d.ts +2 -0
- package/build/tools/get-project-setup.js +212 -0
- package/build/tools/get-theme.js +2 -2
- package/build/tools/get-yaml-docs.js +1 -111
- package/build/tools/list-files.js +24 -5
- package/build/tools/search-project-files.d.ts +2 -0
- package/build/tools/search-project-files.js +69 -0
- package/build/tools/update-yaml.js +1 -1
- package/build/tools/validate-yaml.js +19 -2
- package/build/utils/cache.d.ts +5 -0
- package/build/utils/cache.js +22 -0
- package/build/utils/topic-map.d.ts +7 -0
- package/build/utils/topic-map.js +115 -0
- package/package.json +1 -1
- package/build/tools/get-project-config.d.ts +0 -2
- package/build/tools/get-project-config.js +0 -160
package/build/index.js
CHANGED
|
@@ -25,8 +25,14 @@ import { registerGetAppStateTool } from "./tools/get-app-state.js";
|
|
|
25
25
|
import { registerGetApiEndpointsTool } from "./tools/get-api-endpoints.js";
|
|
26
26
|
import { registerGetDataModelsTool } from "./tools/get-data-models.js";
|
|
27
27
|
import { registerGetCustomCodeTool } from "./tools/get-custom-code.js";
|
|
28
|
-
import { registerGetProjectConfigTool } from "./tools/get-project-config.js";
|
|
29
28
|
import { registerGetThemeTool } from "./tools/get-theme.js";
|
|
29
|
+
import { registerGetEditingGuideTool } from "./tools/get-editing-guide.js";
|
|
30
|
+
import { registerSearchProjectFilesTool } from "./tools/search-project-files.js";
|
|
31
|
+
import { registerGetGeneralSettingsTool } from "./tools/get-general-settings.js";
|
|
32
|
+
import { registerGetProjectSetupTool } from "./tools/get-project-setup.js";
|
|
33
|
+
import { registerGetAppSettingsTool } from "./tools/get-app-settings.js";
|
|
34
|
+
import { registerGetInAppPurchasesTool } from "./tools/get-in-app-purchases.js";
|
|
35
|
+
import { registerGetIntegrationsTool } from "./tools/get-integrations.js";
|
|
30
36
|
const server = new McpServer({
|
|
31
37
|
name: "ff-mcp",
|
|
32
38
|
version: "0.1.0",
|
|
@@ -50,8 +56,14 @@ registerGetAppStateTool(server);
|
|
|
50
56
|
registerGetApiEndpointsTool(server);
|
|
51
57
|
registerGetDataModelsTool(server);
|
|
52
58
|
registerGetCustomCodeTool(server);
|
|
53
|
-
registerGetProjectConfigTool(server);
|
|
54
59
|
registerGetThemeTool(server);
|
|
60
|
+
registerGetEditingGuideTool(server);
|
|
61
|
+
registerSearchProjectFilesTool(server);
|
|
62
|
+
registerGetGeneralSettingsTool(server);
|
|
63
|
+
registerGetProjectSetupTool(server);
|
|
64
|
+
registerGetAppSettingsTool(server);
|
|
65
|
+
registerGetInAppPurchasesTool(server);
|
|
66
|
+
registerGetIntegrationsTool(server);
|
|
55
67
|
// Register resources
|
|
56
68
|
registerResources(server, client);
|
|
57
69
|
registerDocsResources(server);
|
|
@@ -16,14 +16,15 @@ export function registerGeneratePagePrompt(server) {
|
|
|
16
16
|
## Instructions
|
|
17
17
|
|
|
18
18
|
1. First, use the list_project_files tool with projectId "${projectId}" to understand the existing project structure.
|
|
19
|
-
2.
|
|
20
|
-
3.
|
|
19
|
+
2. Use \`get_editing_guide\` with a description of the page you want to create to get the correct YAML schemas, workflow steps, and critical rules.
|
|
20
|
+
3. Then, use get_project_yaml to read a few existing pages to understand the YAML schema and conventions used in this project.
|
|
21
|
+
4. Based on the following description, generate valid FlutterFlow YAML for a new page:
|
|
21
22
|
|
|
22
23
|
**Page Description:** ${description}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
5. Use validate_yaml to check your generated YAML is valid.
|
|
26
|
+
6. If validation passes, use update_project_yaml to push the new page to the project.
|
|
27
|
+
7. If validation fails, fix the errors and try again.
|
|
27
28
|
|
|
28
29
|
## Important
|
|
29
30
|
- Follow the exact YAML structure you observed in existing pages.
|
|
@@ -19,14 +19,15 @@ export function registerModifyComponentPrompt(server) {
|
|
|
19
19
|
## Instructions
|
|
20
20
|
|
|
21
21
|
1. Use get_project_yaml with projectId "${projectId}" and fileName "${fileName}" to read the current component YAML.
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
22
|
+
2. Use \`get_editing_guide\` with a description of the changes you want to make to get the correct YAML schemas and critical rules.
|
|
23
|
+
3. Understand the current structure and widget tree.
|
|
24
|
+
4. Apply the following changes:
|
|
24
25
|
|
|
25
26
|
**Requested Changes:** ${changes}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
5. Use validate_yaml to verify your modified YAML is valid.
|
|
29
|
+
6. If validation passes, use update_project_yaml to push the changes.
|
|
30
|
+
7. If validation fails, fix the errors and try again.
|
|
30
31
|
|
|
31
32
|
## Important
|
|
32
33
|
- Preserve all existing structure you are not modifying.
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import { cacheRead, cacheMeta, cacheAgeFooter } from "../utils/cache.js";
|
|
4
|
+
async function resolvePageName(projectId, scaffoldId) {
|
|
5
|
+
const content = await cacheRead(projectId, `page/id-${scaffoldId}`);
|
|
6
|
+
if (!content)
|
|
7
|
+
return scaffoldId;
|
|
8
|
+
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
9
|
+
return nameMatch ? nameMatch[1].trim() : scaffoldId;
|
|
10
|
+
}
|
|
11
|
+
export function registerGetAppSettingsTool(server) {
|
|
12
|
+
server.tool("get_app_settings", "Get App Settings — Authentication, Push Notifications, Mobile Deployment (version, build, stores), Web Deployment (SEO, title). Mirrors the FlutterFlow 'App Settings' section. Cache-based, no API calls. Run sync_project first.", {
|
|
13
|
+
projectId: z.string().describe("The FlutterFlow project ID"),
|
|
14
|
+
}, async ({ projectId }) => {
|
|
15
|
+
const meta = await cacheMeta(projectId);
|
|
16
|
+
if (!meta) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: `No cache found for project "${projectId}". Run sync_project first.`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const [authRaw, appDetailsRaw, pushRaw, mobileRaw, webRaw] = await Promise.all([
|
|
27
|
+
cacheRead(projectId, "authentication"),
|
|
28
|
+
cacheRead(projectId, "app-details"),
|
|
29
|
+
cacheRead(projectId, "push-notifications"),
|
|
30
|
+
cacheRead(projectId, "mobile-deployment"),
|
|
31
|
+
cacheRead(projectId, "web-publishing"),
|
|
32
|
+
]);
|
|
33
|
+
const sections = ["# App Settings"];
|
|
34
|
+
// --- Authentication ---
|
|
35
|
+
sections.push(`\n## Authentication`);
|
|
36
|
+
if (authRaw) {
|
|
37
|
+
const auth = YAML.parse(authRaw);
|
|
38
|
+
const active = auth.active === true;
|
|
39
|
+
if (active) {
|
|
40
|
+
let provider = "Unknown";
|
|
41
|
+
const firebaseConfigs = auth.firebaseConfigFileInfos;
|
|
42
|
+
const supabase = auth.supabase;
|
|
43
|
+
if (firebaseConfigs && firebaseConfigs.length > 0) {
|
|
44
|
+
provider = "Firebase";
|
|
45
|
+
}
|
|
46
|
+
else if (supabase && Object.keys(supabase).length > 0) {
|
|
47
|
+
provider = "Supabase";
|
|
48
|
+
}
|
|
49
|
+
sections.push(`Status: Active`, `Provider: ${provider}`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
sections.push(`Status: Inactive`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
sections.push(`Status: Inactive`);
|
|
57
|
+
}
|
|
58
|
+
// Auth page info from app-details
|
|
59
|
+
if (appDetailsRaw) {
|
|
60
|
+
const appDetails = YAML.parse(appDetailsRaw);
|
|
61
|
+
const authPageInfo = appDetails.authPageInfo;
|
|
62
|
+
if (authPageInfo) {
|
|
63
|
+
const homeRef = authPageInfo.homePageNodeKeyRef?.key;
|
|
64
|
+
const signInRef = authPageInfo.signInPageNodeKeyRef?.key;
|
|
65
|
+
if (homeRef) {
|
|
66
|
+
const name = await resolvePageName(projectId, homeRef);
|
|
67
|
+
sections.push(`Home page: ${name} (${homeRef})`);
|
|
68
|
+
}
|
|
69
|
+
if (signInRef) {
|
|
70
|
+
const name = await resolvePageName(projectId, signInRef);
|
|
71
|
+
sections.push(`Sign-in page: ${name} (${signInRef})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// --- Push Notifications ---
|
|
76
|
+
sections.push(`\n## Push Notifications`);
|
|
77
|
+
if (pushRaw) {
|
|
78
|
+
const push = YAML.parse(pushRaw);
|
|
79
|
+
const enabled = push.enabled === true;
|
|
80
|
+
const allowScheduled = push.allowScheduledNotifications === true;
|
|
81
|
+
const autoPrompt = push.autoPromptUsersForNotificationsPermission === true;
|
|
82
|
+
sections.push(`Enabled: ${enabled ? "Yes" : "No"}`, `Scheduled notifications: ${allowScheduled ? "Yes" : "No"}`, `Auto-prompt permission: ${autoPrompt ? "Yes" : "No"}`);
|
|
83
|
+
const lastNotif = push.lastNotificationSent;
|
|
84
|
+
if (lastNotif) {
|
|
85
|
+
const title = lastNotif.notificationTitle || "(untitled)";
|
|
86
|
+
const status = lastNotif.status || "unknown";
|
|
87
|
+
sections.push(`Last notification: "${title}" (${status})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
sections.push(`(not configured)`);
|
|
92
|
+
}
|
|
93
|
+
// --- Mobile Deployment ---
|
|
94
|
+
sections.push(`\n## Mobile Deployment`);
|
|
95
|
+
if (mobileRaw) {
|
|
96
|
+
const mobile = YAML.parse(mobileRaw);
|
|
97
|
+
const settingsMap = mobile.codemagicSettingsMap;
|
|
98
|
+
if (settingsMap && Object.keys(settingsMap).length > 0) {
|
|
99
|
+
// Prefer PROD, fall back to first key
|
|
100
|
+
const envKey = settingsMap.PROD
|
|
101
|
+
? "PROD"
|
|
102
|
+
: Object.keys(settingsMap)[0];
|
|
103
|
+
const envSettings = settingsMap[envKey];
|
|
104
|
+
sections.push(`Environment: ${envKey}`);
|
|
105
|
+
// Build version
|
|
106
|
+
const buildVersion = envSettings.buildVersion;
|
|
107
|
+
if (buildVersion) {
|
|
108
|
+
if (buildVersion.buildVersion) {
|
|
109
|
+
sections.push(`Version: ${buildVersion.buildVersion}`);
|
|
110
|
+
}
|
|
111
|
+
if (buildVersion.buildNumber != null) {
|
|
112
|
+
sections.push(`Build number: ${buildVersion.buildNumber}`);
|
|
113
|
+
}
|
|
114
|
+
if (buildVersion.lastSubmitted) {
|
|
115
|
+
sections.push(`Last submitted: ${buildVersion.lastSubmitted}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Play Store
|
|
119
|
+
const playStore = envSettings.playStoreSettings;
|
|
120
|
+
if (playStore?.playTrack) {
|
|
121
|
+
sections.push(`Play Store track: ${playStore.playTrack}`);
|
|
122
|
+
}
|
|
123
|
+
// App Store — NEVER output private keys
|
|
124
|
+
const appStore = envSettings.appStoreSettings;
|
|
125
|
+
if (appStore?.ascAppId) {
|
|
126
|
+
sections.push(`App Store ID: ${appStore.ascAppId}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
sections.push(`(not configured)`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
sections.push(`(not configured)`);
|
|
135
|
+
}
|
|
136
|
+
// --- Web Deployment ---
|
|
137
|
+
sections.push(`\n## Web Deployment`);
|
|
138
|
+
if (webRaw) {
|
|
139
|
+
const web = YAML.parse(webRaw);
|
|
140
|
+
const webSettings = web.webSettings;
|
|
141
|
+
if (webSettings && Object.keys(webSettings).length > 0) {
|
|
142
|
+
// Prefer PROD, fall back to first key
|
|
143
|
+
const envKey = webSettings.PROD
|
|
144
|
+
? "PROD"
|
|
145
|
+
: Object.keys(webSettings)[0];
|
|
146
|
+
const envData = webSettings[envKey];
|
|
147
|
+
sections.push(`Environment: ${envKey}`);
|
|
148
|
+
if (envData.pageTitle) {
|
|
149
|
+
sections.push(`Page title: ${envData.pageTitle}`);
|
|
150
|
+
}
|
|
151
|
+
if (envData.seoDescription) {
|
|
152
|
+
sections.push(`SEO description: ${envData.seoDescription}`);
|
|
153
|
+
}
|
|
154
|
+
if (envData.orientation) {
|
|
155
|
+
sections.push(`Orientation: ${envData.orientation}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
sections.push(`(not configured)`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
sections.push(`(not configured)`);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: sections.join("\n") + cacheAgeFooter(meta) }],
|
|
167
|
+
};
|
|
168
|
+
});
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -82,6 +82,24 @@ function formatAgent(agent) {
|
|
|
82
82
|
}
|
|
83
83
|
return lines.join("\n");
|
|
84
84
|
}
|
|
85
|
+
function formatAppAction(action) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
lines.push(`### ${action.name}`);
|
|
88
|
+
lines.push(`Key: ${action.key} | File: \`${action.fileKey}\``);
|
|
89
|
+
lines.push(`Root action: ${action.rootActionType}(${action.rootActionName})`);
|
|
90
|
+
if (action.description) {
|
|
91
|
+
lines.push(`Description: "${action.description}"`);
|
|
92
|
+
}
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
function formatCustomFile(file) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push(`### ${file.name}`);
|
|
98
|
+
lines.push(`Key: ${file.key} | File: \`${file.fileKey}\``);
|
|
99
|
+
lines.push(`Type: ${file.fileType}`);
|
|
100
|
+
lines.push(`Actions: ${file.initialCount} initial, ${file.finalCount} final`);
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
85
103
|
function resolveReturnType(returnParam) {
|
|
86
104
|
if (!returnParam)
|
|
87
105
|
return null;
|
|
@@ -207,14 +225,80 @@ async function processAgents(projectId, nameFilter) {
|
|
|
207
225
|
return { name: identifierName, key: idKey, fileKey, displayName, status, provider, model, requestTypes, responseType, description };
|
|
208
226
|
}).then((results) => results.filter((r) => r !== null));
|
|
209
227
|
}
|
|
228
|
+
async function processAppActions(projectId, nameFilter) {
|
|
229
|
+
const allKeys = await listCachedKeys(projectId, "app-action-components/id-");
|
|
230
|
+
const topKeys = allKeys.filter((k) => /^app-action-components\/id-[a-z0-9]+$/i.test(k));
|
|
231
|
+
return batchProcess(topKeys, 10, async (fileKey) => {
|
|
232
|
+
const content = await cacheRead(projectId, fileKey);
|
|
233
|
+
if (!content)
|
|
234
|
+
return null;
|
|
235
|
+
const doc = YAML.parse(content);
|
|
236
|
+
const id = doc.identifier;
|
|
237
|
+
const name = id?.name || "unknown";
|
|
238
|
+
if (nameFilter && name.toLowerCase() !== nameFilter.toLowerCase())
|
|
239
|
+
return null;
|
|
240
|
+
const idKey = id?.key || fileKey.match(/id-([a-z0-9]+)$/i)?.[1] || "unknown";
|
|
241
|
+
const description = doc.description || "";
|
|
242
|
+
// Extract root action type and name
|
|
243
|
+
let rootActionType = "unknown";
|
|
244
|
+
let rootActionName = "unknown";
|
|
245
|
+
const actions = doc.actions;
|
|
246
|
+
const rootAction = actions?.rootAction;
|
|
247
|
+
const actionObj = rootAction?.action;
|
|
248
|
+
if (actionObj) {
|
|
249
|
+
const actionKeys = Object.keys(actionObj);
|
|
250
|
+
if (actionKeys.length > 0) {
|
|
251
|
+
rootActionType = actionKeys[0];
|
|
252
|
+
const actionBody = actionObj[rootActionType];
|
|
253
|
+
if (actionBody) {
|
|
254
|
+
// Look for a sub-key ending in "Identifier" to get the name
|
|
255
|
+
for (const subKey of Object.keys(actionBody)) {
|
|
256
|
+
if (subKey.endsWith("Identifier")) {
|
|
257
|
+
const identifierObj = actionBody[subKey];
|
|
258
|
+
rootActionName = identifierObj?.name || rootActionName;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return { name, key: idKey, fileKey, rootActionType, rootActionName, description };
|
|
266
|
+
}).then((results) => results.filter((r) => r !== null));
|
|
267
|
+
}
|
|
268
|
+
async function processCustomFiles(projectId, nameFilter) {
|
|
269
|
+
const allKeys = await listCachedKeys(projectId, "custom-file/id-");
|
|
270
|
+
const topKeys = allKeys.filter((k) => /^custom-file\/id-[^/]+$/i.test(k) && k !== "custom-file/id-MAIN");
|
|
271
|
+
return batchProcess(topKeys, 10, async (fileKey) => {
|
|
272
|
+
const content = await cacheRead(projectId, fileKey);
|
|
273
|
+
if (!content)
|
|
274
|
+
return null;
|
|
275
|
+
const doc = YAML.parse(content);
|
|
276
|
+
const keyMatch = fileKey.match(/^custom-file\/id-(.+)$/i);
|
|
277
|
+
const key = keyMatch?.[1] || "unknown";
|
|
278
|
+
const name = key;
|
|
279
|
+
if (nameFilter && name.toLowerCase() !== nameFilter.toLowerCase())
|
|
280
|
+
return null;
|
|
281
|
+
const fileType = doc.type || "UNKNOWN";
|
|
282
|
+
const actions = doc.actions || [];
|
|
283
|
+
let initialCount = 0;
|
|
284
|
+
let finalCount = 0;
|
|
285
|
+
for (const action of actions) {
|
|
286
|
+
if (action.type === "INITIAL_ACTION")
|
|
287
|
+
initialCount++;
|
|
288
|
+
else if (action.type === "FINAL_ACTION")
|
|
289
|
+
finalCount++;
|
|
290
|
+
}
|
|
291
|
+
return { name, key, fileKey, fileType, initialCount, finalCount };
|
|
292
|
+
}).then((results) => results.filter((r) => r !== null));
|
|
293
|
+
}
|
|
210
294
|
// ---------------------------------------------------------------------------
|
|
211
295
|
// Tool registration
|
|
212
296
|
// ---------------------------------------------------------------------------
|
|
213
297
|
export function registerGetCustomCodeTool(server) {
|
|
214
|
-
server.tool("get_custom_code", "Get custom actions, functions, widgets,
|
|
298
|
+
server.tool("get_custom_code", "Get custom actions, functions, widgets, AI agents, app action components, and custom files from local cache — signatures, arguments, return types, and optionally Dart source code. No API calls. Run sync_project first if not cached.", {
|
|
215
299
|
projectId: z.string().describe("The FlutterFlow project ID"),
|
|
216
300
|
type: z
|
|
217
|
-
.enum(["actions", "functions", "widgets", "agents", "all"])
|
|
301
|
+
.enum(["actions", "functions", "widgets", "agents", "app-actions", "custom-files", "all"])
|
|
218
302
|
.optional()
|
|
219
303
|
.default("all")
|
|
220
304
|
.describe("Type of custom code to retrieve"),
|
|
@@ -240,7 +324,7 @@ export function registerGetCustomCodeTool(server) {
|
|
|
240
324
|
};
|
|
241
325
|
}
|
|
242
326
|
const categories = type === "all"
|
|
243
|
-
? ["actions", "functions", "widgets", "agents"]
|
|
327
|
+
? ["actions", "functions", "widgets", "agents", "app-actions", "custom-files"]
|
|
244
328
|
: [type];
|
|
245
329
|
const sections = [];
|
|
246
330
|
for (const cat of categories) {
|
|
@@ -268,6 +352,18 @@ export function registerGetCustomCodeTool(server) {
|
|
|
268
352
|
sections.push(`## AI Agents (${items.length})\n\n${items.map(formatAgent).join("\n\n")}`);
|
|
269
353
|
}
|
|
270
354
|
}
|
|
355
|
+
else if (cat === "app-actions") {
|
|
356
|
+
const items = await processAppActions(projectId, name);
|
|
357
|
+
if (items.length > 0) {
|
|
358
|
+
sections.push(`## App Action Components (${items.length})\n\n${items.map(formatAppAction).join("\n\n")}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (cat === "custom-files") {
|
|
362
|
+
const items = await processCustomFiles(projectId, name);
|
|
363
|
+
if (items.length > 0) {
|
|
364
|
+
sections.push(`## Custom Files (${items.length})\n\n${items.map(formatCustomFile).join("\n\n")}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
271
367
|
}
|
|
272
368
|
if (sections.length === 0) {
|
|
273
369
|
return {
|
|
@@ -278,7 +374,7 @@ export function registerGetCustomCodeTool(server) {
|
|
|
278
374
|
}
|
|
279
375
|
const output = `# Custom Code\n\n${sections.join("\n\n")}`;
|
|
280
376
|
return {
|
|
281
|
-
content: [{ type: "text", text: output }],
|
|
377
|
+
content: [{ type: "text", text: output + cacheAgeFooter(meta) }],
|
|
282
378
|
};
|
|
283
379
|
});
|
|
284
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
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_editing_guide tool — returns workflow steps, relevant YAML docs,
|
|
3
|
+
* and universal rules for a given FlutterFlow editing task.
|
|
4
|
+
* No API calls: reads from bundled docs/ff-yaml/ directory.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
export declare function registerGetEditingGuideTool(server: McpServer): void;
|