Connecting AI tools over MCP
Flatstack runs a remote MCP server at https://api.flatstack.dev/mcp. Claude, Claude Code, Cursor, Codex and any other client that speaks MCP over Streamable HTTP can connect to it, sign in with your Flatstack account, and then create projects, deploy, set variables and secrets, manage domains and email, import from Supabase or Clerk, read logs and usage, and look things up in these docs for you.
Connecting
You sign in once in the browser: the client opens Flatstack’s consent screen, you choose what it may do, and it receives its own token. You need a Flatstack account first; the MCP server can’t create one.
Claude Code
claude mcp add --transport http flatstack https://api.flatstack.dev/mcp
Then run /mcp in Claude Code, choose flatstack and Authenticate.
Claude (claude.ai and the desktop app): Settings, Connectors, Add custom connector, with the URL https://api.flatstack.dev/mcp. Then choose Connect.
Cursor: add to ~/.cursor/mcp.json (or the project’s .cursor/mcp.json):
{ "mcpServers": { "flatstack": { "url": "https://api.flatstack.dev/mcp" } } }
Cursor shows Needs login next to the server; choose it to sign in.
Other clients: give them the URL. A client that supports MCP authorisation finds the sign-in on its own. One that doesn’t can send a personal access token instead, as Authorization: Bearer fs_pat_…, for example claude mcp add --transport http flatstack https://api.flatstack.dev/mcp --header "Authorization: Bearer $FLATSTACK_TOKEN".
What the consent screen asks
The consent screen names the app, shows the address it will return you to, and lists what it asked to do. Untick anything you don’t want it to have. It never gets more than your role allows (see Teams and roles): a developer can’t give an app projects:write, for example, and if your role changes later, the app’s access changes with it.
| Permission | Lets the app |
|---|---|
projects:read |
See projects, deploys, domains, email and usage |
projects:write |
Create projects and change domains and email settings |
deploy |
Deploy code, apply migrations, roll back, run the security scan |
env:read |
See environment variables and the names of secrets |
env:write |
Set environment variables and secrets |
logs:read |
Read logs |
data:read |
See your apps’ data, files and users |
data:write |
Change your apps’ data, files and users, and import |
addons:buy |
Buy add-ons on your account’s card, each one only after you confirm it (owners only) |
projects:lifecycle |
Export, archive, delete and restore projects, archiving and deleting each only after you confirm it (owners only) |
Like other tokens, an MCP app can’t manage members or tokens, change plan or cards, remove add-ons, or transfer a project to another account. Those need you signed in to the dashboard; an owner sends a project from its Settings in the dashboard and the receiving owner accepts it on Projects, then Transfers (see moving a project). With projects:lifecycle, an owner’s app can delete and restore projects (delete_project needs confirm: true).
Connected apps are listed on Security in the dashboard, with what each can do and when it was last used. Disconnect cuts one off at once. An app nobody uses for 90 days is disconnected on its own. Owners and admins see and can disconnect every member’s apps.
Tools
Every tool calls the same API the dashboard and CLI use, so it behaves the same way and its changes are in the audit log, under the app’s token. Most tools take projectId (from list_projects) and an optional environment: production, the default, or preview/<name>.
| Tool | What it does | Needs |
|---|---|---|
list_projects |
The account’s projects (deleted: true for deleted ones you can still restore). Idle projects come back as suggestions and warnings: park an active one with no requests for 30 days, archive a parked one with none for 90. Only suggestions: act on one only when you ask |
projects:read |
create_project |
Create and provision a project: name, optional slug, region, engine |
projects:write |
get_project |
One project | projects:read |
park_project, promote_project |
Move a project to a parked slot, or a parked one to an active slot; same ID, URL, data and deploys | projects:write |
archive_project |
Archive a project: export it, keep the export 12 months, remove what’s live, free its slot. Needs confirm: true. Owners only |
projects:lifecycle |
restore_project |
Restore a deleted project (within 30 days), or an archived one into a free slot; optional state: active or parked |
projects:lifecycle |
delete_project |
Delete a project; restorable for 30 days. Needs confirm: true. Owners only |
projects:lifecycle |
export_project |
Export a project as one ZIP with SELF_HOST.md; returns a download link that lasts an hour. Works while the account is read-only. Owners only |
projects:lifecycle |
create_sandbox |
Signed in, a parked project in the account with no expiry (template optional). Fails with parked_slots_full when none is free. Without an account, use the sandbox endpoint |
projects:write |
claim_sandbox |
Move an unclaimed sandbox into the account, where it lands parked, or archived when no parked slot is free: claim, its claim URL or fs_clm_ token (keeping a sandbox) |
projects:write |
get_usage |
This period’s usage against each limit, for the account or one project | |
get_activation |
The setup checklist: each step done or not, and next, the step to offer |
|
list_addons |
Add-ons the account can buy (with price, what each adds and onSale), the ones it holds, and the AI credit balance |
addons:buy |
buy_addon |
Buy an add-on: addOnId, optional quantity and projectId (always-on Postgres). Needs confirm: true. Packs return a Checkout url for you to pay |
addons:buy |
deploy |
Deploy a bundled Worker with its migrations, scan sources and cron jobs, and a small site as staticFiles ([{ path, content }], base64) with its hosting config. Larger sites deploy with the CLI |
deploy |
get_deploy_status |
One deploy, or the latest deploys without deployId |
projects:read |
rollback |
Make an earlier deploy live again | deploy |
publish_site |
Put static files (a prototype’s index.html and the rest) live at a shareable URL in one call (quick publish) |
projects:write |
apply_schema |
Apply migration files without deploying; dryRun: true shows what would run |
deploy |
run_security_scan |
The deploy’s security scan, without deploying | deploy |
list_env |
Variables with values, and secrets by name | env:read |
set_env |
Set a variable | env:write |
set_secret |
Set a secret; its value is never shown again | env:write |
list_domains, add_domain, check_domain |
Custom domains and the DNS records to add | projects:read, projects:write |
add_email_domain, get_email_domain, check_email_domain |
Sender domains: the record to add, status and diagnostics | projects:read, projects:write |
remove_email_domain |
Remove a sender domain. Needs confirm: true |
projects:write |
send_test_email |
Send a template to your own address | projects:write |
get_email_log |
Recent sends and their delivery status | projects:read |
import_project |
Import from Supabase, Lovable Cloud or Clerk; dryRun: true returns the migration report and writes nothing |
data:write |
get_import_status |
An import’s progress; advance: true runs its next step, report: true adds the report and the files to write |
data:read |
list_cron_runs |
Cron run history | projects:read |
list_end_users |
Your app’s users, with search | data:read |
get_auth_settings |
How your app’s users sign in: methods, email verification, OAuth providers, Turnstile, trusted origins, email templates and pages. Client secrets show only as clientSecretSet: true |
projects:read |
set_auth_settings |
Change sign-in settings; only the fields sent change. A provider takes clientId and, the first time, clientSecret; leave clientSecret out to keep the stored one, and set a provider or turnstile to null to turn it off. Secrets are never returned by any tool. Reaches the app on its next deploy |
projects:write |
get_logs |
An environment’s logs, newest first: console output, requests, exceptions and cron runs, with the deploy that served each. Filter with since (15m, 2h or an ISO time; default an hour), level (the lowest to include), search and limit. Secrets are redacted before logs are stored. See Logs |
logs:read |
search_docs |
Search these docs and the error reference: query, optional limit (default 5, at most 20). Each result has a title, section, url on flatstack.dev and a short snippet |
More tools arrive as their features ship: branches, errors, SQL, export and type generation.
Sandboxes without an account
https://api.flatstack.dev/mcp/sandbox needs no sign-in. It offers create_sandbox, which makes a 24-hour sandbox and returns its URL, a token and a claim link for the person you’re building for, and then apply_schema, deploy, get_deploy_status, get_logs, run_security_scan, set_env, set_secret and search_docs for that sandbox alone. Send the token as a Bearer token or as each tool’s token argument; projectId isn’t needed. Three sandboxes an hour and ten a day per address (sandbox_rate_limited). Account tokens are refused there, and sandbox tokens are refused here.
Results, warnings and errors
Every result is JSON with a warnings list. It holds cap warnings (a metric at 80% of its limit, or capped at 100%) and security scan findings. Clients should show them to you.
Results of create_project, deploy, publish_site and a completed get_import_status also carry next: the setup step to offer you next (id, label, the CLI command, the mcpTool that does it, and a docs link), or null once the setup checklist is complete. It is the earliest checklist step still undone that a tool can do; get_activation returns the whole checklist. Agents are asked to offer it once, in their own words.
A failed call comes back as a tool error whose body is the API’s error: { "error": { "code", "message", "docsUrl", "details"? }, "warnings": [] }. The codes are the API’s own; each has a page under errors. Arguments that don’t match a tool’s schema get validation_failed with details for each field. A missing permission is forbidden, with details.requiredPermission.
Changes that can’t be undone
Destructive tools refuse to run without confirm: true and answer confirmation_required. remove_email_domain, archive_project and delete_project are. apply_schema and deploy take confirm: true to apply migrations that drop or rewrite data; without it they answer destructive_migration and list the changes. A deploy with critical scan findings is refused with deploy_blocked_by_scanner unless the call passes forceInsecure: { confirm: "<project slug>" }. A client should ask you before setting any of these.
For client developers
The server follows the MCP authorisation spec.
POST /mcptakes one JSON-RPC message and answersapplication/json. There are no sessions and no server-sent stream:GETandDELETEanswer405.- Without a valid token,
/mcpanswers401withWWW-Authenticate: Bearer resource_metadata="https://api.flatstack.dev/.well-known/oauth-protected-resource/mcp". - OAuth 2.1 with PKCE (
S256), dynamic client registration and rotating refresh tokens. The endpoints are in OAuth for MCP clients. - Access tokens last an hour and only work at
/mcp;/v1refuses them. Use a personal access token for the REST API. - Deploy tokens can’t use
/mcp.
As Markdown: apps/mcp.md