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.
Files changed (114) hide show
  1. deepset_mcp/__init__.py +0 -0
  2. deepset_mcp/agents/__init__.py +0 -0
  3. deepset_mcp/agents/debugging/__init__.py +0 -0
  4. deepset_mcp/agents/debugging/debugging_agent.py +37 -0
  5. deepset_mcp/agents/debugging/system_prompt.md +214 -0
  6. deepset_mcp/agents/generalist/__init__.py +0 -0
  7. deepset_mcp/agents/generalist/generalist_agent.py +38 -0
  8. deepset_mcp/agents/generalist/system_prompt.md +241 -0
  9. deepset_mcp/api/README.md +536 -0
  10. deepset_mcp/api/__init__.py +0 -0
  11. deepset_mcp/api/client.py +277 -0
  12. deepset_mcp/api/custom_components/__init__.py +0 -0
  13. deepset_mcp/api/custom_components/models.py +25 -0
  14. deepset_mcp/api/custom_components/protocols.py +17 -0
  15. deepset_mcp/api/custom_components/resource.py +56 -0
  16. deepset_mcp/api/exceptions.py +70 -0
  17. deepset_mcp/api/haystack_service/__init__.py +0 -0
  18. deepset_mcp/api/haystack_service/protocols.py +13 -0
  19. deepset_mcp/api/haystack_service/resource.py +55 -0
  20. deepset_mcp/api/indexes/__init__.py +0 -0
  21. deepset_mcp/api/indexes/models.py +63 -0
  22. deepset_mcp/api/indexes/protocols.py +53 -0
  23. deepset_mcp/api/indexes/resource.py +138 -0
  24. deepset_mcp/api/integrations/__init__.py +1 -0
  25. deepset_mcp/api/integrations/models.py +49 -0
  26. deepset_mcp/api/integrations/protocols.py +27 -0
  27. deepset_mcp/api/integrations/resource.py +57 -0
  28. deepset_mcp/api/pipeline/__init__.py +17 -0
  29. deepset_mcp/api/pipeline/log_level.py +9 -0
  30. deepset_mcp/api/pipeline/models.py +235 -0
  31. deepset_mcp/api/pipeline/protocols.py +83 -0
  32. deepset_mcp/api/pipeline/resource.py +378 -0
  33. deepset_mcp/api/pipeline_template/__init__.py +0 -0
  34. deepset_mcp/api/pipeline_template/models.py +56 -0
  35. deepset_mcp/api/pipeline_template/protocols.py +17 -0
  36. deepset_mcp/api/pipeline_template/resource.py +88 -0
  37. deepset_mcp/api/protocols.py +122 -0
  38. deepset_mcp/api/secrets/__init__.py +0 -0
  39. deepset_mcp/api/secrets/models.py +16 -0
  40. deepset_mcp/api/secrets/protocols.py +29 -0
  41. deepset_mcp/api/secrets/resource.py +112 -0
  42. deepset_mcp/api/shared_models.py +17 -0
  43. deepset_mcp/api/transport.py +336 -0
  44. deepset_mcp/api/user/__init__.py +0 -0
  45. deepset_mcp/api/user/protocols.py +11 -0
  46. deepset_mcp/api/user/resource.py +38 -0
  47. deepset_mcp/api/workspace/__init__.py +7 -0
  48. deepset_mcp/api/workspace/models.py +23 -0
  49. deepset_mcp/api/workspace/protocols.py +41 -0
  50. deepset_mcp/api/workspace/resource.py +94 -0
  51. deepset_mcp/benchmark/README.md +425 -0
  52. deepset_mcp/benchmark/__init__.py +1 -0
  53. deepset_mcp/benchmark/agent_configs/debugging_agent.yml +10 -0
  54. deepset_mcp/benchmark/agent_configs/generalist_agent.yml +6 -0
  55. deepset_mcp/benchmark/dp_validation_error_analysis/__init__.py +0 -0
  56. deepset_mcp/benchmark/dp_validation_error_analysis/eda.ipynb +757 -0
  57. deepset_mcp/benchmark/dp_validation_error_analysis/prepare_interaction_data.ipynb +167 -0
  58. deepset_mcp/benchmark/dp_validation_error_analysis/preprocessing_utils.py +213 -0
  59. deepset_mcp/benchmark/runner/__init__.py +0 -0
  60. deepset_mcp/benchmark/runner/agent_benchmark_runner.py +561 -0
  61. deepset_mcp/benchmark/runner/agent_loader.py +110 -0
  62. deepset_mcp/benchmark/runner/cli.py +39 -0
  63. deepset_mcp/benchmark/runner/cli_agent.py +373 -0
  64. deepset_mcp/benchmark/runner/cli_index.py +71 -0
  65. deepset_mcp/benchmark/runner/cli_pipeline.py +73 -0
  66. deepset_mcp/benchmark/runner/cli_tests.py +226 -0
  67. deepset_mcp/benchmark/runner/cli_utils.py +61 -0
  68. deepset_mcp/benchmark/runner/config.py +73 -0
  69. deepset_mcp/benchmark/runner/config_loader.py +64 -0
  70. deepset_mcp/benchmark/runner/interactive.py +140 -0
  71. deepset_mcp/benchmark/runner/models.py +203 -0
  72. deepset_mcp/benchmark/runner/repl.py +67 -0
  73. deepset_mcp/benchmark/runner/setup_actions.py +238 -0
  74. deepset_mcp/benchmark/runner/streaming.py +360 -0
  75. deepset_mcp/benchmark/runner/teardown_actions.py +196 -0
  76. deepset_mcp/benchmark/runner/tracing.py +21 -0
  77. deepset_mcp/benchmark/tasks/chat_rag_answers_wrong_format.yml +16 -0
  78. deepset_mcp/benchmark/tasks/documents_output_wrong.yml +13 -0
  79. deepset_mcp/benchmark/tasks/jinja_str_instead_of_complex_type.yml +11 -0
  80. deepset_mcp/benchmark/tasks/jinja_syntax_error.yml +11 -0
  81. deepset_mcp/benchmark/tasks/missing_output_mapping.yml +14 -0
  82. deepset_mcp/benchmark/tasks/no_query_input.yml +13 -0
  83. deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_str.yml +141 -0
  84. deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_syntax.yml +141 -0
  85. deepset_mcp/benchmark/tasks/pipelines/chat_rag_answers_wrong_format.yml +181 -0
  86. deepset_mcp/benchmark/tasks/pipelines/chat_rag_missing_output_mapping.yml +189 -0
  87. deepset_mcp/benchmark/tasks/pipelines/rag_documents_wrong_format.yml +193 -0
  88. deepset_mcp/benchmark/tasks/pipelines/rag_no_query_input.yml +191 -0
  89. deepset_mcp/benchmark/tasks/pipelines/standard_index.yml +167 -0
  90. deepset_mcp/initialize_embedding_model.py +12 -0
  91. deepset_mcp/main.py +133 -0
  92. deepset_mcp/prompts/deepset_copilot_prompt.md +271 -0
  93. deepset_mcp/prompts/deepset_debugging_agent.md +214 -0
  94. deepset_mcp/store.py +5 -0
  95. deepset_mcp/tool_factory.py +473 -0
  96. deepset_mcp/tools/__init__.py +0 -0
  97. deepset_mcp/tools/custom_components.py +52 -0
  98. deepset_mcp/tools/doc_search.py +83 -0
  99. deepset_mcp/tools/haystack_service.py +358 -0
  100. deepset_mcp/tools/haystack_service_models.py +97 -0
  101. deepset_mcp/tools/indexes.py +129 -0
  102. deepset_mcp/tools/model_protocol.py +16 -0
  103. deepset_mcp/tools/pipeline.py +335 -0
  104. deepset_mcp/tools/pipeline_template.py +116 -0
  105. deepset_mcp/tools/secrets.py +45 -0
  106. deepset_mcp/tools/tokonomics/__init__.py +73 -0
  107. deepset_mcp/tools/tokonomics/decorators.py +396 -0
  108. deepset_mcp/tools/tokonomics/explorer.py +347 -0
  109. deepset_mcp/tools/tokonomics/object_store.py +177 -0
  110. deepset_mcp/tools/workspace.py +61 -0
  111. deepset_mcp-0.0.2.dist-info/METADATA +288 -0
  112. deepset_mcp-0.0.2.dist-info/RECORD +114 -0
  113. deepset_mcp-0.0.2.dist-info/WHEEL +4 -0
  114. deepset_mcp-0.0.2.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,29 @@
1
+ from typing import Protocol
2
+
3
+ from deepset_mcp.api.secrets.models import Secret, SecretList
4
+ from deepset_mcp.api.shared_models import NoContentResponse
5
+
6
+
7
+ class SecretResourceProtocol(Protocol):
8
+ """Protocol defining the implementation for SecretResource."""
9
+
10
+ async def list(
11
+ self,
12
+ limit: int = 10,
13
+ field: str = "created_at",
14
+ order: str = "DESC",
15
+ ) -> SecretList:
16
+ """List secrets with pagination."""
17
+ ...
18
+
19
+ async def create(self, name: str, secret: str) -> NoContentResponse:
20
+ """Create a new secret."""
21
+ ...
22
+
23
+ async def get(self, secret_id: str) -> Secret:
24
+ """Get a specific secret by ID."""
25
+ ...
26
+
27
+ async def delete(self, secret_id: str) -> NoContentResponse:
28
+ """Delete a secret by ID."""
29
+ ...
@@ -0,0 +1,112 @@
1
+ from typing import Any
2
+
3
+ from deepset_mcp.api.exceptions import ResourceNotFoundError
4
+ from deepset_mcp.api.protocols import AsyncClientProtocol
5
+ from deepset_mcp.api.secrets.models import Secret, SecretList
6
+ from deepset_mcp.api.secrets.protocols import SecretResourceProtocol
7
+ from deepset_mcp.api.shared_models import NoContentResponse
8
+ from deepset_mcp.api.transport import raise_for_status
9
+
10
+
11
+ class SecretResource(SecretResourceProtocol):
12
+ """Resource for managing secrets in deepset."""
13
+
14
+ def __init__(self, client: AsyncClientProtocol) -> None:
15
+ """Initialize a SecretResource.
16
+
17
+ :param client: The API client to use for requests.
18
+ """
19
+ self._client = client
20
+
21
+ async def list(
22
+ self,
23
+ limit: int = 10,
24
+ field: str = "created_at",
25
+ order: str = "DESC",
26
+ ) -> SecretList:
27
+ """List secrets with pagination.
28
+
29
+ :param limit: Maximum number of secrets to return.
30
+ :param field: Field to sort by.
31
+ :param order: Sort order (ASC or DESC).
32
+
33
+ :returns: List of secrets with pagination info.
34
+ """
35
+ params = {
36
+ "limit": str(limit),
37
+ "field": field,
38
+ "order": order,
39
+ }
40
+
41
+ resp = await self._client.request(
42
+ endpoint="v2/secrets",
43
+ method="GET",
44
+ response_type=dict[str, Any],
45
+ params=params,
46
+ )
47
+
48
+ raise_for_status(resp)
49
+
50
+ if resp.json is None:
51
+ raise ResourceNotFoundError("Failed to retrieve secrets.")
52
+
53
+ return SecretList(**resp.json)
54
+
55
+ async def create(self, name: str, secret: str) -> NoContentResponse:
56
+ """Create a new secret.
57
+
58
+ :param name: The name of the secret.
59
+ :param secret: The secret value.
60
+
61
+ :returns: NoContentResponse indicating successful creation.
62
+ """
63
+ data = {
64
+ "name": name,
65
+ "secret": secret,
66
+ }
67
+
68
+ resp = await self._client.request(
69
+ endpoint="v2/secrets",
70
+ method="POST",
71
+ data=data,
72
+ response_type=None,
73
+ )
74
+
75
+ raise_for_status(resp)
76
+ return NoContentResponse(message="Secret created successfully.")
77
+
78
+ async def get(self, secret_id: str) -> Secret:
79
+ """Get a specific secret by ID.
80
+
81
+ :param secret_id: The ID of the secret to retrieve.
82
+
83
+ :returns: Secret information.
84
+ """
85
+ resp = await self._client.request(
86
+ endpoint=f"v2/secrets/{secret_id}",
87
+ method="GET",
88
+ response_type=dict[str, Any],
89
+ )
90
+
91
+ raise_for_status(resp)
92
+
93
+ if resp.json is None:
94
+ raise ResourceNotFoundError(f"Secret '{secret_id}' not found.")
95
+
96
+ return Secret(**resp.json)
97
+
98
+ async def delete(self, secret_id: str) -> NoContentResponse:
99
+ """Delete a secret by ID.
100
+
101
+ :param secret_id: The ID of the secret to delete.
102
+
103
+ :returns: NoContentResponse indicating successful deletion.
104
+ """
105
+ resp = await self._client.request(
106
+ endpoint=f"v2/secrets/{secret_id}",
107
+ method="DELETE",
108
+ response_type=None,
109
+ )
110
+
111
+ raise_for_status(resp)
112
+ return NoContentResponse(message="Secret deleted successfully.")
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class NoContentResponse(BaseModel):
5
+ """Response model for an empty response."""
6
+
7
+ success: bool = True
8
+ message: str = "No content"
9
+
10
+
11
+ class DeepsetUser(BaseModel):
12
+ """Model representing a user on the deepset platform."""
13
+
14
+ id: str = Field(alias="user_id")
15
+ given_name: str | None = None
16
+ family_name: str | None = None
17
+ email: str | None = None
@@ -0,0 +1,336 @@
1
+ import json
2
+ import time
3
+ from collections.abc import AsyncIterator
4
+ from contextlib import AbstractAsyncContextManager, asynccontextmanager
5
+ from dataclasses import dataclass
6
+ from typing import Any, Generic, Literal, Protocol, TypeVar, cast, overload
7
+
8
+ import httpx
9
+
10
+ from deepset_mcp.api.exceptions import BadRequestError, RequestTimeoutError, ResourceNotFoundError, UnexpectedAPIError
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class StreamReaderProtocol(Protocol):
16
+ """Protocol for reading from a stream."""
17
+
18
+ async def aread(self) -> bytes:
19
+ """Read the entire response body."""
20
+ ...
21
+
22
+ def aiter_lines(self) -> AsyncIterator[str]:
23
+ """Iterate over response lines."""
24
+ ...
25
+
26
+
27
+ @dataclass
28
+ class TransportResponse(Generic[T]):
29
+ """Response envelope for regular HTTP transport."""
30
+
31
+ text: str
32
+ status_code: int
33
+ json: T | None = None
34
+
35
+ @property
36
+ def success(self) -> bool:
37
+ """Check if the response was successful (status code < 400)."""
38
+ return self.status_code < 400
39
+
40
+
41
+ @dataclass
42
+ class StreamingResponse:
43
+ """Response envelope for streaming HTTP transport."""
44
+
45
+ status_code: int
46
+ headers: dict[str, str]
47
+ _reader: StreamReaderProtocol
48
+
49
+ @property
50
+ def success(self) -> bool:
51
+ """Check if the response was successful (status code < 400)."""
52
+ return self.status_code < 400
53
+
54
+ async def iter_lines(self) -> AsyncIterator[str]:
55
+ """
56
+ Iterate over response lines.
57
+
58
+ For error responses (status >= 400), reads the entire body and yields it.
59
+ For success responses, yields line by line.
60
+ """
61
+ if self.status_code >= 400:
62
+ # For errors, read entire body at once
63
+ body = await self._reader.aread()
64
+ if body:
65
+ yield body.decode()
66
+ else:
67
+ # For success, stream line by line
68
+ async for line in self._reader.aiter_lines():
69
+ if line.startswith("data: "): # optionally handle SSE 'data: ' prefix
70
+ yield line[6:]
71
+ yield line
72
+
73
+ async def read_body(self) -> str:
74
+ """Read the entire response body. Useful for error handling."""
75
+ body = await self._reader.aread()
76
+ return body.decode() if body else ""
77
+
78
+
79
+ def raise_for_status(response: TransportResponse[Any]) -> None:
80
+ """Raises the appropriate exception based on the response status code."""
81
+ if response.success:
82
+ return
83
+
84
+ # Map status codes to exception classes
85
+ exception_map = {
86
+ 400: BadRequestError,
87
+ 404: ResourceNotFoundError,
88
+ }
89
+
90
+ if isinstance(response.json, dict):
91
+ detail = response.json.get("details") if response.json else None
92
+ message = response.json.get("message") if response.json else response.text
93
+ else:
94
+ detail = json.dumps(response.json) if response.json else None
95
+ message = response.text
96
+
97
+ # Get exception class
98
+ exception_class = exception_map.get(response.status_code)
99
+
100
+ if exception_class:
101
+ # For specific exceptions (BadRequestError, ResourceNotFoundError)
102
+ raise exception_class(message=message, detail=detail)
103
+ else:
104
+ # For the catch-all case, include the status code
105
+ raise UnexpectedAPIError(
106
+ status_code=response.status_code, message=message or "Unexpected API error", detail=detail
107
+ )
108
+
109
+
110
+ class TransportProtocol(Protocol):
111
+ """Protocol for HTTP transport with separate streaming support."""
112
+
113
+ @overload
114
+ async def request(
115
+ self,
116
+ method: str,
117
+ url: str,
118
+ *,
119
+ response_type: type[T],
120
+ timeout: float | None | Literal["config"] = "config",
121
+ **kwargs: Any,
122
+ ) -> TransportResponse[T]: ...
123
+
124
+ @overload
125
+ async def request(
126
+ self,
127
+ method: str,
128
+ url: str,
129
+ *,
130
+ response_type: None = None,
131
+ timeout: float | None | Literal["config"] = "config",
132
+ **kwargs: Any,
133
+ ) -> TransportResponse[Any]: ...
134
+
135
+ async def request(
136
+ self,
137
+ method: str,
138
+ url: str,
139
+ *,
140
+ response_type: type[T] | None = None,
141
+ timeout: float | None | Literal["config"] = "config",
142
+ **kwargs: Any,
143
+ ) -> TransportResponse[Any]:
144
+ """Send a regular HTTP request and return the response."""
145
+ ...
146
+
147
+ def stream(self, method: str, url: str, **kwargs: Any) -> AbstractAsyncContextManager[StreamingResponse]:
148
+ """
149
+ Open a streaming HTTP connection.
150
+
151
+ Must be used as an async context manager to ensure proper cleanup.
152
+ """
153
+ ...
154
+
155
+ async def close(self) -> None:
156
+ """Clean up any resources (e.g., close connections)."""
157
+ ...
158
+
159
+
160
+ class _HttpxStreamReader:
161
+ """Adapter to make httpx.Response conform to StreamReaderProtocol."""
162
+
163
+ def __init__(self, response: httpx.Response):
164
+ self._response = response
165
+
166
+ async def aread(self) -> bytes:
167
+ """Read the entire response body."""
168
+ return await self._response.aread()
169
+
170
+ async def aiter_lines(self) -> AsyncIterator[str]:
171
+ """Iterate over response lines."""
172
+ async for line in self._response.aiter_lines():
173
+ yield line
174
+
175
+
176
+ class AsyncTransport:
177
+ """Asynchronous HTTP transport using httpx.AsyncClient."""
178
+
179
+ def __init__(
180
+ self,
181
+ base_url: str,
182
+ api_key: str,
183
+ config: dict[str, Any] | None = None,
184
+ ):
185
+ """
186
+ Initialize an instance of AsyncTransport.
187
+
188
+ Parameters
189
+ ----------
190
+ base_url : str
191
+ Base URL for the API
192
+ api_key : str
193
+ Bearer token for authentication
194
+ config : dict, optional
195
+ Configuration for httpx.AsyncClient, e.g., {'timeout': 10.0}
196
+ """
197
+ config = config or {}
198
+ # Ensure auth header
199
+ headers = config.pop("headers", {})
200
+ headers.setdefault("Authorization", f"Bearer {api_key}")
201
+ # Build client kwargs
202
+ client_kwargs = {
203
+ "base_url": base_url,
204
+ "headers": headers,
205
+ **config,
206
+ }
207
+ self._client = httpx.AsyncClient(**client_kwargs)
208
+
209
+ @overload
210
+ async def request(
211
+ self,
212
+ method: str,
213
+ url: str,
214
+ *,
215
+ response_type: type[T],
216
+ timeout: float | None | Literal["config"] = "config",
217
+ **kwargs: Any,
218
+ ) -> TransportResponse[T]: ...
219
+
220
+ @overload
221
+ async def request(
222
+ self,
223
+ method: str,
224
+ url: str,
225
+ *,
226
+ response_type: None = None,
227
+ timeout: float | None | Literal["config"] = "config",
228
+ **kwargs: Any,
229
+ ) -> TransportResponse[Any]: ...
230
+
231
+ async def request(
232
+ self,
233
+ method: str,
234
+ url: str,
235
+ *,
236
+ response_type: type[T] | None = None,
237
+ timeout: float | None | Literal["config"] = "config",
238
+ **kwargs: Any,
239
+ ) -> TransportResponse[Any]:
240
+ """
241
+ Send a regular HTTP request and return the response.
242
+
243
+ Parameters
244
+ ----------
245
+ method : str
246
+ HTTP method
247
+ url : str
248
+ URL endpoint
249
+ response_type : type[T], optional
250
+ Expected response type for type checking
251
+ timeout : float | None | Literal["config"], optional
252
+ Request timeout in seconds. If "config", uses transport config timeout.
253
+ If None, disables timeout. If float, uses specific timeout.
254
+ **kwargs : Any
255
+ Additional arguments to pass to httpx
256
+
257
+ Returns
258
+ -------
259
+ TransportResponse[T]
260
+ The response with parsed JSON if available
261
+ """
262
+ if timeout != "config":
263
+ kwargs["timeout"] = timeout
264
+
265
+ start_time = time.time()
266
+ try:
267
+ response = await self._client.request(method, url, **kwargs)
268
+ except httpx.TimeoutException as e:
269
+ duration = time.time() - start_time
270
+ timeout_value = kwargs.get("timeout", "config default")
271
+
272
+ detail = None
273
+ if "search" in url and duration > 60:
274
+ detail = (
275
+ "Search operations can take longer with large document collections or complex pipelines. "
276
+ "Consider increasing the timeout for search requests."
277
+ )
278
+
279
+ raise RequestTimeoutError(
280
+ method=method, url=url, timeout=timeout_value, duration=duration, detail=detail
281
+ ) from e
282
+
283
+ if response_type is not None:
284
+ raw = response.json()
285
+ payload: T = cast(T, raw)
286
+ return TransportResponse(text=response.text, status_code=response.status_code, json=payload)
287
+
288
+ try:
289
+ untyped_response = response.json()
290
+ except json.JSONDecodeError:
291
+ untyped_response = None
292
+
293
+ return TransportResponse(text=response.text, status_code=response.status_code, json=untyped_response)
294
+
295
+ def stream(self, method: str, url: str, **kwargs: Any) -> AbstractAsyncContextManager[StreamingResponse]:
296
+ """
297
+ Open a streaming HTTP connection.
298
+
299
+ Parameters
300
+ ----------
301
+ method : str
302
+ HTTP method
303
+ url : str
304
+ URL endpoint
305
+ **kwargs : Any
306
+ Additional arguments to pass to httpx.stream()
307
+
308
+ Yields
309
+ ------
310
+ StreamingResponse
311
+ Response object with streaming capabilities
312
+
313
+ Examples
314
+ --------
315
+ async with transport.stream("POST", "/api/stream", json=data) as response:
316
+ if response.success:
317
+ async for line in response.iter_lines():
318
+ process_line(line)
319
+ else:
320
+ error = await response.read_body()
321
+ handle_error(error)
322
+ """
323
+
324
+ @asynccontextmanager
325
+ async def _stream() -> AsyncIterator[StreamingResponse]:
326
+ async with self._client.stream(method, url, **kwargs) as response:
327
+ reader = _HttpxStreamReader(response)
328
+ yield StreamingResponse(
329
+ status_code=response.status_code, headers=dict(response.headers), _reader=reader
330
+ )
331
+
332
+ return _stream()
333
+
334
+ async def close(self) -> None:
335
+ """Clean up any resources (e.g., close connections)."""
336
+ await self._client.aclose()
File without changes
@@ -0,0 +1,11 @@
1
+ from typing import Protocol
2
+
3
+ from deepset_mcp.api.shared_models import DeepsetUser
4
+
5
+
6
+ class UserResourceProtocol(Protocol):
7
+ """Protocol defining the implementation for UserResource."""
8
+
9
+ async def get(self, user_id: str) -> DeepsetUser:
10
+ """Get user information by user ID."""
11
+ ...
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ from deepset_mcp.api.exceptions import ResourceNotFoundError
4
+ from deepset_mcp.api.protocols import AsyncClientProtocol
5
+ from deepset_mcp.api.shared_models import DeepsetUser
6
+ from deepset_mcp.api.transport import raise_for_status
7
+ from deepset_mcp.api.user.protocols import UserResourceProtocol
8
+
9
+
10
+ class UserResource(UserResourceProtocol):
11
+ """Resource for managing users in deepset."""
12
+
13
+ def __init__(self, client: AsyncClientProtocol) -> None:
14
+ """Initialize a UserResource.
15
+
16
+ :param client: The API client to use for requests.
17
+ """
18
+ self._client = client
19
+
20
+ async def get(self, user_id: str) -> DeepsetUser:
21
+ """Get user information by user ID.
22
+
23
+ :param user_id: The ID of the user to fetch.
24
+
25
+ :returns: User information.
26
+ """
27
+ resp = await self._client.request(
28
+ endpoint=f"v1/users/{user_id}",
29
+ method="GET",
30
+ response_type=dict[str, Any],
31
+ )
32
+
33
+ raise_for_status(resp)
34
+
35
+ if resp.json is None:
36
+ raise ResourceNotFoundError(f"User '{user_id}' not found.")
37
+
38
+ return DeepsetUser(**resp.json)
@@ -0,0 +1,7 @@
1
+ """Workspace API module."""
2
+
3
+ from .models import Workspace, WorkspaceList
4
+ from .protocols import WorkspaceResourceProtocol
5
+ from .resource import WorkspaceResource
6
+
7
+ __all__ = ["Workspace", "WorkspaceList", "WorkspaceResourceProtocol", "WorkspaceResource"]
@@ -0,0 +1,23 @@
1
+ """Models for workspace API responses."""
2
+
3
+ from typing import Any
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class Workspace(BaseModel):
10
+ """Model representing a workspace on the deepset platform."""
11
+
12
+ name: str
13
+ workspace_id: UUID
14
+ languages: dict[str, Any]
15
+ default_idle_timeout_in_seconds: int
16
+
17
+
18
+ class WorkspaceList(BaseModel):
19
+ """Model representing a list of workspaces."""
20
+
21
+ data: list[Workspace]
22
+ has_more: bool = False
23
+ total: int
@@ -0,0 +1,41 @@
1
+ """Protocols for workspace resources."""
2
+
3
+ from typing import Protocol
4
+
5
+ from deepset_mcp.api.shared_models import NoContentResponse
6
+ from deepset_mcp.api.workspace.models import Workspace, WorkspaceList
7
+
8
+
9
+ class WorkspaceResourceProtocol(Protocol):
10
+ """Protocol defining the interface for workspace resources."""
11
+
12
+ async def list(self) -> WorkspaceList:
13
+ """List all workspaces.
14
+
15
+ :returns: A WorkspaceList containing all workspaces.
16
+ """
17
+ ...
18
+
19
+ async def get(self, workspace_name: str) -> Workspace:
20
+ """Get a specific workspace by name.
21
+
22
+ :param workspace_name: Name of the workspace to fetch.
23
+ :returns: A Workspace instance.
24
+ """
25
+ ...
26
+
27
+ async def create(self, name: str) -> NoContentResponse:
28
+ """Create a new workspace.
29
+
30
+ :param name: Name of the new workspace.
31
+ :returns: NoContentResponse indicating successful creation.
32
+ """
33
+ ...
34
+
35
+ async def delete(self, workspace_name: str) -> NoContentResponse:
36
+ """Delete a workspace.
37
+
38
+ :param workspace_name: Name of the workspace to delete.
39
+ :returns: NoContentResponse indicating successful deletion.
40
+ """
41
+ ...