mcpField
Q
Qdrant RBAC

Qdrant RBAC

本地服务
fidonis.de数据库03 周前更新

关于 Qdrant RBAC

Qdrant RBAC 是通过 OIDC 身份验证和角色范围控制访问的 FastMCP 服务,可将 Qdrant 集合提供给 MCP 客户端。它把授权记录存储在 Qdrant 中,并可执行文档级策略和集合权限控制。

主要功能

  1. 01验证 OIDC 令牌并派生范围受限的 Qdrant JWT
  2. 02通过 MCP 工具管理角色授权
  3. 03按集合设置读取、写入和管理权限
  4. 04按负载策略过滤文档访问
  5. 05搜索、浏览、插入和删除 Qdrant 点

适用场景

  1. 01限制团队对指定 Qdrant 集合的访问
  2. 02按标签或字段限制可见文档
  3. 03运行时授予或撤销角色权限
  4. 04向 MCP 客户端提供经过身份验证的向量搜索

MCP 相关

此流式 HTTP MCP 服务提供 Qdrant 集合和点操作工具,以及角色授权管理工具。它验证客户端的 OIDC 令牌,并将相应的范围权限应用于每个请求。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"de-fidonis-qdrant-mcp-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.0"
],
"env": {
"OIDC_ISSUER_URL": "<OIDC_ISSUER_URL>",
"OIDC_AUDIENCE": "<OIDC_AUDIENCE>",
"QDRANT_URL": "<QDRANT_URL>",
"QDRANT_JWT_SECRET": "<QDRANT_JWT_SECRET>"
}
}
}
}
启动命令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.0
需要配置的环境变量
OIDC_ISSUER_URLOIDC issuer / realm URL, e.g. https://kc.example.com/realms/myrealm
OIDC_AUDIENCEExpected aud claim of incoming access tokens, e.g. mcp-server
QDRANT_URLQdrant HTTP endpoint, e.g. http://qdrant:6333
QDRANT_JWT_SECRET密钥HS256 signing secret, at least 32 bytes; must equal Qdrant's service.api_key

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
ocighcr.io/fidonis/qdrant-mcp-rbac:0.4.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

qdrant-mcp-rbac

许可证:MIT 由 Fidonis 维护 · 商标声明请参阅 TRADEMARK.md。

相同的 MCP 工具调用、两个身份、两组不同的结果——由服务端强制执行。

一个 FastMCP 服务,通过可流式传输的 HTTP 提供 Qdrant 向量数据库,并使用 基于 OIDC 的身份验证(兼容 Keycloak)和基于角色的访问控制, 将角色映射到 Qdrant 原生 JWT 访问规则。

角色授权作为数据存储在专用的 Qdrant 集合中,而非配置文件中——管理员可在运行时通过 MCP 工具管理授权。

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

快速开始(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=<same secret as Qdrant's api-key> \
  ghcr.io/fidonis/qdrant-mcp-rbac:latest

/mcp 是可流式传输的 HTTP 端点,/health 无需身份验证。 所有变量请参阅完整的 docker/.env.example。

工作原理

  1. MCP 客户端发送带有 OIDC 访问令牌(Authorization: Bearer …)的请求。
  2. OIDCAuthMiddleware 根据 OIDC 提供方的 JWKS 验证令牌(签名、过期时间、受众、颁发者;请参阅令牌验证)。
  3. JWTBuilder 为用户生成 Qdrant JWT:
    • 如果用户属于紧急管理员角色(RBAC_ADMIN_ROLE),则直接签发全局管理令牌。
    • 否则,AclResolver 返回基于ACL 集合(RBAC_ACL_COLLECTION,默认值为 _rbac_acl)构建的缓存 role -> [grants] 映射。
  4. Qdrant JWT 会附加到请求作用域。
  5. 每个 MCP 工具都会检查用户对目标集合的有效访问权限,并使用生成的 JWT 将请求转发给 Qdrant。
令牌验证

令牌使用 PyJWT 根据颁发者发布的密钥进行验证({OIDC_ISSUER_URL}/.well-known/openid-configuration,然后使用其中的 jwks_uri):

  • 签名算法取自签名密钥(其 alg;如果密钥未指定,则 RSA 密钥使用 RS256,EC 密钥使用 ES256),绝不取自令牌头部。仅接受 RS256、RS384、RS512、ES256、ES384 和 ES512;拒绝对称算法(HS*)和 none 令牌。
  • 必需声明:exp、aud(必须包含 OIDC_AUDIENCE)、iss(必须与发现文档中的颁发者一致),以及非空的 sub。缺少 exp 或 aud 的令牌会被拒绝。存在 nbf 时会进行验证。
  • 不检查 iat,因此此主机与身份提供方之间的时钟偏差不会导致刚签发的令牌被拒绝。
  • 密钥轮换:密钥缓存时间为 OIDC_JWKS_CACHE_TTL 秒。遇到未知 kid 的令牌时,会先恰好刷新一次 JWKS,再将其拒绝。
  • 从 realm_access.roles 以及每个 resource_access.<client>.roles 中读取角色。
ACL 集合架构

每项授权在 _rbac_acl 中对应一个点。点 ID 为确定性的 uuid5(role, collection),因此重复授权具有幂等性。载荷:

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

access 的取值之一:

  • r – 只读
  • rw – 读写
  • m – 管理(全局;collection 仅作说明,约定值为 "*")

服务会在首次读取时使用自行签发的服务令牌自动创建该集合。

文档级访问控制(doc_policy)

集合级访问控制决定某个角色是否可以读取某个集合。授权还可以包含可选的 doc_policy,用于限制该角色在集合中可见的单个文档(点)。服务会将策略转换为 Qdrant 载荷过滤器,并在每次读取时将其注入为 must 子句——包括 search_collection、 search_collection_by_text、scroll_collection 和 list_documents(仅对角色可见的文档进行分面)。调用方提供的 query_filter 值会予以保留;文档过滤器会与其并列添加,因此两者会同时生效。

未设置 doc_policy 的授权行为与之前完全相同——集合中的所有文档均可见。

架构
{
  "default": "allow" | "deny",
  "conditions": [
    { "field": "<payload field>", "mode": "allow" | "deny", "values": ["<v>", "…"] }
  ]
}
  • default: "allow" — 默认所有文档均可见;deny 条件会隐藏匹配的文档。
  • default: "deny" — 默认没有文档可见;allow 条件会公开匹配的文档。
  • values: ["*"] — 表示“所有文档”的通配符。在 allow 条件(默认拒绝)中,它会取消筛选,公开整个集合;在 deny 条件(默认允许)中,它会返回空结果集。
  • default: "deny" 且没有 allow 条件 → 空结果集。

同一模式的多个条件会取并集(任意 deny 条件都会隐藏文档;任意 allow 条件都会公开文档)。

多角色合并

当用户通过不同角色获得同一集合的多项授权时,服务端会按最宽松规则合并策略:

  • 如果任意匹配的授权没有 doc_policy,则用户对该集合不受限制。
  • 如果任意匹配的授权具有 default: "allow",合并后的策略为 default: "allow",并取这些授权的 deny 条件的并集(忽略默认拒绝的授权)。
  • 否则,所有授权均为 default: "deny",其 allow 条件取并集。
推荐的载荷字段:acl_tags

新增的可选字段 acl_tags: list[str] 专为需要访问控制的文档保留。它不是必需字段——没有 acl_tags 的文档仍可正常使用:

  • 在默认允许的授权中,未标记的文档永远不会匹配 acl_tags 上的 deny 条件 → 始终可见。
  • 在默认拒绝的授权中,未标记的文档永远无法满足 acl_tags 上的 allow 条件 → 会被隐式隐藏。

引导脚本 demo/bootstrap/vectorize.py 会读取可选的旁路文件 demo/data/acl_tags.yml,该文件将每个 Markdown 源文件(相对于数据目录的 POSIX 路径)映射到一个标签列表,这些标签会应用于从该文件摄取的每个文本块。

示例

默认允许,拒绝机密文档:

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

默认拒绝,仅允许公开文档:

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

按 source 字段筛选(无需 acl_tags):

{
  "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 接受相同的格式:

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

目录结构

此仓库将生产服务(src/)与演示材料(demo/)分开。 src/ 和 demo/ 下的每个项目都是独立的 uv 项目,各自拥有自己的 pyproject.toml 和 .venv——仓库根目录没有虚拟环境。

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

设置

前提条件
  • Python 3.11+
  • 已安装 uv
  • 可访问的 OIDC 提供商(例如 Keycloak)
  • 已启用 JWT RBAC 的可访问 Qdrant 实例
安装
cd src
uv sync

这会创建包含所有依赖项的 src/.venv。仓库根目录没有自己的 .venv。

配置

复制示例环境文件并调整配置:

cp src/.env.example src/.env

服务器会读取 src/.env(相对于 config.py 解析),因此无论从哪个工作目录运行,都要将文件保留在 src/ 中。

重要变量:

变量用途
OIDC_ISSUER_URLKeycloak realm URL,例如 https://kc.example.com/realms/myrealm
OIDC_AUDIENCE预期的 aud 声明,例如 mcp-server
OIDC_JWKS_CACHE_TTLOIDC 发现文档和 JWKS 的缓存 TTL(默认 3600 s)
QDRANT_URLQdrant HTTP 端点
QDRANT_JWT_SECRET签名密钥——必须与 Qdrant 的 service.api_key 相同;至少使用 32 字节,PyJWT 会对较短的 HS256 密钥发出警告(例如 openssl rand -hex 32)
QDRANT_JWT_TTL每次请求派生的 Qdrant JWT 有效期(默认 3600 s,最短 60)
MCP_HOST / MCP_PORT / MCP_PATH监听地址和路径(默认 0.0.0.0:8000/mcp)
LOG_LEVEL日志级别(默认 INFO)
EMBEDDING_API_URL兼容 OpenAI 的嵌入接口基础 URL(默认 http://localhost:11434/v1)
EMBEDDING_API_KEY嵌入端点的可选 bearer token
EMBEDDING_META_COLLECTION存储每个 collection 的嵌入元数据的 collection(默认 _collection_meta)
RBAC_ADMIN_ROLE授予全局管理权限的 OIDC 角色(默认 qdrant-admin)
RBAC_ACL_COLLECTION存储授权信息的 Qdrant collection(默认 _rbac_acl)
RBAC_ACL_CACHE_TTL内存 ACL 缓存 TTL(默认 60 s)
RBAC_SERVICE_TOKEN_TTL服务器签发的、用于读取 ACL 的服务 token 的 TTL(默认 300 s)
运行
cd src
uv run python main.py
使用 Docker 运行

若要在容器中而不是主机上运行服务器本身,请使用 docker/ 中的 compose 文件:

cp docker/.env.example docker/.env   # then edit secrets and OIDC_ISSUER_URL
docker compose -f docker/docker-compose.yml up -d

此操作会从 docker/Dockerfile 构建镜像,并启动 mcp-server 容器(发布到 :8000,并配置 /health 健康检查),同时启动 Qdrant。此处不包含 Keycloak——请将 docker/.env 中的 OIDC_ISSUER_URL 指向外部 OIDC 提供方。docker/.env.example 中记录了所有变量。

初始化访问权限

  1. 在 Keycloak 中为一个用户分配 qdrant-admin realm 角色。
  2. 启动服务器。
  3. 以该用户身份调用 grant_access 工具,为其他角色授予权限:
// MCP tool call
{
  "tool": "grant_access",
  "args": { "role": "data-scientist", "collection": "embeddings_v2", "access": "rw" }
}
  1. 现在,其他具有 data-scientist 角色的用户可获得作用域为 embeddings_v2 的 rw JWT。

工具

数据工具
工具所需访问权限
list_collections(任意)——仅返回调用者可读取的 collection
get_collection_info目标 collection 的 r 权限
search_collection目标 collection 的 r 权限
search_collection_by_text目标 collection 的 r 权限
scroll_collection目标 collection 的 r 权限
list_documents目标 collection 的 r 权限
upsert_points目标 collection 的 rw 权限
delete_points目标 collection 的 rw 权限

search_collection_by_text 接受自然语言查询,使用配置的兼容 OpenAI 的端点和初始化时记录的模型对查询进行嵌入,然后执行生成的向量搜索。

scroll_collection 会在没有查询条件的情况下列出原始 points(分块)。它通过返回的 next_offset 游标分页浏览 collection(最后一页时为 null)。可用它检查实际存储的 points。

list_documents 用于回答“collection X 中有哪些文档/有多少文档?”。它在服务器端按 source payload 字段聚合 points(Qdrant 分面),并为每个文档返回一条去重记录及其分块数——对于清单类问题,优先使用此工具而不是 scroll_collection,因为单个文档会跨越多个分块。分面需要为 source 建立 keyword payload 索引;初始化过程会创建该索引,服务器也会在首次使用时于索引缺失的情况下创建,因此无需手动操作。

upsert_points 和 delete_points 不会对系统 collection(_rbac_acl、_collection_meta)执行操作——请改用管理员工具或初始化流程。

ACL 管理工具(需要全局管理权限)
工具效果
list_acl(role?)返回所有授权,可按角色筛选
grant_access(role, collection, access)以幂等方式新增或更新一项授权
revoke_access(role, collection)移除一项授权
refresh_acl()强制重新加载内存缓存

修改会立即使本地缓存失效;对 ACL 集合的带外修改(例如直接通过 Qdrant API 修改)最迟会在 RBAC_ACL_CACHE_TTL 秒后生效。

使用 docker-compose 进行本地开发

若要仅启动 Qdrant 以进行本地服务开发,请使用 docker/docker-compose.yml 中的 qdrant 服务。传入 src/.env,以便 Compose 读取 QDRANT_JWT_SECRET:

docker compose --env-file src/.env -f docker/docker-compose.yml up -d qdrant

这会启动:

  • 运行在 :6333 的 Qdrant(已启用 JWT RBAC,签名密钥来自 QDRANT_JWT_SECRET)

创建一个 realm、一个 audience 为 mcp-server 的机密客户端、qdrant-admin 角色,以及你计划授予访问权限的各个团队角色。 为测试用户分配角色。

Qdrant JWT 设置

qdrant-mcp-rbac 使用 QDRANT_JWT_SECRET(HS256)对 JWT 签名。必须将 Qdrant 配置为接受 JWT,并将相同的密钥用作其 API 密钥:

# qdrant config.yaml
service:
  api_key: <same value as QDRANT_JWT_SECRET>
  jwt_rbac: true

非管理员用户对应的 JWT 结构如下:

{
  "exp": 1735689600,
  "access": [
    { "collection": "col_a", "access": "r" },
    { "collection": "col_b", "access": "rw" }
  ]
}

对于管理员(或任何 access: "m" 授权):

{ "exp": 1735689600, "access": "m" }

从 MCP 客户端调用

在 Authorization 标头中传入 OIDC 访问令牌:

TOKEN=$(curl -s -X POST \
  -d "client_id=mcp-cli" -d "username=alice" -d "password=…" \
  -d "grant_type=password" \
  https://kc.example.com/realms/myrealm/protocol/openid-connect/token | jq -r .access_token)

curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: text/event-stream" \
     http://localhost:8000/mcp

关于 Fidonis

qdrant-mcp-rbac 由 Fidonis 构建和维护。我们帮助企业端到端运行自己的 AI 基础设施——开源、开放标准,不受供应商锁定。本项目涵盖其中一个具体环节:在向量存储中强制执行谁可以读取哪些内容,而无需将 ACL 写入配置文件。

如果你正在构建类似的可自托管技术栈,并希望交流经验,欢迎访问 fidonis.de。

许可证

qdrant-mcp-rbac 基于 MIT license 发布——Copyright (c) 2026 Fidonis GmbH (in Gründung) and contributors.

  • 关于涵盖名称“Fidonis”和项目名称 qdrant-mcp-rbac 的商标声明,请参阅 TRADEMARK.md。
  • 关于本项目捆绑的第三方 Python 依赖项的许可证,请参阅 THIRD_PARTY_LICENSES.md。
  • 关于 Inbound = Outbound (MIT) 规则,以及未经事先批准不得引入的许可证类别列表,请参阅 CONTRIBUTING.md。

“Qdrant”是 Qdrant Solutions GmbH 的注册商标。本项目对该名称的使用仅为指称用途——用于描述本服务构建所基于的上游组件,并不代表获得其认可。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。