flutterflow-mcp
MCP server for FlutterFlow that uses a command-palette model to manage projects, inspect UI, and apply safe edits via transactional ChangeSets.
README
flutterflow-mcp
flutterflow-mcp is an original FlutterFlow MCP server built around a command-palette model.
What Orbit Is
Orbit is different from multi-tool MCP servers by design:
- One primary tool:
orbit - Command palette verbs inside
cmd(help,snapshots.create,changeset.apply, etc.) - SQLite snapshot cache with persisted indices/graphs
- Policy engine (
orbit.policy.json+ env overrides) - Transactional ChangeSet flow:
new -> add -> preview -> validate -> apply
Features
- Node.js 18+ TypeScript strict implementation
- MCP stdio transport for Claude Desktop/Cursor/Windsurf/Claude Code/Codex
- Separate HTTP health/status/policy service on port
8080 - FlutterFlow API adapter (set
FLUTTERFLOW_API_TOKENfor remote reads/writes) - SQLite snapshot store (files, hashes, symbols, edges)
- Navigation + component usage graph index
- Safe editing policy engine with manual-approval mode
- Orbit schema pack resources:
orbit://schema/indexorbit://schema/doc/{id}orbit://schema/snippet/{id}
Quick Start
1) Install
npm i
2) Configure env (optional for local startup)
export FLUTTERFLOW_API_TOKEN=your_token_here
Without a token, the MCP server still starts, but commands that call FlutterFlow APIs (for example projects.list, snapshots.create, snapshots.refresh) will return a missing-token error until you set it.
Optional API path overrides:
export FLUTTERFLOW_API_BASE_URL=https://api.flutterflow.io/v2
export FLUTTERFLOW_API_LIST_PROJECTS_PATH=/l/listProjects
export FLUTTERFLOW_API_LIST_PARTITIONED_FILES_PATH=/listPartitionedFileNames
export FLUTTERFLOW_API_PROJECT_YAMLS_PATH=/projectYamls
export FLUTTERFLOW_API_VALIDATE_PROJECT_YAML_PATH=/validateProjectYaml
export FLUTTERFLOW_API_UPDATE_PROJECT_YAML_PATH=/updateProjectByYaml
export FLUTTERFLOW_API_MIN_INTERVAL_MS=1000
3) Run
npm start
This starts:
- MCP server over stdio
- HTTP server on
http://localhost:8080
Endpoints:
GET /healthGET /statusGET /policy
If the HTTP port is already in use, Orbit now continues with MCP stdio and logs a warning to stderr. You can disable HTTP endpoints entirely with:
export ORBIT_HTTP_ENABLED=0
MCP Client Setup
Use npm start as your MCP command.
Claude Desktop / Cursor / Windsurf
Use templates/mcp-config.local.json as a base:
{
"mcpServers": {
"ff_orbit_mcp": {
"command": "npm",
"args": ["start"],
"cwd": "/absolute/path/to/ff-mcp",
"env": {
"FLUTTERFLOW_API_TOKEN": "YOUR_TOKEN"
}
}
}
}
Claude Code
Use project config file .mcp.json (you can copy templates/claude-code.mcp.json):
{
"mcpServers": {
"ff_orbit_mcp": {
"command": "npm",
"args": ["start"],
"cwd": "/absolute/path/to/ff-mcp",
"env": {
"FLUTTERFLOW_API_TOKEN": "YOUR_TOKEN"
}
}
}
}
Or add it with CLI:
claude mcp add -s project -e FLUTTERFLOW_API_TOKEN=YOUR_TOKEN ff_orbit_mcp -- npm start
Codex (CLI / App)
Option A: add via CLI:
codex mcp add ff_orbit_mcp --env FLUTTERFLOW_API_TOKEN=YOUR_TOKEN -- node /absolute/path/to/ff-mcp/dist/main.js
Build once before using the command above:
npm run build
Option B: add to ~/.codex/config.toml (see templates/codex-config.toml):
[mcp_servers.ff_orbit_mcp]
command = "npm"
args = ["start"]
cwd = "/absolute/path/to/ff-mcp"
env = { FLUTTERFLOW_API_TOKEN = "YOUR_TOKEN" }
enabled = true
Agent Rule Pack
This repository now includes a clone-ready rule/setup pack for agents:
- Rules:
.cursor/rules/ - Playbook:
docs/agent-playbook.md - MCP config templates:
templates/mcp-config.local.json,templates/mcp-config-remote.json,templates/claude-code.mcp.json,templates/codex-config.toml - Installer script:
scripts/install-cursor-rules.sh
Install rules into another repo:
./scripts/install-cursor-rules.sh /absolute/path/to/target-project
Orbit Command Palette
Primary tool signature:
orbit({
cmd: string,
args?: object,
snapshot?: string,
format?: "json" | "explain"
})
Discover commands:
orbit({ cmd: "help" })
Core groups:
- Discovery:
projects.list,snapshots.create,snapshots.refresh,snapshots.refreshSlow,snapshots.ensureFresh,snapshots.info,snapshots.ls - API capabilities:
api.capabilities - Query:
search,page.create,page.scaffold,page.get,page.update,page.preflightDelete,page.remove(recommended),page.delete(low-level),page.clone,component.get,component.extractFromWidget,component.instance.insert,tree.locate,tree.subtree,tree.find(alias),tree.validate,tree.repair,graph.nav,graph.usage,pages.list,textfields.list,widget.get,widget.getMany,widgets.list,widgets.find,widgets.findText,widgets.updateMany,widgets.copyPaste,widget.create,widget.insert,widget.wrap,widget.unwrap,widget.duplicate,widget.deleteSubtree,widget.replaceType,widget.removeChildren,widget.move,widget.moveMany,widget.reorder,widget.action.list,widget.action.get,widget.bindAction,widget.bindData,widget.set,widget.delete,selection.get,selection.clear,intent.run,routes.list,routes.listByPage,routes.validate,routes.upsert,routes.delete,settings.get - Summaries:
summarize.page,summarize.component,summarize.project - Editing:
changeset.new,changeset.add,changeset.preview,changeset.validate,changeset.apply,changeset.applySafe,changeset.rollback,changeset.revert,changeset.drop - Schema:
schema.search,schema.read,schema.snippet
High-ROI recipes
- Generate best-practice pages with deterministic recipes (preview-first):
orbit({ cmd:"page.scaffold", args:{ name:"login2", recipe:"auth.login", preview:true } })orbit({ cmd:"page.scaffold", args:{ name:"preferences", recipe:"settings.basic", params:{ toggles:["Notifications","Dark mode"] }, apply:true } })orbit({ cmd:"intent.run", args:{ text:"create a list page called products with search", preview:true } })
- Duplicate a widget twice:
orbit({ cmd:"widget.duplicate", args:{ nameOrId:"login", nodeId:"id-Text_a", count:2, apply:true } })
- Insert a widget in one call:
orbit({ cmd:"widget.insert", args:{ nameOrId:"login", type:"Divider", beforeNodeId:"id-Button_b", apply:true } })
- Batch-update text widgets:
orbit({ cmd:"widgets.updateMany", args:{ nameOrId:"login", filter:{ type:"Text" }, set:{ text:"James NC" }, apply:true } })
- Find widgets with one canonical search command:
orbit({ cmd:"widgets.find", args:{ nameOrId:"login", type:"TextField", textContains:"Password" } })
- Batch get exact widgets:
orbit({ cmd:"widget.getMany", args:{ nameOrId:"login", nodeIds:["id-Text_a","id-Button_b"] } })
- Route a button to another page:
orbit({ cmd:"routes.upsert", args:{ nameOrId:"login", nodeId:"id-Button_b", toPageNameOrId:"DailyDashboard", apply:true } })
- Inspect and validate page routes:
orbit({ cmd:"routes.listByPage", args:{ nameOrId:"login", direction:"both" } })orbit({ cmd:"routes.validate", args:{ nameOrId:"login", strict:true } })
- Batch move and unwrap widgets:
orbit({ cmd:"widget.moveMany", args:{ nameOrId:"login", nodeIds:["id-Text_b","id-Text_c"], afterNodeId:"id-Button_b", apply:true } })orbit({ cmd:"widget.unwrap", args:{ nameOrId:"login", nodeId:"id-Row_wrap", apply:true } })
- List/get widget trigger actions:
orbit({ cmd:"widget.action.list", args:{ nameOrId:"login", nodeId:"id-Button_b" } })orbit({ cmd:"widget.action.get", args:{ nameOrId:"login", nodeId:"id-Button_b", trigger:"ON_TAP" } })
- Ensure snapshot freshness before read/write:
orbit({ cmd:"snapshots.ensureFresh", args:{ staleMinutes:30 } })
- Use sticky selection in conversational follow-ups:
orbit({ cmd:"selection.get" })orbit({ cmd:"intent.run", args:{ text:"unwrap this", apply:true } })
- Safe apply orchestration:
orbit({ cmd:"changeset.applySafe", args:{ changesetId:"chg_x", confirm:true } })
- Rollback latest applied changeset:
orbit({ cmd:"changeset.rollback", args:{ latestApplied:true, confirm:true, apply:true } })
- Safer page delete flow:
orbit({ cmd:"page.preflightDelete", args:{ nameOrId:"login2" } })orbit({ cmd:"page.remove", args:{ nameOrId:"login2", apply:true } })page.removeattempts hard delete first, and can fall back to archive mode if FlutterFlow rejects delete payloads.
- Validate/repair tree integrity:
orbit({ cmd:"tree.validate", args:{ nameOrId:"login" } })orbit({ cmd:"tree.repair", args:{ nameOrId:"login", fixOrphans:true, fixMissingNodes:true, apply:true } })
Optional support tools:
orbit_policy_getorbit_policy_set(disabled unlessORBIT_ALLOW_POLICY_WRITE=1)orbit_export_changeset
Snapshot Workflow
orbit({cmd:"projects.list"})orbit({cmd:"snapshots.create", args:{projectId:"..."}})- Query against
snapshotid - Run
snapshots.refreshbefore sensitive changes to avoid stale decisions (now throttled by default to reduce 429s)
Orbit snapshots are point-in-time and may be stale.
When FlutterFlow returns versionInfo (partitionerVersion, projectSchemaFingerprint), Orbit stores it and uses it for smarter incremental refresh decisions.
If FlutterFlow is rate-limiting refresh (429), prefer budgeted crawl mode:
orbit({ cmd:"snapshots.refreshSlow", args:{ passes:4, pauseMs:15000, maxFetch:25, concurrency:1, sleepMs:250 } })orbit({ cmd:"snapshots.refresh", args:{ mode:"full", fetchStrategy:"bulk" } })for low-request full sync attemptsorbit({ cmd:"snapshots.refresh", args:{ mode:"full", fetchStrategy:"file", maxFetch:5, concurrency:1, sleepMs:1000 } })for chunked full sync without relisting each pass (chunkedFulldefaults to true for full+file)
Safe Editing Workflow
changeset.newchangeset.add(one or many patches)changeset.preview(diff + risk)changeset.validate(YAML + structure + policy)changeset.applywithconfirm:true(includes remotevalidateProjectYamlchecks before push)
If policy requires manual approval, apply is refused and a manual payload is returned.
Policy Engine
Default file: orbit.policy.json
Fields:
allowProjectsallowFileKeyPrefixesdenyFileKeyPrefixesmaxFilesPerApplymaxLinesChangedrequireManualApprovalallowPlatformConfigEditssafeMode:readOnly | guidedWrite | fullWrite
Env overrides:
ORBIT_POLICY_ALLOW_PROJECTSORBIT_POLICY_ALLOW_FILE_PREFIXESORBIT_POLICY_DENY_FILE_PREFIXESORBIT_POLICY_MAX_FILES_PER_APPLYORBIT_POLICY_MAX_LINES_CHANGEDORBIT_POLICY_REQUIRE_MANUAL_APPROVALORBIT_POLICY_ALLOW_PLATFORM_CONFIG_EDITSORBIT_POLICY_SAFE_MODEORBIT_ALLOW_POLICY_WRITE=1(to enableorbit_policy_set)
Fly.io Deployment
flyctl launch
flyctl secrets set FLUTTERFLOW_API_TOKEN=your_token_here
flyctl deploy
fly.toml is configured with auto-suspend:
auto_start_machines = trueauto_stop_machines = "suspend"min_machines_running = 0
Development
npm run build
npm test
npm start
Security Notes
- Keep
FLUTTERFLOW_API_TOKENonly in environment variables or secret managers. - Snapshot DB can include full project YAML and should be treated as sensitive data.
orbit.policy.jsonshould be code-reviewed because it controls write boundaries.- Built-in read-only guards deny direct apply for custom code and unlocked
lib/main.dart.
FlutterFlow Project APIs Coverage
Orbit supports all currently documented FlutterFlow Project APIs (v2) through the adapter layer:
POST/GET /l/listProjectsGET /listPartitionedFileNamesGET /projectYamls(includingprojectYamlBytesbase64 zip decoding)POST /validateProjectYamlPOST /updateProjectByYaml
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.