community-ff-mcp 0.4.0 → 0.4.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # community-ff-mcp
2
2
 
3
+ > **Disclaimer:** This is an unofficial, community-built project. It is not affiliated with, endorsed by, or sponsored by FlutterFlow. "FlutterFlow" is a trademark of FlutterFlow Inc. This project uses the FlutterFlow public API.
4
+
3
5
  MCP server for the FlutterFlow Project API. Enables AI-assisted FlutterFlow development through Claude and other MCP-compatible clients.
4
6
 
5
7
  ## What This MCP Does Best
@@ -265,13 +267,33 @@ See [docs/ff-yaml/](docs/ff-yaml/) for the full catalog.
265
267
 
266
268
  ## AI Agent Skill
267
269
 
268
- This MCP includes a [skills.sh](https://skills.sh)-compatible skill for AI agents. Install it to teach your AI assistant how to use FlutterFlow MCP effectively:
270
+ This MCP includes a [skills.sh](https://skills.sh)-compatible skill that teaches AI assistants how to use FlutterFlow MCP effectively. The skill provides tool orchestration workflows, critical YAML rules, and reference documentation for widgets, actions, data binding, theming, and more.
271
+
272
+ ### Install the Skill
269
273
 
270
274
  ```bash
271
275
  npx skills add mohn93/ff-mcp --skill community-ff-mcp
272
276
  ```
273
277
 
274
- Compatible with Claude Code, Cursor, GitHub Copilot, Codex, Goose, Windsurf, and 12+ other AI agents. The skill provides tool orchestration workflows, critical YAML rules, and detailed reference documentation for widgets, actions, data binding, theming, and more.
278
+ This works with **18+ AI agents** including Claude Code, Cursor, GitHub Copilot, Codex, Goose, Windsurf, and more. The `skills` CLI auto-detects your AI tool and installs the skill to the correct location.
279
+
280
+ ### What the Skill Teaches Your AI
281
+
282
+ Once installed, your AI assistant will know how to:
283
+
284
+ - **Discover** — List projects, pages, components, and search project files
285
+ - **Read** — Summarize pages/components, trace component usages, inspect navigations
286
+ - **Edit** — Follow the correct read → guide → edit → validate → push workflow
287
+ - **Configure** — Read theme, app state, API endpoints, data models, and integrations
288
+
289
+ ### Skill + MCP Setup
290
+
291
+ The skill and the MCP server are installed separately:
292
+
293
+ 1. **MCP server** gives the AI the tools (see [Quick Start](#quick-start) above)
294
+ 2. **Skill** teaches the AI *how* to use those tools effectively
295
+
296
+ If the skill detects the MCP tools aren't connected, it will guide the user through setup automatically.
275
297
 
276
298
  ## Claude Code Skills
277
299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "community-ff-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.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",
@@ -1,2 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- export declare function registerGetProjectConfigTool(server: McpServer): void;
@@ -1,216 +0,0 @@
1
- import { z } from "zod";
2
- import YAML from "yaml";
3
- import { cacheRead, cacheMeta, listCachedKeys } 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 registerGetProjectConfigTool(server) {
12
- server.tool("get_project_config", "Get core project configuration — app name, entry pages, routing, nav bar, auth, permissions, services, main.dart lifecycle actions, and a project file map. No API calls. Run sync_project first if not cached.", {
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 to download the project YAML files.`,
22
- },
23
- ],
24
- };
25
- }
26
- const [appDetailsRaw, navBarRaw, authRaw, permissionsRaw, revenueCatRaw] = await Promise.all([
27
- cacheRead(projectId, "app-details"),
28
- cacheRead(projectId, "nav-bar"),
29
- cacheRead(projectId, "authentication"),
30
- cacheRead(projectId, "permissions"),
31
- cacheRead(projectId, "revenue-cat"),
32
- ]);
33
- const sections = ["# Project Configuration"];
34
- // --- App Details ---
35
- if (appDetailsRaw) {
36
- const appDetails = YAML.parse(appDetailsRaw);
37
- const appName = appDetails.name || "Unknown";
38
- const initialPageKey = appDetails.initialPageKeyRef?.key;
39
- const routing = appDetails.routingSettings;
40
- const routingEnabled = routing?.enableRouting === true;
41
- const subroutes = routing?.pagesAreSubroutesOfRoot === true;
42
- let initialPageLine = "not set";
43
- if (initialPageKey) {
44
- const name = await resolvePageName(projectId, initialPageKey);
45
- initialPageLine = `${name} (${initialPageKey})`;
46
- }
47
- sections.push(`\n## App Details`, `Name: ${appName}`, `Initial page: ${initialPageLine}`, `Routing: ${routingEnabled ? "enabled" : "disabled"}, pages are subroutes: ${subroutes ? "yes" : "no"}`);
48
- }
49
- else {
50
- sections.push(`\n## App Details`, `(not cached)`);
51
- }
52
- // --- Authentication ---
53
- if (authRaw) {
54
- const auth = YAML.parse(authRaw);
55
- const active = auth.active === true;
56
- if (active) {
57
- let provider = "Unknown";
58
- const firebaseConfigs = auth.firebaseConfigFileInfos;
59
- const supabase = auth.supabase;
60
- if (firebaseConfigs && firebaseConfigs.length > 0) {
61
- provider = "Firebase";
62
- }
63
- else if (supabase && Object.keys(supabase).length > 0) {
64
- provider = "Supabase";
65
- }
66
- sections.push(`\n## Authentication`, `Status: Active`, `Provider: ${provider}`);
67
- }
68
- else {
69
- sections.push(`\n## Authentication`, `Status: Inactive`);
70
- }
71
- }
72
- else {
73
- sections.push(`\n## Authentication`, `Status: Inactive`);
74
- }
75
- // Auth page info comes from app-details
76
- if (appDetailsRaw) {
77
- const appDetails = YAML.parse(appDetailsRaw);
78
- const authPageInfo = appDetails.authPageInfo;
79
- if (authPageInfo) {
80
- const homeRef = authPageInfo.homePageNodeKeyRef?.key;
81
- const signInRef = authPageInfo.signInPageNodeKeyRef?.key;
82
- if (homeRef) {
83
- const name = await resolvePageName(projectId, homeRef);
84
- sections.push(`Home page: ${name} (${homeRef})`);
85
- }
86
- if (signInRef) {
87
- const name = await resolvePageName(projectId, signInRef);
88
- sections.push(`Sign-in page: ${name} (${signInRef})`);
89
- }
90
- }
91
- }
92
- // --- Nav Bar ---
93
- if (navBarRaw) {
94
- const navBar = YAML.parse(navBarRaw);
95
- const visible = navBar.show === true;
96
- if (visible) {
97
- const navType = navBar.navBarType || "UNKNOWN";
98
- const labels = navBar.labels === true;
99
- const pageRefs = navBar.pageKeyRefOrder;
100
- sections.push(`\n## Nav Bar`, `Visible: Yes`, `Type: ${navType}`, `Labels: ${labels ? "Yes" : "No"}`);
101
- if (pageRefs && pageRefs.length > 0) {
102
- const tabs = [];
103
- for (let i = 0; i < pageRefs.length; i++) {
104
- const scaffoldId = pageRefs[i].key;
105
- const name = await resolvePageName(projectId, scaffoldId);
106
- tabs.push(` ${i + 1}. ${name} (${scaffoldId})`);
107
- }
108
- sections.push(`Tabs:`, ...tabs);
109
- }
110
- }
111
- else {
112
- sections.push(`\n## Nav Bar`, `Visible: No`);
113
- }
114
- }
115
- else {
116
- sections.push(`\n## Nav Bar`, `Visible: No`);
117
- }
118
- // --- Permissions ---
119
- if (permissionsRaw) {
120
- const permissions = YAML.parse(permissionsRaw);
121
- const builtIn = permissions.permissionMessages;
122
- const custom = permissions.userDefinedPermissions;
123
- const hasBuiltIn = builtIn && builtIn.length > 0;
124
- const hasCustom = custom && custom.length > 0;
125
- if (hasBuiltIn || hasCustom) {
126
- sections.push(`\n## Permissions`);
127
- if (hasBuiltIn) {
128
- sections.push(`Built-in:`);
129
- for (const perm of builtIn) {
130
- const permType = perm.permissionType || "UNKNOWN";
131
- const msg = perm.message?.textValue?.inputValue;
132
- sections.push(` - ${permType}: ${msg ? `"${msg}"` : "(no message)"}`);
133
- }
134
- }
135
- if (hasCustom) {
136
- sections.push(`Custom:`);
137
- for (const perm of custom) {
138
- const names = perm.names;
139
- const iosName = names?.iosName || "?";
140
- const androidName = names?.androidName || "?";
141
- const msg = perm.message?.textValue?.inputValue;
142
- sections.push(` - ${iosName} / ${androidName}: ${msg ? `"${msg}"` : "(no message)"}`);
143
- }
144
- }
145
- }
146
- }
147
- // --- Services ---
148
- sections.push(`\n## Services`);
149
- if (revenueCatRaw) {
150
- const revenueCat = YAML.parse(revenueCatRaw);
151
- sections.push(`RevenueCat: ${revenueCat.enabled === true ? "enabled" : "disabled"}`);
152
- }
153
- else {
154
- sections.push(`RevenueCat: disabled`);
155
- }
156
- // --- Lifecycle Actions (main.dart) ---
157
- sections.push(`\n## Lifecycle Actions (main.dart)`);
158
- const mainFileRaw = await cacheRead(projectId, "custom-file/id-MAIN");
159
- if (mainFileRaw) {
160
- const mainFile = YAML.parse(mainFileRaw);
161
- const actions = mainFile.actions;
162
- if (actions && actions.length > 0) {
163
- const initialActions = actions.filter((a) => a.type === "INITIAL_ACTION");
164
- const finalActions = actions.filter((a) => a.type === "FINAL_ACTION");
165
- const formatAction = (action, index) => {
166
- const identifier = action.identifier;
167
- const name = identifier?.name || "(unnamed)";
168
- const key = identifier?.key;
169
- const fromProject = identifier?.projectId;
170
- const fromSuffix = fromProject ? `, from: ${fromProject}` : "";
171
- return ` ${index + 1}. ${name} (key: ${key}${fromSuffix})`;
172
- };
173
- if (initialActions.length > 0) {
174
- sections.push(`Initial Actions:`);
175
- initialActions.forEach((a, i) => sections.push(formatAction(a, i)));
176
- }
177
- if (finalActions.length > 0) {
178
- sections.push(`Final Actions:`);
179
- finalActions.forEach((a, i) => sections.push(formatAction(a, i)));
180
- }
181
- }
182
- else {
183
- sections.push(`(none)`);
184
- }
185
- }
186
- else {
187
- sections.push(`(none)`);
188
- }
189
- // --- Project File Map ---
190
- const allKeys = await listCachedKeys(projectId);
191
- sections.push(`\n## Project File Map`);
192
- const categoryPatterns = [
193
- { label: "Pages", pattern: /^page\/id-[^/]+$/ },
194
- { label: "Components", pattern: /^component\/id-[^/]+$/ },
195
- { label: "Custom Actions", pattern: /^custom-actions\/id-[^/]+$/ },
196
- { label: "Custom Functions", pattern: /^custom-functions\/id-[^/]+$/ },
197
- { label: "Custom Widgets", pattern: /^custom-widgets\/id-[^/]+$/ },
198
- { label: "Custom Files", pattern: /^custom-file\/id-[^/]+$/ },
199
- { label: "App Action Components", pattern: /^app-action-components\/id-[^/]+$/ },
200
- { label: "API Endpoints", prefix: "api-endpoint/" },
201
- { label: "Collections", prefix: "collections/" },
202
- { label: "AI Agents", pattern: /^agent\/id-[^/]+$/ },
203
- ];
204
- for (const cat of categoryPatterns) {
205
- const count = cat.pattern
206
- ? allKeys.filter((k) => cat.pattern.test(k)).length
207
- : allKeys.filter((k) => k.startsWith(cat.prefix)).length;
208
- if (count > 0) {
209
- sections.push(`${cat.label}: ${count}`);
210
- }
211
- }
212
- return {
213
- content: [{ type: "text", text: sections.join("\n") }],
214
- };
215
- });
216
- }