MCP setup
Create a key, pick a scope, wire it into Claude or Cursor.
Connecting an MCP client to Beetl takes one API key and one config block. The key is per user and per tenant, so every call an LLM client makes is attributed to you.
Create an API key
Go to Settings, then the User tab. Create a key with a label you will recognise later, such as claude-desktop-laptop.
The key is shown once, in a one-time reveal dialog. Copy it then. There is no way to read it again, and there is no rotation: if you lose it, revoke the row and create a new one.
Pick a scope
There are two scopes.
| Scope | What the client can do |
|---|---|
read | Run SQL through execute_query, list datasets and pipelines, page an existing result, inspect pipeline runs, preview a stored pipeline definition with run_pipeline, and read the platform wiki. |
write | Everything in read, plus build_pipeline, update_pipeline and deploy_pipeline. |
Those three are the only write-scoped tools on the MCP surface. Everything else, run_pipeline included, is read.
Pick read unless you actually intend the client to author and deploy pipelines. Scope is checked at the tool-dispatch choke point, so a read key calling a write tool gets an error result naming both the tool and the scope it needed.
Older keys may still display as read_only, draft or production. That three-scope model was
collapsed by migration in August 2026. read_only and draft both map to read, and
production maps to write. Two scopes is the current model.
Wire it into your client
The endpoint is POST /mcp on your Beetl host. On the hosted platform that is https://app.beetl.io/mcp. Self-hosted deployments use their own host, so check with whoever runs your instance.
Authentication is the X-API-Key header.
{
"mcpServers": {
"beetl": {
"type": "http",
"url": "https://app.beetl.io/mcp",
"headers": {
"X-API-Key": "PASTE_YOUR_KEY_HERE"
}
}
}
}Verify the handshake
The server is stateless over HTTP, so a single POST is enough to prove the key works. No session header is involved.
curl -sS https://app.beetl.io/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: $BEETL_API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": { "name": "curl", "version": "1.0" }
}
}'A working key returns a result carrying protocolVersion, a tools capability with listChanged: false, and serverInfo. A bad or revoked key returns 401.
To see what your key can actually call, follow up with tools/list:
curl -sS https://app.beetl.io/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: $BEETL_API_KEY" \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'Using the tools
Two behaviours surprise people on the first call.
execute_query requires a purpose argument alongside the SQL. It is a short, user-facing sentence saying what the query is meant to establish. This is not optional, and it is what makes the query history readable afterwards.
Results preview at 50 rows with a has_more_rows flag. When it is set, page the rest with get_query_result using the returned result_id rather than re-running the query.
Queries issued this way are attributed as MCP queries in Beetl's own history, separately from chat and automation queries.
Limits worth knowing before you rely on this
API keys have no expiry, no rotation, no per-key resource allow-list, no rate limiting, and no usage log beyond a last_used_at timestamp. Revoking a key means deleting it.
Deactivating the user or the tenant kills the key immediately, because the lookup joins both.
Full detail is on API keys.