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,201 @@
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
+ from typing import Any
17
+
18
+ from datarobot_genai.drmcp.core.mcp_instance import dr_core_mcp_tool
19
+
20
+ from .manager import ToolContext
21
+ from .manager import get_memory_manager
22
+
23
+
24
+ @dr_core_mcp_tool()
25
+ async def store_resource(
26
+ data: Any,
27
+ memory_storage_id: str | None = None,
28
+ agent_identifier: str | None = None,
29
+ prompt: str | None = None,
30
+ tool_name: str | None = None,
31
+ tool_parameters: dict[str, Any] | None = None,
32
+ embedding_vector: list[float] | None = None,
33
+ ) -> str:
34
+ """
35
+ Store a resource in the memory storage.
36
+
37
+ Args:
38
+ data: The data to store (string, json or binary)
39
+ memory_storage_id: Optional storage ID to associate the resource with
40
+ agent_identifier: Required if memory_storage_id is provided
41
+ prompt: Optional prompt used to generate this resource
42
+ tool_name: Optional name of the tool used to generate this resource
43
+ tool_parameters: Optional parameters used with the tool
44
+ embedding_vector: Optional embedding vector for the resource
45
+
46
+ Returns
47
+ -------
48
+ str: The ID of the stored resource
49
+ """
50
+ tool_context = None
51
+ if tool_name and tool_parameters:
52
+ tool_context = ToolContext(name=tool_name, parameters=tool_parameters)
53
+
54
+ memory_manager = get_memory_manager()
55
+ if not memory_manager:
56
+ return "Memory manager not initialized"
57
+
58
+ resource_id = await memory_manager.store_resource(
59
+ data=data,
60
+ memory_storage_id=memory_storage_id,
61
+ agent_identifier=agent_identifier,
62
+ prompt=prompt,
63
+ tool_context=tool_context,
64
+ embedding_vector=embedding_vector,
65
+ )
66
+ return f"Resource stored with ID: {resource_id}"
67
+
68
+
69
+ @dr_core_mcp_tool()
70
+ async def get_resource(
71
+ resource_id: str,
72
+ memory_storage_id: str | None = None,
73
+ agent_identifier: str | None = None,
74
+ include_data: bool = True,
75
+ ) -> str:
76
+ """
77
+ Get a resource and optionally its data from the memory storage.
78
+
79
+ Args:
80
+ resource_id: The ID of the resource to retrieve
81
+ memory_storage_id: Optional storage ID the resource belongs to
82
+ agent_identifier: Required if memory_storage_id is provided
83
+ include_data: Whether to include the resource data in the response
84
+
85
+ Returns
86
+ -------
87
+ str: JSON string containing the resource metadata and optionally its data
88
+ """
89
+ memory_manager = get_memory_manager()
90
+ if not memory_manager:
91
+ return "Memory manager not initialized"
92
+
93
+ resource = await memory_manager.get_resource(
94
+ resource_id=resource_id,
95
+ memory_storage_id=memory_storage_id,
96
+ agent_identifier=agent_identifier,
97
+ )
98
+
99
+ if not resource:
100
+ return "Resource not found"
101
+
102
+ result = {
103
+ "id": resource.id,
104
+ "memory_storage_id": resource.memory_storage_id,
105
+ "prompt": resource.prompt,
106
+ "tool_context": resource.tool_context.model_dump() if resource.tool_context else None,
107
+ "embedding_vector": resource.embedding_vector,
108
+ "created_at": resource.created_at.isoformat(),
109
+ }
110
+
111
+ if include_data:
112
+ data = await memory_manager.get_resource_data(
113
+ resource_id=resource_id,
114
+ memory_storage_id=memory_storage_id,
115
+ agent_identifier=agent_identifier,
116
+ )
117
+ if isinstance(data, bytes):
118
+ try:
119
+ # Try to decode as string if possible
120
+ result["data"] = data.decode("utf-8")
121
+ except UnicodeDecodeError:
122
+ # If binary data, return as is
123
+ result["data"] = data # type: ignore[assignment]
124
+ else:
125
+ result["data"] = data
126
+
127
+ return json.dumps(result, default=str)
128
+
129
+
130
+ @dr_core_mcp_tool()
131
+ async def list_resources(agent_identifier: str, memory_storage_id: str | None = None) -> str:
132
+ """
133
+ List all resources from the memory storage.
134
+
135
+ Args:
136
+ agent_identifier: Agent identifier to scope the search
137
+ memory_storage_id: Optional Storage ID to filter resources
138
+
139
+ Returns
140
+ -------
141
+ str: JSON string containing a list of resources
142
+ """
143
+ memory_manager = get_memory_manager()
144
+ if not memory_manager:
145
+ return "Memory manager not initialized"
146
+
147
+ resources = await memory_manager.list_resources(
148
+ agent_identifier=agent_identifier, memory_storage_id=memory_storage_id
149
+ )
150
+
151
+ if not resources:
152
+ return "No resources found"
153
+
154
+ result = []
155
+ for resource in resources:
156
+ result.append(
157
+ {
158
+ "id": resource.id,
159
+ "memory_storage_id": resource.memory_storage_id,
160
+ "prompt": resource.prompt,
161
+ "tool_context": resource.tool_context.model_dump()
162
+ if resource.tool_context
163
+ else None,
164
+ "created_at": resource.created_at.isoformat(),
165
+ }
166
+ )
167
+
168
+ return json.dumps(result, default=str)
169
+
170
+
171
+ @dr_core_mcp_tool()
172
+ async def delete_resource(
173
+ resource_id: str,
174
+ memory_storage_id: str | None = None,
175
+ agent_identifier: str | None = None,
176
+ ) -> str:
177
+ """
178
+ Delete a resource from the memory storage.
179
+
180
+ Args:
181
+ resource_id: The ID of the resource to delete
182
+ memory_storage_id: Optional storage ID the resource belongs to
183
+ agent_identifier: Required if memory_storage_id is provided
184
+
185
+ Returns
186
+ -------
187
+ str: Success or error message
188
+ """
189
+ memory_manager = get_memory_manager()
190
+ if not memory_manager:
191
+ return "Memory manager not initialized"
192
+
193
+ success = await memory_manager.delete_resource(
194
+ resource_id=resource_id,
195
+ memory_storage_id=memory_storage_id,
196
+ agent_identifier=agent_identifier,
197
+ )
198
+
199
+ if success:
200
+ return f"Resource {resource_id} deleted successfully"
201
+ return f"Failed to delete resource {resource_id}"
@@ -0,0 +1,439 @@
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
+ from http import HTTPStatus
15
+ from logging import getLogger
16
+
17
+ from botocore.exceptions import ClientError
18
+ from starlette.requests import Request
19
+ from starlette.responses import JSONResponse
20
+
21
+ from .dynamic_prompts.controllers import delete_registered_prompt_template
22
+ from .dynamic_prompts.controllers import refresh_registered_prompt_template
23
+ from .dynamic_prompts.controllers import register_prompt_from_prompt_template_id_and_version
24
+ from .dynamic_tools.deployment.controllers import delete_registered_tool_deployment
25
+ from .dynamic_tools.deployment.controllers import get_registered_tool_deployments
26
+ from .dynamic_tools.deployment.controllers import register_tool_for_deployment_id
27
+ from .mcp_instance import TaggedFastMCP
28
+ from .memory_management.manager import get_memory_manager
29
+ from .routes_utils import prefix_mount_path
30
+
31
+ logger = getLogger(__name__)
32
+
33
+
34
+ def register_routes(mcp: TaggedFastMCP) -> None:
35
+ """Register all routes with the MCP server."""
36
+
37
+ @mcp.custom_route(prefix_mount_path("/"), methods=["GET"])
38
+ async def handle_health(_: Request) -> JSONResponse:
39
+ return JSONResponse(
40
+ status_code=HTTPStatus.OK,
41
+ content={
42
+ "status": "healthy",
43
+ "message": "DataRobot MCP Server is running",
44
+ },
45
+ )
46
+
47
+ # Custom endpoint to get all tags
48
+ @mcp.custom_route(prefix_mount_path("/tags"), methods=["GET"])
49
+ async def handle_tags(_: Request) -> JSONResponse:
50
+ try:
51
+ # TaggedFastMCP extends FastMCP with get_all_tags
52
+ tags = await mcp.get_all_tags() # type: ignore[attr-defined]
53
+ return JSONResponse(
54
+ status_code=HTTPStatus.OK,
55
+ content={
56
+ "tags": tags,
57
+ "count": len(tags),
58
+ "message": "All available tags retrieved successfully",
59
+ },
60
+ )
61
+ except Exception as e:
62
+ return JSONResponse(
63
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
64
+ content={
65
+ "error": f"Failed to retrieve tags: {str(e)}",
66
+ },
67
+ )
68
+
69
+ memory_manager = get_memory_manager()
70
+ if memory_manager:
71
+ # Route to initialize a new storage for an agent
72
+ @mcp.custom_route(prefix_mount_path("/agent/{agent_id}/storage/{label}"), methods=["POST"])
73
+ async def initialize_agent_storage(request: Request) -> JSONResponse:
74
+ agent_id = request.path_params["agent_id"]
75
+ label = request.path_params["label"]
76
+
77
+ # Get storage name and config from request body
78
+ body = await request.json()
79
+ config = body.get("config")
80
+
81
+ # Initialize storage
82
+ storage_id = await memory_manager.initialize_storage(
83
+ agent_identifier=agent_id, label=label, storage_config=config
84
+ )
85
+
86
+ return JSONResponse(
87
+ status_code=HTTPStatus.OK,
88
+ content={
89
+ "agentId": agent_id,
90
+ "storageId": storage_id,
91
+ "label": label,
92
+ },
93
+ )
94
+
95
+ # Route to list all storages for an agent
96
+ @mcp.custom_route(prefix_mount_path("/agent/{agent_id}/storages"), methods=["GET"])
97
+ async def list_agent_storages(request: Request) -> JSONResponse:
98
+ agent_id = request.path_params["agent_id"]
99
+ storages = await memory_manager.list_storages(agent_identifier=agent_id)
100
+
101
+ if not storages:
102
+ return JSONResponse(
103
+ status_code=HTTPStatus.OK,
104
+ content={"agentId": agent_id, "storages": []},
105
+ )
106
+
107
+ storage_list = [
108
+ {
109
+ "storageId": storage.id,
110
+ "label": storage.label,
111
+ "createdAt": storage.created_at.isoformat(),
112
+ }
113
+ for storage in storages
114
+ ]
115
+
116
+ return JSONResponse(
117
+ status_code=HTTPStatus.OK,
118
+ content={"agentId": agent_id, "storages": storage_list},
119
+ )
120
+
121
+ # Route to get a specific storage by ID
122
+ @mcp.custom_route(
123
+ prefix_mount_path("/agent/{agent_id}/storages/{storage_id}"),
124
+ methods=["GET"],
125
+ )
126
+ async def get_agent_storage(request: Request) -> JSONResponse:
127
+ agent_id = request.path_params["agent_id"]
128
+ storage_id = request.path_params["storage_id"]
129
+
130
+ storage = await memory_manager.get_storage(
131
+ agent_identifier=agent_id, memory_storage_id=storage_id
132
+ )
133
+
134
+ if storage:
135
+ return JSONResponse(
136
+ status_code=HTTPStatus.OK,
137
+ content={
138
+ "agentId": agent_id,
139
+ "storageId": storage.id,
140
+ "label": storage.label,
141
+ "createdAt": storage.created_at.isoformat(),
142
+ "storageConfig": storage.storage_config,
143
+ },
144
+ )
145
+
146
+ return JSONResponse(
147
+ status_code=HTTPStatus.NOT_FOUND,
148
+ content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
149
+ )
150
+
151
+ # Route to delete a specific storage
152
+ @mcp.custom_route(
153
+ prefix_mount_path("/agent/{agent_id}/storages/{storage_id}"),
154
+ methods=["DELETE"],
155
+ )
156
+ async def delete_agent_storage(request: Request) -> JSONResponse:
157
+ agent_id = request.path_params["agent_id"]
158
+ storage_id = request.path_params["storage_id"]
159
+
160
+ success = await memory_manager.delete_storage(
161
+ memory_storage_id=storage_id, agent_identifier=agent_id
162
+ )
163
+
164
+ if success:
165
+ return JSONResponse(
166
+ status_code=HTTPStatus.OK,
167
+ content={"message": f"Storage {storage_id} deleted successfully"},
168
+ )
169
+
170
+ return JSONResponse(
171
+ status_code=HTTPStatus.NOT_FOUND,
172
+ content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
173
+ )
174
+
175
+ # Route to delete all storages for an agent
176
+ @mcp.custom_route(prefix_mount_path("/agent/{agent_id}"), methods=["DELETE"])
177
+ async def delete_agent(request: Request) -> JSONResponse:
178
+ agent_id = request.path_params["agent_id"]
179
+
180
+ success = await memory_manager.delete_agent(agent_identifier=agent_id)
181
+
182
+ if success:
183
+ return JSONResponse(
184
+ status_code=HTTPStatus.OK,
185
+ content={"message": f"Agent {agent_id} and all storages deleted successfully"},
186
+ )
187
+
188
+ return JSONResponse(
189
+ status_code=HTTPStatus.NOT_FOUND,
190
+ content={"error": f"Agent {agent_id} not found"},
191
+ )
192
+
193
+ # Route to set active storage for an agent
194
+ @mcp.custom_route(
195
+ prefix_mount_path("/agent/{agent_id}/storages/{storage_id}/activate"),
196
+ methods=["POST"],
197
+ )
198
+ async def set_active_storage(request: Request) -> JSONResponse:
199
+ agent_id = request.path_params["agent_id"]
200
+ storage_id = request.path_params["storage_id"]
201
+
202
+ # First verify the storage exists
203
+ storage = await memory_manager.get_storage(
204
+ agent_identifier=agent_id, memory_storage_id=storage_id
205
+ )
206
+
207
+ if not storage:
208
+ return JSONResponse(
209
+ status_code=HTTPStatus.NOT_FOUND,
210
+ content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
211
+ )
212
+
213
+ # Set as active storage
214
+ await memory_manager.set_storage_id_for_agent(
215
+ agent_identifier=agent_id,
216
+ storage_id=storage_id,
217
+ label=storage.label,
218
+ )
219
+
220
+ return JSONResponse(
221
+ status_code=HTTPStatus.OK,
222
+ content={
223
+ "agentId": agent_id,
224
+ "storageId": storage_id,
225
+ "label": storage.label,
226
+ "message": "Active storage set successfully",
227
+ },
228
+ )
229
+
230
+ # Route to get active storage for an agent
231
+ @mcp.custom_route(prefix_mount_path("/agent/{agent_id}/active-storage"), methods=["GET"])
232
+ async def get_active_storage(request: Request) -> JSONResponse:
233
+ agent_id = request.path_params["agent_id"]
234
+
235
+ try:
236
+ storage_id = await memory_manager.get_active_storage_id_for_agent(
237
+ agent_identifier=agent_id
238
+ )
239
+ except ClientError as e:
240
+ if e.response["Error"]["Code"] == "404":
241
+ return JSONResponse(
242
+ status_code=HTTPStatus.NOT_FOUND,
243
+ content={"error": f"No active storage found for agent {agent_id}"},
244
+ )
245
+ return JSONResponse(
246
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content={"error": str(e)}
247
+ )
248
+
249
+ return JSONResponse(
250
+ status_code=HTTPStatus.OK,
251
+ content={
252
+ "agentId": agent_id,
253
+ "storageId": storage_id,
254
+ },
255
+ )
256
+
257
+ # Route to clear active storage for an agent
258
+ @mcp.custom_route(prefix_mount_path("/agent/{agent_id}/active-storage"), methods=["DELETE"])
259
+ async def clear_active_storage(request: Request) -> JSONResponse:
260
+ agent_id = request.path_params["agent_id"]
261
+
262
+ # Clear active storage
263
+ try:
264
+ await memory_manager.clear_storage_id_for_agent(agent_identifier=agent_id)
265
+ except ClientError as e:
266
+ if e.response["Error"]["Code"] == "404":
267
+ return JSONResponse(
268
+ status_code=HTTPStatus.NOT_FOUND,
269
+ content={"error": f"No active storage found for agent {agent_id}"},
270
+ )
271
+ return JSONResponse(status_code=500, content={"error": str(e)})
272
+
273
+ return JSONResponse(
274
+ status_code=HTTPStatus.OK,
275
+ content={"message": f"Active storage cleared for agent {agent_id}"},
276
+ )
277
+ else:
278
+ logger.info("Memory manager not initialized, skipping memory manager routes")
279
+
280
+ @mcp.custom_route(prefix_mount_path("/registeredDeployments/{deployment_id}"), methods=["PUT"])
281
+ async def add_deployment(request: Request) -> JSONResponse:
282
+ """Add or update a deployment with a known deployment_id."""
283
+ deployment_id = request.path_params["deployment_id"]
284
+ try:
285
+ tool = await register_tool_for_deployment_id(deployment_id)
286
+ return JSONResponse(
287
+ status_code=HTTPStatus.CREATED,
288
+ content={
289
+ "name": tool.name,
290
+ "description": tool.description,
291
+ "tags": list(tool.tags),
292
+ "deploymentId": deployment_id,
293
+ },
294
+ )
295
+ except Exception as e:
296
+ return JSONResponse(
297
+ status_code=HTTPStatus.BAD_REQUEST,
298
+ content={"error": f"Failed to add deployment: {str(e)}"},
299
+ )
300
+
301
+ @mcp.custom_route(prefix_mount_path("/registeredDeployments"), methods=["GET"])
302
+ async def list_deployments(_: Request) -> JSONResponse:
303
+ """List all deployments."""
304
+ try:
305
+ deployments = await get_registered_tool_deployments()
306
+ formatted_deployments = [
307
+ {"deploymentId": k, "toolName": v} for k, v in deployments.items()
308
+ ]
309
+ return JSONResponse(
310
+ status_code=HTTPStatus.OK,
311
+ content={
312
+ "deployments": formatted_deployments,
313
+ "count": len(deployments),
314
+ },
315
+ )
316
+ except Exception as e:
317
+ return JSONResponse(
318
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
319
+ content={"error": f"Failed to retrieve deployments: {str(e)}"},
320
+ )
321
+
322
+ @mcp.custom_route(
323
+ prefix_mount_path("/registeredDeployments/{deployment_id}"), methods=["DELETE"]
324
+ )
325
+ async def delete_deployment(request: Request) -> JSONResponse:
326
+ """Delete (de-register) a deployment by deployment_id."""
327
+ deployment_id = request.path_params["deployment_id"]
328
+ try:
329
+ deleted = await delete_registered_tool_deployment(deployment_id)
330
+ if deleted is True:
331
+ return JSONResponse(
332
+ status_code=HTTPStatus.OK,
333
+ content={
334
+ "message": f"Tool with deployment {deployment_id} deleted successfully"
335
+ },
336
+ )
337
+ return JSONResponse(
338
+ status_code=HTTPStatus.NOT_FOUND,
339
+ content={"error": f"Tool with deployment {deployment_id} not found"},
340
+ )
341
+ except Exception as e:
342
+ return JSONResponse(
343
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
344
+ content={"error": f"Failed to delete deployment: {str(e)}"},
345
+ )
346
+
347
+ @mcp.custom_route(prefix_mount_path("/registeredPrompts"), methods=["GET"])
348
+ async def list_prompt_templates(_: Request) -> JSONResponse:
349
+ """List all prompt templates."""
350
+ try:
351
+ prompts = await mcp.get_prompt_mapping()
352
+ formatted_prompts = [
353
+ {
354
+ "promptTemplateId": pt_id,
355
+ "promptTemplateVersionId": ptv_id,
356
+ "promptName": p_name,
357
+ }
358
+ for pt_id, (ptv_id, p_name) in prompts.items()
359
+ ]
360
+ return JSONResponse(
361
+ status_code=HTTPStatus.OK,
362
+ content={
363
+ "promptTemplates": formatted_prompts,
364
+ "count": len(formatted_prompts),
365
+ },
366
+ )
367
+ except Exception as e:
368
+ return JSONResponse(
369
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
370
+ content={"error": f"Failed to retrieve promptTemplates: {str(e)}"},
371
+ )
372
+
373
+ @mcp.custom_route(
374
+ prefix_mount_path("/registeredPrompts/{prompt_template_id}"), methods=["DELETE"]
375
+ )
376
+ async def delete_prompt_template(request: Request) -> JSONResponse:
377
+ """Delete (de-register) a prompt by prompt_template_id."""
378
+ prompt_template_id = request.path_params["prompt_template_id"]
379
+ try:
380
+ deleted = await delete_registered_prompt_template(prompt_template_id)
381
+ if deleted:
382
+ return JSONResponse(
383
+ status_code=HTTPStatus.OK,
384
+ content={
385
+ "message": f"Prompt with prompt template id {prompt_template_id} "
386
+ f"deleted successfully"
387
+ },
388
+ )
389
+ return JSONResponse(
390
+ status_code=HTTPStatus.NOT_FOUND,
391
+ content={"error": f"Prompt with prompt template id {prompt_template_id} not found"},
392
+ )
393
+ except Exception as e:
394
+ return JSONResponse(
395
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
396
+ content={"error": f"Failed to delete prompt: {str(e)}"},
397
+ )
398
+
399
+ @mcp.custom_route(
400
+ prefix_mount_path("/registeredPrompts/{prompt_template_id}"),
401
+ methods=["PUT"],
402
+ )
403
+ async def add_prompt_template(request: Request) -> JSONResponse:
404
+ """Add or update prompt template."""
405
+ prompt_template_id = request.path_params["prompt_template_id"]
406
+ prompt_template_version_id = request.query_params.get("promptTemplateVersionId")
407
+ try:
408
+ prompt = await register_prompt_from_prompt_template_id_and_version(
409
+ prompt_template_id, prompt_template_version_id
410
+ )
411
+ return JSONResponse(
412
+ status_code=HTTPStatus.CREATED,
413
+ content={
414
+ "name": prompt.name,
415
+ "description": prompt.description,
416
+ "promptTemplateId": prompt_template_id,
417
+ "promptTemplateVersionId": prompt.meta["prompt_template_version_id"],
418
+ },
419
+ )
420
+ except Exception as e:
421
+ return JSONResponse(
422
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
423
+ content={"error": f"Failed to add prompt template: {str(e)}"},
424
+ )
425
+
426
+ @mcp.custom_route(prefix_mount_path("/registeredPrompts"), methods=["PUT"])
427
+ async def refresh_prompt_templates(_: Request) -> JSONResponse:
428
+ """Refresh prompt templates."""
429
+ try:
430
+ await refresh_registered_prompt_template()
431
+ return JSONResponse(
432
+ status_code=HTTPStatus.OK,
433
+ content={"message": "Prompts refreshed successfully"},
434
+ )
435
+ except Exception as e:
436
+ return JSONResponse(
437
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
438
+ content={"error": f"Failed to refresh prompt templates: {str(e)}"},
439
+ )
@@ -0,0 +1,30 @@
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
+ from .config import get_config
16
+
17
+
18
+ def prefix_mount_path(endpoint: str) -> str:
19
+ config = get_config()
20
+ mount_path = config.mount_path
21
+
22
+ if mount_path == "/":
23
+ return endpoint
24
+
25
+ if mount_path.endswith("/"):
26
+ mount_path = mount_path[:-1]
27
+
28
+ if not endpoint.startswith("/"):
29
+ endpoint = "/" + endpoint
30
+ return mount_path + endpoint