🤖 RAG Systems - Pipelines vs MCP

Short answer: use pipelines for control, MCP for interoperability, and combine both if you’re building something serious. The “best” setup depends on whether you care more about tight integration and performance or flexibility and tool reuse.

Here’s how to think about it clearly:


🧠 1. Pipelines (best for full control + performance)

In OpenWebUI, pipelines are the most direct and powerful way to integrate a RAG system.

Why pipelines work well for RAG:

  • You control the full flow: query → retrieval → reranking → prompt → LLM → response

  • Easy to plug in:

    • your vector DB (FAISS, Weaviate, Chroma, etc.)
    • custom chunking / embedding logic
    • hybrid search (BM25 + vectors)
  • Lower latency than tool-based approaches

  • Easier to debug and optimize

When to choose pipelines:

  • You’re building a production RAG system
  • You want fine-tuned retrieval quality
  • You need custom ranking or filtering logic
  • You care about speed

👉 In practice, most serious RAG setups in OpenWebUI end up here.


🔌 2. MCP (Model Context Protocol) (best for modularity)

Model Context Protocol (MCP) is more like a universal plugin system for LLMs.

Instead of embedding RAG directly, you expose it as a tool/service.

Why MCP is useful:

  • Decouples your RAG system from the UI
  • Lets multiple models/tools reuse the same backend
  • Works across ecosystems (not just OpenWebUI)
  • Cleaner architecture if you have many tools

Downsides for RAG:

  • More overhead (tool calls, serialization)
  • Less control over prompt composition
  • Slightly worse latency vs pipelines

When to choose MCP:

  • You already have a separate RAG service
  • You want multi-client access (not just OpenWebUI)
  • You’re building a tool ecosystem, not just chat

⚙️ 3. Hybrid approach (what advanced setups do)

This is often the actual “best” answer.

Use:

  • Pipelines → for core RAG flow
  • MCP → for optional tools or external knowledge sources

Example:

  • Pipeline handles:

    • embeddings
    • vector search
    • reranking
  • MCP tools provide:

    • SQL access
    • document fetchers
    • APIs

This gives you:

  • speed where it matters
  • flexibility where it helps

🧩 4. Other options (less ideal but sometimes useful)

Built-in knowledge / simple RAG

  • Quick to set up
  • Not very customizable
  • Fine for prototypes

External orchestration frameworks

  • LangChain
  • LlamaIndex

These can:

  • sit inside a pipeline
  • or act as your RAG backend

But:

  • add abstraction overhead
  • sometimes fight OpenWebUI instead of helping

🏁 Recommendation (practical)

If you’re deciding right now:

  • Start with pipelines
  • Add MCP only if you need cross-system reuse

Simple rule:

  • Want best RAG quality? → pipelines
  • Want reusable tools/services? → MCP
  • Want both? → hybrid

If you want, tell me:

  • what vector DB you’re using
  • whether you need multi-user / multi-agent
  • latency constraints

…and I can sketch a concrete architecture for your setup. You’re seeing the core tradeoff correctly: pipelines live inside OpenWebUI, so ownership naturally falls to that team—which becomes a bottleneck if multiple teams are iterating on RAG.

If you keep everything in pipelines, you’ll end up with:

  • tight coupling
  • slow iteration across teams
  • “please merge my retrieval tweak” PRs to the UI team

That doesn’t scale well organizationally.


🧭 A better division of responsibilities

Instead of pushing RAG logic into pipelines, treat RAG as a product/service owned by its own teams.

Have RAG teams expose APIs like:

  • /retrieve
  • /answer
  • /rerank

Then OpenWebUI pipelines become thin orchestration layers:

User query → call RAG API → inject into prompt → LLM

Why this works:

  • RAG teams deploy independently
  • OpenWebUI team stays focused on UX + orchestration
  • Clear contracts (API schemas instead of shared Python code)

🧠 Option 2 — MCP for clean ownership boundaries

This is where Model Context Protocol becomes very attractive.

Each team exposes their system as an MCP tool:

  • knowledge_base.search
  • legal_docs.query
  • codebase.lookup

Then OpenWebUI just consumes tools, not code.

Big advantages:

  • No pipeline ownership conflict
  • Plug-and-play tools across teams
  • Teams can version independently

Subtle bonus:

You can let the model choose which RAG system to query instead of hardcoding it.


⚙️ Option 3 — Hybrid (what mature setups converge to)

This solves both organizational and performance issues:

  • Pipelines:

    • minimal orchestration
    • prompt shaping
    • caching
  • External RAG services (or MCP tools):

    • retrieval
    • ranking
    • domain logic

So the pipeline becomes something like:

if domain == "finance":
    call finance-rag
elif domain == "legal":
    call legal-rag

Or via MCP:

LLM decides → calls appropriate tool

🧱 Practical architecture pattern

Here’s a setup that works well in multi-team environments:

1. Define a strict RAG contract

All RAG services return something like:

{
  "documents": [...],
  "citations": [...],
  "confidence": 0.87
}

2. Standardize embeddings (optional but helpful)

Avoid each team doing completely incompatible things unless necessary.


3. Keep pipelines “thin”

In OpenWebUI:

  • no heavy retrieval logic
  • no indexing logic
  • no business rules

Just:

  • routing
  • prompt assembly
  • response formatting

4. Decide MCP vs API per use case

Use MCP when:

  • discoverability matters
  • multiple tools exist
  • model-driven selection is useful

Use direct APIs when:

  • you need strict control
  • latency is critical
  • flow must be deterministic

🚨 What to avoid

  • Putting vector DB logic inside pipelines
  • Letting OpenWebUI team “own” retrieval quality
  • Tight coupling to frameworks like LangChain unless all teams agree
  • One giant “universal RAG” system (these usually become unmaintainable)

🏁 Bottom line

You’re right to push back on pipeline ownership.

Best organizational model:

  • RAG teams → own retrieval services (API or MCP)
  • OpenWebUI team → owns orchestration layer (thin pipelines)

That separation gives you:

  • independent deployability
  • faster iteration
  • fewer cross-team dependencies

If you want, I can sketch:

  • a concrete MCP tool schema
  • or a minimal pipeline that cleanly calls external RAG services

just tell me your stack (FastAPI? gRPC? vector DB?). Authorization changes things quite a bit—it’s not just an add-on, it shapes where your RAG logic is allowed to live.

If you ignore it early, you’ll end up leaking data across teams or rebuilding everything later.


🧠 First principle: where is access control enforced?

You have three possible layers:

1. In OpenWebUI (UI-layer auth)

  • Easy to implement
  • Works for simple setups

Problem: The moment you have multiple RAG services, this becomes fragile:

  • UI must understand all permissions
  • Easy to bypass if services are called elsewhere
  • Not reusable outside OpenWebUI

👉 This should not be your primary enforcement point.


Each team’s RAG system enforces:

  • document-level access
  • tenant isolation
  • role-based filtering

Example:

User → RAG API (with JWT) → filtered retrieval → results

Why this works:

  • Security travels with the data
  • Each team owns its own access rules
  • Works across all clients (not just OpenWebUI)

👉 This is the most important layer.


3. Gateway / policy layer (for larger orgs)

You add a central layer that:

  • validates identity
  • injects claims
  • routes requests

This can sit in front of:

  • MCP tools
  • RAG APIs

🔌 How this affects MCP vs pipelines

With pipelines only

If pipelines call RAG APIs:

  • Pass user identity (JWT / headers)
  • RAG service enforces access

✅ Clean separation ❌ Pipeline must correctly forward identity (easy to mess up)


With Model Context Protocol

MCP adds a twist:

  • Tools need user context
  • The protocol must propagate identity

If done right:

User → OpenWebUI → MCP tool → RAG service (with user context)

If done wrong:

Tool runs with system privileges → 🔥 data leak risk

👉 The key question: Does your MCP implementation support per-user auth context propagation?

If not, you’ll need to extend it or wrap it.


✅ Pattern: “Auth travels with the request”

  1. User logs into OpenWebUI
  2. OpenWebUI gets identity token (JWT/OIDC)
  3. Pipeline or MCP forwards token
  4. RAG service enforces access

🧱 Concretely:

In OpenWebUI:

  • Do NOT decide what docs a user can see

  • ONLY forward identity:

    • JWT
    • user ID
    • roles/claims

In RAG services:

  • Enforce:

    • row-level security
    • metadata filtering (e.g. department=finance)
    • document ACLs

In vector DB:

Use one of:

  • filtered search (metadata filters)
  • per-tenant indexes
  • hybrid (tenant + attribute filtering)

⚖️ Design choices you’ll need to make

1. Multi-tenant vs shared index

  • Shared index + filters → flexible, harder to secure
  • Per-tenant index → safer, more ops overhead

2. Pre-filter vs post-filter

  • Pre-filter (best): restrict retrieval query
  • Post-filter: remove docs after retrieval (can leak signals)

👉 Always prefer pre-filtering inside the retrieval step


3. Caching implications

If you cache:

  • cache must be user-aware
  • otherwise: cross-user data leaks

🚨 Common failure modes

  • “We’ll filter in the UI” → ❌ insecure
  • “Service trusts OpenWebUI completely” → ❌ brittle
  • “One API key for all users” → ❌ no isolation
  • “LLM decides what user can see” → ❌ absolutely not

🏁 Practical recommendation

Given your multi-team setup:

👉 Best structure

  • RAG teams:

    • own retrieval + authorization
    • expose secured APIs or MCP tools
  • OpenWebUI team:

    • passes identity
    • orchestrates only

👉 If you want simplicity

Start with:

  • API-based RAG services + JWT auth
  • Thin pipelines

Then later:

  • introduce MCP for discoverability

🔍 One key question for you

How are users authenticated today?

  • OIDC (e.g. Keycloak, Auth0, Azure AD)?
  • Internal tokens?
  • Something else?

That determines:

  • how you propagate identity
  • how hard MCP integration will be

If you tell me that, I can give you a concrete request flow (headers, token shape, etc.). Yes — OpenWebUI can integrate with Microsoft Entra ID, and it’s actually one of the first-class supported SSO options.


✅ What’s supported (short answer)

OpenWebUI supports:

  • OAuth2
  • OpenID Connect (OIDC)
  • Microsoft login out of the box

That means you can use Microsoft Entra ID as your identity provider via standard OIDC/OAuth flows. (Open WebUI)


🧠 How it works (architecture-wise)

Under the hood, the integration looks like this:

User → OpenWebUI → Entra ID (login) → token → OpenWebUI → your backend
  • Microsoft Entra ID acts as the Identity Provider (IdP)
  • OpenWebUI acts as the client application
  • Authentication uses OpenID Connect on top of OAuth2 (Microsoft Learn)

⚙️ What you actually configure

In practice, it’s just environment variables in OpenWebUI:

  • MICROSOFT_CLIENT_ID
  • MICROSOFT_CLIENT_SECRET
  • MICROSOFT_CLIENT_TENANT_ID
  • OPENID_PROVIDER_URL (OIDC discovery endpoint)
  • WEBUI_URL (your app URL)

Example (simplified):

MICROSOFT_CLIENT_ID=...
MICROSOFT_CLIENT_SECRET=...
MICROSOFT_CLIENT_TENANT_ID=...
OPENID_PROVIDER_URL=https://login.microsoftonline.com/<tenant>/v2.0/.well-known/openid-configuration

👉 This is standard OIDC wiring—nothing OpenWebUI-specific magic here. (Open WebUI)


🔐 Important capabilities (relevant to your architecture)

1. Group / role propagation

You can pass Entra groups into OpenWebUI:

  • via groups claim in the token
  • map to OpenWebUI roles/groups

This enables:

  • role-based access (e.g. admin vs user)
  • multi-team separation

But:

  • Entra often sends group IDs, not names by default
  • you may need claim customization (Open WebUI)

2. Single provider limitation

Out of the box:

  • only one OAuth/OIDC provider at a time

So if you want:

  • Entra + something else → needs workaround (Open WebUI)

3. Token handling limitation (important!)

OpenWebUI:

  • authenticates users
  • does NOT act as a full identity proxy

Meaning:

  • you must explicitly forward tokens to your RAG services
  • or re-issue your own backend tokens

👉 This ties directly to your earlier architecture question.


⚠️ Where people get it wrong

This is where your auth + RAG design matters:

❌ Mistake

“OpenWebUI handles auth, so backend is safe”

→ Wrong OpenWebUI is just the entry point, not the enforcement layer.


✅ Correct model

Use Entra ID as the source of truth, but:

User logs in (Entra ID)
        ↓
OpenWebUI gets ID token
        ↓
Pipeline / MCP forwards token
        ↓
RAG service enforces authorization

🧩 How this influences your earlier question (pipelines vs MCP)

With Entra ID in play:

Pipelines

  • must forward JWT tokens manually
  • simple but easy to forget

MCP

  • must support user-context propagation
  • otherwise tools run with system privileges (dangerous)

🏁 Practical recommendation

Given your setup (multiple teams + auth requirements):

👉 Do this:

  1. Use Microsoft Entra ID via OIDC in OpenWebUI

  2. Treat OpenWebUI as:

    • authentication entry point
    • NOT authorization authority
  3. Pass tokens downstream:

    • pipelines → forward JWT
    • MCP → ensure per-user context
  4. Enforce access in:

    • RAG services
    • data layer (vector DB filters, ACLs)

🔍 One key design decision for you

Do you want:

  • pure Entra tokens everywhere (simpler, but leaks coupling), or
  • internal tokens (token exchange) between services (cleaner, more work)

That decision will define:

  • how your pipelines are written
  • how MCP tools authenticate

If you want, I can sketch:

  • a concrete JWT flow (headers, claims, validation)
  • or how to map Entra groups → RAG document filters

Those are usually the next pain points.

Great—these two pieces (JWT flow + group mapping) are exactly where most multi-team RAG systems either become clean… or quietly break.

I’ll sketch both in a practical, implementation-ready way, assuming you’re using OpenWebUI with Microsoft Entra ID.


🔐 1. JWT Flow (End-to-End)

🧠 Goal

  • User authenticates via Entra ID
  • Token flows through OpenWebUI → RAG services
  • RAG enforces access (not OpenWebUI)

🧱 Flow Overview

[User Browser]
      ↓ (login)
[Entra ID]
      ↓ (ID token + access token)
[OpenWebUI]
      ↓ (forward token)
[Pipeline / MCP]
      ↓
[RAG Service]
      ↓
[Vector DB / Documents]

🔑 Step-by-step

1. User logs in via Entra

OpenWebUI receives:

  • ID Token (who the user is)
  • optionally Access Token

Typical JWT claims:

{
  "sub": "user-id",
  "email": "user@company.com",
  "name": "Jane Doe",
  "groups": ["group-id-1", "group-id-2"],
  "tid": "tenant-id",
  "iss": "https://login.microsoftonline.com/...",
  "aud": "your-client-id"
}

2. OpenWebUI stores session

But here’s the key:

👉 Do NOT stop here You must propagate identity downstream.


3. Forward token to RAG

Option A — Pass Entra token directly (simplest)

Pipeline adds header:

Authorization: Bearer <entra_jwt>

Option B — Token exchange (cleaner, more scalable)

OpenWebUI calls an internal auth service:

Entra Token → Auth Service → Internal JWT

Internal token example:

{
  "user_id": "123",
  "roles": ["finance"],
  "permissions": ["read:docs"],
  "source": "entra",
  "exp": 1712345678
}

👉 This decouples your backend from Entra specifics.


4. RAG service validates JWT

At minimum, validate:

  • signature (JWKS endpoint from Entra)
  • aud (audience)
  • iss (issuer)
  • expiration

For Entra:

  • JWKS: https://login.microsoftonline.com/<tenant>/discovery/v2.0/keys

5. Enforce authorization in RAG

This is the critical step:

Extract claims → apply filters → retrieve documents

⚠️ Common mistake

  • Validating JWT but not using claims for filtering

That’s authentication, not authorization.


👥 2. Entra Group Mapping → RAG Filtering

🧠 Goal

Map Entra groups → document access rules


🔑 Problem

Entra sends:

  • group IDs (GUIDs) by default Not human-readable names.

Example:

"groups": [
  "3f5c2a1e-...",
  "9a8b7c6d-..."
]

🧩 Step 1 — Decide mapping strategy

Option A — Static mapping (simple, fast)

In your RAG service:

GROUP_MAP = {
  "3f5c2a1e-...": "finance",
  "9a8b7c6d-...": "legal"
}

Option B — Dynamic lookup (scalable)

Use:

  • Microsoft Graph API
  • cache results

Option C — Custom claims (cleanest)

Configure Entra to emit:

"groups": ["finance", "legal"]

👉 This avoids lookup entirely.


🧱 Step 2 — Attach metadata to documents

When indexing documents:

{
  "text": "...",
  "department": "finance",
  "access_groups": ["finance"]
}

🔍 Step 3 — Filter at query time (critical)

GOOD (pre-filtering)

allowed_groups = user.groups

results = vector_db.search(
    query,
    filters={"access_groups": {"$in": allowed_groups}}
)

BAD (post-filtering)

results = vector_db.search(query)
results = [r for r in results if r.group in allowed_groups]

❌ This can leak:

  • embeddings influence
  • ranking signals

🧠 Advanced patterns

1. Hierarchical access

"user_groups": ["finance.europe"]
"doc_groups": ["finance.*"]

2. Multi-tenant isolation

Add:

"tenant_id": "company-a"

Filter:

filters = {
  "tenant_id": user.tenant_id,
  "access_groups": {"$in": user.groups}
}

3. Hybrid RBAC + ABAC

Combine:

  • roles (admin, viewer)
  • attributes (department, region)

🔐 Where this runs

Always inside the RAG service, not in:

  • OpenWebUI
  • pipelines
  • MCP layer

🏁 Putting it together

✅ Clean architecture

[User]
  ↓
[Entra ID]
  ↓
[OpenWebUI]
  ↓ (JWT)
[Pipeline / MCP]
  ↓ (JWT)
[RAG Service]
  ↓ (enforces groups + tenant)
[Vector DB]

🧭 Key principles

  • AuthN (who you are) → Entra
  • AuthZ (what you can see) → RAG service
  • OpenWebUI → just passes identity

🚨 Final sanity checks

Before going live, verify:

  • JWT is forwarded to every RAG call
  • RAG rejects requests without valid token
  • Retrieval uses pre-filtering
  • Caches are user-aware
  • Group mapping is deterministic

If you want, I can next:

  • sketch a FastAPI middleware for JWT validation
  • or show how to wire this into a specific vector DB (Weaviate, Elastic, pgvector, etc.)

That’s usually where implementation gets tricky.