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.
- datarobot_genai/__init__.py +19 -0
- datarobot_genai/core/__init__.py +0 -0
- datarobot_genai/core/agents/__init__.py +43 -0
- datarobot_genai/core/agents/base.py +195 -0
- datarobot_genai/core/chat/__init__.py +19 -0
- datarobot_genai/core/chat/auth.py +146 -0
- datarobot_genai/core/chat/client.py +178 -0
- datarobot_genai/core/chat/responses.py +297 -0
- datarobot_genai/core/cli/__init__.py +18 -0
- datarobot_genai/core/cli/agent_environment.py +47 -0
- datarobot_genai/core/cli/agent_kernel.py +211 -0
- datarobot_genai/core/custom_model.py +141 -0
- datarobot_genai/core/mcp/__init__.py +0 -0
- datarobot_genai/core/mcp/common.py +218 -0
- datarobot_genai/core/telemetry_agent.py +126 -0
- datarobot_genai/core/utils/__init__.py +3 -0
- datarobot_genai/core/utils/auth.py +234 -0
- datarobot_genai/core/utils/urls.py +64 -0
- datarobot_genai/crewai/__init__.py +24 -0
- datarobot_genai/crewai/agent.py +42 -0
- datarobot_genai/crewai/base.py +159 -0
- datarobot_genai/crewai/events.py +117 -0
- datarobot_genai/crewai/mcp.py +59 -0
- datarobot_genai/drmcp/__init__.py +78 -0
- datarobot_genai/drmcp/core/__init__.py +13 -0
- datarobot_genai/drmcp/core/auth.py +165 -0
- datarobot_genai/drmcp/core/clients.py +180 -0
- datarobot_genai/drmcp/core/config.py +364 -0
- datarobot_genai/drmcp/core/config_utils.py +174 -0
- datarobot_genai/drmcp/core/constants.py +18 -0
- datarobot_genai/drmcp/core/credentials.py +190 -0
- datarobot_genai/drmcp/core/dr_mcp_server.py +350 -0
- datarobot_genai/drmcp/core/dr_mcp_server_logo.py +136 -0
- datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +13 -0
- datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +130 -0
- datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +70 -0
- datarobot_genai/drmcp/core/dynamic_prompts/register.py +205 -0
- datarobot_genai/drmcp/core/dynamic_prompts/utils.py +33 -0
- datarobot_genai/drmcp/core/dynamic_tools/__init__.py +14 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +14 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +72 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +82 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +238 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +228 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +63 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +162 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +87 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +36 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +10 -0
- datarobot_genai/drmcp/core/dynamic_tools/register.py +254 -0
- datarobot_genai/drmcp/core/dynamic_tools/schema.py +532 -0
- datarobot_genai/drmcp/core/exceptions.py +25 -0
- datarobot_genai/drmcp/core/logging.py +98 -0
- datarobot_genai/drmcp/core/mcp_instance.py +515 -0
- datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
- datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
- datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
- datarobot_genai/drmcp/core/routes.py +439 -0
- datarobot_genai/drmcp/core/routes_utils.py +30 -0
- datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
- datarobot_genai/drmcp/core/telemetry.py +424 -0
- datarobot_genai/drmcp/core/tool_config.py +111 -0
- datarobot_genai/drmcp/core/tool_filter.py +117 -0
- datarobot_genai/drmcp/core/utils.py +138 -0
- datarobot_genai/drmcp/server.py +19 -0
- datarobot_genai/drmcp/test_utils/__init__.py +13 -0
- datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
- datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
- datarobot_genai/drmcp/test_utils/clients/base.py +300 -0
- datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
- datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
- datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +89 -0
- datarobot_genai/drmcp/test_utils/integration_mcp_server.py +109 -0
- datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +133 -0
- datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +107 -0
- datarobot_genai/drmcp/test_utils/test_interactive.py +205 -0
- datarobot_genai/drmcp/test_utils/tool_base_ete.py +220 -0
- datarobot_genai/drmcp/test_utils/utils.py +91 -0
- datarobot_genai/drmcp/tools/__init__.py +14 -0
- datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
- datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
- datarobot_genai/drmcp/tools/clients/confluence.py +584 -0
- datarobot_genai/drmcp/tools/clients/gdrive.py +832 -0
- datarobot_genai/drmcp/tools/clients/jira.py +334 -0
- datarobot_genai/drmcp/tools/clients/microsoft_graph.py +479 -0
- datarobot_genai/drmcp/tools/clients/s3.py +28 -0
- datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
- datarobot_genai/drmcp/tools/confluence/tools.py +321 -0
- datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
- datarobot_genai/drmcp/tools/gdrive/tools.py +347 -0
- datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
- datarobot_genai/drmcp/tools/jira/tools.py +243 -0
- datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +13 -0
- datarobot_genai/drmcp/tools/microsoft_graph/tools.py +198 -0
- datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
- datarobot_genai/drmcp/tools/predictive/data.py +133 -0
- datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
- datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
- datarobot_genai/drmcp/tools/predictive/model.py +148 -0
- datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
- datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
- datarobot_genai/drmcp/tools/predictive/project.py +90 -0
- datarobot_genai/drmcp/tools/predictive/training.py +661 -0
- datarobot_genai/langgraph/__init__.py +0 -0
- datarobot_genai/langgraph/agent.py +341 -0
- datarobot_genai/langgraph/mcp.py +73 -0
- datarobot_genai/llama_index/__init__.py +16 -0
- datarobot_genai/llama_index/agent.py +50 -0
- datarobot_genai/llama_index/base.py +299 -0
- datarobot_genai/llama_index/mcp.py +79 -0
- datarobot_genai/nat/__init__.py +0 -0
- datarobot_genai/nat/agent.py +275 -0
- datarobot_genai/nat/datarobot_auth_provider.py +110 -0
- datarobot_genai/nat/datarobot_llm_clients.py +318 -0
- datarobot_genai/nat/datarobot_llm_providers.py +130 -0
- datarobot_genai/nat/datarobot_mcp_client.py +266 -0
- datarobot_genai/nat/helpers.py +87 -0
- datarobot_genai/py.typed +0 -0
- datarobot_genai-0.2.31.dist-info/METADATA +145 -0
- datarobot_genai-0.2.31.dist-info/RECORD +125 -0
- datarobot_genai-0.2.31.dist-info/WHEEL +4 -0
- datarobot_genai-0.2.31.dist-info/entry_points.txt +5 -0
- datarobot_genai-0.2.31.dist-info/licenses/AUTHORS +2 -0
- 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_"
|