deepset-mcp 0.0.2__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.
- deepset_mcp/__init__.py +0 -0
- deepset_mcp/agents/__init__.py +0 -0
- deepset_mcp/agents/debugging/__init__.py +0 -0
- deepset_mcp/agents/debugging/debugging_agent.py +37 -0
- deepset_mcp/agents/debugging/system_prompt.md +214 -0
- deepset_mcp/agents/generalist/__init__.py +0 -0
- deepset_mcp/agents/generalist/generalist_agent.py +38 -0
- deepset_mcp/agents/generalist/system_prompt.md +241 -0
- deepset_mcp/api/README.md +536 -0
- deepset_mcp/api/__init__.py +0 -0
- deepset_mcp/api/client.py +277 -0
- deepset_mcp/api/custom_components/__init__.py +0 -0
- deepset_mcp/api/custom_components/models.py +25 -0
- deepset_mcp/api/custom_components/protocols.py +17 -0
- deepset_mcp/api/custom_components/resource.py +56 -0
- deepset_mcp/api/exceptions.py +70 -0
- deepset_mcp/api/haystack_service/__init__.py +0 -0
- deepset_mcp/api/haystack_service/protocols.py +13 -0
- deepset_mcp/api/haystack_service/resource.py +55 -0
- deepset_mcp/api/indexes/__init__.py +0 -0
- deepset_mcp/api/indexes/models.py +63 -0
- deepset_mcp/api/indexes/protocols.py +53 -0
- deepset_mcp/api/indexes/resource.py +138 -0
- deepset_mcp/api/integrations/__init__.py +1 -0
- deepset_mcp/api/integrations/models.py +49 -0
- deepset_mcp/api/integrations/protocols.py +27 -0
- deepset_mcp/api/integrations/resource.py +57 -0
- deepset_mcp/api/pipeline/__init__.py +17 -0
- deepset_mcp/api/pipeline/log_level.py +9 -0
- deepset_mcp/api/pipeline/models.py +235 -0
- deepset_mcp/api/pipeline/protocols.py +83 -0
- deepset_mcp/api/pipeline/resource.py +378 -0
- deepset_mcp/api/pipeline_template/__init__.py +0 -0
- deepset_mcp/api/pipeline_template/models.py +56 -0
- deepset_mcp/api/pipeline_template/protocols.py +17 -0
- deepset_mcp/api/pipeline_template/resource.py +88 -0
- deepset_mcp/api/protocols.py +122 -0
- deepset_mcp/api/secrets/__init__.py +0 -0
- deepset_mcp/api/secrets/models.py +16 -0
- deepset_mcp/api/secrets/protocols.py +29 -0
- deepset_mcp/api/secrets/resource.py +112 -0
- deepset_mcp/api/shared_models.py +17 -0
- deepset_mcp/api/transport.py +336 -0
- deepset_mcp/api/user/__init__.py +0 -0
- deepset_mcp/api/user/protocols.py +11 -0
- deepset_mcp/api/user/resource.py +38 -0
- deepset_mcp/api/workspace/__init__.py +7 -0
- deepset_mcp/api/workspace/models.py +23 -0
- deepset_mcp/api/workspace/protocols.py +41 -0
- deepset_mcp/api/workspace/resource.py +94 -0
- deepset_mcp/benchmark/README.md +425 -0
- deepset_mcp/benchmark/__init__.py +1 -0
- deepset_mcp/benchmark/agent_configs/debugging_agent.yml +10 -0
- deepset_mcp/benchmark/agent_configs/generalist_agent.yml +6 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/__init__.py +0 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/eda.ipynb +757 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/prepare_interaction_data.ipynb +167 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/preprocessing_utils.py +213 -0
- deepset_mcp/benchmark/runner/__init__.py +0 -0
- deepset_mcp/benchmark/runner/agent_benchmark_runner.py +561 -0
- deepset_mcp/benchmark/runner/agent_loader.py +110 -0
- deepset_mcp/benchmark/runner/cli.py +39 -0
- deepset_mcp/benchmark/runner/cli_agent.py +373 -0
- deepset_mcp/benchmark/runner/cli_index.py +71 -0
- deepset_mcp/benchmark/runner/cli_pipeline.py +73 -0
- deepset_mcp/benchmark/runner/cli_tests.py +226 -0
- deepset_mcp/benchmark/runner/cli_utils.py +61 -0
- deepset_mcp/benchmark/runner/config.py +73 -0
- deepset_mcp/benchmark/runner/config_loader.py +64 -0
- deepset_mcp/benchmark/runner/interactive.py +140 -0
- deepset_mcp/benchmark/runner/models.py +203 -0
- deepset_mcp/benchmark/runner/repl.py +67 -0
- deepset_mcp/benchmark/runner/setup_actions.py +238 -0
- deepset_mcp/benchmark/runner/streaming.py +360 -0
- deepset_mcp/benchmark/runner/teardown_actions.py +196 -0
- deepset_mcp/benchmark/runner/tracing.py +21 -0
- deepset_mcp/benchmark/tasks/chat_rag_answers_wrong_format.yml +16 -0
- deepset_mcp/benchmark/tasks/documents_output_wrong.yml +13 -0
- deepset_mcp/benchmark/tasks/jinja_str_instead_of_complex_type.yml +11 -0
- deepset_mcp/benchmark/tasks/jinja_syntax_error.yml +11 -0
- deepset_mcp/benchmark/tasks/missing_output_mapping.yml +14 -0
- deepset_mcp/benchmark/tasks/no_query_input.yml +13 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_str.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_syntax.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_answers_wrong_format.yml +181 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_missing_output_mapping.yml +189 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_documents_wrong_format.yml +193 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_no_query_input.yml +191 -0
- deepset_mcp/benchmark/tasks/pipelines/standard_index.yml +167 -0
- deepset_mcp/initialize_embedding_model.py +12 -0
- deepset_mcp/main.py +133 -0
- deepset_mcp/prompts/deepset_copilot_prompt.md +271 -0
- deepset_mcp/prompts/deepset_debugging_agent.md +214 -0
- deepset_mcp/store.py +5 -0
- deepset_mcp/tool_factory.py +473 -0
- deepset_mcp/tools/__init__.py +0 -0
- deepset_mcp/tools/custom_components.py +52 -0
- deepset_mcp/tools/doc_search.py +83 -0
- deepset_mcp/tools/haystack_service.py +358 -0
- deepset_mcp/tools/haystack_service_models.py +97 -0
- deepset_mcp/tools/indexes.py +129 -0
- deepset_mcp/tools/model_protocol.py +16 -0
- deepset_mcp/tools/pipeline.py +335 -0
- deepset_mcp/tools/pipeline_template.py +116 -0
- deepset_mcp/tools/secrets.py +45 -0
- deepset_mcp/tools/tokonomics/__init__.py +73 -0
- deepset_mcp/tools/tokonomics/decorators.py +396 -0
- deepset_mcp/tools/tokonomics/explorer.py +347 -0
- deepset_mcp/tools/tokonomics/object_store.py +177 -0
- deepset_mcp/tools/workspace.py +61 -0
- deepset_mcp-0.0.2.dist-info/METADATA +288 -0
- deepset_mcp-0.0.2.dist-info/RECORD +114 -0
- deepset_mcp-0.0.2.dist-info/WHEEL +4 -0
- deepset_mcp-0.0.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Factory for creating workspace-aware MCP tools."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from mcp.server.fastmcp import FastMCP
|
|
13
|
+
|
|
14
|
+
from deepset_mcp.api.client import AsyncDeepsetClient
|
|
15
|
+
from deepset_mcp.initialize_embedding_model import get_initialized_model
|
|
16
|
+
from deepset_mcp.store import STORE
|
|
17
|
+
from deepset_mcp.tools.custom_components import (
|
|
18
|
+
get_latest_custom_component_installation_logs as get_latest_custom_component_installation_logs_tool,
|
|
19
|
+
list_custom_component_installations as list_custom_component_installations_tool,
|
|
20
|
+
)
|
|
21
|
+
from deepset_mcp.tools.doc_search import (
|
|
22
|
+
get_docs_config,
|
|
23
|
+
search_docs as search_docs_tool,
|
|
24
|
+
)
|
|
25
|
+
from deepset_mcp.tools.haystack_service import (
|
|
26
|
+
get_component_definition as get_component_definition_tool,
|
|
27
|
+
get_custom_components as get_custom_components_tool,
|
|
28
|
+
list_component_families as list_component_families_tool,
|
|
29
|
+
search_component_definition as search_component_definition_tool,
|
|
30
|
+
)
|
|
31
|
+
from deepset_mcp.tools.indexes import (
|
|
32
|
+
create_index as create_index_tool,
|
|
33
|
+
deploy_index as deploy_index_tool,
|
|
34
|
+
get_index as get_index_tool,
|
|
35
|
+
list_indexes as list_indexes_tool,
|
|
36
|
+
update_index as update_index_tool,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Import all tool functions
|
|
40
|
+
from deepset_mcp.tools.pipeline import (
|
|
41
|
+
create_pipeline as create_pipeline_tool,
|
|
42
|
+
deploy_pipeline as deploy_pipeline_tool,
|
|
43
|
+
get_pipeline as get_pipeline_tool,
|
|
44
|
+
get_pipeline_logs as get_pipeline_logs_tool,
|
|
45
|
+
list_pipelines as list_pipelines_tool,
|
|
46
|
+
search_pipeline as search_pipeline_tool,
|
|
47
|
+
update_pipeline as update_pipeline_tool,
|
|
48
|
+
validate_pipeline as validate_pipeline_tool,
|
|
49
|
+
)
|
|
50
|
+
from deepset_mcp.tools.pipeline_template import (
|
|
51
|
+
get_pipeline_template as get_pipeline_template_tool,
|
|
52
|
+
list_pipeline_templates as list_pipeline_templates_tool,
|
|
53
|
+
search_pipeline_templates as search_pipeline_templates_tool,
|
|
54
|
+
)
|
|
55
|
+
from deepset_mcp.tools.secrets import (
|
|
56
|
+
get_secret as get_secret_tool,
|
|
57
|
+
list_secrets as list_secrets_tool,
|
|
58
|
+
)
|
|
59
|
+
from deepset_mcp.tools.tokonomics import RichExplorer, explorable, explorable_and_referenceable, referenceable
|
|
60
|
+
from deepset_mcp.tools.workspace import (
|
|
61
|
+
create_workspace as create_workspace_tool,
|
|
62
|
+
get_workspace as get_workspace_tool,
|
|
63
|
+
list_workspaces as list_workspaces_tool,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
EXPLORER = RichExplorer(store=STORE)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_from_object_store(object_id: str, path: str = "") -> str:
|
|
70
|
+
"""Use this tool to fetch an object from the object store.
|
|
71
|
+
|
|
72
|
+
You can fetch a specific object by using the object's id (e.g. `@obj_001`).
|
|
73
|
+
You can also fetch any nested path by using the path-parameter
|
|
74
|
+
(e.g. `{"object_id": "@obj_001", "path": "user_info.given_name"}`
|
|
75
|
+
-> returns the content at obj.user_info.given_name).
|
|
76
|
+
|
|
77
|
+
:param object_id: The id of the object to fetch in the format `@obj_001`.
|
|
78
|
+
:param path: The path of the object to fetch in the format of `access.to.attr` or `["access"]["to"]["attr"]`.
|
|
79
|
+
"""
|
|
80
|
+
return EXPLORER.explore(obj_id=object_id, path=path)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_slice_from_object_store(
|
|
84
|
+
object_id: str,
|
|
85
|
+
start: int = 0,
|
|
86
|
+
end: int | None = None,
|
|
87
|
+
path: str = "",
|
|
88
|
+
) -> str:
|
|
89
|
+
"""Extract a slice from a string or list object that is stored in the object store.
|
|
90
|
+
|
|
91
|
+
:param object_id: Identifier of the object.
|
|
92
|
+
:param start: Start index for slicing.
|
|
93
|
+
:param end: End index for slicing (optional - leave empty to get slice from start to end of sequence).
|
|
94
|
+
:param path: Navigation path to object to slice (optional).
|
|
95
|
+
:return: String representation of the slice.
|
|
96
|
+
"""
|
|
97
|
+
return EXPLORER.slice(obj_id=object_id, start=start, end=end, path=path)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def search_docs(query: str) -> str:
|
|
101
|
+
"""Search the deepset platform documentation.
|
|
102
|
+
|
|
103
|
+
This tool allows you to search through deepset's official documentation to find
|
|
104
|
+
information about features, API usage, best practices, and troubleshooting guides.
|
|
105
|
+
Use this when you need to look up specific deepset functionality or help users
|
|
106
|
+
understand how to use deepset features.
|
|
107
|
+
|
|
108
|
+
:param query: The search query to execute against the documentation.
|
|
109
|
+
:returns: The formatted search results from the documentation.
|
|
110
|
+
"""
|
|
111
|
+
docs_config = get_docs_config()
|
|
112
|
+
if not docs_config:
|
|
113
|
+
raise RuntimeError("Documentation search configuration not available")
|
|
114
|
+
|
|
115
|
+
docs_workspace, docs_pipeline_name, docs_api_key = docs_config
|
|
116
|
+
async with AsyncDeepsetClient(api_key=docs_api_key) as client:
|
|
117
|
+
response = await search_docs_tool(
|
|
118
|
+
client=client,
|
|
119
|
+
workspace=docs_workspace,
|
|
120
|
+
pipeline_name=docs_pipeline_name,
|
|
121
|
+
query=query,
|
|
122
|
+
)
|
|
123
|
+
return response
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class WorkspaceMode(StrEnum):
|
|
127
|
+
"""Configuration for how workspace is provided to tools."""
|
|
128
|
+
|
|
129
|
+
IMPLICIT = "implicit" # workspace from env, no parameter in tool signature
|
|
130
|
+
EXPLICIT = "explicit" # workspace as required parameter in tool signature
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MemoryType(StrEnum):
|
|
134
|
+
"""Configuration for how memory is provided to tools."""
|
|
135
|
+
|
|
136
|
+
EXPLORABLE = "explorable"
|
|
137
|
+
REFERENCEABLE = "referenceable"
|
|
138
|
+
BOTH = "both"
|
|
139
|
+
NO_MEMORY = "no_memory"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class ToolConfig:
|
|
144
|
+
"""Configuration for tool registration."""
|
|
145
|
+
|
|
146
|
+
needs_client: bool = False
|
|
147
|
+
needs_workspace: bool = False
|
|
148
|
+
memory_type: MemoryType = MemoryType.NO_MEMORY
|
|
149
|
+
custom_args: dict[str, Any] | None = None # For special cases like search_component_definition
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_workspace_from_env() -> str:
|
|
153
|
+
"""Gets the workspace configured from environment variable."""
|
|
154
|
+
workspace = os.environ.get("DEEPSET_WORKSPACE")
|
|
155
|
+
if not workspace:
|
|
156
|
+
raise ValueError("DEEPSET_WORKSPACE environment variable not set")
|
|
157
|
+
return workspace
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
TOOL_REGISTRY: dict[str, tuple[Callable[..., Any], ToolConfig]] = {
|
|
161
|
+
# Workspace tools
|
|
162
|
+
"list_pipelines": (
|
|
163
|
+
list_pipelines_tool,
|
|
164
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
165
|
+
),
|
|
166
|
+
"create_pipeline": (
|
|
167
|
+
create_pipeline_tool,
|
|
168
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
169
|
+
),
|
|
170
|
+
"update_pipeline": (
|
|
171
|
+
update_pipeline_tool,
|
|
172
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
173
|
+
),
|
|
174
|
+
"get_pipeline": (
|
|
175
|
+
get_pipeline_tool,
|
|
176
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
177
|
+
),
|
|
178
|
+
"deploy_pipeline": (
|
|
179
|
+
deploy_pipeline_tool,
|
|
180
|
+
ToolConfig(
|
|
181
|
+
needs_client=True,
|
|
182
|
+
needs_workspace=True,
|
|
183
|
+
memory_type=MemoryType.EXPLORABLE,
|
|
184
|
+
custom_args={"wait_for_deployment": True, "timeout_seconds": 600, "poll_interval": 5},
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
"validate_pipeline": (
|
|
188
|
+
validate_pipeline_tool,
|
|
189
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
190
|
+
),
|
|
191
|
+
"get_pipeline_logs": (
|
|
192
|
+
get_pipeline_logs_tool,
|
|
193
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
194
|
+
),
|
|
195
|
+
"search_pipeline": (
|
|
196
|
+
search_pipeline_tool,
|
|
197
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
198
|
+
),
|
|
199
|
+
"list_indexes": (
|
|
200
|
+
list_indexes_tool,
|
|
201
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
202
|
+
),
|
|
203
|
+
"get_index": (
|
|
204
|
+
get_index_tool,
|
|
205
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
206
|
+
),
|
|
207
|
+
"create_index": (
|
|
208
|
+
create_index_tool,
|
|
209
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
210
|
+
),
|
|
211
|
+
"update_index": (
|
|
212
|
+
update_index_tool,
|
|
213
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
214
|
+
),
|
|
215
|
+
"deploy_index": (
|
|
216
|
+
deploy_index_tool,
|
|
217
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
218
|
+
),
|
|
219
|
+
"list_pipeline_templates": (
|
|
220
|
+
list_pipeline_templates_tool,
|
|
221
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
222
|
+
),
|
|
223
|
+
"get_pipeline_template": (
|
|
224
|
+
get_pipeline_template_tool,
|
|
225
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
226
|
+
),
|
|
227
|
+
"search_pipeline_templates": (
|
|
228
|
+
search_pipeline_templates_tool,
|
|
229
|
+
ToolConfig(
|
|
230
|
+
needs_client=True,
|
|
231
|
+
needs_workspace=True,
|
|
232
|
+
memory_type=MemoryType.EXPLORABLE,
|
|
233
|
+
custom_args={"model": get_initialized_model()},
|
|
234
|
+
),
|
|
235
|
+
),
|
|
236
|
+
"list_custom_component_installations": (
|
|
237
|
+
list_custom_component_installations_tool,
|
|
238
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
239
|
+
),
|
|
240
|
+
"get_latest_custom_component_installation_logs": (
|
|
241
|
+
get_latest_custom_component_installation_logs_tool,
|
|
242
|
+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
243
|
+
),
|
|
244
|
+
# Non-workspace tools
|
|
245
|
+
"list_component_families": (
|
|
246
|
+
list_component_families_tool,
|
|
247
|
+
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
248
|
+
),
|
|
249
|
+
"get_component_definition": (
|
|
250
|
+
get_component_definition_tool,
|
|
251
|
+
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
252
|
+
),
|
|
253
|
+
"search_component_definitions": (
|
|
254
|
+
search_component_definition_tool,
|
|
255
|
+
ToolConfig(
|
|
256
|
+
needs_client=True, memory_type=MemoryType.EXPLORABLE, custom_args={"model": get_initialized_model()}
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
"get_custom_components": (
|
|
260
|
+
get_custom_components_tool,
|
|
261
|
+
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
262
|
+
),
|
|
263
|
+
"list_secrets": (list_secrets_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
264
|
+
"get_secret": (get_secret_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
265
|
+
"list_workspaces": (list_workspaces_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
266
|
+
"get_workspace": (get_workspace_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
267
|
+
"create_workspace": (create_workspace_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
268
|
+
"get_from_object_store": (get_from_object_store, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
269
|
+
"get_slice_from_object_store": (get_slice_from_object_store, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
270
|
+
"search_docs": (search_docs, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def create_enhanced_tool(
|
|
275
|
+
base_func: Callable[..., Any], config: ToolConfig, workspace_mode: WorkspaceMode, workspace: str | None = None
|
|
276
|
+
) -> Callable[..., Awaitable[Any]]:
|
|
277
|
+
"""Universal tool creator that handles client injection, workspace, and decorators.
|
|
278
|
+
|
|
279
|
+
This function takes a base tool function and enhances it based on a configuration.
|
|
280
|
+
It can inject a `client`, manage a `workspace` parameter (either explicitly required
|
|
281
|
+
or implicitly provided from the environment), and apply memory-related decorators.
|
|
282
|
+
|
|
283
|
+
It also supports partial application of custom arguments specified in the ToolConfig.
|
|
284
|
+
These arguments are bound to the function, and both the function signature and the
|
|
285
|
+
docstring are updated to hide these implementation details from the end user of the tool.
|
|
286
|
+
|
|
287
|
+
All parameters in the final tool signature are converted to be keyword-only to enforce
|
|
288
|
+
explicit naming of arguments in tool calls.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
base_func: The base tool function.
|
|
292
|
+
config: Tool configuration specifying dependencies and custom arguments.
|
|
293
|
+
workspace_mode: How the workspace should be handled (implicit or explicit).
|
|
294
|
+
workspace: The workspace to use for implicit mode.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
An enhanced, awaitable tool function with an updated signature and docstring.
|
|
298
|
+
"""
|
|
299
|
+
original_func = base_func
|
|
300
|
+
|
|
301
|
+
# If custom arguments are provided, create a wrapper that applies them.
|
|
302
|
+
# This wrapper preserves the original function's metadata so that decorators work correctly.
|
|
303
|
+
func_to_decorate: Any
|
|
304
|
+
if config.custom_args:
|
|
305
|
+
|
|
306
|
+
@functools.wraps(original_func)
|
|
307
|
+
async def func_with_custom_args(*args: Any, **kwargs: Any) -> Any:
|
|
308
|
+
# Create a partial function with the custom arguments bound.
|
|
309
|
+
partial_func = functools.partial(original_func, **(config.custom_args or {}))
|
|
310
|
+
# Await the result of the partial function call.
|
|
311
|
+
return await partial_func(**kwargs)
|
|
312
|
+
|
|
313
|
+
func_to_decorate = func_with_custom_args
|
|
314
|
+
else:
|
|
315
|
+
func_to_decorate = original_func
|
|
316
|
+
|
|
317
|
+
# Apply memory-related decorators to the (potentially wrapped) function
|
|
318
|
+
decorated_func = func_to_decorate
|
|
319
|
+
if config.memory_type != MemoryType.NO_MEMORY:
|
|
320
|
+
store = STORE
|
|
321
|
+
explorer = RichExplorer(store)
|
|
322
|
+
|
|
323
|
+
if config.memory_type == MemoryType.EXPLORABLE:
|
|
324
|
+
decorated_func = explorable(object_store=store, explorer=explorer)(decorated_func)
|
|
325
|
+
elif config.memory_type == MemoryType.REFERENCEABLE:
|
|
326
|
+
decorated_func = referenceable(object_store=store, explorer=explorer)(decorated_func)
|
|
327
|
+
elif config.memory_type == MemoryType.BOTH:
|
|
328
|
+
decorated_func = explorable_and_referenceable(object_store=store, explorer=explorer)(decorated_func)
|
|
329
|
+
|
|
330
|
+
# Determine the parameters to remove from the original function's signature
|
|
331
|
+
params_to_remove: set[str] = set()
|
|
332
|
+
if config.custom_args:
|
|
333
|
+
params_to_remove.update(config.custom_args.keys())
|
|
334
|
+
if config.needs_client:
|
|
335
|
+
params_to_remove.add("client")
|
|
336
|
+
if config.needs_workspace and workspace_mode == WorkspaceMode.IMPLICIT:
|
|
337
|
+
params_to_remove.add("workspace")
|
|
338
|
+
|
|
339
|
+
# Create the new signature from the original function
|
|
340
|
+
original_sig = inspect.signature(original_func)
|
|
341
|
+
final_params = [p for name, p in original_sig.parameters.items() if name not in params_to_remove]
|
|
342
|
+
|
|
343
|
+
# Convert all positional-or-keyword parameters to be keyword-only
|
|
344
|
+
keyword_only_params = [
|
|
345
|
+
p.replace(kind=inspect.Parameter.KEYWORD_ONLY) if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD else p
|
|
346
|
+
for p in final_params
|
|
347
|
+
]
|
|
348
|
+
new_sig = original_sig.replace(parameters=keyword_only_params)
|
|
349
|
+
|
|
350
|
+
# Create the final wrapper function that handles client/workspace injection
|
|
351
|
+
if config.needs_client:
|
|
352
|
+
if config.needs_workspace:
|
|
353
|
+
if workspace_mode == WorkspaceMode.IMPLICIT:
|
|
354
|
+
|
|
355
|
+
async def workspace_implicit_wrapper(**kwargs: Any) -> Any:
|
|
356
|
+
ws = workspace or get_workspace_from_env()
|
|
357
|
+
async with AsyncDeepsetClient() as client:
|
|
358
|
+
return await decorated_func(client=client, workspace=ws, **kwargs)
|
|
359
|
+
|
|
360
|
+
wrapper = workspace_implicit_wrapper
|
|
361
|
+
else: # EXPLICIT mode
|
|
362
|
+
|
|
363
|
+
async def workspace_explicit_wrapper(**kwargs: Any) -> Any:
|
|
364
|
+
async with AsyncDeepsetClient() as client:
|
|
365
|
+
# The first argument is the workspace, which must be passed by keyword.
|
|
366
|
+
return await decorated_func(client=client, **kwargs)
|
|
367
|
+
|
|
368
|
+
wrapper = workspace_explicit_wrapper
|
|
369
|
+
else: # Client-only tools
|
|
370
|
+
|
|
371
|
+
async def client_only_wrapper(**kwargs: Any) -> Any:
|
|
372
|
+
async with AsyncDeepsetClient() as client:
|
|
373
|
+
return await decorated_func(client=client, **kwargs)
|
|
374
|
+
|
|
375
|
+
wrapper = client_only_wrapper
|
|
376
|
+
else: # No injection needed
|
|
377
|
+
if inspect.iscoroutinefunction(decorated_func):
|
|
378
|
+
|
|
379
|
+
async def no_injection_wrapper(**kwargs: Any) -> Any:
|
|
380
|
+
return await decorated_func(**kwargs)
|
|
381
|
+
|
|
382
|
+
wrapper = no_injection_wrapper
|
|
383
|
+
else:
|
|
384
|
+
|
|
385
|
+
@functools.wraps(decorated_func)
|
|
386
|
+
async def async_wrapper(**kwargs: Any) -> Any:
|
|
387
|
+
return decorated_func(**kwargs)
|
|
388
|
+
|
|
389
|
+
wrapper = async_wrapper
|
|
390
|
+
|
|
391
|
+
# Set metadata on the final wrapper
|
|
392
|
+
wrapper.__signature__ = new_sig # type: ignore
|
|
393
|
+
wrapper.__name__ = original_func.__name__
|
|
394
|
+
|
|
395
|
+
# Process the docstring to remove injected and partially applied parameters
|
|
396
|
+
if original_func.__doc__:
|
|
397
|
+
import re
|
|
398
|
+
|
|
399
|
+
doc = original_func.__doc__
|
|
400
|
+
params_to_remove_from_doc = set()
|
|
401
|
+
if config.needs_client:
|
|
402
|
+
params_to_remove_from_doc.add("client")
|
|
403
|
+
if config.needs_workspace and workspace_mode == WorkspaceMode.IMPLICIT:
|
|
404
|
+
params_to_remove_from_doc.add("workspace")
|
|
405
|
+
if config.custom_args:
|
|
406
|
+
params_to_remove_from_doc.update(config.custom_args.keys())
|
|
407
|
+
|
|
408
|
+
for param_name in params_to_remove_from_doc:
|
|
409
|
+
doc = re.sub(
|
|
410
|
+
rf"^\s*:param\s+{re.escape(param_name)}.*?(?=^\s*:|^\s*$|\Z)",
|
|
411
|
+
"",
|
|
412
|
+
doc,
|
|
413
|
+
flags=re.MULTILINE | re.DOTALL,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
wrapper.__doc__ = "\n".join([line.rstrip() for line in doc.strip().split("\n")])
|
|
417
|
+
else:
|
|
418
|
+
wrapper.__doc__ = original_func.__doc__
|
|
419
|
+
|
|
420
|
+
return wrapper
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def register_tools(
|
|
424
|
+
mcp: FastMCP, workspace_mode: WorkspaceMode, workspace: str | None = None, tool_names: set[str] | None = None
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Register tools with unified configuration.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
mcp: FastMCP server instance
|
|
430
|
+
workspace_mode: How workspace should be handled
|
|
431
|
+
workspace: Workspace to use for implicit mode (if None, reads from env)
|
|
432
|
+
tool_names: Set of tool names to register (if None, registers all tools)
|
|
433
|
+
"""
|
|
434
|
+
# Check if docs search is available
|
|
435
|
+
docs_available = get_docs_config() is not None
|
|
436
|
+
|
|
437
|
+
# Validate tool names if provided
|
|
438
|
+
if tool_names is not None:
|
|
439
|
+
all_tools = set(TOOL_REGISTRY.keys())
|
|
440
|
+
invalid_tools = tool_names - all_tools
|
|
441
|
+
if invalid_tools:
|
|
442
|
+
sorted_invalid = sorted(invalid_tools)
|
|
443
|
+
sorted_all = sorted(all_tools)
|
|
444
|
+
raise ValueError(f"Unknown tools: {', '.join(sorted_invalid)}\nAvailable tools: {', '.join(sorted_all)}")
|
|
445
|
+
|
|
446
|
+
# Warn if search_docs was requested but config is missing
|
|
447
|
+
if "search_docs" in tool_names and not docs_available:
|
|
448
|
+
logging.warning(
|
|
449
|
+
"Documentation search tool requested but not available. To enable, set the following environment "
|
|
450
|
+
"variables: DEEPSET_DOCS_WORKSPACE, DEEPSET_DOCS_PIPELINE_NAME, DEEPSET_DOCS_API_KEY"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
tools_to_register = tool_names.copy()
|
|
454
|
+
else:
|
|
455
|
+
tools_to_register = set(TOOL_REGISTRY.keys())
|
|
456
|
+
|
|
457
|
+
# Warn if search_docs would be skipped in "all tools" mode
|
|
458
|
+
if not docs_available:
|
|
459
|
+
logging.warning(
|
|
460
|
+
"Documentation search tool not enabled. To enable, set the following environment "
|
|
461
|
+
"variables: DEEPSET_DOCS_WORKSPACE, DEEPSET_DOCS_PIPELINE_NAME, DEEPSET_DOCS_API_KEY"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Remove search_docs if config is not available
|
|
465
|
+
if not docs_available:
|
|
466
|
+
tools_to_register.discard("search_docs")
|
|
467
|
+
|
|
468
|
+
for tool_name in tools_to_register:
|
|
469
|
+
base_func, config = TOOL_REGISTRY[tool_name]
|
|
470
|
+
# Create enhanced tool
|
|
471
|
+
enhanced_tool = create_enhanced_tool(base_func, config, workspace_mode, workspace)
|
|
472
|
+
|
|
473
|
+
mcp.add_tool(enhanced_tool, name=tool_name, structured_output=False)
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from deepset_mcp.api.custom_components.models import CustomComponentInstallationList
|
|
2
|
+
from deepset_mcp.api.protocols import AsyncClientProtocol
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def list_custom_component_installations(
|
|
6
|
+
*, client: AsyncClientProtocol, workspace: str
|
|
7
|
+
) -> CustomComponentInstallationList | str:
|
|
8
|
+
"""List custom component installations.
|
|
9
|
+
|
|
10
|
+
:param client: The API client to use.
|
|
11
|
+
:param workspace: The workspace to operate in.
|
|
12
|
+
|
|
13
|
+
:returns: Custom component installations or error message.
|
|
14
|
+
"""
|
|
15
|
+
custom_components = client.custom_components(workspace)
|
|
16
|
+
users = client.users()
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
installations = await custom_components.list_installations()
|
|
20
|
+
except Exception as e:
|
|
21
|
+
return f"Failed to retrieve custom component installations: {e}"
|
|
22
|
+
|
|
23
|
+
# Enrich installations with user information
|
|
24
|
+
for installation in installations.data:
|
|
25
|
+
if installation.created_by_user_id:
|
|
26
|
+
try:
|
|
27
|
+
user = await users.get(installation.created_by_user_id)
|
|
28
|
+
installation.user_info = user
|
|
29
|
+
except Exception:
|
|
30
|
+
# If user fetch fails, user_info remains None
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
return installations
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def get_latest_custom_component_installation_logs(*, client: AsyncClientProtocol, workspace: str) -> str:
|
|
37
|
+
"""Get the logs from the latest custom component installation.
|
|
38
|
+
|
|
39
|
+
:param client: The API client to use.
|
|
40
|
+
:param workspace: The workspace to operate in.
|
|
41
|
+
|
|
42
|
+
:returns: The latest installation logs or error message.
|
|
43
|
+
"""
|
|
44
|
+
custom_components = client.custom_components(workspace)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
logs = await custom_components.get_latest_installation_logs()
|
|
48
|
+
if not logs:
|
|
49
|
+
return "No installation logs found."
|
|
50
|
+
return logs
|
|
51
|
+
except Exception as e:
|
|
52
|
+
return f"Failed to retrieve latest installation logs: {e}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from deepset_mcp.api.exceptions import BadRequestError, ResourceNotFoundError, UnexpectedAPIError
|
|
4
|
+
from deepset_mcp.api.pipeline.models import DeepsetSearchResponse
|
|
5
|
+
from deepset_mcp.api.protocols import AsyncClientProtocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def doc_search_results_to_llm_readable_string(*, results: DeepsetSearchResponse) -> str:
|
|
9
|
+
"""Formats results of the doc search pipeline so that they can be read by an LLM.
|
|
10
|
+
|
|
11
|
+
:param results: DeepsetSearchResponse object
|
|
12
|
+
:return: Formatted results.
|
|
13
|
+
"""
|
|
14
|
+
file_segmented_docs = []
|
|
15
|
+
|
|
16
|
+
previous_source_id = None
|
|
17
|
+
for doc in results.documents:
|
|
18
|
+
if previous_source_id != doc.meta["source_id"]:
|
|
19
|
+
file_segmented_docs.append([{"content": doc.content, "file_path": doc.meta.get("original_file_path", "")}])
|
|
20
|
+
previous_source_id = doc.meta.get("source_id")
|
|
21
|
+
else:
|
|
22
|
+
file_segmented_docs[-1].append(
|
|
23
|
+
{"content": doc.content, "file_path": doc.meta.get("original_file_path", "")}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
files = []
|
|
27
|
+
for file_docs in file_segmented_docs:
|
|
28
|
+
start = file_docs[0]["file_path"]
|
|
29
|
+
full_doc = " ".join([doc["content"] for doc in file_docs])
|
|
30
|
+
files.append(start + "\n" + full_doc)
|
|
31
|
+
|
|
32
|
+
return "\n----\n".join(files)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def search_docs(*, client: AsyncClientProtocol, workspace: str, pipeline_name: str, query: str) -> str:
|
|
36
|
+
"""Search deepset documentation using a dedicated docs pipeline.
|
|
37
|
+
|
|
38
|
+
Uses the specified pipeline to perform a search with the given query against the deepset
|
|
39
|
+
documentation. Before executing the search, checks if the pipeline is deployed (status = DEPLOYED).
|
|
40
|
+
Returns search results in a human-readable format.
|
|
41
|
+
|
|
42
|
+
:param client: The async client for API communication.
|
|
43
|
+
:param workspace: The workspace name for the docs pipeline.
|
|
44
|
+
:param pipeline_name: Name of the pipeline to use for doc search.
|
|
45
|
+
:param query: The search query to execute.
|
|
46
|
+
:returns: A string containing the formatted search results or error message.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# First, check if the pipeline exists and get its status
|
|
50
|
+
pipeline = await client.pipelines(workspace=workspace).get(pipeline_name=pipeline_name)
|
|
51
|
+
|
|
52
|
+
# Check if pipeline is deployed
|
|
53
|
+
if pipeline.status != "DEPLOYED":
|
|
54
|
+
return f"Documentation pipeline '{pipeline_name}' is not deployed (current status: {pipeline.status})."
|
|
55
|
+
|
|
56
|
+
# Execute the search
|
|
57
|
+
search_response = await client.pipelines(workspace=workspace).search(pipeline_name=pipeline_name, query=query)
|
|
58
|
+
|
|
59
|
+
return doc_search_results_to_llm_readable_string(results=search_response)
|
|
60
|
+
|
|
61
|
+
except ResourceNotFoundError:
|
|
62
|
+
return f"There is no documentation pipeline named '{pipeline_name}' in workspace '{workspace}'."
|
|
63
|
+
except BadRequestError as e:
|
|
64
|
+
return f"Failed to search documentation using pipeline '{pipeline_name}': {e}"
|
|
65
|
+
except UnexpectedAPIError as e:
|
|
66
|
+
return f"Failed to search documentation using pipeline '{pipeline_name}': {e}"
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return f"An unexpected error occurred while searching documentation with pipeline '{pipeline_name}': {str(e)}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_docs_config() -> tuple[str, str, str] | None:
|
|
72
|
+
"""Get docs search configuration from environment variables.
|
|
73
|
+
|
|
74
|
+
:returns: Tuple of (workspace, pipeline_name, api_key) if all are available, None otherwise.
|
|
75
|
+
"""
|
|
76
|
+
workspace = os.environ.get("DEEPSET_DOCS_WORKSPACE")
|
|
77
|
+
pipeline_name = os.environ.get("DEEPSET_DOCS_PIPELINE_NAME")
|
|
78
|
+
api_key = os.environ.get("DEEPSET_DOCS_API_KEY")
|
|
79
|
+
|
|
80
|
+
if workspace and pipeline_name and api_key:
|
|
81
|
+
return workspace, pipeline_name, api_key
|
|
82
|
+
|
|
83
|
+
return None
|