-
Notifications
You must be signed in to change notification settings - Fork 228
Add direct-mode bundle support for AI Gateway mcp_service #6633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
decf905
f642a9b
ac880f9
acfe16a
30cd894
61b4f2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| * Add bundle support for the AI Gateway `mcp_service` resource (direct engine). ([#6633](https://github.com/databricks/cli/pull/6633)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| bundle: | ||
| name: test-bundle | ||
|
|
||
| resources: | ||
| mcp_services: | ||
| mcp1: | ||
| parent: schemas/main.myschema | ||
| mcp_service_id: mysvc | ||
| comment: bound service |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
|
|
||
| >>> [CLI] bundle deployment bind mcp1 main.myschema.mysvc --auto-approve | ||
| Successfully bound mcp_service with an id 'main.myschema.mysvc' | ||
| Run 'bundle deploy' to deploy changes to your workspace | ||
|
|
||
| >>> [CLI] bundle summary | ||
| Name: test-bundle | ||
| Target: default | ||
| Workspace: | ||
| User: [USERNAME] | ||
| Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default | ||
| Resources: | ||
| MCP services: | ||
| mcp1: | ||
| Name: mysvc | ||
| URL: [DATABRICKS_URL]/explore/data/mcp-services/main/myschema/mysvc?w=[NUMID] | ||
|
|
||
| >>> [CLI] bundle deployment unbind mcp1 | ||
|
|
||
| >>> [CLI] bundle summary | ||
| Name: test-bundle | ||
| Target: default | ||
| Workspace: | ||
| User: [USERNAME] | ||
| Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default | ||
| Resources: | ||
| MCP services: | ||
| mcp1: | ||
| Name: mysvc | ||
| URL: (not deployed) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| trace $CLI bundle deployment bind mcp1 main.myschema.mysvc --auto-approve | ||
| trace $CLI bundle summary | ||
|
|
||
| trace $CLI bundle deployment unbind mcp1 | ||
| trace $CLI bundle summary |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # AI Gateway securables are direct-engine only. | ||
| EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] | ||
|
|
||
| Cloud = false | ||
|
|
||
| Ignore = [ | ||
| ".databricks", | ||
| ] | ||
|
|
||
| # The bind flow issues a GET to confirm the remote resource exists before binding. | ||
| [[Server]] | ||
| Pattern = "GET /api/2.1/unity-catalog/mcp-services/{name}" | ||
| Response.Body = ''' | ||
| { | ||
| "name": "mcp-services/main.myschema.mysvc", | ||
| "comment": "bound service" | ||
| } | ||
| ''' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| bundle: | ||
| name: test-bundle-$UNIQUE_NAME | ||
|
|
||
| resources: | ||
| mcp_services: | ||
| foo: | ||
| parent: schemas/main.default | ||
| mcp_service_id: test-mcp-service-$UNIQUE_NAME | ||
| comment: test mcp service |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| bundle: | ||
| name: my_project | ||
|
|
||
| sync: {paths: []} # don't need to copy files | ||
|
|
||
| python: | ||
| resources: | ||
| - "resources:load_resources" | ||
| mutators: | ||
| - "mutators:update_mcp_service" | ||
|
|
||
| resources: | ||
| mcp_services: | ||
| my_mcp_service_1: | ||
| parent: "schemas/main.default" | ||
| mcp_service_id: "my_mcp_service_1" | ||
| comment: "My MCP service" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from dataclasses import replace | ||
|
|
||
| from databricks.bundles.mcp_services import McpService | ||
| from databricks.bundles.core import mcp_service_mutator | ||
|
|
||
|
|
||
| @mcp_service_mutator | ||
| def update_mcp_service(mcp_service: McpService) -> McpService: | ||
| assert isinstance(mcp_service.comment, str) | ||
|
|
||
| return replace(mcp_service, comment=f"{mcp_service.comment} (updated)") |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
|
|
||
| >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json | ||
| { | ||
| "experimental": { | ||
| "python": { | ||
| "mutators": [ | ||
| "mutators:update_mcp_service" | ||
| ], | ||
| "resources": [ | ||
| "resources:load_resources" | ||
| ] | ||
| } | ||
| }, | ||
| "resources": { | ||
| "mcp_services": { | ||
| "my_mcp_service_1": { | ||
| "comment": "My MCP service (updated)", | ||
| "mcp_service_id": "my_mcp_service_1", | ||
| "parent": "schemas/main.default" | ||
| }, | ||
| "my_mcp_service_2": { | ||
| "comment": "My MCP service (2) (updated)", | ||
| "mcp_service_id": "my_mcp_service_2", | ||
| "parent": "schemas/main.default" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from databricks.bundles.core import Resources | ||
|
|
||
|
|
||
| def load_resources() -> Resources: | ||
| resources = Resources() | ||
|
|
||
| resources.add_mcp_service( | ||
| "my_mcp_service_2", | ||
| { | ||
| "parent": "schemas/main.default", | ||
| "mcp_service_id": "my_mcp_service_2", | ||
| "comment": "My MCP service (2)", | ||
| }, | ||
| ) | ||
|
|
||
| return resources |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|
|
||
| trace uv run $UV_ARGS -q $CLI bundle validate --output json | \ | ||
| jq "pick(.experimental.python, .resources)" | ||
|
|
||
| rm -fr .databricks __pycache__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Cloud = false # tests don't interact with APIs | ||
|
|
||
| # mcp_services are only supported in the current version of the wheel | ||
| EnvMatrix.PYDAB_VERSION = ["current"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| bundle: | ||
| name: test-bundle | ||
|
|
||
| resources: | ||
| mcp_services: | ||
| mcp1: | ||
| parent: schemas/main.myschema | ||
| mcp_service_id: myservice | ||
| comment: COMMENT1 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
|
|
||
| === Initial summary before deploy | ||
| >>> [CLI] bundle summary -o json | ||
| { | ||
| "comment": "COMMENT1", | ||
| "mcp_service_id": "myservice", | ||
| "modified_status": "created", | ||
| "parent": "schemas/main.myschema" | ||
| } | ||
|
|
||
| === Verify it does not exist yet | ||
| >>> musterr [CLI] ai-gateway get-mcp-service mcp-services/main.myschema.myservice | ||
| Error: Resource catalog.McpService not found: main.myschema.myservice | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Created mcp_services.mcp1 | ||
| Files: 3 uploaded, 0 deleted | ||
| Resources: 1 created, 0 changed, 0 deleted, 0 unchanged | ||
|
|
||
| >>> print_requests.py //unity | ||
| { | ||
| "method": "POST", | ||
| "path": "/api/2.1/unity-catalog/mcp-services", | ||
| "q": { | ||
| "mcp_service_id": "myservice", | ||
| "parent": "schemas/main.myschema" | ||
| }, | ||
| "body": { | ||
| "comment": "COMMENT1" | ||
| } | ||
| } | ||
|
|
||
| === Summary should show the id and the Catalog Explorer url | ||
| >>> [CLI] bundle summary -o json | ||
| { | ||
| "id": "main.myschema.myservice", | ||
| "url": "[DATABRICKS_URL]/explore/data/mcp-services/main/myschema/myservice?w=[NUMID]" | ||
| } | ||
|
|
||
| === Verify deployment | ||
| >>> [CLI] ai-gateway get-mcp-service mcp-services/main.myschema.myservice | ||
| { | ||
| "name": "mcp-services/main.myschema.myservice", | ||
| "comment": "COMMENT1" | ||
| } | ||
|
|
||
| === Update comment (should update in place, not recreate) | ||
| >>> update_file.py databricks.yml COMMENT1 COMMENT2 | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... | ||
| Updated mcp_services.mcp1 | ||
| Files: 1 uploaded, 0 deleted | ||
| Resources: 0 created, 1 changed, 0 deleted, 0 unchanged | ||
|
|
||
| >>> print_requests.py //unity | ||
| { | ||
| "method": "PATCH", | ||
| "path": "/api/2.1/unity-catalog/mcp-services/main.myschema.myservice", | ||
| "q": { | ||
| "update_mask": "*" | ||
| }, | ||
| "body": { | ||
| "comment": "COMMENT2" | ||
| } | ||
| } | ||
|
|
||
| >>> [CLI] ai-gateway get-mcp-service mcp-services/main.myschema.myservice | ||
| "COMMENT2" | ||
|
|
||
| === Change an immutable field (should plan a recreate) | ||
| >>> update_file.py databricks.yml myservice myservice-renamed | ||
|
|
||
| >>> [CLI] bundle plan | ||
| recreate mcp_services.mcp1 | ||
|
|
||
| Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged | ||
|
|
||
| >>> [CLI] bundle destroy --auto-approve | ||
| The following resources will be deleted: | ||
| delete resources.mcp_services.mcp1 | ||
|
|
||
| All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default | ||
|
|
||
| Destroy: 1 deleted | ||
|
|
||
| >>> print_requests.py //unity | ||
| { | ||
| "method": "DELETE", | ||
| "path": "/api/2.1/unity-catalog/mcp-services/main.myschema.myservice" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| title "Initial summary before deploy" | ||
| trace $CLI bundle summary -o json | jq .resources.mcp_services.mcp1 | ||
|
|
||
| title "Verify it does not exist yet" | ||
| trace musterr $CLI ai-gateway get-mcp-service mcp-services/main.myschema.myservice | ||
|
|
||
| trace $CLI bundle deploy | ||
| trace print_requests.py //unity | ||
|
|
||
| title "Summary should show the id and the Catalog Explorer url" | ||
| trace $CLI bundle summary -o json | jq ".resources.mcp_services.mcp1 | {id, url}" | ||
|
|
||
| title "Verify deployment" | ||
| trace $CLI ai-gateway get-mcp-service mcp-services/main.myschema.myservice | jq '{name, comment}' | ||
|
|
||
| title "Update comment (should update in place, not recreate)" | ||
| trace update_file.py databricks.yml COMMENT1 COMMENT2 | ||
| trace $CLI bundle deploy | ||
| trace print_requests.py //unity | ||
| trace $CLI ai-gateway get-mcp-service mcp-services/main.myschema.myservice | jq .comment | ||
|
|
||
| title "Change an immutable field (should plan a recreate)" | ||
| trace update_file.py databricks.yml myservice myservice-renamed | ||
| trace $CLI bundle plan | ||
|
|
||
| trace $CLI bundle destroy --auto-approve | ||
| trace print_requests.py //unity | ||
|
|
||
| rm -f out.requests.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Local only: this test inspects the recorded request stream (print_requests), | ||
| # which has no equivalent against a real workspace. | ||
| Cloud = false | ||
|
|
||
| Ignore = [ | ||
| ".databricks", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| bundle: | ||
| name: deploy-mcp-service-test-$UNIQUE_NAME | ||
|
|
||
| resources: | ||
| mcp_services: | ||
| mcp: | ||
| parent: schemas/main.default | ||
| mcp_service_id: test_mcp_$UNIQUE_NAME | ||
| comment: "Points at a UC connection hosting an MCP server" | ||
| config: | ||
| source_connection: | ||
| name: connections/main.default.mcp_conn_$UNIQUE_NAME |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
|
|
||
| >>> [CLI] bundle deploy | ||
| Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-mcp-service-test-[UNIQUE_NAME]/default/files... | ||
| Created mcp_services.mcp | ||
| Files: 4 uploaded, 0 deleted | ||
| Resources: 1 created, 0 changed, 0 deleted, 0 unchanged | ||
|
|
||
| >>> [CLI] ai-gateway get-mcp-service mcp-services/main.default.test_mcp_[UNIQUE_NAME] | ||
| { | ||
| "name": "mcp-services/main.default.test_mcp_[UNIQUE_NAME]", | ||
| "comment": "Points at a UC connection hosting an MCP server" | ||
| } | ||
|
|
||
| >>> [CLI] bundle plan | ||
| Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged | ||
|
|
||
| >>> [CLI] bundle destroy --auto-approve | ||
| The following resources will be deleted: | ||
| delete resources.mcp_services.mcp | ||
|
|
||
| All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-mcp-service-test-[UNIQUE_NAME]/default | ||
|
|
||
| Destroy: 1 deleted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
printing the whole API response here is going to cause the golden files to drift over time as more fields get added -> let's just assert the fields that you want to ensure the resource got deployed as expected.
I think this is also in
model_servicesso if you could clean that up as a follow-up that'd be great :-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are applying filtering to { "name", "comment" } through jq, so I think we should be good. Model service is already clean, mcp and mps are too now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, somehow missed that you use
jqalready