Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .claude/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,21 @@ async def invoke_handler(request: ResponsesAgentRequest) -> ResponsesAgentRespon
async def stream_handler(request: ResponsesAgentRequest) -> AsyncGenerator[ResponsesAgentStreamEvent, None]: ...
```

LangGraph `invoke_handler` delegates to `stream_handler`. OpenAI SDK `invoke_handler` calls `Runner.run()` independently.
LangGraph `invoke_handler` delegates to `stream_handler`. OpenAI SDK `invoke_handler` calls `Runner.run()` independently. ADK `invoke_handler` delegates to `stream_handler` (collecting `response.output_item.done` items).

### MLflow autologging

- LangGraph templates: `mlflow.langchain.autolog()`
- OpenAI SDK templates: `mlflow.openai.autolog()` + `set_trace_processors([])`
- ADK templates: `mlflow.litellm.autolog()` (ADK calls the model through LiteLLM, so LiteLLM autolog captures the model spans)

All handlers tag traces with: `mlflow.update_current_trace(metadata={"mlflow.trace.session": session_id})`

### MCP server initialization

- **LangGraph**: `DatabricksMultiServerMCPClient` wrapping `DatabricksMCPServer` objects. Tools fetched once at agent init via `.get_tools()`.
- **OpenAI SDK**: `McpServer` used as async context manager per-request: `async with await init_mcp_server() as mcp_server:`
- **ADK**: `MCPToolset` (with `StreamableHTTPConnectionParams`, `Authorization: Bearer` header) added to the agent's `tools` list. Requires the `mcp` extra (`google-adk[extensions]`).

### Session/memory patterns

Expand All @@ -54,9 +56,16 @@ All handlers tag traces with: `mlflow.update_current_trace(metadata={"mlflow.tra
| Short-term memory | LangGraph | `AsyncCheckpointSaver` | `thread_id` via `config["configurable"]` |
| Long-term memory | LangGraph | `AsyncDatabricksStore` | `user_id` via `config["configurable"]` |
| Short-term memory | OpenAI | `AsyncDatabricksSession` | `session_id` passed to `Runner.run(..., session=)` |
| Short-term memory | ADK | `DatabaseSessionService` (Lakebase) — *not shipped; base `agent-adk` is stateless* | `session_id` passed to `runner.run_async(..., session_id=)` |

All memory templates return the ID in `custom_outputs` so clients can reuse it.

The base `agent-adk` template is stateless: it replays client-carried history into a fresh
`InMemorySessionService` each request via `agent_server.utils.seed_session_history`, and adapts ADK
`Event`s to `ResponsesAgentStreamEvent`s via `process_adk_events`. The model is reached through
`LiteLlm(model="openai/<endpoint>", api_base="{host}/serving-endpoints", api_key=<token>)`; the
per-request bearer token comes from `get_bearer_token()` (the SDK credential chain).

### `databricks.yml` conventions

- `bundle.name` uses underscores: `agent_langgraph`
Expand All @@ -68,7 +77,7 @@ All memory templates return the ID in `custom_outputs` so clients can reuse it.

### `app.yaml` files

The 3 base templates (`agent-langgraph`, `agent-openai-agents-sdk`, `agent-non-conversational`) have `app.yaml` files for UI-based template creation in the Databricks UI. These are separate from `databricks.yml` and use `valueFrom` (camelCase) for resource references. Memory/multiagent variants do not need separate `app.yaml` files.
The base conversational templates (`agent-langgraph`, `agent-adk`, `agent-openai-agents-sdk`) and `agent-non-conversational` have `app.yaml` files for UI-based template creation in the Databricks UI. These are separate from `databricks.yml` and use `valueFrom` (camelCase) for resource references. Multiagent variants do not need separate `app.yaml` files.

### Per-template AGENTS.md

Expand Down
138 changes: 138 additions & 0 deletions .claude/skills/add-tools-adk/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
name: add-tools
description: "Add tools to your agent and grant required permissions in databricks.yml. Use when: (1) Adding MCP servers, Genie spaces, vector search, or UC functions to agent, (2) Permission errors at runtime, (3) User says 'add tool', 'connect to', 'grant permission', (4) Configuring databricks.yml resources."
---

# Add Tools & Grant Permissions

> **Profile reminder:** All `databricks` CLI commands must include the profile from `.env`: `databricks <command> --profile <profile>`

> Don't have the resource yet? See **create-tools** skill first.

**After adding any MCP server to your agent, you MUST grant the app access in `databricks.yml`.**

Without this, you'll get permission errors when the agent tries to use the resource.

This template uses **Google ADK**. Databricks-hosted tools are connected with an ADK
`MCPToolset`, which needs the `mcp` package — run `uv add "google-adk[extensions]"` once.

## Workflow

**Step 1:** Add an MCP toolset in `agent_server/agent.py`:
```python
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from agent_server.utils import get_bearer_token, get_databricks_host

def create_agent(workspace_client):
host = get_databricks_host(workspace_client)
token = get_bearer_token(workspace_client)
genie = MCPToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{host}/api/2.0/mcp/genie/01234567-89ab-cdef",
headers={"Authorization": f"Bearer {token}"},
),
)
return LlmAgent(name="agent", model=build_model(workspace_client),
instruction="You are a helpful assistant.", tools=[get_current_time, genie])
```

Common Databricks MCP URL paths: UC functions `/api/2.0/mcp/functions/{catalog}/{schema}`,
Genie `/api/2.0/mcp/genie/{space_id}`, Vector Search `/api/2.0/mcp/vector-search/{catalog}/{schema}`,
code interpreter `/api/2.0/mcp/functions/system/ai`.

**Step 2:** Grant access in `databricks.yml`:
```yaml
resources:
apps:
agent_adk:
resources:
- name: 'my_genie_space'
genie_space:
name: 'My Genie Space'
space_id: '01234567-89ab-cdef'
permission: 'CAN_RUN'
```

**Step 3:** Deploy and run:
```bash
databricks bundle deploy
databricks bundle run agent_adk # Required to start app with new code!
```

See **deploy** skill for more details.

## Resource Type Examples

See the `examples/` directory for complete YAML snippets:

| File | Resource Type | When to Use |
|------|--------------|-------------|
| `uc-function.yaml` | Unity Catalog function | UC functions via MCP |
| `uc-connection.yaml` | UC connection | External MCP servers |
| `vector-search.yaml` | Vector search index | RAG applications |
| `sql-warehouse.yaml` | SQL warehouse | SQL execution |
| `serving-endpoint.yaml` | Model serving endpoint | Model inference |
| `genie-space.yaml` | Genie space | Natural language data |
| `lakebase.yaml` | Lakebase database | Session/memory storage (provisioned) |
| `lakebase-autoscaling.yaml` | Lakebase autoscaling postgres | Session/memory storage (autoscaling) |
| `experiment.yaml` | MLflow experiment | Tracing (already configured) |
| `app.yaml` | Databricks App (app-to-app) | Custom MCP servers hosted as Apps |
| `custom-mcp-server.md` | Custom MCP apps | Apps starting with `mcp-*` |

## Custom MCP Servers (Databricks Apps)

Declare the target app as an `app` resource in `databricks.yml` — the bundle grants `CAN_USE` on deploy. Requires Databricks CLI **v0.298.0+**.

```yaml
resources:
apps:
agent_adk:
resources:
- name: 'mcp_server'
app:
name: 'mcp-my-server'
permission: CAN_USE
```

See `examples/custom-mcp-server.md` for the full flow (agent code + YAML + deploy).

## value_from Pattern

**IMPORTANT**: Make sure all `value_from` references in `databricks.yml` `config.env` reference an existing key in the `databricks.yml` `resources` list.
Some resources need environment variables in your app. Use `value_from` in `databricks.yml` `config.env` to reference resources defined in `databricks.yml`:

```yaml
# In databricks.yml, under apps.<app>.config.env:
env:
- name: MLFLOW_EXPERIMENT_ID
value_from: "experiment" # References resources.apps.<app>.resources[name='experiment']
- name: LAKEBASE_INSTANCE_NAME
value_from: "database" # References resources.apps.<app>.resources[name='database']
```

**Critical:** Every `value_from` value must match a `name` field in `databricks.yml` resources.

## MCP Error Handling

MCP tool calls can fail (network issues, permission errors, timeouts). Give slow servers like
Genie a longer timeout, and wrap `runner.run_async(...)` in a try/except so one unavailable
server can't crash the request:

```python
MCPToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{host}/api/2.0/mcp/genie/{space_id}",
headers={"Authorization": f"Bearer {token}"},
timeout=60.0, # increase for slow tools like Genie
),
)
```

## Important Notes

- **MLflow experiment**: Already configured in template, no action needed
- **Multiple resources**: Add multiple entries under `resources:` list
- **Permission types vary**: Each resource type has specific permission values
- **Deploy + Run after changes**: Run both `databricks bundle deploy` AND `databricks bundle run {{BUNDLE_NAME}}`
- **value_from matching**: Ensure `config.env` `value_from` values match `databricks.yml` resource `name` values
9 changes: 9 additions & 0 deletions .claude/skills/add-tools-adk/examples/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Databricks App (for custom MCP servers hosted as Apps)
# Use for: Granting CAN_USE on another Databricks App (e.g., an mcp-* server app)
# Requires: CLI v0.298.0+

# In databricks.yml - add to resources.apps.<app>.resources:
- name: 'mcp_server'
app:
name: '<target-app-name>'
permission: CAN_USE
51 changes: 51 additions & 0 deletions .claude/skills/add-tools-adk/examples/custom-mcp-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Custom MCP Server (Databricks App)

Custom MCP servers are Databricks Apps with names starting with `mcp-*`.

Declare the target app as an `app` resource in `databricks.yml` and the bundle will grant `CAN_USE` on deploy. Requires Databricks CLI **v0.298.0+**.

## Steps

### 1. Add MCP server in `agent_server/agent.py`

```python
from databricks_langchain import DatabricksMCPServer, DatabricksMultiServerMCPClient

custom_mcp = DatabricksMCPServer(
url="https://mcp-my-server.cloud.databricks.com/mcp",
name="my custom mcp server",
)

mcp_client = DatabricksMultiServerMCPClient([custom_mcp])
tools = await mcp_client.get_tools()
```

### 2. Grant access in `databricks.yml`

Add the target app as a resource:

```yaml
resources:
apps:
agent_langgraph:
resources:
- name: 'mcp_server'
app:
name: 'mcp-my-server'
permission: CAN_USE
```

### 3. Deploy

```bash
databricks bundle deploy
databricks bundle run agent_langgraph
```

The bundle grants `CAN_USE` on the target app automatically — no manual permission steps needed.

## Notes

- Requires CLI v0.298.0+ (earlier versions will warn `unknown field: name` on `app.name`)
- The only supported permission is `CAN_USE`
- Subsequent `databricks bundle deploy` commands preserve the `app` resource
8 changes: 8 additions & 0 deletions .claude/skills/add-tools-adk/examples/experiment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# MLflow Experiment
# Use for: Tracing and model logging
# Note: Already configured in template's databricks.yml

- name: 'my_experiment'
experiment:
experiment_id: '12349876'
permission: 'CAN_MANAGE'
9 changes: 9 additions & 0 deletions .claude/skills/add-tools-adk/examples/genie-space.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Genie Space
# Use for: Natural language interface to data
# MCP URL: {host}/api/2.0/mcp/genie/{space_id}

- name: 'my_genie_space'
genie_space:
name: 'My Genie Space'
space_id: '01234567-89ab-cdef'
permission: 'CAN_RUN'
21 changes: 21 additions & 0 deletions .claude/skills/add-tools-adk/examples/lakebase-autoscaling.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Lakebase Autoscaling Postgres (for agent memory)
# Use for: Short-term or long-term memory storage with autoscaling Lakebase

# In databricks.yml - add to resources.apps.<app>.resources:
- name: 'postgres'
postgres:
branch: "projects/<project-name>/branches/<branch-name>"
database: "projects/<project-name>/branches/<branch-name>/databases/<database-id>"
permission: CAN_CONNECT_AND_CREATE

# In databricks.yml config block - add to env:
# - name: LAKEBASE_AUTOSCALING_PROJECT
# value: "<project-name>"
# - name: LAKEBASE_AUTOSCALING_BRANCH
# value: "<branch-name>"

# How to find the values:
# databricks api get /api/2.0/postgres/projects
# databricks api get /api/2.0/postgres/projects/<project-name>/branches
# databricks api get /api/2.0/postgres/projects/<project-name>/branches/<branch-name>/databases
# The database-id is the internal ID (e.g., db-xxxx-xxxxxxxxxx), NOT "databricks_postgres"
18 changes: 18 additions & 0 deletions .claude/skills/add-tools-adk/examples/lakebase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Lakebase Database (for agent memory)
# Use for: Long-term memory storage via AsyncDatabricksStore
# Requires: value_from reference in databricks.yml config block

# In databricks.yml - add to resources.apps.<app>.resources:
- name: 'database'
database:
instance_name: '<your-lakebase-instance-name>'
database_name: 'databricks_postgres'
permission: 'CAN_CONNECT_AND_CREATE'

# In databricks.yml config block - add to env:
# - name: LAKEBASE_INSTANCE_NAME
# value_from: "database"
# - name: EMBEDDING_ENDPOINT
# value: "databricks-gte-large-en"
# - name: EMBEDDING_DIMS
# value: "1024"
7 changes: 7 additions & 0 deletions .claude/skills/add-tools-adk/examples/serving-endpoint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Model Serving Endpoint
# Use for: Model inference endpoints

- name: 'my_endpoint'
serving_endpoint:
name: 'my_endpoint'
permission: 'CAN_QUERY'
7 changes: 7 additions & 0 deletions .claude/skills/add-tools-adk/examples/sql-warehouse.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SQL Warehouse
# Use for: SQL query execution

- name: 'my_warehouse'
sql_warehouse:
sql_warehouse_id: 'abc123def456'
permission: 'CAN_USE'
9 changes: 9 additions & 0 deletions .claude/skills/add-tools-adk/examples/uc-connection.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Unity Catalog Connection
# Use for: External MCP servers via UC connections
# MCP URL: {host}/api/2.0/mcp/external/{connection_name}

- name: 'my_connection'
uc_securable:
securable_full_name: 'my-connection-name'
securable_type: 'CONNECTION'
permission: 'USE_CONNECTION'
9 changes: 9 additions & 0 deletions .claude/skills/add-tools-adk/examples/uc-function.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Unity Catalog Function
# Use for: UC functions accessed via MCP server
# MCP URL: {host}/api/2.0/mcp/functions/{catalog}/{schema}/{function_name}

- name: 'my_uc_function'
uc_securable:
securable_full_name: 'catalog.schema.function_name'
securable_type: 'FUNCTION'
permission: 'EXECUTE'
9 changes: 9 additions & 0 deletions .claude/skills/add-tools-adk/examples/vector-search.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Vector Search Index
# Use for: RAG applications with unstructured data
# MCP URL: {host}/api/2.0/mcp/vector-search/{catalog}/{schema}/{index_name}

- name: 'my_vector_index'
uc_securable:
securable_full_name: 'catalog.schema.index_name'
securable_type: 'TABLE'
permission: 'SELECT'
Loading