# 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**

```bash
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`):

```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](../api/authentication.md#tokens) 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](../api/teams.md)): 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](../api/projects.md#moving-a-project-to-another-account)). 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](../api/projects.md#archived-projects) 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](../api/projects.md#exporting-a-project) 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](#sandboxes-without-an-account) | `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](../agents/sandbox.md#keeping-it)) | `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](publish.md)) | `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](auth.md): 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](../api/logs.md) | `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](../agents/sandbox.md) 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](../api/activation.md) 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](https://flatstack.dev/docs/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 /mcp` takes one JSON-RPC message and answers `application/json`. There are no sessions and no server-sent stream: `GET` and `DELETE` answer `405`.
- Without a valid token, `/mcp` answers `401` with `WWW-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](../api/oauth.md).
- Access tokens last an hour and only work at `/mcp`; `/v1` refuses them. Use a personal access token for the REST API.
- Deploy tokens can't use `/mcp`.