Qdrant RBAC
개요
제작자가 쓴 설명(영어 원문)
Role-based access to your Qdrant vector store, via MCP. OIDC in, scoped JWTs out.
PopNx MCP의 세 기둥
이 세 가지는 절대 하나의 점수로 합치지 않아요. 의견, 측정, 인기는 서로 다른 것이니까요.
- 사용자 의견i아직 리뷰가 없어요리뷰는 다음 단계에서 계정 기능과 함께 열려요
- 우리의 기본 점검i1개 중 1개 통과공개된 정보로 점검했으며, 코드는 아직 스캔하지 않았어요
- 사용 정도i아주 새로움★ –
할 수 있는 일
이 서버는 아직 쉬운 말로 된 요약을 쓰지 못했어요. 아래는 레지스트리에서 읽어 온 사실이에요.
- 연결 방식: 내 컴퓨터에서 실행
- 전송 방식: Streamable HTTP
- 카테고리: 검색 및 AI 기억
- 입력해야 할 값: OIDC_ISSUER_URL, OIDC_AUDIENCE, QDRANT_URL, QDRANT_JWT_SECRET
설치
쓰고 있는 AI 앱을 고른 다음 설정을 복사하세요.
설치하기 전에
- 기본 점검: 1개 중 1개 통과
- 내 컴퓨터에서 내 사용자 계정의 권한(파일, 네트워크, 앱)으로 실행돼요.
- 직접 입력할 값:
OIDC_ISSUER_URL,OIDC_AUDIENCE,QDRANT_URL,QDRANT_JWT_SECRET
비밀로 표시된 값은 내 계정의 열쇠예요. 절대 다른 사람과 공유하거나 채팅에 붙여 넣지 마세요.
필요한 것
- DockerDocker 설치
어떤 앱을 쓰세요?
위에서 앱을 고르세요. 기억해 두었다가 다음부터는 버튼에 “…에 설치”라고 표시해요.
Claude Desktop → Settings → Developer → Edit Config를 열고 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"
}
}
}
}터미널에서 이 명령어를 실행하세요
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실행하기 전에 빈 값을 채워 주세요.
또는 설정을 직접 복사하기
프로젝트의 .cursor/mcp.json 또는 ~/.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"
}
}
}
}브라우저가 앱을 열지 물어본 다음, 앱이 설정을 보여 주고 확인을 요청해요. 키 값은 비워 두니 직접 입력하세요.
또는 설정을 직접 복사하기
프로젝트의 .vscode/mcp.json에 붙여 넣으세요
{
"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"
}
}
}
}브라우저가 앱을 열지 물어본 다음, 앱이 설정을 보여 주고 확인을 요청해요. 키 값은 비워 두니 직접 입력하세요.
이 서버는 내 컴퓨터에서 실행되는데, ChatGPT는 인터넷에 있는 서버에만 연결할 수 있어요. 다른 앱을 고르세요.
선택한 앱은 이 기기의 쿠키에만 저장돼요. 계정은 필요 없어요.
직접 입력할 값
- OIDC issuer / realm URL, e.g. https://kc.example.com/realms/myrealm
- Expected aud claim of incoming access tokens, e.g. mcp-server
- Qdrant HTTP endpoint, e.g. http://qdrant:6333
- HS256 signing secret, at least 32 bytes; must equal Qdrant's service.api_key
이 설정은 제작자의 레지스트리 정보로 만들었어요. README와 다르면 README를 따르세요.
할 수 있는 일
내 컴퓨터에서 실행돼요. 설치하면 AI 앱에 도구 목록이 나타나요.
기본 점검i
누구나 볼 수 있는 정보를 점검했고, 코드는 읽지 않았어요. 해당하지 않는 항목은 개수에서 뺐어요. · 2026년 11월 8일 점검
- ✓ 통과
제작자와 저장소 일치
레지스트리 이름이 도메인 fidonis.de 기준으로 확인되었어요(레지스트리가 도메인 소유를 확인해요)
- … 아직 점검 안 함
계속 관리되고 있음
아직 GitHub에서 이 저장소를 읽어 오지 않았어요
- … 아직 점검 안 함
라이선스 있음
아직 GitHub에서 이 저장소를 읽어 오지 않았어요
- – 해당 없음
엔드포인트 응답
내 컴퓨터에서 실행되는 서버라 인터넷 엔드포인트가 없어요
- – 해당 없음
OAuth로 로그인
내 컴퓨터에서 실행되며, 대신 로컬 설정을 사용해요
- … 아직 점검 안 함
도구 설명에 숨겨진 지시 없음
내 컴퓨터에서 실행돼요. 도구 목록은 인터넷 엔드포인트가 있는 서버에서만 읽을 수 있어요
검색 및 AI 기억 카테고리의 다른 서버와 비교i
- 이 카테고리의 다른 서버 5688개 중 0%보다 스타가 많아요
- 이 카테고리의 다른 서버 5688개 중 98%보다 최근에 레지스트리에 공개됐어요
- 기본 점검 결과가 이 카테고리의 다른 서버 5688개 중 100%와 같거나 더 좋아요
레지스트리에서 이 카테고리의 활성 서버 전체와 비교했어요. 스타는 게시자가 저장소를 소유한 경우에만 세요.
자주 묻는 질문
Qdrant RBAC, 안전한가요?
해당하는 기본 점검 1개 중 1개를 통과했어요. 코드는 읽지 않았어요. 키나 토큰을 넣기 전에 서버가 어떤 권한을 요구하는지 확인하고, 꼭 필요한 권한만 있는 키를 쓰세요.
어떤 키나 코드가 필요한가요?
입력해야 할 값: OIDC_ISSUER_URL, OIDC_AUDIENCE, QDRANT_URL, QDRANT_JWT_SECRET
내 컴퓨터에서 실행되나요, 클라우드에서 실행되나요?
내 컴퓨터에서 실행돼요. AI 앱이 필요할 때 실행하고, 데이터는 제작자의 서버를 거치지 않아요(서버 자체가 온라인 서비스를 호출하는 경우는 제외).
아직 관리되고 있나요?
GitHub 저장소는 마지막으로 – 변경됐고, 최신 버전(0.4.1)은 오늘 레지스트리에 올라왔어요.
어떤 앱에서 쓸 수 있나요?
Claude Desktop, Claude Code, Cursor, VS Code처럼 MCP를 지원하는 앱에서 쓸 수 있어요. 내 컴퓨터에 Docker도 있어야 해요.
대신 쓸 수 있는 건 뭐가 있나요?
같은 카테고리에는: Context7, Codebase Memory, Exa
대안
같은 카테고리에서 다른 제작자가 만든 서버예요. 스타 순으로 정렬했어요.
카드 보는 법:↓ 주간 다운로드i★ GitHub 스타i세 기둥: 리뷰 · 점검 · 사용 정도i업데이트 = 최신 버전을 레지스트리에 공개한 날i
Codebase Memory
Codebase knowledge graph for AI agents — 162 languages, sub-ms queries, 99% fewer tokens.
Exa
Connect AI agents to Exa for web search, content fetching, and multi-step research.
제작자의 README
README 열기
제작자의 저장소에서 쓰인 그대로 가져왔어요. PopNx MCP가 검토하거나 보증한 내용이 아니에요. HTML, 이미지, 스크립트는 제거했어요. 링크를 누르면 이 사이트를 벗어나요.
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
- The MCP client sends a request with an OIDC access token (
Authorization: Bearer …). - The
OIDCAuthMiddlewarevalidates the token against the OIDC provider's JWKS (signature, expiry, audience, issuer; see Token validation). - The
JWTBuilderderives 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
AclResolverreturns the cachedrole -> [grants]mapping built from the ACL collection (RBAC_ACL_COLLECTION, default_rbac_acl).
- The Qdrant JWT is attached to the request scope.
- 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, orRS256/ES256for an RSA / EC key without one), never from the token header. OnlyRS256,RS384,RS512,ES256,ES384andES512are accepted; symmetric (HS*) andnonetokens are rejected. - Required claims:
exp,aud(must containOIDC_AUDIENCE),iss(must equal the issuer in the discovery document) and a non-emptysub. A token withoutexporaudis rejected.nbfis 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_TTLseconds. A token with an unknownkidtriggers exactly one JWKS refresh before it is rejected. - Roles are read from
realm_access.rolesand from everyresource_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-onlyrw– read & writem– manage (global;collectionis 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;denyconditions hide matching ones.default: "deny"— no documents are visible by default;allowconditions expose matching ones.values: ["*"]— wildcard meaning "all documents". On anallowcondition (default-deny) it cancels the filter, exposing the whole collection; on adenycondition (default-allow) it returns an empty result set.default: "deny"with noallowconditions → 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 isdefault: "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
denycondition onacl_tags→ always visible. - In default-deny grants, an untagged document never satisfies an
allowcondition onacl_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:
| Variable | Purpose |
|---|---|
OIDC_ISSUER_URL | Keycloak realm URL, e.g. https://kc.example.com/realms/myrealm |
OIDC_AUDIENCE | Expected aud claim, e.g. mcp-server |
OIDC_JWKS_CACHE_TTL | Cache TTL of the OIDC discovery document and JWKS (default 3600 s) |
QDRANT_URL | Qdrant HTTP endpoint |
QDRANT_JWT_SECRET | Signing 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_TTL | Lifetime of the Qdrant JWTs derived per request (default 3600 s, minimum 60) |
MCP_HOST / MCP_PORT / MCP_PATH | Listen address and path (default 0.0.0.0:8000/mcp) |
LOG_LEVEL | Log level (default INFO) |
EMBEDDING_API_URL | OpenAI-compatible embeddings base URL (default http://localhost:11434/v1) |
| EMBEDDING_API_KEY | Optional bearer token for
앞부분만 보여 드려요. 나머지는 저장소에서 읽어 보세요