datarobot-genai 0.2.25__tar.gz → 0.2.27__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 (118) hide show
  1. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/PKG-INFO +1 -1
  2. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/pyproject.toml +1 -1
  3. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/mcp_instance.py +34 -15
  4. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +127 -0
  5. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +95 -2
  6. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/.gitignore +0 -0
  7. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/AUTHORS +0 -0
  8. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/LICENSE +0 -0
  9. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/README.md +0 -0
  10. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/__init__.py +0 -0
  11. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/__init__.py +0 -0
  12. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/agents/__init__.py +0 -0
  13. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/agents/base.py +0 -0
  14. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/__init__.py +0 -0
  15. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/auth.py +0 -0
  16. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/client.py +0 -0
  17. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/responses.py +0 -0
  18. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/__init__.py +0 -0
  19. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  20. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  21. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/custom_model.py +0 -0
  22. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  23. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/mcp/common.py +0 -0
  24. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  25. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/__init__.py +0 -0
  26. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/auth.py +0 -0
  27. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/urls.py +0 -0
  28. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/__init__.py +0 -0
  29. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/agent.py +0 -0
  30. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/base.py +0 -0
  31. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/events.py +0 -0
  32. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/mcp.py +0 -0
  33. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/__init__.py +0 -0
  34. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  35. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  36. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  37. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/config.py +0 -0
  38. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  39. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  40. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  41. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  42. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  43. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  44. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  45. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  46. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  47. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  48. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  49. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  50. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  51. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  52. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  53. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  54. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  55. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  56. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  57. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  58. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  59. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  60. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  61. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  62. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  63. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  64. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  65. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  66. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  67. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  68. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  69. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  70. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  71. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/tool_config.py +0 -0
  72. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  73. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  74. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/server.py +0 -0
  75. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  76. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  77. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  78. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  79. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  80. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +0 -0
  81. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +0 -0
  82. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  83. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  84. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  85. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  86. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  87. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  88. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  89. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  90. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  91. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  92. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  93. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  94. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  95. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  96. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  97. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  98. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  99. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  100. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  101. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  102. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  103. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  104. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/__init__.py +0 -0
  105. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/agent.py +0 -0
  106. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/mcp.py +0 -0
  107. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/__init__.py +0 -0
  108. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/agent.py +0 -0
  109. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/base.py +0 -0
  110. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/mcp.py +0 -0
  111. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/__init__.py +0 -0
  112. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/agent.py +0 -0
  113. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  114. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  115. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  116. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_mcp_client.py +0 -0
  117. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/helpers.py +0 -0
  118. {datarobot_genai-0.2.25 → datarobot_genai-0.2.27}/src/datarobot_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.25
3
+ Version: 0.2.27
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.25"
7
+ version = "0.2.27"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -16,6 +16,7 @@ import logging
16
16
  from collections.abc import Callable
17
17
  from functools import wraps
18
18
  from typing import Any
19
+ from typing import TypedDict
19
20
 
20
21
  from fastmcp import Context
21
22
  from fastmcp import FastMCP
@@ -26,6 +27,7 @@ from fastmcp.tools import Tool
26
27
  from mcp.types import AnyFunction
27
28
  from mcp.types import Tool as MCPTool
28
29
  from mcp.types import ToolAnnotations
30
+ from typing_extensions import Unpack
29
31
 
30
32
  from .config import MCPServerConfig
31
33
  from .config import get_config
@@ -287,16 +289,37 @@ mcp = TaggedFastMCP(
287
289
  )
288
290
 
289
291
 
292
+ class ToolKwargs(TypedDict, total=False):
293
+ """Keyword arguments passed through to FastMCP's mcp.tool() decorator.
294
+
295
+ All parameters are optional and forwarded directly to FastMCP tool registration.
296
+ See FastMCP documentation for full details on each parameter.
297
+ """
298
+
299
+ name: str | None
300
+ title: str | None
301
+ description: str | None
302
+ icons: list[Any] | None
303
+ tags: set[str] | None
304
+ output_schema: dict[str, Any] | None
305
+ annotations: Any | None
306
+ exclude_args: list[str] | None
307
+ meta: dict[str, Any] | None
308
+ enabled: bool | None
309
+
310
+
290
311
  def dr_core_mcp_tool(
291
- name: str | None = None,
292
- description: str | None = None,
293
- tags: set[str] | None = None,
312
+ **kwargs: Unpack[ToolKwargs],
294
313
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
295
- """Combine decorator that includes mcp.tool() and dr_mcp_extras()."""
314
+ """Combine decorator that includes mcp.tool() and dr_mcp_extras().
315
+
316
+ All keyword arguments are passed through to FastMCP's mcp.tool() decorator.
317
+ See ToolKwargs for available parameters.
318
+ """
296
319
 
297
320
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
298
321
  instrumented = dr_mcp_extras()(func)
299
- mcp.tool(name=name, description=description, tags=tags)(instrumented)
322
+ mcp.tool(**kwargs)(instrumented)
300
323
  return instrumented
301
324
 
302
325
  return decorator
@@ -329,27 +352,23 @@ async def memory_aware_wrapper(func: Callable[..., Any], *args: Any, **kwargs: A
329
352
 
330
353
 
331
354
  def dr_mcp_tool(
332
- name: str | None = None,
333
- description: str | None = None,
334
- tags: set[str] | None = None,
355
+ **kwargs: Unpack[ToolKwargs],
335
356
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
336
357
  """Combine decorator that includes mcp.tool(), dr_mcp_extras(), and capture memory ids from
337
358
  the request headers if they exist.
338
359
 
339
- Args:
340
- name: Tool name
341
- description: Tool description
342
- tags: Optional set of tags to apply to the tool
360
+ All keyword arguments are passed through to FastMCP's mcp.tool() decorator.
361
+ See ToolKwargs for available parameters.
343
362
  """
344
363
 
345
364
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
346
365
  @wraps(func)
347
- async def wrapper(*args: Any, **kwargs: Any) -> Any:
348
- return await memory_aware_wrapper(func, *args, **kwargs)
366
+ async def wrapper(*args: Any, **inner_kwargs: Any) -> Any:
367
+ return await memory_aware_wrapper(func, *args, **inner_kwargs)
349
368
 
350
369
  # Apply the MCP decorators
351
370
  instrumented = dr_mcp_extras()(wrapper)
352
- mcp.tool(name=name, description=description, tags=tags)(instrumented)
371
+ mcp.tool(**kwargs)(instrumented)
353
372
  return instrumented
354
373
 
355
374
  return decorator
@@ -15,7 +15,9 @@
15
15
  """Google Drive API Client and utilities for OAuth."""
16
16
 
17
17
  import io
18
+ import json
18
19
  import logging
20
+ import uuid
19
21
  from typing import Annotated
20
22
  from typing import Any
21
23
 
@@ -45,6 +47,12 @@ GOOGLE_WORKSPACE_EXPORT_MIMES: dict[str, str] = {
45
47
  "application/vnd.google-apps.presentation": "text/plain",
46
48
  }
47
49
 
50
+ # MIME type mappings for content conversion during upload to Google Workspace formats
51
+ UPLOAD_CONTENT_TYPES: dict[str, str] = {
52
+ "application/vnd.google-apps.document": "text/plain",
53
+ "application/vnd.google-apps.spreadsheet": "text/csv",
54
+ }
55
+
48
56
  BINARY_MIME_PREFIXES = (
49
57
  "image/",
50
58
  "audio/",
@@ -599,6 +607,125 @@ class GoogleDriveClient:
599
607
  web_view_link=file_metadata.web_view_link,
600
608
  )
601
609
 
610
+ async def create_file(
611
+ self,
612
+ name: str,
613
+ mime_type: str,
614
+ parent_id: str | None = None,
615
+ initial_content: str | None = None,
616
+ ) -> GoogleDriveFile:
617
+ """Create a new file or folder in Google Drive.
618
+
619
+ Creates a new file with the specified name and MIME type. Optionally places
620
+ it in a specific folder and populates it with initial content.
621
+
622
+ For Google Workspace files (Docs, Sheets), the Drive API automatically
623
+ converts plain text content to the appropriate format.
624
+
625
+ Args:
626
+ name: The name for the new file or folder.
627
+ mime_type: The MIME type of the file (e.g., 'text/plain',
628
+ 'application/vnd.google-apps.document',
629
+ 'application/vnd.google-apps.folder').
630
+ parent_id: Optional ID of the parent folder. If not specified,
631
+ the file is created in the root of the user's Drive.
632
+ initial_content: Optional text content to populate the file.
633
+ Ignored for folders.
634
+
635
+ Returns
636
+ -------
637
+ GoogleDriveFile with the created file's metadata.
638
+
639
+ Raises
640
+ ------
641
+ GoogleDriveError: If file creation fails (permission denied,
642
+ parent not found, rate limited, etc.).
643
+ """
644
+ metadata: dict[str, Any] = {
645
+ "name": name,
646
+ "mimeType": mime_type,
647
+ }
648
+ if parent_id:
649
+ metadata["parents"] = [parent_id]
650
+
651
+ if mime_type == GOOGLE_DRIVE_FOLDER_MIME or not initial_content:
652
+ response = await self._client.post(
653
+ "/",
654
+ json=metadata,
655
+ params={"fields": SUPPORTED_FIELDS_STR, "supportsAllDrives": "true"},
656
+ )
657
+ else:
658
+ response = await self._create_file_with_content(
659
+ metadata=metadata,
660
+ content=initial_content,
661
+ target_mime_type=mime_type,
662
+ )
663
+
664
+ if response.status_code == 404:
665
+ raise GoogleDriveError(
666
+ f"Parent folder with ID '{parent_id}' not found."
667
+ if parent_id
668
+ else "Resource not found."
669
+ )
670
+ if response.status_code == 403:
671
+ raise GoogleDriveError(
672
+ "Permission denied: you don't have permission to create files in this location."
673
+ )
674
+ if response.status_code == 400:
675
+ raise GoogleDriveError(
676
+ f"Bad request: invalid parameters for file creation. "
677
+ f"Check that the MIME type '{mime_type}' is valid."
678
+ )
679
+ if response.status_code == 429:
680
+ raise GoogleDriveError("Rate limit exceeded. Please try again later.")
681
+
682
+ response.raise_for_status()
683
+ return GoogleDriveFile.from_api_response(response.json())
684
+
685
+ async def _create_file_with_content(
686
+ self,
687
+ metadata: dict[str, Any],
688
+ content: str,
689
+ target_mime_type: str,
690
+ ) -> httpx.Response:
691
+ """Create a file with content using multipart upload.
692
+
693
+ Args:
694
+ metadata: File metadata dictionary.
695
+ content: Text content for the file.
696
+ target_mime_type: The target MIME type for the file.
697
+
698
+ Returns
699
+ -------
700
+ The HTTP response from the upload.
701
+ """
702
+ content_type = UPLOAD_CONTENT_TYPES.get(target_mime_type, "text/plain")
703
+ boundary = f"===gdrive_boundary_{uuid.uuid4().hex}==="
704
+ body_parts = [
705
+ f"--{boundary}",
706
+ "Content-Type: application/json; charset=UTF-8",
707
+ "",
708
+ json.dumps(metadata),
709
+ f"--{boundary}",
710
+ f"Content-Type: {content_type}",
711
+ "",
712
+ content,
713
+ f"--{boundary}--",
714
+ ]
715
+ body = "\r\n".join(body_parts)
716
+
717
+ upload_url = "https://www.googleapis.com/upload/drive/v3/files"
718
+ return await self._client.post(
719
+ upload_url,
720
+ content=body.encode("utf-8"),
721
+ params={
722
+ "uploadType": "multipart",
723
+ "fields": SUPPORTED_FIELDS_STR,
724
+ "supportsAllDrives": "true",
725
+ },
726
+ headers={"Content-Type": f"multipart/related; boundary={boundary}"},
727
+ )
728
+
602
729
  async def __aenter__(self) -> "GoogleDriveClient":
603
730
  """Async context manager entry."""
604
731
  return self
@@ -21,6 +21,7 @@ from fastmcp.exceptions import ToolError
21
21
  from fastmcp.tools.tool import ToolResult
22
22
 
23
23
  from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
24
+ from datarobot_genai.drmcp.tools.clients.gdrive import GOOGLE_DRIVE_FOLDER_MIME
24
25
  from datarobot_genai.drmcp.tools.clients.gdrive import LIMIT
25
26
  from datarobot_genai.drmcp.tools.clients.gdrive import MAX_PAGE_SIZE
26
27
  from datarobot_genai.drmcp.tools.clients.gdrive import SUPPORTED_FIELDS
@@ -60,7 +61,7 @@ async def gdrive_find_contents(
60
61
  "Optional list of metadata fields to include. Ex. id, name, mimeType. "
61
62
  f"Default = {SUPPORTED_FIELDS_STR}",
62
63
  ] = None,
63
- ) -> ToolResult | ToolError:
64
+ ) -> ToolResult:
64
65
  """
65
66
  Search or list files in the user's Google Drive with pagination and filtering support.
66
67
  Use this tool to discover file names and IDs for use with other tools.
@@ -121,7 +122,7 @@ async def gdrive_read_content(
121
122
  "(e.g., 'text/markdown' for Docs, 'text/csv' for Sheets). "
122
123
  "If not specified, uses sensible defaults. Has no effect on regular files.",
123
124
  ] = None,
124
- ) -> ToolResult | ToolError:
125
+ ) -> ToolResult:
125
126
  """
126
127
  Retrieve the content of a specific file by its ID. Google Workspace files are
127
128
  automatically exported to LLM-readable formats (Push-Down).
@@ -175,3 +176,95 @@ async def gdrive_read_content(
175
176
  ),
176
177
  structured_content=file_content.as_flat_dict(),
177
178
  )
179
+
180
+
181
+ @dr_mcp_tool(tags={"google", "gdrive", "create", "write", "file", "folder"}, enabled=False)
182
+ async def gdrive_create_file(
183
+ *,
184
+ name: Annotated[str, "The name for the new file or folder."],
185
+ mime_type: Annotated[
186
+ str,
187
+ "The MIME type of the file (e.g., 'text/plain', "
188
+ "'application/vnd.google-apps.document', 'application/vnd.google-apps.folder').",
189
+ ],
190
+ parent_id: Annotated[
191
+ str | None, "The ID of the parent folder where the file should be created."
192
+ ] = None,
193
+ initial_content: Annotated[
194
+ str | None, "Text content to populate the new file, if applicable."
195
+ ] = None,
196
+ ) -> ToolResult:
197
+ """
198
+ Create a new file or folder in Google Drive.
199
+
200
+ This tool is essential for an AI agent to generate new output (like reports or
201
+ documentation) directly into the Drive structure.
202
+
203
+ Usage:
204
+ - Create empty file: gdrive_create_file(name="report.txt", mime_type="text/plain")
205
+ - Create Google Doc: gdrive_create_file(
206
+ name="My Report",
207
+ mime_type="application/vnd.google-apps.document",
208
+ initial_content="# Report Title"
209
+ )
210
+ - Create folder: gdrive_create_file(
211
+ name="Reports",
212
+ mime_type="application/vnd.google-apps.folder"
213
+ )
214
+ - Create in subfolder: gdrive_create_file(
215
+ name="file.txt",
216
+ mime_type="text/plain",
217
+ parent_id="folder_id_here",
218
+ initial_content="File content"
219
+ )
220
+
221
+ Supported MIME types:
222
+ - text/plain: Plain text file
223
+ - application/vnd.google-apps.document: Google Doc (content auto-converted)
224
+ - application/vnd.google-apps.spreadsheet: Google Sheet (CSV content works best)
225
+ - application/vnd.google-apps.folder: Folder (initial_content is ignored)
226
+
227
+ Note: For Google Workspace files, the Drive API automatically converts plain text
228
+ content to the appropriate format.
229
+ """
230
+ if not name or not name.strip():
231
+ raise ToolError("Argument validation error: 'name' cannot be empty.")
232
+
233
+ if not mime_type or not mime_type.strip():
234
+ raise ToolError("Argument validation error: 'mime_type' cannot be empty.")
235
+
236
+ access_token = await get_gdrive_access_token()
237
+ if isinstance(access_token, ToolError):
238
+ raise access_token
239
+
240
+ try:
241
+ async with GoogleDriveClient(access_token) as client:
242
+ created_file = await client.create_file(
243
+ name=name,
244
+ mime_type=mime_type,
245
+ parent_id=parent_id,
246
+ initial_content=initial_content,
247
+ )
248
+ except GoogleDriveError as e:
249
+ logger.error(f"Google Drive error creating file: {e}")
250
+ raise ToolError(str(e))
251
+ except Exception as e:
252
+ logger.error(f"Unexpected error creating Google Drive file: {e}")
253
+ raise ToolError(f"An unexpected error occurred while creating Google Drive file: {str(e)}")
254
+
255
+ # Build response message
256
+ file_type = "folder" if mime_type == GOOGLE_DRIVE_FOLDER_MIME else "file"
257
+ content_info = ""
258
+ if initial_content and mime_type != GOOGLE_DRIVE_FOLDER_MIME:
259
+ content_info = " with initial content"
260
+
261
+ return ToolResult(
262
+ content=f"Successfully created {file_type} '{created_file.name}'{content_info}.",
263
+ structured_content={
264
+ "id": created_file.id,
265
+ "name": created_file.name,
266
+ "mimeType": created_file.mime_type,
267
+ "webViewLink": created_file.web_view_link,
268
+ "createdTime": created_file.created_time,
269
+ },
270
+ )