datarobot-genai 0.1.67__py3-none-any.whl → 0.1.69__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/core/mcp/common.py +82 -48
- datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +45 -0
- datarobot_genai/drmcp/core/routes.py +14 -1
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/METADATA +1 -1
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/RECORD +9 -9
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/WHEEL +0 -0
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/entry_points.txt +0 -0
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/licenses/AUTHORS +0 -0
- {datarobot_genai-0.1.67.dist-info → datarobot_genai-0.1.69.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,16 +13,20 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
+
import logging
|
|
16
17
|
import re
|
|
18
|
+
from http import HTTPStatus
|
|
17
19
|
from typing import Any
|
|
18
20
|
from typing import Literal
|
|
19
|
-
from urllib.parse import urlparse
|
|
20
21
|
|
|
22
|
+
import requests
|
|
21
23
|
from datarobot.core.config import DataRobotAppFrameworkBaseSettings
|
|
22
24
|
from pydantic import field_validator
|
|
23
25
|
|
|
24
26
|
from datarobot_genai.core.utils.auth import AuthContextHeaderHandler
|
|
25
27
|
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
28
32
|
"""Configuration for MCP server connection.
|
|
@@ -39,6 +43,7 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
39
43
|
datarobot_api_token: str | None = None
|
|
40
44
|
authorization_context: dict[str, Any] | None = None
|
|
41
45
|
forwarded_headers: dict[str, str] | None = None
|
|
46
|
+
mcp_server_port: int | None = None
|
|
42
47
|
|
|
43
48
|
_auth_context_handler: AuthContextHeaderHandler | None = None
|
|
44
49
|
_server_config: dict[str, Any] | None = None
|
|
@@ -49,17 +54,14 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
49
54
|
if value is None:
|
|
50
55
|
return None
|
|
51
56
|
|
|
52
|
-
if not isinstance(value, str):
|
|
53
|
-
msg = "external_mcp_headers must be a JSON string"
|
|
54
|
-
raise TypeError(msg)
|
|
55
|
-
|
|
56
57
|
candidate = value.strip()
|
|
57
58
|
|
|
58
59
|
try:
|
|
59
60
|
json.loads(candidate)
|
|
60
|
-
except json.JSONDecodeError
|
|
61
|
+
except json.JSONDecodeError:
|
|
61
62
|
msg = "external_mcp_headers must be valid JSON"
|
|
62
|
-
|
|
63
|
+
logger.warning(msg)
|
|
64
|
+
return None
|
|
63
65
|
|
|
64
66
|
return candidate
|
|
65
67
|
|
|
@@ -69,15 +71,12 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
69
71
|
if value is None:
|
|
70
72
|
return None
|
|
71
73
|
|
|
72
|
-
if not isinstance(value, str):
|
|
73
|
-
msg = "mcp_deployment_id must be a string"
|
|
74
|
-
raise TypeError(msg)
|
|
75
|
-
|
|
76
74
|
candidate = value.strip()
|
|
77
75
|
|
|
78
76
|
if not re.fullmatch(r"[0-9a-fA-F]{24}", candidate):
|
|
79
77
|
msg = "mcp_deployment_id must be a valid 24-character hex ID"
|
|
80
|
-
|
|
78
|
+
logger.warning(msg)
|
|
79
|
+
return None
|
|
81
80
|
|
|
82
81
|
return candidate
|
|
83
82
|
|
|
@@ -112,6 +111,45 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
112
111
|
# Authorization context not available (e.g., in tests)
|
|
113
112
|
return {}
|
|
114
113
|
|
|
114
|
+
def _build_authenticated_headers(self) -> dict[str, str]:
|
|
115
|
+
"""Build headers for authenticated requests.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
Dictionary containing forwarded headers (if available) and authentication headers.
|
|
120
|
+
"""
|
|
121
|
+
headers: dict[str, str] = {}
|
|
122
|
+
if self.forwarded_headers:
|
|
123
|
+
headers.update(self.forwarded_headers)
|
|
124
|
+
headers.update(self._authorization_bearer_header())
|
|
125
|
+
headers.update(self._authorization_context_header())
|
|
126
|
+
return headers
|
|
127
|
+
|
|
128
|
+
def _check_localhost_server(self, url: str, timeout: float = 2.0) -> bool:
|
|
129
|
+
"""Check if MCP server is running on localhost.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
url : str
|
|
134
|
+
The URL to check.
|
|
135
|
+
timeout : float, optional
|
|
136
|
+
Request timeout in seconds (default: 2.0).
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
bool
|
|
141
|
+
True if server is running and responding with OK status, False otherwise.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
response = requests.get(url, timeout=timeout)
|
|
145
|
+
return (
|
|
146
|
+
response.status_code == HTTPStatus.OK
|
|
147
|
+
and response.json().get("message") == "DataRobot MCP Server is running"
|
|
148
|
+
)
|
|
149
|
+
except requests.RequestException as e:
|
|
150
|
+
logger.debug(f"Failed to connect to MCP server at {url}: {e}")
|
|
151
|
+
return False
|
|
152
|
+
|
|
115
153
|
def _build_server_config(self) -> dict[str, Any] | None:
|
|
116
154
|
"""
|
|
117
155
|
Get MCP server configuration.
|
|
@@ -121,34 +159,6 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
121
159
|
Server configuration dict with url, transport, and optional headers,
|
|
122
160
|
or None if not configured.
|
|
123
161
|
"""
|
|
124
|
-
if self.external_mcp_url:
|
|
125
|
-
# External MCP URL - no authentication needed
|
|
126
|
-
headers: dict[str, str] = {}
|
|
127
|
-
|
|
128
|
-
# Forward headers for localhost connections
|
|
129
|
-
if self.forwarded_headers:
|
|
130
|
-
try:
|
|
131
|
-
parsed_url = urlparse(self.external_mcp_url)
|
|
132
|
-
hostname = parsed_url.hostname or ""
|
|
133
|
-
# Check if hostname is localhost or 127.0.0.1
|
|
134
|
-
if hostname in ("localhost", "127.0.0.1", "::1"):
|
|
135
|
-
headers.update(self.forwarded_headers)
|
|
136
|
-
except Exception:
|
|
137
|
-
# If URL parsing fails, fall back to simple string check
|
|
138
|
-
if "localhost" in self.external_mcp_url or "127.0.0.1" in self.external_mcp_url:
|
|
139
|
-
headers.update(self.forwarded_headers)
|
|
140
|
-
|
|
141
|
-
# Merge external headers if provided
|
|
142
|
-
if self.external_mcp_headers:
|
|
143
|
-
external_headers = json.loads(self.external_mcp_headers)
|
|
144
|
-
headers.update(external_headers)
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
"url": self.external_mcp_url.rstrip("/"),
|
|
148
|
-
"transport": self.external_mcp_transport,
|
|
149
|
-
"headers": headers,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
162
|
if self.mcp_deployment_id:
|
|
153
163
|
# DataRobot deployment ID - requires authentication
|
|
154
164
|
if self.datarobot_endpoint is None:
|
|
@@ -165,15 +175,9 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
165
175
|
base_url = f"{base_url}/api/v2"
|
|
166
176
|
|
|
167
177
|
url = f"{base_url}/deployments/{self.mcp_deployment_id}/directAccess/mcp"
|
|
178
|
+
headers = self._build_authenticated_headers()
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
headers = {}
|
|
171
|
-
if self.forwarded_headers:
|
|
172
|
-
headers.update(self.forwarded_headers)
|
|
173
|
-
|
|
174
|
-
# Add authentication headers
|
|
175
|
-
headers.update(self._authorization_bearer_header())
|
|
176
|
-
headers.update(self._authorization_context_header())
|
|
180
|
+
logger.info(f"Using DataRobot hosted MCP deployment: {url}")
|
|
177
181
|
|
|
178
182
|
return {
|
|
179
183
|
"url": url,
|
|
@@ -181,4 +185,34 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
181
185
|
"headers": headers,
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
if self.external_mcp_url:
|
|
189
|
+
# External MCP URL - no authentication needed
|
|
190
|
+
headers = {}
|
|
191
|
+
|
|
192
|
+
# Merge external headers if provided
|
|
193
|
+
if self.external_mcp_headers:
|
|
194
|
+
external_headers = json.loads(self.external_mcp_headers)
|
|
195
|
+
headers.update(external_headers)
|
|
196
|
+
|
|
197
|
+
logger.info(f"Using external MCP URL: {self.external_mcp_url}")
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"url": self.external_mcp_url.rstrip("/"),
|
|
201
|
+
"transport": self.external_mcp_transport,
|
|
202
|
+
"headers": headers,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# No MCP configuration found, setup localhost if running locally
|
|
206
|
+
if self.mcp_server_port:
|
|
207
|
+
url = f"http://localhost:{self.mcp_server_port}"
|
|
208
|
+
if self._check_localhost_server(url):
|
|
209
|
+
headers = self._build_authenticated_headers()
|
|
210
|
+
logger.info(f"Using localhost MCP server: {url}")
|
|
211
|
+
return {
|
|
212
|
+
"url": f"{url}/mcp",
|
|
213
|
+
"transport": "streamable-http",
|
|
214
|
+
"headers": headers,
|
|
215
|
+
}
|
|
216
|
+
logger.warning(f"MCP server is not running or not responding at {url}")
|
|
217
|
+
|
|
184
218
|
return None
|
|
@@ -18,6 +18,8 @@ from fastmcp.prompts.prompt import Prompt
|
|
|
18
18
|
|
|
19
19
|
from datarobot_genai.drmcp.core.dynamic_prompts.dr_lib import get_datarobot_prompt_template
|
|
20
20
|
from datarobot_genai.drmcp.core.dynamic_prompts.dr_lib import get_datarobot_prompt_template_version
|
|
21
|
+
from datarobot_genai.drmcp.core.dynamic_prompts.dr_lib import get_datarobot_prompt_template_versions
|
|
22
|
+
from datarobot_genai.drmcp.core.dynamic_prompts.dr_lib import get_datarobot_prompt_templates
|
|
21
23
|
from datarobot_genai.drmcp.core.dynamic_prompts.register import (
|
|
22
24
|
register_prompt_from_datarobot_prompt_management,
|
|
23
25
|
)
|
|
@@ -83,3 +85,46 @@ async def delete_registered_prompt_template(prompt_template_id: str) -> bool:
|
|
|
83
85
|
f"version {prompt_template_version_id}"
|
|
84
86
|
)
|
|
85
87
|
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def refresh_registered_prompt_template() -> None:
|
|
91
|
+
"""Refresh all registered prompt templates in the MCP instance."""
|
|
92
|
+
prompt_templates = get_datarobot_prompt_templates()
|
|
93
|
+
prompt_templates_ids = {p.id for p in prompt_templates}
|
|
94
|
+
prompt_templates_versions = get_datarobot_prompt_template_versions(list(prompt_templates_ids))
|
|
95
|
+
|
|
96
|
+
mcp_prompt_templates_mappings = await mcp.get_prompt_mapping()
|
|
97
|
+
|
|
98
|
+
for prompt_template in prompt_templates:
|
|
99
|
+
prompt_template_versions = prompt_templates_versions.get(prompt_template.id)
|
|
100
|
+
if not prompt_template_versions:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
latest_version = max(prompt_template_versions, key=lambda v: v.version)
|
|
104
|
+
|
|
105
|
+
if prompt_template.id not in mcp_prompt_templates_mappings:
|
|
106
|
+
# New prompt template -> add
|
|
107
|
+
await register_prompt_from_datarobot_prompt_management(
|
|
108
|
+
prompt_template=prompt_template, prompt_template_version=latest_version
|
|
109
|
+
)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
mcp_prompt_template_version, mcp_prompt = mcp_prompt_templates_mappings[prompt_template.id]
|
|
113
|
+
|
|
114
|
+
if mcp_prompt_template_version != latest_version:
|
|
115
|
+
# Current version saved in MCP is not the latest one => update it
|
|
116
|
+
await register_prompt_from_datarobot_prompt_management(
|
|
117
|
+
prompt_template=prompt_template, prompt_template_version=latest_version
|
|
118
|
+
)
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Else => mcp_prompt_template_version == latest_version
|
|
122
|
+
# For now it means nothing changed as there's no possibility to edit promp template version.
|
|
123
|
+
|
|
124
|
+
for mcp_prompt_template_id, (
|
|
125
|
+
mcp_prompt_template_version_id,
|
|
126
|
+
_,
|
|
127
|
+
) in mcp_prompt_templates_mappings.items():
|
|
128
|
+
if mcp_prompt_template_id not in prompt_templates_ids:
|
|
129
|
+
# We need to also delete prompt templates that are
|
|
130
|
+
await mcp.remove_prompt_mapping(mcp_prompt_template_id, mcp_prompt_template_version_id)
|
|
@@ -19,6 +19,7 @@ from starlette.requests import Request
|
|
|
19
19
|
from starlette.responses import JSONResponse
|
|
20
20
|
|
|
21
21
|
from .dynamic_prompts.controllers import delete_registered_prompt_template
|
|
22
|
+
from .dynamic_prompts.controllers import refresh_registered_prompt_template
|
|
22
23
|
from .dynamic_prompts.controllers import register_prompt_from_prompt_template_id_and_version
|
|
23
24
|
from .dynamic_tools.deployment.controllers import delete_registered_tool_deployment
|
|
24
25
|
from .dynamic_tools.deployment.controllers import get_registered_tool_deployments
|
|
@@ -418,6 +419,18 @@ def register_routes(mcp: TaggedFastMCP) -> None:
|
|
|
418
419
|
)
|
|
419
420
|
except Exception as e:
|
|
420
421
|
return JSONResponse(
|
|
421
|
-
status_code=HTTPStatus.
|
|
422
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
422
423
|
content={"error": f"Failed to add prompt template: {str(e)}"},
|
|
423
424
|
)
|
|
425
|
+
|
|
426
|
+
@mcp.custom_route(prefix_mount_path("/registeredPrompts"), methods=["PUT"])
|
|
427
|
+
async def refresh_prompt_templates(_: Request) -> JSONResponse:
|
|
428
|
+
"""Refresh prompt templates."""
|
|
429
|
+
try:
|
|
430
|
+
await refresh_registered_prompt_template()
|
|
431
|
+
return JSONResponse(status_code=HTTPStatus.NO_CONTENT, content=None)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
return JSONResponse(
|
|
434
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
435
|
+
content={"error": f"Failed to refresh prompt templates: {str(e)}"},
|
|
436
|
+
)
|
|
@@ -13,7 +13,7 @@ datarobot_genai/core/cli/__init__.py,sha256=B93Yb6VavoZpatrh8ltCL6YglIfR5FHgytXb
|
|
|
13
13
|
datarobot_genai/core/cli/agent_environment.py,sha256=BJzQoiDvZF5gW4mFE71U0yeg-l72C--kxiE-fv6W194,1662
|
|
14
14
|
datarobot_genai/core/cli/agent_kernel.py,sha256=3XX58DQ6XPpWB_tn5m3iGb3XTfhZf5X3W9tc6ADieU4,7790
|
|
15
15
|
datarobot_genai/core/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
datarobot_genai/core/mcp/common.py,sha256=
|
|
16
|
+
datarobot_genai/core/mcp/common.py,sha256=Y8SjuquUODKEfI7T9X-QuTMKdIlpCWFI1b3xs6tmHFA,7812
|
|
17
17
|
datarobot_genai/core/utils/__init__.py,sha256=VxtRUz6iwb04eFQQy0zqTNXLAkYpPXcJxVoKV0nOdXk,59
|
|
18
18
|
datarobot_genai/core/utils/auth.py,sha256=Xo1PxVr6oMgtMHkmHdS02klDKK1cyDpjGvIMF4Tx0Lo,7874
|
|
19
19
|
datarobot_genai/core/utils/urls.py,sha256=tk0t13duDEPcmwz2OnS4vwEdatruiuX8lnxMMhSaJik,2289
|
|
@@ -37,14 +37,14 @@ datarobot_genai/drmcp/core/exceptions.py,sha256=eqsGI-lxybgvWL5w4BFhbm3XzH1eU5te
|
|
|
37
37
|
datarobot_genai/drmcp/core/logging.py,sha256=Y_hig4eBWiXGaVV7B_3wBcaYVRNH4ydptbEQhrP9-mY,3414
|
|
38
38
|
datarobot_genai/drmcp/core/mcp_instance.py,sha256=wMsP39xqTmNBYqd49olEQb5UHTSsxj6BOIoIElorRB0,19235
|
|
39
39
|
datarobot_genai/drmcp/core/mcp_server_tools.py,sha256=odNZKozfx0VV38SLZHw9lY0C0JM_JnRI06W3BBXnyE4,4278
|
|
40
|
-
datarobot_genai/drmcp/core/routes.py,sha256=
|
|
40
|
+
datarobot_genai/drmcp/core/routes.py,sha256=nrgzYkiWWcwSy0BUJ-k5AuXdUHNrnGRudCo4AhtYvlY,17856
|
|
41
41
|
datarobot_genai/drmcp/core/routes_utils.py,sha256=vSseXWlplMSnRgoJgtP_rHxWSAVYcx_tpTv4lyTpQoc,944
|
|
42
42
|
datarobot_genai/drmcp/core/server_life_cycle.py,sha256=WKGJWGxalvqxupzJ2y67Kklc_9PgpZT0uyjlv_sr5wc,3419
|
|
43
43
|
datarobot_genai/drmcp/core/telemetry.py,sha256=NEkSTC1w6uQgtukLHI-sWvR4EMgInysgATcvfQ5CplM,15378
|
|
44
44
|
datarobot_genai/drmcp/core/tool_filter.py,sha256=tLOcG50QBvS48cOVHM6OqoODYiiS6KeM_F-2diaHkW0,2858
|
|
45
45
|
datarobot_genai/drmcp/core/utils.py,sha256=dSjrayWVcnC5GxQcvOIOSHaoEymPIVtG_s2ZBMlmSOw,4336
|
|
46
46
|
datarobot_genai/drmcp/core/dynamic_prompts/__init__.py,sha256=y4yapzp3KnFMzSR6HlNDS4uSuyNT7I1iPBvaCLsS0sU,577
|
|
47
|
-
datarobot_genai/drmcp/core/dynamic_prompts/controllers.py,sha256=
|
|
47
|
+
datarobot_genai/drmcp/core/dynamic_prompts/controllers.py,sha256=AGJlKqgHRO0Kd7Gl-Ulw9KYBgzjTTFXWBvOUF-SuKUI,5454
|
|
48
48
|
datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py,sha256=IEdD2Gqm4SfUdiXJB99RiWxkN6frGaxJ2SfATetMM3c,4243
|
|
49
49
|
datarobot_genai/drmcp/core/dynamic_prompts/register.py,sha256=5AEh1m8GX-gPZHUdiE1VATt7IKJQk-eThcxh01sWn0I,7204
|
|
50
50
|
datarobot_genai/drmcp/core/dynamic_prompts/utils.py,sha256=BZ3792AgfvYlwL0_J0MzQfGecyEA5_OKUMynEZYzCds,1136
|
|
@@ -93,9 +93,9 @@ datarobot_genai/nat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
93
93
|
datarobot_genai/nat/agent.py,sha256=siBLDWAff2-JwZ8Q3iNpM_e4_IoSwG9IvY0hyEjNenw,10292
|
|
94
94
|
datarobot_genai/nat/datarobot_llm_clients.py,sha256=IZq_kooUL8QyDTkpEreszLQk9vCzg6-FbTjIkXR9wc0,7203
|
|
95
95
|
datarobot_genai/nat/datarobot_llm_providers.py,sha256=lOVaL_0Fl6-7GFYl3HmfqttqKpKt-2w8o92P3T7B6cU,3683
|
|
96
|
-
datarobot_genai-0.1.
|
|
97
|
-
datarobot_genai-0.1.
|
|
98
|
-
datarobot_genai-0.1.
|
|
99
|
-
datarobot_genai-0.1.
|
|
100
|
-
datarobot_genai-0.1.
|
|
101
|
-
datarobot_genai-0.1.
|
|
96
|
+
datarobot_genai-0.1.69.dist-info/METADATA,sha256=ANM5ghmbD_jGo8UHS87Y63jcMZlZY4Pnm7suIWRdtsg,5918
|
|
97
|
+
datarobot_genai-0.1.69.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
98
|
+
datarobot_genai-0.1.69.dist-info/entry_points.txt,sha256=CZhmZcSyt_RBltgLN_b9xasJD6J5SaDc_z7K0wuOY9Y,150
|
|
99
|
+
datarobot_genai-0.1.69.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
|
|
100
|
+
datarobot_genai-0.1.69.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
|
|
101
|
+
datarobot_genai-0.1.69.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|