Prototype · Preview: every server in the official registry, synced every 30 minutes. Reviews and accounts come in a later step.
PopNx MCPMCP servers for people who use AI · explained in plain words

Qdrant RBAC

by Fidonisversion 0.4.1i★ –iRuns on your computer
Registry namede.fidonis/qdrant-mcp-rbac

Install

Overview

Description from the maker (original English)

Role-based access to your Qdrant vector store, via MCP. OIDC in, scoped JWTs out.

PopNx MCP’s three pillars

These three are never merged into one score: opinions, measurements and popularity are different things.

What you can do with it

We have not written a plain-language summary for this one yet. Below are the facts we read from the registry.

  • Connection: Runs on your computer
  • Transport: Streamable HTTP
  • Category: Search & AI memory
  • You need to fill in: OIDC_ISSUER_URL, OIDC_AUDIENCE, QDRANT_URL, QDRANT_JWT_SECRET

Install

Pick the AI app you use, then copy the settings.

Before you install

  • Basic checks: 1 of 1 passed
  • Runs on your computer with your user’s permissions (files, network, apps).
  • You fill in yourself: OIDC_ISSUER_URL, OIDC_AUDIENCE, QDRANT_URL, QDRANT_JWT_SECRET

Values marked secret are keys to your account. Never share them or paste them into a chat.

You need

Which app do you use?

Pick your app above. We remember it, so next time the button says “Install in …”.

Open Claude Desktop → Settings → Developer → Edit Config, and paste this into claude_desktop_config.json

{
  "mcpServers": {
    "qdrant-rbac": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "OIDC_ISSUER_URL",
        "-e",
        "OIDC_AUDIENCE",
        "-e",
        "QDRANT_URL",
        "-e",
        "QDRANT_JWT_SECRET",
        "ghcr.io/fidonis/qdrant-mcp-rbac:0.4.1"
      ],
      "env": {
        "OIDC_ISSUER_URL": "your-value",
        "OIDC_AUDIENCE": "your-value",
        "QDRANT_URL": "your-value",
        "QDRANT_JWT_SECRET": "your-value"
      }
    }
  }
}

Run this in your terminal

claude mcp add qdrant-rbac --env OIDC_ISSUER_URL=your-value --env OIDC_AUDIENCE=your-value --env QDRANT_URL=your-value --env QDRANT_JWT_SECRET=your-value -- docker run -i --rm -e OIDC_ISSUER_URL -e OIDC_AUDIENCE -e QDRANT_URL -e QDRANT_JWT_SECRET ghcr.io/fidonis/qdrant-mcp-rbac:0.4.1

Fill in the empty values before running it.

Open in Cursor

Or copy the settings yourself

Paste into .cursor/mcp.json in your project, or ~/.cursor/mcp.json

{
  "mcpServers": {
    "qdrant-rbac": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "OIDC_ISSUER_URL",
        "-e",
        "OIDC_AUDIENCE",
        "-e",
        "QDRANT_URL",
        "-e",
        "QDRANT_JWT_SECRET",
        "ghcr.io/fidonis/qdrant-mcp-rbac:0.4.1"
      ],
      "env": {
        "OIDC_ISSUER_URL": "your-value",
        "OIDC_AUDIENCE": "your-value",
        "QDRANT_URL": "your-value",
        "QDRANT_JWT_SECRET": "your-value"
      }
    }
  }
}

Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.

Open in VS Code

Or copy the settings yourself

Paste into .vscode/mcp.json in your project

{
  "servers": {
    "qdrant-rbac": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "OIDC_ISSUER_URL",
        "-e",
        "OIDC_AUDIENCE",
        "-e",
        "QDRANT_URL",
        "-e",
        "QDRANT_JWT_SECRET",
        "ghcr.io/fidonis/qdrant-mcp-rbac:0.4.1"
      ],
      "env": {
        "OIDC_ISSUER_URL": "your-value",
        "OIDC_AUDIENCE": "your-value",
        "QDRANT_URL": "your-value",
        "QDRANT_JWT_SECRET": "your-value"
      }
    }
  }
}

Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.

This one runs on your computer, and ChatGPT can only connect to servers on the internet. Pick another app.

Your choice stays on this device in a cookie; no account needed.

Values you fill in

  • OIDC_ISSUER_URL required env
    OIDC issuer / realm URL, e.g. https://kc.example.com/realms/myrealm
  • OIDC_AUDIENCE required env
    Expected aud claim of incoming access tokens, e.g. mcp-server
  • QDRANT_URL required env
    Qdrant HTTP endpoint, e.g. http://qdrant:6333
  • QDRANT_JWT_SECRET secret required env
    HS256 signing secret, at least 32 bytes; must equal Qdrant's service.api_key

These settings are generated from the maker’s registry entry. If the README says otherwise, follow the README.

What it can do

Runs on your computer; the tool list appears in your AI app after you install it.

Basic checksi

We check information anyone can see; we have not read the code. Checks that do not apply are left out of the count. · Checked Nov 10, 2026

  • ✓ Pass

    Maker matches the repo

    The registry name was verified against the domain fidonis.de (the registry checks domain ownership)

  • … Not checked yet

    Still maintained

    We have not read this repo from GitHub yet

  • … Not checked yet

    Has a licence

    We have not read this repo from GitHub yet

  • – Not applicable

    Endpoint responds

    This one runs on your computer; there is no internet endpoint

  • – Not applicable

    Sign-in with OAuth

    Runs on your computer and uses local settings instead

  • … Not checked yet

    No hidden instructions in tool descriptions

    Runs on your computer; we can only read tool lists from internet endpoints

Compared with others in Search & AI memoryi

  • More stars than 0% of the other 5825 servers in this category
  • Published to the registry more recently than 93% of the other 5825 servers in this category
  • Basic checks equal to or better than 100% of the other 5825 servers in this category

Compared with every active server in this category in the registry. Stars count only when the publisher owns the repo.

Common questions

Is Qdrant RBAC safe?

It passed 1 of 1 applicable basic checks. We have not read the code. Before adding a key or token, see what access the server asks for and use a key with only the permissions you need.

What keys or codes does it need?

You need to fill in: OIDC_ISSUER_URL, OIDC_AUDIENCE, QDRANT_URL, QDRANT_JWT_SECRET

Does it run on my computer or in the cloud?

It runs on your computer. Your AI app starts it when needed, and data does not pass through the maker’s servers (unless it calls an online service itself).

Is it still maintained?

The GitHub repo last changed –, and the latest version (0.4.1) reached the registry 2 days ago.

Which apps can use it?

Apps that support MCP, such as Claude Desktop, Claude Code, Cursor and VS Code. You also need Docker on your computer.

What can I use instead?

In the same category: Context7, Codebase Memory, Exa

Alternatives

Servers in the same category from other makers, sorted by stars.

Reading a card:↓ Downloads per weeki★ GitHub starsiThree pillars: reviews · checks · usageiUpdated = latest version published to the registryi

Context7

Feeds AI the latest docs for libraries and frameworks before it answers, so code it writes does not use outdated, removed functions.

by upstashWidely used↓ 972.7K/wk★ 64.2KUpdated today

Computer or cloud

Codebase Memory

Codebase knowledge graph for AI agents — 162 languages, sub-ms queries, 99% fewer tokens.

by DeusDataWidely used↓ 18K/wk★ 48.1KUpdated 23 days ago

Runs on your computer

Exa

Connect AI agents to Exa for web search, content fetching, and multi-step research.

by exa-labsWidely used★ 5.2KUpdated 2 months ago

Runs in the cloud✓ exa.ai

README from the maker

Open the README

Copied from the maker’s repo as written. PopNx MCP has not reviewed or endorsed it. HTML, images and scripts are removed. Links lead off this site.

qdrant-mcp-rbac

Maintained by Fidonis · See TRADEMARK.md for the trademark notice.

A FastMCP server that exposes a Qdrant vector database over streamable HTTP with OIDC-based authentication (Keycloak-compatible) and role-based access control mapped onto Qdrant's native JWT access rules.

Role grants are stored as data inside a dedicated Qdrant collection, not in configuration files — admins manage them at runtime via MCP tools.

┌──────────┐   OIDC Bearer   ┌─────────────────┐   Qdrant JWT    ┌──────────┐

│ MCP host │ ──────────────▶ │ qdrant-mcp-rbac │ ──────────────▶ │ Qdrant │ │ (client) │ │ (FastMCP) │ │ │ └──────────┘ └─────────────────┘ │ ┌─────┐ │ │ │ │_rbac│ │ ▼ │ │_acl │ │ Keycloak │ └─────┘ │ (JWKS, OIDC discovery) └──────────┘

Quick start (Docker)

docker run --rm -p 8000:8000 \

-e OIDC_ISSUER_URL=https://kc.example.com/realms/myrealm \ -e OIDC_AUDIENCE=mcp-server \ -e QDRANT_URL=http://qdrant:6333 \ -e QDRANT_JWT_SECRET= \ ghcr.io/fidonis/qdrant-mcp-rbac:latest

/mcp is the streamable-HTTP endpoint, /health is unauthenticated. See the full docker/.env.example for every variable.

How it works

  1. The MCP client sends a request with an OIDC access token (Authorization: Bearer …).
  2. The OIDCAuthMiddleware validates the token against the OIDC provider's JWKS (signature, expiry, audience, issuer; see Token validation).
  3. The JWTBuilder derives a Qdrant JWT for the user:
  • If the user is in the break-glass admin role (RBAC_ADMIN_ROLE), a global-manage token is minted directly.
  • Otherwise, the AclResolver returns the cached role -> [grants] mapping built from the ACL collection (RBAC_ACL_COLLECTION, default _rbac_acl).
  1. The Qdrant JWT is attached to the request scope.
  2. Each MCP tool checks the user's effective access for the targeted collection and forwards the request to Qdrant using the derived JWT.
Token validation

Tokens are validated with PyJWT against the keys published by the issuer ({OIDC_ISSUER_URL}/.well-known/openid-configuration, then its jwks_uri):

  • Signature algorithm comes from the signing key (its alg, or RS256 / ES256 for an RSA / EC key without one), never from the token header. Only RS256, RS384, RS512, ES256, ES384 and ES512 are accepted; symmetric (HS*) and none tokens are rejected.
  • Required claims: exp, aud (must contain OIDC_AUDIENCE), iss (must equal the issuer in the discovery document) and a non-empty sub. A token without exp or aud is rejected. nbf is enforced when present.
  • `iat` is not checked, so clock skew between this host and the identity provider does not reject freshly issued tokens.
  • Key rotation: keys are cached for OIDC_JWKS_CACHE_TTL seconds. A token with an unknown kid triggers exactly one JWKS refresh before it is rejected.
  • Roles are read from realm_access.roles and from every resource_access. .roles.
ACL collection schema

Each grant is one point in _rbac_acl. The point id is a deterministic uuid5(role, collection), so re-granting is idempotent. Payload:

{ "role": "data-scientist", "collection": "embeddings_v2", "access": "rw" }

access is one of:

  • r – read-only
  • rw – read & write
  • m – manage (global; collection is informational, convention: "*")

The collection is auto-created on first read by the server using a self-minted service token.

Document-level access control (doc_policy)

Collection-level access decides whether a role can read a collection at all. A grant may additionally carry an optional doc_policy that restricts which individual documents (points) inside that collection the role can see. The server translates the policy into a Qdrant payload filter and injects it as a must clause on every read — search_collection, search_collection_by_text, scroll_collection, and list_documents (faceting only over documents the role may see). Caller-supplied query_filter values are preserved; the doc filter is added alongside, so both apply together.

Grants without doc_policy behave exactly as before — every document in the collection is visible.

Schema
{

"default": "allow" | "deny", "conditions": [ { "field": " ", "mode": "allow" | "deny", "values": [" ", "…"] } ] }

  • default: "allow" — every document is visible by default; deny conditions hide matching ones.
  • default: "deny" — no documents are visible by default; allow conditions expose matching ones.
  • values: ["*"] — wildcard meaning "all documents". On an allow condition (default-deny) it cancels the filter, exposing the whole collection; on a deny condition (default-allow) it returns an empty result set.
  • default: "deny" with no allow conditions → empty result set.

Multiple conditions of the same mode are unioned (a document is hidden by any deny condition; a document is exposed by any allow condition).

Multi-role merge

When a user holds several grants on the same collection (via different roles), the server merges their policies using the most-permissive rule:

  • If any matching grant has no doc_policy, the user is unrestricted on that collection.
  • If any matching grant has default: "allow", the merged policy is default: "allow" with the union of those grants' deny conditions (default-deny grants are ignored).
  • Otherwise all grants are default: "deny" and their allow conditions are unioned.
Recommended payload field: acl_tags

A new optional acl_tags: list[str] field is reserved for documents that need access control. It is never required — documents without acl_tags remain fully functional:

  • In default-allow grants, an untagged document is never matched by a deny condition on acl_tags → always visible.
  • In default-deny grants, an untagged document never satisfies an allow condition on acl_tags → implicitly hidden.

The bootstrap script demo/bootstrap/vectorize.py reads an optional demo/data/acl_tags.yml sidecar mapping each Markdown source (POSIX path relative to the data directory) to a list of tags applied to every chunk ingested from that file.

Examples

Default-allow, deny confidential documents:

{

"role": "finance-analyst", "collection": "finance", "access": "r", "doc_policy": { "default": "allow", "conditions": [ { "field": "acl_tags", "mode": "deny", "values": ["confidential", "board-only"] } ] } }

Default-deny, allow only public documents:

{

"role": "guest", "collection": "finance", "access": "r", "doc_policy": { "default": "deny", "conditions": [ { "field": "acl_tags", "mode": "allow", "values": ["public"] } ] } }

Filtering on the source field (no acl_tags required):

{

"role": "junior-analyst", "collection": "finance", "access": "r", "doc_policy": { "default": "deny", "conditions": [ { "field": "source", "mode": "allow", "values": ["annual-report.md", "q4-summary.md"] } ] } }

grant_access accepts the same shape:

{

"tool": "grant_access", "args": { "role": "guest", "collection": "finance", "access": "r", "doc_policy": { "default": "deny", "conditions": [ { "field": "acl_tags", "mode": "allow", "values": ["public"] } ] } } }

Layout

The repo separates the production server (src/) from demo material (demo/). src/ and each project under demo/ is its own uv project with its own pyproject.toml and .venv — there is no virtual environment in the repo root.

src/                       # uv project: the FastMCP server

pyproject.toml uv.lock .venv/ # created by uv sync (gitignored) .env # local config (gitignored) .env.example # template for .env main.py # Entry point (uvicorn) config.py # Settings (pydantic-settings, .env) auth/ models.py # OIDCClaims, CollectionAccess, AclEntry, QdrantToken oidc.py # JWKS-based OIDC token validator (PyJWT) acl.py # AclResolver: TTL-cached read of the ACL collection jwt_builder.py # Builds Qdrant JWTs from claims + ACL mapping qdrant/ client.py # AsyncQdrantClient factory (per-request, JWT-scoped) collections.py # Thin async wrappers around Qdrant collection operations meta.py # Read/write helpers for the _collection_meta system collection mcp_app/ # named mcp_app to avoid shadowing the installed mcp SDK server.py # FastMCP instance + Starlette app wiring tools.py # MCP tools: data tools + ACL admin tools middleware.py # ASGI auth middleware: OIDC → Qdrant JWT

tests/ # pytest suite, run from src/ (kept out of the Docker image) fakes/oidc.py # In-process OIDC issuer: RSA key, JWKS, token minting test_doc_filter.py test_facet.py test_jwt_builder.py test_oidc_validator.py test_scroll.py

demo/ # everything for the end-to-end demo README.md # Demo quickstart (order of operations) bootstrap/ # uv project: vectorize Markdown → Qdrant collections pyproject.toml uv.lock .venv/ vectorize.py client/ # uv project: OIDC + LLM CLI client pyproject.toml uv.lock .venv/ client.py agent.py oidc.py config.py data/ # demo seed data (Markdown, collection = file stem up to the first "-") finance-2025.md finance-2026.md finance-aquise.md it.md sales.md

Setup

Prerequisites
  • Python 3.11+
  • uv installed
  • A reachable OIDC provider (e.g. Keycloak)
  • A reachable Qdrant instance with JWT RBAC enabled
Install
cd src

uv sync

This creates src/.venv with all dependencies. The repo root has no .venv of its own.

Configure

Copy the example env file and adjust values:

cp src/.env.example src/.env

The server reads src/.env (resolved relative to config.py), so keep it inside src/ regardless of the working directory you run from.

Important variables:

VariablePurpose
OIDC_ISSUER_URLKeycloak realm URL, e.g. https://kc.example.com/realms/myrealm
OIDC_AUDIENCEExpected aud claim, e.g. mcp-server
OIDC_JWKS_CACHE_TTLCache TTL of the OIDC discovery document and JWKS (default 3600 s)
QDRANT_URLQdrant HTTP endpoint
QDRANT_JWT_SECRETSigning secret — must equal Qdrant's `service.api_key`; use at least 32 bytes, PyJWT warns about shorter HS256 keys (e.g. openssl rand -hex 32)
QDRANT_JWT_TTLLifetime of the Qdrant JWTs derived per request (default 3600 s, minimum 60)
MCP_HOST / MCP_PORT / MCP_PATHListen address and path (default 0.0.0.0:8000/mcp)
LOG_LEVELLog level (default INFO)
EMBEDDING_API_URLOpenAI-compatible embeddings base URL (default http://localhost:11434/v1)

| EMBEDDING_API_KEY | Optional bearer token for

Only the beginning is shown; read the rest in the repo

Ask PopNx