datarobot-genai 0.2.37__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datarobot_genai/core/agents/__init__.py +1 -1
- datarobot_genai/core/agents/base.py +5 -2
- datarobot_genai/core/chat/responses.py +6 -1
- datarobot_genai/core/utils/auth.py +188 -31
- datarobot_genai/crewai/__init__.py +1 -4
- datarobot_genai/crewai/agent.py +150 -17
- datarobot_genai/crewai/events.py +11 -4
- datarobot_genai/drmcp/__init__.py +4 -2
- datarobot_genai/drmcp/core/config.py +21 -1
- datarobot_genai/drmcp/core/mcp_instance.py +5 -49
- datarobot_genai/drmcp/core/routes.py +108 -13
- datarobot_genai/drmcp/core/tool_config.py +16 -0
- datarobot_genai/drmcp/core/utils.py +110 -0
- datarobot_genai/drmcp/test_utils/tool_base_ete.py +41 -26
- datarobot_genai/drmcp/tools/clients/gdrive.py +2 -0
- datarobot_genai/drmcp/tools/clients/microsoft_graph.py +141 -0
- datarobot_genai/drmcp/tools/clients/perplexity.py +173 -0
- datarobot_genai/drmcp/tools/clients/tavily.py +199 -0
- datarobot_genai/drmcp/tools/confluence/tools.py +43 -94
- datarobot_genai/drmcp/tools/gdrive/tools.py +44 -133
- datarobot_genai/drmcp/tools/jira/tools.py +19 -41
- datarobot_genai/drmcp/tools/microsoft_graph/tools.py +201 -32
- datarobot_genai/drmcp/tools/perplexity/__init__.py +0 -0
- datarobot_genai/drmcp/tools/perplexity/tools.py +117 -0
- datarobot_genai/drmcp/tools/predictive/data.py +1 -9
- datarobot_genai/drmcp/tools/predictive/deployment.py +0 -8
- datarobot_genai/drmcp/tools/predictive/deployment_info.py +91 -117
- datarobot_genai/drmcp/tools/predictive/model.py +0 -21
- datarobot_genai/drmcp/tools/predictive/predict_realtime.py +3 -0
- datarobot_genai/drmcp/tools/predictive/project.py +3 -19
- datarobot_genai/drmcp/tools/predictive/training.py +1 -19
- datarobot_genai/drmcp/tools/tavily/__init__.py +13 -0
- datarobot_genai/drmcp/tools/tavily/tools.py +141 -0
- datarobot_genai/langgraph/agent.py +10 -2
- datarobot_genai/llama_index/__init__.py +1 -1
- datarobot_genai/llama_index/agent.py +284 -5
- datarobot_genai/nat/agent.py +17 -6
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/METADATA +3 -1
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/RECORD +43 -40
- datarobot_genai/crewai/base.py +0 -159
- datarobot_genai/drmcp/core/tool_filter.py +0 -117
- datarobot_genai/llama_index/base.py +0 -299
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/WHEEL +0 -0
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/entry_points.txt +0 -0
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/licenses/AUTHORS +0 -0
- {datarobot_genai-0.2.37.dist-info → datarobot_genai-0.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,21 +22,17 @@ from fastmcp.exceptions import ToolError
|
|
|
22
22
|
from fastmcp.tools.tool import ToolResult
|
|
23
23
|
|
|
24
24
|
from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
|
|
25
|
-
from datarobot_genai.drmcp.tools.clients.gdrive import GOOGLE_DRIVE_FOLDER_MIME
|
|
26
25
|
from datarobot_genai.drmcp.tools.clients.gdrive import LIMIT
|
|
27
26
|
from datarobot_genai.drmcp.tools.clients.gdrive import MAX_PAGE_SIZE
|
|
28
27
|
from datarobot_genai.drmcp.tools.clients.gdrive import SUPPORTED_FIELDS
|
|
29
28
|
from datarobot_genai.drmcp.tools.clients.gdrive import SUPPORTED_FIELDS_STR
|
|
30
29
|
from datarobot_genai.drmcp.tools.clients.gdrive import GoogleDriveClient
|
|
31
|
-
from datarobot_genai.drmcp.tools.clients.gdrive import GoogleDriveError
|
|
32
30
|
from datarobot_genai.drmcp.tools.clients.gdrive import get_gdrive_access_token
|
|
33
31
|
|
|
34
32
|
logger = logging.getLogger(__name__)
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
@dr_mcp_tool(
|
|
38
|
-
tags={"google", "gdrive", "list", "search", "files", "find", "contents"}, enabled=False
|
|
39
|
-
)
|
|
35
|
+
@dr_mcp_tool(tags={"google", "gdrive", "list", "search", "files", "find", "contents"})
|
|
40
36
|
async def gdrive_find_contents(
|
|
41
37
|
*,
|
|
42
38
|
page_size: Annotated[
|
|
@@ -67,44 +63,34 @@ async def gdrive_find_contents(
|
|
|
67
63
|
) -> ToolResult:
|
|
68
64
|
"""
|
|
69
65
|
Search or list files in the user's Google Drive with pagination and filtering support.
|
|
70
|
-
Use this tool to discover file names and IDs for use with other tools.
|
|
66
|
+
Use this tool to discover GDrive file names and IDs for use with other tools.
|
|
71
67
|
|
|
72
68
|
Limit must be bigger than or equal to page size and it must be multiplication of page size.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
page size =
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
- page size = 10 limit = 50
|
|
73
|
+
- page size = 3 limit = 3
|
|
74
|
+
- page size = 12 limit = 36
|
|
77
75
|
"""
|
|
78
76
|
access_token = await get_gdrive_access_token()
|
|
79
77
|
if isinstance(access_token, ToolError):
|
|
80
78
|
raise access_token
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
except GoogleDriveError as e:
|
|
93
|
-
logger.error(f"Google Drive error listing files: {e}")
|
|
94
|
-
raise ToolError(str(e))
|
|
95
|
-
except Exception as e:
|
|
96
|
-
logger.error(f"Unexpected error listing Google Drive files: {e}")
|
|
97
|
-
raise ToolError(f"An unexpected error occurred while listing Google Drive files: {str(e)}")
|
|
80
|
+
async with GoogleDriveClient(access_token) as client:
|
|
81
|
+
data = await client.list_files(
|
|
82
|
+
page_size=page_size,
|
|
83
|
+
page_token=page_token,
|
|
84
|
+
query=query,
|
|
85
|
+
limit=limit,
|
|
86
|
+
folder_id=folder_id,
|
|
87
|
+
recursive=recursive,
|
|
88
|
+
)
|
|
98
89
|
|
|
99
90
|
filtered_fields = set(fields).intersection(SUPPORTED_FIELDS) if fields else SUPPORTED_FIELDS
|
|
100
91
|
number_of_files = len(data.files)
|
|
101
|
-
|
|
102
|
-
f"Next page token needed to fetch more data: {data.next_page_token}"
|
|
103
|
-
if data.next_page_token
|
|
104
|
-
else "There're no more pages."
|
|
105
|
-
)
|
|
92
|
+
|
|
106
93
|
return ToolResult(
|
|
107
|
-
content=f"Successfully listed {number_of_files} files. {next_page_info}",
|
|
108
94
|
structured_content={
|
|
109
95
|
"files": [
|
|
110
96
|
file.model_dump(by_alias=True, include=filtered_fields) for file in data.files
|
|
@@ -127,8 +113,7 @@ async def gdrive_read_content(
|
|
|
127
113
|
] = None,
|
|
128
114
|
) -> ToolResult:
|
|
129
115
|
"""
|
|
130
|
-
Retrieve the content of a specific file by its ID.
|
|
131
|
-
automatically exported to LLM-readable formats (Push-Down).
|
|
116
|
+
Retrieve the content of a specific Google drive file by its ID.
|
|
132
117
|
|
|
133
118
|
Usage:
|
|
134
119
|
- Basic: gdrive_read_content(file_id="1ABC123def456")
|
|
@@ -155,27 +140,10 @@ async def gdrive_read_content(
|
|
|
155
140
|
if isinstance(access_token, ToolError):
|
|
156
141
|
raise access_token
|
|
157
142
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
file_content = await client.read_file_content(file_id, target_format)
|
|
161
|
-
except GoogleDriveError as e:
|
|
162
|
-
logger.error(f"Google Drive error reading file content: {e}")
|
|
163
|
-
raise ToolError(str(e))
|
|
164
|
-
except Exception as e:
|
|
165
|
-
logger.error(f"Unexpected error reading Google Drive file content: {e}")
|
|
166
|
-
raise ToolError(
|
|
167
|
-
f"An unexpected error occurred while reading Google Drive file content: {str(e)}"
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
export_info = ""
|
|
171
|
-
if file_content.was_exported:
|
|
172
|
-
export_info = f" (exported from {file_content.original_mime_type})"
|
|
143
|
+
async with GoogleDriveClient(access_token) as client:
|
|
144
|
+
file_content = await client.read_file_content(file_id, target_format)
|
|
173
145
|
|
|
174
146
|
return ToolResult(
|
|
175
|
-
content=(
|
|
176
|
-
f"Successfully retrieved content of '{file_content.name}' "
|
|
177
|
-
f"({file_content.mime_type}){export_info}."
|
|
178
|
-
),
|
|
179
147
|
structured_content=file_content.as_flat_dict(),
|
|
180
148
|
)
|
|
181
149
|
|
|
@@ -239,28 +207,15 @@ async def gdrive_create_file(
|
|
|
239
207
|
if isinstance(access_token, ToolError):
|
|
240
208
|
raise access_token
|
|
241
209
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
250
|
-
except GoogleDriveError as e:
|
|
251
|
-
logger.error(f"Google Drive error creating file: {e}")
|
|
252
|
-
raise ToolError(str(e))
|
|
253
|
-
except Exception as e:
|
|
254
|
-
logger.error(f"Unexpected error creating Google Drive file: {e}")
|
|
255
|
-
raise ToolError(f"An unexpected error occurred while creating Google Drive file: {str(e)}")
|
|
256
|
-
|
|
257
|
-
file_type = "folder" if mime_type == GOOGLE_DRIVE_FOLDER_MIME else "file"
|
|
258
|
-
content_info = ""
|
|
259
|
-
if initial_content and mime_type != GOOGLE_DRIVE_FOLDER_MIME:
|
|
260
|
-
content_info = " with initial content"
|
|
210
|
+
async with GoogleDriveClient(access_token) as client:
|
|
211
|
+
created_file = await client.create_file(
|
|
212
|
+
name=name,
|
|
213
|
+
mime_type=mime_type,
|
|
214
|
+
parent_id=parent_id,
|
|
215
|
+
initial_content=initial_content,
|
|
216
|
+
)
|
|
261
217
|
|
|
262
218
|
return ToolResult(
|
|
263
|
-
content=f"Successfully created {file_type} '{created_file.name}'{content_info}.",
|
|
264
219
|
structured_content=created_file.as_flat_dict(),
|
|
265
220
|
)
|
|
266
221
|
|
|
@@ -313,44 +268,20 @@ async def gdrive_update_metadata(
|
|
|
313
268
|
if isinstance(access_token, ToolError):
|
|
314
269
|
raise access_token
|
|
315
270
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
trashed=trash,
|
|
323
|
-
)
|
|
324
|
-
except GoogleDriveError as e:
|
|
325
|
-
logger.error(f"Google Drive error updating file metadata: {e}")
|
|
326
|
-
raise ToolError(str(e))
|
|
327
|
-
except Exception as e:
|
|
328
|
-
logger.error(f"Unexpected error updating Google Drive file metadata: {e}")
|
|
329
|
-
raise ToolError(
|
|
330
|
-
f"An unexpected error occurred while updating Google Drive file metadata: {str(e)}"
|
|
271
|
+
async with GoogleDriveClient(access_token) as client:
|
|
272
|
+
updated_file = await client.update_file_metadata(
|
|
273
|
+
file_id=file_id,
|
|
274
|
+
new_name=new_name,
|
|
275
|
+
starred=starred,
|
|
276
|
+
trashed=trash,
|
|
331
277
|
)
|
|
332
278
|
|
|
333
|
-
changes: list[str] = []
|
|
334
|
-
if new_name is not None:
|
|
335
|
-
changes.append(f"renamed to '{new_name}'")
|
|
336
|
-
if starred is True:
|
|
337
|
-
changes.append("starred")
|
|
338
|
-
elif starred is False:
|
|
339
|
-
changes.append("unstarred")
|
|
340
|
-
if trash is True:
|
|
341
|
-
changes.append("moved to trash")
|
|
342
|
-
elif trash is False:
|
|
343
|
-
changes.append("restored from trash")
|
|
344
|
-
|
|
345
|
-
changes_description = ", ".join(changes)
|
|
346
|
-
|
|
347
279
|
return ToolResult(
|
|
348
|
-
content=f"Successfully updated file '{updated_file.name}': {changes_description}.",
|
|
349
280
|
structured_content=updated_file.as_flat_dict(),
|
|
350
281
|
)
|
|
351
282
|
|
|
352
283
|
|
|
353
|
-
@dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"})
|
|
284
|
+
@dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"}, enabled=False)
|
|
354
285
|
async def gdrive_manage_access(
|
|
355
286
|
*,
|
|
356
287
|
file_id: Annotated[str, "The ID of the file or folder."],
|
|
@@ -408,39 +339,19 @@ async def gdrive_manage_access(
|
|
|
408
339
|
if isinstance(access_token, ToolError):
|
|
409
340
|
raise access_token
|
|
410
341
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
transfer_ownership=transfer_ownership,
|
|
420
|
-
)
|
|
421
|
-
except GoogleDriveError as e:
|
|
422
|
-
logger.error(f"Google Drive permission operation failed: {e}")
|
|
423
|
-
raise ToolError(str(e))
|
|
424
|
-
except Exception as e:
|
|
425
|
-
logger.error(f"Unexpected error changing permissions for Google Drive file {file_id}: {e}")
|
|
426
|
-
raise ToolError(
|
|
427
|
-
f"Unexpected error changing permissions for Google Drive file {file_id}: {str(e)}"
|
|
342
|
+
async with GoogleDriveClient(access_token) as client:
|
|
343
|
+
permission_id = await client.manage_access(
|
|
344
|
+
file_id=file_id,
|
|
345
|
+
action=action,
|
|
346
|
+
role=role,
|
|
347
|
+
email_address=email_address,
|
|
348
|
+
permission_id=permission_id,
|
|
349
|
+
transfer_ownership=transfer_ownership,
|
|
428
350
|
)
|
|
429
351
|
|
|
430
352
|
# Build response
|
|
431
353
|
structured_content = {"affectedFileId": file_id}
|
|
432
354
|
if action == "add":
|
|
433
|
-
content = (
|
|
434
|
-
f"Successfully added role '{role}' for '{email_address}' for gdrive file '{file_id}'. "
|
|
435
|
-
f"New permission id '{permission_id}'."
|
|
436
|
-
)
|
|
437
355
|
structured_content["newPermissionId"] = permission_id
|
|
438
|
-
elif action == "update":
|
|
439
|
-
content = (
|
|
440
|
-
f"Successfully updated role '{role}' (permission '{permission_id}') "
|
|
441
|
-
f"for gdrive file '{file_id}'."
|
|
442
|
-
)
|
|
443
|
-
else: # action == "remove":
|
|
444
|
-
content = f"Successfully removed permission '{permission_id}' for gdrive file '{file_id}'."
|
|
445
356
|
|
|
446
|
-
return ToolResult(
|
|
357
|
+
return ToolResult(structured_content=structured_content)
|
|
@@ -53,10 +53,11 @@ async def jira_search_issues(
|
|
|
53
53
|
async with JiraClient(access_token) as client:
|
|
54
54
|
issues = await client.search_jira_issues(jql_query=jql_query, max_results=max_results)
|
|
55
55
|
|
|
56
|
-
n = len(issues)
|
|
57
56
|
return ToolResult(
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
structured_content={
|
|
58
|
+
"data": [issue.as_flat_dict() for issue in issues],
|
|
59
|
+
"count": len(issues),
|
|
60
|
+
},
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
|
|
@@ -72,17 +73,10 @@ async def jira_get_issue(
|
|
|
72
73
|
if isinstance(access_token, ToolError):
|
|
73
74
|
raise access_token
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
)
|
|
76
|
+
async with JiraClient(access_token) as client:
|
|
77
|
+
issue = await client.get_jira_issue(issue_key)
|
|
83
78
|
|
|
84
79
|
return ToolResult(
|
|
85
|
-
content=f"Successfully retrieved details for issue '{issue_key}'.",
|
|
86
80
|
structured_content=issue.as_flat_dict(),
|
|
87
81
|
)
|
|
88
82
|
|
|
@@ -118,20 +112,15 @@ async def jira_create_issue(
|
|
|
118
112
|
f"Unexpected issue type `{issue_type}`. Possible values are {possible_issue_types}."
|
|
119
113
|
)
|
|
120
114
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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)}")
|
|
115
|
+
async with JiraClient(access_token) as client:
|
|
116
|
+
issue_key = await client.create_jira_issue(
|
|
117
|
+
project_key=project_key,
|
|
118
|
+
summary=summary,
|
|
119
|
+
issue_type_id=issue_type_id,
|
|
120
|
+
description=description,
|
|
121
|
+
)
|
|
132
122
|
|
|
133
123
|
return ToolResult(
|
|
134
|
-
content=f"Successfully created issue '{issue_key}'.",
|
|
135
124
|
structured_content={"newIssueKey": issue_key, "projectKey": project_key},
|
|
136
125
|
)
|
|
137
126
|
|
|
@@ -179,18 +168,12 @@ async def jira_update_issue(
|
|
|
179
168
|
if isinstance(access_token, ToolError):
|
|
180
169
|
raise access_token
|
|
181
170
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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)}")
|
|
171
|
+
async with JiraClient(access_token) as client:
|
|
172
|
+
updated_fields = await client.update_jira_issue(
|
|
173
|
+
issue_key=issue_key, fields=fields_to_update
|
|
174
|
+
)
|
|
190
175
|
|
|
191
|
-
updated_fields_str = ",".join(updated_fields)
|
|
192
176
|
return ToolResult(
|
|
193
|
-
content=f"Successfully updated issue '{issue_key}'. Fields modified: {updated_fields_str}.",
|
|
194
177
|
structured_content={"updatedIssueKey": issue_key, "fields": updated_fields},
|
|
195
178
|
)
|
|
196
179
|
|
|
@@ -226,15 +209,10 @@ async def jira_transition_issue(
|
|
|
226
209
|
f"Possible values are {available_transitions_str}."
|
|
227
210
|
)
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
|
|
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)}")
|
|
212
|
+
async with JiraClient(access_token) as client:
|
|
213
|
+
await client.transition_jira_issue(issue_key=issue_key, transition_id=transition_id)
|
|
235
214
|
|
|
236
215
|
return ToolResult(
|
|
237
|
-
content=f"Successfully transitioned issue '{issue_key}' to status '{transition_name}'.",
|
|
238
216
|
structured_content={
|
|
239
217
|
"transitionedIssueKey": issue_key,
|
|
240
218
|
"newStatusName": transition_name,
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
18
|
from typing import Annotated
|
|
19
|
+
from typing import Any
|
|
20
|
+
from typing import Literal
|
|
19
21
|
|
|
20
22
|
from fastmcp.exceptions import ToolError
|
|
21
23
|
from fastmcp.tools.tool import ToolResult
|
|
22
24
|
|
|
23
25
|
from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
|
|
24
26
|
from datarobot_genai.drmcp.tools.clients.microsoft_graph import MicrosoftGraphClient
|
|
25
|
-
from datarobot_genai.drmcp.tools.clients.microsoft_graph import MicrosoftGraphError
|
|
26
27
|
from datarobot_genai.drmcp.tools.clients.microsoft_graph import get_microsoft_graph_access_token
|
|
27
28
|
from datarobot_genai.drmcp.tools.clients.microsoft_graph import validate_site_url
|
|
28
29
|
|
|
@@ -124,10 +125,6 @@ async def microsoft_graph_search_content(
|
|
|
124
125
|
- Documentation: https://learn.microsoft.com/en-us/graph/api/search-query
|
|
125
126
|
- Search concepts: https://learn.microsoft.com/en-us/graph/search-concept-files
|
|
126
127
|
|
|
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
128
|
"""
|
|
132
129
|
if not search_query:
|
|
133
130
|
raise ToolError("Argument validation error: 'search_query' cannot be empty.")
|
|
@@ -142,25 +139,16 @@ async def microsoft_graph_search_content(
|
|
|
142
139
|
if isinstance(access_token, ToolError):
|
|
143
140
|
raise access_token
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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)}"
|
|
142
|
+
async with MicrosoftGraphClient(access_token=access_token, site_url=site_url) as client:
|
|
143
|
+
items = await client.search_content(
|
|
144
|
+
search_query=search_query,
|
|
145
|
+
site_id=site_id,
|
|
146
|
+
from_offset=from_offset,
|
|
147
|
+
size=size,
|
|
148
|
+
entity_types=entity_types,
|
|
149
|
+
filters=filters,
|
|
150
|
+
include_hidden_content=include_hidden_content,
|
|
151
|
+
region=region,
|
|
164
152
|
)
|
|
165
153
|
|
|
166
154
|
results = []
|
|
@@ -180,12 +168,7 @@ async def microsoft_graph_search_content(
|
|
|
180
168
|
}
|
|
181
169
|
results.append(result_dict)
|
|
182
170
|
|
|
183
|
-
n = len(results)
|
|
184
171
|
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
172
|
structured_content={
|
|
190
173
|
"query": search_query,
|
|
191
174
|
"siteUrl": site_url,
|
|
@@ -193,7 +176,63 @@ async def microsoft_graph_search_content(
|
|
|
193
176
|
"from": from_offset,
|
|
194
177
|
"size": size,
|
|
195
178
|
"results": results,
|
|
196
|
-
"count":
|
|
179
|
+
"count": len(results),
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dr_mcp_tool(tags={"microsoft", "graph api", "sharepoint", "onedrive", "share"}, enabled=False)
|
|
185
|
+
async def microsoft_graph_share_item(
|
|
186
|
+
*,
|
|
187
|
+
file_id: Annotated[str, "The ID of the file or folder to share."],
|
|
188
|
+
document_library_id: Annotated[str, "The ID of the document library containing the item."],
|
|
189
|
+
recipient_emails: Annotated[list[str], "A list of email addresses to invite."],
|
|
190
|
+
role: Annotated[Literal["read", "write"], "The role to assign: 'read' or 'write'."] = "read",
|
|
191
|
+
send_invitation: Annotated[
|
|
192
|
+
bool, "Flag determining if recipients should be notified. Default False"
|
|
193
|
+
] = False,
|
|
194
|
+
) -> ToolResult | ToolError:
|
|
195
|
+
"""
|
|
196
|
+
Share a SharePoint or Onedrive file or folder with one or more users.
|
|
197
|
+
It works with internal users or existing guest users in the
|
|
198
|
+
tenant. It does NOT create new guest accounts and does NOT use the tenant-level
|
|
199
|
+
/invitations endpoint.
|
|
200
|
+
|
|
201
|
+
Microsoft Graph API is treating OneDrive and SharePoint resources as driveItem.
|
|
202
|
+
|
|
203
|
+
API Reference:
|
|
204
|
+
- DriveItem Resource Type: https://learn.microsoft.com/en-us/graph/api/resources/driveitem
|
|
205
|
+
- API Documentation: https://learn.microsoft.com/en-us/graph/api/driveitem-invite
|
|
206
|
+
"""
|
|
207
|
+
if not file_id or not file_id.strip():
|
|
208
|
+
raise ToolError("Argument validation error: 'file_id' cannot be empty.")
|
|
209
|
+
|
|
210
|
+
if not document_library_id or not document_library_id.strip():
|
|
211
|
+
raise ToolError("Argument validation error: 'document_library_id' cannot be empty.")
|
|
212
|
+
|
|
213
|
+
if not recipient_emails:
|
|
214
|
+
raise ToolError("Argument validation error: you must provide at least one 'recipient'.")
|
|
215
|
+
|
|
216
|
+
access_token = await get_microsoft_graph_access_token()
|
|
217
|
+
if isinstance(access_token, ToolError):
|
|
218
|
+
raise access_token
|
|
219
|
+
|
|
220
|
+
async with MicrosoftGraphClient(access_token=access_token) as client:
|
|
221
|
+
await client.share_item(
|
|
222
|
+
file_id=file_id,
|
|
223
|
+
document_library_id=document_library_id,
|
|
224
|
+
recipient_emails=recipient_emails,
|
|
225
|
+
role=role,
|
|
226
|
+
send_invitation=send_invitation,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return ToolResult(
|
|
230
|
+
structured_content={
|
|
231
|
+
"fileId": file_id,
|
|
232
|
+
"documentLibraryId": document_library_id,
|
|
233
|
+
"recipientEmails": recipient_emails,
|
|
234
|
+
"n": len(recipient_emails),
|
|
235
|
+
"role": role,
|
|
197
236
|
},
|
|
198
237
|
)
|
|
199
238
|
|
|
@@ -208,7 +247,8 @@ async def microsoft_graph_search_content(
|
|
|
208
247
|
"create",
|
|
209
248
|
"file",
|
|
210
249
|
"write",
|
|
211
|
-
}
|
|
250
|
+
},
|
|
251
|
+
enabled=False,
|
|
212
252
|
)
|
|
213
253
|
async def microsoft_create_file(
|
|
214
254
|
*,
|
|
@@ -265,7 +305,6 @@ async def microsoft_create_file(
|
|
|
265
305
|
)
|
|
266
306
|
|
|
267
307
|
return ToolResult(
|
|
268
|
-
content=f"File '{created_file.name}' created successfully.",
|
|
269
308
|
structured_content={
|
|
270
309
|
"file_name": created_file.name,
|
|
271
310
|
"destination": "onedrive" if is_personal_onedrive else "sharepoint",
|
|
@@ -275,3 +314,133 @@ async def microsoft_create_file(
|
|
|
275
314
|
"parentFolderId": created_file.parent_folder_id,
|
|
276
315
|
},
|
|
277
316
|
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@dr_mcp_tool(
|
|
320
|
+
tags={
|
|
321
|
+
"microsoft",
|
|
322
|
+
"graph api",
|
|
323
|
+
"sharepoint",
|
|
324
|
+
"onedrive",
|
|
325
|
+
"metadata",
|
|
326
|
+
"update",
|
|
327
|
+
"fields",
|
|
328
|
+
"compliance",
|
|
329
|
+
},
|
|
330
|
+
enabled=False,
|
|
331
|
+
)
|
|
332
|
+
async def microsoft_update_metadata(
|
|
333
|
+
*,
|
|
334
|
+
item_id: Annotated[str, "The ID of the file or list item to update."],
|
|
335
|
+
fields_to_update: Annotated[
|
|
336
|
+
dict[str, Any],
|
|
337
|
+
"Key-value pairs of metadata fields to modify. "
|
|
338
|
+
"For SharePoint list items: any custom column values. "
|
|
339
|
+
"For drive items: 'name' and/or 'description'.",
|
|
340
|
+
],
|
|
341
|
+
site_id: Annotated[
|
|
342
|
+
str | None,
|
|
343
|
+
"The site ID (required for SharePoint list items, along with list_id).",
|
|
344
|
+
] = None,
|
|
345
|
+
list_id: Annotated[
|
|
346
|
+
str | None,
|
|
347
|
+
"The list ID (required for SharePoint list items, along with site_id).",
|
|
348
|
+
] = None,
|
|
349
|
+
document_library_id: Annotated[
|
|
350
|
+
str | None,
|
|
351
|
+
"The drive ID (required for OneDrive/drive item updates). "
|
|
352
|
+
"Cannot be used together with site_id and list_id.",
|
|
353
|
+
] = None,
|
|
354
|
+
) -> ToolResult | ToolError:
|
|
355
|
+
"""
|
|
356
|
+
Update metadata on a SharePoint list item or OneDrive/SharePoint drive item.
|
|
357
|
+
|
|
358
|
+
**SharePoint List Items:** Provide site_id and list_id to update custom
|
|
359
|
+
column values on a list item. All custom columns can be updated.
|
|
360
|
+
|
|
361
|
+
**OneDrive/Drive Items:** Provide document_library_id to update drive item
|
|
362
|
+
properties. Only 'name' and 'description' fields can be updated.
|
|
363
|
+
|
|
364
|
+
**Context Requirements:**
|
|
365
|
+
- For SharePoint list items: Both site_id AND list_id are required
|
|
366
|
+
- For OneDrive/drive items: document_library_id is required
|
|
367
|
+
- Cannot specify both contexts simultaneously
|
|
368
|
+
|
|
369
|
+
**Examples:**
|
|
370
|
+
- SharePoint list item: Update a 'Status' column to 'Approved'
|
|
371
|
+
- Drive item: Rename a file or update its description
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
if not item_id or not item_id.strip():
|
|
375
|
+
raise ToolError("Error: item_id is required.")
|
|
376
|
+
if not fields_to_update:
|
|
377
|
+
raise ToolError("Error: fields_to_update is required and cannot be empty.")
|
|
378
|
+
|
|
379
|
+
# Validate context parameters
|
|
380
|
+
has_sharepoint_context = site_id is not None and list_id is not None
|
|
381
|
+
has_partial_sharepoint_context = (site_id is not None) != (list_id is not None)
|
|
382
|
+
has_drive_context = document_library_id is not None
|
|
383
|
+
|
|
384
|
+
if has_partial_sharepoint_context:
|
|
385
|
+
raise ToolError(
|
|
386
|
+
"Error: For SharePoint list items, both site_id and list_id must be provided."
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if has_sharepoint_context and has_drive_context:
|
|
390
|
+
raise ToolError(
|
|
391
|
+
"Error: Cannot specify both SharePoint (site_id + list_id) and OneDrive "
|
|
392
|
+
"(document_library_id) context. Choose one."
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if not has_sharepoint_context and not has_drive_context:
|
|
396
|
+
raise ToolError(
|
|
397
|
+
"Error: Must specify either SharePoint context (site_id + list_id) or "
|
|
398
|
+
"OneDrive context (document_library_id)."
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
access_token = await get_microsoft_graph_access_token()
|
|
402
|
+
if isinstance(access_token, ToolError):
|
|
403
|
+
raise access_token
|
|
404
|
+
|
|
405
|
+
async with MicrosoftGraphClient(access_token=access_token) as client:
|
|
406
|
+
result = await client.update_item_metadata(
|
|
407
|
+
item_id=item_id.strip(),
|
|
408
|
+
fields_to_update=fields_to_update,
|
|
409
|
+
site_id=site_id,
|
|
410
|
+
list_id=list_id,
|
|
411
|
+
drive_id=document_library_id,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
context_type = "sharepoint_list_item" if has_sharepoint_context else "drive_item"
|
|
415
|
+
structured: dict[str, Any] = {
|
|
416
|
+
"item_id": item_id,
|
|
417
|
+
"context_type": context_type,
|
|
418
|
+
"fields_updated": list(fields_to_update.keys()),
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# Add context-specific IDs for traceability
|
|
422
|
+
if has_sharepoint_context:
|
|
423
|
+
structured["site_id"] = site_id
|
|
424
|
+
structured["list_id"] = list_id
|
|
425
|
+
else:
|
|
426
|
+
structured["document_library_id"] = document_library_id
|
|
427
|
+
|
|
428
|
+
# Include relevant response data
|
|
429
|
+
if isinstance(result, dict):
|
|
430
|
+
# For drive items, include key properties if present
|
|
431
|
+
if has_drive_context:
|
|
432
|
+
if "id" in result:
|
|
433
|
+
structured["id"] = result["id"]
|
|
434
|
+
if "name" in result:
|
|
435
|
+
structured["name"] = result["name"]
|
|
436
|
+
if "webUrl" in result:
|
|
437
|
+
structured["webUrl"] = result["webUrl"]
|
|
438
|
+
if "description" in result:
|
|
439
|
+
structured["description"] = result.get("description")
|
|
440
|
+
# For list items, the response is the fields object itself
|
|
441
|
+
else:
|
|
442
|
+
structured["updated_fields"] = result
|
|
443
|
+
|
|
444
|
+
return ToolResult(
|
|
445
|
+
structured_content=structured,
|
|
446
|
+
)
|
|
File without changes
|