datarobot-genai 0.2.31__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.
Files changed (125) hide show
  1. datarobot_genai/__init__.py +19 -0
  2. datarobot_genai/core/__init__.py +0 -0
  3. datarobot_genai/core/agents/__init__.py +43 -0
  4. datarobot_genai/core/agents/base.py +195 -0
  5. datarobot_genai/core/chat/__init__.py +19 -0
  6. datarobot_genai/core/chat/auth.py +146 -0
  7. datarobot_genai/core/chat/client.py +178 -0
  8. datarobot_genai/core/chat/responses.py +297 -0
  9. datarobot_genai/core/cli/__init__.py +18 -0
  10. datarobot_genai/core/cli/agent_environment.py +47 -0
  11. datarobot_genai/core/cli/agent_kernel.py +211 -0
  12. datarobot_genai/core/custom_model.py +141 -0
  13. datarobot_genai/core/mcp/__init__.py +0 -0
  14. datarobot_genai/core/mcp/common.py +218 -0
  15. datarobot_genai/core/telemetry_agent.py +126 -0
  16. datarobot_genai/core/utils/__init__.py +3 -0
  17. datarobot_genai/core/utils/auth.py +234 -0
  18. datarobot_genai/core/utils/urls.py +64 -0
  19. datarobot_genai/crewai/__init__.py +24 -0
  20. datarobot_genai/crewai/agent.py +42 -0
  21. datarobot_genai/crewai/base.py +159 -0
  22. datarobot_genai/crewai/events.py +117 -0
  23. datarobot_genai/crewai/mcp.py +59 -0
  24. datarobot_genai/drmcp/__init__.py +78 -0
  25. datarobot_genai/drmcp/core/__init__.py +13 -0
  26. datarobot_genai/drmcp/core/auth.py +165 -0
  27. datarobot_genai/drmcp/core/clients.py +180 -0
  28. datarobot_genai/drmcp/core/config.py +364 -0
  29. datarobot_genai/drmcp/core/config_utils.py +174 -0
  30. datarobot_genai/drmcp/core/constants.py +18 -0
  31. datarobot_genai/drmcp/core/credentials.py +190 -0
  32. datarobot_genai/drmcp/core/dr_mcp_server.py +350 -0
  33. datarobot_genai/drmcp/core/dr_mcp_server_logo.py +136 -0
  34. datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +13 -0
  35. datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +130 -0
  36. datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +70 -0
  37. datarobot_genai/drmcp/core/dynamic_prompts/register.py +205 -0
  38. datarobot_genai/drmcp/core/dynamic_prompts/utils.py +33 -0
  39. datarobot_genai/drmcp/core/dynamic_tools/__init__.py +14 -0
  40. datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  41. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +14 -0
  42. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +72 -0
  43. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +82 -0
  44. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +238 -0
  45. datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +228 -0
  46. datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +63 -0
  47. datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +162 -0
  48. datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +87 -0
  49. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +36 -0
  50. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +10 -0
  51. datarobot_genai/drmcp/core/dynamic_tools/register.py +254 -0
  52. datarobot_genai/drmcp/core/dynamic_tools/schema.py +532 -0
  53. datarobot_genai/drmcp/core/exceptions.py +25 -0
  54. datarobot_genai/drmcp/core/logging.py +98 -0
  55. datarobot_genai/drmcp/core/mcp_instance.py +515 -0
  56. datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
  57. datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
  58. datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
  59. datarobot_genai/drmcp/core/routes.py +439 -0
  60. datarobot_genai/drmcp/core/routes_utils.py +30 -0
  61. datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
  62. datarobot_genai/drmcp/core/telemetry.py +424 -0
  63. datarobot_genai/drmcp/core/tool_config.py +111 -0
  64. datarobot_genai/drmcp/core/tool_filter.py +117 -0
  65. datarobot_genai/drmcp/core/utils.py +138 -0
  66. datarobot_genai/drmcp/server.py +19 -0
  67. datarobot_genai/drmcp/test_utils/__init__.py +13 -0
  68. datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
  69. datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
  70. datarobot_genai/drmcp/test_utils/clients/base.py +300 -0
  71. datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
  72. datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
  73. datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +89 -0
  74. datarobot_genai/drmcp/test_utils/integration_mcp_server.py +109 -0
  75. datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +133 -0
  76. datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +107 -0
  77. datarobot_genai/drmcp/test_utils/test_interactive.py +205 -0
  78. datarobot_genai/drmcp/test_utils/tool_base_ete.py +220 -0
  79. datarobot_genai/drmcp/test_utils/utils.py +91 -0
  80. datarobot_genai/drmcp/tools/__init__.py +14 -0
  81. datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
  82. datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
  83. datarobot_genai/drmcp/tools/clients/confluence.py +584 -0
  84. datarobot_genai/drmcp/tools/clients/gdrive.py +832 -0
  85. datarobot_genai/drmcp/tools/clients/jira.py +334 -0
  86. datarobot_genai/drmcp/tools/clients/microsoft_graph.py +479 -0
  87. datarobot_genai/drmcp/tools/clients/s3.py +28 -0
  88. datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
  89. datarobot_genai/drmcp/tools/confluence/tools.py +321 -0
  90. datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  91. datarobot_genai/drmcp/tools/gdrive/tools.py +347 -0
  92. datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
  93. datarobot_genai/drmcp/tools/jira/tools.py +243 -0
  94. datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +13 -0
  95. datarobot_genai/drmcp/tools/microsoft_graph/tools.py +198 -0
  96. datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
  97. datarobot_genai/drmcp/tools/predictive/data.py +133 -0
  98. datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
  99. datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
  100. datarobot_genai/drmcp/tools/predictive/model.py +148 -0
  101. datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
  102. datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
  103. datarobot_genai/drmcp/tools/predictive/project.py +90 -0
  104. datarobot_genai/drmcp/tools/predictive/training.py +661 -0
  105. datarobot_genai/langgraph/__init__.py +0 -0
  106. datarobot_genai/langgraph/agent.py +341 -0
  107. datarobot_genai/langgraph/mcp.py +73 -0
  108. datarobot_genai/llama_index/__init__.py +16 -0
  109. datarobot_genai/llama_index/agent.py +50 -0
  110. datarobot_genai/llama_index/base.py +299 -0
  111. datarobot_genai/llama_index/mcp.py +79 -0
  112. datarobot_genai/nat/__init__.py +0 -0
  113. datarobot_genai/nat/agent.py +275 -0
  114. datarobot_genai/nat/datarobot_auth_provider.py +110 -0
  115. datarobot_genai/nat/datarobot_llm_clients.py +318 -0
  116. datarobot_genai/nat/datarobot_llm_providers.py +130 -0
  117. datarobot_genai/nat/datarobot_mcp_client.py +266 -0
  118. datarobot_genai/nat/helpers.py +87 -0
  119. datarobot_genai/py.typed +0 -0
  120. datarobot_genai-0.2.31.dist-info/METADATA +145 -0
  121. datarobot_genai-0.2.31.dist-info/RECORD +125 -0
  122. datarobot_genai-0.2.31.dist-info/WHEEL +4 -0
  123. datarobot_genai-0.2.31.dist-info/entry_points.txt +5 -0
  124. datarobot_genai-0.2.31.dist-info/licenses/AUTHORS +2 -0
  125. datarobot_genai-0.2.31.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,364 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ from typing import Any
17
+ from typing import Literal
18
+ from urllib.parse import urlparse
19
+ from urllib.parse import urlunparse
20
+
21
+ from fastmcp.settings import DuplicateBehavior
22
+ from pydantic import AliasChoices
23
+ from pydantic import Field
24
+ from pydantic import field_validator
25
+ from pydantic_settings import BaseSettings
26
+ from pydantic_settings import SettingsConfigDict
27
+
28
+ from .config_utils import extract_datarobot_dict_runtime_param_payload
29
+ from .config_utils import extract_datarobot_runtime_param_payload
30
+ from .constants import DEFAULT_DATAROBOT_ENDPOINT
31
+ from .constants import RUNTIME_PARAM_ENV_VAR_NAME_PREFIX
32
+
33
+
34
+ class MCPToolConfig(BaseSettings):
35
+ """Tool configuration for MCP server."""
36
+
37
+ enable_predictive_tools: bool = Field(
38
+ default=True,
39
+ validation_alias=AliasChoices(
40
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_PREDICTIVE_TOOLS",
41
+ "ENABLE_PREDICTIVE_TOOLS",
42
+ ),
43
+ description="Enable/disable predictive tools",
44
+ )
45
+
46
+ enable_jira_tools: bool = Field(
47
+ default=False,
48
+ validation_alias=AliasChoices(
49
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_JIRA_TOOLS",
50
+ "ENABLE_JIRA_TOOLS",
51
+ ),
52
+ description="Enable/disable Jira tools",
53
+ )
54
+
55
+ enable_confluence_tools: bool = Field(
56
+ default=False,
57
+ validation_alias=AliasChoices(
58
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_CONFLUENCE_TOOLS",
59
+ "ENABLE_CONFLUENCE_TOOLS",
60
+ ),
61
+ description="Enable/disable Confluence tools",
62
+ )
63
+
64
+ enable_gdrive_tools: bool = Field(
65
+ default=False,
66
+ validation_alias=AliasChoices(
67
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_GDRIVE_TOOLS",
68
+ "ENABLE_GDRIVE_TOOLS",
69
+ ),
70
+ description="Enable/disable GDrive tools",
71
+ )
72
+
73
+ enable_microsoft_graph_tools: bool = Field(
74
+ default=False,
75
+ validation_alias=AliasChoices(
76
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_MICROSOFT_GRAPH_TOOLS",
77
+ "ENABLE_MICROSOFT_GRAPH_TOOLS",
78
+ ),
79
+ description="Enable/disable Sharepoint tools",
80
+ )
81
+
82
+ is_atlassian_oauth_provider_configured: bool = Field(
83
+ default=False,
84
+ validation_alias=AliasChoices(
85
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "IS_ATLASSIAN_OAUTH_PROVIDER_CONFIGURED",
86
+ "IS_ATLASSIAN_OAUTH_PROVIDER_CONFIGURED",
87
+ ),
88
+ description="Whether Atlassian OAuth provider is configured for Atlassian integration",
89
+ )
90
+
91
+ @property
92
+ def is_atlassian_oauth_configured(self) -> bool:
93
+ """Check if Atlassian OAuth is configured via provider flag or environment variables."""
94
+ return self.is_atlassian_oauth_provider_configured or bool(
95
+ os.getenv("ATLASSIAN_CLIENT_ID") and os.getenv("ATLASSIAN_CLIENT_SECRET")
96
+ )
97
+
98
+ is_google_oauth_provider_configured: bool = Field(
99
+ default=False,
100
+ validation_alias=AliasChoices(
101
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "IS_GOOGLE_OAUTH_PROVIDER_CONFIGURED",
102
+ "IS_GOOGLE_OAUTH_PROVIDER_CONFIGURED",
103
+ ),
104
+ description="Whether Google OAuth provider is configured for Google integration",
105
+ )
106
+
107
+ @property
108
+ def is_google_oauth_configured(self) -> bool:
109
+ return self.is_google_oauth_provider_configured or bool(
110
+ os.getenv("GOOGLE_CLIENT_ID") and os.getenv("GOOGLE_CLIENT_SECRET")
111
+ )
112
+
113
+ is_microsoft_oauth_provider_configured: bool = Field(
114
+ default=False,
115
+ validation_alias=AliasChoices(
116
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "IS_MICROSOFT_OAUTH_PROVIDER_CONFIGURED",
117
+ "IS_MICROSOFT_OAUTH_PROVIDER_CONFIGURED",
118
+ ),
119
+ description="Whether Microsoft OAuth provider is configured for Microsoft integration",
120
+ )
121
+
122
+ @property
123
+ def is_microsoft_oauth_configured(self) -> bool:
124
+ return self.is_microsoft_oauth_provider_configured or bool(
125
+ os.getenv("MICROSOFT_CLIENT_ID") and os.getenv("MICROSOFT_CLIENT_SECRET")
126
+ )
127
+
128
+ @field_validator(
129
+ "enable_predictive_tools",
130
+ "enable_jira_tools",
131
+ "enable_confluence_tools",
132
+ "enable_gdrive_tools",
133
+ "enable_microsoft_graph_tools",
134
+ "is_atlassian_oauth_provider_configured",
135
+ "is_google_oauth_provider_configured",
136
+ "is_microsoft_oauth_provider_configured",
137
+ mode="before",
138
+ )
139
+ @classmethod
140
+ def validate_runtime_params(cls, v: Any) -> Any:
141
+ """Validate runtime parameters."""
142
+ return extract_datarobot_runtime_param_payload(v)
143
+
144
+ model_config = SettingsConfigDict(
145
+ env_file=".env",
146
+ case_sensitive=False,
147
+ env_file_encoding="utf-8",
148
+ extra="ignore",
149
+ )
150
+
151
+
152
+ class MCPServerConfig(BaseSettings):
153
+ """MCP Server configuration using pydantic settings."""
154
+
155
+ mcp_server_name: str = Field(
156
+ default="datarobot-mcp-server",
157
+ validation_alias=AliasChoices(
158
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_NAME",
159
+ "MCP_SERVER_NAME",
160
+ ),
161
+ description="Name of the MCP server",
162
+ )
163
+ mcp_server_port: int = Field(
164
+ default=8080,
165
+ validation_alias=AliasChoices(
166
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_PORT",
167
+ "MCP_SERVER_PORT",
168
+ ),
169
+ description="Port number for the MCP server",
170
+ )
171
+ mcp_server_log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
172
+ default="WARNING",
173
+ validation_alias=AliasChoices(
174
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_LOG_LEVEL",
175
+ "MCP_SERVER_LOG_LEVEL",
176
+ ),
177
+ description="Log level for the MCP server",
178
+ )
179
+ mcp_server_host: str = Field(
180
+ default="0.0.0.0",
181
+ validation_alias=AliasChoices(
182
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_HOST",
183
+ "MCP_SERVER_HOST",
184
+ ),
185
+ description="Host address for the MCP server",
186
+ )
187
+ app_log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
188
+ default="INFO",
189
+ validation_alias=AliasChoices(
190
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "APP_LOG_LEVEL",
191
+ "APP_LOG_LEVEL",
192
+ ),
193
+ description="App log level",
194
+ )
195
+ # When the server is run in a custom model, it is important to mount all routes under the
196
+ # prefix provided in the URL_PREFIX
197
+ mount_path: str = Field(default="/", alias="URL_PREFIX")
198
+
199
+ @staticmethod
200
+ def _get_default_otel_endpoint() -> str:
201
+ """Get the default OpenTelemetry endpoint e.g. https://app.datarobot.com/otel."""
202
+ parsed_url = urlparse(os.environ.get("DATAROBOT_ENDPOINT", DEFAULT_DATAROBOT_ENDPOINT))
203
+ stripped_url = (parsed_url.scheme, parsed_url.netloc, "otel", "", "", "")
204
+ return urlunparse(stripped_url)
205
+
206
+ otel_collector_base_url: str = Field(
207
+ default=_get_default_otel_endpoint(),
208
+ validation_alias=AliasChoices(
209
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "OTEL_COLLECTOR_BASE_URL",
210
+ "OTEL_COLLECTOR_BASE_URL",
211
+ ),
212
+ description="Base URL for the OpenTelemetry collector",
213
+ )
214
+ otel_entity_id: str = Field(
215
+ default="",
216
+ validation_alias=AliasChoices(
217
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "OTEL_ENTITY_ID",
218
+ "OTEL_ENTITY_ID",
219
+ ),
220
+ description="Entity ID for tracing",
221
+ )
222
+ otel_attributes: dict[str, Any] = Field(
223
+ default={},
224
+ validation_alias=AliasChoices(
225
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "OTEL_ATTRIBUTES",
226
+ "OTEL_ATTRIBUTES",
227
+ ),
228
+ description="Attributes for tracing (as JSON string)",
229
+ )
230
+ otel_enabled: bool = Field(
231
+ default=True,
232
+ validation_alias=AliasChoices(
233
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "OTEL_ENABLED",
234
+ "OTEL_ENABLED",
235
+ ),
236
+ description="Enable/disable OpenTelemetry",
237
+ )
238
+ otel_enabled_http_instrumentors: bool = Field(
239
+ default=False,
240
+ validation_alias=AliasChoices(
241
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "OTEL_ENABLED_HTTP_INSTRUMENTORS",
242
+ "OTEL_ENABLED_HTTP_INSTRUMENTORS",
243
+ ),
244
+ description="Enable/disable HTTP instrumentors",
245
+ )
246
+ mcp_server_register_dynamic_tools_on_startup: bool = Field(
247
+ default=False,
248
+ validation_alias=AliasChoices(
249
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_REGISTER_DYNAMIC_TOOLS_ON_STARTUP",
250
+ "MCP_SERVER_REGISTER_DYNAMIC_TOOLS_ON_STARTUP",
251
+ ),
252
+ description="Register dynamic tools on startup. When enabled, the MCP server will "
253
+ "automatically register all DataRobot tool deployments as MCP tools during startup.",
254
+ )
255
+ tool_registration_allow_empty_schema: bool = Field(
256
+ default=False,
257
+ validation_alias=AliasChoices(
258
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_TOOL_REGISTRATION_ALLOW_EMPTY_SCHEMA",
259
+ "MCP_SERVER_TOOL_REGISTRATION_ALLOW_EMPTY_SCHEMA",
260
+ ),
261
+ description="Allow registration of tools with no input parameters. When enabled, "
262
+ "tools can be registered with empty schemas for static endpoints that don't require any "
263
+ "inputs. "
264
+ "Disabled by default, as this is not typical use case and can hide potential issues with "
265
+ "schema.",
266
+ )
267
+ tool_registration_duplicate_behavior: DuplicateBehavior = Field(
268
+ default="warn",
269
+ validation_alias=AliasChoices(
270
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_TOOL_REGISTRATION_DUPLICATE_BEHAVIOR",
271
+ "MCP_SERVER_TOOL_REGISTRATION_DUPLICATE_BEHAVIOR",
272
+ ),
273
+ description="Behavior when a tool with the same name already exists in the MCP server. "
274
+ " - 'warn': will log a warning and replace the existing tool. "
275
+ " - 'replace': will replace the existing tool without a warning. "
276
+ " - 'error': will raise an error and prevent registration. "
277
+ " - 'ignore': will skip registration of the new tool.",
278
+ )
279
+ mcp_server_register_dynamic_prompts_on_startup: bool = Field(
280
+ default=False,
281
+ validation_alias=AliasChoices(
282
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_REGISTER_DYNAMIC_PROMPTS_ON_STARTUP",
283
+ "MCP_SERVER_REGISTER_DYNAMIC_PROMPTS_ON_STARTUP",
284
+ ),
285
+ description="Register dynamic prompts on startup. When enabled, the MCP server will "
286
+ "automatically register all prompts from DataRobot Prompt Management "
287
+ "as MCP prompts during startup.",
288
+ )
289
+ prompt_registration_duplicate_behavior: DuplicateBehavior = Field(
290
+ default="warn",
291
+ validation_alias=AliasChoices(
292
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "MCP_SERVER_PROMPT_REGISTRATION_DUPLICATE_BEHAVIOR",
293
+ "MCP_SERVER_PROMPT_REGISTRATION_DUPLICATE_BEHAVIOR",
294
+ ),
295
+ description="Behavior when a prompt with the same name already exists in the MCP server. "
296
+ " - 'warn': will log a warning and replace the existing tool. "
297
+ " - 'replace': will replace the existing tool without a warning. "
298
+ " - 'error': will raise an error and prevent registration. "
299
+ " - 'ignore': will skip registration of the new tool.",
300
+ )
301
+ enable_memory_management: bool = Field(
302
+ default=False,
303
+ validation_alias=AliasChoices(
304
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_MEMORY_MANAGEMENT",
305
+ "ENABLE_MEMORY_MANAGEMENT",
306
+ ),
307
+ description="Enable/disable memory management",
308
+ )
309
+
310
+ tool_config: MCPToolConfig = Field(
311
+ default_factory=MCPToolConfig,
312
+ description="Tool configuration",
313
+ )
314
+
315
+ @field_validator(
316
+ "otel_attributes",
317
+ mode="before",
318
+ )
319
+ @classmethod
320
+ def validate_dict_runtime_params(cls, v: Any) -> Any:
321
+ """Validate dict runtime parameters."""
322
+ return extract_datarobot_dict_runtime_param_payload(v)
323
+
324
+ @field_validator(
325
+ "mcp_server_name",
326
+ "mcp_server_log_level",
327
+ "app_log_level",
328
+ "otel_collector_base_url",
329
+ "otel_entity_id",
330
+ "otel_enabled",
331
+ "otel_enabled_http_instrumentors",
332
+ "enable_memory_management",
333
+ "tool_registration_allow_empty_schema",
334
+ "mcp_server_register_dynamic_tools_on_startup",
335
+ "tool_registration_duplicate_behavior",
336
+ "mcp_server_register_dynamic_prompts_on_startup",
337
+ mode="before",
338
+ )
339
+ @classmethod
340
+ def validate_runtime_params(cls, v: Any) -> Any:
341
+ """Validate runtime parameters."""
342
+ return extract_datarobot_runtime_param_payload(v)
343
+
344
+ model_config = SettingsConfigDict(
345
+ env_file=".env",
346
+ case_sensitive=False,
347
+ env_file_encoding="utf-8",
348
+ extra="ignore",
349
+ )
350
+
351
+
352
+ # Global configuration instance
353
+ _config: MCPServerConfig | None = None
354
+
355
+
356
+ def get_config() -> MCPServerConfig:
357
+ """Get the global configuration instance."""
358
+ # Use a local variable to avoid global statement warning
359
+ config = _config
360
+ if config is None:
361
+ config = MCPServerConfig()
362
+ # Update the global variable
363
+ globals()["_config"] = config
364
+ return config
@@ -0,0 +1,174 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ from typing import Any
17
+
18
+ from pydantic_core import PydanticUseDefault
19
+
20
+
21
+ def extract_datarobot_runtime_param_payload(v: Any) -> Any:
22
+ """Extract payload from DataRobot runtime parameter JSON format.
23
+
24
+ DataRobot runtime parameters come in the format:
25
+ {"type":"string","payload":"value"} or {"type":"boolean","payload":false}
26
+
27
+ If payload is None, raises PydanticUseDefault so Pydantic uses the field default.
28
+
29
+ This function extracts the payload value for simple types (strings, booleans, numbers).
30
+ For dict types with nested JSON, use extract_datarobot_dict_runtime_param_payload instead.
31
+
32
+ Args:
33
+ v: The input value (may be a raw value, JSON string, or DataRobot runtime param format)
34
+
35
+ Returns
36
+ -------
37
+ The extracted payload value
38
+
39
+ Raises
40
+ ------
41
+ PydanticUseDefault: When payload is None, signaling Pydantic to use field default
42
+ """
43
+ # If it's a string, try to parse as JSON
44
+ if isinstance(v, str):
45
+ # Handle Python-style boolean strings (True/False) by converting to lowercase
46
+ v_normalized = v.lower() if v.lower() in ("true", "false") else v
47
+
48
+ try:
49
+ parsed = json.loads(v_normalized)
50
+ if isinstance(parsed, dict) and "payload" in parsed:
51
+ payload = parsed["payload"]
52
+ if payload is not None:
53
+ return payload
54
+ # If payload is None, use field default
55
+ raise PydanticUseDefault
56
+ # If it's a plain JSON value (boolean, number, etc.), return it
57
+ # This handles cases like "true", "false", "123", etc.
58
+ return parsed
59
+ except (json.JSONDecodeError, ValueError):
60
+ pass
61
+ return v
62
+
63
+
64
+ def extract_datarobot_dict_runtime_param_payload(v: Any) -> Any: # noqa: PLR0911
65
+ r"""Extract and parse dict from DataRobot runtime parameter JSON format.
66
+
67
+ DataRobot runtime parameters for dict fields come in the format:
68
+ {"type":"string","payload":"{\\"key\\":\\"value\\"}"}
69
+
70
+ The payload itself is a JSON string that needs to be parsed into a dict.
71
+ If payload is None, raises PydanticUseDefault so Pydantic uses the field default.
72
+
73
+ Args:
74
+ v: The input value (may be a dict, JSON string, or DataRobot runtime param format)
75
+
76
+ Returns
77
+ -------
78
+ The extracted and parsed dict
79
+
80
+ Raises
81
+ ------
82
+ PydanticUseDefault: When payload is None, signaling Pydantic to use field default
83
+ """
84
+ # If it's already a dict, check if it's in DataRobot format
85
+ if isinstance(v, dict):
86
+ # If it has "payload" key, it's in DataRobot format - extract it
87
+ if "payload" in v:
88
+ payload = v["payload"]
89
+ if payload is None:
90
+ raise PydanticUseDefault
91
+ # If payload is a string, parse it as JSON
92
+ if isinstance(payload, str):
93
+ try:
94
+ return json.loads(payload)
95
+ except (json.JSONDecodeError, ValueError):
96
+ return {}
97
+ return payload
98
+ # Otherwise, return as-is (already a plain dict)
99
+ return v
100
+
101
+ # If it's a string, try to parse as JSON
102
+ if isinstance(v, str):
103
+ try:
104
+ parsed = json.loads(v)
105
+ if isinstance(parsed, dict) and "payload" in parsed:
106
+ payload = parsed["payload"]
107
+ if payload is not None:
108
+ # If payload is a string, parse it as JSON
109
+ if isinstance(payload, str):
110
+ try:
111
+ return json.loads(payload)
112
+ except (json.JSONDecodeError, ValueError):
113
+ return {}
114
+ return payload
115
+ raise PydanticUseDefault
116
+ # If it's already a dict, return it
117
+ if isinstance(parsed, dict):
118
+ return parsed
119
+ except (json.JSONDecodeError, ValueError):
120
+ pass
121
+
122
+ # Fallback: return empty dict
123
+ return {}
124
+
125
+
126
+ def extract_datarobot_credential_runtime_param_payload(v: Any) -> Any:
127
+ """Extract credential payload from DataRobot runtime parameter JSON format.
128
+
129
+ DataRobot credential runtime parameters come in the format:
130
+ {"type":"credential","payload":{...}} where the payload is the full credential object.
131
+
132
+ For credential types, the entire dict is preserved.
133
+ If payload is None, raises PydanticUseDefault so Pydantic uses the field default.
134
+
135
+ Args:
136
+ v: The input value (may be a dict, JSON string, or DataRobot runtime param format)
137
+
138
+ Returns
139
+ -------
140
+ The extracted credential dict
141
+
142
+ Raises
143
+ ------
144
+ PydanticUseDefault: When payload is None, signaling Pydantic to use field default
145
+ """
146
+ # If it's already a dict (credential object or wrapped payload)
147
+ if isinstance(v, dict):
148
+ # If it's a wrapped payload, extract it
149
+ if "payload" in v:
150
+ payload = v["payload"]
151
+ if payload is not None:
152
+ return payload
153
+ # If payload is None, use field default
154
+ raise PydanticUseDefault
155
+ # Otherwise return the dict as-is (it's the credential object)
156
+ return v
157
+
158
+ # If it's a string, try to parse as JSON
159
+ if isinstance(v, str):
160
+ try:
161
+ parsed = json.loads(v)
162
+ if isinstance(parsed, dict):
163
+ # If it's a wrapped payload, extract it
164
+ if "payload" in parsed:
165
+ payload = parsed["payload"]
166
+ if payload is not None:
167
+ return payload
168
+ # If payload is None, use field default
169
+ raise PydanticUseDefault
170
+ # Otherwise return the dict as-is
171
+ return parsed
172
+ except (json.JSONDecodeError, ValueError):
173
+ pass
174
+ return v
@@ -0,0 +1,18 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ MAX_INLINE_SIZE = 1024 * 1024 # 1MB
17
+ DEFAULT_DATAROBOT_ENDPOINT = "https://app.datarobot.com/api/v2"
18
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX = "MLOPS_RUNTIME_PARAM_"