The managed /api/2.0/mcp/sql endpoint ignores every client-side warehouse hint, but it does honor a server-side per-user override API. Here's how the default selection works, when it breaks production workloads, and the two approaches that give you explicit control.
Three takeaways. Everything else in this guide supports them.
URL path, query string, HTTP headers, JSON-RPC arguments — /api/2.0/mcp/sql ignores every client-side pinning attempt. The server picks the warehouse.
A five-tier priority list runs at every tools/call (running > stopped, “shared”-named > generic, serverless > classic, user-owned > not). One MCP session can route to different warehouses across calls.
Option A: Set a per-user default warehouse override via the SQL Warehouses API — the managed MCP honors it natively, no code needed. Option B: Run a 145-line custom MCP when you need per-agent routing or pinning that follows the workload. Reference here.
Databricks ships pre-built MCP endpoints under your workspace URL. You wire /api/2.0/mcp/sql into Claude Code or Cursor with workspace OAuth and ask the agent to run SQL. The query has to land on a warehouse, but the endpoint accepts no warehouse hint from your client.
Four mechanisms could pin a warehouse from the client side. None of them work. Outcomes below come from direct probing of the live service.
| Pinning mechanism attempted | Outcome | Where the query actually ran |
|---|---|---|
/api/2.0/mcp/sql/<warehouse_id> (path) |
HTTP 404 | Not implemented — request rejected |
?warehouse_id=<id> (query string) |
Silently ignored | Different warehouse — pin was dropped server-side |
warehouse_id in tools/call args |
Not in schema | Ignored — tool's JSON schema doesn't declare the field |
Custom HTTP headers (X-Databricks-Warehouse-Id, etc.) |
No effect | No documented header exists; never observed honored |
Your client has no way to tell the managed MCP which warehouse to use. The server decides, every time.
The server walks a five-tier priority algorithm, matching the public CLI command databricks experimental aitools tools get-default-warehouse. Both produce identical results against the same workspace.
Shared endpoint or dbdemos-shared-endpointshared anywhere in its nameshared in nameWhen multiple warehouses match the same tier, the algorithm orders them by:
| Rank | Criterion | Effect |
|---|---|---|
| 1 | Serverless compute | Serverless warehouses always preferred over classic within a tier |
| 2 | Ownership matches calling user | Warehouses created by the authenticated user preferred next |
| 3 | API list order | Final fallback — whichever the warehouses API returns first |
The selection re-evaluates on every tools/call. If one warehouse stops mid-session and another starts, follow-up queries route to whichever is now running. There's no session affinity. Routing is volatile.
Routing outcomes for typical workspace states. The same MCP setup makes different decisions based on what's running.
| Workspace state at query time | Where the managed MCP routes the query |
|---|---|
| Single warehouse, RUNNING | That one (only option in tier 3) |
| Single warehouse, STOPPED | That one — SDK auto-starts it |
| Multiple warehouses, exactly one RUNNING | The running one (tier 3 dominates) |
| Multiple RUNNING, one named “Shared endpoint” | “Shared endpoint” (tier 1) |
| Multiple RUNNING, one has “shared” in name | The “shared”-named one (tier 2) |
| Multiple RUNNING, no “shared” naming convention | Serverless first → user-owned next → API order |
| All STOPPED | Tier 4/5 selection; auto-starts the chosen one |
Most Databricks workspaces ship with a Serverless Starter Warehouse. It's serverless (tier preference), Databricks-managed, and usually the only warehouse running in a new workspace. So managed-MCP queries land on it consistently out of the box. People see this and assume the MCP picks a sensible default. That's coincidence, not policy. The moment a teammate starts a different warehouse, routing flips.
Dynamic routing is acceptable for single-user prototyping. It becomes a liability in four production scenarios.
Agent queries are billed to whichever warehouse runs them. With dynamic routing, your marketing team's warehouse can end up paying for engineering's agent queries. Pinning routes spend back to the team that owns the agent.
A bad agent query on a shared warehouse starves other teams' workloads. Pinning each agent to its team's warehouse stops runaway queries from spilling across team boundaries.
Warehouses differ in size, max scaling limits, and Photon configuration. Production agents with latency SLAs need queries on a specific tuned warehouse, not whatever happens to be warm.
Some regulatory frameworks require fixed compute boundaries. Data with residency constraints can only run on specific compute. Dynamic routing breaks any audit pattern that maps agent activity to specific warehouses.
Pick the one that matches your scope. Option A is native and simpler; Option B handles cases Option A can't.
The Databricks SQL Warehouses API lets admins (and users for themselves) configure a default warehouse per user. The managed /api/2.0/mcp/sql endpoint honors this override when routing that user's queries. Verified end-to-end against the live service. Full walkthrough at the Option A focused guide, including UI, CLI, and REST API paths.
databricks warehouses create-default-warehouse-override \
me CUSTOM \
--warehouse-id <warehouse-id> \
--profile <your-profile>
Type values: CUSTOM (specific warehouse) or LAST_SELECTED (sticky most-recent). Read with get-default-warehouse-override, update with update-default-warehouse-override, list/delete with the obvious siblings. Admins can target any user by replacing me with the numeric user ID. Full API docs.
Every query from a given user should route to the same warehouse. Common for per-team isolation (each user belongs to one team, one team owns one warehouse), cost attribution (spend tracks to the user's department), or compliance audits that map users to compute boundaries. No .mcp.json changes needed — the managed MCP looks up the override server-side from the calling user's identity.
Run a custom MCP server that calls the Statement Execution API with an explicit warehouse_id. The reference implementation at github.com/robkisk/databricks-managed-mcp is one file, ~145 lines.
You need per-agent routing (one user running multiple agents that should each route to different warehouses), you lack admin access to set overrides for other users, you want pinning that follows the workload rather than the user (e.g., a shared agent service principal), or you need behavior beyond execute_sql — custom result shaping, additional tools, server-side logging.
The custom server reads two independent environment variables at startup. One controls auth, the other controls compute:
Your MCP client (Claude Code, Cursor, etc.) sets both env vars when it launches the server, usually via the env block in .mcp.json. The server controls compute routing because it makes the SDK call. No client-side trick lets an LLM bypass the pin.
The managed endpoint's tool schema declares query but not warehouse_id. Even when a client injects the parameter, the server's schema validator strips it. The endpoint also has no path or header surface for warehouse selection. Server-side configuration — specifically the per-user default warehouse override described in Option A above — is the only routing knob the managed MCP exposes today. A future endpoint variant like /api/2.0/mcp/sql/<warehouse_id> (matching the pattern used for Genie spaces, Vector Search indexes, and Unity Catalog functions) would give per-call routing, but that hasn't shipped yet.
One server file (server.py) covers both modes. It picks the right mode at startup by checking for DATABRICKS_APP_PORT, which the Databricks Apps platform injects and local launches don't.
.mcp.json.mcp.jsonWhen queries run through the deployed app, current_user() returns the app's service principal UUID, not the calling user's email. Unity Catalog grants need to be on the SP, not on individual users. For end-user-attributed SQL (each user runs as themselves), enable the “Databricks Apps user-token passthrough” preview and switch to OBO auth. The README in the reference repo covers the upgrade path.
Verify warehouse pinning works in your workspace using the local stdio mode. No deployment needed for the first test.
git clone https://github.com/robkisk/databricks-managed-mcp.git
cd databricks-managed-mcp
databricks auth login --host https://<your-workspace>.cloud.databricks.com
Creates a profile in ~/.databrickscfg named DEFAULT, or whatever you pass to --profile.
export DATABRICKS_CONFIG_PROFILE=DEFAULT
export DATABRICKS_WAREHOUSE_ID=<your-warehouse-id>
uv run smoke_test.py
The smoke test boots the server, sends an execute_sql call, and watches the Databricks control plane to confirm only the pinned warehouse transitioned to RUNNING. Output ends with PASS when pinning works.
{
"mcpServers": {
"pinned-sql": {
"command": "uv",
"args": ["run", "/absolute/path/to/repo/server.py"],
"env": {
"DATABRICKS_CONFIG_PROFILE": "DEFAULT",
"DATABRICKS_WAREHOUSE_ID": "<your-warehouse-id>"
}
}
}
}
Restart your MCP client. The tool mcp__pinned-sql__execute_sql becomes available. The reference repo's README covers the Databricks App deployment path.