Skip to content

Commit fa7481e

Browse files
authored
feat: add HaystackServiceResource to API SDK (#21)
* feat: add HaystackServiceProtocol imports * feat: add HaystackServiceProtocol and extend AsyncClientProtocol * feat: add HaystackComponentSchema model * feat: add HaystackServiceResource implementation * feat: import HaystackServiceResource in client * feat: add haystack_service method to AsyncDeepsetClient * test: add unit tests for HaystackServiceResource * feat: wrapup haystack service; update test structure
1 parent fabed21 commit fa7481e

19 files changed

Lines changed: 321 additions & 169 deletions

src/deepset_mcp/api/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from types import TracebackType
33
from typing import Any, Self
44

5+
from deepset_mcp.api.haystack_service.resource import HaystackServiceResource
56
from deepset_mcp.api.pipeline.resource import PipelineResource
67
from deepset_mcp.api.protocols import AsyncClientProtocol
78
from deepset_mcp.api.transport import AsyncTransport, TransportProtocol, TransportResponse
@@ -93,3 +94,7 @@ async def __aexit__(
9394
def pipelines(self, workspace: str) -> PipelineResource:
9495
"""Resource to interact with pipelines in the specified workspace."""
9596
return PipelineResource(client=self, workspace=workspace)
97+
98+
def haystack_service(self) -> HaystackServiceResource:
99+
"""Resource to interact with the Haystack service API."""
100+
return HaystackServiceResource(client=self)

src/deepset_mcp/api/haystack_service/__init__.py

Whitespace-only changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Any
2+
3+
from deepset_mcp.api.protocols import AsyncClientProtocol, HaystackServiceProtocol
4+
from deepset_mcp.api.transport import raise_for_status
5+
6+
7+
class HaystackServiceResource(HaystackServiceProtocol):
8+
"""Manages interactions with the deepset Haystack service API."""
9+
10+
def __init__(self, client: AsyncClientProtocol) -> None:
11+
"""Initializes a HaystackServiceResource instance."""
12+
self._client = client
13+
14+
async def get_component_schemas(self) -> dict[str, Any]:
15+
"""Fetch the component schema from the API.
16+
17+
Returns:
18+
The component schema as a dictionary
19+
"""
20+
resp = await self._client.request(
21+
endpoint="v1/haystack/components",
22+
method="GET",
23+
headers={"accept": "application/json"},
24+
data={"domain": "deepset-cloud"},
25+
)
26+
27+
raise_for_status(resp)
28+
29+
return resp.json if resp.json is not None else {}

src/deepset_mcp/api/protocols.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
from deepset_mcp.api.transport import TransportResponse
66

77

8+
class HaystackServiceProtocol(Protocol):
9+
"""Protocol defining the implementation for HaystackService."""
10+
11+
async def get_component_schemas(self) -> dict[str, Any]:
12+
"""Fetch the component schema from the API."""
13+
...
14+
15+
816
class AsyncClientProtocol(Protocol):
917
"""Protocol defining the implementation for AsyncClient."""
1018

@@ -36,6 +44,10 @@ def pipelines(self, workspace: str) -> "PipelineResourceProtocol":
3644
"""Access pipelines in the specified workspace."""
3745
...
3846

47+
def haystack_service(self) -> "HaystackServiceProtocol":
48+
"""Access the Haystack service."""
49+
...
50+
3951

4052
class PipelineResourceProtocol(Protocol):
4153
"""Protocol defining the implementation for PipelineResource."""

src/deepset_mcp/main.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from typing import Any
23

34
from mcp.server.fastmcp import FastMCP
45

@@ -86,20 +87,16 @@ async def update_pipeline(
8687
return response
8788

8889

89-
#
90-
#
91-
# @mcp.tool()
92-
# async def get_component_schemas() -> Any:
93-
# """Retrieves the schemas for all available Haystack components from the deepset API.
94-
#
95-
# These schemas define the expected input and output parameters for each component type, which is useful for
96-
# constructing or validating componets in a pipeline YAML.
97-
# """
98-
# async with AsyncDeepsetClient() as client:
99-
# response = await client.request(endpoint="v1/haystack/components", method="GET")
100-
# return response.json
101-
#
102-
#
90+
@mcp.tool()
91+
async def get_component_schemas() -> Any:
92+
"""Retrieves the schemas for all available Haystack components from the deepset API.
93+
94+
These schemas define the expected input and output parameters for each component type, which is useful for
95+
constructing or validating componets in a pipeline YAML.
96+
"""
97+
async with AsyncDeepsetClient() as client:
98+
response = await client.request(endpoint="v1/haystack/components", method="GET")
99+
return response.json
103100

104101

105102
@mcp.tool()

test/__init__.py

Whitespace-only changes.

test/integration/__init__.py

Whitespace-only changes.

test/integration/conftest.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import os
2+
import uuid
3+
from collections.abc import AsyncGenerator
4+
from datetime import datetime
5+
6+
import pytest
7+
from dotenv import load_dotenv
8+
9+
from deepset_mcp.api.client import AsyncDeepsetClient
10+
11+
load_dotenv()
12+
13+
14+
@pytest.fixture
15+
def test_workspace_name() -> str:
16+
"""Create a unique workspace name for testing."""
17+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
18+
return f"test-workspace-{timestamp}-{uuid.uuid4().hex[:8]}"
19+
20+
21+
@pytest.fixture
22+
async def client() -> AsyncGenerator[AsyncDeepsetClient, None]:
23+
"""Create and configure the deepset client."""
24+
api_key = os.environ.get("DEEPSET_API_KEY")
25+
if not api_key:
26+
pytest.skip("DEEPSET_API_KEY environment variable not set")
27+
28+
async with AsyncDeepsetClient(api_key=api_key) as client:
29+
yield client
30+
31+
32+
@pytest.fixture
33+
async def test_workspace(
34+
client: AsyncDeepsetClient,
35+
test_workspace_name: str,
36+
) -> AsyncGenerator[str, None]:
37+
"""Create a test workspace and clean it up after tests."""
38+
# Create a test workspace
39+
await client.request(
40+
endpoint="v1/workspaces",
41+
method="POST",
42+
data={"name": test_workspace_name},
43+
)
44+
45+
yield test_workspace_name
46+
47+
# Clean up the workspace after tests
48+
try:
49+
await client.request(
50+
endpoint=f"v1/workspaces/{test_workspace_name}",
51+
method="DELETE",
52+
)
53+
except Exception as e:
54+
print(f"Failed to delete test workspace: {e}")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
3+
from deepset_mcp.api.client import AsyncDeepsetClient
4+
from deepset_mcp.api.haystack_service.resource import HaystackServiceResource
5+
6+
pytestmark = pytest.mark.integration
7+
8+
9+
@pytest.fixture
10+
async def haystack_service_resource(
11+
client: AsyncDeepsetClient,
12+
) -> HaystackServiceResource:
13+
"""Create a PipelineResource instance for testing."""
14+
return HaystackServiceResource(client=client)
15+
16+
17+
@pytest.mark.asyncio
18+
async def test_get_component_schemas(
19+
haystack_service_resource: HaystackServiceResource,
20+
) -> None:
21+
"""Test for getting component schemas."""
22+
response = await haystack_service_resource.get_component_schemas()
23+
24+
assert isinstance(response, dict)
25+
assert "component_schema" in response

test/integration/test_integration_pipeline_resource.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,11 @@
1-
import os
2-
import uuid
3-
from collections.abc import AsyncGenerator
4-
from datetime import datetime
5-
61
import pytest
7-
from dotenv import load_dotenv
82

93
from deepset_mcp.api.client import AsyncDeepsetClient
104
from deepset_mcp.api.exceptions import ResourceNotFoundError
115
from deepset_mcp.api.pipeline.models import DeepsetPipeline
126
from deepset_mcp.api.pipeline.resource import PipelineResource
137

148
pytestmark = pytest.mark.integration
15-
load_dotenv()
16-
17-
18-
@pytest.fixture
19-
def test_workspace_name() -> str:
20-
"""Create a unique workspace name for testing."""
21-
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
22-
return f"test-workspace-{timestamp}-{uuid.uuid4().hex[:8]}"
23-
24-
25-
@pytest.fixture
26-
async def client() -> AsyncGenerator[AsyncDeepsetClient, None]:
27-
"""Create and configure the deepset client."""
28-
api_key = os.environ.get("DEEPSET_API_KEY")
29-
if not api_key:
30-
pytest.skip("DEEPSET_API_KEY environment variable not set")
31-
32-
async with AsyncDeepsetClient(api_key=api_key) as client:
33-
yield client
34-
35-
36-
@pytest.fixture
37-
async def test_workspace(
38-
client: AsyncDeepsetClient,
39-
test_workspace_name: str,
40-
) -> AsyncGenerator[str, None]:
41-
"""Create a test workspace and clean it up after tests."""
42-
# Create a test workspace
43-
await client.request(
44-
endpoint="v1/workspaces",
45-
method="POST",
46-
data={"name": test_workspace_name},
47-
)
48-
49-
yield test_workspace_name
50-
51-
# Clean up the workspace after tests
52-
try:
53-
await client.request(
54-
endpoint=f"v1/workspaces/{test_workspace_name}",
55-
method="DELETE",
56-
)
57-
except Exception as e:
58-
print(f"Failed to delete test workspace: {e}")
599

6010

6111
@pytest.fixture

0 commit comments

Comments
 (0)