# Tokens and scopes

Source: https://modulify.ai/docs/mcp/tokens-and-scopes

Every MCP scope, what it permits, and how tokens are created, edited and revoked.

An access token is how an AI client signs in to Modulify. It belongs to your user account, carries a fixed list of scopes, and can be limited to specific workspaces. Tokens live at `/dashboard/tokens`, reached from **Tokens** in the user menu.

## The scopes

Twenty-four scopes exist. The label column is what the checkbox says in the Tokens form.

| Scope | Label in the product | What it permits | Ticked by default |
| --- | --- | --- | --- |
| `workspaces:read` | Read workspaces and credits | Your workspaces, your role in each, and the AI credit balance | Yes |
| `sites:read` | Read sites and settings | Listing sites and templates, reading one site, publish status and history, backups | Yes |
| `sites:create` | Create and duplicate sites | Creating a site from a prompt, duplicating an existing one | Yes |
| `sites:write` | Rename and update sites | Name, slug, description, category, server location, badge, free subdomain, sharing settings, the site thumbnail, turning visitor collection on and off, taking and restoring backups, and all folder work short of deleting one | Yes |
| `sites:delete` | Delete sites | Deleting a site, deleting a backup, emptying a collection, dropping a whole database, clearing visitor history, deleting the whole chat history, deleting or moving a folder, removing a queued message, restoring a code version | No |
| `chat:read` | Read chat and generation progress | Generation progress, chat history, the message queue, version history | Yes |
| `chat:write` | Send prompts and cancel generations | Sending a prompt, cancelling a generation, editing, reordering and resuming the queue | Yes |
| `code:read` | Read site source files | Listing and reading source files | Yes |
| `code:write` | Write site source files | Replacing source files directly in the running preview, bypassing the AI and version history | No |
| `publish:write` | Publish and unpublish sites | Publishing a site and taking it offline | Yes |
| `config:read` | Read domains, secrets, crons and webhooks | Variable names, scheduled jobs and their runs, webhooks and deliveries, domain status | Yes |
| `config:write` | Change domains, secrets, crons and webhooks | Setting and deleting variables, creating, editing, deleting, toggling and running scheduled jobs and clearing their history, creating, editing, deleting, toggling and testing webhooks and clearing their delivery log, all domain changes | No |
| `data:read` | Read CMS collections and stored files | Database state, collections, schemas, rows, and listing and reading everything in file storage | Yes |
| `data:sql` | Run SQL against the site database | Running any statement, including one that drops a table or deletes rows | No |
| `data:write` | Change CMS rows and stored files | Creating, updating and deleting rows, giving a site a database, and uploading, creating, moving, deleting and clearing stored files | No |
| `workspaces:write` | Create and change workspaces and roles | Creating and renaming a workspace, setting the default, leaving one, and creating, changing and deleting custom roles | No |
| `members:write` | Invite and remove members | Inviting, resending, cancelling, removing a member and putting one on a different role | No |
| `account:read` | Read your account and referrals | Your own profile and preferences, your plan site usage, invitations waiting for you, your referrals and payouts | Yes |
| `account:write` | Change your account settings | Your display name, phone, avatar, notification preferences, referral code and payout email | No |
| `comments:read` | Read site comments | Review comments, their replies, reactions and attachments, and the open thread count | Yes |
| `comments:write` | Write, resolve and delete comments | Leaving, editing, replying to, resolving, reopening and deleting comments, reactions, pin positions and attachments | No |
| `logs:read` | Read site server logs | The recent server log lines of the preview machine and the published site | Yes |
| `analytics:read` | Read visitor analytics | Visitor stats, realtime visitors, the analytics installation check, and free-form breakdown queries | Yes |
| `credentials:reveal` | Reveal and rotate credentials in plain text | Reading environment variable values, the private storage key and webhook signing secrets, and replacing the last two with new ones | No |

### Which tools each scope unlocks

| Scope | Tools |
| --- | --- |
| `workspaces:read` | `list_workspaces`, `get_workspace`, `list_workspace_members`, `check_member_email`, `get_workspace_credits`, `get_workspace_credit_usage`, `get_role_member_counts`, `get_site_usage` |
| `workspaces:write` | `create_workspace`, `rename_workspace`, `set_default_workspace`, `leave_workspace`, `create_workspace_role`, `update_workspace_role`, `delete_workspace_role` |
| `members:write` | `invite_workspace_member`, `resend_workspace_invite`, `cancel_workspace_invite`, `remove_workspace_member`, `assign_member_role` |
| `account:read` | `get_account`, `list_pending_invitations`, `list_referrals`, `list_payouts`, `check_affiliate_code` |
| `account:write` | `update_account`, `update_affiliate` |
| `sites:read` | `list_sites`, `get_site`, `check_name_available`, `check_subdomain_available`, `list_templates`, `get_publish_status`, `get_publish_timeline`, `list_deployments`, `list_backups`, `list_folders`, `check_folder_slug_available` |
| `sites:create` | `create_site`, `duplicate_site` |
| `sites:write` | `rename_site`, `set_show_badge`, `change_subdomain`, `update_sharing`, `create_backup`, `restore_backup`, `set_analytics_enabled`, `create_folder`, `rename_folder`, `update_folder_slug`, `move_sites_to_folder`, `remove_sites_from_folder`, `reorder_folders`, `set_site_thumbnail`, `clear_site_thumbnail` |
| `sites:delete` | `delete_site`, `delete_backup`, `clear_collection`, `remove_queued_message`, `restore_version`, `clear_site_analytics`, `clear_site_database`, `delete_folder`, `move_folder_to_workspace`, `clear_site_chat` |
| `chat:read` | `get_job`, `list_messages`, `list_queued_messages`, `list_versions` |
| `chat:write` | `send_message`, `cancel_job`, `edit_queued_message`, `reorder_queued_message`, `resume_queue` |
| `code:read` | `list_files`, `read_file` |
| `code:write` | `write_file` |
| `logs:read` | `get_site_logs`, `get_publish_logs` |
| `comments:read` | `list_comments`, `get_comment`, `count_open_comments`, `list_comment_attachments` |
| `comments:write` | `create_comment`, `reply_to_comment`, `update_comment`, `resolve_comment`, `reopen_comment`, `delete_comment`, `move_comment_pin`, `add_comment_reaction`, `remove_comment_reaction`, `upload_comment_attachment`, `delete_comment_attachment` |
| `publish:write` | `publish_site`, `unpublish_site`, `stop_publish` |
| `config:read` | `list_secrets`, `list_site_crons`, `list_site_cron_runs`, `preview_cron_schedule`, `export_site_crons`, `export_site_webhooks`, `list_site_webhooks`, `list_webhook_deliveries`, `get_domain_status`, `get_domain_connect_url` |
| `config:write` | `set_secret`, `set_secrets`, `delete_secret`, `create_site_webhook`, `update_site_webhook`, `delete_site_webhook`, `delete_all_site_webhooks`, `delete_webhook_delivery`, `clear_webhook_deliveries`, `create_site_cron`, `update_site_cron`, `toggle_site_cron`, `run_site_cron`, `delete_site_cron`, `delete_all_site_crons`, `delete_site_cron_run`, `clear_site_cron_runs`, `toggle_site_webhook`, `test_site_webhook`, `add_custom_domain`, `remove_custom_domain`, `add_www_domain`, `remove_www_domain`, `start_domain_verification`, `verify_domain`, `cancel_domain_verification` |
| `data:read` | `get_site_database`, `list_collections`, `get_collection_schema`, `list_rows`, `list_storage_files`, `get_storage_stats`, `get_storage_info`, `get_storage_file_url`, `read_storage_file`, `list_reference_options`, `list_enum_options`, `export_collection` |
| `data:sql` | `execute_sql` |
| `data:write` | `create_row`, `update_row`, `delete_row`, `create_site_database`, `upload_storage_file`, `create_storage_folder`, `move_storage_files`, `delete_storage_files`, `delete_storage_folder`, `clear_storage` |
| `analytics:read` | `get_site_analytics`, `get_site_realtime_visitors`, `check_analytics_installation`, `query_site_analytics`, `export_site_analytics` |
| `credentials:reveal` | `get_secret`, `get_all_secrets`, `get_storage_key`, `rotate_storage_key`, `get_site_webhook_secret`, `rotate_site_webhook_secret` |

### The ten that are off by default

`sites:delete`, `config:write`, `data:write`, `comments:write`, `workspaces:write`, `members:write`, `account:write`, `code:write`, `data:sql` and `credentials:reveal` are unticked when the form opens. They are the scopes behind irreversible work: deleting sites and backups, emptying collections, overwriting rows, uploading over and deleting stored files, clearing a whole bucket, deleting scheduled jobs and their history, removing a domain and its certificate, and rewriting environment variables. Tick them only for a client you want doing that unattended. `members:write` deserves its own thought: it sends real email to real colleagues and can remove someone's access to every site in a workspace.

`credentials:reveal` is the one to think hardest about. It is the only scope that hands a client a live credential in plain text: your environment variable values, the private storage key, which reads and writes every file in your bucket, and webhook signing secrets, which let anyone holding them forge a delivery that passes verification. Whatever you connect keeps those values in its transcript, so grant it only for a one-off job and revoke the token afterwards. Every other scope lets a client work with your storage and your webhooks without ever seeing a secret.

Note that `sites:delete` covers more than sites. It also gates `clear_collection`, `clear_site_database`, `clear_site_analytics`, `delete_backup`, `delete_folder`, `move_folder_to_workspace` and `remove_queued_message` and `restore_version`, which erases the entire site chat history.

## Creating a token

Click **Create token** on the Tokens page. The button is disabled once you hold 10 active tokens, with the tooltip "You can hold 10 active tokens at a time." Revoked and expired tokens do not count towards that.

| Field | Rule |
| --- | --- |
| Token name | Required, up to 60 characters |
| Expires after | 30, 90, 180 or 365 days. Defaults to 90 days |
| Which workspaces it can reach | Every workspace by default. Only shown when you belong to more than one |
| What this token can do | At least one scope must be ticked |

The generated value starts with `mdf_mcp_` and is shown exactly once. Modulify stores only a hash of it and the last six characters, which is what the token list displays. If you lose it, revoke it and create another.

Copying it puts it on your clipboard for one minute, then Modulify clears the clipboard again if the token is still on it.

## Workspace access

A token acts as you, so by default it reaches every workspace where you are a member. Narrowing it to one workspace is the right move when you hand it to a client that only works on that workspace.

Every tool except `list_workspaces` and `list_templates` has to say which workspace or site it is acting on, and three checks run before it does:

1. The call must resolve to a workspace, from its `workspaceId` or from the workspace that owns its `projectId`. A call that resolves to neither is refused before it runs, rather than being left to the endpoint behind it. A `projectId` that does not exist is refused the same way.
2. That workspace must be one the token is allowed to reach. Otherwise the call is refused with a message telling the client to use `list_workspaces` to see where it can work.
3. Your role in that workspace must hold the **API access** permission, described in the product as "Reach this workspace with a token from an AI client". Workspace owners hold every permission. The stock member role does not, so an owner grants it on a custom role.

`list_workspaces` and `list_templates` are the two exceptions, because neither belongs to a workspace: one enumerates the workspaces you can reach, the other lists public templates. Both are read-only.

Picking workspaces you are not a member of is refused outright when you save the token.

## The token list

| Column | What it shows |
| --- | --- |
| Name | The name you gave it, with the last six characters of the token underneath |
| Status | Active, Revoked or Expired |
| Permissions | How many scopes it holds |
| Workspaces | "All workspaces", or the count it is limited to |
| Last Used | Time since the last call, or "Never" |
| Expires | The expiry date, or a dash once revoked |

Last Used is written at most once a minute per token, so a client calling in a tight loop does not move it on every call.

Search filters by token name. The status filter offers All, Active, Revoked and Expired, and the sort offers Newest, Oldest, Name and Recently used. With no tokens at all the page reads "No tokens" and "Create one to connect an AI client."

## Editing a token

The three-dots menu on a row offers **Edit token**. You can change the name, the scopes and the workspaces. You cannot change the token value or its expiry date: as the form says, "The token itself and its expiry date stay as they were issued. New permissions reach every client already using it."

Edit is disabled on a revoked or expired token, with the tooltip "A revoked or expired token cannot be edited."

## Revoking a token

The same menu offers **Revoke token**, in red. Confirming asks: "Any AI client signed in with this token loses access to your account immediately. This cannot be undone, so you would need to create and paste a new token."

Revoking takes effect at once. The next request carrying that token gets a `401` reading "This access token is invalid, revoked or expired." The row stays in the list marked Revoked, so you keep the record of what existed, and it no longer counts towards your 10 active tokens.

Expiry works the same way from the client's side: past the date, every call is refused, and the token cannot be edited or extended. Create a new one.

## Rate limits and result size

A token may make **240 tool calls per minute**. Beyond that, a call comes back as an error naming the seconds to wait: "Too many tool calls. Wait 27 seconds and try again." The window is counted per token, so two clients sharing one token share the budget.

A single tool result is capped at **60,000 characters**. Anything longer is cut off with a note asking for a narrower slice, which is why the listing tools page: `list_sites` and `list_templates` return 8 per call, `list_rows` returns 50 and caps at 200, `list_messages` and `list_versions` return 10, `list_site_cron_runs` returns 25, `list_webhook_deliveries` returns 30, and `list_storage_files` returns 50. `read_storage_file` is capped by size rather than by count, refusing anything over 5MB.

## Next

- [Connect a client](/docs/mcp/connect-a-client) for the setup itself.
- [Tools](/docs/mcp/tools) for what each tool takes and returns.