datarobot-genai 0.2.0__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 (101) 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 +250 -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 +316 -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 +128 -0
  37. datarobot_genai/drmcp/core/dynamic_prompts/register.py +206 -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 +542 -0
  56. datarobot_genai/drmcp/core/mcp_server_tools.py +129 -0
  57. datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
  58. datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
  59. datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
  60. datarobot_genai/drmcp/core/routes.py +436 -0
  61. datarobot_genai/drmcp/core/routes_utils.py +30 -0
  62. datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
  63. datarobot_genai/drmcp/core/telemetry.py +424 -0
  64. datarobot_genai/drmcp/core/tool_filter.py +108 -0
  65. datarobot_genai/drmcp/core/utils.py +131 -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/integration_mcp_server.py +102 -0
  69. datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +96 -0
  70. datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +94 -0
  71. datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +234 -0
  72. datarobot_genai/drmcp/test_utils/tool_base_ete.py +151 -0
  73. datarobot_genai/drmcp/test_utils/utils.py +91 -0
  74. datarobot_genai/drmcp/tools/__init__.py +14 -0
  75. datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
  76. datarobot_genai/drmcp/tools/predictive/data.py +97 -0
  77. datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
  78. datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
  79. datarobot_genai/drmcp/tools/predictive/model.py +148 -0
  80. datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
  81. datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
  82. datarobot_genai/drmcp/tools/predictive/project.py +72 -0
  83. datarobot_genai/drmcp/tools/predictive/training.py +651 -0
  84. datarobot_genai/langgraph/__init__.py +0 -0
  85. datarobot_genai/langgraph/agent.py +341 -0
  86. datarobot_genai/langgraph/mcp.py +73 -0
  87. datarobot_genai/llama_index/__init__.py +16 -0
  88. datarobot_genai/llama_index/agent.py +50 -0
  89. datarobot_genai/llama_index/base.py +299 -0
  90. datarobot_genai/llama_index/mcp.py +79 -0
  91. datarobot_genai/nat/__init__.py +0 -0
  92. datarobot_genai/nat/agent.py +258 -0
  93. datarobot_genai/nat/datarobot_llm_clients.py +249 -0
  94. datarobot_genai/nat/datarobot_llm_providers.py +130 -0
  95. datarobot_genai/py.typed +0 -0
  96. datarobot_genai-0.2.0.dist-info/METADATA +139 -0
  97. datarobot_genai-0.2.0.dist-info/RECORD +101 -0
  98. datarobot_genai-0.2.0.dist-info/WHEEL +4 -0
  99. datarobot_genai-0.2.0.dist-info/entry_points.txt +3 -0
  100. datarobot_genai-0.2.0.dist-info/licenses/AUTHORS +2 -0
  101. datarobot_genai-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,316 @@
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 asyncio
16
+ import glob
17
+ import importlib
18
+ import logging
19
+ import os
20
+ from collections.abc import Callable
21
+ from typing import Any
22
+
23
+ from fastmcp import FastMCP
24
+ from starlette.middleware import Middleware
25
+
26
+ from .auth import initialize_oauth_middleware
27
+ from .config import get_config
28
+ from .credentials import get_credentials
29
+ from .dr_mcp_server_logo import log_server_custom_banner
30
+ from .dynamic_prompts.register import register_prompts_from_datarobot_prompt_management
31
+ from .dynamic_tools.deployment.register import register_tools_of_datarobot_deployments
32
+ from .logging import MCPLogging
33
+ from .mcp_instance import mcp
34
+ from .mcp_server_tools import get_all_available_tags # noqa # pylint: disable=unused-import
35
+ from .mcp_server_tools import get_tool_info_by_name # noqa # pylint: disable=unused-import
36
+ from .mcp_server_tools import list_tools_by_tags # noqa # pylint: disable=unused-import
37
+ from .memory_management.manager import MemoryManager
38
+ from .routes import register_routes
39
+ from .routes_utils import prefix_mount_path
40
+ from .server_life_cycle import BaseServerLifecycle
41
+ from .telemetry import OtelASGIMiddleware
42
+ from .telemetry import initialize_telemetry
43
+
44
+
45
+ def _import_modules_from_dir(
46
+ directory: str, package_prefix: str, module_name: str | None = None
47
+ ) -> None:
48
+ """Dynamically import all modules from a directory."""
49
+ if not os.path.exists(directory):
50
+ return
51
+ if module_name:
52
+ module_name = f"{package_prefix}.{module_name}"
53
+ try:
54
+ importlib.import_module(module_name)
55
+ except ImportError as e:
56
+ logging.warning(f"Failed to import module {module_name}: {e}")
57
+ else:
58
+ for file in glob.glob(os.path.join(directory, "*.py")):
59
+ if os.path.basename(file) != "__init__.py":
60
+ module_name = f"{package_prefix}.{os.path.splitext(os.path.basename(file))[0]}"
61
+ try:
62
+ importlib.import_module(module_name)
63
+ except ImportError as e:
64
+ logging.warning(f"Failed to import module {module_name}: {e}")
65
+
66
+
67
+ class DataRobotMCPServer:
68
+ """
69
+ DataRobot MCP server implementation using FastMCP framework.
70
+
71
+ This server can be extended by providing custom configuration, credentials,
72
+ and lifecycle handlers.
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ mcp: FastMCP,
78
+ transport: str = "streamable-http",
79
+ config_factory: Callable[[], Any] | None = None,
80
+ credentials_factory: Callable[[], Any] | None = None,
81
+ lifecycle: BaseServerLifecycle | None = None,
82
+ additional_module_paths: list[tuple[str, str]] | None = None,
83
+ ):
84
+ """
85
+ Initialize the server.
86
+
87
+ Args:
88
+ mcp: FastMCP instance
89
+ transport: Transport type ("streamable-http" or "stdio")
90
+ config_factory: Optional factory function for user config
91
+ credentials_factory: Optional factory function for user credentials
92
+ lifecycle: Optional lifecycle handler (defaults to BaseServerLifecycle())
93
+ additional_module_paths: Optional list of (directory, package_prefix) tuples for
94
+ loading additional modules
95
+ """
96
+ # Initialize config and logging
97
+ self._config = get_config()
98
+ MCPLogging(self._config.app_log_level)
99
+ self._logger = logging.getLogger(self.__class__.__name__)
100
+ self._logger.info(f"Config initialized: {self._config}")
101
+
102
+ # Initialize credentials
103
+ self._credentials = get_credentials()
104
+ self._logger.info("Credentials initialized")
105
+
106
+ self._user_config = config_factory() if config_factory else None
107
+ self._logger.info(f"User config initialized: {self._user_config}")
108
+ self._user_credentials = credentials_factory() if credentials_factory else None
109
+ self._logger.info("User credentials initialized")
110
+
111
+ # Initialize lifecycle
112
+ self._lifecycle = lifecycle if lifecycle else BaseServerLifecycle()
113
+ self._logger.info("Lifecycle initialized")
114
+
115
+ self._mcp = mcp
116
+ self._mcp_transport = transport
117
+
118
+ # Initialize telemetry
119
+ initialize_telemetry(mcp)
120
+
121
+ # Initialize OAuth middleware
122
+ initialize_oauth_middleware(mcp)
123
+
124
+ # Initialize memory manager if AWS credentials are available
125
+ self._memory_manager: MemoryManager | None = None
126
+ if self._config.enable_memory_management:
127
+ if self._credentials.has_aws_credentials():
128
+ self._logger.info("Initializing memory manager")
129
+ try:
130
+ self._memory_manager = MemoryManager.get_instance()
131
+ except Exception as e:
132
+ self._logger.error(f"Error initializing memory manager: {e}")
133
+ self._logger.info("Skipping memory manager initialization")
134
+ self._memory_manager = None
135
+ else:
136
+ self._logger.info(
137
+ "No AWS credentials found, skipping memory manager initialization"
138
+ )
139
+
140
+ # Load static tools modules
141
+ base_dir = os.path.dirname(os.path.dirname(__file__))
142
+ if self._config.enable_predictive_tools:
143
+ _import_modules_from_dir(
144
+ os.path.join(base_dir, "tools", "predictive"),
145
+ "datarobot_genai.drmcp.tools.predictive",
146
+ )
147
+
148
+ # Load memory management tools if available
149
+ if self._memory_manager:
150
+ _import_modules_from_dir(
151
+ directory=os.path.join(base_dir, "core", "memory_management"),
152
+ package_prefix="datarobot_genai.drmcp.core.memory_management",
153
+ module_name="memory_tools",
154
+ )
155
+
156
+ # Load additional recipe user modules if provided
157
+ if additional_module_paths:
158
+ for directory, package_prefix in additional_module_paths:
159
+ self._logger.info(f"Loading additional modules from {directory}")
160
+ _import_modules_from_dir(directory, package_prefix)
161
+
162
+ # Register HTTP routes if using streamable-http transport
163
+ if transport == "streamable-http":
164
+ register_routes(self._mcp)
165
+
166
+ def run(self, show_banner: bool = False) -> None:
167
+ """Run the DataRobot MCP server synchronously."""
168
+ try:
169
+ # Validate configuration
170
+ if not self._credentials.has_datarobot_credentials():
171
+ self._logger.error("DataRobot credentials not configured")
172
+ raise ValueError("Missing required DataRobot credentials")
173
+
174
+ if self._config.mcp_server_register_dynamic_tools_on_startup:
175
+ self._logger.info("Registering dynamic tools from deployments...")
176
+ asyncio.run(register_tools_of_datarobot_deployments())
177
+
178
+ if self._config.mcp_server_register_dynamic_prompts_on_startup:
179
+ self._logger.info("Registering dynamic prompts from prompt management...")
180
+ asyncio.run(register_prompts_from_datarobot_prompt_management())
181
+
182
+ # List registered tools, prompts, and resources before starting server
183
+ tools = asyncio.run(self._mcp._list_tools_mcp())
184
+ prompts = asyncio.run(self._mcp._list_prompts_mcp())
185
+ resources = asyncio.run(self._mcp._list_resources_mcp())
186
+
187
+ tools_count = len(tools)
188
+ prompts_count = len(prompts)
189
+ resources_count = len(resources)
190
+
191
+ self._logger.info(f"Registered tools: {tools_count}")
192
+ for tool in tools:
193
+ self._logger.info(f" > {tool.name}")
194
+ self._logger.info(f"Registered prompts: {prompts_count}")
195
+ for prompt in prompts:
196
+ self._logger.info(f" > {prompt.name}")
197
+ self._logger.info(f"Registered resources: {resources_count}")
198
+ for resource in resources:
199
+ self._logger.info(f" > {resource.name}")
200
+
201
+ # Execute pre-server start actions
202
+ asyncio.run(self._lifecycle.pre_server_start(self._mcp))
203
+
204
+ # Create event loop for async operations
205
+ loop = asyncio.new_event_loop()
206
+ asyncio.set_event_loop(loop)
207
+
208
+ async def run_server(show_banner: bool = show_banner) -> None:
209
+ # Start server in background based on transport type
210
+
211
+ if show_banner:
212
+ log_server_custom_banner(
213
+ self._mcp,
214
+ self._mcp_transport,
215
+ port=self._config.mcp_server_port,
216
+ tools_count=tools_count,
217
+ prompts_count=prompts_count,
218
+ resources_count=resources_count,
219
+ )
220
+
221
+ if self._mcp_transport == "stdio":
222
+ server_task = asyncio.create_task(self._mcp.run_stdio_async(show_banner=False))
223
+ elif self._mcp_transport == "streamable-http":
224
+ server_task = asyncio.create_task(
225
+ self._mcp.run_http_async(
226
+ transport="http",
227
+ middleware=[Middleware(OtelASGIMiddleware)],
228
+ show_banner=False,
229
+ port=self._config.mcp_server_port,
230
+ log_level=self._config.mcp_server_log_level,
231
+ host=self._config.mcp_server_host,
232
+ stateless_http=True,
233
+ path=prefix_mount_path("/mcp"),
234
+ )
235
+ )
236
+ else:
237
+ raise ValueError(f"Unsupported transport: {self._mcp_transport}")
238
+
239
+ # Give the server a moment to initialize
240
+ await asyncio.sleep(1)
241
+
242
+ # Execute post-server start actions
243
+ await self._lifecycle.post_server_start(self._mcp)
244
+
245
+ # Wait for server to complete
246
+ await server_task
247
+
248
+ # Start the server
249
+ self._logger.info("Starting MCP server...")
250
+ try:
251
+ loop.run_until_complete(run_server(show_banner=show_banner))
252
+ except KeyboardInterrupt:
253
+ self._logger.info("Server interrupted by user")
254
+ finally:
255
+ # Execute pre-shutdown actions
256
+ self._logger.info("Shutting down server...")
257
+ loop.run_until_complete(self._lifecycle.pre_server_shutdown(self._mcp))
258
+ loop.close()
259
+
260
+ except Exception as e:
261
+ self._logger.error(f"Server error: {e}")
262
+ raise
263
+
264
+
265
+ def create_mcp_server(
266
+ config_factory: Callable[[], Any] | None = None,
267
+ credentials_factory: Callable[[], Any] | None = None,
268
+ lifecycle: BaseServerLifecycle | None = None,
269
+ additional_module_paths: list[tuple[str, str]] | None = None,
270
+ transport: str = "streamable-http",
271
+ ) -> DataRobotMCPServer:
272
+ """
273
+ Create a DataRobot MCP server.
274
+
275
+ Args:
276
+ config_factory: Optional factory function for user config
277
+ credentials_factory: Optional factory function for user credentials
278
+ lifecycle: Optional lifecycle handler
279
+ additional_module_paths: Optional list of (directory, package_prefix) tuples
280
+ transport: Transport type ("streamable-http" or "stdio")
281
+
282
+ Returns
283
+ -------
284
+ Configured DataRobotMCPServer instance
285
+
286
+ Example:
287
+ ```python
288
+ # Basic usage with defaults
289
+ server = create_mcp_server()
290
+ server.run()
291
+
292
+ # With custom configuration
293
+ from myapp.config import get_my_config
294
+ from myapp.lifecycle import MyLifecycle
295
+
296
+ server = create_mcp_server(
297
+ config_factory=get_my_config,
298
+ lifecycle=MyLifecycle(),
299
+ additional_module_paths=[
300
+ ("/path/to/my/tools", "myapp.tools"),
301
+ ("/path/to/my/prompts", "myapp.prompts"),
302
+ ]
303
+ )
304
+ server.run()
305
+ ```
306
+ """
307
+ # Use the global mcp instance that tools are registered with
308
+
309
+ return DataRobotMCPServer(
310
+ mcp=mcp,
311
+ transport=transport,
312
+ config_factory=config_factory,
313
+ credentials_factory=credentials_factory,
314
+ lifecycle=lifecycle,
315
+ additional_module_paths=additional_module_paths,
316
+ )
@@ -0,0 +1,136 @@
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
+ from typing import Any
17
+
18
+ from fastmcp import FastMCP
19
+ from rich.align import Align
20
+ from rich.console import Console
21
+ from rich.console import Group
22
+ from rich.panel import Panel
23
+ from rich.table import Table
24
+ from rich.text import Text
25
+
26
+ from datarobot_genai import __version__ as datarobot_genai_version
27
+
28
+
29
+ # Green color #81FBA5 = RGB(129, 251, 165)
30
+ def _apply_green(text: str) -> str:
31
+ """Apply green color #81FBA5 to all characters in the text."""
32
+ # Apply ANSI escape code for RGB color #81FBA5 (129, 251, 165)
33
+ green_start = "\x1b[38;2;129;251;165m"
34
+ green_end = "\x1b[39m"
35
+ # Wrap the entire text (except trailing newline) with green color codes
36
+ lines = text.split("\n")
37
+ colored_lines = [green_start + line + green_end if line else "" for line in lines]
38
+ return "\n".join(colored_lines)
39
+
40
+
41
+ DR_LOGO_ASCII = _apply_green(r"""
42
+ ____ _ ____ _ _
43
+ | _ \ __ _| |_ __ _| _ \ ___ | |__ ___ | |_
44
+ | | | |/ _` | __/ _` | |_) / _ \| '_ \ / _ \| __|
45
+ | |_| | (_| | || (_| | _ < (_) | |_) | (_) | |_
46
+ |____/ \__,_|\__\__,_|_| \_\___/|_.__/ \___/ \__|
47
+ """)
48
+
49
+
50
+ def log_server_custom_banner(
51
+ server: FastMCP[Any],
52
+ transport: str,
53
+ *,
54
+ host: str | None = None,
55
+ port: int | None = None,
56
+ path: str | None = None,
57
+ tools_count: int | None = None,
58
+ prompts_count: int | None = None,
59
+ resources_count: int | None = None,
60
+ ) -> None:
61
+ """
62
+ Create and log a formatted banner with server information and logo.
63
+
64
+ Args:
65
+ transport: The transport protocol being used
66
+ server_name: Optional server name to display
67
+ host: Host address (for HTTP transports)
68
+ port: Port number (for HTTP transports)
69
+ path: Server path (for HTTP transports)
70
+ tools_count: Number of tools registered
71
+ prompts_count: Number of prompts registered
72
+ resources_count: Number of resources registered
73
+ """
74
+ # Create the logo text
75
+ # Use Text with no_wrap and markup disabled to preserve ANSI escape codes
76
+ logo_text = Text.from_ansi(DR_LOGO_ASCII, no_wrap=True)
77
+
78
+ # Create the main title
79
+ title_text = Text(f"DataRobot MCP Server {datarobot_genai_version}", style="dim green")
80
+ stats_text = Text(
81
+ f"{tools_count} tools, {prompts_count} prompts, {resources_count} resources",
82
+ style="bold green",
83
+ )
84
+
85
+ # Create the information table
86
+ info_table = Table.grid(padding=(0, 1))
87
+ info_table.add_column(style="bold", justify="center") # Emoji column
88
+ info_table.add_column(style="cyan", justify="left") # Label column
89
+ info_table.add_column(style="dim", justify="left") # Value column
90
+
91
+ match transport:
92
+ case "http" | "streamable-http":
93
+ display_transport = "HTTP"
94
+ case "sse":
95
+ display_transport = "SSE"
96
+ case "stdio":
97
+ display_transport = "STDIO"
98
+
99
+ info_table.add_row("🖥", "Server name:", Text(server.name + "\n", style="bold blue"))
100
+ info_table.add_row("📦", "Transport:", display_transport)
101
+ info_table.add_row("🌐", "MCP port:", str(port))
102
+
103
+ # Show connection info based on transport
104
+ if transport in ("http", "streamable-http", "sse") and host and port:
105
+ server_url = f"http://{host}:{port}"
106
+ if path:
107
+ server_url += f"/{path.lstrip('/')}"
108
+ info_table.add_row("🔗", "Server URL:", server_url)
109
+
110
+ # Add documentation link
111
+ info_table.add_row("", "", "")
112
+ info_table.add_row("📚", "Docs:", "https://docs.datarobot.com")
113
+ info_table.add_row("🚀", "Hosting:", "https://datarobot.com")
114
+
115
+ # Create panel with logo, title, and information using Group
116
+ panel_content = Group(
117
+ Align.center(logo_text),
118
+ "",
119
+ Align.center(title_text),
120
+ Align.center(stats_text),
121
+ "",
122
+ "",
123
+ Align.center(info_table),
124
+ )
125
+
126
+ panel = Panel(
127
+ panel_content,
128
+ border_style="dim",
129
+ padding=(1, 4),
130
+ # expand=False,
131
+ width=80, # Set max width for the pane
132
+ )
133
+
134
+ console = Console(stderr=True)
135
+ # Center the panel itself
136
+ console.print(Group("\n", Align.center(panel), "\n"))
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,130 @@
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 logging
16
+
17
+ from fastmcp.prompts.prompt import Prompt
18
+
19
+ from datarobot_genai.drmcp.core.dynamic_prompts.dr_lib import get_datarobot_prompt_template
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
23
+ from datarobot_genai.drmcp.core.dynamic_prompts.register import (
24
+ register_prompt_from_datarobot_prompt_management,
25
+ )
26
+ from datarobot_genai.drmcp.core.exceptions import DynamicPromptRegistrationError
27
+ from datarobot_genai.drmcp.core.mcp_instance import mcp
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ async def register_prompt_from_prompt_template_id_and_version(
33
+ prompt_template_id: str, prompt_template_version_id: str | None
34
+ ) -> Prompt:
35
+ """Register a Prompt for a specific prompt template ID and version.
36
+
37
+ Args:
38
+ prompt_template_id: The ID of the DataRobot prompt template.
39
+ prompt_template_version_id: Optional ID of the DataRobot prompt template version.
40
+ If not provided latest will be used
41
+
42
+ Raises
43
+ ------
44
+ DynamicPromptRegistrationError: If registration fails at any step.
45
+
46
+ Returns
47
+ -------
48
+ The registered Prompt instance.
49
+ """
50
+ prompt_template = get_datarobot_prompt_template(prompt_template_id)
51
+
52
+ if not prompt_template:
53
+ raise DynamicPromptRegistrationError("Registration failed. Could not find prompt template.")
54
+
55
+ if not prompt_template_version_id:
56
+ return await register_prompt_from_datarobot_prompt_management(
57
+ prompt_template=prompt_template
58
+ )
59
+
60
+ prompt_template_version = get_datarobot_prompt_template_version(
61
+ prompt_template_id, prompt_template_version_id
62
+ )
63
+
64
+ if not prompt_template_version:
65
+ raise DynamicPromptRegistrationError(
66
+ "Registration failed. Could not find prompt template version."
67
+ )
68
+
69
+ return await register_prompt_from_datarobot_prompt_management(
70
+ prompt_template=prompt_template, prompt_template_version=prompt_template_version
71
+ )
72
+
73
+
74
+ async def delete_registered_prompt_template(prompt_template_id: str) -> bool:
75
+ """Delete the prompt registered for the prompt template id in the MCP instance."""
76
+ prompt_templates_mappings = await mcp.get_prompt_mapping()
77
+ if prompt_template_id not in prompt_templates_mappings:
78
+ logger.debug(f"No prompt registered for prompt template id {prompt_template_id}")
79
+ return False
80
+
81
+ prompt_template_version_id, prompt_name = prompt_templates_mappings[prompt_template_id]
82
+ await mcp.remove_prompt_mapping(prompt_template_id, prompt_template_version_id)
83
+ logger.info(
84
+ f"Deleted prompt name {prompt_name} for prompt template id {prompt_template_id}, "
85
+ f"version {prompt_template_version_id}"
86
+ )
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)