SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Assembling and Deploying Applications

Databricks GenAI Engineer: CI/CD for Agents, Prompt Lifecycle, MCP Servers and Agent Interfaces

18 min readDatabricks GenAI · Assembling and Deploying ApplicationsUpdated

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.

What you’ll learn
  • 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:

PatternWhat moves between environmentsWhen it fits
Deploy codeThe 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 modelsA 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:

LayerWhat it checksWhen it runs
Component (unit) testsOne tool, parser or retriever, with stubs and fixed inputsEvery pull request, in seconds
Evaluation gateThe whole agent or prompt version against an evaluation dataset with scorers and thresholdsBefore promotion
Post-deploy smoke testThe deployed endpoint answers representative requestsAfter the staging endpoint reports Ready, before production
Production monitoringScorers and metrics on live tracesContinuously 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 @latest once 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 setupHow it updatesPipeline step
Delta Sync, Triggered modeOnly when a sync is requestedCall index.sync() (or the REST sync call, or Sync now in the UI) as the last step after the table write
Delta Sync, Continuous modeFollows the source table within seconds, with compute always runningNone; available on standard endpoints only
Direct Vector AccessNo source table; you write vectors yourselfUpsert 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:

  1. Change the source table if needed (add the column).
  2. Create a new index with the new schema or embedding model.
  3. Wait for its initial sync to finish, so it is fully populated.
  4. Switch the agent (configuration or alias in your code) to the new index.
  5. 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.)

URIResolves toUse it for
prompts:/main.support.answer/6Version 6, foreverReproducible regression tests and evaluation of a specific candidate
prompts:/main.support.answer@productionWhatever version the alias points to nowDeployed apps that must pick up approved changes without a redeploy
prompts:/main.support.answer@latestThe 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_seconds to load_prompt(), for example 5 seconds. Most requests are still served from cache.
  • cache_ttl_seconds=0 bypasses 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:

TypeWhat it isChoose it when
Databricks-managedDatabricks-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 SlackDatabricks already provides an MCP server for the capability and you want no server code to write, host or patch
ExternalA third-party server that already speaks MCP over Streamable HTTP, registered in Databricks through a Unity Catalog HTTP connectionA vendor already publishes an MCP server and you want its credentials and access governed by Unity Catalog
CustomYour own MCP server, hosted on Databricks AppsNo 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:

AuthenticationIdentity the vendor sees
Bearer tokenOne shared token
OAuth machine-to-machineOne application identity for every caller
OAuth user-to-machine, sharedOne person's sign-in (for example an admin's) used for everyone
OAuth user-to-machine, per userEach user, who signs in to the vendor on first use
Managed OAuthDatabricks-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:

WhoNeeds
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 administratorsUSE 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.0 on the port in DATABRICKS_APP_PORT; the platform routes traffic only to that address and port.
  • Name: start the app name with mcp- (for example mcp-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:

InterfaceBest forNot for
Databricks Apps chat UI (agent templates)Production web chat for internal users: workspace single sign-on, streamed answers, chat history, little front-end workUsers who must stay inside Slack or Teams
MLflow Review AppPre-launch experts chatting with the agent and recording structured feedback that flows into evaluationDay-to-day production use
AI PlaygroundPrototyping and comparing models, prompts and tools (including MCP servers)Capturing structured feedback or serving end users
Slack appUsers who want to mention the agent in channels and get threaded replies
Microsoft Teams botStaff 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.

Interface authorization: app identity, user authorization and Teams on-behalf-of-user

Every interface decides whose identity reaches the agent and its tools, and that decides which rows a Unity Catalog row filter returns.

Databricks Apps

Each app has its own service principal, whose credentials the platform injects. With app authorization, you declare the serving endpoint as an app resource with the lowest permission that works, Can query for an app that only sends chat requests (Can view under-grants, Can manage over-grants), and reference it from app.yaml. No personal access token belongs in the app's code or configuration.

App authorization means every query runs as the app's service principal, so every user sees the same rows. When each user must see only their own rows, enable user authorization with the model-serving scope so the app forwards each signed-in user's token to the endpoint, and build the agent for on-behalf-of-user authorization (declare the user API scopes its tools need when logging it, and create tool clients with the user's credentials). Both layers are required: forwarding the token does nothing if the agent's tools still run as the endpoint's service principal. Changing how much any identity may do never changes which identity runs the query; only these two layers do.

Microsoft Teams (Public Preview)

The documented pattern is an Azure Bot Service bot (its code hosted for example on Azure App Service) with a Microsoft Entra ID app registration and an OAuth connection that signs users in with Entra ID. To call the agent as that user, create a Databricks OAuth federation policy that trusts the tenant's token issuer and the bot app's audience; the bot then exchanges the user's Entra ID token for a Databricks token for the same user. Add on-behalf-of-user authorization in the agent and tool queries run with that person's permissions. The test for any design is the same: if the token that reaches Databricks belongs to one shared identity, every user gets that identity's rows, whatever user details travel alongside in the request and whatever is logged.

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.

Key takeaways
  • 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.

Spotted a mistake in this lesson?