Baserow ships a built-in Model Context Protocol (MCP) server that exposes database operations as tools so that AI assistants can read and write Baserow tables directly.
LLM / MCP client
│ POST /mcp (Streamable HTTP, bearer token) GET /mcp/{key}/sse (legacy SSE)
▼
Starlette app, ASGI only ← backend/src/baserow/core/mcp/__init__.py
│
BaserowMCPServer ← backend/src/baserow/core/mcp/__init__.py
│
MCPToolRegistry ← backend/src/baserow/core/mcp/registries.py
│
services.py ← backend/src/baserow/contrib/database/mcp/services.py
│
ActionTypes → Django ORM
registries.py) — Base class for all tools. Subclasses define a type, a Pydantic input_schema, and implement _sync_call(). Tools with enabled = False are registered but hidden from MCP clients.services.py) — Workspace-scoped database operations. Shared by both MCP tools and the enterprise AI assistant.backend/src/baserow/core/mcp/sse.py) — DjangoChannelsSseServerTransport serves the legacy GET /mcp/{key}/sse stream and POST /mcp/messages/.backend/src/baserow/core/mcp/oauth/) — Authorization server, client registration and token views for POST /mcp. The consent and connected-apps APIs used by the frontend are in backend/src/baserow/api/mcp/oauth_views.py.An MCPEndpoint links a user to one workspace and to the tools they allowed (allowed_tools, null allows every enabled tool). There are two kinds:
POST /mcp with an access token. They are listed, and can be disconnected, on the OAuth tab of My settings > MCP server.GET /mcp/{key}/sse. POST /mcp also accepts it as Authorization: Bearer <key>.Users install the client side, the MCP server config plus a skill, from baserow/baserow-mcp. Its README has the setup steps for each agent.
The MCP server requires ASGI mode (Django Channels).
# Start dependencies
just dcd up -d db redis
# Run in dev or ASGI mode
cd backend
# dev mode
just run-dev-server
# or ASGI mode
just run-asgi
MCP clients can sign in with OAuth instead of a pasted key. See ADR 009 for the design.
Set BASEROW_MCP_OAUTH_ENABLED=true to turn it on. It is off by default.
All clients share one URL, <PUBLIC_BACKEND_URL>/mcp. The user picks a workspace and the allowed tools on the consent screen.
Each grant is stored as an MCPEndpoint row with oauth_client_id set. Its tokens carry the scope mcp endpoint:<id>, which ties them to that row. Deleting the row revokes the tokens. Grants are also revoked when the user leaves the workspace. A password change or reset deletes the user’s tokens but keeps the grants, so the apps have to sign in again.
BASEROW_MCP_OAUTH_CIMD_ENABLED=false to turn it off. BASEROW_MCP_OAUTH_CIMD_ALLOWED_HOSTS is a comma-separated host list and defaults to *. Its redirect URIs must also be https or loopback.BASEROW_MCP_OAUTH_DCR_ENABLED=false to turn it off. It only accepts public clients that use the authorization code grant, with at most 10 https or loopback redirect URIs and a body of at most 16 KB. Other redirect URIs are dropped; the registration fails only when none is left.A Celery task deletes clients that have no grant: DCR clients once they have not been used for 30 days (issuing or refreshing a token counts as use), CIMD clients one day after they were first fetched, since they are fetched again when needed. An app whose client was deleted gets a 400 asking the user to remove the Baserow server from the app and add it again.
The limits are per IP, as comma-separated lists. The consent API also applies the authorize limits per user, as it can fetch CIMD documents too. Only failed token calls count, since hosted apps refresh every user’s token from a few IPs. Token revocation has its own limit with the token values, because it loads the client and so can fetch a CIMD document.
| Variable | Default |
|---|---|
BASEROW_MCP_OAUTH_AUTHORIZE_RATE_LIMITS |
30/m,300/h |
BASEROW_MCP_OAUTH_REGISTRATION_RATE_LIMITS |
10/m,60/h |
BASEROW_MCP_OAUTH_TOKEN_RATE_LIMITS |
30/m,300/h |
Access tokens last one hour. Clients always get a refresh token, whether or not they ask for offline_access; refresh tokens rotate and expire after 30 days. Replaying a used refresh token revokes the whole family.
BASEROW_MCP_OAUTH_ENABLED=true just run-dev-server
claude mcp add --transport http baserow http://localhost:8000/mcp
ALLOW_LOCALHOST_LOOPBACK is already on in the settings, so clients can use http://localhost redirect URIs.
See the test plan for other clients, the plugin and the skill.
OpenAI verifies the domain of a submitted ChatGPT app by fetching /.well-known/openai-apps-challenge. Set BASEROW_MCP_OPENAI_APPS_CHALLENGE to the token shown in OpenAI’s portal and the backend returns it as plain text. When it’s empty the route returns a 404. Like the other OAuth routes, it only exists with BASEROW_MCP_OAUTH_ENABLED=true.
Some tools are currently disabled (enabled = False) and hidden from MCP clients. The consent page does not list them.
| Tool | Status | Description |
|---|---|---|
list_databases |
enabled | List all databases in the workspace |
list_tables |
enabled | List tables, optionally filtered by database |
get_table_schema |
enabled | Get field definitions for one or more tables |
list_table_rows |
enabled | List rows with optional search and pagination |
create_rows |
enabled | Create one or more rows using field names |
update_rows |
enabled | Update rows by ID using field names |
delete_rows |
enabled | Delete rows by ID |
create_database |
disabled | Create a new database |
create_table |
disabled | Create a table with optional initial fields |
update_table |
disabled | Rename a table |
delete_table |
disabled | Delete (trash) a table |
create_fields |
disabled | Add fields to an existing table |
update_fields |
disabled | Update existing fields |
delete_fields |
disabled | Delete (trash) fields |
list_databases
└─ list_tables(database_id)
└─ get_table_schema([table_id]) ← learn field names and types
├─ create_rows(table_id, [{Name: "Alice"}])
├─ update_rows(table_id, [{id: 1, Name: "Bob"}])
└─ delete_rows(table_id, [1, 2])
MCPTool in the appropriate mcp/*/tools.py file.input_schema._sync_call(self, endpoint, args).backend/src/baserow/contrib/database/apps.py.backend/tests/baserow/contrib/database/mcp/.Set enabled = False on the class if the tool should not be exposed to MCP clients yet.
See docs/testing/mcp-test-plan.md for manual testing instructions.
# Server, transports and OAuth
just b test tests/baserow/core/mcp/
# Tools and services
just b test tests/baserow/contrib/database/mcp/
# Endpoint API
just b test tests/baserow/api/test_mcp_views.py
# OAuth audit log (enterprise)
just b test ../enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_mcp_oauth_audit_log.py
# Service layer only
just b test tests/baserow/contrib/database/mcp/test_mcp_services.py