datarobot-genai 0.2.31__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. datarobot_genai/__init__.py +19 -0
  2. datarobot_genai/core/__init__.py +0 -0
  3. datarobot_genai/core/agents/__init__.py +43 -0
  4. datarobot_genai/core/agents/base.py +195 -0
  5. datarobot_genai/core/chat/__init__.py +19 -0
  6. datarobot_genai/core/chat/auth.py +146 -0
  7. datarobot_genai/core/chat/client.py +178 -0
  8. datarobot_genai/core/chat/responses.py +297 -0
  9. datarobot_genai/core/cli/__init__.py +18 -0
  10. datarobot_genai/core/cli/agent_environment.py +47 -0
  11. datarobot_genai/core/cli/agent_kernel.py +211 -0
  12. datarobot_genai/core/custom_model.py +141 -0
  13. datarobot_genai/core/mcp/__init__.py +0 -0
  14. datarobot_genai/core/mcp/common.py +218 -0
  15. datarobot_genai/core/telemetry_agent.py +126 -0
  16. datarobot_genai/core/utils/__init__.py +3 -0
  17. datarobot_genai/core/utils/auth.py +234 -0
  18. datarobot_genai/core/utils/urls.py +64 -0
  19. datarobot_genai/crewai/__init__.py +24 -0
  20. datarobot_genai/crewai/agent.py +42 -0
  21. datarobot_genai/crewai/base.py +159 -0
  22. datarobot_genai/crewai/events.py +117 -0
  23. datarobot_genai/crewai/mcp.py +59 -0
  24. datarobot_genai/drmcp/__init__.py +78 -0
  25. datarobot_genai/drmcp/core/__init__.py +13 -0
  26. datarobot_genai/drmcp/core/auth.py +165 -0
  27. datarobot_genai/drmcp/core/clients.py +180 -0
  28. datarobot_genai/drmcp/core/config.py +364 -0
  29. datarobot_genai/drmcp/core/config_utils.py +174 -0
  30. datarobot_genai/drmcp/core/constants.py +18 -0
  31. datarobot_genai/drmcp/core/credentials.py +190 -0
  32. datarobot_genai/drmcp/core/dr_mcp_server.py +350 -0
  33. datarobot_genai/drmcp/core/dr_mcp_server_logo.py +136 -0
  34. datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +13 -0
  35. datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +130 -0
  36. datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +70 -0
  37. datarobot_genai/drmcp/core/dynamic_prompts/register.py +205 -0
  38. datarobot_genai/drmcp/core/dynamic_prompts/utils.py +33 -0
  39. datarobot_genai/drmcp/core/dynamic_tools/__init__.py +14 -0
  40. datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  41. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +14 -0
  42. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +72 -0
  43. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +82 -0
  44. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +238 -0
  45. datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +228 -0
  46. datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +63 -0
  47. datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +162 -0
  48. datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +87 -0
  49. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +36 -0
  50. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +10 -0
  51. datarobot_genai/drmcp/core/dynamic_tools/register.py +254 -0
  52. datarobot_genai/drmcp/core/dynamic_tools/schema.py +532 -0
  53. datarobot_genai/drmcp/core/exceptions.py +25 -0
  54. datarobot_genai/drmcp/core/logging.py +98 -0
  55. datarobot_genai/drmcp/core/mcp_instance.py +515 -0
  56. datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
  57. datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
  58. datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
  59. datarobot_genai/drmcp/core/routes.py +439 -0
  60. datarobot_genai/drmcp/core/routes_utils.py +30 -0
  61. datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
  62. datarobot_genai/drmcp/core/telemetry.py +424 -0
  63. datarobot_genai/drmcp/core/tool_config.py +111 -0
  64. datarobot_genai/drmcp/core/tool_filter.py +117 -0
  65. datarobot_genai/drmcp/core/utils.py +138 -0
  66. datarobot_genai/drmcp/server.py +19 -0
  67. datarobot_genai/drmcp/test_utils/__init__.py +13 -0
  68. datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
  69. datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
  70. datarobot_genai/drmcp/test_utils/clients/base.py +300 -0
  71. datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
  72. datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
  73. datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +89 -0
  74. datarobot_genai/drmcp/test_utils/integration_mcp_server.py +109 -0
  75. datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +133 -0
  76. datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +107 -0
  77. datarobot_genai/drmcp/test_utils/test_interactive.py +205 -0
  78. datarobot_genai/drmcp/test_utils/tool_base_ete.py +220 -0
  79. datarobot_genai/drmcp/test_utils/utils.py +91 -0
  80. datarobot_genai/drmcp/tools/__init__.py +14 -0
  81. datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
  82. datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
  83. datarobot_genai/drmcp/tools/clients/confluence.py +584 -0
  84. datarobot_genai/drmcp/tools/clients/gdrive.py +832 -0
  85. datarobot_genai/drmcp/tools/clients/jira.py +334 -0
  86. datarobot_genai/drmcp/tools/clients/microsoft_graph.py +479 -0
  87. datarobot_genai/drmcp/tools/clients/s3.py +28 -0
  88. datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
  89. datarobot_genai/drmcp/tools/confluence/tools.py +321 -0
  90. datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  91. datarobot_genai/drmcp/tools/gdrive/tools.py +347 -0
  92. datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
  93. datarobot_genai/drmcp/tools/jira/tools.py +243 -0
  94. datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +13 -0
  95. datarobot_genai/drmcp/tools/microsoft_graph/tools.py +198 -0
  96. datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
  97. datarobot_genai/drmcp/tools/predictive/data.py +133 -0
  98. datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
  99. datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
  100. datarobot_genai/drmcp/tools/predictive/model.py +148 -0
  101. datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
  102. datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
  103. datarobot_genai/drmcp/tools/predictive/project.py +90 -0
  104. datarobot_genai/drmcp/tools/predictive/training.py +661 -0
  105. datarobot_genai/langgraph/__init__.py +0 -0
  106. datarobot_genai/langgraph/agent.py +341 -0
  107. datarobot_genai/langgraph/mcp.py +73 -0
  108. datarobot_genai/llama_index/__init__.py +16 -0
  109. datarobot_genai/llama_index/agent.py +50 -0
  110. datarobot_genai/llama_index/base.py +299 -0
  111. datarobot_genai/llama_index/mcp.py +79 -0
  112. datarobot_genai/nat/__init__.py +0 -0
  113. datarobot_genai/nat/agent.py +275 -0
  114. datarobot_genai/nat/datarobot_auth_provider.py +110 -0
  115. datarobot_genai/nat/datarobot_llm_clients.py +318 -0
  116. datarobot_genai/nat/datarobot_llm_providers.py +130 -0
  117. datarobot_genai/nat/datarobot_mcp_client.py +266 -0
  118. datarobot_genai/nat/helpers.py +87 -0
  119. datarobot_genai/py.typed +0 -0
  120. datarobot_genai-0.2.31.dist-info/METADATA +145 -0
  121. datarobot_genai-0.2.31.dist-info/RECORD +125 -0
  122. datarobot_genai-0.2.31.dist-info/WHEEL +4 -0
  123. datarobot_genai-0.2.31.dist-info/entry_points.txt +5 -0
  124. datarobot_genai-0.2.31.dist-info/licenses/AUTHORS +2 -0
  125. datarobot_genai-0.2.31.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,243 @@
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
+ from typing import Annotated
17
+ from typing import Any
18
+
19
+ from fastmcp.exceptions import ToolError
20
+ from fastmcp.tools.tool import ToolResult
21
+
22
+ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
23
+ from datarobot_genai.drmcp.tools.clients.atlassian import get_atlassian_access_token
24
+ from datarobot_genai.drmcp.tools.clients.jira import JiraClient
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @dr_mcp_tool(tags={"jira", "search", "issues"})
30
+ async def jira_search_issues(
31
+ *,
32
+ jql_query: Annotated[
33
+ str, "The JQL (Jira Query Language) string used to filter and search for issues."
34
+ ],
35
+ max_results: Annotated[int, "Maximum number of issues to return. Default is 50."] = 50,
36
+ ) -> ToolResult:
37
+ """
38
+ Search for Jira issues using a powerful JQL query string.
39
+
40
+ Refer to JQL documentation for advanced query construction:
41
+ JQL functions: https://support.atlassian.com/jira-service-management-cloud/docs/jql-functions/
42
+ JQL fields: https://support.atlassian.com/jira-service-management-cloud/docs/jql-fields/
43
+ JQL keywords: https://support.atlassian.com/jira-service-management-cloud/docs/use-advanced-search-with-jira-query-language-jql/
44
+ JQL operators: https://support.atlassian.com/jira-service-management-cloud/docs/jql-operators/
45
+ """
46
+ if not jql_query:
47
+ raise ToolError("Argument validation error: 'jql_query' cannot be empty.")
48
+
49
+ access_token = await get_atlassian_access_token()
50
+ if isinstance(access_token, ToolError):
51
+ raise access_token
52
+
53
+ async with JiraClient(access_token) as client:
54
+ issues = await client.search_jira_issues(jql_query=jql_query, max_results=max_results)
55
+
56
+ n = len(issues)
57
+ return ToolResult(
58
+ content=f"Successfully executed JQL query and retrieved {n} issue(s).",
59
+ structured_content={"data": [issue.as_flat_dict() for issue in issues], "count": n},
60
+ )
61
+
62
+
63
+ @dr_mcp_tool(tags={"jira", "read", "get", "issue"})
64
+ async def jira_get_issue(
65
+ *, issue_key: Annotated[str, "The key (ID) of the Jira issue to retrieve, e.g., 'PROJ-123'."]
66
+ ) -> ToolResult:
67
+ """Retrieve all fields and details for a single Jira issue by its key."""
68
+ if not issue_key:
69
+ raise ToolError("Argument validation error: 'issue_key' cannot be empty.")
70
+
71
+ access_token = await get_atlassian_access_token()
72
+ if isinstance(access_token, ToolError):
73
+ raise access_token
74
+
75
+ try:
76
+ async with JiraClient(access_token) as client:
77
+ issue = await client.get_jira_issue(issue_key)
78
+ except Exception as e:
79
+ logger.error(f"Unexpected error while getting Jira issue: {e}")
80
+ raise ToolError(
81
+ f"An unexpected error occurred while getting Jira issue '{issue_key}': {str(e)}"
82
+ )
83
+
84
+ return ToolResult(
85
+ content=f"Successfully retrieved details for issue '{issue_key}'.",
86
+ structured_content=issue.as_flat_dict(),
87
+ )
88
+
89
+
90
+ @dr_mcp_tool(tags={"jira", "create", "add", "issue"})
91
+ async def jira_create_issue(
92
+ *,
93
+ project_key: Annotated[str, "The key of the project where the issue should be created."],
94
+ summary: Annotated[str, "A brief summary or title for the new issue."],
95
+ issue_type: Annotated[str, "The type of issue to create (e.g., 'Task', 'Bug', 'Story')."],
96
+ description: Annotated[str | None, "Detailed description of the issue."] = None,
97
+ ) -> ToolResult:
98
+ """Create a new Jira issue with mandatory project, summary, and type information."""
99
+ if not all([project_key, summary, issue_type]):
100
+ raise ToolError(
101
+ "Argument validation error: project_key, summary, and issue_type are required fields."
102
+ )
103
+
104
+ access_token = await get_atlassian_access_token()
105
+ if isinstance(access_token, ToolError):
106
+ raise access_token
107
+
108
+ async with JiraClient(access_token) as client:
109
+ # Maybe we should cache it somehow?
110
+ # It'll be probably constant through whole mcp server lifecycle...
111
+ issue_types = await client.get_jira_issue_types(project_key=project_key)
112
+
113
+ try:
114
+ issue_type_id = issue_types[issue_type]
115
+ except KeyError:
116
+ possible_issue_types = ",".join(issue_types)
117
+ raise ToolError(
118
+ f"Unexpected issue type `{issue_type}`. Possible values are {possible_issue_types}."
119
+ )
120
+
121
+ try:
122
+ async with JiraClient(access_token) as client:
123
+ issue_key = await client.create_jira_issue(
124
+ project_key=project_key,
125
+ summary=summary,
126
+ issue_type_id=issue_type_id,
127
+ description=description,
128
+ )
129
+ except Exception as e:
130
+ logger.error(f"Unexpected error while creating Jira issue: {e}")
131
+ raise ToolError(f"An unexpected error occurred while creating Jira issue: {str(e)}")
132
+
133
+ return ToolResult(
134
+ content=f"Successfully created issue '{issue_key}'.",
135
+ structured_content={"newIssueKey": issue_key, "projectKey": project_key},
136
+ )
137
+
138
+
139
+ @dr_mcp_tool(tags={"jira", "update", "edit", "issue"})
140
+ async def jira_update_issue(
141
+ *,
142
+ issue_key: Annotated[str, "The key (ID) of the Jira issue to retrieve, e.g., 'PROJ-123'."],
143
+ fields_to_update: Annotated[
144
+ dict[str, Any],
145
+ "A dictionary of field names and their new values (e.g., {'summary': 'New content'}).",
146
+ ],
147
+ ) -> ToolResult:
148
+ """
149
+ Modify descriptive fields or custom fields on an existing Jira issue using its key.
150
+ If you want to update issue status you should use `jira_transition_issue` tool instead.
151
+
152
+ Some fields needs very specific schema to allow update.
153
+ You should follow jira rest api guidance.
154
+ Good example is description field:
155
+ "description": {
156
+ "type": "text",
157
+ "version": 1,
158
+ "text": [
159
+ {
160
+ "type": "paragraph",
161
+ "content": [
162
+ {
163
+ "type": "text",
164
+ "text": "[HERE YOU PUT REAL DESCRIPTION]"
165
+ }
166
+ ]
167
+ }
168
+ ]
169
+ }
170
+ """
171
+ if not issue_key:
172
+ raise ToolError("Argument validation error: 'issue_key' cannot be empty.")
173
+ if not fields_to_update or not isinstance(fields_to_update, dict):
174
+ raise ToolError(
175
+ "Argument validation error: 'fields_to_update' must be a non-empty dictionary."
176
+ )
177
+
178
+ access_token = await get_atlassian_access_token()
179
+ if isinstance(access_token, ToolError):
180
+ raise access_token
181
+
182
+ try:
183
+ async with JiraClient(access_token) as client:
184
+ updated_fields = await client.update_jira_issue(
185
+ issue_key=issue_key, fields=fields_to_update
186
+ )
187
+ except Exception as e:
188
+ logger.error(f"Unexpected error while updating Jira issue: {e}")
189
+ raise ToolError(f"An unexpected error occurred while updating Jira issue: {str(e)}")
190
+
191
+ updated_fields_str = ",".join(updated_fields)
192
+ return ToolResult(
193
+ content=f"Successfully updated issue '{issue_key}'. Fields modified: {updated_fields_str}.",
194
+ structured_content={"updatedIssueKey": issue_key, "fields": updated_fields},
195
+ )
196
+
197
+
198
+ @dr_mcp_tool(tags={"jira", "update", "transition", "issue"})
199
+ async def jira_transition_issue(
200
+ *,
201
+ issue_key: Annotated[str, "The key (ID) of the Jira issue to transition, e.g. 'PROJ-123'."],
202
+ transition_name: Annotated[
203
+ str, "The exact name of the target status/transition (e.g., 'In Progress')."
204
+ ],
205
+ ) -> ToolResult:
206
+ """
207
+ Move a Jira issue through its defined workflow to a new status.
208
+ This leverages Jira's workflow engine directly.
209
+ """
210
+ if not all([issue_key, transition_name]):
211
+ raise ToolError("Argument validation error: issue_key and transition name/ID are required.")
212
+
213
+ access_token = await get_atlassian_access_token()
214
+ if isinstance(access_token, ToolError):
215
+ raise access_token
216
+
217
+ async with JiraClient(access_token) as client:
218
+ available_transitions = await client.get_available_jira_transitions(issue_key=issue_key)
219
+
220
+ try:
221
+ transition_id = available_transitions[transition_name]
222
+ except KeyError:
223
+ available_transitions_str = ",".join(available_transitions)
224
+ raise ToolError(
225
+ f"Unexpected transition name `{transition_name}`. "
226
+ f"Possible values are {available_transitions_str}."
227
+ )
228
+
229
+ try:
230
+ async with JiraClient(access_token) as client:
231
+ await client.transition_jira_issue(issue_key=issue_key, transition_id=transition_id)
232
+ except Exception as e:
233
+ logger.error(f"Unexpected error while transitioning Jira issue: {e}")
234
+ raise ToolError(f"An unexpected error occurred while transitioning Jira issue: {str(e)}")
235
+
236
+ return ToolResult(
237
+ content=f"Successfully transitioned issue '{issue_key}' to status '{transition_name}'.",
238
+ structured_content={
239
+ "transitionedIssueKey": issue_key,
240
+ "newStatusName": transition_name,
241
+ "newStatusId": transition_id,
242
+ },
243
+ )
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,198 @@
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
+ """Microsoft Graph MCP tools for searching SharePoint and OneDrive content."""
16
+
17
+ import logging
18
+ from typing import Annotated
19
+
20
+ from fastmcp.exceptions import ToolError
21
+ from fastmcp.tools.tool import ToolResult
22
+
23
+ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
24
+ from datarobot_genai.drmcp.tools.clients.microsoft_graph import MicrosoftGraphClient
25
+ from datarobot_genai.drmcp.tools.clients.microsoft_graph import MicrosoftGraphError
26
+ from datarobot_genai.drmcp.tools.clients.microsoft_graph import get_microsoft_graph_access_token
27
+ from datarobot_genai.drmcp.tools.clients.microsoft_graph import validate_site_url
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dr_mcp_tool(
33
+ tags={
34
+ "microsoft",
35
+ "graph api",
36
+ "sharepoint",
37
+ "drive",
38
+ "list",
39
+ "search",
40
+ "files",
41
+ "find",
42
+ "contents",
43
+ }
44
+ )
45
+ async def microsoft_graph_search_content(
46
+ *,
47
+ search_query: Annotated[str, "The search string to find files, folders, or list items."],
48
+ site_url: Annotated[
49
+ str | None,
50
+ "Optional SharePoint site URL to scope the search "
51
+ "(e.g., https://tenant.sharepoint.com/sites/sitename). "
52
+ "If not provided, searches across all accessible sites.",
53
+ ] = None,
54
+ site_id: Annotated[
55
+ str | None,
56
+ "Optional ID of the site to scope the search. If provided, takes precedence over site_url.",
57
+ ] = None,
58
+ from_offset: Annotated[
59
+ int,
60
+ "The zero-based index of the first result to return. Use this for pagination. "
61
+ "Default: 0 (start from the beginning). To get the next page, increment by the size "
62
+ "value (e.g., first page: from=0 size=250, second page: from=250 size=250, "
63
+ "third page: from=500 size=250).",
64
+ ] = 0,
65
+ size: Annotated[
66
+ int,
67
+ "Maximum number of results to return in this request. Default is 250, max is 250. "
68
+ "The LLM should control pagination by making multiple calls with different 'from' values.",
69
+ ] = 250,
70
+ entity_types: Annotated[
71
+ list[str] | None,
72
+ "Optional list of entity types to search. Valid values: 'driveItem', 'listItem', "
73
+ "'site', 'list', 'drive'. Default: ['driveItem', 'listItem']. "
74
+ "Multiple types can be specified.",
75
+ ] = None,
76
+ filters: Annotated[
77
+ list[str] | None,
78
+ "Optional list of KQL filter expressions to refine search results "
79
+ "(e.g., ['fileType:docx', 'size>1000']).",
80
+ ] = None,
81
+ include_hidden_content: Annotated[
82
+ bool,
83
+ "Whether to include hidden content in search results. Only works with delegated "
84
+ "permissions, not application permissions. Default: False.",
85
+ ] = False,
86
+ region: Annotated[
87
+ str | None,
88
+ "Optional region code for application permissions (e.g., 'NAM', 'EUR', 'APC'). "
89
+ "Required when using application permissions to search SharePoint content in "
90
+ "specific regions.",
91
+ ] = None,
92
+ ) -> ToolResult | ToolError:
93
+ """
94
+ Search for SharePoint and OneDrive content using Microsoft Graph Search API.
95
+
96
+ Search Scope:
97
+ - When site_url or site_id is provided: searches within the specified SharePoint site
98
+ - When neither is provided: searches across all accessible SharePoint sites and OneDrive
99
+
100
+ Supported Entity Types:
101
+ - driveItem: Files and folders in document libraries and OneDrive
102
+ - listItem: Items in SharePoint lists
103
+ - site: SharePoint sites
104
+ - list: SharePoint lists
105
+ - drive: Document libraries/drives
106
+
107
+ Filtering:
108
+ - Filters use KQL (Keyword Query Language) syntax
109
+ - Multiple filters are combined with AND operators
110
+ - Examples: ['fileType:docx', 'size>1000', 'lastModifiedTime>2024-01-01']
111
+ - Filters are applied in addition to the search query
112
+
113
+ Pagination:
114
+ - Controlled via from_offset (zero-based index) and size parameters
115
+ - Maximum size per request: 250 results
116
+ - To paginate: increment from_offset by size value for each subsequent page
117
+ - Example pagination sequence:
118
+ * Page 1: from_offset=0, size=250 (returns results 0-249)
119
+ * Page 2: from_offset=250, size=250 (returns results 250-499)
120
+ * Page 3: from_offset=500, size=250 (returns results 500-749)
121
+
122
+ API Reference:
123
+ - Endpoint: POST /search/query
124
+ - Documentation: https://learn.microsoft.com/en-us/graph/api/search-query
125
+ - Search concepts: https://learn.microsoft.com/en-us/graph/search-concept-files
126
+
127
+ Permissions:
128
+ - Requires Sites.Read.All or Sites.Search.All permission
129
+ - include_hidden_content only works with delegated permissions
130
+ - region parameter is required for application permissions in multi-region environments
131
+ """
132
+ if not search_query:
133
+ raise ToolError("Argument validation error: 'search_query' cannot be empty.")
134
+
135
+ # Validate site_url if provided
136
+ if site_url:
137
+ validation_error = validate_site_url(site_url)
138
+ if validation_error:
139
+ raise ToolError(validation_error)
140
+
141
+ access_token = await get_microsoft_graph_access_token()
142
+ if isinstance(access_token, ToolError):
143
+ raise access_token
144
+
145
+ try:
146
+ async with MicrosoftGraphClient(access_token=access_token, site_url=site_url) as client:
147
+ items = await client.search_content(
148
+ search_query=search_query,
149
+ site_id=site_id,
150
+ from_offset=from_offset,
151
+ size=size,
152
+ entity_types=entity_types,
153
+ filters=filters,
154
+ include_hidden_content=include_hidden_content,
155
+ region=region,
156
+ )
157
+ except MicrosoftGraphError as e:
158
+ logger.error(f"Microsoft Graph error searching content: {e}")
159
+ raise ToolError(str(e))
160
+ except Exception as e:
161
+ logger.error(f"Unexpected error searching Microsoft Graph content: {e}", exc_info=True)
162
+ raise ToolError(
163
+ f"An unexpected error occurred while searching Microsoft Graph content: {str(e)}"
164
+ )
165
+
166
+ results = []
167
+ for item in items:
168
+ result_dict = {
169
+ "id": item.id, # Unique ID of the file, folder, or list item
170
+ "name": item.name,
171
+ "webUrl": item.web_url,
172
+ "size": item.size,
173
+ "createdDateTime": item.created_datetime,
174
+ "lastModifiedDateTime": item.last_modified_datetime,
175
+ "isFolder": item.is_folder,
176
+ "mimeType": item.mime_type,
177
+ # Document library/drive ID (driveId in Microsoft Graph API)
178
+ "documentLibraryId": item.drive_id,
179
+ "parentFolderId": item.parent_folder_id, # Parent folder ID
180
+ }
181
+ results.append(result_dict)
182
+
183
+ n = len(results)
184
+ return ToolResult(
185
+ content=(
186
+ f"Successfully searched Microsoft Graph and retrieved {n} result(s) for "
187
+ f"'{search_query}' (from={from_offset}, size={size})."
188
+ ),
189
+ structured_content={
190
+ "query": search_query,
191
+ "siteUrl": site_url,
192
+ "siteId": site_id,
193
+ "from": from_offset,
194
+ "size": size,
195
+ "results": results,
196
+ "count": n,
197
+ },
198
+ )
@@ -0,0 +1,27 @@
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
+ DataRobot MCP tools for interacting with DataRobot platform.
17
+
18
+ This package contains pre-built tools for DataRobot operations:
19
+ - data: Upload and manage datasets
20
+ - project: Create and manage projects
21
+ - model: List and interact with models
22
+ - training: Train models with autopilot
23
+ - deployment: Deploy and manage models
24
+ - predict: Make batch predictions
25
+ - predict_realtime: Make real-time predictions
26
+ - deployment_info: Get deployment information
27
+ """
@@ -0,0 +1,133 @@
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
+ import logging
17
+ import os
18
+ from typing import Annotated
19
+
20
+ from fastmcp.exceptions import ToolError
21
+ from fastmcp.tools.tool import ToolResult
22
+
23
+ from datarobot_genai.drmcp.core.clients import get_sdk_client
24
+ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
25
+ from datarobot_genai.drmcp.core.utils import is_valid_url
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @dr_mcp_tool(tags={"predictive", "data", "write", "upload", "catalog"})
31
+ async def upload_dataset_to_ai_catalog(
32
+ *,
33
+ file_path: Annotated[str, "The path to the dataset file to upload."] | None = None,
34
+ file_url: Annotated[str, "The URL to the dataset file to upload."] | None = None,
35
+ ) -> ToolError | ToolResult:
36
+ """Upload a dataset to the DataRobot AI Catalog / Data Registry."""
37
+ if not file_path and not file_url:
38
+ return ToolError("Either file_path or file_url must be provided.")
39
+ if file_path and file_url:
40
+ return ToolError("Please provide either file_path or file_url, not both.")
41
+
42
+ # Get client
43
+ client = get_sdk_client()
44
+ catalog_item = None
45
+ # If file path is provided, create dataset from file.
46
+ if file_path:
47
+ # Does file exist?
48
+ if not os.path.exists(file_path):
49
+ logger.error("File not found: %s", file_path)
50
+ return ToolError(f"File not found: {file_path}")
51
+ catalog_item = client.Dataset.create_from_file(file_path)
52
+ else:
53
+ # Does URL exist?
54
+ if file_url is None or not is_valid_url(file_url):
55
+ logger.error("Invalid file URL: %s", file_url)
56
+ return ToolError(f"Invalid file URL: {file_url}")
57
+ catalog_item = client.Dataset.create_from_url(file_url)
58
+
59
+ if not catalog_item:
60
+ return ToolError("Failed to upload dataset.")
61
+
62
+ return ToolResult(
63
+ content=f"Successfully uploaded dataset: {catalog_item.id}",
64
+ structured_content={
65
+ "dataset_id": catalog_item.id,
66
+ "dataset_version_id": catalog_item.version_id,
67
+ "dataset_name": catalog_item.name,
68
+ },
69
+ )
70
+
71
+
72
+ @dr_mcp_tool(tags={"predictive", "data", "read", "list", "catalog"})
73
+ async def list_ai_catalog_items() -> ToolResult:
74
+ """List all AI Catalog items (datasets) for the authenticated user."""
75
+ client = get_sdk_client()
76
+ datasets = client.Dataset.list()
77
+
78
+ if not datasets:
79
+ logger.info("No AI Catalog items found")
80
+ return ToolResult(
81
+ content="No AI Catalog items found.",
82
+ structured_content={"datasets": []},
83
+ )
84
+
85
+ datasets_dict = {ds.id: ds.name for ds in datasets}
86
+ datasets_count = len(datasets)
87
+
88
+ return ToolResult(
89
+ content=(
90
+ f"Found {datasets_count} AI Catalog items, here are the details:\n"
91
+ f"{json.dumps(datasets_dict, indent=2)}"
92
+ ),
93
+ structured_content={
94
+ "datasets": datasets_dict,
95
+ "count": datasets_count,
96
+ },
97
+ )
98
+
99
+
100
+ # from fastmcp import Context
101
+
102
+ # from datarobot_genai.drmcp.core.memory_management import MemoryManager, get_memory_manager
103
+
104
+
105
+ # @dr_mcp_tool()
106
+ # async def list_ai_catalog_items(
107
+ # ctx: Context, agent_id: str = None, storage_id: str = None
108
+ # ) -> str:
109
+ # """
110
+ # List all AI Catalog items (datasets) for the authenticated user.
111
+
112
+ # Returns:
113
+ # a resource id that can be used to retrieve the list of AI Catalog items using the
114
+ # get_resource tool
115
+ # """
116
+ # client = get_sdk_client()
117
+ # datasets = client.Dataset.list()
118
+ # if not datasets:
119
+ # logger.info("No AI Catalog items found")
120
+ # return "No AI Catalog items found."
121
+ # result = "\n".join(f"{ds.id}: {ds.name}" for ds in datasets)
122
+
123
+ # if MemoryManager.is_initialized():
124
+ # resource_id = await get_memory_manager().store_resource(
125
+ # data=result,
126
+ # memory_storage_id=storage_id,
127
+ # agent_identifier=agent_id,
128
+ # )
129
+ # else:
130
+ # raise ValueError("MemoryManager is not initialized")
131
+
132
+ # logger.info(f"Found {len(datasets)} AI Catalog items")
133
+ # return resource_id