flutterflow-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@flutterflow-mcplist my FlutterFlow projects"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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:
orbitCommand 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
8080FlutterFlow 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 i2) Configure env (optional for local startup)
export FLUTTERFLOW_API_TOKEN=your_token_hereWithout 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=10003) Run
npm startThis 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=0MCP 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 startCodex (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.jsBuild once before using the command above:
npm run buildOption 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 = trueAgent Rule Pack
This repository now includes a clone-ready rule/setup pack for agents:
Rules:
.cursor/rules/Playbook:
docs/agent-playbook.mdMCP config templates:
templates/mcp-config.local.json,templates/mcp-config-remote.json,templates/claude-code.mcp.json,templates/codex-config.tomlInstaller script:
scripts/install-cursor-rules.sh
Install rules into another repo:
./scripts/install-cursor-rules.sh /absolute/path/to/target-projectOrbit 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.lsAPI capabilities:
api.capabilitiesQuery:
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.getSummaries:
summarize.page,summarize.component,summarize.projectEditing:
changeset.new,changeset.add,changeset.preview,changeset.validate,changeset.apply,changeset.applySafe,changeset.rollback,changeset.revert,changeset.dropSchema:
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
snapshotidRun
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 deployfly.toml is configured with auto-suspend:
auto_start_machines = trueauto_stop_machines = "suspend"min_machines_running = 0
Development
npm run build
npm test
npm startSecurity 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
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamesnocode/flutterflow-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server