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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/PKG-INFO +1 -1
  2. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/pyproject.toml +1 -1
  3. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/__init__.py +2 -2
  4. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/exceptions.py +0 -4
  5. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/logging.py +2 -2
  6. datarobot_genai-0.2.32/src/datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
  7. datarobot_genai-0.2.30/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py → datarobot_genai-0.2.32/src/datarobot_genai/drmcp/test_utils/clients/base.py +38 -40
  8. datarobot_genai-0.2.32/src/datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
  9. datarobot_genai-0.2.32/src/datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
  10. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +16 -16
  11. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +1 -1
  12. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/utils.py +1 -1
  13. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +91 -0
  14. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +100 -1
  15. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/data.py +5 -5
  16. datarobot_genai-0.2.32/src/datarobot_genai/drmcp/tools/predictive/model.py +183 -0
  17. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/project.py +2 -2
  18. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/training.py +14 -14
  19. datarobot_genai-0.2.32/src/datarobot_genai/py.typed +0 -0
  20. datarobot_genai-0.2.30/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -148
  21. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/.gitignore +0 -0
  22. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/AUTHORS +0 -0
  23. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/LICENSE +0 -0
  24. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/README.md +0 -0
  25. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/__init__.py +0 -0
  26. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/__init__.py +0 -0
  27. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/agents/__init__.py +0 -0
  28. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/agents/base.py +0 -0
  29. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/chat/__init__.py +0 -0
  30. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/chat/auth.py +0 -0
  31. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/chat/client.py +0 -0
  32. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/chat/responses.py +0 -0
  33. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/cli/__init__.py +0 -0
  34. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  35. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  36. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/custom_model.py +0 -0
  37. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  38. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/mcp/common.py +0 -0
  39. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  40. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/utils/__init__.py +0 -0
  41. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/utils/auth.py +0 -0
  42. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/core/utils/urls.py +0 -0
  43. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/crewai/__init__.py +0 -0
  44. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/crewai/agent.py +0 -0
  45. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/crewai/base.py +0 -0
  46. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/crewai/events.py +0 -0
  47. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/crewai/mcp.py +0 -0
  48. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  49. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  50. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  51. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/config.py +0 -0
  52. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  53. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  54. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  55. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  56. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  57. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  58. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  59. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  60. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  61. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  62. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  63. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  64. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  65. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  66. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  67. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  68. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  69. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  70. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  71. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  72. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  73. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  74. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  75. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  76. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  77. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  78. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  79. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  80. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  81. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  82. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  83. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  84. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/tool_config.py +0 -0
  85. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  86. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  87. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/server.py +0 -0
  88. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  89. {datarobot_genai-0.2.30/src/datarobot_genai/drmcp/tools/gdrive → datarobot_genai-0.2.32/src/datarobot_genai/drmcp/test_utils/clients}/__init__.py +0 -0
  90. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  91. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  92. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  93. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  94. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  95. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  96. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  97. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  98. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  99. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/microsoft_graph.py +0 -0
  100. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  101. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  102. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  103. {datarobot_genai-0.2.30/src/datarobot_genai/langgraph → datarobot_genai-0.2.32/src/datarobot_genai/drmcp/tools/gdrive}/__init__.py +0 -0
  104. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  105. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  106. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +0 -0
  107. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/microsoft_graph/tools.py +0 -0
  108. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  109. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  110. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  111. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  112. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  113. {datarobot_genai-0.2.30/src/datarobot_genai/nat → datarobot_genai-0.2.32/src/datarobot_genai/langgraph}/__init__.py +0 -0
  114. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/langgraph/agent.py +0 -0
  115. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/langgraph/mcp.py +0 -0
  116. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/llama_index/__init__.py +0 -0
  117. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/llama_index/agent.py +0 -0
  118. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/llama_index/base.py +0 -0
  119. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/llama_index/mcp.py +0 -0
  120. /datarobot_genai-0.2.30/src/datarobot_genai/py.typed → /datarobot_genai-0.2.32/src/datarobot_genai/nat/__init__.py +0 -0
  121. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/agent.py +0 -0
  122. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  123. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  124. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  125. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/datarobot_mcp_client.py +0 -0
  126. {datarobot_genai-0.2.30 → datarobot_genai-0.2.32}/src/datarobot_genai/nat/helpers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.30
3
+ Version: 0.2.32
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.2.30"
7
+ version = "0.2.32"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -19,11 +19,11 @@ A reusable library for building Model Context Protocol (MCP) servers with DataRo
19
19
  """
20
20
 
21
21
  # Export main server components
22
+ from datarobot_genai.drmcp.test_utils.clients.openai import OpenAILLMMCPClient
22
23
  from datarobot_genai.drmcp.test_utils.mcp_utils_ete import ete_test_mcp_session
23
24
  from datarobot_genai.drmcp.test_utils.mcp_utils_ete import get_dr_mcp_server_url
24
25
  from datarobot_genai.drmcp.test_utils.mcp_utils_ete import get_headers
25
26
  from datarobot_genai.drmcp.test_utils.mcp_utils_integration import integration_test_mcp_session
26
- from datarobot_genai.drmcp.test_utils.openai_llm_mcp_client import LLMMCPClient
27
27
  from datarobot_genai.drmcp.test_utils.tool_base_ete import ETETestExpectations
28
28
  from datarobot_genai.drmcp.test_utils.tool_base_ete import ToolBaseE2E
29
29
  from datarobot_genai.drmcp.test_utils.tool_base_ete import ToolCallTestExpectations
@@ -70,7 +70,7 @@ __all__ = [
70
70
  "get_dr_mcp_server_url",
71
71
  "get_headers",
72
72
  "ete_test_mcp_session",
73
- "LLMMCPClient",
73
+ "OpenAILLMMCPClient",
74
74
  "ETETestExpectations",
75
75
  "ToolBaseE2E",
76
76
  "ToolCallTestExpectations",
@@ -19,7 +19,3 @@ class DynamicToolRegistrationError(Exception):
19
19
 
20
20
  class DynamicPromptRegistrationError(Exception):
21
21
  """Exception raised for errors in the dynamic prompt registration process."""
22
-
23
-
24
- class MCPError(Exception):
25
- """Base class for MCP errors."""
@@ -20,7 +20,7 @@ from collections.abc import Callable
20
20
  from typing import Any
21
21
  from typing import TypeVar
22
22
 
23
- from .exceptions import MCPError
23
+ from fastmcp.exceptions import ToolError
24
24
 
25
25
  # Secret patterns to redact from logs
26
26
  SECRET_PATTERNS = [
@@ -93,6 +93,6 @@ def log_execution(func: F) -> F:
93
93
  return result
94
94
  except Exception as e:
95
95
  error_msg = _log_error(logger, func.__name__, e, args=args, kwargs=kwargs)
96
- raise MCPError(error_msg)
96
+ raise ToolError(error_msg)
97
97
 
98
98
  return wrapper # type: ignore[return-value]
@@ -0,0 +1,68 @@
1
+ # Copyright 2026 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
+ """Anthropic LLM MCP Client implementation (example).
16
+
17
+ This is an example implementation showing how easy it is to add a new LLM provider.
18
+ Anthropic's API is OpenAI-compatible, so we can use the OpenAI SDK with their endpoint.
19
+ """
20
+
21
+ import openai
22
+
23
+ from .base import BaseLLMMCPClient
24
+
25
+
26
+ class AnthropicMCPClient(BaseLLMMCPClient):
27
+ """
28
+ Client for interacting with LLMs via MCP using Anthropic Claude.
29
+
30
+ Note: Elicitation is handled at the protocol level by FastMCP's ctx.elicit().
31
+ Tools using FastMCP's built-in elicitation will work automatically.
32
+
33
+ Example:
34
+ ```python
35
+ config = {
36
+ "anthropic_api_key": "sk-ant-...",
37
+ "model": "claude-3-5-sonnet-20241022",
38
+ }
39
+ client = AnthropicMCPClient(str(config))
40
+ ```
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ config: str | dict,
46
+ ):
47
+ """
48
+ Initialize the LLM MCP client.
49
+
50
+ Args:
51
+ config: Configuration string or dict with:
52
+ - anthropic_api_key: Anthropic API key
53
+ - model: Model name (default: "claude-3-5-sonnet-20241022")
54
+ - save_llm_responses: Whether to save responses (default: True)
55
+ """
56
+ super().__init__(config)
57
+
58
+ def _create_llm_client(self, config_dict: dict) -> tuple[openai.OpenAI, str]:
59
+ """Create the LLM client for Anthropic (OpenAI-compatible endpoint)."""
60
+ anthropic_api_key = config_dict.get("anthropic_api_key")
61
+ model = config_dict.get("model", "claude-3-5-sonnet-20241022")
62
+
63
+ # Anthropic provides an OpenAI-compatible endpoint
64
+ client = openai.OpenAI(
65
+ api_key=anthropic_api_key,
66
+ base_url="https://api.anthropic.com/v1",
67
+ )
68
+ return client, model
@@ -1,4 +1,4 @@
1
- # Copyright 2025 DataRobot, Inc.
1
+ # Copyright 2026 DataRobot, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,7 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ """Base classes for LLM MCP clients."""
16
+
15
17
  import json
18
+ from abc import ABC
19
+ from abc import abstractmethod
16
20
  from ast import literal_eval
17
21
  from typing import Any
18
22
 
@@ -23,7 +27,7 @@ from mcp.types import ListToolsResult
23
27
  from mcp.types import TextContent
24
28
  from openai.types.chat.chat_completion import ChatCompletion
25
29
 
26
- from .utils import save_response_to_file
30
+ from datarobot_genai.drmcp.test_utils.utils import save_response_to_file
27
31
 
28
32
 
29
33
  class ToolCall:
@@ -44,9 +48,9 @@ class LLMResponse:
44
48
  self.tool_results = tool_results
45
49
 
46
50
 
47
- class LLMMCPClient:
51
+ class BaseLLMMCPClient(ABC):
48
52
  """
49
- Client for interacting with LLMs via MCP.
53
+ Base class for LLM MCP clients.
50
54
 
51
55
  Note: Elicitation is handled at the protocol level by FastMCP's ctx.elicit().
52
56
  Tools using FastMCP's built-in elicitation will work automatically.
@@ -54,54 +58,48 @@ class LLMMCPClient:
54
58
 
55
59
  def __init__(
56
60
  self,
57
- config: str,
61
+ config: str | dict,
58
62
  ):
59
63
  """
60
64
  Initialize the LLM MCP client.
61
65
 
62
66
  Args:
63
- config: Configuration string or dict with:
64
- - openai_api_key: OpenAI API key
65
- - openai_api_base: Optional Azure OpenAI endpoint
66
- - openai_api_deployment_id: Optional Azure deployment ID
67
- - openai_api_version: Optional Azure API version
68
- - model: Model name (default: "gpt-3.5-turbo")
69
- - save_llm_responses: Whether to save responses (default: True)
67
+ config: Configuration string or dict with provider-specific keys.
70
68
  """
71
- # Parse config string to extract parameters
69
+ config_dict = self._parse_config(config)
70
+ self.openai_client, self.model = self._create_llm_client(config_dict)
71
+ self.save_llm_responses = config_dict.get("save_llm_responses", True)
72
+ self.available_tools: list[dict[str, Any]] = []
73
+ self.available_prompts: list[dict[str, Any]] = []
74
+ self.available_resources: list[dict[str, Any]] = []
75
+
76
+ @staticmethod
77
+ def _parse_config(config: str | dict) -> dict:
78
+ """Parse config string to dict."""
72
79
  if isinstance(config, str):
73
80
  # Try JSON first (safer), fall back to literal_eval for Python dict strings
74
81
  try:
75
- config_dict = json.loads(config)
82
+ return json.loads(config)
76
83
  except json.JSONDecodeError:
77
84
  # Fall back to literal_eval for Python dict literal strings
78
- config_dict = literal_eval(config)
79
- else:
80
- config_dict = config
81
-
82
- openai_api_key = config_dict.get("openai_api_key")
83
- openai_api_base = config_dict.get("openai_api_base")
84
- openai_api_deployment_id = config_dict.get("openai_api_deployment_id")
85
- model = config_dict.get("model", "gpt-3.5-turbo")
86
- save_llm_responses = config_dict.get("save_llm_responses", True)
87
-
88
- if openai_api_base and openai_api_deployment_id:
89
- # Azure OpenAI
90
- self.openai_client = openai.AzureOpenAI(
91
- api_key=openai_api_key,
92
- azure_endpoint=openai_api_base,
93
- api_version=config_dict.get("openai_api_version", "2024-02-15-preview"),
94
- )
95
- self.model = openai_api_deployment_id
96
- else:
97
- # Regular OpenAI
98
- self.openai_client = openai.OpenAI(api_key=openai_api_key) # type: ignore[assignment]
99
- self.model = model
85
+ return literal_eval(config)
86
+ return config
100
87
 
101
- self.save_llm_responses = save_llm_responses
102
- self.available_tools: list[dict[str, Any]] = []
103
- self.available_prompts: list[dict[str, Any]] = []
104
- self.available_resources: list[dict[str, Any]] = []
88
+ @abstractmethod
89
+ def _create_llm_client(
90
+ self, config_dict: dict
91
+ ) -> tuple[openai.OpenAI | openai.AzureOpenAI, str]:
92
+ """
93
+ Create the LLM client.
94
+
95
+ Args:
96
+ config_dict: Parsed configuration dictionary
97
+
98
+ Returns
99
+ -------
100
+ Tuple of (LLM client instance, model name)
101
+ """
102
+ pass
105
103
 
106
104
  async def _add_mcp_tool_to_available_tools(self, mcp_session: ClientSession) -> None:
107
105
  """Add a tool to the available tools."""
@@ -0,0 +1,58 @@
1
+ # Copyright 2026 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
+ """DataRobot LLM Gateway MCP Client implementation."""
16
+
17
+ import openai
18
+
19
+ from .base import BaseLLMMCPClient
20
+
21
+
22
+ class DRLLMGatewayMCPClient(BaseLLMMCPClient):
23
+ """
24
+ Client for interacting with LLMs via MCP using DataRobot LLM Gateway.
25
+
26
+ Note: Elicitation is handled at the protocol level by FastMCP's ctx.elicit().
27
+ Tools using FastMCP's built-in elicitation will work automatically.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ config: str | dict,
33
+ ):
34
+ """
35
+ Initialize the LLM MCP client.
36
+
37
+ Args:
38
+ config: Configuration string or dict with:
39
+ - datarobot_api_token: DataRobot API token
40
+ - datarobot_endpoint: DataRobot endpoint URL (default: "https://app.datarobot.com/api/v2")
41
+ - model: Model name (default: "gpt-4o-mini")
42
+ - save_llm_responses: Whether to save responses (default: True)
43
+ """
44
+ super().__init__(config)
45
+
46
+ def _create_llm_client(self, config_dict: dict) -> tuple[openai.OpenAI, str]:
47
+ """Create the LLM client for DataRobot LLM Gateway."""
48
+ datarobot_api_token = config_dict.get("datarobot_api_token")
49
+ datarobot_endpoint = config_dict.get(
50
+ "datarobot_endpoint", "https://app.datarobot.com/api/v2"
51
+ )
52
+ model = config_dict.get("model", "gpt-4o-mini")
53
+
54
+ # Build gateway URL: {endpoint}/genai/llmgw
55
+ gateway_url = datarobot_endpoint.rstrip("/") + "/genai/llmgw"
56
+
57
+ client = openai.OpenAI(api_key=datarobot_api_token, base_url=gateway_url)
58
+ return client, model
@@ -0,0 +1,68 @@
1
+ # Copyright 2026 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
+ """OpenAI LLM MCP Client implementation."""
16
+
17
+ import openai
18
+
19
+ from .base import BaseLLMMCPClient
20
+
21
+
22
+ class OpenAILLMMCPClient(BaseLLMMCPClient):
23
+ """
24
+ Client for interacting with LLMs via MCP using OpenAI or Azure OpenAI.
25
+
26
+ Note: Elicitation is handled at the protocol level by FastMCP's ctx.elicit().
27
+ Tools using FastMCP's built-in elicitation will work automatically.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ config: str | dict,
33
+ ):
34
+ """
35
+ Initialize the LLM MCP client.
36
+
37
+ Args:
38
+ config: Configuration string or dict with:
39
+ - openai_api_key: OpenAI API key
40
+ - openai_api_base: Optional Azure OpenAI endpoint
41
+ - openai_api_deployment_id: Optional Azure deployment ID
42
+ - openai_api_version: Optional Azure API version
43
+ - model: Model name (default: "gpt-3.5-turbo")
44
+ - save_llm_responses: Whether to save responses (default: True)
45
+ """
46
+ super().__init__(config)
47
+
48
+ def _create_llm_client(
49
+ self, config_dict: dict
50
+ ) -> tuple[openai.OpenAI | openai.AzureOpenAI, str]:
51
+ """Create the LLM client for OpenAI or Azure OpenAI."""
52
+ openai_api_key = config_dict.get("openai_api_key")
53
+ openai_api_base = config_dict.get("openai_api_base")
54
+ openai_api_deployment_id = config_dict.get("openai_api_deployment_id")
55
+ model = config_dict.get("model", "gpt-3.5-turbo")
56
+
57
+ if openai_api_base and openai_api_deployment_id:
58
+ # Azure OpenAI
59
+ client = openai.AzureOpenAI(
60
+ api_key=openai_api_key,
61
+ azure_endpoint=openai_api_base,
62
+ api_version=config_dict.get("openai_api_version", "2024-02-15-preview"),
63
+ )
64
+ return client, openai_api_deployment_id
65
+ else:
66
+ # Regular OpenAI
67
+ client = openai.OpenAI(api_key=openai_api_key) # type: ignore[assignment]
68
+ return client, model
@@ -40,40 +40,40 @@ from mcp.types import ElicitResult
40
40
 
41
41
  from datarobot_genai.drmcp import get_dr_mcp_server_url
42
42
  from datarobot_genai.drmcp import get_headers
43
- from datarobot_genai.drmcp.test_utils.openai_llm_mcp_client import LLMMCPClient
44
- from datarobot_genai.drmcp.test_utils.openai_llm_mcp_client import LLMResponse
45
- from datarobot_genai.drmcp.test_utils.openai_llm_mcp_client import ToolCall
43
+ from datarobot_genai.drmcp.test_utils.clients.base import LLMResponse
44
+ from datarobot_genai.drmcp.test_utils.clients.base import ToolCall
45
+ from datarobot_genai.drmcp.test_utils.clients.dr_gateway import DRLLMGatewayMCPClient
46
46
 
47
47
  # Re-export for backwards compatibility
48
- __all__ = ["LLMMCPClient", "LLMResponse", "ToolCall", "test_mcp_interactive"]
48
+ __all__ = ["DRLLMGatewayMCPClient", "LLMResponse", "ToolCall", "test_mcp_interactive"]
49
49
 
50
50
 
51
51
  async def test_mcp_interactive() -> None:
52
52
  """Test the MCP server interactively with LLM agent."""
53
53
  # Check for required environment variables
54
- openai_api_key = os.environ.get("OPENAI_API_KEY")
55
- if not openai_api_key:
56
- print("❌ Error: OPENAI_API_KEY environment variable is required")
54
+ datarobot_api_token = os.environ.get("DATAROBOT_API_TOKEN")
55
+ if not datarobot_api_token:
56
+ print("❌ Error: DATAROBOT_API_TOKEN environment variable is required")
57
57
  print("Please set it in your .env file or export it")
58
58
  return
59
59
 
60
- # Optional Azure OpenAI settings
61
- openai_api_base = os.environ.get("OPENAI_API_BASE")
62
- openai_api_deployment_id = os.environ.get("OPENAI_API_DEPLOYMENT_ID")
63
- openai_api_version = os.environ.get("OPENAI_API_VERSION")
60
+ # Optional DataRobot settings
61
+ datarobot_endpoint = os.environ.get("DATAROBOT_ENDPOINT")
62
+ model = os.environ.get("MODEL")
64
63
 
65
64
  print("🤖 Initializing LLM MCP Client...")
66
65
 
67
66
  # Initialize the LLM client with elicitation handler
68
67
  config = {
69
- "openai_api_key": openai_api_key,
70
- "openai_api_base": openai_api_base,
71
- "openai_api_deployment_id": openai_api_deployment_id,
72
- "openai_api_version": openai_api_version,
68
+ "datarobot_api_token": datarobot_api_token,
73
69
  "save_llm_responses": False,
74
70
  }
71
+ if datarobot_endpoint:
72
+ config["datarobot_endpoint"] = datarobot_endpoint
73
+ if model:
74
+ config["model"] = model
75
75
 
76
- llm_client = LLMMCPClient(str(config))
76
+ llm_client = DRLLMGatewayMCPClient(str(config))
77
77
 
78
78
  # Get MCP server URL
79
79
  mcp_server_url = get_dr_mcp_server_url()
@@ -17,7 +17,7 @@ from typing import Any
17
17
 
18
18
  from pydantic import BaseModel
19
19
 
20
- from .openai_llm_mcp_client import LLMResponse
20
+ from .clients.base import LLMResponse
21
21
 
22
22
 
23
23
  class ToolCallTestExpectations(BaseModel):
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING
19
19
  from typing import Any
20
20
 
21
21
  if TYPE_CHECKING:
22
- from .openai_llm_mcp_client import LLMResponse
22
+ from .clients.base import LLMResponse
23
23
 
24
24
  from dotenv import load_dotenv
25
25
 
@@ -20,6 +20,7 @@ import logging
20
20
  import uuid
21
21
  from typing import Annotated
22
22
  from typing import Any
23
+ from typing import Literal
23
24
 
24
25
  import httpx
25
26
  from datarobot.auth.datarobot.exceptions import OAuthServiceClientErr
@@ -821,6 +822,96 @@ class GoogleDriveClient:
821
822
  headers={"Content-Type": f"multipart/related; boundary={boundary}"},
822
823
  )
823
824
 
825
+ async def manage_access(
826
+ self,
827
+ *,
828
+ file_id: str,
829
+ action: Literal["add", "update", "remove"],
830
+ role: Literal["reader", "commenter", "writer", "fileOrganizer", "organizer", "owner"]
831
+ | None = None,
832
+ email_address: str | None = None,
833
+ permission_id: str | None = None,
834
+ transfer_ownership: bool = False,
835
+ ) -> str:
836
+ """Manage access permissions for a Google Drive file or folder.
837
+
838
+ Adds, updates, or removes sharing permissions on an existing Google Drive
839
+ file or folder using the Google Drive Permissions API.
840
+
841
+ This method supports granting access to users or groups, changing access
842
+ roles, and revoking permissions. Ownership transfer is supported for files
843
+ in "My Drive" when explicitly requested.
844
+
845
+ Args:
846
+ file_id: The ID of the Google Drive file or folder whose permissions
847
+ are being managed.
848
+ action: The permission operation to perform.
849
+ role: The access role to assign or update. Valid values include
850
+ Required for "add" and "update" actions.
851
+ email_address: The email address of the user or group to grant access to.
852
+ Required for the "add" action.
853
+ permission_id: The ID of the permission to update or remove.
854
+ Required for "update" and "remove" actions.
855
+ transfer_ownership: Whether to transfer ownership of the file.
856
+ Only applicable when action="update" and role="owner".
857
+
858
+ Returns
859
+ -------
860
+ Permission id.
861
+ For "add" its newly added permission.
862
+ For "update"/"remove" its previous permission.
863
+
864
+ Raises
865
+ ------
866
+ GoogleDriveError: If the permission operation fails (invalid arguments,
867
+ insufficient permissions, resource not found, ownership transfer
868
+ not allowed, rate limited, etc.).
869
+ """
870
+ if not file_id.strip():
871
+ raise GoogleDriveError("Argument validation error: 'file_id' cannot be empty.")
872
+
873
+ if action == "add" and not email_address:
874
+ raise GoogleDriveError("'email_address' is required for action 'add'.")
875
+
876
+ if action in ("update", "remove") and not permission_id:
877
+ raise GoogleDriveError("'permission_id' is required for action 'update' or 'remove'.")
878
+
879
+ if action != "remove" and not role:
880
+ raise GoogleDriveError("'role' is required for action 'add' or 'update'.")
881
+
882
+ if action == "add":
883
+ response = await self._client.post(
884
+ url=f"/{file_id}/permissions",
885
+ json={
886
+ "type": "user",
887
+ "role": role,
888
+ "emailAddress": email_address,
889
+ },
890
+ params={"sendNotificationEmail": False, "supportsAllDrives": True},
891
+ )
892
+
893
+ elif action == "update":
894
+ response = await self._client.patch(
895
+ url=f"/{file_id}/permissions/{permission_id}",
896
+ json={"role": role},
897
+ params={"transferOwnership": transfer_ownership, "supportsAllDrives": True},
898
+ )
899
+
900
+ elif action == "remove":
901
+ response = await self._client.delete(url=f"/{file_id}/permissions/{permission_id}")
902
+
903
+ else:
904
+ raise GoogleDriveError(f"Invalid action '{action}'")
905
+
906
+ if response.status_code not in (200, 201, 204):
907
+ raise GoogleDriveError(f"Drive API error {response.status_code}: {response.text}")
908
+
909
+ if action == "add":
910
+ return response.json()["id"]
911
+
912
+ # Cannot be null here because of above validators
913
+ return permission_id # type: ignore
914
+
824
915
  async def __aenter__(self) -> "GoogleDriveClient":
825
916
  """Async context manager entry."""
826
917
  return self