Databricks GenAI Engineer: CI/CD for Agents, Prompt Lifecycle, MCP Servers and Agent Interfaces
Assembling and deploying a Databricks agent for real users means running it through CI/CD, managing its prompts as versioned assets, connecting it to tools through MCP servers and putting it in front of people through the right interface. This lesson covers deploying code with bundle targets and federated CI authentication, testing individual components, evaluation gates and smoke tests, keeping a Vector Search index current, prompt versions, aliases, promotion, rollback and caching, choosing managed, external or custom MCP servers, and building user interfaces in Databricks Apps, Slack and Microsoft Teams with the right identity.
On this page10 sections
- CI/CD for agents: deploy reviewed code to each environment with bundles
- Testing individual agent components, evaluation gates and smoke tests
- Keeping a Vector Search index current in a pipeline
- Prompt version control: immutable versions, movable aliases
- Promoting and rolling back prompts across environments
- How fast an alias move reaches running agents: load_prompt caching
- Choosing a managed, external or custom MCP server
- External and custom MCP servers: connections, transport and permissions
- Choosing the user-facing interface: Apps, Review App, Playground, Slack and Teams
- Interface authorization: app identity, user authorization and Teams on-behalf-of-user
- Promote agent code across workspaces with bundle targets and authenticate CI as a service principal through a federation policy
- Test tools, parsers and the retriever in isolation, and place evaluation gates and post-deploy smoke tests in the pipeline
- Keep a Vector Search index current from a pipeline and replace an index without downtime when its schema or embedding model changes
- Manage prompt lifecycle with immutable versions, environment aliases, pinned URIs, promotion, rollback and load_prompt caching
- Choose between Databricks-managed, external and custom MCP servers and configure their connections, transport and permissions
- Pick a user-facing interface (Databricks Apps, Review App, AI Playground, Slack, Teams) and the authorization that gives each user the right data
CI/CD for agents: deploy reviewed code to each environment with bundles
CI/CD for a Databricks agent means that reviewed code, not hand-copied artifacts, reaches each environment automatically, and that every environment builds its own data assets (indexes, endpoints, jobs) from its own data. On Databricks the standard vehicle is a bundle: Declarative Automation Bundles, the current name for Databricks Asset Bundles. A bundle describes the agent code, the jobs that build its Vector Search index and log the model, and the serving endpoints, with one target per environment (dev, staging, prod), each pointing at its own workspace and catalog.
Two promotion patterns exist, and the data constraint decides between them:
| Pattern | What moves between environments | When it fits |
|---|---|---|
| Deploy code | The source code. Each target runs the same jobs in its own workspace, so staging and production build their own index, model version and endpoint from their own data. | The usual recommendation, and required when production data may only be read in the production workspace. |
| Deploy models | A model version built and registered in development, then copied or promoted into the production catalog. | Only when development may build from production-equivalent data, because whatever the dev build embedded (for example a dev-built index) travels with it. |
Folder copies inside one workspace, or exporting notebooks and importing them by hand, fail both goals: they are not automated and they do not separate environments.
How the pipeline authenticates
The CI system (for example GitHub Actions running bundle deploy on merge to main) should run as a service principal, never as a person, and should avoid long-lived secrets. Databricks supports workload identity federation: you create a federation policy on the service principal that trusts the GitHub Actions OIDC token issuer, the workflow presents its short-lived OIDC token, and Databricks exchanges it for a short-lived Databricks token. Compare the alternatives: an OAuth client secret stored in GitHub is still a long-lived credential, and a personal access token both is long-lived and runs as a person.
Testing individual agent components, evaluation gates and smoke tests
Testing an individual component means exercising one piece of the agent (a tool, a parser, the retriever, the routing logic) directly with fixed inputs and asserting its output, without running the whole agent or calling a live model. These tests are fast, cheap and deterministic, so they can run on every pull request and fail only when that component's logic is wrong.
- Tools. A Unity Catalog function or Python tool can be called directly with known arguments; assert the result (for example, that a refund calculation rounds correctly).
- Post-processing and routing. Replace the LLM client with a stub that returns fixed responses, including malformed JSON, and assert that the parser and the tool-routing code handle each case. The stub removes token cost and randomness and lets you test failure formats on purpose.
- Retrieval. Call the retriever directly with a set of known questions and assert that the expected chunk IDs come back. This isolates retrieval from generation and costs no LLM tokens.
The rule for a unit test is that it must be automated, deterministic, cheap and isolated to one component. Anything that adds model randomness, spends tokens, depends on a live endpoint or needs a person to look at the result fails that rule, however useful it is elsewhere. Full evaluation with LLM judges, live endpoint calls, manual exploration and trace review all have their place, at other layers:
| Layer | What it checks | When it runs |
|---|---|---|
| Component (unit) tests | One tool, parser or retriever, with stubs and fixed inputs | Every pull request, in seconds |
| Evaluation gate | The whole agent or prompt version against an evaluation dataset with scorers and thresholds | Before promotion |
| Post-deploy smoke test | The deployed endpoint answers representative requests | After the staging endpoint reports Ready, before production |
| Production monitoring | Scorers and metrics on live traces | Continuously after release |
Evaluation gates and post-deploy smoke tests
An evaluation gate evaluates one exact, immutable candidate and promotes that same candidate only if its scores pass. The identity matters as much as the threshold: production must receive exactly what was evaluated.
- Pin the candidate. Evaluate a prompt by its version number (or a model by its version), then point the production alias at that same number. Resolving
@latestonce for evaluation and again for promotion races against teammates who register new versions in between. - Gate before exposure. Moving the production alias first and evaluating afterwards exposes users to untested text, even if you move it back on failure.
- Do not re-register on promotion. Copying the evaluated template into a new version creates a version that was never evaluated.
A smoke test catches what offline evaluation cannot. A serving endpoint reporting Ready only means its container started. Problems that come from the serving environment, for example a Vector Search index or other resource that was not declared when the agent was logged, so the endpoint's identity cannot reach it, surface only when the deployed endpoint handles a request. After Ready, send a few representative requests to the staging endpoint and fail the pipeline unless valid answers return. Anything that runs outside the endpoint, under a different identity, cannot see this class of fault; only a request through the deployed endpoint exercises the endpoint's own credentials and declared resources.
Keeping a Vector Search index current in a pipeline
A pipeline that rewrites the source table must also make the index pick up the change, and how depends on the index's sync mode. (Databricks has renamed Mosaic AI Vector Search to Databricks AI Search; the exam guide uses the older name. Endpoint types and sync modes in depth are covered in the Mosaic AI Vector Search lesson.)
| Index setup | How it updates | Pipeline step |
|---|---|---|
| Delta Sync, Triggered mode | Only when a sync is requested | Call index.sync() (or the REST sync call, or Sync now in the UI) as the last step after the table write |
| Delta Sync, Continuous mode | Follows the source table within seconds, with compute always running | None; available on standard endpoints only |
| Direct Vector Access | No source table; you write vectors yourself | Upsert and delete through the API in the pipeline |
Change Data Feed on the source table is a prerequisite for Delta Sync, not a trigger: enabling it does not make a Triggered index refresh. In Triggered mode the only thing that moves new rows into the index is a sync request; operations on the surrounding infrastructure do not.
Storage-optimized endpoints, chosen for cost at very large scale, support only Triggered sync, and each sync partially rebuilds the index. So the right design is to call sync() as the final step of each load, which refreshes exactly when new data lands. A separate timer that syncs every few minutes wastes rebuild work between loads, and moving to a standard endpoint for Continuous sync gives freshness the requirement did not ask for at a higher cost.
Changes that need a new index: build, wait, switch, delete
An index's schema and embedding model are fixed when it is created. Adding a column (for example a region column to filter on) or moving to a different embedding model cannot be done by syncing the existing index. The zero-downtime pattern is:
- Change the source table if needed (add the column).
- Create a new index with the new schema or embedding model.
- Wait for its initial sync to finish, so it is fully populated.
- Switch the agent (configuration or alias in your code) to the new index.
- Delete the old index.
Deleting first and recreating under the same name causes an outage; switching before the initial sync completes serves partial results. Direct Access upserts do not apply to a Delta Sync index and never change a schema.
Prompt version control: immutable versions, movable aliases
The MLflow Prompt Registry (prompts stored in Unity Catalog, such as main.support.answer) gives prompts the same lifecycle as code: every registered version is an immutable snapshot, and aliases are mutable pointers to versions. That split is what makes promotion, rollback and reproducible tests possible. (Template syntax, registering versions and loading by alias in chain code are introduced in the LangChain and prompt engineering lesson; this section is about lifecycle.)
| URI | Resolves to | Use it for |
|---|---|---|
prompts:/main.support.answer/6 | Version 6, forever | Reproducible regression tests and evaluation of a specific candidate |
prompts:/main.support.answer@production | Whatever version the alias points to now | Deployed apps that must pick up approved changes without a redeploy |
prompts:/main.support.answer@latest | The most recently registered version (reserved alias) | Quick experiments only; any registration, including a draft, changes it |
Two consequences follow. A deployed agent should load a named alias, never @latest: with @latest, any new registration (an experiment, a draft) becomes what production loads, and nothing attached to a version as metadata can stop that. And a regression suite that must reproduce a past baseline should load the version number that the baseline run logged; aliases move with releases, and a new alias created today points at today's text, not the baseline's.
Versions also carry lineage: a commit message when registered, and tags such as the author or ticket. These are metadata for people; they never affect which version a URI resolves to.
Promoting and rolling back prompts across environments
Prompts are promoted across environments by giving one registered prompt an alias per environment (for example dev, staging and production) and moving the next alias to a version that passed testing. The same application code runs everywhere; each environment's configuration names the alias it loads, so promotion needs no code change and no redeploy. Any scheme that encodes the environment somewhere else (in the prompt name, in code, or in metadata looked up at runtime) reinvents what aliases already do and usually needs a deploy to change.
A promotion step therefore has two parts: resolve the source alias to its version number (a loaded prompt exposes .version), then call mlflow.genai.set_prompt_alias() with the prompt name, the target alias and that number. For example, promoting an evaluated dev candidate to staging for a prompt named main.hr.policy_qa:
cand = mlflow.genai.load_prompt(
"prompts:/main.hr.policy_qa@dev")
mlflow.genai.set_prompt_alias(
"main.hr.policy_qa", alias="staging",
version=cand.version)
Watch the direction: the alias you read is the source, the alias you set is the target. Promotion never registers anything: a version created during promotion is by definition a version that was not evaluated.
Rollback
Because the previous version still holds exactly the text that worked, rollback is one set_prompt_alias() call that points production back to it. An alias-loading app picks it up without a redeploy, no new version is created, and the bad version stays available for investigation. The principle is to move a pointer, not data: aliases never fall back on their own, and any rollback that removes versions or aliases, or creates new versions, either breaks loads, loses evidence or ships text that was never evaluated.
Check the automation too. If a scheduled job promotes whatever staging points to, and staging still references the bad version, the next run undoes the rollback. Move staging off the bad version as well, or pause the job.
How fast an alias move reaches running agents: load_prompt caching
mlflow.genai.load_prompt() caches what it loads, so an alias move reaches running replicas only when their cached copy expires. By default an alias-based load is cached with a 60-second TTL (configurable through the MLFLOW_ALIAS_PROMPT_CACHE_TTL_SECONDS environment variable), while a version-number load is cached with no TTL, because a version can never change.
- To make alias moves propagate faster, pass
cache_ttl_secondstoload_prompt(), for example 5 seconds. Most requests are still served from cache. cache_ttl_seconds=0bypasses the cache, so every call queries the registry: instant propagation, but a registry lookup on every request.- The TTL is the lever: anything that forces propagation by redeploying, or that stops loading by alias, gives up the no-redeploy benefit aliases exist for.
So a lag of about a minute after moving an alias is expected behaviour, not a deployment fault.
Choosing a managed, external or custom MCP server
The Model Context Protocol (MCP) gives agents a standard way to discover and call tools. On Databricks you pick the server type by asking who already provides an MCP server for the capability:
| Type | What it is | Choose it when |
|---|---|---|
| Databricks-managed | Databricks-provided servers with nothing for you to host: for Databricks services (a Genie space, Vector Search indexes, Unity Catalog functions, Databricks SQL) and, increasingly, for some third-party tools such as GitHub and Slack | Databricks already provides an MCP server for the capability and you want no server code to write, host or patch |
| External | A third-party server that already speaks MCP over Streamable HTTP, registered in Databricks through a Unity Catalog HTTP connection | A vendor already publishes an MCP server and you want its credentials and access governed by Unity Catalog |
| Custom | Your own MCP server, hosted on Databricks Apps | No MCP server exists for the capability, for example an internal REST API |
For data questions, the current Databricks guidance points natural-language business questions at a Genie-based MCP server and specific SQL at the Databricks SQL MCP server. Typical traps: rebuilding something Databricks already provides duplicates a managed server; registering a plain REST API as an external MCP server fails because external registration only works for a server that already speaks MCP; calling a vendor MCP URL with a token from a Databricks secret keeps the token out of code but bypasses Unity Catalog governance. For an API described by an OpenAPI specification, Databricks provides an OpenAPI MCP server template for Databricks Apps that reads the spec (for example from a volume) and takes its credentials from an HTTP connection, which is far less code than hand-writing one tool per operation, whether as MCP tools or as Unity Catalog functions.
MCP registration is evolving: Databricks now manages registered MCP servers through Unity Catalog (Unity Gateway), and endpoint URL patterns change. The decisions above are stable; specific URLs are not.
External and custom MCP servers: connections, transport and permissions
External servers: the Unity Catalog HTTP connection
An external MCP server is registered through a Unity Catalog HTTP connection, which stores the server's endpoint and credentials so they never appear in agent code. The connection's authentication type decides whose identity reaches the vendor:
| Authentication | Identity the vendor sees |
|---|---|
| Bearer token | One shared token |
| OAuth machine-to-machine | One application identity for every caller |
| OAuth user-to-machine, shared | One person's sign-in (for example an admin's) used for everyone |
| OAuth user-to-machine, per user | Each user, who signs in to the vendor on first use |
| Managed OAuth | Databricks-managed OAuth for supported providers |
When each person may see only what they can see in the vendor system, and each call must be attributable to them, only per-user OAuth works. Filtering by an email the agent passes along is not enforcement.
Who needs which privilege on a registered MCP
When an external server is registered in Unity Catalog as an MCP (the current Unity Gateway path), the MCP itself is the securable that callers use. Permissions split by role:
| Who | Needs |
|---|---|
| Callers (users and agents that call the MCP's tools) | EXECUTE on the MCP, USE CATALOG and USE SCHEMA on its parents, and assignment to the workspace. They do not need USE CONNECTION. |
| MCP authors and administrators | USE CONNECTION on the underlying HTTP connection, for example to reuse an existing connection when registering an MCP |
Keep USE CONNECTION away from end users: it permits direct use of the connection, which bypasses the tool selection and policies defined on the MCP, so granting it to callers is the over-grant, not the least privilege. Ownership or MANAGE goes further still, letting a principal alter the MCP or grant it on. None of these grants changes record-level access inside the vendor; that is the authentication type's job.
Legacy path: before MCPs became Unity Catalog securables, agents called an external server through a per-connection proxy URL, and there USE CONNECTION on the connection was what callers needed. You may still meet this older pattern in existing projects. Likewise, a custom app built from the OpenAPI MCP server template reads its credentials from an HTTP connection, so that app's callers need USE CONNECTION on it.
Custom servers on Databricks Apps
- Transport: Streamable HTTP. Databricks Apps hosts web servers, so the stdio transport (a local subprocess) does not apply.
- Listening: bind to
0.0.0.0on the port inDATABRICKS_APP_PORT; the platform routes traffic only to that address and port. - Name: start the app name with
mcp-(for examplemcp-ticket-tools) so AI Playground recognises it as an MCP server. - URL: clients use the app URL plus the server's MCP path, typically
/mcp; the bare app root lists no tools. - Permissions: callers need CAN USE on the app; CAN MANAGE adds editing, redeploying and stopping it. Each resource type has its own permission vocabulary: app permissions govern apps, Unity Catalog privileges govern securables such as connections and MCPs, and serving-endpoint permissions govern endpoints.
Choosing the user-facing interface: Apps, Review App, Playground, Slack and Teams
The right interface is the one that fits who is using the agent, at what stage, and where they already work:
| Interface | Best for | Not for |
|---|---|---|
| Databricks Apps chat UI (agent templates) | Production web chat for internal users: workspace single sign-on, streamed answers, chat history, little front-end work | Users who must stay inside Slack or Teams |
| MLflow Review App | Pre-launch experts chatting with the agent and recording structured feedback that flows into evaluation | Day-to-day production use |
| AI Playground | Prototyping and comparing models, prompts and tools (including MCP servers) | Capturing structured feedback or serving end users |
| Slack app | Users who want to mention the agent in channels and get threaded replies | |
| Microsoft Teams bot | Staff who spend their day in Teams |
Direction of the call matters. A tool integration (for example an MCP server for a chat platform) lets the agent call out to that platform; it is not an interface, because nothing carries users' messages in to the agent. A chat interface inside a messaging platform needs a bot registered with that platform: the platform delivers user messages to the bot, and the bot calls the agent's serving endpoint and replies where the user asked. Requirements such as "users must stay in the tool they already use" therefore rule out anything that hands them a link to somewhere else.
Tip. Expect scenario items that describe a release pipeline, a prompt change, a tool integration or a group of users and add a deciding requirement such as no redeploy, no server code, least privilege, no long-lived credentials, per-user data access, zero downtime, cost or a propagation time. Pipeline items ask how code reaches each workspace, how CI authenticates, which test fits a component and where an evaluation gate or smoke test belongs. Vector Search items turn on sync modes, endpoint types and changes that need a new index. Prompt items show short MLflow Python or URIs and probe aliases versus version numbers, promotion direction, rollback with automation in play and load_prompt caching. MCP items ask you to pick managed, external or custom servers, a connection's authentication type, or the transport, name, URL and permission for a server on Databricks Apps. Interface items pair users with Apps, the Review App, AI Playground, Slack or Teams and ask which identity their queries run as. A few items ask you to select two answers.
- Deploy code, not models, when production data may only be read in production: a bundle with dev, staging and prod targets builds each environment's index and endpoints there.
- CI should run as a service principal using a federation policy that trusts the CI system's OIDC token, so no long-lived secret is stored.
- Component tests call one tool, parser or retriever directly with fixed inputs and a stubbed LLM; LLM-judge evaluation belongs in the promotion gate, not the unit tests.
- Endpoint Ready only means the container started; a smoke test of representative requests catches missing resources before production.
- Triggered Delta Sync indexes need sync() after each load; storage-optimized endpoints support only Triggered sync, and schema or embedding-model changes need a new index built, synced, switched to, then the old one deleted.
- Prompt versions are immutable and aliases are pointers: deployed apps load a named alias, regression tests pin a version number, and @latest follows every registration.
- Promote by pointing the next alias at the evaluated version; roll back by pointing production back, and move any alias that automation copies from as well.
- Alias loads are cached for 60 seconds by default; cache_ttl_seconds shortens that, and 0 queries the registry on every call.
- Managed MCP servers are Databricks-provided with nothing to host; external ones need an existing MCP server registered through a Unity Catalog HTTP connection, with callers granted EXECUTE on the MCP and USE CONNECTION kept for authors and admins; custom ones run on Databricks Apps over Streamable HTTP.
- Per-user data in an interface needs the user's token forwarded (Apps user authorization or a Teams federation policy) plus on-behalf-of-user authorization in the agent.
Frequently asked questions
How do you promote a prompt from staging to production in the MLflow Prompt Registry?
Read the version number that the staging alias points to and call mlflow.genai.set_prompt_alias() to point the production alias at that same version. Do not register the template again, because that creates a new version that was never evaluated. Apps that load the production alias pick up the change without a code change or redeploy, after their prompt cache expires.
How do you roll back a bad prompt version on Databricks?
Point the production alias back to the previous version with set_prompt_alias(). Prompt versions are immutable, so the old version still contains exactly the text that worked, and the bad version stays available for investigation. If an automated job promotes from a staging alias that still points to the bad version, move that alias too or the job will undo the rollback.
Why does an agent keep using the old prompt for about a minute after an alias is moved?
mlflow.genai.load_prompt() caches alias-based loads with a default time-to-live of 60 seconds. Pass a smaller cache_ttl_seconds value to propagate moves faster while still caching, or 0 to query the registry on every call. Version-number loads are cached without a time-to-live because a version never changes.
When should you use a Databricks-managed, external or custom MCP server?
Use a Databricks-managed MCP server when Databricks already provides one for the capability, such as a Genie space, Vector Search, Unity Catalog functions, Databricks SQL or some third-party tools, and you want no server code. Use an external MCP server when a third party already publishes an MCP server; register it through a Unity Catalog HTTP connection and grant callers EXECUTE on the registered MCP. Build a custom MCP server on Databricks Apps when no MCP server exists, for example for an internal REST API, where the OpenAPI MCP server template saves most of the code.
What does a custom MCP server need to run on Databricks Apps?
It must use the Streamable HTTP transport and listen on 0.0.0.0 at the port in the DATABRICKS_APP_PORT environment variable. Naming the app with the mcp- prefix lets AI Playground recognise it, clients connect to the app URL plus the server's MCP path (usually /mcp), and callers need CAN USE permission on the app.
How do you keep a Vector Search index up to date in a CI/CD or ETL pipeline?
For a Delta Sync index in Triggered mode, call sync() on the index as the last step after the source table is written. Continuous mode follows the table automatically but is available only on standard endpoints; storage-optimized endpoints support only Triggered sync. Changing the index's columns or embedding model needs a new index that is fully synced before traffic switches to it.
How can a Databricks agent in Microsoft Teams respect each user's Unity Catalog permissions?
Use an Azure Bot Service bot with a Microsoft Entra ID OAuth connection, create a Databricks OAuth federation policy that trusts the tenant's issuer and the bot app's audience so the bot can exchange the user's Entra ID token for a Databricks token, and build the agent for on-behalf-of-user authorization so its tools query data as that user.
What is the difference between the MLflow Review App and AI Playground?
The Review App is for domain experts to chat with an agent before launch and record structured feedback that feeds MLflow evaluation. AI Playground is for developers to prototype and compare models, prompts and tools. Neither is a production chat interface; for that use a Databricks Apps chat UI or a Slack or Teams bot.
Source
This lesson covers the "Assembling and Deploying Applications" domain of the official Databricks GenAI exam guide. Vendors revise their guides — check the source for the current version.
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.