dao-ai 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dao_ai/config.py CHANGED
@@ -418,11 +418,11 @@ class SchemaModel(BaseModel, HasFullName):
418
418
  class DatabricksAppModel(IsDatabricksResource, HasFullName):
419
419
  """
420
420
  Configuration for a Databricks App resource.
421
-
421
+
422
422
  The `name` is the unique instance name of the Databricks App within the workspace.
423
- The `url` is dynamically retrieved from the workspace client by calling
423
+ The `url` is dynamically retrieved from the workspace client by calling
424
424
  `apps.get(name)` and returning the app's URL.
425
-
425
+
426
426
  Example:
427
427
  ```yaml
428
428
  resources:
@@ -431,7 +431,7 @@ class DatabricksAppModel(IsDatabricksResource, HasFullName):
431
431
  name: my-databricks-app
432
432
  ```
433
433
  """
434
-
434
+
435
435
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
436
436
  name: str
437
437
  """The unique instance name of the Databricks App in the workspace."""
@@ -440,10 +440,10 @@ class DatabricksAppModel(IsDatabricksResource, HasFullName):
440
440
  def url(self) -> str:
441
441
  """
442
442
  Retrieve the URL of the Databricks App from the workspace.
443
-
443
+
444
444
  Returns:
445
445
  The URL of the deployed Databricks App.
446
-
446
+
447
447
  Raises:
448
448
  RuntimeError: If the app is not found or URL is not available.
449
449
  """
@@ -455,7 +455,6 @@ class DatabricksAppModel(IsDatabricksResource, HasFullName):
455
455
  )
456
456
  return app.url
457
457
 
458
-
459
458
  @property
460
459
  def full_name(self) -> str:
461
460
  return self.name
@@ -761,11 +760,20 @@ class FunctionModel(IsDatabricksResource, HasFullName):
761
760
 
762
761
 
763
762
  class WarehouseModel(IsDatabricksResource):
764
- model_config = ConfigDict()
765
- name: str
763
+ model_config = ConfigDict(use_enum_values=True, extra="forbid")
764
+ name: Optional[str] = None
766
765
  description: Optional[str] = None
767
766
  warehouse_id: AnyVariable
768
767
 
768
+ _warehouse_details: Optional[GetWarehouseResponse] = PrivateAttr(default=None)
769
+
770
+ def _get_warehouse_details(self) -> GetWarehouseResponse:
771
+ if self._warehouse_details is None:
772
+ self._warehouse_details = self.workspace_client.warehouses.get(
773
+ id=value_of(self.warehouse_id)
774
+ )
775
+ return self._warehouse_details
776
+
769
777
  @property
770
778
  def api_scopes(self) -> Sequence[str]:
771
779
  return [
@@ -786,10 +794,22 @@ class WarehouseModel(IsDatabricksResource):
786
794
  self.warehouse_id = value_of(self.warehouse_id)
787
795
  return self
788
796
 
797
+ @model_validator(mode="after")
798
+ def populate_name(self) -> Self:
799
+ """Populate name from warehouse details if not provided."""
800
+ if self.warehouse_id and not self.name:
801
+ try:
802
+ warehouse_details = self._get_warehouse_details()
803
+ if warehouse_details.name:
804
+ self.name = warehouse_details.name
805
+ except Exception as e:
806
+ logger.debug(f"Could not fetch details from warehouse: {e}")
807
+ return self
808
+
789
809
 
790
810
  class GenieRoomModel(IsDatabricksResource):
791
811
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
792
- name: str
812
+ name: Optional[str] = None
793
813
  description: Optional[str] = None
794
814
  space_id: AnyVariable
795
815
 
@@ -998,15 +1018,17 @@ class GenieRoomModel(IsDatabricksResource):
998
1018
  return self
999
1019
 
1000
1020
  @model_validator(mode="after")
1001
- def update_description_from_space(self) -> Self:
1002
- """Populate description from GenieSpace if not provided."""
1003
- if not self.description:
1021
+ def populate_name_and_description(self) -> Self:
1022
+ """Populate name and description from GenieSpace if not provided."""
1023
+ if self.space_id and (not self.name or not self.description):
1004
1024
  try:
1005
1025
  space_details = self._get_space_details()
1006
- if space_details.description:
1026
+ if not self.name and space_details.title:
1027
+ self.name = space_details.title
1028
+ if not self.description and space_details.description:
1007
1029
  self.description = space_details.description
1008
1030
  except Exception as e:
1009
- logger.debug(f"Could not fetch description from Genie space: {e}")
1031
+ logger.debug(f"Could not fetch details from Genie space: {e}")
1010
1032
  return self
1011
1033
 
1012
1034
 
@@ -2007,7 +2029,7 @@ class McpFunctionModel(BaseFunctionModel, IsDatabricksResource):
2007
2029
  # DBSQL MCP server (serverless, workspace-level)
2008
2030
  if self.sql:
2009
2031
  return f"{workspace_host}/api/2.0/mcp/sql"
2010
-
2032
+
2011
2033
  # Databricks App
2012
2034
  if self.app:
2013
2035
  return self.app.url
dao_ai/tools/mcp.py CHANGED
@@ -26,9 +26,9 @@ from loguru import logger
26
26
  from mcp.types import CallToolResult, TextContent, Tool
27
27
 
28
28
  from dao_ai.config import (
29
+ IsDatabricksResource,
29
30
  McpFunctionModel,
30
31
  TransportType,
31
- value_of,
32
32
  )
33
33
 
34
34
 
@@ -143,12 +143,54 @@ def _should_include_tool(
143
143
  return True
144
144
 
145
145
 
146
+ def _get_auth_resource(function: McpFunctionModel) -> IsDatabricksResource:
147
+ """
148
+ Get the IsDatabricksResource to use for authentication.
149
+
150
+ Follows a priority hierarchy:
151
+ 1. Explicit resource with auth (app, connection, genie_room, vector_search, functions)
152
+ 2. McpFunctionModel itself (which also inherits from IsDatabricksResource)
153
+
154
+ Returns the resource whose workspace_client should be used for authentication.
155
+ """
156
+ # Check each possible resource source in priority order
157
+ # These resources may have their own auth configured
158
+ if function.app:
159
+ return function.app
160
+ if function.connection:
161
+ return function.connection
162
+ if function.genie_room:
163
+ return function.genie_room
164
+ if function.vector_search:
165
+ return function.vector_search
166
+ if function.functions:
167
+ # SchemaModel doesn't have auth - fall through to McpFunctionModel
168
+ pass
169
+
170
+ # Fall back to McpFunctionModel itself (it inherits from IsDatabricksResource)
171
+ return function
172
+
173
+
146
174
  def _build_connection_config(
147
175
  function: McpFunctionModel,
148
176
  ) -> dict[str, Any]:
149
177
  """
150
178
  Build the connection configuration dictionary for MultiServerMCPClient.
151
179
 
180
+ Authentication Strategy:
181
+ -----------------------
182
+ For HTTP transport, authentication is handled consistently using
183
+ DatabricksOAuthClientProvider with the workspace_client from the appropriate
184
+ IsDatabricksResource. The auth resource is selected in this priority:
185
+
186
+ 1. Nested resource (app, connection, genie_room, vector_search) if it has auth
187
+ 2. McpFunctionModel itself (inherits from IsDatabricksResource)
188
+
189
+ This approach ensures:
190
+ - Consistent auth handling across all MCP sources
191
+ - Automatic token refresh for long-running connections
192
+ - Support for OBO, service principal, PAT, and ambient auth
193
+
152
194
  Args:
153
195
  function: The MCP function model configuration.
154
196
 
@@ -162,52 +204,30 @@ def _build_connection_config(
162
204
  "transport": function.transport.value,
163
205
  }
164
206
 
165
- # For HTTP transport with UC Connection, use DatabricksOAuthClientProvider
166
- if function.connection:
167
- from databricks_mcp import DatabricksOAuthClientProvider
207
+ # For HTTP transport, use DatabricksOAuthClientProvider with unified auth
208
+ from databricks_mcp import DatabricksOAuthClientProvider
168
209
 
169
- workspace_client = function.connection.workspace_client
170
- auth_provider = DatabricksOAuthClientProvider(workspace_client)
210
+ # Get the resource to use for authentication
211
+ auth_resource = _get_auth_resource(function)
171
212
 
172
- logger.trace(
173
- "Using DatabricksOAuthClientProvider for authentication",
174
- connection_name=function.connection.name,
175
- )
176
-
177
- return {
178
- "url": function.mcp_url,
179
- "transport": "http",
180
- "auth": auth_provider,
181
- }
182
-
183
- # For HTTP transport with headers-based authentication
184
- headers: dict[str, str] = {
185
- key: str(value_of(val)) for key, val in function.headers.items()
186
- }
213
+ # Get workspace client from the auth resource
214
+ workspace_client = auth_resource.workspace_client
215
+ auth_provider = DatabricksOAuthClientProvider(workspace_client)
187
216
 
188
- if "Authorization" not in headers:
189
- logger.trace("Generating fresh authentication token")
190
-
191
- from dao_ai.providers.databricks import DatabricksProvider
192
-
193
- try:
194
- provider = DatabricksProvider(
195
- workspace_host=value_of(function.workspace_host),
196
- client_id=value_of(function.client_id),
197
- client_secret=value_of(function.client_secret),
198
- pat=value_of(function.pat),
199
- )
200
- headers["Authorization"] = f"Bearer {provider.create_token()}"
201
- logger.trace("Generated fresh authentication token")
202
- except Exception as e:
203
- logger.error("Failed to create fresh token", error=str(e))
204
- else:
205
- logger.trace("Using existing authentication token")
217
+ # Log which resource is providing auth
218
+ resource_name = (
219
+ getattr(auth_resource, "name", None) or auth_resource.__class__.__name__
220
+ )
221
+ logger.trace(
222
+ "Using DatabricksOAuthClientProvider for authentication",
223
+ auth_resource=resource_name,
224
+ resource_type=auth_resource.__class__.__name__,
225
+ )
206
226
 
207
227
  return {
208
228
  "url": function.mcp_url,
209
229
  "transport": "http",
210
- "headers": headers,
230
+ "auth": auth_provider,
211
231
  }
212
232
 
213
233
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dao-ai
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: DAO AI: A modular, multi-agent orchestration framework for complex AI workflows. Supports agent handoff, tool integration, and dynamic configuration via YAML.
5
5
  Project-URL: Homepage, https://github.com/natefleming/dao-ai
6
6
  Project-URL: Documentation, https://natefleming.github.io/dao-ai
@@ -2,7 +2,7 @@ dao_ai/__init__.py,sha256=18P98ExEgUaJ1Byw440Ct1ty59v6nxyWtc5S6Uq2m9Q,1062
2
2
  dao_ai/agent_as_code.py,sha256=xIlLDpPVfmDVzLvbdY_V_CrC4Jvj2ItCWJ-NzdrszTo,538
3
3
  dao_ai/catalog.py,sha256=sPZpHTD3lPx4EZUtIWeQV7VQM89WJ6YH__wluk1v2lE,4947
4
4
  dao_ai/cli.py,sha256=7LGrVDRgSBpznr8c8EksAhzPW_8NJ9h4St3DSpx-0z4,48196
5
- dao_ai/config.py,sha256=GY-n2PtPg4pYtO46KYpHFIGkNkCDVoPA5S7sKLjWpVc,124699
5
+ dao_ai/config.py,sha256=OSAsqb2Rxn3ghnM5Nq7-wh13DizHzWI_6cxuRuT4_j8,125773
6
6
  dao_ai/graph.py,sha256=1-uQlo7iXZQTT3uU8aYu0N5rnhw5_g_2YLwVsAs6M-U,1119
7
7
  dao_ai/logging.py,sha256=lYy4BmucCHvwW7aI3YQkQXKJtMvtTnPDu9Hnd7_O4oc,1556
8
8
  dao_ai/messages.py,sha256=4ZBzO4iFdktGSLrmhHzFjzMIt2tpaL-aQLHOQJysGnY,6959
@@ -55,7 +55,7 @@ dao_ai/tools/agent.py,sha256=plIWALywRjaDSnot13nYehBsrHRpBUpsVZakoGeajOE,1858
55
55
  dao_ai/tools/core.py,sha256=bRIN3BZhRQX8-Kpu3HPomliodyskCqjxynQmYbk6Vjs,3783
56
56
  dao_ai/tools/email.py,sha256=A3TsCoQgJR7UUWR0g45OPRGDpVoYwctFs1MOZMTt_d4,7389
57
57
  dao_ai/tools/genie.py,sha256=4e_5MeAe7kDzHbYeXuNPFbY5z8ci3ouj8l5254CZ2lA,8874
58
- dao_ai/tools/mcp.py,sha256=0OfP4b4skcjeF2rzkOLYqd65ti1Mj55N_l8VoQlH9qo,17818
58
+ dao_ai/tools/mcp.py,sha256=ZNalYo2atZECatZjMT8w4mHEsaUZJQ_fsCjia7px1nc,18689
59
59
  dao_ai/tools/memory.py,sha256=lwObKimAand22Nq3Y63tsv-AXQ5SXUigN9PqRjoWKes,1836
60
60
  dao_ai/tools/python.py,sha256=jWFnZPni2sCdtd8D1CqXnZIPHnWkdK27bCJnBXpzhvo,1879
61
61
  dao_ai/tools/search.py,sha256=cJ3D9FKr1GAR6xz55dLtRkjtQsI0WRueGt9TPDFpOxc,433
@@ -64,8 +64,8 @@ dao_ai/tools/sql.py,sha256=tKd1gjpLuKdQDyfmyYYtMiNRHDW6MGRbdEVaeqyB8Ok,7632
64
64
  dao_ai/tools/time.py,sha256=tufJniwivq29y0LIffbgeBTIDE6VgrLpmVf8Qr90qjw,9224
65
65
  dao_ai/tools/unity_catalog.py,sha256=AjQfW7bvV8NurqDLIyntYRv2eJuTwNdbvex1L5CRjOk,15534
66
66
  dao_ai/tools/vector_search.py,sha256=oe2uBwl2TfeJIXPpwiS6Rmz7wcHczSxNyqS9P3hE6co,14542
67
- dao_ai-0.1.7.dist-info/METADATA,sha256=jzaENv6Ic9S-uds3qTC4Pu7chwzYG8y0zvTBni4VCW8,16685
68
- dao_ai-0.1.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
69
- dao_ai-0.1.7.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
70
- dao_ai-0.1.7.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
71
- dao_ai-0.1.7.dist-info/RECORD,,
67
+ dao_ai-0.1.8.dist-info/METADATA,sha256=kbbCJVZAI-U3czxwWr9z36m14PQk8poQdzOB_6RRLII,16685
68
+ dao_ai-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
69
+ dao_ai-0.1.8.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
70
+ dao_ai-0.1.8.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
71
+ dao_ai-0.1.8.dist-info/RECORD,,
File without changes