Skip to main content
Glama
OliveriGuido

Databricks MCP Server

by OliveriGuido
README.md
# Databricks MCP Server — Natural-Language Analytics POC

A small [Model Context Protocol](https://modelcontextprotocol.io) server that lets
an LLM client (e.g. **Claude Desktop**) answer **business questions in natural
language** over a Databricks dataset — without writing SQL by hand.

It runs against the public `samples.nyctaxi.trips` dataset that ships with every
Databricks workspace, so it's reproducible by anyone.

## What it exposes (the three MCP primitives)

| Primitive | Name | Purpose |
|-----------|------|---------|
| **Tool** | `run_query` | Executes a **read-only** SQL query against `samples.nyctaxi.trips` and returns the rows. |
| **Resource** | `schema://nyctaxi` | Curated schema + **metric definitions** and gotchas — the context layer that makes the generated SQL correct. |
| **Prompts** | `revenue_by_month`, `busiest_pickup_zones`, `trips_by_hour`, `fare_distance_summary` | Ready-made business questions. |

## Safety / governance

Two layers, on purpose:

1. **App-level guard** (`is_read_only`): only a single `SELECT`/`WITH` statement is
   accepted; any write/DDL keyword (`INSERT`, `UPDATE`, `DROP`, ...) is rejected,
   and a `LIMIT 1000` is appended when missing.
2. **The real guarantee**: connect with a Databricks token whose grants are
   **read-only** on the catalog. App guards reduce footguns; permissions are what
   actually protect the data. Never give an LLM a write-capable credential.

## Architecture

```
Claude Desktop  ──stdio──►  MCP server (this repo)  ──Databricks SQL connector──►  samples.nyctaxi.trips
   (client)                  tool · resource · prompts                              (read-only)
```

`run_query` doesn't open the connection in-process — it shells out to
`query_runner.py` (`subprocess.run(..., stdin=subprocess.DEVNULL, capture_output=True)`).
See the note below for why.

## Implementation note: why `run_query` uses a subprocess

Both points were reproduced and verified on **Windows + the FastMCP `stdio`
transport** (Claude Desktop and the MCP Inspector). Symptom in both: the tool call
hangs and the client returns `MCP error -32001: Request timed out` at ~60s, even
though the same query runs in ~4s with the connector directly.

1. **`sql.connect()` stalls ~60s when called *inside* the server process.** From a
   clean child process it connects in ~2s; inside the FastMCP process it blocks
   until the client's request times out. It stalls on the event-loop thread *and*
   on a worker thread, so it's a process-level interaction with the connector — not
   just the event loop being blocked. Running the query in a child process avoids
   it. (Disabling telemetry / `use_cloud_fetch` does **not** help.)

2. **`stdin=subprocess.DEVNULL` is required on the child.** A stdio MCP server's own
   stdin *is* the JSON-RPC pipe from the client. A child started with the default
   `stdin=None` inherits that pipe handle and hangs until the client gives up
   (~60s). Detaching stdin makes it return at query speed. `capture_output=True`
   already detaches stdout/stderr — stdin is the one that's easy to miss, so piping
   the query out to a subprocess without it does **not** fix the hang.

> **Gotcha — don't launch the Inspector from Git Bash on Windows.** MSYS2 rewrites
> the POSIX-looking `DATABRICKS_HTTP_PATH` (`/sql/1.0/warehouses/…` →
> `C:/Program Files/Git/sql/1.0/warehouses/…`), so the server gets a **404**, not a
> timeout. Use PowerShell or `cmd`. Claude Desktop passes env vars directly and is
> unaffected.

## Run it

Prereqs: Python 3.11+, [`uv`](https://docs.astral.sh/uv/), a Databricks workspace
with a running SQL Warehouse and the `samples` catalog.

**Windows / PowerShell** (recommended on Windows — see the Git Bash gotcha above):

```powershell
cd "C:\path\to\databricks-mcp"
uv sync                                    # first time only

# from SQL Warehouses -> Connection details, plus a personal access token.
# These live only in THIS PowerShell window (nothing is written to disk):
$env:DATABRICKS_HOST      = "dbc-xxxxxxxx-xxxx.cloud.databricks.com"
$env:DATABRICKS_HTTP_PATH = "/sql/1.0/warehouses/xxxxxxxxxxxxxxxx"
$env:DATABRICKS_TOKEN     = "dapixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# launch the browser inspector, then run a query from its UI:
npx @modelcontextprotocol/inspector uv run server.py
```

<details>
<summary><b>macOS / Linux (bash)</b></summary>

```bash
uv sync
export DATABRICKS_HOST="adb-....azuredatabricks.net"
export DATABRICKS_HTTP_PATH="/sql/1.0/warehouses/...."
export DATABRICKS_TOKEN="dapi...."
npx @modelcontextprotocol/inspector uv run server.py
```
</details>

### Connect to Claude Desktop

You can reach the config file in two ways:

- **Via the UI (recommended):** in Claude Desktop go to
  **Settings → Developer → Edit Config**. This opens (and creates, if missing)
  `claude_desktop_config.json` in the right folder.
- **By path:** edit it directly at
  `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
  `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS).

Copy the contents of `claude_desktop_config.example.json` into that file,
fill in your real values, and **restart Claude Desktop**. Then ask things like:

> "What were the busiest pickup zones, and how does monthly revenue trend?"

## Notes

- `samples.nyctaxi.trips` is a public Databricks dataset; no private data is used.
- Secrets live in env vars / the Claude Desktop config, both git-ignored.

TDQS

A4/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no risk of confusion between tools. The tool's purpose is clearly defined as running a read-only SQL query on a specific table.

Naming Consistency5/5

With a single tool, naming consistency is not a concern. The tool name 'run_query' follows a common verb_noun pattern.

Tool Count1/5

The server is named 'Databricks MCP Server', implying access to a wide range of Databricks functionality, but only one tool for a single query on a fixed table is provided. This is an extreme mismatch in scope.

Completeness1/5

The tool set is severely incomplete for a Databricks server. It lacks operations for managing databases, tables, clusters, or running arbitrary SQL beyond the fixed sample table.

Maintenance

ActivityInactive
ResponsivenessNo issues