|
| 1 | +## Overview |
| 2 | + |
| 3 | +`deepset-mcp-server` provides an API SDK and MCP (model context protocol) tools to interact with the deepset API. |
| 4 | +deepset is an AI platform that allows users to develop and deploy AI applications. |
| 5 | +All applications are defined as Haystack pipelines. |
| 6 | +Haystack is an Open Source Python framework for AI application building. |
| 7 | + |
| 8 | +## Project Structure |
| 9 | + |
| 10 | +All source code is in `src/deepset_mcp`. |
| 11 | +Code for the API SDK is in `src/deepset_mcp/api`. |
| 12 | +Code for tools is in `src/deepset_mcp/tools`. |
| 13 | +The tools are added to an MCP server which is defined in main.py |
| 14 | + |
| 15 | +Tests are in the `test` directory. |
| 16 | +All unit tests go into `test/unit`, integration tests go into `test/integration`. |
| 17 | + |
| 18 | +We use uv to manage the project. |
| 19 | +All project level configurations are defined in `pyproject.toml` at the root of the repository. |
| 20 | + |
| 21 | +### API SDK structure |
| 22 | + |
| 23 | +The API SDK is 'async first'. |
| 24 | +All API interactions run through a common facade called `AsyncDeepsetClient`. |
| 25 | +The client exposes resources that in turn expose methods to interact with these resources through the deepset API. |
| 26 | + |
| 27 | +As an example, to fetch a specific pipeline a user might do: |
| 28 | + |
| 29 | +```python |
| 30 | +from deepset_mcp.api.client import AsyncDeepsetClient |
| 31 | + |
| 32 | +async with AsyncDeepsetClient() as client: |
| 33 | + response = await client.pipelines(workspace="some_workspace").get("my_pipeline") |
| 34 | + |
| 35 | +``` |
| 36 | + |
| 37 | +### Tool Structure |
| 38 | + |
| 39 | +Tools are meant to be used by large language models. Therefore, the output of a tool should always be a string. |
| 40 | +Known exceptions should usually be caught and converted to strings as well. |
| 41 | +Typically, we have one tool file per resource. |
| 42 | +A tool can make multiple calls to different resources or different methods on the same resource to produce the desired output. |
| 43 | +Most tools are imported into `src/deepset_mcp/main.py` where they are added to the MCP server. |
| 44 | + |
| 45 | + |
| 46 | +## Instructions for common tasks |
| 47 | + |
| 48 | +### Task: Adding a new resource |
| 49 | + |
| 50 | +Let's assume we want to add a `PipelineFeedbackResource`. |
| 51 | +You would need to make the following changes: |
| 52 | + |
| 53 | +1. add a package for the resource at `src/deepset_mcp/api/pipeline_feedback` |
| 54 | +2. the resource goes into `src/deepset_mcp/api/pipeline_feedback/resource.py` |
| 55 | +3. (optional) if you need to define models for API response they would go into `src/deepset_mcp/api/pipeline_feedback/models.py` |
| 56 | +4. add a Protocol for the resource in `src/deepset_mcp/api/protocols.py` |
| 57 | +5. add the resource to the AsyncClientProtocol in the same file (depending on the resource you need client and workspace or just client) |
| 58 | +6. add a method for the resource to the `AsyncDeepsetClient` in `src/deepset_mcp/api/client.py` |
| 59 | + |
| 60 | +#### Testing the resource |
| 61 | +Each resource gets unit and integration tests for all methods. |
| 62 | + |
| 63 | +1. first add a stub for the resource to the `BaseFakeClient` in `test/unit/conftest.py` |
| 64 | +2. create a directory for the unit tests in `test/unit/api/` (i.e. `test/unit/api/pipeline_feedback`) |
| 65 | +3. we use pytest for all unit tests |
| 66 | +4. import the `BaseFakeClient` from `test/unit/conftest.py` |
| 67 | +5. Overwrite the `pipeline_feedback`-method on the client so that it returns your actual `PipelineFeedbackResource` |
| 68 | +6. Add fake responses to the client according to what you want to test |
| 69 | +7. Create comprehensive unit tests |
| 70 | +8. all tests must have complete type hints including return types |
| 71 | +9. `test/unit/api/haystack_service/test_haystack_service_resource.py` has a good example of how to structure unit tests for a resource |
| 72 | + |
| 73 | +10. integration tests go into `test/integration` |
| 74 | +11. create an integration test file at `test/integration/test_integration_pipeline_feedback_resource.py` |
| 75 | +12. `test/integration/test_integration_haystack_service_resource.py` has a good example for how integration tests may look like |
| 76 | +13. don't add too many integration tests, they are mostly a sanity check, the bulk of the testing should be written as unit tests |
| 77 | + |
| 78 | +### Task: Adding a new tool |
| 79 | + |
| 80 | +Let's assume we want to add a `fetch_pipeline_resource`-tool that will use our newly added resource. |
| 81 | +You would need to perform the following steps: |
| 82 | + |
| 83 | +1. the tool would go into `src/deepset_mcp/tools/pipeline_feedback.py` |
| 84 | +2. the client will ALWAYS be passed to the tool as a dependency injection (type: `AsyncClientProtocol`) |
| 85 | +3. the tool should call the methods on the resource through the client |
| 86 | +4. refer to `src/deepset_mcp/tools/pipeline.py` as a good example for tool implementations |
| 87 | +5. extract model or response serialization into reusable helper functions |
| 88 | +6. once you added a tool, import it in `src/deepset_mcp/main.py` |
| 89 | +7. add a corresponding mcp tool using the `@mcp.tool`-decorator |
| 90 | +8. the docstring of the tool will serve as the prompt for the large language model calling the tool, make sure it has good instructions on when to use the tool, how to best use it, and what kind of answer to expect. |
| 91 | + |
| 92 | +#### Testing the tool |
| 93 | + |
| 94 | +We ONLY add unit tests for the tool. The mcp integration will not have tests. |
| 95 | + |
| 96 | +1. the tool tests will go into `test/unit/tools` (i.e. `test/unit/tools/test_pipeline_feedback.py`) |
| 97 | +2. use a FakeResource to test the tool |
| 98 | +3. import the `BaseFakeClient` and overwrite the resource method to return your fake resource |
| 99 | +4. `test/unit/tools/test_pipeline.py` has a good example for how to test a tool |
| 100 | + |
| 101 | + |
| 102 | +## Code Style Guidelines |
| 103 | + |
| 104 | +- Our code is clean and maintainable |
| 105 | +- Pay attention to great code structure and optimize for readability |
| 106 | +- We use mypy (strict) and ruff for type checking and linting |
| 107 | +- Docstrings MUST follow the reStructuredText style (this is a new change and many docstrings follow a different style) |
| 108 | +Example: |
| 109 | +```python |
| 110 | +def foo(arg1: Type1, arg2: Type2) -> ReturnType: |
| 111 | + """Returns the result of fooing ``arg1`` with ``arg2``. |
| 112 | +
|
| 113 | + :param arg1: A good argument. |
| 114 | + :param arg2: Another good argument. |
| 115 | +
|
| 116 | + :returns: Some nifty thing or other. |
| 117 | + """ |
| 118 | +``` |
| 119 | +- We use Python 3.12 |
| 120 | +- We use many modern code constructs throughout the code base (Protocols, Generics, Dependency Injection) |
| 121 | +- Generally follow the coding style that you are already observing throughout the code base |
| 122 | + |
| 123 | + |
| 124 | +## Contributions |
| 125 | + |
| 126 | +- keep changes minimal and only implement what was requested in the issue |
| 127 | +- all changes need to be tested according to our testing guidelines |
| 128 | +- use clear commit messages following the conventional commits style |
| 129 | +- we typically make changes in relatively small PRs (e.g. 1 PR for adding a resource and another PR for adding the tools) |
| 130 | +- use docstrings, add comments only where needed, follow our code style guidelines |
| 131 | +- be kind! |
| 132 | + |
| 133 | + |
0 commit comments