Skip to content

Commit 28f26e2

Browse files
committed
core: pre-commit
1 parent 23ff566 commit 28f26e2

9 files changed

Lines changed: 140 additions & 53 deletions

File tree

CODE_OF_CONDUCT.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,3 @@ For answers to common questions about this code of conduct, see the FAQ at
131131
[Mozilla CoC]: https://github.com/mozilla/diversity
132132
[FAQ]: https://www.contributor-covenant.org/faq
133133
[translations]: https://www.contributor-covenant.org/translations
134-

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
MCPStack
55
<br>
66
</h1>
7-
<h4 align="center">Stack & Orchestrate MCP Tools — The Scikit-Learn Way, For LLMs</h4>
7+
<h4 align="center">Stack & Orchestrate MCP Tools — The Scikit-Learn-Pipeline Way, For LLMs</h4>
88
</div>
99

1010
<div align="center">
@@ -30,29 +30,29 @@
3030

3131
## <a id="about-the-project"></a>💡 About The Project
3232

33-
`MCPStack` is a **`Scikit-Learn-Like` Pipeline orchestrator** for Model Context Protocols (MCPs).
34-
It allows you to **stack multiple MCP tools together** into a pipeline of interest and expose them directly
33+
`MCPStack` is a **`Scikit-Learn-Like` Pipeline orchestrator** for Model Context Protocols (MCPs).
34+
It allows you to **stack multiple MCP tools together** into a pipeline of interest and expose them directly
3535
into your favourite LLM environment, such as **Claude Desktop**.
3636

3737
Think of it as **`scikit-learn` pipelines, but for Large Language Models**:
38-
* In `scikit-learn`, you chain `preprocessors`, `transformers`, and `estimators`.
38+
* In `scikit-learn`, you chain `preprocessors`, `transformers`, and `estimators`.
3939
* In `MCPStack`, you chain MCP tools of interest. If some tools are not of interest, you simply do not include them in the pipeline.
4040

4141
The LLM cannot use a tool that is not included in the pipeline. This makes orchestration both **powerful** and **secure**.
4242
This permits sophisticated compositions in which the LLM can only access the tools you specify – no more, no less.
4343

4444
**Wait, what is a Model Context Protocol (MCP) — In layman's terms ?**
4545

46-
The Model Context Protocol (MCP) standardises interactions with machine learning (Large Language) models,
47-
enabling tools and libraries to communicate successfully with a uniform workflow.
46+
The Model Context Protocol (MCP) standardises interactions with machine learning (Large Language) models,
47+
enabling tools and libraries to communicate successfully with a uniform workflow.
4848

4949
---
5050

5151
## Installation
5252

5353
> [!NOTE]
54-
> MCPStack is the orchestrator — it comes with core utilities and validated tools.
55-
> All validated tools are listed under `mcp_tools` in the `pyproject.toml` and are auto-registered via
54+
> MCPStack is the orchestrator — it comes with core utilities and validated tools.
55+
> All validated tools are listed under `mcp_tools` in the `pyproject.toml` and are auto-registered via
5656
> `[project.entry-points."mcpstack.tools"]`.
5757
5858
### Clone the repository
@@ -92,7 +92,7 @@ pre-commit install
9292

9393
## 🖥️ CLI Workflow
9494

95-
You can manage and run your MCP pipelines directly from the CLI with the `mcpstack` command.
95+
You can manage and run your MCP pipelines directly from the CLI with the `mcpstack` command.
9696
Every command is run with `uv run mcpstack` (or just `mcpstack` if installed globally).
9797

9898
<img src="assets/readme/help.png" width="61.8%" align="left" style="border-radius: 10px;"/>
@@ -226,7 +226,7 @@ More chaining methods are available, such as `with_config(...)` to configure the
226226

227227
You can also create your own MCP tool with the [`mcpstack-tool-builder` CLI](https://github.com/MCP-Pipeline/MCPStack-Tool-Builder), which will generate a skeleton for you to fill in.
228228

229-
That means, creating the `actions` your MCP tool will allow LLMs to perform, and a `CLI` to
229+
That means, creating the `actions` your MCP tool will allow LLMs to perform, and a `CLI` to
230230
initialise it, configure it, and run it. More in the documentation.
231231

232232
<br clear="left">

SECURITY.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ Include the following information in your report:
1313
- **Description**: A clear explanation of the vulnerability, with reproduction steps if possible.
1414
- **Environment**: Operating system, Python version, MCP client, and any relevant setup details.
1515

16-
Send reports to: **simon.gilbert.provost@gmail.com**
16+
Send reports to: **simon.gilbert.provost@gmail.com**
1717

1818
We will acknowledge receipt within 7 days and aim to provide a resolution or mitigation timeline within 30 days. Please do not share vulnerabilities publicly until a fix has been released.
1919

2020
## Scope and Considerations
2121

22-
- **Tool Combinations**: MCPStack is designed to compose with other MCP tools. Unexpected or unsafe interactions between tools cannot be predicted or prevented at the MCPStack-Tool level. The responsibility for safe orchestration rests with the user.
23-
- **Data Breach Risks**: MCPStack runs locally and does not send data to external services. There are no cloud communications by default, so conventional remote data breaches do not apply.
22+
- **Tool Combinations**: MCPStack is designed to compose with other MCP tools. Unexpected or unsafe interactions between tools cannot be predicted or prevented at the MCPStack-Tool level. The responsibility for safe orchestration rests with the user.
23+
- **Data Breach Risks**: MCPStack runs locally and does not send data to external services. There are no cloud communications by default, so conventional remote data breaches do not apply.
2424
- **GitHub Issues and Pull Requests**: Do not upload sensitive data (such as private datasets, logs, or credentials) when filing issues or submitting pull requests. Use synthetic or anonymized examples instead.
2525

2626
## Good Practices
2727

28-
- Keep dependencies updated.
29-
- Test tool compositions in a controlled environment.
30-
- Sanitize any shared examples when discussing issues publicly.
28+
- Keep dependencies updated.
29+
- Test tool compositions in a controlled environment.
30+
- Sanitize any shared examples when discussing issues publicly.

tests/core/test_config.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
from pathlib import Path
3-
from unittest.mock import MagicMock, patch
3+
from unittest.mock import patch
44

55
import pytest
66

@@ -68,11 +68,17 @@ def test_get_env_var_raise_if_missing(self):
6868

6969
def test_validate_for_tools_success(self):
7070
"""Test validate_for_tools method success."""
71+
7172
class Tool(BaseTool):
72-
def actions(self): return []
73-
def to_dict(self): return {}
73+
def actions(self):
74+
return []
75+
76+
def to_dict(self):
77+
return {}
78+
7479
@classmethod
75-
def from_dict(cls, params): return cls()
80+
def from_dict(cls, params):
81+
return cls()
7682

7783
mock_tool = Tool()
7884
mock_tool.__class__.__name__ = "TestTool"
@@ -82,11 +88,17 @@ def from_dict(cls, params): return cls()
8288

8389
def test_validate_for_tools_error(self):
8490
"""Test validate_for_tools raises on error."""
91+
8592
class Tool(BaseTool):
86-
def actions(self): return []
87-
def to_dict(self): return {}
93+
def actions(self):
94+
return []
95+
96+
def to_dict(self):
97+
return {}
98+
8899
@classmethod
89-
def from_dict(cls, params): return cls()
100+
def from_dict(cls, params):
101+
return cls()
90102

91103
mock_tool = Tool()
92104
mock_tool.__class__.__name__ = "TestTool"

tests/core/test_mcp_config_generator.py

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
import pytest
66

7-
from MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config import ClaudeConfigGenerator
8-
from MCPStack.core.mcp_config_generator.mcp_config_generators.fast_mcp_config import FastMCPConfigGenerator
7+
from MCPStack.core.config import StackConfig
8+
from MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config import (
9+
ClaudeConfigGenerator,
10+
)
11+
from MCPStack.core.mcp_config_generator.mcp_config_generators.fast_mcp_config import (
12+
FastMCPConfigGenerator,
13+
)
914
from MCPStack.core.utils.exceptions import MCPStackValidationError
1015
from MCPStack.stack import MCPStackCore
11-
from MCPStack.core.config import StackConfig
1216

1317

1418
@pytest.fixture
@@ -23,8 +27,16 @@ class TestClaudeConfigGenerator:
2327

2428
@patch("shutil.which", return_value="/usr/bin/python")
2529
@patch("os.path.isdir", return_value=True)
26-
@patch("MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config.ClaudeConfigGenerator._get_claude_config_path")
27-
def test_generate_with_defaults(self, mock_get_path: MagicMock, mock_isdir: MagicMock, mock_which: MagicMock, mock_stack: MCPStackCore) -> None:
30+
@patch(
31+
"MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config.ClaudeConfigGenerator._get_claude_config_path"
32+
)
33+
def test_generate_with_defaults(
34+
self,
35+
mock_get_path: MagicMock,
36+
mock_isdir: MagicMock,
37+
mock_which: MagicMock,
38+
mock_stack: MCPStackCore,
39+
) -> None:
2840
"""Test generating config with defaults."""
2941
mock_get_path.return_value = None
3042
config = ClaudeConfigGenerator.generate(mock_stack)
@@ -38,23 +50,38 @@ def test_generate_with_defaults(self, mock_get_path: MagicMock, mock_isdir: Magi
3850
assert "TEST_ENV" in server["env"]
3951

4052
@patch("shutil.which", return_value=None)
41-
def test_invalid_command_raises_error(self, mock_which: MagicMock, mock_stack: MCPStackCore) -> None:
53+
def test_invalid_command_raises_error(
54+
self, mock_which: MagicMock, mock_stack: MCPStackCore
55+
) -> None:
4256
"""Test invalid command raises error."""
4357
with pytest.raises(MCPStackValidationError, match="Invalid command"):
4458
ClaudeConfigGenerator.generate(mock_stack, command="/invalid/python")
4559

4660
@patch("os.path.isdir", return_value=False)
47-
def test_invalid_cwd_raises_error(self, mock_isdir: MagicMock, mock_stack: MCPStackCore) -> None:
61+
def test_invalid_cwd_raises_error(
62+
self, mock_isdir: MagicMock, mock_stack: MCPStackCore
63+
) -> None:
4864
"""Test invalid cwd raises error."""
4965
with pytest.raises(MCPStackValidationError, match="Invalid cwd"):
5066
ClaudeConfigGenerator.generate(mock_stack, cwd="/invalid/dir")
5167

5268
@patch("builtins.open", new_callable=mock_open)
5369
@patch("json.load")
5470
@patch("json.dump")
55-
@patch("MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config.ClaudeConfigGenerator._get_claude_config_path")
71+
@patch(
72+
"MCPStack.core.mcp_config_generator.mcp_config_generators.claude_mcp_config.ClaudeConfigGenerator._get_claude_config_path"
73+
)
5674
@patch("pathlib.Path.exists")
57-
def test_merge_with_existing_config(self, mock_exists: MagicMock, mock_get_path: MagicMock, mock_dump: MagicMock, mock_load: MagicMock, mock_open_file: MagicMock, mock_stack: MCPStackCore, tmp_path: Path) -> None:
75+
def test_merge_with_existing_config(
76+
self,
77+
mock_exists: MagicMock,
78+
mock_get_path: MagicMock,
79+
mock_dump: MagicMock,
80+
mock_load: MagicMock,
81+
mock_open_file: MagicMock,
82+
mock_stack: MCPStackCore,
83+
tmp_path: Path,
84+
) -> None:
5885
"""Test merging with existing Claude config."""
5986
mock_path = tmp_path / "claude_config.json"
6087
mock_get_path.return_value = mock_path
@@ -68,7 +95,13 @@ def test_merge_with_existing_config(self, mock_exists: MagicMock, mock_get_path:
6895

6996
@patch("builtins.open", new_callable=mock_open)
7097
@patch("json.dump")
71-
def test_save_to_custom_path(self, mock_dump: MagicMock, mock_open_file: MagicMock, mock_stack: MCPStackCore, tmp_path: Path) -> None:
98+
def test_save_to_custom_path(
99+
self,
100+
mock_dump: MagicMock,
101+
mock_open_file: MagicMock,
102+
mock_stack: MCPStackCore,
103+
tmp_path: Path,
104+
) -> None:
72105
"""Test saving to custom path."""
73106
save_path = tmp_path / "custom.json"
74107
config = ClaudeConfigGenerator.generate(mock_stack, save_path=str(save_path))
@@ -80,7 +113,9 @@ class TestFastMCPConfigGenerator:
80113

81114
@patch("shutil.which", return_value="/usr/bin/python")
82115
@patch("os.path.isdir", return_value=True)
83-
def test_generate_with_defaults(self, mock_isdir: MagicMock, mock_which: MagicMock, mock_stack: MCPStackCore) -> None:
116+
def test_generate_with_defaults(
117+
self, mock_isdir: MagicMock, mock_which: MagicMock, mock_stack: MCPStackCore
118+
) -> None:
84119
"""Test generating config with defaults."""
85120
config = FastMCPConfigGenerator.generate(mock_stack)
86121
assert isinstance(config, dict)
@@ -93,20 +128,30 @@ def test_generate_with_defaults(self, mock_isdir: MagicMock, mock_which: MagicMo
93128
assert "TEST_ENV" in server["env"]
94129

95130
@patch("shutil.which", return_value=None)
96-
def test_invalid_command_raises_error(self, mock_which: MagicMock, mock_stack: MCPStackCore) -> None:
131+
def test_invalid_command_raises_error(
132+
self, mock_which: MagicMock, mock_stack: MCPStackCore
133+
) -> None:
97134
"""Test invalid command raises error."""
98135
with pytest.raises(MCPStackValidationError, match="Invalid command"):
99136
FastMCPConfigGenerator.generate(mock_stack, command="/invalid/python")
100137

101138
@patch("os.path.isdir", return_value=False)
102-
def test_invalid_cwd_raises_error(self, mock_isdir: MagicMock, mock_stack: MCPStackCore) -> None:
139+
def test_invalid_cwd_raises_error(
140+
self, mock_isdir: MagicMock, mock_stack: MCPStackCore
141+
) -> None:
103142
"""Test invalid cwd raises error."""
104143
with pytest.raises(MCPStackValidationError, match="Invalid cwd"):
105144
FastMCPConfigGenerator.generate(mock_stack, cwd="/invalid/dir")
106145

107146
@patch("builtins.open", new_callable=mock_open)
108147
@patch("json.dump")
109-
def test_save_to_custom_path(self, mock_dump: MagicMock, mock_open_file: MagicMock, mock_stack: MCPStackCore, tmp_path: Path) -> None:
148+
def test_save_to_custom_path(
149+
self,
150+
mock_dump: MagicMock,
151+
mock_open_file: MagicMock,
152+
mock_stack: MCPStackCore,
153+
tmp_path: Path,
154+
) -> None:
110155
"""Test saving to custom path."""
111156
save_path = tmp_path / "custom.json"
112157
config = FastMCPConfigGenerator.generate(mock_stack, save_path=str(save_path))

tests/core/test_server.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class TestMCPServer:
1313
def test_server_can_be_imported_as_module(self) -> None:
1414
"""Test that the server can be imported as a module."""
1515
import MCPStack.core.server
16+
1617
assert hasattr(MCPStack.core.server, "main")
1718
assert callable(MCPStack.core.server.main)
1819

@@ -23,6 +24,7 @@ def test_main_success(self, mock_load: Mock) -> None:
2324
mock_stack = Mock(spec=MCPStackCore)
2425
mock_load.return_value = mock_stack
2526
from MCPStack.core.server import main
27+
2628
main()
2729
mock_load.assert_called_once_with("test_config.json")
2830
mock_stack.build.assert_called_once()
@@ -32,7 +34,10 @@ def test_main_success(self, mock_load: Mock) -> None:
3234
def test_main_no_config_path(self) -> None:
3335
"""Test main raises error when MCPSTACK_CONFIG_PATH is not set."""
3436
from MCPStack.core.server import main
35-
with pytest.raises(MCPStackValidationError, match="MCPSTACK_CONFIG_PATH env var not set"):
37+
38+
with pytest.raises(
39+
MCPStackValidationError, match="MCPSTACK_CONFIG_PATH env var not set"
40+
):
3641
main()
3742

3843
@patch.dict(os.environ, {"MCPSTACK_CONFIG_PATH": "invalid.json"})
@@ -41,6 +46,7 @@ def test_main_load_failure(self, mock_load: Mock) -> None:
4146
"""Test main handles load failure."""
4247
mock_load.side_effect = FileNotFoundError("Config not found")
4348
from MCPStack.core.server import main
49+
4450
with pytest.raises(FileNotFoundError):
4551
main()
4652

@@ -49,9 +55,11 @@ def test_main_load_failure(self, mock_load: Mock) -> None:
4955
def test_main_build_failure(self, mock_load: Mock) -> None:
5056
"""Test main handles build failure."""
5157
from MCPStack.core.utils.exceptions import MCPStackValidationError
58+
5259
mock_stack = Mock(spec=MCPStackCore)
5360
mock_load.return_value = mock_stack
5461
mock_stack.build.side_effect = MCPStackValidationError("Build failed")
5562
from MCPStack.core.server import main
63+
5664
with pytest.raises(MCPStackValidationError):
5765
main()

0 commit comments

Comments
 (0)