datarobot-genai 0.2.28__tar.gz → 0.2.30__tar.gz

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.
Files changed (121) hide show
  1. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/PKG-INFO +1 -1
  2. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/pyproject.toml +1 -1
  3. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +96 -1
  4. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +86 -9
  5. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/datarobot_llm_clients.py +90 -54
  6. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/datarobot_mcp_client.py +47 -15
  7. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/.gitignore +0 -0
  8. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/AUTHORS +0 -0
  9. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/LICENSE +0 -0
  10. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/README.md +0 -0
  11. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/__init__.py +0 -0
  12. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/__init__.py +0 -0
  13. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/agents/__init__.py +0 -0
  14. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/agents/base.py +0 -0
  15. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/chat/__init__.py +0 -0
  16. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/chat/auth.py +0 -0
  17. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/chat/client.py +0 -0
  18. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/chat/responses.py +0 -0
  19. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/cli/__init__.py +0 -0
  20. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  21. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  22. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/custom_model.py +0 -0
  23. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  24. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/mcp/common.py +0 -0
  25. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  26. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/utils/__init__.py +0 -0
  27. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/utils/auth.py +0 -0
  28. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/core/utils/urls.py +0 -0
  29. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/crewai/__init__.py +0 -0
  30. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/crewai/agent.py +0 -0
  31. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/crewai/base.py +0 -0
  32. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/crewai/events.py +0 -0
  33. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/crewai/mcp.py +0 -0
  34. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/__init__.py +0 -0
  35. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  36. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  37. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  38. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/config.py +0 -0
  39. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  40. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  41. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  42. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  43. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  44. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  45. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  46. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  47. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  48. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  49. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  50. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  51. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  52. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  53. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  54. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  55. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  56. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  57. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  58. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  59. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  60. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  61. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  62. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  63. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  64. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  65. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  66. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  67. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  68. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  69. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  70. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  71. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  72. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  73. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/tool_config.py +0 -0
  74. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  75. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  76. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/server.py +0 -0
  77. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  78. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  79. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  80. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  81. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  82. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +0 -0
  83. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +0 -0
  84. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  85. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  86. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  87. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  88. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  89. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  90. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  91. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/microsoft_graph.py +0 -0
  92. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  93. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  94. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  95. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  96. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  97. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  98. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +0 -0
  99. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/microsoft_graph/tools.py +0 -0
  100. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  101. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  102. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  103. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  104. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  105. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  106. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  107. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  108. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  109. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/langgraph/__init__.py +0 -0
  110. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/langgraph/agent.py +0 -0
  111. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/langgraph/mcp.py +0 -0
  112. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/llama_index/__init__.py +0 -0
  113. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/llama_index/agent.py +0 -0
  114. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/llama_index/base.py +0 -0
  115. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/llama_index/mcp.py +0 -0
  116. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/__init__.py +0 -0
  117. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/agent.py +0 -0
  118. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  119. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  120. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/nat/helpers.py +0 -0
  121. {datarobot_genai-0.2.28 → datarobot_genai-0.2.30}/src/datarobot_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.28
3
+ Version: 0.2.30
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.2.28"
7
+ version = "0.2.30"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -33,7 +33,17 @@ from datarobot_genai.drmcp.core.auth import get_access_token
33
33
 
34
34
  logger = logging.getLogger(__name__)
35
35
 
36
- SUPPORTED_FIELDS = {"id", "name", "size", "mimeType", "webViewLink", "createdTime", "modifiedTime"}
36
+ SUPPORTED_FIELDS = {
37
+ "id",
38
+ "name",
39
+ "size",
40
+ "mimeType",
41
+ "webViewLink",
42
+ "createdTime",
43
+ "modifiedTime",
44
+ "starred",
45
+ "trashed",
46
+ }
37
47
  SUPPORTED_FIELDS_STR = ",".join(SUPPORTED_FIELDS)
38
48
  DEFAULT_FIELDS = f"nextPageToken,files({SUPPORTED_FIELDS_STR})"
39
49
  GOOGLE_DRIVE_FOLDER_MIME = "application/vnd.google-apps.folder"
@@ -119,6 +129,8 @@ class GoogleDriveFile(BaseModel):
119
129
  web_view_link: Annotated[str | None, Field(alias="webViewLink")] = None
120
130
  created_time: Annotated[str | None, Field(alias="createdTime")] = None
121
131
  modified_time: Annotated[str | None, Field(alias="modifiedTime")] = None
132
+ starred: bool | None = None
133
+ trashed: bool | None = None
122
134
 
123
135
  model_config = ConfigDict(populate_by_name=True)
124
136
 
@@ -133,8 +145,31 @@ class GoogleDriveFile(BaseModel):
133
145
  web_view_link=data.get("webViewLink"),
134
146
  created_time=data.get("createdTime"),
135
147
  modified_time=data.get("modifiedTime"),
148
+ starred=data.get("starred"),
149
+ trashed=data.get("trashed"),
136
150
  )
137
151
 
152
+ def as_flat_dict(self) -> dict[str, Any]:
153
+ """Return a flat dictionary representation of the file."""
154
+ result: dict[str, Any] = {
155
+ "id": self.id,
156
+ "name": self.name,
157
+ "mimeType": self.mime_type,
158
+ }
159
+ if self.size is not None:
160
+ result["size"] = self.size
161
+ if self.web_view_link is not None:
162
+ result["webViewLink"] = self.web_view_link
163
+ if self.created_time is not None:
164
+ result["createdTime"] = self.created_time
165
+ if self.modified_time is not None:
166
+ result["modifiedTime"] = self.modified_time
167
+ if self.starred is not None:
168
+ result["starred"] = self.starred
169
+ if self.trashed is not None:
170
+ result["trashed"] = self.trashed
171
+ return result
172
+
138
173
 
139
174
  class PaginatedResult(BaseModel):
140
175
  """Result of a paginated API call."""
@@ -440,6 +475,66 @@ class GoogleDriveClient:
440
475
  response.raise_for_status()
441
476
  return GoogleDriveFile.from_api_response(response.json())
442
477
 
478
+ async def update_file_metadata(
479
+ self,
480
+ file_id: str,
481
+ new_name: str | None = None,
482
+ starred: bool | None = None,
483
+ trashed: bool | None = None,
484
+ ) -> GoogleDriveFile:
485
+ """Update file metadata in Google Drive.
486
+
487
+ Args:
488
+ file_id: The ID of the file to update.
489
+ new_name: A new name to rename the file. Must not be empty or whitespace.
490
+ starred: Set to True to star the file or False to unstar it.
491
+ trashed: Set to True to trash the file or False to restore it.
492
+
493
+ Returns
494
+ -------
495
+ GoogleDriveFile with updated metadata.
496
+
497
+ Raises
498
+ ------
499
+ GoogleDriveError: If no update fields are provided, file is not found,
500
+ access is denied, or the request is invalid.
501
+ """
502
+ if new_name is None and starred is None and trashed is None:
503
+ raise GoogleDriveError(
504
+ "At least one of new_name, starred, or trashed must be provided."
505
+ )
506
+
507
+ if new_name is not None and not new_name.strip():
508
+ raise GoogleDriveError("new_name cannot be empty or whitespace.")
509
+
510
+ body: dict[str, Any] = {}
511
+ if new_name is not None:
512
+ body["name"] = new_name
513
+ if starred is not None:
514
+ body["starred"] = starred
515
+ if trashed is not None:
516
+ body["trashed"] = trashed
517
+
518
+ response = await self._client.patch(
519
+ f"/{file_id}",
520
+ json=body,
521
+ params={"fields": SUPPORTED_FIELDS_STR, "supportsAllDrives": "true"},
522
+ )
523
+
524
+ if response.status_code == 404:
525
+ raise GoogleDriveError(f"File with ID '{file_id}' not found.")
526
+ if response.status_code == 403:
527
+ raise GoogleDriveError(
528
+ f"Permission denied: you don't have permission to update file '{file_id}'."
529
+ )
530
+ if response.status_code == 400:
531
+ raise GoogleDriveError("Bad request: invalid parameters for file update.")
532
+ if response.status_code == 429:
533
+ raise GoogleDriveError("Rate limit exceeded. Please try again later.")
534
+
535
+ response.raise_for_status()
536
+ return GoogleDriveFile.from_api_response(response.json())
537
+
443
538
  async def _export_workspace_file(self, file_id: str, export_mime_type: str) -> str:
444
539
  """Export a Google Workspace file to the specified format.
445
540
 
@@ -164,7 +164,6 @@ async def gdrive_read_content(
164
164
  f"An unexpected error occurred while reading Google Drive file content: {str(e)}"
165
165
  )
166
166
 
167
- # Provide helpful context about the conversion
168
167
  export_info = ""
169
168
  if file_content.was_exported:
170
169
  export_info = f" (exported from {file_content.original_mime_type})"
@@ -252,7 +251,6 @@ async def gdrive_create_file(
252
251
  logger.error(f"Unexpected error creating Google Drive file: {e}")
253
252
  raise ToolError(f"An unexpected error occurred while creating Google Drive file: {str(e)}")
254
253
 
255
- # Build response message
256
254
  file_type = "folder" if mime_type == GOOGLE_DRIVE_FOLDER_MIME else "file"
257
255
  content_info = ""
258
256
  if initial_content and mime_type != GOOGLE_DRIVE_FOLDER_MIME:
@@ -260,11 +258,90 @@ async def gdrive_create_file(
260
258
 
261
259
  return ToolResult(
262
260
  content=f"Successfully created {file_type} '{created_file.name}'{content_info}.",
263
- structured_content={
264
- "id": created_file.id,
265
- "name": created_file.name,
266
- "mimeType": created_file.mime_type,
267
- "webViewLink": created_file.web_view_link,
268
- "createdTime": created_file.created_time,
269
- },
261
+ structured_content=created_file.as_flat_dict(),
262
+ )
263
+
264
+
265
+ @dr_mcp_tool(
266
+ tags={"google", "gdrive", "update", "metadata", "rename", "star", "trash"}, enabled=False
267
+ )
268
+ async def gdrive_update_metadata(
269
+ *,
270
+ file_id: Annotated[str, "The ID of the file or folder to update."],
271
+ new_name: Annotated[str | None, "A new name to rename the file."] = None,
272
+ starred: Annotated[bool | None, "Set to True to star the file or False to unstar it."] = None,
273
+ trash: Annotated[bool | None, "Set to True to trash the file or False to restore it."] = None,
274
+ ) -> ToolResult:
275
+ """
276
+ Update non-content metadata fields of a Google Drive file or folder.
277
+
278
+ This tool allows you to:
279
+ - Rename files and folders by setting new_name
280
+ - Star or unstar files (per-user preference) with starred
281
+ - Move files to trash or restore them with trash
282
+
283
+ Usage:
284
+ - Rename: gdrive_update_metadata(file_id="1ABC...", new_name="New Name.txt")
285
+ - Star: gdrive_update_metadata(file_id="1ABC...", starred=True)
286
+ - Unstar: gdrive_update_metadata(file_id="1ABC...", starred=False)
287
+ - Trash: gdrive_update_metadata(file_id="1ABC...", trash=True)
288
+ - Restore: gdrive_update_metadata(file_id="1ABC...", trash=False)
289
+ - Multiple: gdrive_update_metadata(file_id="1ABC...", new_name="New.txt", starred=True)
290
+
291
+ Note:
292
+ - At least one of new_name, starred, or trash must be provided.
293
+ - Starring is per-user: starring a shared file only affects your view.
294
+ - Trashing a folder trashes all contents recursively.
295
+ - Trashing requires permissions (owner for My Drive, organizer for Shared Drives).
296
+ """
297
+ if not file_id or not file_id.strip():
298
+ raise ToolError("Argument validation error: 'file_id' cannot be empty.")
299
+
300
+ if new_name is None and starred is None and trash is None:
301
+ raise ToolError(
302
+ "Argument validation error: at least one of 'new_name', 'starred', or 'trash' "
303
+ "must be provided."
304
+ )
305
+
306
+ if new_name is not None and not new_name.strip():
307
+ raise ToolError("Argument validation error: 'new_name' cannot be empty or whitespace.")
308
+
309
+ access_token = await get_gdrive_access_token()
310
+ if isinstance(access_token, ToolError):
311
+ raise access_token
312
+
313
+ try:
314
+ async with GoogleDriveClient(access_token) as client:
315
+ updated_file = await client.update_file_metadata(
316
+ file_id=file_id,
317
+ new_name=new_name,
318
+ starred=starred,
319
+ trashed=trash,
320
+ )
321
+ except GoogleDriveError as e:
322
+ logger.error(f"Google Drive error updating file metadata: {e}")
323
+ raise ToolError(str(e))
324
+ except Exception as e:
325
+ logger.error(f"Unexpected error updating Google Drive file metadata: {e}")
326
+ raise ToolError(
327
+ f"An unexpected error occurred while updating Google Drive file metadata: {str(e)}"
328
+ )
329
+
330
+ changes: list[str] = []
331
+ if new_name is not None:
332
+ changes.append(f"renamed to '{new_name}'")
333
+ if starred is True:
334
+ changes.append("starred")
335
+ elif starred is False:
336
+ changes.append("unstarred")
337
+ if trash is True:
338
+ changes.append("moved to trash")
339
+ elif trash is False:
340
+ changes.append("restored from trash")
341
+
342
+ changes_description = ", ".join(changes)
343
+
344
+ return ToolResult(
345
+ content=f"Successfully updated file '{updated_file.name}': {changes_description}.",
346
+ structured_content=updated_file.as_flat_dict(),
270
347
  )
@@ -12,22 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from collections.abc import AsyncGenerator
18
+ from typing import TYPE_CHECKING
16
19
  from typing import Any
17
20
  from typing import TypeVar
18
21
 
19
- from crewai import LLM
20
- from langchain_openai import ChatOpenAI
21
- from llama_index.core.base.llms.types import LLMMetadata
22
- from llama_index.llms.litellm import LiteLLM
23
22
  from nat.builder.builder import Builder
24
23
  from nat.builder.framework_enum import LLMFrameworkEnum
25
24
  from nat.cli.register_workflow import register_llm_client
26
25
  from nat.data_models.llm import LLMBaseConfig
27
26
  from nat.data_models.retry_mixin import RetryMixin
28
- from nat.plugins.langchain.llm import (
29
- _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
30
- )
31
27
  from nat.utils.exception_handlers.automatic_retries import patch_with_retry
32
28
 
33
29
  from ..nat.datarobot_llm_providers import DataRobotLLMComponentModelConfig
@@ -35,6 +31,11 @@ from ..nat.datarobot_llm_providers import DataRobotLLMDeploymentModelConfig
35
31
  from ..nat.datarobot_llm_providers import DataRobotLLMGatewayModelConfig
36
32
  from ..nat.datarobot_llm_providers import DataRobotNIMModelConfig
37
33
 
34
+ if TYPE_CHECKING:
35
+ from crewai import LLM
36
+ from langchain_openai import ChatOpenAI
37
+ from llama_index.llms.litellm import LiteLLM
38
+
38
39
  ModelType = TypeVar("ModelType")
39
40
 
40
41
 
@@ -50,42 +51,53 @@ def _patch_llm_based_on_config(client: ModelType, llm_config: LLMBaseConfig) ->
50
51
  return client
51
52
 
52
53
 
53
- class DataRobotChatOpenAI(ChatOpenAI):
54
- def _get_request_payload(
55
- self,
56
- *args: Any,
57
- **kwargs: Any,
58
- ) -> dict:
59
- # We need to default to include_usage=True for streaming but we get 400 response
60
- # if stream_options is present for a non-streaming call.
61
- payload = super()._get_request_payload(*args, **kwargs)
62
- if not payload.get("stream"):
63
- payload.pop("stream_options", None)
64
- return payload
65
-
66
-
67
- class DataRobotLiteLLM(LiteLLM): # type: ignore[misc]
68
- """DataRobotLiteLLM is a small LiteLLM wrapper class that makes all LiteLLM endpoints
69
- compatible with the LlamaIndex library.
70
- """
71
-
72
- @property
73
- def metadata(self) -> LLMMetadata:
74
- """Returns the metadata for the LLM.
75
-
76
- This is required to enable the is_chat_model and is_function_calling_model, which are
77
- mandatory for LlamaIndex agents. By default, LlamaIndex assumes these are false unless each
78
- individual model config in LiteLLM explicitly sets them to true. To use custom LLM
79
- endpoints with LlamaIndex agents, you must override this method to return the appropriate
80
- metadata.
54
+ def _create_datarobot_chat_openai(config: dict[str, Any]) -> Any:
55
+ from langchain_openai import ChatOpenAI # noqa: PLC0415
56
+
57
+ class DataRobotChatOpenAI(ChatOpenAI):
58
+ def _get_request_payload( # type: ignore[override]
59
+ self,
60
+ *args: Any,
61
+ **kwargs: Any,
62
+ ) -> dict:
63
+ # We need to default to include_usage=True for streaming but we get 400 response
64
+ # if stream_options is present for a non-streaming call.
65
+ payload = super()._get_request_payload(*args, **kwargs)
66
+ if not payload.get("stream"):
67
+ payload.pop("stream_options", None)
68
+ return payload
69
+
70
+ return DataRobotChatOpenAI(**config)
71
+
72
+
73
+ def _create_datarobot_litellm(config: dict[str, Any]) -> Any:
74
+ from llama_index.core.base.llms.types import LLMMetadata # noqa: PLC0415
75
+ from llama_index.llms.litellm import LiteLLM # noqa: PLC0415
76
+
77
+ class DataRobotLiteLLM(LiteLLM): # type: ignore[misc]
78
+ """DataRobotLiteLLM is a small LiteLLM wrapper class that makes all LiteLLM endpoints
79
+ compatible with the LlamaIndex library.
81
80
  """
82
- return LLMMetadata(
83
- context_window=128000,
84
- num_output=self.max_tokens or -1,
85
- is_chat_model=True,
86
- is_function_calling_model=True,
87
- model_name=self.model,
88
- )
81
+
82
+ @property
83
+ def metadata(self) -> LLMMetadata:
84
+ """Returns the metadata for the LLM.
85
+
86
+ This is required to enable the is_chat_model and is_function_calling_model, which are
87
+ mandatory for LlamaIndex agents. By default, LlamaIndex assumes these are false unless
88
+ each individual model config in LiteLLM explicitly sets them to true. To use custom LLM
89
+ endpoints with LlamaIndex agents, you must override this method to return the
90
+ appropriate metadata.
91
+ """
92
+ return LLMMetadata(
93
+ context_window=128000,
94
+ num_output=self.max_tokens or -1,
95
+ is_chat_model=True,
96
+ is_function_calling_model=True,
97
+ model_name=self.model,
98
+ )
99
+
100
+ return DataRobotLiteLLM(**config)
89
101
 
90
102
 
91
103
  @register_llm_client(
@@ -94,11 +106,15 @@ class DataRobotLiteLLM(LiteLLM): # type: ignore[misc]
94
106
  async def datarobot_llm_gateway_langchain(
95
107
  llm_config: DataRobotLLMGatewayModelConfig, builder: Builder
96
108
  ) -> AsyncGenerator[ChatOpenAI]:
109
+ from nat.plugins.langchain.llm import ( # noqa: PLC0415
110
+ _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
111
+ )
112
+
97
113
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
98
114
  config["base_url"] = config["base_url"] + "/genai/llmgw"
99
115
  config["stream_options"] = {"include_usage": True}
100
116
  config["model"] = config["model"].removeprefix("datarobot/")
101
- client = DataRobotChatOpenAI(**config)
117
+ client = _create_datarobot_chat_openai(config)
102
118
  yield langchain_patch_llm_based_on_config(client, config)
103
119
 
104
120
 
@@ -108,6 +124,8 @@ async def datarobot_llm_gateway_langchain(
108
124
  async def datarobot_llm_gateway_crewai(
109
125
  llm_config: DataRobotLLMGatewayModelConfig, builder: Builder
110
126
  ) -> AsyncGenerator[LLM]:
127
+ from crewai import LLM # noqa: PLC0415
128
+
111
129
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
112
130
  if not config["model"].startswith("datarobot/"):
113
131
  config["model"] = "datarobot/" + config["model"]
@@ -121,12 +139,12 @@ async def datarobot_llm_gateway_crewai(
121
139
  )
122
140
  async def datarobot_llm_gateway_llamaindex(
123
141
  llm_config: DataRobotLLMGatewayModelConfig, builder: Builder
124
- ) -> AsyncGenerator[LLM]:
142
+ ) -> AsyncGenerator[LiteLLM]:
125
143
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
126
144
  if not config["model"].startswith("datarobot/"):
127
145
  config["model"] = "datarobot/" + config["model"]
128
146
  config["api_base"] = config.pop("base_url").removesuffix("/api/v2")
129
- client = DataRobotLiteLLM(**config)
147
+ client = _create_datarobot_litellm(config)
130
148
  yield _patch_llm_based_on_config(client, config)
131
149
 
132
150
 
@@ -136,6 +154,10 @@ async def datarobot_llm_gateway_llamaindex(
136
154
  async def datarobot_llm_deployment_langchain(
137
155
  llm_config: DataRobotLLMDeploymentModelConfig, builder: Builder
138
156
  ) -> AsyncGenerator[ChatOpenAI]:
157
+ from nat.plugins.langchain.llm import ( # noqa: PLC0415
158
+ _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
159
+ )
160
+
139
161
  config = llm_config.model_dump(
140
162
  exclude={"type", "thinking"},
141
163
  by_alias=True,
@@ -143,7 +165,7 @@ async def datarobot_llm_deployment_langchain(
143
165
  )
144
166
  config["stream_options"] = {"include_usage": True}
145
167
  config["model"] = config["model"].removeprefix("datarobot/")
146
- client = DataRobotChatOpenAI(**config)
168
+ client = _create_datarobot_chat_openai(config)
147
169
  yield langchain_patch_llm_based_on_config(client, config)
148
170
 
149
171
 
@@ -153,6 +175,8 @@ async def datarobot_llm_deployment_langchain(
153
175
  async def datarobot_llm_deployment_crewai(
154
176
  llm_config: DataRobotLLMDeploymentModelConfig, builder: Builder
155
177
  ) -> AsyncGenerator[LLM]:
178
+ from crewai import LLM # noqa: PLC0415
179
+
156
180
  config = llm_config.model_dump(
157
181
  exclude={"type", "thinking"},
158
182
  by_alias=True,
@@ -170,7 +194,7 @@ async def datarobot_llm_deployment_crewai(
170
194
  )
171
195
  async def datarobot_llm_deployment_llamaindex(
172
196
  llm_config: DataRobotLLMDeploymentModelConfig, builder: Builder
173
- ) -> AsyncGenerator[LLM]:
197
+ ) -> AsyncGenerator[LiteLLM]:
174
198
  config = llm_config.model_dump(
175
199
  exclude={"type", "thinking"},
176
200
  by_alias=True,
@@ -179,7 +203,7 @@ async def datarobot_llm_deployment_llamaindex(
179
203
  if not config["model"].startswith("datarobot/"):
180
204
  config["model"] = "datarobot/" + config["model"]
181
205
  config["api_base"] = config.pop("base_url") + "/chat/completions"
182
- client = DataRobotLiteLLM(**config)
206
+ client = _create_datarobot_litellm(config)
183
207
  yield _patch_llm_based_on_config(client, config)
184
208
 
185
209
 
@@ -187,6 +211,10 @@ async def datarobot_llm_deployment_llamaindex(
187
211
  async def datarobot_nim_langchain(
188
212
  llm_config: DataRobotNIMModelConfig, builder: Builder
189
213
  ) -> AsyncGenerator[ChatOpenAI]:
214
+ from nat.plugins.langchain.llm import ( # noqa: PLC0415
215
+ _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
216
+ )
217
+
190
218
  config = llm_config.model_dump(
191
219
  exclude={"type", "thinking"},
192
220
  by_alias=True,
@@ -194,7 +222,7 @@ async def datarobot_nim_langchain(
194
222
  )
195
223
  config["stream_options"] = {"include_usage": True}
196
224
  config["model"] = config["model"].removeprefix("datarobot/")
197
- client = DataRobotChatOpenAI(**config)
225
+ client = _create_datarobot_chat_openai(config)
198
226
  yield langchain_patch_llm_based_on_config(client, config)
199
227
 
200
228
 
@@ -202,6 +230,8 @@ async def datarobot_nim_langchain(
202
230
  async def datarobot_nim_crewai(
203
231
  llm_config: DataRobotNIMModelConfig, builder: Builder
204
232
  ) -> AsyncGenerator[LLM]:
233
+ from crewai import LLM # noqa: PLC0415
234
+
205
235
  config = llm_config.model_dump(
206
236
  exclude={"type", "thinking", "max_retries"},
207
237
  by_alias=True,
@@ -217,7 +247,7 @@ async def datarobot_nim_crewai(
217
247
  @register_llm_client(config_type=DataRobotNIMModelConfig, wrapper_type=LLMFrameworkEnum.LLAMA_INDEX)
218
248
  async def datarobot_nim_llamaindex(
219
249
  llm_config: DataRobotNIMModelConfig, builder: Builder
220
- ) -> AsyncGenerator[LLM]:
250
+ ) -> AsyncGenerator[LiteLLM]:
221
251
  config = llm_config.model_dump(
222
252
  exclude={"type", "thinking"},
223
253
  by_alias=True,
@@ -226,7 +256,7 @@ async def datarobot_nim_llamaindex(
226
256
  if not config["model"].startswith("datarobot/"):
227
257
  config["model"] = "datarobot/" + config["model"]
228
258
  config["api_base"] = config.pop("base_url") + "/chat/completions"
229
- client = DataRobotLiteLLM(**config)
259
+ client = _create_datarobot_litellm(config)
230
260
  yield _patch_llm_based_on_config(client, config)
231
261
 
232
262
 
@@ -236,13 +266,17 @@ async def datarobot_nim_llamaindex(
236
266
  async def datarobot_llm_component_langchain(
237
267
  llm_config: DataRobotLLMComponentModelConfig, builder: Builder
238
268
  ) -> AsyncGenerator[ChatOpenAI]:
269
+ from nat.plugins.langchain.llm import ( # noqa: PLC0415
270
+ _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
271
+ )
272
+
239
273
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
240
274
  if config["use_datarobot_llm_gateway"]:
241
275
  config["base_url"] = config["base_url"] + "/genai/llmgw"
242
276
  config["stream_options"] = {"include_usage": True}
243
277
  config["model"] = config["model"].removeprefix("datarobot/")
244
278
  config.pop("use_datarobot_llm_gateway")
245
- client = DataRobotChatOpenAI(**config)
279
+ client = _create_datarobot_chat_openai(config)
246
280
  yield langchain_patch_llm_based_on_config(client, config)
247
281
 
248
282
 
@@ -252,6 +286,8 @@ async def datarobot_llm_component_langchain(
252
286
  async def datarobot_llm_component_crewai(
253
287
  llm_config: DataRobotLLMComponentModelConfig, builder: Builder
254
288
  ) -> AsyncGenerator[LLM]:
289
+ from crewai import LLM # noqa: PLC0415
290
+
255
291
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
256
292
  if not config["model"].startswith("datarobot/"):
257
293
  config["model"] = "datarobot/" + config["model"]
@@ -269,7 +305,7 @@ async def datarobot_llm_component_crewai(
269
305
  )
270
306
  async def datarobot_llm_component_llamaindex(
271
307
  llm_config: DataRobotLLMComponentModelConfig, builder: Builder
272
- ) -> AsyncGenerator[LLM]:
308
+ ) -> AsyncGenerator[LiteLLM]:
273
309
  config = llm_config.model_dump(exclude={"type", "thinking"}, by_alias=True, exclude_none=True)
274
310
  if not config["model"].startswith("datarobot/"):
275
311
  config["model"] = "datarobot/" + config["model"]
@@ -278,5 +314,5 @@ async def datarobot_llm_component_llamaindex(
278
314
  else:
279
315
  config["api_base"] = config.pop("base_url") + "/chat/completions"
280
316
  config.pop("use_datarobot_llm_gateway")
281
- client = DataRobotLiteLLM(**config)
317
+ client = _create_datarobot_litellm(config)
282
318
  yield _patch_llm_based_on_config(client, config)
@@ -12,57 +12,83 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import logging
16
18
  from datetime import timedelta
19
+ from typing import TYPE_CHECKING
17
20
  from typing import Literal
18
21
 
19
- import httpx
20
- from nat.authentication.interfaces import AuthProviderBase
21
- from nat.builder.builder import Builder
22
22
  from nat.cli.register_workflow import register_function_group
23
23
  from nat.data_models.component_ref import AuthenticationRef
24
24
  from nat.plugins.mcp.client_base import AuthAdapter
25
- from nat.plugins.mcp.client_base import MCPSSEClient
26
- from nat.plugins.mcp.client_base import MCPStdioClient
27
25
  from nat.plugins.mcp.client_base import MCPStreamableHTTPClient
28
26
  from nat.plugins.mcp.client_config import MCPServerConfig
29
27
  from nat.plugins.mcp.client_impl import MCPClientConfig
30
- from nat.plugins.mcp.client_impl import MCPFunctionGroup
31
- from nat.plugins.mcp.client_impl import mcp_apply_tool_alias_and_description
32
- from nat.plugins.mcp.client_impl import mcp_session_tool_function
33
28
  from pydantic import Field
34
29
  from pydantic import HttpUrl
35
30
 
36
- from datarobot_genai.core.mcp.common import MCPConfig
31
+ if TYPE_CHECKING:
32
+ import httpx
33
+ from nat.authentication.interfaces import AuthProviderBase
34
+ from nat.builder.builder import Builder
35
+ from nat.plugins.mcp.client_impl import MCPFunctionGroup
37
36
 
38
37
  logger = logging.getLogger(__name__)
39
38
 
40
- config = MCPConfig().server_config
39
+
40
+ def _default_transport() -> Literal["streamable-http", "sse", "stdio"]:
41
+ from datarobot_genai.core.mcp.common import MCPConfig # noqa: PLC0415
42
+
43
+ server_config = MCPConfig().server_config
44
+ return server_config["transport"] if server_config else "stdio"
45
+
46
+
47
+ def _default_url() -> HttpUrl | None:
48
+ from datarobot_genai.core.mcp.common import MCPConfig # noqa: PLC0415
49
+
50
+ server_config = MCPConfig().server_config
51
+ return server_config["url"] if server_config else None
52
+
53
+
54
+ def _default_auth_provider() -> str | AuthenticationRef | None:
55
+ from datarobot_genai.core.mcp.common import MCPConfig # noqa: PLC0415
56
+
57
+ server_config = MCPConfig().server_config
58
+ return "datarobot_mcp_auth" if server_config else None
59
+
60
+
61
+ def _default_command() -> str | None:
62
+ from datarobot_genai.core.mcp.common import MCPConfig # noqa: PLC0415
63
+
64
+ server_config = MCPConfig().server_config
65
+ return None if server_config else "docker"
41
66
 
42
67
 
43
68
  class DataRobotMCPServerConfig(MCPServerConfig):
44
69
  transport: Literal["streamable-http", "sse", "stdio"] = Field(
45
- default=config["transport"] if config else "stdio",
70
+ default_factory=_default_transport,
46
71
  description="Transport type to connect to the MCP server (sse or streamable-http)",
47
72
  )
48
73
  url: HttpUrl | None = Field(
49
- default=config["url"] if config else None,
74
+ default_factory=_default_url,
50
75
  description="URL of the MCP server (for sse or streamable-http transport)",
51
76
  )
52
77
  # Authentication configuration
53
78
  auth_provider: str | AuthenticationRef | None = Field(
54
- default="datarobot_mcp_auth" if config else None,
79
+ default_factory=_default_auth_provider,
55
80
  description="Reference to authentication provider",
56
81
  )
57
82
  command: str | None = Field(
58
- default=None if config else "docker",
83
+ default_factory=_default_command,
59
84
  description="Command to run for stdio transport (e.g. 'python' or 'docker')",
60
85
  )
61
86
 
62
87
 
63
88
  class DataRobotMCPClientConfig(MCPClientConfig, name="datarobot_mcp_client"): # type: ignore[call-arg]
64
89
  server: DataRobotMCPServerConfig = Field(
65
- default=DataRobotMCPServerConfig(), description="DataRobot MCP Server configuration"
90
+ default_factory=DataRobotMCPServerConfig,
91
+ description="DataRobot MCP Server configuration",
66
92
  )
67
93
 
68
94
 
@@ -128,6 +154,12 @@ async def datarobot_mcp_client_function_group(
128
154
  Returns:
129
155
  The function group
130
156
  """
157
+ from nat.plugins.mcp.client_base import MCPSSEClient # noqa: PLC0415
158
+ from nat.plugins.mcp.client_base import MCPStdioClient # noqa: PLC0415
159
+ from nat.plugins.mcp.client_impl import MCPFunctionGroup # noqa: PLC0415
160
+ from nat.plugins.mcp.client_impl import mcp_apply_tool_alias_and_description # noqa: PLC0415
161
+ from nat.plugins.mcp.client_impl import mcp_session_tool_function # noqa: PLC0415
162
+
131
163
  # Resolve auth provider if specified
132
164
  auth_provider = None
133
165
  if config.server.auth_provider: