diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..2b11178 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore index efa407c..82f9275 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..311f4f7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: python3.10 + +ci: + autofix_prs: true + autoupdate_commit_msg: '[pre-commit.ci] pre-commit suggestions' + autoupdate_schedule: quarterly + # submodules: true + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-ast + - id: check-added-large-files + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + name: "Sort Imports" + args: [ "--profile", "black" ] diff --git a/README.md b/README.md index e05f8b7..1ee679b 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ To use this server with the Claude Desktop app, add the following configuration "qdrant": { "command": "uvx", "args": [ - "mcp-server-qdrant", - "--qdrant-url", + "mcp-server-qdrant", + "--qdrant-url", "http://localhost:6333", - "--qdrant-api-key", + "--qdrant-api-key", "your_api_key", "--collection-name", "your_collection_name" @@ -68,8 +68,8 @@ To use this server with the Claude Desktop app, add the following configuration } ``` -Replace `http://localhost:6333`, `your_api_key` and `your_collection_name` with your Qdrant server URL, Qdrant API key -and collection name, respectively. The use of API key is optional, but recommended for security reasons, and depends on +Replace `http://localhost:6333`, `your_api_key` and `your_collection_name` with your Qdrant server URL, Qdrant API key +and collection name, respectively. The use of API key is optional, but recommended for security reasons, and depends on the Qdrant server configuration. This MCP server will automatically create a collection with the specified name if it doesn't exist. @@ -87,7 +87,7 @@ To use a local mode of Qdrant, you can specify the path to the database using th "qdrant": { "command": "uvx", "args": [ - "mcp-server-qdrant", + "mcp-server-qdrant", "--qdrant-local-path", "/path/to/qdrant/database", "--collection-name", @@ -113,6 +113,6 @@ You cannot provide `QDRANT_URL` and `QDRANT_LOCAL_PATH` at the same time. ## License -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, -subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, +subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/pyproject.toml b/pyproject.toml index 9c0ceef..f5c5569 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,12 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.uv] -dev-dependencies = ["pyright>=1.1.389", "pytest>=8.3.3", "ruff>=0.8.0"] +dev-dependencies = [ + "pre-commit>=4.1.0", + "pyright>=1.1.389", + "pytest>=8.3.3", + "ruff>=0.8.0", +] [project.scripts] mcp-server-qdrant = "mcp_server_qdrant:main" diff --git a/src/mcp_server_qdrant/qdrant.py b/src/mcp_server_qdrant/qdrant.py index 2bc98a6..bbba0a3 100644 --- a/src/mcp_server_qdrant/qdrant.py +++ b/src/mcp_server_qdrant/qdrant.py @@ -1,5 +1,6 @@ from typing import Optional -from qdrant_client import AsyncQdrantClient, models + +from qdrant_client import AsyncQdrantClient class QdrantConnector: @@ -27,7 +28,9 @@ class QdrantConnector: # For the time being, FastEmbed models are the only supported ones. # A list of all available models can be found here: # https://qdrant.github.io/fastembed/examples/Supported_Models/ - self._client = AsyncQdrantClient(location=qdrant_url, api_key=qdrant_api_key, path=qdrant_local_path) + self._client = AsyncQdrantClient( + location=qdrant_url, api_key=qdrant_api_key, path=qdrant_local_path + ) self._client.set_model(fastembed_model_name) async def store_memory(self, information: str): diff --git a/src/mcp_server_qdrant/server.py b/src/mcp_server_qdrant/server.py index 897b77a..66814bc 100644 --- a/src/mcp_server_qdrant/server.py +++ b/src/mcp_server_qdrant/server.py @@ -1,12 +1,11 @@ +import asyncio from typing import Optional -from mcp.server import Server, NotificationOptions -from mcp.server.models import InitializationOptions - import click -import mcp.types as types -import asyncio import mcp +import mcp.types as types +from mcp.server import NotificationOptions, Server +from mcp.server.models import InitializationOptions from .qdrant import QdrantConnector @@ -29,7 +28,11 @@ def serve( server = Server("qdrant") qdrant = QdrantConnector( - qdrant_url, qdrant_api_key, collection_name, fastembed_model_name, qdrant_local_path + qdrant_url, + qdrant_api_key, + collection_name, + fastembed_model_name, + qdrant_local_path, ) @server.list_tools() @@ -151,7 +154,9 @@ def main( ): # XOR of url and local path, since we accept only one of them if not (bool(qdrant_url) ^ bool(qdrant_local_path)): - raise ValueError("Exactly one of qdrant-url or qdrant-local-path must be provided") + raise ValueError( + "Exactly one of qdrant-url or qdrant-local-path must be provided" + ) async def _run(): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): diff --git a/uv.lock b/uv.lock index c46122a..218fb16 100644 --- a/uv.lock +++ b/uv.lock @@ -38,6 +38,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + [[package]] name = "charset-normalizer" version = "3.4.0" @@ -140,6 +149,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, ] +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -405,6 +423,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/de/85a784bcc4a3779d1753a7ec2dee5de90e18c7bcf402e71b51fcf150b129/hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15", size = 12389 }, ] +[[package]] +name = "identify" +version = "2.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/fa/5eb460539e6f5252a7c5a931b53426e49258cde17e3d50685031c300a8fd/identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc", size = 99249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/8c/4bfcab2d8286473b8d83ea742716f4b79290172e75f91142bc1534b05b9a/identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255", size = 99109 }, +] + [[package]] name = "idna" version = "3.10" @@ -455,7 +482,7 @@ wheels = [ [[package]] name = "mcp-server-qdrant" -version = "0.5.1" +version = "0.5.2" source = { editable = "." } dependencies = [ { name = "mcp" }, @@ -464,6 +491,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pre-commit" }, { name = "pyright" }, { name = "pytest" }, { name = "ruff" }, @@ -477,6 +505,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "pre-commit", specifier = ">=4.1.0" }, { name = "pyright", specifier = ">=1.1.389" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "ruff", specifier = ">=0.8.0" }, @@ -719,6 +748,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, ] +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -740,6 +778,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423 }, ] +[[package]] +name = "pre-commit" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, +] + [[package]] name = "protobuf" version = "5.28.3" @@ -1237,6 +1291,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, ] +[[package]] +name = "virtualenv" +version = "20.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, +] + [[package]] name = "win32-setctime" version = "1.1.0"