datarobot-genai 0.2.42__tar.gz → 0.2.44__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 (128) hide show
  1. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/PKG-INFO +2 -1
  2. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/pyproject.toml +2 -1
  3. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/config.py +10 -0
  4. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/tool_config.py +8 -0
  5. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/clients/tavily.py +199 -0
  6. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +2 -4
  7. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/tavily/__init__.py +13 -0
  8. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/tavily/tools.py +148 -0
  9. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/.gitignore +0 -0
  10. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/AUTHORS +0 -0
  11. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/LICENSE +0 -0
  12. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/README.md +0 -0
  13. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/__init__.py +0 -0
  14. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/__init__.py +0 -0
  15. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/agents/__init__.py +0 -0
  16. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/agents/base.py +0 -0
  17. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/__init__.py +0 -0
  18. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/auth.py +0 -0
  19. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/client.py +0 -0
  20. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/responses.py +0 -0
  21. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/__init__.py +0 -0
  22. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  23. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  24. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/custom_model.py +0 -0
  25. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  26. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/mcp/common.py +0 -0
  27. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  28. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/__init__.py +0 -0
  29. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/auth.py +0 -0
  30. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/urls.py +0 -0
  31. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/__init__.py +0 -0
  32. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/agent.py +0 -0
  33. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/base.py +0 -0
  34. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/events.py +0 -0
  35. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/mcp.py +0 -0
  36. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/__init__.py +0 -0
  37. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  38. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  39. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  40. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  41. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  42. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  43. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  44. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  45. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  46. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  47. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  48. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  49. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  50. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  51. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  52. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  53. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  54. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  55. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  56. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  57. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  58. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  59. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  60. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  61. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  62. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  63. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  64. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  65. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  66. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  67. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  68. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  69. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  70. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  71. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  72. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  73. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  74. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  75. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  76. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/server.py +0 -0
  77. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  78. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
  79. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/anthropic.py +0 -0
  80. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/base.py +0 -0
  81. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +0 -0
  82. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/openai.py +0 -0
  83. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  84. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  85. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  86. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  87. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +0 -0
  88. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  89. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  90. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  91. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  92. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  93. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  94. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +0 -0
  95. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  96. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/microsoft_graph.py +0 -0
  97. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  98. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  99. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  100. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  101. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  102. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  103. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +0 -0
  104. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/microsoft_graph/tools.py +0 -0
  105. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  106. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  107. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  108. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  109. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  110. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  111. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  112. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  113. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  114. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/__init__.py +0 -0
  115. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/agent.py +0 -0
  116. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/mcp.py +0 -0
  117. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/__init__.py +0 -0
  118. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/agent.py +0 -0
  119. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/base.py +0 -0
  120. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/mcp.py +0 -0
  121. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/__init__.py +0 -0
  122. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/agent.py +0 -0
  123. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  124. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  125. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  126. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_mcp_client.py +0 -0
  127. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/helpers.py +0 -0
  128. {datarobot_genai-0.2.42 → datarobot_genai-0.2.44}/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.42
3
+ Version: 0.2.44
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -46,6 +46,7 @@ Requires-Dist: opentelemetry-sdk<2.0.0,>=1.22.0; extra == 'drmcp'
46
46
  Requires-Dist: pydantic-settings<3.0.0,>=2.1.0; extra == 'drmcp'
47
47
  Requires-Dist: pydantic<3.0.0,>=2.6.1; extra == 'drmcp'
48
48
  Requires-Dist: python-dotenv<2.0.0,>=1.1.0; extra == 'drmcp'
49
+ Requires-Dist: tavily-python<1.0.0,>=0.7.20; extra == 'drmcp'
49
50
  Provides-Extra: langgraph
50
51
  Requires-Dist: langchain-mcp-adapters<0.2.0,>=0.1.12; extra == 'langgraph'
51
52
  Requires-Dist: langgraph-prebuilt<0.7.0,>=0.2.3; extra == 'langgraph'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.2.42"
7
+ version = "0.2.44"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -84,6 +84,7 @@ drmcp = [
84
84
  "python-dotenv>=1.1.0,<2.0.0",
85
85
  "boto3>=1.34.0,<2.0.0",
86
86
  "httpx>=0.28.1,<1.0.0",
87
+ "tavily-python>=0.7.20,<1.0.0",
87
88
  "pydantic>=2.6.1,<3.0.0",
88
89
  "pydantic-settings>=2.1.0,<3.0.0",
89
90
  "opentelemetry-api>=1.22.0,<2.0.0",
@@ -79,6 +79,15 @@ class MCPToolConfig(BaseSettings):
79
79
  description="Enable/disable Sharepoint tools",
80
80
  )
81
81
 
82
+ enable_tavily_tools: bool = Field(
83
+ default=False,
84
+ validation_alias=AliasChoices(
85
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_TAVILY_TOOLS",
86
+ "ENABLE_TAVILY_TOOLS",
87
+ ),
88
+ description="Enable/disable Tavily search tools",
89
+ )
90
+
82
91
  is_atlassian_oauth_provider_configured: bool = Field(
83
92
  default=False,
84
93
  validation_alias=AliasChoices(
@@ -131,6 +140,7 @@ class MCPToolConfig(BaseSettings):
131
140
  "enable_confluence_tools",
132
141
  "enable_gdrive_tools",
133
142
  "enable_microsoft_graph_tools",
143
+ "enable_tavily_tools",
134
144
  "is_atlassian_oauth_provider_configured",
135
145
  "is_google_oauth_provider_configured",
136
146
  "is_microsoft_oauth_provider_configured",
@@ -31,6 +31,7 @@ class ToolType(str, Enum):
31
31
  CONFLUENCE = "confluence"
32
32
  GDRIVE = "gdrive"
33
33
  MICROSOFT_GRAPH = "microsoft_graph"
34
+ TAVILY = "tavily"
34
35
 
35
36
 
36
37
  class ToolConfig(TypedDict):
@@ -80,6 +81,13 @@ TOOL_CONFIGS: dict[ToolType, ToolConfig] = {
80
81
  package_prefix="datarobot_genai.drmcp.tools.microsoft_graph",
81
82
  config_field_name="enable_microsoft_graph_tools",
82
83
  ),
84
+ ToolType.TAVILY: ToolConfig(
85
+ name="tavily",
86
+ oauth_check=None,
87
+ directory="tavily",
88
+ package_prefix="datarobot_genai.drmcp.tools.tavily",
89
+ config_field_name="enable_tavily_tools",
90
+ ),
83
91
  }
84
92
 
85
93
 
@@ -0,0 +1,199 @@
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
+ """Tavily API Client and utilities for API key authentication."""
16
+
17
+ import logging
18
+ from typing import Any
19
+ from typing import Literal
20
+
21
+ from fastmcp.exceptions import ToolError
22
+ from fastmcp.server.dependencies import get_http_headers
23
+ from pydantic import BaseModel
24
+ from pydantic import ConfigDict
25
+ from tavily import AsyncTavilyClient
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ MAX_RESULTS: int = 20
30
+ MAX_CHUNKS_PER_SOURCE: int = 3
31
+
32
+ MAX_RESULTS_DEFAULT: int = 5
33
+ CHUNKS_PER_SOURCE_DEFAULT: int = 1
34
+
35
+
36
+ async def get_tavily_access_token() -> str:
37
+ """
38
+ Get Tavily API key from HTTP headers.
39
+
40
+ Returns
41
+ -------
42
+ API key string
43
+
44
+ Raises
45
+ ------
46
+ ToolError: If API key is not found in headers
47
+ """
48
+ headers = get_http_headers()
49
+
50
+ api_key = headers.get("x-tavily-api-key")
51
+ if api_key:
52
+ return api_key
53
+
54
+ logger.warning("Tavily API key not found in headers")
55
+ raise ToolError(
56
+ "Tavily API key not found in headers. Please provide it via 'x-tavily-api-key' header."
57
+ )
58
+
59
+
60
+ class TavilySearchResult(BaseModel):
61
+ """A single search result from Tavily API."""
62
+
63
+ title: str
64
+ url: str
65
+ content: str
66
+ score: float
67
+
68
+ model_config = ConfigDict(populate_by_name=True)
69
+
70
+ @classmethod
71
+ def from_tavily_sdk(cls, result: dict[str, Any]) -> "TavilySearchResult":
72
+ """Create a TavilySearchResult from Tavily SDK response data."""
73
+ return cls(
74
+ title=result.get("title", ""),
75
+ url=result.get("url", ""),
76
+ content=result.get("content", ""),
77
+ score=result.get("score", 0.0),
78
+ )
79
+
80
+ def as_flat_dict(self) -> dict[str, Any]:
81
+ """Return a flat dictionary representation of the search result."""
82
+ return self.model_dump(by_alias=True)
83
+
84
+
85
+ class TavilyImage(BaseModel):
86
+ """An image result from Tavily API."""
87
+
88
+ url: str
89
+ description: str | None = None
90
+
91
+ model_config = ConfigDict(populate_by_name=True)
92
+
93
+ @classmethod
94
+ def from_tavily_sdk(cls, image: dict[str, Any] | str) -> "TavilyImage":
95
+ """Create a TavilyImage from Tavily SDK response data."""
96
+ if isinstance(image, str):
97
+ return cls(url=image)
98
+ return cls(
99
+ url=image.get("url", ""),
100
+ description=image.get("description"),
101
+ )
102
+
103
+
104
+ class TavilyClient:
105
+ """Client for interacting with Tavily Search API.
106
+
107
+ This is a wrapper around the official tavily-python SDK.
108
+ """
109
+
110
+ def __init__(self, api_key: str) -> None:
111
+ self._client = AsyncTavilyClient(api_key=api_key)
112
+
113
+ async def search(
114
+ self,
115
+ query: str,
116
+ *,
117
+ topic: Literal["general", "news", "finance"] = "general",
118
+ search_depth: Literal["basic", "advanced"] = "basic",
119
+ max_results: int = MAX_RESULTS_DEFAULT,
120
+ time_range: Literal["day", "week", "month", "year"] | None = None,
121
+ include_images: bool = False,
122
+ include_image_descriptions: bool = False,
123
+ chunks_per_source: int = CHUNKS_PER_SOURCE_DEFAULT,
124
+ include_answer: bool = False,
125
+ ) -> dict[str, Any]:
126
+ """
127
+ Perform a web search using Tavily API.
128
+
129
+ Args:
130
+ query: The search query to execute.
131
+ topic: The category of search ("general", "news", or "finance").
132
+ search_depth: The depth of search ("basic" or "advanced").
133
+ max_results: Maximum number of results to return (1-20).
134
+ time_range: Time range filter ("day", "week", "month", "year").
135
+ include_images: Whether to include images in results.
136
+ include_image_descriptions: Whether to include image descriptions.
137
+ chunks_per_source: Maximum content snippets per URL (1-3).
138
+ include_answer: Whether to include an AI-generated answer.
139
+
140
+ Returns
141
+ -------
142
+ Dict with search results from Tavily API.
143
+
144
+ Raises
145
+ ------
146
+ ValueError: If validation fails.
147
+ TavilyInvalidAPIKeyError: If the API key is invalid.
148
+ TavilyUsageLimitExceededError: If usage limit is exceeded.
149
+ TavilyForbiddenError: If access is forbidden.
150
+ TavilyBadRequestError: If the request is malformed.
151
+ """
152
+ # Validate inputs
153
+ if not query:
154
+ raise ValueError("query cannot be empty.")
155
+ if isinstance(query, str) and not query.strip():
156
+ raise ValueError("query cannot be empty.")
157
+ if max_results <= 0:
158
+ raise ValueError("max_results must be greater than 0.")
159
+ if max_results > MAX_RESULTS:
160
+ raise ValueError(f"max_results must be smaller than or equal to {MAX_RESULTS}.")
161
+ if chunks_per_source <= 0:
162
+ raise ValueError("chunks_per_source must be greater than 0.")
163
+ if chunks_per_source > MAX_CHUNKS_PER_SOURCE:
164
+ raise ValueError(
165
+ f"chunks_per_source must be smaller than or equal to {MAX_CHUNKS_PER_SOURCE}."
166
+ )
167
+
168
+ # Clamp values to valid ranges
169
+ max_results = min(max_results, MAX_RESULTS)
170
+ chunks_per_source = min(chunks_per_source, MAX_CHUNKS_PER_SOURCE)
171
+
172
+ # Build search parameters
173
+ search_kwargs: dict[str, Any] = {
174
+ "query": query,
175
+ "topic": topic,
176
+ "search_depth": search_depth,
177
+ "max_results": max_results,
178
+ "include_images": include_images,
179
+ "include_image_descriptions": include_image_descriptions,
180
+ "chunks_per_source": chunks_per_source,
181
+ "include_answer": include_answer,
182
+ }
183
+
184
+ if time_range:
185
+ search_kwargs["time_range"] = time_range
186
+
187
+ return await self._client.search(**search_kwargs)
188
+
189
+ async def __aenter__(self) -> "TavilyClient":
190
+ """Async context manager entry."""
191
+ return self
192
+
193
+ async def __aexit__(
194
+ self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any
195
+ ) -> None:
196
+ """Async context manager exit."""
197
+ # AsyncTavilyClient doesn't have a close method, but we keep the context manager
198
+ # pattern for consistency with other clients
199
+ pass
@@ -33,9 +33,7 @@ from datarobot_genai.drmcp.tools.clients.gdrive import get_gdrive_access_token
33
33
  logger = logging.getLogger(__name__)
34
34
 
35
35
 
36
- @dr_mcp_tool(
37
- tags={"google", "gdrive", "list", "search", "files", "find", "contents"}, enabled=False
38
- )
36
+ @dr_mcp_tool(tags={"google", "gdrive", "list", "search", "files", "find", "contents"})
39
37
  async def gdrive_find_contents(
40
38
  *,
41
39
  page_size: Annotated[
@@ -317,7 +315,7 @@ async def gdrive_update_metadata(
317
315
  )
318
316
 
319
317
 
320
- @dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"})
318
+ @dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"}, enabled=False)
321
319
  async def gdrive_manage_access(
322
320
  *,
323
321
  file_id: Annotated[str, "The ID of the file or folder."],
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,148 @@
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
+ """Tavily MCP tools for web search."""
16
+
17
+ import logging
18
+ from typing import Annotated
19
+ from typing import Literal
20
+
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.tavily import CHUNKS_PER_SOURCE_DEFAULT
25
+ from datarobot_genai.drmcp.tools.clients.tavily import MAX_CHUNKS_PER_SOURCE
26
+ from datarobot_genai.drmcp.tools.clients.tavily import MAX_RESULTS
27
+ from datarobot_genai.drmcp.tools.clients.tavily import MAX_RESULTS_DEFAULT
28
+ from datarobot_genai.drmcp.tools.clients.tavily import TavilyClient
29
+ from datarobot_genai.drmcp.tools.clients.tavily import TavilyImage
30
+ from datarobot_genai.drmcp.tools.clients.tavily import TavilySearchResult
31
+ from datarobot_genai.drmcp.tools.clients.tavily import get_tavily_access_token
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ @dr_mcp_tool(tags={"search", "tavily", "web", "websearch"})
37
+ async def tavily_search(
38
+ *,
39
+ query: Annotated[str, "The search query to execute."],
40
+ topic: Annotated[
41
+ Literal["general", "news", "finance"],
42
+ "The category of search. Use 'general' for broad web search, "
43
+ "'news' for recent news articles, or 'finance' for financial information.",
44
+ ] = "general",
45
+ search_depth: Annotated[
46
+ Literal["basic", "advanced"],
47
+ "The depth of search. 'basic' is faster and cheaper, "
48
+ "'advanced' provides more comprehensive results.",
49
+ ] = "basic",
50
+ max_results: Annotated[
51
+ int,
52
+ f"Maximum number of search results to return (1-{MAX_RESULTS}).",
53
+ ] = MAX_RESULTS_DEFAULT,
54
+ time_range: Annotated[
55
+ Literal["day", "week", "month", "year"] | None,
56
+ "Filter results by time range. Use 'day' for last 24 hours, "
57
+ "'week' for last 7 days, 'month' for last 30 days, or 'year' for last year.",
58
+ ] = None,
59
+ include_images: Annotated[
60
+ bool,
61
+ "Whether to include related images in the search results.",
62
+ ] = False,
63
+ include_image_descriptions: Annotated[
64
+ bool,
65
+ "Whether to include AI-generated descriptions for images. "
66
+ "Only applicable when include_images is True.",
67
+ ] = False,
68
+ chunks_per_source: Annotated[
69
+ int,
70
+ f"Maximum number of content snippets to return per source URL (1-{MAX_CHUNKS_PER_SOURCE}).",
71
+ ] = CHUNKS_PER_SOURCE_DEFAULT,
72
+ include_answer: Annotated[
73
+ bool,
74
+ "Whether to include an AI-generated answer summarizing the search results.",
75
+ ] = False,
76
+ ) -> ToolResult:
77
+ """
78
+ Perform a real-time web search using Tavily API.
79
+
80
+ Tavily is optimized for AI agents and provides clean, relevant search results
81
+ suitable for LLM consumption. Use this tool to search the web for current
82
+ information, news, financial data, or general knowledge.
83
+
84
+ Usage:
85
+ - Basic search: tavily_search(query="Python best practices 2026")
86
+ - News search: tavily_search(query="AI regulations", topic="news", time_range="week")
87
+ - Financial search: tavily_search(query="AAPL stock analysis", topic="finance")
88
+ - Comprehensive search: tavily_search(
89
+ query="climate change solutions",
90
+ search_depth="advanced",
91
+ max_results=10,
92
+ include_answer=True
93
+ )
94
+
95
+ Note:
96
+ - Advanced search depth consumes more API credits but provides better results
97
+ - Time range filtering is useful for finding recent information
98
+ """
99
+ api_key = await get_tavily_access_token()
100
+
101
+ async with TavilyClient(api_key) as client:
102
+ response = await client.search(
103
+ query=query,
104
+ topic=topic,
105
+ search_depth=search_depth,
106
+ max_results=max_results,
107
+ time_range=time_range,
108
+ include_images=include_images,
109
+ include_image_descriptions=include_image_descriptions,
110
+ chunks_per_source=chunks_per_source,
111
+ include_answer=include_answer,
112
+ )
113
+
114
+ results = [TavilySearchResult.from_tavily_sdk(r) for r in response.get("results", [])]
115
+
116
+ images: list[TavilyImage] | None = None
117
+ if include_images and response.get("images"):
118
+ images = [TavilyImage.from_tavily_sdk(img) for img in response.get("images", [])]
119
+
120
+ result_count = len(results)
121
+ answer = response.get("answer")
122
+ response_time = response.get("response_time", 0.0)
123
+
124
+ answer_info = " with AI-generated answer" if answer else ""
125
+ image_info = f" and {len(images)} images" if images else ""
126
+
127
+ structured_content: dict = {
128
+ "query": response.get("query", query),
129
+ "results": [r.as_flat_dict() for r in results],
130
+ "resultCount": result_count,
131
+ "responseTime": response_time,
132
+ }
133
+
134
+ if answer:
135
+ structured_content["answer"] = answer
136
+
137
+ if images:
138
+ structured_content["images"] = [
139
+ {"url": img.url, "description": img.description} for img in images
140
+ ]
141
+
142
+ return ToolResult(
143
+ content=(
144
+ f"Successfully searched for '{query}'. "
145
+ f"Found {result_count} results{answer_info}{image_info}."
146
+ ),
147
+ structured_content=structured_content,
148
+ )