Building a Multi-Tenant Databricks MCP Server for AI Agents: 2026 Architecture Guide
Learn how to architect a secure, multi-tenant MCP server for Databricks. Handle per-tenant OAuth isolation, HTTP 429 rate limits, and dynamic tool generation.
Building an MCP server for secure Databricks data access for AI agents means implementing a JSON-RPC 2.0 endpoint that dynamically exposes Unity Catalog resources, Databricks SQL execution, and Jobs API operations as tools, isolates OAuth tokens per tenant workspace, and propagates HTTP 429 rate limit errors cleanly back to the agent framework.
That single sentence contains roughly nine months of platform engineering if you build it end-to-end for a multi-tenant B2B SaaS product. To connect your SaaS application's AI agents to your customers' Databricks environments securely, you need a multi-tenant Model Context Protocol (MCP) server. Native connectors fail at scale because they do not handle isolated OAuth lifecycles per tenant, nor do they propagate Databricks' strict API rate limits correctly back to the agent framework.
If you are a product or engineering lead trying to give your customers' AI agents access to their Databricks environments (not just your internal data scientists), the native Databricks tooling only gets you partway. This guide walks through what actually works: the protocol boundary, the OAuth isolation model, how to survive Databricks' per-workspace rate limits, and where dynamic tool generation eliminates the brittle hand-coded connector layer.
The Challenge of Connecting AI Agents to Databricks
The way software interacts with data platforms has fundamentally changed. We are no longer just building static dashboards; we are building autonomous agents that need real-time context to make decisions. Enterprise interest in agentic access to the lakehouse is real, but production adoption is thinner than the hype implies.
A 2026 Deloitte Agentic AI Transformation Survey found that 42% of US enterprises have tested or deployed AI agents, yet only 15% have achieved scaled, orchestrated multi-agent adoption. This is largely because data integration and governance bottlenecks stall the rollout after the initial pilot. Meanwhile, Grand View Research projects the global AI agents market will grow from USD 10.9 billion in 2026 to USD 182.9 billion by 2033, so the pressure on B2B SaaS platforms to ship agent-facing features is not going away. Your customers want their AI agents to query the Unity Catalog, execute SQL statements against the lakehouse, and trigger Databricks jobs autonomously.
The operational reality of connecting an external AI agent to a customer's Databricks workspace introduces severe architectural hurdles. If you build custom API wrappers for your AI agents, you face a combination of integration and security problems:
- The N x M Integration Problem: Every time a new LLM framework emerges, you have to rewrite your tool definitions. You cannot afford to build discrete tools for Claude, OpenAI, and custom LangGraph agents separately.
- Per-Tenant OAuth Isolation: You are building a B2B SaaS product. You cannot use a single service principal. Every customer's workspace has its own OAuth application, its own service principal, and its own token lifecycle. Bleeding one tenant's token into another agent's context is an immediate incident.
- Unity Catalog Permission Passthrough: Agents must run under a user or service principal whose grants are enforced by Unity Catalog (UC), not under a super-user token that ignores row-level and column-level policies.
- Unpredictable Query Payloads & Rate Limit Survival: Agents generate dynamic SQL and fan out requests rapidly. Databricks enforces rate limits for all REST API calls, with limits set per endpoint and per workspace. Requests that exceed the rate limit return a 429 response status code. A naive integration layer will melt under agent-driven fan-out.
- Zero Retention of Lakehouse Content: Sensitive data that lands in a middle tier turns your integration layer into a compliance liability.
None of this is theoretical. The moment you have three customers running Claude or a LangGraph agent against their own Databricks workspaces, you are running a multi-tenant proxy—whether you designed one or not.
Why MCP is the Standard for Databricks Data Access
The choice of how you host and manage your integration infrastructure dictates whether your AI features scale or collapse under the weight of maintenance. The market has standardized entirely on the Model Context Protocol (MCP). For a deeper dive into the protocol itself, see our guide on what an MCP server is.
MCP provides a universal JSON-RPC 2.0 interface between AI agents and external data sources. It solves the N×M problem cleanly: instead of writing custom tool-calling logic for every combination of agent framework and data platform, you build a single MCP server.
The server exposes a tools/list endpoint that describes the available Databricks operations, and a tools/call endpoint that executes them. For Databricks specifically, the MCP surface you want to expose typically includes:
- Databricks SQL Statement Execution API: Run parameterized SQL against a SQL Warehouse, poll for completion, fetch results.
- Unity Catalog Metadata: List catalogs, schemas, tables, and column definitions so the agent can ground its queries.
- Jobs API: Trigger and monitor workflows.
- Clusters / Warehouses API: Read-only inventory for the agent to reason about compute.
- Unity Catalog Functions: Call registered SQL/Python UDFs as tools.
Here is how the protocol boundary operates in practice:
sequenceDiagram
participant Agent as AI Agent (Claude/OpenAI)
participant Client as MCP Client
participant Server as MCP Server
participant Databricks as Databricks Workspace
Agent->>Client: I need to query sales data
Client->>Server: JSON-RPC tools/list
Server-->>Client: Returns execute_databricks_sql schema
Client-->>Agent: Available tools
Agent->>Client: Call execute_databricks_sql with query
Client->>Server: JSON-RPC tools/call
Server->>Databricks: POST /api/2.0/sql/statements
Databricks-->>Server: JSON Results
Server-->>Client: MCP Result Object
Client-->>Agent: Context injected into promptNative Databricks MCP vs. Multi-Tenant SaaS Requirements
If you need to connect AI agents to Databricks, your primary options in 2026 are: use Databricks' native managed MCP servers, self-host an open-source Python server, utilize custom Databricks Apps, or use a unified API platform that dynamically generates MCP tools.
Databricks has invested heavily here. Databricks managed MCP servers are ready-to-use servers that connect AI agents to data in Unity Catalog, Databricks AI Search indexes, Genie Agents, and custom functions. Databricks hosts the servers and manages authentication, and Unity Catalog enforces permissions so agents and users access only the tools and data you grant them.
This is excellent—if your agent lives inside the customer's Databricks workspace. The native model is designed strictly for internal use cases with on-behalf-of-user authentication, where the caller is already a Databricks identity. Every tool call automatically inherits the caller's Unity Catalog permissions, which means a data analyst connecting Claude Desktop to a Genie space can only query tables their UC role allows.
The multi-tenant B2B SaaS use case is completely different. You cannot ask your B2B SaaS customers to configure internal Databricks managed servers and route them back to your application.
| Requirement | Databricks Native Managed MCP | External Multi-Tenant MCP |
|---|---|---|
| Caller identity | Databricks user / service principal in that workspace | Your SaaS application, acting on behalf of a specific customer |
| Auth model | On-behalf-of-user OAuth inside one workspace | OAuth 2.0 authorization code per customer workspace, tokens stored per tenant |
| Tool surface | Genie, AI Search, UC Functions, SQL | Any Databricks API surface your product needs, across all customer workspaces |
| Hosting | Databricks-hosted, per workspace | Your platform, one MCP endpoint per connected account |
| Rate limit domain | The customer's own quota | The customer's quota, isolated so noisy tenants don't affect others |
Open-source options are community-built, single-tenant servers that expose basic Databricks SQL execution. They are suitable for local development but lack the enterprise multi-tenancy required for a SaaS product. Likewise, Databricks recommends hosting custom MCP servers on Databricks Apps to expose domain-specific tools, which requires significant custom engineering per customer. For a full breakdown of these options, review our analysis of the best MCP servers for Databricks.
To serve your customers, you must build or buy an external, multi-tenant MCP infrastructure. This architecture must isolate individual customer connections, refresh tokens automatically, and act as a stateless proxy.
Handling Databricks API Rate Limits (HTTP 429) in MCP
One of the most common failures in custom MCP servers is the mishandling of rate limits. Databricks' rate limits are not one number—they are a matrix.
Most APIs target a few hundred requests per second per workspace; the Jobs API and Files API have lower per-second targets. Some APIs (Statement Execution, Model Serving) use endpoint-specific limits. The MLflow Model Registry, for example, is throttled far more aggressively, with limits set to 40 queries per second, per workspace. Databricks REST API documentation confirms that requests exceeding the rate limit return a 429 response status code, and clients should implement retry logic with exponential backoff.
The critical architectural question for an MCP layer is: who owns the retry?
There are two schools of thought:
1. Absorb 429s inside the MCP server (Anti-Pattern)
The MCP layer catches the 429, backs off, retries, and returns success. This feels helpful, but it breaks in production because:
- The agent has no visibility into how long the tool call took, so it cannot decide to abandon or fall back.
- Concurrent agent calls stack up inside your infrastructure, turning a customer's rate limit into your latency problem.
- You now need per-tenant circuit breakers, jittered retry queues, and a way to tell the agent "still waiting" mid-call. You are essentially building a complex job queue inside your tool server.
2. Propagate 429s to the agent (Best Practice)
A production MCP server should never absorb or automatically retry rate limit errors. When an upstream API returns an HTTP 429, the MCP server must pass that error directly back to the caller. The AI agent framework (like LangGraph or CrewAI, or an Anthropic tool loop) possesses the conversational context and reasoning loop, and already has retry semantics to decide whether to back off or try a different tool.
However, the MCP server must normalize the upstream rate limit information into standardized headers. Databricks uses specific headers for their rate limits. Your proxy layer should intercept these and normalize them to the IETF standard (ratelimit-limit, ratelimit-remaining, ratelimit-reset).
To see this in practice, let's look at both sides of the boundary. First, the proxy handler inside the MCP server must intercept Databricks' specific headers and normalize them:
// Example Proxy Handler for Databricks API Execution inside the MCP Server
async function handleDatabricksProxy(request: Request, context: Context) {
const response = await fetch(databricksUrl, {
method: request.method,
headers: {
'Authorization': `Bearer ${context.oauthToken}`,
'Content-Type': 'application/json'
},
body: request.body
});
if (response.status === 429) {
// Extract Databricks specific headers
const limit = response.headers.get('X-Databricks-RateLimit-Limit');
const remaining = response.headers.get('X-Databricks-RateLimit-Remaining');
const reset = response.headers.get('X-Databricks-RateLimit-Reset');
// Normalize to IETF standard for the MCP client
const normalizedHeaders = new Headers();
if (limit) normalizedHeaders.set('ratelimit-limit', limit);
if (remaining) normalizedHeaders.set('ratelimit-remaining', remaining);
if (reset) normalizedHeaders.set('ratelimit-reset', reset);
// Pass the 429 error back to the LLM
return new Response(JSON.stringify({
isError: true,
message: "Databricks API rate limit exceeded. Please back off and retry."
}), {
status: 429,
headers: normalizedHeaders
});
}
return response;
}Then, on the caller side, the agent framework receives this standardized 429 and executes the backoff. By normalizing these headers, you provide the agent framework with the exact timestamp of when it can safely resume querying the lakehouse:
// Example Agent-Side Caller Loop
async function callDatabricksTool(toolName: string, args: object) {
const res = await mcp.tools.call({ name: toolName, arguments: args });
if (res.isError && res.status === 429) {
// Agent extracts the normalized IETF reset header
const resetSec = Number(res.headers['ratelimit-reset']) || 1;
await sleep(resetSec * 1000 + jitter());
return callDatabricksTool(toolName, args); // agent-owned retry
}
return res;
}Do not silently swallow 429s inside your MCP layer. You destroy the agent's ability to reason about latency and cost, and you turn one customer's quota exhaustion into a shared incident.
Dynamic Tool Generation for Databricks SQL and Unity Catalog
Hardcoding tool definitions is an anti-pattern. Databricks' API surface is massive and constantly evolving. If you manually write a JSON schema for execute_sql, list_catalogs, and get_table_metadata, you assume the maintenance burden of updating those schemas whenever Databricks changes their API. Furthermore, Unity Catalog objects vary wildly by customer.
The pattern that scales is documentation-driven tool generation: derive tool definitions at request time from a resource catalog plus per-resource documentation records. For a comprehensive look at this pattern, see our guide on auto-generated MCP tools.
At the conceptual level, tool generation for Databricks looks like this:
flowchart LR
A["Databricks OpenAPI<br>+ resource config"] --> B[Documentation records]
B --> C["Tool generator<br>(per request)"]
D["Tenant MCP token<br>(method + tag filters)"] --> C
C --> E["tools/list response<br>(JSON Schema per tool)"]
E --> F["Agent<br>(Claude, LangGraph, etc.)"]
F --> G["tools/call"]
G --> H["Proxy execution<br>against Databricks REST"]The system should fetch the Databricks OpenAPI specification and iterate over the available resources. Three things earn their keep in this pattern:
- A Quality Gate on Documentation: A resource method only becomes an exposed tool if it has a documentation record with a description and JSON Schema. Undocumented endpoints stay invisible to the LLM. This is what stops an agent from stumbling into an obscure admin endpoint no one meant to expose.
- Method Filtering & Categories: Tools are labeled
read(get,list),write(create,update,delete), orcustom(e.g.,execute_sql,submit_run). MCP servers are created with a filter, so a read-only analyst agent literally cannot mutate the workspace. - Tags for Functional Grouping: Tag Unity Catalog metadata as
catalog, SQL execution assql, and Jobs asorchestration. Then an MCP server scoped totags: ['catalog', 'sql']gives the agent a coherent surface without dumping every Databricks endpoint into its context window.
For each endpoint, the generator builds an MCP tool object containing a generated snake_case name, a description, and the JSON schemas for query and body parameters. For list methods, it automatically injects limit and next_cursor properties, explicitly instructing the LLM to pass the cursor value back unchanged.
Generated tool names should read naturally to an LLM: list_all_databricks_catalogs, execute_databricks_sql_statement, get_single_databricks_job_by_id. Snake case, verb-first, singular for individual-record operations—the model reasons about tool selection better when the names are boring and predictable.
{
"name": "execute_databricks_sql_statement",
"description": "Executes a SQL statement against the Databricks SQL warehouse. Use this to query lakehouse data.",
"inputSchema": {
"type": "object",
"properties": {
"warehouse_id": {
"type": "string",
"description": "The ID of the SQL warehouse to execute the query against."
},
"statement": {
"type": "string",
"description": "The SQL query to execute."
}
},
"required": ["warehouse_id", "statement"]
}
}MCP clients pass all arguments as a single flat object. Your router must split these arguments into query parameters and body parameters based on the generated schemas before forwarding the request to Databricks. When the LLM calls this tool, the MCP server receives the flat JSON payload, injects the correct OAuth token for that specific customer's integrated account, and proxies the request to the /api/2.0/sql/statements endpoint.
Building a Secure, Zero-Retention MCP Architecture
Security is the primary reason enterprises hesitate to connect AI agents to their data warehouses. If your MCP server caches lakehouse data or exposes OAuth tokens, it will fail InfoSec reviews immediately. Security for a multi-tenant Databricks MCP layer has four load-bearing properties. To learn more about building secure infrastructure, read our hands-on architecture guide for building MCP servers.
1. One MCP Server Per Connected Account (Cryptographic URLs)
Each customer's Databricks connection gets its own MCP endpoint, scoped to a single integrated account (that tenant's Databricks workspace connection). The server URL itself should contain a cryptographic token that encodes which account to use and what tools to expose. For example: https://api.your-saas.com/mcp/a1b2c3d4e5f6...
If the token leaks, you can revoke that one server without disturbing the rest.
2. Token Hashing at Rest
The raw MCP token should never be stored in your database. When generated, hash the token with a signing key via HMAC and store the hashed value in your edge storage (like a fast key-value store). When a request arrives, the server hashes the URL token and looks it up to find the associated Databricks credentials. If your database is ever compromised, the hashes are useless without the signing key.
flowchart TD
A["Agent Request<br>(URL Token)"] --> B["Hash Token via HMAC"]
B --> C{"Lookup in Edge KV"}
C -->|Match| D["Load Databricks OAuth State"]
C -->|Miss| E["Return 401 Unauthorized"]
D --> F["Proxy Request to Databricks<br>(Zero Data Caching)"]
F --> G["Stream Results to Agent"]3. OAuth Lifecycle Isolated Per Tenant
Databricks OAuth access tokens have a short Time-To-Live (TTL). A production MCP layer needs to refresh them shortly before expiry, per tenant, without dropping in-flight tool calls. The refresh has to happen on a schedule ahead of expiry rather than reactively on a 401, because reactive refresh means the first agent call after expiry always fails.
sequenceDiagram
participant Agent as "AI Agent (Claude / LangGraph)"
participant MCP as "MCP Server (per tenant)"
participant Auth as "Auth Layer (per-tenant OAuth)"
participant DBX as "Databricks REST API"
Agent->>MCP: tools/call execute_databricks_sql_statement
MCP->>Auth: get valid access token (tenant X)
Auth-->>MCP: token (refreshed if near expiry)
MCP->>DBX: POST /api/2.0/sql/statements
DBX-->>MCP: 200 or 429
MCP-->>Agent: result or normalized 4294. Stateless Execution and Zero Retention
Your MCP server must operate as a stateless proxy. It should follow a strict zero-retention policy for the actual data payloads returning from Databricks. It receives a tools/call, resolves the tenant's OAuth token, forwards the request to Databricks, and streams the response back. Row-level query results, table contents, and job outputs should not be persisted in the integration layer.
Expirable MCP servers are useful here: a contractor gets read-only access for seven days, the token auto-expires, and a TTL mechanism automatically deletes the database records and KV entries once the expiration timestamp is reached, ensuring no stale access remains. This is what lets you tell an InfoSec reviewer, honestly, that the MCP tier is not a copy of the lakehouse.
Build vs. Buy for Databricks MCP Servers: The Honest Trade-off
Building a custom, multi-tenant MCP server for Databricks is not conceptually hard, but it is operationally expensive. You must implement the JSON-RPC 2.0 protocol, build a dynamic OpenAPI-to-MCP schema generator, manage isolated OAuth token lifecycles (authorization code + refresh), normalize HTTP 429 rate limits, and maintain a highly available, stateless proxy architecture. Realistically, that is a two-engineer, six-to-nine-month build for the first cut, plus ongoing maintenance.
For B2B SaaS companies, these infrastructure challenges distract from building actual AI features. Truto provides a unified API platform that normalizes data across hundreds of SaaS platforms into common data models and automatically exposes them as secure, multi-tenant MCP servers.
Truto handles the complex OAuth lifecycles with pre-expiry refresh, ensures each customer's connection is securely isolated, and dynamically generates MCP tools directly from the integration's documentation. When Databricks returns a rate limit error, Truto normalizes the headers and passes the 429 cleanly back to your agent framework. The entire architecture is stateless, ensuring zero retention of sensitive lakehouse data.
Stop wasting engineering cycles on infrastructure plumbing. If Databricks is one of many integrations you need to expose to agents, buying is almost always cheaper than building. Focus on building intelligent agent workflows.
FAQ
- Can I use Databricks' native managed MCP servers for my SaaS application?
- No. Databricks' managed MCP servers are designed for internal, on-behalf-of-user BI use cases inside a single workspace. B2B SaaS applications require external, multi-tenant MCP infrastructure to isolate individual customer connections and OAuth lifecycles.
- How should an MCP server handle Databricks API rate limits?
- A production MCP server must pass HTTP 429 errors directly back to the calling agent framework, alongside normalized rate limit headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset), allowing the agent to manage its own exponential backoff.
- What is the best way to expose Databricks Unity Catalog to AI agents?
- Instead of hardcoding tools, dynamically generate MCP tools from Databricks' OpenAPI documentation. This ensures agents always have accurate schemas for SQL execution and catalog management, while allowing you to filter tools by read/write categories and tags.
- How do you keep sensitive Databricks data out of the MCP middle tier?
- Use a stateless proxy design. The MCP server should forward requests to Databricks and stream responses back without persisting query results or table contents. Combine this with hashed tokens at rest and expirable server URLs to maintain a zero-retention architecture.
- How do you manage OAuth tokens for multi-tenant Databricks MCP servers?
- Treat each customer Databricks workspace as an isolated integrated account with its own OAuth application and refresh token. Schedule token refresh shortly before the access token expires so in-flight tool calls never hit a 401 error.