{
  "markdown": "# Qdrant MCP\n\n<!-- mcp-name: io.github.avaazquezz/mcp-qdrant -->\n\n[![PyPI version](https://img.shields.io/pypi/v/mcp-qdrant.svg)](https://pypi.org/project/mcp-qdrant/)\n[![CI](https://github.com/avaazquezz/Qdrant-MCP/actions/workflows/ci.yml/badge.svg)](https://github.com/avaazquezz/Qdrant-MCP/actions/workflows/ci.yml)\n[![Python versions](https://img.shields.io/pypi/pyversions/mcp-qdrant.svg)](https://pypi.org/project/mcp-qdrant/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n**A Model Context Protocol (MCP) server that exposes the full [Qdrant](https://qdrant.tech)\nvector database API as tools** — collection management, advanced hybrid search, payload and\nvector editing, snapshots, and server observability. Not just `store`/`find`.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Key Features](#key-features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n  - [PyPI](#pypi)\n  - [Docker](#docker)\n  - [Claude Desktop bundle (`.mcpb`)](#claude-desktop-bundle-mcpb)\n- [Quick Start](#quick-start)\n- [Connecting to Claude](#connecting-to-claude)\n  - [Claude Desktop / Claude Code (local, stdio)](#claude-desktop--claude-code-local-stdio)\n  - [Remote (streamable-http)](#remote-streamable-http)\n  - [Bring your own Qdrant (`QDRANT_MCP_BYO`)](#bring-your-own-qdrant-qdrant_mcp_byo)\n- [Configuration Reference](#configuration-reference)\n- [Available Tools](#available-tools)\n- [Read-Only Mode](#read-only-mode)\n- [Architecture](#architecture)\n- [Security](#security)\n- [Development](#development)\n- [Versioning & Changelog](#versioning--changelog)\n- [License](#license)\n\n## Overview\n\nQdrant MCP is a thin [MCP](https://modelcontextprotocol.io) server that wraps the Qdrant API\none-to-one: it registers a tool per Qdrant operation, validates the input with Pydantic, calls\nthe official `qdrant-client` SDK, and returns the result. It never generates embeddings, never\nparses documents, and never decides how to chunk text — **it is not a RAG system**, on purpose.\nWhatever an LLM client wants to store or query, it brings its own vectors.\n\nThat focus is also what sets it apart from the [official Qdrant MCP\nserver](https://github.com/qdrant/mcp-server-qdrant), which exposes exactly two tools\n(`store`/`find`) and does embed documents for you. This server covers the rest of Qdrant's\nsurface — everything under collections, points, search, payload, indexing, snapshots, and\nobservability — so an LLM client can manage a Qdrant deployment end to end, not just push and\npull memories through a narrow interface.\n\nSee [ROADMAP.md](ROADMAP.md) for the full phase-by-phase design history, including every\nfinding that shaped a decision (in Spanish).\n\n## Key Features\n\n- **Full collection & point lifecycle** — create/update/delete collections, CRUD on points,\n  scrolling, counting.\n- **Every Qdrant search mode** — plain vector search, hybrid search (RRF/DBSF fusion with\n  prefetch stages), grouped queries, recommend, discover, and pairwise distance matrices.\n- **Payload & vector editing** — set/overwrite/delete/clear payload, payload indexes, facet\n  counting, named (dense/sparse) vector management, atomic batch operations.\n- **Snapshots** — per-collection and full-storage backup/restore.\n- **Observability** — telemetry, Prometheus metrics endpoint, resource quotas, self-diagnosed\n  issues.\n- **Opt-in tool surface** — tools are grouped into toolsets you enable explicitly, so a client\n  isn't handed 60+ overlapping tools by default.\n- **Read-only guard** — one flag removes every mutating tool from the registry entirely.\n- **Bring-your-own-Qdrant mode** — run a public endpoint with no database of your own; every\n  caller supplies their own Qdrant, isolated by construction, protected by an SSRF guard.\n- **Resilient by default** — every Qdrant call goes through retry-with-backoff and returns\n  Qdrant's own error message on failure, never a generic exception.\n\n## Requirements\n\n- **Python 3.12+**\n- **Qdrant server v1.19.0 or newer.** Two tools\n  (`qdrant_collection_vector_create`/`qdrant_collection_vector_delete`) depend on an endpoint\n  that returns `404` on older Qdrant servers (verified against v1.13.6 and v1.15.1) — everything\n  else works on older versions, but v1.19.0+ is the only version this project tests against.\n\n## Installation\n\n### PyPI\n\n```bash\n# Run without installing (recommended for Claude Desktop/Code configs)\nuvx mcp-qdrant\n\n# Or install into your environment\npip install mcp-qdrant\n```\n\n### Docker\n\n```bash\ndocker run --rm -p 8000:8000 \\\n  -e QDRANT_URL=http://host.docker.internal:6333 \\\n  -e QDRANT_MCP_TRANSPORT=streamable-http \\\n  -e QDRANT_MCP_HTTP_HOST=0.0.0.0 \\\n  -e QDRANT_MCP_SHARED_SECRET=<a long random secret> \\\n  ghcr.io/avaazquezz/qdrant-mcp:latest\n```\n\nThe image only makes sense with `streamable-http` — `stdio` needs a client to own the process's\nstdin/stdout directly, which a detached container can't provide. The server binds\n`127.0.0.1` by default, so `QDRANT_MCP_HTTP_HOST=0.0.0.0` is required for the port to be\nreachable from outside the container. Images are published on every tagged release as\n`{version}`, `{major}.{minor}`, and `latest`.\n\n### Claude Desktop bundle (`.mcpb`)\n\nDownload `mcp-qdrant.mcpb` from the [latest release](https://github.com/avaazquezz/Qdrant-MCP/releases)\nand double-click it. Claude Desktop installs the server via `uv` (resolving dependencies on your\nmachine, no Python installation required) and prompts for Qdrant URL, API key, local path,\ntoolsets, and read-only mode through its own settings form — no JSON to edit.\n\n## Quick Start\n\nPoint the server at a local Qdrant instance over `stdio` and confirm it's reachable:\n\n```bash\nQDRANT_URL=http://localhost:6333 mcp-qdrant\n```\n\nOnce connected from an MCP client, a typical first exchange looks like:\n\n1. **`qdrant_health_check`** — confirms the configured Qdrant instance is reachable before\n   doing anything else.\n2. **`qdrant_collection_create`** — `{\"collection_name\": \"docs\", \"vector_size\": 4, \"distance\": \"Cosine\"}`.\n3. **`qdrant_points_upsert`** — `{\"collection_name\": \"docs\", \"points\": [{\"id\": 1, \"vector\": [0.1, 0.2, 0.3, 0.4], \"payload\": {\"title\": \"hello\"}}]}`.\n4. **`qdrant_query`** — `{\"collection_name\": \"docs\", \"query_vector\": [0.1, 0.2, 0.3, 0.4], \"limit\": 5}`.\n\nThe full parameter shape of every tool is described in its own MCP schema — an LLM client reads\nthose directly via `tools/list`; the table in [Available Tools](#available-tools) below is a\nhuman-readable summary of the same data.\n\n## Connecting to Claude\n\n### Claude Desktop / Claude Code (local, `stdio`)\n\n`claude_desktop_config.json` (Claude Desktop) or `.mcp.json` (Claude Code):\n\n```json\n{\n  \"mcpServers\": {\n    \"qdrant\": {\n      \"command\": \"uvx\",\n      \"args\": [\"mcp-qdrant\"],\n      \"env\": {\n        \"QDRANT_URL\": \"http://localhost:6333\",\n        \"QDRANT_MCP_TOOLSETS\": \"core,search\"\n      }\n    }\n  }\n}\n```\n\nOr use the `.mcpb` bundle described in [Installation](#claude-desktop-bundle-mcpb) — same\nresult, no JSON to edit.\n\n### Remote (`streamable-http`)\n\nFor a server reachable over the network (e.g. added as a custom connector in Claude.ai) with a\nsingle, fixed backing Qdrant. `QDRANT_MCP_SHARED_SECRET` is **required** in this mode — the\nserver refuses to start as `streamable-http` without one, to avoid serving an unauthenticated\nendpoint over the network (verified hands-on: an open `streamable-http` server is trivially\nusable by anyone with the URL).\n\n```bash\nQDRANT_URL=http://localhost:6333 \\\nQDRANT_MCP_TRANSPORT=streamable-http \\\nQDRANT_MCP_HTTP_HOST=0.0.0.0 \\\nQDRANT_MCP_SHARED_SECRET=<a long random secret> \\\nmcp-qdrant\n```\n\nIn Claude.ai (**Customize → Connectors → Add custom connector**, verified hands-on against a\nreal account): enter the server's HTTPS URL, then on the detected authentication screen choose\n**\"None\"** and add a **Request header** — `Authorization` → `Bearer <the same secret>`.\n(`Authorization` is used here because it's one of the two header names Claude.ai's\ncustom-connector UI accepts without requiring Anthropic's manual approval of a custom name.)\n\n### Bring your own Qdrant (`QDRANT_MCP_BYO`)\n\nA `streamable-http` deployment can run with **no backing Qdrant of its own** — every caller\nsupplies their *own* Qdrant instance (their own Qdrant Cloud account, their company's\nself-hosted Qdrant, whatever) per request, instead of using one the operator hosts and pays for.\nIsolation between callers is automatic — each one talks to their own database — so there's no\nshared secret, no per-user account, and no data at rest on this server.\n\n```bash\nQDRANT_MCP_BYO=1 \\\nQDRANT_MCP_TRANSPORT=streamable-http \\\nQDRANT_MCP_HTTP_HOST=0.0.0.0 \\\nmcp-qdrant\n```\n\nTwo request headers, reused for a different purpose than their name suggests — verified\nhands-on that Claude.ai's custom-connector \"Request headers\" UI rejects made-up header names\noutright unless Anthropic has approved them, so this reuses two pre-approved ones instead of\ninventing `X-Qdrant-Url`/`X-Qdrant-Api-Key`:\n\n- `Authorization` (**required**) — your Qdrant URL, e.g. `https://xyz.cloud.qdrant.io:6333`.\n  Sent verbatim, no `Bearer` prefix (unlike the personal-instance mode above, which uses the\n  same header for a shared secret).\n- `x-api-key` (optional) — your Qdrant API key, if your instance needs one.\n\nIn Claude.ai: **Add custom connector** → authentication **\"None\"** → add both as **Request\nheaders**. Your Qdrant must be reachable from the public internet — see\n[Security](#security) for what the SSRF guard rejects.\n\n**How it works, under the hood:** every tool resolves its Qdrant client lazily, at call time,\nrather than once at startup. In BYO mode that client is a `BYOQdrantClientProxy` that reads the\nreal `AsyncQdrantClient` from a `contextvars.ContextVar`, set per-request by the auth\nmiddleware — so none of the tool implementations need to know BYO mode exists. Clients are\npooled in a bounded LRU cache (256 entries, keyed by URL + API key) so repeat callers don't pay\na fresh TLS handshake on every call, with a 15-second grace period before an evicted client is\nclosed so an in-flight request is never cut off mid-call.\n\n#### Recommended setup: one MCP deployment, one Qdrant per project\n\nThis is the most common way to run this project: deploy the MCP **once**, in BYO mode, as a\nlong-lived service — then, for each new project, spin up your own Qdrant and point a connector\nat it, without ever touching the MCP deployment again.\n\n1. **Deploy the MCP once**, self-hosted (e.g. Docker behind a reverse proxy with TLS), in BYO\n   mode as shown above. This never changes between projects.\n2. **Per project, run your own Qdrant** with `docker-compose`, protected with its own API key\n   ([`QDRANT__SERVICE__API_KEY`](https://qdrant.tech/documentation/guides/security/) — Qdrant's\n   own auth, unrelated to this server):\n\n   ```yaml\n   services:\n     qdrant:\n       image: qdrant/qdrant:latest\n       restart: unless-stopped\n       environment:\n         QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY}\n       volumes:\n         - ./qdrant_storage:/qdrant/storage\n   ```\n\n   All data for that project lives in `./qdrant_storage`, on your own server — the MCP\n   deployment never stores or sees it beyond relaying each request.\n3. **Expose that Qdrant under its own public HTTPS domain** (e.g. via Traefik/Let's Encrypt) —\n   the SSRF guard rejects private/internal addresses, so it must be reachable from the public\n   internet, not just from inside your server's Docker network.\n4. **Add one connector per project**, pointing at the same MCP deployment but with different\n   headers:\n   - **Claude.ai**: a separate custom connector per project — `Authorization` = that project's\n     Qdrant URL, `x-api-key` = its API key.\n   - **Claude Code** (`.mcp.json`, remote HTTP server with custom headers):\n     ```json\n     {\n       \"mcpServers\": {\n         \"qdrant-project-x\": {\n           \"type\": \"http\",\n           \"url\": \"https://your-mcp.example.com/mcp\",\n           \"headers\": {\n             \"Authorization\": \"https://qdrant-project-x.example.com\",\n             \"x-api-key\": \"${QDRANT_PROJECT_X_API_KEY}\"\n           }\n         }\n       }\n     }\n     ```\n\nAdding a project is then just a new `docker-compose up` for its Qdrant plus a new connector —\nthe MCP deployment itself is never redeployed or restarted.\n\n## Configuration Reference\n\nAll configuration is via environment variables, read once at startup — a misconfiguration fails\nimmediately instead of surfacing later as a confusing tool error.\n\n| Variable | Default | Required | Purpose |\n|---|---|---|---|\n| `QDRANT_URL` | — | No¹ | URL of your Qdrant instance, e.g. `http://localhost:6333` or a Qdrant Cloud URL. |\n| `QDRANT_API_KEY` | — | No | API key for `QDRANT_URL`, if your instance requires one. |\n| `QDRANT_LOCAL_PATH` | — | No¹ | Path to an embedded/on-disk Qdrant instance, instead of a URL. |\n| `QDRANT_MCP_READ_ONLY` | `false` | No | Removes every tool not marked read-only from the registry. See [Read-Only Mode](#read-only-mode). |\n| `QDRANT_MCP_TRANSPORT` | `stdio` | No | `stdio` (local, for Claude Desktop/Code) or `streamable-http` (network). |\n| `QDRANT_MCP_TOOLSETS` | `core` | No | Comma-separated list of toolsets to register. See [Available Tools](#available-tools). |\n| `QDRANT_MCP_SHARED_SECRET` | — | Required for `streamable-http` unless BYO² | Bearer token clients must send in the `Authorization` header. |\n| `QDRANT_MCP_HTTP_HOST` | `127.0.0.1` | No | Bind host for `streamable-http`. Use `0.0.0.0` in a container. |\n| `QDRANT_MCP_HTTP_PORT` | `8000` | No | Bind port for `streamable-http`. |\n| `QDRANT_MCP_BYO` | `false` | No | Enables [bring-your-own-Qdrant mode](#bring-your-own-qdrant-qdrant_mcp_byo). |\n\n¹ `QDRANT_URL` and `QDRANT_LOCAL_PATH` are mutually exclusive; if neither is set, the client\nfalls back to the `qdrant-client` SDK's own default of `localhost:6333`.\n² In BYO mode the shared secret is optional — it adds an extra anti-bot gate on top of the\nper-caller isolation BYO already provides, rather than protecting shared data. BYO mode also\nrequires `QDRANT_MCP_TRANSPORT=streamable-http` and is mutually exclusive with\n`QDRANT_URL`/`QDRANT_LOCAL_PATH` (there is nothing \"backing\" to point at).\n\n## Available Tools\n\nTools are grouped into **toolsets**, enabled via `QDRANT_MCP_TOOLSETS` (comma-separated). Only\n`core` is enabled by default — the rest are explicit opt-ins, so a client isn't handed every\ntool at once:\n\n- **`core`** — collection and point CRUD, plus `qdrant_query` and the `qdrant_health_check`\n  smoke test. Enough for a fully working MCP on its own.\n- **`search`** — everything beyond plain vector search: batched/grouped queries, recommend,\n  discover, and pairwise distance matrices.\n- **`payload`** — payload editing and indexing, named-vector management, atomic batch point\n  operations.\n- **`snapshots`** — collection and full-storage backup/restore.\n- **`observability`** — telemetry, metrics, quotas, and self-diagnosed issues.\n- **`admin`** is a reserved toolset name with no registered tools — cluster/shard\n  administration was scoped out (see [ROADMAP.md](ROADMAP.md), Fase 5): its most useful\n  capability, real resharding, only exists on Qdrant Cloud, and the rest only matters for a\n  distributed deployment. Setting `QDRANT_MCP_TOOLSETS=admin` is valid but registers nothing.\n\nThe table below is generated directly from the live tool registry — run\n`uv run python scripts/gen_tools_doc.py` after adding or changing a tool to keep it in sync\n(CI fails the build if it drifts).\n\n<!-- TOOLS_TABLE_START -->\n| Tool | Toolset | Read-only | Destructive | Idempotent | Description |\n|---|---|---|---|---|---|\n| `qdrant_health_check` | `core` | ✅ | ❌ | ✅ | Confirm the configured Qdrant instance is reachable and responding. |\n| `qdrant_collection_create` | `core` | ❌ | ❌ | ❌ | Create a collection: either a single unnamed vector (`vector_size` + `distance`), or one or more named vectors (`vectors`, each a full `VectorParams` — size, distance, and optionally its own `multivector_config` for ColBERT-style multi-vectors or `quantization_config`) — exactly one of the two. |\n| `qdrant_collection_list` | `core` | ✅ | ❌ | ✅ | List every collection name in the configured Qdrant instance. |\n| `qdrant_collection_info` | `core` | ✅ | ❌ | ✅ | Return full config and status of one collection. |\n| `qdrant_collection_update` | `core` | ❌ | ❌ | ✅ | Update optimizer/HNSW/collection/vector params on an existing collection. |\n| `qdrant_collection_delete` | `core` | ❌ | ✅ | ✅ | Delete a collection and all its points; a no-op if it doesn't exist. |\n| `qdrant_collection_exists` | `core` | ✅ | ❌ | ✅ | Check whether a collection exists, without raising if it doesn't. |\n| `qdrant_points_upsert` | `core` | ❌ | ✅ | ✅ | Insert or replace points (id + vector + payload) in a collection. |\n| `qdrant_points_get` | `core` | ✅ | ❌ | ✅ | Retrieve points by id; unknown ids are simply omitted, not an error. |\n| `qdrant_points_delete` | `core` | ❌ | ✅ | ✅ | Delete points by id list or by payload filter — exactly one of the two. |\n| `qdrant_points_scroll` | `core` | ✅ | ❌ | ✅ | Page through all points in a collection, optionally filtered. |\n| `qdrant_points_count` | `core` | ✅ | ❌ | ✅ | Count points in a collection, optionally matching a filter. |\n| `qdrant_query` | `core` | ✅ | ❌ | ✅ | Vector similarity search, with optional hybrid search over multiple prefetch stages. |\n| `qdrant_query_batch` | `search` | ✅ | ❌ | ✅ | Run multiple independent queries against one collection in a single round trip — same query shapes as `qdrant_query` (plain vector or fusion+prefetch hybrid search), one per list item. |\n| `qdrant_query_groups` | `search` | ✅ | ❌ | ✅ | Vector query grouped by a payload field, up to `group_size` hits per group — e.g. the best-matching chunks per source document. |\n| `qdrant_recommend` | `search` | ✅ | ❌ | ✅ | Find points similar to a set of positive examples and dissimilar to a set of negative ones (vectors or point ids) — Qdrant's recommendation API. |\n| `qdrant_recommend_batch` | `search` | ✅ | ❌ | ✅ | Run multiple independent recommend queries against one collection in a single round trip. |\n| `qdrant_recommend_groups` | `search` | ✅ | ❌ | ✅ | Recommend query grouped by a payload field, up to `group_size` hits per group. |\n| `qdrant_discover` | `search` | ✅ | ❌ | ✅ | Rank points by how well they fit a target within positive/negative context pairs (vectors or point ids) — Qdrant's discovery search, a finer-grained alternative to recommend. |\n| `qdrant_discover_batch` | `search` | ✅ | ❌ | ✅ | Run multiple independent discover queries against one collection in a single round trip. |\n| `qdrant_distance_matrix_pairs` | `search` | ✅ | ❌ | ✅ | Pairwise distance matrix between a random sample of points: for each of `sample` points, its `limit` closest neighbors among that same sample — returned as a flat list of (a, b, score) pairs. |\n| `qdrant_distance_matrix_offsets` | `search` | ✅ | ❌ | ✅ | Same distance matrix as `qdrant_distance_matrix_pairs`, in a column-oriented shape (offsets into a shared id list + a parallel score array) — more compact for large samples. |\n| `qdrant_payload_set` | `payload` | ❌ | ❌ | ✅ | Merge fields into the payload of selected points — exactly one of `ids`/`points_filter`. |\n| `qdrant_payload_overwrite` | `payload` | ❌ | ✅ | ✅ | Replace the entire payload of selected points with `payload` — exactly one of `ids`/`points_filter`. |\n| `qdrant_payload_delete` | `payload` | ❌ | ✅ | ✅ | Delete specific payload keys from selected points — exactly one of `ids`/`points_filter`. |\n| `qdrant_payload_clear` | `payload` | ❌ | ✅ | ✅ | Wipe the entire payload of selected points, keeping their vectors — exactly one of `ids`/`points_filter`. |\n| `qdrant_payload_facet` | `payload` | ✅ | ❌ | ✅ | Count distinct values of a payload field across the collection (or a filtered subset) — e.g. how many points per `city`. |\n| `qdrant_payload_index_create` | `payload` | ❌ | ❌ | ✅ | Create a payload index on `field_name`, speeding up filters that use it. |\n| `qdrant_payload_index_delete` | `payload` | ❌ | ✅ | ✅ | Delete the payload index on `field_name`. |\n| `qdrant_collection_vector_create` | `payload` | ❌ | ❌ | ✅ | Add a new named vector (dense or sparse) to a collection that already has points, without touching them. |\n| `qdrant_collection_vector_delete` | `payload` | ❌ | ✅ | ✅ | Remove a named vector (dense or sparse) from a collection — points keep their other vectors and payload. |\n| `qdrant_points_batch_update` | `payload` | ❌ | ✅ | ❌ | Run multiple point operations (upsert, delete, set/overwrite/delete/clear payload, update/delete vectors) atomically against one collection, in the order given. |\n| `qdrant_vectors_update` | `payload` | ❌ | ✅ | ✅ | Replace the vector(s) of existing points by id — leaves their payload untouched. |\n| `qdrant_vectors_delete` | `payload` | ❌ | ✅ | ✅ | Remove specific named vectors from selected points, keeping their payload and other vectors — exactly one of `ids`/`points_filter`. |\n| `qdrant_snapshot_create` | `snapshots` | ❌ | ❌ | ❌ | Create a snapshot of one collection's current state. |\n| `qdrant_snapshot_list` | `snapshots` | ✅ | ❌ | ✅ | List the snapshots stored for one collection. |\n| `qdrant_snapshot_delete` | `snapshots` | ❌ | ✅ | ✅ | Delete a collection snapshot, freeing its disk space on the server — does not touch the live collection. |\n| `qdrant_snapshot_recover` | `snapshots` | ❌ | ✅ | ✅ | Overwrite `collection_name` with the state captured in a snapshot — everything written since that snapshot is lost. |\n| `qdrant_snapshot_download` | `snapshots` | ✅ | ❌ | ✅ | Confirm a collection snapshot exists and return where to fetch it from — this tool does not transfer the (potentially huge) snapshot file itself; download it yourself (e.g. `curl`) from the returned `url`. |\n| `qdrant_storage_snapshot_create` | `snapshots` | ❌ | ❌ | ❌ | Create a snapshot of the whole storage (every collection and server config), not just one collection. |\n| `qdrant_storage_snapshot_list` | `snapshots` | ✅ | ❌ | ✅ | List the full-storage snapshots stored on the server. |\n| `qdrant_storage_snapshot_delete` | `snapshots` | ❌ | ✅ | ✅ | Delete a full-storage snapshot, freeing its disk space. |\n| `qdrant_storage_snapshot_download` | `snapshots` | ✅ | ❌ | ✅ | Confirm a full-storage snapshot exists and return where to fetch it from — same caveat as `qdrant_snapshot_download`: this tool does not transfer the file itself. |\n| `qdrant_telemetry` | `observability` | ✅ | ❌ | ✅ | Server-wide telemetry: build info, per-collection stats, request counters, memory and hardware usage. |\n| `qdrant_metrics_prometheus` | `observability` | ✅ | ❌ | ✅ | Return the URL where Qdrant serves Prometheus-format metrics — this tool does not fetch the metrics themselves (they're plain text, not JSON); point your Prometheus scraper at the returned `url` instead. |\n| `qdrant_quotas_get` | `observability` | ✅ | ❌ | ✅ | Current server-wide resource quotas (memory/disk limits) and actual usage. |\n| `qdrant_quotas_set` | `observability` | ❌ | ❌ | ✅ | Update server-wide resource quotas. |\n| `qdrant_issues_list` | `observability` | ✅ | ❌ | ✅ | List the issues Qdrant has detected about its own configuration (e.g. a heavily-filtered field with no payload index). |\n| `qdrant_issues_clear` | `observability` | ❌ | ✅ | ✅ | Clear all accumulated issues. |\n<!-- TOOLS_TABLE_END -->\n\n## Read-Only Mode\n\nSetting `QDRANT_MCP_READ_ONLY=1` removes every tool whose `destructiveHint` isn't explicitly\n`false` (i.e. anything that isn't `readOnlyHint=true`) **from the registry itself** — a client\ncalling `tools/list` never sees them, rather than seeing them and having calls rejected. This is\nthe mechanism to hand an LLM client safe, read-only access to a Qdrant deployment: point it at\nyour database, set the flag, and there is no code path left for it to write anything.\n\n## Architecture\n\n- **Single shared client.** One `AsyncQdrantClient` (or, in BYO mode, a proxy — see below) is\n  built once at startup and closed over by every tool. Tools resolve it lazily at call time\n  rather than caching anything from it at registration.\n- **One registration choke point.** Every tool module calls into a single `ToolRegistry`, which\n  is where the toolset filter and the read-only guard are both enforced — a tool can't bypass\n  either by registering itself differently.\n- **Resilience.** Every Qdrant call is wrapped with `tenacity`-based retry and backoff (the\n  `qdrant-client` SDK itself only exposes a timeout, not retries), and translated into an MCP\n  `ToolError` carrying Qdrant's own error message instead of a generic failure — so \"collection\n  not found\" reads as exactly that.\n- **`stdio`-safe logging.** All logging goes to `stderr`, configured before the MCP server\n  object is even constructed — writing to `stdout` under the `stdio` transport would corrupt the\n  JSON-RPC message framing.\n- **BYO mode's indirection.** In `QDRANT_MCP_BYO` mode, the \"client\" every tool holds is a proxy\n  that reads the real, per-caller `AsyncQdrantClient` out of a context variable set by request\n  middleware — see [Bring your own Qdrant](#bring-your-own-qdrant-qdrant_mcp_byo) for the full\n  mechanism.\n\n## Security\n\n- **Mandatory authentication for the personal `streamable-http` mode.** The server refuses to\n  start without `QDRANT_MCP_SHARED_SECRET` — verified hands-on that an unauthenticated instance\n  is trivially usable by anyone with the URL.\n- **SSRF protection in BYO mode.** Since a BYO deployment connects to whatever URL an untrusted\n  public caller supplies — from a host that may also run other services on internal networks —\n  every URL is checked before use: only `http`/`https`, DNS-resolved, and rejected if any\n  resolved address is private, loopback, link-local, or the cloud metadata address. This check\n  **re-resolves on every request** rather than caching a prior result, specifically to defeat DNS\n  rebinding.\n- **Input validation everywhere.** Every tool's input is a Pydantic model — malformed input is\n  rejected with a structured error before it reaches the Qdrant SDK.\n- **No secrets at rest, no secrets logged.** BYO mode holds no long-lived credentials; API keys\n  passed via `x-api-key` live only as long as their pooled client connection.\n\n## Development\n\n```bash\ngit clone https://github.com/avaazquezz/Qdrant-MCP.git\ncd Qdrant-MCP\nuv sync --dev\nuv run pre-commit install\n```\n\n```bash\nuv run ruff check .            # lint\nuv run ruff format --check .   # formatting\nuv run mypy .                  # type checking (strict)\nuv run pytest                  # unit tests\nuv run pytest -m integration   # integration tests — needs a running Qdrant (CI runs one as a Docker service)\n```\n\nIf you add or change a tool, regenerate the table in [Available Tools](#available-tools) rather\nthan hand-editing it:\n\n```bash\nuv run python scripts/gen_tools_doc.py          # regenerate\nuv run python scripts/gen_tools_doc.py --check  # verify, no changes (what CI runs)\n```\n\n## Versioning & Changelog\n\nThis project follows [Semantic Versioning](https://semver.org/). See\n[CHANGELOG.md](CHANGELOG.md) for release notes and [ROADMAP.md](ROADMAP.md) for the phased\ndesign history behind each version.\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 27270,
  "sha": "a7e9e26b7c7d90ede3a94d0876d4c2be1b4ecb4077e1ea2ebb988defb8be2641",
  "repo_slug": "avaazquezz/qdrant-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_avaazquezz_mcp_qdrant_55191c48/readme"
}