Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,24 @@ jobs:
run: uv sync --locked --all-extras --group lint

- name: Run lint
run: uv run ruff check
run: uv run ruff check

types:
name: Run mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install Python
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"

- name: Install project
run: uv sync --locked --all-extras --group types

- name: Run types
run: uv run mypy src/ test/
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ lint = [
"ruff",
]

types = [
"mypy",
"types-requests"
]

[tool.pytest.ini_options]
testpaths = ["test"]

Expand Down Expand Up @@ -58,5 +63,8 @@ isort = { combine-as-imports = true, known-first-party = ["deepset_mcp"] }
[tool.ruff.lint.pydocstyle]
convention = "pep257"

[tool.mypy]
strict = true



14 changes: 7 additions & 7 deletions src/deepset_mcp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_workspace() -> str:


# Function to make authenticated requests to deepset Cloud API
def deepset_api_request(endpoint: str, method: str = "GET", data: dict | None = None) -> dict[str, Any]:
def deepset_api_request(endpoint: str, method: str = "GET", data: dict[str, Any] | None = None) -> dict[str, Any]:
"""Makes a request to the deepset API."""
headers = {"Authorization": f"Bearer {get_api_key()}", "Accept": "application/json,text/plain,*/*"}

Expand All @@ -58,7 +58,7 @@ def deepset_api_request(endpoint: str, method: str = "GET", data: dict | None =
return {"status": "success", "message": "API returned empty response body"}

try:
return response.json()
return response.json() # type: ignore
except requests.exceptions.JSONDecodeError:
return {"result": response.text, "warning": "API response was not valid JSON"}

Expand Down Expand Up @@ -150,7 +150,7 @@ def validate_pipeline_yaml(yaml_content: str) -> dict[str, Any]:


@mcp.tool()
def get_pipeline_yaml(pipeline_name: str) -> str:
def get_pipeline_yaml(pipeline_name: str) -> str | dict[str, Any]:
"""Retrieves the complete YAML configuration file for a specific pipeline.

Use this when you need the exact YAML definition of an existing pipeline, for example, to inspect it or use it as
Expand All @@ -166,7 +166,7 @@ def get_pipeline_yaml(pipeline_name: str) -> str:
# The response might be already a string or might need formatting
# depending on the API response structure
if isinstance(response, dict) and "yaml" in response:
return response["yaml"]
return str(response["yaml"])
return str(response)
except Exception as e:
return {"error": str(e)}
Expand Down Expand Up @@ -221,7 +221,7 @@ def update_pipeline_yaml(pipeline_name: str, yaml_content: str) -> dict[str, Any
return {"status": "success", "message": "Pipeline YAML updated successfully (empty response body)"}

try:
return response.json()
return response.json() # type: ignore
except requests.exceptions.JSONDecodeError:
return {"result": response.text, "warning": "API response was not valid JSON"}

Expand Down Expand Up @@ -253,9 +253,9 @@ def get_component_schemas_resource() -> str:


@mcp.resource("pipeline-yaml://{pipeline_name}")
def get_pipeline_yaml_resource(pipeline_name: str) -> str:
def get_pipeline_yaml_resource(pipeline_name: str) -> str | dict[str, Any]:
"""Return the YAML definition of a specific pipeline as a resource."""
return get_pipeline_yaml(pipeline_name)
return get_pipeline_yaml(pipeline_name) # type: ignore


@mcp.tool()
Expand Down
19 changes: 11 additions & 8 deletions test/test_main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from typing import Any
from unittest import mock

import requests
Expand Down Expand Up @@ -27,7 +28,9 @@


# --- Helper Function to Mock Response ---
def create_mock_response(status_code, json_data=None, text_data=None):
def create_mock_response(
status_code: int, json_data: dict[str, Any] | None = None, text_data: str | None = None
) -> requests.Response:
"""Creates a mock requests.Response object."""
mock_resp = mock.Mock(spec=requests.Response)
mock_resp.status_code = status_code
Expand All @@ -46,7 +49,7 @@ def create_mock_response(status_code, json_data=None, text_data=None):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put") # Mock requests.put used in update_pipeline_yaml
def test_update_pipeline_yaml_success_json_response(mock_put):
def test_update_pipeline_yaml_success_json_response(mock_put: mock.Mock) -> None:
"""Tests successful update with JSON response."""
mock_response = create_mock_response(200, json_data={"status": "success", "message": "Updated"})
mock_put.return_value = mock_response
Expand All @@ -65,7 +68,7 @@ def test_update_pipeline_yaml_success_json_response(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put")
def test_update_pipeline_yaml_success_empty_response(mock_put):
def test_update_pipeline_yaml_success_empty_response(mock_put: mock.Mock) -> None:
"""Tests successful update with empty response body."""
mock_response = create_mock_response(200, text_data="") # Empty body
mock_put.return_value = mock_response
Expand All @@ -84,7 +87,7 @@ def test_update_pipeline_yaml_success_empty_response(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put")
def test_update_pipeline_yaml_api_error_422_json(mock_put):
def test_update_pipeline_yaml_api_error_422_json(mock_put: mock.Mock) -> None:
"""Tests API error (422) with JSON details."""
error_details = {"detail": [{"type": "validation_error", "msg": "Something is wrong"}]}
mock_response = create_mock_response(422, json_data=error_details)
Expand All @@ -98,7 +101,7 @@ def test_update_pipeline_yaml_api_error_422_json(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put")
def test_update_pipeline_yaml_api_error_500_text(mock_put):
def test_update_pipeline_yaml_api_error_500_text(mock_put: mock.Mock) -> None:
"""Tests API error (500) with non-JSON text details."""
error_text = "Internal Server Error"
mock_response = create_mock_response(500, text_data=error_text)
Expand All @@ -112,7 +115,7 @@ def test_update_pipeline_yaml_api_error_500_text(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put")
def test_update_pipeline_yaml_request_exception(mock_put):
def test_update_pipeline_yaml_request_exception(mock_put: mock.Mock) -> None:
"""Tests network request failure."""
mock_put.side_effect = requests.exceptions.RequestException("Connection timed out")

Expand All @@ -125,7 +128,7 @@ def test_update_pipeline_yaml_request_exception(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put") # Still need to mock put, even if not called
def test_update_pipeline_yaml_empty_content(mock_put):
def test_update_pipeline_yaml_empty_content(mock_put: mock.Mock) -> None:
"""Tests the function's validation for empty YAML content."""
result = update_pipeline_yaml(TEST_PIPELINE_NAME, "")
assert result == {"error": "Empty YAML content provided"}
Expand All @@ -134,7 +137,7 @@ def test_update_pipeline_yaml_empty_content(mock_put):

@mock.patch.dict(os.environ, {"DEEPSET_WORKSPACE": TEST_WORKSPACE, "DEEPSET_API_KEY": TEST_API_KEY})
@mock.patch("deepset_mcp.main.requests.put")
def test_update_pipeline_yaml_invalid_structure(mock_put):
def test_update_pipeline_yaml_invalid_structure(mock_put: mock.Mock) -> None:
"""Tests the function's validation for missing 'components:'."""
result = update_pipeline_yaml(TEST_PIPELINE_NAME, INVALID_YAML_CONTENT_NO_COMPONENTS)
assert result == {"error": "Invalid YAML content - missing 'components:' section"}
Expand Down
60 changes: 60 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.