dak
Provides governed access to Databricks workspaces: run SQL on SQL warehouses, inspect Unity Catalog metadata, search metadata, describe tables with optional lineage, and export query results, with read-only defaults and authentication via Databricks profiles, PATs, OAuth M2M, or the Databricks CLI.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@dakrun a query to count rows in main.sales.orders"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
databricks-agent-kit
MCP server (dak) that gives coding agents governed access to Databricks: run SQL on a SQL warehouse and inspect Unity
Catalog metadata, over stdio or Streamable HTTP, with persistent operational logs in SQLite.
Talks only to public workspace REST APIs (SQL Statement Execution, Unity Catalog, SQL Warehouses); exports also download result chunks from the presigned cloud-storage links that Databricks returns.
No Databricks-served models, no AI Gateway / Unity Gateway, no Databricks-managed MCP servers, no Genie: your agent keeps its own model subscription.
Read-only by default; the real security boundary is the Unity Catalog permissions of the identity you configure.
Features the workspace does not offer (edition, admin settings, permissions) fail with an explicit message;
dak doctorreports them up front.
Design: docs/design.md.
Requirements
Node.js 22.18 or later (developed on Node.js 24). The log store uses the built-in
node:sqlitemodule.A Databricks workspace with Unity Catalog and at least one SQL warehouse you can use.
Databricks CLI 1.0.0 or later only if you authenticate with OAuth U2M (recommended for personal use) or install the official Databricks skills.
Related MCP server: shop-sql-mcp
Installation
git clone <this-repository> databricks-agent-kit
cd databricks-agent-kit
npm ci
npm run build
npm link # optional: puts `dak` on your PATH
dak --versionWithout npm link, run the server as node <absolute-path>/dist/cli.js. On Windows, some MCP clients cannot launch
.cmd shims such as the one npm link creates; use node plus the absolute path to dist/cli.js in those clients.
Authentication
dak resolves credentials in this order:
Explicit profile (
--profile <name>): only that profile from~/.databrickscfgis used; environment credentials are ignored.Environment variables:
DATABRICKS_HOSTplus eitherDATABRICKS_TOKEN(personal access token) orDATABRICKS_CLIENT_IDandDATABRICKS_CLIENT_SECRET(OAuth M2M).Profile named by
DATABRICKS_CONFIG_PROFILE, elseDEFAULT, from~/.databrickscfg(or the file inDATABRICKS_CONFIG_FILE).
Within a profile, the method is chosen by its keys: token -> personal access token; client_id + client_secret ->
OAuth M2M; auth_type = databricks-cli -> OAuth U2M through the Databricks CLI. Any other auth_type (for example
azure-cli) is rejected with a message listing the supported methods.
Tokens are kept in memory only, refreshed before they expire, and never written to logs or tool output. The workspace
host must use https (plain http is accepted only for loopback test stubs), and tokens are only sent to that host.
Option A - OAuth U2M through the Databricks CLI (recommended for personal use)
Manual steps:
Install or upgrade the Databricks CLI to 1.0.0 or later:
Windows:
winget install Databricks.DatabricksCLI(orwinget upgrade Databricks.DatabricksCLI).macOS/Linux: see the CLI installation guide.
Log in once per workspace. This opens the browser and writes a profile with
auth_type = databricks-clito~/.databrickscfg:databricks auth login --host https://<workspace-host> --profile devOptional: pin a SQL warehouse by adding
warehouse_id = <id>to the[dev]section of~/.databrickscfg.Start the server with
--profile dev(or setDATABRICKS_CONFIG_PROFILE=dev).
dak asks the CLI for a fresh token (databricks auth token --profile dev) when needed; the CLI owns the login, the
token cache (~/.databricks/token-cache.json) and refresh. When the refresh token expires, tool calls return an error
asking you to run databricks auth login --profile dev again; no server restart is needed.
Option B - Personal access token
Manual steps:
In the workspace, open your user menu > Settings > Developer > Access tokens > Manage > Generate new token. Set a lifetime and copy the token. Workspace admins can disable personal access tokens; if the option is missing, use option A or C.
Provide it either through the environment:
export DATABRICKS_HOST=https://<workspace-host> export DATABRICKS_TOKEN=<token>or through a profile in
~/.databrickscfg:[pat] host = https://<workspace-host> token = <token>Rotate the token before it expires. Keep
~/.databrickscfgreadable only by your user and never commit tokens.
Option C - OAuth M2M with a service principal (unattended or shared HTTP servers)
Manual steps (some require a workspace or account admin):
Create a service principal and give it workspace access.
Generate an OAuth secret for it (admin settings > Identity and access > Service principals > the principal > Secrets). Copy the client ID and secret.
Grant it the permissions listed below, ideally read-only.
Provide the credentials through
DATABRICKS_HOST,DATABRICKS_CLIENT_IDandDATABRICKS_CLIENT_SECRET, or a profile withhost,client_idandclient_secret.
dak exchanges them at https://<workspace-host>/oidc/v1/token (client_credentials, scope all-apis) and refreshes
the token automatically.
Permissions to grant
Capability | Required |
Any SQL tool |
|
Read data |
|
| Read access to |
| Unity Catalog system tables ( |
Exports ( | Nothing beyond the privileges to read the data (see below for the local requirements) |
Writes ( |
|
Exports need no extra Databricks privilege, but the workspace must allow EXTERNAL_LINKS results, this machine must
reach the cloud-storage hosts of the result links, and the server needs a writable export directory (see
Exporting results). dak doctor checks all three.
The permissions of this identity are the security boundary. Neither the Statement Execution API nor the MCP protocol
offer a read-only mode (MCP tool annotations are only hints for the client), and the dak read-only check only decides
whether a statement reads; it does not stop what a read can reach. Give agents a dedicated identity (a service
principal, or a user or group used only for agents) with just what they need:
CAN USEon one SQL warehouse, andUSE CATALOG,USE SCHEMAandSELECTon the data they may read.No
MODIFY,CREATEor ownership on data objects, noREADon secret scopes and noUSE CONNECTION: SQL functions such assecret()orhttp_request()could otherwise expose secrets or reach external services.Everything the identity can read can reach the agent's context, and through it any tool the agent has (shell, web). Table comments and data are untrusted input for the agent (prompt injection).
Verify
dak doctor --profile devIt checks authentication, identity, warehouse, SELECT 1, information_schema, lineage system tables, result download,
export directory, log storage, the Databricks CLI version and the write mode, and prints the next step for anything that
fails. Exit code 1 means at least one required check failed.
Result download submits
SELECT 1as a CSV export and downloads its first chunk from the presigned link without credentials, exactly as an export does. It waits up to 60 s for a stopped warehouse to start. It only warns (it never fails) when the workspace gives no external link or when the cloud-storage host cannot be reached, because a proxy or firewall that allows the workspace host can still block that host; only the host name is printed.Export directory creates
DAK_EXPORT_DIRif needed and writes and deletes a temporary file in it. It fails when the directory is not writable.
Register the server in your agent
Examples use a profile named dev. Replace dak with node <absolute-path>/dist/cli.js if you did not run npm link.
stdio (one process per agent session)
Claude Code
claude mcp add --scope user databricks -- dak mcp --profile devCodex (
~/.codex/config.toml)[mcp_servers.databricks] command = "dak" args = ["mcp", "--profile", "dev"]OpenCode (
opencode.json){ "mcp": { "databricks": { "type": "local", "command": ["dak", "mcp", "--profile", "dev"], "enabled": true } } }Cursor (
~/.cursor/mcp.json){ "mcpServers": { "databricks": { "command": "dak", "args": ["mcp", "--profile", "dev"] } } }
Streamable HTTP (one shared server)
dak mcp --transport http --profile dev # http://127.0.0.1:8765/mcpLoopback only by default; requests with a foreign
Hostheader are rejected (DNS rebinding protection).On loopback, browser requests from a non-local
Originare rejected.Binding to another address (
--host 0.0.0.0) requiresDAK_HTTP_TOKEN(at least 32 characters, for exampleopenssl rand -hex 32); clients must sendAuthorization: Bearer <token>. The token can also be set on loopback.The server speaks plain HTTP: off loopback, put it behind a TLS-terminating reverse proxy so the token is not sent in clear text.
All clients share the server's Databricks identity; client credentials are never forwarded to Databricks.
The log resources (
resource://logs/*) show the calls of every client, SQL text included.Exports are written on the machine that runs the server, in its
DAK_EXPORT_DIR, not on the client's machine.
Clients point at the URL, for example claude mcp add --transport http databricks http://127.0.0.1:8765/mcp (add
--header "Authorization: Bearer <token>" when a token is set), or an OpenCode entry with "type": "remote" and
"url": "http://127.0.0.1:8765/mcp".
Tools
Five tools keep the definitions under 2,500 characters in total, so they cost little context in every session.
Tool | Parameters | Purpose |
|
| Without arguments, catalogs; with |
|
| Columns, comments, primary/foreign keys, clustering, partitioning and tags; optional sample (max 100 rows) and upstream/downstream lineage |
|
| Find tables and columns by name or comment |
|
| One statement with |
|
| Follow up on a statement still running after 30 s or on an export ( |
Details that the table leaves out:
list_objectsreturnstruncatedwhen more objects matched thanlimit(default 100, maximum 500).schemawithoutcatalogis an error.search_metadatareturns 50 matches by default (maximum 200).describe_tablereports a sample or lineage that could not be read innotesinstead of failing. Lineage comes fromsystem.access.table_lineage(see Permissions to grant).parametersis a list of{ name, value, type? }; the value is sent through the API, never interpolated into the SQL.
Resource resource://databricks/warehouses lists the SQL warehouses visible to the identity and marks the default one.
Suggested workflow for data questions (it replaces Genie): search_metadata or list_objects -> describe_table (use
sample_rows for a quick look at the data) -> execute_sql. Aggregate in SQL when the result is large, or pass export
to write the full result to a file. The better your Unity Catalog comments, tags and keys, the better the agent's SQL.
SQL safety and limits
Read-only by default. Databricks itself parses each statement, so comments, string literals and dialect features cannot hide a write, and nothing is executed by the check:
A statement with a
;before its end (several statements, or a;inside a string literal) is rejected with aRead-only modemessage and no call: read-only mode cannot tell those two apart. Pass a literal that contains;as a:nameparameter instead. A single trailing;is fine.SHOW,DESCRIBE,DESCandEXPLAINrun directly, with no extra call, only when the keyword is the first word of the statement (nothing but spaces, tabs or line breaks before it) and is not followed by a:(a SQL-scripting label). Comments are deliberately not skipped: a scanner of ours that disagreed with the Databricks lexer could let a write through.Every other statement must pass
DESCRIBE QUERY <statement>, which Databricks accepts only for queries (for exampleSELECTorWITH). A statement that does not parse as a query is rejected with aRead-only modemessage. That includes aSHOW,DESCRIBEorEXPLAINcommand preceded by a comment: remove the comment.
Read commands cost no extra call to the warehouse and queries cost one. The check does not block functions such as
secret()orhttp_request()inside a query: that is the job of the identity's permissions (see Permissions to grant). Enable writes with--allow-writesorDAK_ALLOW_WRITES=true. Anexportgoes through the same check.Results: 100 rows by default, capped by
DAK_MAX_ROWS(default 1000) and by size (100 KB). Both limits are applied by Databricks (row_limitandbyte_limitof the Statement Execution API) and again bydak, so noLIMITclause is needed and a large result never floods the agent's context;truncated: truetells the agent to aggregate, filter or export.Statements still running after 30 s return a
statement_id;get_statementwithwait_seconds(up to 30) waits for them.
Exporting results
Use export when a result is too large for the agent's context, or when you need the data in a file for another tool.
The rows never travel through the agent: the server downloads them from Databricks and writes them to disk.
{
"statement": "SELECT * FROM main.sales.orders",
"export": { "format": "parquet", "name": "orders" }
}export takes a format (csv, parquet or sqlite) and an optional name. The name is a label: characters outside
A-Z a-z 0-9 . _ - become _ and it is cut at 64 characters, so the agent cannot choose a path. The call returns at
once with the statementId and the export descriptor (id, format, path, status); the export runs in the
background. row_limit and DAK_MAX_ROWS do not apply to an export; the limit is DAK_EXPORT_MAX_BYTES.
Where the files go
Every export is written under DAK_EXPORT_DIR (default <storage dir>/exports, see
Configuration). The export identifier is <name or "export">-<full statement id>.
A relative DAK_EXPORT_DIR is resolved against the working directory of the server process, so prefer an absolute one.
Outside Windows the server creates the directory with mode 0700; anyone who can read the directory can read the
exports. In HTTP mode the files are on the server machine.
Format | Result | Notes |
|
| One file, however many chunks Databricks returns; one header line |
|
| One ZSTD-compressed file per chunk, in a directory; |
|
| One file with the table |
Each output is written under a temporary name (<id>.csv.part, <id>.db.part, or the directory <id>.part/ for
Parquet) and renamed when it is complete, so an unfinished export never looks finished. If the target already exists, the
export is rejected with Export <path> already exists; choose another name. and its statement is cancelled in
Databricks.
Following an export
get_statement with the statement_id returns the state of the export under export.status:
Status | Meaning |
| The query is still running in Databricks |
| Chunks are being downloaded; |
| Done: |
| Failed: |
| Cancelled; the partial files were deleted |
Pass wait_seconds (0 to 30) to wait up to that long for the export to end before answering, instead of polling. Pass
cancel: true to cancel the statement in Databricks, stop the download and delete the partial files. When the result is
larger than DAK_EXPORT_MAX_BYTES, Databricks cuts it: the export still ends SUCCEEDED, with truncated: true.
Downloads use plain GET requests without an Authorization header, four at a time. A network error, 429 or 5xx is
retried up to three times (after 500 ms, 1 s and 2 s); a 403 or an expired link gets one fresh link. Anything else
fails the export.
Caveats
CSV
NULL. Databricks writes SQLNULLas the textnull, anddakdoes not change the bytes it receives, so aNULLcannot be told apart from the string"null". Use Parquet or SQLite when the difference matters.SQLite types. Values arrive as text and the column affinity converts them:
Databricks type
SQLite column
Conversion
TINYINT,SMALLINT,INT,BIGINTINTEGERNone;
BIGINTstays exactBOOLEANINTEGERtruebecomes 1 andfalsebecomes 0FLOAT,DOUBLEREALNone
DECIMALof 15 digits or fewerREALNone
DECIMALof more than 15 digitsTEXTNone, so no precision is lost
DATE,TIMESTAMP,STRINGTEXTNone
STRUCT,ARRAY,MAPTEXTwith JSONNone; query them with
json_extractAnything else, such as
BINARYand intervalsTEXTNone
An empty column name becomes
column_<position>and a repeated one (SQLite compares names without case) gets the first free suffix_2,_3, and so on. SQLite allows at most 2000 columns.No resume, no deletion. Exports live in the server process. If it restarts or stops, a running export is lost (on a clean shutdown its partial files are deleted) and
dakdoes not resume it: run the statement again.daknever deletes a finished export. At startup it removes.partleftovers older than 24 hours from the export directory.Presigned links. The result chunks are downloaded from cloud storage, not from the workspace host. A proxy or firewall that allows the workspace can still block them.
dak doctorreports it under Result download.
Coexistence with the official Databricks skills
The official skills (databricks-agent-skills) are installed with the Databricks CLI, not by this project. Some of them conflict with this MCP:
Skill | Conflict |
| Sends every data question to Genie ( |
| Genie |
| AI Gateway / Unity Gateway services |
| Tell the agent to run SQL and explore schemas with |
To skip the Genie and gateway skills while keeping the useful ones (these still mention the CLI commands, so the tiebreak note below is still needed):
databricks aitools install --skills-only --skills databricks-core,databricks-dbsql,databricks-unity-catalog,databricks-metric-views,databricks-dabs,databricks-jobs,databricks-pipelinesIf the official skills are installed and include any conflicting skill, add the following tiebreak note to your global agent instructions so the MCP takes precedence:
Agent | Global instructions file |
Codex |
|
OpenCode |
|
Claude Code |
|
Other agents | Their global |
## Databricks: MCP precedence
- For Databricks SQL execution and Unity Catalog metadata, use the `databricks` MCP server tools (`execute_sql`,
`describe_table`, `search_metadata`, `list_objects`, `get_statement`).
- Do not use `databricks experimental aitools tools query|discover-schema|statement`, `databricks genie ask` or Genie
for those tasks while the MCP server is available, even if a Databricks skill says so. Fall back to the CLI only when
the MCP server is unavailable.
- The Databricks skills remain the reference for everything else (bundles, jobs, pipelines, SQL features).The server also sends MCP instructions with the same precedence, but not every client applies them, so keep the note.
Operational logs
Stored in SQLite at
logs.dbin a per-user state directory,%LOCALAPPDATA%\databricks-agent-kiton Windows and$XDG_STATE_HOME/databricks-agent-kit(default~/.local/state/databricks-agent-kit) elsewhere; override it withDAK_STORAGE_DIR. It is shared by everydakprocess of that user and, outside Windows, readable only by its owner (directory0700, file0600). Logs written by earlier versions in<install dir>/.storageare not migrated.Every tool call is an execution (UUID v7) with start/end time, status (
completed,failed,cancelled) and its ordered log entries; each export runs as its own execution, and server start/stop events belong to a lifecycle execution per process. Readingresource://logs/*opens no execution, so diagnosing does not inflate the statistics it reports;resource://databricks/warehousesis recorded like a tool call.One entry per call, written when it ends (
info): tool name, arguments, duration and a summary of the result (statementId,warehouseId, state, row count,truncated, andcheckMsfor the read-only check). If the process dies first, the execution stays asstarted.Never logged: parameter values (only their name and type), result rows, tokens or secrets (keys such as
token,secret,passwordandauthorizationare redacted). SQL text is logged and cut at 4096 characters.Levels: an expected failure (a
DakError: read-only violation, invalid argument,PERMISSION_DENIED, bad SQL) is onewarnentry without a stack trace; an unexpected error is oneerrorentry with its stack trace. Retries of Databricks requests and export downloads arewarn; the automatic warehouse choice and the start and end of an export areinfo; token refreshes, every Databricks request and every written export chunk aredebug; repeated401rejections of the HTTP transport are onewarnper minute with a counter.Writes go through an in-memory queue flushed every 5 s or every 100 events in one transaction, and on shutdown. Only events still in the queue are lost if the process crashes.
Retention: 90 days, purged at startup and every 24 h.
DAK_LOG_LEVEL(trace,debug,infodefault,warn,error,fatal) filters stored entries.
Diagnostics through MCP:
Resource | Content |
| Latest error and fatal entries |
| Latest executions |
| One execution with all its entries |
| Executions, errors and warnings in the last 24 h |
There is no log search tool, so the log costs no context until an agent reads one of these resources.
Configuration
Setting | Flag | Environment | Default |
Profile |
|
|
|
Config file | - |
|
|
Warehouse | - |
| running, then serverless, then first |
Allow writes |
|
|
|
Max rows | - |
|
|
Transport |
| - |
|
HTTP host / port |
|
|
|
HTTP token | - |
| required off loopback |
Log storage | - |
| Per-user state directory |
Export directory | - |
|
|
Export size cap | - |
|
|
Log level | - |
|
|
Troubleshooting
Symptom | Fix |
| Follow one of the authentication options, then |
|
|
| Install the CLI or use a personal access token |
| Ask an admin for the privilege named in the message |
| Set |
| Expected; restart with |
| The warehouse is starting; retry in a moment |
| Lineage system tables are not enabled or not readable in this workspace |
Statement still | The warehouse is starting; call |
| Choose another |
Export | The result links could not be downloaded: run |
Export | The chunk could not be converted; export as |
Export | SQLite allows 2000 columns; select fewer columns or export as |
Export | The result exceeded |
Export | Point |
An export vanished after a restart | Exports are not resumed; run the statement again |
Development
npm test # unit + end-to-end (stdio and HTTP against a local Databricks API stub)
npm run typecheck
npm run build
DAK_IT_PROFILE=dev npm run test:integration # real workspace; optional DAK_IT_TABLE (default samples.nyctaxi.trips)Tests use node:test and run the TypeScript sources directly (Node.js type stripping). The end-to-end tests serve
presigned result links from a second local origin, so they also cover the exports.
The only runtime dependency added for exports is parquet-wasm, pinned to
an exact version and imported from parquet-wasm/node; it converts the Arrow chunks of Databricks into Parquet files
(see ADR 0004). CSV and SQLite exports use only Node.js built-ins.
Decisions are recorded in docs/adr.
Contributing
Contributions are made through pull requests. The branch flow, the checks and the release process are described in docs/design.md and recorded in ADR 0016.
Requirements: Node.js 22.18 or later. Run
npm cionce; it installs the dependencies and the git hooks.Branch names:
feature/<desc>andbugfix/<desc>branch fromdevelopand merge intodevelop.hotfix/<desc>branches frommain, merges intomainand is then merged back intodevelop. Pull requests intomaincome only fromdeveloporhotfix/*.Commit messages: Conventional Commits,
type(scope): description, for examplefix(export): .... Each commit is checked locally by the commit-msg hook and by CI for every commit of the pull request. Merge commits created by GitHub are not checked.Local hooks:
pre-commitrunsnpm run format:checkandnpm run lint(Biome);commit-msgruns commitlint. Before you push, run the same checks as CI:npm run typecheck,npm run buildandnpm run test:coverage.Review: each pull request needs one approval from a code owner and passing checks (
quality,test,commits,branch-policy,dependency-reviewand CodeQL). The branch must be up to date with its base and conversations must be resolved. Pull requests are merged with a merge commit.
This server cannot be deployed
Maintenance
Related MCP Connectors
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Your Databricks Lakehouse in natural language: run SQL on your SQL warehouses, track long-running qu
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceThis is a Model Context Protocol (MCP) server for executing SQL queries against Databricks using the Statement Execution API. It enables AI assistants to directly query Databricks data warehouses, analyze database schemas, and retrieve query results in a structured format43MIT
- FlicenseNot gradedqualityCmaintenanceProvides AI agents read-only analytical access to a SQLite database over stdio, with tools for listing tables, describing schemas, and running paginated SQL queries.-
- FlicenseNot gradedqualityCmaintenanceExposes a DuckDB-backed MCP server that lets an agent fetch a dataset's schema and run read-only SQL queries against it. All execution is filtered server-side, enforcing single-statement, read-only safety independent of the calling model's behavior.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to inspect schemas, walk Delta table history, run read-only DuckDB queries, and check data-quality and job-run status safely.MIT