Databricks MCP · Technical Guide

Pinning a SQL Warehouse to Your Databricks MCP

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.

Audience: Databricks customers using MCP-based agents Scope: SQL warehouse routing for `/api/2.0/mcp/sql` Verified: Against live Databricks service

01The short version

Three takeaways. Everything else in this guide supports them.

The problem

The managed MCP endpoint cannot be warehouse-pinned

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.

What happens instead

Queries route dynamically through a hidden priority algorithm

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.

The fix

Two paths, depending on scope

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.

02The problem

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.

Client-side pinning approaches that don't work

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
Bottom line

Your client has no way to tell the managed MCP which warehouse to use. The server decides, every time.

03How the managed MCP picks a warehouse

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.

Priority tiers (evaluated top-down)

1
Running warehouse named exactly Shared endpoint or dbdemos-shared-endpoint
Hard-coded preferred names for known shared compute. Wins all subsequent tiers.
2
Running warehouse with shared anywhere in its name
Case-insensitive substring match. Naming convention determines selection.
3
Any running warehouse
Generic catch-all for running compute. Most common tier hit in practice.
4
Stopped warehouse with shared in name
SDK auto-starts before query execution. Serverless: ~5s cold start; classic: 3–5 min.
5
Any stopped warehouse
Last-resort fallback. Same auto-start behavior.

Within-tier tiebreaks

When multiple warehouses match the same tier, the algorithm orders them by:

RankCriterionEffect
1Serverless computeServerless warehouses always preferred over classic within a tier
2Ownership matches calling userWarehouses created by the authenticated user preferred next
3API list orderFinal fallback — whichever the warehouses API returns first
Important — per-request, not per-session

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.

04What this means in practice

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
Why most demos “just work”

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.

05When this matters for your team

Dynamic routing is acceptable for single-user prototyping. It becomes a liability in four production scenarios.

Cost attribution and chargeback

When finance needs to attribute spend

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.

Team isolation

When you want bounded blast radius

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.

Predictable performance

When SLAs depend on warehouse class

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.

Compliance and audit

When “where did this run” matters

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.

06Two paths to predictable warehouse routing

Pick the one that matches your scope. Option A is native and simpler; Option B handles cases Option A can't.

Option A — Per-user default warehouse override (recommended when applicable)

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.

Use Option A when

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.

Option B — Custom MCP server (when Option A doesn't fit)

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.

Use Option B when

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.

How Option B's two-layer environment model works

The custom server reads two independent environment variables at startup. One controls auth, the other controls compute:

AUTH LAYER COMPUTE LAYER DATABRICKS_CONFIG_PROFILE Workspace host + OAuth method from ~/.databrickscfg DATABRICKS_WAREHOUSE_ID Pin all queries to this warehouse (16-char hex ID) Custom MCP Server (server.py) FastMCP → Databricks SDK → execute_statement(warehouse_id=…) Your pinned SQL warehouse

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.

Why client-side pinning fails on the managed MCP

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.

07Two deployment modes

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.

Local stdio mode

For individual developers
  • Transportstdin / stdout
  • How clients reach itSubprocess launch from .mcp.json
  • Auth identityCalling user's OAuth token
  • Warehouse permissionsInherits user's CAN_USE grants
  • Setup time~5 minutes

Databricks App mode

For multi-user, governed deployments
  • TransportHTTPS + OAuth proxy
  • How clients reach itStdio → HTTPS bridge in .mcp.json
  • Auth identityApp's service principal
  • Warehouse permissionsSP CAN_USE (auto-bound on deploy)
  • Setup time~15 minutes (one-time)
SP auth caveat for deployed mode

When 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.

08Getting started in 5 minutes

Verify warehouse pinning works in your workspace using the local stdio mode. No deployment needed for the first test.

1
Clone the reference repo
git clone https://github.com/robkisk/databricks-managed-mcp.git
cd databricks-managed-mcp
2
Authenticate to your Databricks workspace
databricks auth login --host https://<your-workspace>.cloud.databricks.com

Creates a profile in ~/.databrickscfg named DEFAULT, or whatever you pass to --profile.

3
Set the pin and run the smoke test
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.

4
Wire into Claude Code / Cursor
.mcp.json
{
  "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.